应使用IIRFilter类封装差分方程,而非手写递归或硬编码系数;因其可确保相位延迟可控、数值稳定,避免发散或失真。

直接用 IIRFilter 类封装差分方程是最稳妥的路径,而不是手写递归逻辑或硬编码系数。IIR 滤波器在音频处理中必须控制相位延迟和数值稳定性,这两点一旦出错,输出就可能发散或失真。
为什么不能直接套用 y[n] = α·x[n] + (1−α)·y[n−1] 做音频高通
这个公式只适用于一阶低通(EMA),它没有零点,无法构造高通响应。强行“反相减”得到高通会破坏系统稳定性,尤其在浮点累积误差下容易溢出。真实高通需要完整二阶节(biquad)结构,含明确的零点配置(如 z = 1 和 z = −1)。
常见错误现象:output 值随时间持续漂移、突然爆为 inf 或 nan。
正确做法是:用双线性变换从模拟原型(如巴特沃斯)导出离散域传递函数 H(z) = (b₀ + b₁z⁻¹ + b₂z⁻²) / (1 + a₁z⁻¹ + a₂z⁻²),再拆解为标准 biquad 形式。
如何安全地生成 IIR 系数并喂给 C++ 滤波器
别自己推导 Z 变换——用 Python 的 scipy.signal.butter 或 MATLAB 的 butter 生成系数,再转成 C++ 可读格式。例如设计一个 40 Hz 截止、采样率 48 kHz 的二阶巴特沃斯低通:from scipy.signal import butterb, a = butter(2, 40 / (48000/2), btype='low')
输出的 b 和 a 是浮点数组,需四舍五入到 double 精度后复制进 C++ 初始化列表。
注意:a[0] 必须为 1.0,否则滤波器状态更新公式会错;若生成的 a[0] ≠ 1.0,要手动归一化整个 a 和 b 数组。
IIR 滤波器类必须支持 reset() 和内部状态隔离
音频流常需切片处理或实时重置(如播放暂停后重新开始),此时不调用 reset() 会导致前一段残留状态污染新数据。
典型坑:process() 函数里只存了 y_prev1, y_prev2,但没清零输入延迟项 x_prev1, x_prev2,导致首次调用输出异常。
实操建议:
• 所有延迟变量(x_delay, y_delay)统一存在结构体中,reset() 直接 memset 为 0
• 每个滤波器实例独占状态,禁止静态变量或全局缓存
• 对 stereo 音频,左右声道必须用两个独立滤波器实例,不能共享状态
立即学习“C++免费学习笔记(深入)”;
浮点精度与嵌入式平台的实际取舍
在 ARM Cortex-M4 等带 FPU 的 MCU 上,用 float 足够;但在无硬件浮点的平台(如 Cortex-M0),double 会严重拖慢性能,此时应改用 Q15 定点运算,并预计算好缩放后的整数系数。
关键细节:
• double 在 x86_64 上没问题,但某些 DSP 芯片(如 TI C55x)原生只支持 32 位定点
• 若用 std::vector<double></double> 存系数,确保编译器没做自动向量化导致内存对齐异常
• 音频缓冲区长度若为非 2 的幂,biquad 循环中避免用模运算,改用指针回绕(if (ptr >= end) ptr = begin;)更高效
真正难的不是写出能跑的代码,而是让滤波器在不同采样率、不同截止频率、不同信噪比输入下都不发散——这取决于系数生成是否严格归一化、状态重置是否彻底、以及浮点运算顺序是否规避了大数吃小数问题。


















