SDL: Added support for ALSA dmix audio output (thanks @sylware!)

From 76e7bc4c04a6bca6bb19d668869ff1c265686d93 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Tue, 23 Jul 2024 13:17:56 -0700
Subject: [PATCH] Added support for ALSA dmix audio output (thanks @sylware!)

Fixes https://github.com/libsdl-org/SDL/issues/8577
---
 src/audio/alsa/SDL_alsa_audio.c | 1566 +++++++++++++++++++++++--------
 src/audio/alsa/SDL_alsa_audio.h |    7 +-
 2 files changed, 1181 insertions(+), 392 deletions(-)

diff --git a/src/audio/alsa/SDL_alsa_audio.c b/src/audio/alsa/SDL_alsa_audio.c
index 827b54de4bfe0..b2c86faeb2b4e 100644
--- a/src/audio/alsa/SDL_alsa_audio.c
+++ b/src/audio/alsa/SDL_alsa_audio.c
@@ -43,6 +43,12 @@
 #ifdef SDL_AUDIO_DRIVER_ALSA_DYNAMIC
 #endif
 
+// !!! FIXME: remove this.
+#define loop for(;;)
+#define LOGDEBUG(...) SDL_Log("ALSA:" __VA_ARGS__)
+
+//TODO: cleanup once the code settled down
+
 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);
@@ -81,10 +87,30 @@ static int (*ALSA_snd_device_name_hint)(int, const char *, void ***);
 static char *(*ALSA_snd_device_name_get_hint)(const void *, const char *);
 static int (*ALSA_snd_device_name_free_hint)(void **);
 static snd_pcm_sframes_t (*ALSA_snd_pcm_avail)(snd_pcm_t *);
-#ifdef SND_CHMAP_API_VERSION
-static snd_pcm_chmap_t *(*ALSA_snd_pcm_get_chmap)(snd_pcm_t *);
-static int (*ALSA_snd_pcm_chmap_print)(const snd_pcm_chmap_t *map, size_t maxlen, char *buf);
-#endif
+static size_t (*ALSA_snd_ctl_card_info_sizeof)(void);
+static size_t (*ALSA_snd_pcm_info_sizeof)(void);
+static int (*ALSA_snd_card_next)(int*);
+static int (*ALSA_snd_ctl_open)(snd_ctl_t **,const char *,int);
+static int (*ALSA_snd_ctl_close)(snd_ctl_t *);
+static int (*ALSA_snd_ctl_card_info)(snd_ctl_t *, snd_ctl_card_info_t *);
+static int (*ALSA_snd_ctl_pcm_next_device)(snd_ctl_t *, int *);
+static unsigned int (*ALSA_snd_pcm_info_get_subdevices_count)(const snd_pcm_info_t *);
+static void (*ALSA_snd_pcm_info_set_device)(snd_pcm_info_t *, unsigned int);
+static void (*ALSA_snd_pcm_info_set_subdevice)(snd_pcm_info_t *, unsigned int);
+static void (*ALSA_snd_pcm_info_set_stream)(snd_pcm_info_t *, snd_pcm_stream_t);
+static int (*ALSA_snd_ctl_pcm_info)(snd_ctl_t *, snd_pcm_info_t *);
+static unsigned int (*ALSA_snd_pcm_info_get_subdevices_count)(const snd_pcm_info_t *);
+static const char *(*ALSA_snd_ctl_card_info_get_id)(const snd_ctl_card_info_t *);
+static const char *(*ALSA_snd_pcm_info_get_name)(const snd_pcm_info_t *);
+static const char *(*ALSA_snd_pcm_info_get_subdevice_name)(const snd_pcm_info_t *);
+static const char *(*ALSA_snd_ctl_card_info_get_name)(const snd_ctl_card_info_t *);
+static void (*ALSA_snd_ctl_card_info_clear)(snd_ctl_card_info_t *);
+static int (*ALSA_snd_pcm_hw_free)(snd_pcm_t *);
+static int (*ALSA_snd_pcm_hw_params_set_channels_near)(snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int *);
+static snd_pcm_chmap_query_t **(*ALSA_snd_pcm_query_chmaps)(snd_pcm_t *pcm);
+static void (*ALSA_snd_pcm_free_chmaps)(snd_pcm_chmap_query_t **maps);
+static int (*ALSA_snd_pcm_set_chmap)(snd_pcm_t *, const snd_pcm_chmap_t *);
+static int (*ALSA_snd_pcm_chmap_print)(const snd_pcm_chmap_t *, size_t, char *);
 
 #ifdef SDL_AUDIO_DRIVER_ALSA_DYNAMIC
 #define snd_pcm_hw_params_sizeof ALSA_snd_pcm_hw_params_sizeof
@@ -151,10 +177,30 @@ static bool load_alsa_syms(void)
     SDL_ALSA_SYM(snd_device_name_get_hint);
     SDL_ALSA_SYM(snd_device_name_free_hint);
     SDL_ALSA_SYM(snd_pcm_avail);
-#ifdef SND_CHMAP_API_VERSION
-    SDL_ALSA_SYM(snd_pcm_get_chmap);
+    SDL_ALSA_SYM(snd_ctl_card_info_sizeof);
+    SDL_ALSA_SYM(snd_pcm_info_sizeof);
+    SDL_ALSA_SYM(snd_card_next);
+    SDL_ALSA_SYM(snd_ctl_open);
+    SDL_ALSA_SYM(snd_ctl_close);
+    SDL_ALSA_SYM(snd_ctl_card_info);
+    SDL_ALSA_SYM(snd_ctl_pcm_next_device);
+    SDL_ALSA_SYM(snd_pcm_info_get_subdevices_count);
+    SDL_ALSA_SYM(snd_pcm_info_set_device);
+    SDL_ALSA_SYM(snd_pcm_info_set_subdevice);
+    SDL_ALSA_SYM(snd_pcm_info_set_stream);
+    SDL_ALSA_SYM(snd_ctl_pcm_info);
+    SDL_ALSA_SYM(snd_pcm_info_get_subdevices_count);
+    SDL_ALSA_SYM(snd_ctl_card_info_get_id);
+    SDL_ALSA_SYM(snd_pcm_info_get_name);
+    SDL_ALSA_SYM(snd_pcm_info_get_subdevice_name);
+    SDL_ALSA_SYM(snd_ctl_card_info_get_name);
+    SDL_ALSA_SYM(snd_ctl_card_info_clear);
+    SDL_ALSA_SYM(snd_pcm_hw_free);
+    SDL_ALSA_SYM(snd_pcm_hw_params_set_channels_near);
+    SDL_ALSA_SYM(snd_pcm_query_chmaps);
+    SDL_ALSA_SYM(snd_pcm_free_chmaps);
+    SDL_ALSA_SYM(snd_pcm_set_chmap);
     SDL_ALSA_SYM(snd_pcm_chmap_print);
-#endif
 
     return true;
 }
@@ -205,59 +251,282 @@ static bool LoadALSALibrary(void)
 
 typedef struct ALSA_Device
 {
+    // the unicity key is the couple (id,recording)
+    char *id; // empty means canonical default
     char *name;
     bool recording;
     struct ALSA_Device *next;
 } ALSA_Device;
 
 static const ALSA_Device default_playback_handle = {
+    "",
     "default",
     false,
     NULL
 };
 
 static const ALSA_Device default_recording_handle = {
+    "",
     "default",
     true,
     NULL
 };
 
-static const char *get_audio_device(void *handle, const int channels)
+// TODO: Figure out the "right"(TM) way. For the moment we presume that if a system is using a
+// software mixer for application audio sharing which is not the linux native alsa[dmix], for
+// instance jack/pulseaudio2[pipewire]/pulseaudio1/esound/etc, we expect the system integrators did
+// configure the canonical default to the right alsa PCM plugin for their software mixer.
+//
+// All the above may be completely wrong.
+static char *get_pcm_str(void *handle)
 {
-    SDL_assert(handle != NULL);  // SDL2 used NULL to mean "default" but that's not true in SDL3.
+    ALSA_Device *dev;
+    size_t pcm_len;
+    char *pcm_str;
 
-    ALSA_Device *dev = (ALSA_Device *)handle;
-    if (SDL_strcmp(dev->name, "default") == 0) {
-        const char *device = SDL_GetHint(SDL_HINT_AUDIO_ALSA_DEFAULT_DEVICE);
-        if (device) {
-            return device;
-        } else if (channels == 6) {
-            return "plug:surround51";
-        } else if (channels == 4) {
-            return "plug:surround40";
-        }
-        return "default";
+    SDL_assert(handle != NULL);  // SDL2 used NULL to mean "default" but that's not true in SDL3.
+    dev = (ALSA_Device *)handle;
+ 
+    // If the user does not want to go thru the default PCM or the canonical default, the
+    // the configuration space being _massive_, give the user the ability to specify
+    // its own PCMs using environment variables. It will have to fit SDL constraints though.
+    if (dev->recording)
+        pcm_str = (char*)SDL_getenv("SDL_AUDIO_ALSA_PCM_RECORDING");
+    else
+        pcm_str = (char*)SDL_getenv("SDL_AUDIO_ALSA_PCM_PLAYBACK");
+    if (pcm_str)
+        return SDL_strdup(pcm_str);
+
+    if (SDL_strlen(dev->id) == 0)
+            pcm_str = SDL_strdup("default");
+    else {
+#define PCM_STR_FMT "default:CARD=%s"
+        pcm_len = (size_t)SDL_snprintf(0, 0, PCM_STR_FMT, dev->id);
+
+        pcm_str = SDL_malloc(pcm_len + 1);
+        if (pcm_str != NULL)
+            SDL_snprintf(pcm_str, pcm_len + 1, PCM_STR_FMT, dev->id);
+#undef PCM_STR_FMT
     }
-
-    return dev->name;
+    return pcm_str;
 }
 
-// Swizzle channels to match SDL defaults.
-// These are swizzles _from_ SDL's layouts to what ALSA wants.
+// SDL channel map with alsa names "FL FR"
+// The literal names are SDL names.
+// Faith: loading the whole frame in one shot may help naive compilers.
+#define SWIZ2(T)                                                                  \
+    static void swizzle_alsa_channels_2_##T(int *swizzle_map, void *buffer, const Uint32 bufferlen) \
+    {                                                                             \
+        T *ptr = (T *)buffer;                                                     \
+        Uint32 i;                                                                 \
+        for (i = 0; i < bufferlen; i++, ptr += 2) {                               \
+            const T front_left = ptr[0];                                          \
+            const T front_right = ptr[1];                                         \
+            ptr[swizzle_map[0]] = front_left;                                     \
+            ptr[swizzle_map[1]] = front_right;                                    \
+        }                                                                         \
+    }
+// SDL channel map with alsa names "FL FR LFE"
+// The literal names are SDL names.
+// Faith: loading the whole frame in one shot may help naive compilers.
+#define SWIZ3(T)                                                                  \
+    static void swizzle_alsa_channels_3_##T(int *swizzle_map, void *buffer, const Uint32 bufferlen) \
+    {                                                                             \
+        T *ptr = (T *)buffer;                                                     \
+        Uint32 i;                                                                 \
+        for (i = 0; i < bufferlen; i++, ptr += 3) {                               \
+            const T front_left = ptr[0];                                          \
+            const T front_right = ptr[1];                                         \
+            const T subwoofer = ptr[2];                                           \
+            ptr[swizzle_map[0]] = front_left;                                     \
+            ptr[swizzle_map[1]] = front_right;                                    \
+            ptr[swizzle_map[2]] = subwoofer;                                      \
+        }                                                                         \
+    }
+// SDL channel map with alsa names "FL FR RL RR";
+// The literal names are SDL names.
+// Faith: loading the whole frame in one shot may help naive compilers.
+#define SWIZ4(T)                                                                  \
+    static void swizzle_alsa_channels_4_##T(int *swizzle_map, void *buffer, const Uint32 bufferlen) \
+    {                                                                             \
+        T *ptr = (T *)buffer;                                                     \
+        Uint32 i;                                                                 \
+        for (i = 0; i < bufferlen; i++, ptr += 4) {                               \
+            const T front_left = ptr[0];                                          \
+            const T front_right = ptr[1];                                         \
+            const T back_left = ptr[2];                                           \
+            const T back_right = ptr[3];                                          \
+            ptr[swizzle_map[0]] = front_left;                                     \
+            ptr[swizzle_map[1]] = front_right;                                    \
+            ptr[swizzle_map[2]] = back_left;                                      \
+            ptr[swizzle_map[3]] = back_right;                                     \
+        }                                                                         \
+    }
+// SDL channel map with alsa names "FL FR LFE RL RR"
+// The literal names are SDL names.
+// Faith: loading the whole frame in one shot may help naive compilers.
+#define SWIZ5(T)                                                                  \
+    static void swizzle_alsa_channels_5_##T(int *swizzle_map, void *buffer, const Uint32 bufferlen) \
+    {                                                                             \
+        T *ptr = (T *)buffer;                                                     \
+        Uint32 i;                                                                 \
+        for (i = 0; i < bufferlen; i++, ptr += 5) {                               \
+            const T front_left = ptr[0];                                          \
+            const T front_right = ptr[1];                                         \
+            const T subwoofer = ptr[2];                                           \
+            const T back_left = ptr[3];                                           \
+            const T back_right = ptr[4];                                          \
+            ptr[swizzle_map[0]] = front_left;                                     \
+            ptr[swizzle_map[1]] = front_right;                                    \
+            ptr[swizzle_map[2]] = subwoofer;                                      \
+            ptr[swizzle_map[3]] = back_left;                                      \
+            ptr[swizzle_map[4]] = back_right;                                     \
+        }                                                                         \
+    }
+// SDL channel map with alsa names "FL FR FC LFE [SL|RL] [SR|RR]"
+// The literal names are SDL names.
+// Faith: loading the whole frame in one shot may help naive compilers.
+#define SWIZ6(T)                                                                  \
+    static void swizzle_alsa_channels_6_##T(int *swizzle_map, void *buffer, const Uint32 bufferlen) \
+    {                                                                             \
+        T *ptr = (T *)buffer;                                                     \
+        Uint32 i;                                                                 \
+        for (i = 0; i < bufferlen; i++, ptr += 6) {                               \
+            const T front_left = ptr[0];                                          \
+            const T front_right = ptr[1];                                         \
+            const T front_center = ptr[2];                                        \
+            const T subwoofer = ptr[3];                                           \
+            const T side_left = ptr[4];                                           \
+            const T side_right = ptr[5];                                          \
+            ptr[swizzle_map[0]] = front_left;                                     \
+            ptr[swizzle_map[1]] = front_right;                                    \
+            ptr[swizzle_map[2]] = front_center;                                   \
+            ptr[swizzle_map[3]] = subwoofer;                                      \
+            ptr[swizzle_map[4]] = side_left;                                      \
+            ptr[swizzle_map[5]] = side_right;                                     \
+        }                                                                         \
+    }
+// SDL channel map with alsa names "FL FR FC LFE RC SL SR".
+// The literal names are SDL names.
+// Faith: loading the whole frame in one shot may help naive compilers.
+#define SWIZ7(T)                                                                  \
+    static void swizzle_alsa_channels_7_##T(int *swizzle_map, void *buffer, const Uint32 bufferlen) \
+    {                                                                             \
+        T *ptr = (T *)buffer;                                                     \
+        Uint32 i;                                                                 \
+        for (i = 0; i < bufferlen; i++, ptr += 7) {                               \
+            const T front_left = ptr[0];                                          \
+            const T front_right = ptr[1];                                         \
+            const T front_center = ptr[2];                                        \
+            const T subwoofer = ptr[3];                                           \
+            const T back_center = ptr[4];                                         \
+            const T side_left = ptr[5];                                           \
+            const T side_right = ptr[6];                                          \
+            ptr[swizzle_map[0]] = front_left;                                     \
+            ptr[swizzle_map[1]] = front_right;                                    \
+            ptr[swizzle_map[2]] = front_center;                                   \
+            ptr[swizzle_map[3]] = subwoofer;                                      \
+            ptr[swizzle_map[4]] = back_center;                                    \
+            ptr[swizzle_map[5]] = side_left;                                      \
+            ptr[swizzle_map[6]] = side_right;                                     \
+        }                                                                         \
+    }
 
-// 5.1 swizzle:
-// https://bugzilla.libsdl.org/show_bug.cgi?id=110
-//  "For Linux ALSA, this is FL-FR-RL-RR-C-LFE
-//  and for Windows DirectX [and CoreAudio], this is FL-FR-C-LFE-RL-RR"
-static const int swizzle_alsa_channels_6[6] = { 0, 1, 4, 5, 2, 3 };
+// SDL channel map with alsa names "FL FR FC LFE RL RR SL SR"
+// The literal names are SDL names.
+// Faith: loading the whole frame in one shot may help naive compilers.
+#define SWIZ8(T)                                                                  \
+    static void swizzle_alsa_channels_8_##T(int *swizzle_map, void *buffer, const Uint32 bufferlen) \
+    {                                                                             \
+        T *ptr = (T *)buffer;                                                     \
+        Uint32 i;                                                                 \
+        for (i = 0; i < bufferlen; i++, ptr += 8) {                               \
+            const T front_left = ptr[0];                                          \
+            const T front_right = ptr[1];                                         \
+            const T front_center = ptr[2];                                        \
+            const T subwoofer = ptr[3];                                           \
+            const T back_left = ptr[4];                                           \
+            const T back_right = ptr[5];                                          \
+            const T side_left = ptr[6];                                           \
+            const T side_right = ptr[7];                                          \
+            ptr[swizzle_map[0]] = front_left;                                     \
+            ptr[swizzle_map[1]] = front_right;                                    \
+            ptr[swizzle_map[2]] = front_center;                                   \
+            ptr[swizzle_map[3]] = subwoofer;                                      \
+            ptr[swizzle_map[4]] = back_left;                                      \
+            ptr[swizzle_map[5]] = back_right;                                     \
+            ptr[swizzle_map[6]] = side_left;                                      \
+            ptr[swizzle_map[7]] = side_right;                                     \
+        }                                                                         \
+    }
 
-// 7.1 swizzle:
-// https://docs.microsoft.com/en-us/windows-hardware/drivers/audio/mapping-stream-formats-to-speaker-configurations
-//  For Linux ALSA, this appears to be FL-FR-RL-RR-C-LFE-SL-SR
-//  and for Windows DirectX [and CoreAudio], this is FL-FR-C-LFE-SL-SR-RL-RR"
-static const int swizzle_alsa_channels_8[8] = { 0, 1, 6, 7, 2, 3, 4, 5 };
+#define CHANNEL_SWIZZLE(x) \
+    x(Uint64)              \
+        x(Uint32)          \
+            x(Uint16)      \
+                x(Uint8)
+
+CHANNEL_SWIZZLE(SWIZ2)
+CHANNEL_SWIZZLE(SWIZ3)
+CHANNEL_SWIZZLE(SWIZ4)
+CHANNEL_SWIZZLE(SWIZ5)
+CHANNEL_SWIZZLE(SWIZ6)
+CHANNEL_SWIZZLE(SWIZ7)
+CHANNEL_SWIZZLE(SWIZ8)
+
+#undef CHANNEL_SWIZZLE
+#undef SWIZ2
+#undef SWIZ3
+#undef SWIZ4
+#undef SWIZ5
+#undef SWIZ6
+#undef SWIZ7
+#undef SWIZ8
+
+// Called right before feeding device->hidden->mixbuf to the hardware. Swizzle
+//  channels from Windows/Mac order to the format alsalib will want.
+static void swizzle_alsa_channels(SDL_AudioDevice *device, void *buffer, Uint32 bufferlen)
+{
+    int *swizzle_map = device->hidden->swizzle_map;
+    switch (device->spec.channels) {
+#define CHANSWIZ(chans)                                                             \
+    case chans:                                                                     \
+        switch ((device->spec.format & (0xFF))) {                                   \
+        case 8:                                                                     \
+            swizzle_alsa_channels_##chans##_Uint8(swizzle_map, buffer, bufferlen);  \
+            break;                                                                  \
+        case 16:                                                                    \
+            swizzle_alsa_channels_##chans##_Uint16(swizzle_map, buffer, bufferlen); \
+            break;                                                                  \
+        case 32:                                                                    \
+            swizzle_alsa_channels_##chans##_Uint32(swizzle_map, buffer, bufferlen); \
+            break;                                                                  \
+        case 64:                                                                    \
+            swizzle_alsa_channels_##chans##_Uint64(swizzle_map, buffer, bufferlen); \
+            break;                                                                  \
+        default:                                                                    \
+            SDL_assert(!"unhandled bitsize");                                       \
+            break;                                                                  \
+        }                                                                           \
+        return;
 
+        CHANSWIZ(2);
+        CHANSWIZ(3);
+        CHANSWIZ(4);
+        CHANSWIZ(5);
+        CHANSWIZ(6);
+        CHANSWIZ(7);
+        CHANSWIZ(8);
+#undef CHANSWIZ
+    default:
+        break;
+    }
+}
 
+// Some devices have the right channel map, no swizzling necessary
+static void no_swizzle(SDL_AudioDevice *device, void *buffer, Uint32 bufferlen)
+{
+}
 
 // This function waits until it is possible to write a full sound buffer
 static bool ALSA_WaitDevice(SDL_AudioDevice *device)
@@ -266,9 +535,9 @@ static bool ALSA_WaitDevice(SDL_AudioDevice *device)
     const int delay = SDL_max(fulldelay, 10);
 
     while (!SDL_GetAtomicInt(&device->shutdown)) {
-        const int rc = ALSA_snd_pcm_wait(device->hidden->pcm_handle, delay);
+        const int rc = ALSA_snd_pcm_wait(device->hidden->pcm, delay);
         if (rc < 0 && (rc != -EAGAIN)) {
-            const int status = ALSA_snd_pcm_recover(device->hidden->pcm_handle, rc, 0);
+            const int status = ALSA_snd_pcm_recover(device->hidden->pcm, rc, 0);
             if (status < 0) {
                 // Hmm, not much we can do - abort
                 SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "ALSA: snd_pcm_wait failed (unrecoverable): %s", ALSA_snd_strerror(rc));
@@ -294,13 +563,15 @@ static bool ALSA_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int bu
     const int frame_size = SDL_AUDIO_FRAMESIZE(device->spec);
     snd_pcm_uframes_t frames_left = (snd_pcm_uframes_t) (buflen / frame_size);
 
+    device->hidden->swizzle_func(device, sample_buf, frames_left);
+
     while ((frames_left > 0) && !SDL_GetAtomicInt(&device->shutdown)) {
-        const int rc = ALSA_snd_pcm_writei(device->hidden->pcm_handle, sample_buf, frames_left);
+        const int rc = ALSA_snd_pcm_writei(device->hidden->pcm, sample_buf, frames_left);
         //SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, "ALSA PLAYDEVICE: WROTE %d of %d bytes", (rc >= 0) ? ((int) (rc * frame_size)) : rc, (int) (frames_left * frame_size));
         SDL_assert(rc != 0);  // assuming this can't happen if we used snd_pcm_wait and queried for available space.
         if (rc < 0) {
             SDL_assert(rc != -EAGAIN);  // assuming this can't happen if we used snd_pcm_wait and queried for available space. snd_pcm_recover won't handle it!
-            const int status = ALSA_snd_pcm_recover(device->hidden->pcm_handle, rc, 0);
+            const int status = ALSA_snd_pcm_recover(device->hidden->pcm, rc, 0);
             if (status < 0) {
                 // Hmm, not much we can do - abort
                 SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "ALSA write failed (unrecoverable): %s", ALSA_snd_strerror(rc));
@@ -318,12 +589,12 @@ static bool ALSA_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int bu
 
 static Uint8 *ALSA_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
 {
-    snd_pcm_sframes_t rc = ALSA_snd_pcm_avail(device->hidden->pcm_handle);
+    snd_pcm_sframes_t rc = ALSA_snd_pcm_avail(device->hidden->pcm);
     if (rc <= 0) {
         // Wait a bit and try again, maybe the hardware isn't quite ready yet?
         SDL_Delay(1);
 
-        rc = ALSA_snd_pcm_avail(device->hidden->pcm_handle);
+        rc = ALSA_snd_pcm_avail(device->hidden->pcm);
         if (rc <= 0) {
             // We'll catch it next time
             *buffer_size = 0;
@@ -344,21 +615,23 @@ static int ALSA_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen)
     const int frame_size = SDL_AUDIO_FRAMESIZE(device->spec);
     SDL_assert((buflen % frame_size) == 0);
 
-    const snd_pcm_sframes_t total_available = ALSA_snd_pcm_avail(device->hidden->pcm_handle);
+    const snd_pcm_sframes_t total_available = ALSA_snd_pcm_avail(device->hidden->pcm);
     const int total_frames = SDL_min(buflen / frame_size, total_available);
 
-    const int rc = ALSA_snd_pcm_readi(device->hidden->pcm_handle, buffer, total_frames);
+    const int rc = ALSA_snd_pcm_readi(device->hidden->pcm, buffer, total_frames);
 
     SDL_assert(rc != -EAGAIN);  // assuming this can't happen if we used snd_pcm_wait and queried for available space. snd_pcm_recover won't handle it!
 
     if (rc < 0) {
-        const int status = ALSA_snd_pcm_recover(device->hidden->pcm_handle, rc, 0);
+        const int status = ALSA_snd_pcm_recover(device->hidden->pcm, rc, 0);
         if (status < 0) {
             // Hmm, not much we can do - abort
             SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "ALSA read failed (unrecoverable): %s", ALSA_snd_strerror(rc));
             return -1;
         }
         return 0;  // go back to WaitDevice and try again.
+    } else if (rc > 0) {
+        device->hidden->swizzle_func(device, buffer, total_frames - rc);
     }
 
     //SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, "ALSA: recorded %d bytes", rc * frame_size);
@@ -368,455 +641,965 @@ static int ALSA_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen)
 
 static void ALSA_FlushRecording(SDL_AudioDevice *device)
 {
-    ALSA_snd_pcm_reset(device->hidden->pcm_handle);
+    ALSA_snd_pcm_reset(device->hidden->pcm);
 }
 
 static void ALSA_CloseDevice(SDL_AudioDevice *device)
 {
     if (device->hidden) {
-        if (device->hidden->pcm_handle) {
+        if (device->hidden->pcm) {
             // 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);
+            ALSA_snd_pcm_close(device->hidden->pcm);
         }
         SDL_free(device->hidden->mixbuf);
         SDL_free(device->hidden);
     }
 }
 
-static int ALSA_set_buffer_size(SDL_AudioDevice *device, snd_pcm_hw_params_t *params)
-{
-    int status;
-    snd_pcm_hw_params_t *hwparams;
-    snd_pcm_uframes_t persize;
-    unsigned int periods;
 
-    // Copy the hardware parameters for this setup
-    snd_pcm_hw_params_alloca(&hwparams);
-    ALSA_snd_pcm_hw_params_copy(hwparams, params);
-
-    // Attempt to match the period size to the requested buffer size
-    persize = device->sample_frames;
-    status = ALSA_snd_pcm_hw_params_set_period_size_near(
-        device->hidden->pcm_handle, hwparams, &persize, NULL);
-    if (status < 0) {
-        return -1;
-    }
+// Swizzle channels to match SDL defaults.
+// These are swizzles _from_ SDL's layouts to what ALSA wants.
 
-    // Need to at least double buffer
-    periods = 2;
-    status = ALSA_snd_pcm_hw_params_set_periods_min(
-        device->hidden->pcm_handle, hwparams, &periods, NULL);
-    if (status < 0) {
-        return -1;
-    }
+#if 0
+// 5.1 swizzle:
+// https://bugzilla.libsdl.org/show_bug.cgi?id=110
+//  "For Linux ALSA, this is FL-FR-RL-RR-C-LFE
+//  and for Windows DirectX [and CoreAudio], this is FL-FR-C-LFE-RL-RR"
+static const int swizzle_alsa_channels_6[6] = { 0, 1, 4, 5, 2, 3 };
 
-    status = ALSA_snd_pcm_hw_params_set_periods_first(
-        device->hidden->pcm_handle, hwparams, &periods, NULL);
-    if (status < 0) {
-        return -1;
-    }
+// 7.1 swizzle:
+// https://docs.microsoft.com/en-us/windows-hardware/drivers/audio/mapping-stream-formats-to-speaker-configurations
+//  For Linux ALSA, this appears to be FL-FR-RL-RR-C-LFE-SL-SR
+//  and for Windows DirectX [and CoreAudio], this is FL-FR-C-LFE-SL-SR-RL-RR"
+static const int swizzle_alsa_channels_8[8] = { 0, 1, 6, 7, 2, 3, 4, 5 };
+#endif
 
-    // "set" the hardware with the desired parameters
-    status = ALSA_snd_pcm_hw_params(device->hidden->pcm_handle, hwparams);
-    if (status < 0) {
-        return -1;
-    }
+// To make easier to track parameters during the whole alsa pcm configuration:
+struct ALSA_pcm_cfg_ctx {
+    SDL_AudioDevice *device;
 
-    device->sample_frames = persize;
+    snd_pcm_hw_params_t *hwparams;
+    snd_pcm_sw_params_t *swparams;
+
+    SDL_AudioFormat             matched_sdl_format;
+    unsigned int                chans_n;
+    unsigned int                target_chans_n;
+    unsigned int                rate;
+    snd_pcm_uframes_t           persize; // alsa period size, SDL audio device sample_frames
+    snd_pcm_chmap_query_t       **chmap_queries;
+    unsigned int                sdl_chmap[SDL_AUDIO_ALSA__CHMAP_CHANS_N_MAX];
+    unsigned int                alsa_chmap_installed[SDL_AUDIO_ALSA__CHMAP_CHANS_N_MAX];
+
+    unsigned int            periods;
+};
+// The following are SDL channel maps with alsa position values, from 0 channels to 8 channels.
+// See SDL3/SDL_audio.h
+// Strictly speaking those are "parameters" of channel maps, like alsa hwparams and swparams, they
+// have to be "reduced/refined" until an exact channel map. Only the 6 channels map requires such
+// "reduction/refine".
+static enum snd_pcm_chmap_position sdl_channel_maps[SDL_AUDIO_ALSA__SDL_CHMAPS_N][SDL_AUDIO_ALSA__CHMAP_CHANS_N_MAX] = {
+    // 0 channels
+    {
+    },
+    // 1 channel
+    {
+        SND_CHMAP_MONO,
+    },
+    // 2 channels
+    {
+        SND_CHMAP_FL,
+        SND_CHMAP_FR,
+    },
+    // 3 channels
+    {
+        SND_CHMAP_FL,
+        SND_CHMAP_FR,
+        SND_CHMAP_LFE,
+    },
+    // 4 channels
+    {
+        SND_CHMAP_FL,
+        SND_CHMAP_FR,
+        SND_CHMAP_RL,
+        SND_CHMAP_RR,
+    },
+    // 5 channels
+    {
+        SND_CHMAP_FL,
+        SND_CHMAP_FR,
+        SND_CHMAP_LFE,
+        SND_CHMAP_RL,
+        SND_CHMAP_RR,
+    },
+    // 6 channels
+    // XXX: here we encode not a uniq channel map but a set of channel maps. We will reduce it each
+    // time we are going to work with an alsa 6 channels map.
+    {
+        SND_CHMAP_FL,
+        SND_CHMAP_FR,
+        SND_CHMAP_FC,
+        SND_CHMAP_LFE,
+        // The 2 following channel positions are (SND_CHMAP_SL

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