]> code.delx.au - pulseaudio/blob - src/modules/echo-cancel/webrtc.cc
6ced28ce7683c351625878a449e9d71812333968
[pulseaudio] / src / modules / echo-cancel / webrtc.cc
1 /***
2 This file is part of PulseAudio.
3
4 Copyright 2011 Collabora Ltd.
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, write to the Free Software
20 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
21 USA.
22 ***/
23
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
27
28 #include <pulse/cdecl.h>
29
30 PA_C_DECL_BEGIN
31 #include <pulsecore/core-util.h>
32 #include <pulsecore/modargs.h>
33
34 #include <pulse/timeval.h>
35 #include "echo-cancel.h"
36 PA_C_DECL_END
37
38 #include <audio_processing.h>
39 #include <module_common_types.h>
40
41 #define BLOCK_SIZE_US 10000
42
43 #define DEFAULT_HIGH_PASS_FILTER TRUE
44 #define DEFAULT_NOISE_SUPPRESSION TRUE
45 #define DEFAULT_ANALOG_GAIN_CONTROL TRUE
46 #define DEFAULT_DIGITAL_GAIN_CONTROL FALSE
47 #define DEFAULT_MOBILE FALSE
48 #define DEFAULT_ROUTING_MODE "speakerphone"
49 #define DEFAULT_COMFORT_NOISE TRUE
50 #define DEFAULT_DRIFT_COMPENSATION FALSE
51
52 static const char* const valid_modargs[] = {
53 "high_pass_filter",
54 "noise_suppression",
55 "analog_gain_control",
56 "digital_gain_control",
57 "mobile",
58 "routing_mode",
59 "comfort_noise",
60 "drift_compensation",
61 NULL
62 };
63
64 static int routing_mode_from_string(const char *rmode) {
65 if (pa_streq(rmode, "quiet-earpiece-or-headset"))
66 return webrtc::EchoControlMobile::kQuietEarpieceOrHeadset;
67 else if (pa_streq(rmode, "earpiece"))
68 return webrtc::EchoControlMobile::kEarpiece;
69 else if (pa_streq(rmode, "loud-earpiece"))
70 return webrtc::EchoControlMobile::kLoudEarpiece;
71 else if (pa_streq(rmode, "speakerphone"))
72 return webrtc::EchoControlMobile::kSpeakerphone;
73 else if (pa_streq(rmode, "loud-speakerphone"))
74 return webrtc::EchoControlMobile::kLoudSpeakerphone;
75 else
76 return -1;
77 }
78
79 pa_bool_t pa_webrtc_ec_init(pa_core *c, pa_echo_canceller *ec,
80 pa_sample_spec *source_ss, pa_channel_map *source_map,
81 pa_sample_spec *sink_ss, pa_channel_map *sink_map,
82 uint32_t *nframes, const char *args)
83 {
84 webrtc::AudioProcessing *apm = NULL;
85 pa_bool_t hpf, ns, agc, dgc, mobile, cn;
86 int rm = -1;
87 pa_modargs *ma;
88
89 if (!(ma = pa_modargs_new(args, valid_modargs))) {
90 pa_log("Failed to parse submodule arguments.");
91 goto fail;
92 }
93
94
95 hpf = DEFAULT_HIGH_PASS_FILTER;
96 if (pa_modargs_get_value_boolean(ma, "high_pass_filter", &hpf) < 0) {
97 pa_log("Failed to parse high_pass_filter value");
98 goto fail;
99 }
100
101 ns = DEFAULT_NOISE_SUPPRESSION;
102 if (pa_modargs_get_value_boolean(ma, "noise_suppression", &ns) < 0) {
103 pa_log("Failed to parse noise_suppression value");
104 goto fail;
105 }
106
107 agc = DEFAULT_ANALOG_GAIN_CONTROL;
108 if (pa_modargs_get_value_boolean(ma, "analog_gain_control", &agc) < 0) {
109 pa_log("Failed to parse analog_gain_control value");
110 goto fail;
111 }
112
113 dgc = agc ? FALSE : DEFAULT_DIGITAL_GAIN_CONTROL;
114 if (pa_modargs_get_value_boolean(ma, "digital_gain_control", &dgc) < 0) {
115 pa_log("Failed to parse digital_gain_control value");
116 goto fail;
117 }
118
119 if (agc && dgc) {
120 pa_log("You must pick only one between analog and digital gain control");
121 goto fail;
122 }
123
124 mobile = DEFAULT_MOBILE;
125 if (pa_modargs_get_value_boolean(ma, "mobile", &mobile) < 0) {
126 pa_log("Failed to parse mobile value");
127 goto fail;
128 }
129
130 ec->params.drift_compensation = DEFAULT_DRIFT_COMPENSATION;
131 if (pa_modargs_get_value_boolean(ma, "drift_compensation", &ec->params.drift_compensation) < 0) {
132 pa_log("Failed to parse drift_compensation value");
133 goto fail;
134 }
135
136 if (mobile) {
137 if (ec->params.drift_compensation) {
138 pa_log("Can't use drift_compensation in mobile mode");
139 goto fail;
140 }
141
142 if ((rm = routing_mode_from_string(pa_modargs_get_value(ma, "routing_mode", DEFAULT_ROUTING_MODE))) < 0) {
143 pa_log("Failed to parse routing_mode value");
144 goto fail;
145 }
146
147 cn = DEFAULT_COMFORT_NOISE;
148 if (pa_modargs_get_value_boolean(ma, "comfort_noise", &cn) < 0) {
149 pa_log("Failed to parse cn value");
150 goto fail;
151 }
152 } else {
153 if (pa_modargs_get_value(ma, "comfort_noise", NULL) || pa_modargs_get_value(ma, "routing_mode", NULL)) {
154 pa_log("The routing_mode and comfort_noise options are only valid with mobile=true");
155 goto fail;
156 }
157 }
158
159 apm = webrtc::AudioProcessing::Create(0);
160
161 source_ss->format = PA_SAMPLE_S16NE;
162 *sink_ss = *source_ss;
163 /* FIXME: the implementation actually allows a different number of
164 * source/sink channels. Do we want to support that? */
165 *sink_map = *source_map;
166
167 apm->set_sample_rate_hz(source_ss->rate);
168
169 apm->set_num_channels(source_ss->channels, source_ss->channels);
170 apm->set_num_reverse_channels(sink_ss->channels);
171
172 if (hpf)
173 apm->high_pass_filter()->Enable(true);
174
175 if (!mobile) {
176 if (ec->params.drift_compensation) {
177 apm->echo_cancellation()->set_device_sample_rate_hz(source_ss->rate);
178 apm->echo_cancellation()->enable_drift_compensation(true);
179 } else {
180 apm->echo_cancellation()->enable_drift_compensation(false);
181 }
182
183 apm->echo_cancellation()->Enable(true);
184 } else {
185 apm->echo_control_mobile()->set_routing_mode(static_cast<webrtc::EchoControlMobile::RoutingMode>(rm));
186 apm->echo_control_mobile()->enable_comfort_noise(cn);
187 apm->echo_control_mobile()->Enable(true);
188 }
189
190 if (ns) {
191 apm->noise_suppression()->set_level(webrtc::NoiseSuppression::kHigh);
192 apm->noise_suppression()->Enable(true);
193 }
194
195 if (agc || dgc) {
196 if (mobile && rm <= webrtc::EchoControlMobile::kEarpiece) {
197 /* Maybe this should be a knob, but we've got a lot of knobs already */
198 apm->gain_control()->set_mode(webrtc::GainControl::kFixedDigital);
199 ec->params.priv.webrtc.agc = FALSE;
200 } else if (dgc) {
201 apm->gain_control()->set_mode(webrtc::GainControl::kAdaptiveDigital);
202 ec->params.priv.webrtc.agc = FALSE;
203 } else {
204 apm->gain_control()->set_mode(webrtc::GainControl::kAdaptiveAnalog);
205 if (apm->gain_control()->set_analog_level_limits(0, PA_VOLUME_NORM-1) != apm->kNoError) {
206 pa_log("Failed to initialise AGC");
207 goto fail;
208 }
209 ec->params.priv.webrtc.agc = TRUE;
210 }
211
212 apm->gain_control()->Enable(true);
213 }
214
215 apm->voice_detection()->Enable(true);
216
217 ec->params.priv.webrtc.apm = apm;
218 ec->params.priv.webrtc.sample_spec = *source_ss;
219 ec->params.priv.webrtc.blocksize = (uint64_t)pa_bytes_per_second(source_ss) * BLOCK_SIZE_US / PA_USEC_PER_SEC;
220 *nframes = ec->params.priv.webrtc.blocksize / pa_frame_size(source_ss);
221
222 pa_modargs_free(ma);
223 return TRUE;
224
225 fail:
226 if (ma)
227 pa_modargs_free(ma);
228 if (apm)
229 webrtc::AudioProcessing::Destroy(apm);
230
231 return FALSE;
232 }
233
234 void pa_webrtc_ec_play(pa_echo_canceller *ec, const uint8_t *play) {
235 webrtc::AudioProcessing *apm = (webrtc::AudioProcessing*)ec->params.priv.webrtc.apm;
236 webrtc::AudioFrame play_frame;
237 const pa_sample_spec *ss = &ec->params.priv.webrtc.sample_spec;
238
239 play_frame._audioChannel = ss->channels;
240 play_frame._frequencyInHz = ss->rate;
241 play_frame._payloadDataLengthInSamples = ec->params.priv.webrtc.blocksize / pa_frame_size(ss);
242 memcpy(play_frame._payloadData, play, ec->params.priv.webrtc.blocksize);
243
244 apm->AnalyzeReverseStream(&play_frame);
245 }
246
247 void pa_webrtc_ec_record(pa_echo_canceller *ec, const uint8_t *rec, uint8_t *out) {
248 webrtc::AudioProcessing *apm = (webrtc::AudioProcessing*)ec->params.priv.webrtc.apm;
249 webrtc::AudioFrame out_frame;
250 const pa_sample_spec *ss = &ec->params.priv.webrtc.sample_spec;
251 pa_cvolume v;
252
253 out_frame._audioChannel = ss->channels;
254 out_frame._frequencyInHz = ss->rate;
255 out_frame._payloadDataLengthInSamples = ec->params.priv.webrtc.blocksize / pa_frame_size(ss);
256 memcpy(out_frame._payloadData, rec, ec->params.priv.webrtc.blocksize);
257
258 if (ec->params.priv.webrtc.agc) {
259 pa_cvolume_init(&v);
260 pa_echo_canceller_get_capture_volume(ec, &v);
261 apm->gain_control()->set_stream_analog_level(pa_cvolume_avg(&v));
262 }
263
264 apm->set_stream_delay_ms(0);
265 apm->ProcessStream(&out_frame);
266
267 if (ec->params.priv.webrtc.agc) {
268 pa_cvolume_set(&v, ss->channels, apm->gain_control()->stream_analog_level());
269 pa_echo_canceller_set_capture_volume(ec, &v);
270 }
271
272 memcpy(out, out_frame._payloadData, ec->params.priv.webrtc.blocksize);
273 }
274
275 void pa_webrtc_ec_set_drift(pa_echo_canceller *ec, float drift) {
276 webrtc::AudioProcessing *apm = (webrtc::AudioProcessing*)ec->params.priv.webrtc.apm;
277 const pa_sample_spec *ss = &ec->params.priv.webrtc.sample_spec;
278
279 apm->echo_cancellation()->set_stream_drift_samples(drift * ec->params.priv.webrtc.blocksize / pa_frame_size(ss));
280 }
281
282 void pa_webrtc_ec_run(pa_echo_canceller *ec, const uint8_t *rec, const uint8_t *play, uint8_t *out) {
283 pa_webrtc_ec_play(ec, play);
284 pa_webrtc_ec_record(ec, rec, out);
285 }
286
287 void pa_webrtc_ec_done(pa_echo_canceller *ec) {
288 if (ec->params.priv.webrtc.apm) {
289 webrtc::AudioProcessing::Destroy((webrtc::AudioProcessing*)ec->params.priv.webrtc.apm);
290 ec->params.priv.webrtc.apm = NULL;
291 }
292 }