oboe: lock waitForStateChange for AAudio
Call AAudio with timeout zero to avoid blocking.
Prevent crashes if close() called from another thread.
Possible fix for #406
diff --git a/src/aaudio/AudioStreamAAudio.cpp b/src/aaudio/AudioStreamAAudio.cpp
index b8c4394..af43218 100644
--- a/src/aaudio/AudioStreamAAudio.cpp
+++ b/src/aaudio/AudioStreamAAudio.cpp
@@ -20,6 +20,7 @@
#include "aaudio/AAudioLoader.h"
#include "aaudio/AudioStreamAAudio.h"
+#include "common/AudioClock.h"
#include "common/OboeDebug.h"
#include "oboe/Utilities.h"
@@ -370,25 +371,75 @@
}
}
+
+// AAudioStream_waitForStateChange() can crash if it is waiting on a stream and that stream
+// is closed from another thread. We do not want to lock the stream for the duration of the call.
+// So we call AAudioStream_waitForStateChange() with a timeout of zero so that it will not block.
+// Then we can do our own sleep with the lock unlocked.
Result AudioStreamAAudio::waitForStateChange(StreamState currentState,
StreamState *nextState,
int64_t timeoutNanoseconds) {
- AAudioStream *stream = mAAudioStream.load();
- if (stream != nullptr) {
+ Result oboeResult = (timeoutNanoseconds <= 0) ? Result::OK : Result::ErrorTimeout;
+ int64_t durationNanos = 20 * kNanosPerMillisecond; // arbitrary
+ aaudio_stream_state_t currentAAudioState = static_cast<aaudio_stream_state_t>(currentState);
- aaudio_stream_state_t aaudioNextState;
- aaudio_result_t result = mLibLoader->stream_waitForStateChange(
- mAAudioStream,
- static_cast<aaudio_stream_state_t>(currentState),
- &aaudioNextState,
- timeoutNanoseconds);
- *nextState = static_cast<StreamState>(aaudioNextState);
- return static_cast<Result>(result);
- } else {
- *nextState = StreamState::Closed;
+ aaudio_stream_state_t aaudioNextState;
+ aaudio_result_t result = AAUDIO_OK;
+ int64_t timeLeftNanos = timeoutNanoseconds;
+
+ mLock.lock();
+ while (true) {
+ // Do we still have an AAudio stream? If not then stream must have been closed.
+ AAudioStream *stream = mAAudioStream.load();
+ if (stream == nullptr) {
+ if (nextState != nullptr) {
+ *nextState = StreamState::Closed;
+ }
+ oboeResult = Result::ErrorClosed;
+ break;
+ }
+
+ // Update and query state change with no blocking.
+ result = mLibLoader->stream_waitForStateChange(
+ mAAudioStream,
+ currentAAudioState,
+ &aaudioNextState,
+ 0); // timeout=0 for non-blocking
+ if (nextState != nullptr) {
+ *nextState = static_cast<StreamState>(aaudioNextState);
+ }
+ if (result != AAUDIO_OK) {
+ if (result == AAUDIO_ERROR_TIMEOUT) {
+ // bug in AAudio pre-Q caused it to return this
+ result = AAUDIO_OK;
+ } else {
+ LOGD("%s() stream_waitForStateChange returned %d", __func__, result);
+ oboeResult = static_cast<Result>(result);
+ break;
+ }
+ }
+ if (currentAAudioState != aaudioNextState) { // state changed?
+ oboeResult = Result::OK;
+ break;
+ }
+
+ // Did we timeout or did user ask for non-blocking?
+ if (timeLeftNanos <= 0) {
+ break;
+ }
+
+ // No change yet so sleep.
+ mLock.unlock(); // Don't sleep while locked.
+ if (durationNanos > timeLeftNanos) {
+ durationNanos = timeLeftNanos; // last little bit
+ }
+ AudioClock::sleepForNanos(durationNanos);
+ timeLeftNanos -= durationNanos;
+ mLock.lock();
}
- return (currentState != *nextState) ? Result::OK : Result::ErrorTimeout;
+ mLock.unlock();
+ return oboeResult;
}
ResultWithValue<int32_t> AudioStreamAAudio::setBufferSizeInFrames(int32_t requestedFrames) {
diff --git a/src/common/AudioStream.cpp b/src/common/AudioStream.cpp
index 5dfafb1..63f3c44 100644
--- a/src/common/AudioStream.cpp
+++ b/src/common/AudioStream.cpp
@@ -75,9 +75,13 @@
StreamState endingState,
int64_t timeoutNanoseconds)
{
- StreamState state = getState();
- if (state == StreamState::Closed) {
- return Result::ErrorClosed;
+ StreamState state;
+ {
+ std::lock_guard<std::mutex> lock(mLock);
+ state = getState();
+ if (state == StreamState::Closed || state == StreamState::Disconnected) {
+ return Result::ErrorClosed;
+ }
}
StreamState nextState = state;
@@ -87,6 +91,7 @@
return result;
}
}
+
if (nextState != endingState) {
return Result::ErrorInvalidState;
} else {
@@ -98,6 +103,7 @@
{
Result result = requestStart();
if (result != Result::OK) return result;
+ if (timeoutNanoseconds <= 0) return result;
return waitForStateTransition(StreamState::Starting,
StreamState::Started, timeoutNanoseconds);
}
@@ -106,6 +112,7 @@
{
Result result = requestPause();
if (result != Result::OK) return result;
+ if (timeoutNanoseconds <= 0) return result;
return waitForStateTransition(StreamState::Pausing,
StreamState::Paused, timeoutNanoseconds);
}
@@ -114,6 +121,7 @@
{
Result result = requestFlush();
if (result != Result::OK) return result;
+ if (timeoutNanoseconds <= 0) return result;
return waitForStateTransition(StreamState::Flushing,
StreamState::Flushed, timeoutNanoseconds);
}
@@ -122,6 +130,7 @@
{
Result result = requestStop();
if (result != Result::OK) return result;
+ if (timeoutNanoseconds <= 0) return result;
return waitForStateTransition(StreamState::Stopping,
StreamState::Stopped, timeoutNanoseconds);
}