SDL: alsa: Pulled in latest changes from @sylware

From b9c062c85888df936c2b3524637e9ac07ce15a9d Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
Date: Sat, 14 Dec 2024 13:31:52 -0500
Subject: [PATCH] alsa: Pulled in latest changes from @sylware

---
 src/audio/alsa/SDL_alsa_audio.c | 101 +++++++++++++++++++++++++-------
 1 file changed, 79 insertions(+), 22 deletions(-)

diff --git a/src/audio/alsa/SDL_alsa_audio.c b/src/audio/alsa/SDL_alsa_audio.c
index b2c86faeb2b4e..cad4a5de5f8cf 100644
--- a/src/audio/alsa/SDL_alsa_audio.c
+++ b/src/audio/alsa/SDL_alsa_audio.c
@@ -869,7 +869,7 @@ static void swizzle_map_compute(struct ALSA_pcm_cfg_ctx *ctx)
     }
 }
 #define CHMAP_INSTALLED 0
-#define REDUCE_CHANS_N  1
+#define CHANS_N_NEXT            1
 #define CHMAP_NOT_FOUND 2
 // Should always be a queried alsa channel map unless the queried alsa channel map was of type VAR,
 // namely we can program the channel positions directly from the SDL channel map.
@@ -1122,31 +1122,47 @@ static int alsa_chmap_cfg(struct ALSA_pcm_cfg_ctx *ctx)
         return status;
     }
     if (status == CHMAP_NOT_FOUND)
-        return REDUCE_CHANS_N;
+        return CHANS_N_NEXT;
     return status; // < 0 error code
 }
 
-static int ALSA_pcm_cfg_hw(struct ALSA_pcm_cfg_ctx *ctx)
+#define CHANS_N_SCAN_MODE__EQUAL_OR_ABOVE_REQUESTED_CHANS_N 0 /* target more hardware pressure */
+#define CHANS_N_SCAN_MODE__BELOW_REQUESTED_CHANS_N          1 /* target less hardware pressure */
+#define CHANS_N_CONFIGURED      0
+#define CHANS_N_NOT_CONFIGURED  1
+static int ALSA_pcm_cfg_hw_chans_n_scan(struct ALSA_pcm_cfg_ctx *ctx, unsigned int mode)
 {
     unsigned int target_chans_n = ctx->device->spec.channels; // we start at what was specified
+    if (mode == CHANS_N_SCAN_MODE__BELOW_REQUESTED_CHANS_N) {
+        target_chans_n--;
+    }
     loop {
         int status;
         snd_pcm_format_t alsa_format;
         const SDL_AudioFormat *closefmts;
 
+        if (mode == CHANS_N_SCAN_MODE__EQUAL_OR_ABOVE_REQUESTED_CHANS_N) {
+            if (target_chans_n > SDL_AUDIO_ALSA__CHMAP_CHANS_N_MAX) {
+                return CHANS_N_NOT_CONFIGURED;
+            }
+        } else {/* CHANS_N_SCAN_MODE__BELOW_REQUESTED_CHANS_N */
         if (target_chans_n == 0) {
-            return SDL_SetError("ALSA: tried all numbers of channels");
+                return CHANS_N_NOT_CONFIGURED;
+        }
         }
+        LOGDEBUG("target chans_n is %u\n", target_chans_n);
 
         status = ALSA_snd_pcm_hw_params_any(ctx->device->hidden->pcm, ctx->hwparams);
         if (status < 0) {
-            return SDL_SetError("ALSA: Couldn't get hardware config: %s", ALSA_snd_strerror(status));
+            SDL_SetError("ALSA: Couldn't get hardware config: %s", ALSA_snd_strerror(status));
+            return -1;
         }
         // SDL only uses interleaved sample output
         status = ALSA_snd_pcm_hw_params_set_access(ctx->device->hidden->pcm, ctx->hwparams,
                                                                    SND_PCM_ACCESS_RW_INTERLEAVED);
         if (status < 0) {
-            return SDL_SetError("ALSA: Couldn't set interleaved access: %s", ALSA_snd_strerror(status));
+            SDL_SetError("ALSA: Couldn't set interleaved access: %s", ALSA_snd_strerror(status));
+            return -1;
         }
         // Try for a closest match on audio format
         alsa_format = 0;
@@ -1189,88 +1205,129 @@ static int ALSA_pcm_cfg_hw(struct ALSA_pcm_cfg_ctx *ctx)
             }
         }
         if (ctx->matched_sdl_format == 0) {
-            return SDL_SetError("ALSA: Unsupported audio format: %s", ALSA_snd_strerror(status));
+            SDL_SetError("ALSA: Unsupported audio format: %s", ALSA_snd_strerror(status));
+            return -1;
         }
         // let alsa approximate the number of channels
         ctx->chans_n = target_chans_n;
         status = ALSA_snd_pcm_hw_params_set_channels_near(ctx->device->hidden->pcm,
                                                                     ctx->hwparams, &(ctx->chans_n));
         if (status < 0) {
-            return SDL_SetError("ALSA: Couldn't set audio channels: %s", ALSA_snd_strerror(status));
+            SDL_SetError("ALSA: Couldn't set audio channels: %s", ALSA_snd_strerror(status));
+            return -1;
         }
         // let alsa approximate the audio rate
         ctx->rate = ctx->device->spec.freq;
         status = ALSA_snd_pcm_hw_params_set_rate_near(ctx->device->hidden->pcm,
                                                                 ctx->hwparams, &(ctx->rate), NULL);
         if (status < 0) {
-            return SDL_SetError("ALSA: Couldn't set audio frequency: %s", ALSA_snd_strerror(status));
+            SDL_SetError("ALSA: Couldn't set audio frequency: %s", ALSA_snd_strerror(status));
+            return -1;
         }
         // let approximate the period size to the requested buffer size
         ctx->persize = ctx->device->sample_frames;
         status = ALSA_snd_pcm_hw_params_set_period_size_near(ctx->device->hidden->pcm,
                                                             ctx->hwparams, &(ctx->persize), NULL);
         if (status < 0) {
-            return SDL_SetError("ALSA: Couldn't set the period size: %s", ALSA_snd_strerror(status));
+            SDL_SetError("ALSA: Couldn't set the period size: %s", ALSA_snd_strerror(status));
+            return -1;
         }
         // let approximate the minimun number of periods per buffer (we target a double buffer)
         ctx->periods = 2;
         status = ALSA_snd_pcm_hw_params_set_periods_min(ctx->device->hidden->pcm,
                                                             ctx->hwparams, &(ctx->periods), NULL);
         if (status < 0) {
-            return SDL_SetError("ALSA: Couldn't set the minimum number of periods per buffer: %s", ALSA_snd_strerror(status));
+            SDL_SetError("ALSA: Couldn't set the minimum number of periods per buffer: %s", ALSA_snd_strerror(status));
+            return -1;
         }
         // restrict the number of periods per buffer to an approximation of the approximated minimum
         // number of periods per buffer done right above
         status = ALSA_snd_pcm_hw_params_set_periods_first(ctx->device->hidden->pcm,
                                                             ctx->hwparams, &(ctx->periods), NULL);
         if (status < 0) {
-            return SDL_SetError("ALSA: Couldn't set the number of periods per buffer: %s", ALSA_snd_strerror(status));
+            SDL_SetError("ALSA: Couldn't set the number of periods per buffer: %s", ALSA_snd_strerror(status));
+            return -1;
         }
         // install the hw parameters
         status = ALSA_snd_pcm_hw_params(ctx->device->hidden->pcm, ctx->hwparams);
         if (status < 0) {
-            return SDL_SetError("ALSA: installation of hardware parameter failed: %s", ALSA_snd_strerror(status));
+            SDL_SetError("ALSA: installation of hardware parameter failed: %s", ALSA_snd_strerror(status));
+            return -1;
         }
         //==========================================================================================
         // 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 reduce the number of channels though.
+        // SDL channel map, it may request to change the target number of channels though.
         status = alsa_chmap_cfg(ctx);
         if (status < 0)
             return status; // we forward the SDL error
         if (status == CHMAP_INSTALLED)
-            return 0; // we are finished here
-        // status == REDUCE_CHANS_N
-        LOGDEBUG("reducing target chans_n to %u\n",target_chans_n-1);
+            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
+
+        if (mode == CHANS_N_SCAN_MODE__EQUAL_OR_ABOVE_REQUESTED_CHANS_N) {
+            ++target_chans_n;
+        } else {/* CHANS_N_SCAN_MODE__BELOW_REQUESTED_CHANS_N */
         target_chans_n--;
     }
+    }
 }
 #undef CHMAP_INSTALLED
-#undef REDUCE_CHANS_N
+#undef CHANS_N_NEXT
 #undef CHMAP_NOT_FOUND
 
+static int ALSA_pcm_cfg_hw(struct ALSA_pcm_cfg_ctx *ctx)
+{
+    int status;
+
+    LOGDEBUG("target chans_n, equal or above requested chans_n mode\n");
+    status = ALSA_pcm_cfg_hw_chans_n_scan(ctx, CHANS_N_SCAN_MODE__EQUAL_OR_ABOVE_REQUESTED_CHANS_N);
+    if (status < 0) /* something went too wrong */
+        return status;
+    if (status == CHANS_N_CONFIGURED)
+        return 0;
+    /* Here, status == CHANS_N_NOT_CONFIGURED */
+    LOGDEBUG("target chans_n, below requested chans_n mode\n");
+    status = ALSA_pcm_cfg_hw_chans_n_scan(ctx, CHANS_N_SCAN_MODE__BELOW_REQUESTED_CHANS_N);
+    if (status < 0) /* something went too wrong */
+        return status;
+    if (status == CHANS_N_CONFIGURED)
+        return 0;
+    /* Here, status == CHANS_N_NOT_CONFIGURED */
+    SDL_SetError("ALSA: Coudn't configure targetting any SDL supported channel number");
+    return -1;
+}
+#undef CHANS_N_SCAN_MODE__EQUAL_OR_ABOVE_REQUESTED_CHANS_N
+#undef CHANS_N_SCAN_MODE__BELOW_REQUESTED_CHANS_N
+#undef CHANS_N_CONFIGURED
+#undef CHANS_N_NOT_CONFIGURED
+
 static int ALSA_pcm_cfg_sw(struct ALSA_pcm_cfg_ctx *ctx)
 {
     int status;
 
     status = ALSA_snd_pcm_sw_params_current(ctx->device->hidden->pcm, ctx->swparams);
     if (status < 0) {
-        return SDL_SetError("ALSA: Couldn't get software config: %s", ALSA_snd_strerror(status));
+        SDL_SetError("ALSA: Couldn't get software config: %s", ALSA_snd_strerror(status));
+        return -1;
     }
     status = ALSA_snd_pcm_sw_params_set_avail_min(ctx->device->hidden->pcm, ctx->swparams,
                                                                                     ctx->persize); // will become device->sample_frames if the alsa pcm configuration is successful
     if (status < 0) {
-        return SDL_SetError("Couldn't set minimum available samples: %s", ALSA_snd_strerror(status));
+        SDL_SetError("Couldn't set minimum available samples: %s", ALSA_snd_strerror(status));
+        return -1;
     }
     status = ALSA_snd_pcm_sw_params_set_start_threshold(ctx->device->hidden->pcm,
                                                                                 ctx->swparams, 1);
     if (status < 0) {
-        return SDL_SetError("ALSA: Couldn't set start threshold: %s", ALSA_snd_strerror(status));
+        SDL_SetError("ALSA: Couldn't set start threshold: %s", ALSA_snd_strerror(status));
+        return -1;
     }
     status = ALSA_snd_pcm_sw_params(ctx->device->hidden->pcm, ctx->swparams);
     if (status < 0) {
-        return SDL_SetError("Couldn't set software audio parameters: %s", ALSA_snd_strerror(status));
+        SDL_SetError("Couldn't set software audio parameters: %s", ALSA_snd_strerror(status));
+        return -1;
     }
     return 0;
 }