1 /***
2 This file is part of PulseAudio.
3
4 Copyright 2010 Wim Taymans <wim.taymans@gmail.com>
5
6 Contributor: Arun Raghavan <arun.raghavan@collabora.co.uk>
7
8 PulseAudio is free software; you can redistribute it and/or modify
9 it under the terms of the GNU Lesser General Public License as published
10 by the Free Software Foundation; either version 2.1 of the License,
11 or (at your option) any later version.
12
13 PulseAudio is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public License
19 along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <pulsecore/core-util.h>
27 #include <pulsecore/modargs.h>
28 #include "echo-cancel.h"
29
30 /* should be between 10-20 ms */
31 #define DEFAULT_FRAME_SIZE_MS 20
32 /* should be between 100-500 ms */
33 #define DEFAULT_FILTER_SIZE_MS 200
34 #define DEFAULT_AGC_ENABLED true
35 #define DEFAULT_DENOISE_ENABLED true
36 #define DEFAULT_DEREVERB_ENABLED true
37 #define DEFAULT_ECHO_SUPPRESS_ENABLED true
38 #define DEFAULT_ECHO_SUPPRESS_ATTENUATION 0
39
40 static const char* const valid_modargs[] = {
41 "frame_size_ms",
42 "filter_size_ms",
43 "agc",
44 "denoise",
45 "dereverb",
46 "echo_suppress",
47 "echo_suppress_attenuation",
48 "echo_suppress_attenuation_active",
49 NULL
50 };
51
speex_ec_fixate_spec(pa_sample_spec * rec_ss,pa_channel_map * rec_map,pa_sample_spec * play_ss,pa_channel_map * play_map,pa_sample_spec * out_ss,pa_channel_map * out_map)52 static void speex_ec_fixate_spec(pa_sample_spec *rec_ss, pa_channel_map *rec_map,
53 pa_sample_spec *play_ss, pa_channel_map *play_map,
54 pa_sample_spec *out_ss, pa_channel_map *out_map) {
55 out_ss->format = PA_SAMPLE_S16NE;
56
57 *play_ss = *out_ss;
58 *play_map = *out_map;
59 *rec_ss = *out_ss;
60 *rec_map = *out_map;
61 }
62
pa_speex_ec_preprocessor_init(pa_echo_canceller * ec,pa_sample_spec * out_ss,uint32_t nframes,pa_modargs * ma)63 static bool pa_speex_ec_preprocessor_init(pa_echo_canceller *ec, pa_sample_spec *out_ss, uint32_t nframes, pa_modargs *ma) {
64 bool agc;
65 bool denoise;
66 bool dereverb;
67 bool echo_suppress;
68 int32_t echo_suppress_attenuation;
69 int32_t echo_suppress_attenuation_active;
70
71 agc = DEFAULT_AGC_ENABLED;
72 if (pa_modargs_get_value_boolean(ma, "agc", &agc) < 0) {
73 pa_log("Failed to parse agc value");
74 goto fail;
75 }
76
77 denoise = DEFAULT_DENOISE_ENABLED;
78 if (pa_modargs_get_value_boolean(ma, "denoise", &denoise) < 0) {
79 pa_log("Failed to parse denoise value");
80 goto fail;
81 }
82
83 dereverb = DEFAULT_DEREVERB_ENABLED;
84 if (pa_modargs_get_value_boolean(ma, "dereverb", &dereverb) < 0) {
85 pa_log("Failed to parse dereverb value");
86 goto fail;
87 }
88
89 echo_suppress = DEFAULT_ECHO_SUPPRESS_ENABLED;
90 if (pa_modargs_get_value_boolean(ma, "echo_suppress", &echo_suppress) < 0) {
91 pa_log("Failed to parse echo_suppress value");
92 goto fail;
93 }
94
95 echo_suppress_attenuation = DEFAULT_ECHO_SUPPRESS_ATTENUATION;
96 if (pa_modargs_get_value_s32(ma, "echo_suppress_attenuation", &echo_suppress_attenuation) < 0) {
97 pa_log("Failed to parse echo_suppress_attenuation value");
98 goto fail;
99 }
100 if (echo_suppress_attenuation > 0) {
101 pa_log("echo_suppress_attenuation should be a negative dB value");
102 goto fail;
103 }
104
105 echo_suppress_attenuation_active = DEFAULT_ECHO_SUPPRESS_ATTENUATION;
106 if (pa_modargs_get_value_s32(ma, "echo_suppress_attenuation_active", &echo_suppress_attenuation_active) < 0) {
107 pa_log("Failed to parse echo_suppress_attenuation_active value");
108 goto fail;
109 }
110 if (echo_suppress_attenuation_active > 0) {
111 pa_log("echo_suppress_attenuation_active should be a negative dB value");
112 goto fail;
113 }
114
115 if (agc || denoise || dereverb || echo_suppress) {
116 spx_int32_t tmp;
117
118 if (out_ss->channels != 1) {
119 pa_log("AGC, denoising, dereverb and echo suppression only work with channels=1");
120 goto fail;
121 }
122
123 ec->params.speex.pp_state = speex_preprocess_state_init(nframes, out_ss->rate);
124
125 tmp = agc;
126 speex_preprocess_ctl(ec->params.speex.pp_state, SPEEX_PREPROCESS_SET_AGC, &tmp);
127
128 tmp = denoise;
129 speex_preprocess_ctl(ec->params.speex.pp_state, SPEEX_PREPROCESS_SET_DENOISE, &tmp);
130
131 tmp = dereverb;
132 speex_preprocess_ctl(ec->params.speex.pp_state, SPEEX_PREPROCESS_SET_DEREVERB, &tmp);
133
134 if (echo_suppress) {
135 if (echo_suppress_attenuation)
136 speex_preprocess_ctl(ec->params.speex.pp_state, SPEEX_PREPROCESS_SET_ECHO_SUPPRESS,
137 &echo_suppress_attenuation);
138
139 if (echo_suppress_attenuation_active) {
140 speex_preprocess_ctl(ec->params.speex.pp_state, SPEEX_PREPROCESS_SET_ECHO_SUPPRESS_ACTIVE,
141 &echo_suppress_attenuation_active);
142 }
143 }
144
145 speex_preprocess_ctl(ec->params.speex.pp_state, SPEEX_PREPROCESS_SET_ECHO_STATE,
146 ec->params.speex.state);
147
148 pa_log_info("Loaded speex preprocessor with params: agc=%s, denoise=%s, dereverb=%s, echo_suppress=%s",
149 pa_yes_no(agc), pa_yes_no(denoise), pa_yes_no(dereverb), pa_yes_no(echo_suppress));
150 } else
151 pa_log_info("All preprocessing options are disabled");
152
153 return true;
154
155 fail:
156 return false;
157 }
158
pa_speex_ec_init(pa_core * c,pa_echo_canceller * ec,pa_sample_spec * rec_ss,pa_channel_map * rec_map,pa_sample_spec * play_ss,pa_channel_map * play_map,pa_sample_spec * out_ss,pa_channel_map * out_map,uint32_t * nframes,const char * args)159 bool pa_speex_ec_init(pa_core *c, pa_echo_canceller *ec,
160 pa_sample_spec *rec_ss, pa_channel_map *rec_map,
161 pa_sample_spec *play_ss, pa_channel_map *play_map,
162 pa_sample_spec *out_ss, pa_channel_map *out_map,
163 uint32_t *nframes, const char *args) {
164 int rate;
165 uint32_t frame_size_ms, filter_size_ms;
166 pa_modargs *ma;
167
168 if (!(ma = pa_modargs_new(args, valid_modargs))) {
169 pa_log("Failed to parse submodule arguments.");
170 goto fail;
171 }
172
173 filter_size_ms = DEFAULT_FILTER_SIZE_MS;
174 if (pa_modargs_get_value_u32(ma, "filter_size_ms", &filter_size_ms) < 0 || filter_size_ms < 1 || filter_size_ms > 2000) {
175 pa_log("Invalid filter_size_ms specification");
176 goto fail;
177 }
178
179 frame_size_ms = DEFAULT_FRAME_SIZE_MS;
180 if (pa_modargs_get_value_u32(ma, "frame_size_ms", &frame_size_ms) < 0 || frame_size_ms < 1 || frame_size_ms > 200) {
181 pa_log("Invalid frame_size_ms specification");
182 goto fail;
183 }
184
185 speex_ec_fixate_spec(rec_ss, rec_map, play_ss, play_map, out_ss, out_map);
186
187 rate = out_ss->rate;
188 *nframes = pa_echo_canceller_blocksize_power2(rate, frame_size_ms);
189
190 pa_log_debug ("Using nframes %d, channels %d, rate %d", *nframes, out_ss->channels, out_ss->rate);
191 ec->params.speex.state = speex_echo_state_init_mc(*nframes, (rate * filter_size_ms) / 1000, out_ss->channels, out_ss->channels);
192
193 if (!ec->params.speex.state)
194 goto fail;
195
196 speex_echo_ctl(ec->params.speex.state, SPEEX_ECHO_SET_SAMPLING_RATE, &rate);
197
198 if (!pa_speex_ec_preprocessor_init(ec, out_ss, *nframes, ma))
199 goto fail;
200
201 pa_modargs_free(ma);
202 return true;
203
204 fail:
205 if (ma)
206 pa_modargs_free(ma);
207 if (ec->params.speex.pp_state) {
208 speex_preprocess_state_destroy(ec->params.speex.pp_state);
209 ec->params.speex.pp_state = NULL;
210 }
211 if (ec->params.speex.state) {
212 speex_echo_state_destroy(ec->params.speex.state);
213 ec->params.speex.state = NULL;
214 }
215 return false;
216 }
217
pa_speex_ec_run(pa_echo_canceller * ec,const uint8_t * rec,const uint8_t * play,uint8_t * out)218 void pa_speex_ec_run(pa_echo_canceller *ec, const uint8_t *rec, const uint8_t *play, uint8_t *out) {
219 speex_echo_cancellation(ec->params.speex.state, (const spx_int16_t *) rec, (const spx_int16_t *) play,
220 (spx_int16_t *) out);
221
222 /* preprecessor is run after AEC. This is not a mistake! */
223 if (ec->params.speex.pp_state)
224 speex_preprocess_run(ec->params.speex.pp_state, (spx_int16_t *) out);
225 }
226
pa_speex_ec_done(pa_echo_canceller * ec)227 void pa_speex_ec_done(pa_echo_canceller *ec) {
228 if (ec->params.speex.pp_state) {
229 speex_preprocess_state_destroy(ec->params.speex.pp_state);
230 ec->params.speex.pp_state = NULL;
231 }
232
233 if (ec->params.speex.state) {
234 speex_echo_state_destroy(ec->params.speex.state);
235 ec->params.speex.state = NULL;
236 }
237 }
238