时间戳机制:PTS、DTS、PCR 与时间基
做流媒体的人,早晚得跟时间戳打交道。我入行那会儿,第一次调试音画不同步,抓了半天的包,最后发现是 PTS 和 DTS 搞混了。嗯,从那以后,我对这四个概念再也不敢马虎。
说白了,时间戳就是给每一帧数据贴上的「时间标签」。没有它,解码器不知道什么时候该干活,渲染器不知道什么时候该显示。整个系统就乱套了。
2.1 PTS(显示时间戳)
PTS,全称 Presentation Time Stamp。它标记的是这一帧数据什么时候应该被显示出来。
举个例子。你播放一个 24fps 的视频,每帧间隔大约是 41.67ms。第一帧的 PTS 是 0,第二帧就是 41.67ms,第三帧 83.33ms……解码器解完数据后,渲染器会等,等到系统时钟走到 PTS 那个时刻,才把画面推上去。
我在项目中遇到过一个问题:某些播放器在 seek 之后,画面会卡在旧帧上好几秒。查到最后,是 PTS 在 seek 后没有重置,导致渲染器一直在等一个永远不会到来的时间点。修复方式很简单——seek 后清空 PTS 缓存,重新从关键帧开始计算。
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 里把两个时间戳赋值搞反了。
2.3 PCR(节目时钟参考)
PCR,全称 Program Clock Reference。它解决的是时钟同步的问题。
你想想看,发送端和接收端各自有一个时钟。这两个时钟不可能完全一样——晶振有误差,温度有漂移。如果接收端直接用自己本地时钟去比对 PTS,时间一长,偏差会越来越大。
PCR 就是发送端定期告诉接收端:「我的时钟现在走到这个点了,你拿它去校准你的本地时钟。」
在 TS(传输流)里,PCR 通常每 100ms 插入一次。接收端收到 PCR 后,会用它来调整本地时钟的频率和相位。这个过程叫时钟恢复。
我记得有一次做直播系统,画面每隔几分钟就会卡一下。抓了 PCR 的 jitter 一看,最大值超过了 5ms。标准要求 PCR 的抖动不能超过 ±500ns。差了整整一个数量级。后来换了更高精度的时钟源,问题才解决。
- 精度: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。这个公式我写过不下几百次。
2.5 四者关系
这四个概念不是孤立的。它们共同构成了一个完整的同步体系:
- PCR 保证发送端和接收端的时钟一致
- DTS 告诉解码器什么时候开始解
- PTS 告诉渲染器什么时候显示
- Time Base 是所有这些时间戳的刻度尺
没有 PCR,PTS 和 DTS 会随着时间漂移。没有 Time Base,时间戳就是一串没有意义的数字。没有 DTS,解码器不知道先解哪一帧。没有 PTS,画面和声音就对不上。
下面这张图,是我自己画的一个时间戳流转关系:
从图上你能看到:编码器生成 PTS/DTS 时,依赖时间基来定义刻度。同时,PCR 也基于同样的时间基生成。接收端拿到传输流后,先用 PCR 校准本地时钟,再用校准后的时钟去比对 PTS,决定什么时候显示。DTS 则直接驱动解码器的工作节奏。
- PTS 和 DTS 是帧级别的时间标签,PCR 是系统级别的时钟参考
- 时间基是所有时间戳的计量单位,选错了时间基,所有时间戳都会错
- 音画不同步的问题,90% 出在 PTS 和 PCR 的配合上
嗯,这四个概念讲完了。下次你遇到音画不同步,别急着怀疑网络。先看看 PTS 对不对,再查查 PCR 抖不抖。很多时候,问题就出在这些看似基础的时间戳上。