eden/externals/oboe/include/oboe/FullDuplexStream.h
swurl 6c655321e6
Move dead submodules in-tree
Signed-off-by: swurl <swurl@swurl.xyz>
2025-05-31 02:33:02 -04:00

356 lines
12 KiB
C++

/*
* Copyright 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef OBOE_FULL_DUPLEX_STREAM_
#define OBOE_FULL_DUPLEX_STREAM_
#include <cstdint>
#include "oboe/Definitions.h"
#include "oboe/AudioStream.h"
#include "oboe/AudioStreamCallback.h"
namespace oboe {
/**
* FullDuplexStream can be used to synchronize an input and output stream.
*
* For the builder of the output stream, call setDataCallback() with this object.
*
* When both streams are ready, onAudioReady() of the output stream will call onBothStreamsReady().
* Callers must override onBothStreamsReady().
*
* To ensure best results, open an output stream before the input stream.
* Call inputBuilder.setBufferCapacityInFrames(mOutputStream->getBufferCapacityInFrames() * 2).
* Also, call inputBuilder.setSampleRate(mOutputStream->getSampleRate()).
*
* Callers must call setInputStream() and setOutputStream().
* Call start() to start both streams and stop() to stop both streams.
* Caller is responsible for closing both streams.
*
* Callers should handle error callbacks with setErrorCallback() for the output stream.
* When an error callback occurs for the output stream, Oboe will stop and close the output stream.
* The caller is responsible for stopping and closing the input stream.
* The caller should also reopen and restart both streams when the error callback is ErrorDisconnected.
* See the LiveEffect sample as an example of this.
*
*/
class FullDuplexStream : public AudioStreamDataCallback {
public:
FullDuplexStream() {}
virtual ~FullDuplexStream() = default;
/**
* Sets the input stream.
*
* @deprecated Call setSharedInputStream(std::shared_ptr<AudioStream> &stream) instead.
* @param stream the output stream
*/
void setInputStream(AudioStream *stream) {
mRawInputStream = stream;
}
/**
* Sets the input stream. Calling this is mandatory.
*
* @param stream the output stream
*/
void setSharedInputStream(std::shared_ptr<AudioStream> &stream) {
mSharedInputStream = stream;
}
/**
* Gets the current input stream. This function tries to return the shared input stream if it
* is set before the raw input stream.
*
* @return pointer to an output stream or nullptr.
*/
AudioStream *getInputStream() {
if (mSharedInputStream) {
return mSharedInputStream.get();
} else {
return mRawInputStream;
}
}
/**
* Sets the output stream.
*
* @deprecated Call setSharedOutputStream(std::shared_ptr<AudioStream> &stream) instead.
* @param stream the output stream
*/
void setOutputStream(AudioStream *stream) {
mRawOutputStream = stream;
}
/**
* Sets the output stream. Calling this is mandatory.
*
* @param stream the output stream
*/
void setSharedOutputStream(std::shared_ptr<AudioStream> &stream) {
mSharedOutputStream = stream;
}
/**
* Gets the current output stream. This function tries to return the shared output stream if it
* is set before the raw output stream.
*
* @return pointer to an output stream or nullptr.
*/
AudioStream *getOutputStream() {
if (mSharedOutputStream) {
return mSharedOutputStream.get();
} else {
return mRawOutputStream;
}
}
/**
* Attempts to start both streams. Please call setInputStream() and setOutputStream() before
* calling this function.
*
* @return result of the operation
*/
virtual Result start() {
mCountCallbacksToDrain = kNumCallbacksToDrain;
mCountInputBurstsCushion = mNumInputBurstsCushion;
mCountCallbacksToDiscard = kNumCallbacksToDiscard;
// Determine maximum size that could possibly be called.
int32_t bufferSize = getOutputStream()->getBufferCapacityInFrames()
* getOutputStream()->getChannelCount();
if (bufferSize > mBufferSize) {
mInputBuffer = std::make_unique<float[]>(bufferSize);
mBufferSize = bufferSize;
}
oboe::Result result = getInputStream()->requestStart();
if (result != oboe::Result::OK) {
return result;
}
return getOutputStream()->requestStart();
}
/**
* Stops both streams. Returns Result::OK if neither stream had an error during close.
*
* @return result of the operation
*/
virtual Result stop() {
Result outputResult = Result::OK;
Result inputResult = Result::OK;
if (getOutputStream()) {
outputResult = getOutputStream()->requestStop();
}
if (getInputStream()) {
inputResult = getOutputStream()->requestStop();
}
if (outputResult != Result::OK) {
return outputResult;
} else {
return inputResult;
}
}
/**
* Reads input from the input stream. Callers should not call this directly as this is called
* in onAudioReady().
*
* @param numFrames
* @return result of the operation
*/
virtual ResultWithValue<int32_t> readInput(int32_t numFrames) {
return getInputStream()->read(mInputBuffer.get(), numFrames, 0 /* timeout */);
}
/**
* Called when data is available on both streams.
* Caller should override this method.
* numInputFrames and numOutputFrames may be zero.
*
* @param inputData buffer containing input data
* @param numInputFrames number of input frames
* @param outputData a place to put output data
* @param numOutputFrames number of output frames
* @return DataCallbackResult::Continue or DataCallbackResult::Stop
*/
virtual DataCallbackResult onBothStreamsReady(
const void *inputData,
int numInputFrames,
void *outputData,
int numOutputFrames
) = 0;
/**
* Called when the output stream is ready to process audio.
* This in return calls onBothStreamsReady() when data is available on both streams.
* Callers should call this function when the output stream is ready.
* Callers must override onBothStreamsReady().
*
* @param audioStream pointer to the associated stream
* @param audioData a place to put output data
* @param numFrames number of frames to be processed
* @return DataCallbackResult::Continue or DataCallbackResult::Stop
*
*/
DataCallbackResult onAudioReady(
AudioStream * /*audioStream*/,
void *audioData,
int numFrames) {
DataCallbackResult callbackResult = DataCallbackResult::Continue;
int32_t actualFramesRead = 0;
// Silence the output.
int32_t numBytes = numFrames * getOutputStream()->getBytesPerFrame();
memset(audioData, 0 /* value */, numBytes);
if (mCountCallbacksToDrain > 0) {
// Drain the input.
int32_t totalFramesRead = 0;
do {
ResultWithValue<int32_t> result = readInput(numFrames);
if (!result) {
// Ignore errors because input stream may not be started yet.
break;
}
actualFramesRead = result.value();
totalFramesRead += actualFramesRead;
} while (actualFramesRead > 0);
// Only counts if we actually got some data.
if (totalFramesRead > 0) {
mCountCallbacksToDrain--;
}
} else if (mCountInputBurstsCushion > 0) {
// Let the input fill up a bit so we are not so close to the write pointer.
mCountInputBurstsCushion--;
} else if (mCountCallbacksToDiscard > 0) {
mCountCallbacksToDiscard--;
// Ignore. Allow the input to reach to equilibrium with the output.
ResultWithValue<int32_t> resultAvailable = getInputStream()->getAvailableFrames();
if (!resultAvailable) {
callbackResult = DataCallbackResult::Stop;
} else {
int32_t framesAvailable = resultAvailable.value();
if (framesAvailable >= mMinimumFramesBeforeRead) {
ResultWithValue<int32_t> resultRead = readInput(numFrames);
if (!resultRead) {
callbackResult = DataCallbackResult::Stop;
}
}
}
} else {
int32_t framesRead = 0;
ResultWithValue<int32_t> resultAvailable = getInputStream()->getAvailableFrames();
if (!resultAvailable) {
callbackResult = DataCallbackResult::Stop;
} else {
int32_t framesAvailable = resultAvailable.value();
if (framesAvailable >= mMinimumFramesBeforeRead) {
// Read data into input buffer.
ResultWithValue<int32_t> resultRead = readInput(numFrames);
if (!resultRead) {
callbackResult = DataCallbackResult::Stop;
} else {
framesRead = resultRead.value();
}
}
}
if (callbackResult == DataCallbackResult::Continue) {
callbackResult = onBothStreamsReady(mInputBuffer.get(), framesRead,
audioData, numFrames);
}
}
if (callbackResult == DataCallbackResult::Stop) {
getInputStream()->requestStop();
}
return callbackResult;
}
/**
*
* This is a cushion between the DSP and the application processor cursors to prevent collisions.
* Typically 0 for latency measurements or 1 for glitch tests.
*
* @param numBursts number of bursts to leave in the input buffer as a cushion
*/
void setNumInputBurstsCushion(int32_t numBursts) {
mNumInputBurstsCushion = numBursts;
}
/**
* Get the number of bursts left in the input buffer as a cushion.
*
* @return number of bursts in the input buffer as a cushion
*/
int32_t getNumInputBurstsCushion() const {
return mNumInputBurstsCushion;
}
/**
* Minimum number of frames in the input stream buffer before calling readInput().
*
* @param numFrames number of bursts in the input buffer as a cushion
*/
void setMinimumFramesBeforeRead(int32_t numFrames) {
mMinimumFramesBeforeRead = numFrames;
}
/**
* Gets the minimum number of frames in the input stream buffer before calling readInput().
*
* @return minimum number of frames before reading
*/
int32_t getMinimumFramesBeforeRead() const {
return mMinimumFramesBeforeRead;
}
private:
// TODO add getters and setters
static constexpr int32_t kNumCallbacksToDrain = 20;
static constexpr int32_t kNumCallbacksToDiscard = 30;
// let input fill back up, usually 0 or 1
int32_t mNumInputBurstsCushion = 0;
int32_t mMinimumFramesBeforeRead = 0;
// We want to reach a state where the input buffer is empty and
// the output buffer is full.
// These are used in order.
// Drain several callback so that input is empty.
int32_t mCountCallbacksToDrain = kNumCallbacksToDrain;
// Let the input fill back up slightly so we don't run dry.
int32_t mCountInputBurstsCushion = mNumInputBurstsCushion;
// Discard some callbacks so the input and output reach equilibrium.
int32_t mCountCallbacksToDiscard = kNumCallbacksToDiscard;
AudioStream *mRawInputStream = nullptr;
AudioStream *mRawOutputStream = nullptr;
std::shared_ptr<AudioStream> mSharedInputStream;
std::shared_ptr<AudioStream> mSharedOutputStream;
int32_t mBufferSize = 0;
std::unique_ptr<float[]> mInputBuffer;
};
} // namespace oboe
#endif //OBOE_FULL_DUPLEX_STREAM_