diff options
22 files changed, 312 insertions, 114 deletions
diff --git a/apps/OboeTester/app/src/main/cpp/NativeAudioContext.cpp b/apps/OboeTester/app/src/main/cpp/NativeAudioContext.cpp index ea8b1228..7809206f 100644 --- a/apps/OboeTester/app/src/main/cpp/NativeAudioContext.cpp +++ b/apps/OboeTester/app/src/main/cpp/NativeAudioContext.cpp @@ -130,7 +130,7 @@ void ActivityContext::configureBuilder(bool isInput, oboe::AudioStreamBuilder &b if (mUseCallback) { LOGD("ActivityContext::open() set callback to use oboeCallbackProxy, callback size = %d", callbackSize); - builder.setCallback(&oboeCallbackProxy); + builder.setDataCallback(&oboeCallbackProxy); builder.setFramesPerCallback(callbackSize); } } diff --git a/apps/OboeTester/app/src/main/cpp/NativeAudioContext.h b/apps/OboeTester/app/src/main/cpp/NativeAudioContext.h index 50d0e90f..95df9849 100644 --- a/apps/OboeTester/app/src/main/cpp/NativeAudioContext.h +++ b/apps/OboeTester/app/src/main/cpp/NativeAudioContext.h @@ -192,6 +192,7 @@ class ActivityContext { public: ActivityContext() {} + virtual ~ActivityContext() = default; oboe::AudioStream *getStream(int32_t streamIndex) { @@ -288,7 +289,11 @@ public: } oboe::Result getLastErrorCallbackResult() { - return oboeCallbackProxy.getLastErrorCallbackResult(); + oboe::AudioStream *stream = getOutputStream(); + if (stream == nullptr) { + stream = getInputStream(); + } + return stream ? oboe::Result::ErrorNull : stream->getLastErrorCallbackResult(); } int32_t getFramesPerCallback() { diff --git a/apps/OboeTester/app/src/main/cpp/OboeStreamCallbackProxy.cpp b/apps/OboeTester/app/src/main/cpp/OboeStreamCallbackProxy.cpp index f16e861c..a17575bf 100644 --- a/apps/OboeTester/app/src/main/cpp/OboeStreamCallbackProxy.cpp +++ b/apps/OboeTester/app/src/main/cpp/OboeStreamCallbackProxy.cpp @@ -80,17 +80,18 @@ oboe::DataCallbackResult OboeStreamCallbackProxy::onAudioReady( return callbackResult; } -void OboeStreamCallbackProxy::onErrorBeforeClose(oboe::AudioStream *audioStream, oboe::Result error) { - LOGD("OboeStreamCallbackProxy::%s(%p, %d) called", __func__, audioStream, error); - mErrorCallbackResult = error; - if (mCallback != nullptr) { - mCallback->onErrorBeforeClose(audioStream, error); - } -} - -void OboeStreamCallbackProxy::onErrorAfterClose(oboe::AudioStream *audioStream, oboe::Result error) { - LOGD("OboeStreamCallbackProxy::%s(%p, %d) called", __func__, audioStream, error); - if (mCallback != nullptr) { - mCallback->onErrorAfterClose(audioStream, error); - } -} +// FIXME +//void OboeStreamCallbackProxy::onErrorBeforeClose(oboe::AudioStream *audioStream, oboe::Result error) { +// LOGD("OboeStreamCallbackProxy::%s(%p, %d) called", __func__, audioStream, error); +// mErrorCallbackResult = error; +// if (mCallback != nullptr) { +// mCallback->onErrorBeforeClose(audioStream, error); +// } +//} +// +//void OboeStreamCallbackProxy::onErrorAfterClose(oboe::AudioStream *audioStream, oboe::Result error) { +// LOGD("OboeStreamCallbackProxy::%s(%p, %d) called", __func__, audioStream, error); +// if (mCallback != nullptr) { +// mCallback->onErrorAfterClose(audioStream, error); +// } +//} diff --git a/apps/OboeTester/app/src/main/cpp/OboeStreamCallbackProxy.h b/apps/OboeTester/app/src/main/cpp/OboeStreamCallbackProxy.h index e9cd4fe1..9bfeaca1 100644 --- a/apps/OboeTester/app/src/main/cpp/OboeStreamCallbackProxy.h +++ b/apps/OboeTester/app/src/main/cpp/OboeStreamCallbackProxy.h @@ -22,7 +22,7 @@ #include "oboe/Oboe.h" -class OboeStreamCallbackProxy : public oboe::AudioStreamCallback { +class OboeStreamCallbackProxy : public oboe::AudioStreamDataCallback { public: void setCallback(oboe::AudioStreamCallback *callback) { @@ -54,10 +54,6 @@ public: void *audioData, int numFrames) override; - void onErrorBeforeClose(oboe::AudioStream *audioStream, oboe::Result error) override; - - void onErrorAfterClose(oboe::AudioStream *audioStream, oboe::Result error) override; - /** * Specify the amount of artificial workload that will waste CPU cycles * and increase the CPU load. @@ -77,10 +73,6 @@ public: static int64_t getNanoseconds(clockid_t clockId = CLOCK_MONOTONIC); - oboe::Result getLastErrorCallbackResult() { - return mErrorCallbackResult; - } - private: static constexpr int32_t kWorkloadScaler = 500; double mWorkload = 0.0; @@ -90,7 +82,6 @@ private: static bool mCallbackReturnStop; int64_t mCallbackCount = 0; std::atomic<int32_t> mFramesPerCallback{0}; - oboe::Result mErrorCallbackResult = oboe::Result::OK; }; diff --git a/apps/OboeTester/app/src/main/cpp/jni-bridge.cpp b/apps/OboeTester/app/src/main/cpp/jni-bridge.cpp index 067db811..09c273fd 100644 --- a/apps/OboeTester/app/src/main/cpp/jni-bridge.cpp +++ b/apps/OboeTester/app/src/main/cpp/jni-bridge.cpp @@ -381,8 +381,12 @@ Java_com_google_sample_oboe_manualtest_OboeAudioStream_getCallbackCount( JNIEXPORT jint JNICALL Java_com_google_sample_oboe_manualtest_OboeAudioStream_getLastErrorCallbackResult( - JNIEnv *env, jobject) { - return (jint) engine.getCurrentActivity()->getLastErrorCallbackResult(); + JNIEnv *env, jobject, jint streamIndex) { + oboe::AudioStream *oboeStream = engine.getCurrentActivity()->getStream(streamIndex); + if (oboeStream != nullptr) { + return (jint) oboeStream->getLastErrorCallbackResult(); + } + return 0; } JNIEXPORT jdouble JNICALL diff --git a/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/OboeAudioStream.java b/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/OboeAudioStream.java index d13a85ca..32462f15 100644 --- a/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/OboeAudioStream.java +++ b/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/OboeAudioStream.java @@ -204,7 +204,10 @@ abstract class OboeAudioStream extends AudioStreamBase { public native long getCallbackCount(); // TODO Move to another class? @Override - public native int getLastErrorCallbackResult(); // TODO Move to another class? + public int getLastErrorCallbackResult() { + return getLastErrorCallbackResult(streamIndex); + } + public native int getLastErrorCallbackResult(int streamIndex); @Override public long getFramesWritten() { diff --git a/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/TestDisconnectActivity.java b/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/TestDisconnectActivity.java index aa944041..ce9a5f40 100644 --- a/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/TestDisconnectActivity.java +++ b/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/TestDisconnectActivity.java @@ -262,13 +262,14 @@ public class TestDisconnectActivity extends TestAudioActivity implements Runnabl return ((config.getDirection() == StreamConfiguration.DIRECTION_OUTPUT) ? "OUT" : "IN") + ", Perf = " + StreamConfiguration.convertPerformanceModeToText( config.getPerformanceMode()) - + ", " + StreamConfiguration.convertSharingModeToText(config.getSharingMode()); + + ", " + StreamConfiguration.convertSharingModeToText(config.getSharingMode()) + + ", " + config.getSampleRate(); } private void testConfiguration(boolean isInput, int perfMode, int sharingMode, - int channelCount, + int sampleRate, boolean requestPlugin) throws InterruptedException { String actualConfigText = "none"; mSkipTest = false; @@ -295,7 +296,10 @@ public class TestDisconnectActivity extends TestAudioActivity implements Runnabl requestedConfig.reset(); requestedConfig.setPerformanceMode(perfMode); requestedConfig.setSharingMode(sharingMode); - requestedConfig.setChannelCount(channelCount); + requestedConfig.setSampleRate(sampleRate); + if (sampleRate != 0) { + requestedConfig.setRateConversionQuality(StreamConfiguration.RATE_CONVERSION_QUALITY_MEDIUM); + } log("========================== #" + mTestCount); log("Requested:"); @@ -427,12 +431,17 @@ public class TestDisconnectActivity extends TestAudioActivity implements Runnabl } private void testConfiguration(boolean isInput, int performanceMode, - int sharingMode) throws InterruptedException { - int channelCount = 2; + int sharingMode, int sampleRate) throws InterruptedException { boolean requestPlugin = true; // plug IN - testConfiguration(isInput, performanceMode, sharingMode, channelCount, requestPlugin); + testConfiguration(isInput, performanceMode, sharingMode, sampleRate, requestPlugin); requestPlugin = false; // UNplug - testConfiguration(isInput, performanceMode, sharingMode, channelCount, requestPlugin); + testConfiguration(isInput, performanceMode, sharingMode, sampleRate, requestPlugin); + } + + private void testConfiguration(boolean isInput, int performanceMode, + int sharingMode) throws InterruptedException { + final int sampleRate = 0; + testConfiguration(isInput, performanceMode, sharingMode, sampleRate); } private void testConfiguration(int performanceMode, @@ -454,6 +463,8 @@ public class TestDisconnectActivity extends TestAudioActivity implements Runnabl mFailCount = 0; // Try several different configurations. try { + testConfiguration(false, StreamConfiguration.PERFORMANCE_MODE_LOW_LATENCY, + StreamConfiguration.SHARING_MODE_EXCLUSIVE, 44100); testConfiguration(StreamConfiguration.PERFORMANCE_MODE_LOW_LATENCY, StreamConfiguration.SHARING_MODE_EXCLUSIVE); testConfiguration(StreamConfiguration.PERFORMANCE_MODE_LOW_LATENCY, diff --git a/include/oboe/AudioStream.h b/include/oboe/AudioStream.h index a158aafc..c862de6d 100644 --- a/include/oboe/AudioStream.h +++ b/include/oboe/AudioStream.h @@ -393,12 +393,25 @@ public: * Swap old callback for new callback. * This not atomic. * This should only be used internally. - * @param streamCallback - * @return previous streamCallback + * @param dataCallback + * @return previous dataCallback */ - AudioStreamCallback *swapCallback(AudioStreamCallback *streamCallback) { - AudioStreamCallback *previousCallback = mStreamCallback; - mStreamCallback = streamCallback; + AudioStreamDataCallback *swapDataCallback(AudioStreamDataCallback *dataCallback) { + AudioStreamDataCallback *previousCallback = mDataCallback; + mDataCallback = dataCallback; + return previousCallback; + } + + /* + * Swap old callback for new callback. + * This not atomic. + * This should only be used internally. + * @param errorCallback + * @return previous errorCallback + */ + AudioStreamErrorCallback *swapErrorCallback(AudioStreamErrorCallback *errorCallback) { + AudioStreamErrorCallback *previousCallback = mErrorCallback; + mErrorCallback = errorCallback; return previousCallback; } @@ -419,6 +432,13 @@ public: ResultWithValue<int32_t> waitForAvailableFrames(int32_t numFrames, int64_t timeoutNanoseconds); + /** + * @return last result passed from an error callback + */ + virtual oboe::Result getLastErrorCallbackResult() const { + return mErrorCallbackResult; + } + protected: /** @@ -515,8 +535,10 @@ protected: std::mutex mLock; // for synchronizing start/stop/close + oboe::Result mErrorCallbackResult = oboe::Result::OK; private: + // Log the scheduler if it changes. void checkScheduler(); int mPreviousScheduler = -1; diff --git a/include/oboe/AudioStreamBase.h b/include/oboe/AudioStreamBase.h index 787ff20e..73c27dfd 100644 --- a/include/oboe/AudioStreamBase.h +++ b/include/oboe/AudioStreamBase.h @@ -100,10 +100,35 @@ public: int32_t getDeviceId() const { return mDeviceId; } /** - * @return the callback object for this stream, if set. + * For internal use only. + * @return the data callback object for this stream, if set. */ - AudioStreamCallback* getCallback() const { - return mStreamCallback; + AudioStreamDataCallback *getDataCallback() const { + return mDataCallback; + } + + /** + * For internal use only. + * @return the error callback object for this stream, if set. + */ + AudioStreamErrorCallback *getErrorCallback() const { + return mErrorCallback; + } + + /** + * @return true if a data callback was set for this stream + */ + bool isDataCallbackSpecified() const { + return mDataCallback != nullptr; + } + + /** + * Note that if the app does not set an error callback then a + * default one may be provided. + * @return true if an error callback was set for this stream + */ + bool isErrorCallbackSpecified() const { + return mErrorCallback != nullptr; } /** @@ -148,9 +173,15 @@ public: } protected: + /** The callback which will be fired when new data is ready to be read/written. **/ + AudioStreamDataCallback *mDataCallback = nullptr; + + /** The callback which will be fired when an error or a disconnect occurs. **/ + AudioStreamErrorCallback *mErrorCallback = nullptr; + + /** The callback that combines data and error callback. **/ + // AudioStreamCallback *mStreamCallback = nullptr; - /** The callback which will be fired when new data is ready to be read/written **/ - AudioStreamCallback *mStreamCallback = nullptr; /** Number of audio frames which will be requested in each callback */ int32_t mFramesPerCallback = kUnspecified; /** Stream channel count */ diff --git a/include/oboe/AudioStreamBuilder.h b/include/oboe/AudioStreamBuilder.h index 80c5da42..d63578c3 100644 --- a/include/oboe/AudioStreamBuilder.h +++ b/include/oboe/AudioStreamBuilder.h @@ -306,8 +306,44 @@ public: } /** + * Specifies an object to handle data related callbacks from the underlying API. + * + * <strong>Important: See AudioStreamCallback for restrictions on what may be called + * from the callback methods.</strong> + * + * @param dataCallback + * @return pointer to the builder so calls can be chained + */ + AudioStreamBuilder *setDataCallback(oboe::AudioStreamDataCallback *dataCallback) { + mDataCallback = dataCallback; + return this; + } + + /** + * Specifies an object to handle error related callbacks from the underlying API. + * This can occur when a stream is disconnected because a headset is plugged in or unplugged. + * It can also occur if the audio service fails or if an exclusive stream is stolen by + * another stream. + * + * <strong>Important: See AudioStreamCallback for restrictions on what may be called + * from the callback methods.</strong> + * + * <strong>When an error callback occurs, the associated stream must be stopped and closed + * in a separate thread.</strong> + * + * @param errorCallback + * @return pointer to the builder so calls can be chained + */ + AudioStreamBuilder *setErrorCallback(oboe::AudioStreamErrorCallback *errorCallback) { + mErrorCallback = errorCallback; + return this; + } + + /** * Specifies an object to handle data or error related callbacks from the underlying API. * + * This is the equivalent of calling both setDataCallback() and setErrorCallback(). + * * <strong>Important: See AudioStreamCallback for restrictions on what may be called * from the callback methods.</strong> * @@ -327,7 +363,9 @@ public: * @return pointer to the builder so calls can be chained */ AudioStreamBuilder *setCallback(AudioStreamCallback *streamCallback) { - mStreamCallback = streamCallback; + // Use the same callback object for both, dual inheritance. + mDataCallback = streamCallback; + mErrorCallback = streamCallback; return this; } diff --git a/include/oboe/AudioStreamCallback.h b/include/oboe/AudioStreamCallback.h index f427693e..0018623d 100644 --- a/include/oboe/AudioStreamCallback.h +++ b/include/oboe/AudioStreamCallback.h @@ -24,15 +24,16 @@ namespace oboe { class AudioStream; /** - * AudioStreamCallback defines a callback interface for: - * - * 1) moving data to/from an audio stream using `onAudioReady` + * AudioStreamDataCallback defines a callback interface for + * moving data to/from an audio stream using `onAudioReady` * 2) being alerted when a stream has an error using `onError*` methods * + * It is used with AudioStreamBuilder::setDataCallback(). */ -class AudioStreamCallback { + +class AudioStreamDataCallback { public: - virtual ~AudioStreamCallback() = default; + virtual ~AudioStreamDataCallback() = default; /** * A buffer is ready for processing. @@ -75,21 +76,60 @@ public: * If you need to move data, eg. MIDI commands, in or out of the callback function then * we recommend the use of non-blocking techniques such as an atomic FIFO. * - * @param oboeStream pointer to the associated stream + * @param audioStream pointer to the associated stream * @param audioData buffer containing input data or a place to put output data * @param numFrames number of frames to be processed * @return DataCallbackResult::Continue or DataCallbackResult::Stop */ virtual DataCallbackResult onAudioReady( - AudioStream *oboeStream, + AudioStream *audioStream, void *audioData, int32_t numFrames) = 0; +}; + +/** + * AudioStreamDataCallback defines a callback interface for + * being alerted when a stream has an error or is disconnected + * using `onError*` methods. + * + * It is used with AudioStreamBuilder::setErrorCallback(). + */ +class AudioStreamErrorCallback { +public: + virtual ~AudioStreamErrorCallback() = default; /** - * This will be called when an error occurs on a stream or when the stream is disconnected. + * This will be called first when an error occurs on a stream or when the stream is disconnected. + * + * It can be used to override and customize the normal error processing. + * Use of this method is considered an advanced technique. + * It might, for example, be used if an app want to use a high level lock when + * closing and reopening a stream. + * Or it might be used when an app want to signal a management thread that handles + * all of the stream state. + * + * If this method returns false then + * the stream will be stopped, and onErrorBeforeClose() will be called, + * then the stream will be closed and onErrorAfterClose() will be closed. * - * Note that this will be called on a different thread than the onAudioReady() thread. - * This thread will be created by Oboe. + * If this method returns true then the normal error processing will not occur. + * In that case, the app MUST stop() and close() the stream! + * + * Note that this will be called on a thread created by Oboe. + * + * @param audioStream pointer to the associated stream + * @param error + */ + virtual bool onError(AudioStream* /* audioStream */, Result /* error */) { + return false; // false means the stream will be stopped and closed by Oboe + // return true; // true means the stream will be stopped and closed by the app + } + + /** + * This will be called when an error occurs on a stream or when the stream is disconnected + * and if onError() returns false. + * + * Note that this will be called on a thread created by Oboe. * * The underlying stream will already be stopped by Oboe but not yet closed. * So the stream can be queried. @@ -97,27 +137,45 @@ public: * Do not close or delete the stream in this method because it will be * closed after this method returns. * - * @param oboeStream pointer to the associated stream + * @param audioStream pointer to the associated stream * @param error */ - virtual void onErrorBeforeClose(AudioStream* /* oboeStream */, Result /* error */) {} + virtual void onErrorBeforeClose(AudioStream* /* audioStream */, Result /* error */) {} /** - * This will be called when an error occurs on a stream or when the stream is disconnected. + * This will be called when an error occurs on a stream or when the stream is disconnected + * and if onError() returns false. + * * The underlying AAudio or OpenSL ES stream will already be stopped AND closed by Oboe. * So the underlying stream cannot be referenced. * But you can still query most parameters. * * This callback could be used to reopen a new stream on another device. - * You can safely delete the old AudioStream in this method. * - * @param oboeStream pointer to the associated stream + * @param audioStream pointer to the associated stream * @param error */ - virtual void onErrorAfterClose(AudioStream* /* oboeStream */, Result /* error */) {} + virtual void onErrorAfterClose(AudioStream* /* audioStream */, Result /* error */) {} }; +/** + * AudioStreamCallback defines a callback interface for: + * + * 1) moving data to/from an audio stream using `onAudioReady` + * 2) being alerted when a stream has an error using `onError*` methods + * + * It is used with AudioStreamBuilder::setCallback(). + * + * It combines the interfaces defined by AudioStreamDataCallback and AudioStreamErrorCallback. + * This was the original callback object. We now recommend using the individual interfaces. + */ +class AudioStreamCallback : public AudioStreamDataCallback, + public AudioStreamErrorCallback { +public: + virtual ~AudioStreamCallback() = default; +}; + } // namespace oboe #endif //OBOE_STREAM_CALLBACK_H diff --git a/src/aaudio/AudioStreamAAudio.cpp b/src/aaudio/AudioStreamAAudio.cpp index 6d77c509..0b7a2e8c 100644 --- a/src/aaudio/AudioStreamAAudio.cpp +++ b/src/aaudio/AudioStreamAAudio.cpp @@ -62,14 +62,16 @@ static aaudio_data_callback_result_t oboe_aaudio_data_callback_proc( static void oboe_aaudio_error_thread_proc(AudioStreamAAudio *oboeStream, Result error) { LOGD("%s() - entering >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>", __func__); - oboeStream->requestStop(); - if (oboeStream->getCallback() != nullptr) { - oboeStream->getCallback()->onErrorBeforeClose(oboeStream, error); - } - oboeStream->close(); - if (oboeStream->getCallback() != nullptr) { + AudioStreamErrorCallback *errorCallback = oboeStream->getErrorCallback(); + if (errorCallback == nullptr) return; // should be impossible + bool handled = errorCallback->onError(oboeStream, error); + + if (!handled) { + oboeStream->requestStop(); + errorCallback->onErrorBeforeClose(oboeStream, error); + oboeStream->close(); // Warning, oboeStream may get deleted by this callback. - oboeStream->getCallback()->onErrorAfterClose(oboeStream, error); + errorCallback->onErrorAfterClose(oboeStream, error); } LOGD("%s() - exiting <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<", __func__); } @@ -101,14 +103,18 @@ bool AudioStreamAAudio::isSupported() { return openResult == 0; } -// Static 'C' wrapper for the error callback method. +// Static method for the error callback. +// We use a method so we can access protected methods on the stream. // Launch a thread to handle the error. // That other thread can safely stop, close and delete the stream. void AudioStreamAAudio::internalErrorCallback( AAudioStream *stream, void *userData, aaudio_result_t error) { + oboe::Result oboeResult = static_cast<Result>(error); AudioStreamAAudio *oboeStream = reinterpret_cast<AudioStreamAAudio*>(userData); + LOGI("%s() oboeResult = %d", __func__, oboeResult); + oboeStream->mErrorCallbackResult = oboeResult; // Prevents deletion of the stream if the app is using AudioStreamBuilder::openStream(shared_ptr) std::shared_ptr<AudioStream> sharedStream = oboeStream->lockWeakThis(); @@ -118,16 +124,14 @@ void AudioStreamAAudio::internalErrorCallback( if (oboeStream->wasErrorCallbackCalled()) { // block extra error callbacks LOGE("%s() multiple error callbacks called!", __func__); } else if (stream != oboeStream->getUnderlyingStream()) { - LOGW("%s() stream already closed or closing", __func__); // can happen if there are bugs + LOGW("%s() stream already closed or closing", __func__); // might happen if there are bugs } else if (sharedStream) { // Handle error on a separate thread using shared pointer. - std::thread t(oboe_aaudio_error_thread_proc_shared, sharedStream, - static_cast<Result>(error)); + std::thread t(oboe_aaudio_error_thread_proc_shared, sharedStream, oboeResult); t.detach(); } else { // Handle error on a separate thread. - std::thread t(oboe_aaudio_error_thread_proc, oboeStream, - static_cast<Result>(error)); + std::thread t(oboe_aaudio_error_thread_proc, oboeStream, oboeResult); t.detach(); } } @@ -229,13 +233,19 @@ Result AudioStreamAAudio::open() { // TODO get more parameters from the builder? - if (mStreamCallback != nullptr) { + if (isDataCallbackSpecified()) { mLibLoader->builder_setDataCallback(aaudioBuilder, oboe_aaudio_data_callback_proc, this); mLibLoader->builder_setFramesPerDataCallback(aaudioBuilder, getFramesPerCallback()); - // If the data callback is not being used then the write method will return an error - // and the app can stop and close the stream. + + if (!isErrorCallbackSpecified()) { + // The app did not specify a callback so we should specify + // our own so the stream gets closed and stopped. + mErrorCallback = &mDefaultErrorCallback; + } mLibLoader->builder_setErrorCallback(aaudioBuilder, internalErrorCallback, this); } + // Else if the data callback is not being used then the write method will return an error + // and the app can stop and close the stream. // ============= OPEN THE STREAM ================ { @@ -357,7 +367,7 @@ Result AudioStreamAAudio::requestStart() { return Result::OK; } } - if (mStreamCallback != nullptr) { // Was a callback requested? + if (isDataCallbackSpecified()) { setDataCallbackEnabled(true); } return static_cast<Result>(mLibLoader->stream_requestStart(stream)); diff --git a/src/aaudio/AudioStreamAAudio.h b/src/aaudio/AudioStreamAAudio.h index 90958170..83f4b2fe 100644 --- a/src/aaudio/AudioStreamAAudio.h +++ b/src/aaudio/AudioStreamAAudio.h @@ -118,10 +118,13 @@ private: std::atomic<bool> mCallbackThreadEnabled; - // pointer to the underlying AAudio stream, valid if open, null if closed + // pointer to the underlying 'C' AAudio stream, valid if open, null if closed std::atomic<AAudioStream *> mAAudioStream{nullptr}; static AAudioLoader *mLibLoader; + + // We may not use this but it is so small that it is not worth allocating dynamically. + AudioStreamErrorCallback mDefaultErrorCallback; }; } // namespace oboe diff --git a/src/common/AudioSourceCaller.cpp b/src/common/AudioSourceCaller.cpp index 0180c226..854ade9c 100644 --- a/src/common/AudioSourceCaller.cpp +++ b/src/common/AudioSourceCaller.cpp @@ -20,7 +20,7 @@ using namespace oboe; using namespace flowgraph; int32_t AudioSourceCaller::onProcessFixedBlock(uint8_t *buffer, int32_t numBytes) { - oboe::AudioStreamCallback *callback = mStream->getCallback(); + oboe::AudioStreamDataCallback *callback = mStream->getDataCallback(); int32_t result = 0; int32_t numFrames = numBytes / mStream->getBytesPerFrame(); if (callback != nullptr) { diff --git a/src/common/AudioStream.cpp b/src/common/AudioStream.cpp index 7bcd087c..8a9cd452 100644 --- a/src/common/AudioStream.cpp +++ b/src/common/AudioStream.cpp @@ -59,10 +59,10 @@ DataCallbackResult AudioStream::fireDataCallback(void *audioData, int32_t numFra } DataCallbackResult result; - if (mStreamCallback == nullptr) { - result = onDefaultCallback(audioData, numFrames); + if (mDataCallback) { + result = mDataCallback->onAudioReady(this, audioData, numFrames); } else { - result = mStreamCallback->onAudioReady(this, audioData, numFrames); + result = onDefaultCallback(audioData, numFrames); } // On Oreo, we might get called after returning stop. // So block that here. diff --git a/src/common/DataConversionFlowGraph.cpp b/src/common/DataConversionFlowGraph.cpp index 7cc742ee..ef99d6a7 100644 --- a/src/common/DataConversionFlowGraph.cpp +++ b/src/common/DataConversionFlowGraph.cpp @@ -101,8 +101,9 @@ Result DataConversionFlowGraph::configure(AudioStream *sourceStream, AudioStream // Source // IF OUTPUT and using a callback then call back to the app using a SourceCaller. // OR IF INPUT and NOT using a callback then read from the child stream using a SourceCaller. - if ((sourceStream->getCallback() != nullptr && isOutput) - || (sourceStream->getCallback() == nullptr && isInput)) { + bool isDataCallbackSpecified = sourceStream->isDataCallbackSpecified(); + if ((isDataCallbackSpecified && isOutput) + || (!isDataCallbackSpecified && isInput)) { int32_t actualSourceFramesPerCallback = (sourceFramesPerCallback == kUnspecified) ? sourceStream->getFramesPerBurst() : sourceFramesPerCallback; @@ -236,7 +237,7 @@ int32_t DataConversionFlowGraph::write(void *inputBuffer, int32_t numFrames) { int32_t DataConversionFlowGraph::onProcessFixedBlock(uint8_t *buffer, int32_t numBytes) { int32_t numFrames = numBytes / mFilterStream->getBytesPerFrame(); - mCallbackResult = mFilterStream->getCallback()->onAudioReady(mFilterStream, buffer, numFrames); + mCallbackResult = mFilterStream->getDataCallback()->onAudioReady(mFilterStream, buffer, numFrames); // TODO handle STOP from callback, process data remaining in the block adapter return numBytes; -}
\ No newline at end of file +} diff --git a/src/common/FilterAudioStream.cpp b/src/common/FilterAudioStream.cpp index a4715835..bacb54b4 100644 --- a/src/common/FilterAudioStream.cpp +++ b/src/common/FilterAudioStream.cpp @@ -16,6 +16,7 @@ #include <memory> +#include "OboeDebug.h" #include "FilterAudioStream.h" using namespace oboe; @@ -90,3 +91,17 @@ ResultWithValue<int32_t> FilterAudioStream::read(void *buffer, return ResultWithValue<int32_t>::createBasedOnSign(framesRead); } +DataCallbackResult FilterAudioStream::onAudioReady(AudioStream *oboeStream, + void *audioData, + int32_t numFrames) { + int32_t framesProcessed; + if (oboeStream->getDirection() == Direction::Output) { + framesProcessed = mFlowGraph->read(audioData, numFrames, 0 /* timeout */); + } else { + framesProcessed = mFlowGraph->write(audioData, numFrames); + } + LOGI("%s() framesProcessed = %d, numFrames = %d", __func__, framesProcessed, numFrames); + return (framesProcessed < numFrames) + ? DataCallbackResult::Stop + : mFlowGraph->getDataCallbackResult(); +}
\ No newline at end of file diff --git a/src/common/FilterAudioStream.h b/src/common/FilterAudioStream.h index 3949de77..be652c9d 100644 --- a/src/common/FilterAudioStream.h +++ b/src/common/FilterAudioStream.h @@ -42,8 +42,11 @@ public: : AudioStream(builder) , mChildStream(childStream) { // Intercept the callback if used. - if (builder.getCallback() != nullptr) { - mStreamCallback = mChildStream->swapCallback(this); + if (builder.getErrorCallback() != nullptr) { + mErrorCallback = mChildStream->swapErrorCallback(this); + } + if (builder.getDataCallback() != nullptr) { + mDataCallback = mChildStream->swapDataCallback(this); } else { const int size = childStream->getFramesPerBurst() * childStream->getBytesPerFrame(); mBlockingBuffer = std::make_unique<uint8_t[]>(size); @@ -175,32 +178,36 @@ public: DataCallbackResult onAudioReady(AudioStream *oboeStream, void *audioData, - int32_t numFrames) override { - int32_t framesProcessed; - if (oboeStream->getDirection() == Direction::Output) { - framesProcessed = mFlowGraph->read(audioData, numFrames, 0 /* timeout */); - } else { - framesProcessed = mFlowGraph->write(audioData, numFrames); + int32_t numFrames) override; + + bool onError(AudioStream * audioStream, Result error) override { + if (mErrorCallback != nullptr) { + return mErrorCallback->onError(this, error); } - return (framesProcessed < numFrames) - ? DataCallbackResult::Stop - : mFlowGraph->getDataCallbackResult(); + return false; } void onErrorBeforeClose(AudioStream *oboeStream, Result error) override { - if (mStreamCallback != nullptr) { - mStreamCallback->onErrorBeforeClose(this, error); + if (mErrorCallback != nullptr) { + mErrorCallback->onErrorBeforeClose(this, error); } } void onErrorAfterClose(AudioStream *oboeStream, Result error) override { // Close this parent stream because the callback will only close the child. AudioStream::close(); - if (mStreamCallback != nullptr) { - mStreamCallback->onErrorAfterClose(this, error); + if (mErrorCallback != nullptr) { + mErrorCallback->onErrorAfterClose(this, error); } } + /** + * @return last result passed from an error callback + */ + oboe::Result getLastErrorCallbackResult() const override { + return mChildStream->getLastErrorCallbackResult(); + } + private: std::unique_ptr<AudioStream> mChildStream; // this stream wraps the child stream diff --git a/src/common/QuirksManager.cpp b/src/common/QuirksManager.cpp index 21a52048..68f328e0 100644 --- a/src/common/QuirksManager.cpp +++ b/src/common/QuirksManager.cpp @@ -138,7 +138,7 @@ bool QuirksManager::isConversionNeeded( // know if we will get an MMAP stream. So, to be safe, just do the conversion in Oboe. if (OboeGlobals::areWorkaroundsEnabled() && builder.willUseAAudio() - && builder.getCallback() != nullptr + && builder.isDataCallbackSpecified() && builder.getFramesPerCallback() != 0 && getSdkVersion() <= __ANDROID_API_R__) { LOGI("QuirksManager::%s() avoid setFramesPerCallback(n>0)", __func__); diff --git a/src/opensles/AudioStreamBuffered.cpp b/src/opensles/AudioStreamBuffered.cpp index 85b9f8e4..8ee01667 100644 --- a/src/opensles/AudioStreamBuffered.cpp +++ b/src/opensles/AudioStreamBuffered.cpp @@ -260,7 +260,7 @@ int32_t AudioStreamBuffered::getBufferCapacityInFrames() const { bool AudioStreamBuffered::isXRunCountSupported() const { // XRun count is only supported if we're using blocking I/O (not callbacks) - return (getCallback() == nullptr); + return (!isDataCallbackSpecified()); } } // namespace oboe
\ No newline at end of file diff --git a/src/opensles/AudioStreamBuffered.h b/src/opensles/AudioStreamBuffered.h index 5923e8db..2b6152b6 100644 --- a/src/opensles/AudioStreamBuffered.h +++ b/src/opensles/AudioStreamBuffered.h @@ -60,7 +60,7 @@ protected: DataCallbackResult onDefaultCallback(void *audioData, int numFrames) override; // If there is no callback then we need a FIFO between the App and OpenSL ES. - bool usingFIFO() const { return getCallback() == nullptr; } + bool usingFIFO() const { return !isDataCallbackSpecified(); } virtual Result updateServiceFrameCounter() = 0; diff --git a/tests/testStreamClosedMethods.cpp b/tests/testStreamClosedMethods.cpp index a1f1105a..cbb2191c 100644 --- a/tests/testStreamClosedMethods.cpp +++ b/tests/testStreamClosedMethods.cpp @@ -19,15 +19,13 @@ using namespace oboe; -class MyCallback : public AudioStreamCallback { +class MyCallback : public AudioStreamDataCallback { public: DataCallbackResult onAudioReady(AudioStream *oboeStream, void *audioData, int32_t numFrames) override { return DataCallbackResult::Continue; } }; - - class StreamClosedReturnValues : public ::testing::Test { protected: @@ -134,13 +132,13 @@ TEST_F(StreamClosedReturnValues, GetDeviceIdReturnsLastKnownValue) { ASSERT_EQ(mStream->getDeviceId(), d); } -TEST_F(StreamClosedReturnValues, GetCallbackReturnsLastKnownValue) { +TEST_F(StreamClosedReturnValues, GetDataCallbackReturnsLastKnownValue) { - AudioStreamCallback *callback = new MyCallback(); - mBuilder.setCallback(callback); + AudioStreamDataCallback *callback = new MyCallback(); + mBuilder.setDataCallback(callback); ASSERT_TRUE(openAndCloseStream()); - AudioStreamCallback *callback2 = mStream->getCallback(); + AudioStreamDataCallback *callback2 = mStream->getDataCallback(); ASSERT_EQ(callback, callback2); } |