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.
This commit is contained in:
Arun Raghavan 2011-09-26 21:30:49 +05:30
parent a103e82029
commit 3f5c5582f4
2 changed files with 147 additions and 21 deletions

View file

@ -253,7 +253,8 @@ TESTS_norun = \
flist-test \ flist-test \
rtstutter \ rtstutter \
stripnul \ stripnul \
connect-stress connect-stress \
echo-cancel-test
if !OS_IS_WIN32 if !OS_IS_WIN32
TESTS += \ TESTS += \
@ -518,6 +519,11 @@ connect_stress_LDADD = $(AM_LDADD) libpulse.la libpulsecommon-@PA_MAJORMINOR@.la
connect_stress_CFLAGS = $(AM_CFLAGS) connect_stress_CFLAGS = $(AM_CFLAGS)
connect_stress_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) 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 # # Common library #

View file

@ -1322,6 +1322,37 @@ static pa_echo_canceller_method_t get_ec_method_from_string(const char *method)
return PA_ECHO_CANCELLER_INVALID; 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) { int pa__init(pa_module*m) {
struct userdata *u; struct userdata *u;
pa_sample_spec source_ss, sink_ss; pa_sample_spec source_ss, sink_ss;
@ -1334,7 +1365,6 @@ int pa__init(pa_module*m) {
pa_source_new_data source_data; pa_source_new_data source_data;
pa_sink_new_data sink_data; pa_sink_new_data sink_data;
pa_memchunk silence; pa_memchunk silence;
pa_echo_canceller_method_t ec_method;
uint32_t adjust_time_sec; uint32_t adjust_time_sec;
pa_bool_t use_volume_sharing = TRUE; pa_bool_t use_volume_sharing = TRUE;
@ -1366,10 +1396,6 @@ int pa__init(pa_module*m) {
source_ss.rate = DEFAULT_RATE; source_ss.rate = DEFAULT_RATE;
source_ss.channels = DEFAULT_CHANNELS; source_ss.channels = DEFAULT_CHANNELS;
pa_channel_map_init_auto(&source_map, source_ss.channels, PA_CHANNEL_MAP_DEFAULT); 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_ss = sink_master->sample_spec;
sink_map = sink_master->channel_map; sink_map = sink_master->channel_map;
@ -1389,21 +1415,6 @@ int pa__init(pa_module*m) {
m->userdata = u; m->userdata = u;
u->dead = FALSE; 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; adjust_time_sec = DEFAULT_ADJUST_TIME_USEC / PA_USEC_PER_SEC;
if (pa_modargs_get_value_u32(ma, "adjust_time", &adjust_time_sec) < 0) { if (pa_modargs_get_value_u32(ma, "adjust_time", &adjust_time_sec) < 0) {
pa_log("Failed to parse adjust_time value"); pa_log("Failed to parse adjust_time value");
@ -1427,8 +1438,12 @@ int pa__init(pa_module*m) {
goto fail; goto fail;
} }
if (init_common(ma, u, &source_ss, &source_map))
goto fail;
u->asyncmsgq = pa_asyncmsgq_new(0); u->asyncmsgq = pa_asyncmsgq_new(0);
u->need_realign = TRUE; u->need_realign = TRUE;
if (u->ec->init) { 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))) { 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"); pa_log("Failed to init AEC engine");
@ -1725,3 +1740,108 @@ void pa__done(pa_module*m) {
pa_xfree(u); 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 */