ffmpeg源码分析-ffmpeg_parse_options - 弦外之音

/ 0评 / 0

本系列 以 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() 函数用到的主要数据结构,

OptionParseContextOptionGroupListOptionGroupOptionOptionGroupDef

这5个结构体是嵌套的关系,如图所示:

下面仔细分析 OptionParseContext 这个结构体。

OptionParseContext

OptionParseContext 这个结构里面,把命令行参数分为3大类。

  1. 全局参数 :对应 OptionParseContext::global_opts。ffmpeg_opt.c:3309行,parse_optgroup(NULL, &octx.global_opts); 会把这个 global_opts 再次解析到 真正的全局变量。全局变量全部定义在 ffmpeg_opt.c,例如 file_overwrite ,C语言全局变量。
  2. 输入文件参数 : -i 前面那些用来修饰输入文件的参数就是输入文件参数,对应 OptionParseContext::groups[1]->groups[0]
  3. 输出文件参数 : 输出文件前面的用来修饰的就是输出文件参数,对应 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个判断。

  1. 判断参数是否是输入文件的名称。cmdutils.c:795行,if ((ret = match_group_separator(groups, nb_groups, opt)) >= 0) {....}
  2. 判断参数是否是输出文件的名称。cmdutils.c:778行,if (opt[0] != '-' || !opt[1] || dashdash+1 == optindex) {....}
  3. 判断参数是否 能在定义好的 options[] 里面 ,具体定义在 ffmpeg_opt.c:3368行 const OptionDef options[] = {....}
  4. 如果命令行参数在上面3个都不是,就会执行 opt_default() ,在各种 AVClass 里面找。例如 avcodec_get_class()avformat_get_class()。 编码器 AVClass ,容器AVClass ,这些AVClass 里面都有自身相关的option定义,然后赋值给 全局变量 codec_optsformat_opts等,最后在finish_group()的时候赋值给 OptionGroup。 例如 -g 10 设置GOP size,就是在AVClass::AVOption里面 搜索匹配的。
  5. 判断参数是否以--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。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注