SDL: SDL_audio.c: Fix crash if we switch from direct output to streaming, and if the buffersizes change.

From eb1b9b418cbcc777207e7b1672c7fe94df3bcd11 Mon Sep 17 00:00:00 2001
From: jfmu <[EMAIL REDACTED]>
Date: Thu, 4 Jan 2024 01:30:33 +0100
Subject: [PATCH] SDL_audio.c: Fix crash if we switch from direct output to
 streaming, and if the buffersizes change.

---
 src/audio/SDL_audio.c | 33 +++++++++++++++++++++++++++++++--
 1 file changed, 31 insertions(+), 2 deletions(-)

diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c
index a8fd66785896..6e575bd4bf9e 100644
--- a/src/audio/SDL_audio.c
+++ b/src/audio/SDL_audio.c
@@ -677,6 +677,7 @@ static int SDLCALL SDL_RunAudio(void *userdata)
     SDL_AudioCallback callback = device->callbackspec.callback;
     int data_len = 0;
     Uint8 *data;
+    Uint8 *device_buf_keepsafe = NULL;
 
     SDL_assert(!device->iscapture);
 
@@ -703,8 +704,16 @@ static int SDLCALL SDL_RunAudio(void *userdata)
 
         /* Fill the current buffer with sound */
         if (!device->stream && SDL_AtomicGet(&device->enabled)) {
-            SDL_assert(data_len == device->spec.size);
             data = current_audio.impl.GetDeviceBuf(device);
+
+            if (device->stream && SDL_AtomicGet(&device->enabled)) {
+                /* Oops. Audio device reset and now we suddenly use a stream, */
+                /* so save this devicebuf for later, to prevent de-sync */
+                if (data != NULL) {
+                    device_buf_keepsafe = data;
+                }
+                data = NULL;
+            }
         } else {
             /* if the device isn't enabled, we still write to the
                work_buffer, so the app's callback will fire with
@@ -735,7 +744,19 @@ static int SDLCALL SDL_RunAudio(void *userdata)
 
             while (SDL_AudioStreamAvailable(device->stream) >= ((int)device->spec.size)) {
                 int got;
-                data = SDL_AtomicGet(&device->enabled) ? current_audio.impl.GetDeviceBuf(device) : NULL;
+                if (SDL_AtomicGet(&device->enabled)) {
+                    /* if device reset occured - a switch from direct output to streaming */
+                    /* use the already aquired device buffer */
+                    if (device_buf_keepsafe) {
+                        data = device_buf_keepsafe;
+                        device_buf_keepsafe = NULL;
+                    } else {
+                        /* else - normal flow, just acquire the device buffer here */
+                        data = current_audio.impl.GetDeviceBuf(device);
+                    }
+                } else {
+                    data = NULL;
+                }
                 got = SDL_AudioStreamGet(device->stream, data ? data : device->work_buffer, device->spec.size);
                 SDL_assert((got <= 0) || (got == device->spec.size));
 
@@ -750,6 +771,14 @@ static int SDLCALL SDL_RunAudio(void *userdata)
                     current_audio.impl.WaitDevice(device);
                 }
             }
+
+            /* it seems resampling was not fast enough, device_buf_keepsafe was not released yet, so play silence here */
+            if (device_buf_keepsafe) {
+                SDL_memset(device_buf_keepsafe, device->spec.silence, device->spec.size);
+                current_audio.impl.PlayDevice(device);
+                current_audio.impl.WaitDevice(device);
+                device_buf_keepsafe = NULL;
+            }
         } else if (data == device->work_buffer) {
             /* nothing to do; pause like we queued a buffer to play. */
             const Uint32 delay = ((device->spec.samples * 1000) / device->spec.freq);