1 命令行解析
1.1 命令行例子
- 命令行包括三个部分:输入参数,输出参数,和全局选项。
- -i /home/ron/music/avm.mp4是输入参数,a.mp4是输出参数。输入/输出参数可以有专属的选项,这些选项应该紧挨着放在输入输出参数前面。如-vf “split [main][tmp]...[main][flip]”就是输出参数a.mp4的选项。
- 全局选项的位置不需要限定,因为选项是以选项名字查找的。
- 可以有多组输入参数和多组输出参数。
1.2 解析命令行
split_commandline()负责解析命令行。
int split_commandline(OptionParseContext *octx, int argc, char *argv[],
const OptionDef *options,
const OptionGroupDef *groups, int nb_groups);
解析的结果保存在OptionParseContext中。解析时需要参考OptionDef和OptionGroupDef。OptonDef[]是支持ffmpeg的选项列表,OptionGroupDef[]是支持的组列表,包括输入类和输出类,前者以-i开头,加上设备名。后者只有文件名。
下面的类图显示了涉及的类:
- OptionGroup保存一个输入(或输出)和它的选项列表。Option表示一个选项。
- OptionParseContext中包括多个OptionGroup。全局选项保存在global_opts中。所有输入设备的选项保存在一个OptionGroupList实例中,所有输出设备的选项保存在另一个实例中。两者合起来组成数组groups.
1.3 split_commandline()
split_commandline()在一个循环中解析命令行,主要涉及如下函数。
函数 | 功能 |
---|---|
find_option() | 查询支持的option列表,检查当前元素是否一个option |
add_option() | 将option加入一个临时组。(因为option先于group出现,还不知道应该加入到哪个组。) |
match_group_separator() | 查询支持的group列表,检查当前元素是否是一个Group |
finish_group() | 设置临时组的参数,并用它填充OptionParseContext.groups(现在知道应该加入哪个组了) |
1.4 parse_optgroup()
parse_optgroup()负责将OptionGroup转换成OptionsContext。
int parse_optgroup(void *optctx, OptionGroup *g);
- OptionGroup保存的选项值是字符串,而OptionsContext保存的值是由OptionDef定义的实际类型。parse_optgroup()的第一个参数optctx实际上是OptonsContext。
下面的类图显示了涉及的类:
- SpecifierOpt保存实际类型的选项。OptionsContext有若干个SpecifierOpt数组的成员。每个specfier数组保存一类选项。如filters保存”filter”选项。但filter可以是”filter:v”,属于video,也可以是“filter:a”,属于audio。SpecifierOpt.specifier成员就是用来标记这个选项应该属于谁的。对于”filter:v”,SpecifierOpt.specifier就是”v”。
- 这里顺便提一下AVDictionary。解析过程没有用到它。用户设置的选项可能不成功,而选项的最终值会保存在这里。用av_dict_set()函数设置它。
1.5 parse_optgroup()
parse_optgroup()函数遍历OptonGroup中的Option,调用write_option()将其写入OptionsContext。
- 对于基本的选项,它的OptionDef中定义了它在OptionsContext的偏移,所以将字符串转化后,直接写入就好了。比如”filter:v”。
- 有的选项可能是其他选项的别名。这时它的OptionDef指定了一个回调函数。这个函数会重定向到所指向的选项上去。如”vf”就是”filter:v”的别名,它的OptionDef指定了回调函数opt_video_filter()。这个函数会调用parse_option()和find_option()查找”filter:v”对应的OptionDef,并再次调用write_option()。
- 全局选项。它的OptionDef也定义了一个回调函数。这个函数直接设置全局变量。如loglevel,它的OptionDef定义了opt_loglevel()。这个函数调用av_log_set_level()设置日志输出等级。
1.6 MATCH_PER_XXX_OPT()
宏MATCH_PER_TYPE_OPT()和MATCH_PER_STREAM_OPT()用于从OptionsContext读值。
- 前者指定参数mediatype,用它跟OptionsContext.spcifier比较,找出option并读出。后者指定参数AVStream,调用check_stream_specifier(),用AVStream的属性与OptionContext.specifier匹配,找出option并读出。
2 vf选项解析
2.1 avfilter_graph_parse2()
avfilter_graph_parse2()负责解析vf选项内容。
int avfilter_graph_parse2 (AVFilterGraph *graph,
const char *filters,
AVFilterInOut **inputs,
AVFilterInOut **outputs);
输入参数filters是vf选项内容。输出参数Inputs是导出的输入接口,outputs是filters导出的输出接口。
2.2 filters
如下是filters的一个例子。它来自ffmpeg的文档:
http://ffmpeg.org/ffmpeg-filters.html#Filtergraph-description
ffmpeg -i INPUT -vf "split [main][tmp]; [tmp] crop=iw:ih/2:0:0, vflip [flip]; [main][flip] overlay=0:H/2" OUTPUT
对应FilterGraph的结构示意图如下。 矩形框内是vf的内容对应的部分。其中split应该导出到inputs中,overlay应该导出到outputs中。
2.3 vf术语
描述vf的解析过程需要使用一些术语。其中一部分是关于vf语法的,另外一部分是关于生成的FilterGraph结构的。
上图标出了vf语法的术语。
- 过滤器。过滤器用红色标出,包括它的名字和参数。如”split”,只有名字。又如”overlay=0:H/2”,overlay是名字,”0:H/2”是参数。名字和参数用 = 连接。
- 位置点。有两类位置点,有名的和无名的。有名位置点用绿色标出,名字用 [] 包住,如main, flip, tmp。无名位置点不必标出。
- 路径。路径是一条从位置点开始,中间过滤器和位置点交错,在位置点结束的处理流程。多条路径组成整个filtergraph。中间的位置点都是无名的,开始和结束的位置点应该是有名的,除非这条路径在filtergraph的开始和结束位置。路径之间用 ; 隔开。如 [tmp] crop=iw:ih/2:0:0, vflip [flip]。以tmp开始,中间包括crop和vflip和一个无名位置点,在flip结束。有名位置点是该路径与其他路径的连接点,所以需要有一个名字来标记,而无名位置点只存在该路径内部的两个过滤器之间,是隐含的,所以不需要名字。
下图是FilterGraph的结构图。
- FilterGraph是由一系列的过滤器,Pad和Pad Link构成的。
- 过滤器来自FilterGraph语法中的过滤器,它有一组In Pad和一组OutPad, Pad与语法中的位置点对应。过滤器之间通过Pad联系,Pad Link用来将一个In Pad连接到一个OutPad。Pad Link没有对应的语法元素。
- Input/Output用于解析过程,也用于保存整个解析的结果,以返回给调用者。open_inputs标记当前还没有解析(与其他OutPad连接)的InPad,open_outputs标记当前还没有解析的OutPad,curr_inputs标记当前将要解析的InPad。
2.4 avfilter_graph_parse2()
avfilter_graph_parse2()主要调用四个函数进行解析。
函数 | 功能 |
---|---|
parse_input() | 选取若干open_outputs,以更新curr_inputs |
parse_filter() | 解析过滤器 |
link_filter_inouts() | 将新的过滤器连入当前的curr_inputs,并更新curr_inputs |
parse_output() | 结束当前的curr_inputs,加入open_outputs。 |
2.5 解析过程
下面的图描述了上述语法的解析过程。图上部的xxx()是当前步骤调用的函数,下面的字符串是语法,当前变色的部分是正在解析的部分。
- 解析split
- 将split连入curr_inputs,当前的curr_inputs原来为空,所以更新为split的两个out pads。Pads的数量可以来自split class指定的默认值,或者split的参数。这里是前者。
- 给split的outpads命名,以便后面引用。
- 从open_outputs中选取tmp位置开始一段分支路径。将tmp标记为curr_inputs。
- 解析crop。
- 将crop连入curr_inputs。更新curr_inputs,指向crop的out pads。
- 解析vflip。
- 将vflip连入curr_inputs。
- 给vflip的out pads命名为flip,以便后面引用。当前分支路径结束。
- 从open_outputs中选取main和flip,开始新路径。
- 解析overlay。Overlay的pads来自overlay class的默认值。
- 将overlay连入curr_inputs。
- 没有更多的语法元素了,结束前将curr_inputs标记为open_outputs。注意split的in pads一直没有解析,所以它是open_inputs。将open_inputs和open_outputs返回调用者。
2.6 FilterGraph类
下面的类图显示了FilterGraph各元素对应的类。
- AVFilterContext表示过滤器。AVFilter是它的属性类。
- AVFilterPad是Pad类。一个AVFilterContext实例包括AVFilterPad的一组In Pad实例和一组Out Pad实例。AVFilterLink是Pad Link类,它连接两个AVFilterPad实例。
- AVFilterLink有一个FFFrameQueue,用于保存过滤的中间结果。这时一个frame的数据通道。
- AVFilterContext有一个空间,用于保存该特定类型Filter的私有信息,可以是CropContext,SplitContext或其他filter的一种。
- AVFilterInOut用于解析过程标记open_iputs, open_ouputs和curr_inputs。它没有直接引用AVFilterPad,而是引用AVFilterContext,和用序号间接指向AVFilterPad。
- AVFilterGraph和FilterGraph是代表整个FilterGraph的容器类。
2.7 avfilter_graph_parse2()
下图是avfilter_graph_parse2()的函数调用关系。
相关链接
FFMPEG 3.4.2 - ffmpeg源代码分析 (一)
FFMPEG 3.4.2 - ffmpeg源代码分析 (二)
FFMPEG 3.4.2 - ffmpeg源代码分析 (三)
FFMPEG 3.4.2 - ffmpeg源代码分析 (四)- x264
FFMPEG 3.4.2 - ffplay源代码分析 (一)
FFMPEG 3.4.2 - ffplay源代码分析 (二)
FFMPEG 3.4.2 - ffplay源代码分析 (三)