🎬 第五章 FFmpeg命令行实现音视频转码
📄 一句话概括

FFmpeg 与 x264.exe 是两套名字,落到同一组参数上。 最容易踩的坑是码率单位:FFmpeg 用 bit,x264 用 kbit,差一千倍。

📝 本节和前面几节的关系

上一节用的 preset 这些名字其实是 x264 的,FFmpeg 只是转发。 这一节把两套名字对上,并验证它们确实等价。

一、两套名字对照

同一组参数,两种写法:

FFmpeg 里写 x264 里写 含义


crf 23 crf 23 质量档位 preset slow preset slow 速度与效率 g 10 keyint 10 关键帧间隔 b:v 2M bitrate 2000 目标码率 profile:v high profile high 档次 bf 3 bframes 3 连续 B 帧上限

前两行的名字一模一样,因为 FFmpeg 就是原样转发。 后四行才有改名:g 变 keyint、bf 变 bframes。

第四行是唯一真正容易踩的坑:FFmpeg 的码率单位是 bit,x264 的是 kbit。 写成 2M 是 2000000 bit,写成 2000 在 x264 里才是 2000 kbit——同一个意思。

二、透传:两套名字确实等价

用 x264-params 透传,与分开写对比:

写法 字节 关键帧数


crf=23 加 g=10 6245 5 x264-params crf=23:keyint=10 6245 5

两行字节完全一样、关键帧数也一样——说明这两条路最终都落到同一组 x264 参数上。 差 0 字节这件事比「差不多」有说服力得多。

x264-params 的意义就在这儿:FFmpeg 没给命令行开关的 x264 选项, 都能通过它透传进去,用冒号分隔、不用加两个减号。

三、什么时候必须用透传

三类需求只能走透传:

需求 为什么得透传


调 psy 相关的几个参数 FFmpeg 没有对应开关 指定 slice 数量 同上 关掉某个具体工具 如 no-scenecut

代价是可读性变差、也没人帮你检查拼写。 写错一个参数名,x264 只会在日志里抱怨一句,编码照样跑完——结果和你以为的完全不同。

所以透传的正确用法是:先用分开的写法验证参数有效果,再合并成透传字符串。 直接一上手就写一长串,出错时几乎没法定位是哪个名字写错了。

四、动手 Demo:分开写与透传写,比字节与关键帧数

同一组参数(CRF 23、关键帧间隔 10)用两种写法各编一遍。 然后比字节与关键帧数——如果两套名字真的等价,两行就该一模一样。

import av, os

def enc(dst, opts):
    c1 = av.open(dst, u"w")
    s1 = c1.add_stream(u"libx264", rate=25)
    s1.width, s1.height, s1.pix_fmt = 160, 90, u"yuv420p"
    s1.options = opts
    c0 = av.open(u"s5.mp4")
    for f in c0.decode(video=0):
        for o in s1.encode(f.reformat(width=160, height=90, format=u"yuv420p")):
            c1.mux(o)
    for o in s1.encode(None):
        c1.mux(o)
    c1.close()
    c0.close()
    c = av.open(dst)
    k = sum(1 for pk in c.demux(video=0) if pk.size and pk.is_keyframe)
    c.close()
    return os.path.getsize(dst), k

a = enc(u"x_sep.mp4", {u"crf": u"23", u"g": u"10"})
b = enc(u"x_params.mp4", {u"x264-params": u"crf=23:keyint=10"})
print(u"分开写     %d 字节  关键帧 %d" % a)
print(u"x264-params %d 字节  关键帧 %d" % b)
print(u"两行字节差 %d" % (a[0] - b[0]))

实跑输出:

--------------------------------------------------------------------
5.7.2 x264.exe 中的选项名与选项值——两套名字一件事
--------------------------------------------------------------------

**① 同一组参数,两种写法对照**

  FFmpeg 里写         x264 里写          含义
  ----------------------------------------------------------------
  -crf 23             --crf 23           质量档位
  -preset slow        --preset slow      速度与效率的取舍
  -g 10               --keyint 10        关键帧最大间隔
  -b:v 2M             --bitrate 2000     目标码率(x264 用 kbps)
  -profile:v high     --profile high     档次
  -bf 3               --bframes 3        连续 B 帧上限

**第四行是唯一真正容易踩的坑:FFmpeg 的码率单位是 bit,x264 的是 kbit。**
**写成 `-b:v 2000` 会被当成 2 kbps,编码器直接给你一片马赛克。**

**② 用 x264-params 透传,验证两套名字等价**

  写法                             字节     关键帧数
  ---------------------------------------------------
  crf=23 加 g=10                   6245     5
  x264-params crf=23:keyint=10     6245     5

**两行字节完全一样、关键帧数也一样——说明这两条路最终都落到同一组 x264 参数上。**
**`x264-params` 的意义就在这儿:FFmpeg 没给命令行开关的 x264 选项,**
**都能通过它透传进去,用冒号分隔、不用加两个减号。**

**③ 什么时候必须用透传**

  需求                      为什么得透传
  ----------------------------------------------
  调 psy 相关的几个参数     FFmpeg 没有对应开关
  指定 slice 数量           同上
  关掉某个具体工具          如 no-scenecut

**代价是可读性变差、也没人帮你检查拼写。写错一个参数名,x264 只会在日志里抱怨一句,**
**编码照样跑完——结果和你以为的完全不同。**

x264-params 的值是一个冒号分隔的字符串,参数名用 x264 那一套。 所以里面写 keyint 而不是 g,写成 g 反而不行。

两行字节完全相同,是因为它们最终生成的是同一条 x264 命令行。 FFmpeg 的 g 只是被翻译成 keyint 而已。

想验证透传有没有生效,最直接的办法就是比字节与关键帧数。 参数写错时这两列都不会变,正好当探测器用。

30 秒实验

  1. 命令行对照:ffmpeg -i 源.mp4 -c:v libx264 -x264-params crf=23:keyint=10 出.mp4。
  2. 关掉场景切换补帧:透传 scenecut=0,再数关键帧,应当正好等于设置值。
  3. 故意写错名字:透传 keyintt=10,看日志怎么抱怨、编码是否照跑。

常见误区

  1. 把 x264 的 bitrate 当 bit 用。它是 kbit,差一千倍,画面会糊一片。
  2. 在 x264-params 里写 FFmpeg 的短名。里面得用 x264 的参数名。
  3. 以为透传的参数写错会报错。多数情况只写一行日志,编码照跑。
自测三问(点开对答案) 1. FFmpeg 的 `-g` 对应 x264 的哪个参数? —— `keyint`。FFmpeg 只是把它翻译过去,落到的还是同一个 x264 参数。 2. 为什么 `-b:v 2M` 与 `--bitrate 2000` 是一个意思? —— FFmpeg 用 bit 为单位,x264 用 kbit,2000000 bit 就是 2000 kbit。 3. 怎么验证透传参数生效了? —— 比字节与关键帧数。参数没生效时这两列不会有变化。

相关笔记