SDL: audio: Seperate audio capture into Wait/Read operations.

From dac25fe9eb8176e061d86559332360d36ffd3df2 Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
Date: Sat, 24 Jun 2023 14:51:39 -0400
Subject: [PATCH] audio: Seperate audio capture into Wait/Read operations.

Before it would just block in read operations, but separating this out
matches what output devices already do, and also lets us separate out the
unlocked waiting part from the fast part that holds the device lock.
---
 src/audio/SDL_audio.c                 |  9 ++-
 src/audio/SDL_sysaudio.h              |  1 +
 src/audio/disk/SDL_diskaudio.c        |  1 +
 src/audio/pulseaudio/SDL_pulseaudio.c | 86 ++++++++++++++-------------
 4 files changed, 55 insertions(+), 42 deletions(-)

diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c
index 27b2633cfb59..992cfde99e72 100644
--- a/src/audio/SDL_audio.c
+++ b/src/audio/SDL_audio.c
@@ -414,6 +414,7 @@ static void SDL_AudioThreadInit_Default(SDL_AudioDevice *device) { /* no-op. */
 static void SDL_AudioThreadDeinit_Default(SDL_AudioDevice *device) { /* no-op. */ }
 static void SDL_AudioWaitDevice_Default(SDL_AudioDevice *device) { /* no-op. */ }
 static void SDL_AudioPlayDevice_Default(SDL_AudioDevice *device, int buffer_size) { /* no-op. */ }
+static void SDL_AudioWaitCaptureDevice_Default(SDL_AudioDevice *device) { /* no-op. */ }
 static void SDL_AudioFlushCapture_Default(SDL_AudioDevice *device) { /* no-op. */ }
 static void SDL_AudioCloseDevice_Default(SDL_AudioDevice *device) { /* no-op. */ }
 static void SDL_AudioDeinitialize_Default(void) { /* no-op. */ }
@@ -458,6 +459,7 @@ static void CompleteAudioEntryPoints(void)
     FILL_STUB(WaitDevice);
     FILL_STUB(PlayDevice);
     FILL_STUB(GetDeviceBuf);
+    FILL_STUB(WaitCaptureDevice);
     FILL_STUB(CaptureFromDevice);
     FILL_STUB(FlushCapture);
     FILL_STUB(CloseDevice);
@@ -769,6 +771,7 @@ SDL_bool SDL_CaptureAudioThreadIterate(SDL_AudioDevice *device)
     } else if (device->logical_devices == NULL) {
         current_audio.impl.FlushCapture(device); // nothing wants data, dump anything pending.
     } else {
+        // this SHOULD NOT BLOCK, as we are holding a lock right now. Block in WaitCaptureDevice!
         const int rc = current_audio.impl.CaptureFromDevice(device, device->work_buffer, device->buffer_size);
         if (rc < 0) {  // uhoh, device failed for some reason!
             retval = SDL_FALSE;
@@ -818,7 +821,11 @@ static int SDLCALL CaptureAudioThread(void *devicep)  // thread entry point
     SDL_assert(device != NULL);
     SDL_assert(device->iscapture);
     SDL_CaptureAudioThreadSetup(device);
-    while (SDL_CaptureAudioThreadIterate(device)) { /* spin, CaptureAudioThreadIterate will block if necessary. !!! FIXME: maybe this is bad. */ }
+
+    do {
+        current_audio.impl.WaitCaptureDevice(device);
+    } while (SDL_CaptureAudioThreadIterate(device));
+
     SDL_CaptureAudioThreadShutdown(device);
     return 0;
 }
diff --git a/src/audio/SDL_sysaudio.h b/src/audio/SDL_sysaudio.h
index 38bfa2a3f0ee..b9c7da6cc46d 100644
--- a/src/audio/SDL_sysaudio.h
+++ b/src/audio/SDL_sysaudio.h
@@ -109,6 +109,7 @@ typedef struct SDL_AudioDriverImpl
     void (*WaitDevice)(SDL_AudioDevice *device);
     void (*PlayDevice)(SDL_AudioDevice *device, int buffer_size);
     Uint8 *(*GetDeviceBuf)(SDL_AudioDevice *device, int *buffer_size);
+    void (*WaitCaptureDevice)(SDL_AudioDevice *device);
     int (*CaptureFromDevice)(SDL_AudioDevice *device, void *buffer, int buflen);
     void (*FlushCapture)(SDL_AudioDevice *device);
     void (*CloseDevice)(SDL_AudioDevice *device);
diff --git a/src/audio/disk/SDL_diskaudio.c b/src/audio/disk/SDL_diskaudio.c
index 517a4859eebd..e96014ce957c 100644
--- a/src/audio/disk/SDL_diskaudio.c
+++ b/src/audio/disk/SDL_diskaudio.c
@@ -162,6 +162,7 @@ static SDL_bool DISKAUDIO_Init(SDL_AudioDriverImpl *impl)
     /* Set the function pointers */
     impl->OpenDevice = DISKAUDIO_OpenDevice;
     impl->WaitDevice = DISKAUDIO_WaitDevice;
+    impl->WaitCaptureDevice = DISKAUDIO_WaitDevice;
     impl->PlayDevice = DISKAUDIO_PlayDevice;
     impl->GetDeviceBuf = DISKAUDIO_GetDeviceBuf;
     impl->CaptureFromDevice = DISKAUDIO_CaptureFromDevice;
diff --git a/src/audio/pulseaudio/SDL_pulseaudio.c b/src/audio/pulseaudio/SDL_pulseaudio.c
index c5189b7652b5..fcd9d15ecfb1 100644
--- a/src/audio/pulseaudio/SDL_pulseaudio.c
+++ b/src/audio/pulseaudio/SDL_pulseaudio.c
@@ -429,62 +429,65 @@ static void ReadCallback(pa_stream *p, size_t nbytes, void *userdata)
     PULSEAUDIO_pa_threaded_mainloop_signal(pulseaudio_threaded_mainloop, 0);  /* the capture code queries what it needs, we just need to signal to end any wait */
 }
 
-static int PULSEAUDIO_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen)
+static void PULSEAUDIO_WaitCaptureDevice(SDL_AudioDevice *device)
 {
     struct SDL_PrivateAudioData *h = device->hidden;
-    const void *data = NULL;
-    size_t nbytes = 0;
-    int retval = 0;
+
+    if (h->capturebuf != NULL) {
+        return;  // there's still data available to read.
+    }
 
     PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop);
 
     while (!SDL_AtomicGet(&device->shutdown)) {
-        if (h->capturebuf != NULL) {
-            const int cpy = SDL_min(buflen, h->capturelen);
-            SDL_memcpy(buffer, h->capturebuf, cpy);
-            /*printf("PULSEAUDIO: fed %d captured bytes\n", cpy);*/
-            h->capturebuf += cpy;
-            h->capturelen -= cpy;
-            if (h->capturelen == 0) {
-                h->capturebuf = NULL;
-                PULSEAUDIO_pa_stream_drop(h->stream); /* done with this fragment. */
-            }
-            retval = cpy; /* new data, return it. */
+        PULSEAUDIO_pa_threaded_mainloop_wait(pulseaudio_threaded_mainloop);
+        if ((PULSEAUDIO_pa_context_get_state(pulseaudio_context) != PA_CONTEXT_READY) || (PULSEAUDIO_pa_stream_get_state(h->stream) != PA_STREAM_READY)) {
+            //printf("PULSEAUDIO DEVICE FAILURE IN WAITCAPTUREDEVICE!\n");
+            SDL_AudioDeviceDisconnected(device);
             break;
-        }
-
-        while (!SDL_AtomicGet(&device->shutdown) && (PULSEAUDIO_pa_stream_readable_size(h->stream) == 0)) {
-            PULSEAUDIO_pa_threaded_mainloop_wait(pulseaudio_threaded_mainloop);
-            if ((PULSEAUDIO_pa_context_get_state(pulseaudio_context) != PA_CONTEXT_READY) || (PULSEAUDIO_pa_stream_get_state(h->stream) != PA_STREAM_READY)) {
-                /*printf("PULSEAUDIO DEVICE FAILURE IN CAPTUREFROMDEVICE!\n");*/
-                SDL_AudioDeviceDisconnected(device);
-                retval = -1;
+        } else if (PULSEAUDIO_pa_stream_readable_size(h->stream) > 0) {
+            // a new fragment is available!
+            const void *data = NULL;
+            size_t nbytes = 0;
+            PULSEAUDIO_pa_stream_peek(h->stream, &data, &nbytes);
+            SDL_assert(nbytes > 0);
+            if (data == NULL) {  // If NULL, then the buffer had a hole, ignore that
+                PULSEAUDIO_pa_stream_drop(h->stream);  // drop this fragment.
+            } else {
+                // store this fragment's data for use with CaptureFromDevice
+                //printf("PULSEAUDIO: captured %d new bytes\n", (int) nbytes);
+                h->capturebuf = (const Uint8 *)data;
+                h->capturelen = nbytes;
                 break;
             }
         }
+    }
 
-        if ((retval == -1) || SDL_AtomicGet(&device->shutdown)) {  /* in case this happened while we were blocking. */
-            retval = -1;
-            break;
-        }
+    PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop);
+}
 
-        /* a new fragment is available! */
-        PULSEAUDIO_pa_stream_peek(h->stream, &data, &nbytes);
-        SDL_assert(nbytes > 0);
-        /* If data == NULL, then the buffer had a hole, ignore that */
-        if (data == NULL) {
-            PULSEAUDIO_pa_stream_drop(h->stream); /* drop this fragment. */
-        } else {
-            /* store this fragment's data, start feeding it to SDL. */
-            /*printf("PULSEAUDIO: captured %d new bytes\n", (int) nbytes);*/
-            h->capturebuf = (const Uint8 *)data;
-            h->capturelen = nbytes;
+static int PULSEAUDIO_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen)
+{
+    struct SDL_PrivateAudioData *h = device->hidden;
+
+    if (h->capturebuf != NULL) {
+        const int cpy = SDL_min(buflen, h->capturelen);
+        if (cpy > 0) {
+            //printf("PULSEAUDIO: fed %d captured bytes\n", cpy);
+            SDL_memcpy(buffer, h->capturebuf, cpy);
+            h->capturebuf += cpy;
+            h->capturelen -= cpy;
         }
+        if (h->capturelen == 0) {
+            h->capturebuf = NULL;
+            PULSEAUDIO_pa_threaded_mainloop_lock(pulseaudio_threaded_mainloop);  // don't know if you _have_ to lock for this, but just in case.
+            PULSEAUDIO_pa_stream_drop(h->stream); // done with this fragment.
+            PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop);
+        }
+        return cpy; /* new data, return it. */
     }
 
-    PULSEAUDIO_pa_threaded_mainloop_unlock(pulseaudio_threaded_mainloop);
-
-    return retval;
+    return 0;
 }
 
 static void PULSEAUDIO_FlushCapture(SDL_AudioDevice *device)
@@ -991,6 +994,7 @@ static SDL_bool PULSEAUDIO_Init(SDL_AudioDriverImpl *impl)
     impl->GetDeviceBuf = PULSEAUDIO_GetDeviceBuf;
     impl->CloseDevice = PULSEAUDIO_CloseDevice;
     impl->Deinitialize = PULSEAUDIO_Deinitialize;
+    impl->WaitCaptureDevice = PULSEAUDIO_WaitCaptureDevice;
     impl->CaptureFromDevice = PULSEAUDIO_CaptureFromDevice;
     impl->FlushCapture = PULSEAUDIO_FlushCapture;
     #if 0