一段 4 分钟的录屏转码前是 360.0 MB。用 x264 按 CRF 23 转一遍只剩 5.2 MB,差了将近 70 倍,文字清晰度基本没动。把两份文件的帧逐个列出来,扔掉的几乎全是「画面没变也照样写满」的那部分字节。下面是三条 ffprobe 命令:按帧类型统计帧数和字节、只列关键帧、再盯住一小段静止画面看每帧多大。命令都在 2026-11-09 跑过,输出都原样贴出来没有删改。
样本是一段自制的模拟网课录屏
样本是自制的 4 分钟模拟网课录屏。内容有课件翻页、鼠标移动、打字和讲义滚动。规格是 1920×1080、30 fps、12 Mbps 恒定码率,文件 360.0 MB 且没有音轨。它不是录屏软件录的,而是把自写的课件页面逐帧截图再合成出来的一段视频。画面比真实屏幕干净得多,约 78% 的时间完全静止。这意味着它比真实录屏更好压,后面读每个数字时都得记着这一点。12 Mbps 也只是样本设定的假设,Apple 和微软都没公布系统录屏的默认码率。跑测试的是 Apple M4 芯片的 Mac。工具是 ffmpeg / ffprobe 9.0.2 和 x264 preset medium。每个数都只跑了一次。不开命令行时我用的是图映 ImgIng imging.cn/ 的视频压缩,它在电脑浏览器里本地处理,支持批量和 H.265。同一段样本用它的智能推荐 H.264 档出来是 20.2 MB,小字清晰度和 x264 这份同档,体积约是 HandBrake 默认预设的 3 倍。更要紧的是这几次实测压缩时没有上传请求。公司里的录屏多少带点内部资料,能不出电脑就不出。下面的帧统计都只针对 x264 按 CRF 23 转出来的那一份文件。
原文件连静止画面都按满码率写
原文件用 ffprobe 读出来的码率是 11,995,489 bps。时长 240 秒,一共 359,952,205 字节,和生成时的设定对得上。把逐帧输出的时间戳对照时间轴分组,按时间段拆开帧大小。静止段的非关键帧每帧中位数是 50,002 字节,和打字段、滚动段差不多大。这些静止时段占了整个原文件 71.1% 的字节数。恒定码率就是这么记账的。编码器每秒都要交够 12 Mbps,画面不动的时候也得拿填充数据把这笔账补齐。同一段画面交给 x264 按 CRF 23 重编,静止段的非关键帧中位数降到 77 字节。
这里有个反例得先放着。同一段无损母版交给本机硬件编码器,目标码率也设成 12 Mbps 但不强制恒定。出来的文件平均只有约 0.79 Mbps。所以录屏大不大,要看录的时候怎么编码。大不大要看录屏软件是按固定码率写盘,还是按画面变化分配。真实的系统录屏走哪种,这次没测。
按帧类型统计时先认准列顺序
转码后的文件用下面这条命令按帧类型汇总。pkt_size 是这一帧压缩后数据包的字节数。pict_type 是 I、P、B 三种帧类型。后面的 awk 负责按类型累加帧数和字节再算出占比和平均值。
bash
ffprobe -v error -select_streams v:0 -show_entries frame=pict_type,pkt_size -of csv=p=0 input.mp4 \
| awk -F, '{n[$2]++; s[$2]+=$1; t+=$1} END {for (k in n) printf "%s frames=%d bytes=%d share=%.1f%% avg=%d\n", k, n[k], s[k], s[k]*100/t, s[k]/n[k]}' | sort
B frames=5253 bytes=502079 share=9.8% avg=95
I frames=29 bytes=4076157 share=79.4% avg=140557
P frames=1918 bytes=557443 share=10.9% avg=290
输出每行一种帧类型:frames 是帧数,bytes 是这类帧的字节合计,share 是占全部视频流字节的比例,avg 是平均每帧字节。sort 只是让 B、I、P 按字母排好,看的时候先找 I 那一行的 share。
注意 awk 里 $1 当字节、$2 当类型。这和命令里写的 pict_type,pkt_size 正好反着。ffprobe 的 csv 按它内部的字段顺序输出,不管你在命令里怎么排。后面看静止段那条命令也能印证。照着命令顺序去写 $1 的话 awk 不会报错。它只会把字节数当成类型名去分组,输出的结果看着还挺像回事。三行字节数加起来是 5,135,679。这正好是这份文件视频流的大小,拿来自检很方便。这条命令要把每一帧都解出来才读得到帧类型,片子越长跑得越久,长录屏建议先截一段试。
近八成字节落在关键帧上
7,200 帧里只有 29 个 I 帧。它们平均每个 140,557 字节,加起来占了全片 79.4% 的字节。剩下 7,171 个 P 帧和 B 帧合计只占两成出头,其中 B 帧平均 95 字节。P 帧平均 290 字节,比静止帧高,推测是被翻页和滚动那些帧拉高的。同一条命令跑摄像头视频,分布完全是另一个样子:
| 素材 | I 帧字节占比 | P 帧字节占比 | B 帧字节占比 |
|---|---|---|---|
| 模拟网课录屏(7,200 帧) | 79.4% | 10.9% | 9.8% |
| 摄像头视频 A(745 帧) | 4.5% | 59.4% | 36.1% |
| 摄像头视频 B(236 帧) | 6.0% | 77.7% | 16.3% |
摄像头视频 A 的 P 帧平均 48,886 字节。这是录屏 P 帧的一百多倍。录屏和摄像头视频虽然都叫视频,字节却花在完全不同的帧上。同一套转码参数套在两种素材上得到的收益不会一样。两段摄像头对照视频来自维基共享资源的 CC BY-SA 视频,经重新编码后只拿来做帧类型统计。
接下来这一段是推断,没有单独做实验验证。P 帧和 B 帧编的是「和参考帧差在哪」。录屏静止时绝大多数宏块和参考帧一模一样。编码器大概率走跳过模式,只用几个比特说明「沿用参考帧、没有残差」。摄像头就算架着不动,传感器噪点和细小晃动也会让每个宏块带点残差。字节自然都压在 P 帧上。录屏的钱几乎全花在定期整屏重画一遍的关键帧上。想验证这个推断可以去看编码器统计里跳过宏块所占的比例,这一步本次没做。
翻页没有触发新的关键帧
想知道关键帧落在哪,就在第一条命令的基础上加 -skip_frame nokey 让 ffprobe 跳过非关键帧。输出只要 pts_time 和 pkt_size 两列,再接一个 head -5。前五行输出是 0 秒 109,061 字节,往后 8.333333 秒 123,468、16.666667 秒 123,420、25 秒 123,497、33.333333 秒 123,474。间隔整整 8.33 秒。换成帧数是 250 帧,正是 x264 默认的关键帧间隔。想数全片有几个关键帧,把 head -5 换成 wc -l 就行,这份文件数出来应当和第一条统计里 I 帧的 29 对得上。第一行末尾还多了一个逗号,写成「0.000000,109061,」。后面几行都没有这个多出来的逗号,原因没查。用 awk 按逗号切的时候第一行会多出一个空字段,好在不影响取前两列。
翻页发生在第 40、120、180 秒。x264 默认开着场景切换检测,画面突变时可以插一个关键帧。但全片统计下来这三处一个新 I 帧都没有,新页面是用 P 帧编进去的。翻页前后那几个 P 帧大约 22~36 KB,只有关键帧的四分之一到六分之一。为什么没判成场景切换?推断是只有左边的课件区变了。右侧讲义、编辑区和菜单栏都没动,帧间预测仍然划算。这个推断没有用整屏翻页的样本验证过。
静止段的帧只有几十到几百字节
最后挑一段静止画面放大看。-read_intervals 50%+0.3 表示从第 50 秒开始读 0.3 秒,比把整段都解一遍再用 awk 过滤快得多。输出里的 pts_time 是这一帧的显示时间,单位秒,30 fps 下相邻两帧差 0.033 秒,0.3 秒正好九帧。第 40 到 70 秒这段课件没有任何变化,第 50 秒落在这个静止区间里。这 0.3 秒前后都没有鼠标移动或打字。
bash
ffprobe -v error -select_streams v:0 -read_intervals 50%+0.3 -show_entries frame=pts_time,pict_type,pkt_size -of csv=p=0 input.mp4
50.000000,141595,I
50.033333,212,B
50.066667,230,B
50.100000,75,B
50.133333,1221,P
50.166667,84,B
50.200000,92,B
50.233333,69,B
50.266667,168,P
第 50 秒恰好是第 7 个关键帧,141,595 字节,比开头那几个 12 万字节上下的关键帧大,这时已经翻到第 2 页,整屏重画的内容不同,成本也跟着变。列顺序又是 pts_time,pkt_size,pict_type,跟上面那条对上了。帧类型的排列是 B、B、B、P 一组一组往下走,P 帧之间夹着三个 B 帧,这和 x264 默认最多连续 3 个 B 帧的设置一致。后面八帧全在几十到一千多字节之间,最小的一帧只有 69 字节。紧跟关键帧的第一个 P 帧有 1,221 字节,是后面那个 P 帧的七倍多。画面明明没变,这个差别从哪来本次没查清。九帧的样本太小,单看它很容易误判出一个并不存在的规律来。按时间段把全片非关键帧拆开更稳:
| 时间段 | 帧数 | 每帧中位数 | 每帧平均 |
|---|---|---|---|
| 静止段 | 5,483 | 77 B | 85 B |
| 鼠标移动 | 592 | 98 B | 115 B |
| 打字 | 657 | 80 B | 107 B |
| 讲义滚动 | 296 | 906 B | 1,211 B |
鼠标移动和打字几乎不比静止画面贵多少。讲义滚动贵了十来倍,整块讲义区域都在位移。
拿自己的录屏先跑这三条
这三条命令只读本地文件的帧信息和包大小,在公司电脑上跑也不涉及任何文件外发。一段要入库的录屏,可以先对原文件按时间段看帧大小。静止画面每帧也有几万字节的话,多半是按固定码率录的,这种重编一遍基本稳赚。
转码之后再跑第一条统计看 I 帧占比,原文件和转码后的文件各跑一遍就能对比出差别。占比像这段样本一样接近八成时,能动的就是关键帧间隔,代价是拖动进度条时要解的帧变多,间隔该按播放端能接受的拖动延迟去定,不能只看体积。要是 P 帧占大头,它更像摄像头视频,调关键帧没多少油水,该去看 CRF。还有一件事没解决:这段样本比真实录屏干净太多,真实屏幕有抗锯齿抖动,Mac 录屏还常是 Retina 分辨率。换成真录屏后 79.4% 推测会往下走,掉到多少本次没有数据。