本系列 以 ffmpeg4.2 源码为准,下载地址:链接:百度网盘 提取码:g3k8
ffmpeg 源码分析系列以一条简单的命令开始,ffmpeg -i a.mp4 b.flv
,分析其内部逻辑。
a.mp4下载链接:百度网盘,提取码:nl0s 。
前面《FFmpeg源码分析-环境搭建篇》专栏已经教大家如何搭建起来 ffmpeg.c 的断点调试环境。
本文主要分析 ffmpeg_parse_options()
转码的内部逻辑。
FFmpeg 的命令行参数 解析流程 如下图所示。
可以看到函数调用用流程图如下
main() -> avdevice_register_all() -> 一些初始化工作 -> ffmpeg_parse_option() -> transcode() -> exit_programe();
本文主要分析 split_commandline()
的内部逻辑。
这里需要先介绍 split_commandline()
函数用到的主要数据结构,
OptionParseContext
,OptionGroupList
,OptionGroup
,Option
,OptionGroupDef
这5个结构体是嵌套的关系,如图所示:
下面仔细分析 OptionParseContext 这个结构体。
OptionParseContext
在 OptionParseContext
这个结构里面,把命令行参数分为3大类。
- 全局参数 :对应
OptionParseContext::global_opts
。ffmpeg_opt.c:3309行,parse_optgroup(NULL, &octx.global_opts); 会把这个 global_opts 再次解析到 真正的全局变量。全局变量全部定义在 ffmpeg_opt.c,例如 file_overwrite ,C语言全局变量。 - 输入文件参数 : -i 前面那些用来修饰输入文件的参数就是输入文件参数,对应
OptionParseContext::groups[1]->groups[0]
- 输出文件参数 : 输出文件前面的用来修饰的就是输出文件参数,对应
OptionParseContext::groups[0]->groups[0]
OptionParseContext::nb_groups
一般情况都是2,代表有两个GroupList,一个输入,一个输出。
在 ffmpeg_opt.c 3254行已经定义好了 输入输出 GroupList,请看。
static const OptionGroupDef groups[] = {
[GROUP_OUTFILE] = { "output url", NULL, OPT_OUTPUT },
[GROUP_INFILE] = { "input url", "i", OPT_INPUT },
};
通常情况下 一个 OptionGroup
对应一个输入文件或者输出文件。
如果有3个输入文件,那么 OptionParseContext::group[1]->nb_groups
就会是3。
OptionParseContext::groups[1]->groups[0] 代表第一个输入文件命令行参数信息,
OptionParseContext::groups[1]->groups[1] 代表第二个输入文件命令行参数信息。
如果有4个输出文件,那么 OptionParseContext::group[0]->nb_groups
就会是4。
分成3大类是 init_parse_context()
函数做的,init_parse_context()
跑完之后,就会形成上面所说的结构关系,3大类参数。
下面分析OptionParseContext
的初始化以及赋值过程。
分析 OptionParseContext
需要理解 2 个函数,split_commandline()
, init_parse_context()
,函数调用流程图如下。
split_commandline()
这个函数看 ffmpeg_opt.c:3301行,如下:
ret = split_commandline(&octx, argc, argv, options, groups,FF_ARRAY_ELEMS(groups));
看函数传参,一眼就能知道,这个函数干的活就是把 argv 解析到 octx。
下面仔细讲解 split_commandline()
是如何把命令行参数 argv 解析到 octx。
split_commandline() 在解析命令行参数的时候,代码逻辑 主要有5个判断。
- 判断参数是否是输入文件的名称。cmdutils.c:795行,
if ((ret = match_group_separator(groups, nb_groups, opt)) >= 0) {....}
- 判断参数是否是输出文件的名称。cmdutils.c:778行,
if (opt[0] != '-' || !opt[1] || dashdash+1 == optindex) {....}
- 判断参数是否 能在定义好的 options[] 里面 ,具体定义在 ffmpeg_opt.c:3368行
const OptionDef options[] = {....}
。 - 如果命令行参数在上面3个都不是,就会执行
opt_default()
,在各种 AVClass 里面找。例如avcodec_get_class()
,avformat_get_class()
。 编码器 AVClass ,容器AVClass ,这些AVClass 里面都有自身相关的option定义,然后赋值给 全局变量codec_opts
,format_opts
等,最后在finish_group()的时候赋值给OptionGroup
。 例如-g 10
设置GOP size,就是在AVClass::AVOption里面 搜索匹配的。 - 判断参数是否以--no开头,配合 OPT_BOOL 使用,例如 --copy_unknown 0 等价与 --nocopy_unknown
下面来讲解 OptionParseContext::cur_group
的作用
看上图 split_command() 函数里面的不断 while 循环处理命令行参数,在while不断的循环过程中,在匹配到 输入或者输出文件名之前,就会把解析到的 option参数不断地往 cur_group
加。所以 cur_group
是一个缓存的数据结构,为后面的输入或者输出文件缓存他们的参数。
上文说过,FFmpeg命令行支持多个输入文件,一个输入文件就会对应一个 OptionGroup
,在匹配到输入或输出文件名之后,就会执行 finish_group()
代表已经完成了一个输入文件前面的参数解析。finish_group() 的代码比较简单,可以自行查看。就是把 缓存的 cur_group 赋值给新申请的 OptionGroup。
每新增解析一个文件名,都会执行 GROW_ARRAY(l->groups, l->nb_groups);
,然后把 cur_group 赋值给 新申请的 OptionGroup。
finish_group() 还会把 swr_opts,codec_opts 等也赋值,赋值之后会清空 cur_group,再次进入循环 解析下一个文件参数。
版权所属:知识星球:弦外之音,QQ:2338195090。 由于笔者的水平有限, 加之编写的同时还要参与开发工作,文中难免会出现一些错误或者不准确的地方,恳请读者批评指正。如果读者有任何宝贵意见,可以加我微信 Loken1。