ffmpeg 源码分析-转码2

456 阅读4分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

本系列 以 ffmpeg4.2 源码为准,下载地址:链接:百度网盘 提取码:g3k8

ffmpeg 源码分析系列以一条简单的命令开始,ffmpeg -i a.mp4 b.flv,分析其内部逻辑。

a.mp4下载链接:百度网盘,提取码:nl0s 。


本文主要分析 transcode_step() 的内部逻辑,流程图如下

ffmpeg_c-7-1.png

transcode_step() 的代码如下,为了简洁,有些不重要代码缩进了,还有一些无关的if,会在图中标注,重点代码会已经框出来。

ffmpeg_c-7-2.png

如图所示,transcode_step() 主要逻辑有4点。

1,调用 choose_output() 在所有输出流中选出最合适的那个 OutputStreamchoose_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 翻译成中文,不如直接看代码 。

ffmpeg_c-7-3.png

可以看到 transcode_step() 的前半部分,主要功能就是确定 该选择哪个ist 输入流来处理。


接下来仔细分析里面的 transcode_from_filter() 。先上代码图。

ffmpeg_c-7-4.png

如图所示, transcode_from_filter() 有两个地方需要注意,

  1. 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。所以不用特别关注。

  2. 第二个框出的代码,av_buffersrc_get_nb_failed_requests() 获取 buffer filter 的失败次数,来确定 best_ist

    这个的逻辑是这样的,如果之前已经请求了那么多次这个filter都没读出东西,肯定应该先选择这个filter关联的输入流来读取 pakcet,然后发给 filter,让下次 filter能尽快读出数据。因为filter已经读了很多次都读不出数据,所以应该优选处理这个 filter 关联的 InputStream


感谢观看,下一篇文章分析 process_input() 函数的内部逻辑

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