GOP 是两个 I 帧之间的距离,它同时决定三件事:文件大小、seek 快慢、出错恢复速度。 三件事互相打架,所以没有「最优 GOP」,只有「为谁让步」。
上一节拧的是「编多清楚」,这一节拧的是「多久重来一次」。 I/P/B 的参考关系在 26 课,这里只讲间隔的影响。
一、GOP 是什么
GOP = Group of Pictures,从某个 I 帧开始,到下一个 I 帧之前为止的一组帧。 GOP 长度 = 10 就是「每 10 帧一个 I 帧」,30 帧的片子就有 3 个 I 帧。
为什么需要 I 帧:P/B 帧都是差值,必须从某个「完整画面」开始累加。这个起点就是 I 帧。 所以 I 帧是「随机访问点」——想跳到第 25 帧,最近的 I 帧在第 20 帧,那就从 20 开始解 5 帧。
二、实测:三种 GOP 的代价
| GOP | 文件大小 | I 帧数 | seek 平均要解几帧 |
|---|---|---|---|
| 10 | 20.9 KB | 3 | 5.0 帧 |
| 30 | 10.4 KB | 1 | —(只 1 个 I) |
| 120 | 10.4 KB | 1 | —(只 1 个 I) |
seek 平均要解几帧 = GOP ÷ 2:目标点平均落在 GOP 中间,得从这个 GOP 开头的 I 帧解过来。 GOP 120 理论上平均要解 20 帧,按 30 fps 算是 0.66 秒——这就是「拖进度条要等」的来源。
表里 GOP 30 和 120 那两行算不出来,是样本不够:片子只有 30 帧,整片就一个 I 帧, 没有「两个 I 帧之间的距离」。 这个横杠不是 0,是「测不了」。报数时这一层必须写清, 否则读者会得出「GOP 越长 seek 越快」——结论正好反了。
GOP 10 的文件大了一倍(20.9 对 10.4 KB)。 因为 I 帧是完整画面,一个 I 帧的字节数顶十几个 P 帧。
三、三件事怎么取舍
| 想要 | GOP 该 | 代价 | |---|---|---|---| | 文件小 | 长(120 以上) | seek 慢、出错恢复慢 | | seek 快、低延迟 | 短(10~30) | 文件大 | | 监控实时预览 | 1~2 秒(30~60) | 折中 |
监控为什么不选最短的:1 秒一个 I 帧已经能让「点开就出画面」,再短文件要涨两三成, 而硬盘是监控最贵的成本项之一。
「出错恢复」这一栏要说清:网络丢包时,P/B 帧丢了会让后续帧全花, 一直花到下一个 I 帧才恢复。GOP 越长,花屏持续越久。所以丢包严重的链路要缩短 GOP。
四、命令行怎么写
| 参数 | 作用 | 备注 |
|---|---|---|---|
| -g 30 | 最大 GOP | 最常用,别称 keyint |
| -keyint_min 30 | 最小 GOP | 防止场景切换太频繁插 I 帧 |
| -sc_threshold 0 | 关掉场景切换检测 | 直播常用,让 GOP 严格等长 |
| -force_key_frames 2 | 第 2 秒强制 I 帧 | 切片录制用 |
-sc_threshold 是个容易忽略的旋钮:默认 x264 会在「画面突变」时插一个 I 帧,
所以实际 GOP 往往比设的短。
要严格等长(比如按秒切片)就必须关掉它,否则切出来的片子长度不齐。
五、动手 Demo:GOP 是「多久能给一次完整画面」
Demo 只改 gop_size(10/30/120),其余参数全不动,量文件大小和 I 帧数。
seek 代价是用公式算的,不是测的——测它需要一段更长的片子,这里如实标注。
for gop in (10, 30, 120):
c = av.open("gop%d.h264" % gop, "w", format="h264")
s = c.add_stream("libx264", rate=30)
s.width, s.height, s.pix_fmt = 320, 240, "yuv420p"
s.gop_size = gop
s.options = {"crf": "23", "sc_threshold": "0"}
...
n_i = sum(1 for fr in decode(path) if int(fr.pict_type) == 1)
实跑输出:
--------------------------------------------------------------------
GOP 是「多久能给一次完整画面」
--------------------------------------------------------------------
**GOP 就是两个 I 帧之间的距离。**
**它同时决定了三件事:文件大小、seek 快慢、出错恢复速度。**
同一份源,只改 GOP(每秒一个关键帧 vs 两秒一个 vs 全片一个):
GOP 文件大小 I 帧数 seek 平均要解几帧
-----------------------------------------------------
10 20.9 KB 3 5.0 帧
30 10.4 KB 1 —(只 1 个 I)
120 10.4 KB 1 —(只 1 个 I)
**seek 平均要解几帧 = GOP ÷ 2**:因为目标点平均落在 GOP 中间,得从这个 GOP 开头的 I 帧解过来。
**GOP 120 时理论上平均要解 20 帧**——按 30 fps 算就是 0.66 秒,**这就是「拖进度条要等」的原因。**
**表里 GOP 30 和 120 那两行算不出来**:片子只有 30 帧,整片就一个 I 帧,
没有「两帧之间的距离」可算。
**这个横杠不是 0,是「样本不够」**——报数时这种区分必须写清,
否则读者会以为「GOP 越长 seek 越快」,结论正好反了。
**三件事互相打架,必须取舍:**
想要 GOP 该 代价
---------------------------------------------------------------
文件小 长(120 以上) seek 慢、出错恢复慢
seek 快、低延迟 短(10~30) 文件大
监控实时预览 1~2 秒(30~60) 折中
**监控为什么不选最短**:1 秒一个 I 帧已经能让「点开就出画面」,再短文件会涨两三成。
**而如果开了智能编码(只在有运动时编细节)**,GOP 还要再放长,否则每个 I 帧都要完整编一次,
省不下来。
sc_threshold 必须设 0,否则「GOP 30」可能编出 3 个 I 帧,表就对不上了。
这是复现这类实验的第一个坑。
「样本不够写横杠」这一条要养成习惯:宁可留空,不要填 0。填 0 会让读者得出反向结论。
30 秒实验
- 把片子加长到 300 帧(10 秒),重跑,看 GOP 30 和 120 的 seek 代价能不能算出来。
- 把
sc_threshold去掉(用默认),看 GOP 30 那行的 I 帧数会不会变多。 - 用 GOP 1(全 I 帧)编一遍,看文件大多少——这就是「无损编辑」格式的代价。
常见误区
- 以为 GOP 只影响文件大小。 它还决定 seek 粒度和花屏恢复时间,三件事一起变。
- 以为设了
-g 30就一定是 30。 默认开着场景切换检测,实际可能更短, 要等长得关sc_threshold。 - 以为测不出来就填 0。 样本不够要写「测不了」,填 0 会把结论带反。
自测三问(点开对答案)
1. 为什么 I 帧越多文件越大? —— I 帧是完整画面,一个 I 帧的字节数顶十几个 P 帧。 GOP 缩短就是把省字节的 P/B 换成了费字节的 I。 2. 网络丢包严重时,GOP 该长还是短? —— 短。丢一包会让后续帧一直花到下一个 I 帧,**GOP 越短,花屏持续时间越短**。 3. 按秒切片录像为什么必须关场景切换检测? —— 否则画面一突变就插 I 帧,**切出来的每段长度不齐**,后续按时间索引就对不上。下一节换到 libx264 的选项本身:preset 到底是什么。
- ☐ (本节完,下一节 04.02.01 FFmpeg中libx264的选项)