From 2423c514716f0202b098e20cb682f401b3c141fe Mon Sep 17 00:00:00 2001 From: Sam Lantinga 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 b9cad6b6c..91bd11470 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 34c2f2151..2b16fb1ae 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 a563d18e4..d482d00c6 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 4124cacea..0a04b7561 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 */