重采样就是按比例把样本重新数一遍。 但滤波器会「吞」掉最后几个样本,不冲空包就丢尾巴。
10.1.2 讲的是「样本怎么存」,这一节讲「样本数怎么改」。 10.1.4 用手写代码做同样的事,再对比库的做法。
一、四组换算
送一小段进去,看直接出多少、冲空包又出多少。 源→目标 送进去 直接出 冲空包 合计
8000→16000 800 1568 32 1600 16000→8000 1600 784 16 800 44100→48000 4410 4783 17 4800 48000→44100 4800 4394 16 4410
「合计」那一列与理论值分毫不差(送进去 × 目标率 ÷ 源率),但「直接出」全都少一点。 少的那部分去哪了? 重采样不是简单插值,它要过一道低通滤波器; 滤波器有延迟,最后几个样本还在它肚子里。
二、少的那几个就是「尾巴」
四组各少了多少,一张表说清。 源→目标 直接出少了
8000→16000 32 个样本 16000→8000 16 个样本 44100→48000 17 个样本 48000→44100 16 个样本
这几个数没有简单公式,别想着「自己算一下补几个零」。 唯一可靠的办法是冲空包,让库把它吐出来。 换算成时间是几毫秒(32 个样本按 16000 算才 2 毫秒),所以这个错平时听不出来, 只在「结尾被切掉一小块」时才暴露。 这就是音频处理里最典型的一类错:不报错、听不出、但确实丢了数据。
三、升采样与降采样不是对称的
方向不同,难点不同。 方向 做什么 难点
升采样 在样本之间补点 补得平滑 降采样 丢掉一部分点 丢之前必须先低通
升采样补错了只是「有毛刺」,降采样漏了低通就是「有杂音」。 后者更难接受,因为它引入的是原本不存在的声音。 原因是混叠:不滤波直接抽点,高频会折到低频去, 听起来像金属摩擦。 这是降采样必须过滤波器的根本原因。
四、滤波器为什么会有延迟
因为它要用「前后若干点」来算一个点。 要算的点 需要什么 结果
第 n 个 它前后各若干点 后面的还没来,等 结尾那些 再往后没有点 永远等不到,靠冲空包
所以延迟不是 bug,是滤波器的物理代价。 冲空包的意思是「后面没有新数据了,把欠的算出来吧」。 这条逻辑在视频那边也出现过:8.2 的解码器要冲空包, 因为尾部几帧也还压在里面。规律是一样的——凡是带状态的处理,收尾都要冲。
五、重采样会损失音质吗
会,但要看怎么转。 做法 损失
44100 → 48000 理论上无损,只是插值 48000 → 44100 要低通,高频被切掉一点 来回转多次 每次都过一遍滤波,累积损失
降采样一定会丢频率:48k 能到 24000 Hz,转到 44.1k 就只能到 22050 Hz, 那一段被低通滤掉了——这是数学决定的,不是实现不好。 所以工程上的规矩是「一次转到位」:能在源头就采成目标采样率,就别中途转。 来回转更糟:每转一次都要过一遭滤波器,虽然每次损失很小,但会累积。
六、动手 Demo:重采样就是「按比例重新数一遍」
四组采样率各送一小段,看直接出多少、冲空包又出多少。
import av
def conv(src, dst, n):
r = av.AudioResampler(format="s16", layout="mono", rate=dst)
f = av.AudioFrame(format="s16", layout="mono", samples=n)
f.sample_rate = src
got = sum(x.samples for x in r.resample(f))
tail = sum(x.samples for x in r.resample(None))
return got, tail, int(round(n * dst / float(src)))
for src, dst, n in ((8000, 16000, 800), (16000, 8000, 1600),
(44100, 48000, 4410), (48000, 44100, 4800)):
got, tail, want = conv(src, dst, n)
print(src, dst, n, got, tail, got + tail, want)
实跑输出:
--------------------------------------------------------------------
重采样就是「按比例重新数一遍」
--------------------------------------------------------------------
**① 四组换算,各送一小段**
源→目标 送进去 直接出 冲空包 合计 理论值
--------------------------------------------------------------------
8000→16000 800 1568 32 1600 1600
16000→8000 1600 784 16 800 800
44100→48000 4410 4783 17 4800 4800
48000→44100 4800 4394 16 4410 4410
**合计一列与理论值分毫不差。** 但「直接出」那一列全都比理论值少一点,
**少的那部分被重采样滤波器留在肚子里了。**
**② 那点「留在肚子里」的是多少**
源→目标 直接出少了多少
----------------------------------
8000→16000 32 个样本
16000→8000 16 个样本
44100→48000 17 个样本
48000→44100 16 个样本
**所以冲空包不是可选项。** 少则十几、多则三十二个样本,
**换算成时间也就几毫秒,但它是「音频结尾被切掉一小块」的真实来源。**
**③ 比例越怪,延迟越难猜**
**8000→16000 少了 32,16000→8000 少了 16,44100→48000 少了 17。**这几个数没有简单公式可推,
**只能靠冲空包把它们要回来**,所以别想着「自己算一下补几个零」。
「直接出」总是比理论值少,少的部分被滤波器留在肚子里。 冲空包之后合计与理论值完全相等,一个样本不差。
30 秒实验
- 把 8000→16000 的输入改成 80 个样本,看直出与理论差多少(提示:比例更极端)。
- 不冲空包连跑十帧 8000→16000,算总样本与理论的差。
- 试 44100→44100(同一个采样率),看它是不是原样放过。
常见误区
- 以为重采样只是插值。 它还要低通滤波,所以有延迟。
- 不冲空包。 结尾几个样本永远拿不到,而且不报错。
- 反复转采样率。 每次都有损失,能一次到位就别转两次。
自测三问(点开对答案)
1. 为什么「直接出」总比理论值少几个样本? —— 滤波器有延迟,最后几个样本还在它内部没算出来。 2. 冲空包的作用是什么? —— 告诉它后面没数据了,把肚子里欠的样本算出来。 3. 为什么降采样比升采样更难? —— 降采样前必须低通,否则高频会折成噪音频段(混叠)。- ☐ (本节完,下一节 10.1.4 C语言实现PCM重采样)