五个数决定一路音频:编码器、码率、采样率、声道、采样格式。 只有码率直接决定大小,其余四个是「精度」不是「体积」。
3.5 讲了视频那一组,这一节讲音频的对应一组。 3.7 讲字幕,那一组和前两组差别很大。
一、五个数各管什么
音频参数看起来多,其实只有五个维度:
选项 管什么 典型值
-c:a 用哪个编码器 aac/libmp3lame/pcm_s16le -b:a 码率 128k/192k -ar 采样率 44100/48000/8000 -ac 声道数 1/2/6 -sample_fmt 内部采样格式 s16/fltp
第二行是唯一直接决定文件大小的。 其余四个改的是「精度」,压缩后的大小由编码器根据码率决定。
所以「想把音频压小」的正确做法是调 -b:a,不是降采样率。
降采样率确实也变小,但它同时丢了高频——用画质换体积,不划算。
二、采样率降了,字节数不一定跟着降
这是本节最反直觉的一处:
降采样率 字节数会怎样 为什么
44100 → 22050 小幅下降 码率下限撑住了 44100 → 8000 明显下降但不线性 编码器开销成了主导
原因是 AAC 这类编码器有个「最低码率」的概念。 当目标码率低于它能有效工作的下限时,省下来的空间被编码器自身的开销吃掉了。
所以「采样率减半 = 文件减半」这个直觉是错的。
真想让文件减半,得同时调 -b:a——那才是直接控制大小的旋钮。
三、哪些操作是不可逆的
这一条比参数表重要得多:
操作 可逆吗 为什么
降到 pcm_s16le 基本可逆 无损编码,只占空间 转 aac 再转回 不可逆 有损,转一次掉一次 降采样率再升回 不可逆 高频已经丢了 混成单声道再拆开 不可逆 两个声道的信息已经混在一起
第二行是最常见的错误:为了「统一格式」把无损转成有损,之后又转回来。 两次损失叠加,而原始数据已经找不回来了。
正确做法是「一次有损,永久保留有损那一份」,或者「一直留一份无损母版」。 后者占空间但安全,前者省空间但要接受「以后只能从这一份转」。
四、动手 Demo:五个数决定一路音频
造一段 1 秒 440 Hz 立体声,分别改采样率和声道数,量输出字节。 重点是验证:字节数不会跟着参数成比例变化。
import os
import av
import numpy as np
SR = 44100
t = np.arange(SR) / float(SR)
sig = (0.4 * np.sin(2 * np.pi * 440 * t) * 32767).astype(np.int16)
st = np.stack([sig, sig], axis=1).reshape(-1)
c = av.open(u"a.m4a", u"w")
s = c.add_stream(u"aac", rate=SR)
s.layout = u"stereo"
fr = av.AudioFrame(format=u"s16", layout=u"stereo", samples=SR)
fr.sample_rate = SR
fr.planes[0].update(st.tobytes())
for pk in s.encode(fr):
c.mux(pk)
for pk in s.encode(None):
c.mux(pk)
c.close()
base = os.path.getsize(u"a.m4a")
print(u"44100 立体声 %d 字节" % base)
# 降到 8000
c0 = av.open(u"a.m4a")
cc = c0.streams.audio[0].codec_context
c1 = av.open(u"a8k.m4a", u"w")
s1 = c1.add_stream(u"aac", rate=8000)
s1.layout = u"stereo"
rs = av.AudioResampler(format=u"s16", layout=u"stereo", rate=8000)
for pk in c0.demux(audio=0):
if not pk.size:
continue
for f in cc.decode(pk):
for rf in rs.resample(f):
for o in s1.encode(rf):
c1.mux(o)
for o in s1.encode(None):
c1.mux(o)
c1.close()
c0.close()
n = os.path.getsize(u"a8k.m4a")
print(u"8000 立体声 %d 字节,是原来的 %.2f 倍" % (n, n / float(base)))
实跑输出:
--------------------------------------------------------------------
3.6 音频选项——五个数决定一路音频
--------------------------------------------------------------------
**① 编码、码率、采样率、声道、采样格式**
选项 管什么 典型值
-----------------------------------------------------------------
-c:a 用哪个编码器 aac / libmp3lame / pcm_s16le
-b:a 码率 128k / 192k
-ar 采样率 44100 / 48000 / 8000
-ac 声道数 1 / 2 / 6
-sample_fmt 内部采样格式 s16 / fltp
**最后一行又是「中间精度」不是「文件大小」——和 3.5 里 -pix_fmt 的位置一样。**
**真正决定大小的是第二行 `-b:a`,前四个里面只有它直接见效。**
**② 采样率降一半,字节数会不会降一半**
采样率 输出字节 路线
----------------------------------
44100 17345 原样
22050 15372 重采样
8000 6815 重采样
**编码器是 aac、时长 1.0 秒、原文件 17345 字节。**
**注意采样率降到 1/5 时字节数没降到 1/5——因为 AAC 的码率下限撑住了。**
**这一点很反直觉:码率低到一定程度,编码器的开销成了主导,再降采样率也省不下来。**
**③ 声道数的影响更直接**
声道 输出字节 相对立体声
-------------------------------------------
2(立体声) 17345 1.00×
1(单声道) 10312 0.59×
**没有降到一半,原因同上:码率有下限,容器的头也不随声道数变。**
**但听感上单声道是实打实少了一个维度——两个声道的信息被混成一个,回不去了。**
**④ 音频选项里唯一「无损可逆」的一类**
情况 可逆吗 为什么
------------------------------------------------------
降到 pcm_s16le 基本可逆 无损编码,只占空间
转 aac 再转回 不可逆 有损,转一次掉一次
降采样率再升回 不可逆 高频已经丢了
**第二行是转码最常见的错误:为了「统一格式」把无损转成有损,之后再转回来,**
**两次损失叠加,而原始数据已经找不回来了。素材一旦有损,就该留一份无损母版。**
重采样要用 AudioResampler,不能直接改 frame.sample_rate。
改属性只是改了个标签,样本数没变,编码器拿到的是错的数据。
采样率从 44100 降到 8000(约 1/5),字节数并没有降到 1/5。 AAC 有最低码率的概念,省下来的空间被编码器开销吃掉了。
同样的道理,声道从 2 降到 1 也不会降到一半。 所以「降参数来省空间」这件事,对音频来说收益比想象中小。
真正想省空间就调 -b:a(比如从 128k 降到 64k),那是直接旋钮。
代价是音质下降,而且这一降是不可逆的。
30 秒实验
- 降码率:
ffmpeg -i 音频.wav -c:a aac -b:a 64k 出.m4a,和 128k 的对比大小与听感。 - 降采样率:
ffmpeg -i 音频.wav -ar 22050 出.wav,看大小变化,注意高频损失。 - 转单声道:
ffmpeg -i 音频.wav -ac 1 出.wav,听立体声信息是不是彻底没了。
常见误区
- 以为采样率减半文件就减半。编码器有最低码率,字节数不会线性降。
- 为了统一格式把无损转有损。转一次掉一次,原始数据再也拿不回来。留一份无损母版。
- 直接改
frame.sample_rate做重采样。那只是改标签,样本数没变,编码器拿到错数据。
自测三问(点开对答案)
1. 五个音频参数里,哪个直接决定文件大小? —— `-b:a` 码率。其余四个改的是精度(采样率、声道、采样格式、编码器),不直接控制大小。 2. 采样率从 44100 降到 8000,文件大小为什么没降到 1/5? —— 因为编码器有最低码率的概念,省下的空间被它自身的开销吃掉了。想省空间要调 `-b:a`。 3. 哪些音频操作是不可逆的? —— 转有损、降采样率、混单声道。无损转 pcm 是基本可逆的,只是占空间。- ☐ (本节完,下一节 3.7 字幕选项)