153a5a1b3Sopenharmony_ci/*** 253a5a1b3Sopenharmony_ci This file is part of PulseAudio. 353a5a1b3Sopenharmony_ci 453a5a1b3Sopenharmony_ci Copyright 2010 Wim Taymans <wim.taymans@gmail.com> 553a5a1b3Sopenharmony_ci 653a5a1b3Sopenharmony_ci Contributor: Arun Raghavan <arun.raghavan@collabora.co.uk> 753a5a1b3Sopenharmony_ci 853a5a1b3Sopenharmony_ci PulseAudio is free software; you can redistribute it and/or modify 953a5a1b3Sopenharmony_ci it under the terms of the GNU Lesser General Public License as published 1053a5a1b3Sopenharmony_ci by the Free Software Foundation; either version 2.1 of the License, 1153a5a1b3Sopenharmony_ci or (at your option) any later version. 1253a5a1b3Sopenharmony_ci 1353a5a1b3Sopenharmony_ci PulseAudio is distributed in the hope that it will be useful, but 1453a5a1b3Sopenharmony_ci WITHOUT ANY WARRANTY; without even the implied warranty of 1553a5a1b3Sopenharmony_ci MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 1653a5a1b3Sopenharmony_ci General Public License for more details. 1753a5a1b3Sopenharmony_ci 1853a5a1b3Sopenharmony_ci You should have received a copy of the GNU Lesser General Public License 1953a5a1b3Sopenharmony_ci along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. 2053a5a1b3Sopenharmony_ci***/ 2153a5a1b3Sopenharmony_ci 2253a5a1b3Sopenharmony_ci#ifdef HAVE_CONFIG_H 2353a5a1b3Sopenharmony_ci#include <config.h> 2453a5a1b3Sopenharmony_ci#endif 2553a5a1b3Sopenharmony_ci 2653a5a1b3Sopenharmony_ci#include <pulsecore/core-util.h> 2753a5a1b3Sopenharmony_ci#include <pulsecore/modargs.h> 2853a5a1b3Sopenharmony_ci#include "echo-cancel.h" 2953a5a1b3Sopenharmony_ci 3053a5a1b3Sopenharmony_ci/* should be between 10-20 ms */ 3153a5a1b3Sopenharmony_ci#define DEFAULT_FRAME_SIZE_MS 20 3253a5a1b3Sopenharmony_ci/* should be between 100-500 ms */ 3353a5a1b3Sopenharmony_ci#define DEFAULT_FILTER_SIZE_MS 200 3453a5a1b3Sopenharmony_ci#define DEFAULT_AGC_ENABLED true 3553a5a1b3Sopenharmony_ci#define DEFAULT_DENOISE_ENABLED true 3653a5a1b3Sopenharmony_ci#define DEFAULT_DEREVERB_ENABLED true 3753a5a1b3Sopenharmony_ci#define DEFAULT_ECHO_SUPPRESS_ENABLED true 3853a5a1b3Sopenharmony_ci#define DEFAULT_ECHO_SUPPRESS_ATTENUATION 0 3953a5a1b3Sopenharmony_ci 4053a5a1b3Sopenharmony_cistatic const char* const valid_modargs[] = { 4153a5a1b3Sopenharmony_ci "frame_size_ms", 4253a5a1b3Sopenharmony_ci "filter_size_ms", 4353a5a1b3Sopenharmony_ci "agc", 4453a5a1b3Sopenharmony_ci "denoise", 4553a5a1b3Sopenharmony_ci "dereverb", 4653a5a1b3Sopenharmony_ci "echo_suppress", 4753a5a1b3Sopenharmony_ci "echo_suppress_attenuation", 4853a5a1b3Sopenharmony_ci "echo_suppress_attenuation_active", 4953a5a1b3Sopenharmony_ci NULL 5053a5a1b3Sopenharmony_ci}; 5153a5a1b3Sopenharmony_ci 5253a5a1b3Sopenharmony_cistatic void speex_ec_fixate_spec(pa_sample_spec *rec_ss, pa_channel_map *rec_map, 5353a5a1b3Sopenharmony_ci pa_sample_spec *play_ss, pa_channel_map *play_map, 5453a5a1b3Sopenharmony_ci pa_sample_spec *out_ss, pa_channel_map *out_map) { 5553a5a1b3Sopenharmony_ci out_ss->format = PA_SAMPLE_S16NE; 5653a5a1b3Sopenharmony_ci 5753a5a1b3Sopenharmony_ci *play_ss = *out_ss; 5853a5a1b3Sopenharmony_ci *play_map = *out_map; 5953a5a1b3Sopenharmony_ci *rec_ss = *out_ss; 6053a5a1b3Sopenharmony_ci *rec_map = *out_map; 6153a5a1b3Sopenharmony_ci} 6253a5a1b3Sopenharmony_ci 6353a5a1b3Sopenharmony_cistatic bool pa_speex_ec_preprocessor_init(pa_echo_canceller *ec, pa_sample_spec *out_ss, uint32_t nframes, pa_modargs *ma) { 6453a5a1b3Sopenharmony_ci bool agc; 6553a5a1b3Sopenharmony_ci bool denoise; 6653a5a1b3Sopenharmony_ci bool dereverb; 6753a5a1b3Sopenharmony_ci bool echo_suppress; 6853a5a1b3Sopenharmony_ci int32_t echo_suppress_attenuation; 6953a5a1b3Sopenharmony_ci int32_t echo_suppress_attenuation_active; 7053a5a1b3Sopenharmony_ci 7153a5a1b3Sopenharmony_ci agc = DEFAULT_AGC_ENABLED; 7253a5a1b3Sopenharmony_ci if (pa_modargs_get_value_boolean(ma, "agc", &agc) < 0) { 7353a5a1b3Sopenharmony_ci pa_log("Failed to parse agc value"); 7453a5a1b3Sopenharmony_ci goto fail; 7553a5a1b3Sopenharmony_ci } 7653a5a1b3Sopenharmony_ci 7753a5a1b3Sopenharmony_ci denoise = DEFAULT_DENOISE_ENABLED; 7853a5a1b3Sopenharmony_ci if (pa_modargs_get_value_boolean(ma, "denoise", &denoise) < 0) { 7953a5a1b3Sopenharmony_ci pa_log("Failed to parse denoise value"); 8053a5a1b3Sopenharmony_ci goto fail; 8153a5a1b3Sopenharmony_ci } 8253a5a1b3Sopenharmony_ci 8353a5a1b3Sopenharmony_ci dereverb = DEFAULT_DEREVERB_ENABLED; 8453a5a1b3Sopenharmony_ci if (pa_modargs_get_value_boolean(ma, "dereverb", &dereverb) < 0) { 8553a5a1b3Sopenharmony_ci pa_log("Failed to parse dereverb value"); 8653a5a1b3Sopenharmony_ci goto fail; 8753a5a1b3Sopenharmony_ci } 8853a5a1b3Sopenharmony_ci 8953a5a1b3Sopenharmony_ci echo_suppress = DEFAULT_ECHO_SUPPRESS_ENABLED; 9053a5a1b3Sopenharmony_ci if (pa_modargs_get_value_boolean(ma, "echo_suppress", &echo_suppress) < 0) { 9153a5a1b3Sopenharmony_ci pa_log("Failed to parse echo_suppress value"); 9253a5a1b3Sopenharmony_ci goto fail; 9353a5a1b3Sopenharmony_ci } 9453a5a1b3Sopenharmony_ci 9553a5a1b3Sopenharmony_ci echo_suppress_attenuation = DEFAULT_ECHO_SUPPRESS_ATTENUATION; 9653a5a1b3Sopenharmony_ci if (pa_modargs_get_value_s32(ma, "echo_suppress_attenuation", &echo_suppress_attenuation) < 0) { 9753a5a1b3Sopenharmony_ci pa_log("Failed to parse echo_suppress_attenuation value"); 9853a5a1b3Sopenharmony_ci goto fail; 9953a5a1b3Sopenharmony_ci } 10053a5a1b3Sopenharmony_ci if (echo_suppress_attenuation > 0) { 10153a5a1b3Sopenharmony_ci pa_log("echo_suppress_attenuation should be a negative dB value"); 10253a5a1b3Sopenharmony_ci goto fail; 10353a5a1b3Sopenharmony_ci } 10453a5a1b3Sopenharmony_ci 10553a5a1b3Sopenharmony_ci echo_suppress_attenuation_active = DEFAULT_ECHO_SUPPRESS_ATTENUATION; 10653a5a1b3Sopenharmony_ci if (pa_modargs_get_value_s32(ma, "echo_suppress_attenuation_active", &echo_suppress_attenuation_active) < 0) { 10753a5a1b3Sopenharmony_ci pa_log("Failed to parse echo_suppress_attenuation_active value"); 10853a5a1b3Sopenharmony_ci goto fail; 10953a5a1b3Sopenharmony_ci } 11053a5a1b3Sopenharmony_ci if (echo_suppress_attenuation_active > 0) { 11153a5a1b3Sopenharmony_ci pa_log("echo_suppress_attenuation_active should be a negative dB value"); 11253a5a1b3Sopenharmony_ci goto fail; 11353a5a1b3Sopenharmony_ci } 11453a5a1b3Sopenharmony_ci 11553a5a1b3Sopenharmony_ci if (agc || denoise || dereverb || echo_suppress) { 11653a5a1b3Sopenharmony_ci spx_int32_t tmp; 11753a5a1b3Sopenharmony_ci 11853a5a1b3Sopenharmony_ci if (out_ss->channels != 1) { 11953a5a1b3Sopenharmony_ci pa_log("AGC, denoising, dereverb and echo suppression only work with channels=1"); 12053a5a1b3Sopenharmony_ci goto fail; 12153a5a1b3Sopenharmony_ci } 12253a5a1b3Sopenharmony_ci 12353a5a1b3Sopenharmony_ci ec->params.speex.pp_state = speex_preprocess_state_init(nframes, out_ss->rate); 12453a5a1b3Sopenharmony_ci 12553a5a1b3Sopenharmony_ci tmp = agc; 12653a5a1b3Sopenharmony_ci speex_preprocess_ctl(ec->params.speex.pp_state, SPEEX_PREPROCESS_SET_AGC, &tmp); 12753a5a1b3Sopenharmony_ci 12853a5a1b3Sopenharmony_ci tmp = denoise; 12953a5a1b3Sopenharmony_ci speex_preprocess_ctl(ec->params.speex.pp_state, SPEEX_PREPROCESS_SET_DENOISE, &tmp); 13053a5a1b3Sopenharmony_ci 13153a5a1b3Sopenharmony_ci tmp = dereverb; 13253a5a1b3Sopenharmony_ci speex_preprocess_ctl(ec->params.speex.pp_state, SPEEX_PREPROCESS_SET_DEREVERB, &tmp); 13353a5a1b3Sopenharmony_ci 13453a5a1b3Sopenharmony_ci if (echo_suppress) { 13553a5a1b3Sopenharmony_ci if (echo_suppress_attenuation) 13653a5a1b3Sopenharmony_ci speex_preprocess_ctl(ec->params.speex.pp_state, SPEEX_PREPROCESS_SET_ECHO_SUPPRESS, 13753a5a1b3Sopenharmony_ci &echo_suppress_attenuation); 13853a5a1b3Sopenharmony_ci 13953a5a1b3Sopenharmony_ci if (echo_suppress_attenuation_active) { 14053a5a1b3Sopenharmony_ci speex_preprocess_ctl(ec->params.speex.pp_state, SPEEX_PREPROCESS_SET_ECHO_SUPPRESS_ACTIVE, 14153a5a1b3Sopenharmony_ci &echo_suppress_attenuation_active); 14253a5a1b3Sopenharmony_ci } 14353a5a1b3Sopenharmony_ci } 14453a5a1b3Sopenharmony_ci 14553a5a1b3Sopenharmony_ci speex_preprocess_ctl(ec->params.speex.pp_state, SPEEX_PREPROCESS_SET_ECHO_STATE, 14653a5a1b3Sopenharmony_ci ec->params.speex.state); 14753a5a1b3Sopenharmony_ci 14853a5a1b3Sopenharmony_ci pa_log_info("Loaded speex preprocessor with params: agc=%s, denoise=%s, dereverb=%s, echo_suppress=%s", 14953a5a1b3Sopenharmony_ci pa_yes_no(agc), pa_yes_no(denoise), pa_yes_no(dereverb), pa_yes_no(echo_suppress)); 15053a5a1b3Sopenharmony_ci } else 15153a5a1b3Sopenharmony_ci pa_log_info("All preprocessing options are disabled"); 15253a5a1b3Sopenharmony_ci 15353a5a1b3Sopenharmony_ci return true; 15453a5a1b3Sopenharmony_ci 15553a5a1b3Sopenharmony_cifail: 15653a5a1b3Sopenharmony_ci return false; 15753a5a1b3Sopenharmony_ci} 15853a5a1b3Sopenharmony_ci 15953a5a1b3Sopenharmony_cibool pa_speex_ec_init(pa_core *c, pa_echo_canceller *ec, 16053a5a1b3Sopenharmony_ci pa_sample_spec *rec_ss, pa_channel_map *rec_map, 16153a5a1b3Sopenharmony_ci pa_sample_spec *play_ss, pa_channel_map *play_map, 16253a5a1b3Sopenharmony_ci pa_sample_spec *out_ss, pa_channel_map *out_map, 16353a5a1b3Sopenharmony_ci uint32_t *nframes, const char *args) { 16453a5a1b3Sopenharmony_ci int rate; 16553a5a1b3Sopenharmony_ci uint32_t frame_size_ms, filter_size_ms; 16653a5a1b3Sopenharmony_ci pa_modargs *ma; 16753a5a1b3Sopenharmony_ci 16853a5a1b3Sopenharmony_ci if (!(ma = pa_modargs_new(args, valid_modargs))) { 16953a5a1b3Sopenharmony_ci pa_log("Failed to parse submodule arguments."); 17053a5a1b3Sopenharmony_ci goto fail; 17153a5a1b3Sopenharmony_ci } 17253a5a1b3Sopenharmony_ci 17353a5a1b3Sopenharmony_ci filter_size_ms = DEFAULT_FILTER_SIZE_MS; 17453a5a1b3Sopenharmony_ci if (pa_modargs_get_value_u32(ma, "filter_size_ms", &filter_size_ms) < 0 || filter_size_ms < 1 || filter_size_ms > 2000) { 17553a5a1b3Sopenharmony_ci pa_log("Invalid filter_size_ms specification"); 17653a5a1b3Sopenharmony_ci goto fail; 17753a5a1b3Sopenharmony_ci } 17853a5a1b3Sopenharmony_ci 17953a5a1b3Sopenharmony_ci frame_size_ms = DEFAULT_FRAME_SIZE_MS; 18053a5a1b3Sopenharmony_ci if (pa_modargs_get_value_u32(ma, "frame_size_ms", &frame_size_ms) < 0 || frame_size_ms < 1 || frame_size_ms > 200) { 18153a5a1b3Sopenharmony_ci pa_log("Invalid frame_size_ms specification"); 18253a5a1b3Sopenharmony_ci goto fail; 18353a5a1b3Sopenharmony_ci } 18453a5a1b3Sopenharmony_ci 18553a5a1b3Sopenharmony_ci speex_ec_fixate_spec(rec_ss, rec_map, play_ss, play_map, out_ss, out_map); 18653a5a1b3Sopenharmony_ci 18753a5a1b3Sopenharmony_ci rate = out_ss->rate; 18853a5a1b3Sopenharmony_ci *nframes = pa_echo_canceller_blocksize_power2(rate, frame_size_ms); 18953a5a1b3Sopenharmony_ci 19053a5a1b3Sopenharmony_ci pa_log_debug ("Using nframes %d, channels %d, rate %d", *nframes, out_ss->channels, out_ss->rate); 19153a5a1b3Sopenharmony_ci ec->params.speex.state = speex_echo_state_init_mc(*nframes, (rate * filter_size_ms) / 1000, out_ss->channels, out_ss->channels); 19253a5a1b3Sopenharmony_ci 19353a5a1b3Sopenharmony_ci if (!ec->params.speex.state) 19453a5a1b3Sopenharmony_ci goto fail; 19553a5a1b3Sopenharmony_ci 19653a5a1b3Sopenharmony_ci speex_echo_ctl(ec->params.speex.state, SPEEX_ECHO_SET_SAMPLING_RATE, &rate); 19753a5a1b3Sopenharmony_ci 19853a5a1b3Sopenharmony_ci if (!pa_speex_ec_preprocessor_init(ec, out_ss, *nframes, ma)) 19953a5a1b3Sopenharmony_ci goto fail; 20053a5a1b3Sopenharmony_ci 20153a5a1b3Sopenharmony_ci pa_modargs_free(ma); 20253a5a1b3Sopenharmony_ci return true; 20353a5a1b3Sopenharmony_ci 20453a5a1b3Sopenharmony_cifail: 20553a5a1b3Sopenharmony_ci if (ma) 20653a5a1b3Sopenharmony_ci pa_modargs_free(ma); 20753a5a1b3Sopenharmony_ci if (ec->params.speex.pp_state) { 20853a5a1b3Sopenharmony_ci speex_preprocess_state_destroy(ec->params.speex.pp_state); 20953a5a1b3Sopenharmony_ci ec->params.speex.pp_state = NULL; 21053a5a1b3Sopenharmony_ci } 21153a5a1b3Sopenharmony_ci if (ec->params.speex.state) { 21253a5a1b3Sopenharmony_ci speex_echo_state_destroy(ec->params.speex.state); 21353a5a1b3Sopenharmony_ci ec->params.speex.state = NULL; 21453a5a1b3Sopenharmony_ci } 21553a5a1b3Sopenharmony_ci return false; 21653a5a1b3Sopenharmony_ci} 21753a5a1b3Sopenharmony_ci 21853a5a1b3Sopenharmony_civoid pa_speex_ec_run(pa_echo_canceller *ec, const uint8_t *rec, const uint8_t *play, uint8_t *out) { 21953a5a1b3Sopenharmony_ci speex_echo_cancellation(ec->params.speex.state, (const spx_int16_t *) rec, (const spx_int16_t *) play, 22053a5a1b3Sopenharmony_ci (spx_int16_t *) out); 22153a5a1b3Sopenharmony_ci 22253a5a1b3Sopenharmony_ci /* preprecessor is run after AEC. This is not a mistake! */ 22353a5a1b3Sopenharmony_ci if (ec->params.speex.pp_state) 22453a5a1b3Sopenharmony_ci speex_preprocess_run(ec->params.speex.pp_state, (spx_int16_t *) out); 22553a5a1b3Sopenharmony_ci} 22653a5a1b3Sopenharmony_ci 22753a5a1b3Sopenharmony_civoid pa_speex_ec_done(pa_echo_canceller *ec) { 22853a5a1b3Sopenharmony_ci if (ec->params.speex.pp_state) { 22953a5a1b3Sopenharmony_ci speex_preprocess_state_destroy(ec->params.speex.pp_state); 23053a5a1b3Sopenharmony_ci ec->params.speex.pp_state = NULL; 23153a5a1b3Sopenharmony_ci } 23253a5a1b3Sopenharmony_ci 23353a5a1b3Sopenharmony_ci if (ec->params.speex.state) { 23453a5a1b3Sopenharmony_ci speex_echo_state_destroy(ec->params.speex.state); 23553a5a1b3Sopenharmony_ci ec->params.speex.state = NULL; 23653a5a1b3Sopenharmony_ci } 23753a5a1b3Sopenharmony_ci} 238