4、MP4时间模型:Time Scale、Duration、CTS、DTS、PTS与Edit List

做流媒体开发,绕不开时间。我见过太多人栽在时间模型上——画面卡顿、音画不同步、seek不准,十有八九是时间戳没搞明白。MP4的时间模型其实很精巧,但文档写得晦涩。今天咱们把它拆开揉碎了讲。

4.1 时间刻度:Time Scale

Time Scale是MP4时间模型的基石。说白了,它定义了一秒被分成了多少份。

核心概念:Time Scale = 每秒的时钟滴答数(ticks per second)。

举个例子:
Time Scale = 90000,意味着1秒 = 90000个时间单位。
Time Scale = 1000,意味着1秒 = 1000个时间单位(即毫秒精度)。

我个人习惯用90000作为视频的Time Scale。为什么?因为MPEG标准里很多帧率(24、25、30、60)都能被90000整除,计算起来干净利落。音频我一般用采样率作为Time Scale,比如44100或48000。

实战建议:不同轨道可以用不同的Time Scale。视频轨用90000,音频轨用采样率,这是业界常见做法。

4.2 时长:Duration

Duration表示一段媒体持续了多少个时间单位。注意,它本身不是秒数,需要结合Time Scale才能换算成真实时间。

换算公式很简单:

真实时长(秒) = Duration / Time Scale

举个例子:
一个视频轨的Duration = 180000,Time Scale = 90000。
那么它的真实时长 = 180000 / 90000 = 2秒。

Duration在MP4里出现在好几个地方:
- Movie Header(mvhd):整个文件的时长
- Track Header(tkhd):单个轨道的时长
- Media Header(mdhd):媒体数据的时长
- Sample Duration:单个样本的时长

注意:这些Duration的含义不同,千万别混用。我曾经排查过一个bug,发现有人把tkhd的Duration当成了mdhd的Duration来算,结果seek时间全错了。

4.3 时间戳三兄弟:CTS、DTS、PTS

这三个缩写经常把人搞晕。我换个角度讲——你想想看,一个视频帧从编码到显示,经历了什么?

DTS(Decoding Time Stamp):解码时间戳。告诉解码器:什么时候该解码这帧数据。
PTS(Presentation Time Stamp):显示时间戳。告诉渲染器:什么时候该显示这帧画面。
CTS(Composition Time Stamp):合成时间戳。在MP4里,CTS = PTS - DTS,表示这帧相对于解码时间的偏移量。

为什么要有DTS和PTS的区别?因为B帧的存在。B帧依赖前后的帧才能解码,所以解码顺序和显示顺序不一样。

举个例子:

显示顺序:I1  B2  B3  P4  B5  B6  P7
解码顺序:I1  P4  B2  B3  P7  B5  B6

你看,B2和B3虽然显示在P4之前,但解码必须在P4之后。这就是DTS和PTS不同的原因。

MP4里的存储方式:
- DTS = 样本在stts表中的累积时间
- CTS = 样本在ctts表中的偏移量
- PTS = DTS + CTS

如果所有帧都是I帧或P帧(没有B帧),那么CTS = 0,DTS = PTS。这也是为什么很多MP4文件根本没有ctts表——因为没有B帧。

4.4 Edit List:编辑列表

Edit List是个容易被忽略但极其重要的结构。它定义了一段媒体从哪开始、到哪结束、播放速度是多少。

Edit List包含一个或多个Edit Entry,每个Entry有三个字段:

字段 含义 说明
Segment Duration 片段时长 这个片段持续多少个时间单位
Media Time 媒体起始时间 从媒体数据的哪个位置开始播放
Media Rate 播放速率 1.0为正常速度,0为空闲(用于空白段)

Edit List最常见的用途有两个:

  • 跳过片头空白:有些录制设备会在开头录一段静音或黑屏,Edit List可以直接跳过,不用重新编码。
  • 实现快慢放:通过Media Rate字段,可以实现慢动作或快进效果。

避坑指南:我曾经处理过一个视频,播放器总是从第1秒开始黑屏。排查了半天,发现Edit List里Media Time设成了1秒,Segment Duration设成了总时长减1秒。播放器老老实实跳过了第1秒。解决方案?把Media Time改成0,或者干脆删掉Edit List。

4.5 时间模型全景图

下面这张图把整个时间模型的流转关系画出来了。建议你仔细看看,理解每个环节怎么衔接。

MP4时间模型全景图 Time Scale(时间刻度) Duration(时长) DTS(解码时间戳) Edit List(编辑列表) CTS(合成时间偏移) PTS(显示时间戳) 最终播放时间轴 关键公式 PTS = DTS + CTS 真实时间 = Duration / Time Scale

4.6 实战:如何正确计算时间戳

说了这么多理论,来点实际的。假设你要从一个MP4文件里读取第10秒的视频帧,该怎么做?

  1. 读取Time Scale:从mdhd box里拿到视频轨的Time Scale,假设是90000。
  2. 计算目标时间:第10秒 = 10 × 90000 = 900000个时间单位。
  3. 处理Edit List:如果有Edit List,需要把目标时间映射到媒体时间。比如Edit List跳过了前2秒,那么实际要查的是第8秒对应的样本。
  4. 查找样本:用stts表(时间到样本映射)找到900000时间单位对应哪个样本。
  5. 读取CTS:如果有ctts表,读取该样本的CTS偏移量,计算出PTS。
  6. 定位数据:用stbl表(样本到偏移映射)找到该样本在文件中的位置,读取数据。

常见坑:别忘了Edit List!我见过有人直接拿目标时间去找样本,结果因为Edit List的存在,实际读到的画面和预期差了整整几秒。处理Edit List的映射关系,是seek逻辑里最容易出错的地方。

4.7 总结

MP4的时间模型,核心就这几样东西:
- Time Scale定义精度
- Duration定义长度
- DTS/PTS/CTS定义什么时候解码、什么时候显示
- Edit List定义怎么裁剪和变速

理解了这个模型,你就能准确控制播放、seek、快进快退。下次遇到音画不同步的问题,先检查时间戳,八成能找到原因。