1 /***
2 This file is part of PulseAudio.
3
4 Copyright 2011 Collabora Ltd.
5 2015 Aldebaran SoftBank Group
6
7 Contributor: Arun Raghavan <mail@arunraghavan.net>
8
9 PulseAudio is free software; you can redistribute it and/or modify
10 it under the terms of the GNU Lesser General Public License as published
11 by the Free Software Foundation; either version 2.1 of the License,
12 or (at your option) any later version.
13
14 PulseAudio is distributed in the hope that it will be useful, but
15 WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 General Public License for more details.
18
19 You should have received a copy of the GNU Lesser General Public License
20 along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
21 ***/
22
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
26
27 #include <pulse/cdecl.h>
28
29 PA_C_DECL_BEGIN
30 #include <pulsecore/core-util.h>
31 #include <pulsecore/modargs.h>
32
33 #include <pulse/timeval.h>
34 #include "echo-cancel.h"
35 PA_C_DECL_END
36
37 #include <webrtc/modules/audio_processing/include/audio_processing.h>
38 #include <webrtc/modules/interface/module_common_types.h>
39 #include <webrtc/system_wrappers/include/trace.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 #define DEFAULT_VAD true
52 #define DEFAULT_EXTENDED_FILTER false
53 #define DEFAULT_INTELLIGIBILITY_ENHANCER false
54 #define DEFAULT_EXPERIMENTAL_AGC false
55 #define DEFAULT_AGC_START_VOLUME 85
56 #define DEFAULT_BEAMFORMING false
57 #define DEFAULT_TRACE false
58
59 #define WEBRTC_AGC_MAX_VOLUME 255
60
61 static const char* const valid_modargs[] = {
62 "high_pass_filter",
63 "noise_suppression",
64 "analog_gain_control",
65 "digital_gain_control",
66 "mobile",
67 "routing_mode",
68 "comfort_noise",
69 "drift_compensation",
70 "voice_detection",
71 "extended_filter",
72 "intelligibility_enhancer",
73 "experimental_agc",
74 "agc_start_volume",
75 "beamforming",
76 "mic_geometry", /* documented in parse_mic_geometry() */
77 "target_direction", /* documented in parse_mic_geometry() */
78 "trace",
79 NULL
80 };
81
routing_mode_from_string(const char * rmode)82 static int routing_mode_from_string(const char *rmode) {
83 if (pa_streq(rmode, "quiet-earpiece-or-headset"))
84 return webrtc::EchoControlMobile::kQuietEarpieceOrHeadset;
85 else if (pa_streq(rmode, "earpiece"))
86 return webrtc::EchoControlMobile::kEarpiece;
87 else if (pa_streq(rmode, "loud-earpiece"))
88 return webrtc::EchoControlMobile::kLoudEarpiece;
89 else if (pa_streq(rmode, "speakerphone"))
90 return webrtc::EchoControlMobile::kSpeakerphone;
91 else if (pa_streq(rmode, "loud-speakerphone"))
92 return webrtc::EchoControlMobile::kLoudSpeakerphone;
93 else
94 return -1;
95 }
96
97 class PaWebrtcTraceCallback : public webrtc::TraceCallback {
Print(webrtc::TraceLevel level,const char * message,int length)98 void Print(webrtc::TraceLevel level, const char *message, int length)
99 {
100 if (level & webrtc::kTraceError || level & webrtc::kTraceCritical)
101 pa_log(message);
102 else if (level & webrtc::kTraceWarning)
103 pa_log_warn(message);
104 else if (level & webrtc::kTraceInfo)
105 pa_log_info(message);
106 else
107 pa_log_debug(message);
108 }
109 };
110
webrtc_volume_from_pa(pa_volume_t v)111 static int webrtc_volume_from_pa(pa_volume_t v)
112 {
113 return (v * WEBRTC_AGC_MAX_VOLUME) / PA_VOLUME_NORM;
114 }
115
webrtc_volume_to_pa(int v)116 static pa_volume_t webrtc_volume_to_pa(int v)
117 {
118 return (v * PA_VOLUME_NORM) / WEBRTC_AGC_MAX_VOLUME;
119 }
120
webrtc_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,bool beamforming)121 static void webrtc_ec_fixate_spec(pa_sample_spec *rec_ss, pa_channel_map *rec_map,
122 pa_sample_spec *play_ss, pa_channel_map *play_map,
123 pa_sample_spec *out_ss, pa_channel_map *out_map,
124 bool beamforming)
125 {
126 rec_ss->format = PA_SAMPLE_FLOAT32NE;
127 play_ss->format = PA_SAMPLE_FLOAT32NE;
128
129 /* AudioProcessing expects one of the following rates */
130 if (rec_ss->rate >= 48000)
131 rec_ss->rate = 48000;
132 else if (rec_ss->rate >= 32000)
133 rec_ss->rate = 32000;
134 else if (rec_ss->rate >= 16000)
135 rec_ss->rate = 16000;
136 else
137 rec_ss->rate = 8000;
138
139 *out_ss = *rec_ss;
140 *out_map = *rec_map;
141
142 if (beamforming) {
143 /* The beamformer gives us a single channel */
144 out_ss->channels = 1;
145 pa_channel_map_init_mono(out_map);
146 }
147
148 /* Playback stream rate needs to be the same as capture */
149 play_ss->rate = rec_ss->rate;
150 }
151
parse_point(const char ** point,float (& f)[3])152 static bool parse_point(const char **point, float (&f)[3]) {
153 int ret, length;
154
155 ret = sscanf(*point, "%g,%g,%g%n", &f[0], &f[1], &f[2], &length);
156 if (ret != 3)
157 return false;
158
159 /* Consume the bytes we've read so far */
160 *point += length;
161
162 return true;
163 }
164
parse_mic_geometry(const char ** mic_geometry,std::vector<webrtc::Point> & geometry)165 static bool parse_mic_geometry(const char **mic_geometry, std::vector<webrtc::Point>& geometry) {
166 /* The microphone geometry is expressed as cartesian point form:
167 * x1,y1,z1,x2,y2,z2,...
168 *
169 * Where x1,y1,z1 is the position of the first microphone with regards to
170 * the array's "center", x2,y2,z2 the position of the second, and so on.
171 *
172 * 'x' is the horizontal coordinate, with positive values being to the
173 * right from the mic array's perspective.
174 *
175 * 'y' is the depth coordinate, with positive values being in front of the
176 * array.
177 *
178 * 'z' is the vertical coordinate, with positive values being above the
179 * array.
180 *
181 * All distances are in meters.
182 */
183
184 /* The target direction is expected to be in spherical point form:
185 * a,e,r
186 *
187 * Where 'a' is the azimuth of the target point relative to the center of
188 * the array, 'e' its elevation, and 'r' the radius.
189 *
190 * 0 radians azimuth is to the right of the array, and positive angles
191 * move in a counter-clockwise direction.
192 *
193 * 0 radians elevation is horizontal w.r.t. the array, and positive
194 * angles go upwards.
195 *
196 * radius is distance from the array center in meters.
197 */
198
199 long unsigned int i;
200 float f[3];
201
202 for (i = 0; i < geometry.size(); i++) {
203 if (!parse_point(mic_geometry, f)) {
204 pa_log("Failed to parse channel %lu in mic_geometry", i);
205 return false;
206 }
207
208 /* Except for the last point, we should have a trailing comma */
209 if (i != geometry.size() - 1) {
210 if (**mic_geometry != ',') {
211 pa_log("Failed to parse channel %lu in mic_geometry", i);
212 return false;
213 }
214
215 (*mic_geometry)++;
216 }
217
218 pa_log_debug("Got mic #%lu position: (%g, %g, %g)", i, f[0], f[1], f[2]);
219
220 geometry[i].c[0] = f[0];
221 geometry[i].c[1] = f[1];
222 geometry[i].c[2] = f[2];
223 }
224
225 if (**mic_geometry != '\0') {
226 pa_log("Failed to parse mic_geometry value: more parameters than expected");
227 return false;
228 }
229
230 return true;
231 }
232
pa_webrtc_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)233 bool pa_webrtc_ec_init(pa_core *c, pa_echo_canceller *ec,
234 pa_sample_spec *rec_ss, pa_channel_map *rec_map,
235 pa_sample_spec *play_ss, pa_channel_map *play_map,
236 pa_sample_spec *out_ss, pa_channel_map *out_map,
237 uint32_t *nframes, const char *args) {
238 webrtc::AudioProcessing *apm = NULL;
239 webrtc::ProcessingConfig pconfig;
240 webrtc::Config config;
241 bool hpf, ns, agc, dgc, mobile, cn, vad, ext_filter, intelligibility, experimental_agc, beamforming;
242 int rm = -1, i;
243 uint32_t agc_start_volume;
244 pa_modargs *ma;
245 bool trace = false;
246
247 if (!(ma = pa_modargs_new(args, valid_modargs))) {
248 pa_log("Failed to parse submodule arguments.");
249 goto fail;
250 }
251
252 hpf = DEFAULT_HIGH_PASS_FILTER;
253 if (pa_modargs_get_value_boolean(ma, "high_pass_filter", &hpf) < 0) {
254 pa_log("Failed to parse high_pass_filter value");
255 goto fail;
256 }
257
258 ns = DEFAULT_NOISE_SUPPRESSION;
259 if (pa_modargs_get_value_boolean(ma, "noise_suppression", &ns) < 0) {
260 pa_log("Failed to parse noise_suppression value");
261 goto fail;
262 }
263
264 agc = DEFAULT_ANALOG_GAIN_CONTROL;
265 if (pa_modargs_get_value_boolean(ma, "analog_gain_control", &agc) < 0) {
266 pa_log("Failed to parse analog_gain_control value");
267 goto fail;
268 }
269
270 dgc = agc ? false : DEFAULT_DIGITAL_GAIN_CONTROL;
271 if (pa_modargs_get_value_boolean(ma, "digital_gain_control", &dgc) < 0) {
272 pa_log("Failed to parse digital_gain_control value");
273 goto fail;
274 }
275
276 if (agc && dgc) {
277 pa_log("You must pick only one between analog and digital gain control");
278 goto fail;
279 }
280
281 mobile = DEFAULT_MOBILE;
282 if (pa_modargs_get_value_boolean(ma, "mobile", &mobile) < 0) {
283 pa_log("Failed to parse mobile value");
284 goto fail;
285 }
286
287 ec->params.drift_compensation = DEFAULT_DRIFT_COMPENSATION;
288 if (pa_modargs_get_value_boolean(ma, "drift_compensation", &ec->params.drift_compensation) < 0) {
289 pa_log("Failed to parse drift_compensation value");
290 goto fail;
291 }
292
293 if (mobile) {
294 if (ec->params.drift_compensation) {
295 pa_log("Can't use drift_compensation in mobile mode");
296 goto fail;
297 }
298
299 if ((rm = routing_mode_from_string(pa_modargs_get_value(ma, "routing_mode", DEFAULT_ROUTING_MODE))) < 0) {
300 pa_log("Failed to parse routing_mode value");
301 goto fail;
302 }
303
304 cn = DEFAULT_COMFORT_NOISE;
305 if (pa_modargs_get_value_boolean(ma, "comfort_noise", &cn) < 0) {
306 pa_log("Failed to parse cn value");
307 goto fail;
308 }
309 } else {
310 if (pa_modargs_get_value(ma, "comfort_noise", NULL) || pa_modargs_get_value(ma, "routing_mode", NULL)) {
311 pa_log("The routing_mode and comfort_noise options are only valid with mobile=true");
312 goto fail;
313 }
314 }
315
316 vad = DEFAULT_VAD;
317 if (pa_modargs_get_value_boolean(ma, "voice_detection", &vad) < 0) {
318 pa_log("Failed to parse voice_detection value");
319 goto fail;
320 }
321
322 ext_filter = DEFAULT_EXTENDED_FILTER;
323 if (pa_modargs_get_value_boolean(ma, "extended_filter", &ext_filter) < 0) {
324 pa_log("Failed to parse extended_filter value");
325 goto fail;
326 }
327
328 intelligibility = DEFAULT_INTELLIGIBILITY_ENHANCER;
329 if (pa_modargs_get_value_boolean(ma, "intelligibility_enhancer", &intelligibility) < 0) {
330 pa_log("Failed to parse intelligibility_enhancer value");
331 goto fail;
332 }
333
334 experimental_agc = DEFAULT_EXPERIMENTAL_AGC;
335 if (pa_modargs_get_value_boolean(ma, "experimental_agc", &experimental_agc) < 0) {
336 pa_log("Failed to parse experimental_agc value");
337 goto fail;
338 }
339
340 agc_start_volume = DEFAULT_AGC_START_VOLUME;
341 if (pa_modargs_get_value_u32(ma, "agc_start_volume", &agc_start_volume) < 0) {
342 pa_log("Failed to parse agc_start_volume value");
343 goto fail;
344 }
345 if (agc_start_volume > WEBRTC_AGC_MAX_VOLUME) {
346 pa_log("AGC start volume must not exceed %u", WEBRTC_AGC_MAX_VOLUME);
347 goto fail;
348 }
349 ec->params.webrtc.agc_start_volume = agc_start_volume;
350
351 beamforming = DEFAULT_BEAMFORMING;
352 if (pa_modargs_get_value_boolean(ma, "beamforming", &beamforming) < 0) {
353 pa_log("Failed to parse beamforming value");
354 goto fail;
355 }
356
357 if (ext_filter)
358 config.Set<webrtc::ExtendedFilter>(new webrtc::ExtendedFilter(true));
359 if (intelligibility)
360 pa_log_warn("The intelligibility enhancer is not currently supported");
361 if (experimental_agc)
362 config.Set<webrtc::ExperimentalAgc>(new webrtc::ExperimentalAgc(true, ec->params.webrtc.agc_start_volume));
363
364 trace = DEFAULT_TRACE;
365 if (pa_modargs_get_value_boolean(ma, "trace", &trace) < 0) {
366 pa_log("Failed to parse trace value");
367 goto fail;
368 }
369
370 if (trace) {
371 webrtc::Trace::CreateTrace();
372 webrtc::Trace::set_level_filter(webrtc::kTraceAll);
373 ec->params.webrtc.trace_callback = new PaWebrtcTraceCallback();
374 webrtc::Trace::SetTraceCallback((PaWebrtcTraceCallback *) ec->params.webrtc.trace_callback);
375 }
376
377 webrtc_ec_fixate_spec(rec_ss, rec_map, play_ss, play_map, out_ss, out_map, beamforming);
378
379 /* We do this after fixate because we need the capture channel count */
380 if (beamforming) {
381 std::vector<webrtc::Point> geometry(rec_ss->channels);
382 webrtc::SphericalPointf direction(0.0f, 0.0f, 0.0f);
383 const char *mic_geometry, *target_direction;
384
385 if (!(mic_geometry = pa_modargs_get_value(ma, "mic_geometry", NULL))) {
386 pa_log("mic_geometry must be set if beamforming is enabled");
387 goto fail;
388 }
389
390 if (!parse_mic_geometry(&mic_geometry, geometry)) {
391 pa_log("Failed to parse mic_geometry value");
392 goto fail;
393 }
394
395 if ((target_direction = pa_modargs_get_value(ma, "target_direction", NULL))) {
396 float f[3];
397
398 if (!parse_point(&target_direction, f)) {
399 pa_log("Failed to parse target_direction value");
400 goto fail;
401 }
402
403 if (*target_direction != '\0') {
404 pa_log("Failed to parse target_direction value: more parameters than expected");
405 goto fail;
406 }
407
408 #define IS_ZERO(f) ((f) < 0.000001 && (f) > -0.000001)
409
410 if (!IS_ZERO(f[1]) || !IS_ZERO(f[2])) {
411 pa_log("The beamformer currently only supports targeting along the azimuth");
412 goto fail;
413 }
414
415 direction.s[0] = f[0];
416 direction.s[1] = f[1];
417 direction.s[2] = f[2];
418 }
419
420 if (!target_direction)
421 config.Set<webrtc::Beamforming>(new webrtc::Beamforming(true, geometry));
422 else
423 config.Set<webrtc::Beamforming>(new webrtc::Beamforming(true, geometry, direction));
424 }
425
426 apm = webrtc::AudioProcessing::Create(config);
427
428 pconfig = {
429 webrtc::StreamConfig(rec_ss->rate, rec_ss->channels, false), /* input stream */
430 webrtc::StreamConfig(out_ss->rate, out_ss->channels, false), /* output stream */
431 webrtc::StreamConfig(play_ss->rate, play_ss->channels, false), /* reverse input stream */
432 webrtc::StreamConfig(play_ss->rate, play_ss->channels, false), /* reverse output stream */
433 };
434 if (apm->Initialize(pconfig) != webrtc::AudioProcessing::kNoError) {
435 pa_log("Error initialising audio processing module");
436 goto fail;
437 }
438
439 if (hpf)
440 apm->high_pass_filter()->Enable(true);
441
442 if (!mobile) {
443 apm->echo_cancellation()->enable_drift_compensation(ec->params.drift_compensation);
444 apm->echo_cancellation()->Enable(true);
445 } else {
446 apm->echo_control_mobile()->set_routing_mode(static_cast<webrtc::EchoControlMobile::RoutingMode>(rm));
447 apm->echo_control_mobile()->enable_comfort_noise(cn);
448 apm->echo_control_mobile()->Enable(true);
449 }
450
451 if (ns) {
452 apm->noise_suppression()->set_level(webrtc::NoiseSuppression::kHigh);
453 apm->noise_suppression()->Enable(true);
454 }
455
456 if (agc || dgc) {
457 if (mobile && rm <= webrtc::EchoControlMobile::kEarpiece) {
458 /* Maybe this should be a knob, but we've got a lot of knobs already */
459 apm->gain_control()->set_mode(webrtc::GainControl::kFixedDigital);
460 ec->params.webrtc.agc = false;
461 } else if (dgc) {
462 apm->gain_control()->set_mode(webrtc::GainControl::kAdaptiveDigital);
463 ec->params.webrtc.agc = false;
464 } else {
465 apm->gain_control()->set_mode(webrtc::GainControl::kAdaptiveAnalog);
466 if (apm->gain_control()->set_analog_level_limits(0, WEBRTC_AGC_MAX_VOLUME) !=
467 webrtc::AudioProcessing::kNoError) {
468 pa_log("Failed to initialise AGC");
469 goto fail;
470 }
471 ec->params.webrtc.agc = true;
472 }
473
474 apm->gain_control()->Enable(true);
475 }
476
477 if (vad)
478 apm->voice_detection()->Enable(true);
479
480 ec->params.webrtc.apm = apm;
481 ec->params.webrtc.rec_ss = *rec_ss;
482 ec->params.webrtc.play_ss = *play_ss;
483 ec->params.webrtc.out_ss = *out_ss;
484 ec->params.webrtc.blocksize = (uint64_t) out_ss->rate * BLOCK_SIZE_US / PA_USEC_PER_SEC;
485 *nframes = ec->params.webrtc.blocksize;
486 ec->params.webrtc.first = true;
487
488 for (i = 0; i < rec_ss->channels; i++)
489 ec->params.webrtc.rec_buffer[i] = pa_xnew(float, *nframes);
490 for (i = 0; i < play_ss->channels; i++)
491 ec->params.webrtc.play_buffer[i] = pa_xnew(float, *nframes);
492
493 pa_modargs_free(ma);
494 return true;
495
496 fail:
497 if (ma)
498 pa_modargs_free(ma);
499 if (ec->params.webrtc.trace_callback) {
500 webrtc::Trace::ReturnTrace();
501 delete ((PaWebrtcTraceCallback *) ec->params.webrtc.trace_callback);
502 } if (apm)
503 delete apm;
504
505 return false;
506 }
507
pa_webrtc_ec_play(pa_echo_canceller * ec,const uint8_t * play)508 void pa_webrtc_ec_play(pa_echo_canceller *ec, const uint8_t *play) {
509 webrtc::AudioProcessing *apm = (webrtc::AudioProcessing*)ec->params.webrtc.apm;
510 const pa_sample_spec *ss = &ec->params.webrtc.play_ss;
511 int n = ec->params.webrtc.blocksize;
512 float **buf = ec->params.webrtc.play_buffer;
513 webrtc::StreamConfig config(ss->rate, ss->channels, false);
514
515 pa_deinterleave(play, (void **) buf, ss->channels, pa_sample_size(ss), n);
516
517 pa_assert_se(apm->ProcessReverseStream(buf, config, config, buf) == webrtc::AudioProcessing::kNoError);
518
519 /* FIXME: If ProcessReverseStream() makes any changes to the audio, such as
520 * applying intelligibility enhancement, those changes don't have any
521 * effect. This function is called at the source side, but the processing
522 * would have to be done in the sink to be able to feed the processed audio
523 * to speakers. */
524 }
525
pa_webrtc_ec_record(pa_echo_canceller * ec,const uint8_t * rec,uint8_t * out)526 void pa_webrtc_ec_record(pa_echo_canceller *ec, const uint8_t *rec, uint8_t *out) {
527 webrtc::AudioProcessing *apm = (webrtc::AudioProcessing*)ec->params.webrtc.apm;
528 const pa_sample_spec *rec_ss = &ec->params.webrtc.rec_ss;
529 const pa_sample_spec *out_ss = &ec->params.webrtc.out_ss;
530 float **buf = ec->params.webrtc.rec_buffer;
531 int n = ec->params.webrtc.blocksize;
532 int old_volume, new_volume;
533 webrtc::StreamConfig rec_config(rec_ss->rate, rec_ss->channels, false);
534 webrtc::StreamConfig out_config(out_ss->rate, out_ss->channels, false);
535
536 pa_deinterleave(rec, (void **) buf, rec_ss->channels, pa_sample_size(rec_ss), n);
537
538 if (ec->params.webrtc.agc) {
539 pa_volume_t v = pa_echo_canceller_get_capture_volume(ec);
540 old_volume = webrtc_volume_from_pa(v);
541 apm->gain_control()->set_stream_analog_level(old_volume);
542 }
543
544 apm->set_stream_delay_ms(0);
545 pa_assert_se(apm->ProcessStream(buf, rec_config, out_config, buf) == webrtc::AudioProcessing::kNoError);
546
547 if (ec->params.webrtc.agc) {
548 if (PA_UNLIKELY(ec->params.webrtc.first)) {
549 /* We start at a sane default volume (taken from the Chromium
550 * condition on the experimental AGC in audio_processing.h). This is
551 * needed to make sure that there's enough energy in the capture
552 * signal for the AGC to work */
553 ec->params.webrtc.first = false;
554 new_volume = ec->params.webrtc.agc_start_volume;
555 } else {
556 new_volume = apm->gain_control()->stream_analog_level();
557 }
558
559 if (old_volume != new_volume)
560 pa_echo_canceller_set_capture_volume(ec, webrtc_volume_to_pa(new_volume));
561 }
562
563 pa_interleave((const void **) buf, out_ss->channels, out, pa_sample_size(out_ss), n);
564 }
565
pa_webrtc_ec_set_drift(pa_echo_canceller * ec,float drift)566 void pa_webrtc_ec_set_drift(pa_echo_canceller *ec, float drift) {
567 webrtc::AudioProcessing *apm = (webrtc::AudioProcessing*)ec->params.webrtc.apm;
568
569 apm->echo_cancellation()->set_stream_drift_samples(drift * ec->params.webrtc.blocksize);
570 }
571
pa_webrtc_ec_run(pa_echo_canceller * ec,const uint8_t * rec,const uint8_t * play,uint8_t * out)572 void pa_webrtc_ec_run(pa_echo_canceller *ec, const uint8_t *rec, const uint8_t *play, uint8_t *out) {
573 pa_webrtc_ec_play(ec, play);
574 pa_webrtc_ec_record(ec, rec, out);
575 }
576
pa_webrtc_ec_done(pa_echo_canceller * ec)577 void pa_webrtc_ec_done(pa_echo_canceller *ec) {
578 int i;
579
580 if (ec->params.webrtc.trace_callback) {
581 webrtc::Trace::ReturnTrace();
582 delete ((PaWebrtcTraceCallback *) ec->params.webrtc.trace_callback);
583 }
584
585 if (ec->params.webrtc.apm) {
586 delete (webrtc::AudioProcessing*)ec->params.webrtc.apm;
587 ec->params.webrtc.apm = NULL;
588 }
589
590 for (i = 0; i < ec->params.webrtc.rec_ss.channels; i++)
591 pa_xfree(ec->params.webrtc.rec_buffer[i]);
592 for (i = 0; i < ec->params.webrtc.play_ss.channels; i++)
593 pa_xfree(ec->params.webrtc.play_buffer[i]);
594 }
595