PR #23356 opened by toots
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23356
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23356.patch


>From 48b8d19af32592b1e7c5a75e74a61b0f0fce096e Mon Sep 17 00:00:00 2001
From: Romain Beauxis <[email protected]>
Date: Wed, 13 May 2026 08:18:49 -0400
Subject: [PATCH] avdevice/alsa: auto-detect channel layout.

---
 doc/outdevs.texi       |   7 ++
 libavdevice/alsa.c     | 154 ++++++++++++++++++++++++++++++++++++++++-
 libavdevice/alsa.h     |   5 +-
 libavdevice/alsa_dec.c |   2 +-
 4 files changed, 162 insertions(+), 6 deletions(-)

diff --git a/doc/outdevs.texi b/doc/outdevs.texi
index 86c78f31b7..6bd0e880e7 100644
--- a/doc/outdevs.texi
+++ b/doc/outdevs.texi
@@ -22,6 +22,13 @@ A description of the currently available output devices 
follows.
 
 ALSA (Advanced Linux Sound Architecture) output device.
 
+The channel layout is taken from the output stream. If no channel layout is
+set, the device will attempt to use stereo (2 channels), falling back to
+the minimum channel count supported by the device. When a channel layout is
+specified, the device will attempt to configure the ALSA channel map
+accordingly, though support for channel layout in ALSA drivers is still
+sparse and this is done on a best-effort basis.
+
 @subsection Examples
 
 @itemize
diff --git a/libavdevice/alsa.c b/libavdevice/alsa.c
index 966eea519a..4acbcd71a2 100644
--- a/libavdevice/alsa.c
+++ b/libavdevice/alsa.c
@@ -127,6 +127,123 @@ switch(format) {\
     case FORMAT_F32: s->reorder_func = alsa_reorder_f32_out_ ##layout;   
break;\
 }
 
+static const struct {
+    unsigned int   pos;
+    enum AVChannel ch;
+} alsa_chmap_table[] = {
+    { SND_CHMAP_MONO,    AV_CHAN_FRONT_CENTER         },
+    { SND_CHMAP_FL,      AV_CHAN_FRONT_LEFT           },
+    { SND_CHMAP_FR,      AV_CHAN_FRONT_RIGHT          },
+    { SND_CHMAP_RL,      AV_CHAN_BACK_LEFT            },
+    { SND_CHMAP_RR,      AV_CHAN_BACK_RIGHT           },
+    { SND_CHMAP_FC,      AV_CHAN_FRONT_CENTER         },
+    { SND_CHMAP_LFE,     AV_CHAN_LOW_FREQUENCY        },
+    { SND_CHMAP_SL,      AV_CHAN_SIDE_LEFT            },
+    { SND_CHMAP_SR,      AV_CHAN_SIDE_RIGHT           },
+    { SND_CHMAP_RC,      AV_CHAN_BACK_CENTER          },
+    { SND_CHMAP_FLC,     AV_CHAN_FRONT_LEFT_OF_CENTER },
+    { SND_CHMAP_FRC,     AV_CHAN_FRONT_RIGHT_OF_CENTER},
+    { SND_CHMAP_FLW,     AV_CHAN_WIDE_LEFT            },
+    { SND_CHMAP_FRW,     AV_CHAN_WIDE_RIGHT           },
+    { SND_CHMAP_TC,      AV_CHAN_TOP_CENTER           },
+    { SND_CHMAP_TFL,     AV_CHAN_TOP_FRONT_LEFT       },
+    { SND_CHMAP_TFR,     AV_CHAN_TOP_FRONT_RIGHT      },
+    { SND_CHMAP_TFC,     AV_CHAN_TOP_FRONT_CENTER     },
+    { SND_CHMAP_TRL,     AV_CHAN_TOP_BACK_LEFT        },
+    { SND_CHMAP_TRR,     AV_CHAN_TOP_BACK_RIGHT       },
+    { SND_CHMAP_TRC,     AV_CHAN_TOP_BACK_CENTER      },
+    { SND_CHMAP_TSL,     AV_CHAN_TOP_SIDE_LEFT        },
+    { SND_CHMAP_TSR,     AV_CHAN_TOP_SIDE_RIGHT       },
+    { SND_CHMAP_LLFE,    AV_CHAN_LOW_FREQUENCY        },
+    { SND_CHMAP_RLFE,    AV_CHAN_LOW_FREQUENCY_2      },
+    { SND_CHMAP_BC,      AV_CHAN_BOTTOM_FRONT_CENTER  },
+    { SND_CHMAP_BLC,     AV_CHAN_BOTTOM_FRONT_LEFT    },
+    { SND_CHMAP_BRC,     AV_CHAN_BOTTOM_FRONT_RIGHT   },
+    { SND_CHMAP_UNKNOWN, AV_CHAN_UNKNOWN              },
+    { SND_CHMAP_NA,      AV_CHAN_UNUSED               },
+};
+
+static enum AVChannel alsa_chmap_pos_to_av_chan(unsigned int pos)
+{
+    for (unsigned i = 0; i < FF_ARRAY_ELEMS(alsa_chmap_table); i++)
+        if (alsa_chmap_table[i].pos == pos)
+            return alsa_chmap_table[i].ch;
+    return AV_CHAN_NONE;
+}
+
+static unsigned int alsa_av_chan_to_chmap_pos(enum AVChannel ch)
+{
+    for (unsigned i = 0; i < FF_ARRAY_ELEMS(alsa_chmap_table); i++)
+        if (alsa_chmap_table[i].ch == ch)
+            return alsa_chmap_table[i].pos;
+    return SND_CHMAP_UNKNOWN;
+}
+
+static int alsa_get_chmap(AVFormatContext *ctx, snd_pcm_t *h, AVChannelLayout 
*layout)
+{
+    snd_pcm_chmap_t *chmap = snd_pcm_get_chmap(h);
+    int ret = 0;
+
+    if (!chmap)
+        return 0;
+
+    if (layout->nb_channels != chmap->channels) {
+        av_log(ctx, AV_LOG_ERROR, "Inconsistent channel layout requested!\n");
+        ret = AVERROR(EINVAL);
+        goto out;
+    }
+
+    av_channel_layout_uninit(layout);
+    ret = av_channel_layout_custom_init(layout, chmap->channels);
+    if (ret < 0)
+        goto out;
+
+    for (unsigned i = 0; i < chmap->channels; i++) {
+        enum AVChannel ch = alsa_chmap_pos_to_av_chan(chmap->pos[i] &
+                                                      SND_CHMAP_POSITION_MASK);
+        if (ch == AV_CHAN_NONE) {
+            av_log(ctx, AV_LOG_WARNING,
+                   "unknown ALSA channel position %u.\n",
+                   chmap->pos[i] & SND_CHMAP_POSITION_MASK);
+            continue;
+        }
+        layout->u.map[i].id = ch;
+    }
+    ret = av_channel_layout_retype(layout, 0,
+                                   AV_CHANNEL_LAYOUT_RETYPE_FLAG_CANONICAL);
+
+out:
+    free(chmap);
+    return ret;
+}
+
+static int alsa_set_chmap(AVFormatContext *ctx, snd_pcm_t *h,
+                           const AVChannelLayout *layout)
+{
+    snd_pcm_chmap_t *chmap;
+
+    chmap = av_malloc(sizeof(*chmap) + layout->nb_channels * 
sizeof(chmap->pos[0]));
+    if (!chmap)
+        return AVERROR(ENOMEM);
+
+    chmap->channels = layout->nb_channels;
+    for (int i = 0; i < layout->nb_channels; i++) {
+        enum AVChannel ch = av_channel_layout_channel_from_index(layout, i);
+        chmap->pos[i] = alsa_av_chan_to_chmap_pos(ch);
+    }
+
+    if (snd_pcm_set_chmap(h, chmap) < 0)
+        // channels count still match the layout and not many drivers currently
+        // support explicit channel layout setting so not erroring for now.
+        av_log(ctx, AV_LOG_WARNING,
+               "ALSA driver does not implement the requested channel layout "
+               "configuration!\n");
+
+    av_free(chmap);
+
+    return 0;
+}
+
 static av_cold int find_reorder_func(AlsaData *s, int codec_id,
                                      const AVChannelLayout *layout, int out)
 {
@@ -173,7 +290,7 @@ static av_cold int find_reorder_func(AlsaData *s, int 
codec_id,
 
 av_cold int ff_alsa_open(AVFormatContext *ctx, snd_pcm_stream_t mode,
                          unsigned int *sample_rate,
-                         const AVChannelLayout *layout, enum AVCodecID 
*codec_id)
+                         AVChannelLayout *layout, enum AVCodecID *codec_id)
 {
     AlsaData *s = ctx->priv_data;
     const char *audio_device;
@@ -193,8 +310,6 @@ av_cold int ff_alsa_open(AVFormatContext *ctx, 
snd_pcm_stream_t mode,
         av_log(ctx, AV_LOG_ERROR, "sample format 0x%04x is not supported\n", 
*codec_id);
         return AVERROR(ENOSYS);
     }
-    s->frame_size = av_get_bits_per_sample(*codec_id) / 8 * 
layout->nb_channels;
-
     if (ctx->flags & AVFMT_FLAG_NONBLOCK) {
         flags = SND_PCM_NONBLOCK;
     }
@@ -240,6 +355,22 @@ av_cold int ff_alsa_open(AVFormatContext *ctx, 
snd_pcm_stream_t mode,
         goto fail;
     }
 
+    if (layout->nb_channels == 0 && mode == SND_PCM_STREAM_CAPTURE)) {
+        unsigned int channels = 2;
+        if (snd_pcm_hw_params_test_channels(h, hw_params, channels) < 0) {
+            res = snd_pcm_hw_params_get_channels_min(hw_params, &channels);
+            if (res < 0) {
+                av_log(ctx, AV_LOG_ERROR, "cannot get minimum channel count 
(%s)\n",
+                       snd_strerror(res));
+                goto fail;
+            }
+            av_log(ctx, AV_LOG_VERBOSE,
+                   "stereo not supported, falling back to %u channel(s)\n", 
channels);
+        }
+        layout->order       = AV_CHANNEL_ORDER_UNSPEC;
+        layout->nb_channels = channels;
+    }
+
     res = snd_pcm_hw_params_set_channels(h, hw_params, layout->nb_channels);
     if (res < 0) {
         av_log(ctx, AV_LOG_ERROR, "cannot set channel count to %d (%s)\n",
@@ -247,6 +378,8 @@ av_cold int ff_alsa_open(AVFormatContext *ctx, 
snd_pcm_stream_t mode,
         goto fail;
     }
 
+    s->frame_size = av_get_bits_per_sample(*codec_id) / 8 * 
layout->nb_channels;
+
     snd_pcm_hw_params_get_buffer_size_max(hw_params, &buffer_size);
     buffer_size = FFMIN(buffer_size, ALSA_BUFFER_SIZE_MAX);
     /* TODO: maybe use ctx->max_picture_buffer somehow */
@@ -277,7 +410,22 @@ av_cold int ff_alsa_open(AVFormatContext *ctx, 
snd_pcm_stream_t mode,
 
     snd_pcm_hw_params_free(hw_params);
 
+    // TODO: when support for channel layout gets more widespread in alsa
+    // drivers, this might be used to superseed the re-ordering function
+    // below.
+    if (mode != SND_PCM_STREAM_PLAYBACK) {
+        if (layout->order == AV_CHANNEL_ORDER_UNSPEC)
+            res = alsa_get_chmap(ctx, h, layout);
+        else
+            res = alsa_set_chmap(ctx, h, layout);
+
+        if (res < 0)
+            goto fail1;
+    }
+
     if (layout->nb_channels > 2 && layout->order != AV_CHANNEL_ORDER_UNSPEC) {
+        // See comment above. find_reorder_func is currently only implemented 
for
+        // in playback mode.
         if (find_reorder_func(s, *codec_id, layout, mode == 
SND_PCM_STREAM_PLAYBACK) < 0) {
             char name[128];
             av_channel_layout_describe(layout, name, sizeof(name));
diff --git a/libavdevice/alsa.h b/libavdevice/alsa.h
index d3dfa478c5..ed2ac66e93 100644
--- a/libavdevice/alsa.h
+++ b/libavdevice/alsa.h
@@ -72,7 +72,8 @@ typedef struct AlsaData {
  * @param mode either SND_PCM_STREAM_CAPTURE or SND_PCM_STREAM_PLAYBACK
  * @param sample_rate in: requested sample rate;
  *                    out: actually selected sample rate
- * @param layout channel layout
+ * @param layout in: requested channel layout, or nb_channels=0 for 
auto-detect;
+ *               out: actually selected channel layout
  * @param codec_id in: requested AVCodecID or AV_CODEC_ID_NONE;
  *                 out: actually selected AVCodecID, changed only if
  *                 AV_CODEC_ID_NONE was requested
@@ -82,7 +83,7 @@ typedef struct AlsaData {
 av_warn_unused_result
 int ff_alsa_open(AVFormatContext *s, snd_pcm_stream_t mode,
                  unsigned int *sample_rate,
-                 const AVChannelLayout *layout, enum AVCodecID *codec_id);
+                 AVChannelLayout *layout, enum AVCodecID *codec_id);
 
 /**
  * Close the ALSA PCM.
diff --git a/libavdevice/alsa_dec.c b/libavdevice/alsa_dec.c
index 63409a7785..54d9b3a783 100644
--- a/libavdevice/alsa_dec.c
+++ b/libavdevice/alsa_dec.c
@@ -160,7 +160,7 @@ static const AVOption options[] = {
 #if FF_API_ALSA_CHANNELS
     { "channels",    "", offsetof(AlsaData, channels),    AV_OPT_TYPE_INT, 
{.i64 = 0},     0, INT_MAX, AV_OPT_FLAG_DECODING_PARAM | AV_OPT_FLAG_DEPRECATED 
},
 #endif
-    { "ch_layout",   "", offsetof(AlsaData, ch_layout),   
AV_OPT_TYPE_CHLAYOUT, {.str = "2C"}, INT_MIN, INT_MAX, 
AV_OPT_FLAG_DECODING_PARAM },
+    { "ch_layout",   "", offsetof(AlsaData, ch_layout),   
AV_OPT_TYPE_CHLAYOUT, {.str = NULL}, INT_MIN, INT_MAX, 
AV_OPT_FLAG_DECODING_PARAM },
     { NULL },
 };
 
-- 
2.52.0

_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to