[pulseaudio-commits] 8 commits - src/Makefile.am src/modules src/pulse src/pulsecore

Colin Guthrie colin at kemper.freedesktop.org
Wed Jul 20 14:23:44 PDT 2011


 src/Makefile.am                                         |    3 
 src/modules/alsa/alsa-mixer.c                           |  213 +++++++++++++---
 src/modules/alsa/alsa-sink.c                            |  125 +++++----
 src/modules/alsa/alsa-source.c                          |  124 +++++----
 src/modules/alsa/mixer/profile-sets/90-pulseaudio.rules |    9 
 src/modules/alsa/mixer/profile-sets/usb-headset.conf    |   35 --
 src/modules/bluetooth/module-bluetooth-device.c         |   12 
 src/modules/echo-cancel/module-echo-cancel.c            |   16 -
 src/modules/module-equalizer-sink.c                     |    6 
 src/modules/module-ladspa-sink.c                        |    6 
 src/modules/module-solaris.c                            |   16 -
 src/modules/module-tunnel.c                             |    6 
 src/modules/module-virtual-sink.c                       |   13 
 src/modules/module-virtual-source.c                     |   13 
 src/modules/module-waveout.c                            |    4 
 src/modules/oss/module-oss.c                            |   11 
 src/modules/raop/module-raop-sink.c                     |    6 
 src/modules/reserve-wrap.c                              |    4 
 src/pulse/def.h                                         |   18 -
 src/pulsecore/sink.c                                    |  150 ++++++++++-
 src/pulsecore/sink.h                                    |   49 ++-
 src/pulsecore/source.c                                  |  150 ++++++++++-
 src/pulsecore/source.h                                  |   47 ++-
 23 files changed, 748 insertions(+), 288 deletions(-)

New commits:
commit cf0bf5a4c25fc363476620fd875da333e140dd8f
Author: Colin Guthrie <colin at mageia.org>
Date:   Sun Jul 17 18:14:52 2011 +0100

    reserve: Fix compile warning when compiling without dbus

diff --git a/src/modules/reserve-wrap.c b/src/modules/reserve-wrap.c
index 4be19c7..515cb22 100644
--- a/src/modules/reserve-wrap.c
+++ b/src/modules/reserve-wrap.c
@@ -105,9 +105,9 @@ static int request_cb(rd_device *d, int forced) {
 
 pa_reserve_wrapper* pa_reserve_wrapper_get(pa_core *c, const char *device_name) {
     pa_reserve_wrapper *r;
-    int k;
     char *t;
 #ifdef HAVE_DBUS
+    int k;
     DBusError error;
 
     dbus_error_init(&error);
@@ -248,9 +248,9 @@ static void change_cb(rm_monitor *m) {
 
 pa_reserve_monitor_wrapper* pa_reserve_monitor_wrapper_get(pa_core *c, const char *device_name) {
     pa_reserve_monitor_wrapper *w;
-    int k;
     char *t;
 #ifdef HAVE_DBUS
+    int k;
     DBusError error;
 
     dbus_error_init(&error);

commit 906e99b792329d57f286763886c681b8d8e9f0c4
Author: Colin Guthrie <colin at mageia.org>
Date:   Thu Jul 7 10:42:35 2011 +0100

    alsa-mixer: Remove workaround for USB head/handsets
    
    Now that subset mixer paths are removed, this workaround is no longer needed.
    
    This effectively reverts 1c38b5d4787d9466bd7bd5910d27e501298ec778 but due
    to me forgetting to add files and adding a couple extra workarounds after,
    it's easier to just do this manually rather than run git-revert.

diff --git a/src/Makefile.am b/src/Makefile.am
index 9117004..ebf5ebc 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1069,8 +1069,7 @@ dist_alsaprofilesets_DATA = \
 		modules/alsa/mixer/profile-sets/native-instruments-traktor-audio6.conf \
 		modules/alsa/mixer/profile-sets/native-instruments-traktor-audio10.conf \
 		modules/alsa/mixer/profile-sets/native-instruments-traktorkontrol-s4.conf \
-		modules/alsa/mixer/profile-sets/native-instruments-korecontroller.conf \
-		modules/alsa/mixer/profile-sets/usb-headset.conf
+		modules/alsa/mixer/profile-sets/native-instruments-korecontroller.conf
 
 if HAVE_UDEV
 dist_udevrules_DATA = \
diff --git a/src/modules/alsa/mixer/profile-sets/90-pulseaudio.rules b/src/modules/alsa/mixer/profile-sets/90-pulseaudio.rules
index e1da331..0329340 100644
--- a/src/modules/alsa/mixer/profile-sets/90-pulseaudio.rules
+++ b/src/modules/alsa/mixer/profile-sets/90-pulseaudio.rules
@@ -20,15 +20,6 @@ SUBSYSTEM!="sound", GOTO="pulseaudio_end"
 ACTION!="change", GOTO="pulseaudio_end"
 KERNEL!="card*", GOTO="pulseaudio_end"
 
-# Some specific work arounds until we can handle heasets/handsets properly (i.e. "Speaker" only, no "master")
-SUBSYSTEMS=="usb", ATTRS{idVendor}=="046d", ATTRS{idProduct}=="01ab", ENV{PULSE_PROFILE_SET}="usb-headset.conf"
-SUBSYSTEMS=="usb", ATTRS{idVendor}=="046d", ATTRS{idProduct}=="0a0c", ENV{PULSE_PROFILE_SET}="usb-headset.conf"
-SUBSYSTEMS=="usb", ATTRS{idVendor}=="1395", ATTRS{idProduct}=="0002", ENV{PULSE_PROFILE_SET}="usb-headset.conf"
-# UAC1.0 Sennheiser Dongle
-SUBSYSTEMS=="usb", ATTRS{idVendor}=="1395", ATTRS{idProduct}=="3554", ENV{PULSE_PROFILE_SET}="usb-headset.conf"
-# BT Agile Handset
-SUBSYSTEMS=="usb", ATTRS{idVendor}=="1885", ATTRS{idProduct}=="0501", ENV{PULSE_PROFILE_SET}="usb-headset.conf"
-
 SUBSYSTEMS=="usb", ATTRS{idVendor}=="17cc", ATTRS{idProduct}=="1978", ENV{PULSE_PROFILE_SET}="native-instruments-audio8dj.conf"
 SUBSYSTEMS=="usb", ATTRS{idVendor}=="17cc", ATTRS{idProduct}=="0839", ENV{PULSE_PROFILE_SET}="native-instruments-audio4dj.conf"
 SUBSYSTEMS=="usb", ATTRS{idVendor}=="17cc", ATTRS{idProduct}=="baff", ENV{PULSE_PROFILE_SET}="native-instruments-traktorkontrol-s4.conf"
diff --git a/src/modules/alsa/mixer/profile-sets/usb-headset.conf b/src/modules/alsa/mixer/profile-sets/usb-headset.conf
deleted file mode 100644
index adf78d1..0000000
--- a/src/modules/alsa/mixer/profile-sets/usb-headset.conf
+++ /dev/null
@@ -1,35 +0,0 @@
-# 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, write to the Free Software Foundation,
-# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
-
-; This is a workaround - these usb headsets have one output volume control only, labeled "Speaker".
-; This causes the default profile set to not control the volume at all, which is a bug.
-
-[General]
-auto-profiles = yes
-
-[Mapping analog-mono]
-device-strings = hw:%f
-channel-map = mono
-paths-output = analog-output-speaker
-paths-input = analog-input analog-input-mic analog-input-linein analog-input-aux analog-input-video analog-input-tvtuner analog-input-fm analog-input-mic-line
-priority = 1
-
-[Mapping analog-stereo]
-device-strings = front:%f hw:%f
-channel-map = left,right
-paths-output = analog-output-speaker
-paths-input = analog-input analog-input-mic analog-input-linein analog-input-aux analog-input-video analog-input-tvtuner analog-input-fm analog-input-mic-line
-priority = 10

commit c0470fd8c40c17267ca2eefd66713209fe166668
Author: Colin Guthrie <colin at mageia.org>
Date:   Thu Jul 7 10:37:25 2011 +0100

    alsa: No need to go via sink/source to get the core.

diff --git a/src/modules/alsa/alsa-sink.c b/src/modules/alsa/alsa-sink.c
index bb1220a..c1655d4 100644
--- a/src/modules/alsa/alsa-sink.c
+++ b/src/modules/alsa/alsa-sink.c
@@ -589,8 +589,8 @@ static int mmap_write(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polle
             }
 
             /* Make sure that if these memblocks need to be copied they will fit into one slot */
-            if (frames > pa_mempool_block_size_max(u->sink->core->mempool)/u->frame_size)
-                frames = pa_mempool_block_size_max(u->sink->core->mempool)/u->frame_size;
+            if (frames > pa_mempool_block_size_max(u->core->mempool)/u->frame_size)
+                frames = pa_mempool_block_size_max(u->core->mempool)/u->frame_size;
 
             if (!after_avail && frames == 0)
                 break;
diff --git a/src/modules/alsa/alsa-source.c b/src/modules/alsa/alsa-source.c
index ec63e69..6b6f513 100644
--- a/src/modules/alsa/alsa-source.c
+++ b/src/modules/alsa/alsa-source.c
@@ -557,8 +557,8 @@ static int mmap_read(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled
             }
 
             /* Make sure that if these memblocks need to be copied they will fit into one slot */
-            if (frames > pa_mempool_block_size_max(u->source->core->mempool)/u->frame_size)
-                frames = pa_mempool_block_size_max(u->source->core->mempool)/u->frame_size;
+            if (frames > pa_mempool_block_size_max(u->core->mempool)/u->frame_size)
+                frames = pa_mempool_block_size_max(u->core->mempool)/u->frame_size;
 
             if (!after_avail && frames == 0)
                 break;

commit 5c832a5b8a2bc2ce7447eab10acaeed30cd3ed78
Author: Colin Guthrie <colin at mageia.org>
Date:   Sat Jul 2 18:14:48 2011 +0100

    alsa-mixer: Detect and then drop pointless paths in the path set.
    
    In order to try and avoid 'spamming' the user with port choices,
    attempt to detect and remove any pointless paths in a path set. That is
    any paths which are subsets of other paths.
    
    This should solve a problem case with some USB Headsets which result in
    two paths both involving the 'Speaker' element. When no 'Master' element
    exists (which is quite common on head/handsets), then the first path
    (analog-output) will contain the 'Speaker' in a way that completely fits
    with in the use of the 'Speaker' element in the other path
    (analog-output-speaker).

diff --git a/src/modules/alsa/alsa-mixer.c b/src/modules/alsa/alsa-mixer.c
index abd3bf2..f6a2a20 100644
--- a/src/modules/alsa/alsa-mixer.c
+++ b/src/modules/alsa/alsa-mixer.c
@@ -2873,6 +2873,196 @@ void pa_alsa_path_set_dump(pa_alsa_path_set *ps) {
         pa_alsa_path_dump(p);
 }
 
+
+static pa_bool_t options_have_option(pa_alsa_option *options, const char *alsa_name) {
+    pa_alsa_option *o;
+
+    pa_assert(options);
+    pa_assert(alsa_name);
+
+    PA_LLIST_FOREACH(o, options) {
+        if (pa_streq(o->alsa_name, alsa_name))
+            return TRUE;
+    }
+    return FALSE;
+}
+
+static pa_bool_t enumeration_is_subset(pa_alsa_option *a_options, pa_alsa_option *b_options) {
+    pa_alsa_option *oa, *ob;
+
+    pa_assert(a_options);
+    pa_assert(b_options);
+
+    /* If there is an option A offers that B does not, then A is not a subset of B. */
+    PA_LLIST_FOREACH(oa, a_options) {
+        pa_bool_t found = FALSE;
+        PA_LLIST_FOREACH(ob, b_options) {
+            if (pa_streq(oa->alsa_name, ob->alsa_name)) {
+                found = TRUE;
+                break;
+            }
+        }
+        if (!found)
+            return FALSE;
+    }
+    return TRUE;
+}
+
+/**
+ *  Compares two elements to see if a is a subset of b
+ */
+static pa_bool_t element_is_subset(pa_alsa_element *a, pa_alsa_element *b, snd_mixer_t *m) {
+    pa_assert(a);
+    pa_assert(b);
+    pa_assert(m);
+
+    /* General rules:
+     * Every state is a subset of itself (with caveats for volume_limits and options)
+     * IGNORE is a subset of every other state */
+
+    /* Check the volume_use */
+    if (a->volume_use != PA_ALSA_VOLUME_IGNORE) {
+
+        /* "Constant" is subset of "Constant" only when their constant values are equal */
+        if (a->volume_use == PA_ALSA_VOLUME_CONSTANT && b->volume_use == PA_ALSA_VOLUME_CONSTANT && a->constant_volume != b->constant_volume)
+            return FALSE;
+
+        /* Different volume uses when b is not "Merge" means we are definitely not a subset */
+        if (a->volume_use != b->volume_use && b->volume_use != PA_ALSA_VOLUME_MERGE)
+            return FALSE;
+
+        /* "Constant" is a subset of "Merge", if there is not a "volume-limit" in "Merge" below the actual constant.
+         * "Zero" and "Off" are just special cases of "Constant" when comparing to "Merge"
+         * "Merge" with a "volume-limit" is a subset of "Merge" without a "volume-limit" or with a higher "volume-limit" */
+        if (b->volume_use == PA_ALSA_VOLUME_MERGE && b->volume_limit >= 0) {
+            long a_limit;
+
+            if (a->volume_use == PA_ALSA_VOLUME_CONSTANT)
+                a_limit = a->constant_volume;
+            else if (a->volume_use == PA_ALSA_VOLUME_ZERO) {
+                long dB = 0;
+
+                if (a->db_fix) {
+                    int rounding = (a->direction == PA_ALSA_DIRECTION_OUTPUT ? +1 : -1);
+                    a_limit = decibel_fix_get_step(a->db_fix, &dB, rounding);
+                } else {
+                    snd_mixer_selem_id_t *sid;
+                    snd_mixer_elem_t *me;
+
+                    SELEM_INIT(sid, a->alsa_name);
+                    if (!(me = snd_mixer_find_selem(m, sid))) {
+                        pa_log_warn("Element %s seems to have disappeared.", a->alsa_name);
+                        return FALSE;
+                    }
+
+                    if (a->direction == PA_ALSA_DIRECTION_OUTPUT) {
+                        if (snd_mixer_selem_ask_playback_dB_vol(me, dB, +1, &a_limit) < 0)
+                            return FALSE;
+                    } else {
+                        if (snd_mixer_selem_ask_capture_dB_vol(me, dB, -1, &a_limit) < 0)
+                            return FALSE;
+                    }
+                }
+            } else if (a->volume_use == PA_ALSA_VOLUME_OFF)
+                a_limit = a->min_volume;
+            else if (a->volume_use == PA_ALSA_VOLUME_MERGE)
+                a_limit = a->volume_limit;
+            else
+                /* This should never be reached */
+                pa_assert(FALSE);
+
+            if (a_limit > b->volume_limit)
+                return FALSE;
+        }
+    }
+
+    if (a->switch_use != PA_ALSA_SWITCH_IGNORE) {
+        /* "On" is a subset of "Mute".
+         * "Off" is a subset of "Mute".
+         * "On" is a subset of "Select", if there is an "Option:On" in B.
+         * "Off" is a subset of "Select", if there is an "Option:Off" in B.
+         * "Select" is a subset of "Select", if they have the same options (is this always true?). */
+
+        if (a->switch_use != b->switch_use) {
+
+            if (a->switch_use == PA_ALSA_SWITCH_SELECT || a->switch_use == PA_ALSA_SWITCH_MUTE
+                || b->switch_use == PA_ALSA_SWITCH_OFF || b->switch_use == PA_ALSA_SWITCH_ON)
+                return FALSE;
+
+            if (b->switch_use == PA_ALSA_SWITCH_SELECT) {
+                if (a->switch_use == PA_ALSA_SWITCH_ON) {
+                    if (!options_have_option(b->options, "on"))
+                        return FALSE;
+                } else if (a->switch_use == PA_ALSA_SWITCH_OFF) {
+                    if (!options_have_option(b->options, "off"))
+                        return FALSE;
+                }
+            }
+        } else if (a->switch_use == PA_ALSA_SWITCH_SELECT) {
+            if (!enumeration_is_subset(a->options, b->options))
+                return FALSE;
+        }
+    }
+
+    if (a->enumeration_use != PA_ALSA_ENUMERATION_IGNORE) {
+        if (!enumeration_is_subset(a->options, b->options))
+            return FALSE;
+    }
+
+    return TRUE;
+}
+
+static void path_set_condense(pa_alsa_path_set *ps, snd_mixer_t *m) {
+    pa_alsa_path *p, *np;
+
+    pa_assert(ps);
+    pa_assert(m);
+
+    /* If we only have one path, then don't bother */
+    if (!ps->paths || !ps->paths->next)
+        return;
+
+    for (p = ps->paths; p; p = np) {
+        pa_alsa_path *p2;
+        np = p->next;
+
+        PA_LLIST_FOREACH(p2, ps->paths) {
+            pa_alsa_element *ea, *eb;
+            pa_bool_t is_subset = TRUE;
+
+            if (p == p2)
+                continue;
+
+            /* Compare the elements of each set... */
+            pa_assert_se(ea = p->elements);
+            pa_assert_se(eb = p2->elements);
+
+            while (is_subset) {
+                if (pa_streq(ea->alsa_name, eb->alsa_name)) {
+                    if (element_is_subset(ea, eb, m)) {
+                        ea = ea->next;
+                        eb = eb->next;
+                        if ((ea && !eb) || (!ea && eb))
+                            is_subset = FALSE;
+                        else if (!ea && !eb)
+                            break;
+                    } else
+                        is_subset = FALSE;
+
+                } else
+                    is_subset = FALSE;
+            }
+
+            if (is_subset) {
+                pa_log_debug("Removing path '%s' as it is a subset of '%s'.", p->name, p2->name);
+                PA_LLIST_REMOVE(pa_alsa_path, ps->paths, p);
+                pa_alsa_path_free(p);
+                break;
+            }
+        }
+    }
+}
+
 static void path_set_make_paths_unique(pa_alsa_path_set *ps) {
     pa_alsa_path *p, *q;
 
@@ -2928,8 +3118,15 @@ void pa_alsa_path_set_probe(pa_alsa_path_set *ps, snd_mixer_t *m, pa_bool_t igno
         }
     }
 
+    pa_log_debug("Found mixer paths (before tidying):");
+    pa_alsa_path_set_dump(ps);
+
+    path_set_condense(ps, m);
     path_set_make_paths_unique(ps);
     ps->probed = TRUE;
+
+    pa_log_debug("Available mixer paths (after tidying):");
+    pa_alsa_path_set_dump(ps);
 }
 
 static void mapping_free(pa_alsa_mapping *m) {
diff --git a/src/modules/alsa/alsa-sink.c b/src/modules/alsa/alsa-sink.c
index 6c22b55..bb1220a 100644
--- a/src/modules/alsa/alsa-sink.c
+++ b/src/modules/alsa/alsa-sink.c
@@ -1741,9 +1741,6 @@ static void find_mixer(struct userdata *u, pa_alsa_mapping *mapping, const char
             goto fail;
 
         pa_alsa_path_set_probe(u->mixer_path_set, u->mixer_handle, ignore_dB);
-
-        pa_log_debug("Probed mixer paths:");
-        pa_alsa_path_set_dump(u->mixer_path_set);
     }
 
     return;
diff --git a/src/modules/alsa/alsa-source.c b/src/modules/alsa/alsa-source.c
index 8b35ce1..ec63e69 100644
--- a/src/modules/alsa/alsa-source.c
+++ b/src/modules/alsa/alsa-source.c
@@ -1516,9 +1516,6 @@ static void find_mixer(struct userdata *u, pa_alsa_mapping *mapping, const char
             goto fail;
 
         pa_alsa_path_set_probe(u->mixer_path_set, u->mixer_handle, ignore_dB);
-
-        pa_log_debug("Probed mixer paths:");
-        pa_alsa_path_set_dump(u->mixer_path_set);
     }
 
     return;

commit 85834107a0eb81a823287b3a9ae834314961c290
Author: Colin Guthrie <colin at mageia.org>
Date:   Sat Jul 2 16:29:00 2011 +0100

    alsa-mixer: Do not 'unify' mixer paths.
    
    Unification is really just a 'lowest common denominator' system. If any
    paths do not support volume, mute or decibels, then mark them all as not
    having them.
    
    This was originally done this way because the flags set on sinks that
    dictate if it supports h/w volume, mute etc. could not be changed after
    the sink was created.
    
    The fact that these flags could not change has now been change in the
    previous commits, and thus there is now no need to use this 'lowest
    common denominator' approach as we can fully support the various
    different combinations, even if they change after initial creation
    of the sinks/source.

diff --git a/src/modules/alsa/alsa-mixer.c b/src/modules/alsa/alsa-mixer.c
index 348f037..abd3bf2 100644
--- a/src/modules/alsa/alsa-mixer.c
+++ b/src/modules/alsa/alsa-mixer.c
@@ -2873,49 +2873,6 @@ void pa_alsa_path_set_dump(pa_alsa_path_set *ps) {
         pa_alsa_path_dump(p);
 }
 
-static void path_set_unify(pa_alsa_path_set *ps) {
-    pa_alsa_path *p;
-    pa_bool_t has_dB = TRUE, has_volume = TRUE, has_mute = TRUE;
-    pa_assert(ps);
-
-    /* We have issues dealing with paths that vary too wildly. That
-     * means for now we have to have all paths support volume/mute/dB
-     * or none. */
-
-    PA_LLIST_FOREACH(p, ps->paths) {
-        pa_assert(p->probed);
-
-        if (!p->has_volume)
-            has_volume = FALSE;
-        else if (!p->has_dB)
-            has_dB = FALSE;
-
-        if (!p->has_mute)
-            has_mute = FALSE;
-    }
-
-    if (!has_volume || !has_dB || !has_mute) {
-
-        if (!has_volume)
-            pa_log_debug("Some paths of the device lack hardware volume control, disabling hardware control altogether.");
-        else if (!has_dB)
-            pa_log_debug("Some paths of the device lack dB information, disabling dB logic altogether.");
-
-        if (!has_mute)
-            pa_log_debug("Some paths of the device lack hardware mute control, disabling hardware control altogether.");
-
-        PA_LLIST_FOREACH(p, ps->paths) {
-            if (!has_volume)
-                p->has_volume = FALSE;
-            else if (!has_dB)
-                p->has_dB = FALSE;
-
-            if (!has_mute)
-                p->has_mute = FALSE;
-        }
-    }
-}
-
 static void path_set_make_paths_unique(pa_alsa_path_set *ps) {
     pa_alsa_path *p, *q;
 
@@ -2971,7 +2928,6 @@ void pa_alsa_path_set_probe(pa_alsa_path_set *ps, snd_mixer_t *m, pa_bool_t igno
         }
     }
 
-    path_set_unify(ps);
     path_set_make_paths_unique(ps);
     ps->probed = TRUE;
 }

commit 6c6b50d6a8bbd7fc7fb2cfb59a66183741567922
Author: Colin Guthrie <colin at mageia.org>
Date:   Sat Jul 2 16:23:01 2011 +0100

    alsa: Reinitialise the mixer on port change.
    
    This allows us to flip from software to hardware volume control as the port's
    mixer path dictates.

diff --git a/src/modules/alsa/alsa-sink.c b/src/modules/alsa/alsa-sink.c
index 0dd1840..6c22b55 100644
--- a/src/modules/alsa/alsa-sink.c
+++ b/src/modules/alsa/alsa-sink.c
@@ -130,7 +130,7 @@ struct userdata {
     char *device_name;  /* name of the PCM device */
     char *control_device; /* name of the control device */
 
-    pa_bool_t use_mmap:1, use_tsched:1;
+    pa_bool_t use_mmap:1, use_tsched:1, sync_volume:1;
 
     pa_bool_t first, after_rewind;
 
@@ -1372,6 +1372,55 @@ static void sink_set_mute_cb(pa_sink *s) {
     pa_alsa_path_set_mute(u->mixer_path, u->mixer_handle, s->muted);
 }
 
+static void mixer_volume_init(struct userdata *u) {
+    pa_assert(u);
+
+    if (!u->mixer_path->has_volume) {
+        pa_sink_set_write_volume_callback(u->sink, NULL);
+        pa_sink_set_get_volume_callback(u->sink, NULL);
+        pa_sink_set_set_volume_callback(u->sink, NULL);
+
+        pa_log_info("Driver does not support hardware volume control, falling back to software volume control.");
+    } else {
+        pa_sink_set_get_volume_callback(u->sink, sink_get_volume_cb);
+        pa_sink_set_set_volume_callback(u->sink, sink_set_volume_cb);
+
+        if (u->mixer_path->has_dB && u->sync_volume) {
+            pa_sink_set_write_volume_callback(u->sink, sink_write_volume_cb);
+            pa_log_info("Successfully enabled synchronous volume.");
+        } else
+            pa_sink_set_write_volume_callback(u->sink, NULL);
+
+        if (u->mixer_path->has_dB) {
+            pa_sink_enable_decibel_volume(u->sink, TRUE);
+            pa_log_info("Hardware volume ranges from %0.2f dB to %0.2f dB.", u->mixer_path->min_dB, u->mixer_path->max_dB);
+
+            u->sink->base_volume = pa_sw_volume_from_dB(-u->mixer_path->max_dB);
+            u->sink->n_volume_steps = PA_VOLUME_NORM+1;
+
+            pa_log_info("Fixing base volume to %0.2f dB", pa_sw_volume_to_dB(u->sink->base_volume));
+        } else {
+            pa_sink_enable_decibel_volume(u->sink, FALSE);
+            pa_log_info("Hardware volume ranges from %li to %li.", u->mixer_path->min_volume, u->mixer_path->max_volume);
+
+            u->sink->base_volume = PA_VOLUME_NORM;
+            u->sink->n_volume_steps = u->mixer_path->max_volume - u->mixer_path->min_volume + 1;
+        }
+
+        pa_log_info("Using hardware volume control. Hardware dB scale %s.", u->mixer_path->has_dB ? "supported" : "not supported");
+    }
+
+    if (!u->mixer_path->has_mute) {
+        pa_sink_set_get_mute_callback(u->sink, NULL);
+        pa_sink_set_set_mute_callback(u->sink, NULL);
+        pa_log_info("Driver does not support hardware mute control, falling back to software mute control.");
+    } else {
+        pa_sink_set_get_mute_callback(u->sink, sink_get_mute_cb);
+        pa_sink_set_set_mute_callback(u->sink, sink_set_mute_cb);
+        pa_log_info("Using hardware mute control.");
+    }
+}
+
 static int sink_set_port_cb(pa_sink *s, pa_device_port *p) {
     struct userdata *u = s->userdata;
     pa_alsa_port_data *data;
@@ -1385,15 +1434,7 @@ static int sink_set_port_cb(pa_sink *s, pa_device_port *p) {
     pa_assert_se(u->mixer_path = data->path);
     pa_alsa_path_select(u->mixer_path, u->mixer_handle);
 
-    if (u->mixer_path->has_volume && u->mixer_path->has_dB) {
-        s->base_volume = pa_sw_volume_from_dB(-u->mixer_path->max_dB);
-        s->n_volume_steps = PA_VOLUME_NORM+1;
-
-        pa_log_info("Fixing base volume to %0.2f dB", pa_sw_volume_to_dB(s->base_volume));
-    } else {
-        s->base_volume = PA_VOLUME_NORM;
-        s->n_volume_steps = u->mixer_path->max_volume - u->mixer_path->min_volume + 1;
-    }
+    mixer_volume_init(u);
 
     if (data->setting)
         pa_alsa_setting_select(data->setting, u->mixer_handle);
@@ -1723,7 +1764,10 @@ fail:
     }
 }
 
-static int setup_mixer(struct userdata *u, pa_bool_t ignore_dB, pa_bool_t sync_volume) {
+
+static int setup_mixer(struct userdata *u, pa_bool_t ignore_dB) {
+    pa_bool_t need_mixer_callback = FALSE;
+
     pa_assert(u);
 
     if (!u->mixer_handle)
@@ -1759,47 +1803,21 @@ static int setup_mixer(struct userdata *u, pa_bool_t ignore_dB, pa_bool_t sync_v
             return 0;
     }
 
-    if (!u->mixer_path->has_volume) {
-        pa_log_info("Driver does not support hardware volume control, falling back to software volume control.");
-    } else {
+    mixer_volume_init(u);
 
-        if (u->mixer_path->has_dB) {
-            pa_log_info("Hardware volume ranges from %0.2f dB to %0.2f dB.", u->mixer_path->min_dB, u->mixer_path->max_dB);
+    /* Will we need to register callbacks? */
+    if (u->mixer_path_set && u->mixer_path_set->paths) {
+        pa_alsa_path *p;
 
-            u->sink->base_volume = pa_sw_volume_from_dB(-u->mixer_path->max_dB);
-            u->sink->n_volume_steps = PA_VOLUME_NORM+1;
-
-            pa_log_info("Fixing base volume to %0.2f dB", pa_sw_volume_to_dB(u->sink->base_volume));
-
-        } else {
-            pa_log_info("Hardware volume ranges from %li to %li.", u->mixer_path->min_volume, u->mixer_path->max_volume);
-            u->sink->base_volume = PA_VOLUME_NORM;
-            u->sink->n_volume_steps = u->mixer_path->max_volume - u->mixer_path->min_volume + 1;
+        PA_LLIST_FOREACH(p, u->mixer_path_set->paths) {
+            if (p->has_volume || p->has_mute)
+                need_mixer_callback = TRUE;
         }
-
-        pa_sink_set_get_volume_callback(u->sink, sink_get_volume_cb);
-        pa_sink_set_set_volume_callback(u->sink, sink_set_volume_cb);
-
-        if (u->mixer_path->has_dB) {
-            u->sink->flags |= PA_SINK_DECIBEL_VOLUME;
-            if (sync_volume) {
-                pa_sink_set_write_volume_callback(u->sink, sink_write_volume_cb);
-                pa_log_info("Successfully enabled synchronous volume.");
-            }
-        }
-
-        pa_log_info("Using hardware volume control. Hardware dB scale %s.", u->mixer_path->has_dB ? "supported" : "not supported");
-    }
-
-    if (!u->mixer_path->has_mute) {
-        pa_log_info("Driver does not support hardware mute control, falling back to software mute control.");
-    } else {
-        pa_sink_set_get_mute_callback(u->sink, sink_get_mute_cb);
-        pa_sink_set_set_mute_callback(u->sink, sink_set_mute_cb);
-        pa_log_info("Using hardware mute control.");
     }
+    else if (u->mixer_path)
+        need_mixer_callback = u->mixer_path->has_volume || u->mixer_path->has_mute;
 
-    if (u->mixer_path->has_volume || u->mixer_path->has_mute) {
+    if (need_mixer_callback) {
         int (*mixer_callback)(snd_mixer_elem_t *, unsigned int);
         if (u->sink->flags & PA_SINK_SYNC_VOLUME) {
             u->mixer_pd = pa_alsa_mixer_pdata_new();
@@ -1909,6 +1927,7 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca
     u->module = m;
     u->use_mmap = use_mmap;
     u->use_tsched = use_tsched;
+    u->sync_volume = sync_volume;
     u->first = TRUE;
     u->rewind_safeguard = rewind_safeguard;
     u->rtpoll = pa_rtpoll_new();
@@ -2133,7 +2152,7 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca
     if (update_sw_params(u) < 0)
         goto fail;
 
-    if (setup_mixer(u, ignore_dB, sync_volume) < 0)
+    if (setup_mixer(u, ignore_dB) < 0)
         goto fail;
 
     pa_alsa_dump(PA_LOG_DEBUG, u->pcm_handle);
diff --git a/src/modules/alsa/alsa-source.c b/src/modules/alsa/alsa-source.c
index 941aacb..8b35ce1 100644
--- a/src/modules/alsa/alsa-source.c
+++ b/src/modules/alsa/alsa-source.c
@@ -116,7 +116,7 @@ struct userdata {
     char *device_name;  /* name of the PCM device */
     char *control_device; /* name of the control device */
 
-    pa_bool_t use_mmap:1, use_tsched:1;
+    pa_bool_t use_mmap:1, use_tsched:1, sync_volume:1;
 
     pa_bool_t first;
 
@@ -1247,6 +1247,55 @@ static void source_set_mute_cb(pa_source *s) {
     pa_alsa_path_set_mute(u->mixer_path, u->mixer_handle, s->muted);
 }
 
+static void mixer_volume_init(struct userdata *u) {
+    pa_assert(u);
+
+    if (!u->mixer_path->has_volume) {
+        pa_source_set_write_volume_callback(u->source, NULL);
+        pa_source_set_get_volume_callback(u->source, NULL);
+        pa_source_set_set_volume_callback(u->source, NULL);
+
+        pa_log_info("Driver does not support hardware volume control, falling back to software volume control.");
+    } else {
+        pa_source_set_get_volume_callback(u->source, source_get_volume_cb);
+        pa_source_set_set_volume_callback(u->source, source_set_volume_cb);
+
+        if (u->mixer_path->has_dB && u->sync_volume) {
+            pa_source_set_write_volume_callback(u->source, source_write_volume_cb);
+            pa_log_info("Successfully enabled synchronous volume.");
+        } else
+            pa_source_set_write_volume_callback(u->source, NULL);
+
+        if (u->mixer_path->has_dB) {
+            pa_source_enable_decibel_volume(u->source, TRUE);
+            pa_log_info("Hardware volume ranges from %0.2f dB to %0.2f dB.", u->mixer_path->min_dB, u->mixer_path->max_dB);
+
+            u->source->base_volume = pa_sw_volume_from_dB(-u->mixer_path->max_dB);
+            u->source->n_volume_steps = PA_VOLUME_NORM+1;
+
+            pa_log_info("Fixing base volume to %0.2f dB", pa_sw_volume_to_dB(u->source->base_volume));
+        } else {
+            pa_source_enable_decibel_volume(u->source, FALSE);
+            pa_log_info("Hardware volume ranges from %li to %li.", u->mixer_path->min_volume, u->mixer_path->max_volume);
+
+            u->source->base_volume = PA_VOLUME_NORM;
+            u->source->n_volume_steps = u->mixer_path->max_volume - u->mixer_path->min_volume + 1;
+        }
+
+        pa_log_info("Using hardware volume control. Hardware dB scale %s.", u->mixer_path->has_dB ? "supported" : "not supported");
+    }
+
+    if (!u->mixer_path->has_mute) {
+        pa_source_set_get_mute_callback(u->source, NULL);
+        pa_source_set_set_mute_callback(u->source, NULL);
+        pa_log_info("Driver does not support hardware mute control, falling back to software mute control.");
+    } else {
+        pa_source_set_get_mute_callback(u->source, source_get_mute_cb);
+        pa_source_set_set_mute_callback(u->source, source_set_mute_cb);
+        pa_log_info("Using hardware mute control.");
+    }
+}
+
 static int source_set_port_cb(pa_source *s, pa_device_port *p) {
     struct userdata *u = s->userdata;
     pa_alsa_port_data *data;
@@ -1260,15 +1309,7 @@ static int source_set_port_cb(pa_source *s, pa_device_port *p) {
     pa_assert_se(u->mixer_path = data->path);
     pa_alsa_path_select(u->mixer_path, u->mixer_handle);
 
-    if (u->mixer_path->has_volume && u->mixer_path->has_dB) {
-        s->base_volume = pa_sw_volume_from_dB(-u->mixer_path->max_dB);
-        s->n_volume_steps = PA_VOLUME_NORM+1;
-
-        pa_log_info("Fixing base volume to %0.2f dB", pa_sw_volume_to_dB(s->base_volume));
-    } else {
-        s->base_volume = PA_VOLUME_NORM;
-        s->n_volume_steps = u->mixer_path->max_volume - u->mixer_path->min_volume + 1;
-    }
+    mixer_volume_init(u);
 
     if (data->setting)
         pa_alsa_setting_select(data->setting, u->mixer_handle);
@@ -1498,7 +1539,9 @@ fail:
     }
 }
 
-static int setup_mixer(struct userdata *u, pa_bool_t ignore_dB, pa_bool_t sync_volume) {
+static int setup_mixer(struct userdata *u, pa_bool_t ignore_dB) {
+    pa_bool_t need_mixer_callback = FALSE;
+
     pa_assert(u);
 
     if (!u->mixer_handle)
@@ -1534,47 +1577,21 @@ static int setup_mixer(struct userdata *u, pa_bool_t ignore_dB, pa_bool_t sync_v
             return 0;
     }
 
-    if (!u->mixer_path->has_volume) {
-        pa_log_info("Driver does not support hardware volume control, falling back to software volume control.");
-    } else {
-
-        if (u->mixer_path->has_dB) {
-            pa_log_info("Hardware volume ranges from %0.2f dB to %0.2f dB.", u->mixer_path->min_dB, u->mixer_path->max_dB);
-
-            u->source->base_volume = pa_sw_volume_from_dB(-u->mixer_path->max_dB);
-            u->source->n_volume_steps = PA_VOLUME_NORM+1;
-
-            pa_log_info("Fixing base volume to %0.2f dB", pa_sw_volume_to_dB(u->source->base_volume));
-
-        } else {
-            pa_log_info("Hardware volume ranges from %li to %li.", u->mixer_path->min_volume, u->mixer_path->max_volume);
-            u->source->base_volume = PA_VOLUME_NORM;
-            u->source->n_volume_steps = u->mixer_path->max_volume - u->mixer_path->min_volume + 1;
-        }
+    mixer_volume_init(u);
 
-        pa_source_set_get_volume_callback(u->source, source_get_volume_cb);
-        pa_source_set_set_volume_callback(u->source, source_set_volume_cb);
+    /* Will we need to register callbacks? */
+    if (u->mixer_path_set && u->mixer_path_set->paths) {
+        pa_alsa_path *p;
 
-        if (u->mixer_path->has_dB) {
-            u->source->flags |= PA_SOURCE_DECIBEL_VOLUME;
-            if (sync_volume) {
-                pa_source_set_write_volume_callback(u->source, source_write_volume_cb);
-                pa_log_info("Successfully enabled synchronous volume.");
-            }
+        PA_LLIST_FOREACH(p, u->mixer_path_set->paths) {
+            if (p->has_volume || p->has_mute)
+                need_mixer_callback = TRUE;
         }
-
-        pa_log_info("Using hardware volume control. Hardware dB scale %s.", u->mixer_path->has_dB ? "supported" : "not supported");
-    }
-
-    if (!u->mixer_path->has_mute) {
-        pa_log_info("Driver does not support hardware mute control, falling back to software mute control.");
-    } else {
-        pa_source_set_get_mute_callback(u->source, source_get_mute_cb);
-        pa_source_set_set_mute_callback(u->source, source_set_mute_cb);
-        pa_log_info("Using hardware mute control.");
     }
+    else if (u->mixer_path)
+        need_mixer_callback = u->mixer_path->has_volume || u->mixer_path->has_mute;
 
-    if (u->mixer_path->has_volume || u->mixer_path->has_mute) {
+    if (need_mixer_callback) {
         int (*mixer_callback)(snd_mixer_elem_t *, unsigned int);
         if (u->source->flags & PA_SOURCE_SYNC_VOLUME) {
             u->mixer_pd = pa_alsa_mixer_pdata_new();
@@ -1678,6 +1695,7 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p
     u->module = m;
     u->use_mmap = use_mmap;
     u->use_tsched = use_tsched;
+    u->sync_volume = sync_volume;
     u->first = TRUE;
     u->rtpoll = pa_rtpoll_new();
     pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll);
@@ -1893,7 +1911,7 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p
     if (update_sw_params(u) < 0)
         goto fail;
 
-    if (setup_mixer(u, ignore_dB, sync_volume) < 0)
+    if (setup_mixer(u, ignore_dB) < 0)
         goto fail;
 
     pa_alsa_dump(PA_LOG_DEBUG, u->pcm_handle);
diff --git a/src/modules/echo-cancel/module-echo-cancel.c b/src/modules/echo-cancel/module-echo-cancel.c
index 62b436b..961f289 100644
--- a/src/modules/echo-cancel/module-echo-cancel.c
+++ b/src/modules/echo-cancel/module-echo-cancel.c
@@ -1523,7 +1523,6 @@ int pa__init(pa_module*m) {
     }
 
     u->source = pa_source_new(m->core, &source_data,
-                          PA_SOURCE_DECIBEL_VOLUME|
                           (source_master->flags & (PA_SOURCE_LATENCY|PA_SOURCE_DYNAMIC_LATENCY)));
     pa_source_new_data_done(&source_data);
 
@@ -1535,6 +1534,7 @@ int pa__init(pa_module*m) {
     u->source->parent.process_msg = source_process_msg_cb;
     u->source->set_state = source_set_state_cb;
     u->source->update_requested_latency = source_update_requested_latency_cb;
+    pa_source_enable_decibel_volume(u->source, TRUE);
     pa_source_set_get_volume_callback(u->source, source_get_volume_cb);
     pa_source_set_set_volume_callback(u->source, source_set_volume_cb);
     pa_source_set_get_mute_callback(u->source, source_get_mute_cb);
@@ -1571,7 +1571,6 @@ int pa__init(pa_module*m) {
     }
 
     u->sink = pa_sink_new(m->core, &sink_data,
-                          PA_SINK_DECIBEL_VOLUME|
                           (sink_master->flags & (PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY)));
     pa_sink_new_data_done(&sink_data);
 
@@ -1584,6 +1583,7 @@ int pa__init(pa_module*m) {
     u->sink->set_state = sink_set_state_cb;
     u->sink->update_requested_latency = sink_update_requested_latency_cb;
     u->sink->request_rewind = sink_request_rewind_cb;
+    pa_sink_enable_decibel_volume(u->sink, TRUE);
     pa_sink_set_set_volume_callback(u->sink, sink_set_volume_cb);
     pa_sink_set_set_mute_callback(u->sink, sink_set_mute_cb);
     u->sink->userdata = u;
diff --git a/src/modules/module-equalizer-sink.c b/src/modules/module-equalizer-sink.c
index d01d453..ee9b678 100644
--- a/src/modules/module-equalizer-sink.c
+++ b/src/modules/module-equalizer-sink.c
@@ -1178,7 +1178,6 @@ int pa__init(pa_module*m) {
     }
 
     u->sink = pa_sink_new(m->core, &sink_data,
-                          PA_SINK_DECIBEL_VOLUME|
                           (master->flags & (PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY)));
     pa_sink_new_data_done(&sink_data);
 
@@ -1191,6 +1190,7 @@ int pa__init(pa_module*m) {
     u->sink->set_state = sink_set_state_cb;
     u->sink->update_requested_latency = sink_update_requested_latency_cb;
     u->sink->request_rewind = sink_request_rewind_cb;
+    pa_sink_enable_decibel_volume(u->sink, TRUE);
     pa_sink_set_set_volume_callback(u->sink, sink_set_volume_cb);
     pa_sink_set_set_mute_callback(u->sink, sink_set_mute_cb);
     u->sink->userdata = u;
diff --git a/src/modules/module-ladspa-sink.c b/src/modules/module-ladspa-sink.c
index b962b1b..9b4903a 100644
--- a/src/modules/module-ladspa-sink.c
+++ b/src/modules/module-ladspa-sink.c
@@ -883,7 +883,6 @@ int pa__init(pa_module*m) {
     }
 
     u->sink = pa_sink_new(m->core, &sink_data,
-                          PA_SINK_DECIBEL_VOLUME|
                           (master->flags & (PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY)));
     pa_sink_new_data_done(&sink_data);
 
@@ -896,6 +895,7 @@ int pa__init(pa_module*m) {
     u->sink->set_state = sink_set_state_cb;
     u->sink->update_requested_latency = sink_update_requested_latency_cb;
     u->sink->request_rewind = sink_request_rewind_cb;
+    pa_sink_enable_decibel_volume(u->sink, TRUE);
     pa_sink_set_set_volume_callback(u->sink, sink_set_volume_cb);
     pa_sink_set_set_mute_callback(u->sink, sink_set_mute_cb);
     u->sink->userdata = u;
diff --git a/src/modules/module-virtual-sink.c b/src/modules/module-virtual-sink.c
index a880df4..b52a29b 100644
--- a/src/modules/module-virtual-sink.c
+++ b/src/modules/module-virtual-sink.c
@@ -554,8 +554,7 @@ int pa__init(pa_module*m) {
     }
 
     u->sink = pa_sink_new(m->core, &sink_data, (master->flags & (PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY))
-                                               | (use_volume_sharing ? PA_SINK_SHARE_VOLUME_WITH_MASTER : 0)
-                                               | (force_flat_volume ? PA_SINK_FLAT_VOLUME : 0));
+                                               | (use_volume_sharing ? PA_SINK_SHARE_VOLUME_WITH_MASTER : 0));
     pa_sink_new_data_done(&sink_data);
 
     if (!u->sink) {
@@ -567,8 +566,14 @@ int pa__init(pa_module*m) {
     u->sink->set_state = sink_set_state_cb;
     u->sink->update_requested_latency = sink_update_requested_latency_cb;
     u->sink->request_rewind = sink_request_rewind_cb;
-    pa_sink_set_set_volume_callback(u->sink, use_volume_sharing ? NULL : sink_set_volume_cb);
     pa_sink_set_set_mute_callback(u->sink, sink_set_mute_cb);
+    if (use_volume_sharing) {
+        pa_sink_set_set_volume_callback(u->sink, sink_set_volume_cb);
+        pa_sink_enable_decibel_volume(u->sink, TRUE);
+    }
+    /* Normally this flag would be enabled automatically be we can force it. */
+    if (force_flat_volume)
+        u->sink->flags |= PA_SINK_FLAT_VOLUME;
     u->sink->userdata = u;
 
     pa_sink_set_asyncmsgq(u->sink, master->asyncmsgq);
diff --git a/src/modules/module-virtual-source.c b/src/modules/module-virtual-source.c
index f5c5e67..7bcecfa 100644
--- a/src/modules/module-virtual-source.c
+++ b/src/modules/module-virtual-source.c
@@ -581,8 +581,7 @@ int pa__init(pa_module*m) {
     }
 
     u->source = pa_source_new(m->core, &source_data, (master->flags & (PA_SOURCE_LATENCY|PA_SOURCE_DYNAMIC_LATENCY))
-                                                     | (use_volume_sharing ? PA_SOURCE_SHARE_VOLUME_WITH_MASTER : 0)
-                                                     | (force_flat_volume ? PA_SOURCE_FLAT_VOLUME : 0));
+                                                     | (use_volume_sharing ? PA_SOURCE_SHARE_VOLUME_WITH_MASTER : 0));
 
     pa_source_new_data_done(&source_data);
 
@@ -594,8 +593,14 @@ int pa__init(pa_module*m) {
     u->source->parent.process_msg = source_process_msg_cb;
     u->source->set_state = source_set_state_cb;
     u->source->update_requested_latency = source_update_requested_latency_cb;
-    pa_source_set_set_volume_callback(u->source, use_volume_sharing ? NULL : source_set_volume_cb);
     pa_source_set_set_mute_callback(u->source, source_set_mute_cb);
+    if (use_volume_sharing) {
+        pa_source_set_set_volume_callback(u->source, source_set_volume_cb);
+        pa_source_enable_decibel_volume(u->source, TRUE);
+    }
+    /* Normally this flag would be enabled automatically be we can force it. */
+    if (force_flat_volume)
+        u->source->flags |= PA_SOURCE_FLAT_VOLUME;
     u->source->userdata = u;
 
     pa_source_set_asyncmsgq(u->source, master->asyncmsgq);
diff --git a/src/pulsecore/sink.c b/src/pulsecore/sink.c
index b51156f..77c665f 100644
--- a/src/pulsecore/sink.c
+++ b/src/pulsecore/sink.c
@@ -456,29 +456,49 @@ void pa_sink_set_get_volume_callback(pa_sink *s, pa_sink_cb_t cb) {
 }
 
 void pa_sink_set_set_volume_callback(pa_sink *s, pa_sink_cb_t cb) {
-    pa_assert(s);
+    pa_sink_flags_t flags;
 
+    pa_assert(s);
     pa_assert(!s->write_volume || cb);
 
     s->set_volume = cb;
 
-    if (cb)
+    /* Save the current flags so we can tell if they've changed */
+    flags = s->flags;
+
+    if (cb) {
+        /* The sink implementor is responsible for setting decibel volume support */
         s->flags |= PA_SINK_HW_VOLUME_CTRL;
-    else
+    } else {
         s->flags &= ~PA_SINK_HW_VOLUME_CTRL;
+        /* See note below in pa_sink_put() about volume sharing and decibel volumes */
+        pa_sink_enable_decibel_volume(s, !(s->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER));
+    }
+
+    /* If the flags have changed after init, let any clients know via a change event */
+    if (s->state != PA_SINK_INIT && flags != s->flags)
+        pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
 }
 
 void pa_sink_set_write_volume_callback(pa_sink *s, pa_sink_cb_t cb) {
-    pa_assert(s);
+    pa_sink_flags_t flags;
 
+    pa_assert(s);
     pa_assert(!cb || s->set_volume);
 
     s->write_volume = cb;
 
+    /* Save the current flags so we can tell if they've changed */
+    flags = s->flags;
+
     if (cb)
         s->flags |= PA_SINK_SYNC_VOLUME;
     else
         s->flags &= ~PA_SINK_SYNC_VOLUME;
+
+    /* If the flags have changed after init, let any clients know via a change event */
+    if (s->state != PA_SINK_INIT && flags != s->flags)
+        pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
 }
 
 void pa_sink_set_get_mute_callback(pa_sink *s, pa_sink_cb_t cb) {
@@ -488,14 +508,65 @@ void pa_sink_set_get_mute_callback(pa_sink *s, pa_sink_cb_t cb) {
 }
 
 void pa_sink_set_set_mute_callback(pa_sink *s, pa_sink_cb_t cb) {
+    pa_sink_flags_t flags;
+
     pa_assert(s);
 
     s->set_mute = cb;
 
+    /* Save the current flags so we can tell if they've changed */
+    flags = s->flags;
+
     if (cb)
         s->flags |= PA_SINK_HW_MUTE_CTRL;
     else
         s->flags &= ~PA_SINK_HW_MUTE_CTRL;
+
+    /* If the flags have changed after init, let any clients know via a change event */
+    if (s->state != PA_SINK_INIT && flags != s->flags)
+        pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+}
+
+static void enable_flat_volume(pa_sink *s, pa_bool_t enable) {
+    pa_sink_flags_t flags;
+
+    pa_assert(s);
+
+    /* Always follow the overall user preference here */
+    enable = enable && s->core->flat_volumes;
+
+    /* Save the current flags so we can tell if they've changed */
+    flags = s->flags;
+
+    if (enable)
+        s->flags |= PA_SINK_FLAT_VOLUME;
+    else
+        s->flags &= ~PA_SINK_FLAT_VOLUME;
+
+    /* If the flags have changed after init, let any clients know via a change event */
+    if (s->state != PA_SINK_INIT && flags != s->flags)
+        pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+}
+
+void pa_sink_enable_decibel_volume(pa_sink *s, pa_bool_t enable) {
+    pa_sink_flags_t flags;
+
+    pa_assert(s);
+
+    /* Save the current flags so we can tell if they've changed */
+    flags = s->flags;
+
+    if (enable) {
+        s->flags |= PA_SINK_DECIBEL_VOLUME;
+        enable_flat_volume(s, TRUE);
+    } else {
+        s->flags &= ~PA_SINK_DECIBEL_VOLUME;
+        enable_flat_volume(s, FALSE);
+    }
+
+    /* If the flags have changed after init, let any clients know via a change event */
+    if (s->state != PA_SINK_INIT && flags != s->flags)
+        pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
 }
 
 /* Called from main context */
@@ -536,10 +607,12 @@ void pa_sink_put(pa_sink* s) {
      *
      * Note: This flag can also change over the life time of the sink. */
     if (!(s->flags & PA_SINK_HW_VOLUME_CTRL) && !(s->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER))
-        s->flags |= PA_SINK_DECIBEL_VOLUME;
+        pa_sink_enable_decibel_volume(s, TRUE);
 
-    if ((s->flags & PA_SINK_DECIBEL_VOLUME) && s->core->flat_volumes)
-        s->flags |= PA_SINK_FLAT_VOLUME;
+    /* If the sink implementor support DB volumes by itself, we should always
+     * try and enable flat volumes too */
+    if ((s->flags & PA_SINK_DECIBEL_VOLUME))
+        enable_flat_volume(s, TRUE);
 
     if (s->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER) {
         pa_sink *root_sink = s->input_to_master->sink;
diff --git a/src/pulsecore/sink.h b/src/pulsecore/sink.h
index 669ce20..239936b 100644
--- a/src/pulsecore/sink.h
+++ b/src/pulsecore/sink.h
@@ -362,6 +362,7 @@ void pa_sink_set_set_volume_callback(pa_sink *s, pa_sink_cb_t cb);
 void pa_sink_set_write_volume_callback(pa_sink *s, pa_sink_cb_t cb);
 void pa_sink_set_get_mute_callback(pa_sink *s, pa_sink_cb_t cb);
 void pa_sink_set_set_mute_callback(pa_sink *s, pa_sink_cb_t cb);
+void pa_sink_enable_decibel_volume(pa_sink *s, pa_bool_t enable);
 
 void pa_sink_put(pa_sink *s);
 void pa_sink_unlink(pa_sink* s);
diff --git a/src/pulsecore/source.c b/src/pulsecore/source.c
index 120ecdd..9764a88 100644
--- a/src/pulsecore/source.c
+++ b/src/pulsecore/source.c
@@ -388,29 +388,49 @@ void pa_source_set_get_volume_callback(pa_source *s, pa_source_cb_t cb) {
 }
 
 void pa_source_set_set_volume_callback(pa_source *s, pa_source_cb_t cb) {
-    pa_assert(s);
+    pa_source_flags_t flags;
 
+    pa_assert(s);
     pa_assert(!s->write_volume || cb);
 
     s->set_volume = cb;
 
-    if (cb)
+    /* Save the current flags so we can tell if they've changed */
+    flags = s->flags;
+
+    if (cb) {
+        /* The source implementor is responsible for setting decibel volume support */
         s->flags |= PA_SOURCE_HW_VOLUME_CTRL;
-    else
+    } else {
         s->flags &= ~PA_SOURCE_HW_VOLUME_CTRL;
+        /* See note below in pa_source_put() about volume sharing and decibel volumes */
+        pa_source_enable_decibel_volume(s, !(s->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER));
+    }
+
+    /* If the flags have changed after init, let any clients know via a change event */
+    if (s->state != PA_SOURCE_INIT && flags != s->flags)
+        pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
 }
 
 void pa_source_set_write_volume_callback(pa_source *s, pa_source_cb_t cb) {
-    pa_assert(s);
+    pa_source_flags_t flags;
 
+    pa_assert(s);
     pa_assert(!cb || s->set_volume);
 
     s->write_volume = cb;
 
+    /* Save the current flags so we can tell if they've changed */
+    flags = s->flags;
+
     if (cb)
         s->flags |= PA_SOURCE_SYNC_VOLUME;
     else
         s->flags &= ~PA_SOURCE_SYNC_VOLUME;
+
+    /* If the flags have changed after init, let any clients know via a change event */
+    if (s->state != PA_SOURCE_INIT && flags != s->flags)
+        pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
 }
 
 void pa_source_set_get_mute_callback(pa_source *s, pa_source_cb_t cb) {
@@ -420,14 +440,65 @@ void pa_source_set_get_mute_callback(pa_source *s, pa_source_cb_t cb) {
 }
 
 void pa_source_set_set_mute_callback(pa_source *s, pa_source_cb_t cb) {
+    pa_source_flags_t flags;
+
     pa_assert(s);
 
     s->set_mute = cb;
 
+    /* Save the current flags so we can tell if they've changed */
+    flags = s->flags;
+
     if (cb)
         s->flags |= PA_SOURCE_HW_MUTE_CTRL;
     else
         s->flags &= ~PA_SOURCE_HW_MUTE_CTRL;
+
+    /* If the flags have changed after init, let any clients know via a change event */
+    if (s->state != PA_SOURCE_INIT && flags != s->flags)
+        pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+}
+
+static void enable_flat_volume(pa_source *s, pa_bool_t enable) {
+    pa_source_flags_t flags;
+
+    pa_assert(s);
+
+    /* Always follow the overall user preference here */
+    enable = enable && s->core->flat_volumes;
+
+    /* Save the current flags so we can tell if they've changed */
+    flags = s->flags;
+
+    if (enable)
+        s->flags |= PA_SOURCE_FLAT_VOLUME;
+    else
+        s->flags &= ~PA_SOURCE_FLAT_VOLUME;
+
+    /* If the flags have changed after init, let any clients know via a change event */
+    if (s->state != PA_SOURCE_INIT && flags != s->flags)
+        pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+}
+
+void pa_source_enable_decibel_volume(pa_source *s, pa_bool_t enable) {
+    pa_source_flags_t flags;
+
+    pa_assert(s);
+
+    /* Save the current flags so we can tell if they've changed */
+    flags = s->flags;
+
+    if (enable) {
+        s->flags |= PA_SOURCE_DECIBEL_VOLUME;
+        enable_flat_volume(s, TRUE);
+    } else {
+        s->flags &= ~PA_SOURCE_DECIBEL_VOLUME;
+        enable_flat_volume(s, FALSE);
+    }
+
+    /* If the flags have changed after init, let any clients know via a change event */
+    if (s->state != PA_SOURCE_INIT && flags != s->flags)
+        pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
 }
 
 /* Called from main context */
@@ -468,10 +539,12 @@ void pa_source_put(pa_source *s) {
      *
      * Note: This flag can also change over the life time of the source. */
     if (!(s->flags & PA_SOURCE_HW_VOLUME_CTRL) && !(s->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER))
-        s->flags |= PA_SOURCE_DECIBEL_VOLUME;
+        pa_source_enable_decibel_volume(s, TRUE);
 
-    if ((s->flags & PA_SOURCE_DECIBEL_VOLUME) && s->core->flat_volumes)
-        s->flags |= PA_SOURCE_FLAT_VOLUME;
+    /* If the source implementor support DB volumes by itself, we should always
+     * try and enable flat volumes too */
+    if ((s->flags & PA_SOURCE_DECIBEL_VOLUME))
+        enable_flat_volume(s, TRUE);
 
     if (s->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER) {
         pa_source *root_source = s->output_from_master->source;
diff --git a/src/pulsecore/source.h b/src/pulsecore/source.h
index 52186cd..13d279d 100644
--- a/src/pulsecore/source.h
+++ b/src/pulsecore/source.h
@@ -293,6 +293,7 @@ void pa_source_set_set_volume_callback(pa_source *s, pa_source_cb_t cb);
 void pa_source_set_write_volume_callback(pa_source *s, pa_source_cb_t cb);
 void pa_source_set_get_mute_callback(pa_source *s, pa_source_cb_t cb);
 void pa_source_set_set_mute_callback(pa_source *s, pa_source_cb_t cb);
+void pa_source_enable_decibel_volume(pa_source *s, pa_bool_t enable);
 
 void pa_source_put(pa_source *s);
 void pa_source_unlink(pa_source *s);

commit a9cf320bc15a95599a2fe1c7a4357c62555f82ca
Author: Colin Guthrie <colin at mageia.org>
Date:   Tue Jul 5 23:44:06 2011 +0100

    devices: Set certain sink/source flags automatically.
    
    Some sink flags are really just a product of what callbacks
    are set on the device. We still enforce a degree of sanity
    that the flags match the callbacks set, but we also set the
    flags automatically in our callback setter functions to
    help ensure that a) people use them and b) flags & callbacks
    are kept in sync.

diff --git a/src/modules/alsa/alsa-sink.c b/src/modules/alsa/alsa-sink.c
index 98cb75f..0dd1840 100644
--- a/src/modules/alsa/alsa-sink.c
+++ b/src/modules/alsa/alsa-sink.c
@@ -1759,9 +1759,9 @@ static int setup_mixer(struct userdata *u, pa_bool_t ignore_dB, pa_bool_t sync_v
             return 0;
     }
 
-    if (!u->mixer_path->has_volume)
+    if (!u->mixer_path->has_volume) {
         pa_log_info("Driver does not support hardware volume control, falling back to software volume control.");
-    else {
+    } else {
 
         if (u->mixer_path->has_dB) {
             pa_log_info("Hardware volume ranges from %0.2f dB to %0.2f dB.", u->mixer_path->min_dB, u->mixer_path->max_dB);
@@ -1779,13 +1779,11 @@ static int setup_mixer(struct userdata *u, pa_bool_t ignore_dB, pa_bool_t sync_v
 
         pa_sink_set_get_volume_callback(u->sink, sink_get_volume_cb);
         pa_sink_set_set_volume_callback(u->sink, sink_set_volume_cb);
-        pa_sink_set_write_volume_callback(u->sink, sink_write_volume_cb);
 
-        u->sink->flags |= PA_SINK_HW_VOLUME_CTRL;
         if (u->mixer_path->has_dB) {
             u->sink->flags |= PA_SINK_DECIBEL_VOLUME;
             if (sync_volume) {
-                u->sink->flags |= PA_SINK_SYNC_VOLUME;
+                pa_sink_set_write_volume_callback(u->sink, sink_write_volume_cb);
                 pa_log_info("Successfully enabled synchronous volume.");
             }
         }
@@ -1798,11 +1796,10 @@ static int setup_mixer(struct userdata *u, pa_bool_t ignore_dB, pa_bool_t sync_v
     } else {
         pa_sink_set_get_mute_callback(u->sink, sink_get_mute_cb);
         pa_sink_set_set_mute_callback(u->sink, sink_set_mute_cb);
-        u->sink->flags |= PA_SINK_HW_MUTE_CTRL;
         pa_log_info("Using hardware mute control.");
     }
 
-    if (u->sink->flags & (PA_SINK_HW_VOLUME_CTRL|PA_SINK_HW_MUTE_CTRL)) {
+    if (u->mixer_path->has_volume || u->mixer_path->has_mute) {
         int (*mixer_callback)(snd_mixer_elem_t *, unsigned int);
         if (u->sink->flags & PA_SINK_SYNC_VOLUME) {
             u->mixer_pd = pa_alsa_mixer_pdata_new();
diff --git a/src/modules/alsa/alsa-source.c b/src/modules/alsa/alsa-source.c
index 94c14cb..941aacb 100644
--- a/src/modules/alsa/alsa-source.c
+++ b/src/modules/alsa/alsa-source.c
@@ -1534,9 +1534,9 @@ static int setup_mixer(struct userdata *u, pa_bool_t ignore_dB, pa_bool_t sync_v
             return 0;
     }
 
-    if (!u->mixer_path->has_volume)
+    if (!u->mixer_path->has_volume) {
         pa_log_info("Driver does not support hardware volume control, falling back to software volume control.");
-    else {
+    } else {
 
         if (u->mixer_path->has_dB) {
             pa_log_info("Hardware volume ranges from %0.2f dB to %0.2f dB.", u->mixer_path->min_dB, u->mixer_path->max_dB);
@@ -1554,13 +1554,11 @@ static int setup_mixer(struct userdata *u, pa_bool_t ignore_dB, pa_bool_t sync_v
 
         pa_source_set_get_volume_callback(u->source, source_get_volume_cb);
         pa_source_set_set_volume_callback(u->source, source_set_volume_cb);
-        pa_source_set_write_volume_callback(u->source, source_write_volume_cb);
 
-        u->source->flags |= PA_SOURCE_HW_VOLUME_CTRL;
         if (u->mixer_path->has_dB) {
             u->source->flags |= PA_SOURCE_DECIBEL_VOLUME;
             if (sync_volume) {
-                u->source->flags |= PA_SOURCE_SYNC_VOLUME;
+                pa_source_set_write_volume_callback(u->source, source_write_volume_cb);
                 pa_log_info("Successfully enabled synchronous volume.");
             }
         }
@@ -1573,11 +1571,10 @@ static int setup_mixer(struct userdata *u, pa_bool_t ignore_dB, pa_bool_t sync_v
     } else {
         pa_source_set_get_mute_callback(u->source, source_get_mute_cb);
         pa_source_set_set_mute_callback(u->source, source_set_mute_cb);
-        u->source->flags |= PA_SOURCE_HW_MUTE_CTRL;
         pa_log_info("Using hardware mute control.");
     }
 
-    if (u->source->flags & (PA_SOURCE_HW_VOLUME_CTRL|PA_SOURCE_HW_MUTE_CTRL)) {
+    if (u->mixer_path->has_volume || u->mixer_path->has_mute) {
         int (*mixer_callback)(snd_mixer_elem_t *, unsigned int);
         if (u->source->flags & PA_SOURCE_SYNC_VOLUME) {
             u->mixer_pd = pa_alsa_mixer_pdata_new();
diff --git a/src/modules/bluetooth/module-bluetooth-device.c b/src/modules/bluetooth/module-bluetooth-device.c
index 61600d9..9f81336 100644
--- a/src/modules/bluetooth/module-bluetooth-device.c
+++ b/src/modules/bluetooth/module-bluetooth-device.c
@@ -2022,7 +2022,7 @@ static int add_sink(struct userdata *u) {
             return -1;
         }
 
-        u->sink = pa_sink_new(u->core, &data, PA_SINK_HARDWARE|PA_SINK_LATENCY | (u->profile == PROFILE_HSP ? PA_SINK_HW_VOLUME_CTRL : 0));
+        u->sink = pa_sink_new(u->core, &data, PA_SINK_HARDWARE|PA_SINK_LATENCY);
         pa_sink_new_data_done(&data);
 
         if (!u->sink) {
@@ -2084,7 +2084,7 @@ static int add_source(struct userdata *u) {
             return -1;
         }
 
-        u->source = pa_source_new(u->core, &data, PA_SOURCE_HARDWARE|PA_SOURCE_LATENCY | (u->profile == PROFILE_HSP ? PA_SOURCE_HW_VOLUME_CTRL : 0));
+        u->source = pa_source_new(u->core, &data, PA_SOURCE_HARDWARE|PA_SOURCE_LATENCY);
         pa_source_new_data_done(&data);
 
         if (!u->source) {
diff --git a/src/modules/echo-cancel/module-echo-cancel.c b/src/modules/echo-cancel/module-echo-cancel.c
index cc06d47..62b436b 100644
--- a/src/modules/echo-cancel/module-echo-cancel.c
+++ b/src/modules/echo-cancel/module-echo-cancel.c
@@ -1523,7 +1523,7 @@ int pa__init(pa_module*m) {
     }
 
     u->source = pa_source_new(m->core, &source_data,
-                          PA_SOURCE_HW_MUTE_CTRL|PA_SOURCE_HW_VOLUME_CTRL|PA_SOURCE_DECIBEL_VOLUME|
+                          PA_SOURCE_DECIBEL_VOLUME|
                           (source_master->flags & (PA_SOURCE_LATENCY|PA_SOURCE_DYNAMIC_LATENCY)));
     pa_source_new_data_done(&source_data);
 
@@ -1571,7 +1571,7 @@ int pa__init(pa_module*m) {
     }
 
     u->sink = pa_sink_new(m->core, &sink_data,
-                          PA_SINK_HW_MUTE_CTRL|PA_SINK_HW_VOLUME_CTRL|PA_SINK_DECIBEL_VOLUME|
+                          PA_SINK_DECIBEL_VOLUME|
                           (sink_master->flags & (PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY)));
     pa_sink_new_data_done(&sink_data);
 
diff --git a/src/modules/module-equalizer-sink.c b/src/modules/module-equalizer-sink.c
index 03b166b..d01d453 100644
--- a/src/modules/module-equalizer-sink.c
+++ b/src/modules/module-equalizer-sink.c
@@ -1178,7 +1178,7 @@ int pa__init(pa_module*m) {
     }
 
     u->sink = pa_sink_new(m->core, &sink_data,
-                          PA_SINK_HW_MUTE_CTRL|PA_SINK_HW_VOLUME_CTRL|PA_SINK_DECIBEL_VOLUME|
+                          PA_SINK_DECIBEL_VOLUME|
                           (master->flags & (PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY)));
     pa_sink_new_data_done(&sink_data);
 
diff --git a/src/modules/module-ladspa-sink.c b/src/modules/module-ladspa-sink.c
index aed6270..b962b1b 100644
--- a/src/modules/module-ladspa-sink.c
+++ b/src/modules/module-ladspa-sink.c
@@ -883,7 +883,7 @@ int pa__init(pa_module*m) {
     }
 
     u->sink = pa_sink_new(m->core, &sink_data,
-                          PA_SINK_HW_MUTE_CTRL|PA_SINK_HW_VOLUME_CTRL|PA_SINK_DECIBEL_VOLUME|
+                          PA_SINK_DECIBEL_VOLUME|
                           (master->flags & (PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY)));
     pa_sink_new_data_done(&sink_data);
 
diff --git a/src/modules/module-solaris.c b/src/modules/module-solaris.c
index ce6af8a..61ea68c 100644
--- a/src/modules/module-solaris.c
+++ b/src/modules/module-solaris.c
@@ -933,7 +933,7 @@ int pa__init(pa_module *m) {
             goto fail;
         }
 
-        u->source = pa_source_new(m->core, &source_new_data, PA_SOURCE_HARDWARE|PA_SOURCE_LATENCY|PA_SOURCE_HW_VOLUME_CTRL);
+        u->source = pa_source_new(m->core, &source_new_data, PA_SOURCE_HARDWARE|PA_SOURCE_LATENCY);
         pa_source_new_data_done(&source_new_data);
         pa_xfree(name_buf);
 
@@ -981,7 +981,7 @@ int pa__init(pa_module *m) {
             goto fail;
         }
 
-        u->sink = pa_sink_new(m->core, &sink_new_data, PA_SINK_HARDWARE|PA_SINK_LATENCY|PA_SINK_HW_VOLUME_CTRL|PA_SINK_HW_MUTE_CTRL);
+        u->sink = pa_sink_new(m->core, &sink_new_data, PA_SINK_HARDWARE|PA_SINK_LATENCY);
         pa_sink_new_data_done(&sink_new_data);
 
         pa_assert(u->sink);
diff --git a/src/modules/module-tunnel.c b/src/modules/module-tunnel.c
index e7e0871..3efb40b 100644
--- a/src/modules/module-tunnel.c
+++ b/src/modules/module-tunnel.c
@@ -2003,7 +2003,7 @@ int pa__init(pa_module*m) {
         goto fail;
     }
 
-    u->sink = pa_sink_new(m->core, &data, PA_SINK_NETWORK|PA_SINK_LATENCY|PA_SINK_HW_VOLUME_CTRL|PA_SINK_HW_MUTE_CTRL);
+    u->sink = pa_sink_new(m->core, &data, PA_SINK_NETWORK|PA_SINK_LATENCY);
     pa_sink_new_data_done(&data);
 
     if (!u->sink) {
diff --git a/src/modules/oss/module-oss.c b/src/modules/oss/module-oss.c
index a22b315..accac32 100644
--- a/src/modules/oss/module-oss.c
+++ b/src/modules/oss/module-oss.c
@@ -1418,11 +1418,9 @@ int pa__init(pa_module*m) {
 
         if (ioctl(fd, SOUND_MIXER_READ_DEVMASK, &u->mixer_devmask) < 0)
             pa_log_warn("SOUND_MIXER_READ_DEVMASK failed: %s", pa_cstrerror(errno));
-
         else {
             if (u->sink && (u->mixer_devmask & (SOUND_MASK_VOLUME|SOUND_MASK_PCM))) {
                 pa_log_debug("Found hardware mixer track for playback.");
-                u->sink->flags |= PA_SINK_HW_VOLUME_CTRL;
                 pa_sink_set_get_volume_callback(u->sink, sink_get_volume);
                 pa_sink_set_set_volume_callback(u->sink, sink_set_volume);
                 u->sink->n_volume_steps = 101;
@@ -1431,7 +1429,6 @@ int pa__init(pa_module*m) {
 
             if (u->source && (u->mixer_devmask & (SOUND_MASK_RECLEV|SOUND_MASK_IGAIN))) {
                 pa_log_debug("Found hardware mixer track for recording.");
-                u->source->flags |= PA_SOURCE_HW_VOLUME_CTRL;
                 pa_source_set_get_volume_callback(u->source, source_get_volume);
                 pa_source_set_set_volume_callback(u->source, source_set_volume);
                 u->source->n_volume_steps = 101;
diff --git a/src/modules/raop/module-raop-sink.c b/src/modules/raop/module-raop-sink.c
index 2600441..4b21d39 100644
--- a/src/modules/raop/module-raop-sink.c
+++ b/src/modules/raop/module-raop-sink.c
@@ -596,7 +596,7 @@ int pa__init(pa_module*m) {
     u->sink->userdata = u;
     pa_sink_set_set_volume_callback(u->sink, sink_set_volume_cb);
     pa_sink_set_set_mute_callback(u->sink, sink_set_mute_cb);
-    u->sink->flags = PA_SINK_LATENCY|PA_SINK_NETWORK|PA_SINK_HW_VOLUME_CTRL;
+    u->sink->flags = PA_SINK_LATENCY|PA_SINK_NETWORK;
 
     pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq);
     pa_sink_set_rtpoll(u->sink, u->rtpoll);
diff --git a/src/pulse/def.h b/src/pulse/def.h
index 16b2e59..32169de 100644
--- a/src/pulse/def.h
+++ b/src/pulse/def.h
@@ -712,7 +712,8 @@ typedef enum pa_sink_flags {
     /**< Flag to pass when no specific options are needed (used to avoid casting)  \since 0.9.19 */
 
     PA_SINK_HW_VOLUME_CTRL = 0x0001U,
-    /**< Supports hardware volume control */
+    /**< Supports hardware volume control. This is a dynamic flag and may
+     * change at runtime after the sink has initialized */
 
     PA_SINK_LATENCY = 0x0002U,
     /**< Supports latency querying */
@@ -725,10 +726,12 @@ typedef enum pa_sink_flags {
     /**< Is a networked sink of some kind. \since 0.9.7 */
 
     PA_SINK_HW_MUTE_CTRL = 0x0010U,
-    /**< Supports hardware mute control \since 0.9.11 */
+    /**< Supports hardware mute control. This is a dynamic flag and may
+     * change at runtime after the sink has initialized \since 0.9.11 */
 
     PA_SINK_DECIBEL_VOLUME = 0x0020U,
-    /**< Volume can be translated to dB with pa_sw_volume_to_dB()
+    /**< Volume can be translated to dB with pa_sw_volume_to_dB(). This is a
+     * dynamic flag and may change at runtime after the sink has initialized
      * \since 0.9.11 */
 
     PA_SINK_FLAT_VOLUME = 0x0040U,
@@ -820,7 +823,8 @@ typedef enum pa_source_flags {
     /**< Flag to pass when no specific options are needed (used to avoid casting)  \since 0.9.19 */
 
     PA_SOURCE_HW_VOLUME_CTRL = 0x0001U,
-    /**< Supports hardware volume control */
+    /**< Supports hardware volume control. This is a dynamic flag and may
+     * change at runtime after the source has initialized */
 
     PA_SOURCE_LATENCY = 0x0002U,
     /**< Supports latency querying */
@@ -833,10 +837,12 @@ typedef enum pa_source_flags {
     /**< Is a networked source of some kind. \since 0.9.7 */
 
     PA_SOURCE_HW_MUTE_CTRL = 0x0010U,
-    /**< Supports hardware mute control \since 0.9.11 */
+    /**< Supports hardware mute control. This is a dynamic flag and may
+     * change at runtime after the source has initialized \since 0.9.11 */
 
     PA_SOURCE_DECIBEL_VOLUME = 0x0020U,
-    /**< Volume can be translated to dB with pa_sw_volume_to_dB()
+    /**< Volume can be translated to dB with pa_sw_volume_to_dB(). This is a
+     * dynamic flag and may change at runtime after the source has initialized
      * \since 0.9.11 */
 
     PA_SOURCE_DYNAMIC_LATENCY = 0x0040U,
diff --git a/src/pulsecore/sink.c b/src/pulsecore/sink.c
index f7e4b6a..b51156f 100644
--- a/src/pulsecore/sink.c
+++ b/src/pulsecore/sink.c
@@ -458,13 +458,27 @@ void pa_sink_set_get_volume_callback(pa_sink *s, pa_sink_cb_t cb) {
 void pa_sink_set_set_volume_callback(pa_sink *s, pa_sink_cb_t cb) {
     pa_assert(s);
 
+    pa_assert(!s->write_volume || cb);
+
     s->set_volume = cb;
+
+    if (cb)
+        s->flags |= PA_SINK_HW_VOLUME_CTRL;
+    else
+        s->flags &= ~PA_SINK_HW_VOLUME_CTRL;
 }
 
 void pa_sink_set_write_volume_callback(pa_sink *s, pa_sink_cb_t cb) {
     pa_assert(s);
 
+    pa_assert(!cb || s->set_volume);
+
     s->write_volume = cb;
+
+    if (cb)
+        s->flags |= PA_SINK_SYNC_VOLUME;
+    else
+        s->flags &= ~PA_SINK_SYNC_VOLUME;
 }
 
 void pa_sink_set_get_mute_callback(pa_sink *s, pa_sink_cb_t cb) {
@@ -477,6 +491,11 @@ void pa_sink_set_set_mute_callback(pa_sink *s, pa_sink_cb_t cb) {
     pa_assert(s);
 
     s->set_mute = cb;
+
+    if (cb)
+        s->flags |= PA_SINK_HW_MUTE_CTRL;
+    else
+        s->flags &= ~PA_SINK_HW_MUTE_CTRL;
 }
 
 /* Called from main context */
@@ -492,8 +511,18 @@ void pa_sink_put(pa_sink* s) {
     pa_assert(s->thread_info.min_latency <= s->thread_info.max_latency);
 
     /* Generally, flags should be initialized via pa_sink_new(). As a
-     * special exception we allow volume related flags to be set
-     * between _new() and _put(). */
+     * special exception we allow some volume related flags to be set
+     * between _new() and _put() by the callback setter functions above.
+     *
+     * Thus we implement a couple safeguards here which ensure the above
+     * setters were used (or at least the implementor made manual changes
+     * in a compatible way).
+     *
+     * Note: All of these flags set here can change over the life time
+     * of the sink. */
+    pa_assert(!(s->flags & PA_SINK_HW_VOLUME_CTRL) || s->set_volume);
+    pa_assert(!(s->flags & PA_SINK_SYNC_VOLUME) || s->write_volume);
+    pa_assert(!(s->flags & PA_SINK_HW_MUTE_CTRL) || s->set_mute);
 
     /* XXX: Currently decibel volume is disabled for all sinks that use volume
      * sharing. When the master sink supports decibel volume, it would be good
@@ -502,6 +531,10 @@ void pa_sink_put(pa_sink* s) {
      * a master sink to another. One solution for this problem would be to
      * remove user-visible volume altogether from filter sinks when volume
      * sharing is used, but the current approach was easier to implement... */
+    /* We always support decibel volumes in software, otherwise we leave it to
+     * the sink implementor to set this flag as needed.
+     *
+     * Note: This flag can also change over the life time of the sink. */
     if (!(s->flags & PA_SINK_HW_VOLUME_CTRL) && !(s->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER))
         s->flags |= PA_SINK_DECIBEL_VOLUME;
 
@@ -536,10 +569,6 @@ void pa_sink_put(pa_sink* s) {
     pa_assert(!(s->flags & PA_SINK_DYNAMIC_LATENCY) == (s->thread_info.fixed_latency != 0));
     pa_assert(!(s->flags & PA_SINK_LATENCY) == !(s->monitor_source->flags & PA_SOURCE_LATENCY));
     pa_assert(!(s->flags & PA_SINK_DYNAMIC_LATENCY) == !(s->monitor_source->flags & PA_SOURCE_DYNAMIC_LATENCY));
-    pa_assert(!(s->flags & PA_SINK_HW_VOLUME_CTRL) || s->set_volume);
-    pa_assert(!(s->flags & PA_SINK_SYNC_VOLUME) || (s->flags & PA_SINK_HW_VOLUME_CTRL));
-    pa_assert(!(s->flags & PA_SINK_SYNC_VOLUME) || s->write_volume);
-    pa_assert(!(s->flags & PA_SINK_HW_MUTE_CTRL) || s->set_mute);
 
     pa_assert(s->monitor_source->thread_info.fixed_latency == s->thread_info.fixed_latency);
     pa_assert(s->monitor_source->thread_info.min_latency == s->thread_info.min_latency);
diff --git a/src/pulsecore/source.c b/src/pulsecore/source.c
index 1ec8027..120ecdd 100644
--- a/src/pulsecore/source.c
+++ b/src/pulsecore/source.c
@@ -390,13 +390,27 @@ void pa_source_set_get_volume_callback(pa_source *s, pa_source_cb_t cb) {
 void pa_source_set_set_volume_callback(pa_source *s, pa_source_cb_t cb) {
     pa_assert(s);
 
+    pa_assert(!s->write_volume || cb);
+
     s->set_volume = cb;
+
+    if (cb)
+        s->flags |= PA_SOURCE_HW_VOLUME_CTRL;
+    else
+        s->flags &= ~PA_SOURCE_HW_VOLUME_CTRL;
 }
 
 void pa_source_set_write_volume_callback(pa_source *s, pa_source_cb_t cb) {
     pa_assert(s);
 
+    pa_assert(!cb || s->set_volume);
+
     s->write_volume = cb;
+
+    if (cb)
+        s->flags |= PA_SOURCE_SYNC_VOLUME;
+    else
+        s->flags &= ~PA_SOURCE_SYNC_VOLUME;
 }
 
 void pa_source_set_get_mute_callback(pa_source *s, pa_source_cb_t cb) {
@@ -409,6 +423,11 @@ void pa_source_set_set_mute_callback(pa_source *s, pa_source_cb_t cb) {
     pa_assert(s);
 
     s->set_mute = cb;
+
+    if (cb)
+        s->flags |= PA_SOURCE_HW_MUTE_CTRL;
+    else
+        s->flags &= ~PA_SOURCE_HW_MUTE_CTRL;
 }
 
 /* Called from main context */
@@ -424,8 +443,18 @@ void pa_source_put(pa_source *s) {
     pa_assert(s->thread_info.min_latency <= s->thread_info.max_latency);
 
     /* Generally, flags should be initialized via pa_source_new(). As a
-     * special exception we allow volume related flags to be set
-     * between _new() and _put(). */
+     * special exception we allow some volume related flags to be set
+     * between _new() and _put() by the callback setter functions above.
+     *
+     * Thus we implement a couple safeguards here which ensure the above
+     * setters were used (or at least the implementor made manual changes
+     * in a compatible way).
+     *
+     * Note: All of these flags set here can change over the life time
+     * of the source. */
+    pa_assert(!(s->flags & PA_SOURCE_HW_VOLUME_CTRL) || s->set_volume);
+    pa_assert(!(s->flags & PA_SOURCE_SYNC_VOLUME) || s->write_volume);
+    pa_assert(!(s->flags & PA_SOURCE_HW_MUTE_CTRL) || s->set_mute);
 
     /* XXX: Currently decibel volume is disabled for all sources that use volume
      * sharing. When the master source supports decibel volume, it would be good
@@ -434,6 +463,10 @@ void pa_source_put(pa_source *s) {
      * a master source to another. One solution for this problem would be to
      * remove user-visible volume altogether from filter sources when volume
      * sharing is used, but the current approach was easier to implement... */
+    /* We always support decibel volumes in software, otherwise we leave it to
+     * the source implementor to set this flag as needed.
+     *
+     * Note: This flag can also change over the life time of the source. */
     if (!(s->flags & PA_SOURCE_HW_VOLUME_CTRL) && !(s->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER))
         s->flags |= PA_SOURCE_DECIBEL_VOLUME;
 
@@ -466,10 +499,6 @@ void pa_source_put(pa_source *s) {
                   && ((s->flags & PA_SOURCE_DECIBEL_VOLUME || (s->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER)))));
     pa_assert(!(s->flags & PA_SOURCE_DECIBEL_VOLUME) || s->n_volume_steps == PA_VOLUME_NORM+1);
     pa_assert(!(s->flags & PA_SOURCE_DYNAMIC_LATENCY) == (s->thread_info.fixed_latency != 0));
-    pa_assert(!(s->flags & PA_SOURCE_HW_VOLUME_CTRL) || s->set_volume);
-    pa_assert(!(s->flags & PA_SOURCE_SYNC_VOLUME) || (s->flags & PA_SOURCE_HW_VOLUME_CTRL));
-    pa_assert(!(s->flags & PA_SOURCE_SYNC_VOLUME) || s->write_volume);
-    pa_assert(!(s->flags & PA_SOURCE_HW_MUTE_CTRL) || s->set_mute);
 
     pa_assert_se(source_set_state(s, PA_SOURCE_IDLE) == 0);
 

commit ded07a5898a0484783c29912d259ebda7392f5f4
Author: Colin Guthrie <colin at mageia.org>
Date:   Sun Jul 17 15:29:29 2011 +0100

    devices: Use wrapper functions to set the *_volume and *_mute callbacks.
    
    This is not currently useful but future commits will make further
    changes concerning automatic setting of flags and event delivery
    that makes this structure necessary.

diff --git a/src/modules/alsa/alsa-sink.c b/src/modules/alsa/alsa-sink.c
index 0164040..98cb75f 100644
--- a/src/modules/alsa/alsa-sink.c
+++ b/src/modules/alsa/alsa-sink.c
@@ -1777,9 +1777,9 @@ static int setup_mixer(struct userdata *u, pa_bool_t ignore_dB, pa_bool_t sync_v
             u->sink->n_volume_steps = u->mixer_path->max_volume - u->mixer_path->min_volume + 1;
         }
 
-        u->sink->get_volume = sink_get_volume_cb;
-        u->sink->set_volume = sink_set_volume_cb;
-        u->sink->write_volume = sink_write_volume_cb;
+        pa_sink_set_get_volume_callback(u->sink, sink_get_volume_cb);
+        pa_sink_set_set_volume_callback(u->sink, sink_set_volume_cb);
+        pa_sink_set_write_volume_callback(u->sink, sink_write_volume_cb);
 
         u->sink->flags |= PA_SINK_HW_VOLUME_CTRL;
         if (u->mixer_path->has_dB) {
@@ -1796,8 +1796,8 @@ static int setup_mixer(struct userdata *u, pa_bool_t ignore_dB, pa_bool_t sync_v
     if (!u->mixer_path->has_mute) {
         pa_log_info("Driver does not support hardware mute control, falling back to software mute control.");
     } else {
-        u->sink->get_mute = sink_get_mute_cb;
-        u->sink->set_mute = sink_set_mute_cb;
+        pa_sink_set_get_mute_callback(u->sink, sink_get_mute_cb);
+        pa_sink_set_set_mute_callback(u->sink, sink_set_mute_cb);
         u->sink->flags |= PA_SINK_HW_MUTE_CTRL;
         pa_log_info("Using hardware mute control.");
     }
diff --git a/src/modules/alsa/alsa-source.c b/src/modules/alsa/alsa-source.c
index f847b1e..94c14cb 100644
--- a/src/modules/alsa/alsa-source.c
+++ b/src/modules/alsa/alsa-source.c
@@ -1552,9 +1552,9 @@ static int setup_mixer(struct userdata *u, pa_bool_t ignore_dB, pa_bool_t sync_v
             u->source->n_volume_steps = u->mixer_path->max_volume - u->mixer_path->min_volume + 1;
         }
 
-        u->source->get_volume = source_get_volume_cb;
-        u->source->set_volume = source_set_volume_cb;
-        u->source->write_volume = source_write_volume_cb;
+        pa_source_set_get_volume_callback(u->source, source_get_volume_cb);
+        pa_source_set_set_volume_callback(u->source, source_set_volume_cb);
+        pa_source_set_write_volume_callback(u->source, source_write_volume_cb);
 
         u->source->flags |= PA_SOURCE_HW_VOLUME_CTRL;
         if (u->mixer_path->has_dB) {
@@ -1571,8 +1571,8 @@ static int setup_mixer(struct userdata *u, pa_bool_t ignore_dB, pa_bool_t sync_v
     if (!u->mixer_path->has_mute) {
         pa_log_info("Driver does not support hardware mute control, falling back to software mute control.");
     } else {
-        u->source->get_mute = source_get_mute_cb;
-        u->source->set_mute = source_set_mute_cb;
+        pa_source_set_get_mute_callback(u->source, source_get_mute_cb);
+        pa_source_set_set_mute_callback(u->source, source_set_mute_cb);
         u->source->flags |= PA_SOURCE_HW_MUTE_CTRL;
         pa_log_info("Using hardware mute control.");
     }
diff --git a/src/modules/bluetooth/module-bluetooth-device.c b/src/modules/bluetooth/module-bluetooth-device.c
index 288ad2f..61600d9 100644
--- a/src/modules/bluetooth/module-bluetooth-device.c
+++ b/src/modules/bluetooth/module-bluetooth-device.c
@@ -2040,7 +2040,7 @@ static int add_sink(struct userdata *u) {
     }
 
     if (u->profile == PROFILE_HSP) {
-        u->sink->set_volume = sink_set_volume_cb;
+        pa_sink_set_set_volume_callback(u->sink, sink_set_volume_cb);
         u->sink->n_volume_steps = 16;
 
         k = pa_sprintf_malloc("bluetooth-device@%p", (void*) u->sink);
@@ -2111,7 +2111,7 @@ static int add_source(struct userdata *u) {
     }
 
     if (u->profile == PROFILE_HSP) {
-        u->source->set_volume = source_set_volume_cb;
+        pa_source_set_set_volume_callback(u->source, source_set_volume_cb);
         u->source->n_volume_steps = 16;
 
         k = pa_sprintf_malloc("bluetooth-device@%p", (void*) u->source);
@@ -2510,8 +2510,8 @@ static void restore_sco_volume_callbacks(struct userdata *u) {
     pa_assert(u);
     pa_assert(USE_SCO_OVER_PCM(u));
 
-    u->hsp.sco_sink->set_volume = u->hsp.sco_sink_set_volume;
-    u->hsp.sco_source->set_volume = u->hsp.sco_source_set_volume;
+    pa_sink_set_set_volume_callback(u->hsp.sco_sink, u->hsp.sco_sink_set_volume);
+    pa_source_set_set_volume_callback(u->hsp.sco_source, u->hsp.sco_source_set_volume);
 }
 
 /* Run from main thread */
diff --git a/src/modules/echo-cancel/module-echo-cancel.c b/src/modules/echo-cancel/module-echo-cancel.c
index a03fa82..cc06d47 100644
--- a/src/modules/echo-cancel/module-echo-cancel.c
+++ b/src/modules/echo-cancel/module-echo-cancel.c
@@ -1535,10 +1535,10 @@ int pa__init(pa_module*m) {
     u->source->parent.process_msg = source_process_msg_cb;
     u->source->set_state = source_set_state_cb;
     u->source->update_requested_latency = source_update_requested_latency_cb;
-    u->source->set_volume = source_set_volume_cb;
-    u->source->set_mute = source_set_mute_cb;
-    u->source->get_volume = source_get_volume_cb;
-    u->source->get_mute = source_get_mute_cb;
+    pa_source_set_get_volume_callback(u->source, source_get_volume_cb);
+    pa_source_set_set_volume_callback(u->source, source_set_volume_cb);
+    pa_source_set_get_mute_callback(u->source, source_get_mute_cb);
+    pa_source_set_set_mute_callback(u->source, source_set_mute_cb);
     u->source->userdata = u;
 
     pa_source_set_asyncmsgq(u->source, source_master->asyncmsgq);
@@ -1584,8 +1584,8 @@ int pa__init(pa_module*m) {
     u->sink->set_state = sink_set_state_cb;
     u->sink->update_requested_latency = sink_update_requested_latency_cb;
     u->sink->request_rewind = sink_request_rewind_cb;
-    u->sink->set_volume = sink_set_volume_cb;
-    u->sink->set_mute = sink_set_mute_cb;
+    pa_sink_set_set_volume_callback(u->sink, sink_set_volume_cb);
+    pa_sink_set_set_mute_callback(u->sink, sink_set_mute_cb);
     u->sink->userdata = u;
 
     pa_sink_set_asyncmsgq(u->sink, sink_master->asyncmsgq);
diff --git a/src/modules/module-equalizer-sink.c b/src/modules/module-equalizer-sink.c
index e7d8790..03b166b 100644
--- a/src/modules/module-equalizer-sink.c
+++ b/src/modules/module-equalizer-sink.c
@@ -1191,8 +1191,8 @@ int pa__init(pa_module*m) {
     u->sink->set_state = sink_set_state_cb;
     u->sink->update_requested_latency = sink_update_requested_latency_cb;
     u->sink->request_rewind = sink_request_rewind_cb;
-    u->sink->set_volume = sink_set_volume_cb;
-    u->sink->set_mute = sink_set_mute_cb;
+    pa_sink_set_set_volume_callback(u->sink, sink_set_volume_cb);
+    pa_sink_set_set_mute_callback(u->sink, sink_set_mute_cb);
     u->sink->userdata = u;
 
     u->input_q = pa_memblockq_new(0,  MEMBLOCKQ_MAXLENGTH, 0, fs, 1, 1, 0, &u->sink->silence);
diff --git a/src/modules/module-ladspa-sink.c b/src/modules/module-ladspa-sink.c
index 9cce269..aed6270 100644
--- a/src/modules/module-ladspa-sink.c
+++ b/src/modules/module-ladspa-sink.c
@@ -896,8 +896,8 @@ int pa__init(pa_module*m) {
     u->sink->set_state = sink_set_state_cb;
     u->sink->update_requested_latency = sink_update_requested_latency_cb;
     u->sink->request_rewind = sink_request_rewind_cb;
-    u->sink->set_volume = sink_set_volume_cb;
-    u->sink->set_mute = sink_set_mute_cb;
+    pa_sink_set_set_volume_callback(u->sink, sink_set_volume_cb);
+    pa_sink_set_set_mute_callback(u->sink, sink_set_mute_cb);
     u->sink->userdata = u;
 
     pa_sink_set_asyncmsgq(u->sink, master->asyncmsgq);
diff --git a/src/modules/module-solaris.c b/src/modules/module-solaris.c
index 0e4e401..ce6af8a 100644
--- a/src/modules/module-solaris.c
+++ b/src/modules/module-solaris.c
@@ -949,8 +949,8 @@ int pa__init(pa_module *m) {
         pa_source_set_rtpoll(u->source, u->rtpoll);
         pa_source_set_fixed_latency(u->source, pa_bytes_to_usec(u->buffer_size, &u->source->sample_spec));
 
-        u->source->get_volume = source_get_volume;
-        u->source->set_volume = source_set_volume;
+        pa_source_set_get_volume_callback(u->source, source_get_volume);
+        pa_source_set_set_volume_callback(u->source, source_set_volume);
         u->source->refresh_volume = TRUE;
     } else
         u->source = NULL;
@@ -994,10 +994,10 @@ int pa__init(pa_module *m) {
         pa_sink_set_max_request(u->sink, u->buffer_size);
         pa_sink_set_max_rewind(u->sink, u->buffer_size);
 
-        u->sink->get_volume = sink_get_volume;
-        u->sink->set_volume = sink_set_volume;
-        u->sink->get_mute = sink_get_mute;
-        u->sink->set_mute = sink_set_mute;
+        pa_sink_set_get_volume_callback(u->sink, sink_get_volume);
+        pa_sink_set_set_volume_callback(u->sink, sink_set_volume);
+        pa_sink_set_get_mute_callback(u->sink, sink_get_mute);
+        pa_sink_set_set_mute_callback(u->sink, sink_set_mute);
         u->sink->refresh_volume = u->sink->refresh_muted = TRUE;
     } else
         u->sink = NULL;
diff --git a/src/modules/module-tunnel.c b/src/modules/module-tunnel.c
index 4b1ae7d..e7e0871 100644
--- a/src/modules/module-tunnel.c
+++ b/src/modules/module-tunnel.c
@@ -2014,8 +2014,8 @@ int pa__init(pa_module*m) {
     u->sink->parent.process_msg = sink_process_msg;
     u->sink->userdata = u;
     u->sink->set_state = sink_set_state;
-    u->sink->set_volume = sink_set_volume;
-    u->sink->set_mute = sink_set_mute;
+    pa_sink_set_set_volume_callback(u->sink, sink_set_volume);
+    pa_sink_set_set_mute_callback(u->sink, sink_set_mute);
 
     u->sink->refresh_volume = u->sink->refresh_muted = FALSE;
 
diff --git a/src/modules/module-virtual-sink.c b/src/modules/module-virtual-sink.c
index a6be244..a880df4 100644
--- a/src/modules/module-virtual-sink.c
+++ b/src/modules/module-virtual-sink.c
@@ -567,8 +567,8 @@ int pa__init(pa_module*m) {
     u->sink->set_state = sink_set_state_cb;
     u->sink->update_requested_latency = sink_update_requested_latency_cb;
     u->sink->request_rewind = sink_request_rewind_cb;
-    u->sink->set_volume = use_volume_sharing ? NULL : sink_set_volume_cb;
-    u->sink->set_mute = sink_set_mute_cb;
+    pa_sink_set_set_volume_callback(u->sink, use_volume_sharing ? NULL : sink_set_volume_cb);
+    pa_sink_set_set_mute_callback(u->sink, sink_set_mute_cb);
     u->sink->userdata = u;
 
     pa_sink_set_asyncmsgq(u->sink, master->asyncmsgq);
diff --git a/src/modules/module-virtual-source.c b/src/modules/module-virtual-source.c
index e15f4b9..f5c5e67 100644
--- a/src/modules/module-virtual-source.c
+++ b/src/modules/module-virtual-source.c
@@ -594,8 +594,8 @@ int pa__init(pa_module*m) {
     u->source->parent.process_msg = source_process_msg_cb;
     u->source->set_state = source_set_state_cb;
     u->source->update_requested_latency = source_update_requested_latency_cb;
-    u->source->set_volume = use_volume_sharing ? NULL : source_set_volume_cb;
-    u->source->set_mute = source_set_mute_cb;
+    pa_source_set_set_volume_callback(u->source, use_volume_sharing ? NULL : source_set_volume_cb);
+    pa_source_set_set_mute_callback(u->source, source_set_mute_cb);
     u->source->userdata = u;
 
     pa_source_set_asyncmsgq(u->source, master->asyncmsgq);
diff --git a/src/modules/module-waveout.c b/src/modules/module-waveout.c
index f0710b7..53efce9 100644
--- a/src/modules/module-waveout.c
+++ b/src/modules/module-waveout.c
@@ -637,8 +637,8 @@ int pa__init(pa_module *m) {
         pa_sink_new_data_done(&data);
 
         pa_assert(u->sink);
-        u->sink->get_volume = sink_get_volume_cb;
-        u->sink->set_volume = sink_set_volume_cb;
+        pa_sink_set_get_volume_callback(u->sink, sink_get_volume_cb);
+        pa_sink_set_set_volume_callback(u->sink, sink_set_volume_cb);
         u->sink->userdata = u;
         pa_sink_set_description(u->sink, description);
         u->sink->parent.process_msg = process_msg;
diff --git a/src/modules/oss/module-oss.c b/src/modules/oss/module-oss.c
index 2a99d11..a22b315 100644
--- a/src/modules/oss/module-oss.c
+++ b/src/modules/oss/module-oss.c
@@ -1423,8 +1423,8 @@ int pa__init(pa_module*m) {
             if (u->sink && (u->mixer_devmask & (SOUND_MASK_VOLUME|SOUND_MASK_PCM))) {
                 pa_log_debug("Found hardware mixer track for playback.");
                 u->sink->flags |= PA_SINK_HW_VOLUME_CTRL;
-                u->sink->get_volume = sink_get_volume;
-                u->sink->set_volume = sink_set_volume;
+                pa_sink_set_get_volume_callback(u->sink, sink_get_volume);
+                pa_sink_set_set_volume_callback(u->sink, sink_set_volume);
                 u->sink->n_volume_steps = 101;
                 do_close = FALSE;
             }
@@ -1432,8 +1432,8 @@ int pa__init(pa_module*m) {
             if (u->source && (u->mixer_devmask & (SOUND_MASK_RECLEV|SOUND_MASK_IGAIN))) {
                 pa_log_debug("Found hardware mixer track for recording.");
                 u->source->flags |= PA_SOURCE_HW_VOLUME_CTRL;
-                u->source->get_volume = source_get_volume;
-                u->source->set_volume = source_set_volume;
+                pa_source_set_get_volume_callback(u->source, source_get_volume);
+                pa_source_set_set_volume_callback(u->source, source_set_volume);
                 u->source->n_volume_steps = 101;
                 do_close = FALSE;
             }
diff --git a/src/modules/raop/module-raop-sink.c b/src/modules/raop/module-raop-sink.c
index 87e7bc1..2600441 100644
--- a/src/modules/raop/module-raop-sink.c
+++ b/src/modules/raop/module-raop-sink.c
@@ -594,8 +594,8 @@ int pa__init(pa_module*m) {
 
     u->sink->parent.process_msg = sink_process_msg;
     u->sink->userdata = u;
-    u->sink->set_volume = sink_set_volume_cb;
-    u->sink->set_mute = sink_set_mute_cb;
+    pa_sink_set_set_volume_callback(u->sink, sink_set_volume_cb);
+    pa_sink_set_set_mute_callback(u->sink, sink_set_mute_cb);
     u->sink->flags = PA_SINK_LATENCY|PA_SINK_NETWORK|PA_SINK_HW_VOLUME_CTRL;
 
     pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq);
diff --git a/src/pulsecore/sink.c b/src/pulsecore/sink.c
index 45761a6..f7e4b6a 100644
--- a/src/pulsecore/sink.c
+++ b/src/pulsecore/sink.c
@@ -449,6 +449,36 @@ static int sink_set_state(pa_sink *s, pa_sink_state_t state) {
     return 0;
 }
 
+void pa_sink_set_get_volume_callback(pa_sink *s, pa_sink_cb_t cb) {
+    pa_assert(s);
+
+    s->get_volume = cb;
+}
+
+void pa_sink_set_set_volume_callback(pa_sink *s, pa_sink_cb_t cb) {
+    pa_assert(s);
+
+    s->set_volume = cb;
+}
+
+void pa_sink_set_write_volume_callback(pa_sink *s, pa_sink_cb_t cb) {
+    pa_assert(s);
+
+    s->write_volume = cb;
+}
+
+void pa_sink_set_get_mute_callback(pa_sink *s, pa_sink_cb_t cb) {
+    pa_assert(s);
+
+    s->get_mute = cb;
+}
+
+void pa_sink_set_set_mute_callback(pa_sink *s, pa_sink_cb_t cb) {
+    pa_assert(s);
+
+    s->set_mute = cb;
+}
+
 /* Called from main context */
 void pa_sink_put(pa_sink* s) {
     pa_sink_assert_ref(s);
diff --git a/src/pulsecore/sink.h b/src/pulsecore/sink.h
index 85c22ec..669ce20 100644
--- a/src/pulsecore/sink.h
+++ b/src/pulsecore/sink.h
@@ -65,6 +65,9 @@ struct pa_device_port {
 
 #define PA_DEVICE_PORT_DATA(d) ((void*) ((uint8_t*) d + PA_ALIGN(sizeof(pa_device_port))))
 
+/* A generic definition for void callback functions */
+typedef void(*pa_sink_cb_t)(pa_sink *s);
+
 struct pa_sink {
     pa_msgobject parent;
 
@@ -141,8 +144,11 @@ struct pa_sink {
      * PA_SINK_MESSAGE_GET_VOLUME (the main thread is waiting while
      * the message is being processed), so there's no choice of where
      * to do the volume reading - it has to be done in the IO thread
-     * always. */
-    void (*get_volume)(pa_sink *s);             /* may be NULL */
+     * always.
+     *
+     * You must use the function pa_sink_set_get_volume_callback() to
+     * set this callback. */
+    pa_sink_cb_t get_volume; /* may be NULL */
 
     /* Sink drivers that support hardware volume must set this
      * callback. This is called when the hardware volume needs to be
@@ -161,8 +167,11 @@ struct pa_sink {
      * requested volume the hardware volume can be set, and update
      * s->real_volume and/or s->soft_volume so that they together
      * match the actual hardware volume that will be set later in the
-     * write_volume callback. */
-    void (*set_volume)(pa_sink *s);             /* ditto */
+     * write_volume callback.
+     *
+     * You must use the function pa_sink_set_set_volume_callback() to
+     * set this callback. */
+    pa_sink_cb_t set_volume; /* may be NULL */
 
     /* Sink drivers that set PA_SINK_SYNC_VOLUME must provide this
      * callback. This callback is not used with sinks that do not set
@@ -174,27 +183,36 @@ struct pa_sink {
      * The call is done inside pa_sink_volume_change_apply(), which is
      * not called automatically - it is the driver's responsibility to
      * schedule that function to be called at the right times in the
-     * IO thread. */
-    void (*write_volume)(pa_sink *s);           /* ditto */
+     * IO thread.
+     *
+     * You must use the function pa_sink_set_write_volume_callback() to
+     * set this callback. */
+    pa_sink_cb_t write_volume; /* may be NULL */
 
     /* Called when the mute setting is queried. A PA_SINK_MESSAGE_GET_MUTE
      * message will also be sent. Called from IO thread if PA_SINK_SYNC_VOLUME
      * flag is set otherwise from main loop context. If refresh_mute is FALSE
-     * neither this function is called nor a message is sent.*/
-    void (*get_mute)(pa_sink *s);               /* ditto */
+     * neither this function is called nor a message is sent.
+     *
+     * You must use the function pa_sink_set_get_mute_callback() to
+     * set this callback. */
+    pa_sink_cb_t get_mute; /* may be NULL */
 
     /* Called when the mute setting shall be changed. A PA_SINK_MESSAGE_SET_MUTE
      * message will also be sent. Called from IO thread if PA_SINK_SYNC_VOLUME
-     * flag is set otherwise from main loop context. */
-    void (*set_mute)(pa_sink *s);               /* ditto */
+     * flag is set otherwise from main loop context.
+     *
+     * You must use the function pa_sink_set_set_mute_callback() to
+     * set this callback. */
+    pa_sink_cb_t set_mute; /* may be NULL */
 
     /* Called when a rewind request is issued. Called from IO thread
      * context. */
-    void (*request_rewind)(pa_sink *s);        /* ditto */
+    pa_sink_cb_t request_rewind; /* may be NULL */
 
     /* Called when a the requested latency is changed. Called from IO
      * thread context. */
-    void (*update_requested_latency)(pa_sink *s); /* ditto */
+    pa_sink_cb_t update_requested_latency; /* may be NULL */
 
     /* Called whenever the port shall be changed. Called from main
      * thread. */
@@ -339,6 +357,12 @@ pa_sink* pa_sink_new(
         pa_sink_new_data *data,
         pa_sink_flags_t flags);
 
+void pa_sink_set_get_volume_callback(pa_sink *s, pa_sink_cb_t cb);
+void pa_sink_set_set_volume_callback(pa_sink *s, pa_sink_cb_t cb);
+void pa_sink_set_write_volume_callback(pa_sink *s, pa_sink_cb_t cb);
+void pa_sink_set_get_mute_callback(pa_sink *s, pa_sink_cb_t cb);
+void pa_sink_set_set_mute_callback(pa_sink *s, pa_sink_cb_t cb);
+
 void pa_sink_put(pa_sink *s);
 void pa_sink_unlink(pa_sink* s);
 
diff --git a/src/pulsecore/source.c b/src/pulsecore/source.c
index 7024802..1ec8027 100644
--- a/src/pulsecore/source.c
+++ b/src/pulsecore/source.c
@@ -381,6 +381,36 @@ static int source_set_state(pa_source *s, pa_source_state_t state) {
     return 0;
 }
 
+void pa_source_set_get_volume_callback(pa_source *s, pa_source_cb_t cb) {
+    pa_assert(s);
+
+    s->get_volume = cb;
+}
+
+void pa_source_set_set_volume_callback(pa_source *s, pa_source_cb_t cb) {
+    pa_assert(s);
+
+    s->set_volume = cb;
+}
+
+void pa_source_set_write_volume_callback(pa_source *s, pa_source_cb_t cb) {
+    pa_assert(s);
+
+    s->write_volume = cb;
+}
+
+void pa_source_set_get_mute_callback(pa_source *s, pa_source_cb_t cb) {
+    pa_assert(s);
+
+    s->get_mute = cb;
+}
+
+void pa_source_set_set_mute_callback(pa_source *s, pa_source_cb_t cb) {
+    pa_assert(s);
+
+    s->set_mute = cb;
+}
+
 /* Called from main context */
 void pa_source_put(pa_source *s) {
     pa_source_assert_ref(s);
diff --git a/src/pulsecore/source.h b/src/pulsecore/source.h
index b68dfd5..52186cd 100644
--- a/src/pulsecore/source.h
+++ b/src/pulsecore/source.h
@@ -53,6 +53,9 @@ static inline pa_bool_t PA_SOURCE_IS_LINKED(pa_source_state_t x) {
     return x == PA_SOURCE_RUNNING || x == PA_SOURCE_IDLE || x == PA_SOURCE_SUSPENDED;
 }
 
+/* A generic definition for void callback functions */
+typedef void(*pa_source_cb_t)(pa_source *s);
+
 struct pa_source {
     pa_msgobject parent;
 
@@ -111,13 +114,19 @@ struct pa_source {
     /* Callled when the volume is queried. Called from main loop
      * context. If this is NULL a PA_SOURCE_MESSAGE_GET_VOLUME message
      * will be sent to the IO thread instead. If refresh_volume is
-     * FALSE neither this function is called nor a message is sent. */
-    void (*get_volume)(pa_source *s);         /* ditto */
+     * FALSE neither this function is called nor a message is sent.
+     *
+     * You must use the function pa_source_set_get_volume_callback() to
+     * set this callback. */
+    pa_source_cb_t get_volume; /* may be NULL */
 
     /* Called when the volume shall be changed. Called from main loop
      * context. If this is NULL a PA_SOURCE_MESSAGE_SET_VOLUME message
-     * will be sent to the IO thread instead. */
-    void (*set_volume)(pa_source *s);         /* ditto */
+     * will be sent to the IO thread instead.
+     *
+     * You must use the function pa_source_set_set_volume_callback() to
+     * set this callback. */
+    pa_source_cb_t set_volume; /* may be NULL */
 
     /* Source drivers that set PA_SOURCE_SYNC_VOLUME must provide this
      * callback. This callback is not used with source that do not set
@@ -129,23 +138,32 @@ struct pa_source {
      * The call is done inside pa_source_volume_change_apply(), which is
      * not called automatically - it is the driver's responsibility to
      * schedule that function to be called at the right times in the
-     * IO thread. */
-    void (*write_volume)(pa_source *s);       /* ditto */
+     * IO thread.
+     *
+     * You must use the function pa_source_set_write_volume_callback() to
+     * set this callback. */
+    pa_source_cb_t write_volume; /* may be NULL */
 
     /* Called when the mute setting is queried. Called from main loop
      * context. If this is NULL a PA_SOURCE_MESSAGE_GET_MUTE message
      * will be sent to the IO thread instead. If refresh_mute is
-     * FALSE neither this function is called nor a message is sent.*/
-    void (*get_mute)(pa_source *s);           /* ditto */
+     * FALSE neither this function is called nor a message is sent.
+     *
+     * You must use the function pa_source_set_get_mute_callback() to
+     * set this callback. */
+    pa_source_cb_t get_mute; /* may be NULL */
 
     /* Called when the mute setting shall be changed. Called from main
      * loop context. If this is NULL a PA_SOURCE_MESSAGE_SET_MUTE
-     * message will be sent to the IO thread instead. */
-    void (*set_mute)(pa_source *s);           /* ditto */
+     * message will be sent to the IO thread instead.
+     *
+     * You must use the function pa_source_set_set_mute_callback() to
+     * set this callback. */
+    pa_source_cb_t set_mute; /* may be NULL */
 
     /* Called when a the requested latency is changed. Called from IO
      * thread context. */
-    void (*update_requested_latency)(pa_source *s); /* ditto */
+    pa_source_cb_t update_requested_latency; /* may be NULL */
 
     /* Called whenever the port shall be changed. Called from main
      * thread. */
@@ -270,6 +288,12 @@ pa_source* pa_source_new(
         pa_source_new_data *data,
         pa_source_flags_t flags);
 
+void pa_source_set_get_volume_callback(pa_source *s, pa_source_cb_t cb);
+void pa_source_set_set_volume_callback(pa_source *s, pa_source_cb_t cb);
+void pa_source_set_write_volume_callback(pa_source *s, pa_source_cb_t cb);
+void pa_source_set_get_mute_callback(pa_source *s, pa_source_cb_t cb);
+void pa_source_set_set_mute_callback(pa_source *s, pa_source_cb_t cb);
+
 void pa_source_put(pa_source *s);
 void pa_source_unlink(pa_source *s);
 



More information about the pulseaudio-commits mailing list