🎬 第五章 FFmpeg命令行实现音视频转码
📄 一句话概括

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 秒实验

  1. 命令行对照:ffprobe -show_frames -select_streams v -show_entries frame=pict_type。
  2. 关掉 B 帧:加 -bf 0 再数一遍,序列里就没有 B 了。
  3. 封闭 GOP:加 -flags +cgop,再试着从每个 I 帧切一小段。

常见误区

  1. 以为 I 帧就是「第一帧」。I 帧是「不参考别人的帧」,一个文件里有很多个。
  2. 以为 B 帧一定更好。它省字节但增加延迟,直播场景要主动关掉。
  3. 以为所有 I 帧都能当切点。开放 GOP 里的 I 帧后面几帧还参考前一个 GOP。
自测三问(点开对答案) 1. I、P、B 三种帧的关键区别是什么? —— 参考谁不同:I 谁也不参考,P 参考前面,B 参考前后两边。 2. 为什么 B 帧会造成解码延迟? —— 它要等后面的帧先到才能解,解码顺序与显示顺序不一致,需要重排。 3. 开放 GOP 的代价是什么? —— I 帧后面几帧还参考上一个 GOP,所以这个 I 帧不一定能当切点。

相关笔记