]> code.delx.au - pulseaudio/blob - src/modules/echo-cancel/webrtc.cc
echo-cancel: Turn WebRTC analog gain control on by default
[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 *blocksize, const char *args)
83 {
84 webrtc::AudioProcessing *apm = NULL;
85 pa_bool_t hpf, ns, agc, dgc, mobile, cn;
86 int rm;
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 = *blocksize = (uint64_t)pa_bytes_per_second(source_ss) * BLOCK_SIZE_US / PA_USEC_PER_SEC;
220
221 pa_modargs_free(ma);
222 return TRUE;
223
224 fail:
225 if (ma)
226 pa_modargs_free(ma);
227 if (apm)
228 webrtc::AudioProcessing::Destroy(apm);
229
230 return FALSE;
231 }
232
233 void pa_webrtc_ec_play(pa_echo_canceller *ec, const uint8_t *play) {
234 webrtc::AudioProcessing *apm = (webrtc::AudioProcessing*)ec->params.priv.webrtc.apm;
235 webrtc::AudioFrame play_frame;
236 const pa_sample_spec *ss = &ec->params.priv.webrtc.sample_spec;
237
238 play_frame._audioChannel = ss->channels;
239 play_frame._frequencyInHz = ss->rate;
240 play_frame._payloadDataLengthInSamples = ec->params.priv.webrtc.blocksize / pa_frame_size(ss);
241 memcpy(play_frame._payloadData, play, ec->params.priv.webrtc.blocksize);
242
243 apm->AnalyzeReverseStream(&play_frame);
244 }
245
246 void pa_webrtc_ec_record(pa_echo_canceller *ec, const uint8_t *rec, uint8_t *out) {
247 webrtc::AudioProcessing *apm = (webrtc::AudioProcessing*)ec->params.priv.webrtc.apm;
248 webrtc::AudioFrame out_frame;
249 const pa_sample_spec *ss = &ec->params.priv.webrtc.sample_spec;
250 pa_cvolume v;
251
252 out_frame._audioChannel = ss->channels;
253 out_frame._frequencyInHz = ss->rate;
254 out_frame._payloadDataLengthInSamples = ec->params.priv.webrtc.blocksize / pa_frame_size(ss);
255 memcpy(out_frame._payloadData, rec, ec->params.priv.webrtc.blocksize);
256
257 if (ec->params.priv.webrtc.agc) {
258 pa_cvolume_init(&v);
259 pa_echo_canceller_get_capture_volume(ec, &v);
260 apm->gain_control()->set_stream_analog_level(pa_cvolume_avg(&v));
261 }
262
263 apm->set_stream_delay_ms(0);
264 apm->ProcessStream(&out_frame);
265
266 if (ec->params.priv.webrtc.agc) {
267 pa_cvolume_set(&v, ss->channels, apm->gain_control()->stream_analog_level());
268 pa_echo_canceller_set_capture_volume(ec, &v);
269 }
270
271 memcpy(out, out_frame._payloadData, ec->params.priv.webrtc.blocksize);
272 }
273
274 void pa_webrtc_ec_set_drift(pa_echo_canceller *ec, float drift) {
275 webrtc::AudioProcessing *apm = (webrtc::AudioProcessing*)ec->params.priv.webrtc.apm;
276 const pa_sample_spec *ss = &ec->params.priv.webrtc.sample_spec;
277
278 apm->echo_cancellation()->set_stream_drift_samples(drift * ec->params.priv.webrtc.blocksize / pa_frame_size(ss));
279 }
280
281 void pa_webrtc_ec_run(pa_echo_canceller *ec, const uint8_t *rec, const uint8_t *play, uint8_t *out) {
282 pa_webrtc_ec_play(ec, play);
283 pa_webrtc_ec_record(ec, rec, out);
284 }
285
286 void pa_webrtc_ec_done(pa_echo_canceller *ec) {
287 if (ec->params.priv.webrtc.apm) {
288 webrtc::AudioProcessing::Destroy((webrtc::AudioProcessing*)ec->params.priv.webrtc.apm);
289 ec->params.priv.webrtc.apm = NULL;
290 }
291 }