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]
