🎬 第十一章 体验FFmpeg 5.0
📄 一句话概括

5.0 上这条转码命令跟第五章一个字都不差,唯一会变的是「你没写出来的那些默认值」。 -b:a 是目标不是保证:要 128k 实测出 122.1k,验收只能要求接近。

📝 本节和前面几节的关系

第五章把转码参数讲透了,这一节只补一件事:换版本之后哪些东西会悄悄变。 结论会有点反直觉——最不稳的地方,恰好是你最没注意的地方。

一、命令一个字没变

第五章那条:

ffmpeg -i in.mp4 -c:v libx264 -crf 23 -preset medium \
  -c:a aac -b:a 128k out.mp4

在 5.0 上还是这条,在 7.x 上还是这条。 -c:v、-crf、-preset、-c:a 这些选项名,从 4.x 到 7.x 一路没动过。 所以「升级 FFmpeg」这件事,对手上这套命令几乎没有影响。

真正会变的,是那些你没写出来的东西。

二、唯一会变的:AAC 的默认值

5.0 的公告里有一句很容易被忽略:「AAC 编码器的默认设置也做了调整,以提升质量」。 这句话的含义是: 同一条不写 -b:a 的命令,4.x 和 5.0 出来的音频码率可能不一样。

实测同一段 2 秒 440 Hz 单声道音频,三种设法的结果:

音频怎么设 成片字节 折算码率 说明


不写码率 18857 75.4 kbps 默认值也是版本的一部分 -b:a 128k 30516 122.1 kbps 写死就不随版本变 -b:a 64k 17705 70.8 kbps 写死就不随版本变

不写码率那一行出来的 75.4 kbps,就是这套版本「自认为合适」的值。 换一个大版本,它可能就变成别的数——而你的命令一个字都没改。

这就是「依赖默认值等于依赖版本」的具体样子: 升级之后觉得音质「好像变了」、文件「好像大了」,原因往往不在命令里,在默认值里。 要对结果有把握,就把 -b:a、-crf、-preset 显式写出来。

顺带一个反直觉的小坑:写 -b:a 128k,实测出来是 122.1 kbps,不是 128。 -b:a 是给编码器的目标,不是保证: AAC 的帧大小固定,只能取整到最近的那一档;再加上容器的头开销, 最后的总码率跟你要的数总会差一点。 所以对码率做验收,只能要求「接近」,不能要求「等于」。

三、哪些参数跨版本稳

判断标准只有一条:这个选项名归谁定。

选项 名字归谁 跨版本稳不稳


-crf / -preset libx264 自己定 稳,库不换语义就不换 -x264-params libx264 自己定 稳 -b:a / -ar / -ac FFmpeg 通用层 比较稳,语义简单 aac 的默认参数 FFmpeg 自己定 会变,所以别依赖

一句话记法:名字属于外部库的(x264、x265、fdk-aac)就稳, 属于 FFmpeg 自己实现的编码器(aac、mp3)就可能跟着版本变。 因为外部库的选项名是那个库的公开接口,动它要背兼容性包袱; 而「默认开多大码率」是 FFmpeg 自己的内部决策,改起来没负担。

四、跨版本验收该怎么做

如果手上有一个跑了几年的转码脚本,升级 FFmpeg 之后要验三件事:

验什么 怎么验 为什么


命令还认不认 先空跑一遍看有没有报错 选项被删是最早暴露的 体积变没变 同一个源跑两次比字节 默认值变了体积就变 画质变没变 比 SSIM 或直接看片 编码器实现可能微调

这三步里最容易漏第二步。 「能跑」不等于「结果一样」—— 码率、体积这类东西变了不会报错,只会让你月底发现存储涨了。

最省事的办法其实是:升级前先把所有默认值都写死。 把「不写 -b:a」全部改成「写死 -b:a 128k」, 把「不写 -preset」全部改成「写死 -preset medium」, 这样升级之后唯一还可能变的就只剩编码器实现本身了。

五、转码这一节真正要带走的东西

不是新参数,是一条判断习惯: 遇到「升级后行为变了」,先问「我有没有依赖某个默认值」。

这条习惯不止对 FFmpeg 有用。 任何工具,凡是你没显式写的部分,都是那个版本替你做的决定; 版本一换,决定就换。 所以「把关键参数写死」看起来啰唆,实际上是把不确定性关在门外。

六、动手 Demo:AAC 三档码率,看默认值到底是多少

下面这段现造一段 2 秒 440 Hz 单声道音频,编三次:不写码率、128k、64k。 然后按文件大小折算成实际码率——那个「不写」的数,就是默认值。

import os

import av
import numpy as np

SR, NS = 44100, 88200
tt = np.arange(NS) / float(SR)
sig = (0.25 * np.sin(2 * np.pi * 440 * tt)).astype(np.float32)

for label, br in (("不写码率", None), ("128k", 128000), ("64k", 64000)):
    path = "a_%s.m4a" % (br or "def")
    o = av.open(path, "w")
    a = o.add_stream("aac", rate=SR)
    a.layout, a.format = "mono", "fltp"
    if br:
        a.bit_rate = br
    fr = av.AudioFrame.from_ndarray(
        np.ascontiguousarray(sig.reshape(1, -1)), format="fltp", layout="mono")
    fr.sample_rate = SR
    fr.pts = 0
    for pk in a.encode(fr):
        o.mux(pk)
    for pk in a.encode(None):
        o.mux(pk)
    o.close()
    sz = os.path.getsize(path)
    print("%-8s %6d 字节  %.1f kbps" %
          (label, sz, sz * 8.0 / 1000.0 / (NS / float(SR))))

实跑输出:

--------------------------------------------------------------------
11.2 使用 FFmpeg 5.0 实现音视频转码
--------------------------------------------------------------------

**① 命令没变,唯一会变的是「默认值」**

**5.0 公告里那句「AAC 编码器默认参数调整」,是命令行用户唯一会碰到的一处。**
**它意味着:同样一条不写 `-b:a` 的命令,4.x 和 5.0 出来的音频码率可能不一样。**

  音频怎么设        成片字节      折算码率      说明
  ----------------------------------------------------------------------
  不写码率          18857         75.4 kbps     默认值也是版本的一部分
  -b:a 128k         30516         122.1 kbps    写死就不随版本变
  -b:a 64k          17705         70.8 kbps     写死就不随版本变

**同样的 2 秒单声道音频,不写码率出来的那个数,就是这个版本「自认为合适」的值。**
**换一个大版本,它可能就变了——而你的命令一个字都没改。**

**这就是「依赖默认值等于依赖版本」的具体样子:**
**升级之后音质「好像变了」、文件「好像大了」,原因往往不在命令里。**
**要对结果有把握,就把 `-b:a`、`-crf`、`-preset` 显式写出来——**
**工程上这叫「别让默认值替你做决定」。**

**还有个小坑值得顺手记下:写 `-b:a 128k`,实测出来的是 122.1 kbps,不是 128。**
**`-b:a` 是给编码器的目标,不是保证——**
**AAC 的帧大小是固定的,只能取整到最近的那一档;再加上容器的头开销,**
**最后的总码率跟你要的那个数总会差一点。**
**所以对码率做验收时,只能要求「接近」,不能要求「等于」。**

**② 视频那边为什么不受影响**

**因为 `-crf`、`-preset` 是编码器私有选项,名字由 libx264 自己定,**
**FFmpeg 只是原样转发。libx264 没换,参数语义就不会变。**
**而 aac 是 FFmpeg 自带的编码器,默认值写在 FFmpeg 源码里,跟着版本走。**

**所以「哪些参数跨版本稳」有一条简单的判断:**
**选项名属于外部库的(x264、x265、fdk-aac)就稳,属于 FFmpeg 自己的(aac、mp3)就可能变。**

三行数字里,第一行才是「这一版自己觉得合适」的值。 后两行是你说了算——只要写死,换版本也不会变。

注意 128k 那行折算出来是 122.1,不是 128。 这不是算错,是编码器的目标码率跟最终文件大小之间本来就有差: AAC 帧大小固定、容器还有头,差个几个百分点是常态。

30 秒实验

  1. 把采样率从 44100 改成 8000 再跑:看默认码率跟着降到多少。
  2. 把时长从 2 秒拉到 20 秒:看折算码率是否稳定,理解「头开销占比」。
  3. 拿一段真实音乐跑一遍:跟 440 Hz 正弦比,默认码率往往不一样。

常见误区

  1. 以为不写码率就等于「无损」。它等于「让这个版本替你选」。
  2. 要求 -b:a 128k 出来的文件精确等于 128 kbps。它是目标,不是保证。
  3. 升级后只验「能跑」。体积和质量变了不会报错。
自测三问(点开对答案) 1. 换大版本之后,哪一类参数最容易变? —— FFmpeg 自己实现的编码器的默认参数,比如 aac 的默认码率。 2. `-b:a 128k` 为什么出不来 128 kbps? —— AAC 帧大小固定只能取整,加上容器头开销,总会差一点。 3. 升级前最省事的自保办法是什么? —— 把所有默认值显式写死,让结果不再依赖版本。

相关笔记