本系列 以 ffmpeg4.2 源码为准,下载地址:链接:百度网盘 提取码:g3k8
ffmpeg 源码分析系列以一条简单的命令开始,ffmpeg -i a.mp4 b.flv
,分析其内部逻辑。
a.mp4下载链接:百度网盘,提取码:nl0s 。
本文主要分析 open_input_file()
` 的内部逻辑,流程图如下:
open_input_file()
的逻辑实际上是非常复杂的,上面的图做了一些简化,如果多块流程都类似原理,只画了其中一块出来。
open_input_file()
的逻辑 太复杂了,必须再贴一下伪代码图片才能更好地讲解。
接下来对着图片的代码行数,逐行分析 open_input_file()
的主要逻辑。
- 图片
1011~1018
行,可以看到这些代码都是类似的,全都是把OptionsContext::SpecifierOpt
存到OptionsContext::OptionGroup::AVDictionary
。例如把o->audio_sample_rate[o->nb_audio_sample_rate - 1].u.i
存储到o->g->format_opts
。之前说过OptionsContext
的SpecifierOpt
是数组类型,这里只取数组最后一个元素来赋值。也就是说,某些命令行参数即使写了多个,只有最后一个生效,例如-ar 44100 -ar 48000
只有48000生效。o->g->format_opts
是容器层参数,在1026行avformat_open_input()
传递进去打开文件。分析到这里,是不是有种焕然大悟的感觉,命令行的参数 例如-pix_fmt yuv420p
,是怎么一步一步传递到代码里面的。-pix_fmt yuv420p
先经过split_command()
存到了 OptionGroup::opts 里面,然后再经过parse_optgroup()
存到OptionsContext::SpecifierOpt* frame_pix_fmt
里面,最后存进o->g->format_opts
,然后放进去avformat_open_input()
。 - 图片
1020~1023
行,这里用了一个宏函数,MATCH_PER_TYPE_OPT()
,这个函数其实就是遍历OptionsContext
的 某个SpecifierOpt
,再次提醒下OptionsContext::SpecifierOpt
是一个数组。遍历会取第一个符合条件的SpecifierOpt
返回。1020~1023
行 遍历的SpecifierOpt
是OptionsContext::codec_names
,指定的medietype
是'v'
,也就是命令行经常用的-xxx:v
,后面的'v'
就是匹配条件。 - 图片
1026
行,err = avformat_open_input(&ic, filename, file_iformat, &o->g->format_opts);
传递o->g->format_opts
打开文件。 - 图片
1028~1051
行,这部分代码是处理 命令行参数-ss
,-sseof
的,就是只取输入文件的前n秒或者后n秒做处理,这里会涉及到 seek 操作。 - 图片
1075
行,add_input_stream()
,添加文件的输入流到 全局变量input_streams
,这个函数相对复杂,在下一段落详细分析。 - 图片
1080~1100
行,这部分代码 是申请一个InputFile
变量,然后把OptionsContext
的一些参数赋值给InputFile
,一个InputFile
对应一个输入文件。因为跑完open_input_file()
,OptionsContext
就会释放了,所以一些参数必须赋值给InputFile
方便后面处理。InputFile
是一个新的数据结构。
至此,open_input_file 的一些简单逻辑已经分析完了,接下来分析内部函数 add_input_stream()
。
要理解 add_input_stream()
,必须先了解 2个新的数据结构,InputFile
,InputStream
,请先看源码自行阅读这两个结构体的声明。
以命令行ffmpeg -i a.mp4 b.flv
为例,输入文件 a.mp4
只有两个流,一个音频,一个视频,跑完 open_input_file()
之后形成的数据关系图如下,
如图所示,代码里有两个全局变量,input_files[]
,input_streams[]
。代表有多少个输入文体,多少个输入流。文件与流分开存储,InputStream
通过 file_index
定位到所属的 InputFile
结构
接下来贴上 add_input_stream()
的代码,代码经过删减,只保留主干。
接下来,对照图片的代码,逐行分析 add_input_stream()
的逻辑。
- 图片
727~734
行,声明一个InputStream
,把他丢进去全局变量input_streams[]
,然后做一些初始化赋值。 - 图片
737~741
行,这块代码调用了一个宏函数MATCH_PER_STREAM_OPT()
,这个宏函数跟前面的MATCH_PER_TYPE_OPT()
非常类似,都是遍历OptionsContext
的 某个SpecifierOpt
数组,只不过MATCH_PER_STREAM_OPT()
根据 stream 来匹配,而MATCH_PER_TYPE_OPT()
根据 type 来匹配。可以看到,add_input_stream()
里面用了大量的MATCH_PER_STREAM_OPT()
,就是把OptionsContext
的值匹配取出来,赋值给InputStream
。 - 图片
748~846
行,avcodec_alloc_context3(ist->dec)
申请解码器context,avcodec_parameters_to_context(ist->dec_ctx, par);
把容器层解码器信息复制给解码层。中间经过一大堆的操作,最后执行avcodec_parameters_from_context(par, ist->dec_ctx);
把解码层参数,更新回去容器stream层,为什么要更新回去呢?估计是后面用到吧。
可以看到,add_input_stream()
其实并没有太复杂,就干了两件事情。
- 申请 新的
InputStream
,把OptionsContext
的参数丢给InputStream
,主要调MATCH_PER_STREAM_OPT()
来实现。 - 申请解码器context,做一些参数赋值。
至此,open_input_file()
已经分析完毕,第四章分析 open_output_file()
。
©版权所属:知识星球:弦外之音,QQ:2338195090。
由于笔者的水平有限, 加之编写的同时还要参与开发工作,文中难免会出现一些错误或者不准确的地方,恳请读者批评指正。如果读者有任何宝贵意见,或者希望交流音视频技术的,可以加我微信 Loken1。