降采样容易,省空间;升采样补不出高频,只会把文件撑大。 「采样率高」不等于「高频多」,这是重采样里最大的误解。
上一节改的是「存几份」,这一节改的是「每秒记多少个点」。 这两个参数合起来决定音频的原始体积:字节 = 点 × 声道 × 每点字节。
一、三个采样率,时长都一样
同一段声音按三档采样率各存一份:
采样率 采样点 字节 时长
44100 89088 178176 2.02 秒 22050 44544 89088 2.02 秒 48000 96967 193934 2.02 秒
第四列三行相同,说明转采样率不改时长;变的是「每秒记多少个点」。 22050 那一点数正好是一半,字节也是一半——这就是「降采样省空间」的字面含义。
48000 那一行点数比 44100 多了百分之九,字节也多了百分之九。 这一档是为视频准备的:视频的帧率多是 24、25、30 的倍数,音频取 48 kHz 才好对齐。
二、升回来是空的
拿一个 15 kHz 的正弦波做实验:先降到 22050,再升回 44100:
阶段 采样点 均方根
原始 44100(15 kHz) 44100 0.354 降到 22050 22034 — 再升回 44100 44068 0.001
第三行的均方根掉到 0.001,几乎归零——声音没了。 因为 22050 的奈奎斯特频率只有 11025 Hz,15 kHz 在那一步就被彻底滤掉了。
升回 44100 只是把点补密,补出来的全是零。「采样率高」不等于「高频多」。 所以「把 44.1 kHz 升到 96 kHz 提升音质」这件事,物理上不可能成立。
三、什么时候该改采样率
三种需求,三种做法:
需求 做法
体积太大 先降采样率,再降码率 设备不认 只改到设备要求的那一档 想「提升音质」 做不到,重采不能凭空造信息
第一行要注意顺序:先降采样率再降码率,比只降码率省得多。 因为码率是压缩后的目标,采样率直接砍掉了一半的原始数据。
第三行是很多人踩过的坑:升采样之后文件变大了,声音一点没变好, 而且下游还得为多出来的这些零付存储与带宽。
四、动手 Demo:三档采样率各存一份,再试一次升回来
把素材音频按 44100、22050、48000 各解一份裸 PCM,比点数与字节。 再拿一个 15 kHz 的正弦波走一遍「降下去再升回来」,看高频能不能回来。
import av, os
import numpy as np
def to_pcm(dst, rate, src=u"s5.mp4"):
c1 = av.open(dst, u"w", format=u"s16le")
s1 = c1.add_stream(u"pcm_s16le", rate=rate)
s1.layout = u"mono"
rs = av.AudioResampler(format=u"s16", layout=u"mono", rate=rate)
c0 = av.open(src)
n = 0
for f in c0.decode(audio=0):
for rf in rs.resample(f):
n += rf.samples
for o in s1.encode(rf):
c1.mux(o)
for rf in rs.resample(None):
n += rf.samples
for o in s1.encode(rf):
c1.mux(o)
for o in s1.encode(None):
c1.mux(o)
c1.close()
c0.close()
return n, os.path.getsize(dst)
for sr in (44100, 22050, 48000):
n, b = to_pcm(u"sr%d.pcm" % sr, sr)
print(u"%d Hz %d 点 %d 字节 %.2f 秒" % (sr, n, b, n / float(sr)))
SR = 44100
t = np.arange(SR) / float(SR)
sig = (0.5 * np.sin(2 * np.pi * 15000 * t)).astype(np.float32)
fr = av.AudioFrame.from_ndarray(np.ascontiguousarray(sig.reshape(1, -1)),
format=u"fltp", layout=u"mono")
fr.sample_rate = SR
down = av.AudioResampler(format=u"fltp", layout=u"mono", rate=22050)
up = av.AudioResampler(format=u"fltp", layout=u"mono", rate=SR)
mid = down.resample(fr)
back = up.resample(mid[0]) + up.resample(None)
out = np.concatenate([b.to_ndarray()[0] for b in back])
r0 = float(np.sqrt((sig.astype(np.float64) ** 2).mean()))
r1 = float(np.sqrt((out[:len(sig)].astype(np.float64) ** 2).mean()))
print(u"原始 15 kHz 均方根 %.3f" % r0)
print(u"降到 22050 得 %d 点" % mid[0].samples)
print(u"升回 44100 得 %d 点 均方根 %.3f" % (len(out), r1))
实跑输出:
--------------------------------------------------------------------
5.5.2 采样率转换——降下去容易,升回来是空的
--------------------------------------------------------------------
**① 单声道文件,三个采样率各存一份**
采样率 采样点 字节 时长
-----------------------------------------
44100 89088 178176 2.02 秒
22050 44544 89088 2.02 秒
48000 96967 193934 2.02 秒
**第四列三行相同,说明转采样率不改时长;变的是「每秒记多少个点」。**
**22050 那一点数正好是一半,字节也是一半——这就是「降采样省空间」的字面含义。**
**② 升回来能不能恢复高频**
阶段 采样点 均方根
-------------------------------------------
原始 44100(15 kHz) 44100 0.354
降到 22050 22034 —
再升回 44100 44068 0.001
**第三行的均方根掉到 0.001,几乎归零。**
**因为 22050 的奈奎斯特频率只有 11025 Hz,15 kHz 在那一步就被彻底滤掉了。**
**升回 44100 只是把点补密,补出来的全是零——「采样率高」不等于「高频多」。**
**③ 所以转换的正确顺序**
需求 做法
----------------------------------------------
体积太大 先降采样率,再降码率
设备不认 只改到设备要求的那一档
想「提升音质」 做不到,重采不能凭空造信息
**第三行是很多人踩过的坑:把 44.1 kHz 的素材「升」到 96 kHz,文件变大了,声音一点没变好。**
AudioResampler 用完要再次调用 resample(None) 收尾,
把内部缓冲的尾巴吐出来——不收尾会少几十个点。
15 kHz 这个频率是故意选的:它高于 22050 的一半,所以必然被滤掉。 换成 5 kHz 再试一次,你会看到几乎完整的还原——这就是奈奎斯特线的意义。
那段正弦波是代码自己造的,不需要外部音频文件, 所以这个实验可以脱离素材单独跑。
30 秒实验
- 命令行对照:
ffmpeg -i 源.mp4 -ar 48000 出.wav。 - 看奈奎斯特线:把
15000改成5000,均方根会保住。 - 只降码率不降采样率:对比字节,体会两种省法的差别。
常见误区
- 以为采样率越高音质越好。超过原始素材的采样率,多出来的全是补的零。
- 降采样后想升回去。被滤掉的高频找不回来,升回去只是把点补密。
- 忘记
resample(None)收尾。最后几十个点会丢掉,时长差一丁点。
自测三问(点开对答案)
1. 为什么 22050 的奈奎斯特频率是 11025? —— 奈奎斯特频率等于采样率的一半,超过这个频率的成分无法被还原。 2. 把 44.1 kHz 升到 96 kHz 能提升音质吗? —— 不能。原始信号里没有的信息,重采样造不出来,只会让文件变大。 3. 降采样为什么比降码率省得多? —— 降采样直接砍掉原始数据量,降码率只是让压缩更狠,前者是减法不是压缩。- ☐ (本节完,下一节 5.5.3 采样格式转换及音频重采样)