GOP 决定关键帧隔多远:短了好拖动、大了省字节。 但 x264 会在画面突变处自己补关键帧,实际间隔可能比设置更密。
码率决定「每秒花多少字节」,GOP 决定这些字节怎么分配。 它也是「能不能快速拖动」这个体验指标的直接来源。
一、三档 GOP 各编一遍
同一段素材,GOP 设成 10、25、50:
GOP 设置 关键帧数 首个间隔 字节
10 6 0.24 秒 5619 25 3 1.00 秒 4896 50 2 1.76 秒 4719
按设置算,GOP 10 在 25 fps 下应当每 0.40 秒一个关键帧,实测第一个间隔却是 0.24 秒。 因为 x264 除了按间隔插关键帧,还会在画面突变处自己补一个。
这段素材每帧亮度都在跳,所以它补得比设置还密。真实素材上这个偏差会小得多。 这也提醒一件事:设了 GOP 不等于关键帧就按那个间隔出现,只能说「不超过」。
二、字节为什么没怎么变
三行字节相差不到一成,看起来 GOP 对体积没影响:
对比 字节差
GOP 10 与 GOP 50 多 19% 关键帧数差 多 4 个
关键帧多了确实更费,但这段素材太好压,每个关键帧也只有一两千字节。 实拍素材上差距会明显得多:一个关键帧可能就是几十 KB。
所以「GOP 越短体积越大」这个规律方向是对的,但幅度要看素材。 在简单画面上试出来的比例,不能直接套到真实片子上。
三、两头都要付钱
GOP 短与长各有利弊,没有全局最优:
GOP 好处 代价
短 拖动快、容易做切片 体积大 长 体积小、压缩率高 拖动慢、切片难
直播切片是个典型场景:切点必须落在关键帧上,GOP 越长,切片越难对齐。 所以直播常用「2 秒一个关键帧」,代价是比长 GOP 多花一点带宽。
点播也吃这一条:用户拖进度条时,播放器要找到最近的关键帧再往后解。 GOP 50 意味着最坏要等两秒的画面解完,体验上就是「拖了没反应」。
四、动手 Demo:三档 GOP 各编一遍,数关键帧与间隔
用 GOP 10、25、50 各编一遍,数出关键帧个数与第一个间隔。 关键帧靠解复用出来的包标记来数,不需要真的解码。
import av, os
from fractions import Fraction
def enc_gop(dst, g):
c1 = av.open(dst, u"w")
s1 = c1.add_stream(u"libx264", rate=25)
s1.width, s1.height, s1.pix_fmt = 160, 90, u"yuv420p"
s1.options = {u"crf": u"26", u"g": u"%d" % g}
c0 = av.open(u"s5.mp4")
idx = 0
for f in c0.decode(video=0):
nf = av.VideoFrame.from_ndarray(f.to_ndarray(), format=u"yuv420p")
nf.pts = idx
nf.time_base = Fraction(1, 25)
idx += 1
for p in s1.encode(nf):
c1.mux(p)
for p in s1.encode(None):
c1.mux(p)
c1.close()
c0.close()
c = av.open(dst)
tb = float(c.streams.video[0].time_base)
keys = []
for pk in c.demux(video=0):
if pk.size and pk.is_keyframe:
keys.append(float(pk.pts) * tb)
c.close()
gap = keys[1] - keys[0] if len(keys) > 1 else 0.0
return os.path.getsize(dst), len(keys), gap
for g in (10, 25, 50):
b, k, gap = enc_gop(u"gop%d.mp4" % g, g)
print(u"GOP %-2d 关键帧 %d 首个间隔 %.2f 秒 %d 字节" % (g, k, gap, b))
实跑输出:
--------------------------------------------------------------------
5.6.3 控制视频的 GOP——关键帧隔多远
--------------------------------------------------------------------
**① 三档 GOP 各编一遍**
GOP 设置 关键帧数 首个间隔 字节
--------------------------------------------
10 6 0.24 秒 5619
25 3 1.00 秒 4896
50 2 1.76 秒 4719
**按设置算,GOP 10 在 25 fps 下应当每 0.40 秒一个关键帧,实测第一个间隔却是 0.24 秒。**
**因为 x264 除了按间隔插关键帧,还会在画面突变处自己补一个——这段素材每帧亮度都在跳,**
**所以它补得比设置还密。真实素材上这个偏差会小得多。**
**第三列才是关键帧的真实间隔:想精确跳到某一秒,最坏要等这么久。**
**② 字节没怎么变,为什么**
**三行字节相差不到一成。关键帧多了确实更费,但这段素材太好压,**
**每个关键帧也只有一两千字节,多几个看不出来。实拍素材上差距会明显得多。**
**③ GOP 的两头都要付钱**
GOP 好处 代价
----------------------------------------------
短 拖动快、容易做切片 体积大
长 体积小、压缩率高 拖动慢、切片难
**直播切片是个典型场景:切点必须落在关键帧上,GOP 越长,切片越难对齐,**
**所以直播常用「2 秒一个关键帧」,代价是比长 GOP 多花一点带宽。**
pk.is_keyframe 读的是包标记,不用解码,所以很快。
要判断「这个文件能不能快速拖动」,数这个就够。
pk.pts 是时间基单位,要乘 time_base 才是秒。
忘了乘会得到一串整数,看起来像帧号而不是时间。
g 是 x264 的选项名,等价于命令行的 -g。
它只是「最大间隔」,不是「固定间隔」——场景切换会让它更密。
30 秒实验
- 命令行对照:
ffmpeg -i 源.mp4 -g 25 -keyint_min 25 出.mp4,keyint_min才管「最少隔多少」。 - 关掉场景切换补帧:加
-sc_threshold 0,关键帧就只按-g来。 - 看关键帧位置:
ffprobe -show_frames -select_streams v找key_frame=1。
常见误区
- 以为
-g 25就是每 25 帧一个关键帧。场景切换会让它更密,只能当上限。 - 以为 GOP 越短体积越大一定成立。方向对,但简单素材上幅度很小。
- 忘了
pts要乘time_base。算出来的间隔会是帧号,不是秒。
自测三问(点开对答案)
1. 为什么设了 GOP 10,实测首个间隔只有 0.24 秒? —— x264 会额外在场景切换处补关键帧,所以实际间隔可能比设置更密。 2. GOP 短有什么坏处? —— 关键帧多了体积大。关键帧是完整画面,比预测帧贵得多。 3. 要固定关键帧间隔不能多于 25 帧,该给什么参数? —— `g=25` 管最大间隔;想更严格控制还要配 `keyint_min` 与 `sc_threshold`。- ☐ (本节完,下一节 5.6.4 视频 GOP 简介)