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