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

音频压缩赌的是「听觉掩蔽」:被盖住的声音,扔了也听不见。 目标码率是上限,不是承诺——用不完是正常的。

📝 本节和前面几节的关系

上一节视频靠「帧间重复」省,音频没有这么现成的重复可捡。 这一节讲清楚音频压缩凭什么能把 1411 kbps 压到 128 kbps。

一、音频压缩靠的是掩蔽效应

两个声音同时响,响的那个会把轻的那个盖住——这就是听觉掩蔽。 编码器把频段切成很多条,算一算每条上有多少能量, 被邻段盖住的、或者在听阈以下的,直接不传。

编码器在干的事 依据 能不能回头补


频段切分 听觉的临界带宽 能 算掩蔽阈 心理声学模型 能 丢掉听不见的 掩蔽与听阈 不能 量化剩下的 比特预算 不能

所以音频编码是有损的,而且是有「依据」的有损:丢的是人耳本来也听不清的部分。 这条依据对耳机、对音箱、对老年人的听力都不完全一样——这就是「能听出差别」的来源。

二、码率是上限,不是承诺

同一段纯正弦波,三个目标码率各压一遍:

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


64 kbps 17274 69 kbps 108% 128 kbps 33349 133 kbps 104% 192 kbps 36230 145 kbps 75%

第一二行略微超一点:容器头与编码器补齐也算进文件大小,短素材上占比明显。 第三行只做到 75%——这段素材是单频正弦波,编码器找不到更多值得花字节的地方。

所以「设了 192 就是 192」是错的。码率是「不许超过」的预算上限, 复杂素材会顶到上限,简单素材用不完。要「一定不超过」,那才该用上限模式。

三、三个旋钮:采样率、声道、位深

这三个都能改,但三个都是「降下去就回不来」:

旋钮 常见取值 降了会怎样


采样率 44100/48000 高频细节没了,补回来是补零 声道数 单声道/立体声 空间感没了,混成立体声是复制 位深 16 位/24 位浮点 动态范围变小,小声音被噪声盖住

第二行值得多说一句:单声道转立体声,两个声道内容一模一样,听着「在中间」。 它不是「假的」,但确实没有任何新的空间信息被恢复出来。

所以音频转码的方向应该是「尽量不动」: 采样率与位深照着源走,只在目标设备确实不支持时才降。

四、动手 Demo:三个目标码率各压一遍,回算真实码率

同一段声音按 64、128、192 kbps 各压一遍,再用「字节乘 8 除时长」回算实际码率。 素材 2 秒,所以头部的固定开销会被放大,这也正好能看清「差一点点」是多少。

import av, os

for br in (64000, 128000, 192000):
    fn = u"a%d.m4a" % (br // 1000)
    c1 = av.open(fn, u"w")
    s1 = c1.add_stream(u"aac", rate=44100)
    s1.layout, s1.bit_rate = u"stereo", br
    c0 = av.open(u"s5.mp4")
    for f in c0.decode(audio=0):
        for o in s1.encode(f):
            c1.mux(o)
    for o in s1.encode(None):
        c1.mux(o)
    c1.close()
    c0.close()
    n = os.path.getsize(fn)
    real = n * 8 / 2.0
    print(u"目标 %3d kbps  实测 %3.0f kbps  占 %3.0f%%"
          % (br // 1000, real / 1000, real * 100 / br))

实跑输出:

--------------------------------------------------------------------
5.1.2 音频编解码——码率是唯一的硬指标
--------------------------------------------------------------------

**① 同一段声音,三个码率压一遍**

  目标码率     字节      实测码率     占目标
  -------------------------------------------
  64 kbps      17274     69 kbps      108%
  128 kbps     33349     133 kbps     104%
  192 kbps     36230     145 kbps     75%

**第三列是「字节数 × 8 ÷ 时长」算出来的,只有前两行贴得住目标。**
**第三行差了四分之一:这段素材是纯正弦波,编码器认为再高的码率也换不来更多信息,**
**于是按上限之内花了就行。所以目标码率是「不超过」的意思,不是「一定用到」。**

**② 音频编码的三个可调项**

  项         影响什么           不可逆吗
  -----------------------------------------
  码率       每秒花多少字节     不可逆
  采样率     每秒采多少点       降了不可逆
  声道数     几路声音           降了不可逆

**三个都不可逆:降下去之后再升回来,只是把「少掉的信息」补成 0。**
**所以音频转码的方向应该是「尽量不动」,而不是「先降再升」。**

s1.bit_rate 是给编码器的建议值,不是硬约束。 真要卡死上限,得用 -maxrate 加 -bufsize 这一对。

时长这里写死 2 秒,是因为素材就是 2 秒;真实脚本应该读容器的 duration。 读出来的 duration 也未必精确,音视频轨往往差几十毫秒,算码率时取音频轨的更准。

注意第三行:目标翻了三倍,字节只多了百分之九。 这不是编码器偷懒,是这段信号本来就没那么多信息——这就是有损编码的天花板。

30 秒实验

  1. 换素材:换成真实音乐再压一遍,192 kbps 那一行就会顶到目标附近。
  2. 看上限模式:-b:a 128k -maxrate 128k -bufsize 256k,观察字节是否被卡住。
  3. 换编码器:同码率下 OPUS 通常比 AAC 好听,可以对比文件大小与实际听感。

常见误区

  1. 以为设了码率就一定达到。简单素材用不完,这是正常现象。
  2. 以为 320 kbps 一定比 128 kbps 好。源本身只有 128 kbps 的信息量,重压只会更差。
  3. 以为音频转码不影响画质。只转音频时视频可以用 copy,确实一帧都不动。
自测三问(点开对答案) 1. 音频编码凭什么能丢掉大量数据还听着差不多? —— 靠听觉掩蔽:被强音盖住的、或低于听阈的成分直接不传,人耳本来也分辨不出来。 2. 为什么目标 192 kbps 只做到 145 kbps? —— 这段素材是单频正弦波,信息量有限,编码器在上限之内花完就够了,不会硬凑。 3. 单声道转立体声有意义吗? —— 没有新增信息,只是两个声道放同样内容。真要空间感得从源头就是立体声录的。

相关笔记