SDL: alsa: Updated for new SDL3 audio API

https://github.com/libsdl-org/SDL/commit/409b544505eafa8444ff5328493299f36abf687c

From 409b544505eafa8444ff5328493299f36abf687c Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
Date: Tue, 4 Jul 2023 17:28:04 -0400
Subject: [PATCH] alsa: Updated for new SDL3 audio API

---
 CMakeLists.txt                  |   1 -
 src/audio/alsa/SDL_alsa_audio.c | 357 ++++++++++++++++----------------
 src/audio/alsa/SDL_alsa_audio.h |   1 -
 3 files changed, 184 insertions(+), 175 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 56646f9b738c..b745b1bbaaa4 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -354,7 +354,6 @@ set_option(SDL_CLANG_TIDY          "Run clang-tidy static analysis" OFF)
 set(SDL_VENDOR_INFO "" CACHE STRING "Vendor name and/or version to add to SDL_REVISION")
 
 set(SDL_OSS OFF)
-set(SDL_ALSA OFF)
 set(SDL_JACK OFF)
 set(SDL_SNDIO OFF)
 
diff --git a/src/audio/alsa/SDL_alsa_audio.c b/src/audio/alsa/SDL_alsa_audio.c
index 53f392dbf09a..d5bb90cf192f 100644
--- a/src/audio/alsa/SDL_alsa_audio.c
+++ b/src/audio/alsa/SDL_alsa_audio.c
@@ -20,6 +20,8 @@
 */
 #include "SDL_internal.h"
 
+// !!! FIXME: Clean out the fprintf and printf calls, replace with SDL_Log
+
 #ifdef SDL_AUDIO_DRIVER_ALSA
 
 #ifndef SDL_ALSA_NON_BLOCKING
@@ -45,6 +47,7 @@
 
 static int (*ALSA_snd_pcm_open)(snd_pcm_t **, const char *, snd_pcm_stream_t, int);
 static int (*ALSA_snd_pcm_close)(snd_pcm_t *pcm);
+static int (*ALSA_snd_pcm_start)(snd_pcm_t *pcm);
 static snd_pcm_sframes_t (*ALSA_snd_pcm_writei)(snd_pcm_t *, const void *, snd_pcm_uframes_t);
 static snd_pcm_sframes_t (*ALSA_snd_pcm_readi)(snd_pcm_t *, void *, snd_pcm_uframes_t);
 static int (*ALSA_snd_pcm_recover)(snd_pcm_t *, int, int);
@@ -115,6 +118,7 @@ static int load_alsa_syms(void)
 {
     SDL_ALSA_SYM(snd_pcm_open);
     SDL_ALSA_SYM(snd_pcm_close);
+    SDL_ALSA_SYM(snd_pcm_start);
     SDL_ALSA_SYM(snd_pcm_writei);
     SDL_ALSA_SYM(snd_pcm_readi);
     SDL_ALSA_SYM(snd_pcm_recover);
@@ -203,48 +207,21 @@ static int LoadALSALibrary(void)
 
 static const char *get_audio_device(void *handle, const int channels)
 {
-    const char *device;
-
-    if (handle != NULL) {
-        return (const char *)handle;
-    }
-
-    /* !!! FIXME: we also check "SDL_AUDIO_DEVICE_NAME" at the higher level. */
-    device = SDL_getenv("AUDIODEV"); /* Is there a standard variable name? */
-    if (device != NULL) {
-        return device;
-    }
-
-    if (channels == 6) {
-        return "plug:surround51";
-    } else if (channels == 4) {
-        return "plug:surround40";
-    }
-
-    return "default";
-}
-
-/* This function waits until it is possible to write a full sound buffer */
-static void ALSA_WaitDevice(SDL_AudioDevice *device)
-{
-#if SDL_ALSA_NON_BLOCKING
-    const snd_pcm_sframes_t needed = (snd_pcm_sframes_t)device->spec.samples;
-    while (SDL_AtomicGet(&device->enabled)) {
-        const snd_pcm_sframes_t rc = ALSA_snd_pcm_avail(device->hidden->pcm_handle);
-        if ((rc < 0) && (rc != -EAGAIN)) {
-            /* Hmm, not much we can do - abort */
-            fprintf(stderr, "ALSA snd_pcm_avail failed (unrecoverable): %s\n",
-                    ALSA_snd_strerror(rc));
-            SDL_OpenedAudioDeviceDisconnected(device);
-            return;
-        } else if (rc < needed) {
-            const Uint32 delay = ((needed - (SDL_max(rc, 0))) * 1000) / device->spec.freq;
-            SDL_Delay(SDL_max(delay, 10));
-        } else {
-            break; /* ready to go! */
+    SDL_assert(handle != NULL);  // SDL2 used NULL to mean "default" but that's not true in SDL3.
+
+    if (SDL_strcmp((const char *) handle, "default") == 0) {
+        const char *device = SDL_getenv("AUDIODEV"); /* Is there a standard variable name? */
+        if (device != NULL) {
+            return device;
+        } else if (channels == 6) {
+            return "plug:surround51";
+        } else if (channels == 4) {
+            return "plug:surround40";
         }
+        return "default";
     }
-#endif
+
+    return (const char *)handle;
 }
 
 /* !!! FIXME: is there a channel swizzler in alsalib instead? */
@@ -353,16 +330,38 @@ static void no_swizzle(SDL_AudioDevice *device, void *buffer, Uint32 bufferlen)
 }
 #endif /* SND_CHMAP_API_VERSION */
 
-static void ALSA_PlayDevice(SDL_AudioDevice *device)
+/* This function waits until it is possible to write a full sound buffer */
+static void ALSA_WaitDevice(SDL_AudioDevice *device)
 {
-    const Uint8 *sample_buf = (const Uint8 *)device->hidden->mixbuf;
+    const snd_pcm_sframes_t needed = (snd_pcm_sframes_t)device->sample_frames;
+    while (!SDL_AtomicGet(&device->shutdown)) {
+        const snd_pcm_sframes_t rc = ALSA_snd_pcm_avail(device->hidden->pcm_handle);
+        if ((rc < 0) && (rc != -EAGAIN)) {
+            /* Hmm, not much we can do - abort */
+            fprintf(stderr, "ALSA snd_pcm_avail failed (unrecoverable): %s\n",
+                    ALSA_snd_strerror(rc));
+            SDL_AudioDeviceDisconnected(device);
+            return;
+        } else if (rc < needed) {
+            const Uint32 delay = ((needed - (SDL_max(rc, 0))) * 1000) / device->spec.freq;
+            SDL_Delay(SDL_max(delay, 10));
+        } else {
+            break; /* ready to go! */
+        }
+    }
+}
+
+static void ALSA_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
+{
+    SDL_assert(buffer == device->hidden->mixbuf);
+    Uint8 *sample_buf = device->hidden->mixbuf;
     const int frame_size = ((SDL_AUDIO_BITSIZE(device->spec.format)) / 8) *
                            device->spec.channels;
-    snd_pcm_uframes_t frames_left = ((snd_pcm_uframes_t)device->spec.samples);
+    snd_pcm_uframes_t frames_left = (snd_pcm_uframes_t) (buflen / frame_size);
 
-    device->hidden->swizzle_func(device, device->hidden->mixbuf, frames_left);
+    device->hidden->swizzle_func(device, sample_buf, frames_left);
 
-    while (frames_left > 0 && SDL_AtomicGet(&device->enabled)) {
+    while ((frames_left > 0) && !SDL_AtomicGet(&device->shutdown)) {
         int status = ALSA_snd_pcm_writei(device->hidden->pcm_handle,
                                          sample_buf, frames_left);
 
@@ -377,17 +376,16 @@ static void ALSA_PlayDevice(SDL_AudioDevice *device)
             if (status < 0) {
                 /* Hmm, not much we can do - abort */
                 SDL_LogError(SDL_LOG_CATEGORY_AUDIO,
-                             "ALSA write failed (unrecoverable): %s\n",
+                             "ALSA write failed (unrecoverable): %s",
                              ALSA_snd_strerror(status));
-                SDL_OpenedAudioDeviceDisconnected(device);
+                SDL_AudioDeviceDisconnected(device);
                 return;
             }
             continue;
         } else if (status == 0) {
             /* No frames were written (no available space in pcm device).
                Allow other threads to catch up. */
-            Uint32 delay = (frames_left / 2 * 1000) / device->spec.freq;
-            SDL_Delay(delay);
+            SDL_Delay((frames_left / 2 * 1000) / device->spec.freq);
         }
 
         sample_buf += status * frame_size;
@@ -395,7 +393,7 @@ static void ALSA_PlayDevice(SDL_AudioDevice *device)
     }
 }
 
-static Uint8 *ALSA_GetDeviceBuf(SDL_AudioDevice *device)
+static Uint8 *ALSA_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
 {
     return device->hidden->mixbuf;
 }
@@ -411,11 +409,9 @@ static int ALSA_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buf
 
     SDL_assert((buflen % frame_size) == 0);
 
-    while (frames_left > 0 && SDL_AtomicGet(&device->enabled)) {
-        int status;
-
-        status = ALSA_snd_pcm_readi(device->hidden->pcm_handle,
-                                    sample_buf, frames_left);
+    while ((frames_left > 0) && !SDL_AtomicGet(&device->shutdown)) {
+        int status = ALSA_snd_pcm_readi(device->hidden->pcm_handle,
+                                        sample_buf, frames_left);
 
         if (status == -EAGAIN) {
             ALSA_snd_pcm_wait(device->hidden->pcm_handle, wait_time);
@@ -450,17 +446,17 @@ static void ALSA_FlushCapture(SDL_AudioDevice *device)
 
 static void ALSA_CloseDevice(SDL_AudioDevice *device)
 {
-    if (device->hidden->pcm_handle) {
-        /* Wait for the submitted audio to drain
-           ALSA_snd_pcm_drop() can hang, so don't use that.
-         */
-        Uint32 delay = ((device->spec.samples * 1000) / device->spec.freq) * 2;
-        SDL_Delay(delay);
-
-        ALSA_snd_pcm_close(device->hidden->pcm_handle);
+    if (device->hidden) {
+        if (device->hidden->pcm_handle) {
+            /* Wait for the submitted audio to drain
+               ALSA_snd_pcm_drop() can hang, so don't use that.
+             */
+            SDL_Delay(((device->sample_frames * 1000) / device->spec.freq) * 2);
+            ALSA_snd_pcm_close(device->hidden->pcm_handle);
+        }
+        SDL_free(device->hidden->mixbuf);
+        SDL_free(device->hidden);
     }
-    SDL_free(device->hidden->mixbuf);
-    SDL_free(device->hidden);
 }
 
 static int ALSA_set_buffer_size(SDL_AudioDevice *device, snd_pcm_hw_params_t *params)
@@ -475,7 +471,7 @@ static int ALSA_set_buffer_size(SDL_AudioDevice *device, snd_pcm_hw_params_t *pa
     ALSA_snd_pcm_hw_params_copy(hwparams, params);
 
     /* Attempt to match the period size to the requested buffer size */
-    persize = device->spec.samples;
+    persize = device->sample_frames;
     status = ALSA_snd_pcm_hw_params_set_period_size_near(
         device->hidden->pcm_handle, hwparams, &persize, NULL);
     if (status < 0) {
@@ -502,7 +498,7 @@ static int ALSA_set_buffer_size(SDL_AudioDevice *device, snd_pcm_hw_params_t *pa
         return -1;
     }
 
-    device->spec.samples = persize;
+    device->sample_frames = persize;
 
     /* This is useful for debugging */
     if (SDL_getenv("SDL_AUDIO_ALSA_DEBUG")) {
@@ -518,32 +514,20 @@ static int ALSA_set_buffer_size(SDL_AudioDevice *device, snd_pcm_hw_params_t *pa
     return 0;
 }
 
-static int ALSA_OpenDevice(SDL_AudioDevice *device, const char *devname)
+static int ALSA_OpenDevice(SDL_AudioDevice *device)
 {
+    const SDL_bool iscapture = device->iscapture;
     int status = 0;
-    SDL_bool iscapture = device->iscapture;
-    snd_pcm_t *pcm_handle = NULL;
-    snd_pcm_hw_params_t *hwparams = NULL;
-    snd_pcm_sw_params_t *swparams = NULL;
-    snd_pcm_format_t format = 0;
-    SDL_AudioFormat test_format = 0;
-    const SDL_AudioFormat *closefmts;
-    unsigned int rate = 0;
-    unsigned int channels = 0;
-#ifdef SND_CHMAP_API_VERSION
-    snd_pcm_chmap_t *chmap;
-    char chmap_str[64];
-#endif
 
     /* Initialize all variables that we clean on shutdown */
-    device->hidden = (struct SDL_PrivateAudioData *)SDL_malloc(sizeof(*device->hidden));
+    device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden));
     if (device->hidden == NULL) {
         return SDL_OutOfMemory();
     }
-    SDL_zerop(device->hidden);
 
     /* Open the audio device */
     /* Name of device should depend on # channels in spec */
+    snd_pcm_t *pcm_handle = NULL;
     status = ALSA_snd_pcm_open(&pcm_handle,
                                get_audio_device(device->handle, device->spec.channels),
                                iscapture ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK,
@@ -556,6 +540,7 @@ static int ALSA_OpenDevice(SDL_AudioDevice *device, const char *devname)
     device->hidden->pcm_handle = pcm_handle;
 
     /* Figure out what the hardware is capable of */
+    snd_pcm_hw_params_t *hwparams = NULL;
     snd_pcm_hw_params_alloca(&hwparams);
     status = ALSA_snd_pcm_hw_params_any(pcm_handle, hwparams);
     if (status < 0) {
@@ -570,7 +555,9 @@ static int ALSA_OpenDevice(SDL_AudioDevice *device, const char *devname)
     }
 
     /* Try for a closest match on audio format */
-    closefmts = SDL_ClosestAudioFormats(device->spec.format);
+    snd_pcm_format_t format = 0;
+    const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format);
+    SDL_AudioFormat test_format;
     while ((test_format = *(closefmts++)) != 0) {
         switch (test_format) {
         case SDL_AUDIO_U8:
@@ -605,7 +592,7 @@ static int ALSA_OpenDevice(SDL_AudioDevice *device, const char *devname)
         }
     }
     if (!test_format) {
-        return SDL_SetError("%s: Unsupported audio format", "alsa");
+        return SDL_SetError("ALSA: Unsupported audio format");
     }
     device->spec.format = test_format;
 
@@ -614,8 +601,9 @@ static int ALSA_OpenDevice(SDL_AudioDevice *device, const char *devname)
      */
     device->hidden->swizzle_func = swizzle_alsa_channels;
 #ifdef SND_CHMAP_API_VERSION
-    chmap = ALSA_snd_pcm_get_chmap(pcm_handle);
+    snd_pcm_chmap_t *chmap = ALSA_snd_pcm_get_chmap(pcm_handle);
     if (chmap) {
+        char chmap_str[64];
         if (ALSA_snd_pcm_chmap_print(chmap, sizeof(chmap_str), chmap_str) > 0) {
             if (SDL_strcmp("FL FR FC LFE RL RR", chmap_str) == 0 ||
                 SDL_strcmp("FL FR FC LFE SL SR", chmap_str) == 0) {
@@ -629,7 +617,7 @@ static int ALSA_OpenDevice(SDL_AudioDevice *device, const char *devname)
     /* Set the number of channels */
     status = ALSA_snd_pcm_hw_params_set_channels(pcm_handle, hwparams,
                                                  device->spec.channels);
-    channels = device->spec.channels;
+    unsigned int channels = device->spec.channels;
     if (status < 0) {
         status = ALSA_snd_pcm_hw_params_get_channels(hwparams, &channels);
         if (status < 0) {
@@ -639,7 +627,7 @@ static int ALSA_OpenDevice(SDL_AudioDevice *device, const char *devname)
     }
 
     /* Set the audio rate */
-    rate = device->spec.freq;
+    unsigned int rate = device->spec.freq;
     status = ALSA_snd_pcm_hw_params_set_rate_near(pcm_handle, hwparams,
                                                   &rate, NULL);
     if (status < 0) {
@@ -654,12 +642,13 @@ static int ALSA_OpenDevice(SDL_AudioDevice *device, const char *devname)
     }
 
     /* Set the software parameters */
+    snd_pcm_sw_params_t *swparams = NULL;
     snd_pcm_sw_params_alloca(&swparams);
     status = ALSA_snd_pcm_sw_params_current(pcm_handle, swparams);
     if (status < 0) {
         return SDL_SetError("ALSA: Couldn't get software config: %s", ALSA_snd_strerror(status));
     }
-    status = ALSA_snd_pcm_sw_params_set_avail_min(pcm_handle, swparams, device->spec.samples);
+    status = ALSA_snd_pcm_sw_params_set_avail_min(pcm_handle, swparams, device->sample_frames);
     if (status < 0) {
         return SDL_SetError("Couldn't set minimum available samples: %s", ALSA_snd_strerror(status));
     }
@@ -673,17 +662,16 @@ static int ALSA_OpenDevice(SDL_AudioDevice *device, const char *devname)
         return SDL_SetError("Couldn't set software audio parameters: %s", ALSA_snd_strerror(status));
     }
 
-    /* Calculate the final parameters for this audio specification */
-    SDL_CalculateAudioSpec(&device->spec);
+    // Calculate the final parameters for this audio specification
+    SDL_UpdatedAudioDeviceFormat(device);
 
-    /* Allocate mixing buffer */
+    // Allocate mixing buffer
     if (!iscapture) {
-        device->hidden->mixlen = device->spec.size;
-        device->hidden->mixbuf = (Uint8 *)SDL_malloc(device->hidden->mixlen);
+        device->hidden->mixbuf = (Uint8 *)SDL_malloc(device->buffer_size);
         if (device->hidden->mixbuf == NULL) {
             return SDL_OutOfMemory();
         }
-        SDL_memset(device->hidden->mixbuf, device->spec.silence, device->hidden->mixlen);
+        SDL_memset(device->hidden->mixbuf, device->silence_value, device->buffer_size);
     }
 
 #if !SDL_ALSA_NON_BLOCKING
@@ -692,6 +680,8 @@ static int ALSA_OpenDevice(SDL_AudioDevice *device, const char *devname)
     }
 #endif
 
+    ALSA_snd_pcm_start(pcm_handle);
+
     /* We're ready to rock and roll. :-) */
     return 0;
 }
@@ -703,7 +693,7 @@ typedef struct ALSA_Device
     struct ALSA_Device *next;
 } ALSA_Device;
 
-static void add_device(const int iscapture, const char *name, void *hint, ALSA_Device **pSeen)
+static void add_device(const SDL_bool iscapture, const char *name, void *hint, ALSA_Device **pSeen)
 {
     ALSA_Device *dev = SDL_malloc(sizeof(ALSA_Device));
     char *desc;
@@ -765,21 +755,17 @@ static void add_device(const int iscapture, const char *name, void *hint, ALSA_D
 
 static ALSA_Device *hotplug_devices = NULL;
 
-static void ALSA_HotplugIteration(void)
+static void ALSA_HotplugIteration(SDL_bool *has_default_output, SDL_bool *has_default_capture)
 {
     void **hints = NULL;
-    ALSA_Device *dev;
-    ALSA_Device *unseen;
-    ALSA_Device *seen;
-    ALSA_Device *next;
-    ALSA_Device *prev;
+    ALSA_Device *unseen = NULL;
+    ALSA_Device *seen = NULL;
 
     if (ALSA_snd_device_name_hint(-1, "pcm", &hints) == 0) {
-        int i, j;
         const char *match = NULL;
         int bestmatch = 0xFFFF;
+        int has_default = -1;
         size_t match_len = 0;
-        int defaultdev = -1;
         static const char *const prefixes[] = {
             "hw:", "sysdefault:", "default:", NULL
         };
@@ -791,92 +777,100 @@ static void ALSA_HotplugIteration(void)
            actual hardware. It could be prefixed with "hw:" or "default:"
            or "sysdefault:" and maybe others. Go through the list and see
            if we can find a preferred prefix for the system. */
-        for (i = 0; hints[i]; i++) {
+        for (int i = 0; hints[i]; i++) {
             char *name = ALSA_snd_device_name_get_hint(hints[i], "NAME");
             if (name == NULL) {
                 continue;
             }
 
-            /* full name, not a prefix */
-            if ((defaultdev == -1) && (SDL_strcmp(name, "default") == 0)) {
-                defaultdev = i;
-            }
-
-            for (j = 0; prefixes[j]; j++) {
-                const char *prefix = prefixes[j];
-                const size_t prefixlen = SDL_strlen(prefix);
-                if (SDL_strncmp(name, prefix, prefixlen) == 0) {
-                    if (j < bestmatch) {
-                        bestmatch = j;
-                        match = prefix;
-                        match_len = prefixlen;
+            if (SDL_strcmp(name, "default") == 0) {
+                if (has_default < 0) {
+                    has_default = i;
+                }
+            } else {
+                for (int j = 0; prefixes[j]; j++) {
+                    const char *prefix = prefixes[j];
+                    const size_t prefixlen = SDL_strlen(prefix);
+                    if (SDL_strncmp(name, prefix, prefixlen) == 0) {
+                        if (j < bestmatch) {
+                            bestmatch = j;
+                            match = prefix;
+                            match_len = prefixlen;
+                        }
                     }
                 }
-            }
 
-            free(name); /* This should NOT be SDL_free() */
+                free(name); /* This should NOT be SDL_free() */
+            }
         }
 
         /* look through the list of device names to find matches */
-        for (i = 0; hints[i]; i++) {
-            char *name;
-
-            /* if we didn't find a device name prefix we like at all... */
-            if ((match == NULL) && (defaultdev != i)) {
-                continue; /* ...skip anything that isn't the default device. */
-            }
+        if (match || (has_default >= 0)) {  // did we find a device name prefix we like at all...?
+            for (int i = 0; hints[i]; i++) {
+                char *name = ALSA_snd_device_name_get_hint(hints[i], "NAME");
+                if (name == NULL) {
+                    continue;
+                }
 
-            name = ALSA_snd_device_name_get_hint(hints[i], "NAME");
-            if (name == NULL) {
-                continue;
-            }
+                // only want physical hardware interfaces
+                const SDL_bool is_default = (has_default == i) ? SDL_TRUE : SDL_FALSE;
+                if (is_default || SDL_strncmp(name, match, match_len) == 0) {
+                    char *ioid = ALSA_snd_device_name_get_hint(hints[i], "IOID");
+                    const SDL_bool isoutput = (ioid == NULL) || (SDL_strcmp(ioid, "Output") == 0);
+                    const SDL_bool isinput = (ioid == NULL) || (SDL_strcmp(ioid, "Input") == 0);
+                    SDL_bool have_output = SDL_FALSE;
+                    SDL_bool have_input = SDL_FALSE;
 
-            /* only want physical hardware interfaces */
-            if (match == NULL || (SDL_strncmp(name, match, match_len) == 0)) {
-                char *ioid = ALSA_snd_device_name_get_hint(hints[i], "IOID");
-                const SDL_bool isoutput = (ioid == NULL) || (SDL_strcmp(ioid, "Output") == 0);
-                const SDL_bool isinput = (ioid == NULL) || (SDL_strcmp(ioid, "Input") == 0);
-                SDL_bool have_output = SDL_FALSE;
-                SDL_bool have_input = SDL_FALSE;
+                    free(ioid);
 
-                free(ioid); /* This should NOT be SDL_free() */
+                    if (!isoutput && !isinput) {
+                        free(name);
+                        continue;
+                    }
 
-                if (!isoutput && !isinput) {
-                    free(name); /* This should NOT be SDL_free() */
-                    continue;
-                }
+                    if (is_default) {
+                        if (has_default_output && isoutput) {
+                            *has_default_output = SDL_TRUE;
+                        } else if (has_default_capture && isinput) {
+                            *has_default_capture = SDL_TRUE;
+                        }
+                        free(name);
+                        continue;
+                    }
 
-                prev = NULL;
-                for (dev = unseen; dev; dev = next) {
-                    next = dev->next;
-                    if ((SDL_strcmp(dev->name, name) == 0) && (((isinput) && dev->iscapture) || ((isoutput) && !dev->iscapture))) {
-                        if (prev) {
-                            prev->next = next;
+                    ALSA_Device *prev = NULL;
+                    ALSA_Device *next;
+                    for (ALSA_Device *dev = unseen; dev; dev = next) {
+                        next = dev->next;
+                        if ((SDL_strcmp(dev->name, name) == 0) && (((isinput) && dev->iscapture) || ((isoutput) && !dev->iscapture))) {
+                            if (prev) {
+                                prev->next = next;
+                            } else {
+                                unseen = next;
+                            }
+                            dev->next = seen;
+                            seen = dev;
+                            if (isinput) {
+                                have_input = SDL_TRUE;
+                            }
+                            if (isoutput) {
+                                have_output = SDL_TRUE;
+                            }
                         } else {
-                            unseen = next;
-                        }
-                        dev->next = seen;
-                        seen = dev;
-                        if (isinput) {
-                            have_input = SDL_TRUE;
-                        }
-                        if (isoutput) {
-                            have_output = SDL_TRUE;
+                            prev = dev;
                         }
-                    } else {
-                        prev = dev;
                     }
-                }
 
-                if (isinput && !have_input) {
-                    add_device(SDL_TRUE, name, hints[i], &seen);
-                }
-                if (isoutput && !have_output) {
-                    add_device(SDL_FALSE, name, hints[i], &seen);
+                    if (isinput && !have_input) {
+                        add_device(SDL_TRUE, name, hints[i], &seen);
+                    }
+                    if (isoutput && !have_output) {
+                        add_device(SDL_FALSE, name, hints[i], &seen);
+                    }
                 }
-            }
 
-            free(name); /* This should NOT be SDL_free() */
+                free(name); /* This should NOT be SDL_free() */
+            }
         }
 
         ALSA_snd_device_name_free_hint(hints);
@@ -884,10 +878,17 @@ static void ALSA_HotplugIteration(void)
         hotplug_devices = seen; /* now we have a known-good list of attached devices. */
 
         /* report anything still in unseen as removed. */
-        for (dev = unseen; dev; dev = next) {
+        ALSA_Device *next = NULL;
+        for (ALSA_Device *dev = unseen; dev; dev = next) {
             /*printf("ALSA: removing usb %s device '%s'\n", dev->iscapture ? "capture" : "output", dev->name);*/
             next = dev->next;
-            SDL_RemoveAudioDevice(dev->iscapture, dev->name);
+
+            SDL_AudioDevice *device = SDL_ObtainPhysicalAudioDeviceByHandle(dev->name);
+            if (device) {
+                SDL_UnlockMutex(device->lock);  // AudioDeviceDisconnected will relock and verify it's still in the list, but in case this is destroyed, unlock now.
+                SDL_AudioDeviceDisconnected(device);
+            }
+
             SDL_free(dev->name);
             SDL_free(dev);
         }
@@ -909,16 +910,25 @@ static int SDLCALL ALSA_HotplugThread(void *arg)
             SDL_Delay(100);
         }
 
-        ALSA_HotplugIteration(); /* run the check. */
+        ALSA_HotplugIteration(NULL, NULL); /* run the check. */
     }
 
     return 0;
 }
 #endif
 
-static void ALSA_DetectDevices(void)
+static void ALSA_DetectDevices(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture)
 {
-    ALSA_HotplugIteration(); /* run once now before a thread continues to check. */
+    // ALSA doesn't have a concept of a changeable default device, afaik, so we expose a generic default
+    // device here. It's the best we can do at this level.
+    SDL_bool has_default_output = SDL_FALSE, has_default_capture = SDL_FALSE;
+    ALSA_HotplugIteration(&has_default_output, &has_default_capture); // run once now before a thread continues to check. */
+    if (has_default_output) {
+        *default_output = SDL_AddAudioDevice(/*iscapture=*/SDL_FALSE, "ALSA default output device", NULL, SDL_strdup("default"));
+    }
+    if (has_default_capture) {
+        *default_capture = SDL_AddAudioDevice(/*iscapture=*/SDL_TRUE, "ALSA default capture device", NULL, SDL_strdup("default"));
+    }
 
 #if SDL_ALSA_HOTPLUG_THREAD
     SDL_AtomicSet(&ALSA_hotplug_shutdown, 0);
@@ -966,6 +976,7 @@ static SDL_bool ALSA_Init(SDL_AudioDriverImpl *impl)
     impl->PlayDevice = ALSA_PlayDevice;
     impl->CloseDevice = ALSA_CloseDevice;
     impl->Deinitialize = ALSA_Deinitialize;
+    impl->WaitCaptureDevice = ALSA_WaitDevice;
     impl->CaptureFromDevice = ALSA_CaptureFromDevice;
     impl->FlushCapture = ALSA_FlushCapture;
 
diff --git a/src/audio/alsa/SDL_alsa_audio.h b/src/audio/alsa/SDL_alsa_audio.h
index b66318d75159..ef7ce1bb5c3e 100644
--- a/src/audio/alsa/SDL_alsa_audio.h
+++ b/src/audio/alsa/SDL_alsa_audio.h
@@ -34,7 +34,6 @@ struct SDL_PrivateAudioData
 
     /* Raw mixing buffer */
     Uint8 *mixbuf;
-    int mixlen;
 
     /* swizzle function */
     void (*swizzle_func)(SDL_AudioDevice *_this, void *buffer, Uint32 bufferlen);