Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Full Duplex Stream to Oboe library #1920

Merged
merged 11 commits into from
Oct 12, 2023
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
#include <sys/types.h>

#include "oboe/Oboe.h"
#include "oboe/FullDuplexStream.h"
#include "FormatConverterBox.h"

class FullDuplexStreamWithConversion : public oboe::FullDuplexStream {
Expand Down
1 change: 0 additions & 1 deletion apps/OboeTester/app/src/main/cpp/InterpolatingDelayLine.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
#include <sys/types.h>

#include "oboe/Oboe.h"
#include "oboe/FullDuplexStream.h"

/**
* Monophonic delay line.
Expand Down
14 changes: 7 additions & 7 deletions apps/OboeTester/app/src/main/cpp/NativeAudioContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -469,7 +469,7 @@ void ActivityTestOutput::configureStreamGateway() {
}

if (mUseCallback) {
oboeCallbackProxy.setCallback(&audioStreamGateway);
oboeCallbackProxy.setDataCallback(&audioStreamGateway);
}
}

Expand Down Expand Up @@ -521,7 +521,7 @@ oboe::Result ActivityTestOutput::startStreams() {
void ActivityTestInput::configureAfterOpen() {
mInputAnalyzer.reset();
if (mUseCallback) {
oboeCallbackProxy.setCallback(&mInputAnalyzer);
oboeCallbackProxy.setDataCallback(&mInputAnalyzer);
}
mInputAnalyzer.setRecording(mRecording.get());
}
Expand Down Expand Up @@ -648,7 +648,7 @@ void ActivityEcho::configureBuilder(bool isInput, oboe::AudioStreamBuilder &buil
// only output uses a callback, input is polled
if (!isInput) {
builder.setCallback((oboe::AudioStreamCallback *) &oboeCallbackProxy);
oboeCallbackProxy.setCallback(mFullDuplexEcho.get());
oboeCallbackProxy.setDataCallback(mFullDuplexEcho.get());
}
}

Expand All @@ -670,7 +670,7 @@ void ActivityRoundTripLatency::configureBuilder(bool isInput, oboe::AudioStreamB
if (!isInput) {
// only output uses a callback, input is polled
builder.setCallback((oboe::AudioStreamCallback *) &oboeCallbackProxy);
oboeCallbackProxy.setCallback(mFullDuplexLatency.get());
oboeCallbackProxy.setDataCallback(mFullDuplexLatency.get());
}
}

Expand Down Expand Up @@ -725,7 +725,7 @@ void ActivityGlitches::configureBuilder(bool isInput, oboe::AudioStreamBuilder &
if (!isInput) {
// only output uses a callback, input is polled
builder.setCallback((oboe::AudioStreamCallback *) &oboeCallbackProxy);
oboeCallbackProxy.setCallback(mFullDuplexGlitches.get());
oboeCallbackProxy.setDataCallback(mFullDuplexGlitches.get());
}
}

Expand All @@ -748,7 +748,7 @@ void ActivityDataPath::configureBuilder(bool isInput, oboe::AudioStreamBuilder &
if (!isInput) {
// only output uses a callback, input is polled
builder.setCallback((oboe::AudioStreamCallback *) &oboeCallbackProxy);
oboeCallbackProxy.setCallback(mFullDuplexDataPath.get());
oboeCallbackProxy.setDataCallback(mFullDuplexDataPath.get());
}
}

Expand Down Expand Up @@ -786,6 +786,6 @@ void ActivityTestDisconnect::configureAfterOpen() {
} else if (inputStream) {
audioStreamGateway.setAudioSink(nullptr);
}
oboeCallbackProxy.setCallback(&audioStreamGateway);
oboeCallbackProxy.setDataCallback(&audioStreamGateway);
}

1 change: 0 additions & 1 deletion apps/OboeTester/app/src/main/cpp/NativeAudioContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@

#include "FullDuplexAnalyzer.h"
#include "FullDuplexEcho.h"
#include "oboe/FullDuplexStream.h"
#include "analyzer/GlitchAnalyzer.h"
#include "analyzer/DataPathAnalyzer.h"
#include "InputStreamCallbackAnalyzer.h"
Expand Down
4 changes: 2 additions & 2 deletions apps/OboeTester/app/src/main/cpp/OboeStreamCallbackProxy.h
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ class SynthWorkload {
class OboeStreamCallbackProxy : public OboeTesterStreamCallback {
public:

void setCallback(oboe::AudioStreamCallback *callback) {
void setDataCallback(oboe::AudioStreamDataCallback *callback) {
mCallback = callback;
setCallbackCount(0);
mStatistics.clear();
Expand Down Expand Up @@ -251,7 +251,7 @@ class OboeStreamCallbackProxy : public OboeTesterStreamCallback {
SynthWorkload mSynthWorkload;
bool mHearWorkload = false;

oboe::AudioStreamCallback *mCallback = nullptr;
oboe::AudioStreamDataCallback *mCallback = nullptr;
static bool mCallbackReturnStop;

int64_t mCallbackCount = 0;
Expand Down
2 changes: 2 additions & 0 deletions apps/OboeTester/app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,8 @@
<item>1024</item>
<item>2048</item>
<item>4096</item>
<item>8192</item>
<item>16384</item>
</string-array>

<string-array name="glitch_times">
Expand Down
120 changes: 108 additions & 12 deletions include/oboe/FullDuplexStream.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,26 +24,68 @@

namespace oboe {

class FullDuplexStream : public AudioStreamCallback {
/**
* FullDuplexStream can be used to synchronize an input and output stream.
*
* For the builder of the output stream, call setDataCallback() with this stream.
robertwu1 marked this conversation as resolved.
Show resolved Hide resolved
*
* When both streams are ready, onAudioReady() will call onBothStreamsReady().
robertwu1 marked this conversation as resolved.
Show resolved Hide resolved
* Callers must override onBothStreamsReady().
*
* To ensure best results, open an output stream before the input stream.
* Call inputBuilder.setBufferCapacityInFrames(mOutputStream->getBufferSizeInFrames() * 2).
robertwu1 marked this conversation as resolved.
Show resolved Hide resolved
robertwu1 marked this conversation as resolved.
Show resolved Hide resolved
*
* Callers must call setInputStream() and setOutputStream().
* Call start() to start both streams and stop() to stop both streams.
*
robertwu1 marked this conversation as resolved.
Show resolved Hide resolved
*/
robertwu1 marked this conversation as resolved.
Show resolved Hide resolved
class FullDuplexStream : public AudioStreamDataCallback {
public:
FullDuplexStream() {}
virtual ~FullDuplexStream() = default;

/**
* Sets the input stream. Calling this is mandatory.
*
* @param stream the output stream
*/
void setInputStream(AudioStream *stream) {
mInputStream = stream;
}

/**
* Gets the input stream
*
* @return the input stream
*/
AudioStream *getInputStream() {
return mInputStream;
}

/**
* Sets the output stream. Calling this is mandatory.
*
* @param stream the output stream
*/
void setOutputStream(AudioStream *stream) {
mOutputStream = stream;
}

/**
* Gets the output stream
*
* @return the output stream
*/
AudioStream *getOutputStream() {
return mOutputStream;
}

/**
* Attempts to start both streams. Please call setInputStream() and setOutputStream() before
* calling this function.
*
* @return result of the operation
*/
virtual Result start() {
robertwu1 marked this conversation as resolved.
Show resolved Hide resolved
mCountCallbacksToDrain = kNumCallbacksToDrain;
mCountInputBurstsCushion = mNumInputBurstsCushion;
Expand All @@ -64,18 +106,48 @@ class FullDuplexStream : public AudioStreamCallback {
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() {
getOutputStream()->requestStop(); // TODO result?
return getInputStream()->requestStop();
Result outputResult = Result::OK;
Result inputResult = Result::OK;
if (getOutputStream()) {
outputResult = mOutputStream->requestStop();
}
if (getInputStream()) {
inputResult = mInputStream->requestStop();
}
if (outputResult != Result::OK) {
return outputResult;
} else {
return inputResult;
}
}

robertwu1 marked this conversation as resolved.
Show resolved Hide resolved
/**
* 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,
Expand All @@ -85,7 +157,16 @@ class FullDuplexStream : public AudioStreamCallback {
) = 0;

/**
* Called by Oboe when the stream is ready to process audio.
* 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,
Expand Down Expand Up @@ -165,25 +246,40 @@ class FullDuplexStream : public AudioStreamCallback {
return callbackResult;
}

int32_t getMNumInputBurstsCushion() const {
return mNumInputBurstsCushion;
}

/**
* Number of bursts to leave in the input buffer as a cushion.
* Typically 0 for latency measurements
* or 1 for glitch tests.
*
* @param mNumInputBurstsCushion
* 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 setMNumInputBurstsCushion(int32_t numBursts) {
robertwu1 marked this conversation as resolved.
Show resolved Hide resolved
robertwu1 marked this conversation as resolved.
Show resolved Hide resolved
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 getMNumInputBurstsCushion() const {
robertwu1 marked this conversation as resolved.
Show resolved Hide resolved
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;
}
Expand Down
2 changes: 0 additions & 2 deletions samples/LiveEffect/src/main/cpp/FullDuplexPass.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@
#ifndef SAMPLES_FULLDUPLEXPASS_H
#define SAMPLES_FULLDUPLEXPASS_H

#include "oboe/FullDuplexStream.h"

class FullDuplexPass : public oboe::FullDuplexStream {
public:
virtual oboe::DataCallbackResult
Expand Down
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ add_executable(
testOboe
testAAudio.cpp
testFlowgraph.cpp
testFullDuplexStream.cpp
testResampler.cpp
testReturnStop.cpp
testStreamClosedMethods.cpp
Expand Down
Loading