🎬 第七章 流媒体开源库简介
📄 一句话概括

一条 ffmpeg 命令的难点不在参数多,而在参数放的位置。 同一个 -ss,放在 -i 前后是两种行为。

📝 本节和前面几节的关系

7.1.1 认了库,这一节看库的壳怎么用。 7.1.3 讲怎么绕开命令行直接调库。

一、一条命令的六个位置

ffmpeg -i 输入 -ss 定位 -t 时长 -c 编码 -crf 质量 输出 ① 工具名:ffmpeg 本体。 ② 输入:-i 后面跟文件或地址。 ③ 定位与时长:-ss 开始位置、-t 持续多久。 ④ 编码器:-c:v libx264 指定视频编码器。 ⑤ 质量与开关:-crf 28 控质量、-an 丢掉音频。 ⑥ 输出:最后一个参数,没有前缀。

实测一条典型命令有 13 个 token,其中 6 个是参数名、7 个是值。 参数名与值成对出现,只有 -an 这种开关是单独一个。

二、位置决定行为

-ss 放在 -i 前面是「快速定位」: 先跳到最近的关键帧,再从那里开始解,快但不精确。 放在 -i 后面是「解码定位」: 从文件开头逐帧解到那个点,慢但精确到帧。

同一个参数、两个位置、两种行为——这是 ffmpeg 最容易踩的坑。 而且它不报错,只是结果和你想的不一样。

一个实用判据: 切片段、做预览用「快速定位」(放前面), 做精确剪辑用「解码定位」(放后面)。 两者差的时间在大文件上能有几十倍。

三、最常用的十个参数

-i:输入。 -c copy:不重编码,只换容器。最省时间。 -c:v/-c:a:分别指定视频与音频编码器。 -crf:质量,18 到 28 之间最常用,越小越好。 -b:v:指定码率,直播必须用这个而不是 crf。 -r:帧率。 -s:分辨率。 -ss/-t:定位与时长。 -g:GOP 长度,切 HLS 时必须设。

-c copy 是最该记住的一个: 它把「重新编码」变成「重新封装」,一个小时的视频几秒就转完。 代价是编码参数一点都不能改——改分辨率、改码率都必须重编码。

四、动手 Demo:命令行——输入、输出、参数各在哪个位置

拆一条典型命令,看六个位置各是什么。 再用 PyAV 真跑一次「只取前 25 个包」,等价于 -t 1。

cmd = "ffmpeg -i in.mp4 -ss 00:00:10 -t 5 -c:v libx264 -crf 28 -an out.mp4"
toks = cmd.split()
print("共 %d 个 token" % len(toks))
print("  工具   :", toks[0])
print("  输入   :", " ".join(toks[1:3]))
print("  定位   :", " ".join(toks[3:6]))
print("  编码器 : -c:v libx264")
print("  开关   : -crf 28 -an")
print("  输出   :", toks[-1])

print()
import av, numpy as np, tempfile, os
p = os.path.join(tempfile.mkdtemp(), "a.mp4")
c = av.open(p, "w", format="mp4")
st = c.add_stream("libx264", rate=25)
st.width, st.height, st.pix_fmt = 320, 240, "yuv420p"
st.options = {"crf": "30", "preset": "veryfast", "g": "25"}
for i in range(50):
    f = av.VideoFrame(320, 240, "yuv420p")
    y = np.zeros((240, 320), np.uint8)
    y[:, :] = np.linspace(16, 200, 320, dtype=np.uint8)[None, :]
    f.planes[0].update(y.tobytes())
    for pi in (1, 2):
        f.planes[pi].update(bytes([128]) * f.planes[pi].buffer_size)
    f.pts = i
    for pk in st.encode(f):
        c.mux(pk)
for pk in st.encode(None):
    c.mux(pk)
c.close()

n = 0
with av.open(p) as c2:
    for pk in c2.demux(video=0):
        if pk.size == 0:
            continue
        n += 1
        if n >= 25:
            break
print("只取前 25 个包 -> %d 个,等价 -t 1" % n)

实跑输出:

--------------------------------------------------------------------
命令行——输入、输出、参数各在哪个位置
--------------------------------------------------------------------
**① 一条典型命令拆开看**

  **原命令:**
    ffmpeg -i in.mp4 -ss 00:00:10 -t 5 -c:v libx264 -crf 28 -an out.mp4

  位置     片段                作用
  ---------------------------------------------
  1        ffmpeg              工具名
  2        -i in.mp4           输入
  3        -ss 00:00:10 -t     定位与时长
  4        -c:v libx264        视频编码器
  5        -crf 28 -an         质量与丢音频
  6        out.mp4             输出

**② 参数位置错了后果很具体**

  **`-ss` 放在 `-i` 前面是「快速定位」:直接跳到关键帧,快但不精确。**
  **放在 `-i` 后面是「解码定位」:逐帧解到那个点,慢但精确。**
  **同一个参数、两个位置、两种行为——这是 ffmpeg 最容易踩的坑。**

**③ 真跑一次「按时间切一段」**

  **原文件 50 帧、4711 字节。**
  **帧率 25,时长 2.00 秒**
  **只取前 25 个包,实际读到 25 个——就相当于 `-t 1`(1 秒 @25fps)。**

13 个 token 里 6 个是参数名,参数与值成对,只有 -an 单独。 -t 也可以写成 -to(到哪个时间点),两者差一个字的语义。 取前 25 个包就是 1 秒,因为帧率是 25——不是按字节截,是按包截。

30 秒实验

  1. 把 -ss 移到 -i 前面,对比耗时——快很多,但落点会偏到一个关键帧上。
  2. 把 -c:v libx264 换成 -c copy,看还能不能改 crf——不能,copy 模式所有编码参数都被忽略。
  3. 把 -t 5 改成 -to 5,想想差在哪——-t 是时长 5 秒,-to 是到第 5 秒结束。

常见误区

  1. 「-ss 放哪都一样」——前面是快速跳、后面是逐帧解,差得远。
  2. 「-c copy 也能改码率」——不能,只是换容器。
  3. 「参数顺序无所谓」——顺序决定作用对象,是最容易错的地方。
自测三问(点开对答案) 1. `-ss` 放在 `-i` 前后有什么区别? —— **前面是快速定位(跳关键帧),后面是解码定位(逐帧精确)。** 2. `-c copy` 为什么快? —— **它只换容器、不重编码**,所以编码参数一律不能改。 3. 直播为什么用 `-b:v` 而不是 `-crf`? —— **直播要可控码率**,crf 会让码率随画面复杂度上下飘。

相关笔记