[pulseaudio-commits] [SCM] PulseAudio Sound Server branch, stable-queue, updated. v0.9.22-59-g6664650
Colin Guthrie
gitmailer-noreply at 0pointer.de
Mon Mar 28 02:21:00 PDT 2011
This is an automated email from the git hooks/post-receive script. It was
generated because of a push to the "PulseAudio Sound Server" repository.
The stable-queue branch has been updated
from 9f52c1064651667f48877f54ae002e97eecd6289 (commit)
- Log -----------------------------------------------------------------
6664650 i18n: Update POTFILES.*
f93b5e1 echo-cancel: Fix source may_move_to function
d866ade echo-cancel: Use S16NE for adrian module
1212a6f echo-cancel: Ensure correct handling of endianness
d8fab84 echo-cancel: Fix out-of-tree build
d39043c echo-cancel: Fix make distcheck
e936447 echo-cancel: pause timer when echo canceling is off
56a4684 echo-cancel: improve accuracy
9f79c0e echo-cancel: rework alignment code
2923c5e echo-cancel: Mark immutable parameters as const in vfunc
b6b8a7b echo-cancel: Make blocksize a module-wide parameter
4a9fa8c echo-cancel: Allow selection of AEC method using modargs
47e4dd1 echo-cancel: Add alternative echo-cancellation implementation
c975dfa echo-cancel: Let AEC module determine source/sink spec
668f4e4 echo-cancel: Pass arguments to the specific canceller module
57c5983 echo-cancel: Split out speex code from the core module
6d8a907 echo-cancel: Move the module into it's own directory
b30bf12 echo-cancel: keep frame_size a power of 2
1c26694 echo-cancel: improve debug
de6cdf9 echo-cancel: tweak the resync code a little
7b972f5 echo-cancel: use the phone media role
9fa71e7 echo-cancel: take into account snapshot delay
1a8cf00 make echo-cancel module that exposes a new sink and source
c524b4c memblockq: implement new call pa_memblockq_peek_fixed_size()
-----------------------------------------------------------------------
Summary of changes:
LICENSE | 4 +
po/POTFILES.in | 1 +
src/Makefile.am | 14 +
src/modules/echo-cancel/adrian-aec.c | 233 ++++
src/modules/echo-cancel/adrian-aec.h | 370 ++++++
src/modules/echo-cancel/adrian-license.txt | 17 +
src/modules/echo-cancel/adrian.c | 113 ++
src/modules/echo-cancel/adrian.h | 31 +
src/modules/echo-cancel/echo-cancel.h | 80 ++
src/modules/echo-cancel/module-echo-cancel.c | 1680 ++++++++++++++++++++++++++
src/modules/echo-cancel/speex.c | 116 ++
src/pulsecore/memblockq.c | 71 ++-
src/pulsecore/memblockq.h | 5 +
src/tests/memblockq-test.c | 44 +-
14 files changed, 2766 insertions(+), 13 deletions(-)
create mode 100644 src/modules/echo-cancel/adrian-aec.c
create mode 100644 src/modules/echo-cancel/adrian-aec.h
create mode 100644 src/modules/echo-cancel/adrian-license.txt
create mode 100644 src/modules/echo-cancel/adrian.c
create mode 100644 src/modules/echo-cancel/adrian.h
create mode 100644 src/modules/echo-cancel/echo-cancel.h
create mode 100644 src/modules/echo-cancel/module-echo-cancel.c
create mode 100644 src/modules/echo-cancel/speex.c
-----------------------------------------------------------------------
commit c524b4c5b59a11c0a85d27d64dfb607246abc141
Author: Lennart Poettering <lennart at poettering.net>
Date: Thu Feb 25 02:10:45 2010 +0100
memblockq: implement new call pa_memblockq_peek_fixed_size()
diff --git a/src/pulsecore/memblockq.c b/src/pulsecore/memblockq.c
index 2b063fa..c784048 100644
--- a/src/pulsecore/memblockq.c
+++ b/src/pulsecore/memblockq.c
@@ -481,7 +481,6 @@ int pa_memblockq_peek(pa_memblockq* bq, pa_memchunk *chunk) {
/* Do we need to spit out silence? */
if (!bq->current_read || bq->current_read->index > bq->read_index) {
-
size_t length;
/* How much silence shall we return? */
@@ -527,6 +526,76 @@ int pa_memblockq_peek(pa_memblockq* bq, pa_memchunk *chunk) {
return 0;
}
+int pa_memblockq_peek_fixed_size(pa_memblockq *bq, size_t block_size, pa_memchunk *chunk) {
+ pa_memchunk tchunk, rchunk;
+ int64_t ri;
+ struct list_item *item;
+
+ pa_assert(bq);
+ pa_assert(block_size > 0);
+ pa_assert(chunk);
+ pa_assert(bq->silence.memblock);
+
+ if (pa_memblockq_peek(bq, &tchunk) < 0)
+ return -1;
+
+ if (tchunk.length >= block_size) {
+ *chunk = tchunk;
+ chunk->length = block_size;
+ return 0;
+ }
+
+ rchunk.memblock = pa_memblock_new(pa_memblock_get_pool(tchunk.memblock), block_size);
+ rchunk.index = 0;
+ rchunk.length = tchunk.length;
+
+ pa_memchunk_memcpy(&rchunk, &tchunk);
+ pa_memblock_unref(tchunk.memblock);
+
+ rchunk.index += tchunk.length;
+
+ /* We don't need to call fix_current_read() here, since
+ * pa_memblock_peek() already did that */
+ item = bq->current_read;
+ ri = bq->read_index + tchunk.length;
+
+ while (rchunk.index < block_size) {
+
+ if (!item || item->index > ri) {
+ /* Do we need to append silence? */
+ tchunk = bq->silence;
+
+ if (item)
+ tchunk.length = PA_MIN(tchunk.length, (size_t) (item->index - ri));
+
+ } else {
+ int64_t d;
+
+ /* We can append real data! */
+ tchunk = item->chunk;
+
+ d = ri - item->index;
+ tchunk.index += (size_t) d;
+ tchunk.length -= (size_t) d;
+
+ /* Go to next item for the next iteration */
+ item = item->next;
+ }
+
+ rchunk.length = tchunk.length = PA_MIN(tchunk.length, block_size - rchunk.index);
+ pa_memchunk_memcpy(&rchunk, &tchunk);
+
+ rchunk.index += rchunk.length;
+ ri += rchunk.length;
+ }
+
+ rchunk.index = 0;
+ rchunk.length = block_size;
+
+ *chunk = rchunk;
+ return 0;
+}
+
void pa_memblockq_drop(pa_memblockq *bq, size_t length) {
int64_t old;
pa_assert(bq);
diff --git a/src/pulsecore/memblockq.h b/src/pulsecore/memblockq.h
index 6132f31..3775c3f 100644
--- a/src/pulsecore/memblockq.h
+++ b/src/pulsecore/memblockq.h
@@ -95,6 +95,11 @@ void pa_memblockq_seek(pa_memblockq *bq, int64_t offset, pa_seek_mode_t seek, pa
* was passed we return the length of the hole in chunk->length. */
int pa_memblockq_peek(pa_memblockq* bq, pa_memchunk *chunk);
+/* Much like pa_memblockq_peek, but guarantees that the returned chunk
+ * will have a length of the block size passed. You must configure a
+ * silence memchunk for this memblockq if you use this call. */
+int pa_memblockq_peek_fixed_size(pa_memblockq *bq, size_t block_size, pa_memchunk *chunk);
+
/* Drop the specified bytes from the queue. */
void pa_memblockq_drop(pa_memblockq *bq, size_t length);
diff --git a/src/tests/memblockq-test.c b/src/tests/memblockq-test.c
index ec3f542..c3afd0a 100644
--- a/src/tests/memblockq-test.c
+++ b/src/tests/memblockq-test.c
@@ -29,23 +29,43 @@
#include <pulsecore/memblockq.h>
#include <pulsecore/log.h>
+static void dump_chunk(const pa_memchunk *chunk) {
+ size_t n;
+ void *q;
+ char *e;
+
+ pa_assert(chunk);
+
+ printf("[");
+
+ q = pa_memblock_acquire(chunk->memblock);
+ for (e = (char*) q + chunk->index, n = 0; n < chunk->length; n++, e++)
+ printf("%c", *e);
+ pa_memblock_release(chunk->memblock);
+
+ printf("]");
+}
+
static void dump(pa_memblockq *bq) {
- printf(">");
+ pa_memchunk out;
- for (;;) {
- pa_memchunk out;
- char *e;
- size_t n;
- void *q;
+ pa_assert(bq);
+
+ /* First let's dump this as fixed block */
+ printf("FIXED >");
+ pa_memblockq_peek_fixed_size(bq, 64, &out);
+ dump_chunk(&out);
+ pa_memblock_unref(out.memblock);
+ printf("<\n");
+ /* Then let's dump the queue manually */
+ printf("MANUAL>");
+
+ for (;;) {
if (pa_memblockq_peek(bq, &out) < 0)
break;
- q = pa_memblock_acquire(out.memblock);
- for (e = (char*) q + out.index, n = 0; n < out.length; n++)
- printf("%c", *e);
- pa_memblock_release(out.memblock);
-
+ dump_chunk(&out);
pa_memblock_unref(out.memblock);
pa_memblockq_drop(bq, out.length);
}
@@ -70,7 +90,7 @@ int main(int argc, char *argv[]) {
silence.index = 0;
silence.length = pa_memblock_get_length(silence.memblock);
- bq = pa_memblockq_new(0, 40, 10, 2, 4, 4, 40, &silence);
+ bq = pa_memblockq_new(0, 200, 10, 2, 4, 4, 40, &silence);
assert(bq);
chunk1.memblock = pa_memblock_new_fixed(p, (char*) "11", 2, 1);
commit 1a8cf00ba017a9135d2c857ddb100d8fd23dd43d
Author: Wim Taymans <wim.taymans at collabora.co.uk>
Date: Mon Aug 23 16:38:47 2010 +0200
make echo-cancel module that exposes a new sink and source
Make a new echo-cancel module that exposes a new sink and source. All data sent
to the sink is matched against the data captured from the source and
echo-canceled using the speex echo canceler.
diff --git a/src/Makefile.am b/src/Makefile.am
index ec301da..4345e02 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1017,6 +1017,7 @@ modlibexec_LTLIBRARIES += \
module-rescue-streams.la \
module-intended-roles.la \
module-suspend-on-idle.la \
+ module-echo-cancel.la \
module-http-protocol-tcp.la \
module-sine.la \
module-native-protocol-tcp.la \
@@ -1248,6 +1249,7 @@ SYMDEF_FILES = \
modules/module-rescue-streams-symdef.h \
modules/module-intended-roles-symdef.h \
modules/module-suspend-on-idle-symdef.h \
+ modules/module-echo-cancel-symdef.h \
modules/module-hal-detect-symdef.h \
modules/module-udev-detect-symdef.h \
modules/bluetooth/module-bluetooth-proximity-symdef.h \
@@ -1602,6 +1604,12 @@ module_suspend_on_idle_la_LDFLAGS = $(MODULE_LDFLAGS)
module_suspend_on_idle_la_LIBADD = $(AM_LIBADD) libpulsecore- at PA_MAJORMINORMICRO@.la libpulsecommon- at PA_MAJORMINORMICRO@.la libpulse.la
module_suspend_on_idle_la_CFLAGS = $(AM_CFLAGS)
+# echo-cancel module
+module_echo_cancel_la_SOURCES = modules/module-echo-cancel.c
+module_echo_cancel_la_LDFLAGS = $(MODULE_LDFLAGS)
+module_echo_cancel_la_LIBADD = $(AM_LIBADD) libpulsecore- at PA_MAJORMINORMICRO@.la libpulsecommon- at PA_MAJORMINORMICRO@.la libpulse.la $(LIBSPEEX_LIBS)
+module_echo_cancel_la_CFLAGS = $(AM_CFLAGS) $(LIBSPEEX_CFLAGS)
+
# RTP modules
module_rtp_send_la_SOURCES = modules/rtp/module-rtp-send.c
module_rtp_send_la_LDFLAGS = $(MODULE_LDFLAGS)
diff --git a/src/modules/module-echo-cancel.c b/src/modules/module-echo-cancel.c
new file mode 100644
index 0000000..3d7a6ef
--- /dev/null
+++ b/src/modules/module-echo-cancel.c
@@ -0,0 +1,1609 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2010 Wim Taymans <wim.taymans at gmail.com>
+
+ Based on module-virtual-sink.c
+ module-virtual-source.c
+ module-loopback.c
+
+ Copyright 2010 Intel Corporation
+ Contributor: Pierre-Louis Bossart <pierre-louis.bossart at intel.com>
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+
+#include <speex/speex_echo.h>
+
+#include <pulse/xmalloc.h>
+#include <pulse/i18n.h>
+#include <pulse/timeval.h>
+#include <pulse/rtclock.h>
+
+#include <pulsecore/atomic.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/core-error.h>
+#include <pulsecore/namereg.h>
+#include <pulsecore/sink.h>
+#include <pulsecore/module.h>
+#include <pulsecore/core-rtclock.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/core-error.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/log.h>
+#include <pulsecore/thread.h>
+#include <pulsecore/thread-mq.h>
+#include <pulsecore/rtpoll.h>
+#include <pulsecore/sample-util.h>
+#include <pulsecore/ltdl-helper.h>
+
+#include "module-echo-cancel-symdef.h"
+
+PA_MODULE_AUTHOR("Wim Taymans");
+PA_MODULE_DESCRIPTION("Echo Cancelation");
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_LOAD_ONCE(FALSE);
+PA_MODULE_USAGE(
+ _("source_name=<name for the source> "
+ "source_properties=<properties for the source> "
+ "source_master=<name of source to filter> "
+ "sink_name=<name for the sink> "
+ "sink_properties=<properties for the sink> "
+ "sink_master=<name of sink to filter> "
+ "frame_size_ms=<amount of data to process at one time> "
+ "filter_size_ms=<amount of echo to cancel> "
+ "adjust_time=<how often to readjust rates in s> "
+ "format=<sample format> "
+ "rate=<sample rate> "
+ "channels=<number of channels> "
+ "channel_map=<channel map> "
+ "save_aec=<save AEC data in /tmp> "
+ ));
+
+/* should be between 10-20 ms */
+#define DEFAULT_FRAME_SIZE_MS 20
+/* should be between 100-500 ms */
+#define DEFAULT_FILTER_SIZE_MS 200
+
+#define DEFAULT_ADJUST_TIME_USEC (1*PA_USEC_PER_SEC)
+#define DEFAULT_SAVE_AEC 0
+
+#define MEMBLOCKQ_MAXLENGTH (16*1024*1024)
+
+/* This module creates a new (virtual) source and sink.
+ *
+ * The data sent to the new sink is kept in a memblockq before being
+ * forwarded to the real sink_master.
+ *
+ * Data read from source_master is matched against the saved sink data and
+ * echo canceled data is then pushed onto the new source.
+ *
+ * Both source and sink masters have their own threads to push/pull data
+ * respectively. We however perform all our actions in the source IO thread.
+ * To do this we send all played samples to the source IO thread where they
+ * are then pushed into the memblockq.
+ *
+ * Alignment is performed in two steps:
+ *
+ * 1) when something happens that requires quick adjustement of the alignment of
+ * capture and playback samples, we perform a resync. This adjusts the
+ * position in the playback memblock to the requested sample. Quick
+ * adjustements include moving the playback samples before the capture
+ * samples (because else the echo canceler does not work) or when the
+ * playback pointer drifts too far away.
+ *
+ * 2) periodically check the difference between capture and playback. we use a
+ * low and high watermark for adjusting the alignment. playback should always
+ * be before capture and the difference should not be bigger than one frame
+ * size. We would ideally like to resample the sink_input but most driver
+ * don't give enough accuracy to be able to do that right now.
+ */
+
+struct snapshot {
+ pa_usec_t sink_latency;
+ size_t sink_delay;
+ int64_t send_counter;
+
+ pa_usec_t source_latency;
+ size_t source_delay;
+ int64_t recv_counter;
+ size_t rlen;
+ size_t plen;
+};
+
+struct userdata {
+ pa_core *core;
+ pa_module *module;
+
+ uint32_t frame_size_ms;
+ uint32_t save_aec;
+
+ SpeexEchoState *echo_state;
+
+ size_t blocksize;
+ 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;
+
+ pa_source *source;
+ pa_bool_t source_auto_desc;
+ pa_source_output *source_output;
+ pa_memblockq *source_memblockq; /* echo canceler needs fixed sized chunks */
+ pa_atomic_t source_active;
+
+ pa_sink *sink;
+ pa_bool_t sink_auto_desc;
+ pa_sink_input *sink_input;
+ pa_memblockq *sink_memblockq;
+ int64_t send_counter; /* updated in sink IO thread */
+ int64_t recv_counter;
+ pa_atomic_t sink_active;
+
+ pa_atomic_t request_resync;
+
+ pa_time_event *time_event;
+ pa_usec_t adjust_time;
+
+ FILE *captured_file;
+ FILE *played_file;
+ FILE *canceled_file;
+};
+
+static void source_output_snapshot_within_thread(struct userdata *u, struct snapshot *snapshot);
+
+static const char* const valid_modargs[] = {
+ "source_name",
+ "source_properties",
+ "source_master",
+ "sink_name",
+ "sink_properties",
+ "sink_master",
+ "frame_size_ms",
+ "filter_size_ms",
+ "adjust_time",
+ "format",
+ "rate",
+ "channels",
+ "channel_map",
+ "save_aec",
+ NULL
+};
+
+enum {
+ SOURCE_OUTPUT_MESSAGE_POST = PA_SOURCE_OUTPUT_MESSAGE_MAX,
+ SOURCE_OUTPUT_MESSAGE_REWIND,
+ SOURCE_OUTPUT_MESSAGE_LATENCY_SNAPSHOT,
+ SOURCE_OUTPUT_MESSAGE_APPLY_DIFF_TIME
+};
+
+enum {
+ SINK_INPUT_MESSAGE_LATENCY_SNAPSHOT
+};
+
+static int64_t calc_diff(struct userdata *u, struct snapshot *snapshot) {
+ int64_t buffer, diff_time, buffer_latency;
+
+ /* get the number of samples between capture and playback */
+ if (snapshot->plen > snapshot->rlen)
+ buffer = snapshot->plen - snapshot->rlen;
+ else
+ buffer = 0;
+
+ buffer += snapshot->source_delay + snapshot->sink_delay;
+
+ /* add the amount of samples not yet transfered to the source context */
+ if (snapshot->recv_counter <= snapshot->send_counter)
+ buffer += (int64_t) (snapshot->send_counter - snapshot->recv_counter);
+ else
+ buffer += PA_CLIP_SUB(buffer, (int64_t) (snapshot->recv_counter - snapshot->send_counter));
+
+ /* convert to time */
+ buffer_latency = pa_bytes_to_usec(buffer, &u->source_output->sample_spec);
+
+ /* capture and playback samples are perfectly aligned when diff_time is 0 */
+ diff_time = (snapshot->sink_latency - buffer_latency) + snapshot->source_latency;
+
+ pa_log_debug("diff %lld (%lld - %lld + %lld) %lld %lld %lld", (long long) diff_time,
+ (long long) snapshot->sink_latency,
+ (long long) buffer_latency, (long long) snapshot->source_latency,
+ (long long) snapshot->source_delay, (long long) snapshot->sink_delay,
+ (long long) (snapshot->send_counter - snapshot->recv_counter));
+
+ return diff_time;
+}
+
+/* Called from main context */
+static void time_callback(pa_mainloop_api *a, pa_time_event *e, const struct timeval *t, void *userdata) {
+ struct userdata *u = userdata;
+ uint32_t old_rate, base_rate, new_rate;
+ int64_t diff_time;
+ size_t fs;
+ struct snapshot latency_snapshot;
+
+ pa_assert(u);
+ pa_assert(a);
+ pa_assert(u->time_event == e);
+ pa_assert_ctl_context();
+
+ if (pa_atomic_load (&u->sink_active) == 0 || pa_atomic_load (&u->source_active) == 0)
+ goto done;
+
+ /* update our snapshots */
+ pa_asyncmsgq_send(u->source_output->source->asyncmsgq, PA_MSGOBJECT(u->source_output), SOURCE_OUTPUT_MESSAGE_LATENCY_SNAPSHOT, &latency_snapshot, 0, NULL);
+ pa_asyncmsgq_send(u->sink_input->sink->asyncmsgq, PA_MSGOBJECT(u->sink_input), SINK_INPUT_MESSAGE_LATENCY_SNAPSHOT, &latency_snapshot, 0, NULL);
+
+ /* calculate drift between capture and playback */
+ diff_time = calc_diff(u, &latency_snapshot);
+
+ fs = pa_frame_size(&u->source_output->sample_spec);
+ old_rate = u->sink_input->sample_spec.rate;
+ base_rate = u->source_output->sample_spec.rate;
+
+ if (diff_time < 0) {
+ pa_log_info("Playback after capture (%lld), realign", (long long) diff_time);
+ pa_asyncmsgq_post(u->asyncmsgq, PA_MSGOBJECT(u->source_output), SOURCE_OUTPUT_MESSAGE_APPLY_DIFF_TIME,
+ NULL, diff_time, NULL, NULL);
+ /* recording before playback, we need to adjust quickly. The echo
+ * canceler does not work in this case. */
+ //new_rate = base_rate - ((pa_usec_to_bytes (-diff_time, &u->source_output->sample_spec) / fs) * PA_USEC_PER_SEC) / u->adjust_time;
+ new_rate = base_rate;
+ }
+ else {
+ if (diff_time > (u->frame_size_ms / 2) * 1000) {
+ pa_log_info("playback too far ahead (%lld), realign", (long long) diff_time);
+ /* 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);
+ }
+
+ /* recording behind playback, we need to slowly adjust the rate to match */
+ //new_rate = base_rate + ((pa_usec_to_bytes (diff_time, &u->source_output->sample_spec) / fs) * PA_USEC_PER_SEC) / u->adjust_time;
+
+ /* assume equal samplerates for now */
+ new_rate = base_rate;
+ }
+
+ /* make sure we don't make too big adjustements because that sounds horrible */
+ if (new_rate > base_rate * 1.1 || new_rate < base_rate * 0.9)
+ new_rate = base_rate;
+
+ if (new_rate != old_rate) {
+ pa_log_info("Old rate %lu Hz, new rate %lu Hz", (unsigned long) old_rate, (unsigned long) new_rate);
+
+ pa_sink_input_set_rate(u->sink_input, new_rate);
+ }
+
+done:
+ pa_core_rttime_restart(u->core, u->time_event, pa_rtclock_now() + u->adjust_time);
+}
+
+/* Called from source I/O thread context */
+static int source_process_msg_cb(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
+ struct userdata *u = PA_SOURCE(o)->userdata;
+
+ switch (code) {
+
+ case PA_SOURCE_MESSAGE_GET_LATENCY:
+
+ /* The source is _put() before the source output is, so let's
+ * make sure we don't access it in that time. Also, the
+ * source output is first shut down, the source second. */
+ if (!PA_SOURCE_IS_LINKED(u->source->thread_info.state) ||
+ !PA_SOURCE_OUTPUT_IS_LINKED(u->source_output->thread_info.state)) {
+ *((pa_usec_t*) data) = 0;
+ return 0;
+ }
+
+ *((pa_usec_t*) data) =
+
+ /* Get the latency of the master source */
+ pa_source_get_latency_within_thread(u->source_output->source) +
+ /* Add the latency internal to our source output on top */
+ pa_bytes_to_usec(pa_memblockq_get_length(u->source_output->thread_info.delay_memblockq), &u->source_output->source->sample_spec) +
+ /* and the buffering we do on the source */
+ pa_bytes_to_usec(u->blocksize, &u->source_output->source->sample_spec);
+
+ return 0;
+
+ }
+
+ return pa_source_process_msg(o, code, data, offset, chunk);
+}
+
+/* Called from sink I/O thread context */
+static int sink_process_msg_cb(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
+ struct userdata *u = PA_SINK(o)->userdata;
+
+ switch (code) {
+
+ case PA_SINK_MESSAGE_GET_LATENCY:
+
+ /* The sink is _put() before the sink input is, so let's
+ * make sure we don't access it in that time. Also, the
+ * sink input is first shut down, the sink second. */
+ if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) ||
+ !PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state)) {
+ *((pa_usec_t*) data) = 0;
+ return 0;
+ }
+
+ *((pa_usec_t*) data) =
+
+ /* Get the latency of the master sink */
+ pa_sink_get_latency_within_thread(u->sink_input->sink) +
+
+ /* Add the latency internal to our sink input on top */
+ pa_bytes_to_usec(pa_memblockq_get_length(u->sink_input->thread_info.render_memblockq), &u->sink_input->sink->sample_spec);
+
+ return 0;
+ }
+
+ return pa_sink_process_msg(o, code, data, offset, chunk);
+}
+
+
+/* Called from main context */
+static int source_set_state_cb(pa_source *s, pa_source_state_t state) {
+ struct userdata *u;
+
+ pa_source_assert_ref(s);
+ pa_assert_se(u = s->userdata);
+
+ if (!PA_SOURCE_IS_LINKED(state) ||
+ !PA_SOURCE_OUTPUT_IS_LINKED(pa_source_output_get_state(u->source_output)))
+ return 0;
+
+ pa_log_debug("Source state %d", state);
+
+ if (state == PA_SOURCE_RUNNING) {
+ pa_atomic_store (&u->source_active, 1);
+ pa_atomic_store (&u->request_resync, 1);
+ pa_source_output_cork(u->source_output, FALSE);
+ } else if (state == PA_SOURCE_SUSPENDED) {
+ pa_atomic_store (&u->source_active, 0);
+ pa_source_output_cork(u->source_output, TRUE);
+ }
+ return 0;
+}
+
+/* Called from main context */
+static int sink_set_state_cb(pa_sink *s, pa_sink_state_t state) {
+ struct userdata *u;
+
+ pa_sink_assert_ref(s);
+ pa_assert_se(u = s->userdata);
+
+ if (!PA_SINK_IS_LINKED(state) ||
+ !PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(u->sink_input)))
+ return 0;
+
+ pa_log_debug("Sink state %d", state);
+
+ if (state == PA_SINK_RUNNING) {
+ pa_atomic_store (&u->sink_active, 1);
+ pa_atomic_store (&u->request_resync, 1);
+ pa_sink_input_cork(u->sink_input, FALSE);
+ } else if (state == PA_SINK_SUSPENDED) {
+ pa_atomic_store (&u->sink_active, 0);
+ pa_sink_input_cork(u->sink_input, TRUE);
+ }
+ return 0;
+}
+
+/* Called from I/O thread context */
+static void source_update_requested_latency_cb(pa_source *s) {
+ struct userdata *u;
+
+ pa_source_assert_ref(s);
+ pa_assert_se(u = s->userdata);
+
+ if (!PA_SOURCE_IS_LINKED(u->source->thread_info.state) ||
+ !PA_SOURCE_OUTPUT_IS_LINKED(u->source_output->thread_info.state))
+ return;
+
+ pa_log_debug("Source update requested latency");
+
+ /* Just hand this one over to the master source */
+ pa_source_output_set_requested_latency_within_thread(
+ u->source_output,
+ pa_source_get_requested_latency_within_thread(s));
+}
+
+/* Called from I/O thread context */
+static void sink_update_requested_latency_cb(pa_sink *s) {
+ struct userdata *u;
+
+ pa_sink_assert_ref(s);
+ pa_assert_se(u = s->userdata);
+
+ if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) ||
+ !PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state))
+ return;
+
+ pa_log_debug("Sink update requested latency");
+
+ /* Just hand this one over to the master sink */
+ pa_sink_input_set_requested_latency_within_thread(
+ u->sink_input,
+ pa_sink_get_requested_latency_within_thread(s));
+}
+
+/* Called from I/O thread context */
+static void sink_request_rewind_cb(pa_sink *s) {
+ struct userdata *u;
+
+ pa_sink_assert_ref(s);
+ pa_assert_se(u = s->userdata);
+
+ if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) ||
+ !PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state))
+ return;
+
+ pa_log_debug("Sink request rewind %lld", (long long) s->thread_info.rewind_nbytes);
+
+ /* Just hand this one over to the master sink */
+ pa_sink_input_request_rewind(u->sink_input,
+ s->thread_info.rewind_nbytes, TRUE, FALSE, FALSE);
+}
+
+/* Called from main context */
+static void source_set_volume_cb(pa_source *s) {
+ struct userdata *u;
+
+ pa_source_assert_ref(s);
+ pa_assert_se(u = s->userdata);
+
+ if (!PA_SOURCE_IS_LINKED(pa_source_get_state(s)) ||
+ !PA_SOURCE_OUTPUT_IS_LINKED(pa_source_output_get_state(u->source_output)))
+ return;
+
+ /* FIXME, no volume control in source_output, set volume at the master */
+ pa_source_set_volume(u->source_output->source, &s->volume, TRUE);
+}
+
+/* Called from main context */
+static void sink_set_volume_cb(pa_sink *s) {
+ struct userdata *u;
+
+ pa_sink_assert_ref(s);
+ pa_assert_se(u = s->userdata);
+
+ if (!PA_SINK_IS_LINKED(pa_sink_get_state(s)) ||
+ !PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(u->sink_input)))
+ return;
+
+ pa_sink_input_set_volume(u->sink_input, &s->real_volume, s->save_volume, TRUE);
+}
+
+static void source_get_volume_cb(pa_source *s) {
+ struct userdata *u;
+
+ pa_source_assert_ref(s);
+ pa_assert_se(u = s->userdata);
+
+ if (!PA_SOURCE_IS_LINKED(pa_source_get_state(s)) ||
+ !PA_SOURCE_OUTPUT_IS_LINKED(pa_source_output_get_state(u->source_output)))
+ return;
+
+ /* FIXME, no volume control in source_output, get the info from the master */
+ pa_source_get_volume(u->source_output->source, TRUE);
+
+ if (pa_cvolume_equal(&s->volume,&u->source_output->source->volume))
+ /* no change */
+ return;
+
+ s->volume = u->source_output->source->volume;
+ pa_source_set_soft_volume(s, NULL);
+}
+
+
+/* Called from main context */
+static void source_set_mute_cb(pa_source *s) {
+ struct userdata *u;
+
+ pa_source_assert_ref(s);
+ pa_assert_se(u = s->userdata);
+
+ if (!PA_SOURCE_IS_LINKED(pa_source_get_state(s)) ||
+ !PA_SOURCE_OUTPUT_IS_LINKED(pa_source_output_get_state(u->source_output)))
+ return;
+
+ /* FIXME, no volume control in source_output, set mute at the master */
+ pa_source_set_mute(u->source_output->source, TRUE, TRUE);
+}
+
+/* Called from main context */
+static void sink_set_mute_cb(pa_sink *s) {
+ struct userdata *u;
+
+ pa_sink_assert_ref(s);
+ pa_assert_se(u = s->userdata);
+
+ if (!PA_SINK_IS_LINKED(pa_sink_get_state(s)) ||
+ !PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(u->sink_input)))
+ return;
+
+ pa_sink_input_set_mute(u->sink_input, s->muted, s->save_muted);
+}
+
+/* Called from main context */
+static void source_get_mute_cb(pa_source *s) {
+ struct userdata *u;
+
+ pa_source_assert_ref(s);
+ pa_assert_se(u = s->userdata);
+
+ if (!PA_SOURCE_IS_LINKED(pa_source_get_state(s)) ||
+ !PA_SOURCE_OUTPUT_IS_LINKED(pa_source_output_get_state(u->source_output)))
+ return;
+
+ /* FIXME, no volume control in source_output, get the info from the master */
+ pa_source_get_mute(u->source_output->source, TRUE);
+}
+
+/* must be called from the input thread context */
+static void apply_diff_time(struct userdata *u, int64_t diff_time) {
+ int64_t diff;
+
+ if (diff_time < 0) {
+ diff = pa_usec_to_bytes (-diff_time, &u->source_output->sample_spec);
+
+ pa_log_debug("drop sink (%lld)", (long long) diff);
+
+ /* go forwards on the read side */
+ pa_memblockq_drop(u->sink_memblockq, diff);
+ } else {
+ diff = pa_usec_to_bytes (diff_time, &u->source_output->sample_spec);
+
+ pa_log_debug("drop source (%lld)", (long long) diff);
+
+ /* go back on the read side */
+ pa_memblockq_rewind(u->sink_memblockq, diff);
+ }
+}
+
+/* must be called from the input thread */
+static void do_resync(struct userdata *u) {
+ int64_t diff_time;
+ struct snapshot latency_snapshot;
+
+ pa_log("Doing resync");
+
+ /* update our snapshot */
+ source_output_snapshot_within_thread(u, &latency_snapshot);
+ pa_asyncmsgq_send(u->sink_input->sink->asyncmsgq, PA_MSGOBJECT(u->sink_input), SINK_INPUT_MESSAGE_LATENCY_SNAPSHOT, &latency_snapshot, 0, NULL);
+
+ /* calculate drift between capture and playback */
+ diff_time = calc_diff(u, &latency_snapshot);
+
+ /* and adjust for the drift */
+ apply_diff_time(u, diff_time);
+}
+
+/* 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;
+
+ pa_source_output_assert_ref(o);
+ pa_source_output_assert_io_context(o);
+ pa_assert_se(u = o->userdata);
+
+ if (!PA_SOURCE_OUTPUT_IS_LINKED(pa_source_output_get_state(u->source_output))) {
+ pa_log("push when no link?");
+ return;
+ }
+
+ /* handle queued messages */
+ u->in_push = TRUE;
+ while (pa_asyncmsgq_process_one(u->asyncmsgq) > 0)
+ ;
+ u->in_push = FALSE;
+
+ if (pa_atomic_load (&u->request_resync) == 1) {
+ do_resync (u);
+ pa_atomic_store (&u->request_resync, 0);
+ }
+
+ pa_memblockq_push_align(u->source_memblockq, chunk);
+
+ rlen = pa_memblockq_get_length(u->source_memblockq);
+ plen = pa_memblockq_get_length(u->sink_memblockq);
+
+ while (rlen >= u->blocksize) {
+ pa_memchunk rchunk, pchunk;
+
+ /* take fixed block from recorded samples */
+ pa_memblockq_peek_fixed_size(u->source_memblockq, u->blocksize, &rchunk);
+
+ if (plen > u->blocksize) {
+ uint8_t *rdata, *pdata, *cdata;
+ pa_memchunk cchunk;
+
+ /* take fixed block from played samples */
+ pa_memblockq_peek_fixed_size(u->sink_memblockq, u->blocksize, &pchunk);
+
+ rdata = pa_memblock_acquire(rchunk.memblock);
+ rdata += rchunk.index;
+ pdata = pa_memblock_acquire(pchunk.memblock);
+ pdata += pchunk.index;
+
+ cchunk.index = 0;
+ cchunk.length = u->blocksize;
+ cchunk.memblock = pa_memblock_new(u->source->core->mempool, cchunk.length);
+ cdata = pa_memblock_acquire(cchunk.memblock);
+
+ /* perform echo cancelation */
+ speex_echo_cancellation(u->echo_state, (const spx_int16_t *) rdata,
+ (const spx_int16_t *) pdata, (spx_int16_t *) cdata);
+
+ if (u->save_aec) {
+ if (u->captured_file)
+ fwrite(rdata, 1, u->blocksize, u->captured_file);
+ if (u->played_file)
+ fwrite(pdata, 1, u->blocksize, u->played_file);
+ if (u->canceled_file)
+ fwrite(cdata, 1, u->blocksize, u->canceled_file);
+ pa_log_debug("AEC frame saved.");
+ }
+
+ pa_memblock_release(cchunk.memblock);
+ pa_memblock_release(pchunk.memblock);
+ pa_memblock_release(rchunk.memblock);
+
+ /* drop consumed sink samples */
+ pa_memblockq_drop(u->sink_memblockq, u->blocksize);
+ pa_memblock_unref(pchunk.memblock);
+
+ pa_memblock_unref(rchunk.memblock);
+ /* the filtered samples now become the samples from our
+ * source */
+ rchunk = cchunk;
+
+ plen -= u->blocksize;
+ } else {
+ /* not enough played samples to perform echo cancelation,
+ * drop what we have */
+ pa_memblockq_drop(u->sink_memblockq, u->blocksize - plen);
+ plen = 0;
+ }
+
+ /* forward the (echo-canceled) data to the virtual source */
+ pa_source_post(u->source, &rchunk);
+ pa_memblock_unref(rchunk.memblock);
+
+ pa_memblockq_drop(u->source_memblockq, u->blocksize);
+
+ rlen -= u->blocksize;
+ }
+}
+
+/* Called from I/O thread context */
+static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk) {
+ struct userdata *u;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert(chunk);
+ pa_assert_se(u = i->userdata);
+
+ if (u->sink->thread_info.rewind_requested)
+ pa_sink_process_rewind(u->sink, 0);
+
+ pa_sink_render_full(u->sink, nbytes, chunk);
+
+ if (i->thread_info.underrun_for > 0) {
+ pa_log_debug("Handling end of underrun.");
+ pa_atomic_store (&u->request_resync, 1);
+ }
+
+ /* let source thread handle the chunk. pass the sample count as well so that
+ * the source IO thread can update the right variables. */
+ pa_asyncmsgq_post(u->asyncmsgq, PA_MSGOBJECT(u->source_output), SOURCE_OUTPUT_MESSAGE_POST,
+ NULL, 0, chunk, NULL);
+ u->send_counter += chunk->length;
+
+ return 0;
+}
+
+/* Called from input thread context */
+static void source_output_process_rewind_cb(pa_source_output *o, size_t nbytes) {
+ struct userdata *u;
+
+ pa_source_output_assert_ref(o);
+ pa_source_output_assert_io_context(o);
+ pa_assert_se(u = o->userdata);
+
+ pa_source_process_rewind(u->source, nbytes);
+
+ /* go back on read side, we need to use older sink data for this */
+ pa_memblockq_rewind(u->sink_memblockq, nbytes);
+
+ /* manipulate write index */
+ pa_memblockq_seek(u->source_memblockq, -nbytes, PA_SEEK_RELATIVE, TRUE);
+
+ pa_log_debug("Source rewind (%lld) %lld", (long long) nbytes,
+ (long long) pa_memblockq_get_length (u->source_memblockq));
+}
+
+/* Called from I/O thread context */
+static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) {
+ struct userdata *u;
+ size_t amount = 0;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(u = i->userdata);
+
+ pa_log_debug("Sink process rewind %lld", (long long) nbytes);
+
+ if (u->sink->thread_info.rewind_nbytes > 0) {
+ amount = PA_MIN(u->sink->thread_info.rewind_nbytes, nbytes);
+ u->sink->thread_info.rewind_nbytes = 0;
+ }
+
+ pa_sink_process_rewind(u->sink, amount);
+
+ pa_asyncmsgq_post(u->asyncmsgq, PA_MSGOBJECT(u->source_output), SOURCE_OUTPUT_MESSAGE_REWIND, NULL, (int64_t) nbytes, NULL, NULL);
+ u->send_counter -= nbytes;
+}
+
+static void source_output_snapshot_within_thread(struct userdata *u, struct snapshot *snapshot) {
+ size_t delay, rlen, plen;
+ pa_usec_t latency;
+
+ latency = pa_source_get_latency_within_thread(u->source_output->source);
+ delay = pa_memblockq_get_length(u->source_output->thread_info.delay_memblockq);
+
+ delay = (u->source_output->thread_info.resampler ? pa_resampler_request(u->source_output->thread_info.resampler, delay) : delay);
+ rlen = pa_memblockq_get_length(u->source_memblockq);
+ plen = pa_memblockq_get_length(u->sink_memblockq);
+
+ snapshot->source_latency = latency;
+ snapshot->source_delay = delay;
+ snapshot->recv_counter = u->recv_counter;
+ snapshot->rlen = rlen;
+ snapshot->plen = plen;
+}
+
+
+/* Called from output thread context */
+static int source_output_process_msg_cb(pa_msgobject *obj, int code, void *data, int64_t offset, pa_memchunk *chunk) {
+ struct userdata *u = PA_SOURCE_OUTPUT(obj)->userdata;
+
+ switch (code) {
+
+ case SOURCE_OUTPUT_MESSAGE_POST:
+
+ pa_source_output_assert_io_context(u->source_output);
+
+ if (PA_SOURCE_IS_OPENED(u->source_output->source->thread_info.state))
+ pa_memblockq_push_align(u->sink_memblockq, chunk);
+ else
+ pa_memblockq_flush_write(u->sink_memblockq, TRUE);
+
+ u->recv_counter += (int64_t) chunk->length;
+
+ return 0;
+
+ case SOURCE_OUTPUT_MESSAGE_REWIND:
+ pa_source_output_assert_io_context(u->source_output);
+
+ /* manipulate write index, never go past what we have */
+ if (PA_SOURCE_IS_OPENED(u->source_output->source->thread_info.state))
+ pa_memblockq_seek(u->sink_memblockq, -offset, PA_SEEK_RELATIVE, TRUE);
+ else
+ pa_memblockq_flush_write(u->sink_memblockq, TRUE);
+
+ pa_log_debug("Sink rewind (%lld)", (long long) offset);
+
+ u->recv_counter -= offset;
+
+ return 0;
+
+ case SOURCE_OUTPUT_MESSAGE_LATENCY_SNAPSHOT: {
+ struct snapshot *snapshot = (struct snapshot *) data;
+
+ source_output_snapshot_within_thread(u, snapshot);
+ return 0;
+ }
+
+ case SOURCE_OUTPUT_MESSAGE_APPLY_DIFF_TIME:
+ apply_diff_time(u, offset);
+ return 0;
+
+ }
+
+ return pa_source_output_process_msg(obj, code, data, offset, chunk);
+}
+
+static int sink_input_process_msg_cb(pa_msgobject *obj, int code, void *data, int64_t offset, pa_memchunk *chunk) {
+ struct userdata *u = PA_SINK_INPUT(obj)->userdata;
+
+ switch (code) {
+
+ case SINK_INPUT_MESSAGE_LATENCY_SNAPSHOT: {
+ size_t delay;
+ pa_usec_t latency;
+ struct snapshot *snapshot = (struct snapshot *) data;
+
+ pa_sink_input_assert_io_context(u->sink_input);
+
+ latency = pa_sink_get_latency_within_thread(u->sink_input->sink);
+ delay = pa_memblockq_get_length(u->sink_input->thread_info.render_memblockq);
+
+ delay = (u->sink_input->thread_info.resampler ? pa_resampler_request(u->sink_input->thread_info.resampler, delay) : delay);
+
+ snapshot->sink_latency = latency;
+ snapshot->sink_delay = delay;
+ snapshot->send_counter = u->send_counter;
+ return 0;
+ }
+ }
+
+ return pa_sink_input_process_msg(obj, code, data, offset, chunk);
+}
+
+/* Called from I/O thread context */
+static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) {
+ struct userdata *u;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(u = i->userdata);
+
+ pa_log_debug("Sink input update max rewind %lld", (long long) nbytes);
+
+ pa_sink_set_max_rewind_within_thread(u->sink, nbytes);
+}
+
+/* Called from I/O thread context */
+static void source_output_update_max_rewind_cb(pa_source_output *o, size_t nbytes) {
+ struct userdata *u;
+
+ pa_source_output_assert_ref(o);
+ pa_assert_se(u = o->userdata);
+
+ pa_log_debug("Source output update max rewind %lld", (long long) nbytes);
+
+ pa_source_set_max_rewind_within_thread(u->source, nbytes);
+}
+
+/* Called from I/O thread context */
+static void sink_input_update_max_request_cb(pa_sink_input *i, size_t nbytes) {
+ struct userdata *u;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(u = i->userdata);
+
+ pa_log_debug("Sink input update max rewind %lld", (long long) nbytes);
+
+ pa_sink_set_max_request_within_thread(u->sink, nbytes);
+}
+
+/* Called from I/O thread context */
+static void sink_input_update_sink_requested_latency_cb(pa_sink_input *i) {
+ struct userdata *u;
+ pa_usec_t latency;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(u = i->userdata);
+
+ latency = pa_sink_get_requested_latency_within_thread(i->sink);
+
+ pa_log_debug("Sink input update requested latency %lld", (long long) latency);
+ pa_atomic_store (&u->request_resync, 1);
+}
+
+/* Called from I/O thread context */
+static void source_output_update_source_requested_latency_cb(pa_source_output *o) {
+ struct userdata *u;
+ pa_usec_t latency;
+
+ pa_source_output_assert_ref(o);
+ pa_assert_se(u = o->userdata);
+
+ latency = pa_source_get_requested_latency_within_thread(o->source);
+
+ pa_log_debug("source output update requested latency %lld", (long long) latency);
+ pa_atomic_store (&u->request_resync, 1);
+}
+
+/* Called from I/O thread context */
+static void sink_input_update_sink_latency_range_cb(pa_sink_input *i) {
+ struct userdata *u;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(u = i->userdata);
+
+ pa_log_debug("Sink input update latency range %lld %lld",
+ (long long) i->sink->thread_info.min_latency,
+ (long long) i->sink->thread_info.max_latency);
+
+ pa_sink_set_latency_range_within_thread(u->sink, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency);
+}
+
+/* Called from I/O thread context */
+static void source_output_update_source_latency_range_cb(pa_source_output *o) {
+ struct userdata *u;
+
+ pa_source_output_assert_ref(o);
+ pa_assert_se(u = o->userdata);
+
+ pa_log_debug("Source output update latency range %lld %lld",
+ (long long) o->source->thread_info.min_latency,
+ (long long) o->source->thread_info.max_latency);
+
+ pa_source_set_latency_range_within_thread(u->source, o->source->thread_info.min_latency, o->source->thread_info.max_latency);
+}
+
+/* Called from I/O thread context */
+static void sink_input_update_sink_fixed_latency_cb(pa_sink_input *i) {
+ struct userdata *u;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(u = i->userdata);
+
+ pa_log_debug("Sink input update fixed latency %lld",
+ (long long) i->sink->thread_info.fixed_latency);
+
+ pa_sink_set_fixed_latency_within_thread(u->sink, i->sink->thread_info.fixed_latency);
+}
+
+/* Called from I/O thread context */
+static void source_output_update_source_fixed_latency_cb(pa_source_output *o) {
+ struct userdata *u;
+
+ pa_source_output_assert_ref(o);
+ pa_assert_se(u = o->userdata);
+
+ pa_log_debug("Source output update fixed latency %lld",
+ (long long) o->source->thread_info.fixed_latency);
+
+ pa_source_set_fixed_latency_within_thread(u->source, o->source->thread_info.fixed_latency);
+}
+
+/* Called from output thread context */
+static void source_output_attach_cb(pa_source_output *o) {
+ struct userdata *u;
+
+ pa_source_output_assert_ref(o);
+ pa_source_output_assert_io_context(o);
+ pa_assert_se(u = o->userdata);
+
+ pa_source_set_rtpoll(u->source, o->source->thread_info.rtpoll);
+ pa_source_set_latency_range_within_thread(u->source, o->source->thread_info.min_latency, o->source->thread_info.max_latency);
+ pa_source_set_fixed_latency_within_thread(u->source, o->source->thread_info.fixed_latency);
+ pa_source_set_max_rewind_within_thread(u->source, pa_source_output_get_max_rewind(o));
+
+ pa_log_debug("Source output %p attach", o);
+
+ pa_source_attach_within_thread(u->source);
+
+ u->rtpoll_item_read = pa_rtpoll_item_new_asyncmsgq_read(
+ o->source->thread_info.rtpoll,
+ PA_RTPOLL_LATE,
+ u->asyncmsgq);
+}
+
+/* Called from I/O thread context */
+static void sink_input_attach_cb(pa_sink_input *i) {
+ struct userdata *u;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(u = i->userdata);
+
+ pa_sink_set_rtpoll(u->sink, i->sink->thread_info.rtpoll);
+ pa_sink_set_latency_range_within_thread(u->sink, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency);
+
+ /* (8.1) IF YOU NEED A FIXED BLOCK SIZE ADD THE LATENCY FOR ONE
+ * BLOCK MINUS ONE SAMPLE HERE. SEE (7) */
+ pa_sink_set_fixed_latency_within_thread(u->sink, i->sink->thread_info.fixed_latency);
+
+ /* (8.2) IF YOU NEED A FIXED BLOCK SIZE ROUND
+ * pa_sink_input_get_max_request(i) UP TO MULTIPLES OF IT
+ * HERE. SEE (6) */
+ pa_sink_set_max_request_within_thread(u->sink, pa_sink_input_get_max_request(i));
+ pa_sink_set_max_rewind_within_thread(u->sink, pa_sink_input_get_max_rewind(i));
+
+ pa_log_debug("Sink input %p attach", i);
+
+ u->rtpoll_item_write = pa_rtpoll_item_new_asyncmsgq_write(
+ i->sink->thread_info.rtpoll,
+ PA_RTPOLL_LATE,
+ u->asyncmsgq);
+
+ pa_sink_attach_within_thread(u->sink);
+}
+
+
+/* Called from output thread context */
+static void source_output_detach_cb(pa_source_output *o) {
+ struct userdata *u;
+
+ pa_source_output_assert_ref(o);
+ pa_source_output_assert_io_context(o);
+ pa_assert_se(u = o->userdata);
+
+ pa_source_detach_within_thread(u->source);
+ pa_source_set_rtpoll(u->source, NULL);
+
+ pa_log_debug("Source output %p detach", o);
+
+ if (u->rtpoll_item_read) {
+ pa_rtpoll_item_free(u->rtpoll_item_read);
+ u->rtpoll_item_read = NULL;
+ }
+}
+
+/* Called from I/O thread context */
+static void sink_input_detach_cb(pa_sink_input *i) {
+ struct userdata *u;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(u = i->userdata);
+
+ pa_sink_detach_within_thread(u->sink);
+
+ pa_sink_set_rtpoll(u->sink, NULL);
+
+ pa_log_debug("Sink input %p detach", i);
+
+ if (u->rtpoll_item_write) {
+ pa_rtpoll_item_free(u->rtpoll_item_write);
+ u->rtpoll_item_write = NULL;
+ }
+}
+
+/* Called from output thread context */
+static void source_output_state_change_cb(pa_source_output *o, pa_source_output_state_t state) {
+ struct userdata *u;
+
+ pa_source_output_assert_ref(o);
+ pa_source_output_assert_io_context(o);
+ pa_assert_se(u = o->userdata);
+
+ pa_log_debug("Source output %p state %d", o, state);
+}
+
+/* Called from IO thread context */
+static void sink_input_state_change_cb(pa_sink_input *i, pa_sink_input_state_t state) {
+ struct userdata *u;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(u = i->userdata);
+
+ pa_log_debug("Sink input %p state %d", i, state);
+
+ /* If we are added for the first time, ask for a rewinding so that
+ * we are heard right-away. */
+ if (PA_SINK_INPUT_IS_LINKED(state) &&
+ i->thread_info.state == PA_SINK_INPUT_INIT) {
+ pa_log_debug("Requesting rewind due to state change.");
+ pa_sink_input_request_rewind(i, 0, FALSE, TRUE, TRUE);
+ }
+}
+
+/* Called from main thread */
+static void source_output_kill_cb(pa_source_output *o) {
+ struct userdata *u;
+
+ pa_source_output_assert_ref(o);
+ pa_assert_ctl_context();
+ pa_assert_se(u = o->userdata);
+
+ /* The order here matters! We first kill the source output, followed
+ * by the source. That means the source callbacks must be protected
+ * against an unconnected source output! */
+ pa_source_output_unlink(u->source_output);
+ pa_source_unlink(u->source);
+
+ pa_source_output_unref(u->source_output);
+ u->source_output = NULL;
+
+ pa_source_unref(u->source);
+ u->source = NULL;
+
+ pa_log_debug("Source output kill %p", o);
+
+ pa_module_unload_request(u->module, TRUE);
+}
+
+/* Called from main context */
+static void sink_input_kill_cb(pa_sink_input *i) {
+ struct userdata *u;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(u = i->userdata);
+
+ /* The order here matters! We first kill the sink input, followed
+ * by the sink. That means the sink callbacks must be protected
+ * against an unconnected sink input! */
+ pa_sink_input_unlink(u->sink_input);
+ pa_sink_unlink(u->sink);
+
+ pa_sink_input_unref(u->sink_input);
+ u->sink_input = NULL;
+
+ pa_sink_unref(u->sink);
+ u->sink = NULL;
+
+ pa_log_debug("Sink input kill %p", i);
+
+ pa_module_unload_request(u->module, TRUE);
+}
+
+/* Called from main thread */
+static pa_bool_t source_output_may_move_to_cb(pa_source_output *o, pa_source *dest) {
+ struct userdata *u;
+
+ pa_source_output_assert_ref(o);
+ pa_assert_ctl_context();
+ pa_assert_se(u = o->userdata);
+
+ return TRUE;
+}
+
+/* Called from main context */
+static pa_bool_t sink_input_may_move_to_cb(pa_sink_input *i, pa_sink *dest) {
+ struct userdata *u;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(u = i->userdata);
+
+ return u->sink != dest;
+}
+
+/* Called from main thread */
+static void source_output_moving_cb(pa_source_output *o, pa_source *dest) {
+ struct userdata *u;
+
+ pa_source_output_assert_ref(o);
+ pa_assert_ctl_context();
+ pa_assert_se(u = o->userdata);
+
+ if (dest) {
+ pa_source_set_asyncmsgq(u->source, dest->asyncmsgq);
+ pa_source_update_flags(u->source, PA_SOURCE_LATENCY|PA_SOURCE_DYNAMIC_LATENCY, dest->flags);
+ } else
+ pa_source_set_asyncmsgq(u->source, NULL);
+
+ if (u->source_auto_desc && dest) {
+ const char *z;
+ pa_proplist *pl;
+
+ pl = pa_proplist_new();
+ z = pa_proplist_gets(dest->proplist, PA_PROP_DEVICE_DESCRIPTION);
+ pa_proplist_setf(pl, PA_PROP_DEVICE_DESCRIPTION, "Echo-Cancel Source %s on %s",
+ pa_proplist_gets(u->source->proplist, "device.echo-cancel.name"), z ? z : dest->name);
+
+ pa_source_update_proplist(u->source, PA_UPDATE_REPLACE, pl);
+ pa_proplist_free(pl);
+ }
+}
+
+/* Called from main context */
+static void sink_input_moving_cb(pa_sink_input *i, pa_sink *dest) {
+ struct userdata *u;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(u = i->userdata);
+
+ if (dest) {
+ pa_sink_set_asyncmsgq(u->sink, dest->asyncmsgq);
+ pa_sink_update_flags(u->sink, PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY, dest->flags);
+ } else
+ pa_sink_set_asyncmsgq(u->sink, NULL);
+
+ if (u->sink_auto_desc && dest) {
+ const char *z;
+ pa_proplist *pl;
+
+ pl = pa_proplist_new();
+ z = pa_proplist_gets(dest->proplist, PA_PROP_DEVICE_DESCRIPTION);
+ pa_proplist_setf(pl, PA_PROP_DEVICE_DESCRIPTION, "Echo-Cancel Sink %s on %s",
+ pa_proplist_gets(u->sink->proplist, "device.echo-cancel.name"), z ? z : dest->name);
+
+ pa_sink_update_proplist(u->sink, PA_UPDATE_REPLACE, pl);
+ pa_proplist_free(pl);
+ }
+}
+
+/* Called from main context */
+static void sink_input_volume_changed_cb(pa_sink_input *i) {
+ struct userdata *u;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(u = i->userdata);
+
+ pa_sink_volume_changed(u->sink, &i->volume);
+}
+
+/* Called from main context */
+static void sink_input_mute_changed_cb(pa_sink_input *i) {
+ struct userdata *u;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(u = i->userdata);
+
+ pa_sink_mute_changed(u->sink, i->muted);
+}
+
+
+int pa__init(pa_module*m) {
+ struct userdata *u;
+ pa_sample_spec ss;
+ pa_channel_map map;
+ pa_modargs *ma;
+ pa_source *source_master=NULL;
+ pa_sink *sink_master=NULL;
+ pa_source_output_new_data source_output_data;
+ pa_sink_input_new_data sink_input_data;
+ pa_source_new_data source_data;
+ pa_sink_new_data sink_data;
+ pa_memchunk silence;
+ int framelen, rate;
+ uint32_t frame_size_ms, filter_size_ms;
+ uint32_t adjust_time_sec;
+
+ pa_assert(m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log("Failed to parse module arguments.");
+ goto fail;
+ }
+
+ if (!(source_master = pa_namereg_get(m->core, pa_modargs_get_value(ma, "source_master", NULL), PA_NAMEREG_SOURCE))) {
+ pa_log("Master source not found");
+ goto fail;
+ }
+ pa_assert(source_master);
+
+ if (!(sink_master = pa_namereg_get(m->core, pa_modargs_get_value(ma, "sink_master", NULL), PA_NAMEREG_SINK))) {
+ pa_log("Master sink not found");
+ goto fail;
+ }
+ pa_assert(sink_master);
+
+ frame_size_ms = DEFAULT_FRAME_SIZE_MS;
+ if (pa_modargs_get_value_u32(ma, "frame_size_ms", &frame_size_ms) < 0 || frame_size_ms < 1 || frame_size_ms > 200) {
+ pa_log("Invalid frame_size_ms specification");
+ goto fail;
+ }
+
+ filter_size_ms = DEFAULT_FILTER_SIZE_MS;
+ if (pa_modargs_get_value_u32(ma, "filter_size_ms", &filter_size_ms) < 0 || filter_size_ms < 1 || filter_size_ms > 2000) {
+ pa_log("Invalid filter_size_ms specification");
+ goto fail;
+ }
+
+ ss = source_master->sample_spec;
+ ss.format = PA_SAMPLE_S16LE;
+ map = source_master->channel_map;
+ if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0) {
+ pa_log("Invalid sample format specification or channel map");
+ goto fail;
+ }
+
+ u = pa_xnew0(struct userdata, 1);
+ if (!u) {
+ pa_log("Failed to alloc userdata");
+ goto fail;
+ }
+ u->core = m->core;
+ u->module = m;
+ m->userdata = u;
+ u->frame_size_ms = frame_size_ms;
+ rate = ss.rate;
+ framelen = (rate * frame_size_ms) / 1000;
+ u->blocksize = framelen * pa_frame_size (&ss);
+ pa_log_debug ("Using framelen %d, blocksize %lld, channels %d, rate %d", framelen, (long long) u->blocksize,
+ ss.channels, ss.rate);
+
+ 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");
+ goto fail;
+ }
+
+ if (adjust_time_sec != DEFAULT_ADJUST_TIME_USEC / PA_USEC_PER_SEC)
+ u->adjust_time = adjust_time_sec * PA_USEC_PER_SEC;
+ else
+ u->adjust_time = DEFAULT_ADJUST_TIME_USEC;
+
+ u->save_aec = DEFAULT_SAVE_AEC;
+ if (pa_modargs_get_value_u32(ma, "save_aec", &u->save_aec) < 0) {
+ pa_log("Failed to parse save_aec value");
+ goto fail;
+ }
+
+ u->asyncmsgq = pa_asyncmsgq_new(0);
+ u->need_realign = TRUE;
+ u->echo_state = speex_echo_state_init_mc (framelen, (rate * filter_size_ms) / 1000, ss.channels, ss.channels);
+ speex_echo_ctl(u->echo_state, SPEEX_ECHO_SET_SAMPLING_RATE, &rate);
+
+ /* Create source */
+ pa_source_new_data_init(&source_data);
+ source_data.driver = __FILE__;
+ source_data.module = m;
+ if (!(source_data.name = pa_xstrdup(pa_modargs_get_value(ma, "source_name", NULL))))
+ source_data.name = pa_sprintf_malloc("%s.echo-cancel", source_master->name);
+ pa_source_new_data_set_sample_spec(&source_data, &ss);
+ pa_source_new_data_set_channel_map(&source_data, &map);
+ pa_proplist_sets(source_data.proplist, PA_PROP_DEVICE_MASTER_DEVICE, source_master->name);
+ pa_proplist_sets(source_data.proplist, PA_PROP_DEVICE_CLASS, "filter");
+ pa_proplist_sets(source_data.proplist, "device.echo-cancel.name", source_data.name);
+
+ if (pa_modargs_get_proplist(ma, "source_properties", source_data.proplist, PA_UPDATE_REPLACE) < 0) {
+ pa_log("Invalid properties");
+ pa_source_new_data_done(&source_data);
+ goto fail;
+ }
+
+ if ((u->source_auto_desc = !pa_proplist_contains(source_data.proplist, PA_PROP_DEVICE_DESCRIPTION))) {
+ const char *z;
+
+ z = pa_proplist_gets(source_master->proplist, PA_PROP_DEVICE_DESCRIPTION);
+ pa_proplist_setf(source_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Echo-Cancel Source %s on %s", source_data.name, z ? z : source_master->name);
+ }
+
+ u->source = pa_source_new(m->core, &source_data,
+ PA_SOURCE_HW_MUTE_CTRL|PA_SOURCE_HW_VOLUME_CTRL|PA_SOURCE_DECIBEL_VOLUME|
+ (source_master->flags & (PA_SOURCE_LATENCY|PA_SOURCE_DYNAMIC_LATENCY)));
+ pa_source_new_data_done(&source_data);
+
+ if (!u->source) {
+ pa_log("Failed to create source.");
+ goto fail;
+ }
+
+ u->source->parent.process_msg = source_process_msg_cb;
+ u->source->set_state = source_set_state_cb;
+ u->source->update_requested_latency = source_update_requested_latency_cb;
+ u->source->set_volume = source_set_volume_cb;
+ u->source->set_mute = source_set_mute_cb;
+ u->source->get_volume = source_get_volume_cb;
+ u->source->get_mute = source_get_mute_cb;
+ u->source->userdata = u;
+
+ pa_source_set_asyncmsgq(u->source, source_master->asyncmsgq);
+
+ /* Create sink */
+ pa_sink_new_data_init(&sink_data);
+ sink_data.driver = __FILE__;
+ sink_data.module = m;
+ if (!(sink_data.name = pa_xstrdup(pa_modargs_get_value(ma, "sink_name", NULL))))
+ sink_data.name = pa_sprintf_malloc("%s.echo-cancel", sink_master->name);
+ pa_sink_new_data_set_sample_spec(&sink_data, &ss);
+ pa_sink_new_data_set_channel_map(&sink_data, &map);
+ pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_MASTER_DEVICE, sink_master->name);
+ pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_CLASS, "filter");
+ pa_proplist_sets(sink_data.proplist, "device.echo-cancel.name", sink_data.name);
+
+ if (pa_modargs_get_proplist(ma, "sink_properties", sink_data.proplist, PA_UPDATE_REPLACE) < 0) {
+ pa_log("Invalid properties");
+ pa_sink_new_data_done(&sink_data);
+ goto fail;
+ }
+
+ if ((u->sink_auto_desc = !pa_proplist_contains(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION))) {
+ const char *z;
+
+ z = pa_proplist_gets(sink_master->proplist, PA_PROP_DEVICE_DESCRIPTION);
+ pa_proplist_setf(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Echo-Cancel Sink %s on %s", sink_data.name, z ? z : sink_master->name);
+ }
+
+ u->sink = pa_sink_new(m->core, &sink_data,
+ PA_SINK_HW_MUTE_CTRL|PA_SINK_HW_VOLUME_CTRL|PA_SINK_DECIBEL_VOLUME|
+ (sink_master->flags & (PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY)));
+ pa_sink_new_data_done(&sink_data);
+
+ if (!u->sink) {
+ pa_log("Failed to create sink.");
+ goto fail;
+ }
+
+ u->sink->parent.process_msg = sink_process_msg_cb;
+ u->sink->set_state = sink_set_state_cb;
+ u->sink->update_requested_latency = sink_update_requested_latency_cb;
+ u->sink->request_rewind = sink_request_rewind_cb;
+ u->sink->set_volume = sink_set_volume_cb;
+ u->sink->set_mute = sink_set_mute_cb;
+ u->sink->userdata = u;
+
+ pa_sink_set_asyncmsgq(u->sink, sink_master->asyncmsgq);
+
+ /* Create source output */
+ pa_source_output_new_data_init(&source_output_data);
+ source_output_data.driver = __FILE__;
+ source_output_data.module = m;
+ source_output_data.source = source_master;
+ /* FIXME
+ source_output_data.flags = PA_SOURCE_OUTPUT_DONT_INHIBIT_AUTO_SUSPEND; */
+
+ pa_proplist_sets(source_output_data.proplist, PA_PROP_MEDIA_NAME, "Echo-Cancel Source Stream");
+ pa_proplist_sets(source_output_data.proplist, PA_PROP_MEDIA_ROLE, "filter");
+ pa_source_output_new_data_set_sample_spec(&source_output_data, &ss);
+ pa_source_output_new_data_set_channel_map(&source_output_data, &map);
+
+ pa_source_output_new(&u->source_output, m->core, &source_output_data);
+ pa_source_output_new_data_done(&source_output_data);
+
+ if (!u->source_output)
+ goto fail;
+
+ u->source_output->parent.process_msg = source_output_process_msg_cb;
+ u->source_output->push = source_output_push_cb;
+ u->source_output->process_rewind = source_output_process_rewind_cb;
+ u->source_output->update_max_rewind = source_output_update_max_rewind_cb;
+ u->source_output->update_source_requested_latency = source_output_update_source_requested_latency_cb;
+ u->source_output->update_source_latency_range = source_output_update_source_latency_range_cb;
+ u->source_output->update_source_fixed_latency = source_output_update_source_fixed_latency_cb;
+ u->source_output->kill = source_output_kill_cb;
+ u->source_output->attach = source_output_attach_cb;
+ u->source_output->detach = source_output_detach_cb;
+ u->source_output->state_change = source_output_state_change_cb;
+ u->source_output->may_move_to = source_output_may_move_to_cb;
+ u->source_output->moving = source_output_moving_cb;
+ u->source_output->userdata = u;
+
+ /* Create sink input */
+ pa_sink_input_new_data_init(&sink_input_data);
+ sink_input_data.driver = __FILE__;
+ sink_input_data.module = m;
+ sink_input_data.sink = sink_master;
+ pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_NAME, "Echo-Cancel Sink Stream");
+ pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_ROLE, "filter");
+ pa_sink_input_new_data_set_sample_spec(&sink_input_data, &ss);
+ pa_sink_input_new_data_set_channel_map(&sink_input_data, &map);
+ sink_input_data.flags = PA_SINK_INPUT_VARIABLE_RATE;
+
+ pa_sink_input_new(&u->sink_input, m->core, &sink_input_data);
+ pa_sink_input_new_data_done(&sink_input_data);
+
+ if (!u->sink_input)
+ goto fail;
+
+ u->sink_input->parent.process_msg = sink_input_process_msg_cb;
+ u->sink_input->pop = sink_input_pop_cb;
+ u->sink_input->process_rewind = sink_input_process_rewind_cb;
+ u->sink_input->update_max_rewind = sink_input_update_max_rewind_cb;
+ u->sink_input->update_max_request = sink_input_update_max_request_cb;
+ u->sink_input->update_sink_requested_latency = sink_input_update_sink_requested_latency_cb;
+ u->sink_input->update_sink_latency_range = sink_input_update_sink_latency_range_cb;
+ u->sink_input->update_sink_fixed_latency = sink_input_update_sink_fixed_latency_cb;
+ u->sink_input->kill = sink_input_kill_cb;
+ u->sink_input->attach = sink_input_attach_cb;
+ u->sink_input->detach = sink_input_detach_cb;
+ u->sink_input->state_change = sink_input_state_change_cb;
+ u->sink_input->may_move_to = sink_input_may_move_to_cb;
+ u->sink_input->moving = sink_input_moving_cb;
+ u->sink_input->volume_changed = sink_input_volume_changed_cb;
+ u->sink_input->mute_changed = sink_input_mute_changed_cb;
+ u->sink_input->userdata = u;
+
+ pa_sink_input_get_silence(u->sink_input, &silence);
+
+ u->source_memblockq = pa_memblockq_new(0, MEMBLOCKQ_MAXLENGTH, 0,
+ pa_frame_size(&ss), 1, 1, 0, &silence);
+ u->sink_memblockq = pa_memblockq_new(0, MEMBLOCKQ_MAXLENGTH, 0,
+ pa_frame_size(&ss), 1, 1, 0, &silence);
+
+ pa_memblock_unref(silence.memblock);
+
+ if (!u->source_memblockq || !u->sink_memblockq) {
+ pa_log("Failed to create memblockq.");
+ goto fail;
+ }
+
+ if (u->adjust_time > 0)
+ u->time_event = pa_core_rttime_new(m->core, pa_rtclock_now() + u->adjust_time, time_callback, u);
+
+ if (u->save_aec) {
+ pa_log("Creating AEC files in /tmp");
+ u->captured_file = fopen("/tmp/aec_rec.sw", "wb");
+ if (u->captured_file == NULL)
+ perror ("fopen failed");
+ u->played_file = fopen("/tmp/aec_play.sw", "wb");
+ if (u->played_file == NULL)
+ perror ("fopen failed");
+ u->canceled_file = fopen("/tmp/aec_out.sw", "wb");
+ if (u->canceled_file == NULL)
+ perror ("fopen failed");
+ }
+
+ pa_sink_put(u->sink);
+ pa_source_put(u->source);
+
+ pa_sink_input_put(u->sink_input);
+ pa_source_output_put(u->source_output);
+
+ pa_modargs_free(ma);
+
+ return 0;
+
+ fail:
+ if (ma)
+ pa_modargs_free(ma);
+
+ pa__done(m);
+
+ return -1;
+}
+
+int pa__get_n_used(pa_module *m) {
+ struct userdata *u;
+
+ pa_assert(m);
+ pa_assert_se(u = m->userdata);
+
+ return pa_sink_linked_by(u->sink) + pa_source_linked_by(u->source);
+}
+
+void pa__done(pa_module*m) {
+ struct userdata *u;
+
+ pa_assert(m);
+
+ if (!(u = m->userdata))
+ return;
+
+ /* See comments in source_output_kill_cb() above regarding
+ * destruction order! */
+
+ if (u->source_output)
+ pa_source_output_unlink(u->source_output);
+ if (u->sink_input)
+ pa_sink_input_unlink(u->sink_input);
+
+ if (u->source)
+ pa_source_unlink(u->source);
+ if (u->sink)
+ pa_sink_unlink(u->sink);
+
+ if (u->source_output)
+ pa_source_output_unref(u->source_output);
+ if (u->sink_input)
+ pa_sink_input_unref(u->sink_input);
+
+ if (u->source)
+ pa_source_unref(u->source);
+ if (u->sink)
+ pa_sink_unref(u->sink);
+
+ if (u->time_event)
+ u->core->mainloop->time_free(u->time_event);
+
+ if (u->source_memblockq)
+ pa_memblockq_free(u->source_memblockq);
+ if (u->sink_memblockq)
+ pa_memblockq_free(u->sink_memblockq);
+
+ if (u->echo_state)
+ speex_echo_state_destroy (u->echo_state);
+
+ if (u->asyncmsgq)
+ pa_asyncmsgq_unref(u->asyncmsgq);
+
+ pa_xfree(u);
+}
commit 9fa71e75e51f449092cb5cecc4f4d65c1b19219a
Author: Wim Taymans <wim.taymans at collabora.co.uk>
Date: Mon Aug 23 17:47:03 2010 +0200
echo-cancel: take into account snapshot delay
Take into account the delay between taking the snapshot from the source and the
sink. Improves the quality of the timings.
diff --git a/src/modules/module-echo-cancel.c b/src/modules/module-echo-cancel.c
index 3d7a6ef..a03481f 100644
--- a/src/modules/module-echo-cancel.c
+++ b/src/modules/module-echo-cancel.c
@@ -120,10 +120,12 @@ PA_MODULE_USAGE(
*/
struct snapshot {
+ pa_usec_t sink_now;
pa_usec_t sink_latency;
size_t sink_delay;
int64_t send_counter;
+ pa_usec_t source_now;
pa_usec_t source_latency;
size_t source_delay;
int64_t recv_counter;
@@ -224,13 +226,15 @@ static int64_t calc_diff(struct userdata *u, struct snapshot *snapshot) {
buffer_latency = pa_bytes_to_usec(buffer, &u->source_output->sample_spec);
/* capture and playback samples are perfectly aligned when diff_time is 0 */
- diff_time = (snapshot->sink_latency - buffer_latency) + snapshot->source_latency;
+ diff_time = (snapshot->sink_now + snapshot->sink_latency - buffer_latency) -
+ (snapshot->source_now - snapshot->source_latency);
- pa_log_debug("diff %lld (%lld - %lld + %lld) %lld %lld %lld", (long long) diff_time,
+ pa_log_debug("diff %lld (%lld - %lld + %lld) %lld %lld %lld %lld", (long long) diff_time,
(long long) snapshot->sink_latency,
(long long) buffer_latency, (long long) snapshot->source_latency,
(long long) snapshot->source_delay, (long long) snapshot->sink_delay,
- (long long) (snapshot->send_counter - snapshot->recv_counter));
+ (long long) (snapshot->send_counter - snapshot->recv_counter),
+ (long long) (snapshot->sink_now - snapshot->source_now));
return diff_time;
}
@@ -771,8 +775,9 @@ static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) {
static void source_output_snapshot_within_thread(struct userdata *u, struct snapshot *snapshot) {
size_t delay, rlen, plen;
- pa_usec_t latency;
+ pa_usec_t now, latency;
+ now = pa_rtclock_now();
latency = pa_source_get_latency_within_thread(u->source_output->source);
delay = pa_memblockq_get_length(u->source_output->thread_info.delay_memblockq);
@@ -780,6 +785,7 @@ static void source_output_snapshot_within_thread(struct userdata *u, struct snap
rlen = pa_memblockq_get_length(u->source_memblockq);
plen = pa_memblockq_get_length(u->sink_memblockq);
+ snapshot->source_now = now;
snapshot->source_latency = latency;
snapshot->source_delay = delay;
snapshot->recv_counter = u->recv_counter;
@@ -845,16 +851,18 @@ static int sink_input_process_msg_cb(pa_msgobject *obj, int code, void *data, in
case SINK_INPUT_MESSAGE_LATENCY_SNAPSHOT: {
size_t delay;
- pa_usec_t latency;
+ pa_usec_t now, latency;
struct snapshot *snapshot = (struct snapshot *) data;
pa_sink_input_assert_io_context(u->sink_input);
+ now = pa_rtclock_now();
latency = pa_sink_get_latency_within_thread(u->sink_input->sink);
delay = pa_memblockq_get_length(u->sink_input->thread_info.render_memblockq);
delay = (u->sink_input->thread_info.resampler ? pa_resampler_request(u->sink_input->thread_info.resampler, delay) : delay);
+ snapshot->sink_now = now;
snapshot->sink_latency = latency;
snapshot->sink_delay = delay;
snapshot->send_counter = u->send_counter;
commit 7b972f5a4523d166df1f2d933502e08d9f16dd4d
Author: Wim Taymans <wim.taymans at collabora.co.uk>
Date: Tue Aug 24 11:22:20 2010 +0200
echo-cancel: use the phone media role
Tag the source and sink with the phone media roles so that they automatially
connect to phone streams such as Empathy when using the intended-rols module.
diff --git a/src/modules/module-echo-cancel.c b/src/modules/module-echo-cancel.c
index a03481f..7213e36 100644
--- a/src/modules/module-echo-cancel.c
+++ b/src/modules/module-echo-cancel.c
@@ -1360,6 +1360,7 @@ int pa__init(pa_module*m) {
pa_source_new_data_set_channel_map(&source_data, &map);
pa_proplist_sets(source_data.proplist, PA_PROP_DEVICE_MASTER_DEVICE, source_master->name);
pa_proplist_sets(source_data.proplist, PA_PROP_DEVICE_CLASS, "filter");
+ pa_proplist_sets(source_data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "phone");
pa_proplist_sets(source_data.proplist, "device.echo-cancel.name", source_data.name);
if (pa_modargs_get_proplist(ma, "source_properties", source_data.proplist, PA_UPDATE_REPLACE) < 0) {
@@ -1406,6 +1407,7 @@ int pa__init(pa_module*m) {
pa_sink_new_data_set_channel_map(&sink_data, &map);
pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_MASTER_DEVICE, sink_master->name);
pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_CLASS, "filter");
+ pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "phone");
pa_proplist_sets(sink_data.proplist, "device.echo-cancel.name", sink_data.name);
if (pa_modargs_get_proplist(ma, "sink_properties", sink_data.proplist, PA_UPDATE_REPLACE) < 0) {
commit de6cdf94878902e2339e0be55abb2a41dce519bf
Author: Wim Taymans <wim.taymans at collabora.co.uk>
Date: Tue Aug 24 11:46:18 2010 +0200
echo-cancel: tweak the resync code a little
Try to keep the drift between source and sink within 4ms now that we have more
accurate timings.
Don't force a resync on latency changes but let the drift code handle it.
diff --git a/src/modules/module-echo-cancel.c b/src/modules/module-echo-cancel.c
index 7213e36..0abee55 100644
--- a/src/modules/module-echo-cancel.c
+++ b/src/modules/module-echo-cancel.c
@@ -276,7 +276,7 @@ static void time_callback(pa_mainloop_api *a, pa_time_event *e, const struct tim
new_rate = base_rate;
}
else {
- if (diff_time > (u->frame_size_ms / 2) * 1000) {
+ if (diff_time > 4000) {
pa_log_info("playback too far ahead (%lld), realign", (long long) diff_time);
/* diff too big, quickly adjust */
pa_asyncmsgq_post(u->asyncmsgq, PA_MSGOBJECT(u->source_output), SOURCE_OUTPUT_MESSAGE_APPLY_DIFF_TIME,
@@ -627,9 +627,8 @@ static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk)
;
u->in_push = FALSE;
- if (pa_atomic_load (&u->request_resync) == 1) {
+ if (pa_atomic_cmpxchg (&u->request_resync, 1, 0)) {
do_resync (u);
- pa_atomic_store (&u->request_resync, 0);
}
pa_memblockq_push_align(u->source_memblockq, chunk);
@@ -920,7 +919,6 @@ static void sink_input_update_sink_requested_latency_cb(pa_sink_input *i) {
latency = pa_sink_get_requested_latency_within_thread(i->sink);
pa_log_debug("Sink input update requested latency %lld", (long long) latency);
- pa_atomic_store (&u->request_resync, 1);
}
/* Called from I/O thread context */
@@ -934,7 +932,6 @@ static void source_output_update_source_requested_latency_cb(pa_source_output *o
latency = pa_source_get_requested_latency_within_thread(o->source);
pa_log_debug("source output update requested latency %lld", (long long) latency);
- pa_atomic_store (&u->request_resync, 1);
}
/* Called from I/O thread context */
commit 1c2669452b325eb59ebfdfb955c187a2b7e8a7f0
Author: Wim Taymans <wim.taymans at collabora.co.uk>
Date: Tue Aug 24 13:29:18 2010 +0200
echo-cancel: improve debug
diff --git a/src/modules/module-echo-cancel.c b/src/modules/module-echo-cancel.c
index 0abee55..4d44bf7 100644
--- a/src/modules/module-echo-cancel.c
+++ b/src/modules/module-echo-cancel.c
@@ -267,17 +267,15 @@ static void time_callback(pa_mainloop_api *a, pa_time_event *e, const struct tim
base_rate = u->source_output->sample_spec.rate;
if (diff_time < 0) {
- pa_log_info("Playback after capture (%lld), realign", (long long) diff_time);
- pa_asyncmsgq_post(u->asyncmsgq, PA_MSGOBJECT(u->source_output), SOURCE_OUTPUT_MESSAGE_APPLY_DIFF_TIME,
- NULL, diff_time, NULL, NULL);
/* recording before playback, we need to adjust quickly. The echo
* canceler does not work in this case. */
+ pa_asyncmsgq_post(u->asyncmsgq, PA_MSGOBJECT(u->source_output), SOURCE_OUTPUT_MESSAGE_APPLY_DIFF_TIME,
+ NULL, diff_time, NULL, NULL);
//new_rate = base_rate - ((pa_usec_to_bytes (-diff_time, &u->source_output->sample_spec) / fs) * PA_USEC_PER_SEC) / u->adjust_time;
new_rate = base_rate;
}
else {
if (diff_time > 4000) {
- pa_log_info("playback too far ahead (%lld), realign", (long long) diff_time);
/* 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);
@@ -575,17 +573,21 @@ static void apply_diff_time(struct userdata *u, int64_t diff_time) {
if (diff_time < 0) {
diff = pa_usec_to_bytes (-diff_time, &u->source_output->sample_spec);
- pa_log_debug("drop sink (%lld)", (long long) diff);
+ if (diff > 0) {
+ pa_log_info("Playback after capture (%lld), drop sink %lld", (long long) diff_time, (long long) diff);
- /* go forwards on the read side */
- pa_memblockq_drop(u->sink_memblockq, diff);
- } else {
+ /* go forwards on the read side */
+ pa_memblockq_drop(u->sink_memblockq, diff);
+ }
+ } else if (diff_time > 0) {
diff = pa_usec_to_bytes (diff_time, &u->source_output->sample_spec);
- pa_log_debug("drop source (%lld)", (long long) diff);
+ if (diff > 0) {
+ pa_log_info("playback too far ahead (%lld), drop source %lld", (long long) diff_time, (long long) diff);
- /* go back on the read side */
- pa_memblockq_rewind(u->sink_memblockq, diff);
+ /* go back on the read side */
+ pa_memblockq_rewind(u->sink_memblockq, diff);
+ }
}
}
commit b30bf121d20dd2bacf4577f52f9bae4ff86bc2c8
Author: Wim Taymans <wim.taymans at collabora.co.uk>
Date: Tue Aug 31 18:04:33 2010 +0200
echo-cancel: keep frame_size a power of 2
The speex echo canceler prefers a power of 2 for the frame size. Round down the
ideal frame_size to the nearest power of two. This makes sure we don't create
more than the requested frame_size_ms latency while still providing a power of 2
to the speex echo canceller.
diff --git a/src/modules/module-echo-cancel.c b/src/modules/module-echo-cancel.c
index 4d44bf7..d6c2ca1 100644
--- a/src/modules/module-echo-cancel.c
+++ b/src/modules/module-echo-cancel.c
@@ -1269,7 +1269,7 @@ int pa__init(pa_module*m) {
pa_source_new_data source_data;
pa_sink_new_data sink_data;
pa_memchunk silence;
- int framelen, rate;
+ int framelen, rate, y;
uint32_t frame_size_ms, filter_size_ms;
uint32_t adjust_time_sec;
@@ -1323,6 +1323,13 @@ int pa__init(pa_module*m) {
u->frame_size_ms = frame_size_ms;
rate = ss.rate;
framelen = (rate * frame_size_ms) / 1000;
+
+ /* framelen should be a power of 2, round down to nearest power of two */
+ y = 1 << ((8 * sizeof (int)) - 2);
+ while (y > framelen)
+ y >>= 1;
+ framelen = y;
+
u->blocksize = framelen * pa_frame_size (&ss);
pa_log_debug ("Using framelen %d, blocksize %lld, channels %d, rate %d", framelen, (long long) u->blocksize,
ss.channels, ss.rate);
commit 6d8a90709e5d0a3dc5c11e4a7d54c2cdbb774f3a
Author: Arun Raghavan <arun.raghavan at collabora.co.uk>
Date: Mon Sep 6 13:39:25 2010 +0530
echo-cancel: Move the module into it's own directory
This will make splitting out the canceller parts cleaner.
diff --git a/src/Makefile.am b/src/Makefile.am
index 4345e02..49465cc 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1249,7 +1249,7 @@ SYMDEF_FILES = \
modules/module-rescue-streams-symdef.h \
modules/module-intended-roles-symdef.h \
modules/module-suspend-on-idle-symdef.h \
- modules/module-echo-cancel-symdef.h \
+ modules/echo-cancel/module-echo-cancel-symdef.h \
modules/module-hal-detect-symdef.h \
modules/module-udev-detect-symdef.h \
modules/bluetooth/module-bluetooth-proximity-symdef.h \
@@ -1605,7 +1605,7 @@ module_suspend_on_idle_la_LIBADD = $(AM_LIBADD) libpulsecore- at PA_MAJORMINORMICRO
module_suspend_on_idle_la_CFLAGS = $(AM_CFLAGS)
# echo-cancel module
-module_echo_cancel_la_SOURCES = modules/module-echo-cancel.c
+module_echo_cancel_la_SOURCES = modules/echo-cancel/module-echo-cancel.c
module_echo_cancel_la_LDFLAGS = $(MODULE_LDFLAGS)
module_echo_cancel_la_LIBADD = $(AM_LIBADD) libpulsecore- at PA_MAJORMINORMICRO@.la libpulsecommon- at PA_MAJORMINORMICRO@.la libpulse.la $(LIBSPEEX_LIBS)
module_echo_cancel_la_CFLAGS = $(AM_CFLAGS) $(LIBSPEEX_CFLAGS)
diff --git a/src/modules/echo-cancel/module-echo-cancel.c b/src/modules/echo-cancel/module-echo-cancel.c
new file mode 100644
index 0000000..d6c2ca1
--- /dev/null
+++ b/src/modules/echo-cancel/module-echo-cancel.c
@@ -0,0 +1,1625 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2010 Wim Taymans <wim.taymans at gmail.com>
+
+ Based on module-virtual-sink.c
+ module-virtual-source.c
+ module-loopback.c
+
+ Copyright 2010 Intel Corporation
+ Contributor: Pierre-Louis Bossart <pierre-louis.bossart at intel.com>
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+
+#include <speex/speex_echo.h>
+
+#include <pulse/xmalloc.h>
+#include <pulse/i18n.h>
+#include <pulse/timeval.h>
+#include <pulse/rtclock.h>
+
+#include <pulsecore/atomic.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/core-error.h>
+#include <pulsecore/namereg.h>
+#include <pulsecore/sink.h>
+#include <pulsecore/module.h>
+#include <pulsecore/core-rtclock.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/core-error.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/log.h>
+#include <pulsecore/thread.h>
+#include <pulsecore/thread-mq.h>
+#include <pulsecore/rtpoll.h>
+#include <pulsecore/sample-util.h>
+#include <pulsecore/ltdl-helper.h>
+
+#include "module-echo-cancel-symdef.h"
+
+PA_MODULE_AUTHOR("Wim Taymans");
+PA_MODULE_DESCRIPTION("Echo Cancelation");
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_LOAD_ONCE(FALSE);
+PA_MODULE_USAGE(
+ _("source_name=<name for the source> "
+ "source_properties=<properties for the source> "
+ "source_master=<name of source to filter> "
+ "sink_name=<name for the sink> "
+ "sink_properties=<properties for the sink> "
+ "sink_master=<name of sink to filter> "
+ "frame_size_ms=<amount of data to process at one time> "
+ "filter_size_ms=<amount of echo to cancel> "
+ "adjust_time=<how often to readjust rates in s> "
+ "format=<sample format> "
+ "rate=<sample rate> "
+ "channels=<number of channels> "
+ "channel_map=<channel map> "
+ "save_aec=<save AEC data in /tmp> "
+ ));
+
+/* should be between 10-20 ms */
+#define DEFAULT_FRAME_SIZE_MS 20
+/* should be between 100-500 ms */
+#define DEFAULT_FILTER_SIZE_MS 200
+
+#define DEFAULT_ADJUST_TIME_USEC (1*PA_USEC_PER_SEC)
+#define DEFAULT_SAVE_AEC 0
+
+#define MEMBLOCKQ_MAXLENGTH (16*1024*1024)
+
+/* This module creates a new (virtual) source and sink.
+ *
+ * The data sent to the new sink is kept in a memblockq before being
+ * forwarded to the real sink_master.
+ *
+ * Data read from source_master is matched against the saved sink data and
+ * echo canceled data is then pushed onto the new source.
+ *
+ * Both source and sink masters have their own threads to push/pull data
+ * respectively. We however perform all our actions in the source IO thread.
+ * To do this we send all played samples to the source IO thread where they
+ * are then pushed into the memblockq.
+ *
+ * Alignment is performed in two steps:
+ *
+ * 1) when something happens that requires quick adjustement of the alignment of
+ * capture and playback samples, we perform a resync. This adjusts the
+ * position in the playback memblock to the requested sample. Quick
+ * adjustements include moving the playback samples before the capture
+ * samples (because else the echo canceler does not work) or when the
+ * playback pointer drifts too far away.
+ *
+ * 2) periodically check the difference between capture and playback. we use a
+ * low and high watermark for adjusting the alignment. playback should always
+ * be before capture and the difference should not be bigger than one frame
+ * size. We would ideally like to resample the sink_input but most driver
+ * don't give enough accuracy to be able to do that right now.
+ */
+
+struct snapshot {
+ pa_usec_t sink_now;
+ pa_usec_t sink_latency;
+ size_t sink_delay;
+ int64_t send_counter;
+
+ pa_usec_t source_now;
+ pa_usec_t source_latency;
+ size_t source_delay;
+ int64_t recv_counter;
+ size_t rlen;
+ size_t plen;
+};
+
+struct userdata {
+ pa_core *core;
+ pa_module *module;
+
+ uint32_t frame_size_ms;
+ uint32_t save_aec;
+
+ SpeexEchoState *echo_state;
+
+ size_t blocksize;
+ 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;
+
+ pa_source *source;
+ pa_bool_t source_auto_desc;
+ pa_source_output *source_output;
+ pa_memblockq *source_memblockq; /* echo canceler needs fixed sized chunks */
+ pa_atomic_t source_active;
+
+ pa_sink *sink;
+ pa_bool_t sink_auto_desc;
+ pa_sink_input *sink_input;
+ pa_memblockq *sink_memblockq;
+ int64_t send_counter; /* updated in sink IO thread */
+ int64_t recv_counter;
+ pa_atomic_t sink_active;
+
+ pa_atomic_t request_resync;
+
+ pa_time_event *time_event;
+ pa_usec_t adjust_time;
+
+ FILE *captured_file;
+ FILE *played_file;
+ FILE *canceled_file;
+};
+
+static void source_output_snapshot_within_thread(struct userdata *u, struct snapshot *snapshot);
+
+static const char* const valid_modargs[] = {
+ "source_name",
+ "source_properties",
+ "source_master",
+ "sink_name",
+ "sink_properties",
+ "sink_master",
+ "frame_size_ms",
+ "filter_size_ms",
+ "adjust_time",
+ "format",
+ "rate",
+ "channels",
+ "channel_map",
+ "save_aec",
+ NULL
+};
+
+enum {
+ SOURCE_OUTPUT_MESSAGE_POST = PA_SOURCE_OUTPUT_MESSAGE_MAX,
+ SOURCE_OUTPUT_MESSAGE_REWIND,
+ SOURCE_OUTPUT_MESSAGE_LATENCY_SNAPSHOT,
+ SOURCE_OUTPUT_MESSAGE_APPLY_DIFF_TIME
+};
+
+enum {
+ SINK_INPUT_MESSAGE_LATENCY_SNAPSHOT
+};
+
+static int64_t calc_diff(struct userdata *u, struct snapshot *snapshot) {
+ int64_t buffer, diff_time, buffer_latency;
+
+ /* get the number of samples between capture and playback */
+ if (snapshot->plen > snapshot->rlen)
+ buffer = snapshot->plen - snapshot->rlen;
+ else
+ buffer = 0;
+
+ buffer += snapshot->source_delay + snapshot->sink_delay;
+
+ /* add the amount of samples not yet transfered to the source context */
+ if (snapshot->recv_counter <= snapshot->send_counter)
+ buffer += (int64_t) (snapshot->send_counter - snapshot->recv_counter);
+ else
+ buffer += PA_CLIP_SUB(buffer, (int64_t) (snapshot->recv_counter - snapshot->send_counter));
+
+ /* convert to time */
+ buffer_latency = pa_bytes_to_usec(buffer, &u->source_output->sample_spec);
+
+ /* capture and playback samples are perfectly aligned when diff_time is 0 */
+ diff_time = (snapshot->sink_now + snapshot->sink_latency - buffer_latency) -
+ (snapshot->source_now - snapshot->source_latency);
+
+ pa_log_debug("diff %lld (%lld - %lld + %lld) %lld %lld %lld %lld", (long long) diff_time,
+ (long long) snapshot->sink_latency,
+ (long long) buffer_latency, (long long) snapshot->source_latency,
+ (long long) snapshot->source_delay, (long long) snapshot->sink_delay,
+ (long long) (snapshot->send_counter - snapshot->recv_counter),
+ (long long) (snapshot->sink_now - snapshot->source_now));
+
+ return diff_time;
+}
+
+/* Called from main context */
+static void time_callback(pa_mainloop_api *a, pa_time_event *e, const struct timeval *t, void *userdata) {
+ struct userdata *u = userdata;
+ uint32_t old_rate, base_rate, new_rate;
+ int64_t diff_time;
+ size_t fs;
+ struct snapshot latency_snapshot;
+
+ pa_assert(u);
+ pa_assert(a);
+ pa_assert(u->time_event == e);
+ pa_assert_ctl_context();
+
+ if (pa_atomic_load (&u->sink_active) == 0 || pa_atomic_load (&u->source_active) == 0)
+ goto done;
+
+ /* update our snapshots */
+ pa_asyncmsgq_send(u->source_output->source->asyncmsgq, PA_MSGOBJECT(u->source_output), SOURCE_OUTPUT_MESSAGE_LATENCY_SNAPSHOT, &latency_snapshot, 0, NULL);
+ pa_asyncmsgq_send(u->sink_input->sink->asyncmsgq, PA_MSGOBJECT(u->sink_input), SINK_INPUT_MESSAGE_LATENCY_SNAPSHOT, &latency_snapshot, 0, NULL);
+
+ /* calculate drift between capture and playback */
+ diff_time = calc_diff(u, &latency_snapshot);
+
+ fs = pa_frame_size(&u->source_output->sample_spec);
+ old_rate = u->sink_input->sample_spec.rate;
+ base_rate = u->source_output->sample_spec.rate;
+
+ if (diff_time < 0) {
+ /* recording before playback, we need to adjust quickly. The echo
+ * canceler does not work in this case. */
+ pa_asyncmsgq_post(u->asyncmsgq, PA_MSGOBJECT(u->source_output), SOURCE_OUTPUT_MESSAGE_APPLY_DIFF_TIME,
+ NULL, diff_time, NULL, NULL);
+ //new_rate = base_rate - ((pa_usec_to_bytes (-diff_time, &u->source_output->sample_spec) / fs) * PA_USEC_PER_SEC) / u->adjust_time;
+ new_rate = base_rate;
+ }
+ else {
+ if (diff_time > 4000) {
+ /* 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);
+ }
+
+ /* recording behind playback, we need to slowly adjust the rate to match */
+ //new_rate = base_rate + ((pa_usec_to_bytes (diff_time, &u->source_output->sample_spec) / fs) * PA_USEC_PER_SEC) / u->adjust_time;
+
+ /* assume equal samplerates for now */
+ new_rate = base_rate;
+ }
+
+ /* make sure we don't make too big adjustements because that sounds horrible */
+ if (new_rate > base_rate * 1.1 || new_rate < base_rate * 0.9)
+ new_rate = base_rate;
+
+ if (new_rate != old_rate) {
+ pa_log_info("Old rate %lu Hz, new rate %lu Hz", (unsigned long) old_rate, (unsigned long) new_rate);
+
+ pa_sink_input_set_rate(u->sink_input, new_rate);
+ }
+
+done:
+ pa_core_rttime_restart(u->core, u->time_event, pa_rtclock_now() + u->adjust_time);
+}
+
+/* Called from source I/O thread context */
+static int source_process_msg_cb(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
+ struct userdata *u = PA_SOURCE(o)->userdata;
+
+ switch (code) {
+
+ case PA_SOURCE_MESSAGE_GET_LATENCY:
+
+ /* The source is _put() before the source output is, so let's
+ * make sure we don't access it in that time. Also, the
+ * source output is first shut down, the source second. */
+ if (!PA_SOURCE_IS_LINKED(u->source->thread_info.state) ||
+ !PA_SOURCE_OUTPUT_IS_LINKED(u->source_output->thread_info.state)) {
+ *((pa_usec_t*) data) = 0;
+ return 0;
+ }
+
+ *((pa_usec_t*) data) =
+
+ /* Get the latency of the master source */
+ pa_source_get_latency_within_thread(u->source_output->source) +
+ /* Add the latency internal to our source output on top */
+ pa_bytes_to_usec(pa_memblockq_get_length(u->source_output->thread_info.delay_memblockq), &u->source_output->source->sample_spec) +
+ /* and the buffering we do on the source */
+ pa_bytes_to_usec(u->blocksize, &u->source_output->source->sample_spec);
+
+ return 0;
+
+ }
+
+ return pa_source_process_msg(o, code, data, offset, chunk);
+}
+
+/* Called from sink I/O thread context */
+static int sink_process_msg_cb(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
+ struct userdata *u = PA_SINK(o)->userdata;
+
+ switch (code) {
+
+ case PA_SINK_MESSAGE_GET_LATENCY:
+
+ /* The sink is _put() before the sink input is, so let's
+ * make sure we don't access it in that time. Also, the
+ * sink input is first shut down, the sink second. */
+ if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) ||
+ !PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state)) {
+ *((pa_usec_t*) data) = 0;
+ return 0;
+ }
+
+ *((pa_usec_t*) data) =
+
+ /* Get the latency of the master sink */
+ pa_sink_get_latency_within_thread(u->sink_input->sink) +
+
+ /* Add the latency internal to our sink input on top */
+ pa_bytes_to_usec(pa_memblockq_get_length(u->sink_input->thread_info.render_memblockq), &u->sink_input->sink->sample_spec);
+
+ return 0;
+ }
+
+ return pa_sink_process_msg(o, code, data, offset, chunk);
+}
+
+
+/* Called from main context */
+static int source_set_state_cb(pa_source *s, pa_source_state_t state) {
+ struct userdata *u;
+
+ pa_source_assert_ref(s);
+ pa_assert_se(u = s->userdata);
+
+ if (!PA_SOURCE_IS_LINKED(state) ||
+ !PA_SOURCE_OUTPUT_IS_LINKED(pa_source_output_get_state(u->source_output)))
+ return 0;
+
+ pa_log_debug("Source state %d", state);
+
+ if (state == PA_SOURCE_RUNNING) {
+ pa_atomic_store (&u->source_active, 1);
+ pa_atomic_store (&u->request_resync, 1);
+ pa_source_output_cork(u->source_output, FALSE);
+ } else if (state == PA_SOURCE_SUSPENDED) {
+ pa_atomic_store (&u->source_active, 0);
+ pa_source_output_cork(u->source_output, TRUE);
+ }
+ return 0;
+}
+
+/* Called from main context */
+static int sink_set_state_cb(pa_sink *s, pa_sink_state_t state) {
+ struct userdata *u;
+
+ pa_sink_assert_ref(s);
+ pa_assert_se(u = s->userdata);
+
+ if (!PA_SINK_IS_LINKED(state) ||
+ !PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(u->sink_input)))
+ return 0;
+
+ pa_log_debug("Sink state %d", state);
+
+ if (state == PA_SINK_RUNNING) {
+ pa_atomic_store (&u->sink_active, 1);
+ pa_atomic_store (&u->request_resync, 1);
+ pa_sink_input_cork(u->sink_input, FALSE);
+ } else if (state == PA_SINK_SUSPENDED) {
+ pa_atomic_store (&u->sink_active, 0);
+ pa_sink_input_cork(u->sink_input, TRUE);
+ }
+ return 0;
+}
+
+/* Called from I/O thread context */
+static void source_update_requested_latency_cb(pa_source *s) {
+ struct userdata *u;
+
+ pa_source_assert_ref(s);
+ pa_assert_se(u = s->userdata);
+
+ if (!PA_SOURCE_IS_LINKED(u->source->thread_info.state) ||
+ !PA_SOURCE_OUTPUT_IS_LINKED(u->source_output->thread_info.state))
+ return;
+
+ pa_log_debug("Source update requested latency");
+
+ /* Just hand this one over to the master source */
+ pa_source_output_set_requested_latency_within_thread(
+ u->source_output,
+ pa_source_get_requested_latency_within_thread(s));
+}
+
+/* Called from I/O thread context */
+static void sink_update_requested_latency_cb(pa_sink *s) {
+ struct userdata *u;
+
+ pa_sink_assert_ref(s);
+ pa_assert_se(u = s->userdata);
+
+ if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) ||
+ !PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state))
+ return;
+
+ pa_log_debug("Sink update requested latency");
+
+ /* Just hand this one over to the master sink */
+ pa_sink_input_set_requested_latency_within_thread(
+ u->sink_input,
+ pa_sink_get_requested_latency_within_thread(s));
+}
+
+/* Called from I/O thread context */
+static void sink_request_rewind_cb(pa_sink *s) {
+ struct userdata *u;
+
+ pa_sink_assert_ref(s);
+ pa_assert_se(u = s->userdata);
+
+ if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) ||
+ !PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state))
+ return;
+
+ pa_log_debug("Sink request rewind %lld", (long long) s->thread_info.rewind_nbytes);
+
+ /* Just hand this one over to the master sink */
+ pa_sink_input_request_rewind(u->sink_input,
+ s->thread_info.rewind_nbytes, TRUE, FALSE, FALSE);
+}
+
+/* Called from main context */
+static void source_set_volume_cb(pa_source *s) {
+ struct userdata *u;
+
+ pa_source_assert_ref(s);
+ pa_assert_se(u = s->userdata);
+
+ if (!PA_SOURCE_IS_LINKED(pa_source_get_state(s)) ||
+ !PA_SOURCE_OUTPUT_IS_LINKED(pa_source_output_get_state(u->source_output)))
+ return;
+
+ /* FIXME, no volume control in source_output, set volume at the master */
+ pa_source_set_volume(u->source_output->source, &s->volume, TRUE);
+}
+
+/* Called from main context */
+static void sink_set_volume_cb(pa_sink *s) {
+ struct userdata *u;
+
+ pa_sink_assert_ref(s);
+ pa_assert_se(u = s->userdata);
+
+ if (!PA_SINK_IS_LINKED(pa_sink_get_state(s)) ||
+ !PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(u->sink_input)))
+ return;
+
+ pa_sink_input_set_volume(u->sink_input, &s->real_volume, s->save_volume, TRUE);
+}
+
+static void source_get_volume_cb(pa_source *s) {
+ struct userdata *u;
+
+ pa_source_assert_ref(s);
+ pa_assert_se(u = s->userdata);
+
+ if (!PA_SOURCE_IS_LINKED(pa_source_get_state(s)) ||
+ !PA_SOURCE_OUTPUT_IS_LINKED(pa_source_output_get_state(u->source_output)))
+ return;
+
+ /* FIXME, no volume control in source_output, get the info from the master */
+ pa_source_get_volume(u->source_output->source, TRUE);
+
+ if (pa_cvolume_equal(&s->volume,&u->source_output->source->volume))
+ /* no change */
+ return;
+
+ s->volume = u->source_output->source->volume;
+ pa_source_set_soft_volume(s, NULL);
+}
+
+
+/* Called from main context */
+static void source_set_mute_cb(pa_source *s) {
+ struct userdata *u;
+
+ pa_source_assert_ref(s);
+ pa_assert_se(u = s->userdata);
+
+ if (!PA_SOURCE_IS_LINKED(pa_source_get_state(s)) ||
+ !PA_SOURCE_OUTPUT_IS_LINKED(pa_source_output_get_state(u->source_output)))
+ return;
+
+ /* FIXME, no volume control in source_output, set mute at the master */
+ pa_source_set_mute(u->source_output->source, TRUE, TRUE);
+}
+
+/* Called from main context */
+static void sink_set_mute_cb(pa_sink *s) {
+ struct userdata *u;
+
+ pa_sink_assert_ref(s);
+ pa_assert_se(u = s->userdata);
+
+ if (!PA_SINK_IS_LINKED(pa_sink_get_state(s)) ||
+ !PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(u->sink_input)))
+ return;
+
+ pa_sink_input_set_mute(u->sink_input, s->muted, s->save_muted);
+}
+
+/* Called from main context */
+static void source_get_mute_cb(pa_source *s) {
+ struct userdata *u;
+
+ pa_source_assert_ref(s);
+ pa_assert_se(u = s->userdata);
+
+ if (!PA_SOURCE_IS_LINKED(pa_source_get_state(s)) ||
+ !PA_SOURCE_OUTPUT_IS_LINKED(pa_source_output_get_state(u->source_output)))
+ return;
+
+ /* FIXME, no volume control in source_output, get the info from the master */
+ pa_source_get_mute(u->source_output->source, TRUE);
+}
+
+/* must be called from the input thread context */
+static void apply_diff_time(struct userdata *u, int64_t diff_time) {
+ int64_t diff;
+
+ if (diff_time < 0) {
+ diff = pa_usec_to_bytes (-diff_time, &u->source_output->sample_spec);
+
+ if (diff > 0) {
+ pa_log_info("Playback after capture (%lld), drop sink %lld", (long long) diff_time, (long long) diff);
+
+ /* go forwards on the read side */
+ pa_memblockq_drop(u->sink_memblockq, diff);
+ }
+ } else if (diff_time > 0) {
+ diff = pa_usec_to_bytes (diff_time, &u->source_output->sample_spec);
+
+ if (diff > 0) {
+ pa_log_info("playback too far ahead (%lld), drop source %lld", (long long) diff_time, (long long) diff);
+
+ /* go back on the read side */
+ pa_memblockq_rewind(u->sink_memblockq, diff);
+ }
+ }
+}
+
+/* must be called from the input thread */
+static void do_resync(struct userdata *u) {
+ int64_t diff_time;
+ struct snapshot latency_snapshot;
+
+ pa_log("Doing resync");
+
+ /* update our snapshot */
+ source_output_snapshot_within_thread(u, &latency_snapshot);
+ pa_asyncmsgq_send(u->sink_input->sink->asyncmsgq, PA_MSGOBJECT(u->sink_input), SINK_INPUT_MESSAGE_LATENCY_SNAPSHOT, &latency_snapshot, 0, NULL);
+
+ /* calculate drift between capture and playback */
+ diff_time = calc_diff(u, &latency_snapshot);
+
+ /* and adjust for the drift */
+ apply_diff_time(u, diff_time);
+}
+
+/* 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;
+
+ pa_source_output_assert_ref(o);
+ pa_source_output_assert_io_context(o);
+ pa_assert_se(u = o->userdata);
+
+ if (!PA_SOURCE_OUTPUT_IS_LINKED(pa_source_output_get_state(u->source_output))) {
+ pa_log("push when no link?");
+ return;
+ }
+
+ /* handle queued messages */
+ u->in_push = TRUE;
+ while (pa_asyncmsgq_process_one(u->asyncmsgq) > 0)
+ ;
+ u->in_push = FALSE;
+
+ 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);
+
+ while (rlen >= u->blocksize) {
+ pa_memchunk rchunk, pchunk;
+
+ /* take fixed block from recorded samples */
+ pa_memblockq_peek_fixed_size(u->source_memblockq, u->blocksize, &rchunk);
+
+ if (plen > u->blocksize) {
+ uint8_t *rdata, *pdata, *cdata;
+ pa_memchunk cchunk;
+
+ /* take fixed block from played samples */
+ pa_memblockq_peek_fixed_size(u->sink_memblockq, u->blocksize, &pchunk);
+
+ rdata = pa_memblock_acquire(rchunk.memblock);
+ rdata += rchunk.index;
+ pdata = pa_memblock_acquire(pchunk.memblock);
+ pdata += pchunk.index;
+
+ cchunk.index = 0;
+ cchunk.length = u->blocksize;
+ cchunk.memblock = pa_memblock_new(u->source->core->mempool, cchunk.length);
+ cdata = pa_memblock_acquire(cchunk.memblock);
+
+ /* perform echo cancelation */
+ speex_echo_cancellation(u->echo_state, (const spx_int16_t *) rdata,
+ (const spx_int16_t *) pdata, (spx_int16_t *) cdata);
+
+ if (u->save_aec) {
+ if (u->captured_file)
+ fwrite(rdata, 1, u->blocksize, u->captured_file);
+ if (u->played_file)
+ fwrite(pdata, 1, u->blocksize, u->played_file);
+ if (u->canceled_file)
+ fwrite(cdata, 1, u->blocksize, u->canceled_file);
+ pa_log_debug("AEC frame saved.");
+ }
+
+ pa_memblock_release(cchunk.memblock);
+ pa_memblock_release(pchunk.memblock);
+ pa_memblock_release(rchunk.memblock);
+
+ /* drop consumed sink samples */
+ pa_memblockq_drop(u->sink_memblockq, u->blocksize);
+ pa_memblock_unref(pchunk.memblock);
+
+ pa_memblock_unref(rchunk.memblock);
+ /* the filtered samples now become the samples from our
+ * source */
+ rchunk = cchunk;
+
+ plen -= u->blocksize;
+ } else {
+ /* not enough played samples to perform echo cancelation,
+ * drop what we have */
+ pa_memblockq_drop(u->sink_memblockq, u->blocksize - plen);
+ plen = 0;
+ }
+
+ /* forward the (echo-canceled) data to the virtual source */
+ pa_source_post(u->source, &rchunk);
+ pa_memblock_unref(rchunk.memblock);
+
+ pa_memblockq_drop(u->source_memblockq, u->blocksize);
+
+ rlen -= u->blocksize;
+ }
+}
+
+/* Called from I/O thread context */
+static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk) {
+ struct userdata *u;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert(chunk);
+ pa_assert_se(u = i->userdata);
+
+ if (u->sink->thread_info.rewind_requested)
+ pa_sink_process_rewind(u->sink, 0);
+
+ pa_sink_render_full(u->sink, nbytes, chunk);
+
+ if (i->thread_info.underrun_for > 0) {
+ pa_log_debug("Handling end of underrun.");
+ pa_atomic_store (&u->request_resync, 1);
+ }
+
+ /* let source thread handle the chunk. pass the sample count as well so that
+ * the source IO thread can update the right variables. */
+ pa_asyncmsgq_post(u->asyncmsgq, PA_MSGOBJECT(u->source_output), SOURCE_OUTPUT_MESSAGE_POST,
+ NULL, 0, chunk, NULL);
+ u->send_counter += chunk->length;
+
+ return 0;
+}
+
+/* Called from input thread context */
+static void source_output_process_rewind_cb(pa_source_output *o, size_t nbytes) {
+ struct userdata *u;
+
+ pa_source_output_assert_ref(o);
+ pa_source_output_assert_io_context(o);
+ pa_assert_se(u = o->userdata);
+
+ pa_source_process_rewind(u->source, nbytes);
+
+ /* go back on read side, we need to use older sink data for this */
+ pa_memblockq_rewind(u->sink_memblockq, nbytes);
+
+ /* manipulate write index */
+ pa_memblockq_seek(u->source_memblockq, -nbytes, PA_SEEK_RELATIVE, TRUE);
+
+ pa_log_debug("Source rewind (%lld) %lld", (long long) nbytes,
+ (long long) pa_memblockq_get_length (u->source_memblockq));
+}
+
+/* Called from I/O thread context */
+static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) {
+ struct userdata *u;
+ size_t amount = 0;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(u = i->userdata);
+
+ pa_log_debug("Sink process rewind %lld", (long long) nbytes);
+
+ if (u->sink->thread_info.rewind_nbytes > 0) {
+ amount = PA_MIN(u->sink->thread_info.rewind_nbytes, nbytes);
+ u->sink->thread_info.rewind_nbytes = 0;
+ }
+
+ pa_sink_process_rewind(u->sink, amount);
+
+ pa_asyncmsgq_post(u->asyncmsgq, PA_MSGOBJECT(u->source_output), SOURCE_OUTPUT_MESSAGE_REWIND, NULL, (int64_t) nbytes, NULL, NULL);
+ u->send_counter -= nbytes;
+}
+
+static void source_output_snapshot_within_thread(struct userdata *u, struct snapshot *snapshot) {
+ size_t delay, rlen, plen;
+ pa_usec_t now, latency;
+
+ now = pa_rtclock_now();
+ latency = pa_source_get_latency_within_thread(u->source_output->source);
+ delay = pa_memblockq_get_length(u->source_output->thread_info.delay_memblockq);
+
+ delay = (u->source_output->thread_info.resampler ? pa_resampler_request(u->source_output->thread_info.resampler, delay) : delay);
+ rlen = pa_memblockq_get_length(u->source_memblockq);
+ plen = pa_memblockq_get_length(u->sink_memblockq);
+
+ snapshot->source_now = now;
+ snapshot->source_latency = latency;
+ snapshot->source_delay = delay;
+ snapshot->recv_counter = u->recv_counter;
+ snapshot->rlen = rlen;
+ snapshot->plen = plen;
+}
+
+
+/* Called from output thread context */
+static int source_output_process_msg_cb(pa_msgobject *obj, int code, void *data, int64_t offset, pa_memchunk *chunk) {
+ struct userdata *u = PA_SOURCE_OUTPUT(obj)->userdata;
+
+ switch (code) {
+
+ case SOURCE_OUTPUT_MESSAGE_POST:
+
+ pa_source_output_assert_io_context(u->source_output);
+
+ if (PA_SOURCE_IS_OPENED(u->source_output->source->thread_info.state))
+ pa_memblockq_push_align(u->sink_memblockq, chunk);
+ else
+ pa_memblockq_flush_write(u->sink_memblockq, TRUE);
+
+ u->recv_counter += (int64_t) chunk->length;
+
+ return 0;
+
+ case SOURCE_OUTPUT_MESSAGE_REWIND:
+ pa_source_output_assert_io_context(u->source_output);
+
+ /* manipulate write index, never go past what we have */
+ if (PA_SOURCE_IS_OPENED(u->source_output->source->thread_info.state))
+ pa_memblockq_seek(u->sink_memblockq, -offset, PA_SEEK_RELATIVE, TRUE);
+ else
+ pa_memblockq_flush_write(u->sink_memblockq, TRUE);
+
+ pa_log_debug("Sink rewind (%lld)", (long long) offset);
+
+ u->recv_counter -= offset;
+
+ return 0;
+
+ case SOURCE_OUTPUT_MESSAGE_LATENCY_SNAPSHOT: {
+ struct snapshot *snapshot = (struct snapshot *) data;
+
+ source_output_snapshot_within_thread(u, snapshot);
+ return 0;
+ }
+
+ case SOURCE_OUTPUT_MESSAGE_APPLY_DIFF_TIME:
+ apply_diff_time(u, offset);
+ return 0;
+
+ }
+
+ return pa_source_output_process_msg(obj, code, data, offset, chunk);
+}
+
+static int sink_input_process_msg_cb(pa_msgobject *obj, int code, void *data, int64_t offset, pa_memchunk *chunk) {
+ struct userdata *u = PA_SINK_INPUT(obj)->userdata;
+
+ switch (code) {
+
+ case SINK_INPUT_MESSAGE_LATENCY_SNAPSHOT: {
+ size_t delay;
+ pa_usec_t now, latency;
+ struct snapshot *snapshot = (struct snapshot *) data;
+
+ pa_sink_input_assert_io_context(u->sink_input);
+
+ now = pa_rtclock_now();
+ latency = pa_sink_get_latency_within_thread(u->sink_input->sink);
+ delay = pa_memblockq_get_length(u->sink_input->thread_info.render_memblockq);
+
+ delay = (u->sink_input->thread_info.resampler ? pa_resampler_request(u->sink_input->thread_info.resampler, delay) : delay);
+
+ snapshot->sink_now = now;
+ snapshot->sink_latency = latency;
+ snapshot->sink_delay = delay;
+ snapshot->send_counter = u->send_counter;
+ return 0;
+ }
+ }
+
+ return pa_sink_input_process_msg(obj, code, data, offset, chunk);
+}
+
+/* Called from I/O thread context */
+static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) {
+ struct userdata *u;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(u = i->userdata);
+
+ pa_log_debug("Sink input update max rewind %lld", (long long) nbytes);
+
+ pa_sink_set_max_rewind_within_thread(u->sink, nbytes);
+}
+
+/* Called from I/O thread context */
+static void source_output_update_max_rewind_cb(pa_source_output *o, size_t nbytes) {
+ struct userdata *u;
+
+ pa_source_output_assert_ref(o);
+ pa_assert_se(u = o->userdata);
+
+ pa_log_debug("Source output update max rewind %lld", (long long) nbytes);
+
+ pa_source_set_max_rewind_within_thread(u->source, nbytes);
+}
+
+/* Called from I/O thread context */
+static void sink_input_update_max_request_cb(pa_sink_input *i, size_t nbytes) {
+ struct userdata *u;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(u = i->userdata);
+
+ pa_log_debug("Sink input update max rewind %lld", (long long) nbytes);
+
+ pa_sink_set_max_request_within_thread(u->sink, nbytes);
+}
+
+/* Called from I/O thread context */
+static void sink_input_update_sink_requested_latency_cb(pa_sink_input *i) {
+ struct userdata *u;
+ pa_usec_t latency;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(u = i->userdata);
+
+ latency = pa_sink_get_requested_latency_within_thread(i->sink);
+
+ pa_log_debug("Sink input update requested latency %lld", (long long) latency);
+}
+
+/* Called from I/O thread context */
+static void source_output_update_source_requested_latency_cb(pa_source_output *o) {
+ struct userdata *u;
+ pa_usec_t latency;
+
+ pa_source_output_assert_ref(o);
+ pa_assert_se(u = o->userdata);
+
+ latency = pa_source_get_requested_latency_within_thread(o->source);
+
+ pa_log_debug("source output update requested latency %lld", (long long) latency);
+}
+
+/* Called from I/O thread context */
+static void sink_input_update_sink_latency_range_cb(pa_sink_input *i) {
+ struct userdata *u;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(u = i->userdata);
+
+ pa_log_debug("Sink input update latency range %lld %lld",
+ (long long) i->sink->thread_info.min_latency,
+ (long long) i->sink->thread_info.max_latency);
+
+ pa_sink_set_latency_range_within_thread(u->sink, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency);
+}
+
+/* Called from I/O thread context */
+static void source_output_update_source_latency_range_cb(pa_source_output *o) {
+ struct userdata *u;
+
+ pa_source_output_assert_ref(o);
+ pa_assert_se(u = o->userdata);
+
+ pa_log_debug("Source output update latency range %lld %lld",
+ (long long) o->source->thread_info.min_latency,
+ (long long) o->source->thread_info.max_latency);
+
+ pa_source_set_latency_range_within_thread(u->source, o->source->thread_info.min_latency, o->source->thread_info.max_latency);
+}
+
+/* Called from I/O thread context */
+static void sink_input_update_sink_fixed_latency_cb(pa_sink_input *i) {
+ struct userdata *u;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(u = i->userdata);
+
+ pa_log_debug("Sink input update fixed latency %lld",
+ (long long) i->sink->thread_info.fixed_latency);
+
+ pa_sink_set_fixed_latency_within_thread(u->sink, i->sink->thread_info.fixed_latency);
+}
+
+/* Called from I/O thread context */
+static void source_output_update_source_fixed_latency_cb(pa_source_output *o) {
+ struct userdata *u;
+
+ pa_source_output_assert_ref(o);
+ pa_assert_se(u = o->userdata);
+
+ pa_log_debug("Source output update fixed latency %lld",
+ (long long) o->source->thread_info.fixed_latency);
+
+ pa_source_set_fixed_latency_within_thread(u->source, o->source->thread_info.fixed_latency);
+}
+
+/* Called from output thread context */
+static void source_output_attach_cb(pa_source_output *o) {
+ struct userdata *u;
+
+ pa_source_output_assert_ref(o);
+ pa_source_output_assert_io_context(o);
+ pa_assert_se(u = o->userdata);
+
+ pa_source_set_rtpoll(u->source, o->source->thread_info.rtpoll);
+ pa_source_set_latency_range_within_thread(u->source, o->source->thread_info.min_latency, o->source->thread_info.max_latency);
+ pa_source_set_fixed_latency_within_thread(u->source, o->source->thread_info.fixed_latency);
+ pa_source_set_max_rewind_within_thread(u->source, pa_source_output_get_max_rewind(o));
+
+ pa_log_debug("Source output %p attach", o);
+
+ pa_source_attach_within_thread(u->source);
+
+ u->rtpoll_item_read = pa_rtpoll_item_new_asyncmsgq_read(
+ o->source->thread_info.rtpoll,
+ PA_RTPOLL_LATE,
+ u->asyncmsgq);
+}
+
+/* Called from I/O thread context */
+static void sink_input_attach_cb(pa_sink_input *i) {
+ struct userdata *u;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(u = i->userdata);
+
+ pa_sink_set_rtpoll(u->sink, i->sink->thread_info.rtpoll);
+ pa_sink_set_latency_range_within_thread(u->sink, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency);
+
+ /* (8.1) IF YOU NEED A FIXED BLOCK SIZE ADD THE LATENCY FOR ONE
+ * BLOCK MINUS ONE SAMPLE HERE. SEE (7) */
+ pa_sink_set_fixed_latency_within_thread(u->sink, i->sink->thread_info.fixed_latency);
+
+ /* (8.2) IF YOU NEED A FIXED BLOCK SIZE ROUND
+ * pa_sink_input_get_max_request(i) UP TO MULTIPLES OF IT
+ * HERE. SEE (6) */
+ pa_sink_set_max_request_within_thread(u->sink, pa_sink_input_get_max_request(i));
+ pa_sink_set_max_rewind_within_thread(u->sink, pa_sink_input_get_max_rewind(i));
+
+ pa_log_debug("Sink input %p attach", i);
+
+ u->rtpoll_item_write = pa_rtpoll_item_new_asyncmsgq_write(
+ i->sink->thread_info.rtpoll,
+ PA_RTPOLL_LATE,
+ u->asyncmsgq);
+
+ pa_sink_attach_within_thread(u->sink);
+}
+
+
+/* Called from output thread context */
+static void source_output_detach_cb(pa_source_output *o) {
+ struct userdata *u;
+
+ pa_source_output_assert_ref(o);
+ pa_source_output_assert_io_context(o);
+ pa_assert_se(u = o->userdata);
+
+ pa_source_detach_within_thread(u->source);
+ pa_source_set_rtpoll(u->source, NULL);
+
+ pa_log_debug("Source output %p detach", o);
+
+ if (u->rtpoll_item_read) {
+ pa_rtpoll_item_free(u->rtpoll_item_read);
+ u->rtpoll_item_read = NULL;
+ }
+}
+
+/* Called from I/O thread context */
+static void sink_input_detach_cb(pa_sink_input *i) {
+ struct userdata *u;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(u = i->userdata);
+
+ pa_sink_detach_within_thread(u->sink);
+
+ pa_sink_set_rtpoll(u->sink, NULL);
+
+ pa_log_debug("Sink input %p detach", i);
+
+ if (u->rtpoll_item_write) {
+ pa_rtpoll_item_free(u->rtpoll_item_write);
+ u->rtpoll_item_write = NULL;
+ }
+}
+
+/* Called from output thread context */
+static void source_output_state_change_cb(pa_source_output *o, pa_source_output_state_t state) {
+ struct userdata *u;
+
+ pa_source_output_assert_ref(o);
+ pa_source_output_assert_io_context(o);
+ pa_assert_se(u = o->userdata);
+
+ pa_log_debug("Source output %p state %d", o, state);
+}
+
+/* Called from IO thread context */
+static void sink_input_state_change_cb(pa_sink_input *i, pa_sink_input_state_t state) {
+ struct userdata *u;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(u = i->userdata);
+
+ pa_log_debug("Sink input %p state %d", i, state);
+
+ /* If we are added for the first time, ask for a rewinding so that
+ * we are heard right-away. */
+ if (PA_SINK_INPUT_IS_LINKED(state) &&
+ i->thread_info.state == PA_SINK_INPUT_INIT) {
+ pa_log_debug("Requesting rewind due to state change.");
+ pa_sink_input_request_rewind(i, 0, FALSE, TRUE, TRUE);
+ }
+}
+
+/* Called from main thread */
+static void source_output_kill_cb(pa_source_output *o) {
+ struct userdata *u;
+
+ pa_source_output_assert_ref(o);
+ pa_assert_ctl_context();
+ pa_assert_se(u = o->userdata);
+
+ /* The order here matters! We first kill the source output, followed
+ * by the source. That means the source callbacks must be protected
+ * against an unconnected source output! */
+ pa_source_output_unlink(u->source_output);
+ pa_source_unlink(u->source);
+
+ pa_source_output_unref(u->source_output);
+ u->source_output = NULL;
+
+ pa_source_unref(u->source);
+ u->source = NULL;
+
+ pa_log_debug("Source output kill %p", o);
+
+ pa_module_unload_request(u->module, TRUE);
+}
+
+/* Called from main context */
+static void sink_input_kill_cb(pa_sink_input *i) {
+ struct userdata *u;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(u = i->userdata);
+
+ /* The order here matters! We first kill the sink input, followed
+ * by the sink. That means the sink callbacks must be protected
+ * against an unconnected sink input! */
+ pa_sink_input_unlink(u->sink_input);
+ pa_sink_unlink(u->sink);
+
+ pa_sink_input_unref(u->sink_input);
+ u->sink_input = NULL;
+
+ pa_sink_unref(u->sink);
+ u->sink = NULL;
+
+ pa_log_debug("Sink input kill %p", i);
+
+ pa_module_unload_request(u->module, TRUE);
+}
+
+/* Called from main thread */
+static pa_bool_t source_output_may_move_to_cb(pa_source_output *o, pa_source *dest) {
+ struct userdata *u;
+
+ pa_source_output_assert_ref(o);
+ pa_assert_ctl_context();
+ pa_assert_se(u = o->userdata);
+
+ return TRUE;
+}
+
+/* Called from main context */
+static pa_bool_t sink_input_may_move_to_cb(pa_sink_input *i, pa_sink *dest) {
+ struct userdata *u;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(u = i->userdata);
+
+ return u->sink != dest;
+}
+
+/* Called from main thread */
+static void source_output_moving_cb(pa_source_output *o, pa_source *dest) {
+ struct userdata *u;
+
+ pa_source_output_assert_ref(o);
+ pa_assert_ctl_context();
+ pa_assert_se(u = o->userdata);
+
+ if (dest) {
+ pa_source_set_asyncmsgq(u->source, dest->asyncmsgq);
+ pa_source_update_flags(u->source, PA_SOURCE_LATENCY|PA_SOURCE_DYNAMIC_LATENCY, dest->flags);
+ } else
+ pa_source_set_asyncmsgq(u->source, NULL);
+
+ if (u->source_auto_desc && dest) {
+ const char *z;
+ pa_proplist *pl;
+
+ pl = pa_proplist_new();
+ z = pa_proplist_gets(dest->proplist, PA_PROP_DEVICE_DESCRIPTION);
+ pa_proplist_setf(pl, PA_PROP_DEVICE_DESCRIPTION, "Echo-Cancel Source %s on %s",
+ pa_proplist_gets(u->source->proplist, "device.echo-cancel.name"), z ? z : dest->name);
+
+ pa_source_update_proplist(u->source, PA_UPDATE_REPLACE, pl);
+ pa_proplist_free(pl);
+ }
+}
+
+/* Called from main context */
+static void sink_input_moving_cb(pa_sink_input *i, pa_sink *dest) {
+ struct userdata *u;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(u = i->userdata);
+
+ if (dest) {
+ pa_sink_set_asyncmsgq(u->sink, dest->asyncmsgq);
+ pa_sink_update_flags(u->sink, PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY, dest->flags);
+ } else
+ pa_sink_set_asyncmsgq(u->sink, NULL);
+
+ if (u->sink_auto_desc && dest) {
+ const char *z;
+ pa_proplist *pl;
+
+ pl = pa_proplist_new();
+ z = pa_proplist_gets(dest->proplist, PA_PROP_DEVICE_DESCRIPTION);
+ pa_proplist_setf(pl, PA_PROP_DEVICE_DESCRIPTION, "Echo-Cancel Sink %s on %s",
+ pa_proplist_gets(u->sink->proplist, "device.echo-cancel.name"), z ? z : dest->name);
+
+ pa_sink_update_proplist(u->sink, PA_UPDATE_REPLACE, pl);
+ pa_proplist_free(pl);
+ }
+}
+
+/* Called from main context */
+static void sink_input_volume_changed_cb(pa_sink_input *i) {
+ struct userdata *u;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(u = i->userdata);
+
+ pa_sink_volume_changed(u->sink, &i->volume);
+}
+
+/* Called from main context */
+static void sink_input_mute_changed_cb(pa_sink_input *i) {
+ struct userdata *u;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(u = i->userdata);
+
+ pa_sink_mute_changed(u->sink, i->muted);
+}
+
+
+int pa__init(pa_module*m) {
+ struct userdata *u;
+ pa_sample_spec ss;
+ pa_channel_map map;
+ pa_modargs *ma;
+ pa_source *source_master=NULL;
+ pa_sink *sink_master=NULL;
+ pa_source_output_new_data source_output_data;
+ pa_sink_input_new_data sink_input_data;
+ pa_source_new_data source_data;
+ pa_sink_new_data sink_data;
+ pa_memchunk silence;
+ int framelen, rate, y;
+ uint32_t frame_size_ms, filter_size_ms;
+ uint32_t adjust_time_sec;
+
+ pa_assert(m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log("Failed to parse module arguments.");
+ goto fail;
+ }
+
+ if (!(source_master = pa_namereg_get(m->core, pa_modargs_get_value(ma, "source_master", NULL), PA_NAMEREG_SOURCE))) {
+ pa_log("Master source not found");
+ goto fail;
+ }
+ pa_assert(source_master);
+
+ if (!(sink_master = pa_namereg_get(m->core, pa_modargs_get_value(ma, "sink_master", NULL), PA_NAMEREG_SINK))) {
+ pa_log("Master sink not found");
+ goto fail;
+ }
+ pa_assert(sink_master);
+
+ frame_size_ms = DEFAULT_FRAME_SIZE_MS;
+ if (pa_modargs_get_value_u32(ma, "frame_size_ms", &frame_size_ms) < 0 || frame_size_ms < 1 || frame_size_ms > 200) {
+ pa_log("Invalid frame_size_ms specification");
+ goto fail;
+ }
+
+ filter_size_ms = DEFAULT_FILTER_SIZE_MS;
+ if (pa_modargs_get_value_u32(ma, "filter_size_ms", &filter_size_ms) < 0 || filter_size_ms < 1 || filter_size_ms > 2000) {
+ pa_log("Invalid filter_size_ms specification");
+ goto fail;
+ }
+
+ ss = source_master->sample_spec;
+ ss.format = PA_SAMPLE_S16LE;
+ map = source_master->channel_map;
+ if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0) {
+ pa_log("Invalid sample format specification or channel map");
+ goto fail;
+ }
+
+ u = pa_xnew0(struct userdata, 1);
+ if (!u) {
+ pa_log("Failed to alloc userdata");
+ goto fail;
+ }
+ u->core = m->core;
+ u->module = m;
+ m->userdata = u;
+ u->frame_size_ms = frame_size_ms;
+ rate = ss.rate;
+ framelen = (rate * frame_size_ms) / 1000;
+
+ /* framelen should be a power of 2, round down to nearest power of two */
+ y = 1 << ((8 * sizeof (int)) - 2);
+ while (y > framelen)
+ y >>= 1;
+ framelen = y;
+
+ u->blocksize = framelen * pa_frame_size (&ss);
+ pa_log_debug ("Using framelen %d, blocksize %lld, channels %d, rate %d", framelen, (long long) u->blocksize,
+ ss.channels, ss.rate);
+
+ 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");
+ goto fail;
+ }
+
+ if (adjust_time_sec != DEFAULT_ADJUST_TIME_USEC / PA_USEC_PER_SEC)
+ u->adjust_time = adjust_time_sec * PA_USEC_PER_SEC;
+ else
+ u->adjust_time = DEFAULT_ADJUST_TIME_USEC;
+
+ u->save_aec = DEFAULT_SAVE_AEC;
+ if (pa_modargs_get_value_u32(ma, "save_aec", &u->save_aec) < 0) {
+ pa_log("Failed to parse save_aec value");
+ goto fail;
+ }
+
+ u->asyncmsgq = pa_asyncmsgq_new(0);
+ u->need_realign = TRUE;
+ u->echo_state = speex_echo_state_init_mc (framelen, (rate * filter_size_ms) / 1000, ss.channels, ss.channels);
+ speex_echo_ctl(u->echo_state, SPEEX_ECHO_SET_SAMPLING_RATE, &rate);
+
+ /* Create source */
+ pa_source_new_data_init(&source_data);
+ source_data.driver = __FILE__;
+ source_data.module = m;
+ if (!(source_data.name = pa_xstrdup(pa_modargs_get_value(ma, "source_name", NULL))))
+ source_data.name = pa_sprintf_malloc("%s.echo-cancel", source_master->name);
+ pa_source_new_data_set_sample_spec(&source_data, &ss);
+ pa_source_new_data_set_channel_map(&source_data, &map);
+ pa_proplist_sets(source_data.proplist, PA_PROP_DEVICE_MASTER_DEVICE, source_master->name);
+ pa_proplist_sets(source_data.proplist, PA_PROP_DEVICE_CLASS, "filter");
+ pa_proplist_sets(source_data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "phone");
+ pa_proplist_sets(source_data.proplist, "device.echo-cancel.name", source_data.name);
+
+ if (pa_modargs_get_proplist(ma, "source_properties", source_data.proplist, PA_UPDATE_REPLACE) < 0) {
+ pa_log("Invalid properties");
+ pa_source_new_data_done(&source_data);
+ goto fail;
+ }
+
+ if ((u->source_auto_desc = !pa_proplist_contains(source_data.proplist, PA_PROP_DEVICE_DESCRIPTION))) {
+ const char *z;
+
+ z = pa_proplist_gets(source_master->proplist, PA_PROP_DEVICE_DESCRIPTION);
+ pa_proplist_setf(source_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Echo-Cancel Source %s on %s", source_data.name, z ? z : source_master->name);
+ }
+
+ u->source = pa_source_new(m->core, &source_data,
+ PA_SOURCE_HW_MUTE_CTRL|PA_SOURCE_HW_VOLUME_CTRL|PA_SOURCE_DECIBEL_VOLUME|
+ (source_master->flags & (PA_SOURCE_LATENCY|PA_SOURCE_DYNAMIC_LATENCY)));
+ pa_source_new_data_done(&source_data);
+
+ if (!u->source) {
+ pa_log("Failed to create source.");
+ goto fail;
+ }
+
+ u->source->parent.process_msg = source_process_msg_cb;
+ u->source->set_state = source_set_state_cb;
+ u->source->update_requested_latency = source_update_requested_latency_cb;
+ u->source->set_volume = source_set_volume_cb;
+ u->source->set_mute = source_set_mute_cb;
+ u->source->get_volume = source_get_volume_cb;
+ u->source->get_mute = source_get_mute_cb;
+ u->source->userdata = u;
+
+ pa_source_set_asyncmsgq(u->source, source_master->asyncmsgq);
+
+ /* Create sink */
+ pa_sink_new_data_init(&sink_data);
+ sink_data.driver = __FILE__;
+ sink_data.module = m;
+ if (!(sink_data.name = pa_xstrdup(pa_modargs_get_value(ma, "sink_name", NULL))))
+ sink_data.name = pa_sprintf_malloc("%s.echo-cancel", sink_master->name);
+ pa_sink_new_data_set_sample_spec(&sink_data, &ss);
+ pa_sink_new_data_set_channel_map(&sink_data, &map);
+ pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_MASTER_DEVICE, sink_master->name);
+ pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_CLASS, "filter");
+ pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "phone");
+ pa_proplist_sets(sink_data.proplist, "device.echo-cancel.name", sink_data.name);
+
+ if (pa_modargs_get_proplist(ma, "sink_properties", sink_data.proplist, PA_UPDATE_REPLACE) < 0) {
+ pa_log("Invalid properties");
+ pa_sink_new_data_done(&sink_data);
+ goto fail;
+ }
+
+ if ((u->sink_auto_desc = !pa_proplist_contains(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION))) {
+ const char *z;
+
+ z = pa_proplist_gets(sink_master->proplist, PA_PROP_DEVICE_DESCRIPTION);
+ pa_proplist_setf(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Echo-Cancel Sink %s on %s", sink_data.name, z ? z : sink_master->name);
+ }
+
+ u->sink = pa_sink_new(m->core, &sink_data,
+ PA_SINK_HW_MUTE_CTRL|PA_SINK_HW_VOLUME_CTRL|PA_SINK_DECIBEL_VOLUME|
+ (sink_master->flags & (PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY)));
+ pa_sink_new_data_done(&sink_data);
+
+ if (!u->sink) {
+ pa_log("Failed to create sink.");
+ goto fail;
+ }
+
+ u->sink->parent.process_msg = sink_process_msg_cb;
+ u->sink->set_state = sink_set_state_cb;
+ u->sink->update_requested_latency = sink_update_requested_latency_cb;
+ u->sink->request_rewind = sink_request_rewind_cb;
+ u->sink->set_volume = sink_set_volume_cb;
+ u->sink->set_mute = sink_set_mute_cb;
+ u->sink->userdata = u;
+
+ pa_sink_set_asyncmsgq(u->sink, sink_master->asyncmsgq);
+
+ /* Create source output */
+ pa_source_output_new_data_init(&source_output_data);
+ source_output_data.driver = __FILE__;
+ source_output_data.module = m;
+ source_output_data.source = source_master;
+ /* FIXME
+ source_output_data.flags = PA_SOURCE_OUTPUT_DONT_INHIBIT_AUTO_SUSPEND; */
+
+ pa_proplist_sets(source_output_data.proplist, PA_PROP_MEDIA_NAME, "Echo-Cancel Source Stream");
+ pa_proplist_sets(source_output_data.proplist, PA_PROP_MEDIA_ROLE, "filter");
+ pa_source_output_new_data_set_sample_spec(&source_output_data, &ss);
+ pa_source_output_new_data_set_channel_map(&source_output_data, &map);
+
+ pa_source_output_new(&u->source_output, m->core, &source_output_data);
+ pa_source_output_new_data_done(&source_output_data);
+
+ if (!u->source_output)
+ goto fail;
+
+ u->source_output->parent.process_msg = source_output_process_msg_cb;
+ u->source_output->push = source_output_push_cb;
+ u->source_output->process_rewind = source_output_process_rewind_cb;
+ u->source_output->update_max_rewind = source_output_update_max_rewind_cb;
+ u->source_output->update_source_requested_latency = source_output_update_source_requested_latency_cb;
+ u->source_output->update_source_latency_range = source_output_update_source_latency_range_cb;
+ u->source_output->update_source_fixed_latency = source_output_update_source_fixed_latency_cb;
+ u->source_output->kill = source_output_kill_cb;
+ u->source_output->attach = source_output_attach_cb;
+ u->source_output->detach = source_output_detach_cb;
+ u->source_output->state_change = source_output_state_change_cb;
+ u->source_output->may_move_to = source_output_may_move_to_cb;
+ u->source_output->moving = source_output_moving_cb;
+ u->source_output->userdata = u;
+
+ /* Create sink input */
+ pa_sink_input_new_data_init(&sink_input_data);
+ sink_input_data.driver = __FILE__;
+ sink_input_data.module = m;
+ sink_input_data.sink = sink_master;
+ pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_NAME, "Echo-Cancel Sink Stream");
+ pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_ROLE, "filter");
+ pa_sink_input_new_data_set_sample_spec(&sink_input_data, &ss);
+ pa_sink_input_new_data_set_channel_map(&sink_input_data, &map);
+ sink_input_data.flags = PA_SINK_INPUT_VARIABLE_RATE;
+
+ pa_sink_input_new(&u->sink_input, m->core, &sink_input_data);
+ pa_sink_input_new_data_done(&sink_input_data);
+
+ if (!u->sink_input)
+ goto fail;
+
+ u->sink_input->parent.process_msg = sink_input_process_msg_cb;
+ u->sink_input->pop = sink_input_pop_cb;
+ u->sink_input->process_rewind = sink_input_process_rewind_cb;
+ u->sink_input->update_max_rewind = sink_input_update_max_rewind_cb;
+ u->sink_input->update_max_request = sink_input_update_max_request_cb;
+ u->sink_input->update_sink_requested_latency = sink_input_update_sink_requested_latency_cb;
+ u->sink_input->update_sink_latency_range = sink_input_update_sink_latency_range_cb;
+ u->sink_input->update_sink_fixed_latency = sink_input_update_sink_fixed_latency_cb;
+ u->sink_input->kill = sink_input_kill_cb;
+ u->sink_input->attach = sink_input_attach_cb;
+ u->sink_input->detach = sink_input_detach_cb;
+ u->sink_input->state_change = sink_input_state_change_cb;
+ u->sink_input->may_move_to = sink_input_may_move_to_cb;
+ u->sink_input->moving = sink_input_moving_cb;
+ u->sink_input->volume_changed = sink_input_volume_changed_cb;
+ u->sink_input->mute_changed = sink_input_mute_changed_cb;
+ u->sink_input->userdata = u;
+
+ pa_sink_input_get_silence(u->sink_input, &silence);
+
+ u->source_memblockq = pa_memblockq_new(0, MEMBLOCKQ_MAXLENGTH, 0,
+ pa_frame_size(&ss), 1, 1, 0, &silence);
+ u->sink_memblockq = pa_memblockq_new(0, MEMBLOCKQ_MAXLENGTH, 0,
+ pa_frame_size(&ss), 1, 1, 0, &silence);
+
+ pa_memblock_unref(silence.memblock);
+
+ if (!u->source_memblockq || !u->sink_memblockq) {
+ pa_log("Failed to create memblockq.");
+ goto fail;
+ }
+
+ if (u->adjust_time > 0)
+ u->time_event = pa_core_rttime_new(m->core, pa_rtclock_now() + u->adjust_time, time_callback, u);
+
+ if (u->save_aec) {
+ pa_log("Creating AEC files in /tmp");
+ u->captured_file = fopen("/tmp/aec_rec.sw", "wb");
+ if (u->captured_file == NULL)
+ perror ("fopen failed");
+ u->played_file = fopen("/tmp/aec_play.sw", "wb");
+ if (u->played_file == NULL)
+ perror ("fopen failed");
+ u->canceled_file = fopen("/tmp/aec_out.sw", "wb");
+ if (u->canceled_file == NULL)
+ perror ("fopen failed");
+ }
+
+ pa_sink_put(u->sink);
+ pa_source_put(u->source);
+
+ pa_sink_input_put(u->sink_input);
+ pa_source_output_put(u->source_output);
+
+ pa_modargs_free(ma);
+
+ return 0;
+
+ fail:
+ if (ma)
+ pa_modargs_free(ma);
+
+ pa__done(m);
+
+ return -1;
+}
+
+int pa__get_n_used(pa_module *m) {
+ struct userdata *u;
+
+ pa_assert(m);
+ pa_assert_se(u = m->userdata);
+
+ return pa_sink_linked_by(u->sink) + pa_source_linked_by(u->source);
+}
+
+void pa__done(pa_module*m) {
+ struct userdata *u;
+
+ pa_assert(m);
+
+ if (!(u = m->userdata))
+ return;
+
+ /* See comments in source_output_kill_cb() above regarding
+ * destruction order! */
+
+ if (u->source_output)
+ pa_source_output_unlink(u->source_output);
+ if (u->sink_input)
+ pa_sink_input_unlink(u->sink_input);
+
+ if (u->source)
+ pa_source_unlink(u->source);
+ if (u->sink)
+ pa_sink_unlink(u->sink);
+
+ if (u->source_output)
+ pa_source_output_unref(u->source_output);
+ if (u->sink_input)
+ pa_sink_input_unref(u->sink_input);
+
+ if (u->source)
+ pa_source_unref(u->source);
+ if (u->sink)
+ pa_sink_unref(u->sink);
+
+ if (u->time_event)
+ u->core->mainloop->time_free(u->time_event);
+
+ if (u->source_memblockq)
+ pa_memblockq_free(u->source_memblockq);
+ if (u->sink_memblockq)
+ pa_memblockq_free(u->sink_memblockq);
+
+ if (u->echo_state)
+ speex_echo_state_destroy (u->echo_state);
+
+ if (u->asyncmsgq)
+ pa_asyncmsgq_unref(u->asyncmsgq);
+
+ pa_xfree(u);
+}
diff --git a/src/modules/module-echo-cancel.c b/src/modules/module-echo-cancel.c
deleted file mode 100644
index d6c2ca1..0000000
--- a/src/modules/module-echo-cancel.c
+++ /dev/null
@@ -1,1625 +0,0 @@
-/***
- This file is part of PulseAudio.
-
- Copyright 2010 Wim Taymans <wim.taymans at gmail.com>
-
- Based on module-virtual-sink.c
- module-virtual-source.c
- module-loopback.c
-
- Copyright 2010 Intel Corporation
- Contributor: Pierre-Louis Bossart <pierre-louis.bossart at intel.com>
-
- PulseAudio is free software; you can redistribute it and/or modify
- it under the terms of the GNU Lesser General Public License as published
- by the Free Software Foundation; either version 2.1 of the License,
- or (at your option) any later version.
-
- PulseAudio is distributed in the hope that it will be useful, but
- WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public License
- along with PulseAudio; if not, write to the Free Software
- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
- USA.
-***/
-
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif
-
-#include <stdio.h>
-#include <math.h>
-
-#include <speex/speex_echo.h>
-
-#include <pulse/xmalloc.h>
-#include <pulse/i18n.h>
-#include <pulse/timeval.h>
-#include <pulse/rtclock.h>
-
-#include <pulsecore/atomic.h>
-#include <pulsecore/macro.h>
-#include <pulsecore/core-error.h>
-#include <pulsecore/namereg.h>
-#include <pulsecore/sink.h>
-#include <pulsecore/module.h>
-#include <pulsecore/core-rtclock.h>
-#include <pulsecore/core-util.h>
-#include <pulsecore/core-error.h>
-#include <pulsecore/modargs.h>
-#include <pulsecore/log.h>
-#include <pulsecore/thread.h>
-#include <pulsecore/thread-mq.h>
-#include <pulsecore/rtpoll.h>
-#include <pulsecore/sample-util.h>
-#include <pulsecore/ltdl-helper.h>
-
-#include "module-echo-cancel-symdef.h"
-
-PA_MODULE_AUTHOR("Wim Taymans");
-PA_MODULE_DESCRIPTION("Echo Cancelation");
-PA_MODULE_VERSION(PACKAGE_VERSION);
-PA_MODULE_LOAD_ONCE(FALSE);
-PA_MODULE_USAGE(
- _("source_name=<name for the source> "
- "source_properties=<properties for the source> "
- "source_master=<name of source to filter> "
- "sink_name=<name for the sink> "
- "sink_properties=<properties for the sink> "
- "sink_master=<name of sink to filter> "
- "frame_size_ms=<amount of data to process at one time> "
- "filter_size_ms=<amount of echo to cancel> "
- "adjust_time=<how often to readjust rates in s> "
- "format=<sample format> "
- "rate=<sample rate> "
- "channels=<number of channels> "
- "channel_map=<channel map> "
- "save_aec=<save AEC data in /tmp> "
- ));
-
-/* should be between 10-20 ms */
-#define DEFAULT_FRAME_SIZE_MS 20
-/* should be between 100-500 ms */
-#define DEFAULT_FILTER_SIZE_MS 200
-
-#define DEFAULT_ADJUST_TIME_USEC (1*PA_USEC_PER_SEC)
-#define DEFAULT_SAVE_AEC 0
-
-#define MEMBLOCKQ_MAXLENGTH (16*1024*1024)
-
-/* This module creates a new (virtual) source and sink.
- *
- * The data sent to the new sink is kept in a memblockq before being
- * forwarded to the real sink_master.
- *
- * Data read from source_master is matched against the saved sink data and
- * echo canceled data is then pushed onto the new source.
- *
- * Both source and sink masters have their own threads to push/pull data
- * respectively. We however perform all our actions in the source IO thread.
- * To do this we send all played samples to the source IO thread where they
- * are then pushed into the memblockq.
- *
- * Alignment is performed in two steps:
- *
- * 1) when something happens that requires quick adjustement of the alignment of
- * capture and playback samples, we perform a resync. This adjusts the
- * position in the playback memblock to the requested sample. Quick
- * adjustements include moving the playback samples before the capture
- * samples (because else the echo canceler does not work) or when the
- * playback pointer drifts too far away.
- *
- * 2) periodically check the difference between capture and playback. we use a
- * low and high watermark for adjusting the alignment. playback should always
- * be before capture and the difference should not be bigger than one frame
- * size. We would ideally like to resample the sink_input but most driver
- * don't give enough accuracy to be able to do that right now.
- */
-
-struct snapshot {
- pa_usec_t sink_now;
- pa_usec_t sink_latency;
- size_t sink_delay;
- int64_t send_counter;
-
- pa_usec_t source_now;
- pa_usec_t source_latency;
- size_t source_delay;
- int64_t recv_counter;
- size_t rlen;
- size_t plen;
-};
-
-struct userdata {
- pa_core *core;
- pa_module *module;
-
- uint32_t frame_size_ms;
- uint32_t save_aec;
-
- SpeexEchoState *echo_state;
-
- size_t blocksize;
- 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;
-
- pa_source *source;
- pa_bool_t source_auto_desc;
- pa_source_output *source_output;
- pa_memblockq *source_memblockq; /* echo canceler needs fixed sized chunks */
- pa_atomic_t source_active;
-
- pa_sink *sink;
- pa_bool_t sink_auto_desc;
- pa_sink_input *sink_input;
- pa_memblockq *sink_memblockq;
- int64_t send_counter; /* updated in sink IO thread */
- int64_t recv_counter;
- pa_atomic_t sink_active;
-
- pa_atomic_t request_resync;
-
- pa_time_event *time_event;
- pa_usec_t adjust_time;
-
- FILE *captured_file;
- FILE *played_file;
- FILE *canceled_file;
-};
-
-static void source_output_snapshot_within_thread(struct userdata *u, struct snapshot *snapshot);
-
-static const char* const valid_modargs[] = {
- "source_name",
- "source_properties",
- "source_master",
- "sink_name",
- "sink_properties",
- "sink_master",
- "frame_size_ms",
- "filter_size_ms",
- "adjust_time",
- "format",
- "rate",
- "channels",
- "channel_map",
- "save_aec",
- NULL
-};
-
-enum {
- SOURCE_OUTPUT_MESSAGE_POST = PA_SOURCE_OUTPUT_MESSAGE_MAX,
- SOURCE_OUTPUT_MESSAGE_REWIND,
- SOURCE_OUTPUT_MESSAGE_LATENCY_SNAPSHOT,
- SOURCE_OUTPUT_MESSAGE_APPLY_DIFF_TIME
-};
-
-enum {
- SINK_INPUT_MESSAGE_LATENCY_SNAPSHOT
-};
-
-static int64_t calc_diff(struct userdata *u, struct snapshot *snapshot) {
- int64_t buffer, diff_time, buffer_latency;
-
- /* get the number of samples between capture and playback */
- if (snapshot->plen > snapshot->rlen)
- buffer = snapshot->plen - snapshot->rlen;
- else
- buffer = 0;
-
- buffer += snapshot->source_delay + snapshot->sink_delay;
-
- /* add the amount of samples not yet transfered to the source context */
- if (snapshot->recv_counter <= snapshot->send_counter)
- buffer += (int64_t) (snapshot->send_counter - snapshot->recv_counter);
- else
- buffer += PA_CLIP_SUB(buffer, (int64_t) (snapshot->recv_counter - snapshot->send_counter));
-
- /* convert to time */
- buffer_latency = pa_bytes_to_usec(buffer, &u->source_output->sample_spec);
-
- /* capture and playback samples are perfectly aligned when diff_time is 0 */
- diff_time = (snapshot->sink_now + snapshot->sink_latency - buffer_latency) -
- (snapshot->source_now - snapshot->source_latency);
-
- pa_log_debug("diff %lld (%lld - %lld + %lld) %lld %lld %lld %lld", (long long) diff_time,
- (long long) snapshot->sink_latency,
- (long long) buffer_latency, (long long) snapshot->source_latency,
- (long long) snapshot->source_delay, (long long) snapshot->sink_delay,
- (long long) (snapshot->send_counter - snapshot->recv_counter),
- (long long) (snapshot->sink_now - snapshot->source_now));
-
- return diff_time;
-}
-
-/* Called from main context */
-static void time_callback(pa_mainloop_api *a, pa_time_event *e, const struct timeval *t, void *userdata) {
- struct userdata *u = userdata;
- uint32_t old_rate, base_rate, new_rate;
- int64_t diff_time;
- size_t fs;
- struct snapshot latency_snapshot;
-
- pa_assert(u);
- pa_assert(a);
- pa_assert(u->time_event == e);
- pa_assert_ctl_context();
-
- if (pa_atomic_load (&u->sink_active) == 0 || pa_atomic_load (&u->source_active) == 0)
- goto done;
-
- /* update our snapshots */
- pa_asyncmsgq_send(u->source_output->source->asyncmsgq, PA_MSGOBJECT(u->source_output), SOURCE_OUTPUT_MESSAGE_LATENCY_SNAPSHOT, &latency_snapshot, 0, NULL);
- pa_asyncmsgq_send(u->sink_input->sink->asyncmsgq, PA_MSGOBJECT(u->sink_input), SINK_INPUT_MESSAGE_LATENCY_SNAPSHOT, &latency_snapshot, 0, NULL);
-
- /* calculate drift between capture and playback */
- diff_time = calc_diff(u, &latency_snapshot);
-
- fs = pa_frame_size(&u->source_output->sample_spec);
- old_rate = u->sink_input->sample_spec.rate;
- base_rate = u->source_output->sample_spec.rate;
-
- if (diff_time < 0) {
- /* recording before playback, we need to adjust quickly. The echo
- * canceler does not work in this case. */
- pa_asyncmsgq_post(u->asyncmsgq, PA_MSGOBJECT(u->source_output), SOURCE_OUTPUT_MESSAGE_APPLY_DIFF_TIME,
- NULL, diff_time, NULL, NULL);
- //new_rate = base_rate - ((pa_usec_to_bytes (-diff_time, &u->source_output->sample_spec) / fs) * PA_USEC_PER_SEC) / u->adjust_time;
- new_rate = base_rate;
- }
- else {
- if (diff_time > 4000) {
- /* 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);
- }
-
- /* recording behind playback, we need to slowly adjust the rate to match */
- //new_rate = base_rate + ((pa_usec_to_bytes (diff_time, &u->source_output->sample_spec) / fs) * PA_USEC_PER_SEC) / u->adjust_time;
-
- /* assume equal samplerates for now */
- new_rate = base_rate;
- }
-
- /* make sure we don't make too big adjustements because that sounds horrible */
- if (new_rate > base_rate * 1.1 || new_rate < base_rate * 0.9)
- new_rate = base_rate;
-
- if (new_rate != old_rate) {
- pa_log_info("Old rate %lu Hz, new rate %lu Hz", (unsigned long) old_rate, (unsigned long) new_rate);
-
- pa_sink_input_set_rate(u->sink_input, new_rate);
- }
-
-done:
- pa_core_rttime_restart(u->core, u->time_event, pa_rtclock_now() + u->adjust_time);
-}
-
-/* Called from source I/O thread context */
-static int source_process_msg_cb(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
- struct userdata *u = PA_SOURCE(o)->userdata;
-
- switch (code) {
-
- case PA_SOURCE_MESSAGE_GET_LATENCY:
-
- /* The source is _put() before the source output is, so let's
- * make sure we don't access it in that time. Also, the
- * source output is first shut down, the source second. */
- if (!PA_SOURCE_IS_LINKED(u->source->thread_info.state) ||
- !PA_SOURCE_OUTPUT_IS_LINKED(u->source_output->thread_info.state)) {
- *((pa_usec_t*) data) = 0;
- return 0;
- }
-
- *((pa_usec_t*) data) =
-
- /* Get the latency of the master source */
- pa_source_get_latency_within_thread(u->source_output->source) +
- /* Add the latency internal to our source output on top */
- pa_bytes_to_usec(pa_memblockq_get_length(u->source_output->thread_info.delay_memblockq), &u->source_output->source->sample_spec) +
- /* and the buffering we do on the source */
- pa_bytes_to_usec(u->blocksize, &u->source_output->source->sample_spec);
-
- return 0;
-
- }
-
- return pa_source_process_msg(o, code, data, offset, chunk);
-}
-
-/* Called from sink I/O thread context */
-static int sink_process_msg_cb(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
- struct userdata *u = PA_SINK(o)->userdata;
-
- switch (code) {
-
- case PA_SINK_MESSAGE_GET_LATENCY:
-
- /* The sink is _put() before the sink input is, so let's
- * make sure we don't access it in that time. Also, the
- * sink input is first shut down, the sink second. */
- if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) ||
- !PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state)) {
- *((pa_usec_t*) data) = 0;
- return 0;
- }
-
- *((pa_usec_t*) data) =
-
- /* Get the latency of the master sink */
- pa_sink_get_latency_within_thread(u->sink_input->sink) +
-
- /* Add the latency internal to our sink input on top */
- pa_bytes_to_usec(pa_memblockq_get_length(u->sink_input->thread_info.render_memblockq), &u->sink_input->sink->sample_spec);
-
- return 0;
- }
-
- return pa_sink_process_msg(o, code, data, offset, chunk);
-}
-
-
-/* Called from main context */
-static int source_set_state_cb(pa_source *s, pa_source_state_t state) {
- struct userdata *u;
-
- pa_source_assert_ref(s);
- pa_assert_se(u = s->userdata);
-
- if (!PA_SOURCE_IS_LINKED(state) ||
- !PA_SOURCE_OUTPUT_IS_LINKED(pa_source_output_get_state(u->source_output)))
- return 0;
-
- pa_log_debug("Source state %d", state);
-
- if (state == PA_SOURCE_RUNNING) {
- pa_atomic_store (&u->source_active, 1);
- pa_atomic_store (&u->request_resync, 1);
- pa_source_output_cork(u->source_output, FALSE);
- } else if (state == PA_SOURCE_SUSPENDED) {
- pa_atomic_store (&u->source_active, 0);
- pa_source_output_cork(u->source_output, TRUE);
- }
- return 0;
-}
-
-/* Called from main context */
-static int sink_set_state_cb(pa_sink *s, pa_sink_state_t state) {
- struct userdata *u;
-
- pa_sink_assert_ref(s);
- pa_assert_se(u = s->userdata);
-
- if (!PA_SINK_IS_LINKED(state) ||
- !PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(u->sink_input)))
- return 0;
-
- pa_log_debug("Sink state %d", state);
-
- if (state == PA_SINK_RUNNING) {
- pa_atomic_store (&u->sink_active, 1);
- pa_atomic_store (&u->request_resync, 1);
- pa_sink_input_cork(u->sink_input, FALSE);
- } else if (state == PA_SINK_SUSPENDED) {
- pa_atomic_store (&u->sink_active, 0);
- pa_sink_input_cork(u->sink_input, TRUE);
- }
- return 0;
-}
-
-/* Called from I/O thread context */
-static void source_update_requested_latency_cb(pa_source *s) {
- struct userdata *u;
-
- pa_source_assert_ref(s);
- pa_assert_se(u = s->userdata);
-
- if (!PA_SOURCE_IS_LINKED(u->source->thread_info.state) ||
- !PA_SOURCE_OUTPUT_IS_LINKED(u->source_output->thread_info.state))
- return;
-
- pa_log_debug("Source update requested latency");
-
- /* Just hand this one over to the master source */
- pa_source_output_set_requested_latency_within_thread(
- u->source_output,
- pa_source_get_requested_latency_within_thread(s));
-}
-
-/* Called from I/O thread context */
-static void sink_update_requested_latency_cb(pa_sink *s) {
- struct userdata *u;
-
- pa_sink_assert_ref(s);
- pa_assert_se(u = s->userdata);
-
- if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) ||
- !PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state))
- return;
-
- pa_log_debug("Sink update requested latency");
-
- /* Just hand this one over to the master sink */
- pa_sink_input_set_requested_latency_within_thread(
- u->sink_input,
- pa_sink_get_requested_latency_within_thread(s));
-}
-
-/* Called from I/O thread context */
-static void sink_request_rewind_cb(pa_sink *s) {
- struct userdata *u;
-
- pa_sink_assert_ref(s);
- pa_assert_se(u = s->userdata);
-
- if (!PA_SINK_IS_LINKED(u->sink->thread_info.state) ||
- !PA_SINK_INPUT_IS_LINKED(u->sink_input->thread_info.state))
- return;
-
- pa_log_debug("Sink request rewind %lld", (long long) s->thread_info.rewind_nbytes);
-
- /* Just hand this one over to the master sink */
- pa_sink_input_request_rewind(u->sink_input,
- s->thread_info.rewind_nbytes, TRUE, FALSE, FALSE);
-}
-
-/* Called from main context */
-static void source_set_volume_cb(pa_source *s) {
- struct userdata *u;
-
- pa_source_assert_ref(s);
- pa_assert_se(u = s->userdata);
-
- if (!PA_SOURCE_IS_LINKED(pa_source_get_state(s)) ||
- !PA_SOURCE_OUTPUT_IS_LINKED(pa_source_output_get_state(u->source_output)))
- return;
-
- /* FIXME, no volume control in source_output, set volume at the master */
- pa_source_set_volume(u->source_output->source, &s->volume, TRUE);
-}
-
-/* Called from main context */
-static void sink_set_volume_cb(pa_sink *s) {
- struct userdata *u;
-
- pa_sink_assert_ref(s);
- pa_assert_se(u = s->userdata);
-
- if (!PA_SINK_IS_LINKED(pa_sink_get_state(s)) ||
- !PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(u->sink_input)))
- return;
-
- pa_sink_input_set_volume(u->sink_input, &s->real_volume, s->save_volume, TRUE);
-}
-
-static void source_get_volume_cb(pa_source *s) {
- struct userdata *u;
-
- pa_source_assert_ref(s);
- pa_assert_se(u = s->userdata);
-
- if (!PA_SOURCE_IS_LINKED(pa_source_get_state(s)) ||
- !PA_SOURCE_OUTPUT_IS_LINKED(pa_source_output_get_state(u->source_output)))
- return;
-
- /* FIXME, no volume control in source_output, get the info from the master */
- pa_source_get_volume(u->source_output->source, TRUE);
-
- if (pa_cvolume_equal(&s->volume,&u->source_output->source->volume))
- /* no change */
- return;
-
- s->volume = u->source_output->source->volume;
- pa_source_set_soft_volume(s, NULL);
-}
-
-
-/* Called from main context */
-static void source_set_mute_cb(pa_source *s) {
- struct userdata *u;
-
- pa_source_assert_ref(s);
- pa_assert_se(u = s->userdata);
-
- if (!PA_SOURCE_IS_LINKED(pa_source_get_state(s)) ||
- !PA_SOURCE_OUTPUT_IS_LINKED(pa_source_output_get_state(u->source_output)))
- return;
-
- /* FIXME, no volume control in source_output, set mute at the master */
- pa_source_set_mute(u->source_output->source, TRUE, TRUE);
-}
-
-/* Called from main context */
-static void sink_set_mute_cb(pa_sink *s) {
- struct userdata *u;
-
- pa_sink_assert_ref(s);
- pa_assert_se(u = s->userdata);
-
- if (!PA_SINK_IS_LINKED(pa_sink_get_state(s)) ||
- !PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(u->sink_input)))
- return;
-
- pa_sink_input_set_mute(u->sink_input, s->muted, s->save_muted);
-}
-
-/* Called from main context */
-static void source_get_mute_cb(pa_source *s) {
- struct userdata *u;
-
- pa_source_assert_ref(s);
- pa_assert_se(u = s->userdata);
-
- if (!PA_SOURCE_IS_LINKED(pa_source_get_state(s)) ||
- !PA_SOURCE_OUTPUT_IS_LINKED(pa_source_output_get_state(u->source_output)))
- return;
-
- /* FIXME, no volume control in source_output, get the info from the master */
- pa_source_get_mute(u->source_output->source, TRUE);
-}
-
-/* must be called from the input thread context */
-static void apply_diff_time(struct userdata *u, int64_t diff_time) {
- int64_t diff;
-
- if (diff_time < 0) {
- diff = pa_usec_to_bytes (-diff_time, &u->source_output->sample_spec);
-
- if (diff > 0) {
- pa_log_info("Playback after capture (%lld), drop sink %lld", (long long) diff_time, (long long) diff);
-
- /* go forwards on the read side */
- pa_memblockq_drop(u->sink_memblockq, diff);
- }
- } else if (diff_time > 0) {
- diff = pa_usec_to_bytes (diff_time, &u->source_output->sample_spec);
-
- if (diff > 0) {
- pa_log_info("playback too far ahead (%lld), drop source %lld", (long long) diff_time, (long long) diff);
-
- /* go back on the read side */
- pa_memblockq_rewind(u->sink_memblockq, diff);
- }
- }
-}
-
-/* must be called from the input thread */
-static void do_resync(struct userdata *u) {
- int64_t diff_time;
- struct snapshot latency_snapshot;
-
- pa_log("Doing resync");
-
- /* update our snapshot */
- source_output_snapshot_within_thread(u, &latency_snapshot);
- pa_asyncmsgq_send(u->sink_input->sink->asyncmsgq, PA_MSGOBJECT(u->sink_input), SINK_INPUT_MESSAGE_LATENCY_SNAPSHOT, &latency_snapshot, 0, NULL);
-
- /* calculate drift between capture and playback */
- diff_time = calc_diff(u, &latency_snapshot);
-
- /* and adjust for the drift */
- apply_diff_time(u, diff_time);
-}
-
-/* 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;
-
- pa_source_output_assert_ref(o);
- pa_source_output_assert_io_context(o);
- pa_assert_se(u = o->userdata);
-
- if (!PA_SOURCE_OUTPUT_IS_LINKED(pa_source_output_get_state(u->source_output))) {
- pa_log("push when no link?");
- return;
- }
-
- /* handle queued messages */
- u->in_push = TRUE;
- while (pa_asyncmsgq_process_one(u->asyncmsgq) > 0)
- ;
- u->in_push = FALSE;
-
- 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);
-
- while (rlen >= u->blocksize) {
- pa_memchunk rchunk, pchunk;
-
- /* take fixed block from recorded samples */
- pa_memblockq_peek_fixed_size(u->source_memblockq, u->blocksize, &rchunk);
-
- if (plen > u->blocksize) {
- uint8_t *rdata, *pdata, *cdata;
- pa_memchunk cchunk;
-
- /* take fixed block from played samples */
- pa_memblockq_peek_fixed_size(u->sink_memblockq, u->blocksize, &pchunk);
-
- rdata = pa_memblock_acquire(rchunk.memblock);
- rdata += rchunk.index;
- pdata = pa_memblock_acquire(pchunk.memblock);
- pdata += pchunk.index;
-
- cchunk.index = 0;
- cchunk.length = u->blocksize;
- cchunk.memblock = pa_memblock_new(u->source->core->mempool, cchunk.length);
- cdata = pa_memblock_acquire(cchunk.memblock);
-
- /* perform echo cancelation */
- speex_echo_cancellation(u->echo_state, (const spx_int16_t *) rdata,
- (const spx_int16_t *) pdata, (spx_int16_t *) cdata);
-
- if (u->save_aec) {
- if (u->captured_file)
- fwrite(rdata, 1, u->blocksize, u->captured_file);
- if (u->played_file)
- fwrite(pdata, 1, u->blocksize, u->played_file);
- if (u->canceled_file)
- fwrite(cdata, 1, u->blocksize, u->canceled_file);
- pa_log_debug("AEC frame saved.");
- }
-
- pa_memblock_release(cchunk.memblock);
- pa_memblock_release(pchunk.memblock);
- pa_memblock_release(rchunk.memblock);
-
- /* drop consumed sink samples */
- pa_memblockq_drop(u->sink_memblockq, u->blocksize);
- pa_memblock_unref(pchunk.memblock);
-
- pa_memblock_unref(rchunk.memblock);
- /* the filtered samples now become the samples from our
- * source */
- rchunk = cchunk;
-
- plen -= u->blocksize;
- } else {
- /* not enough played samples to perform echo cancelation,
- * drop what we have */
- pa_memblockq_drop(u->sink_memblockq, u->blocksize - plen);
- plen = 0;
- }
-
- /* forward the (echo-canceled) data to the virtual source */
- pa_source_post(u->source, &rchunk);
- pa_memblock_unref(rchunk.memblock);
-
- pa_memblockq_drop(u->source_memblockq, u->blocksize);
-
- rlen -= u->blocksize;
- }
-}
-
-/* Called from I/O thread context */
-static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk) {
- struct userdata *u;
-
- pa_sink_input_assert_ref(i);
- pa_assert(chunk);
- pa_assert_se(u = i->userdata);
-
- if (u->sink->thread_info.rewind_requested)
- pa_sink_process_rewind(u->sink, 0);
-
- pa_sink_render_full(u->sink, nbytes, chunk);
-
- if (i->thread_info.underrun_for > 0) {
- pa_log_debug("Handling end of underrun.");
- pa_atomic_store (&u->request_resync, 1);
- }
-
- /* let source thread handle the chunk. pass the sample count as well so that
- * the source IO thread can update the right variables. */
- pa_asyncmsgq_post(u->asyncmsgq, PA_MSGOBJECT(u->source_output), SOURCE_OUTPUT_MESSAGE_POST,
- NULL, 0, chunk, NULL);
- u->send_counter += chunk->length;
-
- return 0;
-}
-
-/* Called from input thread context */
-static void source_output_process_rewind_cb(pa_source_output *o, size_t nbytes) {
- struct userdata *u;
-
- pa_source_output_assert_ref(o);
- pa_source_output_assert_io_context(o);
- pa_assert_se(u = o->userdata);
-
- pa_source_process_rewind(u->source, nbytes);
-
- /* go back on read side, we need to use older sink data for this */
- pa_memblockq_rewind(u->sink_memblockq, nbytes);
-
- /* manipulate write index */
- pa_memblockq_seek(u->source_memblockq, -nbytes, PA_SEEK_RELATIVE, TRUE);
-
- pa_log_debug("Source rewind (%lld) %lld", (long long) nbytes,
- (long long) pa_memblockq_get_length (u->source_memblockq));
-}
-
-/* Called from I/O thread context */
-static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) {
- struct userdata *u;
- size_t amount = 0;
-
- pa_sink_input_assert_ref(i);
- pa_assert_se(u = i->userdata);
-
- pa_log_debug("Sink process rewind %lld", (long long) nbytes);
-
- if (u->sink->thread_info.rewind_nbytes > 0) {
- amount = PA_MIN(u->sink->thread_info.rewind_nbytes, nbytes);
- u->sink->thread_info.rewind_nbytes = 0;
- }
-
- pa_sink_process_rewind(u->sink, amount);
-
- pa_asyncmsgq_post(u->asyncmsgq, PA_MSGOBJECT(u->source_output), SOURCE_OUTPUT_MESSAGE_REWIND, NULL, (int64_t) nbytes, NULL, NULL);
- u->send_counter -= nbytes;
-}
-
-static void source_output_snapshot_within_thread(struct userdata *u, struct snapshot *snapshot) {
- size_t delay, rlen, plen;
- pa_usec_t now, latency;
-
- now = pa_rtclock_now();
- latency = pa_source_get_latency_within_thread(u->source_output->source);
- delay = pa_memblockq_get_length(u->source_output->thread_info.delay_memblockq);
-
- delay = (u->source_output->thread_info.resampler ? pa_resampler_request(u->source_output->thread_info.resampler, delay) : delay);
- rlen = pa_memblockq_get_length(u->source_memblockq);
- plen = pa_memblockq_get_length(u->sink_memblockq);
-
- snapshot->source_now = now;
- snapshot->source_latency = latency;
- snapshot->source_delay = delay;
- snapshot->recv_counter = u->recv_counter;
- snapshot->rlen = rlen;
- snapshot->plen = plen;
-}
-
-
-/* Called from output thread context */
-static int source_output_process_msg_cb(pa_msgobject *obj, int code, void *data, int64_t offset, pa_memchunk *chunk) {
- struct userdata *u = PA_SOURCE_OUTPUT(obj)->userdata;
-
- switch (code) {
-
- case SOURCE_OUTPUT_MESSAGE_POST:
-
- pa_source_output_assert_io_context(u->source_output);
-
- if (PA_SOURCE_IS_OPENED(u->source_output->source->thread_info.state))
- pa_memblockq_push_align(u->sink_memblockq, chunk);
- else
- pa_memblockq_flush_write(u->sink_memblockq, TRUE);
-
- u->recv_counter += (int64_t) chunk->length;
-
- return 0;
-
- case SOURCE_OUTPUT_MESSAGE_REWIND:
- pa_source_output_assert_io_context(u->source_output);
-
- /* manipulate write index, never go past what we have */
- if (PA_SOURCE_IS_OPENED(u->source_output->source->thread_info.state))
- pa_memblockq_seek(u->sink_memblockq, -offset, PA_SEEK_RELATIVE, TRUE);
- else
- pa_memblockq_flush_write(u->sink_memblockq, TRUE);
-
- pa_log_debug("Sink rewind (%lld)", (long long) offset);
-
- u->recv_counter -= offset;
-
- return 0;
-
- case SOURCE_OUTPUT_MESSAGE_LATENCY_SNAPSHOT: {
- struct snapshot *snapshot = (struct snapshot *) data;
-
- source_output_snapshot_within_thread(u, snapshot);
- return 0;
- }
-
- case SOURCE_OUTPUT_MESSAGE_APPLY_DIFF_TIME:
- apply_diff_time(u, offset);
- return 0;
-
- }
-
- return pa_source_output_process_msg(obj, code, data, offset, chunk);
-}
-
-static int sink_input_process_msg_cb(pa_msgobject *obj, int code, void *data, int64_t offset, pa_memchunk *chunk) {
- struct userdata *u = PA_SINK_INPUT(obj)->userdata;
-
- switch (code) {
-
- case SINK_INPUT_MESSAGE_LATENCY_SNAPSHOT: {
- size_t delay;
- pa_usec_t now, latency;
- struct snapshot *snapshot = (struct snapshot *) data;
-
- pa_sink_input_assert_io_context(u->sink_input);
-
- now = pa_rtclock_now();
- latency = pa_sink_get_latency_within_thread(u->sink_input->sink);
- delay = pa_memblockq_get_length(u->sink_input->thread_info.render_memblockq);
-
- delay = (u->sink_input->thread_info.resampler ? pa_resampler_request(u->sink_input->thread_info.resampler, delay) : delay);
-
- snapshot->sink_now = now;
- snapshot->sink_latency = latency;
- snapshot->sink_delay = delay;
- snapshot->send_counter = u->send_counter;
- return 0;
- }
- }
-
- return pa_sink_input_process_msg(obj, code, data, offset, chunk);
-}
-
-/* Called from I/O thread context */
-static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) {
- struct userdata *u;
-
- pa_sink_input_assert_ref(i);
- pa_assert_se(u = i->userdata);
-
- pa_log_debug("Sink input update max rewind %lld", (long long) nbytes);
-
- pa_sink_set_max_rewind_within_thread(u->sink, nbytes);
-}
-
-/* Called from I/O thread context */
-static void source_output_update_max_rewind_cb(pa_source_output *o, size_t nbytes) {
- struct userdata *u;
-
- pa_source_output_assert_ref(o);
- pa_assert_se(u = o->userdata);
-
- pa_log_debug("Source output update max rewind %lld", (long long) nbytes);
-
- pa_source_set_max_rewind_within_thread(u->source, nbytes);
-}
-
-/* Called from I/O thread context */
-static void sink_input_update_max_request_cb(pa_sink_input *i, size_t nbytes) {
- struct userdata *u;
-
- pa_sink_input_assert_ref(i);
- pa_assert_se(u = i->userdata);
-
- pa_log_debug("Sink input update max rewind %lld", (long long) nbytes);
-
- pa_sink_set_max_request_within_thread(u->sink, nbytes);
-}
-
-/* Called from I/O thread context */
-static void sink_input_update_sink_requested_latency_cb(pa_sink_input *i) {
- struct userdata *u;
- pa_usec_t latency;
-
- pa_sink_input_assert_ref(i);
- pa_assert_se(u = i->userdata);
-
- latency = pa_sink_get_requested_latency_within_thread(i->sink);
-
- pa_log_debug("Sink input update requested latency %lld", (long long) latency);
-}
-
-/* Called from I/O thread context */
-static void source_output_update_source_requested_latency_cb(pa_source_output *o) {
- struct userdata *u;
- pa_usec_t latency;
-
- pa_source_output_assert_ref(o);
- pa_assert_se(u = o->userdata);
-
- latency = pa_source_get_requested_latency_within_thread(o->source);
-
- pa_log_debug("source output update requested latency %lld", (long long) latency);
-}
-
-/* Called from I/O thread context */
-static void sink_input_update_sink_latency_range_cb(pa_sink_input *i) {
- struct userdata *u;
-
- pa_sink_input_assert_ref(i);
- pa_assert_se(u = i->userdata);
-
- pa_log_debug("Sink input update latency range %lld %lld",
- (long long) i->sink->thread_info.min_latency,
- (long long) i->sink->thread_info.max_latency);
-
- pa_sink_set_latency_range_within_thread(u->sink, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency);
-}
-
-/* Called from I/O thread context */
-static void source_output_update_source_latency_range_cb(pa_source_output *o) {
- struct userdata *u;
-
- pa_source_output_assert_ref(o);
- pa_assert_se(u = o->userdata);
-
- pa_log_debug("Source output update latency range %lld %lld",
- (long long) o->source->thread_info.min_latency,
- (long long) o->source->thread_info.max_latency);
-
- pa_source_set_latency_range_within_thread(u->source, o->source->thread_info.min_latency, o->source->thread_info.max_latency);
-}
-
-/* Called from I/O thread context */
-static void sink_input_update_sink_fixed_latency_cb(pa_sink_input *i) {
- struct userdata *u;
-
- pa_sink_input_assert_ref(i);
- pa_assert_se(u = i->userdata);
-
- pa_log_debug("Sink input update fixed latency %lld",
- (long long) i->sink->thread_info.fixed_latency);
-
- pa_sink_set_fixed_latency_within_thread(u->sink, i->sink->thread_info.fixed_latency);
-}
-
-/* Called from I/O thread context */
-static void source_output_update_source_fixed_latency_cb(pa_source_output *o) {
- struct userdata *u;
-
- pa_source_output_assert_ref(o);
- pa_assert_se(u = o->userdata);
-
- pa_log_debug("Source output update fixed latency %lld",
- (long long) o->source->thread_info.fixed_latency);
-
- pa_source_set_fixed_latency_within_thread(u->source, o->source->thread_info.fixed_latency);
-}
-
-/* Called from output thread context */
-static void source_output_attach_cb(pa_source_output *o) {
- struct userdata *u;
-
- pa_source_output_assert_ref(o);
- pa_source_output_assert_io_context(o);
- pa_assert_se(u = o->userdata);
-
- pa_source_set_rtpoll(u->source, o->source->thread_info.rtpoll);
- pa_source_set_latency_range_within_thread(u->source, o->source->thread_info.min_latency, o->source->thread_info.max_latency);
- pa_source_set_fixed_latency_within_thread(u->source, o->source->thread_info.fixed_latency);
- pa_source_set_max_rewind_within_thread(u->source, pa_source_output_get_max_rewind(o));
-
- pa_log_debug("Source output %p attach", o);
-
- pa_source_attach_within_thread(u->source);
-
- u->rtpoll_item_read = pa_rtpoll_item_new_asyncmsgq_read(
- o->source->thread_info.rtpoll,
- PA_RTPOLL_LATE,
- u->asyncmsgq);
-}
-
-/* Called from I/O thread context */
-static void sink_input_attach_cb(pa_sink_input *i) {
- struct userdata *u;
-
- pa_sink_input_assert_ref(i);
- pa_assert_se(u = i->userdata);
-
- pa_sink_set_rtpoll(u->sink, i->sink->thread_info.rtpoll);
- pa_sink_set_latency_range_within_thread(u->sink, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency);
-
- /* (8.1) IF YOU NEED A FIXED BLOCK SIZE ADD THE LATENCY FOR ONE
- * BLOCK MINUS ONE SAMPLE HERE. SEE (7) */
- pa_sink_set_fixed_latency_within_thread(u->sink, i->sink->thread_info.fixed_latency);
-
- /* (8.2) IF YOU NEED A FIXED BLOCK SIZE ROUND
- * pa_sink_input_get_max_request(i) UP TO MULTIPLES OF IT
- * HERE. SEE (6) */
- pa_sink_set_max_request_within_thread(u->sink, pa_sink_input_get_max_request(i));
- pa_sink_set_max_rewind_within_thread(u->sink, pa_sink_input_get_max_rewind(i));
-
- pa_log_debug("Sink input %p attach", i);
-
- u->rtpoll_item_write = pa_rtpoll_item_new_asyncmsgq_write(
- i->sink->thread_info.rtpoll,
- PA_RTPOLL_LATE,
- u->asyncmsgq);
-
- pa_sink_attach_within_thread(u->sink);
-}
-
-
-/* Called from output thread context */
-static void source_output_detach_cb(pa_source_output *o) {
- struct userdata *u;
-
- pa_source_output_assert_ref(o);
- pa_source_output_assert_io_context(o);
- pa_assert_se(u = o->userdata);
-
- pa_source_detach_within_thread(u->source);
- pa_source_set_rtpoll(u->source, NULL);
-
- pa_log_debug("Source output %p detach", o);
-
- if (u->rtpoll_item_read) {
- pa_rtpoll_item_free(u->rtpoll_item_read);
- u->rtpoll_item_read = NULL;
- }
-}
-
-/* Called from I/O thread context */
-static void sink_input_detach_cb(pa_sink_input *i) {
- struct userdata *u;
-
- pa_sink_input_assert_ref(i);
- pa_assert_se(u = i->userdata);
-
- pa_sink_detach_within_thread(u->sink);
-
- pa_sink_set_rtpoll(u->sink, NULL);
-
- pa_log_debug("Sink input %p detach", i);
-
- if (u->rtpoll_item_write) {
- pa_rtpoll_item_free(u->rtpoll_item_write);
- u->rtpoll_item_write = NULL;
- }
-}
-
-/* Called from output thread context */
-static void source_output_state_change_cb(pa_source_output *o, pa_source_output_state_t state) {
- struct userdata *u;
-
- pa_source_output_assert_ref(o);
- pa_source_output_assert_io_context(o);
- pa_assert_se(u = o->userdata);
-
- pa_log_debug("Source output %p state %d", o, state);
-}
-
-/* Called from IO thread context */
-static void sink_input_state_change_cb(pa_sink_input *i, pa_sink_input_state_t state) {
- struct userdata *u;
-
- pa_sink_input_assert_ref(i);
- pa_assert_se(u = i->userdata);
-
- pa_log_debug("Sink input %p state %d", i, state);
-
- /* If we are added for the first time, ask for a rewinding so that
- * we are heard right-away. */
- if (PA_SINK_INPUT_IS_LINKED(state) &&
- i->thread_info.state == PA_SINK_INPUT_INIT) {
- pa_log_debug("Requesting rewind due to state change.");
- pa_sink_input_request_rewind(i, 0, FALSE, TRUE, TRUE);
- }
-}
-
-/* Called from main thread */
-static void source_output_kill_cb(pa_source_output *o) {
- struct userdata *u;
-
- pa_source_output_assert_ref(o);
- pa_assert_ctl_context();
- pa_assert_se(u = o->userdata);
-
- /* The order here matters! We first kill the source output, followed
- * by the source. That means the source callbacks must be protected
- * against an unconnected source output! */
- pa_source_output_unlink(u->source_output);
- pa_source_unlink(u->source);
-
- pa_source_output_unref(u->source_output);
- u->source_output = NULL;
-
- pa_source_unref(u->source);
- u->source = NULL;
-
- pa_log_debug("Source output kill %p", o);
-
- pa_module_unload_request(u->module, TRUE);
-}
-
-/* Called from main context */
-static void sink_input_kill_cb(pa_sink_input *i) {
- struct userdata *u;
-
- pa_sink_input_assert_ref(i);
- pa_assert_se(u = i->userdata);
-
- /* The order here matters! We first kill the sink input, followed
- * by the sink. That means the sink callbacks must be protected
- * against an unconnected sink input! */
- pa_sink_input_unlink(u->sink_input);
- pa_sink_unlink(u->sink);
-
- pa_sink_input_unref(u->sink_input);
- u->sink_input = NULL;
-
- pa_sink_unref(u->sink);
- u->sink = NULL;
-
- pa_log_debug("Sink input kill %p", i);
-
- pa_module_unload_request(u->module, TRUE);
-}
-
-/* Called from main thread */
-static pa_bool_t source_output_may_move_to_cb(pa_source_output *o, pa_source *dest) {
- struct userdata *u;
-
- pa_source_output_assert_ref(o);
- pa_assert_ctl_context();
- pa_assert_se(u = o->userdata);
-
- return TRUE;
-}
-
-/* Called from main context */
-static pa_bool_t sink_input_may_move_to_cb(pa_sink_input *i, pa_sink *dest) {
- struct userdata *u;
-
- pa_sink_input_assert_ref(i);
- pa_assert_se(u = i->userdata);
-
- return u->sink != dest;
-}
-
-/* Called from main thread */
-static void source_output_moving_cb(pa_source_output *o, pa_source *dest) {
- struct userdata *u;
-
- pa_source_output_assert_ref(o);
- pa_assert_ctl_context();
- pa_assert_se(u = o->userdata);
-
- if (dest) {
- pa_source_set_asyncmsgq(u->source, dest->asyncmsgq);
- pa_source_update_flags(u->source, PA_SOURCE_LATENCY|PA_SOURCE_DYNAMIC_LATENCY, dest->flags);
- } else
- pa_source_set_asyncmsgq(u->source, NULL);
-
- if (u->source_auto_desc && dest) {
- const char *z;
- pa_proplist *pl;
-
- pl = pa_proplist_new();
- z = pa_proplist_gets(dest->proplist, PA_PROP_DEVICE_DESCRIPTION);
- pa_proplist_setf(pl, PA_PROP_DEVICE_DESCRIPTION, "Echo-Cancel Source %s on %s",
- pa_proplist_gets(u->source->proplist, "device.echo-cancel.name"), z ? z : dest->name);
-
- pa_source_update_proplist(u->source, PA_UPDATE_REPLACE, pl);
- pa_proplist_free(pl);
- }
-}
-
-/* Called from main context */
-static void sink_input_moving_cb(pa_sink_input *i, pa_sink *dest) {
- struct userdata *u;
-
- pa_sink_input_assert_ref(i);
- pa_assert_se(u = i->userdata);
-
- if (dest) {
- pa_sink_set_asyncmsgq(u->sink, dest->asyncmsgq);
- pa_sink_update_flags(u->sink, PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY, dest->flags);
- } else
- pa_sink_set_asyncmsgq(u->sink, NULL);
-
- if (u->sink_auto_desc && dest) {
- const char *z;
- pa_proplist *pl;
-
- pl = pa_proplist_new();
- z = pa_proplist_gets(dest->proplist, PA_PROP_DEVICE_DESCRIPTION);
- pa_proplist_setf(pl, PA_PROP_DEVICE_DESCRIPTION, "Echo-Cancel Sink %s on %s",
- pa_proplist_gets(u->sink->proplist, "device.echo-cancel.name"), z ? z : dest->name);
-
- pa_sink_update_proplist(u->sink, PA_UPDATE_REPLACE, pl);
- pa_proplist_free(pl);
- }
-}
-
-/* Called from main context */
-static void sink_input_volume_changed_cb(pa_sink_input *i) {
- struct userdata *u;
-
- pa_sink_input_assert_ref(i);
- pa_assert_se(u = i->userdata);
-
- pa_sink_volume_changed(u->sink, &i->volume);
-}
-
-/* Called from main context */
-static void sink_input_mute_changed_cb(pa_sink_input *i) {
- struct userdata *u;
-
- pa_sink_input_assert_ref(i);
- pa_assert_se(u = i->userdata);
-
- pa_sink_mute_changed(u->sink, i->muted);
-}
-
-
-int pa__init(pa_module*m) {
- struct userdata *u;
- pa_sample_spec ss;
- pa_channel_map map;
- pa_modargs *ma;
- pa_source *source_master=NULL;
- pa_sink *sink_master=NULL;
- pa_source_output_new_data source_output_data;
- pa_sink_input_new_data sink_input_data;
- pa_source_new_data source_data;
- pa_sink_new_data sink_data;
- pa_memchunk silence;
- int framelen, rate, y;
- uint32_t frame_size_ms, filter_size_ms;
- uint32_t adjust_time_sec;
-
- pa_assert(m);
-
- if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
- pa_log("Failed to parse module arguments.");
- goto fail;
- }
-
- if (!(source_master = pa_namereg_get(m->core, pa_modargs_get_value(ma, "source_master", NULL), PA_NAMEREG_SOURCE))) {
- pa_log("Master source not found");
- goto fail;
- }
- pa_assert(source_master);
-
- if (!(sink_master = pa_namereg_get(m->core, pa_modargs_get_value(ma, "sink_master", NULL), PA_NAMEREG_SINK))) {
- pa_log("Master sink not found");
- goto fail;
- }
- pa_assert(sink_master);
-
- frame_size_ms = DEFAULT_FRAME_SIZE_MS;
- if (pa_modargs_get_value_u32(ma, "frame_size_ms", &frame_size_ms) < 0 || frame_size_ms < 1 || frame_size_ms > 200) {
- pa_log("Invalid frame_size_ms specification");
- goto fail;
- }
-
- filter_size_ms = DEFAULT_FILTER_SIZE_MS;
- if (pa_modargs_get_value_u32(ma, "filter_size_ms", &filter_size_ms) < 0 || filter_size_ms < 1 || filter_size_ms > 2000) {
- pa_log("Invalid filter_size_ms specification");
- goto fail;
- }
-
- ss = source_master->sample_spec;
- ss.format = PA_SAMPLE_S16LE;
- map = source_master->channel_map;
- if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0) {
- pa_log("Invalid sample format specification or channel map");
- goto fail;
- }
-
- u = pa_xnew0(struct userdata, 1);
- if (!u) {
- pa_log("Failed to alloc userdata");
- goto fail;
- }
- u->core = m->core;
- u->module = m;
- m->userdata = u;
- u->frame_size_ms = frame_size_ms;
- rate = ss.rate;
- framelen = (rate * frame_size_ms) / 1000;
-
- /* framelen should be a power of 2, round down to nearest power of two */
- y = 1 << ((8 * sizeof (int)) - 2);
- while (y > framelen)
- y >>= 1;
- framelen = y;
-
- u->blocksize = framelen * pa_frame_size (&ss);
- pa_log_debug ("Using framelen %d, blocksize %lld, channels %d, rate %d", framelen, (long long) u->blocksize,
- ss.channels, ss.rate);
-
- 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");
- goto fail;
- }
-
- if (adjust_time_sec != DEFAULT_ADJUST_TIME_USEC / PA_USEC_PER_SEC)
- u->adjust_time = adjust_time_sec * PA_USEC_PER_SEC;
- else
- u->adjust_time = DEFAULT_ADJUST_TIME_USEC;
-
- u->save_aec = DEFAULT_SAVE_AEC;
- if (pa_modargs_get_value_u32(ma, "save_aec", &u->save_aec) < 0) {
- pa_log("Failed to parse save_aec value");
- goto fail;
- }
-
- u->asyncmsgq = pa_asyncmsgq_new(0);
- u->need_realign = TRUE;
- u->echo_state = speex_echo_state_init_mc (framelen, (rate * filter_size_ms) / 1000, ss.channels, ss.channels);
- speex_echo_ctl(u->echo_state, SPEEX_ECHO_SET_SAMPLING_RATE, &rate);
-
- /* Create source */
- pa_source_new_data_init(&source_data);
- source_data.driver = __FILE__;
- source_data.module = m;
- if (!(source_data.name = pa_xstrdup(pa_modargs_get_value(ma, "source_name", NULL))))
- source_data.name = pa_sprintf_malloc("%s.echo-cancel", source_master->name);
- pa_source_new_data_set_sample_spec(&source_data, &ss);
- pa_source_new_data_set_channel_map(&source_data, &map);
- pa_proplist_sets(source_data.proplist, PA_PROP_DEVICE_MASTER_DEVICE, source_master->name);
- pa_proplist_sets(source_data.proplist, PA_PROP_DEVICE_CLASS, "filter");
- pa_proplist_sets(source_data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "phone");
- pa_proplist_sets(source_data.proplist, "device.echo-cancel.name", source_data.name);
-
- if (pa_modargs_get_proplist(ma, "source_properties", source_data.proplist, PA_UPDATE_REPLACE) < 0) {
- pa_log("Invalid properties");
- pa_source_new_data_done(&source_data);
- goto fail;
- }
-
- if ((u->source_auto_desc = !pa_proplist_contains(source_data.proplist, PA_PROP_DEVICE_DESCRIPTION))) {
- const char *z;
-
- z = pa_proplist_gets(source_master->proplist, PA_PROP_DEVICE_DESCRIPTION);
- pa_proplist_setf(source_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Echo-Cancel Source %s on %s", source_data.name, z ? z : source_master->name);
- }
-
- u->source = pa_source_new(m->core, &source_data,
- PA_SOURCE_HW_MUTE_CTRL|PA_SOURCE_HW_VOLUME_CTRL|PA_SOURCE_DECIBEL_VOLUME|
- (source_master->flags & (PA_SOURCE_LATENCY|PA_SOURCE_DYNAMIC_LATENCY)));
- pa_source_new_data_done(&source_data);
-
- if (!u->source) {
- pa_log("Failed to create source.");
- goto fail;
- }
-
- u->source->parent.process_msg = source_process_msg_cb;
- u->source->set_state = source_set_state_cb;
- u->source->update_requested_latency = source_update_requested_latency_cb;
- u->source->set_volume = source_set_volume_cb;
- u->source->set_mute = source_set_mute_cb;
- u->source->get_volume = source_get_volume_cb;
- u->source->get_mute = source_get_mute_cb;
- u->source->userdata = u;
-
- pa_source_set_asyncmsgq(u->source, source_master->asyncmsgq);
-
- /* Create sink */
- pa_sink_new_data_init(&sink_data);
- sink_data.driver = __FILE__;
- sink_data.module = m;
- if (!(sink_data.name = pa_xstrdup(pa_modargs_get_value(ma, "sink_name", NULL))))
- sink_data.name = pa_sprintf_malloc("%s.echo-cancel", sink_master->name);
- pa_sink_new_data_set_sample_spec(&sink_data, &ss);
- pa_sink_new_data_set_channel_map(&sink_data, &map);
- pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_MASTER_DEVICE, sink_master->name);
- pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_CLASS, "filter");
- pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "phone");
- pa_proplist_sets(sink_data.proplist, "device.echo-cancel.name", sink_data.name);
-
- if (pa_modargs_get_proplist(ma, "sink_properties", sink_data.proplist, PA_UPDATE_REPLACE) < 0) {
- pa_log("Invalid properties");
- pa_sink_new_data_done(&sink_data);
- goto fail;
- }
-
- if ((u->sink_auto_desc = !pa_proplist_contains(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION))) {
- const char *z;
-
- z = pa_proplist_gets(sink_master->proplist, PA_PROP_DEVICE_DESCRIPTION);
- pa_proplist_setf(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Echo-Cancel Sink %s on %s", sink_data.name, z ? z : sink_master->name);
- }
-
- u->sink = pa_sink_new(m->core, &sink_data,
- PA_SINK_HW_MUTE_CTRL|PA_SINK_HW_VOLUME_CTRL|PA_SINK_DECIBEL_VOLUME|
- (sink_master->flags & (PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY)));
- pa_sink_new_data_done(&sink_data);
-
- if (!u->sink) {
- pa_log("Failed to create sink.");
- goto fail;
- }
-
- u->sink->parent.process_msg = sink_process_msg_cb;
- u->sink->set_state = sink_set_state_cb;
- u->sink->update_requested_latency = sink_update_requested_latency_cb;
- u->sink->request_rewind = sink_request_rewind_cb;
- u->sink->set_volume = sink_set_volume_cb;
- u->sink->set_mute = sink_set_mute_cb;
- u->sink->userdata = u;
-
- pa_sink_set_asyncmsgq(u->sink, sink_master->asyncmsgq);
-
- /* Create source output */
- pa_source_output_new_data_init(&source_output_data);
- source_output_data.driver = __FILE__;
- source_output_data.module = m;
- source_output_data.source = source_master;
- /* FIXME
- source_output_data.flags = PA_SOURCE_OUTPUT_DONT_INHIBIT_AUTO_SUSPEND; */
-
- pa_proplist_sets(source_output_data.proplist, PA_PROP_MEDIA_NAME, "Echo-Cancel Source Stream");
- pa_proplist_sets(source_output_data.proplist, PA_PROP_MEDIA_ROLE, "filter");
- pa_source_output_new_data_set_sample_spec(&source_output_data, &ss);
- pa_source_output_new_data_set_channel_map(&source_output_data, &map);
-
- pa_source_output_new(&u->source_output, m->core, &source_output_data);
- pa_source_output_new_data_done(&source_output_data);
-
- if (!u->source_output)
- goto fail;
-
- u->source_output->parent.process_msg = source_output_process_msg_cb;
- u->source_output->push = source_output_push_cb;
- u->source_output->process_rewind = source_output_process_rewind_cb;
- u->source_output->update_max_rewind = source_output_update_max_rewind_cb;
- u->source_output->update_source_requested_latency = source_output_update_source_requested_latency_cb;
- u->source_output->update_source_latency_range = source_output_update_source_latency_range_cb;
- u->source_output->update_source_fixed_latency = source_output_update_source_fixed_latency_cb;
- u->source_output->kill = source_output_kill_cb;
- u->source_output->attach = source_output_attach_cb;
- u->source_output->detach = source_output_detach_cb;
- u->source_output->state_change = source_output_state_change_cb;
- u->source_output->may_move_to = source_output_may_move_to_cb;
- u->source_output->moving = source_output_moving_cb;
- u->source_output->userdata = u;
-
- /* Create sink input */
- pa_sink_input_new_data_init(&sink_input_data);
- sink_input_data.driver = __FILE__;
- sink_input_data.module = m;
- sink_input_data.sink = sink_master;
- pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_NAME, "Echo-Cancel Sink Stream");
- pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_ROLE, "filter");
- pa_sink_input_new_data_set_sample_spec(&sink_input_data, &ss);
- pa_sink_input_new_data_set_channel_map(&sink_input_data, &map);
- sink_input_data.flags = PA_SINK_INPUT_VARIABLE_RATE;
-
- pa_sink_input_new(&u->sink_input, m->core, &sink_input_data);
- pa_sink_input_new_data_done(&sink_input_data);
-
- if (!u->sink_input)
- goto fail;
-
- u->sink_input->parent.process_msg = sink_input_process_msg_cb;
- u->sink_input->pop = sink_input_pop_cb;
- u->sink_input->process_rewind = sink_input_process_rewind_cb;
- u->sink_input->update_max_rewind = sink_input_update_max_rewind_cb;
- u->sink_input->update_max_request = sink_input_update_max_request_cb;
- u->sink_input->update_sink_requested_latency = sink_input_update_sink_requested_latency_cb;
- u->sink_input->update_sink_latency_range = sink_input_update_sink_latency_range_cb;
- u->sink_input->update_sink_fixed_latency = sink_input_update_sink_fixed_latency_cb;
- u->sink_input->kill = sink_input_kill_cb;
- u->sink_input->attach = sink_input_attach_cb;
- u->sink_input->detach = sink_input_detach_cb;
- u->sink_input->state_change = sink_input_state_change_cb;
- u->sink_input->may_move_to = sink_input_may_move_to_cb;
- u->sink_input->moving = sink_input_moving_cb;
- u->sink_input->volume_changed = sink_input_volume_changed_cb;
- u->sink_input->mute_changed = sink_input_mute_changed_cb;
- u->sink_input->userdata = u;
-
- pa_sink_input_get_silence(u->sink_input, &silence);
-
- u->source_memblockq = pa_memblockq_new(0, MEMBLOCKQ_MAXLENGTH, 0,
- pa_frame_size(&ss), 1, 1, 0, &silence);
- u->sink_memblockq = pa_memblockq_new(0, MEMBLOCKQ_MAXLENGTH, 0,
- pa_frame_size(&ss), 1, 1, 0, &silence);
-
- pa_memblock_unref(silence.memblock);
-
- if (!u->source_memblockq || !u->sink_memblockq) {
- pa_log("Failed to create memblockq.");
- goto fail;
- }
-
- if (u->adjust_time > 0)
- u->time_event = pa_core_rttime_new(m->core, pa_rtclock_now() + u->adjust_time, time_callback, u);
-
- if (u->save_aec) {
- pa_log("Creating AEC files in /tmp");
- u->captured_file = fopen("/tmp/aec_rec.sw", "wb");
- if (u->captured_file == NULL)
- perror ("fopen failed");
- u->played_file = fopen("/tmp/aec_play.sw", "wb");
- if (u->played_file == NULL)
- perror ("fopen failed");
- u->canceled_file = fopen("/tmp/aec_out.sw", "wb");
- if (u->canceled_file == NULL)
- perror ("fopen failed");
- }
-
- pa_sink_put(u->sink);
- pa_source_put(u->source);
-
- pa_sink_input_put(u->sink_input);
- pa_source_output_put(u->source_output);
-
- pa_modargs_free(ma);
-
- return 0;
-
- fail:
- if (ma)
- pa_modargs_free(ma);
-
- pa__done(m);
-
- return -1;
-}
-
-int pa__get_n_used(pa_module *m) {
- struct userdata *u;
-
- pa_assert(m);
- pa_assert_se(u = m->userdata);
-
- return pa_sink_linked_by(u->sink) + pa_source_linked_by(u->source);
-}
-
-void pa__done(pa_module*m) {
- struct userdata *u;
-
- pa_assert(m);
-
- if (!(u = m->userdata))
- return;
-
- /* See comments in source_output_kill_cb() above regarding
- * destruction order! */
-
- if (u->source_output)
- pa_source_output_unlink(u->source_output);
- if (u->sink_input)
- pa_sink_input_unlink(u->sink_input);
-
- if (u->source)
- pa_source_unlink(u->source);
- if (u->sink)
- pa_sink_unlink(u->sink);
-
- if (u->source_output)
- pa_source_output_unref(u->source_output);
- if (u->sink_input)
- pa_sink_input_unref(u->sink_input);
-
- if (u->source)
- pa_source_unref(u->source);
- if (u->sink)
- pa_sink_unref(u->sink);
-
- if (u->time_event)
- u->core->mainloop->time_free(u->time_event);
-
- if (u->source_memblockq)
- pa_memblockq_free(u->source_memblockq);
- if (u->sink_memblockq)
- pa_memblockq_free(u->sink_memblockq);
-
- if (u->echo_state)
- speex_echo_state_destroy (u->echo_state);
-
- if (u->asyncmsgq)
- pa_asyncmsgq_unref(u->asyncmsgq);
-
- pa_xfree(u);
-}
commit 57c598393780e27915c84ff167d82fc85803ffe2
Author: Arun Raghavan <arun.raghavan at collabora.co.uk>
Date: Mon Sep 6 15:51:20 2010 +0530
echo-cancel: Split out speex code from the core module
This splits out the echo-cancelling core from the PA-specific bits to
allow us to plug in other echo-cancellation engines.
diff --git a/src/Makefile.am b/src/Makefile.am
index 49465cc..242532c 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1605,7 +1605,7 @@ module_suspend_on_idle_la_LIBADD = $(AM_LIBADD) libpulsecore- at PA_MAJORMINORMICRO
module_suspend_on_idle_la_CFLAGS = $(AM_CFLAGS)
# echo-cancel module
-module_echo_cancel_la_SOURCES = modules/echo-cancel/module-echo-cancel.c
+module_echo_cancel_la_SOURCES = modules/echo-cancel/module-echo-cancel.c modules/echo-cancel/speex.c
module_echo_cancel_la_LDFLAGS = $(MODULE_LDFLAGS)
module_echo_cancel_la_LIBADD = $(AM_LIBADD) libpulsecore- at PA_MAJORMINORMICRO@.la libpulsecommon- at PA_MAJORMINORMICRO@.la libpulse.la $(LIBSPEEX_LIBS)
module_echo_cancel_la_CFLAGS = $(AM_CFLAGS) $(LIBSPEEX_CFLAGS)
diff --git a/src/modules/echo-cancel/echo-cancel.h b/src/modules/echo-cancel/echo-cancel.h
new file mode 100644
index 0000000..bb6c0ed
--- /dev/null
+++ b/src/modules/echo-cancel/echo-cancel.h
@@ -0,0 +1,61 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2010 Arun Raghavan <arun.raghavan at collabora.co.uk>
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulse/sample.h>
+#include <pulse/channelmap.h>
+#include <pulsecore/macro.h>
+
+#include <speex/speex_echo.h>
+
+/* Common data structures */
+
+typedef struct pa_echo_canceller_params pa_echo_canceller_params;
+
+struct pa_echo_canceller_params {
+ union {
+ struct {
+ uint32_t blocksize;
+ SpeexEchoState *state;
+ } speex;
+ /* each canceller-specific structure goes here */
+ } priv;
+};
+
+typedef struct pa_echo_canceller pa_echo_canceller;
+
+struct pa_echo_canceller {
+ pa_bool_t (*init) (pa_echo_canceller *ec, pa_sample_spec ss, pa_channel_map map, uint32_t filter_size_ms, uint32_t frame_size_ms);
+ void (*run) (pa_echo_canceller *ec, uint8_t *rec, uint8_t *play, uint8_t *out);
+ void (*done) (pa_echo_canceller *ec);
+ uint32_t (*get_block_size) (pa_echo_canceller *ec);
+
+ pa_echo_canceller_params params;
+};
+
+/* Speex canceller functions */
+pa_bool_t pa_speex_ec_init(pa_echo_canceller *ec, pa_sample_spec ss, pa_channel_map map, uint32_t filter_size_ms, uint32_t frame_size_ms);
+void pa_speex_ec_run(pa_echo_canceller *ec, uint8_t *rec, uint8_t *play, uint8_t *out);
+void pa_speex_ec_done(pa_echo_canceller *ec);
+uint32_t pa_speex_ec_get_block_size(pa_echo_canceller *ec);
diff --git a/src/modules/echo-cancel/module-echo-cancel.c b/src/modules/echo-cancel/module-echo-cancel.c
index d6c2ca1..2e72434 100644
--- a/src/modules/echo-cancel/module-echo-cancel.c
+++ b/src/modules/echo-cancel/module-echo-cancel.c
@@ -33,7 +33,7 @@
#include <stdio.h>
#include <math.h>
-#include <speex/speex_echo.h>
+#include "echo-cancel.h"
#include <pulse/xmalloc.h>
#include <pulse/i18n.h>
@@ -80,6 +80,23 @@ PA_MODULE_USAGE(
"save_aec=<save AEC data in /tmp> "
));
+/* NOTE: Make sure the enum and ec_table are maintained in the correct order */
+enum {
+ PA_ECHO_CANCELLER_SPEEX,
+};
+
+#define DEFAULT_ECHO_CANCELLER PA_ECHO_CANCELLER_SPEEX
+
+static const pa_echo_canceller ec_table[] = {
+ {
+ /* Speex */
+ .init = pa_speex_ec_init,
+ .run = pa_speex_ec_run,
+ .done = pa_speex_ec_done,
+ .get_block_size = pa_speex_ec_get_block_size,
+ },
+};
+
/* should be between 10-20 ms */
#define DEFAULT_FRAME_SIZE_MS 20
/* should be between 100-500 ms */
@@ -140,9 +157,8 @@ struct userdata {
uint32_t frame_size_ms;
uint32_t save_aec;
- SpeexEchoState *echo_state;
+ pa_echo_canceller *ec;
- size_t blocksize;
pa_bool_t need_realign;
/* to wakeup the source I/O thread */
@@ -326,7 +342,7 @@ static int source_process_msg_cb(pa_msgobject *o, int code, void *data, int64_t
/* Add the latency internal to our source output on top */
pa_bytes_to_usec(pa_memblockq_get_length(u->source_output->thread_info.delay_memblockq), &u->source_output->source->sample_spec) +
/* and the buffering we do on the source */
- pa_bytes_to_usec(u->blocksize, &u->source_output->source->sample_spec);
+ pa_bytes_to_usec(u->ec->get_block_size(u->ec), &u->source_output->source->sample_spec);
return 0;
@@ -613,6 +629,7 @@ static void do_resync(struct userdata *u) {
static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk) {
struct userdata *u;
size_t rlen, plen;
+ uint32_t blocksize;
pa_source_output_assert_ref(o);
pa_source_output_assert_io_context(o);
@@ -638,18 +655,20 @@ static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk)
rlen = pa_memblockq_get_length(u->source_memblockq);
plen = pa_memblockq_get_length(u->sink_memblockq);
- while (rlen >= u->blocksize) {
+ blocksize = u->ec->get_block_size(u->ec);
+
+ while (rlen >= blocksize) {
pa_memchunk rchunk, pchunk;
/* take fixed block from recorded samples */
- pa_memblockq_peek_fixed_size(u->source_memblockq, u->blocksize, &rchunk);
+ pa_memblockq_peek_fixed_size(u->source_memblockq, blocksize, &rchunk);
- if (plen > u->blocksize) {
+ if (plen > blocksize) {
uint8_t *rdata, *pdata, *cdata;
pa_memchunk cchunk;
/* take fixed block from played samples */
- pa_memblockq_peek_fixed_size(u->sink_memblockq, u->blocksize, &pchunk);
+ pa_memblockq_peek_fixed_size(u->sink_memblockq, blocksize, &pchunk);
rdata = pa_memblock_acquire(rchunk.memblock);
rdata += rchunk.index;
@@ -657,21 +676,20 @@ static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk)
pdata += pchunk.index;
cchunk.index = 0;
- cchunk.length = u->blocksize;
+ cchunk.length = blocksize;
cchunk.memblock = pa_memblock_new(u->source->core->mempool, cchunk.length);
cdata = pa_memblock_acquire(cchunk.memblock);
/* perform echo cancelation */
- speex_echo_cancellation(u->echo_state, (const spx_int16_t *) rdata,
- (const spx_int16_t *) pdata, (spx_int16_t *) cdata);
+ u->ec->run(u->ec, rdata, pdata, cdata);
if (u->save_aec) {
if (u->captured_file)
- fwrite(rdata, 1, u->blocksize, u->captured_file);
+ fwrite(rdata, 1, blocksize, u->captured_file);
if (u->played_file)
- fwrite(pdata, 1, u->blocksize, u->played_file);
+ fwrite(pdata, 1, blocksize, u->played_file);
if (u->canceled_file)
- fwrite(cdata, 1, u->blocksize, u->canceled_file);
+ fwrite(cdata, 1, blocksize, u->canceled_file);
pa_log_debug("AEC frame saved.");
}
@@ -680,7 +698,7 @@ static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk)
pa_memblock_release(rchunk.memblock);
/* drop consumed sink samples */
- pa_memblockq_drop(u->sink_memblockq, u->blocksize);
+ pa_memblockq_drop(u->sink_memblockq, blocksize);
pa_memblock_unref(pchunk.memblock);
pa_memblock_unref(rchunk.memblock);
@@ -688,11 +706,11 @@ static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk)
* source */
rchunk = cchunk;
- plen -= u->blocksize;
+ plen -= blocksize;
} else {
/* not enough played samples to perform echo cancelation,
* drop what we have */
- pa_memblockq_drop(u->sink_memblockq, u->blocksize - plen);
+ pa_memblockq_drop(u->sink_memblockq, blocksize - plen);
plen = 0;
}
@@ -700,9 +718,9 @@ static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk)
pa_source_post(u->source, &rchunk);
pa_memblock_unref(rchunk.memblock);
- pa_memblockq_drop(u->source_memblockq, u->blocksize);
+ pa_memblockq_drop(u->source_memblockq, blocksize);
- rlen -= u->blocksize;
+ rlen -= blocksize;
}
}
@@ -1269,7 +1287,6 @@ int pa__init(pa_module*m) {
pa_source_new_data source_data;
pa_sink_new_data sink_data;
pa_memchunk silence;
- int framelen, rate, y;
uint32_t frame_size_ms, filter_size_ms;
uint32_t adjust_time_sec;
@@ -1321,18 +1338,16 @@ int pa__init(pa_module*m) {
u->module = m;
m->userdata = u;
u->frame_size_ms = frame_size_ms;
- rate = ss.rate;
- framelen = (rate * frame_size_ms) / 1000;
- /* framelen should be a power of 2, round down to nearest power of two */
- y = 1 << ((8 * sizeof (int)) - 2);
- while (y > framelen)
- y >>= 1;
- framelen = y;
-
- u->blocksize = framelen * pa_frame_size (&ss);
- pa_log_debug ("Using framelen %d, blocksize %lld, channels %d, rate %d", framelen, (long long) u->blocksize,
- ss.channels, ss.rate);
+ u->ec = pa_xnew0(pa_echo_canceller, 1);
+ if (!u->ec) {
+ pa_log("Failed to alloc echo canceller");
+ goto fail;
+ }
+ u->ec->init = ec_table[DEFAULT_ECHO_CANCELLER].init;
+ u->ec->run = ec_table[DEFAULT_ECHO_CANCELLER].run;
+ u->ec->done = ec_table[DEFAULT_ECHO_CANCELLER].done;
+ u->ec->get_block_size = ec_table[DEFAULT_ECHO_CANCELLER].get_block_size;
adjust_time_sec = DEFAULT_ADJUST_TIME_USEC / PA_USEC_PER_SEC;
if (pa_modargs_get_value_u32(ma, "adjust_time", &adjust_time_sec) < 0) {
@@ -1353,8 +1368,12 @@ int pa__init(pa_module*m) {
u->asyncmsgq = pa_asyncmsgq_new(0);
u->need_realign = TRUE;
- u->echo_state = speex_echo_state_init_mc (framelen, (rate * filter_size_ms) / 1000, ss.channels, ss.channels);
- speex_echo_ctl(u->echo_state, SPEEX_ECHO_SET_SAMPLING_RATE, &rate);
+ if (u->ec->init) {
+ if (!u->ec->init(u->ec, ss, map, filter_size_ms, frame_size_ms)) {
+ pa_log("Failed to init AEC engine");
+ goto fail;
+ }
+ }
/* Create source */
pa_source_new_data_init(&source_data);
@@ -1615,8 +1634,12 @@ void pa__done(pa_module*m) {
if (u->sink_memblockq)
pa_memblockq_free(u->sink_memblockq);
- if (u->echo_state)
- speex_echo_state_destroy (u->echo_state);
+ if (u->ec) {
+ if (u->ec->done)
+ u->ec->done(u->ec);
+
+ pa_xfree(u->ec);
+ }
if (u->asyncmsgq)
pa_asyncmsgq_unref(u->asyncmsgq);
diff --git a/src/modules/echo-cancel/speex.c b/src/modules/echo-cancel/speex.c
new file mode 100644
index 0000000..1b9e76f
--- /dev/null
+++ b/src/modules/echo-cancel/speex.c
@@ -0,0 +1,64 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2010 Wim Taymans <wim.taymans at gmail.com>
+
+ Contributor: Arun Raghavan <arun.raghavan at collabora.co.uk>
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#include "echo-cancel.h"
+
+pa_bool_t pa_speex_ec_init(pa_echo_canceller *ec, pa_sample_spec ss, pa_channel_map map, uint32_t filter_size_ms, uint32_t frame_size_ms)
+{
+ int framelen, y, rate = ss.rate;
+
+ framelen = (rate * frame_size_ms) / 1000;
+ /* framelen should be a power of 2, round down to nearest power of two */
+ y = 1 << ((8 * sizeof (int)) - 2);
+ while (y > framelen)
+ y >>= 1;
+ framelen = y;
+
+ ec->params.priv.speex.blocksize = framelen * pa_frame_size (&ss);
+
+ pa_log_debug ("Using framelen %d, blocksize %lld, channels %d, rate %d", framelen, (long long) ec->params.priv.speex.blocksize, ss.channels, ss.rate);
+
+ ec->params.priv.speex.state = speex_echo_state_init_mc (framelen, (rate * filter_size_ms) / 1000, ss.channels, ss.channels);
+
+ if (ec->params.priv.speex.state) {
+ speex_echo_ctl(ec->params.priv.speex.state, SPEEX_ECHO_SET_SAMPLING_RATE, &rate);
+ return TRUE;
+ } else
+ return FALSE;
+}
+
+void pa_speex_ec_run(pa_echo_canceller *ec, uint8_t *rec, uint8_t *play, uint8_t *out)
+{
+ speex_echo_cancellation(ec->params.priv.speex.state, (const spx_int16_t *) rec, (const spx_int16_t *) play, (spx_int16_t *) out);
+}
+
+void pa_speex_ec_done(pa_echo_canceller *ec)
+{
+ speex_echo_state_destroy (ec->params.priv.speex.state);
+ ec->params.priv.speex.state = NULL;
+}
+
+uint32_t pa_speex_ec_get_block_size(pa_echo_canceller *ec)
+{
+ return ec->params.priv.speex.blocksize;
+}
commit 668f4e49b6296431a3592e7fc7cc8846f0e7f272
Author: Arun Raghavan <arun.raghavan at collabora.co.uk>
Date: Mon Sep 6 21:24:55 2010 +0530
echo-cancel: Pass arguments to the specific canceller module
This allows us to tweak module parameters for whichever AEC module is
chosen.
diff --git a/src/modules/echo-cancel/echo-cancel.h b/src/modules/echo-cancel/echo-cancel.h
index bb6c0ed..186ce32 100644
--- a/src/modules/echo-cancel/echo-cancel.h
+++ b/src/modules/echo-cancel/echo-cancel.h
@@ -46,7 +46,7 @@ struct pa_echo_canceller_params {
typedef struct pa_echo_canceller pa_echo_canceller;
struct pa_echo_canceller {
- pa_bool_t (*init) (pa_echo_canceller *ec, pa_sample_spec ss, pa_channel_map map, uint32_t filter_size_ms, uint32_t frame_size_ms);
+ pa_bool_t (*init) (pa_echo_canceller *ec, pa_sample_spec ss, pa_channel_map map, const char *args);
void (*run) (pa_echo_canceller *ec, uint8_t *rec, uint8_t *play, uint8_t *out);
void (*done) (pa_echo_canceller *ec);
uint32_t (*get_block_size) (pa_echo_canceller *ec);
@@ -55,7 +55,7 @@ struct pa_echo_canceller {
};
/* Speex canceller functions */
-pa_bool_t pa_speex_ec_init(pa_echo_canceller *ec, pa_sample_spec ss, pa_channel_map map, uint32_t filter_size_ms, uint32_t frame_size_ms);
+pa_bool_t pa_speex_ec_init(pa_echo_canceller *ec, pa_sample_spec ss, pa_channel_map map, const char *args);
void pa_speex_ec_run(pa_echo_canceller *ec, uint8_t *rec, uint8_t *play, uint8_t *out);
void pa_speex_ec_done(pa_echo_canceller *ec);
uint32_t pa_speex_ec_get_block_size(pa_echo_canceller *ec);
diff --git a/src/modules/echo-cancel/module-echo-cancel.c b/src/modules/echo-cancel/module-echo-cancel.c
index 2e72434..d6968cd 100644
--- a/src/modules/echo-cancel/module-echo-cancel.c
+++ b/src/modules/echo-cancel/module-echo-cancel.c
@@ -70,13 +70,12 @@ PA_MODULE_USAGE(
"sink_name=<name for the sink> "
"sink_properties=<properties for the sink> "
"sink_master=<name of sink to filter> "
- "frame_size_ms=<amount of data to process at one time> "
- "filter_size_ms=<amount of echo to cancel> "
"adjust_time=<how often to readjust rates in s> "
"format=<sample format> "
"rate=<sample rate> "
"channels=<number of channels> "
"channel_map=<channel map> "
+ "aec_args=<parameters for the AEC engine> "
"save_aec=<save AEC data in /tmp> "
));
@@ -97,11 +96,6 @@ static const pa_echo_canceller ec_table[] = {
},
};
-/* should be between 10-20 ms */
-#define DEFAULT_FRAME_SIZE_MS 20
-/* should be between 100-500 ms */
-#define DEFAULT_FILTER_SIZE_MS 200
-
#define DEFAULT_ADJUST_TIME_USEC (1*PA_USEC_PER_SEC)
#define DEFAULT_SAVE_AEC 0
@@ -154,7 +148,6 @@ struct userdata {
pa_core *core;
pa_module *module;
- uint32_t frame_size_ms;
uint32_t save_aec;
pa_echo_canceller *ec;
@@ -199,13 +192,12 @@ static const char* const valid_modargs[] = {
"sink_name",
"sink_properties",
"sink_master",
- "frame_size_ms",
- "filter_size_ms",
"adjust_time",
"format",
"rate",
"channels",
"channel_map",
+ "aec_args",
"save_aec",
NULL
};
@@ -1287,7 +1279,6 @@ int pa__init(pa_module*m) {
pa_source_new_data source_data;
pa_sink_new_data sink_data;
pa_memchunk silence;
- uint32_t frame_size_ms, filter_size_ms;
uint32_t adjust_time_sec;
pa_assert(m);
@@ -1309,18 +1300,6 @@ int pa__init(pa_module*m) {
}
pa_assert(sink_master);
- frame_size_ms = DEFAULT_FRAME_SIZE_MS;
- if (pa_modargs_get_value_u32(ma, "frame_size_ms", &frame_size_ms) < 0 || frame_size_ms < 1 || frame_size_ms > 200) {
- pa_log("Invalid frame_size_ms specification");
- goto fail;
- }
-
- filter_size_ms = DEFAULT_FILTER_SIZE_MS;
- if (pa_modargs_get_value_u32(ma, "filter_size_ms", &filter_size_ms) < 0 || filter_size_ms < 1 || filter_size_ms > 2000) {
- pa_log("Invalid filter_size_ms specification");
- goto fail;
- }
-
ss = source_master->sample_spec;
ss.format = PA_SAMPLE_S16LE;
map = source_master->channel_map;
@@ -1337,7 +1316,6 @@ int pa__init(pa_module*m) {
u->core = m->core;
u->module = m;
m->userdata = u;
- u->frame_size_ms = frame_size_ms;
u->ec = pa_xnew0(pa_echo_canceller, 1);
if (!u->ec) {
@@ -1369,7 +1347,7 @@ int pa__init(pa_module*m) {
u->asyncmsgq = pa_asyncmsgq_new(0);
u->need_realign = TRUE;
if (u->ec->init) {
- if (!u->ec->init(u->ec, ss, map, filter_size_ms, frame_size_ms)) {
+ if (!u->ec->init(u->ec, ss, map, pa_modargs_get_value(ma, "aec_args", NULL))) {
pa_log("Failed to init AEC engine");
goto fail;
}
diff --git a/src/modules/echo-cancel/speex.c b/src/modules/echo-cancel/speex.c
index 1b9e76f..a8fcc86 100644
--- a/src/modules/echo-cancel/speex.c
+++ b/src/modules/echo-cancel/speex.c
@@ -21,11 +21,46 @@
USA.
***/
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulsecore/modargs.h>
#include "echo-cancel.h"
-pa_bool_t pa_speex_ec_init(pa_echo_canceller *ec, pa_sample_spec ss, pa_channel_map map, uint32_t filter_size_ms, uint32_t frame_size_ms)
+/* should be between 10-20 ms */
+#define DEFAULT_FRAME_SIZE_MS 20
+/* should be between 100-500 ms */
+#define DEFAULT_FILTER_SIZE_MS 200
+
+static const char* const valid_modargs[] = {
+ "frame_size_ms",
+ "filter_size_ms",
+ NULL
+};
+
+pa_bool_t pa_speex_ec_init(pa_echo_canceller *ec, pa_sample_spec ss, pa_channel_map map, const char *args)
{
int framelen, y, rate = ss.rate;
+ uint32_t frame_size_ms, filter_size_ms;
+ pa_modargs *ma;
+
+ if (!(ma = pa_modargs_new(args, valid_modargs))) {
+ pa_log("Failed to parse submodule arguments.");
+ goto fail;
+ }
+
+ filter_size_ms = DEFAULT_FILTER_SIZE_MS;
+ if (pa_modargs_get_value_u32(ma, "filter_size_ms", &filter_size_ms) < 0 || filter_size_ms < 1 || filter_size_ms > 2000) {
+ pa_log("Invalid filter_size_ms specification");
+ goto fail;
+ }
+
+ frame_size_ms = DEFAULT_FRAME_SIZE_MS;
+ if (pa_modargs_get_value_u32(ma, "frame_size_ms", &frame_size_ms) < 0 || frame_size_ms < 1 || frame_size_ms > 200) {
+ pa_log("Invalid frame_size_ms specification");
+ goto fail;
+ }
framelen = (rate * frame_size_ms) / 1000;
/* framelen should be a power of 2, round down to nearest power of two */
@@ -40,11 +75,18 @@ pa_bool_t pa_speex_ec_init(pa_echo_canceller *ec, pa_sample_spec ss, pa_channel_
ec->params.priv.speex.state = speex_echo_state_init_mc (framelen, (rate * filter_size_ms) / 1000, ss.channels, ss.channels);
- if (ec->params.priv.speex.state) {
- speex_echo_ctl(ec->params.priv.speex.state, SPEEX_ECHO_SET_SAMPLING_RATE, &rate);
- return TRUE;
- } else
- return FALSE;
+ if (!ec->params.priv.speex.state)
+ goto fail;
+
+ speex_echo_ctl(ec->params.priv.speex.state, SPEEX_ECHO_SET_SAMPLING_RATE, &rate);
+
+ pa_modargs_free(ma);
+ return TRUE;
+
+fail:
+ if (ma)
+ pa_modargs_free(ma);
+ return FALSE;
}
void pa_speex_ec_run(pa_echo_canceller *ec, uint8_t *rec, uint8_t *play, uint8_t *out)
commit c975dfa5a532b5bb152128c694094b75a862a540
Author: Arun Raghavan <arun.raghavan at collabora.co.uk>
Date: Mon Sep 6 22:23:51 2010 +0530
echo-cancel: Let AEC module determine source/sink spec
Since the source and sink specification will need to be determined by
the AEC algorithm (can it handle multi-channel audio, does it work with
a fixed sample rate, etc.), we negotiate these using inout parameters at
initialisation time.
There is opportunity to make the sink-handling more elegant. Since the
sink data isn't used for playback (just processing), we could pass
through the data as-is and resample to the required spec before using in
the cancellation algorithm. This isn't too important immediately, but
would be nice to have.
diff --git a/src/modules/echo-cancel/echo-cancel.h b/src/modules/echo-cancel/echo-cancel.h
index 186ce32..205c4d1 100644
--- a/src/modules/echo-cancel/echo-cancel.h
+++ b/src/modules/echo-cancel/echo-cancel.h
@@ -46,7 +46,12 @@ struct pa_echo_canceller_params {
typedef struct pa_echo_canceller pa_echo_canceller;
struct pa_echo_canceller {
- pa_bool_t (*init) (pa_echo_canceller *ec, pa_sample_spec ss, pa_channel_map map, const char *args);
+ pa_bool_t (*init) (pa_echo_canceller *ec,
+ pa_sample_spec *source_ss,
+ pa_channel_map *source_map,
+ pa_sample_spec *sink_ss,
+ pa_channel_map *sink_map,
+ const char *args);
void (*run) (pa_echo_canceller *ec, uint8_t *rec, uint8_t *play, uint8_t *out);
void (*done) (pa_echo_canceller *ec);
uint32_t (*get_block_size) (pa_echo_canceller *ec);
@@ -55,7 +60,10 @@ struct pa_echo_canceller {
};
/* Speex canceller functions */
-pa_bool_t pa_speex_ec_init(pa_echo_canceller *ec, pa_sample_spec ss, pa_channel_map map, const char *args);
+pa_bool_t pa_speex_ec_init(pa_echo_canceller *ec,
+ pa_sample_spec *source_ss, pa_channel_map *source_map,
+ pa_sample_spec *sink_ss, pa_channel_map *sink_map,
+ const char *args);
void pa_speex_ec_run(pa_echo_canceller *ec, uint8_t *rec, uint8_t *play, uint8_t *out);
void pa_speex_ec_done(pa_echo_canceller *ec);
uint32_t pa_speex_ec_get_block_size(pa_echo_canceller *ec);
diff --git a/src/modules/echo-cancel/module-echo-cancel.c b/src/modules/echo-cancel/module-echo-cancel.c
index d6968cd..6a88167 100644
--- a/src/modules/echo-cancel/module-echo-cancel.c
+++ b/src/modules/echo-cancel/module-echo-cancel.c
@@ -1269,8 +1269,8 @@ static void sink_input_mute_changed_cb(pa_sink_input *i) {
int pa__init(pa_module*m) {
struct userdata *u;
- pa_sample_spec ss;
- pa_channel_map map;
+ pa_sample_spec source_ss, sink_ss;
+ pa_channel_map source_map, sink_map;
pa_modargs *ma;
pa_source *source_master=NULL;
pa_sink *sink_master=NULL;
@@ -1300,14 +1300,16 @@ int pa__init(pa_module*m) {
}
pa_assert(sink_master);
- ss = source_master->sample_spec;
- ss.format = PA_SAMPLE_S16LE;
- map = source_master->channel_map;
- if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0) {
+ source_ss = source_master->sample_spec;
+ source_map = source_master->channel_map;
+ 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;
+
u = pa_xnew0(struct userdata, 1);
if (!u) {
pa_log("Failed to alloc userdata");
@@ -1347,7 +1349,7 @@ int pa__init(pa_module*m) {
u->asyncmsgq = pa_asyncmsgq_new(0);
u->need_realign = TRUE;
if (u->ec->init) {
- if (!u->ec->init(u->ec, ss, map, pa_modargs_get_value(ma, "aec_args", NULL))) {
+ if (!u->ec->init(u->ec, &source_ss, &source_map, &sink_ss, &sink_map, pa_modargs_get_value(ma, "aec_args", NULL))) {
pa_log("Failed to init AEC engine");
goto fail;
}
@@ -1359,8 +1361,8 @@ int pa__init(pa_module*m) {
source_data.module = m;
if (!(source_data.name = pa_xstrdup(pa_modargs_get_value(ma, "source_name", NULL))))
source_data.name = pa_sprintf_malloc("%s.echo-cancel", source_master->name);
- pa_source_new_data_set_sample_spec(&source_data, &ss);
- pa_source_new_data_set_channel_map(&source_data, &map);
+ pa_source_new_data_set_sample_spec(&source_data, &source_ss);
+ pa_source_new_data_set_channel_map(&source_data, &source_map);
pa_proplist_sets(source_data.proplist, PA_PROP_DEVICE_MASTER_DEVICE, source_master->name);
pa_proplist_sets(source_data.proplist, PA_PROP_DEVICE_CLASS, "filter");
pa_proplist_sets(source_data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "phone");
@@ -1406,8 +1408,8 @@ int pa__init(pa_module*m) {
sink_data.module = m;
if (!(sink_data.name = pa_xstrdup(pa_modargs_get_value(ma, "sink_name", NULL))))
sink_data.name = pa_sprintf_malloc("%s.echo-cancel", sink_master->name);
- pa_sink_new_data_set_sample_spec(&sink_data, &ss);
- pa_sink_new_data_set_channel_map(&sink_data, &map);
+ pa_sink_new_data_set_sample_spec(&sink_data, &sink_ss);
+ pa_sink_new_data_set_channel_map(&sink_data, &sink_map);
pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_MASTER_DEVICE, sink_master->name);
pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_CLASS, "filter");
pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "phone");
@@ -1456,8 +1458,8 @@ int pa__init(pa_module*m) {
pa_proplist_sets(source_output_data.proplist, PA_PROP_MEDIA_NAME, "Echo-Cancel Source Stream");
pa_proplist_sets(source_output_data.proplist, PA_PROP_MEDIA_ROLE, "filter");
- pa_source_output_new_data_set_sample_spec(&source_output_data, &ss);
- pa_source_output_new_data_set_channel_map(&source_output_data, &map);
+ pa_source_output_new_data_set_sample_spec(&source_output_data, &source_ss);
+ pa_source_output_new_data_set_channel_map(&source_output_data, &source_map);
pa_source_output_new(&u->source_output, m->core, &source_output_data);
pa_source_output_new_data_done(&source_output_data);
@@ -1487,8 +1489,8 @@ int pa__init(pa_module*m) {
sink_input_data.sink = sink_master;
pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_NAME, "Echo-Cancel Sink Stream");
pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_ROLE, "filter");
- pa_sink_input_new_data_set_sample_spec(&sink_input_data, &ss);
- pa_sink_input_new_data_set_channel_map(&sink_input_data, &map);
+ pa_sink_input_new_data_set_sample_spec(&sink_input_data, &sink_ss);
+ pa_sink_input_new_data_set_channel_map(&sink_input_data, &sink_map);
sink_input_data.flags = PA_SINK_INPUT_VARIABLE_RATE;
pa_sink_input_new(&u->sink_input, m->core, &sink_input_data);
@@ -1518,9 +1520,9 @@ int pa__init(pa_module*m) {
pa_sink_input_get_silence(u->sink_input, &silence);
u->source_memblockq = pa_memblockq_new(0, MEMBLOCKQ_MAXLENGTH, 0,
- pa_frame_size(&ss), 1, 1, 0, &silence);
+ pa_frame_size(&source_ss), 1, 1, 0, &silence);
u->sink_memblockq = pa_memblockq_new(0, MEMBLOCKQ_MAXLENGTH, 0,
- pa_frame_size(&ss), 1, 1, 0, &silence);
+ pa_frame_size(&sink_ss), 1, 1, 0, &silence);
pa_memblock_unref(silence.memblock);
diff --git a/src/modules/echo-cancel/speex.c b/src/modules/echo-cancel/speex.c
index a8fcc86..cb8212e 100644
--- a/src/modules/echo-cancel/speex.c
+++ b/src/modules/echo-cancel/speex.c
@@ -39,9 +39,21 @@ static const char* const valid_modargs[] = {
NULL
};
-pa_bool_t pa_speex_ec_init(pa_echo_canceller *ec, pa_sample_spec ss, pa_channel_map map, const char *args)
+static void pa_speex_ec_fixate_spec(pa_sample_spec *source_ss, pa_channel_map *source_map,
+ pa_sample_spec *sink_ss, pa_channel_map *sink_map)
{
- int framelen, y, rate = ss.rate;
+ source_ss->format = PA_SAMPLE_S16LE;
+
+ *sink_ss = *source_ss;
+ *sink_map = *source_map;
+}
+
+pa_bool_t pa_speex_ec_init(pa_echo_canceller *ec,
+ pa_sample_spec *source_ss, pa_channel_map *source_map,
+ pa_sample_spec *sink_ss, pa_channel_map *sink_map,
+ const char *args)
+{
+ int framelen, y, rate;
uint32_t frame_size_ms, filter_size_ms;
pa_modargs *ma;
@@ -62,6 +74,9 @@ pa_bool_t pa_speex_ec_init(pa_echo_canceller *ec, pa_sample_spec ss, pa_channel_
goto fail;
}
+ pa_speex_ec_fixate_spec(source_ss, source_map, sink_ss, sink_map);
+
+ rate = source_ss->rate;
framelen = (rate * frame_size_ms) / 1000;
/* framelen should be a power of 2, round down to nearest power of two */
y = 1 << ((8 * sizeof (int)) - 2);
@@ -69,11 +84,11 @@ pa_bool_t pa_speex_ec_init(pa_echo_canceller *ec, pa_sample_spec ss, pa_channel_
y >>= 1;
framelen = y;
- ec->params.priv.speex.blocksize = framelen * pa_frame_size (&ss);
+ ec->params.priv.speex.blocksize = framelen * pa_frame_size (source_ss);
- pa_log_debug ("Using framelen %d, blocksize %lld, channels %d, rate %d", framelen, (long long) ec->params.priv.speex.blocksize, ss.channels, ss.rate);
+ pa_log_debug ("Using framelen %d, blocksize %lld, channels %d, rate %d", framelen, (long long) ec->params.priv.speex.blocksize, source_ss->channels, source_ss->rate);
- ec->params.priv.speex.state = speex_echo_state_init_mc (framelen, (rate * filter_size_ms) / 1000, ss.channels, ss.channels);
+ ec->params.priv.speex.state = speex_echo_state_init_mc (framelen, (rate * filter_size_ms) / 1000, source_ss->channels, source_ss->channels);
if (!ec->params.priv.speex.state)
goto fail;
commit 47e4dd1ec43298f4d5b8165b772b07f95589966e
Author: Arun Raghavan <arun.raghavan at collabora.co.uk>
Date: Tue Sep 7 13:43:58 2010 +0530
echo-cancel: Add alternative echo-cancellation implementation
This adds Andre Adrian's AEC implementation from his intercom project
(http://andreadrian.de/intercom/) as an alternative to the speex echo
cancellation routines. Since the implementation was in C++ and not in
the form of a library, I have converted the code to C and made a local
copy of the implementation.
The implementation actually works on floating point data, so we can
tweak it to work with both integer and floating point samples (currently
we just use S16LE).
diff --git a/LICENSE b/LICENSE
index 612c234..3a82749 100644
--- a/LICENSE
+++ b/LICENSE
@@ -10,4 +10,8 @@ LGPL licensed and the server part ('libpulsecore') as being GPL licensed. Since
the PulseAudio daemon and the modules link to 'libpulsecore' they are of course
also GPL licensed.
+Andre Adrian's echo cancellation implementation is licensed under a less
+restrictive license - see src/modules/echo-cancel/adrian-license.txt for
+details.
+
-- Lennart Poettering, April 20th, 2006.
diff --git a/src/Makefile.am b/src/Makefile.am
index 242532c..0980488 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1605,7 +1605,10 @@ module_suspend_on_idle_la_LIBADD = $(AM_LIBADD) libpulsecore- at PA_MAJORMINORMICRO
module_suspend_on_idle_la_CFLAGS = $(AM_CFLAGS)
# echo-cancel module
-module_echo_cancel_la_SOURCES = modules/echo-cancel/module-echo-cancel.c modules/echo-cancel/speex.c
+module_echo_cancel_la_SOURCES = modules/echo-cancel/module-echo-cancel.c \
+ modules/echo-cancel/speex.c \
+ modules/echo-cancel/adrian-aec.c \
+ modules/echo-cancel/adrian.c
module_echo_cancel_la_LDFLAGS = $(MODULE_LDFLAGS)
module_echo_cancel_la_LIBADD = $(AM_LIBADD) libpulsecore- at PA_MAJORMINORMICRO@.la libpulsecommon- at PA_MAJORMINORMICRO@.la libpulse.la $(LIBSPEEX_LIBS)
module_echo_cancel_la_CFLAGS = $(AM_CFLAGS) $(LIBSPEEX_CFLAGS)
diff --git a/src/modules/echo-cancel/adrian-aec.c b/src/modules/echo-cancel/adrian-aec.c
new file mode 100644
index 0000000..69107c7
--- /dev/null
+++ b/src/modules/echo-cancel/adrian-aec.c
@@ -0,0 +1,233 @@
+/* aec.cpp
+ *
+ * Copyright (C) DFS Deutsche Flugsicherung (2004, 2005).
+ * All Rights Reserved.
+ *
+ * Acoustic Echo Cancellation NLMS-pw algorithm
+ *
+ * Version 0.3 filter created with www.dsptutor.freeuk.com
+ * Version 0.3.1 Allow change of stability parameter delta
+ * Version 0.4 Leaky Normalized LMS - pre whitening algorithm
+ */
+
+#include <math.h>
+#include <string.h>
+
+#include <pulse/xmalloc.h>
+
+#include "adrian-aec.h"
+
+/* Vector Dot Product */
+static REAL dotp(REAL a[], REAL b[])
+{
+ REAL sum0 = 0.0, sum1 = 0.0;
+ int j;
+
+ for (j = 0; j < NLMS_LEN; j += 2) {
+ // optimize: partial loop unrolling
+ sum0 += a[j] * b[j];
+ sum1 += a[j + 1] * b[j + 1];
+ }
+ return sum0 + sum1;
+}
+
+
+AEC* AEC_init(int RATE)
+{
+ AEC *a = pa_xnew(AEC, 1);
+ a->hangover = 0;
+ memset(a->x, 0, sizeof(a->x));
+ memset(a->xf, 0, sizeof(a->xf));
+ memset(a->w, 0, sizeof(a->w));
+ a->j = NLMS_EXT;
+ a->delta = 0.0f;
+ AEC_setambient(a, NoiseFloor);
+ a->dfast = a->dslow = M75dB_PCM;
+ a->xfast = a->xslow = M80dB_PCM;
+ a->gain = 1.0f;
+ a->Fx = IIR1_init(2000.0f/RATE);
+ a->Fe = IIR1_init(2000.0f/RATE);
+ a->cutoff = FIR_HP_300Hz_init();
+ a->acMic = IIR_HP_init();
+ a->acSpk = IIR_HP_init();
+
+ a->aes_y2 = M0dB;
+
+ a->fdwdisplay = -1;
+ a->dumpcnt = 0;
+ memset(a->ws, 0, sizeof(a->ws));
+
+ return a;
+}
+
+// Adrian soft decision DTD
+// (Dual Average Near-End to Far-End signal Ratio DTD)
+// This algorithm uses exponential smoothing with differnt
+// ageing parameters to get fast and slow near-end and far-end
+// signal averages. The ratio of NFRs term
+// (dfast / xfast) / (dslow / xslow) is used to compute the stepsize
+// A ratio value of 2.5 is mapped to stepsize 0, a ratio of 0 is
+// mapped to 1.0 with a limited linear function.
+static float AEC_dtd(AEC *a, REAL d, REAL x)
+{
+ float stepsize;
+ float ratio, M;
+
+ // fast near-end and far-end average
+ a->dfast += ALPHAFAST * (fabsf(d) - a->dfast);
+ a->xfast += ALPHAFAST * (fabsf(x) - a->xfast);
+
+ // slow near-end and far-end average
+ a->dslow += ALPHASLOW * (fabsf(d) - a->dslow);
+ a->xslow += ALPHASLOW * (fabsf(x) - a->xslow);
+
+ if (a->xfast < M70dB_PCM) {
+ return 0.0; // no Spk signal
+ }
+
+ if (a->dfast < M70dB_PCM) {
+ return 0.0; // no Mic signal
+ }
+
+ // ratio of NFRs
+ ratio = (a->dfast * a->xslow) / (a->dslow * a->xfast);
+
+ // begrenzte lineare Kennlinie
+ M = (STEPY2 - STEPY1) / (STEPX2 - STEPX1);
+ if (ratio < STEPX1) {
+ stepsize = STEPY1;
+ } else if (ratio > STEPX2) {
+ stepsize = STEPY2;
+ } else {
+ // Punktrichtungsform einer Geraden
+ stepsize = M * (ratio - STEPX1) + STEPY1;
+ }
+
+ return stepsize;
+}
+
+
+static void AEC_leaky(AEC *a)
+// The xfast signal is used to charge the hangover timer to Thold.
+// When hangover expires (no Spk signal for some time) the vector w
+// is erased. This is my implementation of Leaky NLMS.
+{
+ if (a->xfast >= M70dB_PCM) {
+ // vector w is valid for hangover Thold time
+ a->hangover = Thold;
+ } else {
+ if (a->hangover > 1) {
+ --(a->hangover);
+ } else if (1 == a->hangover) {
+ --(a->hangover);
+ // My Leaky NLMS is to erase vector w when hangover expires
+ memset(a->w, 0, sizeof(a->w));
+ }
+ }
+}
+
+
+#if 0
+void AEC::openwdisplay() {
+ // open TCP connection to program wdisplay.tcl
+ fdwdisplay = socket_async("127.0.0.1", 50999);
+};
+#endif
+
+
+static REAL AEC_nlms_pw(AEC *a, REAL d, REAL x_, float stepsize)
+{
+ REAL e;
+ REAL ef;
+ a->x[a->j] = x_;
+ a->xf[a->j] = IIR1_highpass(a->Fx, x_); // pre-whitening of x
+
+ // calculate error value
+ // (mic signal - estimated mic signal from spk signal)
+ e = d;
+ if (a->hangover > 0) {
+ e -= dotp(a->w, a->x + a->j);
+ }
+ ef = IIR1_highpass(a->Fe, e); // pre-whitening of e
+
+ // optimize: iterative dotp(xf, xf)
+ a->dotp_xf_xf += (a->xf[a->j] * a->xf[a->j] - a->xf[a->j + NLMS_LEN - 1] * a->xf[a->j + NLMS_LEN - 1]);
+
+ if (stepsize > 0.0) {
+ // calculate variable step size
+ REAL mikro_ef = stepsize * ef / a->dotp_xf_xf;
+
+ // update tap weights (filter learning)
+ int i;
+ for (i = 0; i < NLMS_LEN; i += 2) {
+ // optimize: partial loop unrolling
+ a->w[i] += mikro_ef * a->xf[i + a->j];
+ a->w[i + 1] += mikro_ef * a->xf[i + a->j + 1];
+ }
+ }
+
+ if (--(a->j) < 0) {
+ // optimize: decrease number of memory copies
+ a->j = NLMS_EXT;
+ memmove(a->x + a->j + 1, a->x, (NLMS_LEN - 1) * sizeof(REAL));
+ memmove(a->xf + a->j + 1, a->xf, (NLMS_LEN - 1) * sizeof(REAL));
+ }
+
+ // Saturation
+ if (e > MAXPCM) {
+ return MAXPCM;
+ } else if (e < -MAXPCM) {
+ return -MAXPCM;
+ } else {
+ return e;
+ }
+}
+
+
+int AEC_doAEC(AEC *a, int d_, int x_)
+{
+ REAL d = (REAL) d_;
+ REAL x = (REAL) x_;
+
+ // Mic Highpass Filter - to remove DC
+ d = IIR_HP_highpass(a->acMic, d);
+
+ // Mic Highpass Filter - cut-off below 300Hz
+ d = FIR_HP_300Hz_highpass(a->cutoff, d);
+
+ // Amplify, for e.g. Soundcards with -6dB max. volume
+ d *= a->gain;
+
+ // Spk Highpass Filter - to remove DC
+ x = IIR_HP_highpass(a->acSpk, x);
+
+ // Double Talk Detector
+ a->stepsize = AEC_dtd(a, d, x);
+
+ // Leaky (ageing of vector w)
+ AEC_leaky(a);
+
+ // Acoustic Echo Cancellation
+ d = AEC_nlms_pw(a, d, x, a->stepsize);
+
+#if 0
+ if (fdwdisplay >= 0) {
+ if (++dumpcnt >= (WIDEB*RATE/10)) {
+ // wdisplay creates 10 dumps per seconds = large CPU load!
+ dumpcnt = 0;
+ write(fdwdisplay, ws, DUMP_LEN*sizeof(float));
+ // we don't check return value. This is not production quality!!!
+ memset(ws, 0, sizeof(ws));
+ } else {
+ int i;
+ for (i = 0; i < DUMP_LEN; i += 2) {
+ // optimize: partial loop unrolling
+ ws[i] += w[i];
+ ws[i + 1] += w[i + 1];
+ }
+ }
+ }
+#endif
+
+ return (int) d;
+}
diff --git a/src/modules/echo-cancel/adrian-aec.h b/src/modules/echo-cancel/adrian-aec.h
new file mode 100644
index 0000000..1f5b090
--- /dev/null
+++ b/src/modules/echo-cancel/adrian-aec.h
@@ -0,0 +1,370 @@
+/* aec.h
+ *
+ * Copyright (C) DFS Deutsche Flugsicherung (2004, 2005).
+ * All Rights Reserved.
+ * Author: Andre Adrian
+ *
+ * Acoustic Echo Cancellation Leaky NLMS-pw algorithm
+ *
+ * Version 0.3 filter created with www.dsptutor.freeuk.com
+ * Version 0.3.1 Allow change of stability parameter delta
+ * Version 0.4 Leaky Normalized LMS - pre whitening algorithm
+ */
+
+#ifndef _AEC_H /* include only once */
+
+#define WIDEB 2
+
+// use double if your CPU does software-emulation of float
+typedef float REAL;
+
+/* dB Values */
+#define M0dB 1.0f
+#define M3dB 0.71f
+#define M6dB 0.50f
+#define M9dB 0.35f
+#define M12dB 0.25f
+#define M18dB 0.125f
+#define M24dB 0.063f
+
+/* dB values for 16bit PCM */
+/* MxdB_PCM = 32767 * 10 ^(x / 20) */
+#define M10dB_PCM 10362.0f
+#define M20dB_PCM 3277.0f
+#define M25dB_PCM 1843.0f
+#define M30dB_PCM 1026.0f
+#define M35dB_PCM 583.0f
+#define M40dB_PCM 328.0f
+#define M45dB_PCM 184.0f
+#define M50dB_PCM 104.0f
+#define M55dB_PCM 58.0f
+#define M60dB_PCM 33.0f
+#define M65dB_PCM 18.0f
+#define M70dB_PCM 10.0f
+#define M75dB_PCM 6.0f
+#define M80dB_PCM 3.0f
+#define M85dB_PCM 2.0f
+#define M90dB_PCM 1.0f
+
+#define MAXPCM 32767.0f
+
+/* Design constants (Change to fine tune the algorithms */
+
+/* The following values are for hardware AEC and studio quality
+ * microphone */
+
+/* NLMS filter length in taps (samples). A longer filter length gives
+ * better Echo Cancellation, but maybe slower convergence speed and
+ * needs more CPU power (Order of NLMS is linear) */
+#define NLMS_LEN (100*WIDEB*8)
+
+/* Vector w visualization length in taps (samples).
+ * Must match argv value for wdisplay.tcl */
+#define DUMP_LEN (40*WIDEB*8)
+
+/* minimum energy in xf. Range: M70dB_PCM to M50dB_PCM. Should be equal
+ * to microphone ambient Noise level */
+#define NoiseFloor M55dB_PCM
+
+/* Leaky hangover in taps.
+ */
+#define Thold (60 * WIDEB * 8)
+
+// Adrian soft decision DTD
+// left point. X is ratio, Y is stepsize
+#define STEPX1 1.0
+#define STEPY1 1.0
+// right point. STEPX2=2.0 is good double talk, 3.0 is good single talk.
+#define STEPX2 2.5
+#define STEPY2 0
+#define ALPHAFAST (1.0f / 100.0f)
+#define ALPHASLOW (1.0f / 20000.0f)
+
+
+
+/* Ageing multiplier for LMS memory vector w */
+#define Leaky 0.9999f
+
+/* Double Talk Detector Speaker/Microphone Threshold. Range <=1
+ * Large value (M0dB) is good for Single-Talk Echo cancellation,
+ * small value (M12dB) is good for Doulbe-Talk AEC */
+#define GeigelThreshold M6dB
+
+/* for Non Linear Processor. Range >0 to 1. Large value (M0dB) is good
+ * for Double-Talk, small value (M12dB) is good for Single-Talk */
+#define NLPAttenuation M12dB
+
+/* Below this line there are no more design constants */
+
+typedef struct IIR_HP IIR_HP;
+
+/* Exponential Smoothing or IIR Infinite Impulse Response Filter */
+struct IIR_HP {
+ REAL x;
+};
+
+static IIR_HP* IIR_HP_init(void) {
+ IIR_HP *i = pa_xnew(IIR_HP, 1);
+ i->x = 0.0f;
+ return i;
+ }
+
+static REAL IIR_HP_highpass(IIR_HP *i, REAL in) {
+ const REAL a0 = 0.01f; /* controls Transfer Frequency */
+ /* Highpass = Signal - Lowpass. Lowpass = Exponential Smoothing */
+ i->x += a0 * (in - i->x);
+ return in - i->x;
+ };
+
+typedef struct FIR_HP_300Hz FIR_HP_300Hz;
+
+#if WIDEB==1
+/* 17 taps FIR Finite Impulse Response filter
+ * Coefficients calculated with
+ * www.dsptutor.freeuk.com/KaiserFilterDesign/KaiserFilterDesign.html
+ */
+class FIR_HP_300Hz {
+ REAL z[18];
+
+public:
+ FIR_HP_300Hz() {
+ memset(this, 0, sizeof(FIR_HP_300Hz));
+ }
+
+ REAL highpass(REAL in) {
+ const REAL a[18] = {
+ // Kaiser Window FIR Filter, Filter type: High pass
+ // Passband: 300.0 - 4000.0 Hz, Order: 16
+ // Transition band: 75.0 Hz, Stopband attenuation: 10.0 dB
+ -0.034870606, -0.039650206, -0.044063766, -0.04800318,
+ -0.051370874, -0.054082647, -0.056070227, -0.057283327,
+ 0.8214126, -0.057283327, -0.056070227, -0.054082647,
+ -0.051370874, -0.04800318, -0.044063766, -0.039650206,
+ -0.034870606, 0.0
+ };
+ memmove(z + 1, z, 17 * sizeof(REAL));
+ z[0] = in;
+ REAL sum0 = 0.0, sum1 = 0.0;
+ int j;
+
+ for (j = 0; j < 18; j += 2) {
+ // optimize: partial loop unrolling
+ sum0 += a[j] * z[j];
+ sum1 += a[j + 1] * z[j + 1];
+ }
+ return sum0 + sum1;
+ }
+};
+
+#else
+
+/* 35 taps FIR Finite Impulse Response filter
+ * Passband 150Hz to 4kHz for 8kHz sample rate, 300Hz to 8kHz for 16kHz
+ * sample rate.
+ * Coefficients calculated with
+ * www.dsptutor.freeuk.com/KaiserFilterDesign/KaiserFilterDesign.html
+ */
+struct FIR_HP_300Hz {
+ REAL z[36];
+};
+
+static FIR_HP_300Hz* FIR_HP_300Hz_init(void) {
+ FIR_HP_300Hz *ret = pa_xnew(FIR_HP_300Hz, 1);
+ memset(ret, 0, sizeof(FIR_HP_300Hz));
+ return ret;
+ }
+
+static REAL FIR_HP_300Hz_highpass(FIR_HP_300Hz *f, REAL in) {
+ REAL sum0 = 0.0, sum1 = 0.0;
+ int j;
+ const REAL a[36] = {
+ // Kaiser Window FIR Filter, Filter type: High pass
+ // Passband: 150.0 - 4000.0 Hz, Order: 34
+ // Transition band: 34.0 Hz, Stopband attenuation: 10.0 dB
+ -0.016165324, -0.017454365, -0.01871232, -0.019931411,
+ -0.021104068, -0.022222936, -0.02328091, -0.024271343,
+ -0.025187887, -0.02602462, -0.026776174, -0.027437767,
+ -0.028004972, -0.028474221, -0.028842418, -0.029107114,
+ -0.02926664, 0.8524841, -0.02926664, -0.029107114,
+ -0.028842418, -0.028474221, -0.028004972, -0.027437767,
+ -0.026776174, -0.02602462, -0.025187887, -0.024271343,
+ -0.02328091, -0.022222936, -0.021104068, -0.019931411,
+ -0.01871232, -0.017454365, -0.016165324, 0.0
+ };
+ memmove(f->z + 1, f->z, 35 * sizeof(REAL));
+ f->z[0] = in;
+
+ for (j = 0; j < 36; j += 2) {
+ // optimize: partial loop unrolling
+ sum0 += a[j] * f->z[j];
+ sum1 += a[j + 1] * f->z[j + 1];
+ }
+ return sum0 + sum1;
+ }
+#endif
+
+typedef struct IIR1 IIR1;
+
+/* Recursive single pole IIR Infinite Impulse response High-pass filter
+ *
+ * Reference: The Scientist and Engineer's Guide to Digital Processing
+ *
+ * output[N] = A0 * input[N] + A1 * input[N-1] + B1 * output[N-1]
+ *
+ * X = exp(-2.0 * pi * Fc)
+ * A0 = (1 + X) / 2
+ * A1 = -(1 + X) / 2
+ * B1 = X
+ * Fc = cutoff freq / sample rate
+ */
+struct IIR1 {
+ REAL in0, out0;
+ REAL a0, a1, b1;
+};
+
+#if 0
+ IIR1() {
+ memset(this, 0, sizeof(IIR1));
+ }
+#endif
+
+static IIR1* IIR1_init(REAL Fc) {
+ IIR1 *i = pa_xnew(IIR1, 1);
+ i->b1 = expf(-2.0f * M_PI * Fc);
+ i->a0 = (1.0f + i->b1) / 2.0f;
+ i->a1 = -(i->a0);
+ i->in0 = 0.0f;
+ i->out0 = 0.0f;
+ return i;
+ }
+
+static REAL IIR1_highpass(IIR1 *i, REAL in) {
+ REAL out = i->a0 * in + i->a1 * i->in0 + i->b1 * i->out0;
+ i->in0 = in;
+ i->out0 = out;
+ return out;
+ }
+
+
+#if 0
+/* Recursive two pole IIR Infinite Impulse Response filter
+ * Coefficients calculated with
+ * http://www.dsptutor.freeuk.com/IIRFilterDesign/IIRFiltDes102.html
+ */
+class IIR2 {
+ REAL x[2], y[2];
+
+public:
+ IIR2() {
+ memset(this, 0, sizeof(IIR2));
+ }
+
+ REAL highpass(REAL in) {
+ // Butterworth IIR filter, Filter type: HP
+ // Passband: 2000 - 4000.0 Hz, Order: 2
+ const REAL a[] = { 0.29289323f, -0.58578646f, 0.29289323f };
+ const REAL b[] = { 1.3007072E-16f, 0.17157288f };
+ REAL out =
+ a[0] * in + a[1] * x[0] + a[2] * x[1] - b[0] * y[0] - b[1] * y[1];
+
+ x[1] = x[0];
+ x[0] = in;
+ y[1] = y[0];
+ y[0] = out;
+ return out;
+ }
+};
+#endif
+
+
+// Extention in taps to reduce mem copies
+#define NLMS_EXT (10*8)
+
+// block size in taps to optimize DTD calculation
+#define DTD_LEN 16
+
+typedef struct AEC AEC;
+
+struct AEC {
+ // Time domain Filters
+ IIR_HP *acMic, *acSpk; // DC-level remove Highpass)
+ FIR_HP_300Hz *cutoff; // 150Hz cut-off Highpass
+ REAL gain; // Mic signal amplify
+ IIR1 *Fx, *Fe; // pre-whitening Highpass for x, e
+
+ // Adrian soft decision DTD (Double Talk Detector)
+ REAL dfast, xfast;
+ REAL dslow, xslow;
+
+ // NLMS-pw
+ REAL x[NLMS_LEN + NLMS_EXT]; // tap delayed loudspeaker signal
+ REAL xf[NLMS_LEN + NLMS_EXT]; // pre-whitening tap delayed signal
+ REAL w[NLMS_LEN]; // tap weights
+ int j; // optimize: less memory copies
+ double dotp_xf_xf; // double to avoid loss of precision
+ float delta; // noise floor to stabilize NLMS
+
+ // AES
+ float aes_y2; // not in use!
+
+ // w vector visualization
+ REAL ws[DUMP_LEN]; // tap weights sums
+ int fdwdisplay; // TCP file descriptor
+ int dumpcnt; // wdisplay output counter
+
+ // variables are public for visualization
+ int hangover;
+ float stepsize;
+};
+
+/* Double-Talk Detector
+ *
+ * in d: microphone sample (PCM as REALing point value)
+ * in x: loudspeaker sample (PCM as REALing point value)
+ * return: from 0 for doubletalk to 1.0 for single talk
+ */
+static float AEC_dtd(AEC *a, REAL d, REAL x);
+
+static void AEC_leaky(AEC *a);
+
+/* Normalized Least Mean Square Algorithm pre-whitening (NLMS-pw)
+ * The LMS algorithm was developed by Bernard Widrow
+ * book: Haykin, Adaptive Filter Theory, 4. edition, Prentice Hall, 2002
+ *
+ * in d: microphone sample (16bit PCM value)
+ * in x_: loudspeaker sample (16bit PCM value)
+ * in stepsize: NLMS adaptation variable
+ * return: echo cancelled microphone sample
+ */
+static REAL AEC_nlms_pw(AEC *a, REAL d, REAL x_, float stepsize);
+
+ AEC* AEC_init(int RATE);
+
+/* Acoustic Echo Cancellation and Suppression of one sample
+ * in d: microphone signal with echo
+ * in x: loudspeaker signal
+ * return: echo cancelled microphone signal
+ */
+ int AEC_doAEC(AEC *a, int d_, int x_);
+
+static float AEC_getambient(AEC *a) {
+ return a->dfast;
+ };
+static void AEC_setambient(AEC *a, float Min_xf) {
+ a->dotp_xf_xf -= a->delta; // subtract old delta
+ a->delta = (NLMS_LEN-1) * Min_xf * Min_xf;
+ a->dotp_xf_xf += a->delta; // add new delta
+ };
+static void AEC_setgain(AEC *a, float gain_) {
+ a->gain = gain_;
+ };
+#if 0
+ void AEC_openwdisplay(AEC *a);
+#endif
+static void AEC_setaes(AEC *a, float aes_y2_) {
+ a->aes_y2 = aes_y2_;
+ };
+static double AEC_max_dotp_xf_xf(AEC *a, double u);
+
+#define _AEC_H
+#endif
diff --git a/src/modules/echo-cancel/adrian-license.txt b/src/modules/echo-cancel/adrian-license.txt
new file mode 100644
index 0000000..7c06efd
--- /dev/null
+++ b/src/modules/echo-cancel/adrian-license.txt
@@ -0,0 +1,17 @@
+ Copyright (C) DFS Deutsche Flugsicherung (2004). All Rights Reserved.
+
+ You are allowed to use this source code in any open source or closed
+ source software you want. You are allowed to use the algorithms for a
+ hardware solution. You are allowed to modify the source code.
+ You are not allowed to remove the name of the author from this memo or
+ from the source code files. You are not allowed to monopolize the
+ source code or the algorithms behind the source code as your
+ intellectual property. This source code is free of royalty and comes
+ with no warranty.
+
+--- The following does not apply to the PulseAudio module ---
+
+ Please see g711/gen-lic.txt for the ITU-T G.711 codec copyright.
+ Please see gsm/gen-lic.txt for the ITU-T GSM codec copyright.
+ Please see ilbc/COPYRIGHT and ilbc/NOTICE for the IETF iLBC codec
+ copyright.
diff --git a/src/modules/echo-cancel/adrian.c b/src/modules/echo-cancel/adrian.c
new file mode 100644
index 0000000..86c22cb
--- /dev/null
+++ b/src/modules/echo-cancel/adrian.c
@@ -0,0 +1,121 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2010 Arun Raghavan <arun.raghavan at collabora.co.uk>
+
+ Contributor: Wim Taymans <wim.taymans at gmail.com>
+
+ The actual implementation is taken from the sources at
+ http://andreadrian.de/intercom/ - for the license, look for
+ adrian-license.txt in the same directory as this file.
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulsecore/modargs.h>
+#include "echo-cancel.h"
+
+/* should be between 10-20 ms */
+#define DEFAULT_FRAME_SIZE_MS 20
+
+static const char* const valid_modargs[] = {
+ "frame_size_ms",
+ NULL
+};
+
+static void pa_adrian_ec_fixate_spec(pa_sample_spec *source_ss, pa_channel_map *source_map,
+ pa_sample_spec *sink_ss, pa_channel_map *sink_map)
+{
+ source_ss->format = PA_SAMPLE_S16LE;
+ source_ss->channels = 1;
+ pa_channel_map_init_mono(source_map);
+
+ *sink_ss = *source_ss;
+ *sink_map = *source_map;
+}
+
+pa_bool_t pa_adrian_ec_init(pa_echo_canceller *ec,
+ pa_sample_spec *source_ss, pa_channel_map *source_map,
+ pa_sample_spec *sink_ss, pa_channel_map *sink_map,
+ const char *args)
+{
+ int framelen, rate;
+ uint32_t frame_size_ms;
+ pa_modargs *ma;
+
+ if (!(ma = pa_modargs_new(args, valid_modargs))) {
+ pa_log("Failed to parse submodule arguments.");
+ goto fail;
+ }
+
+ frame_size_ms = DEFAULT_FRAME_SIZE_MS;
+ if (pa_modargs_get_value_u32(ma, "frame_size_ms", &frame_size_ms) < 0 || frame_size_ms < 1 || frame_size_ms > 200) {
+ pa_log("Invalid frame_size_ms specification");
+ goto fail;
+ }
+
+ pa_adrian_ec_fixate_spec(source_ss, source_map, sink_ss, sink_map);
+
+ rate = source_ss->rate;
+ framelen = (rate * frame_size_ms) / 1000;
+
+ ec->params.priv.adrian.blocksize = framelen * pa_frame_size (source_ss);
+
+ pa_log_debug ("Using framelen %d, blocksize %lld, channels %d, rate %d", framelen, (long long) ec->params.priv.adrian.blocksize, source_ss->channels, source_ss->rate);
+
+ ec->params.priv.adrian.aec = AEC_init(rate);
+ if (!ec->params.priv.adrian.aec)
+ goto fail;
+
+ pa_modargs_free(ma);
+ return TRUE;
+
+fail:
+ if (ma)
+ pa_modargs_free(ma);
+ return FALSE;
+}
+
+void pa_adrian_ec_run(pa_echo_canceller *ec, uint8_t *rec, uint8_t *play, uint8_t *out)
+{
+ unsigned int i;
+
+ for (i = 0; i < ec->params.priv.adrian.blocksize; i += 2) {
+ /* We know it's S16LE mono data */
+ int r = (((int8_t) rec[i + 1]) << 8) | rec[i];
+ int p = (((int8_t) play[i + 1]) << 8) | play[i];
+ int res;
+
+ res = AEC_doAEC(ec->params.priv.adrian.aec, r, p);
+ out[i] = (uint8_t) (res & 0xff);
+ out[i + 1] = (uint8_t) ((res >> 8) & 0xff);
+ }
+}
+
+void pa_adrian_ec_done(pa_echo_canceller *ec)
+{
+ pa_xfree(ec->params.priv.adrian.aec);
+ ec->params.priv.adrian.aec = NULL;
+}
+
+uint32_t pa_adrian_ec_get_block_size(pa_echo_canceller *ec)
+{
+ return ec->params.priv.adrian.blocksize;
+}
diff --git a/src/modules/echo-cancel/adrian.h b/src/modules/echo-cancel/adrian.h
new file mode 100644
index 0000000..d02e934
--- /dev/null
+++ b/src/modules/echo-cancel/adrian.h
@@ -0,0 +1,31 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2010 Arun Raghavan <arun.raghavan at collabora.co.uk>
+
+ The actual implementation is taken from the sources at
+ http://andreadrian.de/intercom/ - for the license, look for
+ adrian-license.txt in the same directory as this file.
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+/* Forward declarations */
+
+typedef struct AEC AEC;
+
+AEC* AEC_init(int RATE);
+int AEC_doAEC(AEC *a, int d_, int x_);
diff --git a/src/modules/echo-cancel/echo-cancel.h b/src/modules/echo-cancel/echo-cancel.h
index 205c4d1..65e0e24 100644
--- a/src/modules/echo-cancel/echo-cancel.h
+++ b/src/modules/echo-cancel/echo-cancel.h
@@ -28,6 +28,7 @@
#include <pulsecore/macro.h>
#include <speex/speex_echo.h>
+#include "adrian.h"
/* Common data structures */
@@ -39,6 +40,10 @@ struct pa_echo_canceller_params {
uint32_t blocksize;
SpeexEchoState *state;
} speex;
+ struct {
+ uint32_t blocksize;
+ AEC *aec;
+ } adrian;
/* each canceller-specific structure goes here */
} priv;
};
@@ -67,3 +72,12 @@ pa_bool_t pa_speex_ec_init(pa_echo_canceller *ec,
void pa_speex_ec_run(pa_echo_canceller *ec, uint8_t *rec, uint8_t *play, uint8_t *out);
void pa_speex_ec_done(pa_echo_canceller *ec);
uint32_t pa_speex_ec_get_block_size(pa_echo_canceller *ec);
+
+/* Adrian Andre's echo canceller */
+pa_bool_t pa_adrian_ec_init(pa_echo_canceller *ec,
+ pa_sample_spec *source_ss, pa_channel_map *source_map,
+ pa_sample_spec *sink_ss, pa_channel_map *sink_map,
+ const char *args);
+void pa_adrian_ec_run(pa_echo_canceller *ec, uint8_t *rec, uint8_t *play, uint8_t *out);
+void pa_adrian_ec_done(pa_echo_canceller *ec);
+uint32_t pa_adrian_ec_get_block_size(pa_echo_canceller *ec);
diff --git a/src/modules/echo-cancel/module-echo-cancel.c b/src/modules/echo-cancel/module-echo-cancel.c
index 6a88167..75f74d3 100644
--- a/src/modules/echo-cancel/module-echo-cancel.c
+++ b/src/modules/echo-cancel/module-echo-cancel.c
@@ -82,6 +82,7 @@ PA_MODULE_USAGE(
/* NOTE: Make sure the enum and ec_table are maintained in the correct order */
enum {
PA_ECHO_CANCELLER_SPEEX,
+ PA_ECHO_CANCELLER_ADRIAN,
};
#define DEFAULT_ECHO_CANCELLER PA_ECHO_CANCELLER_SPEEX
@@ -94,6 +95,13 @@ static const pa_echo_canceller ec_table[] = {
.done = pa_speex_ec_done,
.get_block_size = pa_speex_ec_get_block_size,
},
+ {
+ /* Adrian Andre's NLMS implementation */
+ .init = pa_adrian_ec_init,
+ .run = pa_adrian_ec_run,
+ .done = pa_adrian_ec_done,
+ .get_block_size = pa_adrian_ec_get_block_size,
+ },
};
#define DEFAULT_ADJUST_TIME_USEC (1*PA_USEC_PER_SEC)
commit 4a9fa8cc7ff0963fbf794a0018445604f69721e7
Author: Arun Raghavan <arun.raghavan at collabora.co.uk>
Date: Tue Sep 7 14:02:32 2010 +0530
echo-cancel: Allow selection of AEC method using modargs
This adds an "aec_method" module argument to allow us to select the AEC
implementation to use.
diff --git a/src/modules/echo-cancel/module-echo-cancel.c b/src/modules/echo-cancel/module-echo-cancel.c
index 75f74d3..57e60c5 100644
--- a/src/modules/echo-cancel/module-echo-cancel.c
+++ b/src/modules/echo-cancel/module-echo-cancel.c
@@ -75,17 +75,19 @@ PA_MODULE_USAGE(
"rate=<sample rate> "
"channels=<number of channels> "
"channel_map=<channel map> "
+ "aec_method=<implementation to use> "
"aec_args=<parameters for the AEC engine> "
"save_aec=<save AEC data in /tmp> "
));
/* NOTE: Make sure the enum and ec_table are maintained in the correct order */
-enum {
- PA_ECHO_CANCELLER_SPEEX,
+typedef enum {
+ PA_ECHO_CANCELLER_INVALID = -1,
+ PA_ECHO_CANCELLER_SPEEX = 0,
PA_ECHO_CANCELLER_ADRIAN,
-};
+} pa_echo_canceller_method_t;
-#define DEFAULT_ECHO_CANCELLER PA_ECHO_CANCELLER_SPEEX
+#define DEFAULT_ECHO_CANCELLER "speex"
static const pa_echo_canceller ec_table[] = {
{
@@ -205,6 +207,7 @@ static const char* const valid_modargs[] = {
"rate",
"channels",
"channel_map",
+ "aec_method",
"aec_args",
"save_aec",
NULL
@@ -1274,6 +1277,15 @@ static void sink_input_mute_changed_cb(pa_sink_input *i) {
pa_sink_mute_changed(u->sink, i->muted);
}
+static pa_echo_canceller_method_t get_ec_method_from_string(const char *method)
+{
+ if (strcmp(method, "speex") == 0)
+ return PA_ECHO_CANCELLER_SPEEX;
+ else if (strcmp(method, "adrian") == 0)
+ return PA_ECHO_CANCELLER_ADRIAN;
+ else
+ return PA_ECHO_CANCELLER_INVALID;
+}
int pa__init(pa_module*m) {
struct userdata *u;
@@ -1287,6 +1299,7 @@ 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_assert(m);
@@ -1332,10 +1345,16 @@ int pa__init(pa_module*m) {
pa_log("Failed to alloc echo canceller");
goto fail;
}
- u->ec->init = ec_table[DEFAULT_ECHO_CANCELLER].init;
- u->ec->run = ec_table[DEFAULT_ECHO_CANCELLER].run;
- u->ec->done = ec_table[DEFAULT_ECHO_CANCELLER].done;
- u->ec->get_block_size = ec_table[DEFAULT_ECHO_CANCELLER].get_block_size;
+
+ 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;
+ u->ec->get_block_size = ec_table[ec_method].get_block_size;
adjust_time_sec = DEFAULT_ADJUST_TIME_USEC / PA_USEC_PER_SEC;
if (pa_modargs_get_value_u32(ma, "adjust_time", &adjust_time_sec) < 0) {
commit b6b8a7b7a7f4f8badd39ebb4ee2be1c11ff1a204
Author: Arun Raghavan <arun.raghavan at collabora.co.uk>
Date: Tue Sep 7 14:55:38 2010 +0530
echo-cancel: Make blocksize a module-wide parameter
Since all algorithms will need to specify a block size (the amount of
data to be processed together), we make this a common parameter and have
the implementation set it at initialisation time.
diff --git a/src/modules/echo-cancel/adrian.c b/src/modules/echo-cancel/adrian.c
index 86c22cb..1b91373 100644
--- a/src/modules/echo-cancel/adrian.c
+++ b/src/modules/echo-cancel/adrian.c
@@ -54,7 +54,7 @@ static void pa_adrian_ec_fixate_spec(pa_sample_spec *source_ss, pa_channel_map *
pa_bool_t pa_adrian_ec_init(pa_echo_canceller *ec,
pa_sample_spec *source_ss, pa_channel_map *source_map,
pa_sample_spec *sink_ss, pa_channel_map *sink_map,
- const char *args)
+ uint32_t *blocksize, const char *args)
{
int framelen, rate;
uint32_t frame_size_ms;
@@ -76,9 +76,9 @@ pa_bool_t pa_adrian_ec_init(pa_echo_canceller *ec,
rate = source_ss->rate;
framelen = (rate * frame_size_ms) / 1000;
- ec->params.priv.adrian.blocksize = framelen * pa_frame_size (source_ss);
+ *blocksize = ec->params.priv.adrian.blocksize = framelen * pa_frame_size (source_ss);
- pa_log_debug ("Using framelen %d, blocksize %lld, channels %d, rate %d", framelen, (long long) ec->params.priv.adrian.blocksize, source_ss->channels, source_ss->rate);
+ pa_log_debug ("Using framelen %d, blocksize %u, channels %d, rate %d", framelen, ec->params.priv.adrian.blocksize, source_ss->channels, source_ss->rate);
ec->params.priv.adrian.aec = AEC_init(rate);
if (!ec->params.priv.adrian.aec)
@@ -114,8 +114,3 @@ void pa_adrian_ec_done(pa_echo_canceller *ec)
pa_xfree(ec->params.priv.adrian.aec);
ec->params.priv.adrian.aec = NULL;
}
-
-uint32_t pa_adrian_ec_get_block_size(pa_echo_canceller *ec)
-{
- return ec->params.priv.adrian.blocksize;
-}
diff --git a/src/modules/echo-cancel/echo-cancel.h b/src/modules/echo-cancel/echo-cancel.h
index 65e0e24..bf81b1d 100644
--- a/src/modules/echo-cancel/echo-cancel.h
+++ b/src/modules/echo-cancel/echo-cancel.h
@@ -37,7 +37,6 @@ typedef struct pa_echo_canceller_params pa_echo_canceller_params;
struct pa_echo_canceller_params {
union {
struct {
- uint32_t blocksize;
SpeexEchoState *state;
} speex;
struct {
@@ -56,10 +55,10 @@ struct pa_echo_canceller {
pa_channel_map *source_map,
pa_sample_spec *sink_ss,
pa_channel_map *sink_map,
+ uint32_t *blocksize,
const char *args);
void (*run) (pa_echo_canceller *ec, uint8_t *rec, uint8_t *play, uint8_t *out);
void (*done) (pa_echo_canceller *ec);
- uint32_t (*get_block_size) (pa_echo_canceller *ec);
pa_echo_canceller_params params;
};
@@ -68,16 +67,14 @@ struct pa_echo_canceller {
pa_bool_t pa_speex_ec_init(pa_echo_canceller *ec,
pa_sample_spec *source_ss, pa_channel_map *source_map,
pa_sample_spec *sink_ss, pa_channel_map *sink_map,
- const char *args);
+ uint32_t *blocksize, const char *args);
void pa_speex_ec_run(pa_echo_canceller *ec, uint8_t *rec, uint8_t *play, uint8_t *out);
void pa_speex_ec_done(pa_echo_canceller *ec);
-uint32_t pa_speex_ec_get_block_size(pa_echo_canceller *ec);
/* Adrian Andre's echo canceller */
pa_bool_t pa_adrian_ec_init(pa_echo_canceller *ec,
pa_sample_spec *source_ss, pa_channel_map *source_map,
pa_sample_spec *sink_ss, pa_channel_map *sink_map,
- const char *args);
+ uint32_t *blocksize, const char *args);
void pa_adrian_ec_run(pa_echo_canceller *ec, uint8_t *rec, uint8_t *play, uint8_t *out);
void pa_adrian_ec_done(pa_echo_canceller *ec);
-uint32_t pa_adrian_ec_get_block_size(pa_echo_canceller *ec);
diff --git a/src/modules/echo-cancel/module-echo-cancel.c b/src/modules/echo-cancel/module-echo-cancel.c
index 57e60c5..06583f4 100644
--- a/src/modules/echo-cancel/module-echo-cancel.c
+++ b/src/modules/echo-cancel/module-echo-cancel.c
@@ -95,14 +95,12 @@ static const pa_echo_canceller ec_table[] = {
.init = pa_speex_ec_init,
.run = pa_speex_ec_run,
.done = pa_speex_ec_done,
- .get_block_size = pa_speex_ec_get_block_size,
},
{
/* Adrian Andre's NLMS implementation */
.init = pa_adrian_ec_init,
.run = pa_adrian_ec_run,
.done = pa_adrian_ec_done,
- .get_block_size = pa_adrian_ec_get_block_size,
},
};
@@ -161,6 +159,7 @@ struct userdata {
uint32_t save_aec;
pa_echo_canceller *ec;
+ uint32_t blocksize;
pa_bool_t need_realign;
@@ -345,7 +344,7 @@ static int source_process_msg_cb(pa_msgobject *o, int code, void *data, int64_t
/* Add the latency internal to our source output on top */
pa_bytes_to_usec(pa_memblockq_get_length(u->source_output->thread_info.delay_memblockq), &u->source_output->source->sample_spec) +
/* and the buffering we do on the source */
- pa_bytes_to_usec(u->ec->get_block_size(u->ec), &u->source_output->source->sample_spec);
+ pa_bytes_to_usec(u->blocksize, &u->source_output->source->sample_spec);
return 0;
@@ -632,7 +631,6 @@ static void do_resync(struct userdata *u) {
static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk) {
struct userdata *u;
size_t rlen, plen;
- uint32_t blocksize;
pa_source_output_assert_ref(o);
pa_source_output_assert_io_context(o);
@@ -658,20 +656,18 @@ static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk)
rlen = pa_memblockq_get_length(u->source_memblockq);
plen = pa_memblockq_get_length(u->sink_memblockq);
- blocksize = u->ec->get_block_size(u->ec);
-
- while (rlen >= blocksize) {
+ while (rlen >= u->blocksize) {
pa_memchunk rchunk, pchunk;
/* take fixed block from recorded samples */
- pa_memblockq_peek_fixed_size(u->source_memblockq, blocksize, &rchunk);
+ pa_memblockq_peek_fixed_size(u->source_memblockq, u->blocksize, &rchunk);
- if (plen > blocksize) {
+ if (plen > u->blocksize) {
uint8_t *rdata, *pdata, *cdata;
pa_memchunk cchunk;
/* take fixed block from played samples */
- pa_memblockq_peek_fixed_size(u->sink_memblockq, blocksize, &pchunk);
+ pa_memblockq_peek_fixed_size(u->sink_memblockq, u->blocksize, &pchunk);
rdata = pa_memblock_acquire(rchunk.memblock);
rdata += rchunk.index;
@@ -679,7 +675,7 @@ static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk)
pdata += pchunk.index;
cchunk.index = 0;
- cchunk.length = blocksize;
+ cchunk.length = u->blocksize;
cchunk.memblock = pa_memblock_new(u->source->core->mempool, cchunk.length);
cdata = pa_memblock_acquire(cchunk.memblock);
@@ -688,11 +684,11 @@ static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk)
if (u->save_aec) {
if (u->captured_file)
- fwrite(rdata, 1, blocksize, u->captured_file);
+ fwrite(rdata, 1, u->blocksize, u->captured_file);
if (u->played_file)
- fwrite(pdata, 1, blocksize, u->played_file);
+ fwrite(pdata, 1, u->blocksize, u->played_file);
if (u->canceled_file)
- fwrite(cdata, 1, blocksize, u->canceled_file);
+ fwrite(cdata, 1, u->blocksize, u->canceled_file);
pa_log_debug("AEC frame saved.");
}
@@ -701,7 +697,7 @@ static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk)
pa_memblock_release(rchunk.memblock);
/* drop consumed sink samples */
- pa_memblockq_drop(u->sink_memblockq, blocksize);
+ pa_memblockq_drop(u->sink_memblockq, u->blocksize);
pa_memblock_unref(pchunk.memblock);
pa_memblock_unref(rchunk.memblock);
@@ -709,11 +705,11 @@ static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk)
* source */
rchunk = cchunk;
- plen -= blocksize;
+ plen -= u->blocksize;
} else {
/* not enough played samples to perform echo cancelation,
* drop what we have */
- pa_memblockq_drop(u->sink_memblockq, blocksize - plen);
+ pa_memblockq_drop(u->sink_memblockq, u->blocksize - plen);
plen = 0;
}
@@ -721,9 +717,9 @@ static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk)
pa_source_post(u->source, &rchunk);
pa_memblock_unref(rchunk.memblock);
- pa_memblockq_drop(u->source_memblockq, blocksize);
+ pa_memblockq_drop(u->source_memblockq, u->blocksize);
- rlen -= blocksize;
+ rlen -= u->blocksize;
}
}
@@ -1354,7 +1350,6 @@ int pa__init(pa_module*m) {
u->ec->init = ec_table[ec_method].init;
u->ec->run = ec_table[ec_method].run;
u->ec->done = ec_table[ec_method].done;
- u->ec->get_block_size = ec_table[ec_method].get_block_size;
adjust_time_sec = DEFAULT_ADJUST_TIME_USEC / PA_USEC_PER_SEC;
if (pa_modargs_get_value_u32(ma, "adjust_time", &adjust_time_sec) < 0) {
@@ -1376,7 +1371,7 @@ int pa__init(pa_module*m) {
u->asyncmsgq = pa_asyncmsgq_new(0);
u->need_realign = TRUE;
if (u->ec->init) {
- if (!u->ec->init(u->ec, &source_ss, &source_map, &sink_ss, &sink_map, pa_modargs_get_value(ma, "aec_args", NULL))) {
+ if (!u->ec->init(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");
goto fail;
}
diff --git a/src/modules/echo-cancel/speex.c b/src/modules/echo-cancel/speex.c
index cb8212e..0d4d123 100644
--- a/src/modules/echo-cancel/speex.c
+++ b/src/modules/echo-cancel/speex.c
@@ -51,7 +51,7 @@ static void pa_speex_ec_fixate_spec(pa_sample_spec *source_ss, pa_channel_map *s
pa_bool_t pa_speex_ec_init(pa_echo_canceller *ec,
pa_sample_spec *source_ss, pa_channel_map *source_map,
pa_sample_spec *sink_ss, pa_channel_map *sink_map,
- const char *args)
+ uint32_t *blocksize, const char *args)
{
int framelen, y, rate;
uint32_t frame_size_ms, filter_size_ms;
@@ -84,9 +84,9 @@ pa_bool_t pa_speex_ec_init(pa_echo_canceller *ec,
y >>= 1;
framelen = y;
- ec->params.priv.speex.blocksize = framelen * pa_frame_size (source_ss);
+ *blocksize = framelen * pa_frame_size (source_ss);
- pa_log_debug ("Using framelen %d, blocksize %lld, channels %d, rate %d", framelen, (long long) ec->params.priv.speex.blocksize, source_ss->channels, source_ss->rate);
+ pa_log_debug ("Using framelen %d, blocksize %u, channels %d, rate %d", framelen, *blocksize, source_ss->channels, source_ss->rate);
ec->params.priv.speex.state = speex_echo_state_init_mc (framelen, (rate * filter_size_ms) / 1000, source_ss->channels, source_ss->channels);
@@ -114,8 +114,3 @@ void pa_speex_ec_done(pa_echo_canceller *ec)
speex_echo_state_destroy (ec->params.priv.speex.state);
ec->params.priv.speex.state = NULL;
}
-
-uint32_t pa_speex_ec_get_block_size(pa_echo_canceller *ec)
-{
- return ec->params.priv.speex.blocksize;
-}
commit 2923c5eb688cadef9eef9044af8c7e3764ff8cb6
Author: Arun Raghavan <arun.raghavan at collabora.co.uk>
Date: Tue Sep 7 15:07:39 2010 +0530
echo-cancel: Mark immutable parameters as const in vfunc
Marks the recording and playback streams as const in the
pa_echo_canceller->run method for clarity.
diff --git a/src/modules/echo-cancel/adrian.c b/src/modules/echo-cancel/adrian.c
index 1b91373..86db1e2 100644
--- a/src/modules/echo-cancel/adrian.c
+++ b/src/modules/echo-cancel/adrian.c
@@ -93,7 +93,7 @@ fail:
return FALSE;
}
-void pa_adrian_ec_run(pa_echo_canceller *ec, uint8_t *rec, uint8_t *play, uint8_t *out)
+void pa_adrian_ec_run(pa_echo_canceller *ec, const uint8_t *rec, const uint8_t *play, uint8_t *out)
{
unsigned int i;
diff --git a/src/modules/echo-cancel/echo-cancel.h b/src/modules/echo-cancel/echo-cancel.h
index bf81b1d..448ad99 100644
--- a/src/modules/echo-cancel/echo-cancel.h
+++ b/src/modules/echo-cancel/echo-cancel.h
@@ -57,7 +57,7 @@ struct pa_echo_canceller {
pa_channel_map *sink_map,
uint32_t *blocksize,
const char *args);
- void (*run) (pa_echo_canceller *ec, uint8_t *rec, uint8_t *play, uint8_t *out);
+ void (*run) (pa_echo_canceller *ec, const uint8_t *rec, const uint8_t *play, uint8_t *out);
void (*done) (pa_echo_canceller *ec);
pa_echo_canceller_params params;
@@ -68,7 +68,7 @@ pa_bool_t pa_speex_ec_init(pa_echo_canceller *ec,
pa_sample_spec *source_ss, pa_channel_map *source_map,
pa_sample_spec *sink_ss, pa_channel_map *sink_map,
uint32_t *blocksize, const char *args);
-void pa_speex_ec_run(pa_echo_canceller *ec, uint8_t *rec, uint8_t *play, uint8_t *out);
+void pa_speex_ec_run(pa_echo_canceller *ec, const uint8_t *rec, const uint8_t *play, uint8_t *out);
void pa_speex_ec_done(pa_echo_canceller *ec);
/* Adrian Andre's echo canceller */
@@ -76,5 +76,5 @@ pa_bool_t pa_adrian_ec_init(pa_echo_canceller *ec,
pa_sample_spec *source_ss, pa_channel_map *source_map,
pa_sample_spec *sink_ss, pa_channel_map *sink_map,
uint32_t *blocksize, const char *args);
-void pa_adrian_ec_run(pa_echo_canceller *ec, uint8_t *rec, uint8_t *play, uint8_t *out);
+void pa_adrian_ec_run(pa_echo_canceller *ec, const uint8_t *rec, const uint8_t *play, uint8_t *out);
void pa_adrian_ec_done(pa_echo_canceller *ec);
diff --git a/src/modules/echo-cancel/speex.c b/src/modules/echo-cancel/speex.c
index 0d4d123..17a89d2 100644
--- a/src/modules/echo-cancel/speex.c
+++ b/src/modules/echo-cancel/speex.c
@@ -104,7 +104,7 @@ fail:
return FALSE;
}
-void pa_speex_ec_run(pa_echo_canceller *ec, uint8_t *rec, uint8_t *play, uint8_t *out)
+void pa_speex_ec_run(pa_echo_canceller *ec, const uint8_t *rec, const uint8_t *play, uint8_t *out)
{
speex_echo_cancellation(ec->params.priv.speex.state, (const spx_int16_t *) rec, (const spx_int16_t *) play, (spx_int16_t *) out);
}
commit 9f79c0ebb14644762aa032673eadc8c42271b645
Author: Wim Taymans <wim.taymans at collabora.co.uk>
Date: Wed Sep 8 18:49:48 2010 +0200
echo-cancel: rework alignment code
Rework the code to align capture and playback samples so that we can keep more
accurate timings.
diff --git a/src/modules/echo-cancel/module-echo-cancel.c b/src/modules/echo-cancel/module-echo-cancel.c
index 06583f4..4df77c3 100644
--- a/src/modules/echo-cancel/module-echo-cancel.c
+++ b/src/modules/echo-cancel/module-echo-cancel.c
@@ -173,6 +173,7 @@ struct userdata {
pa_source_output *source_output;
pa_memblockq *source_memblockq; /* echo canceler needs fixed sized chunks */
pa_atomic_t source_active;
+ size_t source_skip;
pa_sink *sink;
pa_bool_t sink_auto_desc;
@@ -181,6 +182,7 @@ struct userdata {
int64_t send_counter; /* updated in sink IO thread */
int64_t recv_counter;
pa_atomic_t sink_active;
+ size_t sink_skip;
pa_atomic_t request_resync;
@@ -594,8 +596,8 @@ static void apply_diff_time(struct userdata *u, int64_t diff_time) {
if (diff > 0) {
pa_log_info("Playback after capture (%lld), drop sink %lld", (long long) diff_time, (long long) diff);
- /* go forwards on the read side */
- pa_memblockq_drop(u->sink_memblockq, diff);
+ u->sink_skip = diff;
+ u->source_skip = 0;
}
} else if (diff_time > 0) {
diff = pa_usec_to_bytes (diff_time, &u->source_output->sample_spec);
@@ -603,8 +605,8 @@ static void apply_diff_time(struct userdata *u, int64_t diff_time) {
if (diff > 0) {
pa_log_info("playback too far ahead (%lld), drop source %lld", (long long) diff_time, (long long) diff);
- /* go back on the read side */
- pa_memblockq_rewind(u->sink_memblockq, diff);
+ u->source_skip = diff;
+ u->sink_skip = 0;
}
}
}
@@ -662,55 +664,66 @@ static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk)
/* take fixed block from recorded samples */
pa_memblockq_peek_fixed_size(u->source_memblockq, u->blocksize, &rchunk);
- if (plen > u->blocksize) {
+ if (plen > u->blocksize && u->source_skip == 0) {
uint8_t *rdata, *pdata, *cdata;
pa_memchunk cchunk;
- /* take fixed block from played samples */
- pa_memblockq_peek_fixed_size(u->sink_memblockq, u->blocksize, &pchunk);
-
- rdata = pa_memblock_acquire(rchunk.memblock);
- rdata += rchunk.index;
- pdata = pa_memblock_acquire(pchunk.memblock);
- pdata += pchunk.index;
-
- cchunk.index = 0;
- cchunk.length = u->blocksize;
- cchunk.memblock = pa_memblock_new(u->source->core->mempool, cchunk.length);
- cdata = pa_memblock_acquire(cchunk.memblock);
-
- /* perform echo cancelation */
- u->ec->run(u->ec, rdata, pdata, cdata);
-
- if (u->save_aec) {
- if (u->captured_file)
- fwrite(rdata, 1, u->blocksize, u->captured_file);
- if (u->played_file)
- fwrite(pdata, 1, u->blocksize, u->played_file);
- if (u->canceled_file)
- fwrite(cdata, 1, u->blocksize, u->canceled_file);
- pa_log_debug("AEC frame saved.");
+ 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;
}
- pa_memblock_release(cchunk.memblock);
- pa_memblock_release(pchunk.memblock);
- pa_memblock_release(rchunk.memblock);
-
- /* drop consumed sink samples */
- pa_memblockq_drop(u->sink_memblockq, u->blocksize);
- pa_memblock_unref(pchunk.memblock);
-
- pa_memblock_unref(rchunk.memblock);
- /* the filtered samples now become the samples from our
- * source */
- rchunk = cchunk;
-
- plen -= u->blocksize;
- } else {
- /* not enough played samples to perform echo cancelation,
- * drop what we have */
- pa_memblockq_drop(u->sink_memblockq, u->blocksize - plen);
- plen = 0;
+ if (plen > u->blocksize && u->sink_skip == 0) {
+ /* take fixed block from played samples */
+ pa_memblockq_peek_fixed_size(u->sink_memblockq, u->blocksize, &pchunk);
+
+ rdata = pa_memblock_acquire(rchunk.memblock);
+ rdata += rchunk.index;
+ pdata = pa_memblock_acquire(pchunk.memblock);
+ pdata += pchunk.index;
+
+ cchunk.index = 0;
+ cchunk.length = u->blocksize;
+ cchunk.memblock = pa_memblock_new(u->source->core->mempool, cchunk.length);
+ cdata = pa_memblock_acquire(cchunk.memblock);
+
+ /* perform echo cancelation */
+ u->ec->run(u->ec, rdata, pdata, cdata);
+
+ if (u->save_aec) {
+ if (u->captured_file)
+ fwrite(rdata, 1, u->blocksize, u->captured_file);
+ if (u->played_file)
+ fwrite(pdata, 1, u->blocksize, u->played_file);
+ if (u->canceled_file)
+ fwrite(cdata, 1, u->blocksize, u->canceled_file);
+ pa_log_debug("AEC frame saved.");
+ }
+
+ pa_memblock_release(cchunk.memblock);
+ pa_memblock_release(pchunk.memblock);
+ pa_memblock_release(rchunk.memblock);
+
+ /* drop consumed sink samples */
+ pa_memblockq_drop(u->sink_memblockq, u->blocksize);
+ pa_memblock_unref(pchunk.memblock);
+
+ pa_memblock_unref(rchunk.memblock);
+ /* the filtered samples now become the samples from our
+ * source */
+ rchunk = cchunk;
+
+ plen -= u->blocksize;
+ }
}
/* forward the (echo-canceled) data to the virtual source */
@@ -718,8 +731,17 @@ static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk)
pa_memblock_unref(rchunk.memblock);
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;
+ }
+ }
}
}
@@ -773,19 +795,13 @@ static void source_output_process_rewind_cb(pa_source_output *o, size_t nbytes)
/* Called from I/O thread context */
static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) {
struct userdata *u;
- size_t amount = 0;
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
pa_log_debug("Sink process rewind %lld", (long long) nbytes);
- if (u->sink->thread_info.rewind_nbytes > 0) {
- amount = PA_MIN(u->sink->thread_info.rewind_nbytes, nbytes);
- u->sink->thread_info.rewind_nbytes = 0;
- }
-
- pa_sink_process_rewind(u->sink, amount);
+ pa_sink_process_rewind(u->sink, nbytes);
pa_asyncmsgq_post(u->asyncmsgq, PA_MSGOBJECT(u->source_output), SOURCE_OUTPUT_MESSAGE_REWIND, NULL, (int64_t) nbytes, NULL, NULL);
u->send_counter -= nbytes;
@@ -807,8 +823,8 @@ static void source_output_snapshot_within_thread(struct userdata *u, struct snap
snapshot->source_latency = latency;
snapshot->source_delay = delay;
snapshot->recv_counter = u->recv_counter;
- snapshot->rlen = rlen;
- snapshot->plen = plen;
+ snapshot->rlen = rlen + u->sink_skip;
+ snapshot->plen = plen + u->source_skip;
}
@@ -900,6 +916,7 @@ static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) {
pa_log_debug("Sink input update max rewind %lld", (long long) nbytes);
+ pa_memblockq_set_maxrewind (u->sink_memblockq, nbytes);
pa_sink_set_max_rewind_within_thread(u->sink, nbytes);
}
@@ -922,7 +939,7 @@ static void sink_input_update_max_request_cb(pa_sink_input *i, size_t nbytes) {
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
- pa_log_debug("Sink input update max rewind %lld", (long long) nbytes);
+ pa_log_debug("Sink input update max request %lld", (long long) nbytes);
pa_sink_set_max_request_within_thread(u->sink, nbytes);
}
commit 56a46842f8c782bcce804ffe8466ecab55eb257d
Author: Wim Taymans <wim.taymans at collabora.co.uk>
Date: Thu Sep 9 12:39:15 2010 +0200
echo-cancel: improve accuracy
Make the echo canceler drift up to 1ms now that things are more accurate.
Add 10 samples of headroom to allow for timing inaccuracies.
diff --git a/src/modules/echo-cancel/module-echo-cancel.c b/src/modules/echo-cancel/module-echo-cancel.c
index 4df77c3..467e216 100644
--- a/src/modules/echo-cancel/module-echo-cancel.c
+++ b/src/modules/echo-cancel/module-echo-cancel.c
@@ -295,7 +295,7 @@ static void time_callback(pa_mainloop_api *a, pa_time_event *e, const struct tim
new_rate = base_rate;
}
else {
- if (diff_time > 4000) {
+ if (diff_time > 1000) {
/* 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);
@@ -594,6 +594,10 @@ static void apply_diff_time(struct userdata *u, int64_t diff_time) {
diff = pa_usec_to_bytes (-diff_time, &u->source_output->sample_spec);
if (diff > 0) {
+ /* add some extra safety samples to compensate for jitter in the
+ * timings */
+ diff += 10 * pa_frame_size (&u->source_output->sample_spec);
+
pa_log_info("Playback after capture (%lld), drop sink %lld", (long long) diff_time, (long long) diff);
u->sink_skip = diff;
commit e9364476e4446ff77d1b8b4e861e0c51fd02f366
Author: Wim Taymans <wim.taymans at collabora.co.uk>
Date: Thu Sep 9 16:20:20 2010 +0200
echo-cancel: pause timer when echo canceling is off
While the sink or source is in the suspended state, disable the timer
callback because we are not doing any echo canceling then.
diff --git a/src/modules/echo-cancel/module-echo-cancel.c b/src/modules/echo-cancel/module-echo-cancel.c
index 467e216..8ae45a5 100644
--- a/src/modules/echo-cancel/module-echo-cancel.c
+++ b/src/modules/echo-cancel/module-echo-cancel.c
@@ -172,7 +172,6 @@ struct userdata {
pa_bool_t source_auto_desc;
pa_source_output *source_output;
pa_memblockq *source_memblockq; /* echo canceler needs fixed sized chunks */
- pa_atomic_t source_active;
size_t source_skip;
pa_sink *sink;
@@ -181,11 +180,11 @@ struct userdata {
pa_memblockq *sink_memblockq;
int64_t send_counter; /* updated in sink IO thread */
int64_t recv_counter;
- pa_atomic_t sink_active;
size_t sink_skip;
pa_atomic_t request_resync;
+ int active_mask;
pa_time_event *time_event;
pa_usec_t adjust_time;
@@ -272,8 +271,8 @@ static void time_callback(pa_mainloop_api *a, pa_time_event *e, const struct tim
pa_assert(u->time_event == e);
pa_assert_ctl_context();
- if (pa_atomic_load (&u->sink_active) == 0 || pa_atomic_load (&u->source_active) == 0)
- goto done;
+ if (u->active_mask != 3)
+ return;
/* update our snapshots */
pa_asyncmsgq_send(u->source_output->source->asyncmsgq, PA_MSGOBJECT(u->source_output), SOURCE_OUTPUT_MESSAGE_LATENCY_SNAPSHOT, &latency_snapshot, 0, NULL);
@@ -318,7 +317,6 @@ static void time_callback(pa_mainloop_api *a, pa_time_event *e, const struct tim
pa_sink_input_set_rate(u->sink_input, new_rate);
}
-done:
pa_core_rttime_restart(u->core, u->time_event, pa_rtclock_now() + u->adjust_time);
}
@@ -398,14 +396,18 @@ static int source_set_state_cb(pa_source *s, pa_source_state_t state) {
!PA_SOURCE_OUTPUT_IS_LINKED(pa_source_output_get_state(u->source_output)))
return 0;
- pa_log_debug("Source state %d", state);
+ pa_log_debug("Source state %d %d", state, u->active_mask);
if (state == PA_SOURCE_RUNNING) {
- pa_atomic_store (&u->source_active, 1);
+ /* restart timer when both sink and source are active */
+ u->active_mask |= 1;
+ if (u->active_mask == 3)
+ pa_core_rttime_restart(u->core, u->time_event, pa_rtclock_now() + u->adjust_time);
+
pa_atomic_store (&u->request_resync, 1);
pa_source_output_cork(u->source_output, FALSE);
} else if (state == PA_SOURCE_SUSPENDED) {
- pa_atomic_store (&u->source_active, 0);
+ u->active_mask &= ~1;
pa_source_output_cork(u->source_output, TRUE);
}
return 0;
@@ -422,14 +424,18 @@ static int sink_set_state_cb(pa_sink *s, pa_sink_state_t state) {
!PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(u->sink_input)))
return 0;
- pa_log_debug("Sink state %d", state);
+ pa_log_debug("Sink state %d %d", state, u->active_mask);
if (state == PA_SINK_RUNNING) {
- pa_atomic_store (&u->sink_active, 1);
+ /* restart timer when both sink and source are active */
+ u->active_mask |= 2;
+ if (u->active_mask == 3)
+ pa_core_rttime_restart(u->core, u->time_event, pa_rtclock_now() + u->adjust_time);
+
pa_atomic_store (&u->request_resync, 1);
pa_sink_input_cork(u->sink_input, FALSE);
} else if (state == PA_SINK_SUSPENDED) {
- pa_atomic_store (&u->sink_active, 0);
+ u->active_mask &= ~2;
pa_sink_input_cork(u->sink_input, TRUE);
}
return 0;
@@ -598,7 +604,7 @@ static void apply_diff_time(struct userdata *u, int64_t diff_time) {
* timings */
diff += 10 * pa_frame_size (&u->source_output->sample_spec);
- pa_log_info("Playback after capture (%lld), drop sink %lld", (long long) diff_time, (long long) diff);
+ pa_log("Playback after capture (%lld), drop sink %lld", (long long) diff_time, (long long) diff);
u->sink_skip = diff;
u->source_skip = 0;
@@ -607,7 +613,7 @@ static void apply_diff_time(struct userdata *u, int64_t diff_time) {
diff = pa_usec_to_bytes (diff_time, &u->source_output->sample_spec);
if (diff > 0) {
- pa_log_info("playback too far ahead (%lld), drop source %lld", (long long) diff_time, (long long) diff);
+ pa_log("playback too far ahead (%lld), drop source %lld", (long long) diff_time, (long long) diff);
u->source_skip = diff;
u->sink_skip = 0;
@@ -1574,6 +1580,9 @@ int pa__init(pa_module*m) {
goto fail;
}
+ /* our source and sink are not suspended when we create them */
+ u->active_mask = 3;
+
if (u->adjust_time > 0)
u->time_event = pa_core_rttime_new(m->core, pa_rtclock_now() + u->adjust_time, time_callback, u);
@@ -1629,6 +1638,9 @@ void pa__done(pa_module*m) {
/* See comments in source_output_kill_cb() above regarding
* destruction order! */
+ if (u->time_event)
+ u->core->mainloop->time_free(u->time_event);
+
if (u->source_output)
pa_source_output_unlink(u->source_output);
if (u->sink_input)
@@ -1649,9 +1661,6 @@ void pa__done(pa_module*m) {
if (u->sink)
pa_sink_unref(u->sink);
- if (u->time_event)
- u->core->mainloop->time_free(u->time_event);
-
if (u->source_memblockq)
pa_memblockq_free(u->source_memblockq);
if (u->sink_memblockq)
commit d39043c30c0fbb986d4bcb97c670eb0f8ba608de
Author: Rico Tzschichholz <ricotz at t-online.de>
Date: Fri Sep 10 12:56:40 2010 +0200
echo-cancel: Fix make distcheck
Add missing files to module_echo_cancel_la_SOURCES
Add Adrian license file to EXTRA_DIST
diff --git a/src/Makefile.am b/src/Makefile.am
index 0980488..d60c0a7 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -153,6 +153,7 @@ EXTRA_DIST = \
daemon/pulseaudio-kde.desktop.in \
map-file \
daemon/pulseaudio-system.conf \
+ modules/echo-cancel/adrian-license.txt \
modules/alsa/mixer/profile-sets/90-pulseaudio.rules \
${ALSA_PROFILES} \
${ALSA_PATHS}
@@ -1605,10 +1606,10 @@ module_suspend_on_idle_la_LIBADD = $(AM_LIBADD) libpulsecore- at PA_MAJORMINORMICRO
module_suspend_on_idle_la_CFLAGS = $(AM_CFLAGS)
# echo-cancel module
-module_echo_cancel_la_SOURCES = modules/echo-cancel/module-echo-cancel.c \
+module_echo_cancel_la_SOURCES = modules/echo-cancel/module-echo-cancel.c modules/echo-cancel/echo-cancel.h \
modules/echo-cancel/speex.c \
- modules/echo-cancel/adrian-aec.c \
- modules/echo-cancel/adrian.c
+ modules/echo-cancel/adrian-aec.c modules/echo-cancel/adrian-aec.h \
+ modules/echo-cancel/adrian.c modules/echo-cancel/adrian.h
module_echo_cancel_la_LDFLAGS = $(MODULE_LDFLAGS)
module_echo_cancel_la_LIBADD = $(AM_LIBADD) libpulsecore- at PA_MAJORMINORMICRO@.la libpulsecommon- at PA_MAJORMINORMICRO@.la libpulse.la $(LIBSPEEX_LIBS)
module_echo_cancel_la_CFLAGS = $(AM_CFLAGS) $(LIBSPEEX_CFLAGS)
commit d8fab8429d68e54b05ae444e84840114073ac833
Author: Arun Raghavan <arun.raghavan at collabora.co.uk>
Date: Mon Sep 20 14:07:44 2010 +0530
echo-cancel: Fix out-of-tree build
diff --git a/src/Makefile.am b/src/Makefile.am
index d60c0a7..9f1a89a 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -70,6 +70,8 @@ AM_CFLAGS = \
-I$(top_builddir)/src/modules/x11 \
-I$(top_srcdir)/src/modules/jack \
-I$(top_builddir)/src/modules/jack \
+ -I$(top_srcdir)/src/modules/echo-cancel \
+ -I$(top_builddir)/src/modules/echo-cancel \
$(PTHREAD_CFLAGS) -D_POSIX_PTHREAD_SEMANTICS \
$(LIBSAMPLERATE_CFLAGS) \
$(LIBSNDFILE_CFLAGS) \
commit 1212a6fd78570f1f31e77bf46fa2e46981013ab3
Author: Arun Raghavan <arun.raghavan at collabora.co.uk>
Date: Thu Sep 23 17:46:00 2010 +0530
echo-cancel: Ensure correct handling of endianness
The adrian module was using home-brewed endianness conversion instead of
the appropriate mactos, and speex assumed a little-endian host. This
fixes both of these.
diff --git a/src/modules/echo-cancel/adrian.c b/src/modules/echo-cancel/adrian.c
index 86db1e2..810d6ea 100644
--- a/src/modules/echo-cancel/adrian.c
+++ b/src/modules/echo-cancel/adrian.c
@@ -30,6 +30,7 @@
#endif
#include <pulsecore/modargs.h>
+#include <pulsecore/endianmacros.h>
#include "echo-cancel.h"
/* should be between 10-20 ms */
@@ -99,8 +100,8 @@ void pa_adrian_ec_run(pa_echo_canceller *ec, const uint8_t *rec, const uint8_t *
for (i = 0; i < ec->params.priv.adrian.blocksize; i += 2) {
/* We know it's S16LE mono data */
- int r = (((int8_t) rec[i + 1]) << 8) | rec[i];
- int p = (((int8_t) play[i + 1]) << 8) | play[i];
+ int r = PA_INT16_FROM_LE(*(int16_t *)(rec + i));
+ int p = PA_INT16_FROM_LE(*(int16_t *)(play + i));
int res;
res = AEC_doAEC(ec->params.priv.adrian.aec, r, p);
diff --git a/src/modules/echo-cancel/speex.c b/src/modules/echo-cancel/speex.c
index 17a89d2..dc765f5 100644
--- a/src/modules/echo-cancel/speex.c
+++ b/src/modules/echo-cancel/speex.c
@@ -42,7 +42,7 @@ static const char* const valid_modargs[] = {
static void pa_speex_ec_fixate_spec(pa_sample_spec *source_ss, pa_channel_map *source_map,
pa_sample_spec *sink_ss, pa_channel_map *sink_map)
{
- source_ss->format = PA_SAMPLE_S16LE;
+ source_ss->format = PA_SAMPLE_S16NE;
*sink_ss = *source_ss;
*sink_map = *source_map;
commit d866adee0ee9b79ebe81fac85fa2f31b522b568e
Author: Arun Raghavan <arun.raghavan at collabora.co.uk>
Date: Mon Sep 27 16:57:01 2010 +0530
echo-cancel: Use S16NE for adrian module
This forces us to get native-endian samples in the adrian module so that
we can rely on the existing endianness conversion mechanisms instead of
doing it in the module.
diff --git a/src/modules/echo-cancel/adrian.c b/src/modules/echo-cancel/adrian.c
index 810d6ea..b1d0baf 100644
--- a/src/modules/echo-cancel/adrian.c
+++ b/src/modules/echo-cancel/adrian.c
@@ -44,7 +44,7 @@ static const char* const valid_modargs[] = {
static void pa_adrian_ec_fixate_spec(pa_sample_spec *source_ss, pa_channel_map *source_map,
pa_sample_spec *sink_ss, pa_channel_map *sink_map)
{
- source_ss->format = PA_SAMPLE_S16LE;
+ source_ss->format = PA_SAMPLE_S16NE;
source_ss->channels = 1;
pa_channel_map_init_mono(source_map);
@@ -99,14 +99,10 @@ void pa_adrian_ec_run(pa_echo_canceller *ec, const uint8_t *rec, const uint8_t *
unsigned int i;
for (i = 0; i < ec->params.priv.adrian.blocksize; i += 2) {
- /* We know it's S16LE mono data */
- int r = PA_INT16_FROM_LE(*(int16_t *)(rec + i));
- int p = PA_INT16_FROM_LE(*(int16_t *)(play + i));
- int res;
-
- res = AEC_doAEC(ec->params.priv.adrian.aec, r, p);
- out[i] = (uint8_t) (res & 0xff);
- out[i + 1] = (uint8_t) ((res >> 8) & 0xff);
+ /* We know it's S16NE mono data */
+ int r = *(int16_t *)(rec + i);
+ int p = *(int16_t *)(play + i);
+ *(int16_t *)(out + i) = (int16_t) AEC_doAEC(ec->params.priv.adrian.aec, r, p);
}
}
commit f93b5e1869cc088acb2aec5a8a69015dc30acbe8
Author: Arun Raghavan <arun.raghavan at collabora.co.uk>
Date: Thu Dec 2 16:34:03 2010 +0530
echo-cancel: Fix source may_move_to function
This is required to make sure that the source output between
module-echo-cancel and ALSA can't get plugged to the virtual source or
monitor of the virtual sink that we expose. This could be triggered by
changing the profile of the underlying ALSA device.
diff --git a/src/modules/echo-cancel/module-echo-cancel.c b/src/modules/echo-cancel/module-echo-cancel.c
index 8ae45a5..8f37fd2 100644
--- a/src/modules/echo-cancel/module-echo-cancel.c
+++ b/src/modules/echo-cancel/module-echo-cancel.c
@@ -1212,7 +1212,7 @@ static pa_bool_t source_output_may_move_to_cb(pa_source_output *o, pa_source *de
pa_assert_ctl_context();
pa_assert_se(u = o->userdata);
- return TRUE;
+ return (u->source != dest) && (u->sink != dest->monitor_of);
}
/* Called from main context */
commit 6664650bd5a876e8cb74615b69b7b2aa987715a1
Author: Arun Raghavan <arun.raghavan at collabora.co.uk>
Date: Sun Dec 5 15:47:17 2010 +0530
i18n: Update POTFILES.*
Add new modules, and skip module-virtual-sink for i18n since it's really
meant to be a template for writing other modules.
diff --git a/po/POTFILES.in b/po/POTFILES.in
index e661bfe..c0fb4df 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -195,3 +195,4 @@ src/modules/bluetooth/module-bluetooth-device.c
src/modules/reserve-wrap.c
src/modules/module-rygel-media-server.c
src/modules/alsa/alsa-mixer.c
+src/modules/echo-cancel/module-echo-cancel.c
--
hooks/post-receive
PulseAudio Sound Server
More information about the pulseaudio-commits
mailing list