📄 一句话概括
封装不只是把两路流放进一个文件,还要用时间戳把它们对齐——音画不同步,本质就是两条时间戳对不上。
📝 本节和前面几节的关系
5.2.1 讲了封装是什么,这一节讲它封装的两路(或多路)流怎么配合。
一、封装里装了什么
一个典型的音视频文件里装着两条独立的流: 视频流:一串视频帧,每帧带自己的时间戳。 音频流:一串音频帧,每帧也带自己的时间戳。 再外加一份索引表与元信息(时长、码率、宽高)。
二、PTS 与 DTS
PTS(显示时间戳):这一帧该在什么时候被显示出来。 DTS(解码时间戳):这一帧该在什么时候被解码。 两者不同,是因为 B 帧要参考后面的帧——所以必须【先解码、后显示】。 所以排查音画不同步时,要看的是这两个时间戳的顺序对不对。
三、为什么会不同步
常见三种原因: 时间戳本身写错——编码或转码时对不上; 丢帧但没有跳时间戳——音频少了一段,视频照走; 播放器缓冲策略不同——音频缓冲与视频缓冲不等长。 三种里前两种是【文件的问题】,第三种是【播放器的问题】。
四、封装时的重要决定
封装时要决定几件事:流的顺序、索引放在哪、时间戳的基准是多少。 索引放在文件头,就能边下边播;放在文件尾,就必须下完才能播。 这就是 MP4 文件常见的那个坑——一个【快启动】处理就能解决。
五、动手 Demo:两条流是怎么对上的
封装时,视频流和音频流各自带着时间戳,靠它对齐。
rows = [("视频流", "一串视频帧,每帧带自己的时间戳"),
("音频流", "一串音频帧,每帧带自己的时间戳"),
("PTS", "显示时间戳:这一帧该在什么时候出现"),
("DTS", "解码时间戳:这一帧该在什么时候解码"),
("索引表", "告诉播放器每一帧在文件的哪个位置")]
for a, b in rows:
print("%-8s %s" % (a, b))
实跑输出:
--------------------------------------------------------------------
两条流是怎么对上的
--------------------------------------------------------------------
封装时,视频流和音频流各自带着时间戳,靠它对齐。
元素 作用
----------------------------------------------
视频流 一串视频帧,每帧带自己的时间戳
音频流 一串音频帧,每帧带自己的时间戳
PTS 显示时间戳:这一帧该在什么时候出现
DTS 解码时间戳:这一帧该在什么时候解码
索引表 告诉播放器每一帧在文件的哪个位置
**音画不同步,本质就是这两条时间戳对不上。**
所以排查不同步,先看时间戳,而不是先怀疑解码器。
音画不同步,本质就是这两条时间戳对不上。 所以排查不同步,先看时间戳,而不是先怀疑解码器。
30 秒实验
- 用 ffprobe 加
-show_packets看一个文件的包列表,找出 PTS 与 DTS。 - 看有没有哪一帧两个值不相等。
常见误区
- 把 PTS 和 DTS 当成同一个东西。有 B 帧时它们就不一样。
- 遇到不同步先去改播放器。先确认是文件的问题还是播放器的问题。
自测三问(点开对答案)
1. PTS 和 DTS 分别是什么? —— PTS 是显示时间戳,DTS 是解码时间戳。 2. 为什么有 B 帧时 PTS 与 DTS 会不同? —— B 帧要参考后面的帧,必须先解码、后显示。 3. MP4 的索引放在文件尾会有什么问题? —— 必须把文件下完才能开始播放。- ☐ 下一节(5.2.3 封装格式)
相关笔记
- 数据封装和解封装 — 封装这一步在做什么
- 视频编解码流程 — B 帧为什么存在
- 功能列表 — 看 DTS/PTS 的那类工具
- 🎬 FFmpeg入门详解 · 索引