本系列 以 ffmpeg4.2 源码为准,下载地址:链接:百度网盘 提取码:g3k8
ffmpeg 源码分析系列以一条简单的命令开始,ffmpeg -i a.mp4 b.flv
,分析其内部逻辑。
a.mp4下载链接:百度网盘,提取码:nl0s 。
本文章主要分析 open_output_file()
的内部逻辑。
简单转码命令 的 open_output_file()
流程图如下,一些参数赋值的内容没有画出来,只有主干。
理解 open_output_file()
,必须先了解 2个新的数据结构,OutputFile
,OutputStream
,请先看源码自行阅读这两个结构体的声明。跑完 open_output_file()
之后形成的数据关系图如下,跟上一章的输入非常类似。
下面再贴一个 open_output_file()
的代码图,因为 open_output_file()
里面的逻辑分支比较多,在 ffmpeg -i a.mp4 b.flv
下并不会跑进去某些 if
条件,所以代码图为了简洁,会删减一些if
,有一些参数赋值也会删掉。
接下来会逐行代码分析 open_output_file()
的内部逻辑。
- 图片
1873~1894
行,申明一个OutputFile
结构 ,丢进去全局变量数组outpur_files[]
,然后把OptionsContext
的一些值赋值给of
。 - 图片
1914~2083
行,这个代码块是比较重要的,首先根据算法,选出输入流里最好的 video 流idx
,然后调用new_video_stream(o, oc, idx)
来创建输出流。new_video_stream()
函数比较复杂,这里简单介绍,下一段再仔细分析。 audio流类似 video流,也是根据算法选出最好idx
,然后调new_audio_stream(o, oc, idx);
- 图片
2090~2132
行,这部分代码主要对命令行参数做些检测,检测哪些option 没有用到,命令行写错了,给提示。不是主要逻辑。 - 图片
2133~2213
行,这部分代码就是遍历输出流,然后调init_simple_filtergraph(ist, ost)
初始化Filter,这块比较重要,要细看。 - 后面的代码都不重要,都是一些检测,参数赋值,处理metadata,处理chapter,后面的代码最重要的是调了
avio_open2()
打开输出文件
如此看来,open_output_file()
的内部逻辑就变得极其简单了,创建 OutputFile
,用OptionsContext
来赋值,调new_video_stream()
,new_audio_stream()
创建输出流,最后初始化 filter,然后avio_open2()
打开输出文件。
上面有两个函数没有仔细介绍 new_video_stream()
new_audio_stream()
,接下来仔细分析。
new_video_stream()
的逻辑相对简单。如下面的流程图所示,new_video_stream()
里面有个 new_output_stream()
,
new_output_stream()
主要创建 stream,编码器context,然后做一些初始化。
new_output_stream()
是一个公共函数,音频流,数据流,字幕流都用了它,所以 ffmpeg
把他分出来。video_stream 的一些初始化会在调用 new_output_stream()
后执行。
比较注意的一点是,new_video_stream()
跟 new_output_stream()
里面用了大量的 MATCH_PER_STREAM_OPT()
,把 OptionsContex
的参数提取出来,赋值给 stream参数,或者赋值给编码器参数。所以一定要理解 MATCH_PER_STREAM_OPT()
的实现。
new_audio_stream()
跟 new_video_stream()
类似,只是 new_audio_stream()
调了 new_output_stream()
,对音频流或编码器的某些参数赋值。
最后还有一个重要函数没有分析,init_simple_filtergraph
(),即使是简单的转码命令,ffmpeg
为了通用性,也是会创建 filter
的,只不过是 null filter
跟 anull filter
。空的视频流 filter
,空的音频流filter
。
init_simple_filtergraph()
的逻辑比较简单,只是看 init_simple_filtergraph()
,不太容易看出filter的整体逻辑跟关系结构,需要后面分析 transcode()转码函数 ,ifilter_send_frame()
调用了 configure_filtergraph()
才能形成完整的关系结构体。这里尽量简单介绍,有个印象。
init_simple_filtergraph()
跑完之后形成的关系结构如下。
如图所示,有一个全局变量数组 filtergraphs[]
,一个 FilterGraph
里有一个InputFilter
跟一个 OutputFilter
。InputFilter
跟 OutputFilter
又各自有输入跟输出流,所以代码跑到这里,已经确定把输入输出流关联在一起了。
只要在输入流读数据,然后往输入流关联的输出流丢数据就完成转码了。其实之前 OutputStream
里面有个 source_index
成员本身已经关联了输入流,还要把 输入输出流 关联到 filter ,然后丢进去 FilterGraph
是因为 输入输出流虽然关联了,但他们的filter 还没关联起来。
FilterGraph::InputFilter
跟 FilterGraph::OutputFilter
在 init_simple_filtergraph()
里面也还没开始关联,关联之后就可以 发送 frame 到 buffersrc
,从 buffersink
出口读frame,不了解filter的,请先自己了解filter的基本用法。
关联 FilterGraph::InputFilter
跟 FilterGraph::OutputFilter
是在后面的 configure_filtergraph()
里完成的。
至此,open_output_file()
内部逻辑已经分析完毕
©版权所属:知识星球:弦外之音,QQ:2338195090。
由于笔者的水平有限, 加之编写的同时还要参与开发工作,文中难免会出现一些错误或者不准确的地方,恳请读者批评指正。如果读者有任何宝贵意见,可以加我微信 Loken1。