This is an automated email from the git hooks/post-receive script.
Git pushed a commit to branch master
in repository ffmpeg.
The following commit(s) were added to refs/heads/master by this push:
new 1f24840239 avdevice/alsa: auto-detect channel layout when capturing
audio
1f24840239 is described below
commit 1f2484023936c4da95035459ebf11fc7730f18af
Author: Romain Beauxis <[email protected]>
AuthorDate: Wed May 13 08:18:49 2026 -0400
Commit: Romain Beauxis <[email protected]>
CommitDate: Sat Jul 11 08:45:54 2026 -0500
avdevice/alsa: auto-detect channel layout when capturing audio
---
doc/indevs.texi | 6 ++-
libavdevice/alsa.c | 118 +++++++++++++++++++++++++++++++++++++++++++++++--
libavdevice/alsa.h | 5 ++-
libavdevice/alsa_dec.c | 2 +-
4 files changed, 123 insertions(+), 8 deletions(-)
diff --git a/doc/indevs.texi b/doc/indevs.texi
index 8822e070fe..7c3c7114a4 100644
--- a/doc/indevs.texi
+++ b/doc/indevs.texi
@@ -58,8 +58,10 @@ For more information see:
@item sample_rate
Set the sample rate in Hz. Default is 48000.
-@item channels
-Set the number of channels. Default is 2.
+@item ch_layout
+Set the channel layout. When unset, the layout defaults to stereo, or to the
+minimum channel count supported by the device, and is filled in from the ALSA
+channel map on a best-effort basis.
@end table
diff --git a/libavdevice/alsa.c b/libavdevice/alsa.c
index 966eea519a..a18b532375 100644
--- a/libavdevice/alsa.c
+++ b/libavdevice/alsa.c
@@ -127,6 +127,88 @@ 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 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 av_cold int find_reorder_func(AlsaData *s, int codec_id,
const AVChannelLayout *layout, int out)
{
@@ -173,7 +255,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 +275,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 +320,24 @@ av_cold int ff_alsa_open(AVFormatContext *ctx,
snd_pcm_stream_t mode,
goto fail;
}
+ // For output streams, the stream should already have a layout.
+ // Adding this check for clarity.
+ if (mode == SND_PCM_STREAM_CAPTURE && 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 +345,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 +377,19 @@ 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 supersede the re-ordering function
+ // below.
+ if (mode == SND_PCM_STREAM_CAPTURE &&
+ layout->order == AV_CHANNEL_ORDER_UNSPEC) {
+ res = alsa_get_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
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 f6a85616a8..a1fa667c04 100644
--- a/libavdevice/alsa.h
+++ b/libavdevice/alsa.h
@@ -69,7 +69,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
@@ -79,7 +80,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 e3f73911de..7411fcc5fb 100644
--- a/libavdevice/alsa_dec.c
+++ b/libavdevice/alsa_dec.c
@@ -150,7 +150,7 @@ static int audio_get_device_list(AVFormatContext *h,
AVDeviceInfoList *device_li
static const AVOption options[] = {
{ "sample_rate", "", offsetof(AlsaData, sample_rate), AV_OPT_TYPE_INT,
{.i64 = 48000}, 1, INT_MAX, AV_OPT_FLAG_DECODING_PARAM },
- { "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 },
};
_______________________________________________
ffmpeg-cvslog mailing list -- [email protected]
To unsubscribe send an email to [email protected]