AECM 属于 WebRTC 语音处理引擎(Voice Engine)的子模块,是为移动设备专门设计的回声消除处理模块,其内部有根据芯片类型进行汇编指令级的特殊优化。AECM 的主体工程文件可以从 WebRTC 工程根目录下的modules/audio_processing/aecm
子目录找到,包含 delay_estimator.c
、delay_estimator_wrapper.c、aecm_core.c、aecm_core_c.c、echo_control_mobile.c
这五个回声消除的核心功能的 c 语言实现文件,其中 delay_estimator.c、delay_estimator_wrapper.c
用于回声时延的精确估计,aecm_core.c、aecm_core_c.c
则是回声消除的核心工作流程,包括将远端信号从缓冲区中取出、将信号转换到频域 、远端信号对齐、自适应滤波器更新以及回声消除等功能,echo_control_mobile.c
则是对核心功能的一层包装,它提供对外直接调用的接口函数,其 中 常 用 函 数 有 五 个 : WebRTCAecm_Create
用于为核心数据结构分配内存,WebRTCAecm_Init
用于核心数据结构以及部分函数指针的初始化 ,WebRTCAecm_BufferFarend
用于向回声消除模块写入远端信号,WebRTCAecm_Process
用于写入向回声消除模块写入近端信号并同时得到处理完成后的输出信号。WebRTCAecm_Free
在整个回声消除工作完成后释放内存,其中WebRTCAescm_Process
是回声消除算法的主体部分的入口,由它的内部包装了核心功能的调用。
从图可以看到,AECM模块的自适应滤波、滤波器系数的更新以及回声消除工作都在频域进行,然后将处理后的频域信号变换到时域后直接输出。
从上一小节的介绍可知,AECM回声消除的主体功能都由WebRTCAecm_Process 函数内部调用,因此可以通过一步步分析其调用链来查看和分析 AECM 的核心工作流程。下图为 WebRTCAecm_Process 函数调用链示意图,其中横向箭头表示函数间嵌套调用,纵向箭头表示函数内顺序调用或处理过程,整个调用链比较复杂,因此笔者选择其中比较重要的几个函数和处理过程进行简要分析。
WebRTCAecm_Process 只是一个上层的包装函数,它内部有一个容量为 4000 的环形缓存,用来缓存输入的远端信号,然后由传入的时延估计参数 msInSndCardBuf 计算出远端信号缓存的初始读取位置 readpos 用来完成远端信号和近端信号的初步对齐:
r e a d p o s = m s I n S n d C a r d B u f 1000 ∗ f ∗ 75 % readpos = \frac{msInSndCardBuf}{1000 }*f*75 \% readpos=1000msInSndCardBuf∗f∗75%
其中 f 为抽样频率。当远端信号缓存中的数据未达到初始读取位置 readpos 时,则直接将输入近端数据复制到输出并直接返回;当缓存数据量达到 readpos,则在缓存中将数据从 readpos 处读取一帧出来并和当前近端信号帧后输入 WebRTCAecm_ProcessFrame 函数。由于 readpos 的存在,导致后续处理过程的远端信号和近端信号之间始终有长度为readpos 的时间差,从而使两个信号之间完成初步的对齐。
WebRTCAecm_ProcessFrame 的主要功能则是将传入的远端帧和近端帧数据分别存储到两个环形队列缓存中,根据上一帧计算的回声时延得到与当前近端帧相对应的远端帧在缓存中的位置,然后按照 64 个采样点为单位分块(Block),将指向每一个近端和远端数据块的指针输入 WebRTCAecm_ProcessBlock。
在 WebRTCAecm_ProcessBlock 中的处理过程依次按顺序有 10 个调用或处理过程,这里选取比较重要的几个过程进行阐述和分析。
频域变换的主要实现过程在函数 TimeToFrequencyDomain 内部,它使用快速傅里叶变换将远端信号和近端信号变换到频域。在进行频域变换的时也是使用的定点数计算,输出信号频域表示也是采用定点数的形式。算法内部默认使用长度为 128 点 FFT 且不可更改。考虑到 FFT 可能会引起的印谱泄露和栅栏效应[49],AECM 在变换之前对时域信号采用加汉宁窗的方法缓解这一效应。汉宁窗也成升余弦窗,长度为 N 的离散汉宁窗为:
W H n = 1 2 ( 1 − c o s [ 2 Π N n ] ) , n = 1 , 2 , 3 , . . . . . , N − 1 W_{Hn}= \frac{1}{2}(1-cos[\frac{2Π}{N}n]),n=1,2,3,.....,N-1 WHn=21(1−cos[N2Πn]),n=1,2,3,.....,N−1
同时注意到,传入此函数数据为单个数据块,长度为 64,但是 FFT 运算的长度却是128即两个数据块,这是因为这里运用了重叠保留法(overlap-save)的技巧来处理线性卷积和周期卷积的问题。由数字信号处理基本理论可知,时域的卷积运算可以通过使用傅里叶变换将信号转换到频域,并利用频域的乘法计算后将结果用逆傅里叶变换转换到时域来代替。然而通过这种频域乘法的方法计算的卷积称为周期卷积,而在时域直接计算的卷积称为线性卷积,这两种卷积计算的结果有一定差异,但是它们的前边一部分是相同的。因此可以通过延长输入信号并取卷积计算的一部分作为结果,从而使周期卷积的结果等于线性卷积,这就是重叠保留法的原理。在 AECM 中具体实现过程为,将当前输入的数据块缓存起来,然后将上一次缓存的数据块与当前的数据块一起进行 FFT得到长度为 128 的频谱 X_k。由于 FFT 的对称特性,所以只取变换结果的前 65 个频点取模后作为一个频域数据块输出。
回声时延的计算主要实现在函数 WebRTCAecm_DelayEstimatorProcessFix 内部,它首先从远端频域数据块的 65 个频点中选取序号为 12~43 的 32 个频点,将这 32 个频点值的均值作为门限 H,然后将这它们依次与门限值比较进行二值化,从而将每一个远端频域数据块转换为一个 32 位的无符号整型:
H = 1 32 ∑ i = 32 43 X i H = \frac{1}{32}\sum^{43}_{i=32} X_i H=321i=32∑43Xi
B i = c ( u ) = { 0 , X i − 22 < H 1 , X i − 22 ≥ H B_i=c(u)=\begin{cases} 0,X_{i-22}
其中 Xi 是远端频域块的第 i 个频点值,Bi 为二值化后的第 i 位二进制。将二值化后的 32位整型插入远端二值化整型缓存的最前端,其他数据依次后移,整个过程如下图所示。
在此之后,用相同的方法计算当前近端频域数据块的二值化整型 Db,然后遍历远端二值化整型缓存。将近端二值化整型与缓存中第 i 个历史数据 Xbi相做异或运算得到 di,取缓存中 di 最小的那个整型,它所代表的远端数据块与当前近端数据块的频谱差异最小,然后根据其在缓存中的位置来计算回声时延 delay,单位为 ms:
d i = D b 异 或 X b i , i = a r g { m i n { d 1 , d 2 , d 3 , . . . . d M } } d_i = D_b异或X_{bi},i=arg \{ min \{d_1,d_2,d_3,....d_M\}\} di=Db异或Xbi,i=arg{min{d1,d2,d3,....dM}}
d e l a y = i ∗ N f ∗ 1000 delay = \frac{i*N}{f}*1000 delay=fi∗N∗1000
其中 M 为缓存长度,f=8000 或 16000 为抽样频率,N=64 为数据块长度。完成时延的计算后,紧接着会调用 WebRTCAecm_AlignedFarend 函数并传入时延 delay。该函数的功能为根据时延从远端的历史缓存中取出对应的远端频域数据块并返回,从而完成远端数据块和近端数据块的对齐。
从时延计算算法的流程可以看到,这里的远端二值化整型缓存长度 M 的大小决定了内部时延计算的最大范围。为了提高运算效率,内部的默认值设置为 10,因此如果抽样频率 f=16000Hz,那么的时延计算范围仅为 0 ~ 40 ms。根据 4.1 小节的分析,智能终端设备的回声时延范围 100~400ms,远远超出这里的计算范围。因此这里的回声时延计算以及远、近端数据块的对齐是以 WebRTCAecm_Process 函数中传入 msInSndCardBuf 时延估计参数为前提的,是在其基础上的一种更加精细的小范围内对时延的计算和对齐。如果 msInSndCardBuf 不准确,则将直接影响在这里回声时延计算的效率和准确性。
AECM 在 WebRtcAecm_CalcEnergy 函数中对远端信号进行 VAD。AECM 中远端信号的 VAD 有两个目的:第一个是判定远端信号中是否含有语音,将一个块内远端信号的以 2 为底的对数域能量值 E l o g E_{log} Elog 与判决门限 T1 的值进行比较,如果 E 大于 T1 则认为远端信号中存在语音,反之则认为远端语音不存在;另一个目的是控制滤波器系数的更新,对远端频谱的每个频点的值,在该点对应频域点的幅值设置一个门限 T2,在系数更新时会进行判决,只有当该点频域幅度值大于 T2 才会对该点的滤波器系数进行系数更新。
除了利用远端信号对数域能量值 Elog 进行 VAD,WebRTCAecm_CalcEnergy 还根据该值计算并保存了一些用于后续计算的能量中间状态值,如远端能量最大值 E m a x E_{max} Emax、和远端能量最小值 E m i n E_{min} Emin,其迭代计算规则如下:
其中 E m a x E_{max} Emax 和 E m i n E_{min} Emin的初始值分别设置为-32767 和 32767。
AECM 的滤波器系数更新采用的是频域变步长 NLMS 算法。在更新滤波器之前首先根据计算得带的时延通过调用函数 WebRTCAecm_CalcStepSize 计算以 2 为底的负对数域步长 mu,如果 VAD 的结果为不存在语音,则将步长设置为 0。然后将步长参数和远、近端频域数据块传入 WebRTCAecm_UpdateChannel 函数进行滤波器系数的更新。需要注意的是,AECM 内部默认采用双滤波器的结构进行系数更新。关于具体的更新算法可以参考 NLMS 算法的详细阐述,这里重点只介绍步长的计算方法。
根据上一小节中计算的能量中间状态值 E m a x E_{max} Emax 和 E m i n E_{min} Emin ,负对数域的步长 mu 的计算式
如下:
m u = μ m i n − 1 − ( μ m i n − μ m a x ) E l o g − E m i n E m a x − E m i n mu = \mu_{min}-1-(\mu_{min}-\mu_{max}) \frac{E_{log}-E_{min}}{E{max}-E_{min}} mu=μmin−1−(μmin−μmax)Emax−EminElog−Emin
其中 μ m a x μ_{max} μmax和 μ m i n μ_{min} μmin 都是常数,分别是负对数域步长的最大值和最小值,AECM 内分别设定为 1 和 10。
WebRTC采用了维纳滤波器。此处只给出传递函数的表达式,设估计的语音信号的功率谱为Ps(w),噪声信号的功率谱为Pn(w),则滤波器的传递函数为
H ( w ) = P s ( w ) P s ( w ) + P n ( w ) H(w) =\frac{Ps(w)}{Ps(w)+Pn(w)} H(w)=Ps(w)+Pn(w)Ps(w)
WebRTC采用的舒适噪声生成器比较简单,首先生成在[0 ,1 ]上均匀分布的随机噪声矩阵,再用噪声的功率谱开方后去调制噪声的幅度。
这些操作都是对回声消除之后信号的修饰和完善,可以进一步提高通信质量。但是这些处理不是本文讨论的重点,因此在这里不做详细说明。
总结:在移动终端,由于回声时延很大程度上取决于设备的硬件或者操作系统设置,而 WebRtc 只是一个应用框架,它没有办法获知这些先验知识,因此它将问题交给应用开发人员,要求传入回声时延估计参数 msInSndCardBuf,然后由 AECM 内部在这个基础上进行回声时延的精确计算,因此传入的时延估计参数的准确性将影响到AECM 时延计算的效率和准确性。综上所述,传入参数 msInSndCardBuf 的值会根据设备的变化以及时延的波动而变化。
需要AECM源码可在下方评论或私信我!!!
参考文献: