From 3f5c5582f46ecd99a1c9e8a2dbad94d56df45465 Mon Sep 17 00:00:00 2001 From: Arun Raghavan Date: Mon, 26 Sep 2011 21:30:49 +0530 Subject: [PATCH] echo-cancel: Add a standalone test program This is useful to test the canceller implementation on data from disk rather than testing live. Handy for comparing implementations reliably. --- src/Makefile.am | 8 +- src/modules/echo-cancel/module-echo-cancel.c | 160 +++++++++++++++++++++++---- 2 files changed, 147 insertions(+), 21 deletions(-) diff --git a/src/Makefile.am b/src/Makefile.am index fdb2e99..5637974 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -253,7 +253,8 @@ TESTS_norun = \ flist-test \ rtstutter \ stripnul \ - connect-stress + connect-stress \ + echo-cancel-test if !OS_IS_WIN32 TESTS += \ @@ -518,6 +519,11 @@ connect_stress_LDADD = $(AM_LDADD) libpulse.la libpulsecommon-@PA_MAJORMINOR@.la connect_stress_CFLAGS = $(AM_CFLAGS) connect_stress_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) +echo_cancel_test_SOURCES = $(module_echo_cancel_la_SOURCES) +nodist_echo_cancel_test_SOURCES = $(nodist_module_echo_cancel_la_SOURCES) +echo_cancel_test_LDADD = $(module_echo_cancel_la_LIBADD) +echo_cancel_test_CFLAGS = $(module_echo_cancel_la_CFLAGS) -DECHO_CANCEL_TEST=1 +echo_cancel_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) ################################### # Common library # diff --git a/src/modules/echo-cancel/module-echo-cancel.c b/src/modules/echo-cancel/module-echo-cancel.c index c2db87e..10f4118 100644 --- a/src/modules/echo-cancel/module-echo-cancel.c +++ b/src/modules/echo-cancel/module-echo-cancel.c @@ -1322,6 +1322,37 @@ static pa_echo_canceller_method_t get_ec_method_from_string(const char *method) return PA_ECHO_CANCELLER_INVALID; } +/* Common initialisation bits between module-echo-cancel and the standalone test program */ +static int init_common(pa_modargs *ma, struct userdata *u, pa_sample_spec *source_ss, pa_channel_map *source_map) { + pa_echo_canceller_method_t ec_method; + + if (pa_modargs_get_sample_spec_and_channel_map(ma, source_ss, source_map, PA_CHANNEL_MAP_DEFAULT) < 0) { + pa_log("Invalid sample format specification or channel map"); + goto fail; + } + + u->ec = pa_xnew0(pa_echo_canceller, 1); + if (!u->ec) { + pa_log("Failed to alloc echo canceller"); + goto fail; + } + + if ((ec_method = get_ec_method_from_string(pa_modargs_get_value(ma, "aec_method", DEFAULT_ECHO_CANCELLER))) < 0) { + pa_log("Invalid echo canceller implementation"); + goto fail; + } + + u->ec->init = ec_table[ec_method].init; + u->ec->run = ec_table[ec_method].run; + u->ec->done = ec_table[ec_method].done; + + return 0; + +fail: + return -1; +} + + int pa__init(pa_module*m) { struct userdata *u; pa_sample_spec source_ss, sink_ss; @@ -1334,7 +1365,6 @@ int pa__init(pa_module*m) { pa_source_new_data source_data; pa_sink_new_data sink_data; pa_memchunk silence; - pa_echo_canceller_method_t ec_method; uint32_t adjust_time_sec; pa_bool_t use_volume_sharing = TRUE; @@ -1366,10 +1396,6 @@ int pa__init(pa_module*m) { source_ss.rate = DEFAULT_RATE; source_ss.channels = DEFAULT_CHANNELS; pa_channel_map_init_auto(&source_map, source_ss.channels, PA_CHANNEL_MAP_DEFAULT); - if (pa_modargs_get_sample_spec_and_channel_map(ma, &source_ss, &source_map, PA_CHANNEL_MAP_DEFAULT) < 0) { - pa_log("Invalid sample format specification or channel map"); - goto fail; - } sink_ss = sink_master->sample_spec; sink_map = sink_master->channel_map; @@ -1389,21 +1415,6 @@ int pa__init(pa_module*m) { m->userdata = u; u->dead = FALSE; - u->ec = pa_xnew0(pa_echo_canceller, 1); - if (!u->ec) { - pa_log("Failed to alloc echo canceller"); - goto fail; - } - - if ((ec_method = get_ec_method_from_string(pa_modargs_get_value(ma, "aec_method", DEFAULT_ECHO_CANCELLER))) < 0) { - pa_log("Invalid echo canceller implementation"); - goto fail; - } - - u->ec->init = ec_table[ec_method].init; - u->ec->run = ec_table[ec_method].run; - u->ec->done = ec_table[ec_method].done; - adjust_time_sec = DEFAULT_ADJUST_TIME_USEC / PA_USEC_PER_SEC; if (pa_modargs_get_value_u32(ma, "adjust_time", &adjust_time_sec) < 0) { pa_log("Failed to parse adjust_time value"); @@ -1427,8 +1438,12 @@ int pa__init(pa_module*m) { goto fail; } + if (init_common(ma, u, &source_ss, &source_map)) + goto fail; + u->asyncmsgq = pa_asyncmsgq_new(0); u->need_realign = TRUE; + if (u->ec->init) { if (!u->ec->init(u->core, u->ec, &source_ss, &source_map, &sink_ss, &sink_map, &u->blocksize, pa_modargs_get_value(ma, "aec_args", NULL))) { pa_log("Failed to init AEC engine"); @@ -1725,3 +1740,108 @@ void pa__done(pa_module*m) { pa_xfree(u); } + +#ifdef ECHO_CANCEL_TEST +/* + * Stand-alone test program for running in the canceller on pre-recorded files. + */ +int main(int argc, char* argv[]) { + struct userdata u; + pa_sample_spec source_ss, sink_ss; + pa_channel_map source_map, sink_map; + pa_modargs *ma = NULL; + uint8_t *rdata = NULL, *pdata = NULL, *cdata = NULL; + int ret = 0, unused; + + pa_memzero(&u, sizeof(u)); + + if (argc < 4 || argc > 6) { + goto usage; + } + + u.ec = pa_xnew0(pa_echo_canceller, 1); + if (!u.ec) { + pa_log("Failed to alloc echo canceller"); + goto fail; + } + + u.captured_file = fopen(argv[2], "r"); + if (u.captured_file == NULL) { + perror ("fopen failed"); + goto fail; + } + u.played_file = fopen(argv[1], "r"); + if (u.played_file == NULL) { + perror ("fopen failed"); + goto fail; + } + u.canceled_file = fopen(argv[3], "wb"); + if (u.canceled_file == NULL) { + perror ("fopen failed"); + goto fail; + } + + u.core = pa_xnew0(pa_core, 1); + u.core->cpu_info.cpu_type = PA_CPU_X86; + u.core->cpu_info.flags.x86 |= PA_CPU_X86_SSE; + + if (!(ma = pa_modargs_new(argc > 4 ? argv[4] : NULL, valid_modargs))) { + pa_log("Failed to parse module arguments."); + goto fail; + } + + source_ss.format = PA_SAMPLE_S16LE; + source_ss.rate = DEFAULT_RATE; + source_ss.channels = DEFAULT_CHANNELS; + pa_channel_map_init_auto(&source_map, source_ss.channels, PA_CHANNEL_MAP_DEFAULT); + + init_common(ma, &u, &source_ss, &source_map); + + if (!u.ec->init(u.core, u.ec, &source_ss, &source_map, &sink_ss, &sink_map, &u.blocksize, + (argc > 4) ? argv[5] : NULL )) { + pa_log("Failed to init AEC engine"); + goto fail; + } + + rdata = pa_xmalloc(u.blocksize); + pdata = pa_xmalloc(u.blocksize); + cdata = pa_xmalloc(u.blocksize); + + while (fread(rdata, u.blocksize, 1, u.captured_file) > 0) { + if (fread(pdata, u.blocksize, 1, u.played_file) == 0) { + perror("played file ended before captured file"); + break; + } + + u.ec->run(u.ec, rdata, pdata, cdata); + + unused = fwrite(cdata, u.blocksize, 1, u.canceled_file); + } + + u.ec->done(u.ec); + + fclose(u.captured_file); + fclose(u.played_file); + fclose(u.canceled_file); + +out: + pa_xfree(rdata); + pa_xfree(pdata); + pa_xfree(cdata); + + pa_xfree(u.ec); + pa_xfree(u.core); + + if (ma) + pa_modargs_free(ma); + + return ret; + +usage: + pa_log("Usage: %s play_file rec_file out_file [module args] [aec_args]",argv[0]); + +fail: + ret = -1; + goto out; +} +#endif /* ECHO_CANCEL_TEST */ -- 2.7.4