🎬 第十章 FFmpeg命令行在Linux系统中的应用
📄 一句话概括

转码是「拆—解—改—编—装」五步,命令行只是把五步写进一行。 preset 是拿时间换体积,越换越亏;-c:a copy 省的是时间,不是体积。

📝 本节和前面几节的关系

第五章已经讲过转码的参数,这一章把它放到 Linux 上重走一遍。 补的是三处只有命令行才有的细节:preset 的性价比、copy 的真实作用、换行与引号。

一、一条命令,五步活

Linux 上最常用的一条转码命令是这样:

ffmpeg -i in.mp4 -c:v libx264 -crf 23 -preset medium \
  -c:a copy out.mp4

四个选项各管一摊:-c:v 选视频编码器、-crf 定量化粗细、 -preset 定编码器肯花多少工夫、-c:a 定音频怎么办。 剩下的拆容器、解码、装容器,它自己会做。

步骤 命令行里对应什么 PyAV 里对应什么


拆容器 -i 这一步 av.open(输入) 解码 自动,不用写 packet.decode() 改 -vf / -af / -s / -r frame.reformat() 编码 -c:v / -c:a / -crf add_stream().encode() 装容器 输出文件名 mux()

这张表就是转码的全部内容。换语言、换库、换平台,都还是这五步, 所以「转码慢」永远只能慢在后两步——解码和编码。

二、preset 是拿时间换体积,越换越亏

同一个源、同一个 crf 23,只换 preset,实测三档:

preset 成片字节 耗时 体积相对


ultrafast 249925 约 140 毫秒 1.00 倍 medium 191154 约 180 毫秒 0.76 倍 veryslow 180439 约 410 毫秒 0.72 倍

从 ultrafast 换到 medium,体积省了两成多,时间只多三成——这一档几乎是白捡。 再从 medium 换到 veryslow,体积只省半成,时间却翻了一倍多。

所以 preset 的正确用法不是「越慢越好」,而是「够用就停」。 直播、转码服务这种要吞吐的场景,ultrafast 或 veryfast 才是常态; veryslow 只留给「一次压好、以后不再动」的归档片。

顺带说一句:preset 改的是「搜索有多拼」,crf 改的是「目标质量」, 两个是不同维度。想要小就调 crf,想要快就调 preset,别指望一个管两件事。

三、-c:a copy 省的是时间,不是体积

很多人把 -c:a copy 当成「把音频压小」的开关,这是误解。实测:

音频怎么处理 成片字节 耗时 说明


-c:a copy(流拷贝) 191056 约 101 毫秒 不解码不重编 重新编码 aac 191154 约 185 毫秒 解码再编码

两者体积只差 98 字节,耗时差了四成多。

原因很简单:源里的音频本来就是 AAC,copy 过去还是那个 AAC; 重编码一遍也不会更小,只会多一道解码再编码。 所以正确的结论是「想快就 copy」,不是「想小就 copy」。

真想把音频变小,要么改 -b:a,要么降采样率、降声道—— 这些都得重新编码,copy 一个字节都动不了。

四、Linux 命令行的三处字面差异

同样的命令搬到 Linux 上,有三处写法会变,跟 FFmpeg 无关,是 shell 的规矩:

哪一处 Linux 上怎么写 Windows 上怎么写


路径分隔符 /home/me/in.mp4 D:\work\in.mp4 命令续行 行尾一个反斜杠 cmd 用 ^,PowerShell 用反引号 单引号 一个字都不解释 cmd 里没有单引号这回事

单引号这一条最值得记:Linux 的单引号里,$、*、反斜杠全都是普通字符, 双引号里 $ 仍会被展开。 所以写 -vf 那种又长又带符号的参数,用单引号最省心。

五、转封装与转码差在哪

把 -c:v 也换成 copy,就是转封装:

ffmpeg -i in.mp4 -c copy out.mkv

这时候一帧都不解码、一帧都不重编,只是把同一个码流换个盒子装。 几百兆的片子常常一两秒就完成,速度差着两个数量级。

代价是「只能换盒子,不能改内容」: 想改分辨率、改码率、加水印、剪掉一段,都必须真的转码。 这也是排查时的第一条分流—— 先问「我要改的是盒子还是码流」,盒子用 copy,码流才谈转码。

六、动手 Demo:三档 preset 的体积与耗时对比

沙箱里没有 ffmpeg 这个可执行文件,所以下面用 PyAV 走同一条链路。 源用 lavfi 现造,不落中间文件,代码块自包含。

import os
import time
from fractions import Fraction

import av

W, H, FPS, NF = 320, 240, 25, 50

for preset in ("ultrafast", "medium", "veryslow"):
    t0 = time.time()
    c = av.open("testsrc=size=%dx%d:rate=%d" % (W, H, FPS), format="lavfi")
    o = av.open("o_%s.mp4" % preset, "w")
    v = o.add_stream("libx264", rate=Fraction(FPS, 1))
    v.width, v.height, v.pix_fmt = W, H, "yuv420p"
    v.options = {"crf": "23", "preset": preset}
    n = 0
    for f in c.decode(c.streams[0]):
        g = f.reformat(format="yuv420p")
        g.pts = n
        g.time_base = Fraction(1, FPS)
        for pk in v.encode(g):
            o.mux(pk)
        n += 1
        if n >= NF:
            break
    for pk in v.encode(None):
        o.mux(pk)
    o.close()
    c.close()
    path = "o_%s.mp4" % preset
    print("%-10s %7d 字节 %5.0f 毫秒" %
          (preset, os.path.getsize(path), (time.time() - t0) * 1000))

实跑输出:

--------------------------------------------------------------------
10.1 使用 FFmpeg 实现音视频转码
--------------------------------------------------------------------

**① 本机没有 ffmpeg.exe,这一节用 PyAV 走同一条链路**

**Linux 上那条命令是:**

    ffmpeg -i in.mp4 -c:v libx264 -crf 23 -preset medium -c:a copy out.mp4

**转码这名字听着玄,其实就是五步。**

  步骤        命令行里对应什么          PyAV 里对应什么
  ----------------------------------------------------------------
  拆容器      -i 这一步                 av.open(输入)
  解码        自动,不用写              packet.decode()
  改          -vf / -af / -s / -r    frame.reformat()
  编码        -c:v / -c:a / -crf      add_stream().encode()
  装容器      输出文件名                mux()

**这张表就是转码的全部内容——换语言、换库,都还是这五步。**

**② 同一条命令只换 preset,体积与耗时怎么变**

  preset      成片字节    耗时        体积相对
  ------------------------------------------------
  ultrafast   249925      133 毫秒    1.00 倍
  medium      191154      178 毫秒    0.76 倍
  veryslow    180439      406 毫秒    0.72 倍

**从 ultrafast 换到 medium,体积省了 23.5%,耗时变成 1.34 倍;**
**再从 medium 换到 veryslow,体积只省 5.6%,耗时却变成 2.27 倍。**

**这是整条命令里性价比最高的一处认识:`preset` 是拿时间换体积,**
**而且换到后面越换越亏——从 ultrafast 到 medium 几乎是白捡,**
**从 medium 到 veryslow 是耗时的 2.27 倍只换来 5.6% 的体积。**

**③ `-c:a copy` 省的是时间,不是体积**

  音频怎么处理          成片字节      耗时          说明
  ------------------------------------------------------------------------
  -c:a copy(流拷贝)   191056        100 毫秒      不解码不重编
  重新编码 aac          191154        183 毫秒      解码再编码

**两者体积只差 98 字节,耗时却差 45%——**
**`-c:a copy` 的价值在于「别让音频白占 CPU」,不在于「把文件变小」。**

**原因很简单:源里的音频本来就是 AAC,copy 过去还是那个 AAC;**
**重编码一遍也不会更小,只会多一道解码再编码。**
**所以「想变小就 copy」是错的,「想快就 copy」才是对的。**

三行跑完,preset 这一档就从此有了具体数字,不再是书上的一句「越慢越好」。

注意 v.options 这一句:crf 和 preset 都是往编码器的私有选项里塞, PyAV 里就是一本字典,命令行里就是两个 - 开头的参数,一一对应。 所以「命令行参数 ⇄ 库里的选项名」这件事,两边其实是同一套名字。

30 秒实验

  1. 把 crf 从 23 改成 18 再跑一遍:看体积涨多少、耗时涨多少,体会两个维度不同。
  2. 把 preset 换成 "placebo":等它跑完,就知道「越慢越好」的尽头有多不划算。
  3. 把 -c:a copy 亲手试一次:源里没有音频流时它会报什么,也顺便记住。

常见误区

  1. 以为 -c:a copy 能把音频压小。它一个字节都不改,只省时间。
  2. 以为 preset 越慢画质越好。它只是搜索更细,质量由 crf 说了算。
  3. 在 Windows 上照抄 \ 续行。cmd 的续行符是 ^,直接粘贴会断在半路。
自测三问(点开对答案) 1. 转码五步里,哪两步最花时间? —— 解码和编码。拆容器、装容器只是搬运,改那一步通常也很轻。 2. 想「换容器不改内容」,命令怎么写? —— `-c copy`,一秒完成;只要动分辨率、码率、水印,就必须真转码。 3. `-crf` 与 `-preset` 分别管什么? —— `crf` 管目标质量,`preset` 管编码器花多少工夫去逼近这个质量。

相关笔记