六步:建、填进、填出、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 秒实验
- 把帧数从 10 改成 100,看两遍的差值变不变(提示:差值不变,比例变小)。
- 把每帧样本数改成 10,看差值占的比例有多大。
- 不冲空包但把重采样器
del掉,看样本数会不会变(提示:不会)。
常见误区
- 把冲空包当可选。 它是必做项,跟 close 一个级别。
- 以为不冲会报错。 它一句都不说。
- 只核对样本数不核对时长。 两条都得对。
自测三问(点开对答案)
1. 六步里最容易被漏的是哪一步? —— 第六步冲空包,因为不调也能跑。 2. 漏掉冲空包会丢多少? —— 视比例而定,实测十帧 8000→48000 丢了 96 个样本,2 毫秒。 3. 怎么验自己转对了? —— 样本数与理论一致、时长与输入一致,两条都要。- ☐ (本节完,下一节 10.3.2 使用swr实现PCM重采样案例实战)