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

六步:建、填进、填出、init、convert、冲空包。 第六步漏了不报错,只是每次都少几毫秒。

📝 本节和前面几节的关系

10.2.5 讲了参数,这一节把六步的顺序与后果钉死。 10.3.2 用这六步处理一条真实音轨。

一、六步

每一步缺了都有具体后果。 步 做什么 少了会怎样


一 建上下文 没有转换器 二 填输入参数 不知道从什么转 三 填输出参数 不知道转成什么 四 init 直接崩 五 convert 一帧帧转 没有输出 六 冲空包 丢尾巴,且不报错

前五步的错都会当场暴露(崩、报错、或者干脆没结果), 第六步的错不会。 这就是它最危险的地方。 「能跑」不等于「结果对」,这条在整套书里已经出现过好几次, 音频这边尤其多——因为它没有画面可以看。

二、漏掉第六步的代价

量化一下:十帧、每帧 100 样本、8000 转到 48000。 做法 拿到的样本 与理论差


只 convert,不冲空包 5904 −96 末尾冲一次空包 6000 0

差 96 个样本,按 48000 算是 2 毫秒。 这点时长用耳朵根本听不出来,所以它能一直藏着。 但比例要看料有多长:这十帧本身只有 125 毫秒,丢了 1.6%; 如果是 1 毫秒的短音频,丢的可能就是一大半。

三、为什么它不报错

三条原因叠在一起,就成了「静默出错」。 原因 说明


样本只是留在滤波器里 没算丢,算「还没出来」 上下文没销毁 存货一直在,只是你没取 变化的量很小 几毫秒,听感与肉眼都测不出

第一条最关键:从库的角度看,它没有丢任何东西, 只是「还有一部分没输出」。等你冲空包,它会全给你。 所以规矩要硬:凡是用重采样器,收尾必冲一次空包。 把它当成 close() 一样的存在,别当可选参数。

四、怎么验自己转对了

两条都要对,缺一条都不算对。 检查项 该等于什么


输出总样本 输入总样本 × 目标率 ÷ 源率 输出时长 输入时长(误差小于 1 毫秒)

第一条对、第二条错,说明采样率标错了——样本数没错,但你把它按错的采样率换算成时间了。 两条都对,才说明「转到位了」。 这个双向核对的办法比盯着波形看靠谱得多, 也是验收音频处理的标准动作。

五、在 PyAV 里这六步长什么样

构造函数替掉了前四步。 C 的步 PyAV 里


一、二、三、四 av.AudioResampler(format=, layout=, rate=) 五 r.resample(frame) 返回帧列表 六 r.resample(None) 返回剩下的

两行代码对应六步,看着很省事——但也更容易漏第六步,因为它长得就像「再调一次」。 记住它返回的是列表,所以要 for x in r.resample(fr) 收, 不能写成 fr2 = r.resample(fr)。

六、动手 Demo:SwrContext 的六步,以及漏一步的后果

十帧 8000→48000 的重采样跑两遍:一遍不冲空包,一遍冲,对比样本数。

import av

def run(flush):
    r = av.AudioResampler(format="s16", layout="mono", rate=48000)
    total = 0
    for i in range(10):
        f = av.AudioFrame(format="s16", layout="mono", samples=100)
        f.sample_rate = 8000
        total += sum(x.samples for x in r.resample(f))
    if flush:
        total += sum(x.samples for x in r.resample(None))
    return total

print("不冲", run(False))
print("冲了", run(True))
print("理论", 10 * 100 * 6)

实跑输出:

--------------------------------------------------------------------
SwrContext 的六步,以及漏一步的后果
--------------------------------------------------------------------

**① 六步**

  步     做什么               少了会怎样
  ----------------------------------------------------
  一     建上下文             没有转换器
  二     填输入参数           不知道从什么转
  三     填输出参数           不知道转成什么
  四     init                 直接崩
  五     convert 一帧帧转     没有输出
  六     冲空包               **丢尾巴,且不报错**

**② 第六步漏掉的代价,量化一下**

  做法                     拿到的样本     与理论差
  ----------------------------------------------------
  只 convert,不冲空包     5904           −96
  末尾冲一次空包           6000           0

**差 96 个样本,按 48000 算是 2.0 毫秒。** 这十帧料本来就只有 125 毫秒,丢了 1.6%;
**料越短,丢的比例越大。**

**③ 为什么它不报错**

  原因                     说明
  ---------------------------------------------------
  样本只是留在滤波器里     没算丢,算「还没出来」
  上下文没销毁             存货一直在,只是你没取
  时长差几毫秒             肉眼与耳朵都听不出来

**三条叠起来,就是「静默出错」的完美配方。** 所以规矩要硬:
**凡是用重采样器,结束前必冲一次空包。**

**④ 冲了之后就能对齐**

  检查项         该等于什么
  ----------------------------------------------
  输出总样本     输入总样本 × 目标率 ÷ 源率
  输出时长       输入时长(误差小于 1 毫秒)

**这两条对上,重采样才算做对了。** 只对第一条不够——**样本数对而时长错,说明采样率标错了。**

不冲空包少 96 个样本(5904 对 6000),按 48000 算是 2 毫秒。 两次都不会报错,差别只有那个数字。

30 秒实验

  1. 把帧数从 10 改成 100,看两遍的差值变不变(提示:差值不变,比例变小)。
  2. 把每帧样本数改成 10,看差值占的比例有多大。
  3. 不冲空包但把重采样器 del 掉,看样本数会不会变(提示:不会)。

常见误区

  1. 把冲空包当可选。 它是必做项,跟 close 一个级别。
  2. 以为不冲会报错。 它一句都不说。
  3. 只核对样本数不核对时长。 两条都得对。
自测三问(点开对答案) 1. 六步里最容易被漏的是哪一步? —— 第六步冲空包,因为不调也能跑。 2. 漏掉冲空包会丢多少? —— 视比例而定,实测十帧 8000→48000 丢了 96 个样本,2 毫秒。 3. 怎么验自己转对了? —— 样本数与理论一致、时长与输入一致,两条都要。

相关笔记