FFMPEG 3.4.2 - ffmpeg源代码分析 (一)

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源代码分析 (三)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容