[pulseaudio-commits] 7 commits - src/Makefile.am src/modules
Arun Raghavan
arun at kemper.freedesktop.org
Mon Oct 10 01:03:31 PDT 2011
src/Makefile.am | 8
src/modules/echo-cancel/module-echo-cancel.c | 281 ++++++++++++++++++++-------
2 files changed, 225 insertions(+), 64 deletions(-)
New commits:
commit f1e41a78ccb33d471aabf8c3c9274962780f6f92
Author: Arun Raghavan <arun.raghavan at collabora.co.uk>
Date: Wed Oct 5 13:13:38 2011 +0530
echo-cancel: Drop sink/source samples before processing begins
This moves the bits that skip source/sink samples for resync from inside
the processing loop to just before. The actual effect should be the
the same.
diff --git a/src/modules/echo-cancel/module-echo-cancel.c b/src/modules/echo-cancel/module-echo-cancel.c
index 77c8ff8..b17e18f 100644
--- a/src/modules/echo-cancel/module-echo-cancel.c
+++ b/src/modules/echo-cancel/module-echo-cancel.c
@@ -645,7 +645,8 @@ static void do_resync(struct userdata *u) {
/* Called from input thread context */
static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk) {
struct userdata *u;
- size_t rlen, plen;
+ size_t rlen, plen, to_skip;
+ pa_memchunk rchunk, pchunk;
pa_source_output_assert_ref(o);
pa_source_output_assert_io_context(o);
@@ -674,36 +675,59 @@ static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk)
if (rlen < u->blocksize)
return;
+ /* See if we need to drop samples in order to sync */
if (pa_atomic_cmpxchg (&u->request_resync, 1, 0)) {
do_resync(u);
}
- while (rlen >= u->blocksize) {
- pa_memchunk rchunk, pchunk;
+ /* Okay, skip cancellation for skipped source samples if needed. */
+ if (PA_UNLIKELY(u->source_skip)) {
+ /* The slightly tricky bit here is that we drop all but modulo
+ * blocksize bytes and then adjust for that last bit on the sink side.
+ * We do this because the source data is coming at a fixed rate, which
+ * means the only way to try to catch up is drop sink samples and let
+ * the canceller cope up with this. */
+ to_skip = rlen >= u->source_skip ? u->source_skip : rlen;
+ to_skip -= to_skip % u->blocksize;
+
+ if (to_skip) {
+ pa_memblockq_peek_fixed_size(u->source_memblockq, to_skip, &rchunk);
+ pa_source_post(u->source, &rchunk);
+
+ pa_memblock_unref(rchunk.memblock);
+ pa_memblockq_drop(u->source_memblockq, u->blocksize);
+
+ rlen -= to_skip;
+ u->source_skip -= to_skip;
+ }
+
+ if (rlen && u->source_skip % u->blocksize) {
+ u->sink_skip += u->blocksize - (u->source_skip % u->blocksize);
+ u->source_skip -= (u->source_skip % u->blocksize);
+ }
+ }
+ /* And for the sink, these samples have been played back already, so we can
+ * just drop them and get on with it. */
+ if (PA_UNLIKELY(u->sink_skip)) {
+ to_skip = plen >= u->sink_skip ? u->sink_skip : plen;
+
+ pa_memblockq_drop(u->sink_memblockq, to_skip);
+
+ plen -= to_skip;
+ u->sink_skip -= to_skip;
+ }
+
+ while (rlen >= u->blocksize) {
/* take fixed block from recorded samples */
pa_memblockq_peek_fixed_size(u->source_memblockq, u->blocksize, &rchunk);
- if (plen > u->blocksize && u->source_skip == 0) {
+ if (plen > u->blocksize) {
uint8_t *rdata, *pdata, *cdata;
pa_memchunk cchunk;
int unused;
- if (u->sink_skip) {
- size_t to_skip;
-
- if (u->sink_skip > plen)
- to_skip = plen;
- else
- to_skip = u->sink_skip;
-
- pa_memblockq_drop(u->sink_memblockq, to_skip);
- plen -= to_skip;
-
- u->sink_skip -= to_skip;
- }
-
- if (plen > u->blocksize && u->sink_skip == 0) {
+ if (plen > u->blocksize) {
/* take fixed block from played samples */
pa_memblockq_peek_fixed_size(u->sink_memblockq, u->blocksize, &pchunk);
@@ -755,16 +779,6 @@ static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk)
pa_memblockq_drop(u->source_memblockq, u->blocksize);
rlen -= u->blocksize;
-
- if (u->source_skip) {
- if (u->source_skip > u->blocksize) {
- u->source_skip -= u->blocksize;
- }
- else {
- u->sink_skip += (u->blocksize - u->source_skip);
- u->source_skip = 0;
- }
- }
}
}
commit 17011fcf70f723648c1c76d5d80fe98591e5f8a4
Author: Arun Raghavan <arun.raghavan at collabora.co.uk>
Date: Wed Oct 5 12:29:10 2011 +0530
echo-cancel: Skip processing till there's enough data
This makes sure that we only perform any processing (resync or actual
cancellation) after the source provides enough data to actuall run the
canceller.
diff --git a/src/modules/echo-cancel/module-echo-cancel.c b/src/modules/echo-cancel/module-echo-cancel.c
index 8d77a3b..77c8ff8 100644
--- a/src/modules/echo-cancel/module-echo-cancel.c
+++ b/src/modules/echo-cancel/module-echo-cancel.c
@@ -665,15 +665,19 @@ static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk)
while (pa_asyncmsgq_process_one(u->asyncmsgq) > 0)
;
- if (pa_atomic_cmpxchg (&u->request_resync, 1, 0)) {
- do_resync(u);
- }
-
pa_memblockq_push_align(u->source_memblockq, chunk);
rlen = pa_memblockq_get_length(u->source_memblockq);
plen = pa_memblockq_get_length(u->sink_memblockq);
+ /* Let's not do anything else till we have enough data to process */
+ if (rlen < u->blocksize)
+ return;
+
+ if (pa_atomic_cmpxchg (&u->request_resync, 1, 0)) {
+ do_resync(u);
+ }
+
while (rlen >= u->blocksize) {
pa_memchunk rchunk, pchunk;
commit cee60115720e3e96062e35f0d7ee2ff4e531e6b7
Author: Arun Raghavan <arun.raghavan at collabora.co.uk>
Date: Thu Sep 29 12:49:11 2011 +0530
echo-cancel: Skip canceller when no source outputs are connected
When a source-output isn't connected to our virtual source, we skip echo
cancellation altogether. This makes sense in general, and makes sure
that we don't end up adjusting for delay/drift when nothing is
connected. This should make convergence faster when the canceller
actually starts being used.
diff --git a/src/modules/echo-cancel/module-echo-cancel.c b/src/modules/echo-cancel/module-echo-cancel.c
index a3e7e0d..8d77a3b 100644
--- a/src/modules/echo-cancel/module-echo-cancel.c
+++ b/src/modules/echo-cancel/module-echo-cancel.c
@@ -656,6 +656,11 @@ static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk)
return;
}
+ if (PA_UNLIKELY(u->source->thread_info.state != PA_SOURCE_RUNNING)) {
+ pa_source_post(u->source, chunk);
+ return;
+ }
+
/* handle queued messages, do any message sending of our own */
while (pa_asyncmsgq_process_one(u->asyncmsgq) > 0)
;
@@ -852,7 +857,7 @@ static int source_output_process_msg_cb(pa_msgobject *obj, int code, void *data,
pa_source_output_assert_io_context(u->source_output);
- if (PA_SOURCE_IS_OPENED(u->source_output->source->thread_info.state))
+ if (u->source_output->source->thread_info.state == PA_SOURCE_RUNNING)
pa_memblockq_push_align(u->sink_memblockq, chunk);
else
pa_memblockq_flush_write(u->sink_memblockq, TRUE);
commit 4cacb1b670f61650d0b5495b59359fae00e6e497
Author: Arun Raghavan <arun.raghavan at collabora.co.uk>
Date: Wed Oct 5 13:41:43 2011 +0530
echo-cancel: Increase threshold for resyncing, make it configurable
This increase the threshold for difference between the playback and
capture stream before samples are dropped from 1ms to 5ms (the
cancellers are generally robust to this much and higher). Also, we make
this a module parameter to allow easier experimentation with different
values.
diff --git a/src/modules/echo-cancel/module-echo-cancel.c b/src/modules/echo-cancel/module-echo-cancel.c
index 9ecd787..a3e7e0d 100644
--- a/src/modules/echo-cancel/module-echo-cancel.c
+++ b/src/modules/echo-cancel/module-echo-cancel.c
@@ -66,6 +66,7 @@ PA_MODULE_USAGE(
"sink_properties=<properties for the sink> "
"sink_master=<name of sink to filter> "
"adjust_time=<how often to readjust rates in s> "
+ "adjust_threshold=<how much drift to readjust after in ms> "
"format=<sample format> "
"rate=<sample rate> "
"channels=<number of channels> "
@@ -104,6 +105,7 @@ static const pa_echo_canceller ec_table[] = {
#define DEFAULT_RATE 32000
#define DEFAULT_CHANNELS 1
#define DEFAULT_ADJUST_TIME_USEC (1*PA_USEC_PER_SEC)
+#define DEFAULT_ADJUST_TOLERANCE (5*PA_USEC_PER_MSEC)
#define DEFAULT_SAVE_AEC FALSE
#define DEFAULT_AUTOLOADED FALSE
@@ -188,6 +190,7 @@ struct userdata {
int active_mask;
pa_time_event *time_event;
pa_usec_t adjust_time;
+ int adjust_threshold;
FILE *captured_file;
FILE *played_file;
@@ -204,6 +207,7 @@ static const char* const valid_modargs[] = {
"sink_properties",
"sink_master",
"adjust_time",
+ "adjust_threshold",
"format",
"rate",
"channels",
@@ -297,7 +301,7 @@ static void time_callback(pa_mainloop_api *a, pa_time_event *e, const struct tim
new_rate = base_rate;
}
else {
- if (diff_time > 1000) {
+ if (diff_time > u->adjust_threshold) {
/* diff too big, quickly adjust */
pa_asyncmsgq_post(u->asyncmsgq, PA_MSGOBJECT(u->source_output), SOURCE_OUTPUT_MESSAGE_APPLY_DIFF_TIME,
NULL, diff_time, NULL, NULL);
@@ -1362,7 +1366,7 @@ int pa__init(pa_module*m) {
pa_source_new_data source_data;
pa_sink_new_data sink_data;
pa_memchunk silence;
- uint32_t adjust_time_sec;
+ uint32_t temp;
pa_bool_t use_volume_sharing = TRUE;
pa_assert(m);
@@ -1412,17 +1416,28 @@ int pa__init(pa_module*m) {
m->userdata = u;
u->dead = FALSE;
- adjust_time_sec = DEFAULT_ADJUST_TIME_USEC / PA_USEC_PER_SEC;
- if (pa_modargs_get_value_u32(ma, "adjust_time", &adjust_time_sec) < 0) {
+ temp = DEFAULT_ADJUST_TIME_USEC / PA_USEC_PER_SEC;
+ if (pa_modargs_get_value_u32(ma, "adjust_time", &temp) < 0) {
pa_log("Failed to parse adjust_time value");
goto fail;
}
- if (adjust_time_sec != DEFAULT_ADJUST_TIME_USEC / PA_USEC_PER_SEC)
- u->adjust_time = adjust_time_sec * PA_USEC_PER_SEC;
+ if (temp != DEFAULT_ADJUST_TIME_USEC / PA_USEC_PER_SEC)
+ u->adjust_time = temp * PA_USEC_PER_SEC;
else
u->adjust_time = DEFAULT_ADJUST_TIME_USEC;
+ temp = DEFAULT_ADJUST_TOLERANCE / PA_USEC_PER_MSEC;
+ if (pa_modargs_get_value_u32(ma, "adjust_threshold", &temp) < 0) {
+ pa_log("Failed to parse adjust_threshold value");
+ goto fail;
+ }
+
+ if (temp != DEFAULT_ADJUST_TOLERANCE / PA_USEC_PER_MSEC)
+ u->adjust_threshold = temp * PA_USEC_PER_MSEC;
+ else
+ u->adjust_threshold = DEFAULT_ADJUST_TOLERANCE;
+
u->save_aec = DEFAULT_SAVE_AEC;
if (pa_modargs_get_value_boolean(ma, "save_aec", &u->save_aec) < 0) {
pa_log("Failed to parse save_aec value");
commit 0429fe61537e1f3f184f16f9d7c128d159e41c7d
Author: Arun Raghavan <arun.raghavan at collabora.co.uk>
Date: Thu Oct 6 14:36:50 2011 +0530
echo-cancel: Don't crash if adjust_time = 0
diff --git a/src/modules/echo-cancel/module-echo-cancel.c b/src/modules/echo-cancel/module-echo-cancel.c
index 325014a..9ecd787 100644
--- a/src/modules/echo-cancel/module-echo-cancel.c
+++ b/src/modules/echo-cancel/module-echo-cancel.c
@@ -404,7 +404,7 @@ static int source_set_state_cb(pa_source *s, pa_source_state_t state) {
if (state == PA_SOURCE_RUNNING) {
/* restart timer when both sink and source are active */
u->active_mask |= 1;
- if (u->active_mask == 3)
+ if (u->active_mask == 3 && u->adjust_time)
pa_core_rttime_restart(u->core, u->time_event, pa_rtclock_now() + u->adjust_time);
pa_atomic_store(&u->request_resync, 1);
@@ -432,7 +432,7 @@ static int sink_set_state_cb(pa_sink *s, pa_sink_state_t state) {
if (state == PA_SINK_RUNNING) {
/* restart timer when both sink and source are active */
u->active_mask |= 2;
- if (u->active_mask == 3)
+ if (u->active_mask == 3 && u->adjust_time)
pa_core_rttime_restart(u->core, u->time_event, pa_rtclock_now() + u->adjust_time);
pa_atomic_store(&u->request_resync, 1);
commit f9b59e457c5073372dfdd2d023f743e7d8b016f6
Author: Arun Raghavan <arun.raghavan at collabora.co.uk>
Date: Thu Sep 29 10:17:37 2011 +0530
echo-cancel: Remove redundant variable
diff --git a/src/modules/echo-cancel/module-echo-cancel.c b/src/modules/echo-cancel/module-echo-cancel.c
index 10f4118..325014a 100644
--- a/src/modules/echo-cancel/module-echo-cancel.c
+++ b/src/modules/echo-cancel/module-echo-cancel.c
@@ -166,7 +166,6 @@ struct userdata {
pa_bool_t need_realign;
/* to wakeup the source I/O thread */
- pa_bool_t in_push;
pa_asyncmsgq *asyncmsgq;
pa_rtpoll_item *rtpoll_item_read, *rtpoll_item_write;
@@ -653,11 +652,9 @@ static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk)
return;
}
- /* handle queued messages */
- u->in_push = TRUE;
+ /* handle queued messages, do any message sending of our own */
while (pa_asyncmsgq_process_one(u->asyncmsgq) > 0)
;
- u->in_push = FALSE;
if (pa_atomic_cmpxchg (&u->request_resync, 1, 0)) {
do_resync(u);
commit 3f5c5582f46ecd99a1c9e8a2dbad94d56df45465
Author: Arun Raghavan <arun.raghavan at collabora.co.uk>
Date: Mon Sep 26 21:30:49 2011 +0530
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.
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- at 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 */
More information about the pulseaudio-commits
mailing list