《FFmpeg原理》的社群来了,想加入社群的朋友请购买 VIP 版,VIP 版有更高级的内容与答疑服务。
本文 以 ffmpeg4.4 源码为准,a.mp4下载链接:百度网盘,提取码:nl0s 。
命令如下:
ffmpeg.exe -i a.mp4 -map_channel 0.1.0 left.aac -map_channel 0.1.1 right.aac
上面的命令 是把 a.mp4 里面的左右声道分布存储在 left.aac ,right.aac 里面。
下面主要分析 一下 -map_channel 0.1.0
参数的作用。
在 ffmpeg_opt.c 的定义如下:
{ "map_channel", HAS_ARG | OPT_EXPERT | OPT_PERFILE | OPT_OUTPUT, { .func_arg = opt_map_channel },
"map an audio channel from one stream to another", "file.stream.channel[:syncfile.syncstream]" },
从定义得知,0.1.0 的意思如下:
- 0 代表第一个输入文件。
- 1 代表输入文件的第二个流。
- 0 代表流的第一个声道。
从上面定义可以看出,-map_channel
会调用 opt_map_channel()
函数,这个函数的逻辑比较简单,不进行分析了,自动查看即可。
opt_map_channel()
里面做的是就是 操作两个变量,o->audio_channel_maps
跟 o->nb_audio_channel_maps
,定义如下:
typedef struct {
int file_idx, stream_idx, channel_idx; // input
int ofile_idx, ostream_idx; // output
} AudioChannelMap;
下面继续分析 ,audio_channel_maps
变量在什么地方用到。全局搜索可知,audio_channel_maps
在 new_audio_stream()
函数里面使用了。
new_audio_stream()
函数相对来说也是比较简单,主要就是把 o->audio_channel_maps
转移给 ost->audio_channels_map
,重点代码如下:
//ffmpeg_opt.c 1964行
ost->audio_channels_map[ost->audio_channels_mapped++] = map->channel_idx;
ost->audio_channels_map
的作用是这样的,音频流(输出)的声道 默认是 取 的 输入音频流的全部声道,这个 ost->audio_channels_map
就是表明,我这个输出流 只取 输入流的哪些声道。
下面继续分析 ost->audio_channels_map
在哪些地方有被使用。全局搜索可知,ost->audio_channels_map
主要在 ffmpeg_filter.c
文件的 configure_output_audio_filter()
函数里面使用,重点代码如下:
#define AUTO_INSERT_FILTER(opt_name, filter_name, arg) do { \
AVFilterContext *filt_ctx; \
\
av_log(NULL, AV_LOG_INFO, opt_name " is forwarded to lavfi " \
"similarly to -af " filter_name "=%s.\n", arg); \
\
ret = avfilter_graph_create_filter(&filt_ctx, \
avfilter_get_by_name(filter_name), \
filter_name, arg, NULL, fg->graph); \
if (ret < 0) \
return ret; \
\
ret = avfilter_link(last_filter, pad_idx, filt_ctx, 0); \
if (ret < 0) \
return ret; \
\
last_filter = filt_ctx; \
pad_idx = 0; \
} while (0)
//ffmpeg_filter.c 567行
if (ost->audio_channels_mapped) {
int i;
AVBPrint pan_buf;
av_bprint_init(&pan_buf, 256, 8192);
av_bprintf(&pan_buf, "0x%"PRIx64,
av_get_default_channel_layout(ost->audio_channels_mapped));
for (i = 0; i < ost->audio_channels_mapped; i++)
if (ost->audio_channels_map[i] != -1)
av_bprintf(&pan_buf, "|c%d=c%d", i, ost->audio_channels_map[i]);
AUTO_INSERT_FILTER("-map_channel", "pan", pan_buf.str);
av_bprint_finalize(&pan_buf, NULL);
}
从上面代码可以看出 调用了 pan filter
来实现这个这个 channel map 的功能。从代码的 log 可以看出,-map_channel 0.1.0
是一种过时的写法,主要为了与 lavfi
进行兼容,新的命令行语法如下:
ffmpeg.exe -i a.mp4 -af pan="0x4|c0=c0" left.aac -af pan="0x4|c0=c1" right.aac -y
上面的 0x4 声道布局的描述,是 AV_CH_FRONT_CENTER 的宏定义,如下:
#define AV_CH_FRONT_CENTER 0x00000004
声道数 对应 的声道布局 channel_layout_map 的定义在 源码 libavutil\channel_layout.c
里面,代码如下:
static const struct {
const char *name;
int nb_channels;
uint64_t layout;
} channel_layout_map[] = {
{ "mono", 1, AV_CH_LAYOUT_MONO },
{ "stereo", 2, AV_CH_LAYOUT_STEREO },
{ "2.1", 3, AV_CH_LAYOUT_2POINT1 },
{ "3.0", 3, AV_CH_LAYOUT_SURROUND },
{ "3.0(back)", 3, AV_CH_LAYOUT_2_1 },
{ "4.0", 4, AV_CH_LAYOUT_4POINT0 },
{ "quad", 4, AV_CH_LAYOUT_QUAD },
{ "quad(side)", 4, AV_CH_LAYOUT_2_2 },
{ "3.1", 4, AV_CH_LAYOUT_3POINT1 },
{ "5.0", 5, AV_CH_LAYOUT_5POINT0_BACK },
{ "5.0(side)", 5, AV_CH_LAYOUT_5POINT0 },
{ "4.1", 5, AV_CH_LAYOUT_4POINT1 },
{ "5.1", 6, AV_CH_LAYOUT_5POINT1_BACK },
{ "5.1(side)", 6, AV_CH_LAYOUT_5POINT1 },
{ "6.0", 6, AV_CH_LAYOUT_6POINT0 },
{ "6.0(front)", 6, AV_CH_LAYOUT_6POINT0_FRONT },
{ "hexagonal", 6, AV_CH_LAYOUT_HEXAGONAL },
{ "6.1", 7, AV_CH_LAYOUT_6POINT1 },
{ "6.1(back)", 7, AV_CH_LAYOUT_6POINT1_BACK },
{ "6.1(front)", 7, AV_CH_LAYOUT_6POINT1_FRONT },
{ "7.0", 7, AV_CH_LAYOUT_7POINT0 },
{ "7.0(front)", 7, AV_CH_LAYOUT_7POINT0_FRONT },
{ "7.1", 8, AV_CH_LAYOUT_7POINT1 },
{ "7.1(wide)", 8, AV_CH_LAYOUT_7POINT1_WIDE_BACK },
{ "7.1(wide-side)", 8, AV_CH_LAYOUT_7POINT1_WIDE },
{ "octagonal", 8, AV_CH_LAYOUT_OCTAGONAL },
{ "hexadecagonal", 16, AV_CH_LAYOUT_HEXADECAGONAL },
{ "downmix", 2, AV_CH_LAYOUT_STEREO_DOWNMIX, },
{ "22.2", 24, AV_CH_LAYOUT_22POINT2, },
};
map_channel 分析完毕,总结:通过 pan filter 实现。
本文命令最后还有一个知识点没有分析,就是 ffmpeg 命令行输出多文件的逻辑,请看《ffmpeg命令分析-输出多文件》
由于笔者的水平有限, 加之编写的同时还要参与开发工作,文中难免会出现一些错误或者不准确的地方,恳请读者批评指正。如果读者有任何宝贵意见,可以加我微信 Loken1。