GOP 是「一个关键帧加后面一串预测帧」,能独立解码的最小单元。 三种帧的代价各不相同:I 最贵、P 居中、B 最省但要等后面。
上一节从外面量了 GOP 的间隔,这一节钻进去看里面由什么帧组成。 理解了帧类型,前面所有「体积」「延迟」「能不能切」的问题都能自己推。
一、先看真实的帧类型序列
用 GOP 10 编一段,数三种帧各有多少个:
帧类型 个数
I 6 P 19 B 25
前 24 帧的实际顺序:
I B B B P P I B B P B B P P I B B P P B P B B P
序列里 I 之后紧跟的是 B 与 P 混着来,不是清一色的 P。 这就说明 x264 默认开了 B 帧:它要参考前后两边,所以最省,也最依赖上下文。
B 帧数量比 P 还多,说明这一档设置下编码器的偏好是「多用 B 省字节」。
想关掉就设 bf=0,代价是同样画质下体积变大。
二、三种帧各付什么代价
按「参考谁」分类,就能推出所有性质:
帧类型 参考谁 能不能独立解
I 谁也不参考 能 P 前面的 I 或 P 不能 B 前后各若干帧 不能,且要等后面
I 帧最贵,因为它是一张完整画面;P 只存「与前一帧的差」; B 存的是「前后两帧之间的差」,所以最省。
B 帧最省,但它让解码顺序与显示顺序不一致——这就是「重排」与「解码延迟」的来源。 直播不用 B 帧,多半就是为了省掉这点延迟。
三、开放 GOP 与封闭 GOP
同样是 I 帧,能不能当切点还不一定:
类型 I 帧后的 B 参考谁 能从 I 直接切
封闭 GOP 只参考本 GOP 内部 能 开放 GOP 可能参考上一个 GOP 不能,切了会花屏
开放 GOP 压缩率更高,因为 I 帧后面那几帧还能参考前面的内容。 代价是这个 I 帧不一定能当切点——切在它前面,后面几帧就缺参考了。
切片、拼接、随机访问都要留神这一点。命令行里对应 -flags +cgop(封闭)。
做 HLS 切片时这一条尤其关键,切错位置第一帧就会花。
四、动手 Demo:数一遍三种帧,再把前 24 帧的顺序打出来
读上一节用 GOP 10 编出来的文件,解码一遍数 I、P、B 各多少个。 同时记下前 24 帧的类型顺序,看 I 后面跟的是什么。
import av
PT = {1: u"I", 2: u"P", 3: u"B"}
c = av.open(u"gop10.mp4")
cnt = {}
seq = []
for f in c.decode(video=0):
t = PT.get(int(f.pict_type), u"?")
cnt[t] = cnt.get(t, 0) + 1
if len(seq) < 24:
seq.append(t)
c.close()
print(u" ".join(u"%s %d" % (k, cnt.get(k, 0)) for k in (u"I", u"P", u"B")))
print(u"前 24 帧:" + u" ".join(seq))
实跑输出:
--------------------------------------------------------------------
5.6.4 视频 GOP 简介——一串引用怎么组织
--------------------------------------------------------------------
**① 先看真实的帧类型序列**
帧类型 个数
----------------
I 6
P 19
B 25
**前 24 帧的顺序:**
I B B B P P I B B P B B P P I B B P P B P B B P
**序列里 I 之后紧跟的是 B 与 P 混着来,不是清一色的 P。**
**这就说明 x264 默认开了 B 帧:B 帧要参考前后两边的帧,所以最省,但也最依赖上下文。**
**一个 I 加上后面跟着的一串 B 与 P,合起来叫一个 GOP(Group of Pictures)。**
**它是「能独立解码的最小单元」:只要拿到一个 I,后面这一串就能依次解出来。**
**② 三种帧各付什么代价**
帧类型 参考谁 能不能独立解
----------------------------------------------
I 谁也不参考 能
P 前面的 I 或 P 不能
B 前后各若干帧 不能,且要等后面
**B 帧最省,但它让解码顺序与显示顺序不一致——这就是「重排」与「解码延迟」的来源。**
**直播不用 B 帧,多半就是为了省掉这点延迟。**
**③ 开放 GOP 与封闭 GOP**
类型 I 帧后面的 B 帧参考谁 能不能从 I 帧直接切
-----------------------------------------------------------
封闭 GOP 只参考本 GOP 内部 能
开放 GOP 可能参考上一个 GOP 不能,切了会花屏
**开放 GOP 压缩率更高,但每个 I 帧不一定能当切点。切片、拼接、随机访问都要留神这一点。**
f.pict_type 是个枚举,int() 之后 1 是 I、2 是 P、3 是 B。
直接打枚举会得到 PictureType.I 这样的名字,也能用。
数帧类型必须解码,不能只看包——一个包里可能有多个帧,也可能没有帧。 所以这一步比数关键帧慢得多。
这段代码依赖上一节产出的 gop10.mp4,单独跑要先把它生成出来。
30 秒实验
- 命令行对照:
ffprobe -show_frames -select_streams v -show_entries frame=pict_type。 - 关掉 B 帧:加
-bf 0再数一遍,序列里就没有 B 了。 - 封闭 GOP:加
-flags +cgop,再试着从每个 I 帧切一小段。
常见误区
- 以为 I 帧就是「第一帧」。I 帧是「不参考别人的帧」,一个文件里有很多个。
- 以为 B 帧一定更好。它省字节但增加延迟,直播场景要主动关掉。
- 以为所有 I 帧都能当切点。开放 GOP 里的 I 帧后面几帧还参考前一个 GOP。
自测三问(点开对答案)
1. I、P、B 三种帧的关键区别是什么? —— 参考谁不同:I 谁也不参考,P 参考前面,B 参考前后两边。 2. 为什么 B 帧会造成解码延迟? —— 它要等后面的帧先到才能解,解码顺序与显示顺序不一致,需要重排。 3. 开放 GOP 的代价是什么? —— I 帧后面几帧还参考上一个 GOP,所以这个 I 帧不一定能当切点。- ☐ 5.6 完,下一节 5.7.1 FFmpeg 中 libx264 的选项