sdl2-compat: audiocvt: Move audio conversion into an audio filter function

From c3206086d4727a3a2f888781780289cdd987f74b Mon Sep 17 00:00:00 2001
From: Cameron Gutman <[EMAIL REDACTED]>
Date: Sun, 23 Feb 2025 14:24:55 -0600
Subject: [PATCH] audiocvt: Move audio conversion into an audio filter function

Some apps may examine or modify the filter chain, so we'll perform
our conversion in a filter function for maximum compatibility.

Fixes #379
---
 src/sdl2_compat.c | 135 ++++++++++++++++++++++++++--------------------
 1 file changed, 77 insertions(+), 58 deletions(-)

diff --git a/src/sdl2_compat.c b/src/sdl2_compat.c
index 71278bd..25ace49 100644
--- a/src/sdl2_compat.c
+++ b/src/sdl2_compat.c
@@ -10408,6 +10408,64 @@ typedef struct {
 #define RESAMPLER_BITS_PER_SAMPLE           16
 #define RESAMPLER_SAMPLES_PER_ZERO_CROSSING (1 << ((RESAMPLER_BITS_PER_SAMPLE / 2) + 1))
 
+static void SDLCALL AudioCVTFilter(SDL_AudioCVT *cvt, SDL2_AudioFormat src_format)
+{
+    SDL2_AudioStream *stream2;
+    SDL2_AudioFormat dst_format;
+    int src_channels, src_rate;
+    int dst_channels, dst_rate;
+    int src_len, dst_len, real_dst_len;
+    int src_samplesize;
+
+    { /* Fetch from the end of filters[], aligned */
+        AudioParam ap;
+
+        SDL3_memcpy(
+            &ap,
+            (Uint8 *)&cvt->filters[SDL_AUDIOCVT_MAX_FILTERS + 1] - (sizeof(AudioParam) & ~3),
+            sizeof(ap));
+
+        src_channels = ap.src_channels;
+        src_rate = ap.src_rate;
+        dst_format = ap.dst_format;
+        dst_channels = ap.dst_channels;
+        dst_rate = ap.dst_rate;
+    }
+
+    /* don't use the SDL3 stream directly or even SDL_ConvertAudioSamples; we want the U16 support in the sdl2-compat layer */
+    stream2 = SDL_NewAudioStream(src_format, src_channels, src_rate,
+                                 dst_format, dst_channels, dst_rate);
+    if (stream2 == NULL) {
+        goto exit;
+    }
+
+    src_samplesize = (SDL_AUDIO_BITSIZE(src_format) / 8) * src_channels;
+
+    src_len = cvt->len_cvt & ~(src_samplesize - 1);
+    dst_len = cvt->len * cvt->len_mult;
+
+    /* Run the audio converter */
+    if (SDL_AudioStreamPut(stream2, cvt->buf, src_len) < 0 ||
+        SDL_AudioStreamFlush(stream2) < 0) {
+        goto exit;
+    }
+
+    /* Get back in the same buffer */
+    real_dst_len = SDL_AudioStreamGet(stream2, cvt->buf, dst_len);
+    if (real_dst_len < 0) {
+        goto exit;
+    }
+
+    cvt->len_cvt = real_dst_len;
+
+exit:
+    SDL_FreeAudioStream(stream2);
+
+    /* Call the next filter in the chain */
+    if (cvt->filters[++cvt->filter_index]) {
+        cvt->filters[cvt->filter_index](cvt, dst_format);
+    }
+}
 
 SDL_DECLSPEC int SDLCALL
 SDL_BuildAudioCVT(SDL_AudioCVT *cvt,
@@ -10489,7 +10547,6 @@ SDL_BuildAudioCVT(SDL_AudioCVT *cvt,
             &ap,
             sizeof(ap));
 
-        cvt->filters[0] = NULL;
         cvt->needed = 1;
         if (src_format == dst_format && src_rate == dst_rate && src_channels == dst_channels) {
             cvt->needed = 0;
@@ -10523,74 +10580,36 @@ SDL_BuildAudioCVT(SDL_AudioCVT *cvt,
         }
     }
 
+    if (cvt->needed) {
+        /* Insert a single filter to perform all necessary audio conversion.
+         * Some apps may examine or modify the filter chain, so we use a real
+         * SDL2-style audio filter function to keep those apps happy. */
+        cvt->filters[0] = AudioCVTFilter;
+        cvt->filters[1] = NULL;
+        cvt->filter_index = 1;
+    }
+
     return cvt->needed;
 }
 
 SDL_DECLSPEC int SDLCALL
 SDL_ConvertAudio(SDL_AudioCVT *cvt)
 {
-    SDL2_AudioStream *stream2;
-    SDL2_AudioFormat dst_format;
-    int src_channels, src_rate;
-    int dst_channels, dst_rate;
-
-    int src_len, dst_len, real_dst_len;
-    int src_samplesize;
-
-    /* Sanity check target pointer */
-    if (cvt == NULL) {
-        SDL3_InvalidParamError("cvt");
-        return -1;
-    }
-
-    { /* Fetch from the end of filters[], aligned */
-        AudioParam ap;
-
-        SDL3_memcpy(
-            &ap,
-            (Uint8 *)&cvt->filters[SDL_AUDIOCVT_MAX_FILTERS + 1] - (sizeof(AudioParam) & ~3),
-            sizeof(ap));
-
-        src_channels = ap.src_channels;
-        src_rate = ap.src_rate;
-        dst_format = ap.dst_format;
-        dst_channels = ap.dst_channels;
-        dst_rate = ap.dst_rate;
+    /* Make sure there's data to convert */
+    if (!cvt->buf) {
+        return SDL_SetError("No buffer allocated for conversion");
     }
 
-    /* don't use the SDL3 stream directly or even SDL_ConvertAudioSamples; we want the U16 support in the sdl2-compat layer */
-    stream2 = SDL_NewAudioStream(cvt->src_format, src_channels, src_rate,
-                                 dst_format, dst_channels, dst_rate);
-    if (stream2 == NULL) {
-        goto failure;
-    }
-
-    src_samplesize = (SDL_AUDIO_BITSIZE(cvt->src_format) / 8) * src_channels;
-
-    src_len = cvt->len & ~(src_samplesize - 1);
-    dst_len = cvt->len * cvt->len_mult;
-
-    /* Run the audio converter */
-    if (SDL_AudioStreamPut(stream2, cvt->buf, src_len) < 0 ||
-        SDL_AudioStreamFlush(stream2) < 0) {
-        goto failure;
-    }
-
-    /* Get back in the same buffer */
-    real_dst_len = SDL_AudioStreamGet(stream2, cvt->buf, dst_len);
-    if (real_dst_len < 0) {
-        goto failure;
+    /* Return okay if no conversion is necessary */
+    cvt->len_cvt = cvt->len;
+    if (cvt->filters[0] == NULL) {
+        return 0;
     }
 
-    cvt->len_cvt = real_dst_len;
-
-    SDL_FreeAudioStream(stream2);
-
+    /* Set up the conversion and go! */
+    cvt->filter_index = 0;
+    cvt->filters[0](cvt, cvt->src_format);
     return 0;
-
-failure:
-    SDL_FreeAudioStream(stream2);
-    return -1;
 }
 
 SDL_DECLSPEC void SDLCALL