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

同码率下 MP3 与 AAC 的字节几乎一样,差别在包结构与听感。 但有损转有损是「在近似上再近似」,能不做就不做。

📝 本节和前面几节的关系

上一节是「原始 → 压缩」,这一节是「压缩 → 压缩」,多了一层先解码。 这一层解码是必须的:编码器只吃采样点,不吃别人的码流。

一、两种格式的包结构不一样

同为 128 kbps,两边的账目对照:

项 AAC MP3


字节 33555 33480 包数 88 79 每包采样点 1024 1152

字节几乎一样,包数却差九个——因为两边一包装的东西不一样。 1024 与 1152 都是「2 的幂附近的整数」,为了做频域变换方便。

第一列那个相同说明一件事:码率是共同的分母。 只要码率一样、时长一样,总字节必然接近,这是算术决定的。

二、解码回来为什么能对上

79 包乘 1152 是 91008 点,比原始的 89088 多出 1920 点:

项 值


包数乘 1152 91008 点 实际解出 89088 点 差 1920 点

多出来的是编码器在开头与结尾补的填充,不是丢了声音。 LAME 会把这两个值写进文件头,解码器读到就自动裁掉,所以听感上对得齐。

这也是为什么「MP3 片段拼接」经常差几十毫秒:不是所有工具都读那个标记。 拼接时要按采样点对齐,别指望时间戳。

三、两代有损叠在一起的代价

两条路线的压缩次数不一样:

路线 压了几次 信息损失


PCM 直接转 MP3 1 次 丢一次 AAC 转 MP3 2 次 两次叠加

第二行不是「1 加 1 等于 2」:第一次压缩已经把细节换成一套近似描述, 第二次要在这套近似上再近似,误差会集中在高频与瞬态上。

所以有损转有损要尽量少做。宁可留着源文件,也别在中间来回倒—— 每倒一次,画质/音质就掉一档,而文件大小一点没省。

四、动手 Demo:把 AAC 解回采样点,再压成 MP3

输入是上一节编出来的 m4a,输出 mp3,都是 128 kbps。 然后数包数、算理论点数、再解码回来数实际点数,看差多少。

import av, os

c0 = av.open(u"aac128.m4a")
c1 = av.open(u"out.mp3", u"w")
s1 = c1.add_stream(u"libmp3lame", rate=44100)
s1.layout, s1.bit_rate = u"stereo", 128000
n = 0
for f in c0.decode(audio=0):
    for o in s1.encode(f):
        c1.mux(o)
        n += 1
for o in s1.encode(None):
    c1.mux(o)
    n += 1
c1.close()
c0.close()
print(u"mp3 %d 字节  包数 %d" % (os.path.getsize(u"out.mp3"), n))

c0 = av.open(u"out.mp3")
got = 0
for f in c0.decode(audio=0):
    got += f.samples
c0.close()
print(u"理论 %d 点  实际 %d 点  差 %d" % (n * 1152, got, n * 1152 - got))

实跑输出:

--------------------------------------------------------------------
5.3.2 AAC 转码为 MP3——两代有损叠在一起
--------------------------------------------------------------------

**① 把 AAC 解回采样点,再压成 MP3**

  项             AAC       MP3
  -------------------------------
  字节           33349     33480
  包数           88        79
  每包采样点     1024      1152

**同样是 128 kbps,两种格式的字节几乎一样——码率是它们共同的分母。**
**但包的大小不一样:AAC 一包 1024 点、MP3 一包 1152 点,这是两种格式的根本差异。**

**② 解码回来能对上,靠的是头里的延迟信息**

  项              值
  -------------------------
  包数乘 1152     91008 点
  实际解出        89088 点
  差              1920 点

**差的那 1920 点,是编码器在开头与结尾补的填充,不是丢了声音。**
**LAME 会把这两个值写进文件头的标记里,解码器读到就自动裁掉,所以听感上是对得齐的。**
**这也是为什么「MP3 片段拼接」经常差几十毫秒:不是所有工具都读那个标记。**

**③ 两代有损叠在一起会怎样**

  路线               压了几次     信息损失
  ---------------------------------------------
  PCM 直接转 MP3     1 次         只丢一次
  AAC 转 MP3         2 次         两次损失叠加

**第二行不是「1 加 1 等于 2」那么简单:第一次压缩已经把细节换成了一套近似描述,**
**第二次压缩要在这套近似上再近似一遍,误差会集中在高频与瞬态上。**
**所以有损转有损要尽量少做,宁可留着源文件,也别在中间来回倒。**

libmp3lame 是编码器名,mp3 是它能产的格式名。 写 add_stream(u"mp3") 多数情况也能过,因为容器会替你把编码器选出来。

MP3 的帧长是 1152 点,写死在规范里。 所以算点数时乘 1152,不要乘 AAC 的 1024。

差的那 1920 点在开头与结尾,解码器按文件头里的标记裁掉了。 如果换个不认这个标记的库,你会看到开头多出十几毫秒的静音。

30 秒实验

  1. 命令行对照:ffmpeg -i 出.m4a -c:a libmp3lame -b:a 128k 出.mp3。
  2. 换码率:-b:a 320k,字节会翻两倍多,但源只有 128k 的信息量,听感不会变好。
  3. 看延迟:ffprobe -show_entries stream=start_time,MP3 常有一个小正数。

常见误区

  1. 以为 320 kbps 的 MP3 比 128 kbps 的 AAC 好。源信息量才是上限。
  2. 忽略编码延迟。拼接、对齐字幕时,那几十毫秒的差会露出来。
  3. 在流程中间反复转格式。每转一次都掉一档,且文件并不会更小。
自测三问(点开对答案) 1. MP3 一包多少采样点?和 AAC 一样吗? —— 1152 点,不一样。AAC 是 1024 点,两者都接近 2 的幂。 2. 为什么解码回来的点数比「包数乘 1152」少? —— 编码器在头尾加了填充,解码器读文件头里的标记后自动裁掉了。 3. 为什么说「有损转有损」比「无损转有损」更糟? —— 第一次压缩已经丢了信息,第二次是在残缺的信息上再压一遍,误差会叠加放大。

相关笔记