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

/ 0评 / 0

本系列 以 ffmpeg4.2 源码为准,下载地址:链接:百度网盘 提取码:g3k8

ffmpeg 源码分析系列以一条简单的命令开始,ffmpeg -i a.mp4 b.flv,分析其内部逻辑。

a.mp4下载链接:百度网盘,提取码:nl0s 。


本文主要分析 parse_optgroup() 的内部逻辑,函数调用用流程图如下。

parse_optgroup(void *optctx, OptionGroup *g) 这个函数的 optgroup 其实是 OptionGroup 的缩写。看函数名字跟形参,就知道这个函数干的活就是把OptionGroup 解析给 optctx。这里的 optctx 是一个新的结构体 OptionsContext,之前没有介绍过。

OptionGroup *g 是从哪里来的呢?如下图所示,全部都在OptionParseContext octx 这个变量。

如前面一章所说,split_commandline() 已经把 cli 命令行参数全局解析 到 OptionParseContext octx 这个变量了。

OptionGroup 其实是有多个的,分别存放在 octx 的3个地方。

  1. octx.global_opts ,存放全局参数的 OptionGroup
  2. octx.groups[1]->groups[0] , 这里也是 OptionGroup,存放的是输入文件的 OptionGroupoctx.groups[1]->nb_groups 代表有多少个 OptionGroup,之前说过,一个输入或输出文件,对应一个 OptionGroup
  3. octx.groups[0]->groups[0] ,输出文件 OptionGroup ,与输入同理。

接下来分析 parse_optgroup() 是如何把 octx 里面那么多 OptionGroup 一个一个解析给 OptionsContext 的。

  1. ffmpeg_opt.c:3309行,解析全局变量,parse_optgroup(NULL, &octx.global_opts) 这里是个例外,没有传递 OptionsContext,所以会把 octx.global_opt 解析到全局变量,看 parse_optgroup 的代码实现就知道了,具体操作是在 write_option(),这个稍后分析。
  2. ffmpeg_opt.c:3319行,利用 open_files() 函数解析输入文件的 OptionGroupopen_files() 的第一个参数是 OptionGroupList ,是输入文件的 OptionGroup 集合。如果有3个输入文件,OptionGroupList->nb_groups 就是3,通过遍历 OptionGroupList->nb_groups ,不断调用parse_optgroup()把各个输入文件的 OptionGroup 解析到 OptionsContext
  3. fmpeg_opt.c:3333行,也是调 open_files(),不过这里处理的是输出文件的 OptionGroup,跟第二点原理一样。


由此可见 parse_optgroup() 主要做两件事。

  1. 解析 OptionGroup 结构到全局变量,这个不用说了,自己在代码里搜一下全局变量,就知道某些全局变量在哪里用到。
  2. 转换 OptionGroup 结构成 OptionsContext,这个 OptionsContext 也是对应一个输入文件或者输出文件。一个输入文件的参数,命令行 -i 前面的这些 -vcodec-itsoffset 等等参数,最后都会存到 OptionsContext 里。

要理解 parse_optgroup() 的内部逻辑 就需要理解 OptionsContext 这个结构,还有内部的 write_option() 函数。

我画了个图,OptionsContext 这个结构 太大了,会省略某一部分成员参数,原理是一样的,请看

据本人统计,OptionsContext 里面的成员参数有50个以上,非常庞大。但其实,他的成员参数就只有3种类型。

  1. OptionGroup *g,这里也有 OptionGroupOptionsContextOptionGroup 是直接拷贝传递进来的 OptionGroup,代码在 ffmpeg_opt.c:3269 行,o.g = g;
  2. 常规类型,intint64_tcha* 等等C语言的类型。
  3. SpecifierOpt,这个类型特别重要,不过只有两个成员参数。char *specifier 通常是 'a','v','',代表这是一个视频或音频,或者不区分音视频的参数。后面的 union str 是一个联合体,可以是 字符串,int,浮点数。注意,每一个 SpecifierOpt xxx 后面都有一个 SpecifierOpt nb_xxx,代表有多少个SpecifierOpt。例如 nb_codec_names 等于2,就代表有两个 codec,分别是 codec_names[0]codec_names[1]。虽然一个输入文件通常只会指定一个解码器,但 ffmpeg 为了通用性,每个 SpecifierOpt后面都会有一个数量参数 nb_xxx

OptionsContext已经分析完了,接下来讲讲 write_option() 这个函数的内部逻辑。

write_option() 的调用在 cmdutils.c:437行,write_option(optctx, o->opt, o->key, o->val)

write_option()parse_optgroup() 的内部函数,从 cmdutils.c:437行 的传参可以看出,parse_optgroup() 不断遍历 OptionGroup::opts

Option 传递给 write_option(),写到 optctx 里面。

write_option() 的内部逻辑相对复杂,请结合源码看本文章。

如上图所示,第一行代码就是初步确定 dst ,为什么说是初步,因为 dst 在后面有可能会变化。理解 write_option() 需要先解释一下 OptionDef这个结构的一些常见值。

ffmpeg_opt.c:3368行定义了一个 OptionDef的数组。如图:

OptionDef 有5个成员 char *nameint flags, union:uchar *helpchar *argname

  1. char *name 对应命令行中的 -xxxxxx
  2. int flags ,flag值的介绍如下:
    • HAS_ARG :代表这个Option必须有value,就是 -xxx 后面必须还有一个字符串参数 例如 -xxx yyy 如果没有 yyy 就会报错,具体代码在 cmd_utils.c:809行。
    • OPT_INT :代表 dst 是一个 INT,会把 传递进 write_option()Option::val 转换成 int 赋值给 dst 。这个时候 dst 指向一个全局变量,或者 OptionsContext 里面的某个 int 成员。
    • OPT_BOOL :代表 dst 是一个BOOL,C语言没有布尔,等值于 OPT_INT, dst 被设置成0或1。
    • OPT_FLOAT :代表 dst 是一个 浮点数,跟OPT_INT 原理一样
    • OPT_STRING :代表 dst 是一个 char * , 跟OPT_INT 原理一样。
    • OPT_INT64 :代表 dst 是一个 INT64 , 跟OPT_INT 原理一样。
    • OPT_DOUBLE:代表 dst 是一个 Double , 跟OPT_INT 原理一样。
    • OPT_EXIT : write_option() 里面处理完这个标志的 Option 就退出程序,具体代码在cmd_utils.c:344
    • OPT_OFFSET :代表 dstOptionsContext 里面的 int float 等普通成员,具体是哪个成员,通过 u.off 偏移取得地址,(uint8_t *)optctx + po->u.off
    • OPT_SPEC :代表 dstOptionsContext 里面的 SpecifierOpt 成员。注意,这里是一个数组,dst 会指向数组的某个下标地址。
    • OPT_TIME :代表传递进 write_option()Option::val 是一个时间字符串 "01:00" 之类的。会调用 parse_time_or_die() 转换这个字符串成Int64,然后 赋值给dst 。OPT_INPUT
    • OPT_INPUT :代表此命令行参数是输入参数,只能在 -i 前面出现,不能在 -i 后面出现。如果出现会报错,cmd_utils.c:424
    • OPT_OUTPUT :代表此命令行参数是输出参数,只能在输出文件名前面出现,不能在后面出现。如果出现会报错,cmd_utils.c:424
    • OPT_VIDEO :视频标示,只是一个标示,可以在 show_help 的时候指定 flags 为 OPT_VIDEO ,只显示视频相关的命令行参数。
    • OPT_AUDIO :音频标示,同OPT_VIDEO 。
    • OPT_SUBTITLE :字幕标示,同OPT_VIDEO 。
    • OPT_DATA :数据标示,同OPT_VIDEO 。
    • OPT_EXPERT :专家标示,同OPT_VIDEO 。
    • OPT_PERFILE :不知道干嘛的。
  3. union:u,u 是一个联合体,分别有 void *dst_ptrint (*func_arg){..}size_t off
    • void *dst_ptr :设置了就代表 u 指向一个全局变量。
    • int (*func_arg){..} :设置了就代表 u 指向 一个函数指针,write_option() 里会调用指向的那个函数
    • size_t off :代表 OptionsContext 的OFFSET,用来确定 dst 要指向 OptionsContext的哪个成员参数,(uint8_t *)optctx + po->u.off
  4. char *help,命令行参数help。
  5. char *argname,也是help的时候用的。

至此,write_option()也已经分析完毕。FFmpeg 命令行解析流程分析已经完成一大半,最后做下小总结。

main() 入口开始,到 split_command()argv 所有命令行参数转换成 OptionParseContext,再到 parse_optgroup(NULL, &octx.global_opts)OptionParseContext::global_opts 解析给 全局变量。再到 open_file() 打开输入/输出文件,然后把每个输入/输出文件的专属 OptionGroup 转换成 OptionsContext,然后再把 OptionsContext丢给 open_input_file()open_output_file()

下一章 会介绍 open_input_file()open_output_file()的内部实现。本文介绍的 OptionGroup 如何转成全局变量跟 OptionsContext 已经结束了。


©版权所属:知识星球:弦外之音,QQ:2338195090。

由于笔者的水平有限, 加之编写的同时还要参与开发工作,文中难免会出现一些错误或者不准确的地方,恳请读者批评指正。如果读者有任何宝贵意见,可以加我微信 Loken1。

发表回复

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