同码率下 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 秒实验
- 命令行对照:
ffmpeg -i 出.m4a -c:a libmp3lame -b:a 128k 出.mp3。 - 换码率:
-b:a 320k,字节会翻两倍多,但源只有 128k 的信息量,听感不会变好。 - 看延迟:
ffprobe -show_entries stream=start_time,MP3 常有一个小正数。
常见误区
- 以为 320 kbps 的 MP3 比 128 kbps 的 AAC 好。源信息量才是上限。
- 忽略编码延迟。拼接、对齐字幕时,那几十毫秒的差会露出来。
- 在流程中间反复转格式。每转一次都掉一档,且文件并不会更小。
自测三问(点开对答案)
1. MP3 一包多少采样点?和 AAC 一样吗? —— 1152 点,不一样。AAC 是 1024 点,两者都接近 2 的幂。 2. 为什么解码回来的点数比「包数乘 1152」少? —— 编码器在头尾加了填充,解码器读文件头里的标记后自动裁掉了。 3. 为什么说「有损转有损」比「无损转有损」更糟? —— 第一次压缩已经丢了信息,第二次是在残缺的信息上再压一遍,误差会叠加放大。- ☐ (本节完,下一节 5.3.3 AAC 转码为 AC-3)