SDL: Work around hang in AAudioStream_write() during extended shared object loading while running in a debugger. Observed on a...

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 */