FFplay源码分析-nobuffer - C语言音视频技术

/ 0评 / 0

本系列 以 ffmpeg4.4 源码为准,主要讲解 ffplay 的 RTMP 协议解析,播放。本文使用的命令如下:

ffplay -fflags nobuffer -i rtmp://192.168.0.122/live/livestream

在使用 FFplay 播放 RTMP 流的时候,如果 不开启 nobuffer 选项,画面延迟会高达 7 秒左右,开启了,局域网延迟可降低到100毫秒左右。

因此本文主要研究 nobuffer 的具体实现,以及播放端 缓存 7 秒的数据有何作用。

fflags 的定义在 libavformat/options_table.h,如下图代码,这是一个通用选项,所有的 解复用器都有这个选项。

也就是说,这个命令行参数,是在调 avformat_open_input() 函数的时候丢进去的,我为什么知道是在这个地方丢进去的?请看之前的专栏《FFplay源码分析》,所有的解复用参数,也叫格式参数,都是在 avformat_open_input() 丢进去的。可以看下图证明一下:

记得改 Clion 的调试参数,把 -fflags nobuffer 加上去。


avformat_open_input() 函数内部,会把 fflags 这个 AVOption 丢给 AVClass,如下图所示,AVClass 里面存储了好几个 AVOption ,fflags 这个 AVOption 的下标是 5 ,前面的是 默认的选项,自动加进去的。

注意 ,av_opt_set_dict() 这个函数会改变 tmp 的值,把能用 选项应用之后剔除。


nobuffer 这个参数 传递过程中的函数调用有点长,推荐看之前的《FFmpeg源码分析-参数解析篇》,原理类似,我知道这个 参数最后会赋值到 struct AVFormatContextflags 字段里面,如下图:

如上图所示,所以 avformat_open_input() 函数执行完之后 AVFormatContext::flags 的第7位应该会被置为1,因为 0x40 的二进制是 1000000。请看下图:

从上图可以看出, ic->flags 直接就是 64 ,也就是 16 进制的 0x40。所以上面的分析没错。 avformat_open_input() 函数只是把 命令行参数解析 到这个 flags 字段,但是真正使用这个字段 是在 avformat_find_stream_info() 里面。直接搜 AVFMT_FLAG_NOBUFFER 就能找到使用的位置。

avformat_find_stream_info() 函数的内部逻辑实际上非常复杂,我直接讲重点代码,如下:

if (!(ic->flags & AVFMT_FLAG_NOBUFFER)) {
    ret = avpriv_packet_list_put(&ic->internal->packet_buffer,
                                        &ic->internal->packet_buffer_end,
    pkt1, NULL, 0);
    if (ret < 0)
        goto unref_then_goto_end;
​
    pkt = &ic->internal->packet_buffer_end->pkt;
} else {
    pkt = pkt1;
}

AVFMT_FLAG_NOBUFFER 标记 如果没设置,就会导致 探测的数据包丢进去队列,我们知道 avformat_find_stream_info() 会先读一段数据包分析出流是什么编码器之类的,为了重用这个 探测的数据包,这里就会丢进去队列,播放的时候,就从这些数据包开始,但是整个探测过程,长达 5秒,也就是 ffplay 大概会读 5秒的数据,来分析输入流的情况。如果开启 nobuffer,就不会重用这些探测数据,ffpaly 探测完输入流之后,就会重新读取新的数据包来播放。不用缓存的,所以延迟就低了。

如下图,我在 ffpaly.c 的 avformat_find_stream_info() 前后输出了个时间,正好相差5秒。

double start_time3 = av_gettime_relative() / 1000000.0;
if (find_stream_info) {
    ...省略代码...
    err = avformat_find_stream_info(ic, opts);
    ...省略代码...
}
double end_time3 = av_gettime_relative() / 1000000.0;
printf("start is %f , end is %f \r\n",start_time3,end_time3);

所以实际上 ffplay 在 实时的场景下,缓存是个鸡肋,本来这个 buffer 功能是为了分析本地文件,避免重复读取,但是影响到了实时的场景。实时场景必须把 buffer 关掉。

补充,因为我是虚拟机做服务器,所以都是本机通信,不启用 buffer 也能很流畅,但是如果我把 SRS 部署在局域网另一台机器,不开启 buffer ,视频有卡顿,估计是还没来得及解码丢进去队列,所以 ffplay 不断丢弃视频帧。因为视频比音频慢了,得丢弃。

相关阅读

1,《FFmpeg 框架简读-Demux 部分》


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

发表回复

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