]> code.delx.au - pulseaudio/blob - src/modules/echo-cancel/webrtc.cc
echo-cancel: Add the WebRTC echo canceller
[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 FALSE
46 #define DEFAULT_DIGITAL_GAIN_CONTROL TRUE
47 #define DEFAULT_MOBILE FALSE
48 #define DEFAULT_ROUTING_MODE "speakerphone"
49 #define DEFAULT_COMFORT_NOISE TRUE
50
51 static const char* const valid_modargs[] = {
52 "high_pass_filter",
53 "noise_suppression",
54 "analog_gain_control",
55 "digital_gain_control",
56 "mobile",
57 "routing_mode",
58 "comfort_noise",
59 NULL
60 };
61
62 static int routing_mode_from_string(const char *rmode) {
63 if (pa_streq(rmode, "quiet-earpiece-or-headset"))
64 return webrtc::EchoControlMobile::kQuietEarpieceOrHeadset;
65 else if (pa_streq(rmode, "earpiece"))
66 return webrtc::EchoControlMobile::kEarpiece;
67 else if (pa_streq(rmode, "loud-earpiece"))
68 return webrtc::EchoControlMobile::kLoudEarpiece;
69 else if (pa_streq(rmode, "speakerphone"))
70 return webrtc::EchoControlMobile::kSpeakerphone;
71 else if (pa_streq(rmode, "loud-speakerphone"))
72 return webrtc::EchoControlMobile::kLoudSpeakerphone;
73 else
74 return -1;
75 }
76
77 pa_bool_t pa_webrtc_ec_init(pa_core *c, pa_echo_canceller *ec,
78 pa_sample_spec *source_ss, pa_channel_map *source_map,
79 pa_sample_spec *sink_ss, pa_channel_map *sink_map,
80 uint32_t *blocksize, const char *args)
81 {
82 webrtc::AudioProcessing *apm = NULL;
83 pa_bool_t hpf, ns, agc, dgc, mobile, cn;
84 int rm;
85 pa_modargs *ma;
86
87 if (!(ma = pa_modargs_new(args, valid_modargs))) {
88 pa_log("Failed to parse submodule arguments.");
89 goto fail;
90 }
91
92
93 hpf = DEFAULT_HIGH_PASS_FILTER;
94 if (pa_modargs_get_value_boolean(ma, "high_pass_filter", &hpf) < 0) {
95 pa_log("Failed to parse high_pass_filter value");
96 goto fail;
97 }
98
99 ns = DEFAULT_NOISE_SUPPRESSION;
100 if (pa_modargs_get_value_boolean(ma, "noise_suppression", &ns) < 0) {
101 pa_log("Failed to parse noise_suppression value");
102 goto fail;
103 }
104
105 agc = DEFAULT_ANALOG_GAIN_CONTROL;
106 if (pa_modargs_get_value_boolean(ma, "analog_gain_control", &agc) < 0) {
107 pa_log("Failed to parse analog_gain_control value");
108 goto fail;
109 }
110
111 dgc = DEFAULT_DIGITAL_GAIN_CONTROL;
112 if (pa_modargs_get_value_boolean(ma, "analog_gain_control", &dgc) < 0) {
113 pa_log("Failed to parse digital_gain_control value");
114 goto fail;
115 }
116
117 if (agc && dgc) {
118 pa_log("You must pick only one between analog and digital gain control");
119 goto fail;
120 }
121
122 mobile = DEFAULT_MOBILE;
123 if (pa_modargs_get_value_boolean(ma, "mobile", &mobile) < 0) {
124 pa_log("Failed to parse mobile value");
125 goto fail;
126 }
127
128 if (mobile) {
129 if ((rm = routing_mode_from_string(pa_modargs_get_value(ma, "routing_mode", DEFAULT_ROUTING_MODE))) < 0) {
130 pa_log("Failed to parse routing_mode value");
131 goto fail;
132 }
133
134 cn = DEFAULT_COMFORT_NOISE;
135 if (pa_modargs_get_value_boolean(ma, "comfort_noise", &cn) < 0) {
136 pa_log("Failed to parse cn value");
137 goto fail;
138 }
139 } else {
140 if (pa_modargs_get_value(ma, "comfort_noise", NULL) || pa_modargs_get_value(ma, "routing_mode", NULL)) {
141 pa_log("The routing_mode and comfort_noise options are only valid with mobile=true");
142 goto fail;
143 }
144 }
145
146 apm = webrtc::AudioProcessing::Create(0);
147
148 source_ss->format = PA_SAMPLE_S16NE;
149 *sink_ss = *source_ss;
150 /* FIXME: the implementation actually allows a different number of
151 * source/sink channels. Do we want to support that? */
152 *sink_map = *source_map;
153
154 apm->set_sample_rate_hz(source_ss->rate);
155
156 apm->set_num_channels(source_ss->channels, source_ss->channels);
157 apm->set_num_reverse_channels(sink_ss->channels);
158
159 if (hpf)
160 apm->high_pass_filter()->Enable(true);
161
162 if (!mobile) {
163 apm->echo_cancellation()->enable_drift_compensation(false);
164 apm->echo_cancellation()->Enable(true);
165 } else {
166 apm->echo_control_mobile()->set_routing_mode(static_cast<webrtc::EchoControlMobile::RoutingMode>(rm));
167 apm->echo_control_mobile()->enable_comfort_noise(cn);
168 apm->echo_control_mobile()->Enable(true);
169 }
170
171 if (ns) {
172 apm->noise_suppression()->set_level(webrtc::NoiseSuppression::kHigh);
173 apm->noise_suppression()->Enable(true);
174 }
175
176 if (agc || dgc) {
177 if (mobile && rm <= webrtc::EchoControlMobile::kEarpiece)
178 /* Maybe this should be a knob, but we've got a lot of knobs already */
179 apm->gain_control()->set_mode(webrtc::GainControl::kFixedDigital);
180 else if (dgc)
181 apm->gain_control()->set_mode(webrtc::GainControl::kAdaptiveDigital);
182 else {
183 /* FIXME: Hook up for analog AGC */
184 pa_log("Analog gain control isn't implemented yet -- using ditital gain control.");
185 apm->gain_control()->set_mode(webrtc::GainControl::kAdaptiveDigital);
186 }
187 }
188
189 apm->voice_detection()->Enable(true);
190
191 ec->params.priv.webrtc.apm = apm;
192 ec->params.priv.webrtc.sample_spec = *source_ss;
193 ec->params.priv.webrtc.blocksize = *blocksize = (uint64_t)pa_bytes_per_second(source_ss) * BLOCK_SIZE_US / PA_USEC_PER_SEC;
194
195 pa_modargs_free(ma);
196 return TRUE;
197
198 fail:
199 if (ma)
200 pa_modargs_free(ma);
201 if (apm)
202 webrtc::AudioProcessing::Destroy(apm);
203
204 return FALSE;
205 }
206
207 void pa_webrtc_ec_run(pa_echo_canceller *ec, const uint8_t *rec, const uint8_t *play, uint8_t *out) {
208 webrtc::AudioProcessing *apm = (webrtc::AudioProcessing*)ec->params.priv.webrtc.apm;
209 webrtc::AudioFrame play_frame, out_frame;
210 const pa_sample_spec *ss = &ec->params.priv.webrtc.sample_spec;
211
212 play_frame._audioChannel = ss->channels;
213 play_frame._frequencyInHz = ss->rate;
214 play_frame._payloadDataLengthInSamples = ec->params.priv.webrtc.blocksize / pa_frame_size(ss);
215 memcpy(play_frame._payloadData, play, ec->params.priv.webrtc.blocksize);
216
217 out_frame._audioChannel = ss->channels;
218 out_frame._frequencyInHz = ss->rate;
219 out_frame._payloadDataLengthInSamples = ec->params.priv.webrtc.blocksize / pa_frame_size(ss);
220 memcpy(out_frame._payloadData, rec, ec->params.priv.webrtc.blocksize);
221
222 apm->AnalyzeReverseStream(&play_frame);
223 apm->set_stream_delay_ms(0);
224 apm->ProcessStream(&out_frame);
225
226 memcpy(out, out_frame._payloadData, ec->params.priv.webrtc.blocksize);
227 }
228
229 void pa_webrtc_ec_done(pa_echo_canceller *ec) {
230 if (ec->params.priv.webrtc.apm) {
231 webrtc::AudioProcessing::Destroy((webrtc::AudioProcessing*)ec->params.priv.webrtc.apm);
232 ec->params.priv.webrtc.apm = NULL;
233 }
234 }