From 2423c514716f0202b098e20cb682f401b3c141fe Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Wed, 13 Oct 2021 09:33:51 -0700
Subject: [PATCH] Work around hang in AAudioStream_write() during extended
shared object loading while running in a debugger. Observed on a OnePlus 8T
(KB2005) running Oxygen OS 11.0.10.10.KB05AA. The observed behavior is that
any nonzero timeout value would hang until the device was paused and resumed.
And a zero timeout value would always return 0 frames written even when audio
fragments could be heard. Making a manual timeout system unworkable. None of
the straightforward systems imply that there's a detectable problem before
the call to AAudioStream_write(). And the callback set within
AAudioStreamBuilder_setErrorCallback() does not get called as we enter the
hang state. I've found that AAudioStream_getTimestamp() will report an error
state from another thread. So this change codifies that behavior a bit until
a better fix or more root cause can be found.
---
src/audio/aaudio/SDL_aaudio.c | 35 +++++++++++++++++++++++++++
src/audio/aaudio/SDL_aaudio.h | 1 +
src/audio/aaudio/SDL_aaudiofuncs.h | 8 +++---
src/video/android/SDL_androidevents.c | 12 +++++++++
4 files changed, 52 insertions(+), 4 deletions(-)
diff --git a/src/audio/aaudio/SDL_aaudio.c b/src/audio/aaudio/SDL_aaudio.c
index b9cad6b6c1..91bd114706 100644
--- a/src/audio/aaudio/SDL_aaudio.c
+++ b/src/audio/aaudio/SDL_aaudio.c
@@ -62,6 +62,12 @@ static int aaudio_LoadFunctions(AAUDIO_Data *data)
return 0;
}
+void aaudio_errorCallback( AAudioStream *stream, void *userData, aaudio_result_t error );
+void aaudio_errorCallback( AAudioStream *stream, void *userData, aaudio_result_t error )
+{
+ LOGI( "SDL aaudio_errorCallback: %d - %s", error, ctx.AAudio_convertResultToText( error ) );
+}
+
#define LIB_AAUDIO_SO "libaaudio.so"
static int
@@ -109,6 +115,8 @@ aaudio_OpenDevice(_THIS, void *handle, const char *devname, int iscapture)
ctx.AAudioStreamBuilder_setFormat(ctx.builder, format);
}
+ ctx.AAudioStreamBuilder_setErrorCallback( ctx.builder, aaudio_errorCallback, private );
+
LOGI("AAudio Try to open %u hz %u bit chan %u %s samples %u",
this->spec.freq, SDL_AUDIO_BITSIZE(this->spec.format),
this->spec.channels, (this->spec.format & 0x1000) ? "BE" : "LE", this->spec.samples);
@@ -412,6 +420,33 @@ void aaudio_ResumeDevices(void)
}
}
+/*
+ We can sometimes get into a state where AAudioStream_write() will just block forever until we pause and unpause.
+ None of the standard state queries indicate any problem in my testing. And the error callback doesn't actually get called.
+ But, AAudioStream_getTimestamp() does return AAUDIO_ERROR_INVALID_STATE
+*/
+SDL_bool aaudio_DetectBrokenPlayState( void )
+{
+ if ( !audioDevice || !audioDevice->hidden ) {
+ return SDL_FALSE;
+ }
+
+ struct SDL_PrivateAudioData *private = audioDevice->hidden;
+
+ int64_t framePosition, timeNanoseconds;
+ aaudio_result_t res = ctx.AAudioStream_getTimestamp( private->stream, CLOCK_MONOTONIC, &framePosition, &timeNanoseconds );
+ if ( res == AAUDIO_ERROR_INVALID_STATE ) {
+ aaudio_stream_state_t currentState = ctx.AAudioStream_getState( private->stream );
+ /* AAudioStream_getTimestamp() will also return AAUDIO_ERROR_INVALID_STATE while the stream is still initially starting. But we only care if it silently went invalid while playing. */
+ if ( currentState == AAUDIO_STREAM_STATE_STARTED ) {
+ LOGI( "SDL aaudio_DetectBrokenPlayState: detected invalid audio device state: AAudioStream_getTimestamp result=%d, framePosition=%lld, timeNanoseconds=%lld, getState=%d", (int)res, (long long)framePosition, (long long)timeNanoseconds, (int)currentState );
+ return SDL_TRUE;
+ }
+ }
+
+ return SDL_FALSE;
+}
+
#endif /* SDL_AUDIO_DRIVER_AAUDIO */
/* vi: set ts=4 sw=4 expandtab: */
diff --git a/src/audio/aaudio/SDL_aaudio.h b/src/audio/aaudio/SDL_aaudio.h
index 34c2f2151f..2b16fb1ae2 100644
--- a/src/audio/aaudio/SDL_aaudio.h
+++ b/src/audio/aaudio/SDL_aaudio.h
@@ -44,6 +44,7 @@ struct SDL_PrivateAudioData
void aaudio_ResumeDevices(void);
void aaudio_PauseDevices(void);
+SDL_bool aaudio_DetectBrokenPlayState(void);
#endif /* _SDL_aaudio_h */
diff --git a/src/audio/aaudio/SDL_aaudiofuncs.h b/src/audio/aaudio/SDL_aaudiofuncs.h
index a563d18e4f..d482d00c68 100644
--- a/src/audio/aaudio/SDL_aaudiofuncs.h
+++ b/src/audio/aaudio/SDL_aaudiofuncs.h
@@ -22,7 +22,7 @@
#define SDL_PROC_UNUSED(ret,func,params)
SDL_PROC(const char *, AAudio_convertResultToText, (aaudio_result_t returnCode))
-SDL_PROC_UNUSED(const char *, AAudio_convertStreamStateToText, (aaudio_stream_state_t state))
+SDL_PROC(const char *, AAudio_convertStreamStateToText, (aaudio_stream_state_t state))
SDL_PROC(aaudio_result_t, AAudio_createStreamBuilder, (AAudioStreamBuilder** builder))
SDL_PROC_UNUSED(void, AAudioStreamBuilder_setDeviceId, (AAudioStreamBuilder* builder, int32_t deviceId))
SDL_PROC(void, AAudioStreamBuilder_setSampleRate, (AAudioStreamBuilder* builder, int32_t sampleRate))
@@ -41,7 +41,7 @@ SDL_PROC_UNUSED(void, AAudioStreamBuilder_setSessionId, (AAudioStreamBuilder* bu
SDL_PROC_UNUSED(void, AAudioStreamBuilder_setPrivacySensitive, (AAudioStreamBuilder* builder, bool privacySensitive)) /* API 30 */
SDL_PROC_UNUSED(void, AAudioStreamBuilder_setDataCallback, (AAudioStreamBuilder* builder, AAudioStream_dataCallback callback, void *userData))
SDL_PROC_UNUSED(void, AAudioStreamBuilder_setFramesPerDataCallback, (AAudioStreamBuilder* builder, int32_t numFrames))
-SDL_PROC_UNUSED(void, AAudioStreamBuilder_setErrorCallback, (AAudioStreamBuilder* builder, AAudioStream_errorCallback callback, void *userData))
+SDL_PROC(void, AAudioStreamBuilder_setErrorCallback, (AAudioStreamBuilder* builder, AAudioStream_errorCallback callback, void *userData))
SDL_PROC(aaudio_result_t , AAudioStreamBuilder_openStream, (AAudioStreamBuilder* builder, AAudioStream** stream))
SDL_PROC(aaudio_result_t , AAudioStreamBuilder_delete, (AAudioStreamBuilder* builder))
SDL_PROC_UNUSED(aaudio_result_t , AAudioStream_release, (AAudioStream* stream)) /* API 30 */
@@ -50,7 +50,7 @@ SDL_PROC(aaudio_result_t , AAudioStream_requestStart, (AAudioStream* stream))
SDL_PROC(aaudio_result_t , AAudioStream_requestPause, (AAudioStream* stream))
SDL_PROC_UNUSED(aaudio_result_t , AAudioStream_requestFlush, (AAudioStream* stream))
SDL_PROC(aaudio_result_t , AAudioStream_requestStop, (AAudioStream* stream))
-SDL_PROC_UNUSED(aaudio_stream_state_t, AAudioStream_getState, (AAudioStream* stream))
+SDL_PROC(aaudio_stream_state_t, AAudioStream_getState, (AAudioStream* stream))
SDL_PROC_UNUSED(aaudio_result_t, AAudioStream_waitForStateChange, (AAudioStream* stream, aaudio_stream_state_t inputState, aaudio_stream_state_t *nextState, int64_t timeoutNanoseconds))
SDL_PROC(aaudio_result_t, AAudioStream_read, (AAudioStream* stream, void *buffer, int32_t numFrames, int64_t timeoutNanoseconds))
SDL_PROC(aaudio_result_t, AAudioStream_write, (AAudioStream* stream, const void *buffer, int32_t numFrames, int64_t timeoutNanoseconds))
@@ -71,7 +71,7 @@ SDL_PROC_UNUSED(aaudio_direction_t, AAudioStream_getDirection, (AAudioStream* st
SDL_PROC_UNUSED(int64_t, AAudioStream_getFramesWritten, (AAudioStream* stream))
SDL_PROC_UNUSED(int64_t, AAudioStream_getFramesRead, (AAudioStream* stream))
SDL_PROC_UNUSED(aaudio_session_id_t, AAudioStream_getSessionId, (AAudioStream* stream)) /* API 28 */
-SDL_PROC_UNUSED(aaudio_result_t, AAudioStream_getTimestamp, (AAudioStream* stream, clockid_t clockid, int64_t *framePosition, int64_t *timeNanoseconds))
+SDL_PROC(aaudio_result_t, AAudioStream_getTimestamp, (AAudioStream* stream, clockid_t clockid, int64_t *framePosition, int64_t *timeNanoseconds))
SDL_PROC_UNUSED(aaudio_usage_t, AAudioStream_getUsage, (AAudioStream* stream)) /* API 28 */
SDL_PROC_UNUSED(aaudio_content_type_t, AAudioStream_getContentType, (AAudioStream* stream)) /* API 28 */
SDL_PROC_UNUSED(aaudio_input_preset_t, AAudioStream_getInputPreset, (AAudioStream* stream)) /* API 28 */
diff --git a/src/video/android/SDL_androidevents.c b/src/video/android/SDL_androidevents.c
index 4124cacea8..0a04b75619 100644
--- a/src/video/android/SDL_androidevents.c
+++ b/src/video/android/SDL_androidevents.c
@@ -51,9 +51,11 @@ static void openslES_PauseDevices(void) {}
#if !SDL_AUDIO_DISABLED && SDL_AUDIO_DRIVER_AAUDIO
extern void aaudio_ResumeDevices(void);
extern void aaudio_PauseDevices(void);
+SDL_bool aaudio_DetectBrokenPlayState( void );
#else
static void aaudio_ResumeDevices(void) {}
static void aaudio_PauseDevices(void) {}
+static SDL_bool aaudio_DetectBrokenPlayState( void ) { return SDL_FALSE; }
#endif
@@ -168,6 +170,11 @@ Android_PumpEvents_Blocking(_THIS)
}
}
}
+
+ if ( aaudio_DetectBrokenPlayState() ) {
+ aaudio_PauseDevices();
+ aaudio_ResumeDevices();
+ }
}
void
@@ -246,6 +253,11 @@ Android_PumpEvents_NonBlocking(_THIS)
}
}
}
+
+ if ( aaudio_DetectBrokenPlayState() ) {
+ aaudio_PauseDevices();
+ aaudio_ResumeDevices();
+ }
}
#endif /* SDL_VIDEO_DRIVER_ANDROID */