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]

Reply via email to