1 /*
2 mediastreamer2 library - modular sound and video processing and streaming
3 Copyright (C) 2012 Belledonne Communications, Grenoble, France
4
5 This program is free software; you can redistribute it and/or
6 modify it under the terms of the GNU General Public License
7 as published by the Free Software Foundation; either version 2
8 of the License, or (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 */
19
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif
23
24 #include "mediastreamer2/msfilter.h"
25 #include "mediastreamer2/msticker.h"
26 #ifdef BUILD_AEC
27 #include "echo_cancellation.h"
28 #include "aec_splitting_filter.h"
29 #endif
30 #ifdef BUILD_AECM
31 #include "echo_control_mobile.h"
32 #endif
33 #include "ortp/b64.h"
34
35 #ifdef _WIN32
36 #include <malloc.h> /* for alloca */
37 #endif
38
39 //#define EC_DUMP 1
40 #ifdef ANDROID
41 #define EC_DUMP_PREFIX "/sdcard"
42 #else
43 #define EC_DUMP_PREFIX "/dynamic/tests"
44 #endif
45
46 #include "mediastreamer2/flowcontrol.h"
47
48 static const float smooth_factor = 0.05f;
49 static const int framesize = 80;
50
51
52 typedef enum _WebRTCAECType {
53 WebRTCAECTypeNormal,
54 WebRTCAECTypeMobile
55 } WebRTCAECType;
56
57 typedef struct WebRTCAECState {
58 void *aecInst;
59 MSBufferizer delayed_ref;
60 MSFlowControlledBufferizer ref;
61 MSBufferizer echo;
62 int framesize;
63 int samplerate;
64 int delay_ms;
65 int nominal_ref_samples;
66 char *state_str;
67 #ifdef EC_DUMP
68 FILE *echofile;
69 FILE *reffile;
70 FILE *cleanfile;
71 #endif
72 bool_t echostarted;
73 bool_t bypass_mode;
74 bool_t using_zeroes;
75 WebRTCAECType aec_type;
76 #ifdef BUILD_AEC
77 MSWebRtcAecSplittingFilter *splitting_filter;
78 #endif
79 } WebRTCAECState;
80
webrtc_aecgeneric_init(MSFilter * f,WebRTCAECType aec_type)81 static void webrtc_aecgeneric_init(MSFilter *f, WebRTCAECType aec_type) {
82 WebRTCAECState *s = (WebRTCAECState *) ms_new0(WebRTCAECState, 1);
83
84 s->samplerate = 8000;
85 ms_bufferizer_init(&s->delayed_ref);
86 ms_bufferizer_init(&s->echo);
87 ms_flow_controlled_bufferizer_init(&s->ref, f, s->samplerate, 1);
88 s->delay_ms = 0;
89 s->aecInst = NULL;
90 s->framesize = framesize;
91 s->state_str = NULL;
92 s->using_zeroes = FALSE;
93 s->echostarted = FALSE;
94 s->bypass_mode = FALSE;
95 s->aec_type = aec_type;
96
97 #ifdef EC_DUMP
98 {
99 char *fname = ms_strdup_printf("%s/mswebrtcaec-%p-echo.raw", EC_DUMP_PREFIX, f);
100 s->echofile = fopen(fname, "w");
101 ms_free(fname);
102 fname = ms_strdup_printf("%s/mswebrtcaec-%p-ref.raw", EC_DUMP_PREFIX, f);
103 s->reffile = fopen(fname, "w");
104 ms_free(fname);
105 fname = ms_strdup_printf("%s/mswebrtcaec-%p-clean.raw", EC_DUMP_PREFIX, f);
106 s->cleanfile = fopen(fname, "w");
107 ms_free(fname);
108 }
109 #endif
110
111 f->data = s;
112 }
113
114 #ifdef BUILD_AEC
webrtc_aec_init(MSFilter * f)115 static void webrtc_aec_init(MSFilter *f) {
116 webrtc_aecgeneric_init(f, WebRTCAECTypeNormal);
117 }
118 #endif
119
120 #ifdef BUILD_AECM
webrtc_aecm_init(MSFilter * f)121 static void webrtc_aecm_init(MSFilter *f) {
122 webrtc_aecgeneric_init(f, WebRTCAECTypeMobile);
123 }
124 #endif
125
webrtc_aec_uninit(MSFilter * f)126 static void webrtc_aec_uninit(MSFilter *f) {
127 WebRTCAECState *s = (WebRTCAECState *) f->data;
128 if (s->state_str) ms_free(s->state_str);
129 ms_bufferizer_uninit(&s->delayed_ref);
130 #ifdef EC_DUMP
131 if (s->echofile)
132 fclose(s->echofile);
133 if (s->reffile)
134 fclose(s->reffile);
135 #endif
136 ms_free(s);
137 }
138
configure_flow_controlled_bufferizer(WebRTCAECState * s)139 static void configure_flow_controlled_bufferizer(WebRTCAECState *s) {
140 ms_flow_controlled_bufferizer_set_samplerate(&s->ref, s->samplerate);
141 ms_flow_controlled_bufferizer_set_max_size_ms(&s->ref, s->delay_ms);
142 ms_flow_controlled_bufferizer_set_granularity_ms(&s->ref, (s->framesize * 1000) / s->samplerate);
143 }
144
webrtc_aec_preprocess(MSFilter * f)145 static void webrtc_aec_preprocess(MSFilter *f) {
146 WebRTCAECState *s = (WebRTCAECState *) f->data;
147 #ifdef BUILD_AEC
148 AecConfig aec_config;
149 #endif
150 #ifdef BUILD_AECM
151 AecmConfig aecm_config;
152 int error_code;
153 #endif
154 int delay_samples = 0;
155 mblk_t *m;
156
157 s->echostarted = FALSE;
158 delay_samples = s->delay_ms * s->samplerate / 1000;
159 s->framesize=(framesize*s->samplerate)/8000;
160 ms_message("Initializing WebRTC echo canceler with framesize=%i, delay_ms=%i, delay_samples=%i", s->framesize, s->delay_ms, delay_samples);
161 configure_flow_controlled_bufferizer(s);
162
163 #ifdef BUILD_AEC
164 if (s->aec_type == WebRTCAECTypeNormal) {
165 if ((s->aecInst = WebRtcAec_Create()) == NULL) {
166 s->bypass_mode = TRUE;
167 ms_error("WebRtcAec_Create(): error, entering bypass mode");
168 return;
169 }
170 if ((WebRtcAec_Init(s->aecInst, MIN(48000, s->samplerate), s->samplerate)) < 0) {
171 ms_error("WebRtcAec_Init(): WebRTC echo canceller does not support %d samplerate", s->samplerate);
172 s->bypass_mode = TRUE;
173 ms_error("Entering bypass mode");
174 return;
175 }
176 aec_config.nlpMode = kAecNlpAggressive;
177 aec_config.skewMode = kAecFalse;
178 aec_config.metricsMode = kAecFalse;
179 aec_config.delay_logging = kAecFalse;
180 if (WebRtcAec_set_config(s->aecInst, aec_config) != 0) {
181 ms_error("WebRtcAec_set_config(): failed.");
182 }
183 }
184 #endif
185 #ifdef BUILD_AECM
186 if (s->aec_type == WebRTCAECTypeMobile) {
187 if ((s->aecInst = WebRtcAecm_Create()) == NULL) {
188 s->bypass_mode = TRUE;
189 ms_error("WebRtcAecm_Create(): error, entering bypass mode");
190 return;
191 }
192 if ((error_code = WebRtcAecm_Init(s->aecInst, s->samplerate)) < 0) {
193 if (error_code == AECM_BAD_PARAMETER_ERROR) {
194 ms_error("WebRtcAecm_Init(): WebRTC echo canceller does not support %d samplerate", s->samplerate);
195 }
196 s->bypass_mode = TRUE;
197 ms_error("Entering bypass mode");
198 return;
199 }
200 aecm_config.cngMode = TRUE;
201 aecm_config.echoMode = 3;
202 if (WebRtcAecm_set_config(s->aecInst, aecm_config)!=0){
203 ms_error("WebRtcAecm_set_config(): failed.");
204 }
205 }
206 #endif
207
208 /* fill with zeroes for the time of the delay*/
209 m = allocb(delay_samples * 2, 0);
210 m->b_wptr += delay_samples * 2;
211 ms_bufferizer_put(&s->delayed_ref, m);
212 s->nominal_ref_samples = delay_samples;
213 }
214
215 /* inputs[0]= reference signal from far end (sent to soundcard)
216 * inputs[1]= near speech & echo signal (read from soundcard)
217 * outputs[0]= is a copy of inputs[0] to be sent to soundcard
218 * outputs[1]= near end speech, echo removed - towards far end
219 */
webrtc_aec_process(MSFilter * f)220 static void webrtc_aec_process(MSFilter *f) {
221 WebRTCAECState *s = (WebRTCAECState *) f->data;
222 int nbytes = s->framesize * sizeof(int16_t);
223 mblk_t *refm;
224 int16_t *ref, *echo;
225 int nbands = 1;
226 int bandsize = s->framesize;
227
228 if (s->bypass_mode) {
229 while ((refm = ms_queue_get(f->inputs[0])) != NULL) {
230 ms_queue_put(f->outputs[0], refm);
231 }
232 while ((refm = ms_queue_get(f->inputs[1])) != NULL) {
233 ms_queue_put(f->outputs[1], refm);
234 }
235 return;
236 }
237
238 if (f->inputs[0] != NULL) {
239 if (s->echostarted) {
240 while ((refm = ms_queue_get(f->inputs[0])) != NULL) {
241 mblk_t *cp=dupmsg(refm);
242 ms_bufferizer_put(&s->delayed_ref,cp);
243 ms_flow_controlled_bufferizer_put(&s->ref,refm);
244 }
245 } else {
246 ms_warning("Getting reference signal but no echo to synchronize on.");
247 ms_queue_flush(f->inputs[0]);
248 }
249 }
250
251 ms_bufferizer_put_from_queue(&s->echo, f->inputs[1]);
252
253 ref = (int16_t *) alloca(nbytes);
254 echo = (int16_t *) alloca(nbytes);
255 #ifdef BUILD_AEC
256 if (s->aec_type == WebRTCAECTypeNormal) {
257 if (s->samplerate > 16000) {
258 nbands = s->samplerate / 16000;
259 bandsize = 160;
260 }
261 if (!s->splitting_filter) {
262 s->splitting_filter = mswebrtc_aec_splitting_filter_create(nbands, bandsize);
263 }
264 }
265 #endif
266 while (ms_bufferizer_read(&s->echo, (uint8_t *)echo, (size_t)nbytes) >= (size_t)nbytes) {
267 mblk_t *oecho = allocb(nbytes, 0);
268 int avail;
269 int avail_samples;
270
271 if (!s->echostarted) s->echostarted = TRUE;
272 if ((avail = ms_bufferizer_get_avail(&s->delayed_ref)) < ((s->nominal_ref_samples * 2) + nbytes)) {
273 /*we don't have enough to read in a reference signal buffer, inject silence instead*/
274 refm = allocb(nbytes, 0);
275 memset(refm->b_wptr, 0, nbytes);
276 refm->b_wptr += nbytes;
277 ms_bufferizer_put(&s->delayed_ref, refm);
278 ms_queue_put(f->outputs[0], dupmsg(refm));
279 if (!s->using_zeroes) {
280 ms_warning("Not enough ref samples, using zeroes");
281 s->using_zeroes = TRUE;
282 }
283 } else {
284 if (s->using_zeroes) {
285 ms_message("Samples are back.");
286 s->using_zeroes = FALSE;
287 }
288 /* read from our no-delay buffer and output */
289 refm = allocb(nbytes, 0);
290 if (ms_flow_controlled_bufferizer_read(&s->ref, refm->b_wptr, nbytes) == 0) {
291 ms_fatal("Should never happen");
292 }
293 refm->b_wptr += nbytes;
294 ms_queue_put(f->outputs[0], refm);
295 }
296
297 /*now read a valid buffer of delayed ref samples*/
298 if (ms_bufferizer_read(&s->delayed_ref, (uint8_t *)ref, nbytes) == 0) {
299 ms_fatal("Should never happen");
300 }
301 avail -= nbytes;
302 avail_samples = avail / 2;
303
304 #ifdef EC_DUMP
305 if (s->reffile)
306 fwrite(ref, nbytes, 1, s->reffile);
307 if (s->echofile)
308 fwrite(echo, nbytes, 1, s->echofile);
309 #endif
310 #ifdef BUILD_AEC
311 if (s->aec_type == WebRTCAECTypeNormal) {
312 mswebrtc_aec_splitting_filter_analysis(s->splitting_filter, ref, echo);
313 if (WebRtcAec_BufferFarend(s->aecInst,
314 mswebrtc_aec_splitting_filter_get_ref(s->splitting_filter),
315 (size_t)mswebrtc_aec_splitting_filter_get_bandsize(s->splitting_filter)) != 0)
316 ms_error("WebRtcAec_BufferFarend() failed.");
317 if (WebRtcAec_Process(s->aecInst,
318 mswebrtc_aec_splitting_filter_get_echo_bands(s->splitting_filter),
319 mswebrtc_aec_splitting_filter_get_number_of_bands(s->splitting_filter),
320 mswebrtc_aec_splitting_filter_get_output_bands(s->splitting_filter),
321 (size_t)mswebrtc_aec_splitting_filter_get_bandsize(s->splitting_filter), 0, 0) != 0)
322 ms_error("WebRtcAec_Process() failed.");
323 mswebrtc_aec_splitting_filter_synthesis(s->splitting_filter, (int16_t *)oecho->b_wptr);
324 }
325 #endif
326 #ifdef BUILD_AECM
327 if (s->aec_type == WebRTCAECTypeMobile) {
328 if (WebRtcAecm_BufferFarend(s->aecInst, ref, (size_t)s->framesize) != 0)
329 ms_error("WebRtcAecm_BufferFarend() failed.");
330 if (WebRtcAecm_Process(s->aecInst, echo, NULL, (int16_t *)oecho->b_wptr, (size_t)s->framesize, 0) != 0)
331 ms_error("WebRtcAecm_Process() failed.");
332 }
333 #endif
334 #ifdef EC_DUMP
335 if (s->cleanfile)
336 fwrite(oecho->b_wptr, nbytes, 1, s->cleanfile);
337 #endif
338 oecho->b_wptr += nbytes;
339 ms_queue_put(f->outputs[1], oecho);
340 }
341 }
342
webrtc_aec_postprocess(MSFilter * f)343 static void webrtc_aec_postprocess(MSFilter *f) {
344 WebRTCAECState *s = (WebRTCAECState *) f->data;
345
346 ms_bufferizer_flush(&s->delayed_ref);
347 ms_bufferizer_flush(&s->echo);
348 ms_flow_controlled_bufferizer_flush(&s->ref);
349 #ifdef BUILD_AEC
350 if (s->splitting_filter) {
351 mswebrtc_aec_splitting_filter_destroy(s->splitting_filter);
352 s->splitting_filter = NULL;
353 }
354 #endif
355 if (s->aecInst != NULL) {
356 #ifdef BUILD_AEC
357 if (s->aec_type == WebRTCAECTypeNormal) {
358 WebRtcAec_Free(s->aecInst);
359 }
360 #endif
361 #ifdef BUILD_AECM
362 if (s->aec_type == WebRTCAECTypeMobile) {
363 WebRtcAecm_Free(s->aecInst);
364 }
365 #endif
366 s->aecInst = NULL;
367 }
368 }
369
webrtc_aec_set_sr(MSFilter * f,void * arg)370 static int webrtc_aec_set_sr(MSFilter *f, void *arg) {
371 WebRTCAECState *s = (WebRTCAECState *) f->data;
372 int requested_sr = *(int *) arg;
373 int sr = requested_sr;
374
375 if ((requested_sr != 8000) && (requested_sr != 16000) && (requested_sr != 32000) && (requested_sr != 48000)) {
376 if ((s->aec_type == WebRTCAECTypeNormal) && (requested_sr > 48000)) {
377 sr = 48000;
378 } else if ((s->aec_type == WebRTCAECTypeNormal) && (requested_sr > 32000)) {
379 sr = 32000;
380 } else if (requested_sr > 16000) {
381 sr = 16000;
382 } else {
383 sr = 8000;
384 }
385 ms_message("Webrtc aec does not support sampling rate %i, using %i instead", requested_sr, sr);
386 }
387 s->samplerate = sr;
388 configure_flow_controlled_bufferizer(s);
389 return 0;
390 }
391
webrtc_aec_get_sr(MSFilter * f,void * arg)392 static int webrtc_aec_get_sr(MSFilter *f, void *arg) {
393 WebRTCAECState *s = (WebRTCAECState *) f->data;
394 *(int *) arg=s->samplerate;
395 return 0;
396 }
397
webrtc_aec_set_framesize(MSFilter * f,void * arg)398 static int webrtc_aec_set_framesize(MSFilter *f, void *arg) {
399 /* Do nothing because the WebRTC echo canceller only accept specific values: 80 and 160. We use 80 at 8khz, and 160 at 16khz */
400 return 0;
401 }
402
webrtc_aec_set_delay(MSFilter * f,void * arg)403 static int webrtc_aec_set_delay(MSFilter *f, void *arg) {
404 WebRTCAECState *s = (WebRTCAECState *) f->data;
405 s->delay_ms = *(int *) arg;
406 configure_flow_controlled_bufferizer(s);
407 return 0;
408 }
409
webrtc_aec_set_tail_length(MSFilter * f,void * arg)410 static int webrtc_aec_set_tail_length(MSFilter *f, void *arg) {
411 /* Do nothing because this is not needed by the WebRTC echo canceller. */
412 return 0;
413 }
webrtc_aec_set_bypass_mode(MSFilter * f,void * arg)414 static int webrtc_aec_set_bypass_mode(MSFilter *f, void *arg) {
415 WebRTCAECState *s = (WebRTCAECState *) f->data;
416 s->bypass_mode = *(bool_t *) arg;
417 ms_message("set EC bypass mode to [%i]", s->bypass_mode);
418 return 0;
419 }
webrtc_aec_get_bypass_mode(MSFilter * f,void * arg)420 static int webrtc_aec_get_bypass_mode(MSFilter *f, void *arg) {
421 WebRTCAECState *s = (WebRTCAECState *) f->data;
422 *(bool_t *) arg = s->bypass_mode;
423 return 0;
424 }
425
webrtc_aec_set_state(MSFilter * f,void * arg)426 static int webrtc_aec_set_state(MSFilter *f, void *arg) {
427 WebRTCAECState *s = (WebRTCAECState *) f->data;
428 s->state_str = ms_strdup((const char *) arg);
429 return 0;
430 }
431
webrtc_aec_get_state(MSFilter * f,void * arg)432 static int webrtc_aec_get_state(MSFilter *f, void *arg) {
433 WebRTCAECState *s = (WebRTCAECState *) f->data;
434 *(char **) arg = s->state_str;
435 return 0;
436 }
437
438 static MSFilterMethod webrtc_aec_methods[] = {
439 { MS_FILTER_SET_SAMPLE_RATE , webrtc_aec_set_sr },
440 { MS_FILTER_GET_SAMPLE_RATE , webrtc_aec_get_sr },
441 { MS_ECHO_CANCELLER_SET_TAIL_LENGTH , webrtc_aec_set_tail_length },
442 { MS_ECHO_CANCELLER_SET_DELAY , webrtc_aec_set_delay },
443 { MS_ECHO_CANCELLER_SET_FRAMESIZE , webrtc_aec_set_framesize },
444 { MS_ECHO_CANCELLER_SET_BYPASS_MODE , webrtc_aec_set_bypass_mode },
445 { MS_ECHO_CANCELLER_GET_BYPASS_MODE , webrtc_aec_get_bypass_mode },
446 { MS_ECHO_CANCELLER_GET_STATE_STRING , webrtc_aec_get_state },
447 { MS_ECHO_CANCELLER_SET_STATE_STRING , webrtc_aec_set_state }
448 };
449
450
451 #ifdef BUILD_AEC
452
453 #define MS_WEBRTC_AEC_NAME "MSWebRTCAEC"
454 #define MS_WEBRTC_AEC_DESCRIPTION "Echo canceller using WebRTC library."
455 #define MS_WEBRTC_AEC_CATEGORY MS_FILTER_OTHER
456 #define MS_WEBRTC_AEC_ENC_FMT NULL
457 #define MS_WEBRTC_AEC_NINPUTS 2
458 #define MS_WEBRTC_AEC_NOUTPUTS 2
459 #define MS_WEBRTC_AEC_FLAGS 0
460
461 #ifdef _MSC_VER
462
463 MSFilterDesc ms_webrtc_aec_desc = {
464 MS_FILTER_PLUGIN_ID,
465 MS_WEBRTC_AEC_NAME,
466 MS_WEBRTC_AEC_DESCRIPTION,
467 MS_WEBRTC_AEC_CATEGORY,
468 MS_WEBRTC_AEC_ENC_FMT,
469 MS_WEBRTC_AEC_NINPUTS,
470 MS_WEBRTC_AEC_NOUTPUTS,
471 webrtc_aec_init,
472 webrtc_aec_preprocess,
473 webrtc_aec_process,
474 webrtc_aec_postprocess,
475 webrtc_aec_uninit,
476 webrtc_aec_methods,
477 MS_WEBRTC_AEC_FLAGS
478 };
479
480 #else
481
482 MSFilterDesc ms_webrtc_aec_desc = {
483 .id = MS_FILTER_PLUGIN_ID,
484 .name = MS_WEBRTC_AEC_NAME,
485 .text = MS_WEBRTC_AEC_DESCRIPTION,
486 .category = MS_WEBRTC_AEC_CATEGORY,
487 .enc_fmt = MS_WEBRTC_AEC_ENC_FMT,
488 .ninputs = MS_WEBRTC_AEC_NINPUTS,
489 .noutputs = MS_WEBRTC_AEC_NOUTPUTS,
490 .init = webrtc_aec_init,
491 .preprocess = webrtc_aec_preprocess,
492 .process = webrtc_aec_process,
493 .postprocess = webrtc_aec_postprocess,
494 .uninit = webrtc_aec_uninit,
495 .methods = webrtc_aec_methods,
496 .flags = MS_WEBRTC_AEC_FLAGS
497 };
498
499 #endif
500
501 MS_FILTER_DESC_EXPORT(ms_webrtc_aec_desc)
502
503 #endif /* BUILD_AEC */
504
505 #ifdef BUILD_AECM
506
507 #define MS_WEBRTC_AECM_NAME "MSWebRTCAECM"
508 #define MS_WEBRTC_AECM_DESCRIPTION "Echo canceller for mobile using WebRTC library."
509 #define MS_WEBRTC_AECM_CATEGORY MS_FILTER_OTHER
510 #define MS_WEBRTC_AECM_ENC_FMT NULL
511 #define MS_WEBRTC_AECM_NINPUTS 2
512 #define MS_WEBRTC_AECM_NOUTPUTS 2
513 #define MS_WEBRTC_AECM_FLAGS 0
514
515 #ifdef _MSC_VER
516
517 MSFilterDesc ms_webrtc_aecm_desc = {
518 MS_FILTER_PLUGIN_ID,
519 MS_WEBRTC_AECM_NAME,
520 MS_WEBRTC_AECM_DESCRIPTION,
521 MS_WEBRTC_AECM_CATEGORY,
522 MS_WEBRTC_AECM_ENC_FMT,
523 MS_WEBRTC_AECM_NINPUTS,
524 MS_WEBRTC_AECM_NOUTPUTS,
525 webrtc_aecm_init,
526 webrtc_aec_preprocess,
527 webrtc_aec_process,
528 webrtc_aec_postprocess,
529 webrtc_aec_uninit,
530 webrtc_aec_methods,
531 MS_WEBRTC_AECM_FLAGS
532 };
533
534 #else
535
536 MSFilterDesc ms_webrtc_aecm_desc = {
537 .id = MS_FILTER_PLUGIN_ID,
538 .name = MS_WEBRTC_AECM_NAME,
539 .text = MS_WEBRTC_AECM_DESCRIPTION,
540 .category = MS_WEBRTC_AECM_CATEGORY,
541 .enc_fmt = MS_WEBRTC_AECM_ENC_FMT,
542 .ninputs = MS_WEBRTC_AECM_NINPUTS,
543 .noutputs = MS_WEBRTC_AECM_NOUTPUTS,
544 .init = webrtc_aecm_init,
545 .preprocess = webrtc_aec_preprocess,
546 .process = webrtc_aec_process,
547 .postprocess = webrtc_aec_postprocess,
548 .uninit = webrtc_aec_uninit,
549 .methods = webrtc_aec_methods,
550 .flags = MS_WEBRTC_AECM_FLAGS
551 };
552
553 #endif
554
555 MS_FILTER_DESC_EXPORT(ms_webrtc_aecm_desc)
556
557 #endif /* BUILD_AECM */
558