第四章 数字信号处理基础回顾:采样定理、FFT与STFT、滤波器设计(FIR/IIR),在车规芯片上的定点数实现考量

各位同学,欢迎来到第四讲。

说实话,做语音降噪,绕不开数字信号处理。很多刚入行的朋友觉得DSP理论太枯燥,上来就调算法库。但我要说,在车规芯片上,不懂底层DSP原理,你连定点数溢出了怎么死的都不知道。

这一章,我们快速回顾三个核心知识点:采样定理、FFT/STFT、滤波器设计。重点不是讲数学推导,而是讲它们在车规芯片上落地时,那些「坑」在哪里。

4.1 采样定理:别让混叠毁了你的降噪

采样定理,说白了就是一句话:采样频率必须大于信号最高频率的两倍。否则就会混叠。

我在项目中遇到过一件事。某次做车内语音增强,麦克风采集的语音听起来正常,但降噪算法一跑,高频段出现奇怪的「吱吱」声。查了半天,发现是ADC前端的抗混叠滤波器设计得太随意,16kHz采样率下,8kHz以上的高频噪声直接折叠到了语音频带内。嗯,这就是典型的混叠。

避坑指南: 我曾经在车规芯片上调试时,发现芯片内部的ADC自带抗混叠滤波器截止频率不够陡峭。建议你在选型时,务必确认芯片的模拟前端是否支持可编程抗混叠滤波,或者至少预留外部RC滤波器的位置。

在车规芯片上,采样率通常固定为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仿真一下,看看频响曲线是否还在容忍范围内。

特别注意: IIR滤波器的系数对量化误差非常敏感。我曾经试过把浮点IIR系数直接截断成16位定点,结果滤波器直接自激振荡了。后来改用Q31格式,并做了系数舍入(rounding)而不是截断(truncation),才解决问题。

4.5 小结

这一章我们回顾了采样定理、FFT/STFT、滤波器设计,并重点讨论了它们在车规芯片上的定点数实现。

说白了,DSP理论是基础,但真正落地时,定点数的溢出、量化误差、计算效率才是决定成败的关键。我建议你在做算法移植时,先花时间把定点数仿真做透,不要急着上板子调。

下一章,我们会进入语音降噪的核心算法——谱减法与维纳滤波。到时候再聊。