SDL: alsa: Reenable SDL's internal channel map support when ALSA can't swizzle.

From 483ceb53559f2966164de6b47181b639e0a9f09d Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
Date: Sun, 15 Dec 2024 16:48:21 -0500
Subject: [PATCH] alsa: Reenable SDL's internal channel map support when ALSA
 can't swizzle.

---
 src/audio/alsa/SDL_alsa_audio.c | 288 ++++----------------------------
 src/audio/alsa/SDL_alsa_audio.h |   6 -
 2 files changed, 36 insertions(+), 258 deletions(-)

diff --git a/src/audio/alsa/SDL_alsa_audio.c b/src/audio/alsa/SDL_alsa_audio.c
index 1b635dbf38962..8969a6448f7e6 100644
--- a/src/audio/alsa/SDL_alsa_audio.c
+++ b/src/audio/alsa/SDL_alsa_audio.c
@@ -345,223 +345,6 @@ static char *get_pcm_str(void *handle)
     return pcm_str;
 }
 
-// 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;                                     \
-        }                                                                         \
-    }
-
-// 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;                                     \
-        }                                                                         \
-    }
-
-#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)
 {
@@ -597,8 +380,6 @@ 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, 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));
@@ -664,8 +445,6 @@ static int ALSA_RecordDevice(SDL_AudioDevice *device, void *buffer, int buflen)
             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);
@@ -692,23 +471,6 @@ static void ALSA_CloseDevice(SDL_AudioDevice *device)
 }
 
 
-// Swizzle channels to match SDL defaults.
-// These are swizzles _from_ SDL's layouts to what ALSA wants.
-
-#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 };
-
-// 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
-
 // To make easier to track parameters during the whole alsa pcm configuration:
 struct ALSA_pcm_cfg_ctx {
     SDL_AudioDevice *device;
@@ -876,23 +638,28 @@ static void sdl_6chans_set_rear_or_side_channels_from_alsa_6chans(unsigned int *
 #undef HAVE_SIDE
 #undef HAVE_BOTH
 
-static void swizzle_map_compute_alsa_subscan(struct ALSA_pcm_cfg_ctx *ctx, unsigned int sdl_pos_idx)
+static void swizzle_map_compute_alsa_subscan(struct ALSA_pcm_cfg_ctx *ctx, int *swizzle_map, unsigned int sdl_pos_idx)
 {
+    swizzle_map[sdl_pos_idx] = -1;
     for (unsigned int alsa_pos_idx = 0; ; alsa_pos_idx++) {
         SDL_assert(alsa_pos_idx != ctx->chans_n);  // no 0 channels or not found matching position should happen here (actually enforce playback/recording symmetry).
         if (ctx->alsa_chmap_installed[alsa_pos_idx] == ctx->sdl_chmap[sdl_pos_idx]) {
             LOGDEBUG("swizzle SDL %u <-> alsa %u", sdl_pos_idx,alsa_pos_idx);
-            ctx->device->hidden->swizzle_map[sdl_pos_idx] = alsa_pos_idx;
+            swizzle_map[sdl_pos_idx] = (int) alsa_pos_idx;
             return;
         }
     }
 }
 
 // XXX: this must stay playback/recording symetric.
-static void swizzle_map_compute(struct ALSA_pcm_cfg_ctx *ctx)
+static void swizzle_map_compute(struct ALSA_pcm_cfg_ctx *ctx, int *swizzle_map, bool *needs_swizzle)
 {
+    *needs_swizzle = false;
     for (unsigned int sdl_pos_idx = 0; sdl_pos_idx != ctx->chans_n; sdl_pos_idx++) {
-        swizzle_map_compute_alsa_subscan(ctx, sdl_pos_idx);
+        swizzle_map_compute_alsa_subscan(ctx, swizzle_map, sdl_pos_idx);
+        if (swizzle_map[sdl_pos_idx] != sdl_pos_idx) {
+            *needs_swizzle = true;
+        }
     }
 }
 
@@ -1093,7 +860,6 @@ static int alsa_chmap_cfg(struct ALSA_pcm_cfg_ctx *ctx)
     if (ctx->chmap_queries == NULL) {
         // We couldn't query the channel map, assume no swizzle necessary
         LOGDEBUG("couldn't query channel map, swizzling off");
-        ctx->device->hidden->swizzle_func = no_swizzle;
         return CHMAP_INSTALLED;
     }
 
@@ -1101,23 +867,39 @@ static int alsa_chmap_cfg(struct ALSA_pcm_cfg_ctx *ctx)
     status = alsa_chmap_cfg_ordered(ctx); // we prefer first channel maps we don't need to swizzle
     if (status == CHMAP_INSTALLED) {
         LOGDEBUG("swizzling off");
-        ctx->device->hidden->swizzle_func = no_swizzle;
         return status;
-    }
-    if (status != CHMAP_NOT_FOUND) {
+    } else if (status != CHMAP_NOT_FOUND) {
         return status; // < 0 error code
     }
+
     // Fall-thru
     //----------------------------------------------------------------------------------------------
     status = alsa_chmap_cfg_unordered(ctx); // those we will have to swizzle
     if (status == CHMAP_INSTALLED) {
         LOGDEBUG("swizzling on");
-        swizzle_map_compute(ctx); // fine grained swizzle configuration
-        ctx->device->hidden->swizzle_func = swizzle_alsa_channels;
-        return status;
+
+        bool isstack;
+        int *swizzle_map = SDL_small_alloc(int, ctx->chans_n, &isstack);
+        if (!swizzle_map) {
+            status = -1;
+        } else {
+            bool needs_swizzle;
+            swizzle_map_compute(ctx, swizzle_map, &needs_swizzle); // fine grained swizzle configuration
+            if (needs_swizzle) {
+                // let SDL's swizzler handle this one.
+                ctx->device->chmap = SDL_ChannelMapDup(swizzle_map, ctx->chans_n);
+                if (!ctx->device->chmap) {
+                    status = -1;
+                }
+            }
+            SDL_small_free(swizzle_map, isstack);
+        }
     }
-    if (status == CHMAP_NOT_FOUND)
+
+    if (status == CHMAP_NOT_FOUND) {
         return CHANS_N_NEXT;
+    }
+
     return status; // < 0 error code
 }
 
@@ -1249,10 +1031,12 @@ static int ALSA_pcm_cfg_hw_chans_n_scan(struct ALSA_pcm_cfg_ctx *ctx, unsigned i
         // Here the alsa pcm is in SND_PCM_STATE_PREPARED state, let's figure out a good fit for
         // SDL channel map, it may request to change the target number of channels though.
         status = alsa_chmap_cfg(ctx);
-        if (status < 0)
+        if (status < 0) {
             return status; // we forward the SDL error
-        if (status == CHMAP_INSTALLED)
+        } else if (status == CHMAP_INSTALLED) {
             return CHANS_N_CONFIGURED; // we are finished here
+        }
+
         // status == CHANS_N_NEXT
         ALSA_snd_pcm_free_chmaps(ctx->chmap_queries);
         ALSA_snd_pcm_hw_free(ctx->device->hidden->pcm); // uninstall those hw params
diff --git a/src/audio/alsa/SDL_alsa_audio.h b/src/audio/alsa/SDL_alsa_audio.h
index 952bfb26b7c88..e840b2f6c5a37 100644
--- a/src/audio/alsa/SDL_alsa_audio.h
+++ b/src/audio/alsa/SDL_alsa_audio.h
@@ -36,12 +36,6 @@ struct SDL_PrivateAudioData
 
     // Raw mixing buffer
     Uint8 *mixbuf;
-
-    // swizzle function
-    void (*swizzle_func)(SDL_AudioDevice *_this, void *buffer, Uint32 bufferlen);
-    // Up to a channel map of 8 channels, will define the sample indexes into the alsa frame
-    // from a sdl sample index.
-    int swizzle_map[SDL_AUDIO_ALSA__CHMAP_CHANS_N_MAX];
 };
 
 #endif // SDL_ALSA_audio_h_