📹 第四章 H.264/H.265视频编码并存储
📄 一句话概括

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 秒实验

  1. 把源换成 1280×720、300 帧,重跑,看耗时那列会不会变单调。
  2. 加一档 veryslow,看体积还能不能再降。
  3. 给 ultrafast 加 tune=zerolatency,看 B 帧数和体积怎么变。

常见误区

  1. 以为 preset 只影响速度。 它也影响体积——ultrafast 比 veryfast 大将近一倍。
  2. 以为 preset 是个独立开关。 它是一整套参数预设, 会顺带改掉 B 帧数、参考帧数这些你没设的东西。
  3. 以为小样本测不出就是「没关系」。 测不出要说「测不出」,并且说清需要多大样本才能测。
自测三问(点开对答案) 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 那边对名字:同一个功能,两套写法。

相关笔记