3、延时求和波束成形(DSB):窄带与宽带DSB原理、时域实现方法、频域实现方法、波束图分析

好,咱们今天聊点硬核的——延时求和波束成形,简称 DSB。这是波束成形里最基础、最直观的方法。说白了,就是让麦克风阵列“听”向某个方向。

我刚开始做助听器算法时,第一个接触的就是 DSB。为什么?因为它简单、稳定、好调试。你想想看,如果连 DSB 都调不明白,后面那些自适应算法就更别想了。

3.1 窄带 DSB 原理

先讲窄带。窄带信号,就是频率单一的正弦波。比如 1kHz 的纯音。

假设有两个麦克风,间距 d。声波从 θ 方向传来。那么,到达两个麦克风的时间差 τ 就是:

τ = d * sin(θ) / c

其中 c 是声速,约 340 m/s。

窄带 DSB 的核心思想:把第二个麦克风的信号延时 τ,再和第一个麦克风的信号相加。这样,来自 θ 方向的信号就同相叠加,幅度加倍。其他方向的信号呢?相位不对齐,叠加后幅度变小。

用公式表达就是:

y(t) = x₁(t) + x₂(t - τ)

嗯,就这么简单。但要注意,窄带情况下,延时 τ 对应的是相位差 φ:

φ = 2π * f * τ

所以窄带 DSB 也可以看成是“相位补偿后求和”。

关键点:窄带 DSB 只对单一频率有效。频率变了,相位补偿也得跟着变。

3.2 宽带 DSB 原理

现实中的声音,比如语音,是宽带信号。频率从 100Hz 到 8kHz 都有。窄带 DSB 就不够用了。

宽带 DSB 怎么做?很简单——对每个频率分别做延时补偿。

我个人习惯把宽带 DSB 理解成“时域上的精确对齐”。因为不同频率的波速相同(都是声速),所以时间延迟 τ 对所有频率都一样。但相位变化不同。

宽带 DSB 的时域实现:

y(t) = Σᵢ xᵢ(t - τᵢ)

其中 τᵢ 是第 i 个麦克风相对于参考点的延时。

我在项目中遇到过一个问题:采样率不够高时,延时 τ 可能不是采样周期的整数倍。这时候就需要插值。我一般用线性插值,效果够用。如果追求精度,可以用 sinc 插值。

我的经验:助听器里采样率通常是 16kHz 或 32kHz。对于 16kHz,采样周期 62.5μs。如果延时差小于这个值,直接四舍五入到最近采样点,听感差异不大。

3.3 时域实现方法

时域实现最直观。就是给每个通道加一个延时线。

伪代码如下:

// 假设 4 个麦克风,间距 1cm
// 目标方向 30 度
// 采样率 16kHz

float delays[4];
float buffer[4][BUFFER_SIZE];
int write_ptr[4] = {0};

// 计算延时(采样点为单位)
for (int i = 0; i < 4; i++) {
    float tau = i * d * sin(theta) / c;
    delays[i] = tau * fs;  // 转换为采样点
}

// 每个采样点处理
for (int n = 0; n < BLOCK_SIZE; n++) {
    float y = 0;
    for (int i = 0; i < 4; i++) {
        int read_ptr = write_ptr[i] - (int)delays[i];
        if (read_ptr < 0) read_ptr += BUFFER_SIZE;
        y += buffer[i][read_ptr];
        buffer[i][write_ptr[i]] = input[i][n];
    }
    output[n] = y / 4;  // 归一化
    for (int i = 0; i < 4; i++) {
        write_ptr[i] = (write_ptr[i] + 1) % BUFFER_SIZE;
    }
}

注意看,我用了环形缓冲区。这是嵌入式开发的标配。为什么?因为内存有限,不能无限存数据。

曾经踩过的坑:环形缓冲区的读写指针要小心处理。我曾经因为取模运算写错,导致数据错位,波束指向完全偏了。调试了两天才发现。

3.4 频域实现方法

时域实现虽然直观,但计算量大。尤其是延时不是整数倍时,需要插值。

频域实现就优雅多了。把信号变换到频域,延时变成相位旋转:

Y(f) = Σᵢ Xᵢ(f) * exp(-j * 2π * f * τᵢ)

具体步骤:

  1. 对每通道信号做 FFT
  2. 对每个频点,乘以对应的相位因子
  3. 求和
  4. IFFT 回到时域

伪代码:

// 频域 DSB
for (int k = 0; k < FFT_SIZE/2; k++) {
    float freq = k * fs / FFT_SIZE;
    complex sum = 0;
    for (int i = 0; i < 4; i++) {
        float phase = -2 * PI * freq * delays[i];
        sum += X[i][k] * exp(complex(0, phase));
    }
    Y[k] = sum / 4;
}

频域实现的优点:精度高,没有插值误差。缺点:有块处理延迟,适合非实时场景。

我个人在助听器里更常用时域实现。为什么?因为助听器对延迟极其敏感。频域 FFT 会引入至少一个 block 的延迟,用户会觉得声音“闷”。

3.5 波束图分析

波束图,就是阵列对不同方向信号的响应。画出来像一朵花。

对于均匀线阵,窄带 DSB 的波束图公式:

B(θ) = | Σᵢ exp(-j * 2π * f * i * d * (sin(θ) - sin(θ₀)) / c) |

其中 θ₀ 是目标方向。

波束图有几个关键指标:

指标 含义 典型值
主瓣宽度 主瓣的 -3dB 宽度 随频率和阵列尺寸变化
旁瓣电平 旁瓣相对于主瓣的幅度 均匀加权时约 -13dB
指向性指数 阵列对目标方向的增益 随麦克风数量增加

我画波束图时,习惯用极坐标。横轴是角度,纵轴是幅度(dB)。这样一眼就能看出阵列的指向性。

注意:DSB 的波束图在低频时主瓣很宽。比如 500Hz 以下,波束几乎没指向性。这是物理限制,不是算法能解决的。所以助听器里,低频段通常不做波束成形,或者用其他方法。

举个例子。4 麦克风线阵,间距 1cm,目标方向 0 度。在 2kHz 时,主瓣宽度约 30 度。在 4kHz 时,主瓣宽度约 15 度。频率越高,指向性越强。

但高频也有问题——会出现栅瓣。当频率高到一定程度,d/λ > 0.5 时,波束图会出现多个主瓣。这就是空间混叠。

避坑指南:我曾经设计过一个阵列,麦克风间距 2cm,结果在 8kHz 时出现了栅瓣。用户转头时,声音忽大忽小。后来把间距改到 1cm,栅瓣消失了。记住:d ≤ λ/2 是基本要求。

3.6 总结与思考

DSB 是最简单的波束成形方法。它不依赖信号统计特性,鲁棒性好。但性能有限,旁瓣高,低频指向性差。

什么时候用 DSB?

  • 麦克风数量少(2-4 个)
  • 计算资源有限
  • 需要低延迟
  • 作为其他算法的初始化或参考

什么时候不用?

  • 需要强干扰抑制(比如 -20dB 以下)
  • 噪声环境复杂多变
  • 需要自适应调整波束

嗯,DSB 就讲到这里。下一章咱们聊自适应波束成形,那才是真正有意思的东西。