声学回声消除是通过消除或者移除本地话筒中拾取到的远端的音频信号来阻止远端的声音返回去的一种处理方

法。linphone上使用speex 库实现了回声消除插件,speex库是目前开源的声学回声消除做的比较好的库。下

面总结一下,linphone上的回声消除部分。

 

第一部分--配置

linphone的声音部分,是可以配置的,初始化linphone的时候,会根据配置文件的内容来配置声音部分,包括

回声消除部分。

linphone_core_new()
      |
linphone_core_init()
      |
sound_config_read(lc)
{
......
#ifndef __ios 
     tmp=TRUE;
 #else
     tmp=FALSE; /* on iOS we have builtin echo cancellation.*/
 #endif    /从配置文件读取ec的配置,读取失败,则为tmp的值,否则,tmp被赋值
     tmp=lp_config_get_int(lc->config,"sound","echocancellation",tmp);    linphone_core_enable_echo_cancellation(lc,tmp);//保存ec
    //读取ea的值,默认值为0
     linphone_core_enable_echo_limiter(lc,  lp_config_get_int(lc->config,"sound","echolimiter",0));     linphone_core_enable_agc(lc,  lp_config_get_int(lc->config,"sound","agc",0));//读取agc的值,默认为0
......
}

这里,会从配置文件读取 echocancellation、echolimiter和agc的键值,如果配置文件里没有配置,那么用tmp

作为默认值。看看配置文件,竟然没有这几项的配置。。。那就是用tmp的值了,我是基于android平台,那么

配置的结果是:ec =1,ea=0,agc = 0;

其中一个地方要展开一下,

void linphone_call_enable_echo_limiter(LinphoneCall *call, bool_t val){
  if (call!=NULL && call->audiostream!=NULL ) {
   if (val) {
   const char *type=lp_config_get_string(call->core->config,"sound","el_type","mic");
   if (strcasecmp(type,"mic")==0)
    audio_stream_enable_echo_limiter(call->audiostream,ELControlMic);
   else if (strcasecmp(type,"full")==0)
    audio_stream_enable_echo_limiter(call->audiostream,ELControlFull);
   } else {
    audio_stream_enable_echo_limiter(call->audiostream,ELInactive);
   }
  }
 }

配置ea的时候,还会读取el_type 的配置,这是个字符串,值有三种:mic、full 和其他。

这个值用来设置什么呢?

void audio_stream_enable_echo_limiter(AudioStream *stream, EchoLimiterType type){
  stream->el_type=type;
  if (stream->volsend){
   bool_t enable_noise_gate = stream->el_type==ELControlFull;
   ms_filter_call_method(stream->volrecv,MS_VOLUME_ENABLE_NOISE_GATE,&enable_noise_gate);
   ms_filter_call_method(stream->volsend,MS_VOLUME_SET_PEER,type!=ELInactive?stream->volrecv:NULL);
  } else {
   ms_warning("cannot set echo limiter to mode [%i] because no volume send",type);
  }
 }stream->volrecv和stream->volsend都是插件MS_VOLUME_ID,可见,当键值是full的时候,会enable noise gate,

当键值是mic或full的时候,会把volsend 的peer插件设置为volrecv。

简单的说,是在VOLUME插件(声音增益插件)里处理回声。后面再细说。

 

第二部分--初始化

当接听电话时,会初始化音频流,同时会初始化回声消除插件。

call_accepted()
   |
linphone_core_update_streams()
   |
linphone_call_init_media_streams()
{
......
//1
call->audiostream=audiostream=audio_stream_new(md->streams[0].port,linphone_core_ipv6_enabled(lc));
//2
  if (linphone_core_echo_limiter_enabled(lc)){
   const char *type=lp_config_get_string(lc->config,"sound","el_type","mic");
   if (strcasecmp(type,"mic")==0)
    audio_stream_enable_echo_limiter(audiostream,ELControlMic);
   else if (strcasecmp(type,"full")==0)
    audio_stream_enable_echo_limiter(audiostream,ELControlFull);
  }//3
  audio_stream_enable_gain_control(audiostream,TRUE);//4
  if (linphone_core_echo_cancellation_enabled(lc)){
   int len,delay,framesize;
   const char *statestr=lp_config_get_string(lc->config,"sound","ec_state",NULL);
   len=lp_config_get_int(lc->config,"sound","ec_tail_len",0);
   delay=lp_config_get_int(lc->config,"sound","ec_delay",0);
   framesize=lp_config_get_int(lc->config,"sound","ec_framesize",0);
   audio_stream_set_echo_canceller_params(audiostream,len,delay,framesize);
   if (statestr && audiostream->ec){
    ms_filter_call_method(audiostream->ec,MS_ECHO_CANCELLER_SET_STATE_STRING,(void*)statestr);
   }
  }//5
  audio_stream_enable_automatic_gain_control(audiostream,linphone_core_agc_enabled(lc));
  {
   int enabled=lp_config_get_int(lc->config,"sound","noisegate",0);
   audio_stream_enable_noise_gate(audiostream,enabled);
  }...... 
}

1.在audio_stream_new函数里,会新建回声消除插件:stream->ec=ms_filter_new(MS_SPEEX_EC_ID);新建插

件时,会调用此插件的init回调。

2.根据ea的值配置VOLUME插件。

3.设置use_gc 为true。

4.根据配置文件,配置speexec插件。

5.根据agc配置use_agc,读取noisegate的键值,并配置volsend。

 

**这一部分代码都是初始化插件和配置插件,配置完了就可以启动音频流了。

顺便说下linphone 的音频插件组合:

发送:

ms_filter_link: MSAndSoundRead:0-->MSSpeexEC:1
ms_filter_link: MSSpeexEC:1-->MSVolume:0
ms_filter_link: MSVolume:0-->MSDtmfGen:0
ms_filter_link: MSDtmfGen:0-->MSAlawEnc:0
ms_filter_link: MSAlawEnc:0-->MSRtpSend:0

接收: 

ms_filter_link: MSRtpRecv:0-->MSAlawDec:0
ms_filter_link: MSAlawDec:0-->MSDtmfGen:0
 ms_filter_link: MSDtmfGen:0-->MSVolume:0
ms_filter_link: MSVolume:0-->MSEqualizer:0
ms_filter_link: MSEqualizer:0-->MSSpeexEC:0
ms_filter_link: MSSpeexEC:0-->MSAndSoundWrite:0

 

 

第三部分--运转

在调用init之后,马上就会开始链接发送和接收插件,并启动音频流。

call_accepted()
   |
linphone_core_update_streams()
   |
linphone_call_init_media_streams();
linphone_call_start_media_streams();
   |
linphone_call_start_audio_stream()
   |
audio_stream_start_full()
{
new rtp-send   dtmf-gen,volume filter;

调用插件的方法,设置参数;

链接音频插件发送的串;

链接音频插件接收的串;

启动ticker,运行这两个插件串;

 

总结一下,对回声消除起作用的两个插件是speexec 和volume,下面具体看这两个插件。

 

第四部分:MS_SPEEX_EC_ID插件

前面我们看到,在音频接收和发送的插件串里都有speexec插件,但是此插件只在audio_stream_new里新建了一次,

也就是说,发送和接收的插件串里使用的speexec是同一个!没错,speexec插件有两个输入和两个输出,

/* inputs[0]= reference signal from far end (sent to soundcard)
  * inputs[1]= near speech & echo signal (read from soundcard)
  * outputs[0]=  is a copy of inputs[0] to be sent to soundcard
  * outputs[1]=  near end speech, echo removed - towards far end
 */

speexec如何工作?

1.speexec插件先缓存本地录音的pcm流和远程的pcm流;

2.把远端的pcm流复制一份,交给声卡。

2.参考远端发送过来的pcm流,通过算法过滤掉本地录音pcm流的回声;

3.把得到的纯净的pcm流发给远端;

4.释放所有缓存;

5.同步本地音频流和远端音频流。

 

关于同步:我们知道,消除回声算法要做的就是从录制的声音数据中找到回声,并去除,这就需要参考正确的

远程声音数据,如果参考的内容不对,那肯定找不到回声了。所以远程的声音数据和本地声音数据的同步,就是

关键。我们本地录制的速度是均匀稳定的,而网络传输过来的远端数据的速度是不稳定的,linphone每间隔固定

的时间段,检查一次远程数据的大小,如果大于framesize,则认为积累了过多的远端数据,需要丢弃一部分。

 

 

第五部分:MS_VOLUME_ID插件

噪声抑制