同一段视频里有三个顺序:显示顺序、编码顺序、解码顺序——后两个相同,且都和显示顺序不同,原因就是 B 帧要参考后面的帧。
8.2 讲了流程,从这一节开始钻进去——先看帧与帧之间那套最基础的约定。
一、三个顺序
这是一段 I B B P 结构的四帧视频,三个顺序的对照: 显示顺序:1、2、3、4——观众看到的先后; 编码顺序:1、4、2、3——P 帧被提前; 解码顺序:1、4、2、3——与编码顺序相同。 编码顺序与解码顺序必然一致:码流怎么写,就怎么解。
二、为什么 P 帧要被提前
全部原因只有一句话:B 帧要参考它后面的那个 P 帧。 B 帧是双向预测帧,既参考前面、也参考后面(第 6.4.5 节); 后面那个参考帧必须在 B 帧之前进入解码器,否则 B 帧无从解起; 所以编码时就先把 P 编出来、写进码流,位置排在 B 帧前面。 这不是代码顺序问题,是物理约束——谁先用,谁就得先到。
三、重排序缓冲
于是解码器不得不准备一块缓冲: 先解出来的 P 帧,显示时间戳比已解出的帧还晚,暂时不能显示; 把它存进缓冲,等 B 帧解完、按显示时间戳排序,再依次吐出去。 缓冲的深度取决于连续 B 帧的数量与参考距离。 这就是播放器里【解码缓冲】这块设置的来由,也是 DTS 与 PTS 要分开传的直接原因(第 6.5.3 节)。
四、三帧的编码代价
把三种帧放在一起比,代价与收益很清楚: I 帧——不参考别人、码流最大,但提供了随机访问点; P 帧——只参考前面、码流中等,被参考所以有传递风险; B 帧——前后都参考、码流最小,但不被别人参考,等于【只进不出】。 所以工程上的常见配置是多放 B 帧——它们几乎白赚压缩比,只要解码端缓冲与算力跟得上。
五、动手 Demo:显示顺序、编码顺序、解码顺序
以一段 4 帧的小 GOP 为例(I B B P),三个【顺序】并不一样。
frames = [("1", "I", 1, 1), ("2", "B", 2, 3),
("3", "B", 3, 4), ("4", "P", 4, 2)]
for fid, kind, disp, enc in frames:
print("帧 %-3s %s 显示序 %d 编码序 %d"
% (fid, kind, disp, enc))
实跑输出:
--------------------------------------------------------------------
显示顺序、编码顺序、解码顺序
--------------------------------------------------------------------
以一段 4 帧的小 GOP 为例(I B B P),三个【顺序】并不一样。
帧 类型 显示序 编码序 解码序
---------------------------------------------
1 I 1 1 1
2 B 2 3 3
3 B 3 4 4
4 P 4 2 2
三个关键结论:
一是 **P 帧要抢在 B 帧之前解码**——因为 B 帧一边要参考它;
二是 **编码顺序就是解码顺序**,两者相同,都和显示顺序不同;
三是 所以解码器必须有一块**重排序缓冲**,把解出来的帧先存着,按显示序吐出去。
**这就是第 6.5.3 节里 DTS 与 PTS 要分开传的全部原因。**
P 帧要抢在 B 帧之前解码——因为 B 帧一边要参考它; 编码顺序就是解码顺序,两者相同,都和显示顺序不同。 所以解码器必须有一块重排序缓冲(第 6.5.3 节的 DTS 与 PTS)。
30 秒实验
- 用 MediaInfo 打开一个含 B 帧的视频,看视频流的【帧率模式】是不是可变帧率。
- 再用码流分析工具随便点几个帧,找找有没有 B 帧、它们的 DTS 与 PTS 差多少。
常见误区
- 以为编码顺序与显示顺序一样。B 帧的存在让两者必然不同。
- 以为解码顺序可以和编码顺序不一样。码流怎么写就怎么解,两者恒等。
- 以为 B 帧是【多余的】。B 帧码流最小,是压缩比的重要来源。
自测三问(点开对答案)
1. 三个顺序里哪两个是相同的? —— 编码顺序与解码顺序相同,都与显示顺序不同。 2. 为什么 P 帧要排在 B 帧前面? —— 因为 B 帧要参考后面的 P 帧,被参考的必须先到位。 3. 重排序缓冲的深度由什么决定? —— 连续 B 帧的数量与参考帧的距离。- ☐ 下一节(8.3.2 I/P/B帧的特点)
相关笔记
- I/P/B帧的特点 — 三类帧的性质对照
- I/P/B帧的基本流程 — 解码器逐帧在干什么
- DTS和PTS详细讲解 — 两个时间戳为什么要分开
- 双向预测 — B 帧的原理
- 🎬 FFmpeg入门详解 · 索引