用 ffmpeg 转录屏时,关键帧间隔(-g)这一项很容易被放过去,x264 默认 250 帧看着也挺合理。这次把同一段录屏按 1 秒、2 秒、5 秒、10 秒、30 秒和不限六档重新编码,别的参数一个不动。按这次的数据,间隔从 2 秒拉到 10 秒,录屏体积小了 75.6%。同样的操作放到摄像头拍的视频上只小了 5.0%。文字清晰度在几档之间几乎没差。真正变的是跳转时要多解码多少帧。
样本是自制的模拟网课录屏
录屏样本是一段自制的 4 分钟模拟网课录屏,里面有课件翻页、鼠标、打字和讲义滚动。规格是 1920×1080、30 fps。它按 12 Mbps 恒定码率生成,原文件 360.0 MB,没有音轨。它是拿课件页面截图合成的,不是哪个录屏软件录出来的。整段约 78% 的时间画面完全不动,也没有噪点。它比真实录屏干净也更好压,读下面的数字时要记着这一点。对照组是维基共享资源上两段 CC 协议的摄像头视频:一段 30 秒 1080p25,另一段 7.87 秒 1080p30。编码都在一台 Apple M4 的 Mac 上用 ffmpeg 加 x264 跑。参数是 preset medium、CRF 23 并去掉音频,每档只跑一次。动命令行之前同一段录屏先在浏览器里过了一遍。我用的是图映 ImgIng imging.cn/ 的视频压缩,智能推荐档 H.264 出来 20.2 MB。它的小字清晰度和后面 ffmpeg 1080p 那几档同档。下文的 MB 一律按 1 MB = 1,000,000 字节算。
录屏体积随间隔拉长一路往下掉
五条命令只有 -g 那一处不同。「不限」用的是 x264 的 keyint=infinite,整段只在开头留一个关键帧。行尾注释是这段样本实际输出的字节数:
bash
ffmpeg -i input.mp4 -an -c:v libx264 -preset medium -crf 23 -g 30 gop1s.mp4 # 34,550,226 字节
ffmpeg -i input.mp4 -an -c:v libx264 -preset medium -crf 23 -g 60 gop2s.mp4 # 18,065,145
ffmpeg -i input.mp4 -an -c:v libx264 -preset medium -crf 23 -g 150 gop5s.mp4 # 7,835,468
ffmpeg -i input.mp4 -an -c:v libx264 -preset medium -crf 23 -g 300 gop10s.mp4 # 4,402,121
ffmpeg -i input.mp4 -an -c:v libx264 -preset medium -crf 23 -x264-params keyint=infinite gopinf.mp4 # 1,299,050
30 秒那档是 -g 900,出来 2,167,967 字节。六档加上 x264 默认值放进一张表。摄像头那列用的是 30 秒那段对照视频:
| 关键帧间隔 | 录屏字节 | 录屏相对 1 秒 | 录屏平均需解码帧数 | 摄像头相对 1 秒 |
|---|---|---|---|---|
| 1 秒 | 34,550,226 | 100% | 15.5 | 100% |
| 2 秒 | 18,065,145 | 52% | 30.5 | 94% |
| 5 秒 | 7,835,468 | 23% | 75.5 | 90% |
| 10 秒 | 4,402,121 | 13% | 150.5 | 89% |
| 默认 250 帧(8.3 秒) | 5,222,157 | 15% | 124.8 | 与 10 秒相同 |
| 30 秒 | 2,167,967 | 6.3% | 450.5 | 未测(片长只有 30 秒) |
| 不限 | 1,299,050 | 3.8% | 3,600.5 | 89% |

这张图看两条线和灰柱。蓝线是录屏体积,它从 1 秒档的 100% 一路掉到不限档的 4%。橙线是摄像头视频。2 秒以后它就趴在 89%~94% 之间。灰柱是录屏跳到任意位置平均要解码的帧数,看右侧的对数轴,从 16 涨到 3600。摄像头那条线的素材取自维基共享资源(CC BY-SA),重新编码过。
从 2 秒到 10 秒录屏从 18,065,145 字节降到 4,402,121 字节,一共小了 75.6%。摄像头那段从 19,159,323 字节降到 18,202,432 字节只小了 5.0%。另一段 7.87 秒的摄像头视频相对 1 秒档在 2 秒档是 85%,在 10 秒档是 72%。它太短了,10 秒和不限两档对它来说都只剩一个关键帧。同一个参数在两类视频上的反应差了十几倍。
摄像头视频的字节压在P帧上
差别出在字节落在哪类帧上。默认参数下,录屏视频流有约八成压在那二十几个关键帧上,另外七千多帧 P、B 帧合起来才占两成;摄像头视频正好倒过来,30 秒那段有 59.4% 的字节在 P 帧上,7.87 秒那段是 77.7%。
静止帧为什么能小成这样?下面是推断,没有单独测。P、B 帧记的是和参考帧的差。画面不动的宏块大概率走了跳过模式。几个比特就能说清「照抄参考帧」。摄像头就算机位不动也有传感器噪点和细小晃动。每个宏块都有残差要记。我是中文系转行的,编码器内部怎么判跳过讲不深,但这个推断和上面的数对得上。录屏里每多一个关键帧就是整屏重画一遍,平均 140,557 字节实打实加上去。1 秒档在 240 秒里要放 240 个关键帧。按每个约 140 KB 粗算就是三十多 MB。这和表里 1 秒档的总量在一个量级。
文字清晰度不随间隔变化
体积少了这么多,字会不会糊?按这次的数据并没有。拿第 60 秒同一块英文小字和无损原始画面比 SSIM,1 秒档是 0.9686,10 秒档 0.9689,不限档 0.9677。整段和原文件比的话是 1 秒档 0.99932、默认档 0.99934、不限档 0.99899。CRF 23 管的是每一帧画多细,关键帧间隔管的是隔多久整屏重画一次,两件事在这段样本上基本是分开的。不限档那个 0.99899 低了一点点,原因没查。
代价是跳转要多解码很多帧
关键帧一稀拖进度条就得从前一个关键帧一路解到目标位置。表里第四列就是这个代价的代理指标。算法是对每一帧数从前一个关键帧解到它要多少帧再取全片平均。2 秒档是 30.5 帧,10 秒档涨到 150.5 帧,30 秒档到了 450.5 帧。不限档 3,600.5 帧。这相当于平均要从半段之前开始解。单线程精确跳转的中位耗时只能看相对大小:10 秒档是 163.4 ms,不限档是 2,623.5 ms。两档差了十几倍。
要拖着看的课程录屏,可以停在 10 秒这一档。体积已经是 1 秒档的 13%,比 x264 默认的 250 帧那档还小一些。平均要解的 150 帧换成单线程跳转中位也就 163.4 ms。30 秒档又省一半。可平均要解 450 帧。值不值我还拿不准,得看播放器和观看习惯。不限档更适合只从头放到尾的存档,谁要在里面来回拖会很难受。
关掉场景切换检测几乎没差
不限档还顺带对了一下场景切换检测:关掉(scenecut=0)是 1,299,049 字节,开着是 1,299,050 字节,只差 1 字节。原因是这段样本第 40、120、180 秒的三次翻页本来就没触发关键帧,新页面都走了 P 帧,所以开关它都一样。真实录屏要是从 PPT 整屏切到浏览器,结果可能不一样。这种情况这次没测。
这段样本只有一段 4 分钟,单次跑完。它没有 60 fps 版本,也不是 Retina 那种更高的分辨率。真实录屏的噪点和抗锯齿抖动都比它多。HLS、DASH 切片时关键帧间隔要和分片时长对齐,这一点我也只是推断,没测过。想在自己的录屏上核对,可以挑一段 5 分钟左右的各跑一次 -g 60 和 -g 300 比体积。再拖几下进度条看卡不卡。