Sent to the mailing-list since the web shit discarded the mail I sent.
> From 77e06052e0269ad7a49857d879de6c5cf792c052 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.
>
> ---
> libavdevice/alsa.c | 140 ++++++++++++++++++++++++++++++++++++++++-
> libavdevice/alsa.h | 5 +-
> libavdevice/alsa_dec.c | 2 +-
> 3 files changed, 141 insertions(+), 6 deletions(-)
>
> diff --git a/libavdevice/alsa.c b/libavdevice/alsa.c
> index 966eea519a..4b10fdf3e5 100644
> --- a/libavdevice/alsa.c
> +++ b/libavdevice/alsa.c
> @@ -127,6 +127,119 @@ switch(format) {\
> case FORMAT_F32: s->reorder_func = alsa_reorder_f32_out_ ##layout;
> break;\
> }
>
> +static enum AVChannel alsa_chmap_pos_to_av_chan(unsigned int pos)
> +{
> + switch (pos) {
> + case SND_CHMAP_MONO: return AV_CHAN_FRONT_CENTER;
> + case SND_CHMAP_FL: return AV_CHAN_FRONT_LEFT;
> + case SND_CHMAP_FR: return AV_CHAN_FRONT_RIGHT;
> + case SND_CHMAP_RL: return AV_CHAN_BACK_LEFT;
> + case SND_CHMAP_RR: return AV_CHAN_BACK_RIGHT;
> + case SND_CHMAP_FC: return AV_CHAN_FRONT_CENTER;
> + case SND_CHMAP_LFE: return AV_CHAN_LOW_FREQUENCY;
> + case SND_CHMAP_SL: return AV_CHAN_SIDE_LEFT;
> + case SND_CHMAP_SR: return AV_CHAN_SIDE_RIGHT;
> + case SND_CHMAP_RC: return AV_CHAN_BACK_CENTER;
> + case SND_CHMAP_FLC: return AV_CHAN_FRONT_LEFT_OF_CENTER;
> + case SND_CHMAP_FRC: return AV_CHAN_FRONT_RIGHT_OF_CENTER;
> + case SND_CHMAP_FLW: return AV_CHAN_WIDE_LEFT;
> + case SND_CHMAP_FRW: return AV_CHAN_WIDE_RIGHT;
> + case SND_CHMAP_TC: return AV_CHAN_TOP_CENTER;
> + case SND_CHMAP_TFL: return AV_CHAN_TOP_FRONT_LEFT;
> + case SND_CHMAP_TFR: return AV_CHAN_TOP_FRONT_RIGHT;
> + case SND_CHMAP_TFC: return AV_CHAN_TOP_FRONT_CENTER;
> + case SND_CHMAP_TRL: return AV_CHAN_TOP_BACK_LEFT;
> + case SND_CHMAP_TRR: return AV_CHAN_TOP_BACK_RIGHT;
> + case SND_CHMAP_TRC: return AV_CHAN_TOP_BACK_CENTER;
> + case SND_CHMAP_TSL: return AV_CHAN_TOP_SIDE_LEFT;
> + case SND_CHMAP_TSR: return AV_CHAN_TOP_SIDE_RIGHT;
> + case SND_CHMAP_LLFE: return AV_CHAN_LOW_FREQUENCY;
> + case SND_CHMAP_RLFE: return AV_CHAN_LOW_FREQUENCY_2;
> + case SND_CHMAP_BC: return AV_CHAN_BOTTOM_FRONT_CENTER;
> + case SND_CHMAP_BLC: return AV_CHAN_BOTTOM_FRONT_LEFT;
> + case SND_CHMAP_BRC: return AV_CHAN_BOTTOM_FRONT_RIGHT;
> + default: return AV_CHAN_NONE;
> + }
> +}
> +
> +static void alsa_get_chmap(AVFormatContext *ctx, snd_pcm_t *h,
> AVChannelLayout *layout)
> +{
> + snd_pcm_chmap_t *chmap = snd_pcm_get_chmap(h);
> + uint64_t mask = 0;
> +
> + if (!chmap)
> + return;
No warning? Is it something that can fail in a normal setting?
> +
> + for (unsigned i = 0; i < chmap->channels; i++) {
Need to check that chmap->channels and layout->nb_channels match.
> + 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_VERBOSE,
> + "unknown ALSA channel position %u, keeping unspecified
> layout\n",
> + chmap->pos[i] & SND_CHMAP_POSITION_MASK);
> + free(chmap);
> + return;
> + }
> + mask |= 1ULL << ch;
This will not work: you are checking what channels are present, but
assuming they are in the same order. They are not in the same order, it
is the reason we have all the reorder functions.
> + }
> + free(chmap);
> +
> + av_channel_layout_uninit(layout);
> + av_channel_layout_from_mask(layout, mask);
> +}
> +
> +static void alsa_set_chmap(AVFormatContext *ctx, snd_pcm_t *h,
> + const AVChannelLayout *layout)
> +{
> + snd_pcm_chmap_t *chmap;
> +
> + if (layout->order != AV_CHANNEL_ORDER_NATIVE)
> + return;
Same as above: the native order of ffmpeg is not the standard order of
ALSA.
> +
> + chmap = av_malloc(sizeof(*chmap) + layout->nb_channels *
> sizeof(chmap->pos[0]));
> + if (!chmap)
> + return;
> +
> + 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);
> + switch (ch) {
> + case AV_CHAN_FRONT_LEFT: chmap->pos[i] = SND_CHMAP_FL;
> break;
Concerns have been expressed in the past about UB when accessing an
array like that, but I do not believe in them.
> + case AV_CHAN_FRONT_RIGHT: chmap->pos[i] = SND_CHMAP_FR;
> break;
> + case AV_CHAN_BACK_LEFT: chmap->pos[i] = SND_CHMAP_RL;
> break;
> + case AV_CHAN_BACK_RIGHT: chmap->pos[i] = SND_CHMAP_RR;
> break;
> + case AV_CHAN_FRONT_CENTER: chmap->pos[i] = SND_CHMAP_FC;
> break;
> + case AV_CHAN_LOW_FREQUENCY: chmap->pos[i] = SND_CHMAP_LFE;
> break;
> + case AV_CHAN_SIDE_LEFT: chmap->pos[i] = SND_CHMAP_SL;
> break;
> + case AV_CHAN_SIDE_RIGHT: chmap->pos[i] = SND_CHMAP_SR;
> break;
> + case AV_CHAN_BACK_CENTER: chmap->pos[i] = SND_CHMAP_RC;
> break;
> + case AV_CHAN_FRONT_LEFT_OF_CENTER: chmap->pos[i] = SND_CHMAP_FLC;
> break;
> + case AV_CHAN_FRONT_RIGHT_OF_CENTER: chmap->pos[i] = SND_CHMAP_FRC;
> break;
> + case AV_CHAN_WIDE_LEFT: chmap->pos[i] = SND_CHMAP_FLW;
> break;
> + case AV_CHAN_WIDE_RIGHT: chmap->pos[i] = SND_CHMAP_FRW;
> break;
> + case AV_CHAN_TOP_CENTER: chmap->pos[i] = SND_CHMAP_TC;
> break;
> + case AV_CHAN_TOP_FRONT_LEFT: chmap->pos[i] = SND_CHMAP_TFL;
> break;
> + case AV_CHAN_TOP_FRONT_RIGHT: chmap->pos[i] = SND_CHMAP_TFR;
> break;
> + case AV_CHAN_TOP_FRONT_CENTER: chmap->pos[i] = SND_CHMAP_TFC;
> break;
> + case AV_CHAN_TOP_BACK_LEFT: chmap->pos[i] = SND_CHMAP_TRL;
> break;
> + case AV_CHAN_TOP_BACK_RIGHT: chmap->pos[i] = SND_CHMAP_TRR;
> break;
> + case AV_CHAN_TOP_BACK_CENTER: chmap->pos[i] = SND_CHMAP_TRC;
> break;
> + case AV_CHAN_TOP_SIDE_LEFT: chmap->pos[i] = SND_CHMAP_TSL;
> break;
> + case AV_CHAN_TOP_SIDE_RIGHT: chmap->pos[i] = SND_CHMAP_TSR;
> break;
> + case AV_CHAN_LOW_FREQUENCY_2: chmap->pos[i] = SND_CHMAP_RLFE;
> break;
> + case AV_CHAN_BOTTOM_FRONT_CENTER: chmap->pos[i] = SND_CHMAP_BC;
> break;
> + case AV_CHAN_BOTTOM_FRONT_LEFT: chmap->pos[i] = SND_CHMAP_BLC;
> break;
> + case AV_CHAN_BOTTOM_FRONT_RIGHT: chmap->pos[i] = SND_CHMAP_BRC;
> break;
> + default:
> + av_free(chmap);
> + return;
> + }
> + }
> +
> + if (snd_pcm_set_chmap(h, chmap) < 0)
> + av_log(ctx, AV_LOG_VERBOSE, "failed to set channel map\n");
> + av_free(chmap);
> +}
> +
> static av_cold int find_reorder_func(AlsaData *s, int codec_id,
> const AVChannelLayout *layout, int out)
> {
> @@ -173,7 +286,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 +306,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 +351,22 @@ av_cold int ff_alsa_open(AVFormatContext *ctx,
> snd_pcm_stream_t mode,
> goto fail;
> }
>
> + if (layout->nb_channels == 0) {
> + 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 +374,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,6 +406,11 @@ av_cold int ff_alsa_open(AVFormatContext *ctx,
> snd_pcm_stream_t mode,
>
> snd_pcm_hw_params_free(hw_params);
>
> + if (layout->order == AV_CHANNEL_ORDER_UNSPEC)
> + alsa_get_chmap(ctx, h, layout);
> + else
> + alsa_set_chmap(ctx, h, layout);
> +
> if (layout->nb_channels > 2 && layout->order != AV_CHANNEL_ORDER_UNSPEC)
> {
> if (find_reorder_func(s, *codec_id, layout, mode ==
> SND_PCM_STREAM_PLAYBACK) < 0) {
> char name[128];
> 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 },
> };
Regards,
--
Nicolas George
_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]