时间戳机制:PTS、DTS、PCR 与时间基

做流媒体的人,早晚得跟时间戳打交道。我入行那会儿,第一次调试音画不同步,抓了半天的包,最后发现是 PTS 和 DTS 搞混了。嗯,从那以后,我对这四个概念再也不敢马虎。

说白了,时间戳就是给每一帧数据贴上的「时间标签」。没有它,解码器不知道什么时候该干活,渲染器不知道什么时候该显示。整个系统就乱套了。

核心一句话:时间戳是多媒体同步的基石。PTS 告诉显示器「什么时候亮出来」,DTS 告诉解码器「什么时候开工」,PCR 告诉接收端「你的时钟准不准」。

2.1 PTS(显示时间戳)

PTS,全称 Presentation Time Stamp。它标记的是这一帧数据什么时候应该被显示出来。

举个例子。你播放一个 24fps 的视频,每帧间隔大约是 41.67ms。第一帧的 PTS 是 0,第二帧就是 41.67ms,第三帧 83.33ms……解码器解完数据后,渲染器会等,等到系统时钟走到 PTS 那个时刻,才把画面推上去。

我在项目中遇到过一个问题:某些播放器在 seek 之后,画面会卡在旧帧上好几秒。查到最后,是 PTS 在 seek 后没有重置,导致渲染器一直在等一个永远不会到来的时间点。修复方式很简单——seek 后清空 PTS 缓存,重新从关键帧开始计算。

个人习惯:调试音画同步问题时,我第一件事就是打印 PTS 日志。如果音频 PTS 和视频 PTS 的差值超过一个阈值(比如 100ms),基本就能定位到问题。

2.2 DTS(解码时间戳)

DTS,全称 Decoding Time Stamp。它标记的是什么时候开始解码这一帧。

你可能会问:「PTS 和 DTS 不是一样的吗?」

还真不一定。在大多数情况下,PTS 和 DTS 是相等的。但一旦遇到 B 帧(双向预测帧),事情就变了。

B 帧需要参考前后的帧才能解码。所以解码器必须先解出后面的参考帧,才能回头解 B 帧。这就导致了解码顺序和显示顺序不一致。

举个例子:一个 GOP 的显示顺序是 I1 - B2 - B3 - P4。但解码顺序必须是 I1 - P4 - B2 - B3。你看,P4 的 DTS 比 B2 和 B3 都早,但它的 PTS 却在后面。

帧类型 显示顺序 解码顺序 PTS DTS
I1 1 1 0 0
B2 2 3 40 80
B3 3 4 80 120
P4 4 2 120 40

我曾经踩过一个坑:在封装 MP4 时,把 PTS 和 DTS 写反了。结果播放器解码正常,但画面顺序全乱了,人物动作像倒放一样。排查了两天才发现,是 muxer 里把两个时间戳赋值搞反了。

注意:不是所有编码格式都有 B 帧。比如某些低延迟场景会强制使用 IPPP 结构,此时 PTS 和 DTS 完全一致。但一旦遇到 B 帧,必须严格区分。

2.3 PCR(节目时钟参考)

PCR,全称 Program Clock Reference。它解决的是时钟同步的问题。

你想想看,发送端和接收端各自有一个时钟。这两个时钟不可能完全一样——晶振有误差,温度有漂移。如果接收端直接用自己本地时钟去比对 PTS,时间一长,偏差会越来越大。

PCR 就是发送端定期告诉接收端:「我的时钟现在走到这个点了,你拿它去校准你的本地时钟。」

在 TS(传输流)里,PCR 通常每 100ms 插入一次。接收端收到 PCR 后,会用它来调整本地时钟的频率和相位。这个过程叫时钟恢复。

我记得有一次做直播系统,画面每隔几分钟就会卡一下。抓了 PCR 的 jitter 一看,最大值超过了 5ms。标准要求 PCR 的抖动不能超过 ±500ns。差了整整一个数量级。后来换了更高精度的时钟源,问题才解决。

PCR 的关键指标:
  • 精度:PCR 的基值是 27MHz,精度要求极高
  • 间隔:标准要求 PCR 插入间隔不超过 100ms
  • 抖动:PCR 的到达抖动应控制在 ±500ns 以内

2.4 时间基(Time Base)

时间基,说白了就是时间戳的刻度单位。

你不能直接说「这一帧在 3.14159 秒显示」。计算机需要整数运算。所以我们需要一个固定的刻度——时间基。

常见的 Time Base 有几种:

  • 秒(1/1):精度太低,基本不用
  • 毫秒(1/1000):够用,但做音视频同步时精度不够
  • 微秒(1/1000000):常用,精度足够
  • 90kHz(1/90000):MPEG-TS 的标准时间基,PTS 和 DTS 都用这个
  • 27MHz(1/27000000):PCR 用的时间基,精度极高

为什么 MPEG-TS 选 90kHz?因为 90000 能被 24、25、30、50、60 这些常见帧率整除。你算算看:24fps 每帧 3750 个 tick,25fps 每帧 3600 个 tick,都是整数。这样就不会有舍入误差。

我在做 FFmpeg 开发时,经常要处理时间基转换。比如把 90kHz 的 PTS 转成微秒:pts_us = pts * 1000000 / 90000。这个公式我写过不下几百次。

避坑指南:我曾经在转封装时,忘记转换时间基。直接把 MP4 的毫秒时间戳写进了 TS 流。结果播放器把毫秒当成了 90kHz tick,时间放大了 90 倍。画面快进到飞起。从那以后,我每次做时间戳运算,都会先确认当前的时间基是什么。

2.5 四者关系

这四个概念不是孤立的。它们共同构成了一个完整的同步体系:

  • PCR 保证发送端和接收端的时钟一致
  • DTS 告诉解码器什么时候开始解
  • PTS 告诉渲染器什么时候显示
  • Time Base 是所有这些时间戳的刻度尺

没有 PCR,PTS 和 DTS 会随着时间漂移。没有 Time Base,时间戳就是一串没有意义的数字。没有 DTS,解码器不知道先解哪一帧。没有 PTS,画面和声音就对不上。

下面这张图,是我自己画的一个时间戳流转关系:

时间戳机制核心关系图 发送端编码器 PCR 生成器 时间基(Time Base) PTS / DTS 生成 传输流(TS/MP4) 接收端解码器 时钟恢复(PCR 校准) 渲染显示(PTS 比对) 说明: PCR 校准接收端时钟 PTS 控制显示时机 DTS 控制解码顺序 Time Base 定义刻度

从图上你能看到:编码器生成 PTS/DTS 时,依赖时间基来定义刻度。同时,PCR 也基于同样的时间基生成。接收端拿到传输流后,先用 PCR 校准本地时钟,再用校准后的时钟去比对 PTS,决定什么时候显示。DTS 则直接驱动解码器的工作节奏。

总结一下:
  • PTS 和 DTS 是帧级别的时间标签,PCR 是系统级别的时钟参考
  • 时间基是所有时间戳的计量单位,选错了时间基,所有时间戳都会错
  • 音画不同步的问题,90% 出在 PTS 和 PCR 的配合上

嗯,这四个概念讲完了。下次你遇到音画不同步,别急着怀疑网络。先看看 PTS 对不对,再查查 PCR 抖不抖。很多时候,问题就出在这些看似基础的时间戳上。