libx264 的选项分三层:通用编码参数、libx264 私有选项、x264-params 后门。
preset 不是一个开关,是一整套参数预设值——调它会顺带改掉你没注意的东西。
前一节讲的 GOP/码率都是通用概念,这一节讲「x264 这一家的具体按钮」。 下一节讲 x264.exe 那边的选项名,两套名字要对上。
一、选项分三层
| 层 | 例子 | 怎么传 |
|---|---|---|
| 通用编码参数 | bit_rate/gop_size |
直接设属性 |
| libx264 私有 | crf/preset/tune |
s.options = {...} |
| x264 全集 | ref=3:me=umh |
x264-params |
这三层的区别很实际:第一层 FFmpeg 帮你转成 AVCodecContext 字段;
第二层要靠 av_opt_set 逐个塞名字;第三层是把 x264 自己的命令行参数原样透传。
实测踩到的差异:gop_size、max_b_frames、bit_rate 能当属性设;
crf、preset、tune 只能走 options 字典——直接 s.crf = 23 会抛 AttributeError。
这不是 PyAV 的毛病,是 FFmpeg 的分层本来就这样。
二、preset:拿时间换体积
从 ultrafast 到 veryslow 共十档,本质是「搜索多细」。
| preset | 文件大小 | 耗时 | 耗时倍数 |
|---|---|---|---|
| ultrafast | 19.2 KB | 21 毫秒 | 1.00 倍 |
| superfast | 14.9 KB | 34 毫秒 | 1.59 倍 |
| veryfast | 10.2 KB | 38 毫秒 | 1.79 倍 |
| medium | 10.4 KB | 56 毫秒 | 2.64 倍 |
| slow | 10.2 KB | 58 毫秒 | 2.76 倍 |
体积那列是单调的:19.2 → 14.9 → 10.2 KB,从 ultrafast 到 veryfast 省掉近一半,再往后几乎不动。 但耗时那列不单调(superfast 甚至比 ultrafast 快)——这个样本量测不出 preset 的耗时差异。
原因是片子太小:30 帧 240p,整段编码只要几十毫秒, 而 libx264 首次调用要初始化查找表,这部分开销和各档差异是同一量级。 想量准得用 1080p、几百帧以上。 所以这一节只能给一个结论:preset 对体积的影响是真的,对速度的影响这里测不出来。
三、tune 和 profile
| 选项 | 取值 | 什么时候用 |
|---|---|---|
tune |
zerolatency |
直播/监控,先砍 B 帧和前瞻 |
tune |
film/animation |
按内容类型微调 |
profile |
baseline/main/high |
看对端能不能解 |
zerolatency 是监控里最常见的一个:它关掉 B 帧、关掉前瞻,
代价是同样码率下画质差一点,好处是编码延迟从几百毫秒降到几十毫秒。
监控要的是「现在」,不是「好看」。
profile 是兼容性开关:baseline 最老最稳(没有 B 帧、没有 CABAC),high 压缩率最好。
对端是老设备就降 profile,别指望它升级。
四、实际怎么选
| 场景 | preset | 理由 | |---|---|---|---| | 实时监控编码 | ultrafast/veryfast | CPU 不够,先保住帧率 | | 录像存盘 | medium | 一次转完,省硬盘 | | 离线压制 | slow/veryslow | 时间不值钱,体积值钱 |
第一行是监控的常态:一台 NVR 要同时编十几路,preset 用 medium 直接跑满 CPU,帧就丢了。 这也是为什么监控设备普遍用硬件编码器(NVENC 等)——压缩率低一点,但 CPU 占用几乎为零。
五、动手 Demo:preset 是「用多少时间换多少体积」
Demo 固定 CRF,只换 preset,跑五档,量体积和耗时。 耗时这一列我试过「跑两遍取最小」,仍然不单调,所以如实写成「测不出」。
for p in ("ultrafast", "superfast", "veryfast", "medium", "slow"):
t0 = time.perf_counter()
c = av.open("p_%s.h264" % p, "w", format="h264")
s = c.add_stream("libx264", rate=30)
s.width, s.height, s.pix_fmt = 320, 240, "yuv420p"
s.options = {"crf": "23", "preset": p}
...
dt = time.perf_counter() - t0
实跑输出:
--------------------------------------------------------------------
preset 是「用多少时间换多少体积」
--------------------------------------------------------------------
**x264 的 preset 从 ultrafast 到 veryslow 共十个档,本质是「搜索多细」。**
同一份源、同一个 CRF,只换 preset:
preset 耗时 文件大小 耗时倍数
---------------------------------------------------
ultrafast 21 毫秒 19.2 KB 1.00 倍
superfast 34 毫秒 14.9 KB 1.59 倍
veryfast 38 毫秒 10.2 KB 1.79 倍
medium 56 毫秒 10.4 KB 2.64 倍
slow 58 毫秒 10.2 KB 2.76 倍
**体积那列是单调的:19.2 → 14.9 → 10.2 KB**,从 ultrafast 到 veryfast 省掉将近一半,
再往后几乎不动。
**但耗时那列不单调**(superfast 甚至比 ultrafast 快)——**这个样本量测不出 preset 的耗时差异。**
**原因是片子太小**:30 帧 240p,整段编码只要几十毫秒,
**而 libx264 首次调用要初始化查找表,这部分开销和各档的差异是同一量级。** 想量准得用 1080p、
几百帧以上。
**所以这一节只能给一个结论:preset 对体积的影响是真的,对速度的影响这里测不出来。**
**不硬编数字**——这是我一贯的规矩,测不到就说测不到。
**实际选择很简单:**
场景 preset 理由
-----------------------------------------------------------------
实时监控编码 ultrafast/veryfast CPU 不够,先保住帧率
录像存盘 medium 一次转完,省硬盘
离线压制 slow/veryslow 时间不值钱,体积值钱
**第一行是监控的常态**:一台 NVR 要同时编十几路,**preset 用 medium 直接跑满 CPU,帧就丢了。**
**这也是为什么监控设备普遍用硬件编码器**(NVENC 等)——虽然压缩率低一点,但 CPU 占用几乎为零。
「跑两遍取最小」这个做法要说出来:单次计时会被首次初始化污染, 但这次即使取最小也不单调,说明样本量根本不够,不是方法问题。 规格和实测要分开写:规格是「档越低越快」,实测是「这里测不出」,两句都留着,才叫如实。
30 秒实验
- 把源换成 1280×720、300 帧,重跑,看耗时那列会不会变单调。
- 加一档
veryslow,看体积还能不能再降。 - 给
ultrafast加tune=zerolatency,看 B 帧数和体积怎么变。
常见误区
- 以为 preset 只影响速度。 它也影响体积——ultrafast 比 veryfast 大将近一倍。
- 以为 preset 是个独立开关。 它是一整套参数预设, 会顺带改掉 B 帧数、参考帧数这些你没设的东西。
- 以为小样本测不出就是「没关系」。 测不出要说「测不出」,并且说清需要多大样本才能测。
自测三问(点开对答案)
1. 为什么 `s.crf = 23` 会报错,`s.gop_size = 30` 不会? —— `gop_size` 是 AVCodecContext 的字段,PyAV 暴露成属性; **`crf` 是 libx264 的私有选项,只能通过 options 字典传给 `av_opt_set`。** 2. 监控为什么偏爱 `tune=zerolatency`? —— 它关掉 B 帧和前瞻,**编码延迟从几百毫秒降到几十毫秒**。监控要实时,宁可画质差一点。 3. 这次为什么测不出 preset 的速度差异? —— 片子只有 30 帧 240p,整段编码几十毫秒,**和 libx264 首次初始化的开销同一量级**, 信号被噪声盖住了。下一节去 x264.exe 那边对名字:同一个功能,两套写法。
- ☐ (本节完,下一节 04.02.02 x264.exe中的选项名与选项值)