本系列 以 ffmpeg4.2 源码为准,下载地址:链接:百度网盘 提取码:g3k8
ffmpeg 源码分析系列以一条简单的命令开始,ffmpeg -i a.mp4 b.flv
,分析其内部逻辑。
a.mp4下载链接:百度网盘,提取码:nl0s 。
本文主要分析 transcode_step()
的内部逻辑,流程图如下
transcode_step()
的代码如下,为了简洁,有些不重要代码缩进了,还有一些无关的if,会在图中标注,重点代码会已经框出来。
如图所示,transcode_step()
主要逻辑有4点。
1,调用 choose_output()
在所有输出流中选出最合适的那个 OutputStream
,choose_output()
的内部实现是优选选择 没有初始化的 OutputStream
,如果 OutputStream
都初始化了,就选当前 stream 时长最短的返回,最短时间通过 ost->st->cur_dts
来判断,choose_output()
比较简单,自行阅读代码可理解。
2,根据已经选择的 OutputStream
来确定 InputStream
,确定 InputStream
这个逻辑比较绕,需要重点讲一下。图中可以看到,一共有3个 if 条件来设置 ist
。这里我直接指明一下,第一第二个if
会跑进去,第三个if
在当前命令不会跑进去。那第一第二个if
什么时候会跑进去呢?
- 第一个 if 的判断条件是
if(ost->filter && ost->filter->graph->graph)
,刚开始的时候ost->filter->graph->graph
是空的,只有在解码器已经解码出第一个frame,因为解码出了 frame 所以要把 frame 发送给buffer filter
,所以必须调configure_filtergraph()
初始化 graph,这时候ost->filter->graph->graph
才有值。也就是说,只有解码器输出了 frame,第一个if
才能跑进去。跑进去之后是调了transcode_from_filter()
来确定输入流,这个函数稍微有点复杂,这里简单介绍,transcode_from_filter()
是根据buffersink filter
最大请求失败次数来确定输入流,下一段再讲解。这里提醒一点,解码器输出了 frame,ist->got_output
会等于1,第二个if
用了这个判断。 - 第二个 if 的判断条件是
if(ost->filter)
,这个条件肯定是成立的,主要是里面的if
,if (!ifilter->ist->got_output && !input_files[ifilter->ist->file_index]->eof_reached)
,注意,这里用了 got_output。所以,第二个if
是在解码器还没有输出frame的时候跑进去的,优先选择那些解码器还没输出frame的输入流。
从代码上看,确定输入流 InputStream
有两个分支,解码器有无开始输出 AVFrame
,输出了跑第一个if,没有开始输出继续跑第二个if。
从逻辑上看,确定输入流 InputStream
的逻辑是,优先选择解码器还没输出frame
的输入流,如果所有解码器都已经输出过 frame
,就调用 transcode_from_filter()
来确定选择哪个输入流来处理。
3,调用 process_input()
,process_input()
里面的逻辑这里简单介绍一下,就是从文件读出 AVPacket
,丢给解码器解码,如果解出 frame,就把frame丢给 filter 链。这里有个注意点需要提及一下,虽然以上逻辑已经选出了 InputStream
,假如InputStream
是video,但从文件读出来的 packet
或许是audio。
4,调用 reap_filter()
从 buffersink filter
里面读取 frame,然后丢给编码器,然后mux,写进去文件。这里只是简单介绍 reap_filter()
的作用,后续会详细讲解。reap 的意思是收割的意思。
至此,transcode_step()
的内部逻辑分析了一大半了。下面画个流程图,流程图会把一些关联逻辑画出来,不是简单地把代码里的 if
翻译成中文,因为那样会让初学者感到很茫然,本文想表达的是,if 中的条件在什么样的场景下会是 true,什么样的场景下是false,便于读者理解整体逻辑,如果只是把 if
翻译成中文,不如直接看代码 。
可以看到 transcode_step()
的前半部分,主要功能就是确定 该选择哪个ist
输入流来处理。
接下来仔细分析里面的 transcode_from_filter()
。先上代码图。
如图所示, transcode_from_filter()
有两个地方需要注意,
avfilter_graph_request_oldest()
这个api函数的作用是获取graph 里面有多少frame可以读,就是从buffersink filter
能读出多少 frame。这里的返回值,在当前命令ffmpeg -i a.mp4 b.flv
下,大部分的返回值都是AVERROR(EAGAIN)
,小于0的值。为什么这里ret大部分都小于0,是因为 之前已经执行过一遍reap_filter()
把 filter 的 frame 收割完了。所以这里的reap_filter()
大部分情况不会执行。只有在后面刷 graph 的末点数据的时候才会有可能返回 ret 大于等于0。所以不用特别关注。- 第二个框出的代码,
av_buffersrc_get_nb_failed_requests()
获取buffer filter
的失败次数,来确定best_ist
。这个的逻辑是这样的,如果之前已经请求了那么多次这个filter都没读出东西,肯定应该先选择这个filter关联的输入流来读取pakcet
,然后发给 filter,让下次 filter能尽快读出数据。因为filter已经读了很多次都读不出数据,所以应该优选处理这个 filter 关联的InputStream
。
下一篇文章分析 process_input()
函数的内部逻辑
©版权所属:知识星球:弦外之音,QQ:2338195090。
由于笔者的水平有限, 加之编写的同时还要参与开发工作,文中难免会出现一些错误或者不准确的地方,恳请读者批评指正。如果读者有任何宝贵意见,可以加我微信 Loken1。