时间戳基础:PTS、DTS、time_base 的概念与关系
做流媒体的人都知道一句话:同步是灵魂,时间戳是骨架。没有时间戳,你拿到的就是一坨乱序的字节流,谁也分不清哪帧该什么时候显示。
我记得刚入行那会儿,接手一个多路摄像头拼接的项目。四路 1080p 视频流,硬生生被我拼成了“鬼畜”画面——每路各跑各的节奏,有的快有的慢。后来排查了一整天,才发现是时间戳单位没统一。嗯,从那以后,我再也不敢小看 time_base 了。
2.1 什么是 PTS 和 DTS?
先看两个最核心的概念:
- PTS(Presentation Time Stamp):显示时间戳。告诉解码器:这帧画面应该在什么时候显示给用户。
- DTS(Decoding Time Stamp):解码时间戳。告诉解码器:这帧数据应该在什么时候开始解码。
你可能会问:为什么需要两个时间戳?直接一个 PTS 不就行了?
原因在于视频编码里的 B 帧(双向预测帧)。B 帧依赖前后的 I 帧和 P 帧才能解码,所以解码顺序和显示顺序往往不一致。举个例子:
显示顺序: I₁ → B₂ → P₃ → B₄ → P₅
解码顺序: I₁ → P₃ → B₂ → P₅ → B₄
你看,B₂ 虽然显示在 P₃ 之前,但解码时必须等 P₃ 先解完。这就是 DTS 存在的意义。
说白了,DTS 管“什么时候解”,PTS 管“什么时候放”。对于没有 B 帧的编码(比如大部分音频编码),PTS 和 DTS 是相等的。
2.2 time_base:时间戳的“刻度尺”
时间戳本身是个整数,比如 3600、7200。但 3600 代表多少秒?这取决于你用什么单位去量。
time_base 就是那个单位。它是一个有理数,通常用分数表示,比如 1/90000、1/1000。
在 FFmpeg 里,每个流都有自己的 time_base。常见的几种:
| 媒体类型 | 常见 time_base | 精度 | 适用场景 |
|---|---|---|---|
| 视频(MPEG-TS) | 1/90000 | 高 | 广播电视、直播流 |
| 视频(MP4) | 1/1000 或 1/90000 | 中/高 | 文件封装、点播 |
| 音频(AAC) | 1/44100 或 1/48000 | 极高 | 采样率对齐 |
| 音频(通用) | 1/1000 | 中 | 简单场景 |
我个人习惯把 time_base 想象成一把尺子。1/90000 意味着尺子上每格是 1/90000 秒,约 11 微秒。对于 25fps 的视频,每帧间隔是 3600 个刻度(因为 90000/25 = 3600)。
小技巧:在 FFmpeg 里用 av_rescale_q() 或 av_rescale_q_rnd() 做 time_base 转换。千万别自己手算,容易翻车。我曾经在项目里手写除法,结果精度丢失导致音画不同步,排查了整整两天。
2.3 PTS、DTS、time_base 的关系
这三者的关系可以用一个公式概括:
实际时间(秒) = 时间戳 × time_base
举个例子:
- PTS = 3600,time_base = 1/90000 → 实际时间 = 3600 × (1/90000) = 0.04 秒
- PTS = 7200,time_base = 1/90000 → 实际时间 = 7200 × (1/90000) = 0.08 秒
你看,只要 time_base 统一,不同流的时间戳就能换算到同一个时间轴上。这就是同步的基石。
但在实际项目中,不同来源的流往往 time_base 不同。比如摄像头输出的是 1/90000,音频采集卡给的是 1/48000。这时候必须做一次 时间基转换,否则直接比较 PTS 就是鸡同鸭讲。
避坑指南:我曾经遇到过一个坑——某个 RTSP 流的 time_base 是 1/90000,但它的 PTS 值却以毫秒为单位递增。这明显是封装不规范。遇到这种情况,别硬算,先检查一下 av_stream->time_base 和实际 PTS 的步长是否匹配。
2.4 为什么时间戳是同步的基石?
多路流同步,说白了就是让各路媒体在同一个时间线上对齐。没有时间戳,你根本不知道:
- 这帧画面应该和哪段音频配对
- 当前播放进度到了哪里
- 是否需要丢帧或插帧来补偿延迟
我参与过一个远程医疗项目,需要同步传输医生的视频、患者的生命体征数据和实时语音。三路流,三种不同的采样率,三种 time_base。最后靠一套统一的时间戳映射机制,才把画面、波形和声音对齐到毫秒级。
说白了,时间戳就是各路流的“共同语言”。没有它,多路流就是一群各说各话的哑巴。
2.5 核心知识图谱
下面这张图总结了 PTS、DTS、time_base 的关系,以及它们在同步中的位置:
2.6 实战中的常见问题
光讲理论不够,我列几个实际项目中踩过的坑:
- 时间戳回绕(Wrap Around):PTS 是 32 位或 64 位整数,总有上限。达到最大值后会归零。如果不做回绕检测,同步逻辑会瞬间崩溃。
- 非单调递增的 PTS:有些编码器输出的 PTS 会偶尔倒退。FFmpeg 的
ffplay遇到这种情况会直接报错,但生产环境里你得自己处理。 - time_base 精度不足:比如用 1/1000 做视频 time_base,对于 29.97fps 的 NTSC 制式,每帧间隔是 33.3667 毫秒,但 1/1000 只能精确到 33 或 34 毫秒,累积误差会越来越大。
我的建议:做多路流同步时,统一用一个高精度的 time_base 作为“主时钟”。我个人偏爱 1/90000,兼容性好,精度也够。音频流通过重采样或插值对齐到这个主时钟上。