ffmpeg源码分析-open_input_file - 弦外之音

/ 0评 / 0

本系列 以 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() 的主要逻辑。

  1. 图片1011~1018 行,可以看到这些代码都是类似的,全都是把 OptionsContext::SpecifierOpt 存到 OptionsContext::OptionGroup::AVDictionary 。例如把 o->audio_sample_rate[o->nb_audio_sample_rate - 1].u.i 存储到 o->g->format_opts。之前说过 OptionsContextSpecifierOpt 是数组类型,这里只取数组最后一个元素来赋值。也就是说,某些命令行参数即使写了多个,只有最后一个生效,例如 -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()
  2. 图片1020~1023 行,这里用了一个宏函数,MATCH_PER_TYPE_OPT(),这个函数其实就是遍历 OptionsContext 的 某个SpecifierOpt,再次提醒下 OptionsContext::SpecifierOpt 是一个数组。遍历会取第一个符合条件的 SpecifierOpt返回。1020~1023 行 遍历的SpecifierOptOptionsContext::codec_names ,指定的medietype'v' ,也就是命令行经常用的 -xxx:v ,后面的 'v' 就是匹配条件。
  3. 图片1026 行,err = avformat_open_input(&ic, filename, file_iformat, &o->g->format_opts);传递 o->g->format_opts打开文件。
  4. 图片1028~1051 行,这部分代码是处理 命令行参数 -ss-sseof的,就是只取输入文件的前n秒或者后n秒做处理,这里会涉及到 seek 操作。
  5. 图片1075 行,add_input_stream(),添加文件的输入流到 全局变量 input_streams,这个函数相对复杂,在下一段落详细分析。
  6. 图片1080~1100 行,这部分代码 是申请一个 InputFile变量,然后把 OptionsContext 的一些参数赋值给 InputFile,一个 InputFile对应一个输入文件。因为跑完 open_input_file()OptionsContext 就会释放了,所以一些参数必须赋值给 InputFile 方便后面处理。InputFile 是一个新的数据结构。


至此,open_input_file 的一些简单逻辑已经分析完了,接下来分析内部函数 add_input_stream()

要理解 add_input_stream(),必须先了解 2个新的数据结构,InputFileInputStream,请先看源码自行阅读这两个结构体的声明。

以命令行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() 的逻辑。

  1. 图片727~734 行,声明一个 InputStream ,把他丢进去全局变量 input_streams[] ,然后做一些初始化赋值。
  2. 图片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
  3. 图片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()其实并没有太复杂,就干了两件事情。

  1. 申请 新的InputStream,把 OptionsContext 的参数丢给 InputStream,主要调 MATCH_PER_STREAM_OPT() 来实现。
  2. 申请解码器context,做一些参数赋值。

至此,open_input_file() 已经分析完毕,第四章分析 open_output_file()


©版权所属:知识星球:弦外之音,QQ:2338195090。

由于笔者的水平有限, 加之编写的同时还要参与开发工作,文中难免会出现一些错误或者不准确的地方,恳请读者批评指正。如果读者有任何宝贵意见,或者希望交流音视频技术的,可以加我微信 Loken1。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注