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

    xiaoxiao2022-07-02  128

    翻看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实现:

    /* $Id: echo_suppress.c 1417 2007-08-16 10:11:44Z bennylp $ */   /*    * Copyright (C) 2003-2007 Benny Prijono <benny@prijono.org>   *   * This program is free software; you can redistribute it and/or modify   * it under the terms of the GNU General Public License as published by   * the Free Software Foundation; either version 2 of the License, or   * (at your option) any later version.   *   * This program is distributed in the hope that it will be useful,   * but WITHOUT ANY WARRANTY; without even the implied warranty of   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the   * GNU General Public License for more details.   *   * You should have received a copy of the GNU General Public License   * along with this program; if not, write to the Free Software   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA    */   #include <pjmedia/types.h>   #include <pjmedia/errno.h>   #include <pjmedia/silencedet.h>   #include <pj/assert.h>   #include <pj/lock.h>   #include <pj/log.h>   #include <pj/os.h>   #include <pj/pool.h>      #include "echo_internal.h"      #define THIS_FILE               "echo_suppress.c"         /*   * Simple echo suppresor   */   typedef struct echo_supp   {       pj_bool_t        suppressing;       pjmedia_silence_det *sd;       pj_time_val      last_signal;       unsigned         samples_per_frame;       unsigned         tail_ms;   } echo_supp;            /*   * Create.    */   PJ_DEF(pj_status_t) echo_supp_create( pj_pool_t *pool,                         unsigned clock_rate,                         unsigned samples_per_frame,                         unsigned tail_ms,                         unsigned latency_ms,                         unsigned options,                         void **p_state )   {       echo_supp *ec;       pj_status_t status;          PJ_UNUSED_ARG(clock_rate);       PJ_UNUSED_ARG(options);       PJ_UNUSED_ARG(latency_ms);          ec = PJ_POOL_ZALLOC_T(pool, struct echo_supp);       ec->samples_per_frame = samples_per_frame;       ec->tail_ms = tail_ms;          status = pjmedia_silence_det_create(pool, clock_rate, samples_per_frame,                       &ec->sd);       if (status != PJ_SUCCESS)       return status;          pjmedia_silence_det_set_name(ec->sd, "ecsu%p");       pjmedia_silence_det_set_adaptive(ec->sd, PJMEDIA_ECHO_SUPPRESS_THRESHOLD);       pjmedia_silence_det_set_params(ec->sd, 100, 500, 3000);          *p_state = ec;       return PJ_SUCCESS;   }         /*   * Destroy.    */   PJ_DEF(pj_status_t) echo_supp_destroy(void *state)   {       PJ_UNUSED_ARG(state);       return PJ_SUCCESS;   }         /*   * Let the AEC knows that a frame has been played to the speaker.   */   PJ_DEF(pj_status_t) echo_supp_playback( void *state,                       pj_int16_t *play_frm )   {       echo_supp *ec = (echo_supp*) state;       pj_bool_t silence;       pj_bool_t last_suppressing = ec->suppressing;          silence = pjmedia_silence_det_detect(ec->sd, play_frm,                        ec->samples_per_frame, NULL);          ec->suppressing = !silence;          if (ec->suppressing) {       pj_gettimeofday(&ec->last_signal);       }          if (ec->suppressing!=0 && last_suppressing==0) {       PJ_LOG(5,(THIS_FILE, "Start suppressing.."));       } else if (ec->suppressing==0 && last_suppressing!=0) {       PJ_LOG(5,(THIS_FILE, "Stop suppressing.."));       }          return PJ_SUCCESS;   }         /*   * Let the AEC knows that a frame has been captured from the microphone.   */   PJ_DEF(pj_status_t) echo_supp_capture( void *state,                          pj_int16_t *rec_frm,                          unsigned options )   {       echo_supp *ec = (echo_supp*) state;       pj_time_val now;       unsigned delay_ms;          PJ_UNUSED_ARG(options);          pj_gettimeofday(&now);          PJ_TIME_VAL_SUB(now, ec->last_signal);       delay_ms = PJ_TIME_VAL_MSEC(now);          if (delay_ms < ec->tail_ms) {   #if defined(PJMEDIA_ECHO_SUPPRESS_FACTOR) && PJMEDIA_ECHO_SUPPRESS_FACTOR!=0       unsigned i;       for (i=0; i<ec->samples_per_frame; ++i) {           rec_frm[i] = (pj_int16_t)(rec_frm[i] >>                          PJMEDIA_ECHO_SUPPRESS_FACTOR);       }   #else       pjmedia_zero_samples(rec_frm, ec->samples_per_frame);   #endif       }          return PJ_SUCCESS;   }         /*   * Perform echo cancellation.   */   PJ_DEF(pj_status_t) echo_supp_cancel_echo( void *state,                          pj_int16_t *rec_frm,                          const pj_int16_t *play_frm,                          unsigned options,                          void *reserved )   {       echo_supp *ec = (echo_supp*) state;       pj_bool_t silence;          PJ_UNUSED_ARG(options);       PJ_UNUSED_ARG(reserved);          silence = pjmedia_silence_det_detect(ec->sd, play_frm,                         ec->samples_per_frame, NULL);          if (!silence) {   #if defined(PJMEDIA_ECHO_SUPPRESS_FACTOR) && PJMEDIA_ECHO_SUPPRESS_FACTOR!=0       unsigned i;       for (i=0; i<ec->samples_per_frame; ++i) {           rec_frm[i] = (pj_int16_t)(rec_frm[i] >>                          PJMEDIA_ECHO_SUPPRESS_FACTOR);       }   #else       pjmedia_zero_samples(rec_frm, ec->samples_per_frame);   #endif       }          return PJ_SUCCESS;   }    

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

     

    最新回复(0)