Refactor AudioStream methods to handle closed state
diff --git a/include/oboe/AudioStream.h b/include/oboe/AudioStream.h
index 46a496e..cb54a1c 100644
--- a/include/oboe/AudioStream.h
+++ b/include/oboe/AudioStream.h
@@ -140,11 +140,16 @@
}
/**
+ * @return true if XRun counts are supported on the stream
+ */
+ virtual bool isXRunCountSupported() const = 0;
+
+ /**
* Query the number of frames that are read or written by the endpoint at one time.
*
* @return burst size
*/
- virtual int32_t getFramesPerBurst() const = 0;
+ virtual int32_t getFramesPerBurst() = 0;
bool isPlaying();
@@ -156,9 +161,9 @@
* This monotonic counter will never get reset.
* @return the number of frames written so far
*/
- virtual int64_t getFramesWritten() const { return mFramesWritten; }
+ virtual int64_t getFramesWritten() { return mFramesWritten; }
- virtual int64_t getFramesRead() const { return mFramesRead; }
+ virtual int64_t getFramesRead() { return mFramesRead; }
/**
* Calculate the latency of a stream based on getTimestamp().
@@ -282,10 +287,12 @@
// These do not change after open.
AudioFormat mNativeFormat = AudioFormat::Invalid;
-private:
// TODO these should be atomic like in AAudio
int64_t mFramesWritten = 0;
int64_t mFramesRead = 0;
+
+private:
+ // TODO these should be atomic like in AAudio
int mPreviousScheduler = -1;
};
diff --git a/include/oboe/AudioStreamBase.h b/include/oboe/AudioStreamBase.h
index 230aadb..9b38fa5 100644
--- a/include/oboe/AudioStreamBase.h
+++ b/include/oboe/AudioStreamBase.h
@@ -71,13 +71,11 @@
/**
* Query the maximum number of frames that can be filled without blocking.
+ * If the stream has been closed the last known value will be returned.
*
- * @return buffer size or a negative error.
+ * @return buffer size
*/
- virtual int32_t getBufferSizeInFrames() const {
- // By default assume the effective size is the same as capacity.
- return getBufferCapacityInFrames();
- }
+ virtual int32_t getBufferSizeInFrames() { return mBufferSizeInFrames; };
/**
* @return capacityInFrames or OBOE_UNSPECIFIED
@@ -109,6 +107,8 @@
int32_t mSampleRate = kUnspecified;
int32_t mDeviceId = kUnspecified;
int32_t mBufferCapacityInFrames = kUnspecified;
+ int32_t mBufferSizeInFrames = kUnspecified;
+ int32_t mFramesPerBurst = kUnspecified;
SharingMode mSharingMode = SharingMode::Shared;
AudioFormat mFormat = AudioFormat::Unspecified;
diff --git a/include/oboe/Definitions.h b/include/oboe/Definitions.h
index 7724f5d..1637981 100644
--- a/include/oboe/Definitions.h
+++ b/include/oboe/Definitions.h
@@ -96,6 +96,7 @@
ErrorOutOfRange = AAUDIO_ERROR_OUT_OF_RANGE,
ErrorNoService = AAUDIO_ERROR_NO_SERVICE,
ErrorInvalidRate = AAUDIO_ERROR_INVALID_RATE,
+ ErrorClosed,
};
enum class SharingMode : aaudio_sharing_mode_t {
diff --git a/samples/hello-oboe/src/main/cpp/PlayAudioEngine.cpp b/samples/hello-oboe/src/main/cpp/PlayAudioEngine.cpp
index 3dd58c1..1647380 100644
--- a/samples/hello-oboe/src/main/cpp/PlayAudioEngine.cpp
+++ b/samples/hello-oboe/src/main/cpp/PlayAudioEngine.cpp
@@ -163,8 +163,8 @@
if (mBufferSizeSelection == kBufferSizeAutomatic) {
mLatencyTuner->tune();
} else if (bufferSize != (mBufferSizeSelection * mFramesPerBurst)) {
- audioStream->setBufferSizeInFrames(mBufferSizeSelection * mFramesPerBurst);
- bufferSize = audioStream->getBufferSizeInFrames();
+ auto setBufferResult = audioStream->setBufferSizeInFrames(mBufferSizeSelection * mFramesPerBurst);
+ if (setBufferResult == oboe::Result::OK) bufferSize = setBufferResult.value();
}
/**
diff --git a/src/aaudio/AudioStreamAAudio.cpp b/src/aaudio/AudioStreamAAudio.cpp
index dd29f9b..8ea76ff 100644
--- a/src/aaudio/AudioStreamAAudio.cpp
+++ b/src/aaudio/AudioStreamAAudio.cpp
@@ -164,7 +164,7 @@
goto error2;
}
- // Query and cache the values that will not change.
+ // Query and cache the stream properties
mDeviceId = mLibLoader->stream_getDeviceId(mAAudioStream);
mChannelCount = mLibLoader->stream_getChannelCount(mAAudioStream);
mSampleRate = mLibLoader->stream_getSampleRate(mAAudioStream);
@@ -176,6 +176,8 @@
mPerformanceMode = static_cast<PerformanceMode>(
mLibLoader->stream_getPerformanceMode(mAAudioStream));
mBufferCapacityInFrames = mLibLoader->stream_getBufferCapacity(mAAudioStream);
+ mBufferSizeInFrames = mLibLoader->stream_getBufferSize(mAAudioStream);
+
// These were added in P so we have to check for the function pointer.
if (mLibLoader->stream_getUsage != nullptr) {
@@ -213,13 +215,14 @@
// is being executed because of a disconnect. The close will delete the stream,
// which could otherwise cause the requestStop() to crash.
std::lock_guard<std::mutex> lock(mLock);
- Result result = Result::OK;
+
// This will delete the AAudio stream object so we need to null out the pointer.
AAudioStream *stream = mAAudioStream.exchange(nullptr);
if (stream != nullptr) {
- result = static_cast<Result>(mLibLoader->stream_close(stream));
+ return static_cast<Result>(mLibLoader->stream_close(stream));
+ } else {
+ return Result::ErrorClosed;
}
- return result;
}
DataCallbackResult AudioStreamAAudio::callOnAudioReady(AAudioStream *stream,
@@ -268,7 +271,7 @@
if (stream != nullptr) {
return static_cast<Result>(mLibLoader->stream_requestStart(stream));
} else {
- return Result::ErrorNull;
+ return Result::ErrorClosed;
}
}
@@ -278,7 +281,7 @@
if (stream != nullptr) {
return static_cast<Result>(mLibLoader->stream_requestPause(stream));
} else {
- return Result::ErrorNull;
+ return Result::ErrorClosed;
}
}
@@ -288,7 +291,7 @@
if (stream != nullptr) {
return static_cast<Result>(mLibLoader->stream_requestFlush(stream));
} else {
- return Result::ErrorNull;
+ return Result::ErrorClosed;
}
}
@@ -298,7 +301,7 @@
if (stream != nullptr) {
return static_cast<Result>(mLibLoader->stream_requestStop(stream));
} else {
- return Result::ErrorNull;
+ return Result::ErrorClosed;
}
}
@@ -343,7 +346,7 @@
*nextState = static_cast<StreamState>(aaudioNextState);
return static_cast<Result>(result);
} else {
- return Result::ErrorNull;
+ return Result::ErrorClosed;
}
}
@@ -352,6 +355,10 @@
requestedFrames = mBufferCapacityInFrames;
}
int32_t newBufferSize = mLibLoader->stream_setBufferSize(mAAudioStream, requestedFrames);
+
+ // Cache the result if it's valid
+ if (newBufferSize > 0) mBufferSizeInFrames = newBufferSize;
+
return ResultWithValue<int32_t>::createBasedOnSign(newBufferSize);
}
@@ -364,40 +371,36 @@
}
}
-int32_t AudioStreamAAudio::getBufferSizeInFrames() const {
+int32_t AudioStreamAAudio::getBufferSizeInFrames() {
AAudioStream *stream = mAAudioStream.load();
if (stream != nullptr) {
- return mLibLoader->stream_getBufferSize(stream);
- } else {
- return static_cast<int32_t>(Result::ErrorNull);
+ mBufferSizeInFrames = mLibLoader->stream_getBufferSize(stream);
}
+ return mBufferSizeInFrames;
}
-int32_t AudioStreamAAudio::getFramesPerBurst() const {
+int32_t AudioStreamAAudio::getFramesPerBurst() {
AAudioStream *stream = mAAudioStream.load();
if (stream != nullptr) {
- return mLibLoader->stream_getFramesPerBurst(stream);
- } else {
- return static_cast<int32_t>(Result::ErrorNull);
+ mFramesPerBurst = mLibLoader->stream_getFramesPerBurst(stream);
}
+ return mFramesPerBurst;
}
-int64_t AudioStreamAAudio::getFramesRead() const {
+int64_t AudioStreamAAudio::getFramesRead() {
AAudioStream *stream = mAAudioStream.load();
if (stream != nullptr) {
- return mLibLoader->stream_getFramesRead(stream);
- } else {
- return static_cast<int32_t>(Result::ErrorNull);
+ mFramesRead = mLibLoader->stream_getFramesRead(stream);
}
+ return mFramesRead;
}
-int64_t AudioStreamAAudio::getFramesWritten() const {
+int64_t AudioStreamAAudio::getFramesWritten() {
AAudioStream *stream = mAAudioStream.load();
if (stream != nullptr) {
- return mLibLoader->stream_getFramesWritten(stream);
- } else {
- return static_cast<int64_t>(Result::ErrorNull);
+ mFramesWritten = mLibLoader->stream_getFramesWritten(stream);
}
+ return mFramesWritten;
}
ResultWithValue<int32_t> AudioStreamAAudio::getXRunCount() const {
diff --git a/src/aaudio/AudioStreamAAudio.h b/src/aaudio/AudioStreamAAudio.h
index 3752e91..5f5aeff 100644
--- a/src/aaudio/AudioStreamAAudio.h
+++ b/src/aaudio/AudioStreamAAudio.h
@@ -69,12 +69,13 @@
int64_t timeoutNanoseconds) override;
ResultWithValue<int32_t> setBufferSizeInFrames(int32_t requestedFrames) override;
- int32_t getBufferSizeInFrames() const override;
- int32_t getFramesPerBurst() const override;
+ int32_t getBufferSizeInFrames() override;
+ int32_t getFramesPerBurst() override;
ResultWithValue<int32_t> getXRunCount() const override;
+ bool isXRunCountSupported() const override { return true; }
- int64_t getFramesRead() const override;
- int64_t getFramesWritten() const override;
+ int64_t getFramesRead() override;
+ int64_t getFramesWritten() override;
ResultWithValue<double> calculateLatencyMillis() override;
diff --git a/src/common/AudioStream.cpp b/src/common/AudioStream.cpp
index 1c468dd..42a1f97 100644
--- a/src/common/AudioStream.cpp
+++ b/src/common/AudioStream.cpp
@@ -65,17 +65,21 @@
int64_t timeoutNanoseconds)
{
StreamState state = getState();
- StreamState nextState = state;
- if (state == startingState && state != endingState) {
- Result result = waitForStateChange(state, &nextState, timeoutNanoseconds);
- if (result != Result::OK) {
- return result;
- }
- }
- if (nextState != endingState) {
- return Result::ErrorInvalidState;
+ if (state == StreamState::Closed) {
+ return Result::ErrorClosed;
} else {
- return Result::OK;
+ StreamState nextState = state;
+ if (state == startingState && state != endingState) {
+ Result result = waitForStateChange(state, &nextState, timeoutNanoseconds);
+ if (result != Result::OK) {
+ return result;
+ }
+ }
+ if (nextState != endingState) {
+ return Result::ErrorInvalidState;
+ } else {
+ return Result::OK;
+ }
}
}
diff --git a/src/opensles/AudioInputStreamOpenSLES.cpp b/src/opensles/AudioInputStreamOpenSLES.cpp
index 469f002..424cf8b 100644
--- a/src/opensles/AudioInputStreamOpenSLES.cpp
+++ b/src/opensles/AudioInputStreamOpenSLES.cpp
@@ -182,50 +182,70 @@
}
Result AudioInputStreamOpenSLES::close() {
- requestStop();
- mRecordInterface = NULL;
- return AudioStreamOpenSLES::close();
+
+ if (mState == StreamState::Closed){
+ return Result::ErrorClosed;
+ } else {
+ requestStop();
+ mRecordInterface = NULL;
+ return AudioStreamOpenSLES::close();
+ }
}
Result AudioInputStreamOpenSLES::setRecordState(SLuint32 newState) {
- Result result = Result::OK;
- LOGD("AudioInputStreamOpenSLES::setRecordState(%d)", newState);
- if (mRecordInterface == nullptr) {
- LOGE("AudioInputStreamOpenSLES::SetRecordState() mRecordInterface is null");
- return Result::ErrorInvalidState;
- }
- SLresult slResult = (*mRecordInterface)->SetRecordState(mRecordInterface, newState);
- if (SL_RESULT_SUCCESS != slResult) {
- LOGE("AudioInputStreamOpenSLES::SetRecordState() returned %s", getSLErrStr(slResult));
- result = Result::ErrorInvalidState; // TODO review
+
+ if (mState == StreamState::Closed){
+ return Result::ErrorClosed;
} else {
- setState(StreamState::Pausing);
+ Result result = Result::OK;
+ LOGD("AudioInputStreamOpenSLES::setRecordState(%d)", newState);
+ if (mRecordInterface == nullptr) {
+ LOGE("AudioInputStreamOpenSLES::SetRecordState() mRecordInterface is null");
+ return Result::ErrorInvalidState;
+ }
+ SLresult slResult = (*mRecordInterface)->SetRecordState(mRecordInterface, newState);
+ if (SL_RESULT_SUCCESS != slResult) {
+ LOGE("AudioInputStreamOpenSLES::SetRecordState() returned %s", getSLErrStr(slResult));
+ result = Result::ErrorInvalidState; // TODO review
+ } else {
+ setState(StreamState::Pausing);
+ }
+ return result;
}
- return result;
}
Result AudioInputStreamOpenSLES::requestStart() {
LOGD("AudioInputStreamOpenSLES::requestStart()");
- Result result = setRecordState(SL_RECORDSTATE_RECORDING);
- if (result == Result::OK) {
- // Enqueue the first buffer so that we have data ready in the callback.
- enqueueCallbackBuffer(mSimpleBufferQueueInterface);
- setState(StreamState::Starting);
+
+ if (mState == StreamState::Closed){
+ return Result::ErrorClosed;
+ } else {
+ Result result = setRecordState(SL_RECORDSTATE_RECORDING);
+ if (result == Result::OK) {
+ // Enqueue the first buffer so that we have data ready in the callback.
+ enqueueCallbackBuffer(mSimpleBufferQueueInterface);
+ setState(StreamState::Starting);
+ }
+ return result;
}
- return result;
}
Result AudioInputStreamOpenSLES::requestPause() {
- LOGD("AudioInputStreamOpenSLES::requestStop()");
- Result result = setRecordState(SL_RECORDSTATE_PAUSED);
- if (result != Result::OK) {
- result = Result::ErrorInvalidState; // TODO review
+
+ if (mState == StreamState::Closed){
+ return Result::ErrorClosed;
} else {
- setState(StreamState::Pausing);
- mPositionMillis.reset32(); // OpenSL ES resets its millisecond position when paused.
+ LOGD("AudioInputStreamOpenSLES::requestStop()");
+ Result result = setRecordState(SL_RECORDSTATE_PAUSED);
+ if (result != Result::OK) {
+ result = Result::ErrorInvalidState; // TODO review
+ } else {
+ setState(StreamState::Pausing);
+ mPositionMillis.reset32(); // OpenSL ES resets its millisecond position when paused.
+ }
+ return result;
}
- return result;
}
Result AudioInputStreamOpenSLES::requestFlush() {
@@ -234,17 +254,22 @@
Result AudioInputStreamOpenSLES::requestStop() {
LOGD("AudioInputStreamOpenSLES::requestStop()");
- Result result = setRecordState(SL_RECORDSTATE_STOPPED);
- if (result != Result::OK) {
- result = Result::ErrorInvalidState; // TODO review
+
+ if (mState == StreamState::Closed){
+ return Result::ErrorClosed;
} else {
- setState(StreamState::Stopping);
- mPositionMillis.reset32(); // OpenSL ES resets its millisecond position when stopped.
+ Result result = setRecordState(SL_RECORDSTATE_STOPPED);
+ if (result != Result::OK) {
+ result = Result::ErrorInvalidState; // TODO review
+ } else {
+ setState(StreamState::Stopping);
+ mPositionMillis.reset32(); // OpenSL ES resets its millisecond position when stopped.
+ }
+ return result;
}
- return result;
}
-int64_t AudioInputStreamOpenSLES::getFramesWritten() const {
+int64_t AudioInputStreamOpenSLES::getFramesWritten() {
return getFramesProcessedByServer();
}
@@ -252,7 +277,9 @@
StreamState *nextState,
int64_t timeoutNanoseconds) {
LOGD("AudioInputStreamOpenSLES::waitForStateChange()");
- if (mRecordInterface == NULL) {
+ if (getState() == StreamState::Closed){
+ return Result::ErrorClosed;
+ } else if (mRecordInterface == NULL) {
return Result::ErrorInvalidState;
}
return Result::ErrorUnimplemented; // TODO
diff --git a/src/opensles/AudioInputStreamOpenSLES.h b/src/opensles/AudioInputStreamOpenSLES.h
index 0cd95e4..7410c4b 100644
--- a/src/opensles/AudioInputStreamOpenSLES.h
+++ b/src/opensles/AudioInputStreamOpenSLES.h
@@ -49,7 +49,7 @@
StreamState *nextState,
int64_t timeoutNanoseconds) override;
- int64_t getFramesWritten() const override;
+ int64_t getFramesWritten() override;
protected:
diff --git a/src/opensles/AudioOutputStreamOpenSLES.cpp b/src/opensles/AudioOutputStreamOpenSLES.cpp
index ebeacf9..8316bd8 100644
--- a/src/opensles/AudioOutputStreamOpenSLES.cpp
+++ b/src/opensles/AudioOutputStreamOpenSLES.cpp
@@ -173,78 +173,112 @@
}
Result AudioOutputStreamOpenSLES::close() {
- requestPause();
- // invalidate any interfaces
- mPlayInterface = NULL;
- return AudioStreamOpenSLES::close();
+
+ if (mState == StreamState::Closed){
+ return Result::ErrorClosed;
+ } else {
+ requestPause();
+ // invalidate any interfaces
+ mPlayInterface = NULL;
+ return AudioStreamOpenSLES::close();
+ }
}
Result AudioOutputStreamOpenSLES::setPlayState(SLuint32 newState) {
- Result result = Result::OK;
+
LOGD("AudioOutputStreamOpenSLES(): setPlayState()");
- if (mPlayInterface == NULL) {
- return Result::ErrorInvalidState;
- }
- SLresult slResult = (*mPlayInterface)->SetPlayState(mPlayInterface, newState);
- if(SL_RESULT_SUCCESS != slResult) {
- LOGD("AudioOutputStreamOpenSLES(): setPlayState() returned %s", getSLErrStr(slResult));
- result = Result::ErrorInvalidState; // TODO review
+
+ if (mState == StreamState::Closed){
+ return Result::ErrorClosed;
} else {
- setState(StreamState::Pausing);
+ Result result = Result::OK;
+ if (mPlayInterface == NULL) {
+ return Result::ErrorInvalidState;
+ }
+ SLresult slResult = (*mPlayInterface)->SetPlayState(mPlayInterface, newState);
+ if (SL_RESULT_SUCCESS != slResult) {
+ LOGD("AudioOutputStreamOpenSLES(): setPlayState() returned %s", getSLErrStr(slResult));
+ result = Result::ErrorInvalidState; // TODO review
+ } else {
+ setState(StreamState::Pausing);
+ }
+ return result;
}
- return result;
}
Result AudioOutputStreamOpenSLES::requestStart() {
+
LOGD("AudioOutputStreamOpenSLES(): requestStart()");
- Result result = setPlayState(SL_PLAYSTATE_PLAYING);
- if(result != Result::OK) {
- result = Result::ErrorInvalidState; // TODO review
+
+ if (mState == StreamState::Closed){
+ return Result::ErrorClosed;
} else {
- processBufferCallback(mSimpleBufferQueueInterface);
- setState(StreamState::Starting);
+ Result result = setPlayState(SL_PLAYSTATE_PLAYING);
+ if (result != Result::OK) {
+ result = Result::ErrorInvalidState; // TODO review
+ } else {
+ processBufferCallback(mSimpleBufferQueueInterface);
+ setState(StreamState::Starting);
+ }
+ return result;
}
- return result;
}
Result AudioOutputStreamOpenSLES::requestPause() {
+
LOGD("AudioOutputStreamOpenSLES(): requestPause()");
- Result result = setPlayState(SL_PLAYSTATE_PAUSED);
- if(result != Result::OK) {
- result = Result::ErrorInvalidState; // TODO review
+
+ if (mState == StreamState::Closed){
+ return Result::ErrorClosed;
} else {
- setState(StreamState::Pausing);
- // Note that OpenSL ES does NOT reset its millisecond position when OUTPUT is paused.
- int64_t framesWritten = getFramesWritten();
- if (framesWritten >= 0) {
- setFramesRead(framesWritten);
+ Result result = setPlayState(SL_PLAYSTATE_PAUSED);
+ if (result != Result::OK) {
+ result = Result::ErrorInvalidState; // TODO review
+ } else {
+ setState(StreamState::Pausing);
+ // Note that OpenSL ES does NOT reset its millisecond position when OUTPUT is paused.
+ int64_t framesWritten = getFramesWritten();
+ if (framesWritten >= 0) {
+ setFramesRead(framesWritten);
+ }
}
+ return result;
}
- return result;
}
Result AudioOutputStreamOpenSLES::requestFlush() {
LOGD("AudioOutputStreamOpenSLES(): requestFlush()");
- if (mPlayInterface == NULL) {
- return Result::ErrorInvalidState;
+
+ if (mState == StreamState::Closed){
+ return Result::ErrorClosed;
+ } else {
+ if (mPlayInterface == NULL) {
+ return Result::ErrorInvalidState;
+ }
+ return Result::ErrorUnimplemented; // TODO
}
- return Result::ErrorUnimplemented; // TODO
}
Result AudioOutputStreamOpenSLES::requestStop() {
LOGD("AudioOutputStreamOpenSLES(): requestStop()");
- Result result = setPlayState(SL_PLAYSTATE_STOPPED);
- if(result != Result::OK) {
- result = Result::ErrorInvalidState; // TODO review
+
+ if (mState == StreamState::Closed){
+ return Result::ErrorClosed;
} else {
- setState(StreamState::Stopping);
- mPositionMillis.reset32(); // OpenSL ES resets its millisecond position when stopped.
- int64_t framesWritten = getFramesWritten();
- if (framesWritten >= 0) {
- setFramesRead(framesWritten);
+
+ Result result = setPlayState(SL_PLAYSTATE_STOPPED);
+ if (result != Result::OK) {
+ result = Result::ErrorInvalidState; // TODO review
+ } else {
+ setState(StreamState::Stopping);
+ mPositionMillis.reset32(); // OpenSL ES resets its millisecond position when stopped.
+ int64_t framesWritten = getFramesWritten();
+ if (framesWritten >= 0) {
+ setFramesRead(framesWritten);
+ }
}
+ return result;
}
- return result;
}
void AudioOutputStreamOpenSLES::setFramesRead(int64_t framesRead) {
@@ -252,7 +286,7 @@
mPositionMillis.set(millisWritten);
}
-int64_t AudioOutputStreamOpenSLES::getFramesRead() const {
+int64_t AudioOutputStreamOpenSLES::getFramesRead() {
return getFramesProcessedByServer();
}
@@ -260,7 +294,10 @@
StreamState *nextState,
int64_t timeoutNanoseconds) {
LOGD("AudioOutputStreamOpenSLES::waitForStateChange()");
- if (mPlayInterface == NULL) {
+
+ if (getState() == StreamState::Closed){
+ return Result::ErrorClosed;
+ } else if (mPlayInterface == NULL) {
return Result::ErrorInvalidState;
}
return Result::ErrorUnimplemented; // TODO
diff --git a/src/opensles/AudioOutputStreamOpenSLES.h b/src/opensles/AudioOutputStreamOpenSLES.h
index 58222da..225fbb0 100644
--- a/src/opensles/AudioOutputStreamOpenSLES.h
+++ b/src/opensles/AudioOutputStreamOpenSLES.h
@@ -49,7 +49,7 @@
int64_t timeoutNanoseconds) override;
- int64_t getFramesRead() const override;
+ int64_t getFramesRead() override;
protected:
diff --git a/src/opensles/AudioStreamBuffered.cpp b/src/opensles/AudioStreamBuffered.cpp
index 76358b1..d1b0054 100644
--- a/src/opensles/AudioStreamBuffered.cpp
+++ b/src/opensles/AudioStreamBuffered.cpp
@@ -44,20 +44,18 @@
}
}
-int64_t AudioStreamBuffered::getFramesWritten() const {
+int64_t AudioStreamBuffered::getFramesWritten() {
if (usingFIFO()) {
- return (int64_t) mFifoBuffer->getWriteCounter();
- } else {
- return AudioStream::getFramesWritten();
+ mFramesWritten = (int64_t) mFifoBuffer->getWriteCounter();
}
+ return mFramesWritten;
}
-int64_t AudioStreamBuffered::getFramesRead() const {
+int64_t AudioStreamBuffered::getFramesRead() {
if (usingFIFO()) {
- return (int64_t) mFifoBuffer->getReadCounter();
- } else {
- return AudioStream::getFramesRead();
+ mFramesRead = (int64_t) mFifoBuffer->getReadCounter();
}
+ return mFramesRead;
}
// This is called by the OpenSL ES callback to read or write the back end of the FIFO.
@@ -96,7 +94,7 @@
// Common code for read/write.
// @return Result::OK with frames read/written, or Result::Error*
-ResultWithValue<int32_t> AudioStreamBuffered::transfer(void *buffer,
+ResultWithValue<int32_t> AudioStreamBuffered::transfer(void *buffer,
int32_t numFrames,
int64_t timeoutNanoseconds) {
// Validate arguments.
@@ -173,11 +171,7 @@
}
} while(repeat);
- if (result < 0) {
- return ResultWithValue<int32_t>(static_cast<Result>(result));
- } else {
- return ResultWithValue<int32_t>((numFrames - framesLeft));
- }
+ return ResultWithValue<int32_t>::createBasedOnSign(result);
}
// Write to the FIFO so the callback can read from it.
@@ -218,12 +212,12 @@
}
}
-int32_t AudioStreamBuffered::getBufferSizeInFrames() const {
+int32_t AudioStreamBuffered::getBufferSizeInFrames() {
+
if (mFifoBuffer != nullptr) {
- return mFifoBuffer->getThresholdFrames();
- } else {
- return AudioStream::getBufferSizeInFrames();
+ mBufferSizeInFrames = mFifoBuffer->getThresholdFrames();
}
+ return mBufferSizeInFrames;
}
int32_t AudioStreamBuffered::getBufferCapacityInFrames() const {
diff --git a/src/opensles/AudioStreamBuffered.h b/src/opensles/AudioStreamBuffered.h
index ebb3bc0..6c2effe 100644
--- a/src/opensles/AudioStreamBuffered.h
+++ b/src/opensles/AudioStreamBuffered.h
@@ -47,7 +47,7 @@
ResultWithValue<int32_t> setBufferSizeInFrames(int32_t requestedFrames) override;
- int32_t getBufferSizeInFrames() const override;
+ int32_t getBufferSizeInFrames() override;
int32_t getBufferCapacityInFrames() const override;
@@ -55,9 +55,11 @@
return ResultWithValue<int32_t>(mXRunCount);
}
- int64_t getFramesWritten() const override;
+ bool isXRunCountSupported() const override { return false; }
- int64_t getFramesRead() const override;
+ int64_t getFramesWritten() override;
+
+ int64_t getFramesRead() override;
protected:
diff --git a/src/opensles/AudioStreamOpenSLES.cpp b/src/opensles/AudioStreamOpenSLES.cpp
index 3e7789f..ef96a06 100644
--- a/src/opensles/AudioStreamOpenSLES.cpp
+++ b/src/opensles/AudioStreamOpenSLES.cpp
@@ -176,20 +176,26 @@
}
Result AudioStreamOpenSLES::close() {
- onBeforeDestroy();
- if (mObjectInterface != nullptr) {
- (*mObjectInterface)->Destroy(mObjectInterface);
- mObjectInterface = nullptr;
+ if (mState == StreamState::Closed){
+ return Result::ErrorClosed;
+ } else {
+ onBeforeDestroy();
+ if (mObjectInterface != nullptr) {
+ (*mObjectInterface)->Destroy(mObjectInterface);
+ mObjectInterface = nullptr;
+
+ }
+
+ onAfterDestroy();
+
+ mSimpleBufferQueueInterface = nullptr;
+ EngineOpenSLES::getInstance().close();
+
+ mState = StreamState::Closed;
+ return Result::OK;
}
-
- onAfterDestroy();
-
- mSimpleBufferQueueInterface = nullptr;
- EngineOpenSLES::getInstance().close();
-
- return Result::OK;
}
SLresult AudioStreamOpenSLES::enqueueCallbackBuffer(SLAndroidSimpleBufferQueueItf bq) {
@@ -233,7 +239,7 @@
return result;
}
-int32_t AudioStreamOpenSLES::getFramesPerBurst() const {
+int32_t AudioStreamOpenSLES::getFramesPerBurst() {
return mFramesPerBurst;
}
diff --git a/src/opensles/AudioStreamOpenSLES.h b/src/opensles/AudioStreamOpenSLES.h
index e7dd1d4..b1da5c5 100644
--- a/src/opensles/AudioStreamOpenSLES.h
+++ b/src/opensles/AudioStreamOpenSLES.h
@@ -57,7 +57,7 @@
*/
StreamState getState() override { return mState; }
- int32_t getFramesPerBurst() const override;
+ int32_t getFramesPerBurst() override;
AudioApi getAudioApi() const override {
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index bd9b5cf..70ad965 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -15,5 +15,6 @@
add_executable(testOboe
testUtilities.cpp
testStreamClosedMethods.cpp
+ testXRunBehaviour.cpp
)
target_link_libraries(testOboe gtest oboe)
\ No newline at end of file
diff --git a/tests/run_tests.sh b/tests/run_tests.sh
index c4a8a81..5feab06 100755
--- a/tests/run_tests.sh
+++ b/tests/run_tests.sh
@@ -39,7 +39,9 @@
BUILD_DIR=build
CMAKE=cmake
TEST_BINARY_FILENAME=testOboe
-REMOTE_DIR=/data/local/tmp/oboe
+REMOTE_TEMP_DIR=/data/local/tmp
+TEST_PACKAGE_NAME=com.google.sample.oboe.liveeffect
+REMOTE_DIR=/data/data/${TEST_PACKAGE_NAME}
# Check prerequisites
if [ -z "$ANDROID_NDK" ]; then
@@ -102,9 +104,12 @@
# Push the test binary to the device and run it
-echo "Pushing test binary to device/emulator"
adb shell mkdir -p ${REMOTE_DIR}
-adb push ${BUILD_DIR}/${TEST_BINARY_FILENAME} ${REMOTE_DIR}
+adb push ${BUILD_DIR}/${TEST_BINARY_FILENAME} ${REMOTE_TEMP_DIR}
+echo "Pushed ${BUILD_DIR}/${TEST_BINARY_FILENAME} to ${REMOTE_TEMP_DIR}"
+
+echo "Copying ${TEST_BINARY_FILENAME} from ${REMOTE_TEMP_DIR} to ${REMOTE_DIR}"
+adb shell run-as ${TEST_PACKAGE_NAME} cp ${REMOTE_TEMP_DIR}/${TEST_BINARY_FILENAME} ${REMOTE_DIR}
echo "Running test binary"
-adb shell ${REMOTE_DIR}/${TEST_BINARY_FILENAME}
\ No newline at end of file
+adb shell run-as ${TEST_PACKAGE_NAME} ${REMOTE_DIR}/${TEST_BINARY_FILENAME}
\ No newline at end of file
diff --git a/tests/testStreamClosedMethods.cpp b/tests/testStreamClosedMethods.cpp
index 6e88ad1..e148ee5 100644
--- a/tests/testStreamClosedMethods.cpp
+++ b/tests/testStreamClosedMethods.cpp
@@ -14,74 +14,308 @@
* limitations under the License.
*/
+/*
+ * TODO:
+ Create test runner APK
+ Remove hardcoded ANDROID_NDK from cmake config
+ */
#include <gtest/gtest.h>
-#include <oboe/Definitions.h>
-#include <oboe/Utilities.h>
-
-/**
- * Tests needing to be written:
- *
- * getChannelCount
- * getDirection
-getSampleRate
-getFramesPerCallback
-getFormat
-getBufferSizeInFrames
-getBufferCapacityInFrames
-getSharingMode
-getPerformanceMode
-getDeviceId
-getCallback
-getUsage
-getContentType
-getInputPreset
-getSessionId
-getState
-getXRunCount
-getFramesPerBurst
-isPlaying
-getBytesPerFrame
-getBytesPerSample
-getFramesWritten
-getFramesRead
-getTimestamp
-getAudioApi
-usesAAudio
-
-Stream state control
-open
-close
-start
-pause
-flush
-stop
-requestStart
-requestPause
-requestFlush
-requestStop
-waitForStateChange
-
-Setters
-setBufferSizeInFrames
-
-Helpers
-calculateLatencyMillis
-
-I/O
-write
-read
- */
+#include <oboe/Oboe.h>
using namespace oboe;
+class MyCallback : public AudioStreamCallback {
+public:
+ DataCallbackResult onAudioReady(AudioStream *oboeStream, void *audioData, int32_t numFrames) override {
+ return DataCallbackResult::Continue;
+ }
+};
+
+
+
class StreamClosedReturnValues : public ::testing::Test {
+protected:
+ void SetUp(){
+
+ }
+
+ bool openStream(){
+ Result r = mBuilder.openStream(&mStream);
+ EXPECT_EQ(r, Result::OK) << "Failed to open stream " << convertToText(r);
+ return (r == Result::OK);
+ }
+
+ void closeStream(){
+ Result r = mStream->close();
+ if (r != Result::OK){
+ FAIL() << "Failed to close stream. " << convertToText(r);
+ }
+ }
+
+ void openAndCloseStream(){
+
+ openStream();
+ closeStream();
+ ASSERT_EQ(mStream->getState(), StreamState::Closed) << "Stream state " << convertToText(mStream->getState());
+ }
+
+ AudioStreamBuilder mBuilder;
+ AudioStream *mStream = nullptr;
};
TEST_F(StreamClosedReturnValues, GetChannelCountReturnsLastKnownValue){
- FAIL();
-}
\ No newline at end of file
+ mBuilder.setChannelCount(2);
+ openAndCloseStream();
+ ASSERT_EQ(mStream->getChannelCount(), 2);
+}
+
+TEST_F(StreamClosedReturnValues, GetDirectionReturnsLastKnownValue){
+
+ // Note that when testing on the emulator setting the direction to Input will result in ErrorInternal when
+ // opening the stream
+ mBuilder.setDirection(Direction::Input);
+ openAndCloseStream();
+ ASSERT_EQ(mStream->getDirection(), Direction::Input);
+}
+
+TEST_F(StreamClosedReturnValues, GetSampleRateReturnsLastKnownValue){
+
+ mBuilder.setSampleRate(8000);
+ openAndCloseStream();
+ ASSERT_EQ(mStream->getSampleRate(), 8000);
+}
+
+TEST_F(StreamClosedReturnValues, GetFramesPerCallbackReturnsLastKnownValue) {
+
+ mBuilder.setFramesPerCallback(192);
+ openAndCloseStream();
+ ASSERT_EQ(mStream->getFramesPerCallback(), 192);
+}
+
+TEST_F(StreamClosedReturnValues, GetFormatReturnsLastKnownValue) {
+
+ mBuilder.setFormat(AudioFormat::I16);
+ openAndCloseStream();
+ ASSERT_EQ(mStream->getFormat(), AudioFormat::I16);
+}
+
+TEST_F(StreamClosedReturnValues, GetBufferSizeInFramesReturnsLastKnownValue) {
+
+ openStream();
+ int32_t bufferSize = mStream->getBufferSizeInFrames();
+ closeStream();
+ ASSERT_EQ(mStream->getBufferSizeInFrames(), bufferSize);
+}
+
+TEST_F(StreamClosedReturnValues, GetBufferCapacityInFramesReturnsLastKnownValue) {
+
+ openStream();
+ int32_t bufferCapacity = mStream->getBufferCapacityInFrames();
+ closeStream();
+ ASSERT_EQ(mStream->getBufferCapacityInFrames(), bufferCapacity);
+}
+
+TEST_F(StreamClosedReturnValues, GetSharingModeReturnsLastKnownValue) {
+
+ openStream();
+ SharingMode s = mStream->getSharingMode();
+ closeStream();
+ ASSERT_EQ(mStream->getSharingMode(), s);
+}
+
+TEST_F(StreamClosedReturnValues, GetPerformanceModeReturnsLastKnownValue) {
+
+ openStream();
+ PerformanceMode p = mStream->getPerformanceMode();
+ closeStream();
+ ASSERT_EQ(mStream->getPerformanceMode(), p);
+}
+
+TEST_F(StreamClosedReturnValues, GetDeviceIdReturnsLastKnownValue) {
+
+ openStream();
+ int32_t d = mStream->getDeviceId();
+ closeStream();
+ ASSERT_EQ(mStream->getDeviceId(), d);
+}
+
+TEST_F(StreamClosedReturnValues, GetCallbackReturnsLastKnownValue) {
+
+ AudioStreamCallback *callback = new MyCallback();
+ mBuilder.setCallback(callback);
+ openAndCloseStream();
+
+ AudioStreamCallback *callback2 = mStream->getCallback();
+ ASSERT_EQ(callback, callback2);
+}
+
+TEST_F(StreamClosedReturnValues, GetUsageReturnsLastKnownValue){
+ openStream();
+ Usage u = mStream->getUsage();
+ closeStream();
+ ASSERT_EQ(mStream->getUsage(), u);
+}
+
+TEST_F(StreamClosedReturnValues, GetContentTypeReturnsLastKnownValue){
+ openStream();
+ ContentType c = mStream->getContentType();
+ closeStream();
+ ASSERT_EQ(mStream->getContentType(), c);
+}
+
+TEST_F(StreamClosedReturnValues, GetInputPresetReturnsLastKnownValue){
+ openStream();
+ auto i = mStream->getInputPreset();
+ closeStream();
+ ASSERT_EQ(mStream->getInputPreset(), i);
+}
+
+TEST_F(StreamClosedReturnValues, GetSessionIdReturnsLastKnownValue){
+ openStream();
+ auto s = mStream->getSessionId();
+ closeStream();
+ ASSERT_EQ(mStream->getSessionId(), s);
+}
+
+TEST_F(StreamClosedReturnValues, StreamStateIsClosed){
+ openAndCloseStream();
+ ASSERT_EQ(mStream->getState(), StreamState::Closed);
+}
+
+TEST_F(StreamClosedReturnValues, GetXRunCountReturnsLastKnownValue){
+
+ openStream();
+ if (mStream->isXRunCountSupported()){
+ auto i = mStream->getXRunCount();
+ ASSERT_EQ(mStream->getXRunCount(), i);
+ }
+ closeStream();
+}
+
+TEST_F(StreamClosedReturnValues, GetFramesPerBurstReturnsLastKnownValue){
+
+ openStream();
+ auto f = mStream->getFramesPerBurst();
+ closeStream();
+ ASSERT_EQ(mStream->getFramesPerBurst(), f);
+}
+
+TEST_F(StreamClosedReturnValues, IsPlayingReturnsFalse){
+
+ openAndCloseStream();
+ ASSERT_FALSE(mStream->isPlaying());
+}
+
+TEST_F(StreamClosedReturnValues, GetBytesPerFrameReturnsLastKnownValue){
+ openStream();
+ auto f = mStream->getBytesPerFrame();
+ closeStream();
+ ASSERT_EQ(mStream->getBytesPerFrame(), f);
+}
+
+TEST_F(StreamClosedReturnValues, GetBytesPerSampleReturnsLastKnownValue){
+ openStream();
+ auto f = mStream->getBytesPerSample();
+ closeStream();
+ ASSERT_EQ(mStream->getBytesPerSample(), f);
+}
+
+TEST_F(StreamClosedReturnValues, GetFramesWrittenReturnsLastKnownValue){
+ mBuilder.setFormat(AudioFormat::I16);
+ mBuilder.setChannelCount(1);
+ openStream();
+ mStream->start();
+
+ int16_t buffer[4] = { 1, 2, 3, 4 };
+ Result r = mStream->write(&buffer, 4, 0);
+ if (r != Result::OK){
+ FAIL() << "Could not write to audio stream";
+ }
+
+ auto f = mStream->getFramesWritten();
+ ASSERT_EQ(f, 4);
+
+ closeStream();
+ ASSERT_EQ(mStream->getFramesWritten(), f);
+}
+
+// TODO: Reading a positive value doesn't work on OpenSL ES in this test - why?
+TEST_F(StreamClosedReturnValues, GetFramesReadReturnsLastKnownValue) {
+
+ mBuilder.setDirection(Direction::Input);
+ mBuilder.setFormat(AudioFormat::I16);
+ mBuilder.setChannelCount(1);
+
+ if (openStream()){
+ mStream->start();
+
+/*
+ int16_t buffer[192];
+ auto r = mStream->read(&buffer, 192, 0);
+ ASSERT_EQ(r.value(), 192);
+*/
+
+ auto f = mStream->getFramesRead();
+// ASSERT_EQ(f, 192);
+
+ closeStream();
+ ASSERT_EQ(mStream->getFramesRead(), f);
+ };
+}
+
+TEST_F(StreamClosedReturnValues, GetTimestampReturnsErrorClosedIfSupported){
+
+ openStream();
+
+ int64_t framePosition;
+ int64_t presentationTime;
+
+ auto r = mStream->getTimestamp(CLOCK_MONOTONIC, &framePosition, &presentationTime);
+ bool isTimestampSupported = (r == Result::OK);
+
+ closeStream();
+
+ if (isTimestampSupported){
+ ASSERT_EQ(mStream->getTimestamp(CLOCK_MONOTONIC, &framePosition, &presentationTime), Result::ErrorClosed);
+ }
+}
+
+TEST_F(StreamClosedReturnValues, GetAudioApiReturnsLastKnownValue){
+ openStream();
+ AudioApi a = mStream->getAudioApi();
+ closeStream();
+ ASSERT_EQ(mStream->getAudioApi(), a);
+}
+
+TEST_F(StreamClosedReturnValues, GetUsesAAudioReturnsLastKnownValue){
+ openStream();
+ bool a = mStream->usesAAudio();
+ closeStream();
+ ASSERT_EQ(mStream->usesAAudio(), a);
+}
+
+TEST_F(StreamClosedReturnValues, StreamStateControlsReturnClosed){
+
+ openAndCloseStream();
+ EXPECT_EQ(mStream->close(), Result::ErrorClosed);
+ EXPECT_EQ(mStream->start(), Result::ErrorClosed);
+ EXPECT_EQ(mStream->pause(), Result::ErrorClosed);
+ EXPECT_EQ(mStream->flush(), Result::ErrorClosed);
+ EXPECT_EQ(mStream->stop(), Result::ErrorClosed);
+ EXPECT_EQ(mStream->requestStart(), Result::ErrorClosed);
+ EXPECT_EQ(mStream->requestPause(), Result::ErrorClosed);
+ EXPECT_EQ(mStream->requestFlush(), Result::ErrorClosed);
+ EXPECT_EQ(mStream->requestStop(), Result::ErrorClosed);
+}
+
+TEST_F(StreamClosedReturnValues, WaitForStateChangeReturnsClosed){
+
+ openAndCloseStream();
+ StreamState next;
+ ASSERT_EQ(mStream->waitForStateChange(StreamState::Open, &next, 0), Result::ErrorClosed);
+}
diff --git a/tests/testXRunBehaviour.cpp b/tests/testXRunBehaviour.cpp
new file mode 100644
index 0000000..472a5a6
--- /dev/null
+++ b/tests/testXRunBehaviour.cpp
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#include <gtest/gtest.h>
+#include <oboe/Oboe.h>
+
+using namespace oboe;
+
+class XRunBehaviour : public ::testing::Test {
+
+protected:
+
+ void SetUp(){
+
+ }
+
+
+ void openStream(){
+ Result r = mBuilder.openStream(&mStream);
+ if (r != Result::OK){
+ FAIL() << "Failed to open stream. " << convertToText(r);
+ }
+ }
+
+ void closeStream(){
+ Result r = mStream->close();
+ if (r != Result::OK){
+ FAIL() << "Failed to close stream. " << convertToText(r);
+ }
+ }
+
+ void openAndCloseStream(){
+
+ openStream();
+ closeStream();
+ ASSERT_EQ(mStream->getState(), StreamState::Closed);
+ }
+
+ AudioStreamBuilder mBuilder;
+ AudioStream *mStream = nullptr;
+
+};
+
+// TODO figure out this behaviour - On OpenSLES xRuns are supported withing AudioStreamBuffered, however, these aren't
+// the same as the actual stream underruns
+TEST_F(XRunBehaviour, SupportedWhenStreamIsUsingAAudio){
+
+ openStream();
+ if (mStream->getAudioApi() == AudioApi::AAudio){
+ ASSERT_TRUE(mStream->isXRunCountSupported());
+ }
+}
+
+TEST_F(XRunBehaviour, NotSupportedWhenStreamIsUsingOpenSLES){
+
+ openStream();
+ if (mStream->getAudioApi() == AudioApi::OpenSLES){
+ ASSERT_FALSE(mStream->isXRunCountSupported());
+ }
+}
\ No newline at end of file