sdl2-compat: audio: Updated for the SDL3 audio redesign.

From 4db248b1db356301e80f32ba46e3870e0ea8c9ef Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
Date: Thu, 3 Aug 2023 21:42:15 -0400
Subject: [PATCH] audio: Updated for the SDL3 audio redesign.

---
 src/dynapi/SDL_dynapi_procs.h |  14 +-
 src/sdl2_compat.c             | 697 ++++++++++++++++++++++++++++++----
 src/sdl2_compat.h             |  34 ++
 src/sdl2_protos.h             |  14 +-
 src/sdl3_include_wrapper.h    | 335 ++++++++--------
 src/sdl3_syms.h               |  64 ++--
 6 files changed, 890 insertions(+), 268 deletions(-)

diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h
index ff40085..1ab612c 100644
--- a/src/dynapi/SDL_dynapi_procs.h
+++ b/src/dynapi/SDL_dynapi_procs.h
@@ -109,15 +109,15 @@ SDL_DYNAPI_PROC(const char*,SDL_GetAudioDriver,(int a),(a),return)
 SDL_DYNAPI_PROC(int,SDL_AudioInit,(const char *a),(a),return)
 SDL_DYNAPI_PROC(void,SDL_AudioQuit,(void),(),)
 SDL_DYNAPI_PROC(const char*,SDL_GetCurrentAudioDriver,(void),(),return)
-SDL_DYNAPI_PROC(int,SDL_OpenAudio,(SDL_AudioSpec *a, SDL_AudioSpec *b),(a,b),return)
+SDL_DYNAPI_PROC(int,SDL_OpenAudio,(SDL2_AudioSpec *a, SDL2_AudioSpec *b),(a,b),return)
 SDL_DYNAPI_PROC(int,SDL_GetNumAudioDevices,(int a),(a),return)
 SDL_DYNAPI_PROC(const char*,SDL_GetAudioDeviceName,(int a, int b),(a,b),return)
-SDL_DYNAPI_PROC(SDL_AudioDeviceID,SDL_OpenAudioDevice,(const char *a, int b, const SDL_AudioSpec *c, SDL_AudioSpec *d, int e),(a,b,c,d,e),return)
-SDL_DYNAPI_PROC(SDL_AudioStatus,SDL_GetAudioStatus,(void),(),return)
-SDL_DYNAPI_PROC(SDL_AudioStatus,SDL_GetAudioDeviceStatus,(SDL_AudioDeviceID a),(a),return)
+SDL_DYNAPI_PROC(SDL_AudioDeviceID,SDL_OpenAudioDevice,(const char *a, int b, const SDL2_AudioSpec *c, SDL2_AudioSpec *d, int e),(a,b,c,d,e),return)
+SDL_DYNAPI_PROC(SDL2_AudioStatus,SDL_GetAudioStatus,(void),(),return)
+SDL_DYNAPI_PROC(SDL2_AudioStatus,SDL_GetAudioDeviceStatus,(SDL_AudioDeviceID a),(a),return)
 SDL_DYNAPI_PROC(void,SDL_PauseAudio,(int a),(a),)
 SDL_DYNAPI_PROC(void,SDL_PauseAudioDevice,(SDL_AudioDeviceID a, int b),(a,b),)
-SDL_DYNAPI_PROC(SDL_AudioSpec*,SDL_LoadWAV_RW,(SDL2_RWops *a, int b, SDL_AudioSpec *c, Uint8 **d, Uint32 *e),(a,b,c,d,e),return)
+SDL_DYNAPI_PROC(SDL2_AudioSpec*,SDL_LoadWAV_RW,(SDL2_RWops *a, int b, SDL2_AudioSpec *c, Uint8 **d, Uint32 *e),(a,b,c,d,e),return)
 SDL_DYNAPI_PROC(void,SDL_FreeWAV,(Uint8 *a),(a),)
 SDL_DYNAPI_PROC(int,SDL_BuildAudioCVT,(SDL_AudioCVT *a, SDL_AudioFormat b, Uint8 c, int d, SDL_AudioFormat e, Uint8 f, int g),(a,b,c,d,e,f,g),return)
 SDL_DYNAPI_PROC(int,SDL_ConvertAudio,(SDL_AudioCVT *a),(a),return)
@@ -876,7 +876,7 @@ SDL_DYNAPI_PROC(int,SDL_isgraph,(int a),(a),return)
 #ifdef __ANDROID__
 SDL_DYNAPI_PROC(int,SDL_AndroidShowToast,(const char *a, int b, int c, int d, int e),(a,b,c,d,e),return)
 #endif
-SDL_DYNAPI_PROC(int,SDL_GetAudioDeviceSpec,(int a, int b, SDL_AudioSpec *c),(a,b,c),return)
+SDL_DYNAPI_PROC(int,SDL_GetAudioDeviceSpec,(int a, int b, SDL2_AudioSpec *c),(a,b,c),return)
 SDL_DYNAPI_PROC(void,SDL_TLSCleanup,(void),(),)
 SDL_DYNAPI_PROC(void,SDL_SetWindowAlwaysOnTop,(SDL_Window *a, SDL_bool b),(a,b),)
 SDL_DYNAPI_PROC(int,SDL_FlashWindow,(SDL_Window *a, SDL_FlashOperation b),(a,b),return)
@@ -962,7 +962,7 @@ SDL_DYNAPI_PROC(int,SDL_GDKRunApp,(SDL_main_func a, void *b),(a,b),return)
 #endif
 SDL_DYNAPI_PROC(void,SDL_GetOriginalMemoryFunctions,(SDL_malloc_func *a, SDL_calloc_func *b, SDL_realloc_func *c, SDL_free_func *d),(a,b,c,d),)
 SDL_DYNAPI_PROC(void,SDL_ResetKeyboard,(void),(),)
-SDL_DYNAPI_PROC(int,SDL_GetDefaultAudioInfo,(char **a, SDL_AudioSpec *b, int c),(a,b,c),return)
+SDL_DYNAPI_PROC(int,SDL_GetDefaultAudioInfo,(char **a, SDL2_AudioSpec *b, int c),(a,b,c),return)
 SDL_DYNAPI_PROC(int,SDL_GetPointDisplayIndex,(const SDL_Point *a),(a),return)
 SDL_DYNAPI_PROC(int,SDL_GetRectDisplayIndex,(const SDL_Rect *a),(a),return)
 SDL_DYNAPI_PROC(SDL_bool,SDL_ResetHint,(const char *a),(a),return)
diff --git a/src/sdl2_compat.c b/src/sdl2_compat.c
index c926c5c..87e2d29 100644
--- a/src/sdl2_compat.c
+++ b/src/sdl2_compat.c
@@ -1090,6 +1090,17 @@ struct SDL2_hid_device_info
     struct SDL2_hid_device_info *next;
 };
 
+typedef struct AudioDeviceInfo
+{
+    SDL_AudioDeviceID devid;
+    char *name;
+} AudioDeviceInfo;
+
+typedef struct AudioDeviceList
+{
+    AudioDeviceInfo *devices;
+    int num_devices;
+} AudioDeviceList;
 
 
 /* Some SDL2 state we need to keep... */
@@ -1107,6 +1118,12 @@ static int num_sensors = 0;
 static SDL_mutex *joystick_lock = NULL;
 static SDL_mutex *sensor_lock = NULL;
 
+static SDL_Mutex *AudioDeviceLock = NULL;
+static SDL2_AudioStream *AudioOpenDevices[16];  /* SDL2 had a limit of 16 simultaneous devices opens (and the first slot was for the 1.2 legacy interface). We track these as _SDL2_ audio streams. */
+static AudioDeviceList AudioSDL3OutputDevices;
+static AudioDeviceList AudioSDL3CaptureDevices;
+
+
 /* Functions! */
 
 static int SDLCALL EventFilter3to2(void *userdata, SDL_Event *event3);
@@ -1130,6 +1147,11 @@ SDL2Compat_InitOnStartup(void)
         goto fail;
     }
 
+    AudioDeviceLock = SDL3_CreateMutex();
+    if (AudioDeviceLock == NULL) {
+        goto fail;
+    }
+
     SDL3_SetEventFilter(EventFilter3to2, NULL);
 
     SDL3_SetHint("SDL_WINDOWS_DPI_SCALING", 0);
@@ -1150,7 +1172,9 @@ SDL2Compat_InitOnStartup(void)
     if (joystick_lock) {
         SDL3_DestroyMutex(joystick_lock);
     }
-
+    if (AudioDeviceLock) {
+        SDL3_DestroyMutex(AudioDeviceLock);
+    }
     return 0;
 }
 
@@ -2151,21 +2175,39 @@ SDL_LoadFile_RW(SDL2_RWops *rwops2, size_t *datasize, int freesrc)
     return retval;
 }
 
-DECLSPEC SDL_AudioSpec *SDLCALL
-SDL_LoadWAV_RW(SDL2_RWops *rwops2, int freesrc, SDL_AudioSpec *spec, Uint8 **audio_buf, Uint32 *audio_len)
+DECLSPEC SDL2_AudioSpec *SDLCALL
+SDL_LoadWAV_RW(SDL2_RWops *rwops2, int freesrc, SDL2_AudioSpec *spec2, Uint8 **audio_buf, Uint32 *audio_len)
 {
-    SDL_AudioSpec *retval = NULL;
-    SDL_RWops *rwops3 = RWops2to3(rwops2);
-    if (rwops3) {
-        retval = SDL3_LoadWAV_RW(rwops3, freesrc ? SDL_TRUE : SDL_FALSE, spec, audio_buf, audio_len);
-        if (!freesrc) {
-            SDL3_DestroyRW(rwops3);  /* don't close it because that'll close the SDL2_RWops. */
-        }
-    } else if (rwops2) {
-        if (freesrc) {
-            SDL_RWclose(rwops2);
+    SDL2_AudioSpec *retval = NULL;
+
+    if (spec2 == NULL) {
+        SDL3_InvalidParamError("spec");
+    } else {
+        SDL_RWops *rwops3 = RWops2to3(rwops2);
+        if (rwops3) {
+            SDL_AudioSpec spec3;
+            const int rc = SDL3_LoadWAV_RW(rwops3, freesrc ? SDL_TRUE : SDL_FALSE, &spec3, audio_buf, audio_len);
+            SDL3_zerop(spec2);
+            if (rc == 0) {
+                spec2->format = spec3.format;
+                spec2->channels = spec3.channels;
+                spec2->freq = spec3.freq;
+                spec2->samples = 4096; /* This is what SDL2 hardcodes, also. */
+                spec2->silence = SDL3_GetSilenceValueForFormat(spec3.format);
+                retval = spec2;
+            }
+            if (!freesrc) {
+                SDL3_DestroyRW(rwops3);  /* don't close it because that'll close the SDL2_RWops. */
+            } else {
+                freesrc = 0;  /* this was handled already, don't do it again. */
+            }
         }
     }
+
+    if (rwops2 && freesrc) {
+        SDL_RWclose(rwops2);
+    }
+
     return retval;
 }
 
@@ -3463,66 +3505,512 @@ SDL_GL_GetSwapInterval(void)
     return val;
 }
 
-/* !!! FIXME: move these up with the other globals. */
-static SDL_AudioDeviceID g_audio_id = 0;
-static SDL_AudioSpec g_audio_spec;
+static Uint16 GetDefaultSamplesFromFreq(int freq)
+{
+    /* Pick a default of ~46 ms at desired frequency */
+    /* !!! FIXME: remove this when the non-Po2 resampling is in. */
+    const Uint16 max_sample = (freq / 1000) * 46;
+    Uint16 current_sample = 1;
+    while (current_sample < max_sample) {
+        current_sample *= 2;
+    }
+    return current_sample;
+}
 
-DECLSPEC SDL_AudioDeviceID SDLCALL
-SDL_OpenAudioDevice(const char *device, int iscapture, const SDL_AudioSpec *desired, SDL_AudioSpec *obtained, int allowed_changes)
+static int GetNumAudioDevices(int iscapture)
+{
+    AudioDeviceList newlist;
+    AudioDeviceList *list = iscapture ? &AudioSDL3CaptureDevices : &AudioSDL3OutputDevices;
+    SDL_AudioDeviceID *devices;
+    int num_devices;
+    int i;
+
+    /* SDL_GetNumAudioDevices triggers a device redetect in SDL2, so we'll just build our list from here. */
+    devices = iscapture ? SDL3_GetAudioCaptureDevices(&num_devices) : SDL3_GetAudioOutputDevices(&num_devices);
+    if (!devices) {
+        return list->num_devices;  /* just return the existing one for now. Oh well. */
+    }
+
+    SDL3_zero(newlist);
+    if (num_devices > 0) {
+        newlist.num_devices = num_devices;
+        newlist.devices = (AudioDeviceInfo *) SDL3_malloc(sizeof (AudioDeviceInfo) * num_devices);
+        if (!newlist.devices) {
+            SDL3_free(devices);
+            return list->num_devices;  /* just return the existing one for now. Oh well. */
+        }
+
+        for (i = 0; i < num_devices; i++) {
+            char *newname = SDL3_GetAudioDeviceName(devices[i]);
+            char *fullname = NULL;
+            if (newname == NULL) {
+                /* ugh, whatever, just make up a name. */
+                newname = SDL3_strdup("Unidentified device");
+            }
+
+            /* Device names must be unique in SDL2, as that's how we open them.
+               SDL2 took serious pains to try to add numbers to the end of duplicate device names ("SoundBlaster Pro" and then "SoundBlaster Pro (2)"),
+               but here we're just putting the actual SDL3 instance id at the end of everything. Good enough. I hope. */
+            if (!newname || (SDL3_asprintf(&fullname, "%s (id=%u)", newname, (unsigned int) devices[i]) < 0)) {
+                /* we're in real trouble now.  :/  */
+                int j;
+                for (j = 0; j < i; j++) {
+                    SDL3_free(newlist.devices[i].name);
+                }
+                SDL3_free(devices);
+                SDL3_free(newname);
+                SDL3_free(fullname);
+                SDL3_OutOfMemory();
+                return list->num_devices;  /* just return the existing one for now. Oh well. */
+            }
+
+            SDL3_free(newname);
+            newlist.devices[i].devid = devices[i];
+            newlist.devices[i].name = fullname;
+        }
+    }
+
+    for (i = 0; i < list->num_devices; i++) {
+        SDL3_free(list->devices[i].name);
+    }
+    SDL3_free(list->devices);
+
+    SDL3_memcpy(list, &newlist, sizeof (AudioDeviceList));
+    return num_devices;
+}
+
+DECLSPEC int SDLCALL
+SDL_GetNumAudioDevices(int iscapture)
 {
-    SDL_AudioSpec desired3;
-    SDL3_memcpy(&desired3, desired, sizeof (SDL_AudioSpec));
+    int retval;
+
+    if (!SDL3_GetCurrentAudioDriver()) {
+        return SDL3_SetError("Audio subsystem is not initialized");
+    }
 
-    if ((desired3.format == SDL2_AUDIO_U16LSB) || (desired3.format == SDL2_AUDIO_U16MSB)) {
-        if (allowed_changes & SDL_AUDIO_ALLOW_FORMAT_CHANGE) {
-            desired3.format = SDL_AUDIO_S16SYS;  /* just pick a supported format instead. */
+    SDL3_LockMutex(AudioDeviceLock);
+    retval = GetNumAudioDevices(iscapture);
+    SDL3_UnlockMutex(AudioDeviceLock);
+    return retval;
+}
+
+DECLSPEC const char * SDLCALL
+SDL_GetAudioDeviceName(int index, int iscapture)
+{
+    const char *retval = NULL;
+    AudioDeviceList *list;
+
+    if (!SDL3_GetCurrentAudioDriver()) {
+        SDL3_SetError("Audio subsystem is not initialized");
+        return NULL;
+    }
+
+    SDL3_LockMutex(AudioDeviceLock);
+    list = iscapture ? &AudioSDL3CaptureDevices : &AudioSDL3OutputDevices;
+    if ((index < 0) || (index >= list->num_devices)) {
+        SDL3_InvalidParamError("index");
+    } else {
+        retval = list->devices[index].name;
+    }
+    SDL3_UnlockMutex(AudioDeviceLock);
+
+    return retval;
+}
+
+DECLSPEC int SDLCALL
+SDL_GetAudioDeviceSpec(int index, int iscapture, SDL2_AudioSpec *spec2)
+{
+    AudioDeviceList *list;
+    SDL_AudioSpec spec3;
+    int retval = -1;
+
+    if (spec2 == NULL) {
+        return SDL3_InvalidParamError("spec");
+    } else if (!SDL3_GetCurrentAudioDriver()) {
+        return SDL3_SetError("Audio subsystem is not initialized");
+    }
+
+    SDL3_LockMutex(AudioDeviceLock);
+    list = iscapture ? &AudioSDL3CaptureDevices : &AudioSDL3OutputDevices;
+    if ((index < 0) || (index >= list->num_devices)) {
+        SDL3_InvalidParamError("index");
+    } else {
+        retval = SDL3_GetAudioDeviceFormat(list->devices[index].devid, &spec3);
+    }
+    SDL3_UnlockMutex(AudioDeviceLock);
+
+    if (retval == 0) {
+        SDL3_zerop(spec2);
+        spec2->format = spec3.format;
+        spec2->channels = spec3.channels;
+        spec2->freq = spec3.freq;
+    }
+
+    return retval;
+}
+
+DECLSPEC int SDLCALL
+SDL_GetDefaultAudioInfo(char **name, SDL2_AudioSpec *spec2, int iscapture)
+{
+    SDL_AudioSpec spec3;
+    int retval;
+
+    if (spec2 == NULL) {
+        return SDL3_InvalidParamError("spec");
+    } else if (!SDL3_GetCurrentAudioDriver()) {
+        return SDL3_SetError("Audio subsystem is not initialized");
+    }
+
+    retval = SDL3_GetAudioDeviceFormat(iscapture ? SDL_AUDIO_DEVICE_DEFAULT_CAPTURE : SDL_AUDIO_DEVICE_DEFAULT_OUTPUT, &spec3);
+    if (retval == 0) {
+        if (name) {
+            *name = SDL3_strdup("System default");  /* the default device can change to different physical hardware on-the-fly in SDL3, so we don't provide a name for it. */
+            if (*name == NULL) {
+                return SDL3_OutOfMemory();
+            }
+        }
+
+        SDL3_zerop(spec2);
+        spec2->format = spec3.format;
+        spec2->channels = spec3.channels;
+        spec2->freq = spec3.freq;
+    }
+
+    return retval;
+}
+
+static SDL_AudioFormat ParseAudioFormat(const char *string)
+{
+#define CHECK_FMT_STRING(x) if (SDL3_strcmp(string, #x) == 0) { return SDL_AUDIO_##x; }
+    CHECK_FMT_STRING(U8);
+    CHECK_FMT_STRING(S8);
+    CHECK_FMT_STRING(S16LSB);
+    CHECK_FMT_STRING(S16MSB);
+    CHECK_FMT_STRING(S16SYS);
+    CHECK_FMT_STRING(S16);
+    CHECK_FMT_STRING(S32LSB);
+    CHECK_FMT_STRING(S32MSB);
+    CHECK_FMT_STRING(S32SYS);
+    CHECK_FMT_STRING(S32);
+    CHECK_FMT_STRING(F32LSB);
+    CHECK_FMT_STRING(F32MSB);
+    CHECK_FMT_STRING(F32SYS);
+    CHECK_FMT_STRING(F32);
+#undef CHECK_FMT_STRING
+
+    /* removed U16 support in SDL3... */
+    if (SDL3_strcmp(string, "U16LSB") == 0) { return SDL_AUDIO_S16LSB & ~SDL_AUDIO_MASK_SIGNED; }
+    if (SDL3_strcmp(string, "U16MSB") == 0) { return SDL_AUDIO_S16MSB & ~SDL_AUDIO_MASK_SIGNED; }
+    if (SDL3_strcmp(string, "U16SYS") == 0) { return SDL_AUDIO_S16SYS & ~SDL_AUDIO_MASK_SIGNED; }
+    if (SDL3_strcmp(string, "U16") == 0) { return SDL_AUDIO_S16 & ~SDL_AUDIO_MASK_SIGNED; }
+
+    return 0;
+}
+
+static int PrepareAudiospec(const SDL2_AudioSpec *orig2, SDL2_AudioSpec *prepared2)
+{
+    SDL3_copyp(prepared2, orig2);
+
+    if (orig2->freq == 0) {
+        static const int DEFAULT_FREQ = 22050;
+        const char *env = SDL3_getenv("SDL_AUDIO_FREQUENCY");
+        if (env != NULL) {
+            int freq = SDL3_atoi(env);
+            prepared2->freq = freq != 0 ? freq : DEFAULT_FREQ;
+        } else {
+            prepared2->freq = DEFAULT_FREQ;
+        }
+    }
+
+    if (orig2->format == 0) {
+        const char *env = SDL3_getenv("SDL_AUDIO_FORMAT");
+        if (env != NULL) {
+            const SDL_AudioFormat format = ParseAudioFormat(env);
+            prepared2->format = format != 0 ? format : SDL_AUDIO_S16;
+        } else {
+            prepared2->format = SDL_AUDIO_S16;
+        }
+    }
+
+    if (orig2->channels == 0) {
+        const char *env = SDL3_getenv("SDL_AUDIO_CHANNELS");
+        if (env != NULL) {
+            Uint8 channels = (Uint8)SDL3_atoi(env);
+            prepared2->channels = channels != 0 ? channels : 2;
+        } else {
+            prepared2->channels = 2;
+        }
+    } else if (orig2->channels > 8) {
+        SDL_SetError("Unsupported number of audio channels.");
+        return 0;
+    }
+
+    if (orig2->samples == 0) {
+        const char *env = SDL3_getenv("SDL_AUDIO_SAMPLES");
+        if (env != NULL) {
+            Uint16 samples = (Uint16)SDL3_atoi(env);
+            prepared2->samples = samples != 0 ? samples : GetDefaultSamplesFromFreq(prepared2->freq);
         } else {
-            /* technically we should convert this behind the scenes, but all of this is getting
-               replaced with a different interface in SDL3 soon, so it's not worth
-               hooking the audio callback to manage it before that work is done. */
-            SDL3_SetError("U16 audio unsupported");
+            prepared2->samples = GetDefaultSamplesFromFreq(prepared2->freq);
+        }
+    }
+
+    /* Calculate the silence and size of the audio specification */
+    if ((prepared2->format == SDL_AUDIO_U8) || (prepared2->format == (SDL_AUDIO_S16LSB & ~SDL_AUDIO_MASK_SIGNED)) || (prepared2->format == (SDL_AUDIO_S16MSB & ~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;
+
+    return 1;
+}
+
+static void SDLCALL SDL2AudioDeviceQueueingCallback(SDL_AudioStream *stream3, int approx_request, void *userdata)
+{
+    SDL2_AudioStream *stream2 = (SDL2_AudioStream *) userdata;
+    Uint8 *buffer;
+
+    SDL_assert(stream2 != NULL);
+    SDL_assert(stream3 == stream2->stream3);
+    SDL_assert(stream2->dataqueue3 != NULL);
+
+    buffer = (Uint8 *) SDL3_malloc(approx_request);
+    if (!buffer) {
+        return;  /* oh well */
+    }
+
+    if (stream2->iscapture) {
+        const int br = SDL_AudioStreamGet(stream2, buffer, approx_request);
+        if (br > 0) {
+            SDL3_PutAudioStreamData(stream2->dataqueue3, buffer, br);
+        }
+    } else {
+        const int br = SDL3_GetAudioStreamData(stream2->dataqueue3, buffer, approx_request);
+        if (br > 0) {
+            SDL_AudioStreamPut(stream2, buffer, br);
+        }
+    }
+
+    SDL3_free(buffer);
+}
+
+static void SDLCALL SDL2AudioDeviceCallbackBridge(SDL_AudioStream *stream3, int approx_request, void *userdata)
+{
+    SDL2_AudioStream *stream2 = (SDL2_AudioStream *) userdata;
+    Uint8 *buffer;
+
+    SDL_assert(stream2 != NULL);
+    SDL_assert(stream3 == stream2->stream3);
+    SDL_assert(stream2->dataqueue3 == NULL);
+
+    buffer = (Uint8 *) SDL3_malloc(approx_request);
+    if (!buffer) {
+        return;  /* oh well */
+    }
+
+    if (stream2->iscapture) {
+        const int br = SDL_AudioStreamGet(stream2, buffer, approx_request);
+        stream2->callback2(stream2->callback2_userdata, buffer, br);
+    } else {
+        stream2->callback2(stream2->callback2_userdata, buffer, approx_request);
+        SDL_AudioStreamPut(stream2, buffer, approx_request);
+    }
+
+    SDL3_free(buffer);
+}
+
+static SDL_AudioDeviceID OpenAudioDeviceLocked(const char *devname, int iscapture,
+                                               const SDL2_AudioSpec *desired2, SDL2_AudioSpec *obtained2,
+                                               int allowed_changes, int min_id)
+{
+    /* we use an SDL2 audio stream for this, since it'll use an SDL3 audio stream behind the scenes, but also support the removed-from-SDL3 U16 audio format. */
+    SDL2_AudioStream *stream2;
+    SDL_AudioDeviceID device3 = 0;
+    SDL_AudioSpec spec3;
+    int id;
+
+    SDL_assert(obtained2 != NULL);  /* we checked this before locking. */
+
+    /* Find an available device ID... */
+    for (id = min_id - 1; id < (int)SDL_arraysize(AudioOpenDevices); id++) {
+        if (AudioOpenDevices[id] == NULL) {
+            break;
+        }
+    }
+
+    if (id == SDL_arraysize(AudioOpenDevices)) {
+        SDL3_SetError("Too many open audio devices");
+        return 0;
+    }
+
+    /* (also note that SDL2 doesn't check if `desired2` is NULL before dereferencing, either.) */
+    if (!PrepareAudiospec(desired2, obtained2)) {
+        return 0;
+    }
+
+    /* If app doesn't care about a specific device, let the user override. */
+    if (devname == NULL) {
+        devname = SDL3_getenv("SDL_AUDIO_DEVICE_NAME");
+    }
+
+    if (devname == NULL) {
+        device3 = iscapture ? SDL_AUDIO_DEVICE_DEFAULT_CAPTURE : SDL_AUDIO_DEVICE_DEFAULT_OUTPUT;
+    } else {
+        AudioDeviceList *list = iscapture ? &AudioSDL3CaptureDevices : &AudioSDL3OutputDevices;
+        const int total = list->num_devices;
+        int i;
+        for (i = 0; i < total; i++) {
+            if (SDL3_strcmp(list->devices[i].name, devname) == 0) {
+                device3 = list->devices[i].devid;
+                break;
+            }
+        }
+        if (device3 == 0) {
+            SDL3_SetError("No such device.");
             return 0;
         }
     }
 
-    return SDL3_OpenAudioDevice(device, iscapture, &desired3, obtained, allowed_changes);
+    spec3.format = obtained2->format;
+    spec3.channels = obtained2->channels;
+    spec3.freq = obtained2->freq;
+
+    /* Don't try to open the SDL3 audio device with the (abandoned) U16 formats... */
+    if (spec3.format == SDL2_AUDIO_U16LSB) {
+        spec3.format = SDL_AUDIO_S16LSB;
+    } else if (spec3.format == SDL2_AUDIO_U16MSB) {
+        spec3.format = SDL_AUDIO_S16MSB;
+    }
+
+    device3 = SDL3_OpenAudioDevice(device3, &spec3);
+    if (device3 == 0) {
+        return 0;
+    }
+
+    SDL3_PauseAudioDevice(device3);  /* they start paused in SDL2 */
+    SDL3_GetAudioDeviceFormat(device3, &spec3);
+
+    if ((spec3.format != obtained2->format) && (allowed_changes & SDL2_AUDIO_ALLOW_FORMAT_CHANGE)) {
+        obtained2->format = spec3.format;
+    }
+    if ((spec3.channels != obtained2->channels) && (allowed_changes & SDL2_AUDIO_ALLOW_CHANNELS_CHANGE)) {
+        obtained2->freq = spec3.channels;
+    }
+    if ((spec3.freq != obtained2->freq) && (allowed_changes & SDL2_AUDIO_ALLOW_FREQUENCY_CHANGE)) {
+        obtained2->freq = spec3.freq;
+    }
+
+    if (iscapture) {
+        stream2 = SDL_NewAudioStream(spec3.format, spec3.channels, spec3.freq, obtained2->format, obtained2->channels, obtained2->freq);
+    } else {
+        stream2 = SDL_NewAudioStream(obtained2->format, obtained2->channels, obtained2->freq, spec3.format, spec3.channels, spec3.freq);
+    }
+
+    if (!stream2) {
+        SDL3_CloseAudioDevice(device3);
+        return 0;
+    }
+
+    if (desired2->callback) {
+        stream2->callback2 = desired2->callback;
+        stream2->callback2_userdata = desired2->userdata;
+        if (iscapture) {
+            SDL3_SetAudioStreamPutCallback(stream2->stream3, SDL2AudioDeviceCallbackBridge, stream2);
+        } else {
+            SDL3_SetAudioStreamGetCallback(stream2->stream3, SDL2AudioDeviceCallbackBridge, stream2);
+        }
+    } else {
+        /* SDL2 treats this as a simple data queue that doesn't convert before the audio callback gets it,
+           so just make a second audiostream with no conversion and let it work like a flexible buffer. */
+        stream2->dataqueue3 = SDL3_CreateAudioStream(&spec3, &spec3);
+        if (!stream2->dataqueue3) {
+            SDL3_CloseAudioDevice(device3);
+            SDL_FreeAudioStream(stream2);
+        }
+        if (iscapture) {
+            SDL3_SetAudioStreamPutCallback(stream2->stream3, SDL2AudioDeviceQueueingCallback, stream2);
+        } else {
+            SDL3_SetAudioStreamGetCallback(stream2->stream3, SDL2AudioDeviceQueueingCallback, stream2);
+        }
+    }
+
+    if (SDL3_BindAudioStream(device3, stream2->stream3) < 0) {
+        SDL_FreeAudioStream(stream2);
+        SDL3_CloseAudioDevice(device3);
+        return 0;
+    }
+
+    stream2->iscapture = iscapture ? SDL_TRUE : SDL_FALSE;
+    AudioOpenDevices[id] = stream2;
+
+    return id + 1;
+}
+
+static SDL_AudioDeviceID OpenAudioDevice(const char *devname, int iscapture,
+                                         const SDL2_AudioSpec *desired2, SDL2_AudioSpec *obtained2,
+                                         int allowed_changes, int min_id)
+{
+    SDL2_AudioSpec _obtained2;
+    SDL_AudioDeviceID retval;
+
+    if (!SDL3_GetCurrentAudioDriver()) {
+        SDL_SetError("Audio subsystem is not initialized");
+        return 0;
+    }
+
+    if (!obtained2) {
+        obtained2 = &_obtained2;
+    }
+
+    SDL3_LockMutex(AudioDeviceLock);
+    retval = OpenAudioDeviceLocked(devname, iscapture, desired2, obtained2, allowed_changes, min_id);
+    SDL3_UnlockMutex(AudioDeviceLock);
+
+    return retval;
+}
+
+
+DECLSPEC SDL_AudioDeviceID SDLCALL
+SDL_OpenAudioDevice(const char *device, int iscapture, const SDL2_AudioSpec *desired2, SDL2_AudioSpec *obtained2, int allowed_changes)
+{
+    return OpenAudioDevice(device, iscapture, desired2, obtained2, allowed_changes, 2);
 }
 
 DECLSPEC int SDLCALL
-SDL_OpenAudio(SDL_AudioSpec *desired, SDL_AudioSpec *obtained)
+SDL_OpenAudio(SDL2_AudioSpec *desired2, SDL2_AudioSpec *obtained2)
 {
     SDL_AudioDeviceID id = 0;
 
     /* Start up the audio driver, if necessary. This is legacy behaviour! */
-    if (!SDL_WasInit(SDL_INIT_AUDIO)) {
+    if (!SDL3_WasInit(SDL_INIT_AUDIO)) {
         if (SDL3_InitSubSystem(SDL_INIT_AUDIO) < 0) {
             return -1;
         }
     }
 
-    if (g_audio_id > 0) {
+    if (AudioOpenDevices[0] != NULL) {
         return SDL3_SetError("Audio device is already opened");
     }
 
-    if (obtained) {
-        id = SDL_OpenAudioDevice(NULL, 0, desired, obtained, SDL_AUDIO_ALLOW_ANY_CHANGE);
-
-        g_audio_spec = *obtained;
+    if (obtained2) {
+        id = OpenAudioDevice(NULL, 0, desired2, obtained2, SDL2_AUDIO_ALLOW_ANY_CHANGE, 1);
     } else {
-        SDL_AudioSpec _obtained;
-        SDL3_zero(_obtained);
-        id = SDL_OpenAudioDevice(NULL, 0, desired, &_obtained, 0);
+        SDL2_AudioSpec _obtained2;
+        SDL3_zero(_obtained2);
+        id = OpenAudioDevice(NULL, 0, desired2, &_obtained2, 0, 1);
+
         /* On successful open, copy calculated values into 'desired'. */
         if (id > 0) {
-            desired->size = _obtained.size;
-            desired->silence = _obtained.silence;
+            desired2->size = _obtained2.size;
+            desired2->silence = _obtained2.silence;
         }
-
-        g_audio_spec = _obtained;
     }
 
     if (id > 0) {
-        g_audio_id = id;
         return 0;
     }
     return -1;
@@ -3569,7 +4057,9 @@ SDL_NewAudioStream(const SDL_AudioFormat real_src_format, const Uint8 src_channe
 {
     SDL_AudioFormat src_format = real_src_format;
     SDL_AudioFormat dst_format = real_dst_format;
-    SDL2_AudioStream *retval = (SDL2_AudioStream *) SDL3_malloc(sizeof (SDL2_AudioStream));
+    SDL2_AudioStream *retval = (SDL2_AudioStream *) SDL3_calloc(1, sizeof (SDL2_AudioStream));
+    SDL_AudioSpec srcspec3, dstspec3;
+
     if (!retval) {
         SDL3_OutOfMemory();
         return NULL;
@@ -3583,7 +4073,13 @@ SDL_NewAudioStream(const SDL_AudioFormat real_src_format, const Uint8 src_channe
         dst_format = SDL_AUDIO_S16SYS;
     }
 
-    retval->stream3 = SDL3_CreateAudioStream(src_format, src_channels, src_rate, dst_format, dst_channels, dst_rate);
+    srcspec3.format = src_format;
+    srcspec3.channels = src_channels;
+    srcspec3.freq = src_rate;
+    dstspec3.format = dst_format;
+    dstspec3.channels = dst_channels;
+    dstspec3.freq = dst_rate;
+    retval->stream3 = SDL3_CreateAudioStream(&srcspec3, &dstspec3);
     if (retval->stream3 == NULL) {
         SDL3_free(retval);
         return NULL;
@@ -3659,6 +4155,7 @@ SDL_FreeAudioStream(SDL2_AudioStream *stream2)
 {
     if (stream2) {
         SDL3_DestroyAudioStream(stream2->stream3);
+        SDL3_DestroyAudioStream(stream2->dataqueue3);
         SDL3_free(stream2);
     }
 }
@@ -3690,84 +4187,131 @@ SDL_MixAudioFormat(Uint8 *dst, const Uint8 *src, SDL_AudioFormat format, Uint32
     }
 }
 
-/*
- * Moved here from SDL_mixer.c, since it relies on internals of an opened
- *  audio device (and is deprecated, by the way!).
- */
+static SDL2_AudioStream *GetOpenAudioDevice(SDL_AudioDeviceID id)
+{
+    id--;
+    if ((id >= SDL_arraysize(AudioOpenDevices)) || (AudioOpenDevices[id] == NULL)) {
+        SDL3_SetError("Invalid audio device ID");
+        return NULL;
+    }
+    return AudioOpenDevices[id];
+}
+
 DECLSPEC void SDLCALL
 SDL_MixAudio(Uint8 *dst, const Uint8 *src, Uint32 len, int volume)
 {
-    /* Mix the user-level audio format */
-    if (g_audio_id > 0) {
-        SDL_MixAudioFormat(dst, src, g_audio_spec.format, len, volume);  /* call the sdl2-compat version, since it needs to handle U16 audio data. */
+    SDL2_AudioStream *stream2 = GetOpenAudioDevice(1);
+    if (stream2 != NULL) {
+        SDL_MixAudioFormat(dst, src, stream2->dst_format, len, volume);  /* call the sdl2-compat version, since it needs to handle U16 audio data. */
     }
 }
 
 DECLSPEC Uint32 SDLCALL
 SDL_GetQueuedAudioSize(SDL_AudioDeviceID dev)
 {
-    SDL_AudioDeviceID id = dev == 1 ? g_audio_id : dev;
-    return SDL3_GetQueuedAudioSize(id);
+    SDL2_AudioStream *stream2 = GetOpenAudioDevice(dev);
+    return (stream2 && (stream2->dataqueue3 != NULL)) ? SDL3_GetAudioStreamAvailable(stream2->dataqueue3) : 0;
 }
 
 DECLSPEC int SDLCALL
 SDL_QueueAudio(SDL_AudioDeviceID dev, const void *data, Uint32 len)
 {
-    SDL_AudioDeviceID id = dev == 1 ? g_audio_id : dev;
-    return SDL3_QueueAudio(id, data, len);
+    SDL2_AudioStream *stream2 = GetOpenAudioDevice(dev);
+    if (!stream2) {
+        return -1;
+    } else if (stream2->iscapture) {
+        return SDL3_SetError("This is a capture device, queueing not allowed");
+    } else if (stream2->dataqueue3 == NULL) {
+        return SDL3_SetError("Audio device has a callback, queueing not allowed");
+    } else if (len == 0) {
+        return 0;  /* nothing to do. */
+    }
+
+    SDL_assert(stream2->dataqueue3 != NULL);
+    return SDL3_PutAudioStreamData(stream2->dataqueue3, data, len);
 }
 
 DECLSPEC Uint32 SDLCALL
 SDL_DequeueAudio(SDL_AudioDeviceID dev, void *data, Uint32 len)
 {
-    SDL_AudioDeviceID id = dev == 1 ? g_audio_id : dev;
-    return SDL3_DequeueAudio(id, data, len);
+    SDL2_AudioStream *stream2 = GetOpenAudioDevice(dev);
+    if ((len == 0) ||                      /* nothing to do? */
+        (!stream2) ||                      /* called with bogus device id */
+        (!stream2->iscapture) ||           /* playback devices can't dequeue */
+        (stream2->dataqueue3 == NULL)) {   /* not set for queueing */
+        return 0;                          /* just report zero bytes dequeued. */
+    }
+
+    SDL_assert(stream2->dataqueue3 != NULL);
+    return SDL3_GetAudioStreamData(stream2->dataqueue3, data, len);
 }
 
 DECLSPEC void SDLCALL
 SDL_ClearQueuedAudio(SDL_AudioDeviceID dev)
 {
-    SDL_AudioDeviceID id = dev == 1 ? g_audio_id : dev;
-    SDL3_ClearQueuedAudio(id);
+    SDL2_AudioStream *stream2 = GetOpenAudioDevice(dev);
+    if (stream2 && stream2->dataqueue3) {
+        SDL3_ClearAudioStream(stream2->dataqueue3);
+    }
 }
 
 DECLSPEC void SDLCALL
 SDL_PauseAudioDevice(SDL_AudioDeviceID dev, int pause_on)
 {
-    SDL_AudioDeviceID id = dev == 1 ? g_audio_id : dev;
-    if (pause_on) {
-        SDL3_PauseAudioDevice(id);
-    } else {
-        SDL3_PlayAudioDevice(id);
+    SDL2_AudioStream *stream2 = 

(Patch may be truncated, please check the link at the top of this post.)