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

目标码率是预算上限,不是承诺:三档目标实测只花掉 47%、35%、20%。 定码率下比字节没有意义,比容量要换定质量。

📝 本节和前面几节的关系

帧率管「每秒几张」,码率管「每秒多少字节」,分辨率管「一张多大」。 这三个参数互相牵连,动一个另外两个的感受就变。

一、定码率:三档都花不完

同一段素材,三个目标码率各压一遍:

目标码率 字节 实际码率 占目标


100 kbps 11793 47 kbps 47% 200 kbps 17408 69 kbps 35% 400 kbps 19722 78 kbps 20%

三行都远低于目标:目标 100 kbps 只花掉 47 kbps,目标 400 kbps 也只花掉 78 kbps。 这段素材太简单,编码器在预算之内就够用了。

这和 5.1.2 里 AAC 目标 192 只做到 145 是同一件事。 所以「码率」这个词有两层意思:说目标的时候它是预算上限,说实际的时候它是结果。

要注意第三列的边际收益:目标翻四倍,实际码率只从 47 涨到 78。 说明这段素材的信息量就在那儿,多给的预算它花不出去。

二、定码率下比分辨率会得到反直觉的结果

同一个 200 kbps 目标,两个分辨率:

分辨率 目标码率 字节


160×90 200 kbps 17408 96×54 200 kbps 26194

小画面反而更大,差了 50%——这跟直觉相反。 两者都在「花不完预算」的状态里,谁花得多只取决于编码器在各自尺寸上的决策。

所以定码率模式下比字节没有意义。 要比就得比画质,或者比「谁能用更少的字节达到同样画质」。

三、换定质量,关系就正常了

同一档 CRF,两个分辨率:

分辨率 CRF 字节


160×90 26 4696 96×54 26 3653

小画面掉到 78%——质量固定时,像素少了自然花得少。 这才是可以拿来估容量的一列:CRF 与分辨率定了,字节大致就有了。

所以两种模式的用途完全不同:定质量用来交付,定码率用来控带宽。 拿定码率去估算存储,会算得偏高,因为实际用量可能只有一半。

四、什么时候用哪个

按场景选,不按「哪个更专业」选:

场景 选哪个


要估算流量与存储 定码率,并且设 maxrate 要保证画质一致 定质量 直播、带宽有限 定码率加缓冲区上限 本地存档 定质量

第一行那句「并且设 maxrate」是关键:单给目标码率只是「想花这么多」, 加上 maxrate 与 bufsize 才是「不许超过这么多」——真要卡带宽,两个都得给。

这也是本次实跑三档都「花不完」的根本原因:bit_rate 只传了目标,没给上限, 编码器完全可以少花,而且它确实少花了。

五、动手 Demo:三个目标码率各压一遍,再看两个分辨率

先按 100、200、400 kbps 三个目标各压一遍,比实际码率与目标的比例。 再看同一目标下两个分辨率的字节,最后换成 CRF 26 对照。

import av, os

def enc(dst, w, h, br=None, crf=None):
    c1 = av.open(dst, u"w")
    s1 = c1.add_stream(u"libx264", rate=25)
    s1.width, s1.height, s1.pix_fmt = w, h, u"yuv420p"
    if crf is None:
        s1.bit_rate = br
    else:
        s1.options = {u"crf": u"%d" % crf}
    c0 = av.open(u"s5.mp4")
    for f in c0.decode(video=0):
        for o in s1.encode(f.reformat(width=w, height=h, format=u"yuv420p")):
            c1.mux(o)
    for o in s1.encode(None):
        c1.mux(o)
    c1.close()
    c0.close()
    return os.path.getsize(dst)

for br in (100000, 200000, 400000):
    n = enc(u"br%d.mp4" % (br // 1000), 160, 90, br=br)
    real = n * 8 / 2.0
    print(u"%d kbps -> %d 字节  实际 %d kbps  占 %.0f%%"
          % (br // 1000, n, real / 1000, real * 100 / br))

print(u"160x90 200kbps %d" % enc(u"br_big.mp4", 160, 90, br=200000))
print(u"96x54  200kbps %d" % enc(u"br_small.mp4", 96, 54, br=200000))
c = enc(u"crf_big.mp4", 160, 90, crf=26)
d = enc(u"crf_small.mp4", 96, 54, crf=26)
print(u"CRF 26 160x90 %d" % c)
print(u"CRF 26 96x54  %d  占 %.0f%%" % (d, d * 100.0 / c))

实跑输出:

--------------------------------------------------------------------
5.6.2 控制视频的码率及分辨率
--------------------------------------------------------------------

**① 定码率:三个目标码率各压一遍**

  目标码率     字节      实际码率     占目标
  -------------------------------------------
  100 kbps     11793     47 kbps      47%
  200 kbps     17408     69 kbps      35%
  400 kbps     19722     78 kbps      20%

**三行都远低于目标:目标 100 kbps 只花掉 47 kbps,目标 400 kbps 也只花掉 78 kbps。**
**这段素材太简单,编码器在预算之内就够用了——和 5.1.2 里 AAC 目标 192 只做到 145 是同一件事。**
**所以「码率」这个词有两层意思:说目标的时候它是预算上限,说实际的时候它是结果。**

**② 同一个目标码率,两个分辨率**

  分辨率     目标码率     字节
  ------------------------------
  160×90     200 kbps     17408
  96×54      200 kbps     26194

**小画面反而更大,差了 50%——这跟直觉相反,但原因很简单:**
**两者都在「花不完预算」的状态里,谁省得多只取决于编码器在各自尺寸上的决策。**
**所以定码率模式下比字节没有意义——要比画质,或者比「谁能用更少的字节达到同样画质」。**

**③ 换定质量,关系就正常了**

  分辨率     CRF     字节
  ------------------------
  160×90     26      4696
  96×54      26      3653

**小画面掉到 78%——质量固定时,像素少了自然花得少,这才是可以拿来估容量的一列。**

**④ 什么时候用哪个**

  场景                 选哪个
  --------------------------------------------
  要估算流量与存储     定码率,并且设 maxrate
  要保证画质一致       定质量
  直播、带宽有限       定码率加缓冲区上限
  本地存档             定质量

**第一行那句「并且设 maxrate」是关键:单给目标码率只是「想花这么多」,**
**加上 `maxrate` 与 `bufsize` 才是「不许超过这么多」——真要卡带宽,两个都得给。**

s1.bit_rate 与 s1.options = {"maxrate": ..., "bufsize": ...} 是两回事: 前者是目标,后者才是硬上限。要真卡带宽两个都要给。

f.reformat(width=w, height=h, ...) 一步完成缩放与色彩空间转换, 比先解码成 ndarray 再用别的库缩放快得多,也更不容易出错。

实际码率是用「文件字节乘 8 除时长」算的,这里的时长取 2 秒。 严格来说该用容器里的真实时长,差一点点不影响结论。

30 秒实验

  1. 命令行对照:ffmpeg -i 源.mp4 -b:v 200k -maxrate 200k -bufsize 400k 出.mp4。
  2. 改质量:把 -b:v 换成 -crf 26,看字节怎么变。
  3. 同码率比画质:两档编码器给同样 -b:v,人眼对比谁的块效应少。

常见误区

  1. 以为给了目标码率就一定用满。它只是上限,简单画面上编码器会少花。
  2. 在定码率模式下比字节。两者都没花完预算,比出来的是编码器的决策差异。
  3. 只给 -b:v 就当卡住了带宽。不加 maxrate 与 bufsize 就压不住瞬时峰值。
自测三问(点开对答案) 1. 目标 400 kbps 为什么只花掉 78 kbps? —— 目标只是预算上限。这段素材简单,编码器在预算内就够用了。 2. 为什么小画面在定码率下反而更大? —— 两者都没花完预算,字节取决于各自尺寸上的编码决策,与像素数没有单调关系。 3. 要真卡住带宽还得加什么? —— `maxrate` 与 `bufsize`。只给目标码率压不住瞬时峰值。

相关笔记