本系列 以 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。

