sdl2-compat: audio: Call audio callbacks with consistent buffer size.

From 9110cc1f391aee8c5d6b6ca323a3337b26355bc8 Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
Date: Thu, 30 Jan 2025 21:47:32 -0500
Subject: [PATCH] audio: Call audio callbacks with consistent buffer size.

Fixes #264.
---
 src/sdl2_compat.c | 78 ++++++++++++++++++++++++++---------------------
 src/sdl2_compat.h |  2 ++
 2 files changed, 46 insertions(+), 34 deletions(-)

diff --git a/src/sdl2_compat.c b/src/sdl2_compat.c
index 2a4c1a2..58a2182 100644
--- a/src/sdl2_compat.c
+++ b/src/sdl2_compat.c
@@ -5563,6 +5563,20 @@ static SDL2_AudioFormat ParseAudioFormat(const char *string)
     return 0;
 }
 
+static void UpdateAudiospec(SDL2_AudioSpec *spec2)
+{
+    /* Calculate the silence and size of the audio specification */
+    if ((spec2->format == SDL_AUDIO_U8) || (spec2->format == (SDL_AUDIO_S16LE & ~SDL_AUDIO_MASK_SIGNED)) || (spec2->format == (SDL_AUDIO_S16BE & ~SDL_AUDIO_MASK_SIGNED))) {
+        spec2->silence = 0x80;
+    } else {
+        spec2->silence = 0x00;
+    }
+
+    spec2->size = SDL_AUDIO_BITSIZE(spec2->format) / 8;
+    spec2->size *= spec2->channels;
+    spec2->size *= spec2->samples;
+}
+
 static int PrepareAudiospec(const SDL2_AudioSpec *orig2, SDL2_AudioSpec *prepared2)
 {
     SDL3_memcpy(prepared2, orig2, sizeof(*orig2));
@@ -5611,16 +5625,7 @@ static int PrepareAudiospec(const SDL2_AudioSpec *orig2, SDL2_AudioSpec *prepare
         }
     }
 
-    /* Calculate the silence and size of the audio specification */
-    if ((prepared2->format == SDL_AUDIO_U8) || (prepared2->format == (SDL_AUDIO_S16LE & ~SDL_AUDIO_MASK_SIGNED)) || (prepared2->format == (SDL_AUDIO_S16BE & ~SDL_AUDIO_MASK_SIGNED))) {
-        prepared2->silence = 0x80;
-    } else {
-        prepared2->silence = 0x00;
-    }
-
-    prepared2->size = SDL_AUDIO_BITSIZE(prepared2->format) / 8;
-    prepared2->size *= prepared2->channels;
-    prepared2->size *= prepared2->samples;
+    UpdateAudiospec(prepared2);
 
     return 1;
 }
@@ -5628,7 +5633,6 @@ static int PrepareAudiospec(const SDL2_AudioSpec *orig2, SDL2_AudioSpec *prepare
 static void SDLCALL SDL2AudioDeviceQueueingCallback(void *userdata, SDL_AudioStream *stream3, int approx_amount, int total_amount)
 {
     SDL2_AudioStream *stream2 = (SDL2_AudioStream *) userdata;
-    Uint8 *buffer;
 
     SDL_assert(stream2 != NULL);
     SDL_assert(stream3 == stream2->stream3);
@@ -5638,30 +5642,22 @@ static void SDLCALL SDL2AudioDeviceQueueingCallback(void *userdata, SDL_AudioStr
         return;  /* nothing to do right now. */
     }
 
-    buffer = (Uint8 *) SDL3_malloc(approx_amount);
-    if (!buffer) {
-        return;  /* oh well */
-    }
-
     if (stream2->iscapture) {
-        const int br = SDL_AudioStreamGet(stream2, buffer, approx_amount);
+        const int br = SDL_AudioStreamGet(stream2, stream2->callback2_buffer, SDL_min(stream2->bytes_per_callbacks, approx_amount));
         if (br > 0) {
-            SDL3_PutAudioStreamData(stream2->dataqueue3, buffer, br);
+            SDL3_PutAudioStreamData(stream2->dataqueue3, stream2->callback2_buffer, br);
         }
     } else {
-        const int br = SDL3_GetAudioStreamData(stream2->dataqueue3, buffer, approx_amount);
+        const int br = SDL3_GetAudioStreamData(stream2->dataqueue3, stream2->callback2_buffer, SDL_min(stream2->bytes_per_callbacks, approx_amount));
         if (br > 0) {
-            SDL_AudioStreamPut(stream2, buffer, br);
+            SDL_AudioStreamPut(stream2, stream2->callback2_buffer, br);
         }
     }
-
-    SDL3_free(buffer);
 }
 
 static void SDLCALL SDL2AudioDeviceCallbackBridge(void *userdata, SDL_AudioStream *stream3, int approx_amount, int total_amount)
 {
     SDL2_AudioStream *stream2 = (SDL2_AudioStream *) userdata;
-    Uint8 *buffer;
 
     if (approx_amount == 0) {
         return;  /* nothing to do right now. */
@@ -5671,20 +5667,23 @@ static void SDLCALL SDL2AudioDeviceCallbackBridge(void *userdata, SDL_AudioStrea
     SDL_assert(stream3 == stream2->stream3);
     SDL_assert(stream2->dataqueue3 == NULL);
 
-    buffer = (Uint8 *) SDL3_malloc(approx_amount);
-    if (!buffer) {
-        return;  /* oh well */
-    }
-
     if (stream2->iscapture) {
-        const int br = SDL_AudioStreamGet(stream2, buffer, approx_amount);
-        stream2->callback2(stream2->callback2_userdata, buffer, br);
+        while (SDL_AudioStreamAvailable(stream2) >= stream2->bytes_per_callbacks) {
+            const int br = SDL_AudioStreamGet(stream2, stream2->callback2_buffer, stream2->bytes_per_callbacks);
+            SDL_assert(br == stream2->bytes_per_callbacks);
+            approx_amount -= br;
+            stream2->callback2(stream2->callback2_userdata, stream2->callback2_buffer, br);
+        }
     } else {
-        stream2->callback2(stream2->callback2_userdata, buffer, approx_amount);
-        SDL_AudioStreamPut(stream2, buffer, approx_amount);
+        while (approx_amount > 0) {
+            stream2->callback2(stream2->callback2_userdata, stream2->callback2_buffer, stream2->bytes_per_callbacks);
+            SDL_AudioStreamPut(stream2, stream2->callback2_buffer, stream2->bytes_per_callbacks);
+            approx_amount -= stream2->bytes_per_callbacks;
+            if (approx_amount < 0) {
+                approx_amount = 0;
+            }
+        }
     }
-
-    SDL3_free(buffer);
 }
 
 static SDL_AudioDeviceID OpenAudioDeviceLocked(const char *devicename, int iscapture,
@@ -5774,6 +5773,8 @@ static SDL_AudioDeviceID OpenAudioDeviceLocked(const char *devicename, int iscap
         obtained2->freq = spec3.freq;
     }
 
+    UpdateAudiospec(obtained2);
+
     if (iscapture) {
         stream2 = SDL_NewAudioStream((SDL2_AudioFormat)spec3.format, spec3.channels, spec3.freq, obtained2->format, obtained2->channels, obtained2->freq);
     } else {
@@ -5785,6 +5786,14 @@ static SDL_AudioDeviceID OpenAudioDeviceLocked(const char *devicename, int iscap
         return 0;
     }
 
+    stream2->bytes_per_callbacks = obtained2->size;
+    stream2->callback2_buffer = SDL3_malloc(stream2->bytes_per_callbacks);
+    if (!stream2->callback2_buffer) {
+        SDL_FreeAudioStream(stream2);
+        SDL3_CloseAudioDevice(device3);
+        return 0;
+    }
+
     if (desired2->callback) {
         stream2->callback2 = desired2->callback;
         stream2->callback2_userdata = desired2->userdata;
@@ -6026,6 +6035,7 @@ SDL_FreeAudioStream(SDL2_AudioStream *stream2)
     if (stream2) {
         SDL3_DestroyAudioStream(stream2->stream3);
         SDL3_DestroyAudioStream(stream2->dataqueue3);
+        SDL3_free(stream2->callback2_buffer);
         SDL3_free(stream2);
     }
 }
diff --git a/src/sdl2_compat.h b/src/sdl2_compat.h
index f35d5ba..05cbbce 100644
--- a/src/sdl2_compat.h
+++ b/src/sdl2_compat.h
@@ -1164,6 +1164,8 @@ typedef struct SDL2_AudioStream
     SDL2_AudioFormat dst_format;
 
     /* these are used when this stream is powering an opened audio device, and not when just used as an SDL2 audio stream. */
+    int bytes_per_callbacks;
+    void *callback2_buffer;
     SDL2_AudioCallback callback2;
     void *callback2_userdata;
     SDL_AudioStream *dataqueue3;