Le dim. 17 mai 2026 à 05:49, Nicolas George via ffmpeg-devel <
[email protected]> a écrit :
>
> 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?
>
Yes, Most drivers do not implement this yet it seems.
>
> > +
> > + for (unsigned i = 0; i < chmap->channels; i++) {
>
> Need to check that chmap->channels and layout->nb_channels match.
Adding. Worth noting that, at the moment, this function is only called
after setting the number of channels but erroring makes it more general so
it's cool.
> > + 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.
Good point addressed.
> > + }
> > + 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.
This is different here, the mapping is done explicitly below.
However, we were skipping AV_CHANNEL_ORDER_CUSTOM. This is fixed.
> > +
> > + 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,
Thanks for your time looking into this!
> --
> Nicolas George
> _______________________________________________
> ffmpeg-devel mailing list -- [email protected]
> To unsubscribe send an email to [email protected]
_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]