AEC3的延迟估计算法与AEC的非线性处理的延迟估计算法思想一致,因为回声能量是呈指数衰减,所以计算滤波器能量最大块作为延迟估计值,但是比AEC的延迟估计算法复杂的多
AEC3延迟估计模块由步长为0.7的5个时域NLMS自适应滤波器组成,每个NLMS滤波器默认32块,每块16个sample共 512点,5个滤波器之间互相重叠8块,这里的重叠指的是输入的信号在时间上重叠
滤波器的输入信号是经过分频后的0~16kHz低频段信号然后经过4倍下采样相当于采样率为4000kHz的信号,5个nlms滤波器可以估计32165 - 8164 =2048个samples,也就是说该延迟估计算法最多能估计2048/4000 = 512ms的延迟
滤波器更新算法:NLMS更新公式
滤波器更新条件:
延时值:将每个滤波器系数能量最大块作为延时值
延时值可信度:
挑选方法:滤波器更新且可信度为真,且accuracy最大作为延时值估计
accuracy:近端能量-误差能量
使用大小为250的历史窗口统计历史延时值,统计历史窗口中出现次数最多的延时值,当出现次数大于20次认为这是一个质量较高的估计值,反之认为是较差的估计值
质量好的延时值会用来做时钟漂移检测,时钟漂移检测使用大小为3的历史窗口判断时钟漂移是否产生,窗口内的值时前3个历史时刻延时估计值:
判断标准:d1,d2,d3分别时历史窗口历史值与当前时刻延时值之差
增量漂移:d1==-1且d2==-2 或d1==-2且d2==-1 d3==-3确认产生反之可能产生
const bool probable_drift_up =
(d1 == -1 && d2 == -2) || (d1 == -2 && d2 == -1);
const bool drift_up = probable_drift_up && d3 == -3;
减量漂移:d1= =1且d2= =2 或 d12且d21 d3==3确认产生反之可能产生
const bool probable_drift_down = (d1 == 1 && d2 == 2) || (d1 == 2 && d2 == 1);
const bool drift_down = probable_drift_down && d3 == 3;
目前aec3只做了时钟漂移检测,没有做相应处理
质量好的延时值和质量差的延时值都会用来进行远端对齐
另外,当上一次延时值和当前延时值的质量都是高的情况下会有一个迟滞量,即延迟增量小于某一阈值时认为延时相较于上一次没有改变
线性回声消除使用主辅滤波器结构,总体来说,主滤波器会通过计算失调以及变步长保证主滤波器不会发散,但可能更新较缓慢
辅滤波器会一直更新,能快速对回声路径进行追踪,当辅滤波器发散时主滤波器系数拷贝至辅滤波器
因此在双工状态下主滤波器比辅滤波器好
在单工状态下辅滤波器比主滤波器好
更新方式1:通过计算主滤波器系数的失调增益调整滤波器系数
mis = mis + 0.1*( e2/y2 - mis)
scale = 2 / sqrt(mis)
H(t)=H(t) * scale
通过公式可以看出:如果误差信号一直大于某个阈值会导致失调量持续增加,当mis大于10时,计算失调增益scale来调整滤波器系数
总体来说该算法会一直限制主滤波器误差输出在与近端能量有关的一定范围内,即滤波器不会发散
更新方式2:通过系数更新公式更新滤波器系数
Erl(t) = H(t) * H(t)
mu(t) = H_error(t) / (0.5 * H_error(t) * X2(t) + N * E2(t)) —— (X2(t) > noise_gate)
mu(t) = 0 —— (X2(t) <= noise_gate)
H_error(t) = H_error(t) - 0.5 * mu(t) * X2(t) * H_error(t)
G(t) = mu(t) * E(t)
factor = 0.00005f —— (E2_shadow>=E2_main)
factor = 0.05f —— (E2_shadow
公式不具体分析,但可以看出:
选择线性回声消除结果使用主滤波器误差还是使用辅滤波器误差,然后进行平滑过渡
假如配置了使用辅助滤波器的输出,则满足以下情况使用辅滤波器误差输出:
假如没有配置使用辅助滤波器的输出,则满足以下情况使用辅助滤波器误差输出
1、根据主辅滤波器的误差与近端信号分析滤波器收敛与发散状态
2、根据主滤波器的时域响应分析主滤波器的属性,主要分析如下参数:
3、计算相对于滤波器开始的直接路径延迟
4、更新远近端语音活动统计
5、计算远端信号混响功率
6、检测回声是否过饱和:通过最大回声增益值和远端信号最大值来判断
7、更新erle和erl:根据erle的估计计算线性滤波器的好坏,以及提供erle以计算残余回声
8、更新AEC工作状态标志,在AEC由初始状态转为正常工作状态erle估计器会重置
9、transparent_mode检测:检测滤波器是否工作正常
10、计算滤波器工作性能,来决定是否使用线性滤波的结果作为非线性处理的输入,以及使用不同的非线性抑制函数
在滤波器工作良好时,使用线性滤波结果计算残余回声,工作良好判定需同时满足以下条件:
11、更新混响估计,计算混响decay和频域响应
加窗、FFT,计算频谱
计算近端信号、误差信号、线性回声信号频谱
滤波器工作良好情况下:
滤波器工作较差情况下:
更新公式:
N2 = Y2_smoothed < N2 ? (0.9*Y2_smoothed + 0.1*N2)* 1.0002f : N2*1.0002f
缓慢更新N2噪声能量,当近端语音能量小于噪音能量时,噪音能量快速降低至近端能量水平,当近端语音能量大于噪音能量时,噪音能量N2缓慢增加
如果近端(误差)能量显著大于残余回声能量和舒适噪声能量,且连续超过12个block时则认为产生了近端语音,最短保持时间设置为50个block
根据近端频谱、线性回声估计频谱、残余回声估计频谱、舒适噪声频谱计算非线性增益值
低频段非线性增益值核心计算函数GainToNoAudibleEcho代码如下:
const auto& p = dominant_nearend_detector_->IsNearendState() ? nearend_params_
: normal_params_;
for (size_t k = 0; k < gain->size(); ++k) {
float enr = echo[k] / (nearend[k] + 1.f); // Echo-to-nearend ratio.
float emr = echo[k] / (masker[k] + 1.f); // Echo-to-masker (noise) ratio.
float g = 1.0f;
if (enr > p.enr_transparent_[k] && emr > p.emr_transparent_[k]) {
g = (p.enr_suppress_[k] - enr) /
(p.enr_suppress_[k] - p.enr_transparent_[k]);
g = std::max(g, p.emr_transparent_[k] / emr);
}
(*gain)[k] = g;
}
修改这里的近端语音检测参数以及抑制参数对非线性抑制效果影响较大,aec3参数可以尝试调整这两组参数
暂时只是对webrtc aec3的算法粗略的了解了一下,可以发现aec3算法对工程化做了大量的处理,例如延迟估计算法、处理回声路径变化、初始状态设置、线性回声消除以及非线性回声消除,其中的每一部分内还有更多复杂的细节及逻辑,因此不难看出Google推出aec3就是为了针对总类繁多的webrtc设备终端一站式解决适配问题。
限于个人技术水平,对AEC3的算法也没有完全理解透彻,比如AEC3所使用的主滤波器更新公式没找到详细资料,AEC3的非线性处理也非常复杂,远远不止上述提到的这些,所以难免有些错误之处,希望各路大神们指点一二
有任何疑问,欢迎加微信交流:xu19315519614
最后附上我单独提取测试的AEC3代码: https://github.com/ewan-xu/AEC3