题目说明

  • ACE 声学回声消除
  • FDAF 频域自适应滤波算法

回音产生原因

声学回音

  • 麦克风直接采集到喇叭播放的声音:直接耦合
  • 麦克风采集到空间反射造成的回声:远端信号

20240810173049

  • 直接耦合:距离很近,基本为接收信号延迟,一般设备会直接滤除。
  • 远端信号:成分复杂,为接收信号的多次残留叠加。

线路回音

  • 由于2-4线匹配耦合转换引入的线路回音,不做详细介绍。

20240810173102

系统建模

先验条件

  1. 麦克风采集信号有延迟
  2. 开始对话前有空白时间
  3. 对话过程会产生双向通话,产生混叠。

20240810173114

设:
接收信号的人为AA,发送信号到BB
接收信号为xx
经过空间反射的回声为yy
近端麦克风采集信号为vv
那么麦克风采集声音为d=v+yd=v+y
xxyy有很高的相关度
希望找到一个关系来模拟回声路径,设y=hxy‘ = hx
理想输出为v=dyv=d-y'
误差e=yy=yhxe=y-y'=y-hx
对于一个固定场景,hh最终应该是一个较为稳定的场景。

假设BB先说话,对于AA短时间处有:
v=0v=0,麦克风采集声音为yy,理想输出为00,实际为ee此时可以对hh估计;
双向通话中无法确定理想输出vv,此时无法对h进行估计;
反射回声y=0y=0时无法更新。
hh的估计建立在对话开始很短一段时间和单项通话过程。

完整的回声消除系统

此处主要针对后两个进行建模处理。

  1. 时延估计
  2. (线性)回声消除
  3. 双讲检测
  4. 非线性残余声学回声抑制

算法实现

FDAF算法

xx分块LLhh长度MM,一般取L=ML=M

Xk=FFT[xkLxk]2M点后端补0Hk=FFT[hk0]Yk=XkHk,取后Myk=IFFT(Yk)ek=dkyk2M点前端补0Ek=FFT[0ek]k=IFFT(XKHEk),取前MHk+1=Hk+μFFT[kL0]\begin{split} &X_k=FFT\begin{bmatrix} x_{k-L}\\ x_k \end{bmatrix}\\ &2M点后端补0,H_k = FFT\begin{bmatrix} h_{k}\\ 0 \end{bmatrix}\\ &Y_k = X_k \odot H_k,取后M点\\ &y_k = IFFT(Y_k)\\ &e_k = d_k - y_k\\ &2M点前端补0,E_k = FFT\begin{bmatrix} 0\\ e_k \end{bmatrix}\\ &\nabla _k = IFFT(X_K^H\odot E_k),取前M点\\ &H_{k+1} = H_k + \mu FFT\begin{bmatrix} \nabla _{k-L}\\ 0 \end{bmatrix} \end{split}

在数值上麦克风期望输出等于eke_k

双讲检测&确定h更新条件

采用DTD信号相关度,余弦相似性。
当没有近处信号vv,
麦克风采集信号d=v+y=yd=v+y=y,和输入信号相似度很大:

xTdxTx+dTd=xTyxTx+yTy=xThxxTx+(hx)Thx1\begin{split} \frac{x^Td}{\sqrt{x^Tx+d^Td}}=&\frac{x^Ty}{\sqrt{x^Tx+y^Ty}}\\ =&\frac{x^Thx}{\sqrt{x^Tx+(hx)^Thx}}\\ \approx & 1 \end{split}

当双向通信时候麦克风采集信号dd和输入信号xx相似度很小:

xTdxTx+dTd0\frac{x^Td}{\sqrt{x^Tx+d^Td}}\approx 0

设置合适阈值判断是否更新

  1. vvyy均为00hh不更新;
  2. v=0v=0yy \nehh开始更新;
  3. v0v \ne 0y=0y=0hh不更新.

为了确保1.可以有效计算,修正公式,其中ϵ\epsilon为一很小的给定常数。

xTdxTx+dTd+ϵ\frac{x^Td}{\sqrt{x^Tx+d^Td+\epsilon}}

性能分析&对比NLMS算法:

此处选择了一段5s以后开始的双讲混合音频。音频采样率16k,滤波器阶数2k。
从时域上来看还是很难看出性能,但是看时频谱图就可以看出FDAF算法还是很强的。
其实就是把NLMS的batch_size开大了。

音频对比

原始音频

NLMS滤波

DADF滤波

NLMS 时域

20240810173134

FDAF 时域

20240810173144

NLMS 对数

20240810173155

FDAF 对数

20240810173206

NLMS 时谱图

20240810173215

FDAF 时谱图

20240810173229

其他应用

  • 经过一定程度修改可用于音频中的背景音乐和人声分离。

不足

  • 未考虑滤波器参数更新计算时间。
  • 环境建模过于理想,实际的回声传播很复杂

代码参考

实际上并没有做双讲检测,但是效果依然不错。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
function [en, H] = FDAF(d,x,mu,mu_unconst, M, DTDbegin)
% 参数:
% d 输入信号(麦克风语音)
% x 远端语音
% mu 约束FDAF的步长
% M 滤波器阶数

x_new = zeros(M,1); % 将新块的M个样本初始化为0
x_old = zeros(M,1); % 将旧块的M个样本初始化为0

AdaptStart = 2*M; % 在获得2M个样本块后开始自适应
H = zeros(2*M,1); % 将2M个滤波器权重初始化为0
d_block = zeros(M,1); % 将期望信号的M个样本初始化为0

power_alpha = 0.5; % 常数以更新每个frequency bin的功率
power = zeros(2*M,1); % 将每个bin的平均功率初始化为0
d_length = length(d); % 输入序列的长度
en = []; % 误差向量
window_save_first_M = [ones(1,M), zeros(1,M)]'; % 设置向量以提取前M个元素 (2M,1)

for k = 1:d_length
x_new = [x_new(2:end); x(k)]; % 开始的输入信号块 (2M,1)
d_block = [d_block(2:end); d(k)]; % 开始的期望信号快 (M,1)
if mod(k,M)==0 % If iteration == block length,
x_blocks = [x_old; x_new]; % 2M样本的输入信号样本块 (2M,1)
x_old = x_new;
if k >= AdaptStart % 频域自适应滤波器

% 将参考信号转换到频域
Xk = fft(x_blocks); % (2M,1)

% 计算滤波器估计信号
Yk = Xk.*H; % 输入和权重向量的乘积(2M,1)*(2M,1)=(2M,1)
temp = real(ifft(Yk)); % IFFT 输出的实部 (2M,1)
yk = temp(M+1:2*M); % 抛弃前M个元素,保留后M个元素 (M,1)
% 计算误差信号
error = d_block-yk; % 误差信号块 (M,1)
Ek = fft([zeros(1,M),error']'); % 在FFT之前插入零块以形成2M块(2M,1)

% 更新信号功率估算
power = (power_alpha.*power) + (1 - power_alpha).*(abs(Xk).^2); % (2M,1)
% 计算频域中的梯度和权重更新
if k <DTDbegin
gradient = real(ifft((1./power).*conj(Xk).* Ek)); % (2M,1)
gradient = gradient.*window_save_first_M; % 去除后一个数据块,并且补零 (2M,1)
H = H + mu.*(fft(gradient)); % 权重是频域的 (2M,1)
else
gradient = conj(Xk).* Ek; % (2M,1)
H = H + mu_unconst.*gradient; % (2M,1)
end
en = [en; error];
end
end
end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
clear all;

[x, ~] = audioread('ref.wav');
[d, Fs] = audioread('mic_sig_spe_5.wav');
M =2001;
mu = 1;
mu_unconst = 0;
N=length(x);
DTDbegin = 50000;

[en, W] = FDAF(d,x,mu,mu_unconst, M, DTDbegin);
audiowrite('output_FDAF.wav',en,Fs)
%
figure(1)
subplot(3,1,1)
plot(d)
xlabel('time (samples)');
ylabel('d(n)');
grid on
axis([0 N -1 1]);
subplot(3,1,2)
plot(x)
xlabel('time (samples)');
ylabel('x(n)');
grid on
axis([0 N -1 1]);
subplot(3,1,3)
plot(en)
xlabel('time (samples)');
ylabel('e(n)');
grid on
axis([0 N -1 1]);


figure(2);
ee= 20 * log10(abs(en));
figure(2)
plot(ee)
xlabel('time (samples)');
ylabel('e(n)/dB');
grid on

s = spectrogram(d, 256);
t = linspace(0, 4*16000/Fs, size(s,1));
f = linspace(0, Fs/2, size(s,2));
figure(3);
subplot(1,3,1)
imagesc(t, f, 20*log10((abs(s))));xlabel('Samples'); ylabel('dn/Freqency');
colorbar;

s = spectrogram(d, 256);
t = linspace(0, 4*16000/Fs, size(s,1));
f = linspace(0, Fs/2, size(s,2));
subplot(1,3,2)
imagesc(t, f, 20*log10((abs(s))));xlabel('Samples'); ylabel('xn/Freqency');
colorbar;

s = spectrogram(en, 256);
t = linspace(0, 4*16000/Fs, size(s,1));
f = linspace(0, Fs/2, size(s,2));
subplot(1,3,3)
imagesc(t, f, 20*log10((abs(s))));xlabel('Samples'); ylabel('en/Freqency');
colorbar;