/* $Id$ */ /* * Copyright (C) 2011-2015 Teluu Inc. (http://www.teluu.com) * * 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 #include #include #include #include #include #if defined(PJMEDIA_HAS_WEBRTC_AEC) && PJMEDIA_HAS_WEBRTC_AEC != 0 #include #include #include #include "echo_internal.h" #define THIS_FILE "echo_webrtc.c" #if PJMEDIA_WEBRTC_AEC_USE_MOBILE == 1 #include #define NsHandle NsxHandle #define WebRtcNs_Create WebRtcNsx_Create #define WebRtcNs_Init WebRtcNsx_Init #define WebRtcNs_Free WebRtcNsx_Free #define WebRtcAec_Create WebRtcAecm_Create #define WebRtcAec_Init(handle, clock, sclock) WebRtcAecm_Init(handle, clock) #define WebRtcAec_Free WebRtcAecm_Free #define WebRtcAec_get_error_code WebRtcAecm_get_error_code #define WebRtcAec_enable_delay_agnostic(core, enable) #define WebRtcAec_set_config WebRtcAecm_set_config #define WebRtcAec_BufferFarend WebRtcAecm_BufferFarend #define AecConfig AecmConfig typedef short sample; #else #include typedef float sample; #endif #define BUF_LEN 160 /* Set this to 0 to disable metrics calculation. */ #define SHOW_DELAY_METRICS 1 typedef struct webrtc_ec { void *AEC_inst; NsHandle *NS_inst; unsigned options; unsigned samples_per_frame; unsigned tail; unsigned clock_rate; unsigned channel_count; unsigned subframe_len; sample tmp_buf[BUF_LEN]; sample tmp_buf2[BUF_LEN]; } webrtc_ec; static void print_webrtc_aec_error(const char *tag, void *AEC_inst) { unsigned status = WebRtcAec_get_error_code(AEC_inst); PJ_LOG(3, (THIS_FILE, "WebRTC AEC error (%s) %d ", tag, status)); } static void set_config(void *AEC_inst, unsigned options) { unsigned aggr_opt = options & PJMEDIA_ECHO_AGGRESSIVENESS_MASK; int status; AecConfig aec_config; #if PJMEDIA_WEBRTC_AEC_USE_MOBILE aec_config.echoMode = 3; if (aggr_opt == PJMEDIA_ECHO_AGGRESSIVENESS_CONSERVATIVE) aec_config.echoMode = 0; else if (aggr_opt == PJMEDIA_ECHO_AGGRESSIVENESS_AGGRESSIVE) aec_config.echoMode = 4; aec_config.cngMode = AecmTrue; #else aec_config.nlpMode = kAecNlpModerate; if (aggr_opt == PJMEDIA_ECHO_AGGRESSIVENESS_CONSERVATIVE) aec_config.nlpMode = kAecNlpConservative; else if (aggr_opt == PJMEDIA_ECHO_AGGRESSIVENESS_AGGRESSIVE) aec_config.nlpMode = kAecNlpAggressive; else aec_config.nlpMode = kAecNlpModerate; aec_config.skewMode = kAecFalse; #if SHOW_DELAY_METRICS aec_config.metricsMode = kAecTrue; aec_config.delay_logging = kAecTrue; #else aec_config.metricsMode = kAecFalse; aec_config.delay_logging = kAecFalse; #endif #endif status = WebRtcAec_set_config(AEC_inst, aec_config); if (status != 0) { print_webrtc_aec_error("Init config", AEC_inst); } } /* * Create the AEC. */ PJ_DEF(pj_status_t) webrtc_aec_create(pj_pool_t *pool, unsigned clock_rate, unsigned channel_count, unsigned samples_per_frame, unsigned tail_ms, unsigned options, void **p_echo ) { webrtc_ec *echo; int status; *p_echo = NULL; echo = PJ_POOL_ZALLOC_T(pool, webrtc_ec); PJ_ASSERT_RETURN(echo != NULL, PJ_ENOMEM); /* Currently we only support mono. */ if (channel_count != 1) { PJ_LOG(3, (THIS_FILE, "WebRTC AEC doesn't support stereo")); return PJ_ENOTSUP; } echo->channel_count = channel_count; echo->samples_per_frame = samples_per_frame; echo->tail = tail_ms; echo->clock_rate = clock_rate; /* SWB is processed as 160 frame size */ if (clock_rate > 8000) echo->subframe_len = 160; else echo->subframe_len = 80; echo->options = options; /* Create WebRTC AEC */ echo->AEC_inst = WebRtcAec_Create(); if (!echo->AEC_inst) { return PJ_ENOMEM; } /* Init WebRTC AEC */ status = WebRtcAec_Init(echo->AEC_inst, clock_rate, clock_rate); if (status != 0) { print_webrtc_aec_error("Init", echo->AEC_inst); WebRtcAec_Free(echo->AEC_inst); return PJ_ENOTSUP; } /* WebRtc is very dependent on delay calculation, which will be passed * to WebRtcAec_Process() below. A poor estimate, even by as little as * 40ms, may affect the echo cancellation results greatly. * Hence, we need to enable delay-agnostic echo cancellation. This * low-level feature relies on internally estimated delays between * the process and reverse streams, thus not relying on reported * system delays. * Still, with the delay agnostic feature, it may take some time (5-10s * or more) for the Aec module to learn the optimal delay, thus * a good initial estimate is necessary for good EC quality in * the beginning of a call. */ WebRtcAec_enable_delay_agnostic(WebRtcAec_aec_core(echo->AEC_inst), 1); set_config(echo->AEC_inst, options); if (options & PJMEDIA_ECHO_USE_NOISE_SUPPRESSOR) { echo->NS_inst = WebRtcNs_Create(); if (echo->NS_inst) { status = WebRtcNs_Init(echo->NS_inst, clock_rate); if (status != 0) { WebRtcNs_Free(echo->NS_inst); echo->NS_inst = NULL; } } if (!echo->NS_inst) { PJ_LOG(3, (THIS_FILE, "Unable to create WebRTC noise suppressor")); } } #if PJMEDIA_WEBRTC_AEC_USE_MOBILE PJ_LOG(3, (THIS_FILE, "WebRTC AEC mobile successfully created with " "options %d", options)); #else PJ_LOG(3, (THIS_FILE, "WebRTC AEC successfully created with " "options %d", options)); #endif /* Done */ *p_echo = echo; return PJ_SUCCESS; } /* * Destroy AEC */ PJ_DEF(pj_status_t) webrtc_aec_destroy(void *state ) { webrtc_ec *echo = (webrtc_ec*) state; PJ_ASSERT_RETURN(echo, PJ_EINVAL); if (echo->AEC_inst) { WebRtcAec_Free(echo->AEC_inst); echo->AEC_inst = NULL; } if (echo->NS_inst) { WebRtcNs_Free(echo->NS_inst); echo->NS_inst = NULL; } return PJ_SUCCESS; } /* * Reset AEC */ PJ_DEF(void) webrtc_aec_reset(void *state ) { webrtc_ec *echo = (webrtc_ec*) state; int status; pj_assert(echo != NULL); /* Re-initialize the EC */ status = WebRtcAec_Init(echo->AEC_inst, echo->clock_rate, echo->clock_rate); if (status != 0) { print_webrtc_aec_error("reset", echo->AEC_inst); return; } set_config(echo->AEC_inst, echo->options); PJ_LOG(4, (THIS_FILE, "WebRTC AEC reset succeeded")); } /* * Perform echo cancellation. */ PJ_DEF(pj_status_t) webrtc_aec_cancel_echo( void *state, pj_int16_t *rec_frm, const pj_int16_t *play_frm, unsigned options, void *reserved ) { webrtc_ec *echo = (webrtc_ec*) state; int status; unsigned i, j, frm_idx = 0; const sample * buf_ptr; sample * out_buf_ptr; PJ_UNUSED_ARG(options); PJ_UNUSED_ARG(reserved); /* Sanity checks */ PJ_ASSERT_RETURN(echo && rec_frm && play_frm, PJ_EINVAL); for(i = echo->samples_per_frame / echo->subframe_len; i > 0; i--) { #if PJMEDIA_WEBRTC_AEC_USE_MOBILE buf_ptr = &play_frm[frm_idx]; #else for (j = 0; j < echo->subframe_len; j++) { echo->tmp_buf[j] = rec_frm[frm_idx+j]; echo->tmp_buf2[j] = play_frm[frm_idx+j]; } buf_ptr = echo->tmp_buf2; #endif /* Feed farend buffer */ status = WebRtcAec_BufferFarend(echo->AEC_inst, buf_ptr, echo->subframe_len); if (status != 0) { print_webrtc_aec_error("Buffer farend", echo->AEC_inst); return PJ_EUNKNOWN; } buf_ptr = echo->tmp_buf; out_buf_ptr = echo->tmp_buf2; if (echo->NS_inst) { #if PJMEDIA_WEBRTC_AEC_USE_MOBILE buf_ptr = &rec_frm[frm_idx]; WebRtcNsx_Process(echo->NS_inst, &buf_ptr, echo->channel_count, &out_buf_ptr); buf_ptr = out_buf_ptr; out_buf_ptr = echo->tmp_buf; #else WebRtcNs_Analyze(echo->NS_inst, buf_ptr); #endif } /* Process echo cancellation */ #if PJMEDIA_WEBRTC_AEC_USE_MOBILE status = WebRtcAecm_Process(echo->AEC_inst, &rec_frm[frm_idx], (echo->NS_inst? buf_ptr: NULL), out_buf_ptr, echo->subframe_len, echo->tail); #else status = WebRtcAec_Process(echo->AEC_inst, &buf_ptr, echo->channel_count, &out_buf_ptr, echo->subframe_len, (int16_t)echo->tail, 0); #endif if (status != 0) { print_webrtc_aec_error("Process echo", echo->AEC_inst); return PJ_EUNKNOWN; } #if !PJMEDIA_WEBRTC_AEC_USE_MOBILE if (echo->NS_inst) { /* Noise suppression */ buf_ptr = echo->tmp_buf2; out_buf_ptr = echo->tmp_buf; WebRtcNs_Process(echo->NS_inst, &buf_ptr, echo->channel_count, &out_buf_ptr); } #endif for (j = 0; j < echo->subframe_len; j++) { rec_frm[frm_idx++] = (pj_int16_t)out_buf_ptr[j]; } } return PJ_SUCCESS; } PJ_DEF(pj_status_t) webrtc_aec_get_stat(void *state, pjmedia_echo_stat *p_stat) { webrtc_ec *echo = (webrtc_ec*) state; if (WebRtcAec_GetDelayMetrics(echo->AEC_inst, &p_stat->median, &p_stat->std, &p_stat->frac_delay) != 0) { return PJ_EUNKNOWN; } p_stat->name = "WebRTC AEC"; p_stat->stat_info.ptr = p_stat->buf_; p_stat->stat_info.slen = pj_ansi_snprintf(p_stat->buf_, sizeof(p_stat->buf_), "WebRTC delay metric: median=%d, std=%d, " "frac of poor delay=%.02f", p_stat->median, p_stat->std, p_stat->frac_delay); return PJ_SUCCESS; } #endif