双讲检测(DTD):双讲场景下的滤波器发散问题

回声消除里最头疼的场景是什么?我个人觉得,就是双讲。两个人同时说话,麦克风信号里既有近端语音,又有远端回声。这时候自适应滤波器很容易跑偏,甚至直接发散。

为什么会这样?因为滤波器赖以收敛的误差信号被近端语音污染了。它以为那是回声残留,拼命调整系数,结果越调越乱。我在项目中遇到过好几次,双讲检测没做好,滤波器直接崩掉,用户体验极差。

双讲场景下的滤波器发散机制

先看一个典型的双讲场景:

  • 远端信号 x(n) 播放到扬声器
  • 近端语音 v(n) 同时产生
  • 麦克风采集信号 d(n) = y(n) + v(n)
  • 滤波器输出 ŷ(n) 试图逼近 y(n)
  • 误差信号 e(n) = d(n) - ŷ(n) = v(n) + [y(n) - ŷ(n)]

问题就在这。当 v(n) 远大于 y(n) - ŷ(n) 时,误差信号几乎等于近端语音。NLMS 更新公式里,系数调整量正比于 e(n)。滤波器以为回声很大,拼命调整,实际上是在追踪近端语音。发散是必然的。

核心结论:双讲时如果不做 DTD,自适应滤波器会在近端语音上收敛,导致回声路径模型被破坏。一旦切回单讲,回声会突然冒出来,需要很长时间重新收敛。

Geigel 算法:最经典的 DTD 方法

Geigel 算法是 ITU-T G.168 推荐的方法,也是我最早接触的 DTD 方案。原理很简单:比较远端信号和麦克风信号的幅度。

判断逻辑:

if |d(n)| >= T * max(|x(n)|, |x(n-1)|, ..., |x(n-L+1)|)
    双讲状态
else
    单讲状态

其中 T 是阈值,通常取 0.5 或 0.707(对应 -6dB 或 -3dB)。L 是回声路径长度,一般取 8-16 个样本。

为什么这么设计?因为回声路径有衰减,麦克风收到的回声幅度一定小于远端信号。如果麦克风信号比远端信号还大,那肯定有近端语音。

我的经验:阈值 T 不要死磕 0.5。我在一个车载项目里,车内空间小,回声衰减只有 -3dB,T 设 0.5 导致频繁误判。后来改成 0.707,效果好了很多。建议根据实际回声衰减量动态调整 T。

Geigel 算法的优缺点

维度 说明
优点 计算量极小,只需比较幅度;实现简单,适合嵌入式
缺点 对噪声敏感;无法区分近端语音和突发噪声;阈值固定时适应性差
适用场景 信噪比高、回声路径稳定的环境,如免提电话
不适用场景 强噪声环境、回声路径快速变化、音乐信号

基于相关性的 DTD 方法

Geigel 只看幅度,信息量有限。更可靠的方法是利用相关性。核心思想:远端信号和回声是相关的,和近端语音不相关。

常用的相关性指标:

  • 互相关系数:计算远端信号和麦克风信号的归一化互相关
  • 相干函数:频域上的相关性度量
  • 误差信号与远端信号的相关性:如果误差和远端相关,说明还有残留回声

我比较喜欢用归一化互相关(NCC):

ρ = E[x(n) * d(n)] / sqrt(E[x²(n)] * E[d²(n)])

当 ρ 接近 1 时,说明麦克风信号主要是回声,单讲状态。当 ρ 接近 0 时,说明近端语音占主导,双讲状态。

注意:相关性方法计算量大,嵌入式上要谨慎。我曾在 Cortex-M4 上跑 NCC,每帧 10ms 的数据,计算量占用了 15% 的 CPU 时间。后来改用分段计算,每 4 帧算一次,才压到 3%。

Geigel 与相关性方法的对比

对比项 Geigel 相关性方法
计算量 极低 较高
抗噪声能力 弱 强
检测延迟 低(逐样本判断) 高(需要统计窗口)
误判率 高(噪声易误判) 低
嵌入式适配 非常适合 需要优化

双讲检测的核心逻辑

下面这张图展示了 DTD 在 AEC 中的位置和作用:

双讲检测(DTD)在 AEC 中的核心逻辑 远端信号 x(n) 扬声器播放 回声路径 h(n) 麦克风采集 近端语音 v(n) + d(n) = y+v 自适应滤波器 ŷ(n) - e(n) 双讲检测 (DTD) Geigel / 相关性 DTD 检测到双讲时,冻结滤波器系数更新,防止发散

实际项目中的 DTD 策略

我在多个嵌入式项目里总结了一套 DTD 策略,分享给你:

  1. 先用 Geigel 做粗检:计算量小,快速判断是否可能双讲
  2. 再用相关性做精检:只在 Geigel 判为双讲时触发,节省算力
  3. 加入滞回比较:避免频繁切换状态,设置进入和退出双讲的不同阈值
  4. 冻结滤波器系数:双讲时不更新滤波器,但继续计算误差用于输出
  5. 缓慢恢复:双讲结束后,先小步长更新几帧,再恢复正常步长

避坑指南:我曾经在一个项目里只用了 Geigel,结果在嘈杂环境中频繁误判。近端安静时它判成双讲,滤波器不更新;等真正双讲时它又判成单讲,滤波器直接发散。后来加了相关性验证,问题才解决。

嵌入式上的优化建议

嵌入式资源有限,DTD 也要精打细算:

  • 定点化:Geigel 用整数比较,避免浮点运算
  • 查表法:相关性计算中的平方根用查表代替
  • 帧间复用:上一帧的部分计算结果可以复用
  • 降采样:相关性计算可以在降采样域进行,精度损失不大

嗯,双讲检测就讲到这里。记住一个原则:宁可漏检,不要误判。漏检最多导致回声残留,误判会让滤波器发散,恢复起来更麻烦。