🎬 第十章 SWResample音频重采样理论及案例实战
📄 一句话概括

重采样就是按比例把样本重新数一遍。 但滤波器会「吞」掉最后几个样本,不冲空包就丢尾巴。

📝 本节和前面几节的关系

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 秒实验

  1. 把 8000→16000 的输入改成 80 个样本,看直出与理论差多少(提示:比例更极端)。
  2. 不冲空包连跑十帧 8000→16000,算总样本与理论的差。
  3. 试 44100→44100(同一个采样率),看它是不是原样放过。

常见误区

  1. 以为重采样只是插值。 它还要低通滤波,所以有延迟。
  2. 不冲空包。 结尾几个样本永远拿不到,而且不报错。
  3. 反复转采样率。 每次都有损失,能一次到位就别转两次。
自测三问(点开对答案) 1. 为什么「直接出」总比理论值少几个样本? —— 滤波器有延迟,最后几个样本还在它内部没算出来。 2. 冲空包的作用是什么? —— 告诉它后面没数据了,把肚子里欠的样本算出来。 3. 为什么降采样比升采样更难? —— 降采样前必须低通,否则高频会折成噪音频段(混叠)。

相关笔记