transcode_step转码总函数

FFmpeg 的社群来了,想加入微信社群的朋友请购买《FFmpeg原理》VIP版 电子书,里有更高级的内容与答疑服务。


transcode_step() 函数是 ffmpeg.exe 转换编码格式,或者转换封装格式的总函数,它在函数调用中的位置如下:

transcode_step() 内部每次都会读取一个AVPacket,发送给解码器进行解码,解码器可能会输出 0个 ~ N个 AVFrame,然后把 AVFrame 发送给滤镜容器,如果滤镜容器有数据能出来,就会接着发送给编码器进行编码,如果编码器有 AVPacket 数据可以出来,就把 AVPacket 写入文件保存。

这就是 transcode_step() 总函数的内部逻辑,不过有几点需要注意的。

1,往解码器发送一个 AVPacekt ,解码器可能没数据输出,这样就会回到 while 循环,再次调 transcode_step() 读第二个 AVPacekt 往解码器丢。

2,往滤镜容器发送一个 AVFrame,滤镜容器不一定就立马有数据可读,没数据可读,又会回到 while 循环。

3,往编码器发送一个 AVFrame,编码器不一定就立即输出 AVPacekt,所以又会回到 while 循环,再次调 transcode_step() 解码出第二个 AVFrame 往编码器丢。

这个函数名称之所以有 step,step 就是一步的意思,每次只从文件读取一个 AVPacekt,只处理一个 AVPacket


下面一起来学习一下 transcode_step() 转码总函数的代码,重点如下:

choose_output() 函数的作用是在所有输出流(OutputStream)中选出最合适的一个。 代码如下:

可以看到,会优选选择未初始化的 OutputStream,如果所有输出流都初始化了,就选择时间最短的那个输出流。最短时间通过 ost->st->cur_dts 来判断,例如音频流已经输出了2分钟了,视频流才输出1分钟,choose_output() 函数就会返回 视频流。

ost->initialized 在出口滤镜输出 AVFrame 的时候就会初始化,被赋值为 1,注意不是在解码器输出 AVFrame 的时候,而是出口滤镜输出 AVFrame 的时候才会初始化输出流,因为这时候才知道具体要编码的像素格式等信息。(不过对于音频输出流,会提前进行初始化)

//init_output_stream 函数的代码
ost->initialized = 1;

ost->inputs_done 代表没有找到输出流对应的输入流,通常不会没找到,所以通常一直都是 0


第二个重点是三个 if 判断,这 3 个 if 判断分别是处理不同的场景的,如下:

第一个 if 条件,的代码如下:

if (ost->filter && !ost->filter->graph->graph) {
    if (ifilter_has_all_input_formats(ost->filter->graph)) {
        ret = configure_filtergraph(ost->filter->graph);
        if (ret < 0) {
            av_log(NULL, AV_LOG_ERROR, "Error reinitializing filters!\n");
            return ret;
        }
    }
}

上面这段代码比较复杂,我也是过了几个月才明白它的场景,

这个 if 我个人认为很有可能是多余的,我在简单滤镜以及复杂滤镜的场景下都测了,没有跑进去。

上面的条件是 ost->filter 不为 NULL,并且还未创建 graph 滤镜容器,这两个条件有可能成立。但是这里的 ifilter_has_all_input_formats() 不可能同时成立。

补充: ifilter_has_all_input_formats() 函数是判断所有的 InputFilter::format 不为 -1 就成立。

因为 transcode_step() 函数后面的代码,只要解码出一个 AVFrame,就会调 ifilter_send_frame() 函数来把这个 AVFrame 发送给 InputFilter,而 ifilter_send_frame() 会调 ifilter_parameters_from_frame() 来把 InputFilter::format 设置为非 -1 的值。

当所有的 InputFilter::format 都设置为非 -1 的时候, ifilter_send_frame() 里面就会调 configure_filtergraph() 来创建 graph 滤镜容器了。

如果已经创建了 graph 滤镜容器,下次再循环跑进去上面的 if!ost->filter->graph->graph 就不等于真了。

所以我观察下来下面上面这 3 个条件不会同时成立,所以我个人判断,上面的这点 if 代码可能是多余的,具体我后面在 FFmpeg 社区发个邮件问一下再补充。


第二个 if 条件,从滤镜容器选出最合适的输入流,如下:

 if (ost->filter && ost->filter->graph->graph) {
        /*
         * 省略注释
         */
        if (av_buffersink_get_type(ost->filter->filter) == AVMEDIA_TYPE_AUDIO)
            init_output_stream_wrapper(ost, NULL, 1);

        if ((ret = transcode_from_filter(ost->filter->graph, &ist)) < 0)
            return ret;
        if (!ist)
            return 0;
    }

上面的代码主要是调用了 transcode_from_filter() 函数,确定应该从哪个输入流读取 AVPacekt

具体的算法,是用 av_buffersrc_get_nb_failed_requests() 获取每个 buffer入口滤镜的失败次数,选出失败次数最多的入口滤镜,最后因为入口滤镜绑定了输入流。所以最合适的输入流也就确定了。


第三个 if 条件,当还未打开滤镜容器的时候,选一个从未解码出 AVFrame 的输入流来进行处理,如下:

else if (ost->filter) {
    int i;
    for (i = 0; i < ost->filter->graph->nb_inputs; i++) {
        InputFilter *ifilter = ost->filter->graph->inputs[i];
        if (!ifilter->ist->got_output && !input_files[ifilter->ist->file_index]->eof_reached) {
            ist = ifilter->ist;
            break;
        }
    }
    if (!ist) {
        ost->inputs_done = 1;
        return 0;
    }
}

上面的 got_output 字段,代表这个输入流是否已经解码出来 AVFrame 了,解码出来 AVFrame 就可以配置对应的入口滤镜的参数。

当所有的入口滤镜都配置完成(format 等于 -1),就可以成功打开滤镜容器,函数的调用流程如下:

关于 configure_filtergraph 函数的介绍,推荐阅读《configure_filtergraph配置滤镜容器》。


第四个 else 条件,是最后的保留策略,直接用输出流对应的输入流,如下:

else {
    av_assert0(ost->source_index >= 0);
    ist = input_streams[ost->source_index];
}

我也不太清楚什么场景会跑进这里,ffmpeg.exe 转换器,就是分为简单滤镜 与 复杂滤镜,不能不使用滤镜,即使不使用滤镜,默认也会创建一个空滤镜。

所以我倾向于认为最后这个保留策略是多余的。


做个阶段性的小总结,transcode_step() 内部选出输入流的策略只有两个需要关注的地方,就是第二 跟 第三个条件。

1,第二个条件是滤镜容器已经打开了,所以从 transcode_from_filter() 选出输入流来进行处理。

2,第三个条件是,只要有一个入口滤镜未配置好,也就是 format 等于 -1 的时候,滤镜容器就不能打开。这时候,就选出一个从未解码出 AVFrame 的输入流出来,进行处理。

所以,无论你是用简单滤镜,还是复杂滤镜,ffmpeg.exe 它都会先跑到 第三个条件,把所有的输入流都至少解码出一个 AVFrame。然后打开滤镜容器,这样才会跑进去第二个条件。


transcode_step() 转码总函数的流程图如下:

从上图可以看到,选出输入流之后,就会调 process_input() 从输入流关联的输入文件里面读取 AVPacekt

process_input() 函数的主要逻辑是,读取一个 AVPacekt,然后往解码器丢,解码器可能不会出数据,也可能会出来多个 AVFrame,无论出来多少个 AVFrame,都调 send_frame_to_filters() 把它们往入口滤镜发送。

process_input()只会往解码器发送一个 AVPacket,但是会不断往解码器读 AVFrame,直到解码器返回 EAGAIN。

而最后的 reap_filter() 函数,就负责从出口滤镜里 读取 AVFrame,然后发送给编码器编码,最后保存进去文件。

更详细的介绍,推荐阅读《process_input处理输入文件》《reap_filter收割滤镜

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
敬告:本课程基于最新版FFmpeg4.3.1开发,仅供学习参考,不可以直接商用(想直接购买商用源代码的同学,请放弃!)。我将带领大家从以下几个方面来深入剖析ffmpeg4.3:“拿来主义”、“为我所用”。通过学习本课程,您将收获:三大多媒体框架(DirectShow、GStreamer、FFmpeg)简介与分析FFmpeg4.3的开发环境搭建(重要讲解windows下的源码编译及vs2015的环境)ffmpeg.c(4.3.1)源码剖析(分析开源大师们的设计理念和编码技巧)亲手封装私有函数:获取音视频流信息(类似于MediaInfo)、获取实时转码进度(改善用户体验)两套实现代码: MFC和Qt。-------------------------------------------------------------------音视频是一门很复杂的技术,涉及的概念、原理、理论非常多,很多初学者不学 基础理论,而是直接做项目,往往会看到c/c++的代码时一头雾水,不知道代码到底是什么意思,这是为什么呢? 因为没有学习音视频的基础理论,就比如学习英语,不学习基本单词,而是天天听英语新闻,也听不懂。所以呢,一定要认真学习基础理论,然后再学习播放器、转码器、非编、流媒体直播、视频监控、等等。 梅老师从事音视频与流媒体行业18年;曾在永新视博、中科大洋、百度、美国Harris广播事业部等公司就职,经验丰富;曾亲手主导广电直播全套项目,精通h.264/h.265/aac,曾亲自参与百度app上的网页播放器等实战产品。目前全身心自主创业,主要聚焦音视频+流媒体行业,精通音视频加密、流媒体在线转码快编等热门产品。  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Loken2020

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值