[pulseaudio-commits] 20 commits - po/POTFILES.in src/daemon src/Makefile.am src/modules src/pulsecore src/utils

Tanu Kaskinen tanuk at kemper.freedesktop.org
Tue Sep 5 10:56:49 UTC 2017


 po/POTFILES.in                                                        |    1 
 src/Makefile.am                                                       |   17 
 src/daemon/main.c                                                     |   10 
 src/modules/alsa/alsa-mixer.c                                         |    3 
 src/modules/alsa/mixer/paths/iec958-stereo-input.conf                 |   20 +
 src/modules/alsa/mixer/paths/steelseries-arctis-input.conf            |   25 +
 src/modules/alsa/mixer/paths/steelseries-arctis-output-mono.conf      |   29 +
 src/modules/alsa/mixer/paths/steelseries-arctis-output-stereo.conf    |   27 +
 src/modules/alsa/mixer/profile-sets/90-pulseaudio.rules               |    2 
 src/modules/alsa/mixer/profile-sets/dell-dock-tb16-usb-audio.conf     |   55 ++
 src/modules/alsa/mixer/profile-sets/steelseries-arctis-usb-audio.conf |   43 ++
 src/modules/bluetooth/backend-native.c                                |    2 
 src/modules/bluetooth/bluez5-util.c                                   |    1 
 src/modules/bluetooth/bluez5-util.h                                   |   11 
 src/modules/bluetooth/module-bluetooth-discover.c                     |    4 
 src/modules/bluetooth/module-bluetooth-policy.c                       |    6 
 src/modules/bluetooth/module-bluez4-discover.c                        |    2 
 src/modules/bluetooth/module-bluez5-device.c                          |   22 +
 src/modules/bluetooth/module-bluez5-discover.c                        |    2 
 src/modules/dbus/iface-core.c                                         |    2 
 src/modules/gconf/module-gconf.c                                      |    2 
 src/modules/jack/module-jackdbus-detect.c                             |    2 
 src/modules/macosx/module-coreaudio-detect.c                          |    2 
 src/modules/module-allow-passthrough.c                                |    2 
 src/modules/module-always-sink.c                                      |    2 
 src/modules/module-always-source.c                                    |  189 ++++++++++
 src/modules/module-combine.c                                          |    2 
 src/modules/module-detect.c                                           |   14 
 src/modules/module-equalizer-sink.c                                   |   26 +
 src/modules/module-filter-apply.c                                     |  132 +++++-
 src/modules/module-hal-detect-compat.c                                |    2 
 src/modules/module-loopback.c                                         |   49 +-
 src/modules/module-switch-on-port-available.c                         |    5 
 src/modules/module-udev-detect.c                                      |    2 
 src/modules/module-volume-restore.c                                   |    2 
 src/modules/module-zeroconf-discover.c                                |    2 
 src/modules/raop/module-raop-discover.c                               |    2 
 src/modules/raop/raop-sink.c                                          |    7 
 src/pulsecore/cli-command.c                                           |   16 
 src/pulsecore/device-port.c                                           |    2 
 src/pulsecore/module.c                                                |   20 -
 src/pulsecore/module.h                                                |    2 
 src/pulsecore/protocol-native.c                                       |    2 
 src/pulsecore/sink.c                                                  |   24 +
 src/pulsecore/sink.h                                                  |    1 
 src/utils/padsp.c                                                     |   15 
 46 files changed, 709 insertions(+), 101 deletions(-)

New commits:
commit 703d95fd00aff96fc66c5f960345945dd04e8980
Author: Arun Raghavan <arun at arunraghavan.net>
Date:   Sun Sep 3 13:26:14 2017 +0530

    always-source: Fix pa_module_load() usage
    
    The API changed slightly since the original patch was written.

diff --git a/src/modules/module-always-source.c b/src/modules/module-always-source.c
index 09ac7aa5..5c1f22aa 100644
--- a/src/modules/module-always-source.c
+++ b/src/modules/module-always-source.c
@@ -80,7 +80,7 @@ static void load_null_source_if_needed(pa_core *c, pa_source *source, struct use
     u->ignore = true;
 
     t = pa_sprintf_malloc("source_name=%s", u->source_name);
-    m = pa_module_load(c, "module-null-source", t);
+    pa_module_load(&m, c, "module-null-source", t);
     u->null_module = m ? m->index : PA_INVALID_INDEX;
     pa_xfree(t);
 

commit 15d28f21b4c00bd38a3797461b28bb6f90709aae
Author: Pierre-Louis Bossart <pierre-louis.bossart at linux.intel.com>
Date:   Mon Aug 28 17:49:16 2017 -0500

    sink: force suspend/resume on passthrough transitions
    
    A race condition prevents the AES non-audio bit from being set
    when enabling IEC61937 passthrough on resume with no sink-input
    connected (pa_sink_is_passthrough returns false). The non-audio
    bit should really be set when opening the sink.
    
    Force the sink to suspend/resume when actually entering passthrough
    mode, and likewise force a suspend-resume on leaving passthrough mode.
    
    Tested with E-AC3 streams which do need the AES bit set for my
    Onkyon receiver to detect the format instead of playing it as
    PCM.
    
    Signed-off-by: Pierre-Louis Bossart <pierre-louis.bossart at linux.intel.com>

diff --git a/src/pulsecore/sink.c b/src/pulsecore/sink.c
index a8b4cd3d..0cc76514 100644
--- a/src/pulsecore/sink.c
+++ b/src/pulsecore/sink.c
@@ -1626,6 +1626,11 @@ bool pa_sink_is_passthrough(pa_sink *s) {
 void pa_sink_enter_passthrough(pa_sink *s) {
     pa_cvolume volume;
 
+    if (s->is_passthrough_set) {
+	pa_log_debug("Sink %s is already in passthrough mode, nothing to do", s->name);
+	return;
+    }
+
     /* disable the monitor in passthrough mode */
     if (s->monitor_source) {
         pa_log_debug("Suspending monitor source %s, because the sink is entering the passthrough mode.", s->monitor_source->name);
@@ -1638,10 +1643,23 @@ void pa_sink_enter_passthrough(pa_sink *s) {
 
     pa_cvolume_set(&volume, s->sample_spec.channels, PA_MIN(s->base_volume, PA_VOLUME_NORM));
     pa_sink_set_volume(s, &volume, true, false);
+
+    pa_log_debug("Suspending/Restarting sink %s to enter passthrough mode", s->name);
+
+    /* force sink to be resumed in passthrough mode */
+    pa_sink_suspend(s, true, PA_SUSPEND_INTERNAL);
+    s->is_passthrough_set = true;
+    pa_sink_suspend(s, false, PA_SUSPEND_INTERNAL);
 }
 
 /* Called from main context */
 void pa_sink_leave_passthrough(pa_sink *s) {
+
+    if (!s->is_passthrough_set) {
+	pa_log_debug("Sink %s is not in passthrough mode, nothing to do", s->name);
+	return;
+    }
+
     /* Unsuspend monitor */
     if (s->monitor_source) {
         pa_log_debug("Resuming monitor source %s, because the sink is leaving the passthrough mode.", s->monitor_source->name);
@@ -1653,6 +1671,12 @@ void pa_sink_leave_passthrough(pa_sink *s) {
 
     pa_cvolume_init(&s->saved_volume);
     s->saved_save_volume = false;
+
+    /* force sink to be resumed in non-passthrough mode */
+    pa_sink_suspend(s, true, PA_SUSPEND_INTERNAL);
+    s->is_passthrough_set = false;
+    pa_sink_suspend(s, false, PA_SUSPEND_INTERNAL);
+
 }
 
 /* Called from main context. */
diff --git a/src/pulsecore/sink.h b/src/pulsecore/sink.h
index 0e79cf36..7deafdd8 100644
--- a/src/pulsecore/sink.h
+++ b/src/pulsecore/sink.h
@@ -108,6 +108,7 @@ struct pa_sink {
     /* Saved volume state while we're in passthrough mode */
     pa_cvolume saved_volume;
     bool saved_save_volume:1;
+    bool is_passthrough_set:1;
 
     pa_asyncmsgq *asyncmsgq;
 

commit b4d2443249c2ab31f58ed97a968db41c1fe61b49
Author: Sebastian Dröge <sebastian at centricular.com>
Date:   Fri Jun 30 00:57:36 2017 +0300

    Implement module-always-source
    
    This is basically a copy of module-always-sink but doing the same for
    sources. Whenever no source is available, a module-null-source is loaded
    and whenever a new source is available again, module-null-source is
    unloaded.
    
    By this, anything using a source will automatically be switched to the
    null source when the actual source disappears, and back to the actual
    source if it appears again.

diff --git a/po/POTFILES.in b/po/POTFILES.in
index e2d6d118..d4c78cf5 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -24,6 +24,7 @@ src/modules/jack/module-jack-source.c
 src/modules/macosx/module-coreaudio-device.c
 src/modules/module-allow-passthrough.c
 src/modules/module-always-sink.c
+src/modules/module-always-source.c
 src/modules/module-cli.c
 src/modules/module-combine.c
 src/modules/module-console-kit.c
diff --git a/src/Makefile.am b/src/Makefile.am
index ba2ea97e..d7a551e2 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1193,6 +1193,7 @@ modlibexec_LTLIBRARIES += \
 		module-card-restore.la \
 		module-default-device-restore.la \
 		module-always-sink.la \
+		module-always-source.la \
 		module-rescue-streams.la \
 		module-intended-roles.la \
 		module-suspend-on-idle.la \
@@ -1534,6 +1535,7 @@ SYMDEF_FILES = \
 		module-card-restore-symdef.h \
 		module-default-device-restore-symdef.h \
 		module-always-sink-symdef.h \
+		module-always-source-symdef.h \
 		module-rescue-streams-symdef.h \
 		module-intended-roles-symdef.h \
 		module-suspend-on-idle-symdef.h \
@@ -2021,6 +2023,12 @@ module_always_sink_la_LDFLAGS = $(MODULE_LDFLAGS)
 module_always_sink_la_LIBADD = $(MODULE_LIBADD)
 module_always_sink_la_CFLAGS = $(AM_CFLAGS)
 
+# Always Source module
+module_always_source_la_SOURCES = modules/module-always-source.c
+module_always_source_la_LDFLAGS = $(MODULE_LDFLAGS)
+module_always_source_la_LIBADD = $(MODULE_LIBADD)
+module_always_source_la_CFLAGS = $(AM_CFLAGS)
+
 # Rescue streams module
 module_rescue_streams_la_SOURCES = modules/module-rescue-streams.c
 module_rescue_streams_la_LDFLAGS = $(MODULE_LDFLAGS)
diff --git a/src/modules/module-always-source.c b/src/modules/module-always-source.c
new file mode 100644
index 00000000..09ac7aa5
--- /dev/null
+++ b/src/modules/module-always-source.c
@@ -0,0 +1,189 @@
+/***
+    This file is part of PulseAudio.
+
+    Copyright 2008 Colin Guthrie
+    Copyright 2017 Sebastian Dröge <sebastian at centricular.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, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/core.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/i18n.h>
+#include <pulsecore/source.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/log.h>
+
+#include "module-always-source-symdef.h"
+
+PA_MODULE_AUTHOR("Sebastian Dröge");
+PA_MODULE_DESCRIPTION(_("Always keeps at least one source loaded even if it's a null one"));
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_LOAD_ONCE(true);
+PA_MODULE_USAGE(
+        "source_name=<name of source>");
+
+#define DEFAULT_SOURCE_NAME "auto_null"
+
+static const char* const valid_modargs[] = {
+    "source_name",
+    NULL,
+};
+
+struct userdata {
+    uint32_t null_module;
+    bool ignore;
+    char *source_name;
+};
+
+static void load_null_source_if_needed(pa_core *c, pa_source *source, struct userdata* u) {
+    pa_source *target;
+    uint32_t idx;
+    char *t;
+    pa_module *m;
+
+    pa_assert(c);
+    pa_assert(u);
+
+    if (u->null_module != PA_INVALID_INDEX)
+        return; /* We've already got a null-source loaded */
+
+    /* Loop through all sources and check to see if we have *any*
+     * sources. Ignore the source passed in (if it's not null), and
+     * don't count filter or monitor sources. */
+    PA_IDXSET_FOREACH(target, c->sources, idx)
+        if (!source || ((target != source) && !pa_source_is_filter(target) && target->monitor_of == NULL))
+            break;
+
+    if (target)
+        return;
+
+    pa_log_debug("Autoloading null-source as no other sources detected.");
+
+    u->ignore = true;
+
+    t = pa_sprintf_malloc("source_name=%s", u->source_name);
+    m = pa_module_load(c, "module-null-source", t);
+    u->null_module = m ? m->index : PA_INVALID_INDEX;
+    pa_xfree(t);
+
+    u->ignore = false;
+
+    if (!m)
+        pa_log_warn("Unable to load module-null-source");
+}
+
+static pa_hook_result_t put_hook_callback(pa_core *c, pa_source *source, void* userdata) {
+    struct userdata *u = userdata;
+
+    pa_assert(c);
+    pa_assert(source);
+    pa_assert(u);
+
+    /* This is us detecting ourselves on load... just ignore this. */
+    if (u->ignore)
+        return PA_HOOK_OK;
+
+    /* There's no point in doing anything if the core is shut down anyway */
+    if (c->state == PA_CORE_SHUTDOWN)
+        return PA_HOOK_OK;
+
+    /* Auto-loaded null-source not active, so ignoring newly detected source. */
+    if (u->null_module == PA_INVALID_INDEX)
+        return PA_HOOK_OK;
+
+    /* This is us detecting ourselves on load in a different way... just ignore this too. */
+    if (source->module && source->module->index == u->null_module)
+        return PA_HOOK_OK;
+
+    /* We don't count filter or monitor sources since they need a real source */
+    if (pa_source_is_filter(source) || source->monitor_of != NULL)
+        return PA_HOOK_OK;
+
+    pa_log_info("A new source has been discovered. Unloading null-source.");
+
+    pa_module_unload_request_by_index(c, u->null_module, true);
+    u->null_module = PA_INVALID_INDEX;
+
+    return PA_HOOK_OK;
+}
+
+static pa_hook_result_t unlink_hook_callback(pa_core *c, pa_source *source, void* userdata) {
+    struct userdata *u = userdata;
+
+    pa_assert(c);
+    pa_assert(source);
+    pa_assert(u);
+
+    /* First check to see if it's our own null-source that's been removed... */
+    if (u->null_module != PA_INVALID_INDEX && source->module && source->module->index == u->null_module) {
+        pa_log_debug("Autoloaded null-source removed");
+        u->null_module = PA_INVALID_INDEX;
+        return PA_HOOK_OK;
+    }
+
+    /* There's no point in doing anything if the core is shut down anyway */
+    if (c->state == PA_CORE_SHUTDOWN)
+        return PA_HOOK_OK;
+
+    load_null_source_if_needed(c, source, u);
+
+    return PA_HOOK_OK;
+}
+
+int pa__init(pa_module*m) {
+    pa_modargs *ma = NULL;
+    struct userdata *u;
+
+    pa_assert(m);
+
+    if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+        pa_log("Failed to parse module arguments");
+        return -1;
+    }
+
+    m->userdata = u = pa_xnew(struct userdata, 1);
+    u->source_name = pa_xstrdup(pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME));
+    pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_SOURCE_PUT], PA_HOOK_LATE, (pa_hook_cb_t) put_hook_callback, u);
+    pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], PA_HOOK_EARLY, (pa_hook_cb_t) unlink_hook_callback, u);
+    u->null_module = PA_INVALID_INDEX;
+    u->ignore = false;
+
+    pa_modargs_free(ma);
+
+    load_null_source_if_needed(m->core, NULL, u);
+
+    return 0;
+}
+
+void pa__done(pa_module*m) {
+    struct userdata *u;
+
+    pa_assert(m);
+
+    if (!(u = m->userdata))
+        return;
+
+    if (u->null_module != PA_INVALID_INDEX && m->core->state != PA_CORE_SHUTDOWN)
+        pa_module_unload_request_by_index(m->core, u->null_module, true);
+
+    pa_xfree(u->source_name);
+    pa_xfree(u);
+}

commit c7fe78c9f73ded2c3428666722ec9c1af4b82812
Author: Tanu Kaskinen <tanuk at iki.fi>
Date:   Sat Sep 2 18:23:12 2017 +0300

    build-sys: add the Arctis configuration

diff --git a/src/Makefile.am b/src/Makefile.am
index 1d974037..ba2ea97e 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1310,7 +1310,8 @@ dist_alsaprofilesets_DATA = \
 		modules/alsa/mixer/profile-sets/native-instruments-traktorkontrol-s4.conf \
 		modules/alsa/mixer/profile-sets/native-instruments-korecontroller.conf \
 		modules/alsa/mixer/profile-sets/kinect-audio.conf \
-		modules/alsa/mixer/profile-sets/sb-omni-surround-5.1.conf
+		modules/alsa/mixer/profile-sets/sb-omni-surround-5.1.conf \
+		modules/alsa/mixer/profile-sets/steelseries-arctis-usb-audio.conf
 
 if HAVE_UDEV
 dist_udevrules_DATA = \
@@ -1352,7 +1353,10 @@ dist_alsapaths_DATA = \
 		modules/alsa/mixer/paths/hdmi-output-4.conf \
 		modules/alsa/mixer/paths/hdmi-output-5.conf \
 		modules/alsa/mixer/paths/hdmi-output-6.conf \
-		modules/alsa/mixer/paths/hdmi-output-7.conf
+		modules/alsa/mixer/paths/hdmi-output-7.conf \
+		modules/alsa/mixer/paths/steelseries-arctis-input.conf \
+		modules/alsa/mixer/paths/steelseries-arctis-output-mono.conf \
+		modules/alsa/mixer/paths/steelseries-arctis-output-stereo.conf
 
 endif
 

commit 9c7a9be7cd907262683c720f4f9afae3f056a4f1
Author: Tanu Kaskinen <tanuk at iki.fi>
Date:   Sat Sep 2 15:44:58 2017 +0300

    bluetooth: recognize another HSP HS UUID
    
    There are actually two HSP HS UUIDs. My theory is that the second one
    was added, because someone was not happy with the old UUID being used
    for identifying two different things (the HSP profile as a whole, and
    the HS role within the HSP profile). Some headsets only use the new
    UUID, and those headsets won't work if we don't recognize the new UUID.
    
    BugLink: https://bugs.freedesktop.org/show_bug.cgi?id=93898

diff --git a/src/modules/bluetooth/backend-native.c b/src/modules/bluetooth/backend-native.c
index 6eb4e168..1ed436f2 100644
--- a/src/modules/bluetooth/backend-native.c
+++ b/src/modules/bluetooth/backend-native.c
@@ -335,7 +335,7 @@ static void register_profile(pa_bluetooth_backend *b, const char *profile, const
     pa_assert_se(dbus_message_iter_append_basic(&i, DBUS_TYPE_STRING, &uuid));
     dbus_message_iter_open_container(&i, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING
             DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &d);
-    if (pa_streq (uuid, PA_BLUETOOTH_UUID_HSP_HS)) {
+    if (pa_bluetooth_uuid_is_hsp_hs(uuid)) {
         /* In the headset role, the connection will only be initiated from the remote side */
         autoconnect = 0;
         pa_dbus_append_basic_variant_dict_entry(&d, "AutoConnect", DBUS_TYPE_BOOLEAN, &autoconnect);
diff --git a/src/modules/bluetooth/bluez5-util.c b/src/modules/bluetooth/bluez5-util.c
index c9283232..304a26e8 100644
--- a/src/modules/bluetooth/bluez5-util.c
+++ b/src/modules/bluetooth/bluez5-util.c
@@ -176,6 +176,7 @@ static bool device_supports_profile(pa_bluetooth_device *device, pa_bluetooth_pr
             return !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_A2DP_SOURCE);
         case PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT:
             return !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_HSP_HS)
+                || !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_HSP_HS_ALT)
                 || !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_HFP_HF);
         case PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY:
             return !!pa_hashmap_get(device->uuids, PA_BLUETOOTH_UUID_HSP_AG)
diff --git a/src/modules/bluetooth/bluez5-util.h b/src/modules/bluetooth/bluez5-util.h
index a3e7bf3d..ad30708f 100644
--- a/src/modules/bluetooth/bluez5-util.h
+++ b/src/modules/bluetooth/bluez5-util.h
@@ -24,7 +24,14 @@
 
 #define PA_BLUETOOTH_UUID_A2DP_SOURCE "0000110a-0000-1000-8000-00805f9b34fb"
 #define PA_BLUETOOTH_UUID_A2DP_SINK   "0000110b-0000-1000-8000-00805f9b34fb"
+
+/* There are two HSP HS UUIDs. The first one (older?) is used both as the HSP
+ * profile identifier and as the HS role identifier, while the second one is
+ * only used to identify the role. As far as PulseAudio is concerned, the two
+ * UUIDs mean exactly the same thing. */
 #define PA_BLUETOOTH_UUID_HSP_HS      "00001108-0000-1000-8000-00805f9b34fb"
+#define PA_BLUETOOTH_UUID_HSP_HS_ALT  "00001131-0000-1000-8000-00805f9b34fb"
+
 #define PA_BLUETOOTH_UUID_HSP_AG      "00001112-0000-1000-8000-00805f9b34fb"
 #define PA_BLUETOOTH_UUID_HFP_HF      "0000111e-0000-1000-8000-00805f9b34fb"
 #define PA_BLUETOOTH_UUID_HFP_AG      "0000111f-0000-1000-8000-00805f9b34fb"
@@ -157,6 +164,10 @@ pa_hook* pa_bluetooth_discovery_hook(pa_bluetooth_discovery *y, pa_bluetooth_hoo
 
 const char *pa_bluetooth_profile_to_string(pa_bluetooth_profile_t profile);
 
+static inline bool pa_bluetooth_uuid_is_hsp_hs(const char *uuid) {
+    return pa_streq(uuid, PA_BLUETOOTH_UUID_HSP_HS) || pa_streq(uuid, PA_BLUETOOTH_UUID_HSP_HS_ALT);
+}
+
 #define HEADSET_BACKEND_OFONO 0
 #define HEADSET_BACKEND_NATIVE 1
 #define HEADSET_BACKEND_AUTO 2
diff --git a/src/modules/bluetooth/module-bluez5-device.c b/src/modules/bluetooth/module-bluez5-device.c
index 7b5d313b..ebe7c774 100644
--- a/src/modules/bluetooth/module-bluez5-device.c
+++ b/src/modules/bluetooth/module-bluez5-device.c
@@ -1960,7 +1960,7 @@ static int uuid_to_profile(const char *uuid, pa_bluetooth_profile_t *_r) {
         *_r = PA_BLUETOOTH_PROFILE_A2DP_SINK;
     else if (pa_streq(uuid, PA_BLUETOOTH_UUID_A2DP_SOURCE))
         *_r = PA_BLUETOOTH_PROFILE_A2DP_SOURCE;
-    else if (pa_streq(uuid, PA_BLUETOOTH_UUID_HSP_HS) || pa_streq(uuid, PA_BLUETOOTH_UUID_HFP_HF))
+    else if (pa_bluetooth_uuid_is_hsp_hs(uuid) || pa_streq(uuid, PA_BLUETOOTH_UUID_HFP_HF))
         *_r = PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT;
     else if (pa_streq(uuid, PA_BLUETOOTH_UUID_HSP_AG) || pa_streq(uuid, PA_BLUETOOTH_UUID_HFP_AG))
         *_r = PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY;

commit 15386a710c1500f70085a6312fb4d84be4d254c9
Author: Johan Heikkilä <johan.heikkila at gmail.com>
Date:   Sun Aug 27 16:29:37 2017 +0300

    alsa-mixer: add support for Steelseries Arctis 7 headset

diff --git a/src/modules/alsa/alsa-mixer.c b/src/modules/alsa/alsa-mixer.c
index aeaf12c4..08ea45d3 100644
--- a/src/modules/alsa/alsa-mixer.c
+++ b/src/modules/alsa/alsa-mixer.c
@@ -2469,6 +2469,7 @@ static int path_verify(pa_alsa_path *p) {
         { "analog-input-video",         N_("Video") },
         { "analog-output",              N_("Analog Output") },
         { "analog-output-headphones",   N_("Headphones") },
+        { "analog-output-headphones-mono",    N_("Headphones Mono Output") },
         { "analog-output-lfe-on-mono",  N_("LFE on Separate Mono Output") },
         { "analog-output-lineout",      N_("Line Out") },
         { "analog-output-mono",         N_("Analog Mono Output") },
diff --git a/src/modules/alsa/mixer/paths/steelseries-arctis-input.conf b/src/modules/alsa/mixer/paths/steelseries-arctis-input.conf
new file mode 100644
index 00000000..f3115ba6
--- /dev/null
+++ b/src/modules/alsa/mixer/paths/steelseries-arctis-input.conf
@@ -0,0 +1,25 @@
+# This file is part of PulseAudio.
+#
+# 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, see <http://www.gnu.org/licenses/>.
+
+; Steelseries Arctis 7 USB headset microphone path.
+
+[General]
+description-key = analog-input-microphone-headset
+
+[Element Headset]
+volume = merge
+switch = mute
+override-map.1 = all
+override-map.2 = all-left,all-right
diff --git a/src/modules/alsa/mixer/paths/steelseries-arctis-output-mono.conf b/src/modules/alsa/mixer/paths/steelseries-arctis-output-mono.conf
new file mode 100644
index 00000000..67950618
--- /dev/null
+++ b/src/modules/alsa/mixer/paths/steelseries-arctis-output-mono.conf
@@ -0,0 +1,29 @@
+# This file is part of PulseAudio.
+#
+# 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, see <http://www.gnu.org/licenses/>.
+
+; Steelseries Arctis 7 USB headset mono output path. The headset has two
+; output devices. The first one is mono, meant for voice audio, and the
+; second one is stereo, meant for everything else. The purpose of this
+; unusual design is to provide separate volume controls for voice and
+; other audio, which can be useful in gaming.
+
+[General]
+description-key = analog-output-headphones-mono
+
+[Element PCM]
+volume = merge
+switch = mute
+override-map.1 = all
+override-map.2 = all-left,all-right
diff --git a/src/modules/alsa/mixer/paths/steelseries-arctis-output-stereo.conf b/src/modules/alsa/mixer/paths/steelseries-arctis-output-stereo.conf
new file mode 100644
index 00000000..4e10c800
--- /dev/null
+++ b/src/modules/alsa/mixer/paths/steelseries-arctis-output-stereo.conf
@@ -0,0 +1,27 @@
+# This file is part of PulseAudio.
+#
+# 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, see <http://www.gnu.org/licenses/>.
+
+; Steelseries Arctis 7 USB headset stereo output path. The headset has two
+; output devices. The first one is mono, meant for voice audio, and the
+; second one is stereo, meant for everything else. The purpose of this
+; unusual design is to provide separate volume controls for voice and
+; other audio, which can be useful in gaming.
+;
+; This path doesn't provide hardware volume control, because the stereo
+; output is controlled by the PCM element with index 1, and currently
+; PulseAudio only supports elements with index 0.
+
+[General]
+description-key = analog-output-headphones
diff --git a/src/modules/alsa/mixer/profile-sets/90-pulseaudio.rules b/src/modules/alsa/mixer/profile-sets/90-pulseaudio.rules
index 805a05b2..2392ca50 100644
--- a/src/modules/alsa/mixer/profile-sets/90-pulseaudio.rules
+++ b/src/modules/alsa/mixer/profile-sets/90-pulseaudio.rules
@@ -99,5 +99,6 @@ ATTRS{idVendor}=="0763", ATTRS{idProduct}=="2012", ENV{PULSE_PROFILE_SET}="maudi
 ATTRS{idVendor}=="045e", ATTRS{idProduct}=="02bb", ENV{PULSE_PROFILE_SET}="kinect-audio.conf"
 ATTRS{idVendor}=="041e", ATTRS{idProduct}=="322c", ENV{PULSE_PROFILE_SET}="sb-omni-surround-5.1.conf"
 ATTRS{idVendor}=="0bda", ATTRS{idProduct}=="4014", ENV{PULSE_PROFILE_SET}="dell-dock-tb16-usb-audio.conf"
+ATTRS{idVendor}=="1038", ATTRS{idProduct}=="1260", ENV{PULSE_PROFILE_SET}="steelseries-arctis-usb-audio.conf"
 
 LABEL="pulseaudio_end"
diff --git a/src/modules/alsa/mixer/profile-sets/steelseries-arctis-usb-audio.conf b/src/modules/alsa/mixer/profile-sets/steelseries-arctis-usb-audio.conf
new file mode 100644
index 00000000..d3563a16
--- /dev/null
+++ b/src/modules/alsa/mixer/profile-sets/steelseries-arctis-usb-audio.conf
@@ -0,0 +1,43 @@
+# This file is part of PulseAudio.
+#
+# 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, see <http://www.gnu.org/licenses/>.
+
+; Steelseries Arctis 7 USB headset. The headset has a microphone and two output
+; devices. The first output device is mono, meant for voice audio, and the
+; second one is stereo, meant for everything else. The purpose of this unusual
+; design is to provide separate volume controls for voice and other audio,
+; which can be useful in gaming.
+;
+; See default.conf for an explanation on the directives used here.
+
+[General]
+auto-profiles = yes
+
+[Mapping analog-mono]
+device-strings = hw:%f,0,0
+channel-map = mono
+paths-output = steelseries-arctis-output-mono
+paths-input = steelseries-arctis-input
+
+[Mapping analog-stereo]
+device-strings = hw:%f,1,0
+channel-map = left,right
+paths-output = steelseries-arctis-output-stereo
+direction = output
+
+[Profile output:analog-mono+output:analog-stereo+input:analog-mono]
+output-mappings = analog-mono analog-stereo
+input-mappings = analog-mono
+priority = 5100
+skip-probe = yes

commit 739a4b3d2318f05eb7101c2baa861e5c636125a5
Author: Ian Ray <ian.ray at ge.com>
Date:   Wed Aug 30 11:09:48 2017 +0300

    alsa-mixer: round, not truncate, in to_alsa_dB
    
    to_alsa_dB() returns a result rounded to two decimal places (instead of
    using integer truncation) to avoid small errors when converting between
    dB and volume.
    
    Consider playback at -22 dB (which is supported by ALSA) but results in
    the higher level of -21 dB plus software attenuation.
    
        pa_sw_volume_from_dB(-22) = 28172
        pa_sw_volume_to_dB(28172) = -21.9997351
        to_alsa_dB(-21.9997351)   = -2199
    
        ALSA value 106 = -2200
        ALSA value 107 = -2100
        ...
    
        rounding = +1  /* "accurate or first above" */
        snd_mixer_selem_ask_playback_dB_vol(me, -2199, rounding, &alsa_val)
        alsa_val = -2100
    
    Signed-off-by: Ian Ray <ian.ray at ge.com>

diff --git a/src/modules/alsa/alsa-mixer.c b/src/modules/alsa/alsa-mixer.c
index f59cad39..aeaf12c4 100644
--- a/src/modules/alsa/alsa-mixer.c
+++ b/src/modules/alsa/alsa-mixer.c
@@ -700,7 +700,7 @@ void pa_alsa_path_set_free(pa_alsa_path_set *ps) {
 }
 
 static long to_alsa_dB(pa_volume_t v) {
-    return (long) (pa_sw_volume_to_dB(v) * 100.0);
+    return lround(pa_sw_volume_to_dB(v) * 100.0);
 }
 
 static pa_volume_t from_alsa_dB(long v) {

commit f0dfddead3cf1ff7af4c9c09a8027fde26065003
Author: Colin Leroy <colin at colino.net>
Date:   Sat Aug 26 11:21:15 2017 +0200

    cli-command: don't exit on "module already loaded" errors
    
    Some modules may only be loaded once, and trying to load them
    twice from default.pa makes PulseAudio startup fail. While that could
    be considered a user error, it's nicer to not be so strict. It's not
    necessarily easy to figure what went wrong, if for example the user
    plays with RAOP and adds module-raop-discover to default.pa, which first
    works fine, but suddenly stops working when the user at some point
    enables RAOP support in paprefs. Enabling RAOP in paprefs makes
    module-gconf load the module too, so the module gets loaded twice.
    
    This patch adds a way to differentiate module load errors, and
    make cli-command ignore the error when the module is already
    loaded.

diff --git a/src/daemon/main.c b/src/daemon/main.c
index 9d99b8fe..55af4eca 100644
--- a/src/daemon/main.c
+++ b/src/daemon/main.c
@@ -111,19 +111,21 @@ int deny_severity = LOG_WARNING;
 int __padsp_disabled__ = 7;
 #endif
 
-static void signal_callback(pa_mainloop_api*m, pa_signal_event *e, int sig, void *userdata) {
+static void signal_callback(pa_mainloop_api* m, pa_signal_event *e, int sig, void *userdata) {
+    pa_module *module = NULL;
+
     pa_log_info("Got signal %s.", pa_sig2str(sig));
 
     switch (sig) {
 #ifdef SIGUSR1
         case SIGUSR1:
-            pa_module_load(userdata, "module-cli", NULL);
+            pa_module_load(&module, userdata, "module-cli", NULL);
             break;
 #endif
 
 #ifdef SIGUSR2
         case SIGUSR2:
-            pa_module_load(userdata, "module-cli-protocol-unix", NULL);
+            pa_module_load(&module, userdata, "module-cli-protocol-unix", NULL);
             break;
 #endif
 
diff --git a/src/modules/bluetooth/module-bluetooth-discover.c b/src/modules/bluetooth/module-bluetooth-discover.c
index d69a77f1..509db443 100644
--- a/src/modules/bluetooth/module-bluetooth-discover.c
+++ b/src/modules/bluetooth/module-bluetooth-discover.c
@@ -52,13 +52,13 @@ int pa__init(pa_module* m) {
     u->bluez4_module_idx = PA_INVALID_INDEX;
 
     if (pa_module_exists("module-bluez5-discover")) {
-        mm = pa_module_load(m->core, "module-bluez5-discover", m->argument);
+        pa_module_load(&mm, m->core, "module-bluez5-discover", m->argument);
         if (mm)
             u->bluez5_module_idx = mm->index;
     }
 
     if (pa_module_exists("module-bluez4-discover")) {
-        mm = pa_module_load(m->core, "module-bluez4-discover",  NULL);
+        pa_module_load(&mm, m->core, "module-bluez4-discover",  NULL);
         if (mm)
             u->bluez4_module_idx = mm->index;
     }
diff --git a/src/modules/bluetooth/module-bluetooth-policy.c b/src/modules/bluetooth/module-bluetooth-policy.c
index 316b9a82..24930450 100644
--- a/src/modules/bluetooth/module-bluetooth-policy.c
+++ b/src/modules/bluetooth/module-bluetooth-policy.c
@@ -71,6 +71,7 @@ static pa_hook_result_t source_put_hook_callback(pa_core *c, pa_source *source,
     const char *s;
     const char *role;
     char *args;
+    pa_module *m = NULL;
 
     pa_assert(c);
     pa_assert(source);
@@ -100,7 +101,7 @@ static pa_hook_result_t source_put_hook_callback(pa_core *c, pa_source *source,
     /* Load module-loopback */
     args = pa_sprintf_malloc("source=\"%s\" source_dont_move=\"true\" sink_input_properties=\"media.role=%s\"", source->name,
                              role);
-    (void) pa_module_load(c, "module-loopback", args);
+    (void) pa_module_load(&m, c, "module-loopback", args);
     pa_xfree(args);
 
     return PA_HOOK_OK;
@@ -112,6 +113,7 @@ static pa_hook_result_t sink_put_hook_callback(pa_core *c, pa_sink *sink, void *
     const char *s;
     const char *role;
     char *args;
+    pa_module *m = NULL;
 
     pa_assert(c);
     pa_assert(sink);
@@ -139,7 +141,7 @@ static pa_hook_result_t sink_put_hook_callback(pa_core *c, pa_sink *sink, void *
     /* Load module-loopback */
     args = pa_sprintf_malloc("sink=\"%s\" sink_dont_move=\"true\" source_output_properties=\"media.role=%s\"", sink->name,
                              role);
-    (void) pa_module_load(c, "module-loopback", args);
+    (void) pa_module_load(&m, c, "module-loopback", args);
     pa_xfree(args);
 
     return PA_HOOK_OK;
diff --git a/src/modules/bluetooth/module-bluez4-discover.c b/src/modules/bluetooth/module-bluez4-discover.c
index cac75106..a4eaee3c 100644
--- a/src/modules/bluetooth/module-bluez4-discover.c
+++ b/src/modules/bluetooth/module-bluez4-discover.c
@@ -93,7 +93,7 @@ static pa_hook_result_t load_module_for_device(pa_bluez4_discovery *y, const pa_
             }
 
             pa_log_debug("Loading module-bluez4-device %s", args);
-            m = pa_module_load(u->module->core, "module-bluez4-device", args);
+            pa_module_load(&m, u->module->core, "module-bluez4-device", args);
             pa_xfree(args);
 
             if (m) {
diff --git a/src/modules/bluetooth/module-bluez5-discover.c b/src/modules/bluetooth/module-bluez5-discover.c
index 97ff9435..6dbf24ea 100644
--- a/src/modules/bluetooth/module-bluez5-discover.c
+++ b/src/modules/bluetooth/module-bluez5-discover.c
@@ -76,7 +76,7 @@ static pa_hook_result_t device_connection_changed_cb(pa_bluetooth_discovery *y,
         char *args = pa_sprintf_malloc("path=%s autodetect_mtu=%i", d->path, (int)u->autodetect_mtu);
 
         pa_log_debug("Loading module-bluez5-device %s", args);
-        m = pa_module_load(u->module->core, "module-bluez5-device", args);
+        pa_module_load(&m, u->module->core, "module-bluez5-device", args);
         pa_xfree(args);
 
         if (m)
diff --git a/src/modules/dbus/iface-core.c b/src/modules/dbus/iface-core.c
index 7177455b..5229c046 100644
--- a/src/modules/dbus/iface-core.c
+++ b/src/modules/dbus/iface-core.c
@@ -1506,7 +1506,7 @@ static void handle_load_module(DBusConnection *conn, DBusMessage *msg, void *use
 
     arg_string = pa_strbuf_to_string(arg_buffer);
 
-    if (!(module = pa_module_load(c->core, name, arg_string))) {
+    if (pa_module_load(&module, c->core, name, arg_string) < 0) {
         pa_dbus_send_error(conn, msg, DBUS_ERROR_FAILED, "Failed to load module.");
         goto finish;
     }
diff --git a/src/modules/gconf/module-gconf.c b/src/modules/gconf/module-gconf.c
index 1e1b8555..7a2b975c 100644
--- a/src/modules/gconf/module-gconf.c
+++ b/src/modules/gconf/module-gconf.c
@@ -190,7 +190,7 @@ static void load_module(
     m->items[i].args = pa_xstrdup(args);
     m->items[i].index = PA_INVALID_INDEX;
 
-    if (!(mod = pa_module_load(u->core, name, args))) {
+    if (pa_module_load(&mod, u->core, name, args) < 0) {
         pa_log("pa_module_load() failed");
         return;
     }
diff --git a/src/modules/jack/module-jackdbus-detect.c b/src/modules/jack/module-jackdbus-detect.c
index 26910b43..20db2184 100644
--- a/src/modules/jack/module-jackdbus-detect.c
+++ b/src/modules/jack/module-jackdbus-detect.c
@@ -111,7 +111,7 @@ static void ensure_ports_started(struct userdata* u) {
             } else {
                 args = pa_sprintf_malloc("connect=%s", pa_yes_no(u->autoconnect_ports));
             }
-            m = pa_module_load(u->core, modnames[i], args);
+            pa_module_load(&m, u->core, modnames[i], args);
             pa_xfree(args);
 
             if (m) {
diff --git a/src/modules/macosx/module-coreaudio-detect.c b/src/modules/macosx/module-coreaudio-detect.c
index d9c09da5..81067ce7 100644
--- a/src/modules/macosx/module-coreaudio-detect.c
+++ b/src/modules/macosx/module-coreaudio-detect.c
@@ -92,7 +92,7 @@ static int ca_device_added(struct pa_module *m, AudioObjectID id) {
         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_module_load(&mod, m->core, DEVICE_MODULE_NAME, args);
     pa_xfree(args);
 
     if (!mod) {
diff --git a/src/modules/module-allow-passthrough.c b/src/modules/module-allow-passthrough.c
index 31ff2707..63b621fb 100644
--- a/src/modules/module-allow-passthrough.c
+++ b/src/modules/module-allow-passthrough.c
@@ -71,7 +71,7 @@ static pa_sink *ensure_null_sink_for_sink(struct userdata *u, pa_sink *s, pa_cor
 
     t = pa_sprintf_malloc("sink_name=allow_passthrough_null_%s sink_properties='device.description=\"%s\"'",
                           name ? name : "", _("Dummy Output"));
-    m = pa_module_load(c, "module-null-sink", t);
+    pa_module_load(&m, c, "module-null-sink", t);
     pa_xfree(t);
 
     if (m == NULL)
diff --git a/src/modules/module-always-sink.c b/src/modules/module-always-sink.c
index 12f63f75..246cb94c 100644
--- a/src/modules/module-always-sink.c
+++ b/src/modules/module-always-sink.c
@@ -80,7 +80,7 @@ static void load_null_sink_if_needed(pa_core *c, pa_sink *sink, struct userdata*
 
     t = pa_sprintf_malloc("sink_name=%s sink_properties='device.description=\"%s\"'", u->sink_name,
                           _("Dummy Output"));
-    m = pa_module_load(c, "module-null-sink", t);
+    pa_module_load(&m, c, "module-null-sink", t);
     u->null_module = m ? m->index : PA_INVALID_INDEX;
     pa_xfree(t);
 
diff --git a/src/modules/module-combine.c b/src/modules/module-combine.c
index 5a12a135..0c56071b 100644
--- a/src/modules/module-combine.c
+++ b/src/modules/module-combine.c
@@ -48,7 +48,7 @@ int pa__init(pa_module*m) {
 
     pa_log_warn("We will now load module-combine-sink. Please make sure to remove module-combine from your configuration.");
 
-    module = pa_module_load(m->core, "module-combine-sink", m->argument);
+    pa_module_load(&module, m->core, "module-combine-sink", m->argument);
     u->module_index = module ? module->index : PA_INVALID_INDEX;
 
     return module ? 0 : -1;
diff --git a/src/modules/module-detect.c b/src/modules/module-detect.c
index d6c6b76d..91951640 100644
--- a/src/modules/module-detect.c
+++ b/src/modules/module-detect.c
@@ -74,6 +74,7 @@ static int detect_alsa(pa_core *c, int just_one) {
         char line[64], args[64];
         unsigned device, subdevice;
         int is_sink;
+        pa_module *m = NULL;
 
         if (!fgets(line, sizeof(line), f))
             break;
@@ -101,7 +102,7 @@ static int detect_alsa(pa_core *c, int just_one) {
             continue;
 
         pa_snprintf(args, sizeof(args), "device_id=%u", device);
-        if (!pa_module_load(c, is_sink ? "module-alsa-sink" : "module-alsa-source", args))
+        if (pa_module_load(&m, c, is_sink ? "module-alsa-sink" : "module-alsa-source", args) < 0)
             continue;
 
         n++;
@@ -136,6 +137,7 @@ static int detect_oss(pa_core *c, int just_one) {
     while (!feof(f)) {
         char line[256], args[64];
         unsigned device;
+        pa_module *m = NULL;
 
         if (!fgets(line, sizeof(line), f))
             break;
@@ -156,14 +158,14 @@ static int detect_oss(pa_core *c, int just_one) {
             else
                 pa_snprintf(args, sizeof(args), "device=/dev/dsp%u", device);
 
-            if (!pa_module_load(c, "module-oss", args))
+            if (pa_module_load(&m, c, "module-oss", args) < 0)
                 continue;
 
         } else if (sscanf(line, "pcm%u: ", &device) == 1) {
             /* FreeBSD support, the devices are named /dev/dsp0.0, dsp0.1 and so on */
             pa_snprintf(args, sizeof(args), "device=/dev/dsp%u.0", device);
 
-            if (!pa_module_load(c, "module-oss", args))
+            if (pa_module_load(&m, c, "module-oss", args) < 0)
                 continue;
         }
 
@@ -183,6 +185,7 @@ static int detect_solaris(pa_core *c, int just_one) {
     struct stat s;
     const char *dev;
     char args[64];
+    pa_module *m = NULL;
 
     dev = getenv("AUDIODEV");
     if (!dev)
@@ -199,7 +202,7 @@ static int detect_solaris(pa_core *c, int just_one) {
 
     pa_snprintf(args, sizeof(args), "device=%s", dev);
 
-    if (!pa_module_load(c, "module-solaris", args))
+    if (pa_module_load(&m, c, "module-solaris", args) < 0)
         return 0;
 
     return 1;
@@ -208,11 +211,12 @@ static int detect_solaris(pa_core *c, int just_one) {
 
 #ifdef OS_IS_WIN32
 static int detect_waveout(pa_core *c, int just_one) {
+    pa_module *m = NULL;
     /*
      * FIXME: No point in enumerating devices until the plugin supports
      * selecting anything but the first.
      */
-    if (!pa_module_load(c, "module-waveout", ""))
+    if (pa_module_load(&m, c, "module-waveout", "") < 0)
         return 0;
 
     return 1;
diff --git a/src/modules/module-filter-apply.c b/src/modules/module-filter-apply.c
index 6ca062ba..6d4475dd 100644
--- a/src/modules/module-filter-apply.c
+++ b/src/modules/module-filter-apply.c
@@ -574,7 +574,7 @@ static pa_hook_result_t process(struct userdata *u, pa_object *o, bool is_sink_i
 
             pa_log_debug("Loading %s with arguments '%s'", module_name, args);
 
-            if ((m = pa_module_load(u->core, module_name, args))) {
+            if (pa_module_load(&m, u->core, module_name, args) >= 0) {
                 find_filters_for_module(u, m, want, parameters);
                 filter = pa_hashmap_get(u->filters, fltr);
                 done_something = true;
diff --git a/src/modules/module-hal-detect-compat.c b/src/modules/module-hal-detect-compat.c
index 719e357c..e326340c 100644
--- a/src/modules/module-hal-detect-compat.c
+++ b/src/modules/module-hal-detect-compat.c
@@ -64,7 +64,7 @@ int pa__init(pa_module*m) {
     pa_log_warn("We will now load module-udev-detect. Please make sure to remove module-hal-detect from your configuration.");
 
     t = pa_sprintf_malloc("tsched=%s", pa_yes_no(tsched));
-    n = pa_module_load(m->core, "module-udev-detect", t);
+    pa_module_load(&n, m->core, "module-udev-detect", t);
     pa_xfree(t);
 
     if (n)
diff --git a/src/modules/module-udev-detect.c b/src/modules/module-udev-detect.c
index 3d7064f1..6ea6034a 100644
--- a/src/modules/module-udev-detect.c
+++ b/src/modules/module-udev-detect.c
@@ -332,7 +332,7 @@ static void verify_access(struct userdata *u, struct device *d) {
 
                 if (pa_ratelimit_test(&d->ratelimit, PA_LOG_DEBUG)) {
                     pa_log_debug("Loading module-alsa-card with arguments '%s'", d->args);
-                    m = pa_module_load(u->core, "module-alsa-card", d->args);
+                    pa_module_load(&m, u->core, "module-alsa-card", d->args);
 
                     if (m) {
                         d->module = m->index;
diff --git a/src/modules/module-volume-restore.c b/src/modules/module-volume-restore.c
index e7dbe94c..e2b95cad 100644
--- a/src/modules/module-volume-restore.c
+++ b/src/modules/module-volume-restore.c
@@ -65,7 +65,7 @@ int pa__init(pa_module*m) {
     pa_log_warn("We will now load module-stream-restore. Please make sure to remove module-volume-restore from your configuration.");
 
     t = pa_sprintf_malloc("restore_volume=%s restore_device=%s", pa_yes_no(restore_volume), pa_yes_no(restore_device));
-    n = pa_module_load(m->core, "module-stream-restore", t);
+    pa_module_load(&n, m->core, "module-stream-restore", t);
     pa_xfree(t);
 
     if (n)
diff --git a/src/modules/module-zeroconf-discover.c b/src/modules/module-zeroconf-discover.c
index 96476b7a..a170380c 100644
--- a/src/modules/module-zeroconf-discover.c
+++ b/src/modules/module-zeroconf-discover.c
@@ -242,7 +242,7 @@ static void resolver_cb(
 
         pa_log_debug("Loading %s with arguments '%s'", module_name, args);
 
-        if ((m = pa_module_load(u->core, module_name, args))) {
+        if (pa_module_load(&m, u->core, module_name, args) >= 0) {
             tnl->module_index = m->index;
             pa_hashmap_put(u->tunnels, tnl, tnl);
             tnl = NULL;
diff --git a/src/modules/raop/module-raop-discover.c b/src/modules/raop/module-raop-discover.c
index 9c7ac3cd..d65615b2 100644
--- a/src/modules/raop/module-raop-discover.c
+++ b/src/modules/raop/module-raop-discover.c
@@ -310,7 +310,7 @@ static void resolver_cb(
 
     pa_log_debug("Loading module-raop-sink with arguments '%s'", args);
 
-    if ((m = pa_module_load(u->core, "module-raop-sink", args))) {
+    if (pa_module_load(&m, u->core, "module-raop-sink", args) >= 0) {
         tnl->module_index = m->index;
         pa_hashmap_put(u->tunnels, tnl, tnl);
         tnl = NULL;
diff --git a/src/pulsecore/cli-command.c b/src/pulsecore/cli-command.c
index 0d56ba94..44795b0d 100644
--- a/src/pulsecore/cli-command.c
+++ b/src/pulsecore/cli-command.c
@@ -421,6 +421,8 @@ static int pa_cli_command_info(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool
 
 static int pa_cli_command_load(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool *fail) {
     const char *name;
+    pa_error_code_t err;
+    pa_module *m = NULL;
 
     pa_core_assert_ref(c);
     pa_assert(t);
@@ -432,9 +434,13 @@ static int pa_cli_command_load(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bool
         return -1;
     }
 
-    if (!pa_module_load(c, name,  pa_tokenizer_get(t, 2))) {
-        pa_strbuf_puts(buf, "Module load failed.\n");
-        return -1;
+    if ((err = pa_module_load(&m, c, name,  pa_tokenizer_get(t, 2))) < 0) {
+        if (err == PA_ERR_EXIST) {
+            pa_strbuf_puts(buf, "Module already loaded; ignoring.\n");
+        } else {
+            pa_strbuf_puts(buf, "Module load failed.\n");
+            return -1;
+        }
     }
 
     return 0;
diff --git a/src/pulsecore/module.c b/src/pulsecore/module.c
index ac158159..0478b6fb 100644
--- a/src/pulsecore/module.c
+++ b/src/pulsecore/module.c
@@ -107,17 +107,21 @@ void pa_module_hook_connect(pa_module *m, pa_hook *hook, pa_hook_priority_t prio
     pa_dynarray_append(m->hooks, pa_hook_connect(hook, prio, cb, data));
 }
 
-pa_module* pa_module_load(pa_core *c, const char *name, const char *argument) {
+int pa_module_load(pa_module** module, pa_core *c, const char *name, const char *argument) {
     pa_module *m = NULL;
     bool (*load_once)(void);
     const char* (*get_deprecated)(void);
     pa_modinfo *mi;
+    int errcode;
 
+    pa_assert(module);
     pa_assert(c);
     pa_assert(name);
 
-    if (c->disallow_module_loading)
+    if (c->disallow_module_loading) {
+        errcode = -PA_ERR_ACCESS;
         goto fail;
+    }
 
     m = pa_xnew(pa_module, 1);
     m->name = pa_xstrdup(name);
@@ -135,6 +139,7 @@ pa_module* pa_module_load(pa_core *c, const char *name, const char *argument) {
          * loader, which never finds anything, and therefore says "file not
          * found". */
         pa_log("Failed to open module \"%s\".", name);
+        errcode = -PA_ERR_IO;
         goto fail;
     }
 
@@ -150,6 +155,7 @@ pa_module* pa_module_load(pa_core *c, const char *name, const char *argument) {
             PA_IDXSET_FOREACH(i, c->modules, idx) {
                 if (pa_streq(name, i->name)) {
                     pa_log("Module \"%s\" should be loaded once at most. Refusing to load.", name);
+                    errcode = -PA_ERR_EXIST;
                     goto fail;
                 }
             }
@@ -165,6 +171,7 @@ pa_module* pa_module_load(pa_core *c, const char *name, const char *argument) {
 
     if (!(m->init = (int (*)(pa_module*_m)) pa_load_sym(m->dl, name, PA_SYMBOL_INIT))) {
         pa_log("Failed to load module \"%s\": symbol \""PA_SYMBOL_INIT"\" not found.", name);
+        errcode = -PA_ERR_IO;
         goto fail;
     }
 
@@ -179,6 +186,7 @@ pa_module* pa_module_load(pa_core *c, const char *name, const char *argument) {
 
     if (m->init(m) < 0) {
         pa_log_error("Failed to load module \"%s\" (argument: \"%s\"): initialization failed.", name, argument ? argument : "");
+        errcode = -PA_ERR_IO;
         goto fail;
     }
 
@@ -202,7 +210,9 @@ pa_module* pa_module_load(pa_core *c, const char *name, const char *argument) {
 
     pa_hook_fire(&m->core->hooks[PA_CORE_HOOK_MODULE_NEW], m);
 
-    return m;
+    *module = m;
+
+    return 0;
 
 fail:
 
@@ -225,7 +235,9 @@ fail:
         pa_xfree(m);
     }
 
-    return NULL;
+    *module = NULL;
+
+    return errcode;
 }
 
 static void postponed_dlclose(pa_mainloop_api *api, void *userdata) {
diff --git a/src/pulsecore/module.h b/src/pulsecore/module.h
index 41e2189c..ec2de0b9 100644
--- a/src/pulsecore/module.h
+++ b/src/pulsecore/module.h
@@ -52,7 +52,7 @@ struct pa_module {
 
 bool pa_module_exists(const char *name);
 
-pa_module* pa_module_load(pa_core *c, const char *name, const char *argument);
+int pa_module_load(pa_module** m, pa_core *c, const char *name, const char *argument);
 
 void pa_module_unload(pa_module *m, bool force);
 void pa_module_unload_by_index(pa_core *c, uint32_t idx, bool force);
diff --git a/src/pulsecore/protocol-native.c b/src/pulsecore/protocol-native.c
index 866e2c64..6ff5ed40 100644
--- a/src/pulsecore/protocol-native.c
+++ b/src/pulsecore/protocol-native.c
@@ -4468,7 +4468,7 @@ static void command_load_module(pa_pdispatch *pd, uint32_t command, uint32_t tag
     CHECK_VALIDITY(c->pstream, name && *name && pa_utf8_valid(name) && !strchr(name, '/'), tag, PA_ERR_INVALID);
     CHECK_VALIDITY(c->pstream, !argument || pa_utf8_valid(argument), tag, PA_ERR_INVALID);
 
-    if (!(m = pa_module_load(c->protocol->core, name, argument))) {
+    if (pa_module_load(&m, c->protocol->core, name, argument) < 0) {
         pa_pstream_send_error(c->pstream, tag, PA_ERR_MODINITFAILED);
         return;
     }

commit 1a66715320a254ed5d45d4c555d9da55ea3f8682
Author: Tanu Kaskinen <tanuk at iki.fi>
Date:   Thu Aug 17 20:24:39 2017 +0300

    main: set umask to 077 instead of 022
    
    It was reported that PulseAudio weakens the umask to 022 if it's
    initially set to 077. That's not as big problem as it might seem,
    but it's still a problem. The umask affects the permissions of the state
    files, and those aren't readable by other users anyway in the per-user
    mode, because PulseAudio puts them in directories that aren't
    accessible to other users. In the system mode the state files will be
    readable by everyone, though, even by those users that don't otherwise
    have access to PulseAudio. The state files are slightly
    privacy-sensitive, because they contain e.g. history of applications
    that have used PulseAudio.
    
    I can't think of any use cases where access to the state files by other
    users would be necessary, either in the per-user mode or in the system
    mode, so let's use umask 077. This doesn't prevent access to any
    sockets in the system mode, because all directories that PulseAudio
    creates in the system mode will have permissions 755 regardless of the
    umask, and the sockets themselves always have permissions 777.
    
    BugLink: https://bugs.freedesktop.org/show_bug.cgi?id=102060

diff --git a/src/daemon/main.c b/src/daemon/main.c
index f35252d0..9d99b8fe 100644
--- a/src/daemon/main.c
+++ b/src/daemon/main.c
@@ -888,7 +888,7 @@ int main(int argc, char *argv[]) {
 
     pa_set_env_and_record("PULSE_INTERNAL", "1");
     pa_assert_se(chdir("/") == 0);
-    umask(0022);
+    umask(0077);
 
 #ifdef HAVE_SYS_RESOURCE_H
     set_all_rlimits(conf);

commit 95404ce3f398a6882a73c09264a44cc8a9e394ec
Author: Tanu Kaskinen <tanuk at iki.fi>
Date:   Fri Aug 18 10:07:27 2017 +0300

    cli-command: unload modules synchronously
    
    Users may want to change the parameters of some load-once modules in
    ~/.config/pulse/default.pa. That should be possible by including
    /etc/pulse/default.pa from the per-user configuration file, and then
    unloading a module and reloading it with different parameters. However,
    that doesn't work, because the unload-module command will not unload the
    module immediately, so the subsequent load-module command will fail when
    the module can be loaded only once.
    
    This patch makes the module unloading synchronous. "pacmd unload-module
    module-cli-protocol-unix" is something that might not like this change,
    since the command will unload the code that is processing the command,
    but I tested it and it works fine. When pa_module_unload() is called,
    that won't yet remove the module code from memory, the lt_dlclose() call
    is postponed until it's safe to remove the code from memory.
    
    BugLink: https://bugs.freedesktop.org/show_bug.cgi?id=102205

diff --git a/src/pulsecore/cli-command.c b/src/pulsecore/cli-command.c
index 01fea475..0d56ba94 100644
--- a/src/pulsecore/cli-command.c
+++ b/src/pulsecore/cli-command.c
@@ -462,13 +462,13 @@ static int pa_cli_command_unload(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, bo
             return -1;
         }
 
-        pa_module_unload_request(m, false);
+        pa_module_unload(m, false);
 
     } else {
         PA_IDXSET_FOREACH(m, c->modules, idx)
             if (pa_streq(i, m->name)) {
                 unloaded = true;
-                pa_module_unload_request(m, false);
+                pa_module_unload(m, false);
             }
 
         if (unloaded == false) {

commit 5f27c2ec2fb0477f213e6794113182eaee1c43ba
Author: Tanu Kaskinen <tanuk at iki.fi>
Date:   Mon Jul 31 09:42:31 2017 +0300

    device-port, switch-on-port-available: fix automatic profile changing when current profile is off
    
    module-switch-on-port-available didn't do anything when a port changes
    its status if the card didn't have any sinks or sources. This was to
    avoid bad things during card initialization, but the if condition also
    prevented any profile switches away from the "off" profile, because the
    card has no sinks or sources when the "off" profile is active.
    
    pa_card nowadays has the "linked" flag that
    module-switch-on-port-available could have checked instead, but since it
    doesn't make sense to emit port status change events before the card has
    been initialized, I added the check in pa_device_port_set_available()
    instead.
    
    BugLink: https://bugs.freedesktop.org/show_bug.cgi?id=101794

diff --git a/src/modules/module-switch-on-port-available.c b/src/modules/module-switch-on-port-available.c
index b9a0f3b3..4020987f 100644
--- a/src/modules/module-switch-on-port-available.c
+++ b/src/modules/module-switch-on-port-available.c
@@ -281,11 +281,6 @@ static pa_hook_result_t port_available_hook_callback(pa_core *c, pa_device_port
         return PA_HOOK_OK;
     }
 
-    if (pa_idxset_size(port->card->sinks) == 0 && pa_idxset_size(port->card->sources) == 0)
-        /* This card is not initialized yet. We'll handle it in
-           sink_new / source_new callbacks later. */
-        return PA_HOOK_OK;
-
     switch (port->available) {
     case PA_AVAILABLE_YES:
         switch_to_port(port);
diff --git a/src/pulsecore/device-port.c b/src/pulsecore/device-port.c
index 76a7e80a..5cf4ac63 100644
--- a/src/pulsecore/device-port.c
+++ b/src/pulsecore/device-port.c
@@ -92,7 +92,7 @@ void pa_device_port_set_available(pa_device_port *p, pa_available_t status) {
      * before the card object has been created. The card object should probably
      * be created before port objects, and then p->card could be non-NULL for
      * the whole lifecycle of pa_device_port. */
-    if (p->card) {
+    if (p->card && p->card->linked) {
         /* A sink or source whose active port is unavailable can't be the
          * default sink/source, so port availability changes may affect the
          * default sink/source choice. */

commit 4e6d9e321400cbb660b4373db6b50bea71707641
Author: Tanu Kaskinen <tanuk at iki.fi>
Date:   Fri Aug 4 16:43:02 2017 +0300

    build-sys: add iec958-stereo-input.conf to dist_alsapaths_DATA

diff --git a/src/Makefile.am b/src/Makefile.am
index 3ff1139f..1d974037 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1343,6 +1343,7 @@ dist_alsapaths_DATA = \
 		modules/alsa/mixer/paths/analog-output-headphones-2.conf \
 		modules/alsa/mixer/paths/analog-output-lineout.conf \
 		modules/alsa/mixer/paths/analog-output-mono.conf \
+		modules/alsa/mixer/paths/iec958-stereo-input.conf \
 		modules/alsa/mixer/paths/iec958-stereo-output.conf \
 		modules/alsa/mixer/paths/hdmi-output-0.conf \
 		modules/alsa/mixer/paths/hdmi-output-1.conf \

commit ec325304cdca5e2a46f5a84f93c8b614a204d87f
Author: Tanu Kaskinen <tanuk at iki.fi>
Date:   Fri Aug 4 11:14:43 2017 +0300

    alsa-mixer: set PCM Capture Source for iec958 input
    
    It was reported that on a certain USB card, identified as
    "0d8c:0102 C-Media Electronics, Inc. CM106 Like Sound Device",
    the "PCM Capture Source" element had to be set to "IEC958 In" before
    the iec958 input would work.
    
    The iec958-stereo-input.conf file didn't exist before, although the path
    was referenced in the default.conf profile configuration file.
    
    BugLink: https://bugs.freedesktop.org/show_bug.cgi?id=101973

diff --git a/src/modules/alsa/mixer/paths/iec958-stereo-input.conf b/src/modules/alsa/mixer/paths/iec958-stereo-input.conf
new file mode 100644
index 00000000..babc8398
--- /dev/null
+++ b/src/modules/alsa/mixer/paths/iec958-stereo-input.conf
@@ -0,0 +1,20 @@
+# This file is part of PulseAudio.
+#
+# 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, see <http://www.gnu.org/licenses/>.
+
+[Element PCM Capture Source]
+enumeration = select
+
+[Option PCM Capture Source:IEC958 In]
+name = iec958-input

commit 295d4db8cfeca8d257f73fa29821e4a662790268
Author: Tanu Kaskinen <tanuk at iki.fi>
Date:   Fri Jul 28 04:07:49 2017 +0300

    raop: silence a Coverity complaint
    
    CID: 1398155

diff --git a/src/modules/raop/raop-sink.c b/src/modules/raop/raop-sink.c
index e5d219e8..4d13927f 100644
--- a/src/modules/raop/raop-sink.c
+++ b/src/modules/raop/raop-sink.c
@@ -391,6 +391,13 @@ static void thread_func(void *userdata) {
         if (!pa_raop_client_can_stream(u->raop))
             continue;
 
+        /* This assertion is meant to silence a complaint from Coverity about
+         * pollfd being possibly NULL when we access it later. That's a false
+         * positive, because we check pa_raop_client_can_stream() above, and if
+         * that returns true, it means that the connection is up, and when the
+         * connection is up, pollfd will be non-NULL. */
+        pa_assert(pollfd);
+
         if (u->memchunk.length <= 0) {
             if (u->memchunk.memblock)
                 pa_memblock_unref(u->memchunk.memblock);

commit e03e01b0891debe691267dd74bff30c0a4938831
Author: Georg Chini <georg at chini.tk>
Date:   Sat Jul 22 10:47:07 2017 +0200

    bluez5-device: Set transport state correctly for AG role
    
    When connecting a headset via the native backend, the transport state was
    not updated correctly.
    
    This patch sets the state to PLAYING in transport_acquire() if necessary.

diff --git a/src/modules/bluetooth/module-bluez5-device.c b/src/modules/bluetooth/module-bluez5-device.c
index c0e681b1..7b5d313b 100644
--- a/src/modules/bluetooth/module-bluez5-device.c
+++ b/src/modules/bluetooth/module-bluez5-device.c
@@ -77,6 +77,7 @@ static const char* const valid_modargs[] = {
 enum {
     BLUETOOTH_MESSAGE_IO_THREAD_FAILED,
     BLUETOOTH_MESSAGE_STREAM_FD_HUP,
+    BLUETOOTH_MESSAGE_SET_TRANSPORT_PLAYING,
     BLUETOOTH_MESSAGE_MAX
 };
 
@@ -755,9 +756,18 @@ static int transport_acquire(struct userdata *u, bool optional) {
     if (u->stream_fd < 0)
         return u->stream_fd;
 
+    /* transport_acquired must be set before calling
+     * pa_bluetooth_transport_set_state() */
     u->transport_acquired = true;
     pa_log_info("Transport %s acquired: fd %d", u->transport->path, u->stream_fd);
 
+    if (u->transport->state == PA_BLUETOOTH_TRANSPORT_STATE_IDLE) {
+        if (pa_thread_mq_get() != NULL)
+            pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(u->msg), BLUETOOTH_MESSAGE_SET_TRANSPORT_PLAYING, NULL, 0, NULL, NULL);
+        else
+            pa_bluetooth_transport_set_state(u->transport, PA_BLUETOOTH_TRANSPORT_STATE_PLAYING);
+    }
+
     return 0;
 }
 
@@ -2217,6 +2227,16 @@ static int device_process_msg(pa_msgobject *obj, int code, void *data, int64_t o
             if (u->transport->state > PA_BLUETOOTH_TRANSPORT_STATE_IDLE)
                 pa_bluetooth_transport_set_state(u->transport, PA_BLUETOOTH_TRANSPORT_STATE_IDLE);
             break;
+        case BLUETOOTH_MESSAGE_SET_TRANSPORT_PLAYING:
+            /* transport_acquired needs to be checked here, because a message could have been
+             * pending when the profile was switched. If the new transport has been acquired
+             * correctly, the call below will have no effect because the transport state is
+             * already PLAYING. If transport_acquire() failed for the new profile, the transport
+             * state should not be changed. If the transport has been released for other reasons
+             * (I/O thread shutdown), transport_acquired will also be false. */
+            if (u->transport_acquired)
+                pa_bluetooth_transport_set_state(u->transport, PA_BLUETOOTH_TRANSPORT_STATE_PLAYING);
+            break;
     }
 
     return 0;

commit 9e725ac3dbe024e6a4f10575f8cf1b80e8223dbe
Author: Tanu Kaskinen <tanuk at iki.fi>
Date:   Wed Jul 12 22:29:56 2017 +0300

    equalizer-sink: update sink description when moving
    
    If the description is not updated when moving, the old automatically
    generated description will refer to the old master sink after the move,
    which is not nice.

diff --git a/src/modules/module-equalizer-sink.c b/src/modules/module-equalizer-sink.c
index 280ca25f..489062fc 100644
--- a/src/modules/module-equalizer-sink.c
+++ b/src/modules/module-equalizer-sink.c
@@ -127,6 +127,8 @@ struct userdata {
 
     pa_database *database;
     char **base_profiles;
+
+    bool automatic_description;
 };
 
 static const char* const valid_modargs[] = {
@@ -1080,6 +1082,17 @@ static void sink_input_moving_cb(pa_sink_input *i, pa_sink *dest) {
     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);
+
+        if (u->automatic_description) {
+            const char *master_description;
+            char *new_description;
+
+            master_description = pa_proplist_gets(dest->proplist, PA_PROP_DEVICE_DESCRIPTION);
+            new_description = pa_sprintf_malloc(_("FFT based equalizer on %s"),
+                                                master_description ? master_description : dest->name);
+            pa_sink_set_description(u->sink, new_description);
+            pa_xfree(new_description);
+        }
     } else
         pa_sink_set_asyncmsgq(u->sink, NULL);
 }
@@ -1089,7 +1102,6 @@ int pa__init(pa_module*m) {
     pa_sample_spec ss;
     pa_channel_map map;
     pa_modargs *ma;
-    const char *z;
     pa_sink *master;
     pa_sink_input_new_data sink_input_data;
     pa_sink_new_data sink_data;
@@ -1185,9 +1197,6 @@ int pa__init(pa_module*m) {
     pa_sink_new_data_set_sample_spec(&sink_data, &ss);
     pa_sink_new_data_set_channel_map(&sink_data, &map);
 
-    z = pa_proplist_gets(master->proplist, PA_PROP_DEVICE_DESCRIPTION);
-    pa_proplist_setf(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "FFT based equalizer on %s", z ? z : master->name);
-
     pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_MASTER_DEVICE, master->name);
     pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_CLASS, "filter");
 
@@ -1197,6 +1206,15 @@ int pa__init(pa_module*m) {
         goto fail;
     }
 
+    if (!pa_proplist_contains(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION)) {
+        const char *master_description;
+
+        master_description = pa_proplist_gets(master->proplist, PA_PROP_DEVICE_DESCRIPTION);
+        pa_proplist_setf(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION,
+                         _("FFT based equalizer on %s"), master_description ? master_description : master->name);
+        u->automatic_description = true;
+    }
+
     u->autoloaded = DEFAULT_AUTOLOADED;
     if (pa_modargs_get_value_boolean(ma, "autoloaded", &u->autoloaded) < 0) {
         pa_log("Failed to parse autoloaded value");

commit 1d29247ac7f58fe1a766fa7c0ea3856f6820e15e
Author: Georg Chini <georg at chini.tk>
Date:   Mon Apr 17 21:37:43 2017 +0200

    loopback: Use new allow_negative flag of pa_{source, sink}_get_latency_within_thread()
    
    Setting the allow_negative flag of pa_{source,sink}_get_latency_within_thread() to true
    leads to improved end to end latency estimation and to correct handling of negative port
    latency offsets.

diff --git a/src/modules/module-loopback.c b/src/modules/module-loopback.c
index ded59e1a..ab38c292 100644
--- a/src/modules/module-loopback.c
+++ b/src/modules/module-loopback.c
@@ -110,12 +110,12 @@ struct userdata {
     /* Used for sink input and source output snapshots */
     struct {
         int64_t send_counter;
-        pa_usec_t source_latency;
+        int64_t source_latency;
         pa_usec_t source_timestamp;
 
         int64_t recv_counter;
         size_t loopback_memblockq_length;
-        pa_usec_t sink_latency;
+        int64_t sink_latency;
         pa_usec_t sink_timestamp;
     } latency_snapshot;
 
@@ -125,9 +125,9 @@ struct userdata {
     /* Output thread variables */
     struct {
         int64_t recv_counter;
+        pa_usec_t effective_source_latency;
 
         /* Copied from main thread */
-        pa_usec_t effective_source_latency;
         pa_usec_t minimum_latency;
 
         /* Various booleans */
@@ -305,7 +305,8 @@ static void adjust_rates(struct userdata *u) {
     size_t buffer;
     uint32_t old_rate, base_rate, new_rate, run_hours;
     int32_t latency_difference;
-    pa_usec_t current_buffer_latency, snapshot_delay, current_source_sink_latency, current_latency, latency_at_optimum_rate;
+    pa_usec_t current_buffer_latency, snapshot_delay;
+    int64_t current_source_sink_latency, current_latency, latency_at_optimum_rate;
     pa_usec_t final_latency;
 
     pa_assert(u);
@@ -352,7 +353,7 @@ static void adjust_rates(struct userdata *u) {
     latency_at_optimum_rate = current_source_sink_latency + current_buffer_latency * old_rate / base_rate;
 
     final_latency = PA_MAX(u->latency, u->minimum_latency);
-    latency_difference = (int32_t)((int64_t)latency_at_optimum_rate - final_latency);
+    latency_difference = (int32_t)(latency_at_optimum_rate - final_latency);
 
     pa_log_debug("Loopback overall latency is %0.2f ms + %0.2f ms + %0.2f ms = %0.2f ms",
                 (double) u->latency_snapshot.sink_latency / PA_USEC_PER_MSEC,
@@ -466,12 +467,23 @@ static void update_latency_boundaries(struct userdata *u, pa_source *source, pa_
 
 /* Called from output context
  * Sets the memblockq to the configured latency corrected by latency_offset_usec */
-static void memblockq_adjust(struct userdata *u, pa_usec_t latency_offset_usec, bool allow_push) {
+static void memblockq_adjust(struct userdata *u, int64_t latency_offset_usec, bool allow_push) {
     size_t current_memblockq_length, requested_memblockq_length, buffer_correction;
-    pa_usec_t requested_buffer_latency, final_latency;
+    int64_t requested_buffer_latency;
+    pa_usec_t final_latency, requested_sink_latency;
 
     final_latency = PA_MAX(u->latency, u->output_thread_info.minimum_latency);
-    requested_buffer_latency = PA_CLIP_SUB(final_latency, latency_offset_usec);
+
+    /* If source or sink have some large negative latency offset, we might want to
+     * hold more than final_latency in the memblockq */
+    requested_buffer_latency = (int64_t)final_latency - latency_offset_usec;
+
+    /* Keep at least one sink latency in the queue to make sure that the sink
+     * never underruns initially */
+    requested_sink_latency = pa_sink_get_requested_latency_within_thread(u->sink_input->sink);
+    if (requested_buffer_latency < (int64_t)requested_sink_latency)
+        requested_buffer_latency = requested_sink_latency;
+
     requested_memblockq_length = pa_usec_to_bytes(requested_buffer_latency, &u->sink_input->sample_spec);
     current_memblockq_length = pa_memblockq_get_length(u->memblockq);
 
@@ -492,7 +504,8 @@ static void memblockq_adjust(struct userdata *u, pa_usec_t latency_offset_usec,
 /* Called from input thread context */
 static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk) {
     struct userdata *u;
-    pa_usec_t push_time, current_source_latency;
+    pa_usec_t push_time;
+    int64_t current_source_latency;
 
     pa_source_output_assert_ref(o);
     pa_source_output_assert_io_context(o);
@@ -500,9 +513,9 @@ static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk)
 
     /* Send current source latency and timestamp with the message */
     push_time = pa_rtclock_now();
-    current_source_latency = pa_source_get_latency_within_thread(u->source_output->source, false);
+    current_source_latency = pa_source_get_latency_within_thread(u->source_output->source, true);
 
-    pa_asyncmsgq_post(u->asyncmsgq, PA_MSGOBJECT(u->sink_input), SINK_INPUT_MESSAGE_POST, PA_UINT_TO_PTR(current_source_latency), push_time, chunk, NULL);
+    pa_asyncmsgq_post(u->asyncmsgq, PA_MSGOBJECT(u->sink_input), SINK_INPUT_MESSAGE_POST, PA_INT_TO_PTR(current_source_latency), push_time, chunk, NULL);
     u->send_counter += (int64_t) chunk->length;
 }
 
@@ -531,7 +544,7 @@ static int source_output_process_msg_cb(pa_msgobject *obj, int code, void *data,
 
             u->latency_snapshot.send_counter = u->send_counter;
             /* Add content of delay memblockq to the source latency */
-            u->latency_snapshot.source_latency = pa_source_get_latency_within_thread(u->source_output->source, false) +
+            u->latency_snapshot.source_latency = pa_source_get_latency_within_thread(u->source_output->source, true) +
                                                  pa_bytes_to_usec(length, &u->source_output->source->sample_spec);
             u->latency_snapshot.source_timestamp = pa_rtclock_now();
 
@@ -813,14 +826,14 @@ static int sink_input_process_msg_cb(pa_msgobject *obj, int code, void *data, in
              * are enabled. Disable them on first push and correct the memblockq. If pop
              * has not been called yet, wait until the pop_cb() requests the adjustment */
             if (u->output_thread_info.pop_called && (!u->output_thread_info.push_called || u->output_thread_info.pop_adjust)) {
-                pa_usec_t time_delta;
+                int64_t time_delta;
 
                 /* This is the source latency at the time push was called */
-                time_delta = PA_PTR_TO_UINT(data);
+                time_delta = PA_PTR_TO_INT(data);
                 /* Add the time between push and post */
                 time_delta += pa_rtclock_now() - (pa_usec_t) offset;
                 /* Add the sink latency */
-                time_delta += pa_sink_get_latency_within_thread(u->sink_input->sink, false);
+                time_delta += pa_sink_get_latency_within_thread(u->sink_input->sink, true);
 
                 /* The source latency report includes the audio in the chunk,
                  * but since we already pushed the chunk to the memblockq, we need
@@ -840,9 +853,9 @@ static int sink_input_process_msg_cb(pa_msgobject *obj, int code, void *data, in
                  * next push also contains too much data, and in that case the
                  * resulting latency will be wrong. */
                 if (pa_bytes_to_usec(chunk->length, &u->sink_input->sample_spec) > u->output_thread_info.effective_source_latency)
-                    time_delta = PA_CLIP_SUB(time_delta, u->output_thread_info.effective_source_latency);
+                    time_delta -= (int64_t)u->output_thread_info.effective_source_latency;
                 else
-                    time_delta = PA_CLIP_SUB(time_delta, pa_bytes_to_usec(chunk->length, &u->sink_input->sample_spec));
+                    time_delta -= (int64_t)pa_bytes_to_usec(chunk->length, &u->sink_input->sample_spec);
 
                 /* FIXME: We allow pushing silence here to fix up the latency. This
                  * might lead to a gap in the stream */
@@ -895,7 +908,7 @@ static int sink_input_process_msg_cb(pa_msgobject *obj, int code, void *data, in
             u->latency_snapshot.recv_counter = u->output_thread_info.recv_counter;
             u->latency_snapshot.loopback_memblockq_length = pa_memblockq_get_length(u->memblockq);
             /* Add content of render memblockq to sink latency */
-            u->latency_snapshot.sink_latency = pa_sink_get_latency_within_thread(u->sink_input->sink, false) +
+            u->latency_snapshot.sink_latency = pa_sink_get_latency_within_thread(u->sink_input->sink, true) +
                                                pa_bytes_to_usec(length, &u->sink_input->sink->sample_spec);
             u->latency_snapshot.sink_timestamp = pa_rtclock_now();
 

commit 60c0edd5286dbb731c671ad3e6886c1e3e1eb067
Author: Hui Wang <hui.wang at canonical.com>
Date:   Fri May 26 15:42:40 2017 +0800

    alsa-mixer: Add support for usb audio in the Dell dock TB16
    
    There are one headset jack on the front panel of TB16, through this
    jack, we have one stereo headphone output (hw:%f,0,0) and one mono
    headset-mic input (hw:%f,0,0); and there is one speaker output jack
    (hw:%f,1,0) on the rear panel of TB16.
    
    The detail information of the Dell dock TB16:
    http://www.dell.com/support/article/sg/en/sgbsdt1/SLN301105
    
    Signed-off-by: Hui Wang <hui.wang at canonical.com>

diff --git a/src/modules/alsa/mixer/profile-sets/90-pulseaudio.rules b/src/modules/alsa/mixer/profile-sets/90-pulseaudio.rules
index 70e34e6f..805a05b2 100644
--- a/src/modules/alsa/mixer/profile-sets/90-pulseaudio.rules
+++ b/src/modules/alsa/mixer/profile-sets/90-pulseaudio.rules
@@ -98,5 +98,6 @@ ATTRS{idVendor}=="17cc", ATTRS{idProduct}=="1021", ENV{PULSE_PROFILE_SET}="nativ
 ATTRS{idVendor}=="0763", ATTRS{idProduct}=="2012", ENV{PULSE_PROFILE_SET}="maudio-fasttrack-pro.conf"
 ATTRS{idVendor}=="045e", ATTRS{idProduct}=="02bb", ENV{PULSE_PROFILE_SET}="kinect-audio.conf"
 ATTRS{idVendor}=="041e", ATTRS{idProduct}=="322c", ENV{PULSE_PROFILE_SET}="sb-omni-surround-5.1.conf"
+ATTRS{idVendor}=="0bda", ATTRS{idProduct}=="4014", ENV{PULSE_PROFILE_SET}="dell-dock-tb16-usb-audio.conf"
 
 LABEL="pulseaudio_end"
diff --git a/src/modules/alsa/mixer/profile-sets/dell-dock-tb16-usb-audio.conf b/src/modules/alsa/mixer/profile-sets/dell-dock-tb16-usb-audio.conf
new file mode 100644
index 00000000..11865524
--- /dev/null
+++ b/src/modules/alsa/mixer/profile-sets/dell-dock-tb16-usb-audio.conf
@@ -0,0 +1,55 @@
+# This file is part of PulseAudio.
+#
+# 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, see <http://www.gnu.org/licenses/>.
+
+; Dell Dock TB16 USB audio
+;
+; This card has two stereo pairs of output, One Mono input.
+;
+; See default.conf for an explanation on the directives used here.
+
+[General]
+auto-profiles = no
+
+[Mapping analog-stereo-headphone]
+description = Headphone
+device-strings = hw:%f,0,0
+channel-map = left,right
+direction = output
+
+[Mapping analog-stereo-speaker]
+description = Speaker
+device-strings = hw:%f,1,0
+channel-map = left,right
+direction = output
+
+[Mapping analog-stereo-mic]
+description = Headset-Mic
+device-strings = hw:%f,0,0
+channel-map = left,right
+direction = input
+
+
+[Profile output:analog-stereo-speaker]
+description = Speaker
+output-mappings = analog-stereo-speaker
+priority = 60
+skip-probe = yes
+
+[Profile output:analog-stereo-headphone+input:analog-stereo-mic]
+description = Headset
+output-mappings = analog-stereo-headphone
+input-mappings = analog-stereo-mic
+priority = 80
+skip-probe = yes

commit 66885ad633db0f371693475c72133e91f1e09ee5
Author: Khem Raj <raj.khem at gmail.com>
Date:   Mon Apr 6 21:56:31 2015 -0700

    padsp: Make it compile on musl
    
    break assumptions on glibc and there is no stat64 on non
    glibc C libraries
    
    See pulseaudio bug
    
    https://bugs.freedesktop.org/show_bug.cgi?id=85319
    
    Signed-off-by: Khem Raj <raj.khem at gmail.com>

diff --git a/src/utils/padsp.c b/src/utils/padsp.c
index 251f2011..a53b161c 100644
--- a/src/utils/padsp.c
+++ b/src/utils/padsp.c
@@ -2394,7 +2394,7 @@ fail:
     return ret;
 }
 
-#ifdef sun
+#ifndef __GLIBC__
 int ioctl(int fd, int request, ...) {
 #else
 int ioctl(int fd, unsigned long request, ...) {
@@ -2534,10 +2534,13 @@ int stat(const char *pathname, struct stat *buf) {
 
     return 0;
 }
-
 #ifdef HAVE_OPEN64
-
+#undef stat64
+#ifdef __GLIBC__
 int stat64(const char *pathname, struct stat64 *buf) {
+#else
+int stat64(const char *pathname, struct stat *buf) {
+#endif
     struct stat oldbuf;
     int ret;
 
@@ -2570,7 +2573,7 @@ int stat64(const char *pathname, struct stat64 *buf) {
 
     return 0;
 }
-
+#undef open64
 int open64(const char *filename, int flags, ...) {
     va_list args;
     mode_t mode = 0;
@@ -2696,8 +2699,8 @@ FILE* fopen(const char *filename, const char *mode) {
 }
 
 #ifdef HAVE_OPEN64
-
-FILE *fopen64(const char *filename, const char *mode) {
+#undef fopen64
+FILE *fopen64(const char *__restrict filename, const char *__restrict mode) {
 
     debug(DEBUG_LEVEL_VERBOSE, __FILE__": fopen64(%s)\n", filename?filename:"NULL");
 

commit 46fb1b8c5dfeb9611ba9035ff7a09dbb620c54ea
Author: KimJeongYeon <jeongyeon.kim at samsung.com>
Date:   Sat Apr 29 11:32:23 2017 +0200

    filter-apply: Do not re-route stream to master sink/source when manually moved to filter
    
    Currently, if a stream is manually moved to a filter sink or source managed by
    module-filter-apply, the stream will be silently re-routed to the master sink
    or source, because the filter.apply property is not set on that stream. We can
    assume, that the users intention however was to have the stream filtered.
    
    Therefore this patch changes the logic, so that the stream will not be moved
    to the master but remains on the filter sink or source. To handle the change
    of a property correctly, the filter.apply property must be set temporarily.
    An additional property filter.apply.set_by_mfa was introduced to mark those
    streams, so that filter.apply can be removed again when the stream moves away
    from the filter.

diff --git a/src/modules/module-filter-apply.c b/src/modules/module-filter-apply.c
index e91fb37d..6ca062ba 100644
--- a/src/modules/module-filter-apply.c
+++ b/src/modules/module-filter-apply.c
@@ -39,6 +39,7 @@
 
 #define PA_PROP_FILTER_APPLY_PARAMETERS PA_PROP_FILTER_APPLY".%s.parameters"
 #define PA_PROP_FILTER_APPLY_MOVING     "filter.apply.moving"
+#define PA_PROP_FILTER_APPLY_SET_BY_MFA "filter.apply.set_by_mfa"
 #define PA_PROP_MDM_AUTO_FILTERED       "module-device-manager.auto_filtered"
 
 PA_MODULE_AUTHOR("Colin Guthrie");
@@ -161,6 +162,67 @@ static const char* get_filter_parameters(pa_object *o, const char *want, bool is
     return parameters;
 }
 
+/* This function is used to set or unset the filter related stream properties. This is necessary
+ * if a stream does not have filter.apply set and is manually moved to a filter sink or source.
+ * In this case, the properies must be temporarily set and removed when the stream is moved away
+ * from the filter. */
+static void set_filter_properties(pa_proplist *pl, struct filter *filter, bool set_properties) {
+    char *prop_parameters;
+
+    if (set_properties) {
+        pa_assert(filter);
+
+        pa_proplist_sets(pl, PA_PROP_FILTER_APPLY, filter->name);
+
+        if (filter->parameters) {
+            prop_parameters = pa_sprintf_malloc(PA_PROP_FILTER_APPLY_PARAMETERS, filter->name);
+            pa_proplist_sets(pl, prop_parameters, filter->parameters);
+            pa_xfree(prop_parameters);
+        }
+
+        pa_proplist_sets(pl, PA_PROP_FILTER_APPLY_SET_BY_MFA, "1");
+
+    } else {
+        const char *old_filter_name = NULL;
+
+        if (filter)
+            old_filter_name = filter->name;
+        else
+            old_filter_name = pa_proplist_gets(pl, PA_PROP_FILTER_APPLY);
+
+        /* If the filter name cannot be determined, properties cannot be removed. */
+        if (!old_filter_name)
+            return;
+
+        prop_parameters = pa_sprintf_malloc(PA_PROP_FILTER_APPLY_PARAMETERS, old_filter_name);
+        pa_proplist_unset(pl, prop_parameters);
+        pa_xfree(prop_parameters);
+
+        pa_proplist_unset(pl, PA_PROP_FILTER_APPLY);
+        pa_proplist_unset(pl, PA_PROP_FILTER_APPLY_SET_BY_MFA);
+    }
+}
+
+static struct filter* get_filter_for_object(struct userdata *u, pa_object *o, bool is_sink_input) {
+    pa_sink *sink = NULL;
+    pa_source *source = NULL;
+    struct filter *filter = NULL;
+    void *state;
+
+    if (is_sink_input)
+        sink = PA_SINK_INPUT(o)->sink;
+    else
+        source = PA_SOURCE_OUTPUT(o)->source;
+
+    PA_HASHMAP_FOREACH(filter, u->filters, state) {
+        if ((is_sink_input && sink == filter->sink) || (!is_sink_input && source == filter->source)) {
+            return filter;
+        }
+    }
+
+    return NULL;
+}
+
 static bool should_group_filter(struct filter *filter) {
     return pa_streq(filter->name, "echo-cancel");
 }
@@ -309,7 +371,7 @@ static int do_move(struct userdata *u, pa_object *obj, pa_object *parent, bool i
     }
 }
 
-static void move_object_for_filter(struct userdata *u, pa_object *o, struct filter* filter, bool restore, bool is_sink_input) {
+static void move_object_for_filter(struct userdata *u, pa_object *o, struct filter *filter, bool restore, bool is_sink_input) {
     pa_object *parent;
     pa_proplist *pl;
     const char *name;
@@ -343,7 +405,7 @@ static void move_object_for_filter(struct userdata *u, pa_object *o, struct filt
     pa_proplist_unset(pl, PA_PROP_FILTER_APPLY_MOVING);
 }
 
-static void move_objects_for_filter(struct userdata *u, pa_object *o, struct filter* filter, bool restore,
+static void move_objects_for_filter(struct userdata *u, pa_object *o, struct filter *filter, bool restore,
         bool is_sink_input) {
 
     if (!should_group_filter(filter))
@@ -431,7 +493,7 @@ static bool can_unload_module(struct userdata *u, uint32_t idx) {
     return true;
 }
 
-static pa_hook_result_t process(struct userdata *u, pa_object *o, bool is_sink_input) {
+static pa_hook_result_t process(struct userdata *u, pa_object *o, bool is_sink_input, bool is_property_change) {
     const char *want;
     const char *parameters;
     bool done_something = false;
@@ -440,24 +502,23 @@ static pa_hook_result_t process(struct userdata *u, pa_object *o, bool is_sink_i
     pa_module *module = NULL;
     char *module_name = NULL;
     struct filter *fltr = NULL, *filter = NULL;
+    pa_proplist *pl;
 
     if (is_sink_input) {
-        sink = PA_SINK_INPUT(o)->sink;
-
-        if (sink)
+        if ((sink = PA_SINK_INPUT(o)->sink))
             module = sink->module;
+        pl = PA_SINK_INPUT(o)->proplist;
     } else {
-        source = PA_SOURCE_OUTPUT(o)->source;
-
-        if (source)
+        if ((source = PA_SOURCE_OUTPUT(o)->source))
             module = source->module;
+        pl = PA_SOURCE_OUTPUT(o)->proplist;
     }
 
     /* If there is no sink/source yet, we can't do much */
     if ((is_sink_input && !sink) || (!is_sink_input && !source))
         goto done;
 
-    /* If the stream doesn't what any filter, then let it be. */
+    /* If the stream doesn't want any filter, then let it be. */
     if ((want = get_filter_name(o, is_sink_input))) {
         /* We need to ensure the SI is playing on a sink of this type
          * attached to the sink it's "officially" playing on */
@@ -471,6 +532,24 @@ static pa_hook_result_t process(struct userdata *u, pa_object *o, bool is_sink_i
             goto done;
         }
 
+        /* If the stream originally did not have the filter.apply property set and is
+         * manually moved away from the filter, remove the filter properties from the
+         * stream */
+        if (pa_proplist_gets(pl, PA_PROP_FILTER_APPLY_SET_BY_MFA)) {
+
+            set_filter_properties(pl, NULL, false);
+
+            /* If the new sink/source is also a filter, the stream has been moved from
+             * one filter to another, so add the properties for the new filter. */
+            if ((filter = get_filter_for_object(u, o, is_sink_input)))
+                set_filter_properties(pl, filter, true);
+
+            done_something = true;
+            goto done;
+        }
+
+        /* The stream needs be moved to a filter. */
+
         /* Some filter modules might require parameters by default.
          * (e.g 'plugin', 'label', 'control' of module-ladspa-sink) */
         parameters = get_filter_parameters(o, want, is_sink_input);
@@ -515,23 +594,28 @@ static pa_hook_result_t process(struct userdata *u, pa_object *o, bool is_sink_i
             done_something = true;
         }
     } else {
-        void *state;
+        /* The filter.apply property is not set. If the stream is nevertheless using a
+         * filter sink/source, it either has been moved to the filter manually or the
+         * user just removed the filter.apply property. */
 
-        /* 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 ((is_sink_input && sink == filter->sink) || (!is_sink_input && source == filter->source)) {
+        if ((filter = get_filter_for_object(u, o, is_sink_input))) {
+            if (is_property_change) {
+                /* 'filter.apply' has been manually unset. Do restore. */
                 move_objects_for_filter(u, o, filter, true, is_sink_input);
+                set_filter_properties(pl, filter, false);
                 done_something = true;
-                break;
+            } else {
+                /* Stream has been manually moved to a filter sink/source
+                 * without 'filter.apply' set. Leave sink as it is. */
+                set_filter_properties(pl, filter, true);
             }
         }
     }
 
+done:
     if (done_something)
         trigger_housekeeping(u);
 
-done:
     pa_xfree(module_name);
     filter_free(fltr);
 
@@ -542,7 +626,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, PA_OBJECT(i), true);
+    return process(u, PA_OBJECT(i), true, false);
 }
 
 static pa_hook_result_t sink_input_move_finish_cb(pa_core *core, pa_sink_input *i, struct userdata *u) {
@@ -555,14 +639,14 @@ static pa_hook_result_t sink_input_move_finish_cb(pa_core *core, pa_sink_input *
     /* If we're managing m-d-m.auto_filtered on this, remove and re-add if we're continuing to manage it */
     pa_hashmap_remove(u->mdm_ignored_inputs, i);
 
-    return process(u, PA_OBJECT(i), true);
+    return process(u, PA_OBJECT(i), true, false);
 }
 
 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, PA_OBJECT(i), true);
+    return process(u, PA_OBJECT(i), true, true);
 }
 
 static pa_hook_result_t sink_input_unlink_cb(pa_core *core, pa_sink_input *i, struct userdata *u) {
@@ -619,7 +703,7 @@ static pa_hook_result_t source_output_put_cb(pa_core *core, pa_source_output *o,
     pa_core_assert_ref(core);
     pa_source_output_assert_ref(o);
 
-    return process(u, PA_OBJECT(o), false);
+    return process(u, PA_OBJECT(o), false, false);
 }
 
 static pa_hook_result_t source_output_move_finish_cb(pa_core *core, pa_source_output *o, struct userdata *u) {
@@ -632,14 +716,14 @@ static pa_hook_result_t source_output_move_finish_cb(pa_core *core, pa_source_ou
     /* If we're managing m-d-m.auto_filtered on this, remove and re-add if we're continuing to manage it */
     pa_hashmap_remove(u->mdm_ignored_outputs, o);
 
-    return process(u, PA_OBJECT(o), false);
+    return process(u, PA_OBJECT(o), false, 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);
+    return process(u, PA_OBJECT(o), false, true);
 }
 
 static pa_hook_result_t source_output_unlink_cb(pa_core *core, pa_source_output *o, struct userdata *u) {



More information about the pulseaudio-commits mailing list