回声消除原理、算法-LMS(Least Mean Square)

发布时间 2023-11-20 17:40:32作者: 阿风小子

回声消除是语音通信前端处理中的一种重要技术,产生的原因是:在实时音视频通话中,扬声器播放的声音有再次录进了麦克风去。

在即时通讯应用中,需要进行双方,或是多方的实时语音交流,在要求较高的场合,通常都是采用外置音箱放音,这样必然会产生回音,即一方说话后,通过对方的音箱放音,然后又被对方的Mic采集到回传给自己(如下图所示)。如果不对回音进行处理,将会影响通话质量和用户体验,更严重的还会形成震荡,产生啸叫。

声学回声是指扬声器播出的声音在接受者听到的同时,也通过多种路径被麦克风拾取到。多路径反射的结果产生了不同延时的回声,包括直接回声和间接回声。

直接回声是指由扬声器播出的声音未经任何反射直接进入麦克风。这种回声的延时最短 ,它同远端说话者的语音能量,扬声器与麦克风之间的距离、角度 ,扬声器的播放音量,麦克风的拾取灵敏度等因素直接相关;

间接回声是指由扬声器播出的声音经过不同的路径 (如房屋或房屋内的任何物体 )的一次或多次反射后进入麦克风所产生的回声的集合。房屋内的任何物体的任何变动都会改变回声的通道。因此,这种回声的特点是多路径的、时变的。

  自适应回声消除的基本思想是估计回音路径的特征参数,产生一个模拟的回音路径,得出模拟回音信号,从接收信号中减去该信号,实现回音抵消。其关键就是得到回声路径的冲击响应,由于回音路径通常是未知的和时变的,所以一般采用自适应滤波器来模拟回音路径。自适应回音消除的显著特点是实时跟踪,实时性强。

 图中y(n)代表来自远端的信号 , r(n)是经过回声通道而产生的回声,x(n)是近端的语音信号。D端是近端麦克风,麦克风采集到的房间叠加的回声和近端说话人的语音。对回声消除器来说,接收到的远端信号作为一个参考信号,回声消除器根据参考信号由自适应滤波器产生回声的估计值,将从近端带有回声的语音信号减去,就得到近端传送出去的信号 。在理想且是单讲的情况下,经过回声消除器处理后,残留的回声误差e(n)=r(n)−将为0,从而实现回音消除。若是双讲的情况(近端有人说话,远端也在说话,而且还存在回声的情况下),希望回声误差e(n)对于近端的语音信号。

性能指标:

收敛速度:滤波器的收敛速度越快越好,使正常通话开始后,通话者很快就感觉不到明显的回波存在。
稳态残留回波(稳定性):即当滤波器收敛达到稳态后的回波输出量,实际中总是希望该参数越小越好。
算法复杂度:良好的算法应该在保持收敛速度的同时尽量降低计算复杂度,同时也能减少功耗
ITU-T G.168对各种回音抵消器产品在包括以上两个主要指标在内的各种指标规定了必须达到的标准

AEC技术的基本原理如下图所示:

 

程序如下:

import numpy as np
import librosa
import soundfile as sf
import pyroomacoustics as pra
# x 参考信号
# d 麦克风信号
# N 滤波器阶数
# mu 迭代步长
def lms(x, d, N = 4, mu = 0.1):
  nIters = min(len(x),len(d)) - N
  u = np.zeros(N)
  w = np.zeros(N)
  e = np.zeros(nIters)
  for n in range(nIters):
    u[1:] = u[:-1]
    u[0] = x[n]
    e_n = d[n] - np.dot(u, w)
    w = w + mu * e_n * u
    e[n] = e_n
  return e
 
# x 原始参考信号
# v 理想mic信号 
# 生成模拟的mic信号和参考信号
def creat_sim_sound(x,v):
    rt60_tgt = 0.08
    room_dim = [2, 2, 2]
 
    e_absorption, max_order = pra.inverse_sabine(rt60_tgt, room_dim)
    room = pra.ShoeBox(room_dim, fs=sr, materials=pra.Material(e_absorption), max_order=max_order)
    room.add_source([1.5, 1.5, 1.5])
    room.add_microphone([0.1, 0.5, 0.1])
    room.compute_rir()
    rir = room.rir[0][0]
    rir = rir[np.argmax(rir):]
    # x 经过房间反射得到 y
    y = np.convolve(x,rir)
    scale = np.sqrt(np.mean(x**2)) /  np.sqrt(np.mean(y**2))
    # y 为经过反射后到达麦克风的声音
    y = y*scale
 
    L = max(len(y),len(v))
    y = np.pad(y,[0,L-len(y)]) # 补零,使其信号长度一致
    v = np.pad(v,[L-len(v),0]) # 补零,使其信号长度一致
    x = np.pad(x,[0,L-len(x)]) # 补零,使其信号长度一致
    d = v + y
    return x,d
 
if __name__ == "__main__":
    x_org, sr  = librosa.load('female.wav',sr=8000)
    v_org, sr  = librosa.load('male.wav',sr=8000)  # 采样率为8000,不加sr的话默认是22kHz
 
    x,d = creat_sim_sound(x_org,v_org)
 
    e =  lms(x, d,N=256,mu=0.1)
    sf.write('x.wav', x, sr, subtype='PCM_16')
    sf.write('d.wav', d, sr, subtype='PCM_16')
    sf.write('lms.wav', e, sr, subtype='PCM_16')
参考资料:

https://www.bilibili.com/video/BV1LP411j7yy/?spm_id_from=333.788&vd_source=77c874a500ef21df351103560dada737

https://www.cnblogs.com/LXP-Never/p/11773190.html