音视频同步是什么呢?它就是指在音视频数据播放的时候,播放的画面和声音是需要同步的,是能对得上的。相信你一定遇到过这种情况,就是看电视、电影或者直播的时候,人的口型和声音是对不上的,这样看起来会让人非常难受,这种问题就是音视频不同步导致的。因此做好音视频同步是非常重要的,当然也会有一定的难度,我们不妨先从一些基础知识讲起。
PTS 和 DTS
首先就是 PTS 和 DTS 这两个概念。其实,我们在讲音视频封装的时候已经提到过了。
PTS 表示的是视频帧的显示时间,DTS 表示的是视频帧的解码时间。对于同一帧来说,DTS 和 PTS 可能是不一样的。
为什么呢?主要的原因是 B 帧,因为 B 帧可以双向参考,可以参考后面的 P 帧,那么就需要将后面用作参考的 P 帧先编码或解码,然后才能进行 B 帧的编码和解码。所以就会导致一个现象,后面显示的帧需要先编码或解码,这样就有解码时间和显示时间不同的问题了。如果说没有 B 帧的话,只有 I 帧和 P 帧就不会有 PTS 和 DTS 不同的问题了。
时间基
时间基是什么呢?很简单,它就是时间的单位。比如说,编程的时候我们经常使用 ms(毫秒)这个时间单位,毫秒是 1/1000 秒,如果你用毫秒表示时间的话,时间基就是1/1000。再比如说 RTP 的时间戳,它的单位是 1/90000 秒,也就是说 RTP 时间戳的时间基是 1/90000。意思是 RTP 的时间戳每增加 1,就是指时间增加了 1/90000 秒。
而对于 FLV 封装,时间基是 1/1000,意思是 FLV 里面的 DTS 和 PTS 的单位都是 ms。MP4 的话,时间基就是在 box 中的 time_scale,是需要从 box 中读取解析出来的,不是固定的。
音视频同步的类型
音视频同步主要的类型有三种:视频同步到音频、音频同步到视频、音频和视频都做调整同步。我们逐一看下。
首先,视频同步到音频是指音频按照自己的节奏播放,不需要调节。如果视频相对音频快了的话,就延长当前播放视频帧的时间,以此来减慢视频帧的播放速度。如果视频相对音频慢了的话,就加快视频帧的播放速度,甚至通过丢帧的方式来快速赶上音频。
这种方式是最常用的音视频同步方式,也是我们今天讲述的重点,后面我们就会以这种方式来深入探讨其原理。
音频同步到视频是指视频按照自己的节奏播放,不需要调节。如果音频相对视频快了的话,就降低音频播放的速度,比如说重采样音频增加音频的采样点,延长音频的播放时间。如果音频相对视频慢了,就加快音频的播放速度,比如说重采样音频数据减少音频的采样点,缩短音频的播放时间。
一般来说这种方式是不常用的,因为人耳的敏感度很高,相对于视频来说,音频的调整更容易被人耳发现。因此对音频做调节,要做好的话,难度要高于调节视频的速度,所以我们一般不太会使用这种同步方法。
最后一种是音频和视频都做调整,具体是指音频和视频都需要为音视频同步做出调整。比如说 WebRTC 里面的音视频同步就是音频和视频都做调整,如果前一次调节的是视频的话,下一次就调节音频,相互交替进行,整体的思路还是跟前面两种方法差不多。音频快了就将音频的速度调低一些或者将视频的速度调高一些,视频快了就将视频的速度调低一些或者将音频的速度调高一些。这种一般在非 RTC 场景也不怎么使用。
视频同步到音频
首先,我们使用的时间戳是 PTS,因为播放视频的时间我们应该使用显示时间。而且我们需要先通过时间基将对应的时间戳转换到常用的时间单位,一般是秒或者毫秒。
然后,我们有一个视频时钟和一个音频时钟来记录当前视频播放到的 PTS 和音频播放到的PTS。注意这里的 PTS 还不是实际视频帧的 PTS 或者音频帧的 PTS,稍微有点区别。
区别是什么呢?比如说一帧视频的 PTS 的 100s,这一帧视频已经在渲染到屏幕上了,并且播放了 0.02s 的时间,那么当前的视频时钟是 100.02s。也就是说视频时钟和音频时钟不仅仅需要考虑当前正在播放的帧的 PTS,还要考虑当前正在播放的这一帧播放了多长时间,这个值才是最准确的时钟。
而视频时钟和音频时钟的差值就是不同步的时间差。这个时间差我们记为 diff,表示了当前音频和视频的不同步程度。我们需要做的就是尽量调节来减小这个时间差的绝对值。
我们知道,我们可以通过计算得到当前正在播放的视频帧理论上应该播放
多长时间(不考虑音视频同步的话)。计算方法就是用还没有播放但是紧接着要播放的帧的 PTS 减去正在播放的帧的 PTS,我们记为 last_duration。
如果说当前视频时钟相比音频时钟要大,也就是 diff 大于 0,说明视频快了。这个时候我们就可以延长正在播放的视频帧的播放时间,也就是增加 last_duration 的值,是不是视频的播放画面就会慢下来了?因为后面的待播放帧需要等更长的时间才会播放,而音频的播放速度不变,是不是就相当于待播放的视频帧在等音频了?
反之,如果说当前的视频时钟相比音频时钟要小,也就是 diff 小于 0,说明视频慢了。这个时候我们就缩短正在播放的视频帧的播放时间,也就是减小 last_duration 的值,是不是视频的播放画面就会加快速度渲染,就相当于待播放的视频帧在加快脚步赶上前面的音频了?
具体到底对 last_duration 加多少或者减多少呢?我们来看看 FFplay 的代码是怎么做的。
最重要的函数 compute_target_delay 具体是怎么实现的
结合代码我们可以看出,音视频同步并不是完完全全同步的,而是通过调整正在播放的视频帧的播放时间来尽量达到一个动态的同步状态,这个状态里面的视频时钟和音频时钟并不是完全相等的,只是相差得比较少,人眼的敏感度看不出来而已。这就是音视频同步的原理。