数字信号处理基础:离散时间信号、线性时不变系统、卷积与相关、z变换入门、差分方程
各位同学,欢迎来到第二章。说实话,这一章是整个音频DSP的“地基”。你后面学滤波器设计、学FFT、学各种音频算法,追根溯源都绕不开今天这几个概念。我当年刚入行时,觉得这些东西太理论,结果做第一个降噪项目就被“线性时不变”这个前提坑了一把——嗯,后面我会细说。
咱们今天的目标很明确:搞懂离散时间信号长什么样,线性时不变系统为什么是“亲儿子”,卷积到底在算什么,z变换怎么用,以及差分方程怎么跟代码对应起来。别怕,咱们用工程师的视角来啃。
离散时间信号:从连续世界到数字世界的第一步
咱们做音频DSP,处理的不是麦克风里出来的连续电压,而是经过ADC采样后的离散序列。说白了,就是把连续的时间轴切成一个个均匀的“切片”,每个切片用一个数字表示。
一个离散时间信号可以写成 x[n],n 是整数索引。比如 CD 音质的采样率是 44100 Hz,那就意味着每秒钟有 44100 个 x[n] 样本。我刚开始做嵌入式音频时,总把 n 当成时间,其实 n 只是序号,真正的时间是 n * T,T 是采样间隔。
常见的离散信号有这么几种:
- 单位脉冲序列 δ[n]:只在 n=0 时为 1,其余全为 0。这是系统的“试金石”。
- 单位阶跃序列 u[n]:n≥0 时为 1,n<0 时为 0。用来表示信号的“开启”。
- 正弦序列:x[n] = A·cos(ωn + φ)。注意这里的 ω 是数字角频率,单位是弧度/样本。
- 指数序列:x[n] = aⁿ。|a|<1 时衰减,|a|>1 时发散。
线性时不变系统:音频算法的“黄金假设”
为什么几乎所有音频DSP教材都先讲LTI系统?因为现实中的音频处理,大部分都建立在“线性”和“时不变”这两个假设上。你想想看,如果同一个算法今天处理一段人声和明天处理结果不一样,那还怎么用?
线性:系统对输入信号的缩放和叠加保持一致性。简单说就是:输入 a·x₁[n] + b·x₂[n],输出就是 a·y₁[n] + b·y₂[n]。混音器就是典型的线性系统——两路信号叠加,输出就是各自处理结果的叠加。
时不变:系统特性不随时间变化。今天输入一个脉冲,明天输入同样的脉冲,输出完全一样。我踩过的坑就在这里——有一次做自适应滤波器,忘了检查系统是否真的时不变,结果在某个频段上出现了奇怪的“呼吸效应”。后来发现是某个系数更新逻辑里混入了时间戳依赖。
卷积与相关:两个看似相似但用途完全不同的运算
卷积是LTI系统的核心运算。系统对任意输入 x[n] 的响应,等于输入与系统单位脉冲响应 h[n] 的卷积:
y[n] = x[n] * h[n] = Σ x[k] · h[n - k] (k 从 -∞ 到 +∞)
说白了,卷积就是“翻转、平移、相乘、求和”。我当年学的时候觉得这定义太抽象,直到做第一个FIR滤波器项目才真正理解——滤波器的系数就是 h[n],输入信号跟系数做卷积,就是滤波过程。
相关跟卷积长得很像,但少了一个翻转步骤:
r_{xy}[n] = Σ x[k] · y[k + n] (k 从 -∞ 到 +∞)
相关用来衡量两个信号的相似度。音频对齐、回声检测、模式识别都靠它。我做过一个语音唤醒项目,就是用互相关来检测关键词模板在音频流中的位置。
| 运算 | 公式特点 | 主要用途 | 计算量(N点) |
|---|---|---|---|
| 卷积 | 翻转 + 平移 + 乘加 | 滤波、系统响应 | O(N²) |
| 互相关 | 平移 + 乘加(无翻转) | 信号匹配、时延估计 | O(N²) |
| 自相关 | 信号与自身的互相关 | 周期检测、基频提取 | O(N²) |
z变换入门:从时域到频域的“桥梁”
z变换把离散时间信号从时域映射到复频域。定义很简单:
X(z) = Σ x[n] · z^{-n} (n 从 -∞ 到 +∞)
其中 z 是复变量。你可能会问:为什么要引入这么个抽象的东西?
原因有两个:
- 卷积变乘法:时域的卷积在z域变成乘法。y[n] = x[n] * h[n] → Y(z) = X(z) · H(z)。这太方便了!
- 系统分析:通过 H(z) 的零极点位置,可以直接判断系统的稳定性、频率响应特性。
我记得第一次用z变换分析一个IIR滤波器时,看到极点落在单位圆外,就知道这滤波器会发散。当时在仿真里跑确实如此——输出越来越大,最后溢出。z变换让我在写代码之前就能发现问题。
常用的z变换对:
| 时域序列 | z变换 | 收敛域 |
|---|---|---|
| δ[n] | 1 | 整个z平面 |
| u[n] | 1 / (1 - z⁻¹) | |z| > 1 |
| aⁿ · u[n] | 1 / (1 - a·z⁻¹) | |z| > |a| |
| cos(ω₀n) · u[n] | (1 - cos(ω₀)·z⁻¹) / (1 - 2cos(ω₀)·z⁻¹ + z⁻²) | |z| > 1 |
差分方程:从数学公式到C代码
差分方程是LTI系统的时域描述。一个N阶差分方程长这样:
y[n] = b₀·x[n] + b₁·x[n-1] + ... + b_M·x[n-M]
- a₁·y[n-1] - a₂·y[n-2] - ... - a_N·y[n-N]
这其实就是滤波器的“真身”。b系数是前馈部分,a系数是反馈部分。如果所有 a 系数都是0,那就是FIR滤波器;否则就是IIR滤波器。
把差分方程写成C代码,我一般这样做:
// 一阶IIR低通滤波器实现
float process_iir_lpf(float x, float *state, float b0, float a1) {
float y = b0 * x + a1 * state[0];
state[0] = y; // 更新状态
return y;
}
你看,就这么几行。但实际工程中要注意:
- 状态变量管理:多个通道时,每个通道要有独立的状态数组。
- 定点化:如果MCU没有FPU,所有系数和状态都要转成Q格式定点数。
- 溢出保护:IIR的反馈环路容易累积误差,需要做饱和处理。
差分方程和z变换是天生一对。对差分方程两边做z变换,就能得到系统函数 H(z):
H(z) = (b₀ + b₁·z⁻¹ + ... + b_M·z⁻ᴹ) / (1 + a₁·z⁻¹ + ... + a_N·z⁻ᴺ)
有了 H(z),零极点、频率响应、稳定性,全都能算出来。这就是为什么我说z变换是“桥梁”——它把差分方程这种时域实现,和频域分析连接起来了。
- 离散信号 x[n] 是连续世界的“快照”,采样率决定了信息量
- LTI 系统是音频算法的基石,线性保证叠加,时不变保证可重复
- 卷积是LTI系统的运算核心,相关是信号匹配的利器
- z变换把卷积变乘法,零极点分析让系统设计有据可依
- 差分方程就是滤波器的C代码原型,实现时注意定点化和状态管理