导读:回声消除作为3A算法之一,是语音前处理的重要环节。回声消除问题,并不是一个简单的算法问题,它其实是一个系统性的问题。空间、传播介质、器材特性等等都会影响到回声的特性,目前算法的处理问题能力是有限的,所以做好回声消除需要从更系统的视角来看问题。本文从几个简单的视角来聊一聊回声消除的原理。
Part1
回声是如何产生的?
在实时音视频通话中,回声产生的主要原因是扬声器播放的声音又再次录进麦克风里去,而这个现象在A,B双方开外放的通话下,就转化成一个问题,A(下图左)说话,A的声音从B(下图右)的扬声器放出,又从B的麦克风采集进来,从而又传回给A产生回声。
Part2
麦克风采集声音是播放声音(回声)和说话声音的简单合成?
当然不是,事实上:
一来在实际场景下回声是经过多次反射和环境的噪声(如图中电风扇的噪声)以及本地说话的声音传入麦克风的。
二来,由于设备(扬声器或者麦克风都会存在失真),那么麦克风录到的回声和扬声器播放数据已经发生变化,在实际情况下这一过程中,声音在频域通常不仅包含一些线性变化,更会有很多的非线性变化,如下图(一个真实手机录的数据的only far end talk 部分选段),左声道(上面)是播放前的数据也就是算法的远端参考数据(即far end reference),右声道(下面)是麦克风采集的声音,可以看到不管是时域还是频域都能看到比较大的不同。
又如下图是绿色的远端参考信号和蓝色的本端信号数据的频谱分析,区别可以自己看看。
Part3
回声消除的基本思想
回声消除的论文非常多,各家的算法也有很多不同的优化,这里不讲公式,只讲回声消除的基本思想。
医生在开刀的时候,知道哪里要切哪里不切,是因为他们有基于大量人体解剖实验而得来的先验的知识,清楚知道好的组织是什么样子,然后把基于先验知识和经验把不好的切除。回声消除算法在输入声音数据的时候,并没有太多的先验信息,所以并不清楚什么声音是回声,什么声音是本端人说话。回声和本端说话只在频谱特性上很难区别,所以回声消除算法需要提供一个先验信息——即播放前的数据做参考信号(far end reference),因为这组信号是回声的源头,被扬声器播放后回录进麦克风从而形成回声。根据前面说的,我们知道reference的输入并不等于麦克风采集的数据里面的echo,播放到采集的中间经过的一系列过程称之为 echo path。那么回声消除的基本思想简单来说就是:
为了估计麦克风数据中的回声部分,用滤波器组来模拟echo path,让far end reference 经过滤波器组来模拟实际的声音经过echo path的过程来逼近麦克风中回声部分,得到了echo的估计后,再利用维纳滤波或其他方式来消除麦克风数据中的回声部分。
由于目前实际大部分工业界的实际算法的滤波器组都是模拟线性变化的滤波器组,对于非线性变化,还需要再做针对非线性的残余回声的处理。
Part4
回声消除算法的落地要求
回声消除大部分的应用场景都是实时场景,不管是传统电话、视频会议,还是人工智能音箱为语音识别做的前处理回声消除等。其中有非常多的ARM 设备,所以对于回声消除算法的性能是有一定要求的,最起码的要求是在应用的平台能达到处理一帧数据的时间是低于一帧数据本身的时间。考虑到还有其他的处理,所以处理的时间是越短越好的。
为什么要在频域做呢?绝大部分原因是因为性能问题,如果在时域做,滤波器的长度会非常长,对性能的影响也会非常大。同时FFT在工程化的时候,都会考虑性能,来做汇编的优化。
Part5
为什么要用类似nlms的迭代算法?
nlms是一种迭代算法,使用它的主要原因有两点:
计算量的优化
直接解方程求矩阵的逆非常的费计算量,很多场景下的设备无法满足实时性的需求。
短时echo path 变化的假设
在绝大多数情况下,短时间内echo path是不会发生太大的变化。在不必要浪费计算资源的基础上又能合理的跟踪echo path的变化,迭代算法就是非常好的选择了。
Part6
落地可能会遇到的实际影响因素
echo delay 问题
ref 信号和 麦克风采集的echo 存在一定delay,这个delay 一般是由于播放和采集的模块的缓存以及空间传播的时间造成的。所以通过Ref 信号来估计echo,首先Ref 数据本身不能错,不然回声消除算法效果不会好。 另外软件层的回声消除算法,还会因为系统或工程框架问题产生delay的跳变等问题。
实际使用空间的影响
空旷的会议室、楼道等等场景下,空间的混响本身就很重,这个时候产生回声也会有明显多次反射的情况。
实际使用的姿势的影响
在手机包了手机壳挡住扬声器,放在振动的桌面上,一直摇动设备等一系列情况下都会产品不同的回声反馈。
Part7
算法开发和数据获取
根据上面描述,可以看出来,实际场景下的回声消除是一个非常具有挑战性的工作,也是行业的难点之一。做回声消除算法训练的时候,尽量不要自己合成测试数据再做仿真,更加建议根据应用设备和真实的场景来:
发现问题 -> 动手挖掘有效信息 -> 分析问题 -> 思考解决方案 -> 实现解决方案 -> 测试体验 -> 持续优化 -> 再体验 ...
愿大家在实践中,发现探索的乐趣!