4. 傅里叶变换与频谱分析:从DFT到MFCC

傅里叶变换,说白了就是把声音从时间轴掰到频率轴。你听到的是一段波形,但耳朵真正关心的是里面有哪些频率成分。这一章我们就来聊聊怎么用数字信号处理的手段,把时域信号变成频域信息。

我个人习惯把傅里叶变换看作「拆解」——把一个复杂的波形拆成一堆正弦波的叠加。这个思路贯穿了整个音频DSP领域。你想想看,如果连信号里有什么频率都不知道,后面做滤波、压缩、识别都无从下手。

核心认知:时域信号我们看得见摸得着,但频域信息才是音频算法的真正战场。从DFT到MFCC,每一步都是在解决「如何更高效、更准确地提取频域特征」这个问题。

第4章 知识体系:傅里叶变换与频谱分析 时域音频信号 DFT → FFT:从定义到快速实现 频谱泄露 & 窗函数 STFT:短时傅里叶变换 梅尔频谱 → MFCC特征提取 语音识别 / 音乐分析 / 音频分类

4.1 DFT与FFT:从数学定义到工程实现

离散傅里叶变换(DFT)的公式其实很简单:

X[k] = Σ(n=0 to N-1) x[n] · e^(-j·2π·k·n/N)

但你要是真拿这个公式去算一段10秒的音频,采样率44.1kHz,那就是441000个点。直接算DFT的复杂度是O(N²)——算到天荒地老。

所以有了FFT。FFT不是另一种变换,它只是DFT的一种快速算法。利用旋转因子的对称性和周期性,把复杂度降到O(N·log₂N)。

我记得第一次在Cortex-M4上实现FFT时,用的就是经典的基2时间抽取法。核心思路就八个字:分而治之,蝶形运算。

工程小贴士:实际项目中别自己手写FFT。CMSIS-DSP、ARM DSP库、FFTW、KissFFT都经过充分优化。我踩过的坑是——自己写的FFT在定点芯片上精度漂移严重,后来换了CMSIS的q15版本,问题直接解决。

4.2 频谱泄露与窗函数

做FFT有个前提:你得对信号截一段出来。这一截,就出问题了。

假设信号里有个1000Hz的纯正弦波,你截取的长度刚好是它的整数倍周期,那FFT结果很干净,就一根谱线。但现实中没有这么巧的事。截断导致频谱能量从主瓣「泄露」到旁瓣,这就是频谱泄露。

怎么解决?加窗。

窗函数的作用是让截断边界平滑过渡,减少频谱泄露。常用的窗函数有这些:

窗函数 主瓣宽度 旁瓣衰减 适用场景
矩形窗 最窄 -13 dB 瞬态信号、频率分辨率优先
汉宁窗 较宽 -32 dB 通用音频分析(我最常用)
海明窗 较宽 -43 dB 语音处理、窄带信号
布莱克曼窗 最宽 -58 dB 需要极低旁瓣的场景

避坑指南:我曾经在一个语音端点检测项目里用了矩形窗,结果静音段的频谱泄露把噪声能量带进了语音频段,导致误触发率飙升。后来换成汉宁窗,误报直接降了60%。记住——加窗不是可选项,是必选项。

4.3 STFT:短时傅里叶变换

FFT只能告诉你整个信号段里有哪些频率,但不知道这些频率在时间轴上的变化。对于音乐、语音这种非平稳信号,这远远不够。

STFT的思路很简单:把信号切成一小段一小段(帧),对每一帧做FFT,然后把结果按时间堆叠起来。这样你就得到了一个时间-频率-幅度的三维表示,也就是频谱图(spectrogram)。

STFT有两个关键参数:

  • 帧长(window size):决定了频率分辨率。帧越长,频率分辨率越高,但时间分辨率越低。
  • 帧移(hop size):相邻帧之间的步长。通常取帧长的25%~50%。

这里有个经典的矛盾:时间分辨率和频率分辨率不可兼得。这就是海森堡不确定性原理在信号处理中的体现。你想想看,要精确知道一个频率,就需要观察足够长的时间;但时间长了,你又不知道这个频率具体在哪个时刻出现的。

// 伪代码:STFT核心流程
for (frame = 0; frame < total_frames; frame++) {
    // 1. 从信号中截取一帧
    memcpy(frame_buffer, signal + frame * hop_size, window_size);
    
    // 2. 加窗
    for (i = 0; i < window_size; i++) {
        frame_buffer[i] *= hann_window[i];
    }
    
    // 3. 做FFT
    fft(frame_buffer, spectrum);
    
    // 4. 取幅度谱(或功率谱)
    for (k = 0; k < fft_size/2; k++) {
        magnitude[frame][k] = sqrt(real[k]^2 + imag[k]^2);
    }
}

经验之谈:我一般用1024点帧长、512点帧移做语音STFT。这个配置在大多数嵌入式平台上都能跑,频率分辨率够用(约43Hz@16kHz采样率),时间分辨率也还行(每帧32ms)。

4.4 梅尔频谱与MFCC特征

STFT得到的频谱是线性频率刻度。但人耳对频率的感知不是线性的——低频区域我们分辨得很细,高频区域则越来越粗。这就是梅尔刻度的由来。

梅尔频率和线性频率的转换公式:

mel = 2595 · log10(1 + f/700)

生成梅尔频谱的步骤:

  1. 对STFT得到的幅度谱,通过一组梅尔滤波器组(三角滤波器)做加权求和
  2. 每个滤波器的输出就是该梅尔频带的能量
  3. 把这些能量按时间堆叠,就得到梅尔频谱图

MFCC(梅尔频率倒谱系数)是在梅尔频谱基础上再走两步:

  • 取对数(模拟人耳对响度的对数感知)
  • 做DCT(离散余弦变换),得到倒谱系数

为什么要做DCT?因为梅尔频谱的各个频带之间有相关性,DCT可以去相关,把能量集中到前几个系数上。通常取前12~13个MFCC系数就够了,再加上一阶差分和二阶差分,构成完整的特征向量。

特征 维度 信息量 典型应用
梅尔频谱 40~128 完整频域能量分布 深度学习声学模型输入
MFCC 13~39 紧凑的倒谱特征 传统GMM/HMM语音识别
MFCC+Δ+ΔΔ 39 含动态信息 说话人识别、关键词检测

实现建议:在嵌入式平台上提取MFCC时,注意DCT可以用查表法加速。我曾在STM32H7上做过优化,40维梅尔滤波器+13维MFCC,单帧处理时间不到0.5ms,完全满足实时需求。

嗯,到这里傅里叶变换与频谱分析的核心内容就讲完了。从DFT/FFT的数学原理,到频谱泄露的工程陷阱,再到STFT的时频分析,最后到梅尔频谱和MFCC的特征提取——这一整套链路,是音频DSP的基石。你在实际项目中遇到的绝大多数音频问题,最终都会回到这些基本操作上来。