diff --git a/include/oboe/AudioStream.h b/include/oboe/AudioStream.h index 90b98a8dc..e394aaa4d 100644 --- a/include/oboe/AudioStream.h +++ b/include/oboe/AudioStream.h @@ -88,10 +88,12 @@ class AudioStream : public AudioStreamBase { * *

      * int64_t timeoutNanos = 500 * kNanosPerMillisecond; // arbitrary 1/2 second
-     * StreamState currentState = stream->getState(stream);
-     * while (currentState >= 0 && currentState != StreamState::Paused) {
-     *     currentState = stream->waitForStateChange(
-     *                                   stream, currentState, timeoutNanos);
+     * StreamState currentState = stream->getState();
+     * StreamState nextState = StreamState::Unknown;
+     * while (result == Result::OK && currentState != StreamState::Paused) {
+     *     result = stream->waitForStateChange(
+     *                                   currentState, &nextState, timeoutNanos);
+     *     currentState = nextState;
      * }
      * 
* @@ -130,7 +132,7 @@ class AudioStream : public AudioStreamBase { * * @return the count or negative error. */ - virtual int32_t getXRunCount() { + virtual int32_t getXRunCount() const { return static_cast(Result::ErrorUnimplemented); } @@ -151,9 +153,9 @@ class AudioStream : public AudioStreamBase { * This monotonic counter will never get reset. * @return the number of frames written so far */ - virtual int64_t getFramesWritten() { return mFramesWritten; } + virtual int64_t getFramesWritten() const { return mFramesWritten; } - virtual int64_t getFramesRead() { return static_cast(Result::ErrorUnimplemented); } + virtual int64_t getFramesRead() const { return mFramesRead; } virtual Result getTimestamp(clockid_t clockId, int64_t *framePosition, @@ -197,6 +199,9 @@ class AudioStream : public AudioStreamBase { virtual int64_t incrementFramesWritten(int32_t frames) { return mFramesWritten += frames; } + virtual int64_t incrementFramesRead(int32_t frames) { + return mFramesRead += frames; + } /** * Wait for a transition from one state to another. @@ -208,7 +213,18 @@ class AudioStream : public AudioStreamBase { StreamState endingState, int64_t timeoutNanoseconds); - Result fireCallback(void *audioData, int numFrames); + /** + * Override this to provide a default for when the application did not specify a callback. + * + * @param audioData + * @param numFrames + * @return result + */ + virtual DataCallbackResult onDefaultCallback(void *audioData, int numFrames) { + return DataCallbackResult::Stop; + } + + DataCallbackResult fireCallback(void *audioData, int numFrames); virtual void setNativeFormat(AudioFormat format) { mNativeFormat = format; @@ -219,7 +235,9 @@ class AudioStream : public AudioStreamBase { AudioFormat mNativeFormat = AudioFormat::Invalid; private: + // TODO these should be atomic like in AAudio int64_t mFramesWritten = 0; + int64_t mFramesRead = 0; int mPreviousScheduler = -1; }; diff --git a/src/aaudio/AudioStreamAAudio.cpp b/src/aaudio/AudioStreamAAudio.cpp index 452f8407e..d44ee9c3b 100644 --- a/src/aaudio/AudioStreamAAudio.cpp +++ b/src/aaudio/AudioStreamAAudio.cpp @@ -27,11 +27,9 @@ #include #endif - using namespace oboe; AAudioLoader *AudioStreamAAudio::mLibLoader = nullptr; - // 'C' wrapper for the data callback method static aaudio_data_callback_result_t oboe_aaudio_data_callback_proc( AAudioStream *stream, @@ -70,7 +68,6 @@ static void oboe_aaudio_error_callback_proc( } } - namespace oboe { /* @@ -80,8 +77,7 @@ AudioStreamAAudio::AudioStreamAAudio(const AudioStreamBuilder &builder) : AudioStream(builder) , mFloatCallbackBuffer(nullptr) , mShortCallbackBuffer(nullptr) - , mAAudioStream(nullptr) -{ + , mAAudioStream(nullptr) { mCallbackThreadEnabled.store(false); LOGD("AudioStreamAAudio() call isSupported()"); isSupported(); @@ -245,8 +241,7 @@ Result AudioStreamAAudio::convertApplicationDataToNative(int32_t numFrames) { return result; } -Result AudioStreamAAudio::requestStart() -{ +Result AudioStreamAAudio::requestStart() { std::lock_guard lock(mLock); AAudioStream *stream = mAAudioStream.load(); if (stream != nullptr) { @@ -256,8 +251,7 @@ Result AudioStreamAAudio::requestStart() } } -Result AudioStreamAAudio::requestPause() -{ +Result AudioStreamAAudio::requestPause() { std::lock_guard lock(mLock); AAudioStream *stream = mAAudioStream.load(); if (stream != nullptr) { @@ -277,8 +271,7 @@ Result AudioStreamAAudio::requestFlush() { } } -Result AudioStreamAAudio::requestStop() -{ +Result AudioStreamAAudio::requestStop() { std::lock_guard lock(mLock); AAudioStream *stream = mAAudioStream.load(); if (stream != nullptr) { @@ -291,8 +284,7 @@ Result AudioStreamAAudio::requestStop() // TODO: Update to return tuple of Result and framesWritten (avoids cast) int32_t AudioStreamAAudio::write(const void *buffer, int32_t numFrames, - int64_t timeoutNanoseconds) -{ + int64_t timeoutNanoseconds) { AAudioStream *stream = mAAudioStream.load(); if (stream != nullptr) { return mLibLoader->stream_write(mAAudioStream, buffer, numFrames, timeoutNanoseconds); @@ -301,10 +293,20 @@ int32_t AudioStreamAAudio::write(const void *buffer, } } +int32_t AudioStreamAAudio::read(void *buffer, + int32_t numFrames, + int64_t timeoutNanoseconds) { + AAudioStream *stream = mAAudioStream.load(); + if (stream != nullptr) { + return mLibLoader->stream_read(mAAudioStream, buffer, numFrames, timeoutNanoseconds); + } else { + return static_cast(Result::ErrorNull); + } +} + Result AudioStreamAAudio::waitForStateChange(StreamState currentState, StreamState *nextState, - int64_t timeoutNanoseconds) -{ + int64_t timeoutNanoseconds) { AAudioStream *stream = mAAudioStream.load(); if (stream != nullptr) { @@ -321,16 +323,14 @@ Result AudioStreamAAudio::waitForStateChange(StreamState currentState, } } -Result AudioStreamAAudio::setBufferSizeInFrames(int32_t requestedFrames) -{ +Result AudioStreamAAudio::setBufferSizeInFrames(int32_t requestedFrames) { if (requestedFrames > mBufferCapacityInFrames) { requestedFrames = mBufferCapacityInFrames; } return static_cast(mLibLoader->stream_setBufferSize(mAAudioStream, requestedFrames)); } -StreamState AudioStreamAAudio::getState() -{ +StreamState AudioStreamAAudio::getState() { AAudioStream *stream = mAAudioStream.load(); if (stream != nullptr) { return static_cast(mLibLoader->stream_getState(stream)); @@ -348,8 +348,7 @@ int32_t AudioStreamAAudio::getBufferSizeInFrames() const { } } -int32_t AudioStreamAAudio::getFramesPerBurst() -{ +int32_t AudioStreamAAudio::getFramesPerBurst() { AAudioStream *stream = mAAudioStream.load(); if (stream != nullptr) { return mLibLoader->stream_getFramesPerBurst(stream); @@ -358,8 +357,7 @@ int32_t AudioStreamAAudio::getFramesPerBurst() } } -int64_t AudioStreamAAudio::getFramesRead() -{ +int64_t AudioStreamAAudio::getFramesRead() const { AAudioStream *stream = mAAudioStream.load(); if (stream != nullptr) { return mLibLoader->stream_getFramesRead(stream); @@ -367,8 +365,8 @@ int64_t AudioStreamAAudio::getFramesRead() return static_cast(Result::ErrorNull); } } -int64_t AudioStreamAAudio::getFramesWritten() -{ + +int64_t AudioStreamAAudio::getFramesWritten() const { AAudioStream *stream = mAAudioStream.load(); if (stream != nullptr) { return mLibLoader->stream_getFramesWritten(stream); @@ -377,8 +375,7 @@ int64_t AudioStreamAAudio::getFramesWritten() } } -int32_t AudioStreamAAudio::getXRunCount() -{ +int32_t AudioStreamAAudio::getXRunCount() const { AAudioStream *stream = mAAudioStream.load(); if (stream != nullptr) { return mLibLoader->stream_getXRunCount(stream); diff --git a/src/aaudio/AudioStreamAAudio.h b/src/aaudio/AudioStreamAAudio.h index 9c5c40658..0abab90b5 100644 --- a/src/aaudio/AudioStreamAAudio.h +++ b/src/aaudio/AudioStreamAAudio.h @@ -61,16 +61,20 @@ class AudioStreamAAudio : public AudioStream { Result requestStop() override; int32_t write(const void *buffer, - int32_t numFrames, - int64_t timeoutNanoseconds) override; + int32_t numFrames, + int64_t timeoutNanoseconds) override; + + int32_t read(void *buffer, + int32_t numFrames, + int64_t timeoutNanoseconds) override; Result setBufferSizeInFrames(int32_t requestedFrames) override; int32_t getBufferSizeInFrames() const override; int32_t getFramesPerBurst() override; - int32_t getXRunCount() override; + int32_t getXRunCount() const override; - int64_t getFramesRead() override; - int64_t getFramesWritten() override; + int64_t getFramesRead() const override; + int64_t getFramesWritten() const override; Result waitForStateChange(StreamState currentState, StreamState *nextState, diff --git a/src/common/AudioStream.cpp b/src/common/AudioStream.cpp index 9ece16682..56fd9bd29 100644 --- a/src/common/AudioStream.cpp +++ b/src/common/AudioStream.cpp @@ -30,12 +30,11 @@ AudioStream::AudioStream(const AudioStreamBuilder &builder) } Result AudioStream::open() { - // TODO validate parameters or let underlyng API validate them? + // TODO validate parameters or let underlying API validate them? return Result::OK; } -Result AudioStream::fireCallback(void *audioData, int32_t numFrames) -{ +DataCallbackResult AudioStream::fireCallback(void *audioData, int32_t numFrames) { int scheduler = sched_getscheduler(0) & ~SCHED_RESET_ON_FORK; // for current thread if (scheduler != mPreviousScheduler) { LOGD("AudioStream::fireCallback() scheduler = %s", @@ -45,21 +44,20 @@ Result AudioStream::fireCallback(void *audioData, int32_t numFrames) ); mPreviousScheduler = scheduler; } + + DataCallbackResult result; if (mStreamCallback == nullptr) { - return Result::ErrorNull; + result = onDefaultCallback(audioData, numFrames); } else { - /** - * TODO: onAudioRead doesn't return an Result, it returns either Continue or Stop - * neither of which tells us whether an error occured. Figure out what to do here. - */ - /*Result result = mStreamCallback->onAudioReady(this, audioData, numFrames); - if (result == OBOE_OK) { - mFramesWritten += numFrames; - }*/ - mStreamCallback->onAudioReady(this, audioData, numFrames); - mFramesWritten += numFrames; - return Result::OK; + result = mStreamCallback->onAudioReady(this, audioData, numFrames); + if (getDirection() == Direction::Input) { + incrementFramesRead(numFrames); + } else { + incrementFramesWritten(numFrames); + } } + + return result; } Result AudioStream::waitForStateTransition(StreamState startingState, diff --git a/src/fifo/FifoBuffer.cpp b/src/fifo/FifoBuffer.cpp index 030e57df1..ab00ee8d0 100644 --- a/src/fifo/FifoBuffer.cpp +++ b/src/fifo/FifoBuffer.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include "common/OboeDebug.h" #include "fifo/FifoControllerBase.h" @@ -31,17 +32,19 @@ FifoBuffer::FifoBuffer(uint32_t bytesPerFrame, uint32_t capacityInFrames) : mFrameCapacity(capacityInFrames) , mBytesPerFrame(bytesPerFrame) , mStorage(NULL) - , mReadAtNanoseconds(0) , mFramesReadCount(0) , mFramesUnderrunCount(0) , mUnderrunCount(0) { + assert(bytesPerFrame > 0); + assert(capacityInFrames > 0); mFifo = new FifoController(capacityInFrames, capacityInFrames); // allocate buffer int32_t bytesPerBuffer = bytesPerFrame * capacityInFrames; mStorage = new uint8_t[bytesPerBuffer]; mStorageOwned = true; - LOGD("FifoProcessor: numFrames = %d, bytesPerFrame = %d", capacityInFrames, bytesPerFrame); + LOGD("FifoProcessor: capacityInFrames = %d, bytesPerFrame = %d", + capacityInFrames, bytesPerFrame); } FifoBuffer::FifoBuffer( uint32_t bytesPerFrame, @@ -53,18 +56,20 @@ FifoBuffer::FifoBuffer( uint32_t bytesPerFrame, : mFrameCapacity(capacityInFrames) , mBytesPerFrame(bytesPerFrame) , mStorage(dataStorageAddress) - , mReadAtNanoseconds(0) , mFramesReadCount(0) , mFramesUnderrunCount(0) , mUnderrunCount(0) { + assert(bytesPerFrame > 0); + assert(capacityInFrames > 0); mFifo = new FifoControllerIndirect(capacityInFrames, capacityInFrames, readIndexAddress, writeIndexAddress); mStorage = dataStorageAddress; mStorageOwned = false; - LOGD("FifoProcessor: capacityInFrames = %d, bytesPerFrame = %d", capacityInFrames, bytesPerFrame); + LOGD("FifoProcessor: capacityInFrames = %d, bytesPerFrame = %d", + capacityInFrames, bytesPerFrame); } FifoBuffer::~FifoBuffer() { @@ -156,7 +161,6 @@ int32_t FifoBuffer::write(const void *buffer, int32_t framesToWrite) { } int32_t FifoBuffer::readNow(void *buffer, int32_t numFrames) { - mLastReadSize = numFrames; int32_t framesLeft = numFrames; int32_t framesRead = read(buffer, numFrames); framesLeft -= framesRead; @@ -168,19 +172,10 @@ int32_t FifoBuffer::readNow(void *buffer, int32_t numFrames) { int32_t bytesToZero = convertFramesToBytes(framesLeft); memset(buffer, 0, bytesToZero); } - mReadAtNanoseconds = AudioClock::getNanoseconds(); return framesRead; } -int64_t FifoBuffer::getNextReadTime(int frameRate) { - if (mReadAtNanoseconds == 0) { - return 0; - } - int64_t nanosPerBuffer = (kNanosPerSecond * mLastReadSize) / frameRate; - return mReadAtNanoseconds + nanosPerBuffer; -} - uint32_t FifoBuffer::getThresholdFrames() const { return mFifo->getThreshold(); } diff --git a/src/fifo/FifoBuffer.h b/src/fifo/FifoBuffer.h index e94e53a8e..9d9944a43 100644 --- a/src/fifo/FifoBuffer.h +++ b/src/fifo/FifoBuffer.h @@ -52,8 +52,6 @@ class FifoBuffer { int32_t readNow(void *buffer, int32_t numFrames); - int64_t getNextReadTime(int32_t frameRate); - uint32_t getUnderrunCount() const { return mUnderrunCount; } FifoControllerBase *getFifoControllerBase() { return mFifo; } @@ -85,11 +83,9 @@ class FifoBuffer { uint8_t* mStorage; bool mStorageOwned; // did this object allocate the storage? FifoControllerBase *mFifo; - int64_t mReadAtNanoseconds; uint64_t mFramesReadCount; uint64_t mFramesUnderrunCount; uint32_t mUnderrunCount; // need? just use frames - uint32_t mLastReadSize; }; } // namespace oboe diff --git a/src/opensles/AudioInputStreamOpenSLES.cpp b/src/opensles/AudioInputStreamOpenSLES.cpp index 3ff10e64f..dfe851398 100644 --- a/src/opensles/AudioInputStreamOpenSLES.cpp +++ b/src/opensles/AudioInputStreamOpenSLES.cpp @@ -64,7 +64,7 @@ Result AudioInputStreamOpenSLES::open() { // configure audio sink SLDataLocator_AndroidSimpleBufferQueue loc_bufq = { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, // locatorType - static_cast(mBurstsPerBuffer)}; // numBuffers + static_cast(kBufferQueueLength)}; // numBuffers // Define the audio data format. SLDataFormat_PCM format_pcm = { @@ -139,6 +139,8 @@ Result AudioInputStreamOpenSLES::open() { goto error; } + allocateFifo(); + return Result::OK; error: diff --git a/src/opensles/AudioOutputStreamOpenSLES.cpp b/src/opensles/AudioOutputStreamOpenSLES.cpp index 21297661e..e05962ae6 100644 --- a/src/opensles/AudioOutputStreamOpenSLES.cpp +++ b/src/opensles/AudioOutputStreamOpenSLES.cpp @@ -88,7 +88,7 @@ Result AudioOutputStreamOpenSLES::open() { // configure audio source SLDataLocator_AndroidSimpleBufferQueue loc_bufq = { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, // locatorType - static_cast(mBurstsPerBuffer)}; // numBuffers + static_cast(kBufferQueueLength)}; // numBuffers // Define the audio data format. SLDataFormat_PCM format_pcm = { @@ -141,6 +141,8 @@ Result AudioOutputStreamOpenSLES::open() { goto error; } + allocateFifo(); + return Result::OK; error: return Result::ErrorInternal; // TODO convert error from SLES to OBOE diff --git a/src/opensles/AudioStreamBuffered.cpp b/src/opensles/AudioStreamBuffered.cpp index ceb4c850c..fb6614032 100644 --- a/src/opensles/AudioStreamBuffered.cpp +++ b/src/opensles/AudioStreamBuffered.cpp @@ -21,66 +21,191 @@ namespace oboe { +constexpr int kDefaultBurstsPerBuffer = 16; // arbitrary, allows dynamic latency tuning + /* * AudioStream with a FifoBuffer */ AudioStreamBuffered::AudioStreamBuffered(const AudioStreamBuilder &builder) - : AudioStream(builder) - , mFifoBuffer(nullptr) -{ + : AudioStream(builder) { } -Result AudioStreamBuffered::open() { +void AudioStreamBuffered::allocateFifo() { + // If the caller does not provide a callback use our own internal + // callback that reads data from the FIFO. + if (usingFIFO()) { + // FIFO is configured with the same format and channels as the stream. + int32_t capacity = getBufferCapacityInFrames(); + if (capacity == oboe::kUnspecified) { + capacity = getFramesPerBurst() * kDefaultBurstsPerBuffer; + mBufferCapacityInFrames = capacity; + } + mFifoBuffer.reset(new FifoBuffer(getBytesPerFrame(), capacity)); + } +} - Result result = AudioStream::open(); - if (result != Result::OK) { - return result; +int64_t AudioStreamBuffered::getFramesWritten() const { + if (usingFIFO()) { + return (int64_t) mFifoBuffer->getWriteCounter(); + } else { + return AudioStream::getFramesWritten(); } +} - // If the caller does not provide a callback use our own internal - // callback that reads data from the FIFO. - if (getCallback() == nullptr) { - LOGD("AudioStreamBuffered(): new FifoBuffer"); - // TODO: Fix memory leak here - mFifoBuffer = new FifoBuffer(getBytesPerFrame(), 1024); // TODO size? - // Create a callback that reads from the FIFO - mInternalCallback = std::unique_ptr(new AudioStreamBufferedCallback(this)); - mStreamCallback = mInternalCallback.get(); - LOGD("AudioStreamBuffered(): mStreamCallback = %p", mStreamCallback); +int64_t AudioStreamBuffered::getFramesRead() const { + if (usingFIFO()) { + return (int64_t) mFifoBuffer->getReadCounter(); + } else { + return AudioStream::getFramesRead(); } - return Result::OK; } -// TODO: This method should return a tuple of Result,int32_t where the 2nd return param is the frames written -int32_t AudioStreamBuffered::write(const void *buffer, - int32_t numFrames, - int64_t timeoutNanoseconds) -{ + + // This is called by the OpenSL ES callback to read or write the back end of the FIFO. +DataCallbackResult AudioStreamBuffered::onDefaultCallback(void *audioData, int numFrames) { + int32_t framesTransferred = 0; + + if (getDirection() == oboe::Direction::Output) { + // Read from the FIFO and write to audioData + framesTransferred = mFifoBuffer->readNow(audioData, numFrames); + } else { + // Read from audioData and write to the FIFO + framesTransferred = mFifoBuffer->write(audioData, numFrames); // FIXME writeNow???? + } + + if (framesTransferred < numFrames) { + // TODO If we do not allow FIFO to wrap then our timestamps will drift when there is an XRun! + incrementXRunCount(); + } + markCallbackTime(numFrames); // so foreground knows how long to wait. + return DataCallbackResult::Continue; +} + +void AudioStreamBuffered::markCallbackTime(int numFrames) { + mLastBackgroundSize = numFrames; + mBackgroundRanAtNanoseconds = AudioClock::getNanoseconds(); +} + +int64_t AudioStreamBuffered::predictNextCallbackTime() { + if (mBackgroundRanAtNanoseconds == 0) { + return 0; + } + int64_t nanosPerBuffer = (kNanosPerSecond * mLastBackgroundSize) / getSampleRate(); + const int64_t margin = 200 * kNanosPerMicrosecond; // arbitrary delay so we wake up just after + return mBackgroundRanAtNanoseconds + nanosPerBuffer + margin; +} + +// TODO: Consider returning an error_or_value struct instead. +// Common code for read/write. +// @return number of frames transferred or negative error +int32_t AudioStreamBuffered::transfer(void *buffer, + int32_t numFrames, + int64_t timeoutNanoseconds) { + // Validate arguments. + if (buffer == nullptr) { + LOGE("AudioStreamBuffered::%s(): buffer is NULL", __func__); + return (int32_t) Result ::ErrorNull; + } + if (numFrames < 0) { + LOGE("AudioStreamBuffered::%s(): numFrames is negative", __func__); + return (int32_t) Result::ErrorOutOfRange; + } else if (numFrames == 0) { + return 0; + } + if (timeoutNanoseconds < 0) { + LOGE("AudioStreamBuffered::%s(): timeoutNanoseconds is negative", __func__); + return (int32_t) Result ::ErrorOutOfRange; + } + int32_t result = 0; - uint8_t *source = (uint8_t *)buffer; + uint8_t *data = (uint8_t *)buffer; int32_t framesLeft = numFrames; - while(framesLeft > 0 && result >= 0) { - result = mFifoBuffer->write(source, numFrames); - LOGD("AudioStreamBuffered::writeNow(): wrote %d/%d frames", result, numFrames); + int64_t timeToQuit = 0; + bool repeat = true; + + // Calculate when to timeout. + if (timeoutNanoseconds > 0) { + timeToQuit = AudioClock::getNanoseconds() + timeoutNanoseconds; + } + + // Loop until we get the data, or we have an error, or we timeout. + do { + // read or write + if (getDirection() == Direction::Input) { + result = mFifoBuffer->read(data, framesLeft); + } else { + result = mFifoBuffer->write(data, framesLeft); + } if (result > 0) { - source += mFifoBuffer->convertFramesToBytes(result); - incrementFramesWritten(result); + data += mFifoBuffer->convertFramesToBytes(result); framesLeft -= result; } - if (framesLeft > 0 && result >= 0) { - int64_t wakeTimeNanos = mFifoBuffer->getNextReadTime(getSampleRate()); - // TODO use timeoutNanoseconds - AudioClock::sleepUntilNanoTime(wakeTimeNanos); + + // If we need more data then sleep and try again. + if (framesLeft > 0 && result >= 0 && timeoutNanoseconds > 0) { + int64_t timeNow = AudioClock::getNanoseconds(); + if (timeNow >= timeToQuit) { + LOGE("AudioStreamBuffered::%s(): TIMEOUT", __func__); + repeat = false; // TIMEOUT + } else { + // Figure out how long to sleep. + int64_t sleepForNanos; + int64_t wakeTimeNanos = predictNextCallbackTime(); + if (wakeTimeNanos <= 0) { + // No estimate available. Sleep for one burst. + sleepForNanos = (getFramesPerBurst() * kNanosPerSecond) / getSampleRate(); + } else { + // Don't sleep past timeout. + if (wakeTimeNanos > timeToQuit) { + wakeTimeNanos = timeToQuit; + } + sleepForNanos = wakeTimeNanos - timeNow; + // Avoid rapid loop with no sleep. + const int64_t minSleepTime = kNanosPerMillisecond; // arbitrary + if (sleepForNanos < minSleepTime) { + sleepForNanos = minSleepTime; + } + } + + AudioClock::sleepForNanos(sleepForNanos); + } + + } else { + repeat = false; } + } while(repeat); + + return result < 0 ? result : (numFrames - framesLeft); +} + +// Write to the FIFO so the callback can read from it. +int32_t AudioStreamBuffered::write(const void *buffer, + int32_t numFrames, + int64_t timeoutNanoseconds) { + if (getDirection() == Direction::Input) { + return (int32_t) Result::ErrorUnavailable; // TODO review, better error code? } - return result; + return transfer((void *) buffer, numFrames, timeoutNanoseconds); } +// Read data from the FIFO that was written by the callback. +int32_t AudioStreamBuffered::read(void *buffer, + int32_t numFrames, + int64_t timeoutNanoseconds) { + if (getDirection() == Direction::Output) { + return (int32_t) Result::ErrorUnavailable; // TODO review, better error code? + } + return transfer(buffer, numFrames, timeoutNanoseconds); +} + +// Only supported when we are not using a callback. Result AudioStreamBuffered::setBufferSizeInFrames(int32_t requestedFrames) { if (mFifoBuffer != nullptr) { if (requestedFrames > mFifoBuffer->getBufferCapacityInFrames()) { requestedFrames = mFifoBuffer->getBufferCapacityInFrames(); + } else if (requestedFrames < getFramesPerBurst()) { + requestedFrames = getFramesPerBurst(); } mFifoBuffer->setThresholdFrames(requestedFrames); return Result::OK; @@ -89,7 +214,6 @@ Result AudioStreamBuffered::setBufferSizeInFrames(int32_t requestedFrames) } } - int32_t AudioStreamBuffered::getBufferSizeInFrames() const { if (mFifoBuffer != nullptr) { return mFifoBuffer->getThresholdFrames(); @@ -100,7 +224,7 @@ int32_t AudioStreamBuffered::getBufferSizeInFrames() const { int32_t AudioStreamBuffered::getBufferCapacityInFrames() const { if (mFifoBuffer != nullptr) { - return mFifoBuffer->getBufferCapacityInFrames(); // Maybe set mBufferCapacity in constructor + return mFifoBuffer->getBufferCapacityInFrames(); } else { return AudioStream::getBufferCapacityInFrames(); } diff --git a/src/opensles/AudioStreamBuffered.h b/src/opensles/AudioStreamBuffered.h index c4c291448..23b3dbe4b 100644 --- a/src/opensles/AudioStreamBuffered.h +++ b/src/opensles/AudioStreamBuffered.h @@ -17,6 +17,8 @@ #ifndef OBOE_STREAM_BUFFERED_H #define OBOE_STREAM_BUFFERED_H +#include +#include #include "common/OboeDebug.h" #include "oboe/AudioStream.h" #include "oboe/AudioStreamCallback.h" @@ -25,52 +27,64 @@ namespace oboe { // A stream that contains a FIFO buffer. +// This is used to implement blocking reads and writes. class AudioStreamBuffered : public AudioStream { public: AudioStreamBuffered(); explicit AudioStreamBuffered(const AudioStreamBuilder &builder); - Result open() override; + void allocateFifo(); + int32_t write(const void *buffer, int32_t numFrames, int64_t timeoutNanoseconds) override; + int32_t read(void *buffer, + int32_t numFrames, + int64_t timeoutNanoseconds) override; + Result setBufferSizeInFrames(int32_t requestedFrames) override; int32_t getBufferSizeInFrames() const override; int32_t getBufferCapacityInFrames() const override; -protected: + int32_t getXRunCount() const override { + return mXRunCount; + } - class AudioStreamBufferedCallback : public AudioStreamCallback { - public: - AudioStreamBufferedCallback(AudioStreamBuffered *bufferedStream) - : mBufferedStream(bufferedStream) { - } + int64_t getFramesWritten() const override; - virtual ~AudioStreamBufferedCallback() {} + int64_t getFramesRead() const override; - virtual DataCallbackResult onAudioReady( - AudioStream *audioStream, - void *audioData, - int numFrames) { - int32_t framesRead = mBufferedStream->mFifoBuffer->readNow(audioData, numFrames); - //LOGD("AudioStreamBufferedCallback(): read %d / %d frames", framesRead, numFrames); - return (framesRead >= 0) ? DataCallbackResult::Continue : DataCallbackResult::Stop; - } +protected: + + DataCallbackResult onDefaultCallback(void *audioData, int numFrames) override; - virtual void onExit(Result reason) {} - private: - AudioStreamBuffered *mBufferedStream; - }; + // If there is no callback then we need a FIFO between the App and OpenSL ES. + bool usingFIFO() const { return getCallback() == nullptr; } private: - FifoBuffer *mFifoBuffer; - std::unique_ptr mInternalCallback; + + int64_t predictNextCallbackTime(); + + void markCallbackTime(int numFrames); + + // Read or write to the FIFO. + int32_t transfer(void *buffer, int32_t numFrames, int64_t timeoutNanoseconds); + + void incrementXRunCount() { + mXRunCount++; + } + + std::unique_ptr mFifoBuffer; + + int64_t mBackgroundRanAtNanoseconds = 0; + int32_t mLastBackgroundSize = 0; + int32_t mXRunCount = 0; }; } // namespace oboe diff --git a/src/opensles/AudioStreamOpenSLES.cpp b/src/opensles/AudioStreamOpenSLES.cpp index 3830eeb7c..d81c8a401 100644 --- a/src/opensles/AudioStreamOpenSLES.cpp +++ b/src/opensles/AudioStreamOpenSLES.cpp @@ -74,6 +74,7 @@ Result AudioStreamOpenSLES::open() { // API 21+: FLOAT // API <21: INT16 if (mFormat == AudioFormat::Unspecified){ + // TODO use runtime check mFormat = (__ANDROID_API__ < __ANDROID_API_L__) ? AudioFormat::I16 : AudioFormat::Float; } @@ -107,18 +108,21 @@ Result AudioStreamOpenSLES::open() { LOGD("AudioStreamOpenSLES(): mBytesPerCallback = %d", mBytesPerCallback); mSharingMode = SharingMode::Shared; - mBufferCapacityInFrames = mFramesPerBurst * mBurstsPerBuffer; + + if (!usingFIFO()) { + mBufferCapacityInFrames = mFramesPerBurst * kBufferQueueLength; + } return Result::OK; } Result AudioStreamOpenSLES::close() { - if (mObjectInterface != NULL) { + if (mObjectInterface != nullptr) { (*mObjectInterface)->Destroy(mObjectInterface); - mObjectInterface = NULL; + mObjectInterface = nullptr; } - mSimpleBufferQueueInterface = NULL; + mSimpleBufferQueueInterface = nullptr; EngineOpenSLES::getInstance().close(); return Result::OK; } @@ -129,10 +133,10 @@ SLresult AudioStreamOpenSLES::enqueueCallbackBuffer(SLAndroidSimpleBufferQueueIt SLresult AudioStreamOpenSLES::processBufferCallback(SLAndroidSimpleBufferQueueItf bq) { // Ask the callback to fill the output buffer with data. - Result result = fireCallback(mCallbackBuffer, mFramesPerCallback); - if (result != Result::OK) { + DataCallbackResult result = fireCallback(mCallbackBuffer, mFramesPerCallback); + if (result != DataCallbackResult::Continue) { LOGE("Oboe callback returned %d", result); - return SL_RESULT_INTERNAL_ERROR; + return SL_RESULT_INTERNAL_ERROR; // TODO How should we stop OpenSL ES. } else { // Pass the data to OpenSLES. return enqueueCallbackBuffer(bq); diff --git a/src/opensles/AudioStreamOpenSLES.h b/src/opensles/AudioStreamOpenSLES.h index 7056e57b4..a524387d7 100644 --- a/src/opensles/AudioStreamOpenSLES.h +++ b/src/opensles/AudioStreamOpenSLES.h @@ -27,6 +27,7 @@ namespace oboe { constexpr int kBitsPerByte = 8; +constexpr int kBufferQueueLength = 2; // double buffered for callbacks /** * INTERNAL USE ONLY