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 三者之间的关系:一张图说清楚

好,咱们把这三个概念串起来:

  1. 时间基 定义了时间的「刻度」。
  2. PTS/DTS 用这个刻度来表示每一帧的「解码时间」和「显示时间」。
  3. 时钟模型 决定了这些时间戳是相对于哪个「参考时钟」来计算的。

举个例子:

假设你有一个视频流,时间基是 1/90000,音频时钟是主时钟。某一帧的 PTS 是 90000,那它的实际显示时间就是 90000 * 1/90000 = 1 秒。这个 1 秒,是相对于音频时钟的 1 秒。

嗯,到这里你应该明白了。时间戳不是孤立的数字,它必须结合时间基和时钟模型才有意义。

2.5 避坑指南:我踩过的那些坑

最后,分享几个实战中容易出问题的地方:

  • 时间基换算精度丢失:不同时间基之间换算时,如果用了整数除法,很容易丢失精度。我建议一律用浮点数或高精度整数来算。
  • PTS 回绕:时间戳是有限长度的整数,当它达到最大值后会回绕到 0。如果你的播放器没有处理回绕,那同步就会乱套。我记得有一次调试了整整两天,才发现是 PTS 回绕导致的时间跳变。
  • 时钟漂移:即使你用了同一个时钟模型,不同设备的晶振也会有微小的频率差异。长时间播放下来,这个差异会累积成明显的不同步。所以,一定要有定期的时钟校准机制。

好了,这一节的内容就到这里。时间戳是音视频同步的基石,搞懂了它,后面的内容你就轻松多了。