第四章 数字信号处理基础回顾:采样定理、FFT与STFT、滤波器设计(FIR/IIR),在车规芯片上的定点数实现考量
各位同学,欢迎来到第四讲。
说实话,做语音降噪,绕不开数字信号处理。很多刚入行的朋友觉得DSP理论太枯燥,上来就调算法库。但我要说,在车规芯片上,不懂底层DSP原理,你连定点数溢出了怎么死的都不知道。
这一章,我们快速回顾三个核心知识点:采样定理、FFT/STFT、滤波器设计。重点不是讲数学推导,而是讲它们在车规芯片上落地时,那些「坑」在哪里。
4.1 采样定理:别让混叠毁了你的降噪
采样定理,说白了就是一句话:采样频率必须大于信号最高频率的两倍。否则就会混叠。
我在项目中遇到过一件事。某次做车内语音增强,麦克风采集的语音听起来正常,但降噪算法一跑,高频段出现奇怪的「吱吱」声。查了半天,发现是ADC前端的抗混叠滤波器设计得太随意,16kHz采样率下,8kHz以上的高频噪声直接折叠到了语音频带内。嗯,这就是典型的混叠。
在车规芯片上,采样率通常固定为8kHz、16kHz或48kHz。我个人习惯用16kHz,因为语音主要能量在4kHz以下,16kHz采样既能保证质量,又不会让FFT点数太大。
4.2 FFT与STFT:时频分析的「手术刀」
FFT是数字信号处理的基石。但很多同学只会在MATLAB里调fft()函数,到了嵌入式C代码里就懵了。
为什么?因为定点数实现。
车规芯片大多是定点DSP或ARM Cortex-R系列,没有硬件浮点单元。你写个浮点FFT,跑一次要几十毫秒,实时性根本没法保证。
我建议的做法是:
- 使用16位或32位定点数:把浮点系数提前量化成Q15或Q31格式。
- 注意溢出:FFT蝶形运算中,加法可能溢出。我习惯在每级蝶形运算后做一次饱和截位,或者用移位来防止溢出。
- 查表法代替计算:旋转因子(twiddle factor)提前算好存成表格,运行时直接查表,省去三角函数计算。
经验之谈: 我曾经在某个项目里,用Cortex-M7做256点FFT,浮点版本耗时约1.2ms,改成定点Q15版本后降到0.15ms。差距就是这么大。
STFT(短时傅里叶变换)其实就是加窗后的FFT。窗函数的选择很关键。汉宁窗、汉明窗是常用选择。我个人偏爱汉宁窗,因为它的旁瓣衰减快,适合语音降噪场景。
你想想看,STFT的帧长和帧移怎么定?帧长决定了频率分辨率,帧移决定了时间分辨率。我一般用20ms帧长(320点@16kHz),10ms帧移。这样频率分辨率够用,时间上也能捕捉到语音的快速变化。
4.3 滤波器设计:FIR vs IIR,车规芯片上的选择
滤波器设计,是降噪算法的核心模块之一。FIR和IIR,各有千秋。
| 特性 | FIR | IIR |
|---|---|---|
| 稳定性 | 绝对稳定 | 可能不稳定(极点需在单位圆内) |
| 相位特性 | 线性相位(无相位失真) | 非线性相位(有相位失真) |
| 计算量 | 较大(阶数高) | 较小(阶数低) |
| 定点实现难度 | 容易(无反馈) | 较难(反馈环路易溢出) |
在车规芯片上,我个人更倾向于FIR。为什么?
- 稳定性有保障:车规芯片工作温度范围宽(-40°C到125°C),IIR滤波器的极点可能因温度漂移而偏移,导致不稳定。FIR没有这个问题。
- 定点实现简单:FIR只有乘加运算,没有反馈。你只需要注意累加器的位宽,防止溢出即可。
- 线性相位:语音降噪中,相位失真会引入「金属声」或「混响感」。FIR的线性相位特性可以避免这个问题。
小技巧: 如果你非要用IIR,我建议用双二阶节(Biquad)级联形式。每个Biquad的系数都做归一化,并在内部做饱和处理。我曾经在车规芯片上实现过8阶IIR,用4个Biquad级联,定点Q31格式,跑起来还算稳定。
4.4 定点数实现的三个关键考量
好了,前面提到了很多次「定点数」。这里我专门展开讲一下。
第一,数据格式选择。 车规芯片常见的有Q15(16位)、Q31(32位)。语音信号动态范围大约40-60dB,用Q15基本够用。但如果你做FFT,中间结果可能放大,建议用Q31。
第二,溢出处理。 这是最容易出问题的地方。我见过一个案例,算法在PC上跑得好好的,移植到芯片上就出现「爆音」。查下来是某个中间变量溢出了,符号位被吃掉,变成了负数。
我的做法是:在每个关键运算后,加一个饱和函数。比如ARM CMSIS-DSP库里的__QADD()、__QSUB(),或者自己写一个简单的饱和宏。
// 示例:Q31饱和加法
int32_t sat_add_q31(int32_t a, int32_t b) {
int32_t sum;
sum = a + b;
// 如果两个正数相加得到负数,说明正溢出
if ((a > 0) && (b > 0) && (sum < 0)) {
sum = 0x7FFFFFFF; // 饱和到最大正值
}
// 如果两个负数相加得到正数,说明负溢出
if ((a < 0) && (b < 0) && (sum > 0)) {
sum = 0x80000000; // 饱和到最小负值
}
return sum;
}
第三,系数量化误差。 滤波器系数从浮点转定点时,会有量化误差。这会导致实际频率响应和设计值有偏差。我建议在量化后,用MATLAB或Python仿真一下,看看频响曲线是否还在容忍范围内。
4.5 小结
这一章我们回顾了采样定理、FFT/STFT、滤波器设计,并重点讨论了它们在车规芯片上的定点数实现。
说白了,DSP理论是基础,但真正落地时,定点数的溢出、量化误差、计算效率才是决定成败的关键。我建议你在做算法移植时,先花时间把定点数仿真做透,不要急着上板子调。
下一章,我们会进入语音降噪的核心算法——谱减法与维纳滤波。到时候再聊。