]> code.delx.au - pulseaudio/blob - src/modules/echo-cancel/webrtc.cc
7517d4ad6bbdfb923c2ed9ff0d9d38d68d5df1de
[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 *rec_ss, pa_channel_map *rec_map,
81 pa_sample_spec *play_ss, pa_channel_map *play_map,
82 pa_sample_spec *out_ss, pa_channel_map *out_map,
83 uint32_t *nframes, const char *args) {
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 out_ss->format = PA_SAMPLE_S16NE;
162 *play_ss = *out_ss;
163 /* FIXME: the implementation actually allows a different number of
164 * source/sink channels. Do we want to support that? */
165 *play_map = *out_map;
166 *rec_ss = *out_ss;
167 *rec_map = *out_map;
168
169 apm->set_sample_rate_hz(out_ss->rate);
170
171 apm->set_num_channels(out_ss->channels, out_ss->channels);
172 apm->set_num_reverse_channels(play_ss->channels);
173
174 if (hpf)
175 apm->high_pass_filter()->Enable(true);
176
177 if (!mobile) {
178 if (ec->params.drift_compensation) {
179 apm->echo_cancellation()->set_device_sample_rate_hz(out_ss->rate);
180 apm->echo_cancellation()->enable_drift_compensation(true);
181 } else {
182 apm->echo_cancellation()->enable_drift_compensation(false);
183 }
184
185 apm->echo_cancellation()->Enable(true);
186 } else {
187 apm->echo_control_mobile()->set_routing_mode(static_cast<webrtc::EchoControlMobile::RoutingMode>(rm));
188 apm->echo_control_mobile()->enable_comfort_noise(cn);
189 apm->echo_control_mobile()->Enable(true);
190 }
191
192 if (ns) {
193 apm->noise_suppression()->set_level(webrtc::NoiseSuppression::kHigh);
194 apm->noise_suppression()->Enable(true);
195 }
196
197 if (agc || dgc) {
198 if (mobile && rm <= webrtc::EchoControlMobile::kEarpiece) {
199 /* Maybe this should be a knob, but we've got a lot of knobs already */
200 apm->gain_control()->set_mode(webrtc::GainControl::kFixedDigital);
201 ec->params.priv.webrtc.agc = FALSE;
202 } else if (dgc) {
203 apm->gain_control()->set_mode(webrtc::GainControl::kAdaptiveDigital);
204 ec->params.priv.webrtc.agc = FALSE;
205 } else {
206 apm->gain_control()->set_mode(webrtc::GainControl::kAdaptiveAnalog);
207 if (apm->gain_control()->set_analog_level_limits(0, PA_VOLUME_NORM-1) != apm->kNoError) {
208 pa_log("Failed to initialise AGC");
209 goto fail;
210 }
211 ec->params.priv.webrtc.agc = TRUE;
212 }
213
214 apm->gain_control()->Enable(true);
215 }
216
217 apm->voice_detection()->Enable(true);
218
219 ec->params.priv.webrtc.apm = apm;
220 ec->params.priv.webrtc.sample_spec = *out_ss;
221 ec->params.priv.webrtc.blocksize = (uint64_t)pa_bytes_per_second(out_ss) * BLOCK_SIZE_US / PA_USEC_PER_SEC;
222 *nframes = ec->params.priv.webrtc.blocksize / pa_frame_size(out_ss);
223
224 pa_modargs_free(ma);
225 return TRUE;
226
227 fail:
228 if (ma)
229 pa_modargs_free(ma);
230 if (apm)
231 webrtc::AudioProcessing::Destroy(apm);
232
233 return FALSE;
234 }
235
236 void pa_webrtc_ec_play(pa_echo_canceller *ec, const uint8_t *play) {
237 webrtc::AudioProcessing *apm = (webrtc::AudioProcessing*)ec->params.priv.webrtc.apm;
238 webrtc::AudioFrame play_frame;
239 const pa_sample_spec *ss = &ec->params.priv.webrtc.sample_spec;
240
241 play_frame._audioChannel = ss->channels;
242 play_frame._frequencyInHz = ss->rate;
243 play_frame._payloadDataLengthInSamples = ec->params.priv.webrtc.blocksize / pa_frame_size(ss);
244 memcpy(play_frame._payloadData, play, ec->params.priv.webrtc.blocksize);
245
246 apm->AnalyzeReverseStream(&play_frame);
247 }
248
249 void pa_webrtc_ec_record(pa_echo_canceller *ec, const uint8_t *rec, uint8_t *out) {
250 webrtc::AudioProcessing *apm = (webrtc::AudioProcessing*)ec->params.priv.webrtc.apm;
251 webrtc::AudioFrame out_frame;
252 const pa_sample_spec *ss = &ec->params.priv.webrtc.sample_spec;
253 pa_cvolume v;
254
255 out_frame._audioChannel = ss->channels;
256 out_frame._frequencyInHz = ss->rate;
257 out_frame._payloadDataLengthInSamples = ec->params.priv.webrtc.blocksize / pa_frame_size(ss);
258 memcpy(out_frame._payloadData, rec, ec->params.priv.webrtc.blocksize);
259
260 if (ec->params.priv.webrtc.agc) {
261 pa_cvolume_init(&v);
262 pa_echo_canceller_get_capture_volume(ec, &v);
263 apm->gain_control()->set_stream_analog_level(pa_cvolume_avg(&v));
264 }
265
266 apm->set_stream_delay_ms(0);
267 apm->ProcessStream(&out_frame);
268
269 if (ec->params.priv.webrtc.agc) {
270 pa_cvolume_set(&v, ss->channels, apm->gain_control()->stream_analog_level());
271 pa_echo_canceller_set_capture_volume(ec, &v);
272 }
273
274 memcpy(out, out_frame._payloadData, ec->params.priv.webrtc.blocksize);
275 }
276
277 void pa_webrtc_ec_set_drift(pa_echo_canceller *ec, float drift) {
278 webrtc::AudioProcessing *apm = (webrtc::AudioProcessing*)ec->params.priv.webrtc.apm;
279 const pa_sample_spec *ss = &ec->params.priv.webrtc.sample_spec;
280
281 apm->echo_cancellation()->set_stream_drift_samples(drift * ec->params.priv.webrtc.blocksize / pa_frame_size(ss));
282 }
283
284 void pa_webrtc_ec_run(pa_echo_canceller *ec, const uint8_t *rec, const uint8_t *play, uint8_t *out) {
285 pa_webrtc_ec_play(ec, play);
286 pa_webrtc_ec_record(ec, rec, out);
287 }
288
289 void pa_webrtc_ec_done(pa_echo_canceller *ec) {
290 if (ec->params.priv.webrtc.apm) {
291 webrtc::AudioProcessing::Destroy((webrtc::AudioProcessing*)ec->params.priv.webrtc.apm);
292 ec->params.priv.webrtc.apm = NULL;
293 }
294 }