在开发多媒体播放器或直播系统时,音视频的同步是非常关键且复杂的点。要想把音视频同步搞明白,我们必须要了解一些基本的知识。只有了解了这些基本知识,才能为你打下理解音视频同步的基础。 本文将从下面几个主题介绍这些知识点:
对于 I帧,B帧,P帧,我想很多人对它们已经了解的很多了。
但在实时互动直播系统中,很少使用B帧。主要的原因是压缩和解码B帧时,由于要双向参考,所以它需要缓冲更多的数据,且使用的CPU也会更高。由于实时性的要求,所以一般不使用它。不过对于播放器来说,遇到带有B帧的H264数据是常有的事儿。
对于 H264 编码原理相关的知识请看我的免费视频课《移动端音视频入门》
有了上面 I/B/P帧的概念,我们再来理解 PTS/DTS 就非常容易了。PTS(Presentation TimeStamp)是渲染用的时间戳,也就是说,我们的视频帧是按照 PTS 的时间戳来展示的。DTS(Decoding TimeStamp)解码时间戳,是用于视频解码的。
那为什么有了 PTS 还要有 DTS呢?这就与我们上面所讲的 I/B/P帧有关了。如果我们的视频中没有B帧,那显示的帧的顺序与存放的帧的顺序是一样的,此时PTS与DTS 的值就是一样的,也就没有存在两个时间戳的必要了。
但有了B帧之后,就不是这个样子了。我们举个简单的例子:
第一行,实际应展示的顺序:I B B P
第二行,实际在存放的顺序:I P B B
第三行,按实际顺序号展示:1 4 2 3
第四行,按实际顺序号展示:1 2 3 4
对于上面这个例子我们作下说明:
有了时间戳之后,最终进行展示时还要需要将 PTS时间戳转成以秒为单位的时间。那这里需要向大家介绍一下 ffmpeg的时间基。
我们在执行 ffmpeg/ffplay命令时,可以通过控制台看到几个参数,分别是 tbr, tbn, tbc。这几个值是什么含义呢?其实就是不同的时间基。
在ffmpeg中,不同的时间戳对应不同的时间基。对于视频的渲染我们使用的是视频流的时间基,也就是 tbn。那我们如何理解时间基呢?其实非常简单,就是时间刻度。我们以帧率为例,如果每秒钟的帧率是 25帧,那么它的时间基(时间刻度)就是 1/25。也就是说每隔1/25 秒后,显示一帧。
所以如我们当前的时间是 100, 时间基是 1/25,那么转成秒的时间是多少呢? 100*(1/时间基),也就是100 * 1/25 = 4秒。是不是非常的简单?
除了我上面所讲的几个时间基之外,ffmpeg内部还有一个时间基。即我们通过所见到的 AV_TIME_BASE
。它在ffmpeg内部定义如下:
#define AV_TIME_BASE 1000000
它还有一种分数所表式法:
#define AV_TIME_BASE_Q (AVRational){1, AV_TIME_BASE}
在 ffmpeg中进行换算,将不同时间基的值转成按秒为单位的值计算如下:
timestamp(秒) = pts * av_q2d(time_base)
这里引入了 av_q2d 这个函数,它的定义非常简单:
typedef struct AVRational{
int num; //numerator
int den; //denominator
} AVRational;
static inline double av_q2d(AVRational a){
/**
* Convert rational to double.
* @param a rational to convert
**/
return a.num / (double) a.den;
}
从这里我们可以看到,它与我上面所讲的公式是一样的。
在上面我向大家介绍了 ffmpeg有好几种不同的时间基,有时候我们需要在不同的时间基之间做换算。ffmpeg为我们提供了非常方便的函数。即
av_rescale_q()
av_rescale_q(a,b,c)
的作用是,把时间戳从一个时基调整到另外一个时基时候用的函数。其中,a 表式要换算的值;b 表式原来的时间基;c表式要转换的时间基。其计算公式为 a * b / c
。
既然公式这么简单,我们自己写就OK了,为什么ffmpeg还要单独提供一个函数呢?其实这个看似简单的方法,还要考虑数值溢出的问题。所以把这块的逻辑加上之后,就没我们看到的这么简单了。不过没关系,我们只要清楚 av_rescale_q 是做什么的,怎么用就可以了。
下面我再给出两个算计公式:
以上我通过几个主题向大家介绍了ffmpeg中的时间戳与时间基,以及音视频同步的基本知识。通过本文大家会了解到,其实ffmpeg中的时间戳与时间基并不复杂。但就是这些不复杂的知识点的交互最终完成了音视频的同步。