(感谢雷博士:雷神)
函数背景色
函数在图中以方框的形式表现出来。不同的背景色标志了该函数不同的作用:
粉红色背景函数:FFmpeg的API函数。
白色背景的函数:FFmpeg的内部函数。
黄色背景的函数:URLProtocol结构体中的函数,包含了读写各种协议的功能。
绿色背景的函数:AVOutputFormat结构体中的函数,包含了读写各种封装格式的功能。
蓝色背景的函数:AVCodec结构体中的函数,包含了编解码的功能。
区域
整个关系图可以分为以下几个区域:
左边区域——架构函数区域:这些函数并不针对某一特定的视频格式。
右上方黄色区域——协议处理函数区域:不同的协议(RTP,RTMP,FILE)会调用不同的协议处理函数。
右边中间绿色区域——封装格式处理函数区域:不同的封装格式(MKV,FLV,MPEG2TS,AVI)会调用不同的封装格式处理函数。
右边下方蓝色区域——编解码函数区域:不同的编码标准(HEVC,H.264,MPEG2)会调用不同的编解码函数。
箭头线
为了把调用关系表示的更明显,图中的箭头线也使用了不同的颜色:
红色的箭头线:标志了编码的流程。
其他颜色的箭头线:标志了函数之间的调用关系。其中:
调用URLProtocol结构体中的函数用黄色箭头线标识;
调用AVOutputFormat结构体中的函数用绿色箭头线标识;
调用AVCodec结构体中的函数用蓝色箭头线标识。
函数所在的文件
每个函数标识了它所在的文件路径。
左边区域(架构函数)
右上区域(URLProtocol协议处理函数)
URLProtocol结构体包含如下协议处理函数指针:
url_open():打开
url_read():读取
url_write():写入
url_seek():调整进度
url_close():关闭
【例子】不同的协议对应着上述接口有不同的实现函数,举几个例子:
File协议(即文件)对应的URLProtocol结构体ff_file_protocol:
url_open() -> file_open() -> open()
url_read() -> file_read() -> read()
url_write() -> file_write() -> write()
url_seek() -> file_seek() -> lseek()
url_close() -> file_close() -> close()
RTMP协议(libRTMP)对应的URLProtocol结构体ff_librtmp_protocol:
url_open() -> rtmp_open() -> RTMP_Init(), RTMP_SetupURL(), RTMP_Connect(), RTMP_ConnectStream()
url_read() -> rtmp_read() -> RTMP_Read()
url_write() -> rtmp_write() -> RTMP_Write()
url_seek() -> rtmp_read_seek() -> RTMP_SendSeek()
url_close() -> rtmp_close() -> RTMP_Close()
UDP协议对应的URLProtocol结构体ff_udp_protocol:
url_open() -> udp_open()
url_read() -> udp_read()
url_write() -> udp_write()
url_seek() -> udp_close()
url_close() -> udp_close()
右中区域(AVOutputFormat封装格式处理函数)
AVOutputFormat包含如下封装格式处理函数指针:
write_header():写文件头
write_packet():写一帧数据
write_trailer():写文件尾
【例子】不同的封装格式对应着上述接口有不同的实现函数,举几个例子:
FLV封装格式对应的AVOutputFormat结构体ff_flv_muxer:
write_header() -> flv_write_header()
write_packet() –> flv_write_packet()
write_trailer() -> flv_write_trailer()
MKV封装格式对应的AVOutputFormat结构体ff_matroska_muxer:
write_header() -> mkv_write_header()
write_packet() –> mkv_write_flush_packet()
write_trailer() -> mkv_write_trailer()
MPEG2TS封装格式对应的AVOutputFormat结构体ff_mpegts_muxer:
write_header() -> mpegts_write_header()
write_packet() –> mpegts_write_packet()
write_trailer() -> mpegts_write_end()
AVI封装格式对应的AVOutputFormat结构体ff_avi_muxer:
write_header() -> avi_write_header()
write_packet() –> avi_write_packet()
write_trailer() -> avi_write_trailer()
右下区域(AVCodec编解码函数)
AVCodec包含如下编解码函数指针:
init():初始化
encode2():编码一帧数据
close():关闭
【例子】不同的编解码器对应着上述接口有不同的实现函数,举几个例子:
HEVC编码器对应的AVCodec结构体ff_libx265_encoder:
init() -> libx265_encode_init() -> x265_param_alloc(), x265_param_default_preset(), x265_encoder_open()
encode2() -> libx265_encode_frame() -> x265_encoder_encode()
close() -> libx265_encode_close() -> x265_param_free(), x265_encoder_close()
H.264编码器对应的AVCodec结构体ff_libx264_encoder:
init() -> X264_init() -> x264_param_default(), x264_encoder_open(), x264_encoder_headers()
encode2() -> X264_frame() -> x264_encoder_encode()
close() -> X264_close() -> x264_encoder_close()
VP8编码器(libVPX)对应的AVCodec结构体ff_libvpx_vp8_encoder:
init() -> vpx_init() -> vpx_codec_enc_config_default()
encode2() -> vp8_encode() -> vpx_codec_enc_init(), vpx_codec_encode()
close() -> vp8_free() -> vpx_codec_destroy()
MPEG2编码器对应的AVCodec结构体ff_mpeg2video_encoder:
init() -> encode_init()
encode2() -> ff_mpv_encode_picture()
close() -> ff_mpv_encode_end()
:全局变量与结构体
InputStream **input_streams = NULL;
int nb_input_streams = 0;
InputFile **input_files = NULL;
int nb_input_files = 0;
OutputStream **output_streams = NULL;
int nb_output_streams = 0;
OutputFile **output_files = NULL;
int nb_output_files = 0;
其中: input_streams是输入流的数组,nb_input_streams是输入流的个数。 input_files是输入文件(也可能是设备)的数组,nb_input_files是输入文件的个数。 下面的输出相关的变量们就不用解释了。
可以看出,文件和流是分别保存的。于是,可以想象,结构InputStream中应有其所属的文件在InputFile中的序号(file_index)。输入流数组应是这样填充的:每当在输入文件中找到一个流时,就把它添加到input_streams中,所以一个输入文件对应的输入流在input_streams中是紧靠着的,于是InputFile结构中应有其第一个流在input_streams中的开始序号(ist_index)和被放在input_streams中的流的总个数(nb_streams)。
在输出流output_streams中,除了要保存其所在的输出文件在output_files中的序号(index),还应保存其对应的输入流在input_streams中的序号( source_index),也应保存其在所属输出文件中的流序号(file_index)。而输出文件中呢,只需保存它的第一个流在output_streams中的序号(ost_index)
流和文件都准备好了,下面就是转换,那么转换过程是怎样的呢?
还是我来猜一猜吧:
首先打开输入文件们,然后跟据输入流们准备并打开解码器们,然后跟据输出流们准备并打开编码器们,然后创建输出文件们,然后为所有输出文件们写好头部,然后就在循环中把输入流转换到输出流并写入输出文件中,转换完后跳出循环,然后写入文件尾,最后关闭所有的输出文件.
1.初始化工作
2.解析命令行参数
3.编码
4.收尾
1.1 命令行例子
ffmpeg -i abc.mp4 -i bbb.avi -vcodec libx264 -acodec aac -vf scale=640:480 -f flv -y abc.flv
命令行包括三个部分:输入参数,输出参数,和全局选项。
-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()是当前步骤调用的函数,下面的字符串是语法,当前变色的部分是正在解析的部分。
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()的函数调用关系。
transcode函数
transcode_init()
transcode_step
transcode_init函数
初始化工作:
AVFormatContext *oc;//输出流的编解码器结构
OutputStream *ost; //输出流
InputStream *ist; //输入流
init_input_stream
init_output_stream
transcode_step函数