SDL: Android AAUDIO: handle multiple devices

From 117169d6103d0a783d25e91befb70f8090d263ed Mon Sep 17 00:00:00 2001
From: Sylvain <[EMAIL REDACTED]>
Date: Thu, 6 Apr 2023 23:17:29 +0200
Subject: [PATCH] Android AAUDIO: handle multiple devices

---
 src/audio/SDL_audio.c         |  10 ++
 src/audio/SDL_sysaudio.h      |   3 +
 src/audio/aaudio/SDL_aaudio.c | 221 ++++++++++++++++++----------------
 3 files changed, 132 insertions(+), 102 deletions(-)

diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c
index aca42620619e..1a306c675291 100644
--- a/src/audio/SDL_audio.c
+++ b/src/audio/SDL_audio.c
@@ -201,6 +201,16 @@ static SDL_AudioDevice *get_audio_device(SDL_AudioDeviceID id)
     return open_devices[id];
 }
 
+int get_max_num_audio_dev(void)
+{
+    return SDL_arraysize(open_devices);
+}
+
+SDL_AudioDevice *get_audio_dev(SDL_AudioDeviceID id)
+{
+    return open_devices[id];
+}
+
 /* stubs for audio drivers that don't need a specific entry point... */
 static void SDL_AudioDetectDevices_Default(void)
 {
diff --git a/src/audio/SDL_sysaudio.h b/src/audio/SDL_sysaudio.h
index 7fb7ac591ff2..7a49b4e9ce75 100644
--- a/src/audio/SDL_sysaudio.h
+++ b/src/audio/SDL_sysaudio.h
@@ -200,4 +200,7 @@ extern AudioBootStrap N3DSAUDIO_bootstrap;
 extern AudioBootStrap EMSCRIPTENAUDIO_bootstrap;
 extern AudioBootStrap QSAAUDIO_bootstrap;
 
+extern SDL_AudioDevice *get_audio_dev(SDL_AudioDeviceID id);
+extern int get_max_num_audio_dev(void);
+
 #endif /* SDL_sysaudio_h_ */
diff --git a/src/audio/aaudio/SDL_aaudio.c b/src/audio/aaudio/SDL_aaudio.c
index 693b5a98cd60..9388111afffe 100644
--- a/src/audio/aaudio/SDL_aaudio.c
+++ b/src/audio/aaudio/SDL_aaudio.c
@@ -43,9 +43,6 @@ typedef struct AAUDIO_Data
 } AAUDIO_Data;
 static AAUDIO_Data ctx;
 
-static SDL_AudioDevice *audioDevice = NULL;
-static SDL_AudioDevice *captureDevice = NULL;
-
 static int aaudio_LoadFunctions(AAUDIO_Data *data)
 {
 #define SDL_PROC(ret, func, params)                                                             \
@@ -75,18 +72,6 @@ static int aaudio_OpenDevice(_THIS, const char *devname)
     aaudio_result_t res;
     LOGI(__func__);
 
-    if (iscapture) {
-        if (captureDevice) {
-            return SDL_SetError("An audio capture device is already opened");
-        }
-    }
-
-    if (!iscapture) {
-        if (audioDevice) {
-            return SDL_SetError("An audio playback device is already opened");
-        }
-    }
-
     if (iscapture) {
         if (!Android_JNI_RequestPermission("android.permission.RECORD_AUDIO")) {
             LOGI("This app doesn't have RECORD_AUDIO permission");
@@ -94,12 +79,6 @@ static int aaudio_OpenDevice(_THIS, const char *devname)
         }
     }
 
-    if (iscapture) {
-        captureDevice = this;
-    } else {
-        audioDevice = this;
-    }
-
     this->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*this->hidden));
     if (this->hidden == NULL) {
         return SDL_OutOfMemory();
@@ -200,14 +179,6 @@ static void aaudio_CloseDevice(_THIS)
         }
     }
 
-    if (this->iscapture) {
-        SDL_assert(captureDevice == this);
-        captureDevice = NULL;
-    } else {
-        SDL_assert(audioDevice == this);
-        audioDevice = NULL;
-    }
-
     SDL_free(this->hidden->mixbuf);
     SDL_free(this->hidden);
 }
@@ -349,89 +320,120 @@ AudioBootStrap aaudio_bootstrap = {
 /* Pause (block) all non already paused audio devices by taking their mixer lock */
 void aaudio_PauseDevices(void)
 {
-    /* TODO: Handle multiple devices? */
-    struct SDL_PrivateAudioData *private;
-    if (audioDevice != NULL && audioDevice->hidden != NULL) {
-        private = (struct SDL_PrivateAudioData *)audioDevice->hidden;
-
-        if (private->stream) {
-            aaudio_result_t res = ctx.AAudioStream_requestPause(private->stream);
-            if (res != AAUDIO_OK) {
-                LOGI("SDL Failed AAudioStream_requestPause %d", res);
-                SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res));
-            }
+    int i;
+    for (i = 0; i < get_max_num_audio_dev(); i++) {
+        SDL_AudioDevice *this = get_audio_dev(i);
+        SDL_AudioDevice *audioDevice = NULL;
+        SDL_AudioDevice *captureDevice = NULL;
+
+        if (this == NULL) {
+            continue;
         }
 
-        if (SDL_AtomicGet(&audioDevice->paused)) {
-            /* The device is already paused, leave it alone */
-            private->resume = SDL_FALSE;
+        if (this->iscapture) {
+            captureDevice = this;
         } else {
-            SDL_LockMutex(audioDevice->mixer_lock);
-            SDL_AtomicSet(&audioDevice->paused, 1);
-            private->resume = SDL_TRUE;
+            audioDevice = this;
         }
-    }
 
-    if (captureDevice != NULL && captureDevice->hidden != NULL) {
-        private = (struct SDL_PrivateAudioData *)captureDevice->hidden;
+        if (audioDevice != NULL && audioDevice->hidden != NULL) {
+            struct SDL_PrivateAudioData *private = (struct SDL_PrivateAudioData *)audioDevice->hidden;
 
-        if (private->stream) {
-            /* Pause() isn't implemented for 'capture', use Stop() */
-            aaudio_result_t res = ctx.AAudioStream_requestStop(private->stream);
-            if (res != AAUDIO_OK) {
-                LOGI("SDL Failed AAudioStream_requestStop %d", res);
-                SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res));
+            if (private->stream) {
+                aaudio_result_t res = ctx.AAudioStream_requestPause(private->stream);
+                if (res != AAUDIO_OK) {
+                    LOGI("SDL Failed AAudioStream_requestPause %d", res);
+                    SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res));
+                }
+            }
+
+            if (SDL_AtomicGet(&audioDevice->paused)) {
+                /* The device is already paused, leave it alone */
+                private->resume = SDL_FALSE;
+            } else {
+                SDL_LockMutex(audioDevice->mixer_lock);
+                SDL_AtomicSet(&audioDevice->paused, 1);
+                private->resume = SDL_TRUE;
             }
         }
 
-        if (SDL_AtomicGet(&captureDevice->paused)) {
-            /* The device is already paused, leave it alone */
-            private->resume = SDL_FALSE;
-        } else {
-            SDL_LockMutex(captureDevice->mixer_lock);
-            SDL_AtomicSet(&captureDevice->paused, 1);
-            private->resume = SDL_TRUE;
+        if (captureDevice != NULL && captureDevice->hidden != NULL) {
+            struct SDL_PrivateAudioData *private = (struct SDL_PrivateAudioData *)audioDevice->hidden;
+
+            if (private->stream) {
+                /* Pause() isn't implemented for 'capture', use Stop() */
+                aaudio_result_t res = ctx.AAudioStream_requestStop(private->stream);
+                if (res != AAUDIO_OK) {
+                    LOGI("SDL Failed AAudioStream_requestStop %d", res);
+                    SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res));
+                }
+            }
+
+            if (SDL_AtomicGet(&captureDevice->paused)) {
+                /* The device is already paused, leave it alone */
+                private->resume = SDL_FALSE;
+            } else {
+                SDL_LockMutex(captureDevice->mixer_lock);
+                SDL_AtomicSet(&captureDevice->paused, 1);
+                private->resume = SDL_TRUE;
+            }
         }
+
     }
 }
 
 /* Resume (unblock) all non already paused audio devices by releasing their mixer lock */
 void aaudio_ResumeDevices(void)
 {
-    /* TODO: Handle multiple devices? */
-    struct SDL_PrivateAudioData *private;
-    if (audioDevice != NULL && audioDevice->hidden != NULL) {
-        private = (struct SDL_PrivateAudioData *)audioDevice->hidden;
+    int i;
+    for (i = 0; i < get_max_num_audio_dev(); i++) {
+        SDL_AudioDevice *this = get_audio_dev(i);
+        SDL_AudioDevice *audioDevice = NULL;
+        SDL_AudioDevice *captureDevice = NULL;
+
+        if (this == NULL) {
+            continue;
+        }
 
-        if (private->resume) {
-            SDL_AtomicSet(&audioDevice->paused, 0);
-            private->resume = SDL_FALSE;
-            SDL_UnlockMutex(audioDevice->mixer_lock);
+        if (this->iscapture) {
+            captureDevice = this;
+        } else {
+            audioDevice = this;
         }
 
-        if (private->stream) {
-            aaudio_result_t res = ctx.AAudioStream_requestStart(private->stream);
-            if (res != AAUDIO_OK) {
-                LOGI("SDL Failed AAudioStream_requestStart %d", res);
-                SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res));
+        if (audioDevice != NULL && audioDevice->hidden != NULL) {
+            struct SDL_PrivateAudioData *private = audioDevice->hidden;
+
+            if (private->resume) {
+                SDL_AtomicSet(&audioDevice->paused, 0);
+                private->resume = SDL_FALSE;
+                SDL_UnlockMutex(audioDevice->mixer_lock);
+            }
+
+            if (private->stream) {
+                aaudio_result_t res = ctx.AAudioStream_requestStart(private->stream);
+                if (res != AAUDIO_OK) {
+                    LOGI("SDL Failed AAudioStream_requestStart %d", res);
+                    SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res));
+                }
             }
         }
-    }
 
-    if (captureDevice != NULL && captureDevice->hidden != NULL) {
-        private = (struct SDL_PrivateAudioData *)captureDevice->hidden;
+        if (captureDevice != NULL && captureDevice->hidden != NULL) {
+            struct SDL_PrivateAudioData *private = audioDevice->hidden;
 
-        if (private->resume) {
-            SDL_AtomicSet(&captureDevice->paused, 0);
-            private->resume = SDL_FALSE;
-            SDL_UnlockMutex(captureDevice->mixer_lock);
-        }
+            if (private->resume) {
+                SDL_AtomicSet(&captureDevice->paused, 0);
+                private->resume = SDL_FALSE;
+                SDL_UnlockMutex(captureDevice->mixer_lock);
+            }
 
-        if (private->stream) {
-            aaudio_result_t res = ctx.AAudioStream_requestStart(private->stream);
-            if (res != AAUDIO_OK) {
-                LOGI("SDL Failed AAudioStream_requestStart %d", res);
-                SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res));
+            if (private->stream) {
+                aaudio_result_t res = ctx.AAudioStream_requestStart(private->stream);
+                if (res != AAUDIO_OK) {
+                    LOGI("SDL Failed AAudioStream_requestStart %d", res);
+                    SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res));
+                }
             }
         }
     }
@@ -444,24 +446,39 @@ void aaudio_ResumeDevices(void)
 */
 SDL_bool aaudio_DetectBrokenPlayState(void)
 {
-    struct SDL_PrivateAudioData *private;
-    int64_t framePosition, timeNanoseconds;
-    aaudio_result_t res;
+    int i;
+    for (i = 0; i < get_max_num_audio_dev(); i++) {
+        SDL_AudioDevice *this = get_audio_dev(i);
+        SDL_AudioDevice *audioDevice = NULL;
+        SDL_AudioDevice *captureDevice = NULL;
+
+        if (this == NULL) {
+            continue;
+        }
 
-    if (audioDevice == NULL || !audioDevice->hidden) {
-        return SDL_FALSE;
-    }
+        if (this->iscapture) {
+            captureDevice = this;
+        } else {
+            audioDevice = this;
+        }
 
-    private = audioDevice->hidden;
+        if (audioDevice != NULL && audioDevice->hidden != NULL) {
+            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;
+                }
+            }
 
-    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;
         }
+
+        (void) captureDevice;
     }
 
     return SDL_FALSE;