2. 时间戳基础:PTS/DTS概念、时间基(Timebase)理解、时钟模型
好,咱们进入正题。多屏同步这件事,说白了就是跟时间打交道。你连时间都搞不清楚,那同步就是空谈。这一节,我把时间戳这块最核心的三个概念掰开揉碎了讲给你听。
2.1 PTS 和 DTS:两个时间戳,各司其职
先问个问题:视频文件里,每一帧数据是怎么播放的?
你可能会说,按顺序播呗。嗯,大体没错,但实际情况要复杂一点。因为视频编码的时候,帧和帧之间是有依赖关系的。比如 B 帧(双向预测帧),它需要参考前后的帧才能解码出来。
这就引出了两个关键时间戳:
- DTS(Decoding Time Stamp):解码时间戳。告诉解码器,这一帧什么时候该被解码。
- PTS(Presentation Time Stamp):显示时间戳。告诉渲染器,这一帧什么时候该显示出来。
我打个比方你就明白了。你想想看,做菜的时候,有些菜需要提前准备食材。DTS 就是「备菜时间」,PTS 就是「上菜时间」。备菜肯定要比上菜早,对吧?
核心要点:对于 I 帧和 P 帧,PTS 和 DTS 通常是相等的。但对于 B 帧,DTS 会小于 PTS。因为 B 帧需要先解码,但得等到前后帧都准备好了才能显示。
我在项目中遇到过一个问题:有个播放器,画面总是卡顿,但音频是正常的。查了半天,发现是解码器把 B 帧的 PTS 当成了 DTS 来用,导致解码顺序和显示顺序搞混了。嗯,这种坑,踩过一次就记住了。
2.2 时间基(Timebase):时间的「刻度尺」
好,现在你知道了 PTS 和 DTS。但问题来了:这些时间戳是用什么单位表示的?
秒?毫秒?微秒?
都不是。在音视频领域,我们用的是 时间基(Timebase)。你可以把它理解成一把「刻度尺」。这把尺子的最小刻度是多少,时间戳的数值就代表多少个刻度。
举个例子:
- 如果时间基是
1/1000,那时间戳的单位就是毫秒。PTS = 1000,就代表第 1 秒。 - 如果时间基是
1/90000,那时间戳的单位就是 1/90000 秒。这是 MPEG 标准里常用的时间基。
我个人习惯用 FFmpeg 里的 AVRational 来表示时间基。它就是一个分数:
typedef struct AVRational {
int num; // 分子
int den; // 分母
} AVRational;
// 例如:时间基为 1/1000
AVRational timebase = {1, 1000};
// 计算实际时间(秒)
double real_time = pts * timebase.num / (double)timebase.den;
为什么要搞得这么复杂?直接统一用微秒不好吗?
你想想看,不同的编码标准、不同的容器格式,它们对时间精度的要求不一样。MPEG-TS 流需要高精度,所以用 90kHz 的时间基。而一些简单的封装格式,用毫秒就够了。时间基的存在,就是为了让不同系统之间能灵活地换算时间。
我的小技巧:在做多屏同步的时候,我建议把所有时间戳都统一换算成微秒(microseconds)来处理。这样能避免不同时间基之间换算带来的精度损失。换算公式很简单:time_us = pts * timebase.num * 1000000 / timebase.den。
2.3 时钟模型:谁才是「标准时间」?
好了,现在你有了时间戳,也有了时间基。但还有一个更根本的问题:这些时间戳是相对于哪个时钟来算的?
这就引出了时钟模型。在音视频系统中,常见的时钟模型有三种:
| 时钟模型 | 说明 | 典型应用 |
|---|---|---|
| 系统时钟 | 以播放设备的系统时间为基准 | 本地播放器、简单的同步场景 |
| 音频时钟 | 以音频设备的采样时钟为基准 | 音视频同步时,通常以音频时钟为主 |
| 外部时钟 | 以网络或外部设备提供的时钟为基准 | 多屏互动、直播推流、NTP 同步 |
我重点说一下音频时钟。为什么它这么重要?
因为人耳对声音的抖动特别敏感。视频画面稍微卡一下,你可能觉得还行。但音频如果出现断续、卡顿,你立马就能感觉到。所以,在大多数音视频同步方案里,我们都是以音频时钟作为「主时钟」,视频去跟随音频。
我曾经在一个多屏互动项目里踩过坑。当时我们用了系统时钟来做同步基准,结果两台设备的系统时间差了 200 毫秒。画面倒是同步了,但声音对不上。后来改成以其中一台设备的音频时钟为主时钟,通过网络把时钟信息广播出去,问题才解决。
注意:千万不要假设所有设备的系统时钟是同步的。即使你用了 NTP 对时,网络延迟和时钟漂移也会导致误差。在多屏场景下,一定要有一个明确的「主时钟」概念。
2.4 三者之间的关系:一张图说清楚
好,咱们把这三个概念串起来:
- 时间基 定义了时间的「刻度」。
- PTS/DTS 用这个刻度来表示每一帧的「解码时间」和「显示时间」。
- 时钟模型 决定了这些时间戳是相对于哪个「参考时钟」来计算的。
举个例子:
假设你有一个视频流,时间基是 1/90000,音频时钟是主时钟。某一帧的 PTS 是 90000,那它的实际显示时间就是 90000 * 1/90000 = 1 秒。这个 1 秒,是相对于音频时钟的 1 秒。
嗯,到这里你应该明白了。时间戳不是孤立的数字,它必须结合时间基和时钟模型才有意义。
2.5 避坑指南:我踩过的那些坑
最后,分享几个实战中容易出问题的地方:
- 时间基换算精度丢失:不同时间基之间换算时,如果用了整数除法,很容易丢失精度。我建议一律用浮点数或高精度整数来算。
- PTS 回绕:时间戳是有限长度的整数,当它达到最大值后会回绕到 0。如果你的播放器没有处理回绕,那同步就会乱套。我记得有一次调试了整整两天,才发现是 PTS 回绕导致的时间跳变。
- 时钟漂移:即使你用了同一个时钟模型,不同设备的晶振也会有微小的频率差异。长时间播放下来,这个差异会累积成明显的不同步。所以,一定要有定期的时钟校准机制。
好了,这一节的内容就到这里。时间戳是音视频同步的基石,搞懂了它,后面的内容你就轻松多了。