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

swr 要你交代六个参数:进三个、出三个。 它不只转采样率,还顺手转格式与声道——三件事一起做。

📝 本节和前面几节的关系

10.2 讲的是音频本身,这一节进到「谁来转」这件事。 10.3 会把这个流程拆成六步细讲。

一、六个参数

输入三个、输出三个,少一个都不行。 侧 参数 含义


输入 format 进来的样本格式 输入 layout 进来的声道布局 输入 rate 进来的采样率 输出 format 要的样本格式 输出 layout 要的声道布局 输出 rate 要的采样率

PyAV 里只写输出那三个,输入那三个是从你送进去的帧上读的。 这是很贴心的简化,但也藏了一件事:输入变了它不会提醒你。 所以整条链上如果中途换了输入格式,最好重建一个重采样器,别指望它自己跟着变。

二、它一次能干三件事

一帧进去,三个属性同时变了。 项 进 出


格式 fltp s16 布局 stereo mono 采样率 44100 48000 样本数 441 480

441 个样本按 44100 算是 10 毫秒,480 个按 48000 算也是 10 毫秒。 时长没变,只是换了个刻度表达。 这也是为什么它的名字里除了 sample 还带 format 与 matrix: libswresample 实际做了「重采样 + 重格式 + 重矩阵」三件事。 所以别把它当成「只改采样率的工具」,它是音频格式的总转换器——理解这一点,很多用法就顺了。

三、C 的五个函数

建、设、init、转、放,五行。 C 的 API PyAV 里


swr_alloc av.AudioResampler(...) swr_alloc_set_opts2 构造时传的三个参数 swr_init 构造里做掉了 swr_convert resample(frame) swr_free 交给垃圾回收

顺序不能乱:先设参数、再 init、然后才 convert。 跳过 init 直接崩——因为滤波器系数还没算出来。 PyAV 把这些压进了构造函数,所以你在 Python 里看不到 init 这一步。 但知道它在,就能理解为什么「构造完才能用」。

四、一帧进不了一帧出

这是音频与视频最大的手感差别。 送进去 第一帧直接出 理论该有


800 样本 @ 44100 → 8000 129 145

出的比理论少 16 个,因为滤波器肚子里还没攒满。这 16 个要等冲空包才吐出来。 所以「一帧进、一帧出」这种假设在音频上完全不成立。 代码里必须用循环收帧,不能用「转换一次拿一帧」。 这条与视频的 8.2 正好相反:视频是「送一个包可能得零帧或多帧」, 音频是「送一帧可能得零帧或一帧,且数量对不上」——两边都不能假设一一对应。

五、什么时候该重建

三种情况必须重建,不能复用。 情况 为什么


目标参数变了 内部系数按旧参数算的 输入格式变了 它不会自己发现 切换素材/音轨 滤波器状态是旧素材的

第三条最隐蔽:两个素材接起来播,中间不重建, 上一段留在滤波器里的那点尾巴会「漏」到下一段开头,听感是接缝处有一声轻响。 规矩很简单:换素材就重建。 反正构造一个重采样器很便宜。

六、动手 Demo:swr 要你交代哪三件事

一帧同时改格式、声道、采样率;再验一次「送多少不一定出多少」。

import numpy as np
import av

# 一帧同时改三样:fltp/stereo/44100 -> s16/mono/48000
a = np.zeros((2, 441), dtype=np.float32)
f = av.AudioFrame.from_ndarray(a, format="fltp", layout="stereo")
f.sample_rate = 44100

r = av.AudioResampler(format="s16", layout="mono", rate=48000)
out = list(r.resample(f)) + list(r.resample(None))
for x in out:
    print(x.format.name, x.layout.name, x.sample_rate, x.samples)

# 送多少就出多少吗:8000/44100 -> 8000,理论 145
r2 = av.AudioResampler(format="s16", layout="mono", rate=8000)
g = av.AudioFrame(format="s16", layout="mono", samples=800)
g.sample_rate = 44100
o1 = r2.resample(g)
print(sum(x.samples for x in o1), int(round(800 * 8000 / 44100.0)))

实跑输出:

--------------------------------------------------------------------
swr 要你交代哪三件事
--------------------------------------------------------------------

**① 六个参数,进三个出三个**

  侧       参数       含义
  --------------------------------------
  输入     format     进来的样本格式
  输入     layout     进来的声道布局
  输入     rate       进来的采样率
  输出     format     要的样本格式
  输出     layout     要的声道布局
  输出     rate       要的采样率

**六个都交代清楚,它才知道该做什么。** 而 PyAV 里只写输出那三个——输入那三个是从你送的帧上读的。

**② 一口气改三样**

  项         进         出
  -------------------------------
  格式       fltp       s16
  布局       stereo     mono
  采样率     44100      48000
  样本数     441        480

**一帧进去,格式、声道、采样率三样同时变了。**
这就是 swr 的名字里「resample」之外还带「reformat」与「rematrix」的原因——**它一次能干三件事。**

**③ C 的 API 对照**

  C 的 API                PyAV 里
  -----------------------------------------------------
  swr_alloc               av.AudioResampler(...)
  swr_alloc_set_opts2     构造时传的三个参数
  swr_init                构造里做掉了
  swr_convert             resampler.resample(frame)
  swr_convert(NULL)       resample(None) 冲空包
  swr_free                交给垃圾回收

**五行 C 里要自己写的,PyAV 里两行就完了。** 但顺序不能乱:
**先设参数、再 init、然后才 convert**——跳过 init 就直接崩。

**④ 送多少就出多少吗**

  项               值
  -------------------------------------
  送进去           800 样本 @ 44100
  第一帧直接出     129 样本
  理论该有         145 样本

**出得比理论少**——滤波器肚子里还没攒满(或还留着上一批的尾巴),
差的那 16 个样本要等冲空包才吐出来。**所以「一帧进一帧出」这种假设在音频上完全不成立。**

格式、声道、采样率三样一起变,样本数从 441 变 480,时长不变。 第一帧直出 129,理论 145,差的 16 个要等冲空包。

30 秒实验

  1. 把目标再改成 format="fltp", layout="stereo", rate=48000,看输出 shape。
  2. 送一个 44100 的帧、目标也是 44100,看输出样本数是不是原样。
  3. 连着送三帧,看第一帧与后两帧的输出样本数是否不同。

常见误区

  1. 以为 swr 只改采样率。 格式与声道一起改。
  2. 以为一帧进一帧出。 数量对不上是常态。
  3. 输入格式变了还复用同一个重采样器。 它不会提醒你。
自测三问(点开对答案) 1. swr 要交代哪六个参数? —— 输入与输出各三个:format、layout、rate。 2. 它一次能做几件事? —— 三件:重采样、换格式、重映射声道。 3. 什么时候必须重建重采样器? —— 目标参数变、输入格式变、换素材时都要重建。

相关笔记