浅谈回声消除中的回声抑制(echo suppress)

翻看pjproject中的源码,发现它实现了一个回声消除的例子aectest.c,它主要依赖三种算法(1=speex, 2=echo suppress, 3=WebRtc),这是可选的,实际使用时选择其中的一种。

它调用的一个命令行为:aectest -d 100 -a 1 ../bin/orig8.wav ../bin/echo8.wav ../bin/result8.wav

-d选项是延迟,因为远端进来的参考声音信号被扬声器播放出来,再到麦克风拾取后读出来,存在一定的延时。

-a选项是选择算法类型,其中echo suppress是pjproject自己实现的,在源码echo_suppress.c中。而speex和WebRtc是从开源项目中搬过来的,放在了third_party目录下面。

这个例子对于学习和研究回声消除是比较有用的。

这里的echo suppress是回声抑制算法,这个依赖于双端发生检测(这是一个对二者电平进行不断统计的过程),就是将远端传过来的声音的电平与近端录制的声音的电平相比较,如果远端在发声,则将近端录制的声音进行抑制,抑制的过程也是一个平滑过渡的过程。

这种抑制的方法是非线性的,有时候会造成扬声器的播放断断续续,但也简单实用。

一份较老的代码echo_suppress.c实现:

  1. /* $Id: echo_suppress.c 1417 2007-08-16 10:11:44Z bennylp $ */   
  2. /*   
  3.  * Copyright (C) 2003-2007 Benny Prijono   
  4.  *  
  5.  * This program is free software; you can redistribute it and/or modify  
  6.  * it under the terms of the GNU General Public License as published by  
  7.  * the Free Software Foundation; either version 2 of the License, or  
  8.  * (at your option) any later version.  
  9.  *  
  10.  * This program is distributed in the hope that it will be useful,  
  11.  * but WITHOUT ANY WARRANTY; without even the implied warranty of  
  12.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the  
  13.  * GNU General Public License for more details.  
  14.  *  
  15.  * You should have received a copy of the GNU General Public License  
  16.  * along with this program; if not, write to the Free Software  
  17.  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA   
  18.  */   
  19. #include    
  20. #include    
  21. #include    
  22. #include    
  23. #include    
  24. #include    
  25. #include    
  26. #include    
  27.    
  28. #include "echo_internal.h"   
  29.    
  30. #define THIS_FILE               "echo_suppress.c"   
  31.    
  32.    
  33. /*  
  34.  * Simple echo suppresor  
  35.  */   
  36. typedef struct echo_supp   
  37. {   
  38.     pj_bool_t        suppressing;   
  39.     pjmedia_silence_det *sd;   
  40.     pj_time_val      last_signal;   
  41.     unsigned         samples_per_frame;   
  42.     unsigned         tail_ms;   
  43. } echo_supp;   
  44.    
  45.    
  46.    
  47. /*  
  48.  * Create.   
  49.  */   
  50. PJ_DEF(pj_status_t) echo_supp_create( pj_pool_t *pool,   
  51.                       unsigned clock_rate,   
  52.                       unsigned samples_per_frame,   
  53.                       unsigned tail_ms,   
  54.                       unsigned latency_ms,   
  55.                       unsigned options,   
  56.                       void **p_state )   
  57. {   
  58.     echo_supp *ec;   
  59.     pj_status_t status;   
  60.    
  61.     PJ_UNUSED_ARG(clock_rate);   
  62.     PJ_UNUSED_ARG(options);   
  63.     PJ_UNUSED_ARG(latency_ms);   
  64.    
  65.     ec = PJ_POOL_ZALLOC_T(pool, struct echo_supp);   
  66.     ec->samples_per_frame = samples_per_frame;   
  67.     ec->tail_ms = tail_ms;   
  68.    
  69.     status = pjmedia_silence_det_create(pool, clock_rate, samples_per_frame,   
  70.                     &ec->sd);   
  71.     if (status != PJ_SUCCESS)   
  72.     return status;   
  73.    
  74.     pjmedia_silence_det_set_name(ec->sd, "ecsu%p");   
  75.     pjmedia_silence_det_set_adaptive(ec->sd, PJMEDIA_ECHO_SUPPRESS_THRESHOLD);   
  76.     pjmedia_silence_det_set_params(ec->sd, 100, 500, 3000);   
  77.    
  78.     *p_state = ec;   
  79.     return PJ_SUCCESS;   
  80. }   
  81.    
  82.    
  83. /*  
  84.  * Destroy.   
  85.  */   
  86. PJ_DEF(pj_status_t) echo_supp_destroy(void *state)   
  87. {   
  88.     PJ_UNUSED_ARG(state);   
  89.     return PJ_SUCCESS;   
  90. }   
  91.    
  92.    
  93. /*  
  94.  * Let the AEC knows that a frame has been played to the speaker.  
  95.  */   
  96. PJ_DEF(pj_status_t) echo_supp_playback( void *state,   
  97.                     pj_int16_t *play_frm )   
  98. {   
  99.     echo_supp *ec = (echo_supp*) state;   
  100.     pj_bool_t silence;   
  101.     pj_bool_t last_suppressing = ec->suppressing;   
  102.    
  103.     silence = pjmedia_silence_det_detect(ec->sd, play_frm,   
  104.                      ec->samples_per_frame, NULL);   
  105.    
  106.     ec->suppressing = !silence;   
  107.    
  108.     if (ec->suppressing) {   
  109.     pj_gettimeofday(&ec->last_signal);   
  110.     }   
  111.    
  112.     if (ec->suppressing!=0 && last_suppressing==0) {   
  113.     PJ_LOG(5,(THIS_FILE, "Start suppressing.."));   
  114.     } else if (ec->suppressing==0 && last_suppressing!=0) {   
  115.     PJ_LOG(5,(THIS_FILE, "Stop suppressing.."));   
  116.     }   
  117.    
  118.     return PJ_SUCCESS;   
  119. }   
  120.    
  121.    
  122. /*  
  123.  * Let the AEC knows that a frame has been captured from the microphone.  
  124.  */   
  125. PJ_DEF(pj_status_t) echo_supp_capture( void *state,   
  126.                        pj_int16_t *rec_frm,   
  127.                        unsigned options )   
  128. {   
  129.     echo_supp *ec = (echo_supp*) state;   
  130.     pj_time_val now;   
  131.     unsigned delay_ms;   
  132.    
  133.     PJ_UNUSED_ARG(options);   
  134.    
  135.     pj_gettimeofday(&now);   
  136.    
  137.     PJ_TIME_VAL_SUB(now, ec->last_signal);   
  138.     delay_ms = PJ_TIME_VAL_MSEC(now);   
  139.    
  140.     if (delay_ms < ec->tail_ms) {   
  141. #if defined(PJMEDIA_ECHO_SUPPRESS_FACTOR) && PJMEDIA_ECHO_SUPPRESS_FACTOR!=0   
  142.     unsigned i;   
  143.     for (i=0; isamples_per_frame; ++i) {   
  144.         rec_frm[i] = (pj_int16_t)(rec_frm[i] >>    
  145.                       PJMEDIA_ECHO_SUPPRESS_FACTOR);   
  146.     }   
  147. #else   
  148.     pjmedia_zero_samples(rec_frm, ec->samples_per_frame);   
  149. #endif   
  150.     }   
  151.    
  152.     return PJ_SUCCESS;   
  153. }   
  154.    
  155.    
  156. /*  
  157.  * Perform echo cancellation.  
  158.  */   
  159. PJ_DEF(pj_status_t) echo_supp_cancel_echo( void *state,   
  160.                        pj_int16_t *rec_frm,   
  161.                        const pj_int16_t *play_frm,   
  162.                        unsigned options,   
  163.                        void *reserved )   
  164. {   
  165.     echo_supp *ec = (echo_supp*) state;   
  166.     pj_bool_t silence;   
  167.    
  168.     PJ_UNUSED_ARG(options);   
  169.     PJ_UNUSED_ARG(reserved);   
  170.    
  171.     silence = pjmedia_silence_det_detect(ec->sd, play_frm,    
  172.                      ec->samples_per_frame, NULL);   
  173.    
  174.     if (!silence) {   
  175. #if defined(PJMEDIA_ECHO_SUPPRESS_FACTOR) && PJMEDIA_ECHO_SUPPRESS_FACTOR!=0   
  176.     unsigned i;   
  177.     for (i=0; isamples_per_frame; ++i) {   
  178.         rec_frm[i] = (pj_int16_t)(rec_frm[i] >>    
  179.                       PJMEDIA_ECHO_SUPPRESS_FACTOR);   
  180.     }   
  181. #else   
  182.     pjmedia_zero_samples(rec_frm, ec->samples_per_frame);   
  183. #endif   
  184.     }   
  185.    
  186.     return PJ_SUCCESS;   
  187. }   
  188.  

可供初学者一睹其原貌,但最新的pjproject中的实现就比较复杂。

 

你可能感兴趣的:(流媒体与服务器)