赞
踩
微信扫一扫,关注公众号“音频算法与工程实践”
Speex AEC算法分两部分:自适应滤波和最优步长因子的计算,分别基于以下两篇论文:
[1] J.-S. Soo and K. Pang, Multidelayblock frequency domain adaptive filter, 1990
[2] Jean-Marc Valin, On Adjusting theLearning Rate in Frequency Domain Echo Cancellation With Double-Talk, 2007
网上已经有博客讲解Speex AEC的代码流程,这种做法容易陷入代码运算细节而忽略对算法原理的整体把握。本系列文章先根据论文公式讲解代码实现,然后梳理AEC算法的前因后果,最后再回顾这两篇论文。
官网speex-1.1.11.1.tar.gz是浮点AEC,代码与论文基本一致。speex-1.1.12.tar.gz的AEC代码支持浮点和定点版本,代码新增加了一些其他处理。因此本文选择speex-1.1.11.1.tar.gz讲解,MDF论文公式如下图。
公式中N是指滤波器系数的长度。
公式中FFT长度N',论文要求N'>=2N/M,2是overlap-save方法要求使用两帧数据。
公式中delay blocks数量M=取整(滤波器长度/帧长)。
因此AEC初始化函数speex_echo_state_init()计算代码如下:frame_size、window_size、M分别对应公式中的N、N'、M。
/** Creates a new echo canceller state */
SpeexEchoState*speex_echo_state_init
(int frame_size, int filter_length)
{
SpeexEchoState *st = (SpeexEchoState *)
speex_alloc(sizeof(SpeexEchoState));
st->frame_size= frame_size;//160
st->window_size= 2*frame_size;//320
st->M =
(filter_length+st->frame_size-1)/frame_size;
//(8*160+160-1)/160=8
return st;
}
测试代码testecho.c如下,设置参数frame_size=160、filter_length=8*160、window_size=320、M=8,对应公式中的N=8*160、N' =320、M=8。
#define NN 160
st = speex_echo_state_init(NN,8*NN);
公式1是将连续的2帧音频数据从时域转换到频域,公式2是更新前M-1帧频域缓存数据,因此公式2应该在公式1的FFT计算之前完成。
对应AEC处理函数speex_echo_cancel()代码如下。
/** Performs echo cancellation on a frame */
void speex_echo_cancel(SpeexEchoState *st,
short*ref, short *echo,
short *out, float *Yout)
{
int i,j;
int N,M;
N =st->window_size;//320
M =st->M;//8
/* Copyinput data to buffer */
for(i=0;i<st->frame_size;i++)
{
st->x[i] = st->x[i+st->frame_size];
st->x[i+st->frame_size] = echo[i];
st->d[i] = st->d[i+st->frame_size];
st->d[i+st->frame_size] = ref[i];
}
/* Shiftmemory:
this could be optimized eventually*/
for(i=0;i<N*(M-1);i++)
st->X[i]=st->X[i+N];
/* Copynew echo frame */
for(i=0;i<N;i++)
st->X[(M-1)*N+i]=st->x[i];
/* Convertx (echo input) to
frequency domain */
spx_drft_forward(st->fft_lookup,
&st->X[(M-1)*N]);
}
代码说明:
1、在现在的技术交流中,ref一般指远端信号,echo指麦克风接收到的回声信号。但是V1.1.11.1代码含义刚好相反:变量echo是送给喇叭的声音(远端信号),也是自适应滤波器的输入信号。变量ref是麦克风采集的声音,也是自适应滤波器的期望输出信号。V1.1.12已经修改了这两个变量命名。
2、代码中变量N=window_size=320对应公式中N',与公式中N的含义不同。
3、spx_drft_forward输入输出buf都是同一个buf,频域数据丢弃两个0元素i[0]和i[N/2],因此时域和频域所需的buf长度也相同。最终的顺序是r[0], r[1],i[1],r[2],i[2], … , r[N/2-1],i[N/2-1], r[N/2]。
4、下列代码实现公式2,
/* Shiftmemory:
this could be optimized eventually*/
for(i=0;i<N*(M-1);i++)
st->X[i]=st->X[i+N];
变量d缓存的数据在下一帧不会再用,因此这2行代码多余。计算公式4时直接使用变量ref。
st->d[i] = st->d[i+st->frame_size];
st->d[i+st->frame_size] = ref[i];
其余代码都是实现公式1。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。