[pulseaudio-commits] [SCM] PulseAudio Sound Server branch, master, updated. v1.0-dev-281-gfd14a9a

Colin Guthrie gitmailer-noreply at 0pointer.de
Sat Apr 23 10:43:16 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 master branch has been updated
      from  575ba65714ab85d81b0e520f348d78c2ae80a6f9 (commit)

- Log -----------------------------------------------------------------
fd14a9a filter-heuristics: Match roles correctly
8460466 echo-cancel: Play nice with module-filter-*
8757052 filters: Handle filters on sources as well
ceca42f filters: Handle stream moves properly
42b378b filter-heuristics: Only apply AEC if we're not already on a phone sink
b620e32 test: Make the connect-stress less likely to bail out due to >32 streams.
9ade504 build-sys: Make -isysroot and -mmacosx-version-min configurable
dcab6e1 protocol-dbus: Fix some memory management bugs.
15257b0 match: Match rule earlier, in SINK_INPUT_NEW
9b58b7d match: Don't double free in case of missing table file
ff273b4 module-coreaudio-device: Fix two build warnings
e6003e8 module-coreaudio-device: Set the thread name to device name
cda8ebf module-coreaudio-device: Dispatch sink/source state messages from main loop
2411d9a thread-posix: Use pthread_(get|set)name_np() if available
98f2209 pulsecore:: Define _POSIX_C_SOURCE locally for rtclock on OSX
7a3b3a3 util: Implement pa_get_binary_name() for Mac OS X
2a44304 module-coreaudio-detect: Add 'ioproc_frames' parameter
-----------------------------------------------------------------------

Summary of changes:
 configure.ac                                 |   21 ++-
 src/modules/echo-cancel/module-echo-cancel.c |   16 ++-
 src/modules/macosx/module-coreaudio-detect.c |   23 ++-
 src/modules/macosx/module-coreaudio-device.c |   94 +++++----
 src/modules/module-filter-apply.c            |  314 ++++++++++++++++++++-----
 src/modules/module-filter-heuristics.c       |  116 +++++++++-
 src/modules/module-match.c                   |   36 +--
 src/pulse/proplist.h                         |    5 +-
 src/pulse/util.c                             |   26 ++
 src/pulsecore/core-rtclock.c                 |    4 +
 src/pulsecore/protocol-dbus.c                |   12 +-
 src/pulsecore/thread-posix.c                 |    9 +
 src/tests/connect-stress.c                   |   10 +-
 13 files changed, 533 insertions(+), 153 deletions(-)

-----------------------------------------------------------------------

commit 2a44304bee40135a9a90b9af176729f895b0d5e8
Author: Daniel Mack <zonque at gmail.com>
Date:   Thu Apr 7 12:59:52 2011 +0200

    module-coreaudio-detect: Add 'ioproc_frames' parameter
    
    This value is passed on to the instances of module-coreaudio-device that
    are loaded upon device detection. The value is purely optional, as the
    device module will fall back to to its default if it's not given.

diff --git a/src/modules/macosx/module-coreaudio-detect.c b/src/modules/macosx/module-coreaudio-detect.c
index 263fc72..f4f2ee2 100644
--- a/src/modules/macosx/module-coreaudio-detect.c
+++ b/src/modules/macosx/module-coreaudio-detect.c
@@ -41,7 +41,12 @@ PA_MODULE_AUTHOR("Daniel Mack");
 PA_MODULE_DESCRIPTION("CoreAudio device detection");
 PA_MODULE_VERSION(PACKAGE_VERSION);
 PA_MODULE_LOAD_ONCE(TRUE);
-PA_MODULE_USAGE("");
+PA_MODULE_USAGE("ioproc_frames=<passed on to module-coreaudio-device> ");
+
+static const char* const valid_modargs[] = {
+    "ioproc_frames",
+    NULL
+};
 
 typedef struct ca_device ca_device;
 
@@ -54,7 +59,7 @@ struct ca_device {
 struct userdata {
     int detect_fds[2];
     pa_io_event *detect_io;
-
+    unsigned int ioproc_frames;
     PA_LLIST_HEAD(ca_device, devices);
 };
 
@@ -83,7 +88,11 @@ static int ca_device_added(struct pa_module *m, AudioObjectID id) {
     if (!err && strcmp(tmp, "pulseaudio.org") == 0)
         return 0;
 
-    args = pa_sprintf_malloc("object_id=%d", (int) id);
+    if (u->ioproc_frames)
+        args = pa_sprintf_malloc("object_id=%d ioproc_frames=%d", (int) id, u->ioproc_frames);
+    else
+        args = pa_sprintf_malloc("object_id=%d", (int) id);
+
     pa_log_debug("Loading %s with arguments '%s'", DEVICE_MODULE_NAME, args);
     mod = pa_module_load(m->core, DEVICE_MODULE_NAME, args);
     pa_xfree(args);
@@ -203,11 +212,19 @@ static void detect_handle(pa_mainloop_api *a, pa_io_event *e, int fd, pa_io_even
 int pa__init(pa_module *m) {
     struct userdata *u = pa_xnew0(struct userdata, 1);
     AudioObjectPropertyAddress property_address;
+    pa_modargs *ma;
 
     pa_assert(m);
 
     m->userdata = u;
 
+    if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+        pa_log("Failed to parse module arguments.");
+        goto fail;
+    }
+
+    pa_modargs_get_value_u32(ma, "ioproc_frames", &u->ioproc_frames);
+
     property_address.mSelector = kAudioHardwarePropertyDevices;
     property_address.mScope = kAudioObjectPropertyScopeGlobal;
     property_address.mElement = kAudioObjectPropertyElementMaster;

commit 7a3b3a376308df191b19b73fe8dbe97659a1061a
Author: Daniel Mack <zonque at gmail.com>
Date:   Fri Apr 15 19:35:25 2011 +0200

    util: Implement pa_get_binary_name() for Mac OS X

diff --git a/src/pulse/util.c b/src/pulse/util.c
index c5cfc8c..48ccf29 100644
--- a/src/pulse/util.c
+++ b/src/pulse/util.c
@@ -49,6 +49,11 @@
 #include <sys/prctl.h>
 #endif
 
+#ifdef OS_IS_DARWIN
+#include <libgen.h>
+#include <sys/sysctl.h>
+#endif
+
 #include <pulse/xmalloc.h>
 #include <pulse/timeval.h>
 
@@ -221,6 +226,27 @@ char *pa_get_binary_name(char *s, size_t l) {
     }
 #endif
 
+#ifdef OS_IS_DARWIN
+    {
+        int mib[] = { CTL_KERN, KERN_PROCARGS, getpid(), 0 };
+        size_t len, nmib = (sizeof(mib) / sizeof(mib[0])) - 1;
+        char *buf;
+
+        sysctl(mib, nmib, NULL, &len, NULL, 0);
+        buf = (char *) pa_xmalloc(len);
+
+        if (sysctl(mib, nmib, buf, &len, NULL, 0) == 0) {
+            pa_strlcpy(s, basename(buf), l);
+            pa_xfree(buf);
+            return s;
+        }
+
+        pa_xfree(buf);
+
+        /* fall thru */
+    }
+#endif /* OS_IS_DARWIN */
+
     errno = ENOENT;
     return NULL;
 }

commit 98f2209663596ec7d022af2aa8328ef224f2b80b
Author: Daniel Mack <zonque at gmail.com>
Date:   Fri Apr 22 02:27:35 2011 +0200

    pulsecore:: Define _POSIX_C_SOURCE locally for rtclock on OSX
    
    Defining this macro on a global level is disadvantageous for other APIs,
    and as we need it for clock_gettime() only on Mac OS X, define it
    locally in pulsecore/core-rtclock.c only.

diff --git a/configure.ac b/configure.ac
index 5b41bb4..e2b5a6a 100644
--- a/configure.ac
+++ b/configure.ac
@@ -89,7 +89,6 @@ case $host in
       AC_DEFINE(__EXTENSIONS__,         1, Needed to get declarations for msg_control and msg_controllen on Solaris)
       ;;
    *-*-darwin* )
-      AC_DEFINE([_POSIX_C_SOURCE], [200112L], [Needed to get clock_gettime on Mac OS X])
       AC_DEFINE([_DARWIN_C_SOURCE], [200112L], [Needed to get NSIG on Mac OS X])
       ;;
 esac
diff --git a/src/pulsecore/core-rtclock.c b/src/pulsecore/core-rtclock.c
index 331ac11..6632cc6 100644
--- a/src/pulsecore/core-rtclock.c
+++ b/src/pulsecore/core-rtclock.c
@@ -24,6 +24,10 @@
 #include <config.h>
 #endif
 
+#ifdef OS_IS_DARWIN
+#define _POSIX_C_SOURCE 1
+#endif
+
 #include <stddef.h>
 #include <time.h>
 #include <sys/time.h>

commit 2411d9accd3b7b31e37fcfe083f06b5e35d84679
Author: Daniel Mack <zonque at gmail.com>
Date:   Fri Apr 22 04:28:11 2011 +0200

    thread-posix: Use pthread_(get|set)name_np() if available
    
    Newer generations of libpthread have functions to set and get the thread
    names. If available, use them.

diff --git a/configure.ac b/configure.ac
index e2b5a6a..6f262fb 100644
--- a/configure.ac
+++ b/configure.ac
@@ -448,6 +448,8 @@ AC_SEARCH_LIBS([shm_open], [rt])
 AC_SEARCH_LIBS([inet_ntop], [nsl])
 AC_SEARCH_LIBS([timer_create], [rt])
 AC_SEARCH_LIBS([pthread_setaffinity_np], [pthread])
+AC_SEARCH_LIBS([pthread_getname_np], [pthread])
+AC_SEARCH_LIBS([pthread_setname_np], [pthread])
 
 # BSD
 AC_SEARCH_LIBS([connect], [socket])
@@ -489,7 +491,7 @@ AC_FUNC_SELECT_ARGTYPES
 AC_CHECK_FUNCS_ONCE([chmod chown fstat fchown fchmod clock_gettime getaddrinfo getgrgid_r getgrnam_r \
     getpwnam_r getpwuid_r gettimeofday getuid mlock nanosleep \
     pipe posix_fadvise posix_madvise posix_memalign setpgid setsid shm_open \
-    sigaction sleep symlink sysconf uname pthread_setaffinity_np])
+    sigaction sleep symlink sysconf uname pthread_setaffinity_np pthread_getname_np pthread_setname_np])
 AC_CHECK_FUNCS([mkfifo], [HAVE_MKFIFO=1], [HAVE_MKFIFO=0])
 
 AM_CONDITIONAL(HAVE_MKFIFO, test "x$HAVE_MKFIFO" = "x1")
diff --git a/src/pulsecore/thread-posix.c b/src/pulsecore/thread-posix.c
index 7d5252d..58bcb72 100644
--- a/src/pulsecore/thread-posix.c
+++ b/src/pulsecore/thread-posix.c
@@ -73,6 +73,8 @@ static void* internal_thread_func(void *userdata) {
 
 #ifdef __linux__
     prctl(PR_SET_NAME, t->name);
+#elif defined(HAVE_PTHREAD_SETNAME_NP) && defined(OS_IS_DARWIN)
+    pthread_setname_np(t->name);
 #endif
 
     t->id = pthread_self();
@@ -177,6 +179,8 @@ void pa_thread_set_name(pa_thread *t, const char *name) {
 
 #ifdef __linux__
     prctl(PR_SET_NAME, name);
+#elif defined(HAVE_PTHREAD_SETNAME_NP) && defined(OS_IS_DARWIN)
+    pthread_setname_np(name);
 #endif
 }
 
@@ -194,6 +198,11 @@ const char *pa_thread_get_name(pa_thread *t) {
             t->name = NULL;
         }
     }
+#elif defined(HAVE_PTHREAD_GETNAME_NP) && defined(OS_IS_DARWIN)
+    if (!t->name) {
+        t->name = pa_xmalloc0(17);
+        pthread_getname_np(t->id, t->name, 16);
+    }
 #endif
 
     return t->name;

commit cda8ebf8f1d120ab946d20a689f29703aeee2110
Author: Daniel Mack <zonque at gmail.com>
Date:   Fri Apr 22 04:08:45 2011 +0200

    module-coreaudio-device: Dispatch sink/source state messages from main loop
    
    This fixes a long standing race condition when tearing down streams on
    Mac OS X.

diff --git a/src/modules/macosx/module-coreaudio-device.c b/src/modules/macosx/module-coreaudio-device.c
index cc4600b..0fad9c5 100644
--- a/src/modules/macosx/module-coreaudio-device.c
+++ b/src/modules/macosx/module-coreaudio-device.c
@@ -297,26 +297,6 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse
             *((pa_usec_t *) data) = get_latency_us(PA_OBJECT(o));
             return 0;
         }
-
-        case PA_SINK_MESSAGE_SET_STATE:
-            switch ((pa_sink_state_t) PA_PTR_TO_UINT(data)) {
-                case PA_SINK_SUSPENDED:
-                case PA_SINK_IDLE:
-                    sink->active = FALSE;
-                    break;
-
-                case PA_SINK_RUNNING:
-                    sink->active = TRUE;
-                    break;
-
-                case PA_SINK_UNLINKED:
-                case PA_SINK_INIT:
-                case PA_SINK_INVALID_STATE:
-                    ;
-            }
-
-            ca_device_check_device_state(sink->userdata);
-            break;
     }
 
     return pa_sink_process_msg(o, code, data, offset, chunk);
@@ -355,29 +335,34 @@ static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t off
             *((pa_usec_t *) data) = get_latency_us(PA_OBJECT(o));
             return 0;
         }
+    }
 
-        case PA_SOURCE_MESSAGE_SET_STATE:
-            switch ((pa_source_state_t) PA_PTR_TO_UINT(data)) {
-                case PA_SOURCE_SUSPENDED:
-                case PA_SOURCE_IDLE:
-                    source->active = FALSE;
-                    break;
-
-                case PA_SOURCE_RUNNING:
-                    source->active = TRUE;
-                    break;
-
-                case PA_SOURCE_UNLINKED:
-                case PA_SOURCE_INIT:
-                case PA_SOURCE_INVALID_STATE:
-                    ;
-            }
+    return pa_source_process_msg(o, code, data, offset, chunk);;
+}
 
-            ca_device_check_device_state(source->userdata);
+static int ca_sink_set_state(pa_sink *s, pa_sink_state_t state)
+{
+    coreaudio_sink *sink = s->userdata;
+
+    switch (state) {
+        case PA_SINK_SUSPENDED:
+        case PA_SINK_IDLE:
+            sink->active = FALSE;
+            break;
+
+        case PA_SINK_RUNNING:
+            sink->active = TRUE;
             break;
+
+        case PA_SINK_UNLINKED:
+        case PA_SINK_INIT:
+        case PA_SINK_INVALID_STATE:
+            ;
     }
 
-    return pa_source_process_msg(o, code, data, offset, chunk);
+    ca_device_check_device_state(sink->userdata);
+
+    return 0;
 }
 
 static int ca_device_create_sink(pa_module *m, AudioBuffer *buf, int channel_idx) {
@@ -461,6 +446,7 @@ static int ca_device_create_sink(pa_module *m, AudioBuffer *buf, int channel_idx
 
     sink->parent.process_msg = sink_process_msg;
     sink->userdata = ca_sink;
+    sink->set_state = ca_sink_set_state;
 
     pa_sink_set_asyncmsgq(sink, u->thread_mq.inq);
     pa_sink_set_rtpoll(sink, u->rtpoll);
@@ -473,6 +459,31 @@ static int ca_device_create_sink(pa_module *m, AudioBuffer *buf, int channel_idx
     return 0;
 }
 
+static int ca_source_set_state(pa_source *s, pa_source_state_t state)
+{
+    coreaudio_source *source = s->userdata;
+
+    switch (state) {
+        case PA_SOURCE_SUSPENDED:
+        case PA_SOURCE_IDLE:
+            source->active = FALSE;
+            break;
+
+        case PA_SOURCE_RUNNING:
+            source->active = TRUE;
+            break;
+
+        case PA_SOURCE_UNLINKED:
+        case PA_SOURCE_INIT:
+        case PA_SOURCE_INVALID_STATE:
+            ;
+    }
+
+    ca_device_check_device_state(source->userdata);
+
+    return 0;
+}
+
 static int ca_device_create_source(pa_module *m, AudioBuffer *buf, int channel_idx) {
     OSStatus err;
     UInt32 size;
@@ -554,6 +565,7 @@ static int ca_device_create_source(pa_module *m, AudioBuffer *buf, int channel_i
 
     source->parent.process_msg = source_process_msg;
     source->userdata = ca_source;
+    source->set_state = ca_source_set_state;
 
     pa_source_set_asyncmsgq(source, u->thread_mq.inq);
     pa_source_set_rtpoll(source, u->rtpoll);

commit e6003e8fc54ce21fe6648ffdf8c7e6bdda9e3f12
Author: Daniel Mack <zonque at gmail.com>
Date:   Fri Apr 22 04:10:46 2011 +0200

    module-coreaudio-device: Set the thread name to device name
    
    This makes gdb's "info threads" better understandable

diff --git a/src/modules/macosx/module-coreaudio-device.c b/src/modules/macosx/module-coreaudio-device.c
index 0fad9c5..7a1f177 100644
--- a/src/modules/macosx/module-coreaudio-device.c
+++ b/src/modules/macosx/module-coreaudio-device.c
@@ -759,7 +759,7 @@ int pa__init(pa_module *m) {
     ca_device_create_streams(m, TRUE);
 
     /* create the message thread */
-    if (!(u->thread = pa_thread_new("coreaudio", thread_func, u))) {
+    if (!(u->thread = pa_thread_new(u->device_name, thread_func, u))) {
         pa_log("Failed to create thread.");
         goto fail;
     }

commit ff273b49d7200c4ed20a533816fe534430bfa0a4
Author: Daniel Mack <zonque at gmail.com>
Date:   Fri Apr 22 04:12:36 2011 +0200

    module-coreaudio-device: Fix two build warnings

diff --git a/src/modules/macosx/module-coreaudio-device.c b/src/modules/macosx/module-coreaudio-device.c
index 7a1f177..393ce7f 100644
--- a/src/modules/macosx/module-coreaudio-device.c
+++ b/src/modules/macosx/module-coreaudio-device.c
@@ -393,7 +393,7 @@ static int ca_device_create_sink(pa_module *m, AudioBuffer *buf, int channel_idx
         size = sizeof(tmp);
         err = AudioObjectGetPropertyData(u->object_id, &property_address, 0, NULL, &size, tmp);
         if (err || !strlen(tmp))
-            snprintf(tmp, sizeof(tmp), "Channel %d", property_address.mElement);
+            snprintf(tmp, sizeof(tmp), "Channel %d", (int) property_address.mElement);
 
         if (i > 0)
             pa_strbuf_puts(strbuf, ", ");
@@ -512,7 +512,7 @@ static int ca_device_create_source(pa_module *m, AudioBuffer *buf, int channel_i
         size = sizeof(tmp);
         err = AudioObjectGetPropertyData(u->object_id, &property_address, 0, NULL, &size, tmp);
         if (err || !strlen(tmp))
-            snprintf(tmp, sizeof(tmp), "Channel %d", property_address.mElement);
+            snprintf(tmp, sizeof(tmp), "Channel %d", (int) property_address.mElement);
 
         if (i > 0)
             pa_strbuf_puts(strbuf, ", ");

commit 9b58b7d6c200a58299f400f67f48f20098b392bb
Author: Marc-André Lureau <marcandre.lureau at gmail.com>
Date:   Wed Apr 20 14:49:18 2011 +0300

    match: Don't double free in case of missing table file

diff --git a/src/modules/module-match.c b/src/modules/module-match.c
index c8f2706..3eba149 100644
--- a/src/modules/module-match.c
+++ b/src/modules/module-match.c
@@ -95,7 +95,6 @@ static int load_rules(struct userdata *u, const char *filename) {
         f = pa_open_config_file(DEFAULT_MATCH_TABLE_FILE, DEFAULT_MATCH_TABLE_FILE_USER, NULL, &fn);
 
     if (!f) {
-        pa_xfree(fn);
         pa_log("Failed to open file config file: %s", pa_cstrerror(errno));
         goto finish;
     }

commit 15257b0ec34b1b20fc531060dc9b953a93514f4f
Author: Marc-André Lureau <marcandre.lureau at gmail.com>
Date:   Wed Apr 20 15:22:46 2011 +0300

    match: Match rule earlier, in SINK_INPUT_NEW

diff --git a/src/modules/module-match.c b/src/modules/module-match.c
index 3eba149..b3316d4 100644
--- a/src/modules/module-match.c
+++ b/src/modules/module-match.c
@@ -43,7 +43,6 @@
 #include <pulsecore/core-util.h>
 #include <pulsecore/modargs.h>
 #include <pulsecore/log.h>
-#include <pulsecore/core-subscribe.h>
 #include <pulsecore/sink-input.h>
 #include <pulsecore/core-util.h>
 
@@ -77,7 +76,7 @@ struct rule {
 struct userdata {
     struct rule *rules;
     char *property_key;
-    pa_subscription *subscription;
+    pa_hook_slot *sink_input_new_hook_slot;
 };
 
 static int load_rules(struct userdata *u, const char *filename) {
@@ -191,23 +190,15 @@ finish:
     return ret;
 }
 
-static void callback(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) {
-    struct userdata *u =  userdata;
-    pa_sink_input *si;
+static pa_hook_result_t sink_input_new_hook_callback(pa_core *c, pa_sink_input_new_data *si, struct userdata *u) {
     struct rule *r;
     const char *n;
 
     pa_assert(c);
     pa_assert(u);
 
-    if (t != (PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_NEW))
-        return;
-
-    if (!(si = pa_idxset_get_by_index(c->sink_inputs, idx)))
-        return;
-
     if (!(n = pa_proplist_gets(si->proplist, u->property_key)))
-        return;
+        return PA_HOOK_OK;
 
     pa_log_debug("Matching with %s", n);
 
@@ -216,14 +207,17 @@ static void callback(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, v
             if (r->proplist) {
                 pa_log_debug("updating proplist of sink input '%s'", n);
                 pa_proplist_update(si->proplist, PA_UPDATE_MERGE, r->proplist);
-            } else {
+            } else if (si->volume_writable) {
                 pa_cvolume cv;
                 pa_log_debug("changing volume of sink input '%s' to 0x%03x", n, r->volume);
                 pa_cvolume_set(&cv, si->sample_spec.channels, r->volume);
-                pa_sink_input_set_volume(si, &cv, TRUE, FALSE);
-            }
+                pa_sink_input_new_data_set_volume(si, &cv);
+            } else
+                pa_log_debug("the volume of sink input '%s' is not writable, can't change it", n);
         }
     }
+
+    return PA_HOOK_OK;
 }
 
 int pa__init(pa_module*m) {
@@ -239,7 +233,6 @@ int pa__init(pa_module*m) {
 
     u = pa_xnew(struct userdata, 1);
     u->rules = NULL;
-    u->subscription = NULL;
     m->userdata = u;
 
     u->property_key = pa_xstrdup(pa_modargs_get_value(ma, "key", PA_PROP_MEDIA_NAME));
@@ -247,10 +240,8 @@ int pa__init(pa_module*m) {
     if (load_rules(u, pa_modargs_get_value(ma, "table", NULL)) < 0)
         goto fail;
 
-    /* FIXME: Doing this asynchronously is just broken. This needs to
-     * use a hook! */
-
-    u->subscription = pa_subscription_new(m->core, PA_SUBSCRIPTION_MASK_SINK_INPUT, callback, u);
+    /* hook EARLY - 1, to match before stream-restore */
+    u->sink_input_new_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_NEW], PA_HOOK_EARLY - 1, (pa_hook_cb_t) sink_input_new_hook_callback, u);
 
     pa_modargs_free(ma);
     return 0;
@@ -272,8 +263,8 @@ void pa__done(pa_module*m) {
     if (!(u = m->userdata))
         return;
 
-    if (u->subscription)
-        pa_subscription_free(u->subscription);
+    if (u->sink_input_new_hook_slot)
+        pa_hook_slot_free(u->sink_input_new_hook_slot);
 
     if (u->property_key)
         pa_xfree(u->property_key);

commit dcab6e15618ff44d814d29aaf92ade6613c70657
Author: Tanu Kaskinen <tanuk at iki.fi>
Date:   Thu Apr 21 08:06:53 2011 +0300

    protocol-dbus: Fix some memory management bugs.
    
    There were several memory leaks. In addition to those,
    pa_dbus_protocol_add_interface() used a string from the
    caller as a key to a hashmap, instead of a copy of the
    string. This caused trouble when the caller freed the
    string while the key was still in use in the hashmap.

diff --git a/src/pulsecore/protocol-dbus.c b/src/pulsecore/protocol-dbus.c
index c329915..8784c34 100644
--- a/src/pulsecore/protocol-dbus.c
+++ b/src/pulsecore/protocol-dbus.c
@@ -742,7 +742,7 @@ int pa_dbus_protocol_add_interface(pa_dbus_protocol *p,
         obj_entry->interfaces = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
         obj_entry->introspection = NULL;
 
-        pa_hashmap_put(p->objects, path, obj_entry);
+        pa_hashmap_put(p->objects, obj_entry->path, obj_entry);
         obj_entry_created = TRUE;
     }
 
@@ -770,11 +770,6 @@ int pa_dbus_protocol_add_interface(pa_dbus_protocol *p,
     return 0;
 
 fail:
-    if (obj_entry_created) {
-        pa_hashmap_remove(p->objects, path);
-        pa_xfree(obj_entry);
-    }
-
     return -1;
 }
 
@@ -804,6 +799,7 @@ static void method_handler_free_cb(void *p, void *userdata) {
     }
 
     pa_xfree((pa_dbus_arg_info *) h->arguments);
+    pa_xfree(h);
 }
 
 static void method_signature_free_cb(void *p, void *userdata) {
@@ -945,6 +941,7 @@ static void signal_paths_entry_free(struct signal_paths_entry *e) {
     while ((path = pa_idxset_steal_first(e->paths, NULL)))
         pa_xfree(path);
 
+    pa_idxset_free(e->paths, NULL, NULL);
     pa_xfree(e);
 }
 
@@ -966,9 +963,12 @@ int pa_dbus_protocol_unregister_connection(pa_dbus_protocol *p, DBusConnection *
     while ((object_path = pa_idxset_steal_first(conn_entry->all_signals_objects, NULL)))
         pa_xfree(object_path);
 
+    pa_idxset_free(conn_entry->all_signals_objects, NULL, NULL);
+
     while ((signal_paths_entry = pa_hashmap_steal_first(conn_entry->listening_signals)))
         signal_paths_entry_free(signal_paths_entry);
 
+    pa_hashmap_free(conn_entry->listening_signals, NULL, NULL);
     pa_xfree(conn_entry);
 
     return 0;

commit 9ade504d4582b24e266a8f5585e3b1951d8e12b2
Author: Daniel Mack <zonque at gmail.com>
Date:   Fri Apr 22 02:41:24 2011 +0200

    build-sys: Make -isysroot and -mmacosx-version-min configurable

diff --git a/configure.ac b/configure.ac
index 6f262fb..980d148 100644
--- a/configure.ac
+++ b/configure.ac
@@ -172,6 +172,13 @@ AC_ARG_ENABLE(mac-universal,
     AS_HELP_STRING([--enable-mac-universal], [Build Mac universal binaries]),
 		enable_mac_universal=$enableval, enable_mac_universal="no")
 
+AC_ARG_WITH(mac-version-min,
+    AS_HELP_STRING([--with-mac-version-min=<version>], [Defines the earliest version of MacOS X that the executables will run on.]),
+		mac_version_min=$withval, mac_version_min="10.5")
+
+AC_ARG_WITH(mac-sysroot,
+    AS_HELP_STRING([--with-mac-sysroot=<path>], [SDK basedir to use as the logical root directory for headers and libraries.]),
+		mac_sysroot=$withval, mac_sysroot="/Developer/SDKs/MacOSX10.5.sdk")
 
 AC_MSG_CHECKING([target operating system])
 case $host in
@@ -183,12 +190,13 @@ case $host in
             AC_MSG_RESULT([darwin])
             pulse_target_os=darwin
 
+            LDFLAGS="$LDFLAGS -isysroot $mac_sysroot -mmacosx-version-min=$mac_version_min"
+            CFLAGS="$CFLAGS -isysroot $mac_sysroot -mmacosx-version-min=$mac_version_min"
+
             if test "x$enable_mac_universal" = "xyes" ; then
-	        mac_version_min="-mmacosx-version-min=10.5"
                 mac_arches="-arch i386 -arch x86_64"
-                mac_sysroot="-isysroot /Developer/SDKs/MacOSX10.5.sdk"
-                LDFLAGS="$LDFLAGS $mac_arches $mac_sysroot $mac_version_min"
-                CFLAGS="$CFLAGS $CFLAGS $mac_arches $mac_sysroot $mac_version_min"
+                LDFLAGS="$LDFLAGS $mac_arches"
+                CFLAGS="$CFLAGS $mac_arches"
             fi
         ;;
         *)

commit b620e322894a3dbf5c59ffb55df4e221d169c510
Author: Colin Guthrie <colin at mageia.org>
Date:   Fri Apr 22 10:25:42 2011 +0100

    test: Make the connect-stress less likely to bail out due to >32 streams.
    
    When running two connect-stress tests at the same time the liklihood of >32 streams
    per sink increases. All it takes is for an event sound to fire to trigger an abort of
    the test.
    
    This leaves just a little bit of wriggle room for a couple external streams.
    Of course the overall problem is still there but this just makes it
    slightly less likely without really affecting the test itself.

diff --git a/src/tests/connect-stress.c b/src/tests/connect-stress.c
index f427195..5476675 100644
--- a/src/tests/connect-stress.c
+++ b/src/tests/connect-stress.c
@@ -32,7 +32,15 @@
 #include <pulse/pulseaudio.h>
 #include <pulse/mainloop.h>
 
-#define NSTREAMS 16
+#include <pulsecore/sink.h>
+
+/* Set the number of streams such that it allows two simultaneous instances of
+ * connect-stress to be run and not go above the max limit for streams-per-sink.
+ * This leaves enough room for a couple other streams from regular system usage,
+ * which makes a non-error abort less likely (although still easily possible of
+ * playing >=3 streams outside of the test - including internal loopback, rtp,
+ * combine, remap streams etc.) */
+#define NSTREAMS ((PA_MAX_INPUTS_PER_SINK/2) - 1)
 #define NTESTS 1000
 #define SAMPLE_HZ 44100
 

commit 42b378b13028751cbdd607cb6b7c840b9fb28876
Author: Arun Raghavan <arun.raghavan at collabora.co.uk>
Date:   Wed Apr 20 17:45:26 2011 +0530

    filter-heuristics: Only apply AEC if we're not already on a phone sink
    
    This makes sure that we don't apply AEC on sinks that are already
    connected to a "phone" device, the assumptiong being that anything
    marked as such either doesn't have need it, or handles it itself.

diff --git a/src/modules/module-filter-heuristics.c b/src/modules/module-filter-heuristics.c
index bd8a600..a385ff2 100644
--- a/src/modules/module-filter-heuristics.c
+++ b/src/modules/module-filter-heuristics.c
@@ -50,7 +50,7 @@ struct userdata {
 };
 
 static pa_hook_result_t sink_input_put_cb(pa_core *core, pa_sink_input *i, struct userdata *u) {
-    const char *role;
+    const char *sink_role, *si_role;
 
     pa_core_assert_ref(core);
     pa_sink_input_assert_ref(i);
@@ -60,7 +60,10 @@ static pa_hook_result_t sink_input_put_cb(pa_core *core, pa_sink_input *i, struc
     if (pa_proplist_gets(i->proplist, PA_PROP_FILTER_WANT))
         return PA_HOOK_OK;
 
-    if ((role = pa_proplist_gets(i->proplist, PA_PROP_MEDIA_ROLE)) && pa_streq(role, "phone"))
+    if ((sink_role = pa_proplist_gets(i->sink->proplist, PA_PROP_DEVICE_INTENDED_ROLES)) && strstr(sink_role, "phone"))
+        return PA_HOOK_OK;
+
+    if ((si_role = pa_proplist_gets(i->proplist, PA_PROP_MEDIA_ROLE)) && pa_streq(si_role, "phone"))
         pa_proplist_sets(i->proplist, PA_PROP_FILTER_WANT, "echo-cancel");
 
     return PA_HOOK_OK;

commit ceca42f706c7d193cbda4e201d84f62790f0a465
Author: Arun Raghavan <arun.raghavan at collabora.co.uk>
Date:   Thu Apr 21 12:52:27 2011 +0530

    filters: Handle stream moves properly
    
    This makes sure that we handle streams moving between sinks properly. To
    do this, we change the way the filter.* properties are handled a little
    bit.
    
    Firstly, this splits up the "filter.apply" property into two properties
    - "filter.want" and "filter.apply". "filter.apply" acts as before - it
    bypasses module-filter-heuristics and directly tells module-filter-apply
    what filters are to be applied.
    
    "filter.want" is used to tell module-filter-heuristics what filters the
    client wants. The module then decides whether to actually apply the
    filter or not (for now, this makes sure we don't apply echo-cancellation
    even if requested on phone sinks (where it is assumed AEC is taken care
    of or is not required).
    
    Next, we also make sure that we track whether the client set
    "filter.apply" or module-filter-heuristics did - and in the latter case,
    we recalculate "filter.apply" and then have module-filter-apply apply
    the filter if required. This introduces some evil in the form of causing
    the move_finish callback to possibly trigger another move, but we
    protect for this case (with a property) to be doubly sure of not causing
    an infinite loop.

diff --git a/src/modules/module-filter-apply.c b/src/modules/module-filter-apply.c
index c898971..faa918b 100644
--- a/src/modules/module-filter-apply.c
+++ b/src/modules/module-filter-apply.c
@@ -37,6 +37,7 @@
 
 #include "module-filter-apply-symdef.h"
 
+#define PA_PROP_FILTER_APPLY_MOVING "filter.apply.moving"
 
 PA_MODULE_AUTHOR("Colin Guthrie");
 PA_MODULE_DESCRIPTION("Load filter sinks automatically when needed");
@@ -64,6 +65,7 @@ struct userdata {
     pa_hashmap *filters;
     pa_hook_slot
         *sink_input_put_slot,
+        *sink_input_move_finish_slot,
         *sink_input_proplist_slot,
         *sink_input_unlink_slot,
         *sink_unlink_slot;
@@ -110,14 +112,14 @@ static void filter_free(struct filter *f) {
 }
 
 static const char* should_filter(pa_sink_input *i) {
-    const char *want;
+    const char *apply;
 
     /* If the stream doesn't what any filter, then let it be. */
-    if ((want = pa_proplist_gets(i->proplist, PA_PROP_FILTER_WANT)) && !pa_streq(want, "")) {
+    if ((apply = pa_proplist_gets(i->proplist, PA_PROP_FILTER_APPLY)) && !pa_streq(apply, "")) {
         const char* suppress = pa_proplist_gets(i->proplist, PA_PROP_FILTER_SUPPRESS);
 
-        if (!suppress || !pa_streq(suppress, want))
-            return want;
+        if (!suppress || !pa_streq(suppress, apply))
+            return apply;
     }
 
     return NULL;
@@ -171,12 +173,16 @@ static void move_input_for_filter(pa_sink_input *i, struct filter* filter, pa_bo
 
     pa_assert_se(sink = (restore ? filter->parent_sink : filter->sink));
 
+    pa_proplist_sets(i->proplist, PA_PROP_FILTER_APPLY_MOVING, "1");
+
     if (pa_sink_input_move_to(i, sink, FALSE) < 0)
         pa_log_info("Failed to move sink input %u \"%s\" to <%s>.", i->index,
                     pa_strnull(pa_proplist_gets(i->proplist, PA_PROP_APPLICATION_NAME)), sink->name);
     else
         pa_log_info("Sucessfully moved sink input %u \"%s\" to <%s>.", i->index,
                     pa_strnull(pa_proplist_gets(i->proplist, PA_PROP_APPLICATION_NAME)), sink->name);
+
+    pa_proplist_unset(i->proplist, PA_PROP_FILTER_APPLY_MOVING);
 }
 
 static pa_hook_result_t process(struct userdata *u, pa_sink_input *i) {
@@ -281,6 +287,16 @@ static pa_hook_result_t sink_input_put_cb(pa_core *core, pa_sink_input *i, struc
     return process(u, i);
 }
 
+static pa_hook_result_t sink_input_move_finish_cb(pa_core *core, pa_sink_input *i, struct userdata *u) {
+    pa_core_assert_ref(core);
+    pa_sink_input_assert_ref(i);
+
+    if (pa_proplist_gets(i->proplist, PA_PROP_FILTER_APPLY_MOVING))
+        return PA_HOOK_OK;
+
+    return process(u, i);
+}
+
 static pa_hook_result_t sink_input_proplist_cb(pa_core *core, pa_sink_input *i, struct userdata *u) {
     pa_core_assert_ref(core);
     pa_sink_input_assert_ref(i);
@@ -359,6 +375,7 @@ int pa__init(pa_module *m) {
     u->filters = pa_hashmap_new(filter_hash, filter_compare);
 
     u->sink_input_put_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_PUT], PA_HOOK_LATE, (pa_hook_cb_t) sink_input_put_cb, u);
+    u->sink_input_move_finish_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_FINISH], PA_HOOK_LATE, (pa_hook_cb_t) sink_input_move_finish_cb, u);
     u->sink_input_proplist_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_PROPLIST_CHANGED], PA_HOOK_LATE, (pa_hook_cb_t) sink_input_proplist_cb, u);
     u->sink_input_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_UNLINK], PA_HOOK_LATE, (pa_hook_cb_t) sink_input_unlink_cb, u);
     u->sink_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK], PA_HOOK_LATE, (pa_hook_cb_t) sink_unlink_cb, u);
@@ -386,6 +403,8 @@ void pa__done(pa_module *m) {
 
     if (u->sink_input_put_slot)
         pa_hook_slot_free(u->sink_input_put_slot);
+    if (u->sink_input_move_finish_slot)
+        pa_hook_slot_free(u->sink_input_move_finish_slot);
     if (u->sink_input_proplist_slot)
         pa_hook_slot_free(u->sink_input_proplist_slot);
     if (u->sink_input_unlink_slot)
diff --git a/src/modules/module-filter-heuristics.c b/src/modules/module-filter-heuristics.c
index a385ff2..20a4835 100644
--- a/src/modules/module-filter-heuristics.c
+++ b/src/modules/module-filter-heuristics.c
@@ -33,6 +33,8 @@
 
 #include "module-filter-heuristics-symdef.h"
 
+#define PA_PROP_FILTER_APPLY_MOVING "filter.apply.moving"
+#define PA_PROP_FILTER_HEURISTICS "filter.heuristics"
 
 PA_MODULE_AUTHOR("Colin Guthrie");
 PA_MODULE_DESCRIPTION("Detect when various filters are desirable");
@@ -46,27 +48,63 @@ static const char* const valid_modargs[] = {
 struct userdata {
     pa_core *core;
     pa_hook_slot
-        *sink_input_put_slot;
+        *sink_input_put_slot,
+        *sink_input_move_finish_slot;
 };
 
-static pa_hook_result_t sink_input_put_cb(pa_core *core, pa_sink_input *i, struct userdata *u) {
-    const char *sink_role, *si_role;
+static pa_hook_result_t process(struct userdata *u, pa_sink_input *i) {
+    const char *want, *sink_role, *si_role;
+
+    /* If the stream already specifies what it must have, then let it be. */
+    if (!pa_proplist_gets(i->proplist, PA_PROP_FILTER_HEURISTICS) && pa_proplist_gets(i->proplist, PA_PROP_FILTER_APPLY))
+        return PA_HOOK_OK;
+
+    want = pa_proplist_gets(i->proplist, PA_PROP_FILTER_WANT);
+    if (!want) {
+        /* This is a phone stream, maybe we want echo cancellation */
+        if ((si_role = pa_proplist_gets(i->proplist, PA_PROP_MEDIA_ROLE)) && pa_streq(si_role, "phone"))
+            want = "echo-cancel";
+    }
+
+    /* On phone sinks, make sure we're not applying echo cancellation */
+    if ((sink_role = pa_proplist_gets(i->sink->proplist, PA_PROP_DEVICE_INTENDED_ROLES)) && strstr(sink_role, "phone")) {
+        const char *apply = pa_proplist_gets(i->proplist, PA_PROP_FILTER_APPLY);
+
+        if (apply && pa_streq(apply, "echo-cancel")) {
+            pa_proplist_unset(i->proplist, PA_PROP_FILTER_APPLY);
+            pa_proplist_unset(i->proplist, PA_PROP_FILTER_HEURISTICS);
+        }
+
+        return PA_HOOK_OK;
+    }
+
+    if (want) {
+        /* There's a filter that we want, ask module-filter-apply to apply it, and remember that we're managing filter.apply */
+        pa_proplist_sets(i->proplist, PA_PROP_FILTER_APPLY, want);
+        pa_proplist_sets(i->proplist, PA_PROP_FILTER_HEURISTICS, "1");
+    }
 
+    return PA_HOOK_OK;
+}
+
+static pa_hook_result_t sink_input_put_cb(pa_core *core, pa_sink_input *i, struct userdata *u) {
     pa_core_assert_ref(core);
     pa_sink_input_assert_ref(i);
     pa_assert(u);
 
-    /* If the stream already specifies what it wants, then let it be. */
-    if (pa_proplist_gets(i->proplist, PA_PROP_FILTER_WANT))
-        return PA_HOOK_OK;
+    return process(u, i);
+}
 
-    if ((sink_role = pa_proplist_gets(i->sink->proplist, PA_PROP_DEVICE_INTENDED_ROLES)) && strstr(sink_role, "phone"))
-        return PA_HOOK_OK;
+static pa_hook_result_t sink_input_move_finish_cb(pa_core *core, pa_sink_input *i, struct userdata *u) {
+    pa_core_assert_ref(core);
+    pa_sink_input_assert_ref(i);
+    pa_assert(u);
 
-    if ((si_role = pa_proplist_gets(i->proplist, PA_PROP_MEDIA_ROLE)) && pa_streq(si_role, "phone"))
-        pa_proplist_sets(i->proplist, PA_PROP_FILTER_WANT, "echo-cancel");
+    /* module-filter-apply triggered this move, ignore */
+    if (pa_proplist_gets(i->proplist, PA_PROP_FILTER_APPLY_MOVING))
+        return PA_HOOK_OK;
 
-    return PA_HOOK_OK;
+    return process(u, i);
 }
 
 int pa__init(pa_module *m) {
@@ -85,6 +123,7 @@ int pa__init(pa_module *m) {
     u->core = m->core;
 
     u->sink_input_put_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_PUT], PA_HOOK_LATE-1, (pa_hook_cb_t) sink_input_put_cb, u);
+    u->sink_input_move_finish_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_FINISH], PA_HOOK_LATE-1, (pa_hook_cb_t) sink_input_move_finish_cb, u);
 
     pa_modargs_free(ma);
 
@@ -111,6 +150,8 @@ void pa__done(pa_module *m) {
 
     if (u->sink_input_put_slot)
         pa_hook_slot_free(u->sink_input_put_slot);
+    if (u->sink_input_move_finish_slot)
+        pa_hook_slot_free(u->sink_input_move_finish_slot);
 
     pa_xfree(u);
 
diff --git a/src/pulse/proplist.h b/src/pulse/proplist.h
index 4670d61..7d02699 100644
--- a/src/pulse/proplist.h
+++ b/src/pulse/proplist.h
@@ -62,9 +62,12 @@ PA_C_DECL_BEGIN
 /** For streams: logic role of this media. One of the strings "video", "music", "game", "event", "phone", "animation", "production", "a11y", "test" */
 #define PA_PROP_MEDIA_ROLE                     "media.role"
 
-/** For streams: the name of a filter that is desired, e.g. "echo-cancel" or "equalizer-sink" \since 1.0 */
+/** For streams: the name of a filter that is desired, e.g. "echo-cancel" or "equalizer-sink". PulseAudio may choose to not apply the filter if it does not make sense (for example, applying echo-cancellation on a Bluetooth headset probably does not make sense. \since 1.0 */
 #define PA_PROP_FILTER_WANT "filter.want"
 
+/** For streams: the name of a filter that is desired, e.g. "echo-cancel" or "equalizer-sink". Differs from PA_PROP_FILTER_WANT in that it forces PulseAudio to apply the filter, regardless of whether PulseAudio thinks it makes sense to do so or not. If this is set, PA_PROP_FILTER_WANT is ignored. In other words, you almost certainly do not want to use this. \since 1.0 */
+#define PA_PROP_FILTER_APPLY "filter.apply"
+
 /** For streams: the name of a filter that should specifically suppressed (i.e. overrides PA_PROP_FILTER_WANT). Useful for the times that PA_PROP_FILTER_WANT is automatically added (e.g. echo-cancellation for phone streams when $VOIP_APP does it's own, internal AEC) \since 1.0 */
 #define PA_PROP_FILTER_SUPPRESS "filter.suppress"
 

commit 87570523f84817b19486ab9302314091424bbad8
Author: Arun Raghavan <arun.raghavan at collabora.co.uk>
Date:   Fri Apr 22 17:33:03 2011 +0530

    filters: Handle filters on sources as well
    
    This makes the core code in the filter-* modules generic enough to be
    used on sources or sinks. We need special handling for modules that
    introduce more than one sink (for now echo-cancel only).

diff --git a/src/modules/module-filter-apply.c b/src/modules/module-filter-apply.c
index faa918b..79558f2 100644
--- a/src/modules/module-filter-apply.c
+++ b/src/modules/module-filter-apply.c
@@ -55,9 +55,10 @@ static const char* const valid_modargs[] = {
 
 struct filter {
     char *name;
-    pa_sink* parent_sink;
     uint32_t module_index;
-    pa_sink* sink;
+    pa_bool_t is_sink;
+    pa_object *parent_obj;      /* source or sink that the filter is connected to */
+    pa_object *obj;             /* source or sink of the filter */
 };
 
 struct userdata {
@@ -68,7 +69,12 @@ struct userdata {
         *sink_input_move_finish_slot,
         *sink_input_proplist_slot,
         *sink_input_unlink_slot,
-        *sink_unlink_slot;
+        *sink_unlink_slot,
+        *source_output_put_slot,
+        *source_output_move_finish_slot,
+        *source_output_proplist_slot,
+        *source_output_unlink_slot,
+        *source_unlink_slot;
     pa_bool_t autoclean;
     pa_time_event *housekeeping_time_event;
 };
@@ -76,16 +82,17 @@ struct userdata {
 static unsigned filter_hash(const void *p) {
     const struct filter *f = p;
 
-    return
-        (unsigned) f->parent_sink->index +
-        pa_idxset_string_hash_func(f->name);
+    if (f->is_sink)
+        return (unsigned) (PA_SINK(f->parent_obj)->index + pa_idxset_string_hash_func(f->name));
+    else
+        return (unsigned) ((PA_SOURCE(f->parent_obj)->index << 16) + pa_idxset_string_hash_func(f->name));
 }
 
 static int filter_compare(const void *a, const void *b) {
     const struct filter *fa = a, *fb = b;
     int r;
 
-    if (fa->parent_sink != fb->parent_sink)
+    if (fa->parent_obj != fb->parent_obj)
         return 1;
     if ((r = strcmp(fa->name, fb->name)))
         return r;
@@ -93,14 +100,15 @@ static int filter_compare(const void *a, const void *b) {
     return 0;
 }
 
-static struct filter *filter_new(const char *name, pa_sink* parent_sink) {
+static struct filter *filter_new(const char *name, pa_object* parent_obj, pa_bool_t is_sink) {
     struct filter *f;
 
     f = pa_xnew(struct filter, 1);
     f->name = pa_xstrdup(name);
-    pa_assert_se(f->parent_sink = parent_sink);
+    pa_assert_se(f->parent_obj = parent_obj);
+    f->is_sink = is_sink;
     f->module_index = PA_INVALID_INDEX;
-    f->sink = NULL;
+    f->obj = NULL;
     return f;
 }
 
@@ -111,12 +119,18 @@ static void filter_free(struct filter *f) {
     pa_xfree(f);
 }
 
-static const char* should_filter(pa_sink_input *i) {
+static const char* should_filter(pa_object *o, pa_bool_t is_sink_input) {
     const char *apply;
+    pa_proplist *pl;
+
+    if (is_sink_input)
+        pl = PA_SINK_INPUT(o)->proplist;
+    else
+        pl = PA_SOURCE_OUTPUT(o)->proplist;
 
     /* If the stream doesn't what any filter, then let it be. */
-    if ((apply = pa_proplist_gets(i->proplist, PA_PROP_FILTER_APPLY)) && !pa_streq(apply, "")) {
-        const char* suppress = pa_proplist_gets(i->proplist, PA_PROP_FILTER_SUPPRESS);
+    if ((apply = pa_proplist_gets(pl, PA_PROP_FILTER_APPLY)) && !pa_streq(apply, "")) {
+        const char* suppress = pa_proplist_gets(pl, PA_PROP_FILTER_SUPPRESS);
 
         if (!suppress || !pa_streq(suppress, apply))
             return apply;
@@ -125,6 +139,14 @@ static const char* should_filter(pa_sink_input *i) {
     return NULL;
 }
 
+static pa_bool_t nothing_attached(pa_object *obj, pa_bool_t is_sink)
+{
+    if (is_sink)
+        return pa_idxset_isempty(PA_SINK(obj)->inputs);
+    else
+        return pa_idxset_isempty(PA_SOURCE(obj)->outputs);
+}
+
 static void housekeeping_time_callback(pa_mainloop_api*a, pa_time_event* e, const struct timeval *t, void *userdata) {
     struct userdata *u = userdata;
     struct filter *filter;
@@ -139,10 +161,10 @@ static void housekeeping_time_callback(pa_mainloop_api*a, pa_time_event* e, cons
     u->housekeeping_time_event = NULL;
 
     PA_HASHMAP_FOREACH(filter, u->filters, state) {
-        if (filter->sink && pa_idxset_size(filter->sink->inputs) == 0) {
+        if (filter->obj && nothing_attached(filter->obj, filter->is_sink)) {
             uint32_t idx;
 
-            pa_log_debug("Detected filter %s as no longer used on sink %s. Unloading.", filter->name, filter->sink->name);
+            pa_log_debug("Detected filter %s as no longer used. Unloading.", filter->name);
             idx = filter->module_index;
             pa_hashmap_remove(u->filters, filter);
             filter_free(filter);
@@ -165,98 +187,157 @@ static void trigger_housekeeping(struct userdata *u) {
     u->housekeeping_time_event = pa_core_rttime_new(u->core, pa_rtclock_now() + HOUSEKEEPING_INTERVAL, housekeeping_time_callback, u);
 }
 
-static void move_input_for_filter(pa_sink_input *i, struct filter* filter, pa_bool_t restore) {
-    pa_sink *sink;
+static int do_move(pa_object *obj, pa_object *parent, pa_bool_t restore, pa_bool_t is_input) {
+    if (is_input)
+        return pa_sink_input_move_to(PA_SINK_INPUT(obj), PA_SINK(parent), restore);
+    else
+        return pa_source_output_move_to(PA_SOURCE_OUTPUT(obj), PA_SOURCE(parent), restore);
+}
 
-    pa_assert(i);
+static void move_object_for_filter(pa_object *o, struct filter* filter, pa_bool_t restore, pa_bool_t is_sink_input) {
+    pa_object *parent;
+    pa_proplist *pl;
+    const char *name;
+
+    pa_assert(o);
     pa_assert(filter);
 
-    pa_assert_se(sink = (restore ? filter->parent_sink : filter->sink));
+    pa_assert_se(parent = (restore ? filter->parent_obj : filter->obj));
 
-    pa_proplist_sets(i->proplist, PA_PROP_FILTER_APPLY_MOVING, "1");
+    if (is_sink_input) {
+        pl = PA_SINK_INPUT(o)->proplist;
+        name = PA_SINK(parent)->name;
+    } else {
+        pl = PA_SOURCE_OUTPUT(o)->proplist;
+        name = PA_SOURCE(parent)->name;
+    }
 
-    if (pa_sink_input_move_to(i, sink, FALSE) < 0)
-        pa_log_info("Failed to move sink input %u \"%s\" to <%s>.", i->index,
-                    pa_strnull(pa_proplist_gets(i->proplist, PA_PROP_APPLICATION_NAME)), sink->name);
+    pa_proplist_sets(pl, PA_PROP_FILTER_APPLY_MOVING, "1");
+
+    if (do_move(o, parent, FALSE, is_sink_input) < 0)
+        pa_log_info("Failed to move %s for \"%s\" to <%s>.", is_sink_input ? "sink-input" : "source-output",
+                    pa_strnull(pa_proplist_gets(pl, PA_PROP_APPLICATION_NAME)), name);
     else
-        pa_log_info("Sucessfully moved sink input %u \"%s\" to <%s>.", i->index,
-                    pa_strnull(pa_proplist_gets(i->proplist, PA_PROP_APPLICATION_NAME)), sink->name);
+        pa_log_info("Sucessfully moved %s for \"%s\" to <%s>.", is_sink_input ? "sink-input" : "source-output",
+                    pa_strnull(pa_proplist_gets(pl, PA_PROP_APPLICATION_NAME)), name);
 
-    pa_proplist_unset(i->proplist, PA_PROP_FILTER_APPLY_MOVING);
+    pa_proplist_unset(pl, PA_PROP_FILTER_APPLY_MOVING);
 }
 
-static pa_hook_result_t process(struct userdata *u, pa_sink_input *i) {
+static void find_filters_for_module(struct userdata *u, pa_module *m, const char *name) {
+    uint32_t idx;
+    pa_sink *sink;
+    pa_source *source;
+    struct filter *fltr;
+
+    PA_IDXSET_FOREACH(sink, u->core->sinks, idx) {
+        if (sink->module == m) {
+            pa_assert(sink->input_to_master != NULL);
+
+            fltr = filter_new(name, PA_OBJECT(sink->input_to_master->sink), TRUE);
+            fltr->module_index = m->index;
+            fltr->obj = PA_OBJECT(sink);
+
+            pa_hashmap_put(u->filters, fltr, fltr);
+        }
+    }
+
+    PA_IDXSET_FOREACH(source, u->core->sources, idx) {
+        if (source->module == m && !source->monitor_of) {
+            pa_assert(source->output_from_master != NULL);
+
+            fltr = filter_new(name, PA_OBJECT(source->output_from_master->source), FALSE);
+            fltr->module_index = m->index;
+            fltr->obj = PA_OBJECT(source);
+
+            pa_hashmap_put(u->filters, fltr, fltr);
+        }
+    }
+}
+
+static pa_bool_t can_unload_module(struct userdata *u, uint32_t idx) {
+    void *state;
+    struct filter *filter;
+
+    /* Check if any other struct filters point to the same module */
+    PA_HASHMAP_FOREACH(filter, u->filters, state) {
+        if (filter->module_index == idx && !nothing_attached(filter->obj, pa_sink_isinstance(filter->obj)))
+            return FALSE;
+    }
+
+    return TRUE;
+}
+
+static pa_hook_result_t process(struct userdata *u, pa_object *o, pa_bool_t is_sink_input) {
     const char *want;
     pa_bool_t done_something = FALSE;
 
-    pa_assert(u);
-    pa_sink_input_assert_ref(i);
+    pa_object *parent; /* source/sink of the given source-output/sink-input */
+    const char *parent_name;
+    pa_module *module;
+
+    if (is_sink_input) {
+        parent = PA_OBJECT(PA_SINK_INPUT(o)->sink);
+        parent_name = PA_SINK_INPUT(o)->sink->name;
+        module = PA_SINK_INPUT(o)->sink->module;
+    } else {
+        parent = PA_OBJECT(PA_SOURCE_OUTPUT(o)->source);
+        parent_name = PA_SOURCE_OUTPUT(o)->source->name;
+        module = PA_SOURCE_OUTPUT(o)->source->module;
+    }
 
     /* If there is no sink yet, we can't do much */
-    if (!i->sink)
+    if (!parent)
         return PA_HOOK_OK;
 
     /* If the stream doesn't what any filter, then let it be. */
-    if ((want = should_filter(i))) {
+    if ((want = should_filter(o, is_sink_input))) {
         char *module_name;
         struct filter *fltr, *filter;
 
         /* We need to ensure the SI is playing on a sink of this type
          * attached to the sink it's "officially" playing on */
 
-        if (!i->sink->module)
+        if (!module)
             return PA_HOOK_OK;
 
         module_name = pa_sprintf_malloc("module-%s", want);
-        if (pa_streq(i->sink->module->name, module_name)) {
+        if (pa_streq(module->name, module_name)) {
             pa_log_debug("Stream appears to be playing on an appropriate sink already. Ignoring.");
             pa_xfree(module_name);
             return PA_HOOK_OK;
         }
 
-        fltr = filter_new(want, i->sink);
+        fltr = filter_new(want, parent, is_sink_input);
 
         if (!(filter = pa_hashmap_get(u->filters, fltr))) {
             char *args;
             pa_module *m;
 
-            args = pa_sprintf_malloc("sink_master=%s", i->sink->name);
+            args = pa_sprintf_malloc("%s_master=%s", is_sink_input ? "sink" : "source", parent_name);
             pa_log_debug("Loading %s with arguments '%s'", module_name, args);
 
             if ((m = pa_module_load(u->core, module_name, args))) {
-                uint32_t idx;
-                pa_sink *sink;
-
-                fltr->module_index = m->index;
-                /* We cannot use the SINK_PUT hook here to detect our sink as it'll
-                 * be called during the module load so we wont yet have put the filter
-                 * in our hashmap to compare... so we have to search for it */
-                PA_IDXSET_FOREACH(sink, u->core->sinks, idx) {
-                    if (sink->module == m) {
-                        fltr->sink = sink;
-                        break;
-                    }
-                }
-                pa_hashmap_put(u->filters, fltr, fltr);
-                filter = fltr;
-                fltr = NULL;
+                find_filters_for_module(u, m, want);
+                filter = pa_hashmap_get(u->filters, fltr);
                 done_something = TRUE;
             }
             pa_xfree(args);
         }
+
         pa_xfree(fltr);
 
         if (!filter) {
-            pa_log("Unable to load %s for sink <%s>", module_name, i->sink->name);
+            pa_log("Unable to load %s for <%s>", module_name, parent_name);
             pa_xfree(module_name);
             return PA_HOOK_OK;
         }
         pa_xfree(module_name);
 
-        if (filter->sink) {
+        if (filter->obj) {
             /* We can move the sink_input now as the know the destination.
              * If this isn't true, we will do it later when the sink appears. */
-            move_input_for_filter(i, filter, FALSE);
+            move_object_for_filter(o, filter, FALSE, is_sink_input);
             done_something = TRUE;
         }
     } else {
@@ -266,8 +347,8 @@ static pa_hook_result_t process(struct userdata *u, pa_sink_input *i) {
         /* We do not want to filter... but are we already filtered?
          * This can happen if an input's proplist changes */
         PA_HASHMAP_FOREACH(filter, u->filters, state) {
-            if (i->sink == filter->sink) {
-                move_input_for_filter(i, filter, TRUE);
+            if (parent == filter->obj) {
+                move_object_for_filter(o, filter, TRUE, is_sink_input);
                 done_something = TRUE;
                 break;
             }
@@ -284,7 +365,7 @@ static pa_hook_result_t sink_input_put_cb(pa_core *core, pa_sink_input *i, struc
     pa_core_assert_ref(core);
     pa_sink_input_assert_ref(i);
 
-    return process(u, i);
+    return process(u, PA_OBJECT(i), TRUE);
 }
 
 static pa_hook_result_t sink_input_move_finish_cb(pa_core *core, pa_sink_input *i, struct userdata *u) {
@@ -294,14 +375,14 @@ static pa_hook_result_t sink_input_move_finish_cb(pa_core *core, pa_sink_input *
     if (pa_proplist_gets(i->proplist, PA_PROP_FILTER_APPLY_MOVING))
         return PA_HOOK_OK;
 
-    return process(u, i);
+    return process(u, PA_OBJECT(i), TRUE);
 }
 
 static pa_hook_result_t sink_input_proplist_cb(pa_core *core, pa_sink_input *i, struct userdata *u) {
     pa_core_assert_ref(core);
     pa_sink_input_assert_ref(i);
 
-    return process(u, i);
+    return process(u, PA_OBJECT(i), TRUE);
 }
 
 static pa_hook_result_t sink_input_unlink_cb(pa_core *core, pa_sink_input *i, struct userdata *u) {
@@ -327,29 +408,102 @@ static pa_hook_result_t sink_unlink_cb(pa_core *core, pa_sink *sink, struct user
     /* If either the parent or the sink we've loaded disappears,
      * we should remove it from our hashmap */
     PA_HASHMAP_FOREACH(filter, u->filters, state) {
-        if (filter->parent_sink == sink || filter->sink == sink) {
+        if (filter->parent_obj == PA_OBJECT(sink) || filter->obj == PA_OBJECT(sink)) {
             uint32_t idx;
 
             /* Attempt to rescue any streams to the parent sink as this is likely
              * the best course of action (as opposed to a generic rescue via
              * module-rescue-streams */
-            if (filter->sink == sink) {
+            if (filter->obj == PA_OBJECT(sink)) {
                 pa_sink_input *i;
 
                 PA_IDXSET_FOREACH(i, sink->inputs, idx)
-                    move_input_for_filter(i, filter, TRUE);
+                    move_object_for_filter(PA_OBJECT(i), filter, TRUE, TRUE);
             }
 
             idx = filter->module_index;
             pa_hashmap_remove(u->filters, filter);
             filter_free(filter);
-            pa_module_unload_request_by_index(u->core, idx, TRUE);
+
+            if (can_unload_module(u, idx))
+                pa_module_unload_request_by_index(u->core, idx, TRUE);
         }
     }
 
     return PA_HOOK_OK;
 }
 
+static pa_hook_result_t source_output_put_cb(pa_core *core, pa_source_output *o, struct userdata *u) {
+    pa_core_assert_ref(core);
+    pa_source_output_assert_ref(o);
+
+    return process(u, PA_OBJECT(o), FALSE);
+}
+
+static pa_hook_result_t source_output_move_finish_cb(pa_core *core, pa_source_output *o, struct userdata *u) {
+    pa_core_assert_ref(core);
+    pa_source_output_assert_ref(o);
+
+    if (pa_proplist_gets(o->proplist, PA_PROP_FILTER_APPLY_MOVING))
+        return PA_HOOK_OK;
+
+    return process(u, PA_OBJECT(o), FALSE);
+}
+
+static pa_hook_result_t source_output_proplist_cb(pa_core *core, pa_source_output *o, struct userdata *u) {
+    pa_core_assert_ref(core);
+    pa_source_output_assert_ref(o);
+
+    return process(u, PA_OBJECT(o), FALSE);
+}
+
+static pa_hook_result_t source_output_unlink_cb(pa_core *core, pa_source_output *o, struct userdata *u) {
+    pa_core_assert_ref(core);
+    pa_source_output_assert_ref(o);
+
+    pa_assert(u);
+
+    if (pa_hashmap_size(u->filters) > 0)
+        trigger_housekeeping(u);
+
+    return PA_HOOK_OK;
+}
+
+static pa_hook_result_t source_unlink_cb(pa_core *core, pa_source *source, struct userdata *u) {
+    void *state;
+    struct filter *filter = NULL;
+
+    pa_core_assert_ref(core);
+    pa_source_assert_ref(source);
+    pa_assert(u);
+
+    /* If either the parent or the source we've loaded disappears,
+     * we should remove it from our hashmap */
+    PA_HASHMAP_FOREACH(filter, u->filters, state) {
+        if (filter->parent_obj == PA_OBJECT(source) || filter->obj == PA_OBJECT(source)) {
+            uint32_t idx;
+
+            /* Attempt to rescue any streams to the parent source as this is likely
+             * the best course of action (as opposed to a generic rescue via
+             * module-rescue-streams */
+            if (filter->obj == PA_OBJECT(source)) {
+                pa_source_output *o;
+
+                PA_IDXSET_FOREACH(o, source->outputs, idx)
+                    move_object_for_filter(PA_OBJECT(o), filter, TRUE, FALSE);
+            }
+
+            idx = filter->module_index;
+            pa_hashmap_remove(u->filters, filter);
+            filter_free(filter);
+
+            if (can_unload_module(u, idx))
+                pa_module_unload_request_by_index(u->core, idx, TRUE);
+        }
+    }
+
+    return PA_HOOK_OK;
+}
 
 int pa__init(pa_module *m) {
     pa_modargs *ma = NULL;
@@ -379,6 +533,11 @@ int pa__init(pa_module *m) {
     u->sink_input_proplist_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_PROPLIST_CHANGED], PA_HOOK_LATE, (pa_hook_cb_t) sink_input_proplist_cb, u);
     u->sink_input_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_UNLINK], PA_HOOK_LATE, (pa_hook_cb_t) sink_input_unlink_cb, u);
     u->sink_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK], PA_HOOK_LATE, (pa_hook_cb_t) sink_unlink_cb, u);
+    u->source_output_put_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_PUT], PA_HOOK_LATE, (pa_hook_cb_t) source_output_put_cb, u);
+    u->source_output_move_finish_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MOVE_FINISH], PA_HOOK_LATE, (pa_hook_cb_t) source_output_move_finish_cb, u);
+    u->source_output_proplist_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_PROPLIST_CHANGED], PA_HOOK_LATE, (pa_hook_cb_t) source_output_proplist_cb, u);
+    u->source_output_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK], PA_HOOK_LATE, (pa_hook_cb_t) source_output_unlink_cb, u);
+    u->source_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], PA_HOOK_LATE, (pa_hook_cb_t) source_unlink_cb, u);
 
     pa_modargs_free(ma);
 
@@ -411,6 +570,16 @@ void pa__done(pa_module *m) {
         pa_hook_slot_free(u->sink_input_unlink_slot);
     if (u->sink_unlink_slot)
         pa_hook_slot_free(u->sink_unlink_slot);
+    if (u->source_output_put_slot)
+        pa_hook_slot_free(u->source_output_put_slot);
+    if (u->source_output_move_finish_slot)
+        pa_hook_slot_free(u->source_output_move_finish_slot);
+    if (u->source_output_proplist_slot)
+        pa_hook_slot_free(u->source_output_proplist_slot);
+    if (u->source_output_unlink_slot)
+        pa_hook_slot_free(u->source_output_unlink_slot);
+    if (u->source_unlink_slot)
+        pa_hook_slot_free(u->source_unlink_slot);
 
     if (u->housekeeping_time_event)
         u->core->mainloop->time_free(u->housekeeping_time_event);
diff --git a/src/modules/module-filter-heuristics.c b/src/modules/module-filter-heuristics.c
index 20a4835..4fba291 100644
--- a/src/modules/module-filter-heuristics.c
+++ b/src/modules/module-filter-heuristics.c
@@ -49,30 +49,41 @@ struct userdata {
     pa_core *core;
     pa_hook_slot
         *sink_input_put_slot,
-        *sink_input_move_finish_slot;
+        *sink_input_move_finish_slot,
+        *source_output_put_slot,
+        *source_output_move_finish_slot;
 };
 
-static pa_hook_result_t process(struct userdata *u, pa_sink_input *i) {
-    const char *want, *sink_role, *si_role;
+static pa_hook_result_t process(struct userdata *u, pa_object *o, pa_bool_t is_sink_input) {
+    const char *want, *int_role, *stream_role;
+    pa_proplist *pl, *parent_pl;
+
+    if (is_sink_input) {
+        pl = PA_SINK_INPUT(o)->proplist;
+        parent_pl = PA_SINK_INPUT(o)->sink->proplist;
+    } else {
+        pl = PA_SOURCE_OUTPUT(o)->proplist;
+        parent_pl = PA_SOURCE_OUTPUT(o)->source->proplist;
+    }
 
     /* If the stream already specifies what it must have, then let it be. */
-    if (!pa_proplist_gets(i->proplist, PA_PROP_FILTER_HEURISTICS) && pa_proplist_gets(i->proplist, PA_PROP_FILTER_APPLY))
+    if (!pa_proplist_gets(pl, PA_PROP_FILTER_HEURISTICS) && pa_proplist_gets(pl, PA_PROP_FILTER_APPLY))
         return PA_HOOK_OK;
 
-    want = pa_proplist_gets(i->proplist, PA_PROP_FILTER_WANT);
+    want = pa_proplist_gets(pl, PA_PROP_FILTER_WANT);
     if (!want) {
         /* This is a phone stream, maybe we want echo cancellation */
-        if ((si_role = pa_proplist_gets(i->proplist, PA_PROP_MEDIA_ROLE)) && pa_streq(si_role, "phone"))
+        if ((stream_role = pa_proplist_gets(pl, PA_PROP_MEDIA_ROLE)) && pa_streq(stream_role, "phone"))
             want = "echo-cancel";
     }
 
     /* On phone sinks, make sure we're not applying echo cancellation */
-    if ((sink_role = pa_proplist_gets(i->sink->proplist, PA_PROP_DEVICE_INTENDED_ROLES)) && strstr(sink_role, "phone")) {
-        const char *apply = pa_proplist_gets(i->proplist, PA_PROP_FILTER_APPLY);
+    if ((int_role = pa_proplist_gets(parent_pl, PA_PROP_DEVICE_INTENDED_ROLES)) && strstr(int_role, "phone")) {
+        const char *apply = pa_proplist_gets(pl, PA_PROP_FILTER_APPLY);
 
         if (apply && pa_streq(apply, "echo-cancel")) {
-            pa_proplist_unset(i->proplist, PA_PROP_FILTER_APPLY);
-            pa_proplist_unset(i->proplist, PA_PROP_FILTER_HEURISTICS);
+            pa_proplist_unset(pl, PA_PROP_FILTER_APPLY);
+            pa_proplist_unset(pl, PA_PROP_FILTER_HEURISTICS);
         }
 
         return PA_HOOK_OK;
@@ -80,8 +91,8 @@ static pa_hook_result_t process(struct userdata *u, pa_sink_input *i) {
 
     if (want) {
         /* There's a filter that we want, ask module-filter-apply to apply it, and remember that we're managing filter.apply */
-        pa_proplist_sets(i->proplist, PA_PROP_FILTER_APPLY, want);
-        pa_proplist_sets(i->proplist, PA_PROP_FILTER_HEURISTICS, "1");
+        pa_proplist_sets(pl, PA_PROP_FILTER_APPLY, want);
+        pa_proplist_sets(pl, PA_PROP_FILTER_HEURISTICS, "1");
     }
 
     return PA_HOOK_OK;
@@ -92,7 +103,7 @@ static pa_hook_result_t sink_input_put_cb(pa_core *core, pa_sink_input *i, struc
     pa_sink_input_assert_ref(i);
     pa_assert(u);
 
-    return process(u, i);
+    return process(u, PA_OBJECT(i), TRUE);
 }
 
 static pa_hook_result_t sink_input_move_finish_cb(pa_core *core, pa_sink_input *i, struct userdata *u) {
@@ -104,7 +115,27 @@ static pa_hook_result_t sink_input_move_finish_cb(pa_core *core, pa_sink_input *
     if (pa_proplist_gets(i->proplist, PA_PROP_FILTER_APPLY_MOVING))
         return PA_HOOK_OK;
 
-    return process(u, i);
+    return process(u, PA_OBJECT(i), TRUE);
+}
+
+static pa_hook_result_t source_output_put_cb(pa_core *core, pa_source_output *i, struct userdata *u) {
+    pa_core_assert_ref(core);
+    pa_source_output_assert_ref(i);
+    pa_assert(u);
+
+    return process(u, PA_OBJECT(i), FALSE);
+}
+
+static pa_hook_result_t source_output_move_finish_cb(pa_core *core, pa_source_output *i, struct userdata *u) {
+    pa_core_assert_ref(core);
+    pa_source_output_assert_ref(i);
+    pa_assert(u);
+
+    /* module-filter-apply triggered this move, ignore */
+    if (pa_proplist_gets(i->proplist, PA_PROP_FILTER_APPLY_MOVING))
+        return PA_HOOK_OK;
+
+    return process(u, PA_OBJECT(i), FALSE);
 }
 
 int pa__init(pa_module *m) {
@@ -124,6 +155,8 @@ int pa__init(pa_module *m) {
 
     u->sink_input_put_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_PUT], PA_HOOK_LATE-1, (pa_hook_cb_t) sink_input_put_cb, u);
     u->sink_input_move_finish_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_FINISH], PA_HOOK_LATE-1, (pa_hook_cb_t) sink_input_move_finish_cb, u);
+    u->source_output_put_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_PUT], PA_HOOK_LATE-1, (pa_hook_cb_t) source_output_put_cb, u);
+    u->source_output_move_finish_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MOVE_FINISH], PA_HOOK_LATE-1, (pa_hook_cb_t) source_output_move_finish_cb, u);
 
     pa_modargs_free(ma);
 
@@ -152,6 +185,10 @@ void pa__done(pa_module *m) {
         pa_hook_slot_free(u->sink_input_put_slot);
     if (u->sink_input_move_finish_slot)
         pa_hook_slot_free(u->sink_input_move_finish_slot);
+    if (u->source_output_put_slot)
+        pa_hook_slot_free(u->source_output_put_slot);
+    if (u->source_output_move_finish_slot)
+        pa_hook_slot_free(u->source_output_move_finish_slot);
 
     pa_xfree(u);
 

commit 8460466f8645c3b5596652c566f3624260e4d437
Author: Arun Raghavan <arun.raghavan at collabora.co.uk>
Date:   Fri Apr 22 17:44:50 2011 +0530

    echo-cancel: Play nice with module-filter-*
    
    With automaticl filter loading by module-filter-apply, setting the
    virtual sink/source to have the "phone" intended role will break routing
    when you first connect a phone stream to an ALSA device and then turn on
    your Bluetooth headset. This happens because module-intended-roles
    doesn't move a stream if it is already on a device that provides the
    required role.
    
    This patch introduces a "manual_load" parameter that is meant to be used
    when not using module-filter-apply for loading the AEC module. If this
    parameter is set, the virtual devices are given the "phone" role, else
    we count on module-filter-heuristics to do the right thing.

diff --git a/src/modules/echo-cancel/module-echo-cancel.c b/src/modules/echo-cancel/module-echo-cancel.c
index b8f269f..746028b 100644
--- a/src/modules/echo-cancel/module-echo-cancel.c
+++ b/src/modules/echo-cancel/module-echo-cancel.c
@@ -78,6 +78,7 @@ PA_MODULE_USAGE(
           "aec_method=<implementation to use> "
           "aec_args=<parameters for the AEC engine> "
           "save_aec=<save AEC data in /tmp> "
+          "manual_load=<set if this module is being loaded manually> "
         ));
 
 /* NOTE: Make sure the enum and ec_table are maintained in the correct order */
@@ -106,6 +107,7 @@ static const pa_echo_canceller ec_table[] = {
 
 #define DEFAULT_ADJUST_TIME_USEC (1*PA_USEC_PER_SEC)
 #define DEFAULT_SAVE_AEC 0
+#define DEFAULT_MANUAL_LOAD FALSE
 
 #define MEMBLOCKQ_MAXLENGTH (16*1024*1024)
 
@@ -156,6 +158,7 @@ struct userdata {
     pa_core *core;
     pa_module *module;
 
+    pa_bool_t manual_load;
     uint32_t save_aec;
 
     pa_echo_canceller *ec;
@@ -210,6 +213,7 @@ static const char* const valid_modargs[] = {
     "aec_method",
     "aec_args",
     "save_aec",
+    "manual_load",
     NULL
 };
 
@@ -1394,6 +1398,12 @@ int pa__init(pa_module*m) {
         goto fail;
     }
 
+    u->manual_load = DEFAULT_MANUAL_LOAD;
+    if (pa_modargs_get_value_boolean(ma, "manual_load", &u->manual_load) < 0) {
+        pa_log("Failed to parse manual_load value");
+        goto fail;
+    }
+
     u->asyncmsgq = pa_asyncmsgq_new(0);
     u->need_realign = TRUE;
     if (u->ec->init) {
@@ -1413,7 +1423,8 @@ int pa__init(pa_module*m) {
     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");
+    if (u->manual_load)
+        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) {
@@ -1460,7 +1471,8 @@ int pa__init(pa_module*m) {
     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");
+    if (u->manual_load)
+        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 fd14a9af359657c5c3b9bd8b031c4b7cacd6e357
Author: Arun Raghavan <arun.raghavan at collabora.co.uk>
Date:   Fri Apr 22 19:57:29 2011 +0530

    filter-heuristics: Match roles correctly

diff --git a/src/modules/module-filter-heuristics.c b/src/modules/module-filter-heuristics.c
index 4fba291..1285c46 100644
--- a/src/modules/module-filter-heuristics.c
+++ b/src/modules/module-filter-heuristics.c
@@ -54,8 +54,29 @@ struct userdata {
         *source_output_move_finish_slot;
 };
 
+static pa_bool_t role_match(pa_proplist *proplist, const char *role) {
+    const char *ir;
+    char *r;
+    const char *state = NULL;
+
+    if (!(ir = pa_proplist_gets(proplist, PA_PROP_DEVICE_INTENDED_ROLES)))
+        return FALSE;
+
+    while ((r = pa_split_spaces(ir, &state))) {
+
+        if (pa_streq(role, r)) {
+            pa_xfree(r);
+            return TRUE;
+        }
+
+        pa_xfree(r);
+    }
+
+    return FALSE;
+}
+
 static pa_hook_result_t process(struct userdata *u, pa_object *o, pa_bool_t is_sink_input) {
-    const char *want, *int_role, *stream_role;
+    const char *want, *stream_role;
     pa_proplist *pl, *parent_pl;
 
     if (is_sink_input) {
@@ -78,7 +99,7 @@ static pa_hook_result_t process(struct userdata *u, pa_object *o, pa_bool_t is_s
     }
 
     /* On phone sinks, make sure we're not applying echo cancellation */
-    if ((int_role = pa_proplist_gets(parent_pl, PA_PROP_DEVICE_INTENDED_ROLES)) && strstr(int_role, "phone")) {
+    if (role_match(parent_pl, "phone")) {
         const char *apply = pa_proplist_gets(pl, PA_PROP_FILTER_APPLY);
 
         if (apply && pa_streq(apply, "echo-cancel")) {

-- 
hooks/post-receive
PulseAudio Sound Server



More information about the pulseaudio-commits mailing list