[pulseaudio-commits] [SCM] PulseAudio Sound Server branch, master, updated. v0.9.13-401-gd5f46e8

Lennart Poettering gitmailer-noreply at 0pointer.de
Mon Jan 26 20:04:07 PST 2009


This is an automated email from the git hooks/post-receive script. It was
generated because of a push to the "PulseAudio Sound Server" repository.

The master branch has been updated
      from  afd817a0b607d732a7bfd2b31cef8a5b8cf9ab09 (commit)

- Log -----------------------------------------------------------------
d5f46e8... move flat volume logic into the core. while doing so add n_volume_steps field to sinks/sources
4bfa5d7... fix size calculation
eca3223... get rid of module-flat-volumes since we are moving this into the core
1be39e4... allow samples to be played with 'default' (i.e. unspecified) volume.
5449d79... swap argument order of pa_cvolume_get_balance() to be a bit more systematic
df8ad5d... add a few missing doxygen comments
6058530... import version.h in all header files to make sure that version-based feature testing works
1249cf6... always define PA_MAJOR/PA_MINOR/PA_MICRO to ease feature checking in client applications
948be36... invert an ill-placed assert
0658d9a... show pretty channel map name if possible
07db64b... remove redundant cast
9ba4084... store requested resampling method in a seperate field and use it when create a new resampler after a move
ccd21f3... make a few comments appear in doxygen
3bcbe1d... check for availability of RLIMIT_NOFILE and RLIMIT_AS before we make use of it
4e31e00... implement pa_cvolume_scale()
e52c5ea... implement new API functions pa_channel_map_can_balance(), pa_channel_map_to_name() and pa_channel_map_to_pretty_name()
24b3a74... add a bitset implementation
-----------------------------------------------------------------------

Summary of changes:
 configure.ac                                    |   15 +-
 man/pulse-daemon.conf.5.xml.in                  |    9 +-
 src/Makefile.am                                 |   11 +-
 src/daemon/daemon-conf.c                        |   24 ++-
 src/daemon/daemon-conf.h                        |   12 +-
 src/daemon/daemon.conf.in                       |    2 +
 src/daemon/default.pa.in                        |    3 -
 src/daemon/main.c                               |    3 +
 src/map-file                                    |    4 +
 src/modules/alsa/alsa-sink.c                    |   43 ++---
 src/modules/alsa/alsa-source.c                  |   43 ++---
 src/modules/bluetooth/module-bluetooth-device.c |    2 +-
 src/modules/module-flat-volume.c                |  224 --------------------
 src/modules/module-lirc.c                       |    4 +-
 src/modules/module-mmkbd-evdev.c                |    4 +-
 src/modules/module-position-event-sounds.c      |   16 +-
 src/modules/module-raop-sink.c                  |   32 +--
 src/modules/module-stream-restore.c             |    4 +-
 src/modules/module-tunnel.c                     |   16 +-
 src/modules/module-volume-restore.c             |    2 +-
 src/modules/oss/module-oss.c                    |   62 +++----
 src/pulse/browser.h                             |    1 +
 src/pulse/channelmap.c                          |  187 ++++++++++++++++-
 src/pulse/channelmap.h                          |   22 ++-
 src/pulse/context.h                             |    5 +-
 src/pulse/def.h                                 |    8 +-
 src/pulse/error.h                               |    1 +
 src/pulse/ext-stream-restore.h                  |    1 +
 src/pulse/gccmacro.h                            |    4 +
 src/pulse/glib-mainloop.h                       |    1 +
 src/pulse/i18n.h                                |    1 +
 src/pulse/introspect.c                          |    8 +-
 src/pulse/introspect.h                          |    7 +-
 src/pulse/mainloop-api.h                        |    1 +
 src/pulse/mainloop-signal.h                     |    2 +
 src/pulse/operation.h                           |    1 +
 src/pulse/proplist.h                            |    3 +-
 src/pulse/sample.h                              |    1 +
 src/pulse/scache.c                              |    8 +
 src/pulse/scache.h                              |    5 +-
 src/pulse/simple.h                              |    1 +
 src/pulse/subscribe.h                           |    1 +
 src/pulse/thread-mainloop.h                     |    1 +
 src/pulse/timeval.h                             |   12 +
 src/pulse/utf8.h                                |    1 +
 src/pulse/util.h                                |    1 +
 src/pulse/version.h.in                          |    9 +
 src/pulse/volume.c                              |   23 ++-
 src/pulse/volume.h                              |   12 +-
 src/pulse/xmalloc.h                             |    1 +
 src/pulsecore/{semaphore-win32.c => bitset.c}   |   58 +++---
 src/pulsecore/{sioman.c => bitset.h}            |   26 +--
 src/pulsecore/cli-command.c                     |    2 +-
 src/pulsecore/cli-text.c                        |   54 ++++-
 src/pulsecore/core-scache.c                     |   30 ++-
 src/pulsecore/core-scache.h                     |    1 +
 src/pulsecore/core.c                            |    1 +
 src/pulsecore/core.h                            |    2 +-
 src/pulsecore/play-memblockq.c                  |    2 +-
 src/pulsecore/protocol-native.c                 |   15 +-
 src/pulsecore/sink-input.c                      |  167 ++++++++++-----
 src/pulsecore/sink-input.h                      |   46 ++---
 src/pulsecore/sink.c                            |  257 +++++++++++++++++------
 src/pulsecore/sink.h                            |   41 ++--
 src/pulsecore/sound-file-stream.c               |    2 +-
 src/pulsecore/source-output.c                   |   21 +-
 src/pulsecore/source-output.h                   |    4 +-
 src/pulsecore/source.c                          |   85 ++++----
 src/pulsecore/source.h                          |   27 ++-
 src/tests/voltest.c                             |    8 +-
 src/utils/pactl.c                               |    8 +-
 71 files changed, 1023 insertions(+), 698 deletions(-)
 delete mode 100644 src/modules/module-flat-volume.c
 copy src/pulsecore/{semaphore-win32.c => bitset.c} (51%)
 copy src/pulsecore/{sioman.c => bitset.h} (64%)

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

commit 24b3a743bd8a18741b2c1e0370f18afb90ed1ea5
Author: Lennart Poettering <lennart at poettering.net>
Date:   Tue Jan 27 00:44:45 2009 +0100

    add a bitset implementation

diff --git a/src/Makefile.am b/src/Makefile.am
index d82d8a6..f313bb5 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -556,6 +556,7 @@ libpulsecommon_ at PA_MAJORMINORMICRO@_la_SOURCES = \
 		pulsecore/refcnt.h \
 		pulsecore/rtclock.c pulsecore/rtclock.h \
 		pulsecore/shm.c pulsecore/shm.h \
+		pulsecore/bitset.c pulsecore/bitset.h \
 		pulsecore/socket-client.c pulsecore/socket-client.h \
 		pulsecore/socket-server.c pulsecore/socket-server.h \
 		pulsecore/socket-util.c pulsecore/socket-util.h \
diff --git a/src/pulsecore/bitset.c b/src/pulsecore/bitset.c
new file mode 100644
index 0000000..4beeb1c
--- /dev/null
+++ b/src/pulsecore/bitset.c
@@ -0,0 +1,67 @@
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2009 Lennart Poettering
+
+  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 of the License,
+  or (at your option) any later version.
+
+  PulseAudio is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with PulseAudio; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+  USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include <pulse/xmalloc.h>
+
+#include "bitset.h"
+
+void pa_bitset_set(pa_bitset_t *b, unsigned k, pa_bool_t v) {
+    pa_assert(b);
+
+    if (v)
+        b[k >> 5] |= 1 << (k & 31);
+    else
+        b[k >> 5] &= ~((uint32_t) (1 << (k & 31)));
+}
+
+pa_bool_t pa_bitset_get(const pa_bitset_t *b, unsigned k) {
+    return !!(b[k >> 5] & (1 << (k & 31)));
+}
+
+pa_bool_t pa_bitset_equals(const pa_bitset_t *b, unsigned n, ...) {
+    va_list ap;
+    pa_bitset_t *a;
+    pa_bool_t equal;
+
+    a = pa_xnew0(pa_bitset_t, PA_BITSET_ELEMENTS(n));
+
+    va_start(ap, n);
+    for (;;) {
+        int j = va_arg(ap, int);
+
+        if (j < 0)
+            break;
+
+        pa_bitset_set(a, j, TRUE);
+    }
+    va_end(ap);
+
+    equal = memcmp(a, b, PA_BITSET_SIZE(n)) == 0;
+    pa_xfree(a);
+
+    return equal;
+}
diff --git a/src/pulsecore/bitset.h b/src/pulsecore/bitset.h
new file mode 100644
index 0000000..21e840a
--- /dev/null
+++ b/src/pulsecore/bitset.h
@@ -0,0 +1,37 @@
+#ifndef foopulsecorebitsethfoo
+#define foopulsecorebitsethfoo
+
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2009 Lennart Poettering
+
+  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 of the License,
+  or (at your option) any later version.
+
+  PulseAudio is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with PulseAudio; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+  USA.
+***/
+
+#include <inttypes.h>
+#include <pulsecore/macro.h>
+
+#define PA_BITSET_ELEMENTS(n) (((n)+31)/32)
+#define PA_BITSET_SIZE(n) (PA_BITSET_ELEMENTS(n)*32)
+
+typedef uint32_t pa_bitset_t;
+
+void pa_bitset_set(pa_bitset_t *b, unsigned k, pa_bool_t v);
+pa_bool_t pa_bitset_get(const pa_bitset_t *b, unsigned k);
+pa_bool_t pa_bitset_equals(const pa_bitset_t *b, unsigned n, ...);
+
+#endif

commit e52c5ea68a9c3bf6e7c4b30cb6c2d4706f214cd3
Author: Lennart Poettering <lennart at poettering.net>
Date:   Tue Jan 27 00:46:39 2009 +0100

    implement new API functions pa_channel_map_can_balance(), pa_channel_map_to_name() and pa_channel_map_to_pretty_name()

diff --git a/src/map-file b/src/map-file
index 55b13e1..cb5c749 100644
--- a/src/map-file
+++ b/src/map-file
@@ -9,6 +9,7 @@ pa_browser_unref;
 pa_bytes_per_second;
 pa_bytes_snprint;
 pa_bytes_to_usec;
+pa_channel_map_can_balance;
 pa_channel_map_compatible;
 pa_channel_map_equal;
 pa_channel_map_init;
@@ -19,6 +20,8 @@ pa_channel_map_init_stereo;
 pa_channel_map_parse;
 pa_channel_map_snprint;
 pa_channel_map_superset;
+pa_channel_map_to_name;
+pa_channel_map_to_pretty_name;
 pa_channel_map_valid;
 pa_channel_position_to_pretty_string;
 pa_channel_position_to_string;
diff --git a/src/pulse/channelmap.c b/src/pulse/channelmap.c
index 26eae59..455bda1 100644
--- a/src/pulse/channelmap.c
+++ b/src/pulse/channelmap.c
@@ -32,6 +32,7 @@
 #include <pulse/i18n.h>
 #include <pulsecore/core-util.h>
 #include <pulsecore/macro.h>
+#include <pulsecore/bitset.h>
 
 #include "channelmap.h"
 
@@ -497,11 +498,58 @@ pa_channel_map *pa_channel_map_parse(pa_channel_map *rmap, const char *s) {
 
     pa_channel_map_init(&map);
 
+    /* We don't need to match against the well known channel mapping
+     * "mono" here explicitly, because that can be understood as
+     * listing with one channel called "mono". */
+
     if (strcmp(s, "stereo") == 0) {
         map.channels = 2;
         map.map[0] = PA_CHANNEL_POSITION_LEFT;
         map.map[1] = PA_CHANNEL_POSITION_RIGHT;
         goto finish;
+    } else if (strcmp(s, "surround-40") == 0) {
+        map.channels = 4;
+        map.map[0] = PA_CHANNEL_POSITION_FRONT_LEFT;
+        map.map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT;
+        map.map[2] = PA_CHANNEL_POSITION_REAR_LEFT;
+        map.map[3] = PA_CHANNEL_POSITION_REAR_RIGHT;
+        goto finish;
+    } else if (strcmp(s, "surround-41") == 0) {
+        map.channels = 5;
+        map.map[0] = PA_CHANNEL_POSITION_FRONT_LEFT;
+        map.map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT;
+        map.map[2] = PA_CHANNEL_POSITION_REAR_LEFT;
+        map.map[3] = PA_CHANNEL_POSITION_REAR_RIGHT;
+        map.map[4] = PA_CHANNEL_POSITION_LFE;
+        goto finish;
+    } else if (strcmp(s, "surround-50") == 0) {
+        map.channels = 5;
+        map.map[0] = PA_CHANNEL_POSITION_FRONT_LEFT;
+        map.map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT;
+        map.map[2] = PA_CHANNEL_POSITION_REAR_LEFT;
+        map.map[3] = PA_CHANNEL_POSITION_REAR_RIGHT;
+        map.map[4] = PA_CHANNEL_POSITION_FRONT_CENTER;
+        goto finish;
+    } else if (strcmp(s, "surround-51") == 0) {
+        map.channels = 6;
+        map.map[0] = PA_CHANNEL_POSITION_FRONT_LEFT;
+        map.map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT;
+        map.map[2] = PA_CHANNEL_POSITION_REAR_LEFT;
+        map.map[3] = PA_CHANNEL_POSITION_REAR_RIGHT;
+        map.map[4] = PA_CHANNEL_POSITION_FRONT_CENTER;
+        map.map[5] = PA_CHANNEL_POSITION_LFE;
+        goto finish;
+    } else if (strcmp(s, "surround-71") == 0) {
+        map.channels = 8;
+        map.map[0] = PA_CHANNEL_POSITION_FRONT_LEFT;
+        map.map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT;
+        map.map[2] = PA_CHANNEL_POSITION_REAR_LEFT;
+        map.map[3] = PA_CHANNEL_POSITION_REAR_RIGHT;
+        map.map[4] = PA_CHANNEL_POSITION_FRONT_CENTER;
+        map.map[5] = PA_CHANNEL_POSITION_LFE;
+        map.map[6] = PA_CHANNEL_POSITION_SIDE_LEFT;
+        map.map[7] = PA_CHANNEL_POSITION_SIDE_RIGHT;
+        goto finish;
     }
 
     state = NULL;
@@ -579,7 +627,7 @@ int pa_channel_map_compatible(const pa_channel_map *map, const pa_sample_spec *s
 }
 
 int pa_channel_map_superset(const pa_channel_map *a, const pa_channel_map *b) {
-    pa_bool_t in_a[PA_CHANNEL_POSITION_MAX];
+    pa_bitset_t in_a[PA_BITSET_ELEMENTS(PA_CHANNEL_POSITION_MAX)];
     unsigned i;
 
     pa_assert(a);
@@ -588,11 +636,144 @@ int pa_channel_map_superset(const pa_channel_map *a, const pa_channel_map *b) {
     memset(in_a, 0, sizeof(in_a));
 
     for (i = 0; i < a->channels; i++)
-        in_a[a->map[i]] = TRUE;
+        pa_bitset_set(in_a, a->map[i], TRUE);
 
     for (i = 0; i < b->channels; i++)
-        if (!in_a[b->map[i]])
+        if (!pa_bitset_get(in_a, b->map[i]))
             return 0;
 
     return 1;
 }
+
+int pa_channel_map_can_balance(const pa_channel_map *map) {
+    unsigned c;
+
+    pa_assert(map);
+
+    for (c = 0; c < map->channels; c++)
+
+        switch (map->map[c]) {
+            case PA_CHANNEL_POSITION_LEFT:
+            case PA_CHANNEL_POSITION_RIGHT:
+            case PA_CHANNEL_POSITION_REAR_LEFT:
+            case PA_CHANNEL_POSITION_REAR_RIGHT:
+            case PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER:
+            case PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER:
+            case PA_CHANNEL_POSITION_SIDE_LEFT:
+            case PA_CHANNEL_POSITION_SIDE_RIGHT:
+            case PA_CHANNEL_POSITION_TOP_FRONT_LEFT:
+            case PA_CHANNEL_POSITION_TOP_FRONT_RIGHT:
+            case PA_CHANNEL_POSITION_TOP_REAR_LEFT:
+            case PA_CHANNEL_POSITION_TOP_REAR_RIGHT:
+                return 1;
+
+            default:
+                ;
+        }
+
+    return 0;
+}
+
+const char* pa_channel_map_to_name(const pa_channel_map *map) {
+    pa_bitset_t in_map[PA_BITSET_ELEMENTS(PA_CHANNEL_POSITION_MAX)];
+    unsigned c;
+
+    pa_assert(map);
+
+    memset(in_map, 0, sizeof(in_map));
+
+    for (c = 0; c < map->channels; c++)
+        pa_bitset_set(in_map, map->map[c], TRUE);
+
+    if (pa_bitset_equals(in_map, PA_CHANNEL_POSITION_MAX,
+                         PA_CHANNEL_POSITION_MONO, -1))
+        return "mono";
+
+    if (pa_bitset_equals(in_map, PA_CHANNEL_POSITION_MAX,
+                         PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT, -1))
+        return "stereo";
+
+    if (pa_bitset_equals(in_map, PA_CHANNEL_POSITION_MAX,
+                         PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT,
+                         PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, -1))
+        return "surround-40";
+
+    if (pa_bitset_equals(in_map, PA_CHANNEL_POSITION_MAX,
+                         PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT,
+                         PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT,
+                         PA_CHANNEL_POSITION_LFE, -1))
+        return "surround-41";
+
+    if (pa_bitset_equals(in_map, PA_CHANNEL_POSITION_MAX,
+                         PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT,
+                         PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT,
+                         PA_CHANNEL_POSITION_FRONT_CENTER, -1))
+        return "surround-50";
+
+    if (pa_bitset_equals(in_map, PA_CHANNEL_POSITION_MAX,
+                         PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT,
+                         PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT,
+                         PA_CHANNEL_POSITION_FRONT_CENTER, PA_CHANNEL_POSITION_LFE, -1))
+        return "surround-51";
+
+    if (pa_bitset_equals(in_map, PA_CHANNEL_POSITION_MAX,
+                         PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT,
+                         PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT,
+                         PA_CHANNEL_POSITION_FRONT_CENTER, PA_CHANNEL_POSITION_LFE,
+                         PA_CHANNEL_POSITION_SIDE_LEFT, PA_CHANNEL_POSITION_SIDE_RIGHT, -1))
+        return "surround-71";
+
+    return NULL;
+}
+
+const char* pa_channel_map_to_pretty_name(const pa_channel_map *map) {
+    pa_bitset_t in_map[PA_BITSET_ELEMENTS(PA_CHANNEL_POSITION_MAX)];
+    unsigned c;
+
+    pa_assert(map);
+
+    memset(in_map, 0, sizeof(in_map));
+
+    for (c = 0; c < map->channels; c++)
+        pa_bitset_set(in_map, map->map[c], TRUE);
+
+    if (pa_bitset_equals(in_map, PA_CHANNEL_POSITION_MAX,
+                         PA_CHANNEL_POSITION_MONO, -1))
+        return _("Mono");
+
+    if (pa_bitset_equals(in_map, PA_CHANNEL_POSITION_MAX,
+                         PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT, -1))
+        return _("Stereo");
+
+    if (pa_bitset_equals(in_map, PA_CHANNEL_POSITION_MAX,
+                         PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT,
+                         PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, -1))
+        return _("Surround 4.0");
+
+    if (pa_bitset_equals(in_map, PA_CHANNEL_POSITION_MAX,
+                         PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT,
+                         PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT,
+                         PA_CHANNEL_POSITION_LFE, -1))
+        return _("Surround 4.1");
+
+    if (pa_bitset_equals(in_map, PA_CHANNEL_POSITION_MAX,
+                         PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT,
+                         PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT,
+                         PA_CHANNEL_POSITION_FRONT_CENTER, -1))
+        return _("Surround 5.0");
+
+    if (pa_bitset_equals(in_map, PA_CHANNEL_POSITION_MAX,
+                         PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT,
+                         PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT,
+                         PA_CHANNEL_POSITION_FRONT_CENTER, PA_CHANNEL_POSITION_LFE, -1))
+        return _("Surround 5.1");
+
+    if (pa_bitset_equals(in_map, PA_CHANNEL_POSITION_MAX,
+                         PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT,
+                         PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT,
+                         PA_CHANNEL_POSITION_FRONT_CENTER, PA_CHANNEL_POSITION_LFE,
+                         PA_CHANNEL_POSITION_SIDE_LEFT, PA_CHANNEL_POSITION_SIDE_RIGHT, -1))
+        return _("Surround 7.1");
+
+    return NULL;
+}
diff --git a/src/pulse/channelmap.h b/src/pulse/channelmap.h
index a6c044f..6e92e76 100644
--- a/src/pulse/channelmap.h
+++ b/src/pulse/channelmap.h
@@ -214,7 +214,10 @@ const char* pa_channel_position_to_pretty_string(pa_channel_position_t pos);
 /** Make a humand readable string from the specified channel map */
 char* pa_channel_map_snprint(char *s, size_t l, const pa_channel_map *map);
 
-/** Parse a channel position list into a channel map structure. */
+/** Parse a channel position list or well-known mapping name into a
+ * channel map structure. This turns the output of
+ * pa_channel_map_snprint() and pa_channel_map_to_name() back into a
+ * pa_channel_map */
 pa_channel_map *pa_channel_map_parse(pa_channel_map *map, const char *s);
 
 /** Compare two channel maps. Return 1 if both match. */
@@ -230,6 +233,22 @@ int pa_channel_map_compatible(const pa_channel_map *map, const pa_sample_spec *s
 /** Returns non-zero if every channel defined in b is also defined in a. \since 0.9.15 */
 int pa_channel_map_superset(const pa_channel_map *a, const pa_channel_map *b) PA_GCC_PURE;
 
+/** Returns non-zero if it makes sense to apply a volume 'balance'
+ * with this mapping, i.e. if there are left/right channels
+ * available. \since 0.9.15 */
+int pa_channel_map_can_balance(const pa_channel_map *map) PA_GCC_PURE;
+
+/** Tries to find a well-known channel mapping name for this channel
+ * mapping. I.e. "stereo", "surround-71" and so on. If the channel
+ * mapping is unknown NULL will be returned. This name can be parsed
+ * with pa_channel_map_parse() \since 0.9.15 */
+const char* pa_channel_map_to_name(const pa_channel_map *map) PA_GCC_PURE;
+
+/** Tries to find a human readable text label for this channel
+mapping. I.e. "Stereo", "Surround 7.1" and so on. If the channel
+mapping is unknown NULL will be returned. \since 0.9.15 */
+const char* pa_channel_map_to_pretty_name(const pa_channel_map *map) PA_GCC_PURE;
+
 PA_C_DECL_END
 
 #endif

commit 4e31e00b63117f36df6b8ed4850e7ad6264e3da7
Author: Lennart Poettering <lennart at poettering.net>
Date:   Tue Jan 27 00:47:07 2009 +0100

    implement pa_cvolume_scale()

diff --git a/src/map-file b/src/map-file
index cb5c749..d613759 100644
--- a/src/map-file
+++ b/src/map-file
@@ -113,6 +113,7 @@ pa_cvolume_get_balance;
 pa_cvolume_init;
 pa_cvolume_max;
 pa_cvolume_remap;
+pa_cvolume_scale;
 pa_cvolume_set;
 pa_cvolume_set_balance;
 pa_cvolume_snprint;
diff --git a/src/pulse/volume.c b/src/pulse/volume.c
index 10a44da..2c97784 100644
--- a/src/pulse/volume.c
+++ b/src/pulse/volume.c
@@ -500,3 +500,22 @@ pa_cvolume* pa_cvolume_set_balance(const pa_channel_map *map, pa_cvolume *v, flo
 
     return v;
 }
+
+pa_cvolume* pa_cvolume_scale(pa_cvolume *v, pa_volume_t max) {
+    unsigned c;
+    pa_volume_t t = 0;
+
+    pa_assert(c);
+
+    for (c = 0; c < v->channels; c++)
+        if (v->values[c] > t)
+            t = v->values[c];
+
+    if (t <= 0)
+        return pa_cvolume_set(v, v->channels, max);
+
+    for (c = 0; c < v->channels; c++)
+        v->values[c] = (pa_volume_t) (((uint64_t)  v->values[c] * (uint64_t) max) / (uint64_t) t);
+
+    return v;
+}
diff --git a/src/pulse/volume.h b/src/pulse/volume.h
index 38da5df..c8b73f4 100644
--- a/src/pulse/volume.h
+++ b/src/pulse/volume.h
@@ -244,9 +244,14 @@ float pa_cvolume_get_balance(const pa_channel_map *map, const pa_cvolume *v) PA_
  * operation might not be reversable! Also, after this call
  * pa_cvolume_get_balance() is not guaranteed to actually return the
  * requested balance (e.g. when the input volume was zero anyway for
- * all channels)- \since 0.9.15 */
+ * all channels) \since 0.9.15 */
 pa_cvolume* pa_cvolume_set_balance(const pa_channel_map *map, pa_cvolume *v, float new_balance);
 
+/** Scale the passed pa_cvolume structure so that the maximum volume
+ * of all channels equals max. The proportions between the channel
+ * volumes are kept. \since 0.9.15 */
+pa_cvolume* pa_cvolume_scale(pa_cvolume *v, pa_volume_t max);
+
 PA_C_DECL_END
 
 #endif

commit 3bcbe1d18f08667ca7b9d690e5c211c25831e0ad
Author: Lennart Poettering <lennart at poettering.net>
Date:   Tue Jan 27 00:48:53 2009 +0100

    check for availability of RLIMIT_NOFILE and RLIMIT_AS before we make use of it

diff --git a/src/daemon/daemon-conf.c b/src/daemon/daemon-conf.c
index c3abc09..c8c1b6f 100644
--- a/src/daemon/daemon-conf.c
+++ b/src/daemon/daemon-conf.c
@@ -97,11 +97,15 @@ static const pa_daemon_conf default_conf = {
 #ifdef RLIMIT_NPROC
    ,.rlimit_nproc = { .value = 0, .is_set = FALSE }
 #endif
+#ifdef RLIMIT_NOFILE
    ,.rlimit_nofile = { .value = 256, .is_set = TRUE }
+#endif
 #ifdef RLIMIT_MEMLOCK
    ,.rlimit_memlock = { .value = 0, .is_set = FALSE }
 #endif
+#ifdef RLIMIT_AS
    ,.rlimit_as = { .value = 0, .is_set = FALSE }
+#endif
 #ifdef RLIMIT_LOCKS
    ,.rlimit_locks = { .value = 0, .is_set = FALSE }
 #endif
@@ -442,8 +446,12 @@ int pa_daemon_conf_load(pa_daemon_conf *c, const char *filename) {
         { "rlimit-stack",               parse_rlimit,             NULL },
         { "rlimit-core",                parse_rlimit,             NULL },
         { "rlimit-rss",                 parse_rlimit,             NULL },
+#ifdef RLIMIT_NOFILE
         { "rlimit-nofile",              parse_rlimit,             NULL },
+#endif
+#ifdef RLIMIT_AS
         { "rlimit-as",                  parse_rlimit,             NULL },
+#endif
 #ifdef RLIMIT_NPROC
         { "rlimit-nproc",               parse_rlimit,             NULL },
 #endif
@@ -508,10 +516,14 @@ int pa_daemon_conf_load(pa_daemon_conf *c, const char *filename) {
     table[i++].data = &c->rlimit_fsize;
     table[i++].data = &c->rlimit_data;
     table[i++].data = &c->rlimit_stack;
-    table[i++].data = &c->rlimit_as;
     table[i++].data = &c->rlimit_core;
+    table[i++].data = &c->rlimit_rss;
+#ifdef RLIMIT_NOFILE
     table[i++].data = &c->rlimit_nofile;
+#endif
+#ifdef RLIMIT_AS
     table[i++].data = &c->rlimit_as;
+#endif
 #ifdef RLIMIT_NPROC
     table[i++].data = &c->rlimit_nproc;
 #endif
@@ -662,12 +674,16 @@ char *pa_daemon_conf_dump(pa_daemon_conf *c) {
     pa_strbuf_printf(s, "rlimit-data = %li\n", c->rlimit_data.is_set ? (long int) c->rlimit_data.value : -1);
     pa_strbuf_printf(s, "rlimit-stack = %li\n", c->rlimit_stack.is_set ? (long int) c->rlimit_stack.value : -1);
     pa_strbuf_printf(s, "rlimit-core = %li\n", c->rlimit_core.is_set ? (long int) c->rlimit_core.value : -1);
-    pa_strbuf_printf(s, "rlimit-as = %li\n", c->rlimit_as.is_set ? (long int) c->rlimit_as.value : -1);
     pa_strbuf_printf(s, "rlimit-rss = %li\n", c->rlimit_rss.is_set ? (long int) c->rlimit_rss.value : -1);
+#ifdef RLIMIT_AS
+    pa_strbuf_printf(s, "rlimit-as = %li\n", c->rlimit_as.is_set ? (long int) c->rlimit_as.value : -1);
+#endif
 #ifdef RLIMIT_NPROC
     pa_strbuf_printf(s, "rlimit-nproc = %li\n", c->rlimit_nproc.is_set ? (long int) c->rlimit_nproc.value : -1);
 #endif
+#ifdef RLIMIT_NOFILE
     pa_strbuf_printf(s, "rlimit-nofile = %li\n", c->rlimit_nofile.is_set ? (long int) c->rlimit_nofile.value : -1);
+#endif
 #ifdef RLIMIT_MEMLOCK
     pa_strbuf_printf(s, "rlimit-memlock = %li\n", c->rlimit_memlock.is_set ? (long int) c->rlimit_memlock.value : -1);
 #endif
diff --git a/src/daemon/daemon-conf.h b/src/daemon/daemon-conf.h
index fffa35e..3b75b83 100644
--- a/src/daemon/daemon-conf.h
+++ b/src/daemon/daemon-conf.h
@@ -84,7 +84,14 @@ typedef struct pa_daemon_conf {
     char *config_file;
 
 #ifdef HAVE_SYS_RESOURCE_H
-    pa_rlimit rlimit_fsize, rlimit_data, rlimit_stack, rlimit_core, rlimit_rss, rlimit_nofile, rlimit_as;
+    pa_rlimit rlimit_fsize, rlimit_data, rlimit_stack, rlimit_core, rlimit_rss;
+
+#ifdef RLIMIT_NOFILE
+    pa_rlimit rlimit_nofile;
+#endif
+#ifdef RLIMIT_AS
+    pa_rlimit rlimit_as;
+#endif
 #ifdef RLIMIT_NPROC
     pa_rlimit rlimit_nproc;
 #endif
diff --git a/src/daemon/main.c b/src/daemon/main.c
index f483607..9705f4c 100644
--- a/src/daemon/main.c
+++ b/src/daemon/main.c
@@ -298,7 +298,9 @@ static void set_all_rlimits(const pa_daemon_conf *conf) {
 #ifdef RLIMIT_NPROC
     set_one_rlimit(&conf->rlimit_nproc, RLIMIT_NPROC, "RLIMIT_NPROC");
 #endif
+#ifdef RLIMIT_NOFILE
     set_one_rlimit(&conf->rlimit_nofile, RLIMIT_NOFILE, "RLIMIT_NOFILE");
+#endif
 #ifdef RLIMIT_MEMLOCK
     set_one_rlimit(&conf->rlimit_memlock, RLIMIT_MEMLOCK, "RLIMIT_MEMLOCK");
 #endif

commit ccd21f33cf254cad6348769b42646fe8b0c5d0a5
Author: Lennart Poettering <lennart at poettering.net>
Date:   Tue Jan 27 00:49:58 2009 +0100

    make a few comments appear in doxygen

diff --git a/src/pulse/context.h b/src/pulse/context.h
index 3b51397..fd791b9 100644
--- a/src/pulse/context.h
+++ b/src/pulse/context.h
@@ -232,14 +232,14 @@ uint32_t pa_context_get_protocol_version(pa_context *c);
 /** Return the protocol version of the connected server. */
 uint32_t pa_context_get_server_protocol_version(pa_context *c);
 
-/* Update the property list of the client, adding new entries. Please
+/** Update the property list of the client, adding new entries. Please
  * note that it is highly recommended to set as much properties
  * initially via pa_context_new_with_proplist() as possible instead a
  * posteriori with this function, since that information may then be
  * used to route streams of the client to the right device. \since 0.9.11 */
 pa_operation *pa_context_proplist_update(pa_context *c, pa_update_mode_t mode, pa_proplist *p, pa_context_success_cb_t cb, void *userdata);
 
-/* Update the property list of the client, remove entries. \since 0.9.11 */
+/** Update the property list of the client, remove entries. \since 0.9.11 */
 pa_operation *pa_context_proplist_remove(pa_context *c, const char *const keys[], pa_context_success_cb_t cb, void *userdata);
 
 /** Return the client index this context is

commit 9ba408415c28c1113291062b4dbb38cf90a3c232
Author: Lennart Poettering <lennart at poettering.net>
Date:   Tue Jan 27 00:52:28 2009 +0100

    store requested resampling method in a seperate field and use it when create a new resampler after a move

diff --git a/src/pulsecore/sink-input.c b/src/pulsecore/sink-input.c
index e25e0c2..a33d62f 100644
--- a/src/pulsecore/sink-input.c
+++ b/src/pulsecore/sink-input.c
@@ -218,8 +218,6 @@ pa_sink_input* pa_sink_input_new(
             pa_log_warn("Unsupported resampling operation.");
             return NULL;
         }
-
-        data->resample_method = pa_resampler_get_method(resampler);
     }
 
     i = pa_msgobject_new(pa_sink_input);
@@ -235,7 +233,8 @@ pa_sink_input* pa_sink_input_new(
     i->sink = data->sink;
     i->client = data->client;
 
-    i->resample_method = data->resample_method;
+    i->requested_resample_method = data->resample_method;
+    i->actual_resample_method = resampler ? pa_resampler_get_method(resampler) : PA_RESAMPLER_INVALID;
     i->sample_spec = data->sample_spec;
     i->channel_map = data->channel_map;
 
@@ -946,7 +945,7 @@ void pa_sink_input_set_name(pa_sink_input *i, const char *name) {
 pa_resample_method_t pa_sink_input_get_resample_method(pa_sink_input *i) {
     pa_sink_input_assert_ref(i);
 
-    return i->resample_method;
+    return i->actual_resample_method;
 }
 
 /* Called from main context */
@@ -1062,7 +1061,7 @@ int pa_sink_input_finish_move(pa_sink_input *i, pa_sink *dest) {
                       i->core->mempool,
                       &i->sample_spec, &i->channel_map,
                       &dest->sample_spec, &dest->channel_map,
-                      i->resample_method,
+                      i->requested_resample_method,
                       ((i->flags & PA_SINK_INPUT_VARIABLE_RATE) ? PA_RESAMPLER_VARIABLE_RATE : 0) |
                       ((i->flags & PA_SINK_INPUT_NO_REMAP) ? PA_RESAMPLER_NO_REMAP : 0) |
                       (i->core->disable_remixing || (i->flags & PA_SINK_INPUT_NO_REMIX) ? PA_RESAMPLER_NO_REMIX : 0)))) {
diff --git a/src/pulsecore/sink-input.h b/src/pulsecore/sink-input.h
index a533046..66ec613 100644
--- a/src/pulsecore/sink-input.h
+++ b/src/pulsecore/sink-input.h
@@ -95,7 +95,7 @@ struct pa_sink_input {
     pa_cvolume volume;
     pa_bool_t muted;
 
-    pa_resample_method_t resample_method;
+    pa_resample_method_t requested_resample_method, actual_resample_method;
 
     /* Returns the chunk of audio data and drops it from the
      * queue. Returns -1 on failure. Called from IO thread context. If
diff --git a/src/pulsecore/source-output.c b/src/pulsecore/source-output.c
index d4f0367..1d21ffb 100644
--- a/src/pulsecore/source-output.c
+++ b/src/pulsecore/source-output.c
@@ -181,8 +181,6 @@ pa_source_output* pa_source_output_new(
             pa_log_warn("Unsupported resampling operation.");
             return NULL;
         }
-
-        data->resample_method = pa_resampler_get_method(resampler);
     }
 
     o = pa_msgobject_new(pa_source_output);
@@ -198,7 +196,9 @@ pa_source_output* pa_source_output_new(
     o->source = data->source;
     o->client = data->client;
 
-    o->resample_method = data->resample_method;
+
+    o->actual_resample_method = resampler ? pa_resampler_get_method(resampler) : PA_RESAMPLER_INVALID;
+    o->requested_resample_method = data->resample_method;
     o->sample_spec = data->sample_spec;
     o->channel_map = data->channel_map;
 
@@ -628,7 +628,7 @@ pa_bool_t pa_source_output_update_proplist(pa_source_output *o, pa_update_mode_t
 pa_resample_method_t pa_source_output_get_resample_method(pa_source_output *o) {
     pa_source_output_assert_ref(o);
 
-    return o->resample_method;
+    return o->actual_resample_method;
 }
 
 /* Called from main context */
@@ -730,7 +730,7 @@ int pa_source_output_finish_move(pa_source_output *o, pa_source *dest) {
                       o->core->mempool,
                       &dest->sample_spec, &dest->channel_map,
                       &o->sample_spec, &o->channel_map,
-                      o->resample_method,
+                      o->requested_resample_method,
                       ((o->flags & PA_SOURCE_OUTPUT_VARIABLE_RATE) ? PA_RESAMPLER_VARIABLE_RATE : 0) |
                       ((o->flags & PA_SOURCE_OUTPUT_NO_REMAP) ? PA_RESAMPLER_NO_REMAP : 0) |
                       (o->core->disable_remixing || (o->flags & PA_SOURCE_OUTPUT_NO_REMIX) ? PA_RESAMPLER_NO_REMIX : 0)))) {
diff --git a/src/pulsecore/source-output.h b/src/pulsecore/source-output.h
index a27602e..d60e425 100644
--- a/src/pulsecore/source-output.h
+++ b/src/pulsecore/source-output.h
@@ -79,7 +79,7 @@ struct pa_source_output {
     pa_sample_spec sample_spec;
     pa_channel_map channel_map;
 
-    pa_resample_method_t resample_method;
+    pa_resample_method_t requested_resample_method, actual_resample_method;
 
     /* Pushes a new memchunk into the output. Called from IO thread
      * context. */
@@ -139,7 +139,7 @@ struct pa_source_output {
     struct {
         pa_source_output_state_t state;
 
-        pa_bool_t attached; /* True only between ->attach() and ->detach() calls */
+        pa_bool_t attached:1; /* True only between ->attach() and ->detach() calls */
 
         pa_sample_spec sample_spec;
 

commit 07db64b9d31cb2d7315200058e4b3680b88db408
Author: Lennart Poettering <lennart at poettering.net>
Date:   Tue Jan 27 00:53:31 2009 +0100

    remove redundant cast

diff --git a/src/modules/bluetooth/module-bluetooth-device.c b/src/modules/bluetooth/module-bluetooth-device.c
index b22578e..804be35 100644
--- a/src/modules/bluetooth/module-bluetooth-device.c
+++ b/src/modules/bluetooth/module-bluetooth-device.c
@@ -600,7 +600,7 @@ static int sco_process_render(struct userdata *u) {
     for (;;) {
         ssize_t l;
 
-        l = pa_loop_write(u->stream_fd, (uint8_t*) p, memchunk.length, NULL);
+        l = pa_loop_write(u->stream_fd, p, memchunk.length, NULL);
         pa_log_debug("Memblock written to socket: %li bytes", (long) l);
 
         pa_assert(l != 0);

commit 0658d9ae929d4d2f79c5cdbc650745cafc3f8217
Author: Lennart Poettering <lennart at poettering.net>
Date:   Tue Jan 27 00:55:35 2009 +0100

    show pretty channel map name if possible

diff --git a/src/pulsecore/cli-text.c b/src/pulsecore/cli-text.c
index 1a3778a..7f3a745 100644
--- a/src/pulsecore/cli-text.c
+++ b/src/pulsecore/cli-text.c
@@ -221,6 +221,9 @@ char *pa_sink_list_to_string(pa_core *c) {
             vdb[PA_SW_VOLUME_SNPRINT_DB_MAX],
             cm[PA_CHANNEL_MAP_SNPRINT_MAX], *t;
         pa_usec_t min_latency, max_latency;
+        const char *cmn;
+
+        cmn = pa_channel_map_to_pretty_name(&sink->channel_map);
 
         pa_sink_get_latency_range(sink, &min_latency, &max_latency);
 
@@ -241,7 +244,7 @@ char *pa_sink_list_to_string(pa_core *c) {
             "\tmax rewind: %lu KiB\n"
             "\tmonitor source: %u\n"
             "\tsample spec: %s\n"
-            "\tchannel map: %s\n"
+            "\tchannel map: %s%s%s\n"
             "\tused by: %u\n"
             "\tlinked by: %u\n",
             c->default_sink_name && !strcmp(sink->name, c->default_sink_name) ? '*' : ' ',
@@ -272,6 +275,8 @@ char *pa_sink_list_to_string(pa_core *c) {
             sink->monitor_source ? sink->monitor_source->index : PA_INVALID_INDEX,
             pa_sample_spec_snprint(ss, sizeof(ss), &sink->sample_spec),
             pa_channel_map_snprint(cm, sizeof(cm), &sink->channel_map),
+            cmn ? "\n\t            " : "",
+            cmn ? cmn : "",
             pa_sink_used_by(sink),
             pa_sink_linked_by(sink));
 
@@ -306,6 +311,9 @@ char *pa_source_list_to_string(pa_core *c) {
             vdb[PA_SW_VOLUME_SNPRINT_DB_MAX],
             cm[PA_CHANNEL_MAP_SNPRINT_MAX], *t;
         pa_usec_t min_latency, max_latency;
+        const char *cmn;
+
+        cmn = pa_channel_map_to_pretty_name(&source->channel_map);
 
         pa_source_get_latency_range(source, &min_latency, &max_latency);
 
@@ -324,7 +332,7 @@ char *pa_source_list_to_string(pa_core *c) {
             "\tconfigured latency: %0.2f ms; range is %0.2f .. %0.2f ms\n"
             "\tmax rewind: %lu KiB\n"
             "\tsample spec: %s\n"
-            "\tchannel map: %s\n"
+            "\tchannel map: %s%s%s\n"
             "\tused by: %u\n"
             "\tlinked by: %u\n",
             c->default_source_name && !strcmp(source->name, c->default_source_name) ? '*' : ' ',
@@ -353,6 +361,8 @@ char *pa_source_list_to_string(pa_core *c) {
             (unsigned long) pa_source_get_max_rewind(source) / 1024,
             pa_sample_spec_snprint(ss, sizeof(ss), &source->sample_spec),
             pa_channel_map_snprint(cm, sizeof(cm), &source->channel_map),
+            cmn ? "\n\t            " : "",
+            cmn ? cmn : "",
             pa_source_used_by(source),
             pa_source_linked_by(source));
 
@@ -391,6 +401,9 @@ char *pa_source_output_list_to_string(pa_core *c) {
     for (o = pa_idxset_first(c->source_outputs, &idx); o; o = pa_idxset_next(c->source_outputs, &idx)) {
         char ss[PA_SAMPLE_SPEC_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX], *t, clt[28];
         pa_usec_t cl;
+        const char *cmn;
+
+        cmn = pa_channel_map_to_pretty_name(&o->channel_map);
 
         if ((cl = pa_source_output_get_requested_latency(o)) == (pa_usec_t) -1)
             pa_snprintf(clt, sizeof(clt), "n/a");
@@ -409,7 +422,7 @@ char *pa_source_output_list_to_string(pa_core *c) {
             "\tcurrent latency: %0.2f ms\n"
             "\trequested latency: %s\n"
             "\tsample spec: %s\n"
-            "\tchannel map: %s\n"
+            "\tchannel map: %s%s%s\n"
             "\tresample method: %s\n",
             o->index,
             o->driver,
@@ -427,6 +440,8 @@ char *pa_source_output_list_to_string(pa_core *c) {
             clt,
             pa_sample_spec_snprint(ss, sizeof(ss), &o->sample_spec),
             pa_channel_map_snprint(cm, sizeof(cm), &o->channel_map),
+            cmn ? "\n\t            " : "",
+            cmn ? cmn : "",
             pa_resample_method_to_string(pa_source_output_get_resample_method(o)));
         if (o->module)
             pa_strbuf_printf(s, "\towner module: %u\n", o->module->index);
@@ -463,6 +478,9 @@ char *pa_sink_input_list_to_string(pa_core *c) {
     for (i = pa_idxset_first(c->sink_inputs, &idx); i; i = pa_idxset_next(c->sink_inputs, &idx)) {
         char ss[PA_SAMPLE_SPEC_SNPRINT_MAX], cvdb[PA_SW_CVOLUME_SNPRINT_DB_MAX], cv[PA_CVOLUME_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX], *t, clt[28];
         pa_usec_t cl;
+        const char *cmn;
+
+        cmn = pa_channel_map_to_pretty_name(&i->channel_map);
 
         if ((cl = pa_sink_input_get_requested_latency(i)) == (pa_usec_t) -1)
             pa_snprintf(clt, sizeof(clt), "n/a");
@@ -485,7 +503,7 @@ char *pa_sink_input_list_to_string(pa_core *c) {
             "\tcurrent latency: %0.2f ms\n"
             "\trequested latency: %s\n"
             "\tsample spec: %s\n"
-            "\tchannel map: %s\n"
+            "\tchannel map: %s%s%s\n"
             "\tresample method: %s\n",
             i->index,
             i->driver,
@@ -507,6 +525,8 @@ char *pa_sink_input_list_to_string(pa_core *c) {
             clt,
             pa_sample_spec_snprint(ss, sizeof(ss), &i->sample_spec),
             pa_channel_map_snprint(cm, sizeof(cm), &i->channel_map),
+            cmn ? "\n\t            " : "",
+            cmn ? cmn : "",
             pa_resample_method_to_string(pa_sink_input_get_resample_method(i)));
 
         if (i->module)
@@ -537,6 +557,9 @@ char *pa_scache_list_to_string(pa_core *c) {
         for (e = pa_idxset_first(c->scache, &idx); e; e = pa_idxset_next(c->scache, &idx)) {
             double l = 0;
             char ss[PA_SAMPLE_SPEC_SNPRINT_MAX] = "n/a", cv[PA_CVOLUME_SNPRINT_MAX], cvdb[PA_SW_CVOLUME_SNPRINT_DB_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX] = "n/a", *t;
+            const char *cmn;
+
+            cmn = pa_channel_map_to_pretty_name(&e->channel_map);
 
             if (e->memchunk.memblock) {
                 pa_sample_spec_snprint(ss, sizeof(ss), &e->sample_spec);
@@ -549,7 +572,7 @@ char *pa_scache_list_to_string(pa_core *c) {
                 "    name: <%s>\n"
                 "\tindex: %u\n"
                 "\tsample spec: %s\n"
-                "\tchannel map: %s\n"
+                "\tchannel map: %s%s%s\n"
                 "\tlength: %lu\n"
                 "\tduration: %0.1f s\n"
                 "\tvolume: %s\n"
@@ -561,6 +584,8 @@ char *pa_scache_list_to_string(pa_core *c) {
                 e->index,
                 ss,
                 cm,
+                cmn ? "\n\t            " : "",
+                cmn ? cmn : "",
                 (long unsigned)(e->memchunk.memblock ? e->memchunk.length : 0),
                 l,
                 pa_cvolume_snprint(cv, sizeof(cv), &e->volume),

commit 948be361c44f16f9982a92d33259625b7256dd3e
Author: Lennart Poettering <lennart at poettering.net>
Date:   Tue Jan 27 00:56:57 2009 +0100

    invert an ill-placed assert

diff --git a/src/pulsecore/sink-input.c b/src/pulsecore/sink-input.c
index a33d62f..d1ffb0f 100644
--- a/src/pulsecore/sink-input.c
+++ b/src/pulsecore/sink-input.c
@@ -1110,7 +1110,7 @@ int pa_sink_input_finish_move(pa_sink_input *i, pa_sink *dest) {
 int pa_sink_input_move_to(pa_sink_input *i, pa_sink *dest) {
     pa_sink_input_assert_ref(i);
     pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
-    pa_assert(!i->sink);
+    pa_assert(i->sink);
     pa_sink_assert_ref(dest);
 
     if (dest == i->sink)

commit 1249cf6dc9032810575fc5b94c002bf633316bfb
Author: Lennart Poettering <lennart at poettering.net>
Date:   Tue Jan 27 02:16:20 2009 +0100

    always define PA_MAJOR/PA_MINOR/PA_MICRO to ease feature checking in client applications

diff --git a/configure.ac b/configure.ac
index 34ce25a..c02b20e 100644
--- a/configure.ac
+++ b/configure.ac
@@ -22,18 +22,21 @@
 
 AC_PREREQ(2.63)
 
-m4_define(PA_MAJOR, [0])
-m4_define(PA_MINOR, [9])
-m4_define(PA_MICRO, [15])
+m4_define(pa_major, [0])
+m4_define(pa_minor, [9])
+m4_define(pa_micro, [15])
 
-AC_INIT([pulseaudio],[PA_MAJOR.PA_MINOR.PA_MICRO],[mzchyfrnhqvb (at) 0pointer (dot) net])
+AC_INIT([pulseaudio],[pa_major.pa_minor.pa_micro],[mzchyfrnhqvb (at) 0pointer (dot) net])
 AC_CONFIG_SRCDIR([src/daemon/main.c])
 AC_CONFIG_MACRO_DIR([m4])
 AC_CONFIG_HEADERS([config.h])
 AM_INIT_AUTOMAKE([foreign 1.10 -Wall])
 
-AC_SUBST(PA_MAJORMINOR, PA_MAJOR.PA_MINOR)
-AC_SUBST(PA_MAJORMINORMICRO, PA_MAJOR.PA_MINOR.PA_MICRO)
+AC_SUBST(PA_MAJOR, pa_major)
+AC_SUBST(PA_MINOR, pa_minor)
+AC_SUBST(PA_MICRO, pa_micro)
+AC_SUBST(PA_MAJORMINOR, pa_major.pa_minor)
+AC_SUBST(PA_MAJORMINORMICRO, pa_major.pa_minor.pa_micro)
 AC_SUBST(PACKAGE_URL, [http://pulseaudio.org/])
 
 AC_SUBST(PA_API_VERSION, 12)
diff --git a/src/pulse/version.h.in b/src/pulse/version.h.in
index 0e37f98..566dd55 100644
--- a/src/pulse/version.h.in
+++ b/src/pulse/version.h.in
@@ -51,6 +51,15 @@ const char* pa_get_library_version(void);
  * 0.8/PulseAudio 0.9. */
 #define PA_PROTOCOL_VERSION @PA_PROTOCOL_VERSION@
 
+/** The major version of PA. \since 0.9.15 */
+#define PA_MAJOR @PA_MAJOR@
+
+/** The minor version of PA. \since 0.9.15 */
+#define PA_MINOR @PA_MINOR@
+
+/** The micro version of PA. \since 0.9.15 */
+#define PA_MICRO @PA_MICRO@
+
 PA_C_DECL_END
 
 #endif

commit 605853057114892c8e6b2e1ef1b5f745d36f88a4
Author: Lennart Poettering <lennart at poettering.net>
Date:   Tue Jan 27 02:36:42 2009 +0100

    import version.h in all header files to make sure that version-based feature testing works

diff --git a/src/pulse/browser.h b/src/pulse/browser.h
index c4e0a17..499fae2 100644
--- a/src/pulse/browser.h
+++ b/src/pulse/browser.h
@@ -26,6 +26,7 @@
 #include <pulse/sample.h>
 #include <pulse/channelmap.h>
 #include <pulse/cdecl.h>
+#include <pulse/version.h>
 
 /** \file
  * An abstract interface for Zeroconf browsing of PulseAudio servers */
diff --git a/src/pulse/channelmap.h b/src/pulse/channelmap.h
index 6e92e76..de2d712 100644
--- a/src/pulse/channelmap.h
+++ b/src/pulse/channelmap.h
@@ -26,6 +26,7 @@
 #include <pulse/sample.h>
 #include <pulse/cdecl.h>
 #include <pulse/gccmacro.h>
+#include <pulse/version.h>
 
 /** \page channelmap Channel Maps
  *
diff --git a/src/pulse/context.h b/src/pulse/context.h
index fd791b9..dfb7e4a 100644
--- a/src/pulse/context.h
+++ b/src/pulse/context.h
@@ -29,6 +29,7 @@
 #include <pulse/cdecl.h>
 #include <pulse/operation.h>
 #include <pulse/proplist.h>
+#include <pulse/version.h>
 
 /** \page async Asynchronous API
  *
diff --git a/src/pulse/def.h b/src/pulse/def.h
index be5cc23..c2c90e9 100644
--- a/src/pulse/def.h
+++ b/src/pulse/def.h
@@ -29,6 +29,7 @@
 
 #include <pulse/cdecl.h>
 #include <pulse/sample.h>
+#include <pulse/version.h>
 
 /** \file
  * Global definitions */
diff --git a/src/pulse/error.h b/src/pulse/error.h
index 9f9e3d3..c30b80b 100644
--- a/src/pulse/error.h
+++ b/src/pulse/error.h
@@ -25,6 +25,7 @@
 
 #include <inttypes.h>
 #include <pulse/cdecl.h>
+#include <pulse/version.h>
 
 /** \file
  * Error management */
diff --git a/src/pulse/ext-stream-restore.h b/src/pulse/ext-stream-restore.h
index 2038eb4..ac01d23 100644
--- a/src/pulse/ext-stream-restore.h
+++ b/src/pulse/ext-stream-restore.h
@@ -23,6 +23,7 @@
 ***/
 
 #include <pulse/context.h>
+#include <pulse/version.h>
 
 /** \file
  *
diff --git a/src/pulse/glib-mainloop.h b/src/pulse/glib-mainloop.h
index 60fd61a..fd68f8a 100644
--- a/src/pulse/glib-mainloop.h
+++ b/src/pulse/glib-mainloop.h
@@ -27,6 +27,7 @@
 
 #include <pulse/mainloop-api.h>
 #include <pulse/cdecl.h>
+#include <pulse/version.h>
 
 /** \page glib-mainloop GLIB Main Loop Bindings
  *
diff --git a/src/pulse/i18n.h b/src/pulse/i18n.h
index 4c0ef9d..f91c0bf 100644
--- a/src/pulse/i18n.h
+++ b/src/pulse/i18n.h
@@ -24,6 +24,7 @@
 
 #include <pulse/cdecl.h>
 #include <pulse/gccmacro.h>
+#include <pulse/version.h>
 
 PA_C_DECL_BEGIN
 
diff --git a/src/pulse/introspect.h b/src/pulse/introspect.h
index 972b454..d8a28ff 100644
--- a/src/pulse/introspect.h
+++ b/src/pulse/introspect.h
@@ -32,6 +32,7 @@
 #include <pulse/channelmap.h>
 #include <pulse/volume.h>
 #include <pulse/proplist.h>
+#include <pulse/version.h>
 
 /** \page introspect Server Query and Control
  *
diff --git a/src/pulse/mainloop-api.h b/src/pulse/mainloop-api.h
index 53c7411..e353ed9 100644
--- a/src/pulse/mainloop-api.h
+++ b/src/pulse/mainloop-api.h
@@ -27,6 +27,7 @@
 #include <time.h>
 
 #include <pulse/cdecl.h>
+#include <pulse/version.h>
 
 /** \file
  *
diff --git a/src/pulse/operation.h b/src/pulse/operation.h
index 188e2cb..b68e781 100644
--- a/src/pulse/operation.h
+++ b/src/pulse/operation.h
@@ -24,6 +24,7 @@
 
 #include <pulse/cdecl.h>
 #include <pulse/def.h>
+#include <pulse/version.h>
 
 /** \file
  * Asynchronous operations */
diff --git a/src/pulse/proplist.h b/src/pulse/proplist.h
index 529871f..984a656 100644
--- a/src/pulse/proplist.h
+++ b/src/pulse/proplist.h
@@ -26,6 +26,7 @@
 
 #include <pulse/cdecl.h>
 #include <pulse/gccmacro.h>
+#include <pulse/version.h>
 
 PA_C_DECL_BEGIN
 
diff --git a/src/pulse/sample.h b/src/pulse/sample.h
index 3ba13db..45a481f 100644
--- a/src/pulse/sample.h
+++ b/src/pulse/sample.h
@@ -30,6 +30,7 @@
 
 #include <pulse/gccmacro.h>
 #include <pulse/cdecl.h>
+#include <pulse/version.h>
 
 /** \page sample Sample Format Specifications
  *
diff --git a/src/pulse/scache.h b/src/pulse/scache.h
index f380b4e..69e813c 100644
--- a/src/pulse/scache.h
+++ b/src/pulse/scache.h
@@ -28,6 +28,7 @@
 #include <pulse/context.h>
 #include <pulse/stream.h>
 #include <pulse/cdecl.h>
+#include <pulse/version.h>
 
 /** \page scache Sample Cache
  *
diff --git a/src/pulse/simple.h b/src/pulse/simple.h
index a1380a0..3f57a65 100644
--- a/src/pulse/simple.h
+++ b/src/pulse/simple.h
@@ -29,6 +29,7 @@
 #include <pulse/channelmap.h>
 #include <pulse/def.h>
 #include <pulse/cdecl.h>
+#include <pulse/version.h>
 
 /** \page simple Simple API
  *
diff --git a/src/pulse/subscribe.h b/src/pulse/subscribe.h
index 0e4be8c..2707cec 100644
--- a/src/pulse/subscribe.h
+++ b/src/pulse/subscribe.h
@@ -28,6 +28,7 @@
 #include <pulse/def.h>
 #include <pulse/context.h>
 #include <pulse/cdecl.h>
+#include <pulse/version.h>
 
 /** \page subscribe Event Subscription
  *
diff --git a/src/pulse/thread-mainloop.h b/src/pulse/thread-mainloop.h
index 521e29b..4de338a 100644
--- a/src/pulse/thread-mainloop.h
+++ b/src/pulse/thread-mainloop.h
@@ -25,6 +25,7 @@
 
 #include <pulse/mainloop-api.h>
 #include <pulse/cdecl.h>
+#include <pulse/version.h>
 
 PA_C_DECL_BEGIN
 
diff --git a/src/pulse/utf8.h b/src/pulse/utf8.h
index 6c7e7a5..4d75195 100644
--- a/src/pulse/utf8.h
+++ b/src/pulse/utf8.h
@@ -25,6 +25,7 @@
 
 #include <pulse/cdecl.h>
 #include <pulse/gccmacro.h>
+#include <pulse/version.h>
 
 /** \file
  * UTF8 Validation functions
diff --git a/src/pulse/util.h b/src/pulse/util.h
index cf06d4f..f6dd40c 100644
--- a/src/pulse/util.h
+++ b/src/pulse/util.h
@@ -27,6 +27,7 @@
 
 #include <pulse/cdecl.h>
 #include <pulse/gccmacro.h>
+#include <pulse/version.h>
 
 /** \file
  * Assorted utility functions */
diff --git a/src/pulse/volume.h b/src/pulse/volume.h
index c8b73f4..e8670ab 100644
--- a/src/pulse/volume.h
+++ b/src/pulse/volume.h
@@ -29,6 +29,7 @@
 #include <pulse/gccmacro.h>
 #include <pulse/sample.h>
 #include <pulse/channelmap.h>
+#include <pulse/version.h>
 
 /** \page volume Volume Control
  *
diff --git a/src/pulse/xmalloc.h b/src/pulse/xmalloc.h
index b264358..c30d4df 100644
--- a/src/pulse/xmalloc.h
+++ b/src/pulse/xmalloc.h
@@ -29,6 +29,7 @@
 
 #include <pulse/cdecl.h>
 #include <pulse/gccmacro.h>
+#include <pulse/version.h>
 
 /** \file
  * Memory allocation functions.

commit df8ad5d18fe67b93393fb6f81d7598a95d021680
Author: Lennart Poettering <lennart at poettering.net>
Date:   Tue Jan 27 02:37:13 2009 +0100

    add a few missing doxygen comments

diff --git a/src/pulse/gccmacro.h b/src/pulse/gccmacro.h
index 0533b10..0b1a1a6 100644
--- a/src/pulse/gccmacro.h
+++ b/src/pulse/gccmacro.h
@@ -22,6 +22,9 @@
   USA.
 ***/
 
+/** \file
+ * GCC attribute macros */
+
 #ifdef __GNUC__
 #define PA_GCC_PRINTF_ATTR(a,b) __attribute__ ((format (printf, a, b)))
 #else
@@ -100,6 +103,7 @@
 #else
 /** Macro for usage of GCC's alloc_size attribute */
 #define PA_GCC_ALLOC_SIZE(x)
+/** Macro for usage of GCC's alloc_size attribute */
 #define PA_GCC_ALLOC_SIZE2(x,y)
 #endif
 #endif
diff --git a/src/pulse/mainloop-signal.h b/src/pulse/mainloop-signal.h
index a6c16f2..a9e250b 100644
--- a/src/pulse/mainloop-signal.h
+++ b/src/pulse/mainloop-signal.h
@@ -40,8 +40,10 @@ PA_C_DECL_BEGIN
 /** An opaque UNIX signal event source object */
 typedef struct pa_signal_event pa_signal_event;
 
+/** Callback prototype for signal events */
 typedef void (*pa_signal_cb_t) (pa_mainloop_api *api, pa_signal_event*e, int sig, void *userdata);
 
+/** Destroy callback prototype for signal events */
 typedef void (*pa_signal_destroy_cb_t) (pa_mainloop_api *api, pa_signal_event*e, void *userdata);
 
 /** Initialize the UNIX signal subsystem and bind it to the specified main loop */
diff --git a/src/pulse/proplist.h b/src/pulse/proplist.h
index 984a656..203a28c 100644
--- a/src/pulse/proplist.h
+++ b/src/pulse/proplist.h
@@ -162,7 +162,7 @@ int pa_proplist_setf(pa_proplist *p, const char *key, const char *format, ...) P
  * internal copy of the data passed is made. \since 0.9.11 */
 int pa_proplist_set(pa_proplist *p, const char *key, const void *data, size_t nbytes);
 
-/* Return a string entry for the specified key. Will return NULL if
+/** Return a string entry for the specified key. Will return NULL if
  * the data is not valid UTF-8. Will return a NUL-terminated string in
  * an internally allocated buffer. The caller should make a copy of
  * the data before accessing the property list again. \since 0.9.11 */
diff --git a/src/pulse/timeval.h b/src/pulse/timeval.h
index ee39829..2b3faf1 100644
--- a/src/pulse/timeval.h
+++ b/src/pulse/timeval.h
@@ -26,17 +26,29 @@
 #include <pulse/cdecl.h>
 #include <pulse/gccmacro.h>
 #include <pulse/sample.h>
+#include <pulse/version.h>
 
 /** \file
  * Utility functions for handling timeval calculations */
 
 PA_C_DECL_BEGIN
 
+/** The number of milliseconds in a second */
 #define PA_MSEC_PER_SEC ((pa_usec_t) 1000ULL)
+
+/** The number of microseconds in a second */
 #define PA_USEC_PER_SEC ((pa_usec_t) 1000000ULL)
+
+/** The number of nanoseconds in a second */
 #define PA_NSEC_PER_SEC ((pa_usec_t) 1000000000ULL)
+
+/** The number of microseconds in a millisecond */
 #define PA_USEC_PER_MSEC ((pa_usec_t) 1000ULL)
+
+/** The number of nanoseconds in a millisecond */
 #define PA_NSEC_PER_MSEC ((pa_usec_t) 1000000ULL)
+
+/** The number of nanoseconds in a microsecond */
 #define PA_NSEC_PER_USEC ((pa_usec_t) 1000ULL)
 
 struct timeval;

commit 5449d793ae7800ad236f027f6c6893a1d2db3929
Author: Lennart Poettering <lennart at poettering.net>
Date:   Tue Jan 27 02:45:37 2009 +0100

    swap argument order of pa_cvolume_get_balance() to be a bit more systematic

diff --git a/src/pulse/volume.c b/src/pulse/volume.c
index 2c97784..822fe39 100644
--- a/src/pulse/volume.c
+++ b/src/pulse/volume.c
@@ -435,7 +435,7 @@ static void get_avg_lr(const pa_channel_map *map, const pa_cvolume *v, pa_volume
         *r = right / n_right;
 }
 
-float pa_cvolume_get_balance(const pa_channel_map *map, const pa_cvolume *v) {
+float pa_cvolume_get_balance(const pa_cvolume *v, const pa_channel_map *map) {
     pa_volume_t left, right;
 
     pa_assert(v);
@@ -462,7 +462,7 @@ float pa_cvolume_get_balance(const pa_channel_map *map, const pa_cvolume *v) {
         return 1.0f - ((float) left / (float) right);
 }
 
-pa_cvolume* pa_cvolume_set_balance(const pa_channel_map *map, pa_cvolume *v, float new_balance) {
+pa_cvolume* pa_cvolume_set_balance(pa_cvolume *v, const pa_channel_map *map, float new_balance) {
     pa_volume_t left, nleft, right, nright, m;
     unsigned c;
 
diff --git a/src/pulse/volume.h b/src/pulse/volume.h
index e8670ab..c1967b3 100644
--- a/src/pulse/volume.h
+++ b/src/pulse/volume.h
@@ -237,7 +237,7 @@ int pa_cvolume_compatible(const pa_cvolume *v, const pa_sample_spec *ss) PA_GCC_
 /** Calculate a 'balance' value for the specified volume with the
  * specified channel map. The return value will range from -1.0f
  * (left) to +1.0f (right) \since 0.9.15 */
-float pa_cvolume_get_balance(const pa_channel_map *map, const pa_cvolume *v) PA_GCC_PURE;
+float pa_cvolume_get_balance(const pa_cvolume *v, const pa_channel_map *map) PA_GCC_PURE;
 
 /** Adjust the 'balance' value for the specified volume with the
  * specified channel map. v will be modified in place and
@@ -246,7 +246,7 @@ float pa_cvolume_get_balance(const pa_channel_map *map, const pa_cvolume *v) PA_
  * pa_cvolume_get_balance() is not guaranteed to actually return the
  * requested balance (e.g. when the input volume was zero anyway for
  * all channels) \since 0.9.15 */
-pa_cvolume* pa_cvolume_set_balance(const pa_channel_map *map, pa_cvolume *v, float new_balance);
+pa_cvolume* pa_cvolume_set_balance(pa_cvolume *v, const pa_channel_map *map, float new_balance);
 
 /** Scale the passed pa_cvolume structure so that the maximum volume
  * of all channels equals max. The proportions between the channel
diff --git a/src/pulsecore/cli-text.c b/src/pulsecore/cli-text.c
index 7f3a745..e87abe0 100644
--- a/src/pulsecore/cli-text.c
+++ b/src/pulsecore/cli-text.c
@@ -261,7 +261,7 @@ char *pa_sink_list_to_string(pa_core *c) {
             pa_cvolume_snprint(cv, sizeof(cv), pa_sink_get_volume(sink, FALSE)),
             sink->flags & PA_SINK_DECIBEL_VOLUME ? "\n\t        " : "",
             sink->flags & PA_SINK_DECIBEL_VOLUME ? pa_sw_cvolume_snprint_dB(cvdb, sizeof(cvdb), pa_sink_get_volume(sink, FALSE)) : "",
-            pa_cvolume_get_balance(&sink->channel_map, pa_sink_get_volume(sink, FALSE)),
+            pa_cvolume_get_balance(pa_sink_get_volume(sink, FALSE), &sink->channel_map),
             pa_volume_snprint(v, sizeof(v), sink->base_volume),
             sink->flags & PA_SINK_DECIBEL_VOLUME ? "\n\t             " : "",
             sink->flags & PA_SINK_DECIBEL_VOLUME ? pa_sw_volume_snprint_dB(vdb, sizeof(vdb), sink->base_volume) : "",
@@ -349,7 +349,7 @@ char *pa_source_list_to_string(pa_core *c) {
             pa_cvolume_snprint(cv, sizeof(cv), pa_source_get_volume(source, FALSE)),
             source->flags & PA_SOURCE_DECIBEL_VOLUME ? "\n\t        " : "",
             source->flags & PA_SOURCE_DECIBEL_VOLUME ? pa_sw_cvolume_snprint_dB(cvdb, sizeof(cvdb), pa_source_get_volume(source, FALSE)) : "",
-            pa_cvolume_get_balance(&source->channel_map, pa_source_get_volume(source, FALSE)),
+            pa_cvolume_get_balance(pa_source_get_volume(source, FALSE), &source->channel_map),
             pa_volume_snprint(v, sizeof(v), source->base_volume),
             source->flags & PA_SOURCE_DECIBEL_VOLUME ? "\n\t             " : "",
             source->flags & PA_SOURCE_DECIBEL_VOLUME ? pa_sw_volume_snprint_dB(vdb, sizeof(vdb), source->base_volume) : "",
@@ -519,7 +519,7 @@ char *pa_sink_input_list_to_string(pa_core *c) {
             i->sink->index, i->sink->name,
             pa_cvolume_snprint(cv, sizeof(cv), pa_sink_input_get_volume(i)),
             pa_sw_cvolume_snprint_dB(cvdb, sizeof(cvdb), pa_sink_input_get_volume(i)),
-            pa_cvolume_get_balance(&i->channel_map, pa_sink_input_get_volume(i)),
+            pa_cvolume_get_balance(pa_sink_input_get_volume(i), &i->channel_map),
             pa_yes_no(pa_sink_input_get_mute(i)),
             (double) pa_sink_input_get_latency(i, NULL) / PA_USEC_PER_MSEC,
             clt,
@@ -590,7 +590,7 @@ char *pa_scache_list_to_string(pa_core *c) {
                 l,
                 pa_cvolume_snprint(cv, sizeof(cv), &e->volume),
                 pa_sw_cvolume_snprint_dB(cvdb, sizeof(cvdb), &e->volume),
-                e->memchunk.memblock ? pa_cvolume_get_balance(&e->channel_map, &e->volume) : 0.0f,
+                e->memchunk.memblock ? pa_cvolume_get_balance(&e->volume, &e->channel_map) : 0.0f,
                 pa_yes_no(e->lazy),
                 e->filename ? e->filename : "n/a");
 
diff --git a/src/tests/voltest.c b/src/tests/voltest.c
index 879d86b..0c6d2ea 100644
--- a/src/tests/voltest.c
+++ b/src/tests/voltest.c
@@ -38,7 +38,7 @@ int main(int argc, char *argv[]) {
         for (cv.values[1] = PA_VOLUME_MUTED; cv.values[1] <= PA_VOLUME_NORM*2; cv.values[1] += 4096) {
             char s[PA_CVOLUME_SNPRINT_MAX];
 
-            printf("Volume: [%s]; balance: %2.1f\n", pa_cvolume_snprint(s, sizeof(s), &cv), pa_cvolume_get_balance(&map, &cv));
+            printf("Volume: [%s]; balance: %2.1f\n", pa_cvolume_snprint(s, sizeof(s), &cv), pa_cvolume_get_balance(&cv, &map));
         }
 
     for (cv.values[0] = PA_VOLUME_MUTED+4096; cv.values[0] <= PA_VOLUME_NORM*2; cv.values[0] += 4096)
@@ -48,12 +48,12 @@ int main(int argc, char *argv[]) {
                 pa_cvolume r;
                 float k;
 
-                printf("Before: volume: [%s]; balance: %2.1f\n", pa_cvolume_snprint(s, sizeof(s), &cv), pa_cvolume_get_balance(&map, &cv));
+                printf("Before: volume: [%s]; balance: %2.1f\n", pa_cvolume_snprint(s, sizeof(s), &cv), pa_cvolume_get_balance(&cv, &map));
 
                 r = cv;
-                pa_cvolume_set_balance(&map, &r, b);
+                pa_cvolume_set_balance(&r, &map,b);
 
-                k = pa_cvolume_get_balance(&map, &r);
+                k = pa_cvolume_get_balance(&r, &map);
                 printf("After: volume: [%s]; balance: %2.1f (intended: %2.1f) %s\n", pa_cvolume_snprint(s, sizeof(s), &r), k, b, k < b-.05 || k > b+.5 ? "MISMATCH" : "");
             }
 
diff --git a/src/utils/pactl.c b/src/utils/pactl.c
index 0820641..154e7f9 100644
--- a/src/utils/pactl.c
+++ b/src/utils/pactl.c
@@ -216,7 +216,7 @@ static void get_sink_info_callback(pa_context *c, const pa_sink_info *i, int is_
            pa_cvolume_snprint(cv, sizeof(cv), &i->volume),
            i->flags & PA_SINK_DECIBEL_VOLUME ? "\n\t        " : "",
            i->flags & PA_SINK_DECIBEL_VOLUME ? pa_sw_cvolume_snprint_dB(cvdb, sizeof(cvdb), &i->volume) : "",
-           pa_cvolume_get_balance(&i->channel_map, &i->volume),
+           pa_cvolume_get_balance(&i->volume, &i->channel_map),
            pa_volume_snprint(v, sizeof(v), i->base_volume),
            i->flags & PA_SINK_DECIBEL_VOLUME ? "\n\t             " : "",
            i->flags & PA_SINK_DECIBEL_VOLUME ? pa_sw_volume_snprint_dB(vdb, sizeof(vdb), i->base_volume) : "",
@@ -296,7 +296,7 @@ static void get_source_info_callback(pa_context *c, const pa_source_info *i, int
            pa_cvolume_snprint(cv, sizeof(cv), &i->volume),
            i->flags & PA_SOURCE_DECIBEL_VOLUME ? "\n\t        " : "",
            i->flags & PA_SOURCE_DECIBEL_VOLUME ? pa_sw_cvolume_snprint_dB(cvdb, sizeof(cvdb), &i->volume) : "",
-           pa_cvolume_get_balance(&i->channel_map, &i->volume),
+           pa_cvolume_get_balance(&i->volume, &i->channel_map),
            pa_volume_snprint(v, sizeof(v), i->base_volume),
            i->flags & PA_SOURCE_DECIBEL_VOLUME ? "\n\t             " : "",
            i->flags & PA_SOURCE_DECIBEL_VOLUME ? pa_sw_volume_snprint_dB(vdb, sizeof(vdb), i->base_volume) : "",
@@ -483,7 +483,7 @@ static void get_sink_input_info_callback(pa_context *c, const pa_sink_input_info
            pa_yes_no(i->mute),
            pa_cvolume_snprint(cv, sizeof(cv), &i->volume),
            pa_sw_cvolume_snprint_dB(cvdb, sizeof(cvdb), &i->volume),
-           pa_cvolume_get_balance(&i->channel_map, &i->volume),
+           pa_cvolume_get_balance(&i->volume, &i->channel_map),
            (double) i->buffer_usec,
            (double) i->sink_usec,
            i->resample_method ? i->resample_method : _("n/a"),
@@ -584,7 +584,7 @@ static void get_sample_info_callback(pa_context *c, const pa_sample_info *i, int
            pa_sample_spec_valid(&i->sample_spec) ? pa_channel_map_snprint(cm, sizeof(cm), &i->channel_map) : _("n/a"),
            pa_cvolume_snprint(cv, sizeof(cv), &i->volume),
            pa_sw_cvolume_snprint_dB(cvdb, sizeof(cvdb), &i->volume),
-           pa_cvolume_get_balance(&i->channel_map, &i->volume),
+           pa_cvolume_get_balance(&i->volume, &i->channel_map),
            (double) i->duration/1000000.0,
            t,
            pa_yes_no(i->lazy),

commit 1be39e4fa5b8f6a2d995593a4c1fd66eeafd6328
Author: Lennart Poettering <lennart at poettering.net>
Date:   Tue Jan 27 03:05:40 2009 +0100

    allow samples to be played with 'default' (i.e. unspecified) volume.

diff --git a/src/pulse/scache.c b/src/pulse/scache.c
index fd3b987..c96c42a 100644
--- a/src/pulse/scache.c
+++ b/src/pulse/scache.c
@@ -188,6 +188,10 @@ pa_operation *pa_context_play_sample(pa_context *c, const char *name, const char
     t = pa_tagstruct_command(c, PA_COMMAND_PLAY_SAMPLE, &tag);
     pa_tagstruct_putu32(t, PA_INVALID_INDEX);
     pa_tagstruct_puts(t, dev);
+
+    if (volume == (pa_volume_t) -1 && c->version < 15)
+        volume = PA_VOLUME_NORM;
+
     pa_tagstruct_putu32(t, volume);
     pa_tagstruct_puts(t, name);
 
@@ -225,6 +229,10 @@ pa_operation *pa_context_play_sample_with_proplist(pa_context *c, const char *na
     t = pa_tagstruct_command(c, PA_COMMAND_PLAY_SAMPLE, &tag);
     pa_tagstruct_putu32(t, PA_INVALID_INDEX);
     pa_tagstruct_puts(t, dev);
+
+    if (volume == (pa_volume_t) -1 && c->version < 15)
+        volume = PA_VOLUME_NORM;
+
     pa_tagstruct_putu32(t, volume);
     pa_tagstruct_puts(t, name);
     pa_tagstruct_put_proplist(t, p);
diff --git a/src/pulse/scache.h b/src/pulse/scache.h
index 69e813c..79fcfbc 100644
--- a/src/pulse/scache.h
+++ b/src/pulse/scache.h
@@ -101,7 +101,7 @@ pa_operation* pa_context_play_sample(
         pa_context *c               /**< Context */,
         const char *name            /**< Name of the sample to play */,
         const char *dev             /**< Sink to play this sample on */,
-        pa_volume_t volume          /**< Volume to play this sample with */ ,
+        pa_volume_t volume          /**< Volume to play this sample with. Starting with 0.9.15 you may pass here (pa_volume_t) -1 which will leave the decision about the volume to the server side which is a good idea. */ ,
         pa_context_success_cb_t cb  /**< Call this function after successfully starting playback, or NULL */,
         void *userdata              /**< Userdata to pass to the callback */);
 
@@ -113,7 +113,7 @@ pa_operation* pa_context_play_sample_with_proplist(
         pa_context *c                   /**< Context */,
         const char *name                /**< Name of the sample to play */,
         const char *dev                 /**< Sink to play this sample on */,
-        pa_volume_t volume              /**< Volume to play this sample with */ ,
+        pa_volume_t volume              /**< Volume to play this sample with. Starting with 0.9.15 you may pass here (pa_volume_t) -1 which will leave the decision about the volume to the server side which is a good idea.  */ ,
         pa_proplist *proplist           /**< Property list for this sound. The property list of the cached entry will be merged into this property list */,
         pa_context_play_sample_cb_t cb  /**< Call this function after successfully starting playback, or NULL */,
         void *userdata                  /**< Userdata to pass to the callback */);
diff --git a/src/pulsecore/cli-text.c b/src/pulsecore/cli-text.c
index e87abe0..c74ca5d 100644
--- a/src/pulsecore/cli-text.c
+++ b/src/pulsecore/cli-text.c
@@ -588,9 +588,9 @@ char *pa_scache_list_to_string(pa_core *c) {
                 cmn ? cmn : "",
                 (long unsigned)(e->memchunk.memblock ? e->memchunk.length : 0),
                 l,
-                pa_cvolume_snprint(cv, sizeof(cv), &e->volume),
-                pa_sw_cvolume_snprint_dB(cvdb, sizeof(cvdb), &e->volume),
-                e->memchunk.memblock ? pa_cvolume_get_balance(&e->volume, &e->channel_map) : 0.0f,
+                e->volume_is_set ? pa_cvolume_snprint(cv, sizeof(cv), &e->volume) : "n/a",
+                e->volume_is_set ? pa_sw_cvolume_snprint_dB(cvdb, sizeof(cvdb), &e->volume) : "n/a",
+                (e->memchunk.memblock && e->volume_is_set) ? pa_cvolume_get_balance(&e->volume, &e->channel_map) : 0.0f,
                 pa_yes_no(e->lazy),
                 e->filename ? e->filename : "n/a");
 
diff --git a/src/pulsecore/core-scache.c b/src/pulsecore/core-scache.c
index 0f34c9d..6d2ae93 100644
--- a/src/pulsecore/core-scache.c
+++ b/src/pulsecore/core-scache.c
@@ -137,6 +137,7 @@ static pa_scache_entry* scache_add_item(pa_core *c, const char *name) {
     pa_sample_spec_init(&e->sample_spec);
     pa_channel_map_init(&e->channel_map);
     pa_cvolume_init(&e->volume);
+    e->volume_is_set = FALSE;
 
     pa_proplist_sets(e->proplist, PA_PROP_MEDIA_ROLE, "event");
 
@@ -175,6 +176,7 @@ int pa_scache_add_item(
     pa_sample_spec_init(&e->sample_spec);
     pa_channel_map_init(&e->channel_map);
     pa_cvolume_init(&e->volume);
+    e->volume_is_set = FALSE;
 
     if (ss) {
         e->sample_spec = *ss;
@@ -308,6 +310,7 @@ int pa_scache_play_item(pa_core *c, const char *name, pa_sink *sink, pa_volume_t
     pa_scache_entry *e;
     pa_cvolume r;
     pa_proplist *merged;
+    pa_bool_t pass_volume;
 
     pa_assert(c);
     pa_assert(name);
@@ -324,10 +327,12 @@ int pa_scache_play_item(pa_core *c, const char *name, pa_sink *sink, pa_volume_t
 
         pa_subscription_post(c, PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE|PA_SUBSCRIPTION_EVENT_CHANGE, e->index);
 
-        if (pa_cvolume_valid(&e->volume))
-            pa_cvolume_remap(&e->volume, &old_channel_map, &e->channel_map);
-        else
-            pa_cvolume_reset(&e->volume, e->sample_spec.channels);
+        if (e->volume_is_set) {
+            if (pa_cvolume_valid(&e->volume))
+                pa_cvolume_remap(&e->volume, &old_channel_map, &e->channel_map);
+            else
+                pa_cvolume_reset(&e->volume, e->sample_spec.channels);
+        }
     }
 
     if (!e->memchunk.memblock)
@@ -335,19 +340,26 @@ int pa_scache_play_item(pa_core *c, const char *name, pa_sink *sink, pa_volume_t
 
     pa_log_debug("Playing sample \"%s\" on \"%s\"", name, sink->name);
 
-    pa_cvolume_set(&r, e->volume.channels, volume);
-    pa_sw_cvolume_multiply(&r, &r, &e->volume);
+    pass_volume = TRUE;
 
-    merged = pa_proplist_new();
+    if (e->volume_is_set && volume != (pa_volume_t) -1) {
+        pa_cvolume_set(&r, e->sample_spec.channels, volume);
+        pa_sw_cvolume_multiply(&r, &r, &e->volume);
+    } else if (e->volume_is_set)
+        r = e->volume;
+    else if (volume != (pa_volume_t) -1)
+        pa_cvolume_set(&r, e->sample_spec.channels, volume);
+    else
+        pass_volume = FALSE;
 
+    merged = pa_proplist_new();
     pa_proplist_setf(merged, PA_PROP_MEDIA_NAME, "Sample %s", name);
-
     pa_proplist_update(merged, PA_UPDATE_REPLACE, e->proplist);
 
     if (p)
         pa_proplist_update(merged, PA_UPDATE_REPLACE, p);
 
-    if (pa_play_memchunk(sink, &e->sample_spec, &e->channel_map, &e->memchunk, &r, merged, sink_input_idx) < 0) {
+    if (pa_play_memchunk(sink, &e->sample_spec, &e->channel_map, &e->memchunk, pass_volume ? &r : NULL, merged, sink_input_idx) < 0) {
         pa_proplist_free(merged);
         return -1;
     }
diff --git a/src/pulsecore/core-scache.h b/src/pulsecore/core-scache.h
index ba65a96..a75f8ac 100644
--- a/src/pulsecore/core-scache.h
+++ b/src/pulsecore/core-scache.h
@@ -36,6 +36,7 @@ typedef struct pa_scache_entry {
     char *name;
 
     pa_cvolume volume;
+    pa_bool_t volume_is_set;
     pa_sample_spec sample_spec;
     pa_channel_map channel_map;
     pa_memchunk memchunk;
diff --git a/src/pulsecore/protocol-native.c b/src/pulsecore/protocol-native.c
index 7ddc010..3896dff 100644
--- a/src/pulsecore/protocol-native.c
+++ b/src/pulsecore/protocol-native.c
@@ -2840,6 +2840,7 @@ static void source_output_fill_tagstruct(pa_native_connection *c, pa_tagstruct *
 
 static void scache_fill_tagstruct(pa_native_connection *c, pa_tagstruct *t, pa_scache_entry *e) {
     pa_sample_spec fixed_ss;
+    pa_cvolume v;
 
     pa_assert(t);
     pa_assert(e);
@@ -2851,7 +2852,13 @@ static void scache_fill_tagstruct(pa_native_connection *c, pa_tagstruct *t, pa_s
 
     pa_tagstruct_putu32(t, e->index);
     pa_tagstruct_puts(t, e->name);
-    pa_tagstruct_put_cvolume(t, &e->volume);
+
+    if (e->volume_is_set)
+        v = e->volume;
+    else
+        pa_cvolume_init(&v);
+
+    pa_tagstruct_put_cvolume(t, &v);
     pa_tagstruct_put_usec(t, e->memchunk.memblock ? pa_bytes_to_usec(e->memchunk.length, &e->sample_spec) : 0);
     pa_tagstruct_put_sample_spec(t, &fixed_ss);
     pa_tagstruct_put_channel_map(t, &e->channel_map);

commit eca32235fb38961cf27aa741219fd7c184d73d1e
Author: Lennart Poettering <lennart at poettering.net>
Date:   Tue Jan 27 04:29:25 2009 +0100

    get rid of module-flat-volumes since we are moving this into the core

diff --git a/src/Makefile.am b/src/Makefile.am
index f313bb5..00dd53d 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -863,7 +863,6 @@ libavahi_wrap_la_LIBADD = $(AM_LIBADD) $(AVAHI_CFLAGS) libpulsecore- at PA_MAJORMIN
 ###################################
 
 modlibexec_LTLIBRARIES += \
-		module-flat-volume.la \
 		module-cli.la \
 		module-cli-protocol-tcp.la \
 		module-simple-protocol-tcp.la \
@@ -1086,8 +1085,7 @@ SYMDEF_FILES = \
 		modules/module-raop-discover-symdef.h \
 		modules/gconf/module-gconf-symdef.h \
 		modules/module-position-event-sounds-symdef.h \
-		modules/module-console-kit-symdef.h \
-		modules/module-flat-volume-symdef.h
+		modules/module-console-kit-symdef.h
 
 EXTRA_DIST += $(SYMDEF_FILES)
 BUILT_SOURCES += $(SYMDEF_FILES)
@@ -1096,12 +1094,6 @@ $(SYMDEF_FILES): modules/module-defs.h.m4
 	$(MKDIR_P) $(dir $@)
 	$(M4) -Dfname="$@" $< > $@
 
-# Flat volume
-
-module_flat_volume_la_SOURCES = modules/module-flat-volume.c
-module_flat_volume_la_LDFLAGS = $(MODULE_LDFLAGS)
-module_flat_volume_la_LIBADD = $(AM_LIBADD) libpulsecore- at PA_MAJORMINORMICRO@.la libpulsecommon- at PA_MAJORMINORMICRO@.la libpulse.la
-
 # Simple protocol
 
 module_simple_protocol_tcp_la_SOURCES = modules/module-protocol-stub.c
diff --git a/src/daemon/default.pa.in b/src/daemon/default.pa.in
index d761f5f..5d69926 100755
--- a/src/daemon/default.pa.in
+++ b/src/daemon/default.pa.in
@@ -77,9 +77,6 @@ load-module module-native-protocol-unix
 #load-module module-null-sink sink_name=rtp format=s16be channels=2 rate=44100 description="RTP Multicast Sink"
 #load-module module-rtp-send source=rtp.monitor
 
-### Enable flat volumes where possible
-load-module module-flat-volume
-
 ### Automatically restore the default sink/source when changed by the user during runtime
 load-module module-default-device-restore
 
diff --git a/src/modules/module-flat-volume.c b/src/modules/module-flat-volume.c
deleted file mode 100644
index fe6dc92..0000000
--- a/src/modules/module-flat-volume.c
+++ /dev/null
@@ -1,224 +0,0 @@
-/***
-  This file is part of PulseAudio.
-
-  Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies).
-  Copyright 2004-2006, 2008 Lennart Poettering
-
-  Contact: Marc-Andre Lureau <marc-andre.lureau at nokia.com>
-
-  PulseAudio is free software; you can redistribute it and/or modify
-  it under the terms of the GNU Lesser General Public License as published
-  by the Free Software Foundation; either version 2 of the License,
-  or (at your option) any later version.
-
-  PulseAudio is distributed in the hope that it will be useful, but
-  WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-  General Public License for more details.
-
-  You should have received a copy of the GNU Lesser General Public License
-  along with PulseAudio; if not, write to the Free Software
-  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
-  USA.
-***/
-
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif
-
-#include <unistd.h>
-#include <string.h>
-#include <errno.h>
-#include <sys/types.h>
-#include <regex.h>
-#include <stdio.h>
-#include <stdlib.h>
-
-#include <pulse/xmalloc.h>
-
-#include <pulsecore/core-error.h>
-#include <pulsecore/module.h>
-#include <pulsecore/core-util.h>
-#include <pulsecore/log.h>
-#include <pulsecore/sink-input.h>
-#include <pulsecore/core-util.h>
-#include <pulsecore/macro.h>
-
-#include "module-flat-volume-symdef.h"
-
-PA_MODULE_AUTHOR("Marc-Andre Lureau");
-PA_MODULE_DESCRIPTION("Flat volume");
-PA_MODULE_VERSION(PACKAGE_VERSION);
-PA_MODULE_LOAD_ONCE(TRUE);
-PA_MODULE_USAGE("");
-
-struct userdata {
-    pa_subscription *subscription;
-    pa_hook_slot *sink_input_set_volume_hook_slot;
-    pa_hook_slot *sink_input_fixate_hook_slot;
-};
-
-static void process_input_volume_change(
-        pa_cvolume *dest_volume,
-        const pa_cvolume *dest_virtual_volume,
-        pa_channel_map *dest_channel_map,
-        pa_sink_input *this,
-        pa_sink *sink) {
-
-    pa_sink_input *i;
-    uint32_t idx;
-    pa_cvolume max_volume, sink_volume;
-
-    pa_assert(dest_volume);
-    pa_assert(dest_virtual_volume);
-    pa_assert(dest_channel_map);
-    pa_assert(sink);
-
-    if (!(sink->flags & PA_SINK_DECIBEL_VOLUME))
-        return;
-
-    pa_log_debug("Sink input volume changed");
-
-    max_volume = *dest_virtual_volume;
-    pa_cvolume_remap(&max_volume, dest_channel_map, &sink->channel_map);
-
-    for (i = PA_SINK_INPUT(pa_idxset_first(sink->inputs, &idx)); i; i = PA_SINK_INPUT(pa_idxset_next(sink->inputs, &idx))) {
-        /* skip this sink-input if we are processing a volume change request */
-        if (this && this == i)
-            continue;
-
-        if (pa_cvolume_max(&i->virtual_volume) > pa_cvolume_max(&max_volume)) {
-            max_volume = i->virtual_volume;
-            pa_cvolume_remap(&max_volume, &i->channel_map, &sink->channel_map);
-        }
-    }
-
-    /* Set the master volume, and normalize inputs */
-    if (!pa_cvolume_equal(&max_volume, pa_sink_get_volume(sink, TRUE))) {
-
-        pa_sink_set_volume(sink, &max_volume);
-
-        pa_log_debug("sink = %.2f (changed)", (double)pa_cvolume_avg(pa_sink_get_volume(sink, TRUE))/PA_VOLUME_NORM);
-
-        /* Now, normalize each of the internal volume (client sink-input volume / sink master volume) */
-        for (i = PA_SINK_INPUT(pa_idxset_first(sink->inputs, &idx)); i; i = PA_SINK_INPUT(pa_idxset_next(sink->inputs, &idx))) {
-            /* skip this sink-input if we are processing a volume change request */
-            if (this && this == i)
-                continue;
-
-            sink_volume = max_volume;
-            pa_cvolume_remap(&sink_volume, &sink->channel_map, &i->channel_map);
-            pa_sw_cvolume_divide(&i->volume, &i->virtual_volume, &sink_volume);
-            pa_log_debug("sink input { id = %d, flat = %.2f, true = %.2f }",
-                         i->index,
-                         (double)pa_cvolume_avg(&i->virtual_volume)/PA_VOLUME_NORM,
-                         (double)pa_cvolume_avg(&i->volume)/PA_VOLUME_NORM);
-            pa_asyncmsgq_post(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_SET_VOLUME, pa_xnewdup(struct pa_cvolume, &i->volume, 1), 0, NULL, pa_xfree);
-        }
-    } else
-        pa_log_debug("sink = %.2f", (double)pa_cvolume_avg(pa_sink_get_volume(sink, TRUE))/PA_VOLUME_NORM);
-
-    /* and this one */
-
-    sink_volume = max_volume;
-    pa_cvolume_remap(&sink_volume, &sink->channel_map, dest_channel_map);
-    pa_sw_cvolume_divide(dest_volume, dest_virtual_volume, &sink_volume);
-    pa_log_debug("caller sink input: { id = %d, flat = %.2f, true = %.2f }",
-                 this ? (int)this->index : -1,
-                 (double)pa_cvolume_avg(dest_virtual_volume)/PA_VOLUME_NORM,
-                 (double)pa_cvolume_avg(dest_volume)/PA_VOLUME_NORM);
-}
-
-static pa_hook_result_t sink_input_set_volume_hook_callback(pa_core *c, pa_sink_input_set_volume_data *this, struct userdata *u) {
-    pa_assert(this);
-    pa_assert(this->sink_input);
-
-    process_input_volume_change(&this->volume, &this->virtual_volume, &this->sink_input->channel_map,
-                                this->sink_input, this->sink_input->sink);
-
-    return PA_HOOK_OK;
-}
-
-static pa_hook_result_t sink_input_fixate_hook_callback(pa_core *core, pa_sink_input_new_data *this, struct userdata *u) {
-    pa_assert(this);
-    pa_assert(this->sink);
-
-    process_input_volume_change(&this->volume, &this->virtual_volume, &this->channel_map,
-                                NULL, this->sink);
-
-    return PA_HOOK_OK;
-}
-
-static void subscribe_callback(pa_core *core, pa_subscription_event_type_t t, uint32_t idx, void *userdata) {
-    struct userdata *u = userdata;
-    pa_sink *sink;
-    pa_sink_input *i;
-    uint32_t iidx;
-    pa_cvolume sink_volume;
-
-    pa_assert(core);
-    pa_assert(u);
-
-    if (t != (PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_NEW) &&
-        t != (PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE))
-        return;
-
-    if (!(sink = pa_idxset_get_by_index(core->sinks, idx)))
-        return;
-
-    if (!(sink->flags & PA_SINK_DECIBEL_VOLUME))
-        return;
-
-    pa_log_debug("Sink volume changed");
-    pa_log_debug("sink = %.2f", (double)pa_cvolume_avg(pa_sink_get_volume(sink, TRUE)) / PA_VOLUME_NORM);
-
-    sink_volume = *pa_sink_get_volume(sink, TRUE);
-
-    for (i = PA_SINK_INPUT(pa_idxset_first(sink->inputs, &iidx)); i; i = PA_SINK_INPUT(pa_idxset_next(sink->inputs, &iidx))) {
-        pa_cvolume si_volume;
-
-        si_volume = sink_volume;
-        pa_cvolume_remap(&si_volume, &sink->channel_map, &i->channel_map);
-        pa_sw_cvolume_multiply(&i->virtual_volume, &i->volume, &si_volume);
-        pa_log_debug("sink input = { id = %d, flat = %.2f, true = %.2f }",
-                     i->index,
-                     (double)pa_cvolume_avg(&i->virtual_volume)/PA_VOLUME_NORM,
-                     (double)pa_cvolume_avg(&i->volume)/PA_VOLUME_NORM);
-        pa_subscription_post(i->sink->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index);
-    }
-}
-
-int pa__init(pa_module*m) {
-    struct userdata *u;
-
-    pa_assert(m);
-
-    u = pa_xnew(struct userdata, 1);
-    m->userdata = u;
-
-    u->sink_input_fixate_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_FIXATE], PA_HOOK_LATE, (pa_hook_cb_t) sink_input_fixate_hook_callback, u);
-    u->sink_input_set_volume_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_SET_VOLUME], PA_HOOK_LATE, (pa_hook_cb_t) sink_input_set_volume_hook_callback, u);
-
-    u->subscription = pa_subscription_new(m->core, PA_SUBSCRIPTION_MASK_SINK, subscribe_callback, u);
-
-    return 0;
-}
-
-void pa__done(pa_module*m) {
-    struct userdata* u;
-
-    pa_assert(m);
-
-    if (!(u = m->userdata))
-        return;
-
-    if (u->subscription)
-      pa_subscription_free(u->subscription);
-
-    if (u->sink_input_set_volume_hook_slot)
-        pa_hook_slot_free(u->sink_input_set_volume_hook_slot);
-    if (u->sink_input_fixate_hook_slot)
-        pa_hook_slot_free(u->sink_input_fixate_hook_slot);
-
-    pa_xfree(u);
-}

commit 4bfa5d7d13350bd0eac439dbe251812ce437ea43
Author: Lennart Poettering <lennart at poettering.net>
Date:   Tue Jan 27 04:35:11 2009 +0100

    fix size calculation

diff --git a/src/pulsecore/bitset.h b/src/pulsecore/bitset.h
index 21e840a..95f5cfc 100644
--- a/src/pulsecore/bitset.h
+++ b/src/pulsecore/bitset.h
@@ -26,7 +26,7 @@
 #include <pulsecore/macro.h>
 
 #define PA_BITSET_ELEMENTS(n) (((n)+31)/32)
-#define PA_BITSET_SIZE(n) (PA_BITSET_ELEMENTS(n)*32)
+#define PA_BITSET_SIZE(n) (PA_BITSET_ELEMENTS(n)*4)
 
 typedef uint32_t pa_bitset_t;
 

commit d5f46e824e3f8a042e6f67dd4c3fc385545edd74
Author: Lennart Poettering <lennart at poettering.net>
Date:   Tue Jan 27 04:39:07 2009 +0100

    move flat volume logic into the core. while doing so add n_volume_steps field to sinks/sources

diff --git a/man/pulse-daemon.conf.5.xml.in b/man/pulse-daemon.conf.5.xml.in
index a516ee3..9ddcd6a 100644
--- a/man/pulse-daemon.conf.5.xml.in
+++ b/man/pulse-daemon.conf.5.xml.in
@@ -140,7 +140,6 @@ USA.
       precedence.</p>
     </option>
 
-
     <option>
       <p><opt>system-instance=</opt> Run the daemon as system-wide
       instance, requires root priviliges. Takes a boolean argument,
@@ -148,7 +147,6 @@ USA.
       argument takes precedence.</p>
     </option>
 
-
     <option>
       <p><opt>disable-shm=</opt> Disable data transfer via POSIX
       shared memory. Takes a boolean argument, defaults to
@@ -165,6 +163,13 @@ USA.
       memory overcommit.</p>
     </option>
 
+    <option>
+      <p><opt>flat-volumes=</opt> Enable 'flat' volumes, i.e. where
+      possible let the sink volume equal the maximum of the volumes of
+      the inputs connected to it. Takes a boolean argument, defaults
+      to <opt>yes</opt>.</p>
+    </option>
+
   </section>
 
   <section name="Scheduling">
diff --git a/src/daemon/daemon-conf.c b/src/daemon/daemon-conf.c
index c8c1b6f..279fb7b 100644
--- a/src/daemon/daemon-conf.c
+++ b/src/daemon/daemon-conf.c
@@ -64,6 +64,7 @@ static const pa_daemon_conf default_conf = {
     .realtime_priority = 5,  /* Half of JACK's default rtprio */
     .disallow_module_loading = FALSE,
     .disallow_exit = FALSE,
+    .flat_volumes = TRUE,
     .exit_idle_time = 20,
     .scache_idle_time = 20,
     .auto_log_target = 1,
@@ -418,6 +419,7 @@ int pa_daemon_conf_load(pa_daemon_conf *c, const char *filename) {
         { "system-instance",            pa_config_parse_bool,     NULL },
         { "no-cpu-limit",               pa_config_parse_bool,     NULL },
         { "disable-shm",                pa_config_parse_bool,     NULL },
+        { "flat-volumes",               pa_config_parse_bool,     NULL },
         { "exit-idle-time",             pa_config_parse_int,      NULL },
         { "scache-idle-time",           pa_config_parse_int,      NULL },
         { "realtime-priority",          parse_rtprio,             NULL },
@@ -490,6 +492,7 @@ int pa_daemon_conf_load(pa_daemon_conf *c, const char *filename) {
     table[i++].data = &c->system_instance;
     table[i++].data = &c->no_cpu_limit;
     table[i++].data = &c->disable_shm;
+    table[i++].data = &c->flat_volumes;
     table[i++].data = &c->exit_idle_time;
     table[i++].data = &c->scache_idle_time;
     table[i++].data = c;
@@ -650,6 +653,7 @@ char *pa_daemon_conf_dump(pa_daemon_conf *c) {
     pa_strbuf_printf(s, "system-instance = %s\n", pa_yes_no(c->system_instance));
     pa_strbuf_printf(s, "no-cpu-limit = %s\n", pa_yes_no(c->no_cpu_limit));
     pa_strbuf_printf(s, "disable-shm = %s\n", pa_yes_no(c->disable_shm));
+    pa_strbuf_printf(s, "flat-volumes = %s\n", pa_yes_no(c->flat_volumes));
     pa_strbuf_printf(s, "exit-idle-time = %i\n", c->exit_idle_time);
     pa_strbuf_printf(s, "scache-idle-time = %i\n", c->scache_idle_time);
     pa_strbuf_printf(s, "dl-search-path = %s\n", pa_strempty(c->dl_search_path));
diff --git a/src/daemon/daemon-conf.h b/src/daemon/daemon-conf.h
index 3b75b83..aa9d298 100644
--- a/src/daemon/daemon-conf.h
+++ b/src/daemon/daemon-conf.h
@@ -70,7 +70,8 @@ typedef struct pa_daemon_conf {
         load_default_script_file,
         disallow_exit,
         log_meta,
-        log_time;
+        log_time,
+        flat_volumes;
     int exit_idle_time,
         scache_idle_time,
         auto_log_target,
diff --git a/src/daemon/daemon.conf.in b/src/daemon/daemon.conf.in
index 00a9593..69d17f2 100644
--- a/src/daemon/daemon.conf.in
+++ b/src/daemon/daemon.conf.in
@@ -53,6 +53,8 @@
 ; disable-remixing = no
 ; disable-lfe-remixing = yes
 
+; flat-volumes = yes
+
 ; no-cpu-limit = no
 
 ; rlimit-fsize = -1
diff --git a/src/daemon/main.c b/src/daemon/main.c
index 9705f4c..bd8ad1d 100644
--- a/src/daemon/main.c
+++ b/src/daemon/main.c
@@ -917,6 +917,7 @@ int main(int argc, char *argv[]) {
     c->disable_lfe_remixing = !!conf->disable_lfe_remixing;
     c->running_as_daemon = !!conf->daemonize;
     c->disallow_exit = conf->disallow_exit;
+    c->flat_volumes = conf->flat_volumes;
 
     pa_assert_se(pa_signal_init(pa_mainloop_get_api(mainloop)) == 0);
     pa_signal_new(SIGINT, signal_callback, c);
diff --git a/src/modules/alsa/alsa-sink.c b/src/modules/alsa/alsa-sink.c
index 5020eac..3503c4a 100644
--- a/src/modules/alsa/alsa-sink.c
+++ b/src/modules/alsa/alsa-sink.c
@@ -757,7 +757,7 @@ static long to_alsa_volume(struct userdata *u, pa_volume_t vol) {
     return PA_CLAMP_UNLIKELY(alsa_vol, u->hw_volume_min, u->hw_volume_max);
 }
 
-static int sink_get_volume_cb(pa_sink *s) {
+static void sink_get_volume_cb(pa_sink *s) {
     struct userdata *u = s->userdata;
     int err;
     unsigned i;
@@ -820,27 +820,24 @@ static int sink_get_volume_cb(pa_sink *s) {
 
     if (!pa_cvolume_equal(&u->hardware_volume, &r)) {
 
-        u->hardware_volume = s->volume = r;
+        s->virtual_volume = u->hardware_volume = r;
 
         if (u->hw_dB_supported) {
             pa_cvolume reset;
 
             /* Hmm, so the hardware volume changed, let's reset our software volume */
-
             pa_cvolume_reset(&reset, s->sample_spec.channels);
             pa_sink_set_soft_volume(s, &reset);
         }
     }
 
-    return 0;
+    return;
 
 fail:
     pa_log_error("Unable to read volume: %s", snd_strerror(err));
-
-    return -1;
 }
 
-static int sink_set_volume_cb(pa_sink *s) {
+static void sink_set_volume_cb(pa_sink *s) {
     struct userdata *u = s->userdata;
     int err;
     unsigned i;
@@ -857,7 +854,7 @@ static int sink_set_volume_cb(pa_sink *s) {
             long alsa_vol;
             pa_volume_t vol;
 
-            vol = s->volume.values[i];
+            vol = s->virtual_volume.values[i];
 
             if (u->hw_dB_supported) {
 
@@ -894,7 +891,7 @@ static int sink_set_volume_cb(pa_sink *s) {
         pa_volume_t vol;
         long alsa_vol;
 
-        vol = pa_cvolume_max(&s->volume);
+        vol = pa_cvolume_max(&s->virtual_volume);
 
         if (u->hw_dB_supported) {
             alsa_vol = (long) (pa_sw_volume_to_dB(vol) * 100);
@@ -911,7 +908,7 @@ static int sink_set_volume_cb(pa_sink *s) {
             VALGRIND_MAKE_MEM_DEFINED(&alsa_vol, sizeof(alsa_vol));
 #endif
 
-            pa_cvolume_set(&r, s->volume.channels, pa_sw_volume_from_dB((double) (alsa_vol - u->hw_dB_max) / 100.0));
+            pa_cvolume_set(&r, s->sample_spec.channels, pa_sw_volume_from_dB((double) (alsa_vol - u->hw_dB_max) / 100.0));
 
         } else {
             alsa_vol = to_alsa_volume(u, vol);
@@ -932,11 +929,9 @@ static int sink_set_volume_cb(pa_sink *s) {
         char t[PA_CVOLUME_SNPRINT_MAX];
 
         /* Match exactly what the user requested by software */
+        pa_sw_cvolume_divide(&s->soft_volume, &s->virtual_volume, &r);
 
-        pa_sw_cvolume_divide(&r, &s->volume, &r);
-        pa_sink_set_soft_volume(s, &r);
-
-        pa_log_debug("Requested volume: %s", pa_cvolume_snprint(t, sizeof(t), &s->volume));
+        pa_log_debug("Requested volume: %s", pa_cvolume_snprint(t, sizeof(t), &s->virtual_volume));
         pa_log_debug("Got hardware volume: %s", pa_cvolume_snprint(t, sizeof(t), &u->hardware_volume));
         pa_log_debug("Calculated software volume: %s", pa_cvolume_snprint(t, sizeof(t), &r));
 
@@ -945,17 +940,15 @@ static int sink_set_volume_cb(pa_sink *s) {
         /* We can't match exactly what the user requested, hence let's
          * at least tell the user about it */
 
-        s->volume = r;
+        s->virtual_volume = r;
 
-    return 0;
+    return;
 
 fail:
     pa_log_error("Unable to set volume: %s", snd_strerror(err));
-
-    return -1;
 }
 
-static int sink_get_mute_cb(pa_sink *s) {
+static void sink_get_mute_cb(pa_sink *s) {
     struct userdata *u = s->userdata;
     int err, sw;
 
@@ -964,15 +957,13 @@ static int sink_get_mute_cb(pa_sink *s) {
 
     if ((err = snd_mixer_selem_get_playback_switch(u->mixer_elem, 0, &sw)) < 0) {
         pa_log_error("Unable to get switch: %s", snd_strerror(err));
-        return -1;
+        return;
     }
 
     s->muted = !sw;
-
-    return 0;
 }
 
-static int sink_set_mute_cb(pa_sink *s) {
+static void sink_set_mute_cb(pa_sink *s) {
     struct userdata *u = s->userdata;
     int err;
 
@@ -981,10 +972,8 @@ static int sink_set_mute_cb(pa_sink *s) {
 
     if ((err = snd_mixer_selem_set_playback_switch_all(u->mixer_elem, !s->muted)) < 0) {
         pa_log_error("Unable to set switch: %s", snd_strerror(err));
-        return -1;
+        return;
     }
-
-    return 0;
 }
 
 static void sink_update_requested_latency_cb(pa_sink *s) {
@@ -1552,6 +1541,8 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca
                 u->sink->flags |= PA_SINK_HW_VOLUME_CTRL | (u->hw_dB_supported ? PA_SINK_DECIBEL_VOLUME : 0);
                 pa_log_info("Using hardware volume control. Hardware dB scale %s.", u->hw_dB_supported ? "supported" : "not supported");
 
+                if (!u->hw_dB_supported)
+                    u->sink->n_volume_steps = u->hw_volume_max - u->hw_volume_min + 1;
             } else
                 pa_log_info("Using software volume control.");
         }
diff --git a/src/modules/alsa/alsa-source.c b/src/modules/alsa/alsa-source.c
index 96e0d89..c4d3418 100644
--- a/src/modules/alsa/alsa-source.c
+++ b/src/modules/alsa/alsa-source.c
@@ -703,7 +703,7 @@ static long to_alsa_volume(struct userdata *u, pa_volume_t vol) {
     return PA_CLAMP_UNLIKELY(alsa_vol, u->hw_volume_min, u->hw_volume_max);
 }
 
-static int source_get_volume_cb(pa_source *s) {
+static void source_get_volume_cb(pa_source *s) {
     struct userdata *u = s->userdata;
     int err;
     unsigned i;
@@ -766,27 +766,24 @@ static int source_get_volume_cb(pa_source *s) {
 
     if (!pa_cvolume_equal(&u->hardware_volume, &r)) {
 
-        u->hardware_volume = s->volume = r;
+        s->virtual_volume = u->hardware_volume = r;
 
         if (u->hw_dB_supported) {
             pa_cvolume reset;
 
             /* Hmm, so the hardware volume changed, let's reset our software volume */
-
             pa_cvolume_reset(&reset, s->sample_spec.channels);
             pa_source_set_soft_volume(s, &reset);
         }
     }
 
-    return 0;
+    return;
 
 fail:
     pa_log_error("Unable to read volume: %s", snd_strerror(err));
-
-    return -1;
 }
 
-static int source_set_volume_cb(pa_source *s) {
+static void source_set_volume_cb(pa_source *s) {
     struct userdata *u = s->userdata;
     int err;
     unsigned i;
@@ -803,7 +800,7 @@ static int source_set_volume_cb(pa_source *s) {
             long alsa_vol;
             pa_volume_t vol;
 
-            vol = s->volume.values[i];
+            vol = s->virtual_volume.values[i];
 
             if (u->hw_dB_supported) {
 
@@ -840,7 +837,7 @@ static int source_set_volume_cb(pa_source *s) {
         pa_volume_t vol;
         long alsa_vol;
 
-        vol = pa_cvolume_max(&s->volume);
+        vol = pa_cvolume_max(&s->virtual_volume);
 
         if (u->hw_dB_supported) {
             alsa_vol = (long) (pa_sw_volume_to_dB(vol) * 100);
@@ -857,7 +854,7 @@ static int source_set_volume_cb(pa_source *s) {
             VALGRIND_MAKE_MEM_DEFINED(&alsa_vol, sizeof(alsa_vol));
 #endif
 
-            pa_cvolume_set(&r, s->volume.channels, pa_sw_volume_from_dB((double) (alsa_vol - u->hw_dB_max) / 100.0));
+            pa_cvolume_set(&r, s->sample_spec.channels, pa_sw_volume_from_dB((double) (alsa_vol - u->hw_dB_max) / 100.0));
 
         } else {
             alsa_vol = to_alsa_volume(u, vol);
@@ -879,10 +876,9 @@ static int source_set_volume_cb(pa_source *s) {
 
         /* Match exactly what the user requested by software */
 
-        pa_sw_cvolume_divide(&r, &s->volume, &r);
-        pa_source_set_soft_volume(s, &r);
+        pa_sw_cvolume_divide(&s->soft_volume, &s->virtual_volume, &r);
 
-        pa_log_debug("Requested volume: %s", pa_cvolume_snprint(t, sizeof(t), &s->volume));
+        pa_log_debug("Requested volume: %s", pa_cvolume_snprint(t, sizeof(t), &s->virtual_volume));
         pa_log_debug("Got hardware volume: %s", pa_cvolume_snprint(t, sizeof(t), &u->hardware_volume));
         pa_log_debug("Calculated software volume: %s", pa_cvolume_snprint(t, sizeof(t), &r));
 
@@ -891,17 +887,15 @@ static int source_set_volume_cb(pa_source *s) {
         /* We can't match exactly what the user requested, hence let's
          * at least tell the user about it */
 
-        s->volume = r;
+        s->virtual_volume = r;
 
-    return 0;
+    return;
 
 fail:
     pa_log_error("Unable to set volume: %s", snd_strerror(err));
-
-    return -1;
 }
 
-static int source_get_mute_cb(pa_source *s) {
+static void source_get_mute_cb(pa_source *s) {
     struct userdata *u = s->userdata;
     int err, sw;
 
@@ -910,15 +904,13 @@ static int source_get_mute_cb(pa_source *s) {
 
     if ((err = snd_mixer_selem_get_capture_switch(u->mixer_elem, 0, &sw)) < 0) {
         pa_log_error("Unable to get switch: %s", snd_strerror(err));
-        return -1;
+        return;
     }
 
     s->muted = !sw;
-
-    return 0;
 }
 
-static int source_set_mute_cb(pa_source *s) {
+static void source_set_mute_cb(pa_source *s) {
     struct userdata *u = s->userdata;
     int err;
 
@@ -927,10 +919,8 @@ static int source_set_mute_cb(pa_source *s) {
 
     if ((err = snd_mixer_selem_set_capture_switch_all(u->mixer_elem, !s->muted)) < 0) {
         pa_log_error("Unable to set switch: %s", snd_strerror(err));
-        return -1;
+        return;
     }
-
-    return 0;
 }
 
 static void source_update_requested_latency_cb(pa_source *s) {
@@ -1372,6 +1362,9 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p
                 u->source->set_volume = source_set_volume_cb;
                 u->source->flags |= PA_SOURCE_HW_VOLUME_CTRL | (u->hw_dB_supported ? PA_SOURCE_DECIBEL_VOLUME : 0);
                 pa_log_info("Using hardware volume control. Hardware dB scale %s.", u->hw_dB_supported ? "supported" : "not supported");
+
+                if (!u->hw_dB_supported)
+                    u->source->n_volume_steps = u->hw_volume_max - u->hw_volume_min + 1;
             } else
                 pa_log_info("Using software volume control.");
         }
diff --git a/src/modules/module-lirc.c b/src/modules/module-lirc.c
index bbe4f7c..9a782ca 100644
--- a/src/modules/module-lirc.c
+++ b/src/modules/module-lirc.c
@@ -135,7 +135,7 @@ static void io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io_event
                                     cv.values[i] = PA_VOLUME_NORM;
                             }
 
-                            pa_sink_set_volume(s, &cv);
+                            pa_sink_set_volume(s, &cv, TRUE, TRUE);
                             break;
 
                         case DOWN:
@@ -146,7 +146,7 @@ static void io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io_event
                                     cv.values[i] = PA_VOLUME_MUTED;
                             }
 
-                            pa_sink_set_volume(s, &cv);
+                            pa_sink_set_volume(s, &cv, TRUE, TRUE);
                             break;
 
                         case MUTE:
diff --git a/src/modules/module-mmkbd-evdev.c b/src/modules/module-mmkbd-evdev.c
index aa6832b..a379923 100644
--- a/src/modules/module-mmkbd-evdev.c
+++ b/src/modules/module-mmkbd-evdev.c
@@ -126,7 +126,7 @@ static void io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io_event
                                     cv.values[i] = PA_VOLUME_NORM;
                             }
 
-                            pa_sink_set_volume(s, &cv);
+                            pa_sink_set_volume(s, &cv, TRUE, TRUE);
                             break;
 
                         case DOWN:
@@ -137,7 +137,7 @@ static void io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io_event
                                     cv.values[i] = PA_VOLUME_MUTED;
                             }
 
-                            pa_sink_set_volume(s, &cv);
+                            pa_sink_set_volume(s, &cv, TRUE, TRUE);
                             break;
 
                         case MUTE_TOGGLE:
diff --git a/src/modules/module-position-event-sounds.c b/src/modules/module-position-event-sounds.c
index 90e693a..e75b1c5 100644
--- a/src/modules/module-position-event-sounds.c
+++ b/src/modules/module-position-event-sounds.c
@@ -101,23 +101,23 @@ static pa_hook_result_t sink_input_fixate_hook_callback(pa_core *core, pa_sink_i
 
     pa_log_debug("Positioning event sound '%s' at %0.2f.", pa_strnull(pa_proplist_gets(data->proplist, PA_PROP_EVENT_ID)), f);
 
-    if (!data->volume_is_set) {
-        pa_cvolume_reset(&data->volume, data->sample_spec.channels);
-        data->volume_is_set = TRUE;
+    if (!data->virtual_volume_is_set) {
+        pa_cvolume_reset(&data->virtual_volume, data->sample_spec.channels);
+        data->virtual_volume_is_set = TRUE;
     }
 
     for (c = 0; c < data->sample_spec.channels; c++) {
 
         if (is_left(data->channel_map.map[c]))
-            data->volume.values[c] =
-                pa_sw_volume_multiply(data->volume.values[c], (pa_volume_t) (PA_VOLUME_NORM * (1.0 - f)));
+            data->virtual_volume.values[c] =
+                pa_sw_volume_multiply(data->virtual_volume.values[c], (pa_volume_t) (PA_VOLUME_NORM * (1.0 - f)));
 
         if (is_right(data->channel_map.map[c]))
-            data->volume.values[c] =
-                pa_sw_volume_multiply(data->volume.values[c], (pa_volume_t) (PA_VOLUME_NORM * f));
+            data->virtual_volume.values[c] =
+                pa_sw_volume_multiply(data->virtual_volume.values[c], (pa_volume_t) (PA_VOLUME_NORM * f));
     }
 
-    pa_log_debug("Final volume %s.", pa_cvolume_snprint(t, sizeof(t), &data->volume));
+    pa_log_debug("Final volume %s.", pa_cvolume_snprint(t, sizeof(t), &data->virtual_volume));
 
     return PA_HOOK_OK;
 }
diff --git a/src/modules/module-raop-sink.c b/src/modules/module-raop-sink.c
index bb93ca8..02ef2aa 100644
--- a/src/modules/module-raop-sink.c
+++ b/src/modules/module-raop-sink.c
@@ -255,20 +255,17 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse
     return pa_sink_process_msg(o, code, data, offset, chunk);
 }
 
-static int sink_get_volume_cb(pa_sink *s) {
+static void sink_get_volume_cb(pa_sink *s) {
     struct userdata *u = s->userdata;
     int i;
 
     pa_assert(u);
 
-    for (i = 0; i < s->sample_spec.channels; i++) {
-        s->volume.values[i] = u->volume;
-    }
-
-    return 0;
+    for (i = 0; i < s->sample_spec.channels; i++)
+        s->virtual_volume.values[i] = u->volume;
 }
 
-static int sink_set_volume_cb(pa_sink *s) {
+static void sink_set_volume_cb(pa_sink *s) {
     struct userdata *u = s->userdata;
     int rv;
 
@@ -276,39 +273,34 @@ static int sink_set_volume_cb(pa_sink *s) {
 
     /* If we're muted, we fake it */
     if (u->muted)
-        return 0;
+        return;
 
     pa_assert(s->sample_spec.channels > 0);
 
     /* Avoid pointless volume sets */
-    if (u->volume == s->volume.values[0])
-        return 0;
+    if (u->volume == s->virtual_volume.values[0])
+        return;
 
-    rv = pa_raop_client_set_volume(u->raop, s->volume.values[0]);
+    rv = pa_raop_client_set_volume(u->raop, s->virtual_volume.values[0]);
     if (0 == rv)
-        u->volume = s->volume.values[0];
-
-    return rv;
+        u->volume = s->virtual_volume.values[0];
 }
 
-static int sink_get_mute_cb(pa_sink *s) {
+static void sink_get_mute_cb(pa_sink *s) {
     struct userdata *u = s->userdata;
 
     pa_assert(u);
 
     s->muted = u->muted;
-    return 0;
 }
 
-static int sink_set_mute_cb(pa_sink *s) {
+static void sink_set_mute_cb(pa_sink *s) {
     struct userdata *u = s->userdata;
-    int rv;
 
     pa_assert(u);
 
-    rv = pa_raop_client_set_volume(u->raop, (s->muted ? PA_VOLUME_MUTED : u->volume));
+    pa_raop_client_set_volume(u->raop, (s->muted ? PA_VOLUME_MUTED : u->volume));
     u->muted = s->muted;
-    return rv;
 }
 
 static void thread_func(void *userdata) {
diff --git a/src/modules/module-stream-restore.c b/src/modules/module-stream-restore.c
index 734b2c5..464ff2d 100644
--- a/src/modules/module-stream-restore.c
+++ b/src/modules/module-stream-restore.c
@@ -334,9 +334,9 @@ static pa_hook_result_t sink_input_fixate_hook_callback(pa_core *c, pa_sink_inpu
 
         if (u->restore_volume) {
 
-            if (!new_data->volume_is_set) {
+            if (!new_data->virtual_volume_is_set) {
                 pa_log_info("Restoring volume for sink input %s.", name);
-                pa_sink_input_new_data_set_volume(new_data, pa_cvolume_remap(&e->volume, &e->channel_map, &new_data->channel_map));
+                pa_sink_input_new_data_set_virtual_volume(new_data, pa_cvolume_remap(&e->volume, &e->channel_map, &new_data->channel_map));
             } else
                 pa_log_debug("Not restoring volume for sink input %s, because already set.", name);
         }
diff --git a/src/modules/module-tunnel.c b/src/modules/module-tunnel.c
index 61b9516..5c7a6e5 100644
--- a/src/modules/module-tunnel.c
+++ b/src/modules/module-tunnel.c
@@ -1056,10 +1056,10 @@ static void sink_input_info_cb(pa_pdispatch *pd, uint32_t command,  uint32_t tag
     pa_assert(u->sink);
 
     if ((u->version < 11 || !!mute == !!u->sink->muted) &&
-        pa_cvolume_equal(&volume, &u->sink->volume))
+        pa_cvolume_equal(&volume, &u->sink->virtual_volume))
         return;
 
-    memcpy(&u->sink->volume, &volume, sizeof(pa_cvolume));
+    memcpy(&u->sink->virtual_volume, &volume, sizeof(pa_cvolume));
 
     if (u->version >= 11)
         u->sink->muted = !!mute;
@@ -1621,7 +1621,7 @@ static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata
 #ifdef TUNNEL_SINK
 
 /* Called from main context */
-static int sink_set_volume(pa_sink *sink) {
+static void sink_set_volume(pa_sink *sink) {
     struct userdata *u;
     pa_tagstruct *t;
     uint32_t tag;
@@ -1634,14 +1634,12 @@ static int sink_set_volume(pa_sink *sink) {
     pa_tagstruct_putu32(t, PA_COMMAND_SET_SINK_INPUT_VOLUME);
     pa_tagstruct_putu32(t, tag = u->ctag++);
     pa_tagstruct_putu32(t, u->device_index);
-    pa_tagstruct_put_cvolume(t, &sink->volume);
+    pa_tagstruct_put_cvolume(t, &sink->virtual_volume);
     pa_pstream_send_tagstruct(u->pstream, t);
-
-    return 0;
 }
 
 /* Called from main context */
-static int sink_set_mute(pa_sink *sink) {
+static void sink_set_mute(pa_sink *sink) {
     struct userdata *u;
     pa_tagstruct *t;
     uint32_t tag;
@@ -1651,7 +1649,7 @@ static int sink_set_mute(pa_sink *sink) {
     pa_assert(u);
 
     if (u->version < 11)
-        return -1;
+        return;
 
     t = pa_tagstruct_new(NULL, 0);
     pa_tagstruct_putu32(t, PA_COMMAND_SET_SINK_INPUT_MUTE);
@@ -1659,8 +1657,6 @@ static int sink_set_mute(pa_sink *sink) {
     pa_tagstruct_putu32(t, u->device_index);
     pa_tagstruct_put_boolean(t, !!sink->muted);
     pa_pstream_send_tagstruct(u->pstream, t);
-
-    return 0;
 }
 
 #endif
diff --git a/src/modules/module-volume-restore.c b/src/modules/module-volume-restore.c
index bdfd381..e32552c 100644
--- a/src/modules/module-volume-restore.c
+++ b/src/modules/module-volume-restore.c
@@ -443,7 +443,7 @@ static pa_hook_result_t sink_input_fixate_hook_callback(pa_core *c, pa_sink_inpu
 
         if (r->volume_is_set && data->sample_spec.channels == r->volume.channels) {
             pa_log_info("Restoring volume for <%s>", r->name);
-            pa_sink_input_new_data_set_volume(data, &r->volume);
+            pa_sink_input_new_data_set_virtual_volume(data, &r->volume);
         }
     }
 
diff --git a/src/modules/oss/module-oss.c b/src/modules/oss/module-oss.c
index 7271a08..eac0c8e 100644
--- a/src/modules/oss/module-oss.c
+++ b/src/modules/oss/module-oss.c
@@ -443,7 +443,6 @@ static pa_usec_t io_sink_get_latency(struct userdata *u) {
     return r;
 }
 
-
 static pa_usec_t io_source_get_latency(struct userdata *u) {
     pa_usec_t r = 0;
 
@@ -527,9 +526,6 @@ static int suspend(struct userdata *u) {
     return 0;
 }
 
-static int sink_get_volume(pa_sink *s);
-static int source_get_volume(pa_source *s);
-
 static int unsuspend(struct userdata *u) {
     int m;
     pa_sample_spec ss, *ss_original;
@@ -620,10 +616,10 @@ static int unsuspend(struct userdata *u) {
 
     build_pollfd(u);
 
-    if (u->sink && u->sink->get_volume)
-        u->sink->get_volume(u->sink);
-    if (u->source && u->source->get_volume)
-        u->source->get_volume(u->source);
+    if (u->sink)
+        pa_sink_get_volume(u->sink, TRUE);
+    if (u->source)
+        pa_source_get_volume(u->source, TRUE);
 
     pa_log_info("Resumed successfully...");
 
@@ -798,84 +794,76 @@ static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t off
     return ret;
 }
 
-static int sink_get_volume(pa_sink *s) {
+static void sink_get_volume(pa_sink *s) {
     struct userdata *u;
-    int r;
 
     pa_assert_se(u = s->userdata);
 
     pa_assert(u->mixer_devmask & (SOUND_MASK_VOLUME|SOUND_MASK_PCM));
 
     if (u->mixer_devmask & SOUND_MASK_VOLUME)
-        if ((r = pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_VOLUME, &s->sample_spec, &s->volume)) >= 0)
-            return r;
+        if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_VOLUME, &s->sample_spec, &s->virtual_volume) >= 0)
+            return;
 
     if (u->mixer_devmask & SOUND_MASK_PCM)
-        if ((r = pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_PCM, &s->sample_spec, &s->volume)) >= 0)
-            return r;
+        if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_PCM, &s->sample_spec, &s->virtual_volume) >= 0)
+            return;
 
     pa_log_info("Device doesn't support reading mixer settings: %s", pa_cstrerror(errno));
-    return -1;
 }
 
-static int sink_set_volume(pa_sink *s) {
+static void sink_set_volume(pa_sink *s) {
     struct userdata *u;
-    int r;
 
     pa_assert_se(u = s->userdata);
 
     pa_assert(u->mixer_devmask & (SOUND_MASK_VOLUME|SOUND_MASK_PCM));
 
     if (u->mixer_devmask & SOUND_MASK_VOLUME)
-        if ((r = pa_oss_set_volume(u->mixer_fd, SOUND_MIXER_WRITE_VOLUME, &s->sample_spec, &s->volume)) >= 0)
-            return r;
+        if (pa_oss_set_volume(u->mixer_fd, SOUND_MIXER_WRITE_VOLUME, &s->sample_spec, &s->virtual_volume) >= 0)
+            return;
 
     if (u->mixer_devmask & SOUND_MASK_PCM)
-        if ((r = pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_WRITE_PCM, &s->sample_spec, &s->volume)) >= 0)
-            return r;
+        if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_WRITE_PCM, &s->sample_spec, &s->virtual_volume) >= 0)
+            return;
 
     pa_log_info("Device doesn't support writing mixer settings: %s", pa_cstrerror(errno));
-    return -1;
 }
 
-static int source_get_volume(pa_source *s) {
+static void source_get_volume(pa_source *s) {
     struct userdata *u;
-    int r;
 
     pa_assert_se(u = s->userdata);
 
     pa_assert(u->mixer_devmask & (SOUND_MASK_IGAIN|SOUND_MASK_RECLEV));
 
     if (u->mixer_devmask & SOUND_MASK_IGAIN)
-        if ((r = pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_IGAIN, &s->sample_spec, &s->volume)) >= 0)
-            return r;
+        if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_IGAIN, &s->sample_spec, &s->virtual_volume) >= 0)
+            return;
 
     if (u->mixer_devmask & SOUND_MASK_RECLEV)
-        if ((r = pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_RECLEV, &s->sample_spec, &s->volume)) >= 0)
-            return r;
+        if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_RECLEV, &s->sample_spec, &s->virtual_volume) >= 0)
+            return;
 
     pa_log_info("Device doesn't support reading mixer settings: %s", pa_cstrerror(errno));
-    return -1;
 }
 
-static int source_set_volume(pa_source *s) {
+static void source_set_volume(pa_source *s) {
     struct userdata *u;
-    int r;
 
     pa_assert_se(u = s->userdata);
 
     pa_assert(u->mixer_devmask & (SOUND_MASK_IGAIN|SOUND_MASK_RECLEV));
 
     if (u->mixer_devmask & SOUND_MASK_IGAIN)
-        if ((r = pa_oss_set_volume(u->mixer_fd, SOUND_MIXER_WRITE_IGAIN, &s->sample_spec, &s->volume)) >= 0)
-            return r;
+        if (pa_oss_set_volume(u->mixer_fd, SOUND_MIXER_WRITE_IGAIN, &s->sample_spec, &s->virtual_volume) >= 0)
+            return;
 
     if (u->mixer_devmask & SOUND_MASK_RECLEV)
-        if ((r = pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_WRITE_RECLEV, &s->sample_spec, &s->volume)) >= 0)
-            return r;
+        if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_WRITE_RECLEV, &s->sample_spec, &s->virtual_volume) >= 0)
+            return;
 
     pa_log_info("Device doesn't support writing mixer settings: %s", pa_cstrerror(errno));
-    return -1;
 }
 
 static void thread_func(void *userdata) {
@@ -1417,6 +1405,7 @@ int pa__init(pa_module*m) {
                 u->sink->flags |= PA_SINK_HW_VOLUME_CTRL;
                 u->sink->get_volume = sink_get_volume;
                 u->sink->set_volume = sink_set_volume;
+                u->sink->n_volume_steps = 101;
                 do_close = FALSE;
             }
 
@@ -1425,6 +1414,7 @@ int pa__init(pa_module*m) {
                 u->source->flags |= PA_SOURCE_HW_VOLUME_CTRL;
                 u->source->get_volume = source_get_volume;
                 u->source->set_volume = source_set_volume;
+                u->source->n_volume_steps = 101;
                 do_close = FALSE;
             }
         }
diff --git a/src/pulse/def.h b/src/pulse/def.h
index c2c90e9..7517a7a 100644
--- a/src/pulse/def.h
+++ b/src/pulse/def.h
@@ -600,9 +600,13 @@ typedef enum pa_sink_flags {
     PA_SINK_HW_MUTE_CTRL = 0x0010U,
     /**< Supports hardware mute control \since 0.9.11 */
 
-    PA_SINK_DECIBEL_VOLUME = 0x0020U
+    PA_SINK_DECIBEL_VOLUME = 0x0020U,
     /**< Volume can be translated to dB with pa_sw_volume_to_dB()
      * \since 0.9.11 */
+
+    PA_SINK_FLAT_VOLUME = 0x0040U
+    /**< This sink is in flat volume mode, i.e. always the maximum of
+     * the volume of all connected inputs. \since 0.9.15 */
 } pa_sink_flags_t;
 
 /** \cond fulldocs */
@@ -612,6 +616,7 @@ typedef enum pa_sink_flags {
 #define PA_SINK_NETWORK PA_SINK_NETWORK
 #define PA_SINK_HW_MUTE_CTRL PA_SINK_HW_MUTE_CTRL
 #define PA_SINK_DECIBEL_VOLUME PA_SINK_DECIBEL_VOLUME
+#define PA_SINK_FLAT_VOLUME PA_SINK_FLAT_VOLUME
 /** \endcond */
 
 /** Sink state. \since 0.9.15 */
diff --git a/src/pulse/introspect.c b/src/pulse/introspect.c
index c5c9678..1d50939 100644
--- a/src/pulse/introspect.c
+++ b/src/pulse/introspect.c
@@ -159,6 +159,7 @@ static void context_get_sink_info_callback(pa_pdispatch *pd, uint32_t command, u
             memset(&i, 0, sizeof(i));
             i.proplist = pa_proplist_new();
             i.base_volume = PA_VOLUME_NORM;
+            i.n_volume_steps = PA_VOLUME_NORM+1;
             mute = FALSE;
             state = PA_SINK_INVALID_STATE;
 
@@ -180,7 +181,8 @@ static void context_get_sink_info_callback(pa_pdispatch *pd, uint32_t command, u
                   pa_tagstruct_get_usec(t, &i.configured_latency) < 0)) ||
                 (o->context->version >= 15 &&
                  (pa_tagstruct_get_volume(t, &i.base_volume) < 0 ||
-                  pa_tagstruct_getu32(t, &state) < 0))) {
+                  pa_tagstruct_getu32(t, &state) < 0 ||
+                  pa_tagstruct_getu32(t, &i.n_volume_steps) < 0))) {
 
                 pa_context_fail(o->context, PA_ERR_PROTOCOL);
                 pa_proplist_free(i.proplist);
@@ -288,6 +290,7 @@ static void context_get_source_info_callback(pa_pdispatch *pd, uint32_t command,
             memset(&i, 0, sizeof(i));
             i.proplist = pa_proplist_new();
             i.base_volume = PA_VOLUME_NORM;
+            i.n_volume_steps = PA_VOLUME_NORM+1;
             mute = FALSE;
             state = PA_SOURCE_INVALID_STATE;
 
@@ -309,7 +312,8 @@ static void context_get_source_info_callback(pa_pdispatch *pd, uint32_t command,
                   pa_tagstruct_get_usec(t, &i.configured_latency) < 0)) ||
                 (o->context->version >= 15 &&
                  (pa_tagstruct_get_volume(t, &i.base_volume) < 0 ||
-                  pa_tagstruct_getu32(t, &state) < 0))) {
+                  pa_tagstruct_getu32(t, &state) < 0 ||
+                  pa_tagstruct_getu32(t, &i.n_volume_steps) < 0))) {
 
                 pa_context_fail(o->context, PA_ERR_PROTOCOL);
                 pa_proplist_free(i.proplist);
diff --git a/src/pulse/introspect.h b/src/pulse/introspect.h
index d8a28ff..badc787 100644
--- a/src/pulse/introspect.h
+++ b/src/pulse/introspect.h
@@ -214,6 +214,7 @@ typedef struct pa_sink_info {
     pa_usec_t configured_latency;      /**< The latency this device has been configured to. \since 0.9.11 */
     pa_volume_t base_volume;           /**< Some kind of "base" volume that refers to unamplified/unattenuated volume in the context of the output device. \since 0.9.15 */
     pa_sink_state_t state;             /**< State \since 0.9.15 */
+    uint32_t n_volume_steps;           /**< Number of volume steps for sinks which do not support arbitrary volumes. \since 0.9.15 */
 } pa_sink_info;
 
 /** Callback prototype for pa_context_get_sink_info_by_name() and friends */
@@ -269,8 +270,9 @@ typedef struct pa_source_info {
     pa_source_flags_t flags;            /**< Flags */
     pa_proplist *proplist;              /**< Property list \since 0.9.11 */
     pa_usec_t configured_latency;       /**< The latency this device has been configured to. \since 0.9.11 */
-    pa_volume_t base_volume;           /**< Some kind of "base" volume that refers to unamplified/unattenuated volume in the context of the input device. \since 0.9.15 */
-    pa_source_state_t state;             /**< State \since 0.9.15 */
+    pa_volume_t base_volume;            /**< Some kind of "base" volume that refers to unamplified/unattenuated volume in the context of the input device. \since 0.9.15 */
+    pa_source_state_t state;            /**< State \since 0.9.15 */
+    uint32_t n_volume_steps;            /**< Number of volume steps for sources which do not support arbitrary volumes. \since 0.9.15 */
 } pa_source_info;
 
 /** Callback prototype for pa_context_get_source_info_by_name() and friends */
diff --git a/src/pulsecore/cli-command.c b/src/pulsecore/cli-command.c
index da3ef1e..b93f24f 100644
--- a/src/pulsecore/cli-command.c
+++ b/src/pulsecore/cli-command.c
@@ -518,7 +518,7 @@ static int pa_cli_command_sink_volume(pa_core *c, pa_tokenizer *t, pa_strbuf *bu
     }
 
     pa_cvolume_set(&cvolume, sink->sample_spec.channels, volume);
-    pa_sink_set_volume(sink, &cvolume);
+    pa_sink_set_volume(sink, &cvolume, TRUE, TRUE);
     return 0;
 }
 
diff --git a/src/pulsecore/cli-text.c b/src/pulsecore/cli-text.c
index c74ca5d..647fc1b 100644
--- a/src/pulsecore/cli-text.c
+++ b/src/pulsecore/cli-text.c
@@ -232,11 +232,12 @@ char *pa_sink_list_to_string(pa_core *c) {
             "  %c index: %u\n"
             "\tname: <%s>\n"
             "\tdriver: <%s>\n"
-            "\tflags: %s%s%s%s%s%s\n"
+            "\tflags: %s%s%s%s%s%s%s\n"
             "\tstate: %s\n"
             "\tvolume: %s%s%s\n"
             "\t        balance %0.2f\n"
             "\tbase volume: %s%s%s\n"
+            "\tvolume steps: %u\n"
             "\tmuted: %s\n"
             "\tcurrent latency: %0.2f ms\n"
             "\tconfigured latency: %0.2f ms; range is %0.2f .. %0.2f ms\n"
@@ -257,6 +258,7 @@ char *pa_sink_list_to_string(pa_core *c) {
             sink->flags & PA_SINK_HW_VOLUME_CTRL ? "HW_VOLUME_CTRL " : "",
             sink->flags & PA_SINK_DECIBEL_VOLUME ? "DECIBEL_VOLUME " : "",
             sink->flags & PA_SINK_LATENCY ? "LATENCY " : "",
+            sink->flags & PA_SINK_FLAT_VOLUME ? "FLAT_VOLUME" : "",
             sink_state_to_string(pa_sink_get_state(sink)),
             pa_cvolume_snprint(cv, sizeof(cv), pa_sink_get_volume(sink, FALSE)),
             sink->flags & PA_SINK_DECIBEL_VOLUME ? "\n\t        " : "",
@@ -265,6 +267,7 @@ char *pa_sink_list_to_string(pa_core *c) {
             pa_volume_snprint(v, sizeof(v), sink->base_volume),
             sink->flags & PA_SINK_DECIBEL_VOLUME ? "\n\t             " : "",
             sink->flags & PA_SINK_DECIBEL_VOLUME ? pa_sw_volume_snprint_dB(vdb, sizeof(vdb), sink->base_volume) : "",
+            sink->n_volume_steps,
             pa_yes_no(pa_sink_get_mute(sink, FALSE)),
             (double) pa_sink_get_latency(sink) / (double) PA_USEC_PER_MSEC,
             (double) pa_sink_get_requested_latency(sink) / (double) PA_USEC_PER_MSEC,
@@ -275,7 +278,7 @@ char *pa_sink_list_to_string(pa_core *c) {
             sink->monitor_source ? sink->monitor_source->index : PA_INVALID_INDEX,
             pa_sample_spec_snprint(ss, sizeof(ss), &sink->sample_spec),
             pa_channel_map_snprint(cm, sizeof(cm), &sink->channel_map),
-            cmn ? "\n\t            " : "",
+            cmn ? "\n\t             " : "",
             cmn ? cmn : "",
             pa_sink_used_by(sink),
             pa_sink_linked_by(sink));
@@ -327,6 +330,7 @@ char *pa_source_list_to_string(pa_core *c) {
             "\tvolume: %s%s%s\n"
             "\t        balance %0.2f\n"
             "\tbase volume: %s%s%s\n"
+            "\tvolume steps: %u\n"
             "\tmuted: %s\n"
             "\tcurrent latency: %0.2f ms\n"
             "\tconfigured latency: %0.2f ms; range is %0.2f .. %0.2f ms\n"
@@ -353,6 +357,7 @@ char *pa_source_list_to_string(pa_core *c) {
             pa_volume_snprint(v, sizeof(v), source->base_volume),
             source->flags & PA_SOURCE_DECIBEL_VOLUME ? "\n\t             " : "",
             source->flags & PA_SOURCE_DECIBEL_VOLUME ? pa_sw_volume_snprint_dB(vdb, sizeof(vdb), source->base_volume) : "",
+            source->n_volume_steps,
             pa_yes_no(pa_source_get_mute(source, FALSE)),
             (double) pa_source_get_latency(source) / PA_USEC_PER_MSEC,
             (double) pa_source_get_requested_latency(source) / PA_USEC_PER_MSEC,
@@ -361,7 +366,7 @@ char *pa_source_list_to_string(pa_core *c) {
             (unsigned long) pa_source_get_max_rewind(source) / 1024,
             pa_sample_spec_snprint(ss, sizeof(ss), &source->sample_spec),
             pa_channel_map_snprint(cm, sizeof(cm), &source->channel_map),
-            cmn ? "\n\t            " : "",
+            cmn ? "\n\t             " : "",
             cmn ? cmn : "",
             pa_source_used_by(source),
             pa_source_linked_by(source));
@@ -440,7 +445,7 @@ char *pa_source_output_list_to_string(pa_core *c) {
             clt,
             pa_sample_spec_snprint(ss, sizeof(ss), &o->sample_spec),
             pa_channel_map_snprint(cm, sizeof(cm), &o->channel_map),
-            cmn ? "\n\t            " : "",
+            cmn ? "\n\t             " : "",
             cmn ? cmn : "",
             pa_resample_method_to_string(pa_source_output_get_resample_method(o)));
         if (o->module)
@@ -525,7 +530,7 @@ char *pa_sink_input_list_to_string(pa_core *c) {
             clt,
             pa_sample_spec_snprint(ss, sizeof(ss), &i->sample_spec),
             pa_channel_map_snprint(cm, sizeof(cm), &i->channel_map),
-            cmn ? "\n\t            " : "",
+            cmn ? "\n\t             " : "",
             cmn ? cmn : "",
             pa_resample_method_to_string(pa_sink_input_get_resample_method(i)));
 
@@ -584,7 +589,7 @@ char *pa_scache_list_to_string(pa_core *c) {
                 e->index,
                 ss,
                 cm,
-                cmn ? "\n\t            " : "",
+                cmn ? "\n\t             " : "",
                 cmn ? cmn : "",
                 (long unsigned)(e->memchunk.memblock ? e->memchunk.length : 0),
                 l,
diff --git a/src/pulsecore/core.c b/src/pulsecore/core.c
index c6e96c1..381a677 100644
--- a/src/pulsecore/core.c
+++ b/src/pulsecore/core.c
@@ -127,6 +127,7 @@ pa_core* pa_core_new(pa_mainloop_api *m, pa_bool_t shared, size_t shm_size) {
 
     c->exit_idle_time = -1;
     c->scache_idle_time = 20;
+    c->flat_volumes = TRUE;
 
     c->resample_method = PA_RESAMPLER_SPEEX_FLOAT_BASE + 3;
 
diff --git a/src/pulsecore/core.h b/src/pulsecore/core.h
index ffac6d3..e33a245 100644
--- a/src/pulsecore/core.h
+++ b/src/pulsecore/core.h
@@ -55,7 +55,6 @@ typedef enum pa_core_hook {
     PA_CORE_HOOK_SINK_UNLINK_POST,
     PA_CORE_HOOK_SINK_STATE_CHANGED,
     PA_CORE_HOOK_SINK_PROPLIST_CHANGED,
-    PA_CORE_HOOK_SINK_SET_VOLUME,
     PA_CORE_HOOK_SOURCE_NEW,
     PA_CORE_HOOK_SOURCE_FIXATE,
     PA_CORE_HOOK_SOURCE_PUT,
@@ -129,6 +128,7 @@ struct pa_core {
     pa_silence_cache silence_cache;
 
     int exit_idle_time, scache_idle_time;
+    pa_bool_t flat_volumes;
 
     pa_time_event *exit_event;
 
diff --git a/src/pulsecore/play-memblockq.c b/src/pulsecore/play-memblockq.c
index 758c0de..eac21de 100644
--- a/src/pulsecore/play-memblockq.c
+++ b/src/pulsecore/play-memblockq.c
@@ -197,7 +197,7 @@ pa_sink_input* pa_memblockq_sink_input_new(
     data.driver = __FILE__;
     pa_sink_input_new_data_set_sample_spec(&data, ss);
     pa_sink_input_new_data_set_channel_map(&data, map);
-    pa_sink_input_new_data_set_volume(&data, volume);
+    pa_sink_input_new_data_set_virtual_volume(&data, volume);
     pa_proplist_update(data.proplist, PA_UPDATE_REPLACE, p);
 
     u->sink_input = pa_sink_input_new(sink->core, &data, 0);
diff --git a/src/pulsecore/protocol-native.c b/src/pulsecore/protocol-native.c
index 3896dff..80edb20 100644
--- a/src/pulsecore/protocol-native.c
+++ b/src/pulsecore/protocol-native.c
@@ -1012,7 +1012,7 @@ static playback_stream* playback_stream_new(
     pa_sink_input_new_data_set_sample_spec(&data, ss);
     pa_sink_input_new_data_set_channel_map(&data, map);
     if (volume)
-        pa_sink_input_new_data_set_volume(&data, volume);
+        pa_sink_input_new_data_set_virtual_volume(&data, volume);
     if (muted_set)
         pa_sink_input_new_data_set_muted(&data, muted);
     data.sync_base = ssync ? ssync->sink_input : NULL;
@@ -2691,6 +2691,7 @@ static void sink_fill_tagstruct(pa_native_connection *c, pa_tagstruct *t, pa_sin
         if (PA_UNLIKELY(pa_sink_get_state(sink) == PA_SINK_INVALID_STATE))
             pa_log_error("Internal sink state is invalid.");
         pa_tagstruct_putu32(t, pa_sink_get_state(sink));
+        pa_tagstruct_putu32(t, sink->n_volume_steps);
     }
 }
 
@@ -2729,6 +2730,7 @@ static void source_fill_tagstruct(pa_native_connection *c, pa_tagstruct *t, pa_s
         if (PA_UNLIKELY(pa_source_get_state(source) == PA_SOURCE_INVALID_STATE))
             pa_log_error("Internal source state is invalid.");
         pa_tagstruct_putu32(t, pa_source_get_state(source));
+        pa_tagstruct_putu32(t, source->n_volume_steps);
     }
 }
 
@@ -3164,7 +3166,7 @@ static void command_set_volume(
     CHECK_VALIDITY(c->pstream, si || sink || source, tag, PA_ERR_NOENTITY);
 
     if (sink)
-        pa_sink_set_volume(sink, &volume);
+        pa_sink_set_volume(sink, &volume, TRUE, TRUE);
     else if (source)
         pa_source_set_volume(source, &volume);
     else if (si)
diff --git a/src/pulsecore/sink-input.c b/src/pulsecore/sink-input.c
index d1ffb0f..8b76df0 100644
--- a/src/pulsecore/sink-input.c
+++ b/src/pulsecore/sink-input.c
@@ -72,11 +72,18 @@ void pa_sink_input_new_data_set_channel_map(pa_sink_input_new_data *data, const
         data->channel_map = *map;
 }
 
-void pa_sink_input_new_data_set_volume(pa_sink_input_new_data *data, const pa_cvolume *volume) {
+void pa_sink_input_new_data_set_soft_volume(pa_sink_input_new_data *data, const pa_cvolume *volume) {
     pa_assert(data);
 
-    if ((data->volume_is_set = !!volume))
-        data->volume = data->virtual_volume = *volume;
+    if ((data->soft_volume_is_set = !!volume))
+        data->soft_volume = *volume;
+}
+
+void pa_sink_input_new_data_set_virtual_volume(pa_sink_input_new_data *data, const pa_cvolume *volume) {
+    pa_assert(data);
+
+    if ((data->virtual_volume_is_set = !!volume))
+        data->virtual_volume = *volume;
 }
 
 void pa_sink_input_new_data_set_muted(pa_sink_input_new_data *data, pa_bool_t mute) {
@@ -153,17 +160,34 @@ pa_sink_input* pa_sink_input_new(
     pa_return_null_if_fail(pa_channel_map_valid(&data->channel_map));
     pa_return_null_if_fail(pa_channel_map_compatible(&data->channel_map, &data->sample_spec));
 
-    if (!data->volume_is_set) {
-        pa_cvolume_reset(&data->volume, data->sample_spec.channels);
-        pa_cvolume_reset(&data->virtual_volume, data->sample_spec.channels);
-    }
+    if (!data->virtual_volume_is_set) {
 
-    pa_return_null_if_fail(pa_cvolume_valid(&data->volume));
-    pa_return_null_if_fail(pa_cvolume_compatible(&data->volume, &data->sample_spec));
+        if (data->sink->flags & PA_SINK_FLAT_VOLUME) {
+            data->virtual_volume = data->sink->virtual_volume;
+            pa_cvolume_remap(&data->virtual_volume, &data->sink->channel_map, &data->channel_map);
+        } else
+            pa_cvolume_reset(&data->virtual_volume, data->sample_spec.channels);
+
+    } else if (!data->virtual_volume_is_absolute) {
+
+        /* When the 'absolute' bool is set then we'll treat the volume
+         * as relative to the sink volume even in flat volume mode */
+        if (data->sink->flags & PA_SINK_FLAT_VOLUME) {
+            pa_cvolume t = data->sink->virtual_volume;
+            pa_cvolume_remap(&t, &data->sink->channel_map, &data->channel_map);
+            pa_sw_cvolume_multiply(&data->virtual_volume, &data->virtual_volume, &t);
+        }
+    }
 
     pa_return_null_if_fail(pa_cvolume_valid(&data->virtual_volume));
     pa_return_null_if_fail(pa_cvolume_compatible(&data->virtual_volume, &data->sample_spec));
 
+    if (!data->soft_volume_is_set)
+        data->soft_volume = data->virtual_volume;
+
+    pa_return_null_if_fail(pa_cvolume_valid(&data->soft_volume));
+    pa_return_null_if_fail(pa_cvolume_compatible(&data->soft_volume, &data->sample_spec));
+
     if (!data->muted_is_set)
         data->muted = FALSE;
 
@@ -184,7 +208,8 @@ pa_sink_input* pa_sink_input_new(
     pa_assert(pa_channel_map_valid(&data->channel_map));
 
     /* Due to the fixing of the sample spec the volume might not match anymore */
-    pa_cvolume_remap(&data->volume, &original_cm, &data->channel_map);
+    pa_cvolume_remap(&data->soft_volume, &original_cm, &data->channel_map);
+    pa_cvolume_remap(&data->virtual_volume, &original_cm, &data->channel_map);
 
     if (data->resample_method == PA_RESAMPLER_INVALID)
         data->resample_method = core->resample_method;
@@ -239,7 +264,7 @@ pa_sink_input* pa_sink_input_new(
     i->channel_map = data->channel_map;
 
     i->virtual_volume = data->virtual_volume;
-    i->volume = data->volume;
+    i->soft_volume = data->soft_volume;
 
     i->muted = data->muted;
 
@@ -263,7 +288,7 @@ pa_sink_input* pa_sink_input_new(
     pa_atomic_store(&i->thread_info.drained, 1);
     i->thread_info.sample_spec = i->sample_spec;
     i->thread_info.resampler = resampler;
-    i->thread_info.volume = i->volume;
+    i->thread_info.soft_volume = i->soft_volume;
     i->thread_info.muted = i->muted;
     i->thread_info.requested_sink_latency = (pa_usec_t) -1;
     i->thread_info.rewrite_nbytes = 0;
@@ -395,9 +420,17 @@ void pa_sink_input_unlink(pa_sink_input *i) {
     update_n_corked(i, PA_SINK_INPUT_UNLINKED);
     i->state = PA_SINK_INPUT_UNLINKED;
 
-    if (linked && i->sink)
+    if (linked && i->sink) {
+        /* We might need to update the sink's volume if we are in flat volume mode. */
+        if (i->sink->flags & PA_SINK_FLAT_VOLUME) {
+            pa_cvolume new_volume;
+            pa_sink_update_flat_volume(i->sink, &new_volume);
+            pa_sink_set_volume(i->sink, &new_volume, FALSE, FALSE);
+        }
+
         if (i->sink->asyncmsgq)
             pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i->sink), PA_SINK_MESSAGE_REMOVE_INPUT, i, 0, NULL) == 0);
+    }
 
     reset_callbacks(i);
 
@@ -459,7 +492,7 @@ void pa_sink_input_put(pa_sink_input *i) {
     pa_assert(i->process_rewind);
     pa_assert(i->kill);
 
-    i->thread_info.volume = i->volume;
+    i->thread_info.soft_volume = i->soft_volume;
     i->thread_info.muted = i->muted;
 
     state = i->flags & PA_SINK_INPUT_START_CORKED ? PA_SINK_INPUT_CORKED : PA_SINK_INPUT_RUNNING;
@@ -467,6 +500,13 @@ void pa_sink_input_put(pa_sink_input *i) {
     update_n_corked(i, state);
     i->state = state;
 
+    /* We might need to update the sink's volume if we are in flat volume mode. */
+    if (i->sink->flags & PA_SINK_FLAT_VOLUME) {
+        pa_cvolume new_volume;
+        pa_sink_update_flat_volume(i->sink, &new_volume);
+        pa_sink_set_volume(i->sink, &new_volume, FALSE, FALSE);
+    }
+
     pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i->sink), PA_SINK_MESSAGE_ADD_INPUT, i, 0, NULL) == 0);
 
     pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_NEW, i->index);
@@ -552,7 +592,7 @@ int pa_sink_input_peek(pa_sink_input *i, size_t slength /* in sink frames */, pa
      * it after and leave it for the sink code */
 
     do_volume_adj_here = !pa_channel_map_equal(&i->channel_map, &i->sink->channel_map);
-    volume_is_norm = pa_cvolume_is_norm(&i->thread_info.volume) && !i->thread_info.muted;
+    volume_is_norm = pa_cvolume_is_norm(&i->thread_info.soft_volume) && !i->thread_info.muted;
 
     while (!pa_memblockq_is_readable(i->thread_info.render_memblockq)) {
         pa_memchunk tchunk;
@@ -598,7 +638,7 @@ int pa_sink_input_peek(pa_sink_input *i, size_t slength /* in sink frames */, pa
                 if (i->thread_info.muted)
                     pa_silence_memchunk(&wchunk, &i->thread_info.sample_spec);
                 else
-                    pa_volume_memchunk(&wchunk, &i->thread_info.sample_spec, &i->thread_info.volume);
+                    pa_volume_memchunk(&wchunk, &i->thread_info.sample_spec, &i->thread_info.soft_volume);
             }
 
             if (!i->thread_info.resampler)
@@ -644,7 +684,7 @@ int pa_sink_input_peek(pa_sink_input *i, size_t slength /* in sink frames */, pa
         /* We've both the same channel map, so let's have the sink do the adjustment for us*/
         pa_cvolume_mute(volume, i->sink->sample_spec.channels);
     else
-        *volume = i->thread_info.volume;
+        *volume = i->thread_info.soft_volume;
 
     return 0;
 }
@@ -816,34 +856,41 @@ pa_usec_t pa_sink_input_get_requested_latency(pa_sink_input *i) {
 
 /* Called from main context */
 void pa_sink_input_set_volume(pa_sink_input *i, const pa_cvolume *volume) {
-    pa_sink_input_set_volume_data data;
-
     pa_sink_input_assert_ref(i);
     pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
     pa_assert(volume);
     pa_assert(pa_cvolume_valid(volume));
     pa_assert(pa_cvolume_compatible(volume, &i->sample_spec));
 
-    data.sink_input = i;
-    data.virtual_volume = *volume;
-    data.volume = *volume;
+    if (pa_cvolume_equal(volume, &i->virtual_volume))
+        return;
 
-    /* If you change something here, consider looking into
-     * module-flat-volume.c as well since it uses very similar
-     * code. */
+    i->virtual_volume = *volume;
 
-    if (pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_SET_VOLUME], &data) < 0)
-        return;
+    if (i->sink->flags & PA_SINK_FLAT_VOLUME) {
+        pa_cvolume new_volume;
 
-    if (!pa_cvolume_equal(&i->volume, &data.volume)) {
-        i->volume = data.volume;
-        pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_SET_VOLUME, &data.volume, 0, NULL) == 0);
-    }
+        /* We are in flat volume mode, so let's update all sink input
+         * volumes and update the flat volume of the sink */
 
-    if (!pa_cvolume_equal(&i->virtual_volume, &data.virtual_volume)) {
-        i->virtual_volume = data.virtual_volume;
-        pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index);
+        pa_sink_update_flat_volume(i->sink, &new_volume);
+        pa_sink_set_volume(i->sink, &new_volume, FALSE, TRUE);
+
+    } else {
+
+        /* OK, we are in normal volume mode. The volume only affects
+         * ourselves */
+
+        i->soft_volume = *volume;
+
+        /* Hooks have the ability to play games with i->soft_volume */
+        pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_SET_VOLUME], i);
+
+        pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_SET_SOFT_VOLUME, NULL, 0, NULL) == 0);
     }
+
+    /* The virtual volume changed, let's tell people so */
+    pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index);
 }
 
 /* Called from main context */
@@ -865,7 +912,7 @@ void pa_sink_input_set_mute(pa_sink_input *i, pa_bool_t mute) {
 
     i->muted = mute;
 
-    pa_asyncmsgq_post(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_SET_MUTE, PA_UINT_TO_PTR(mute), 0, NULL, NULL);
+    pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_SET_SOFT_MUTE, NULL, 0, NULL) == 0);
     pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index);
 }
 
@@ -1013,15 +1060,22 @@ int pa_sink_input_start_move(pa_sink_input *i) {
     }
     pa_assert(pa_idxset_isempty(i->direct_outputs));
 
-    pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i->sink), PA_SINK_MESSAGE_START_MOVE, i, 0, NULL) == 0);
+    pa_idxset_remove_by_data(i->sink->inputs, i, NULL);
 
     if (pa_sink_input_get_state(i) == PA_SINK_INPUT_CORKED)
         pa_assert_se(i->sink->n_corked-- >= 1);
 
-    pa_idxset_remove_by_data(i->sink->inputs, i, NULL);
-    i->sink = NULL;
+    /* We might need to update the sink's volume if we are in flat volume mode. */
+    if (i->sink->flags & PA_SINK_FLAT_VOLUME) {
+        pa_cvolume new_volume;
+        pa_sink_update_flat_volume(i->sink, &new_volume);
+        pa_sink_set_volume(i->sink, &new_volume, FALSE, FALSE);
+    }
+
+    pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i->sink), PA_SINK_MESSAGE_START_MOVE, i, 0, NULL) == 0);
 
-    pa_sink_update_status(origin);
+    pa_sink_update_status(i->sink);
+    i->sink = NULL;
 
     return 0;
 }
@@ -1092,6 +1146,14 @@ int pa_sink_input_finish_move(pa_sink_input *i, pa_sink *dest) {
     }
 
     pa_sink_update_status(dest);
+
+    /* We might need to update the sink's volume if we are in flat volume mode. */
+    if (i->sink->flags & PA_SINK_FLAT_VOLUME) {
+        pa_cvolume new_volume;
+        pa_sink_update_flat_volume(i->sink, &new_volume);
+        pa_sink_set_volume(i->sink, &new_volume, FALSE, FALSE);
+    }
+
     pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i->sink), PA_SINK_MESSAGE_FINISH_MOVE, i, 0, NULL) == 0);
 
     pa_log_debug("Successfully moved sink input %i to %s.", i->index, dest->name);
@@ -1176,14 +1238,18 @@ int pa_sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int64_t
 
     switch (code) {
 
-        case PA_SINK_INPUT_MESSAGE_SET_VOLUME:
-            i->thread_info.volume = *((pa_cvolume*) userdata);
-            pa_sink_input_request_rewind(i, 0, TRUE, FALSE, FALSE);
+        case PA_SINK_INPUT_MESSAGE_SET_SOFT_VOLUME:
+            if (!pa_cvolume_equal(&i->thread_info.soft_volume, &i->soft_volume)) {
+                i->thread_info.soft_volume = i->soft_volume;
+                pa_sink_input_request_rewind(i, 0, TRUE, FALSE, FALSE);
+            }
             return 0;
 
-        case PA_SINK_INPUT_MESSAGE_SET_MUTE:
-            i->thread_info.muted = PA_PTR_TO_UINT(userdata);
-            pa_sink_input_request_rewind(i, 0, TRUE, FALSE, FALSE);
+        case PA_SINK_INPUT_MESSAGE_SET_SOFT_MUTE:
+            if (i->thread_info.muted != i->muted) {
+                i->thread_info.muted = i->muted;
+                pa_sink_input_request_rewind(i, 0, TRUE, FALSE, FALSE);
+            }
             return 0;
 
         case PA_SINK_INPUT_MESSAGE_GET_LATENCY: {
diff --git a/src/pulsecore/sink-input.h b/src/pulsecore/sink-input.h
index 66ec613..5bf38de 100644
--- a/src/pulsecore/sink-input.h
+++ b/src/pulsecore/sink-input.h
@@ -90,9 +90,7 @@ struct pa_sink_input {
 
     pa_sink_input *sync_prev, *sync_next;
 
-    pa_cvolume virtual_volume;
-
-    pa_cvolume volume;
+    pa_cvolume virtual_volume, soft_volume;
     pa_bool_t muted;
 
     pa_resample_method_t requested_resample_method, actual_resample_method;
@@ -170,7 +168,15 @@ struct pa_sink_input {
         pa_sink_input_state_t state;
         pa_atomic_t drained;
 
-        pa_bool_t attached; /* True only between ->attach() and ->detach() calls */
+        pa_cvolume soft_volume;
+        pa_bool_t muted:1;
+
+        pa_bool_t attached:1; /* True only between ->attach() and ->detach() calls */
+
+        /* 0: rewrite nothing, (size_t) -1: rewrite everything, otherwise how many bytes to rewrite */
+        pa_bool_t rewrite_flush:1, dont_rewind_render:1;
+        size_t rewrite_nbytes;
+        uint64_t underrun_for, playing_for;
 
         pa_sample_spec sample_spec;
 
@@ -179,16 +185,8 @@ struct pa_sink_input {
         /* We maintain a history of resampled audio data here. */
         pa_memblockq *render_memblockq;
 
-        /* 0: rewrite nothing, (size_t) -1: rewrite everything, otherwise how many bytes to rewrite */
-        size_t rewrite_nbytes;
-        pa_bool_t rewrite_flush, dont_rewind_render;
-        uint64_t underrun_for, playing_for;
-
         pa_sink_input *sync_prev, *sync_next;
 
-        pa_cvolume volume;
-        pa_bool_t muted;
-
         /* The requested latency for the sink */
         pa_usec_t requested_sink_latency;
 
@@ -202,8 +200,8 @@ PA_DECLARE_CLASS(pa_sink_input);
 #define PA_SINK_INPUT(o) pa_sink_input_cast(o)
 
 enum {
-    PA_SINK_INPUT_MESSAGE_SET_VOLUME,
-    PA_SINK_INPUT_MESSAGE_SET_MUTE,
+    PA_SINK_INPUT_MESSAGE_SET_SOFT_VOLUME,
+    PA_SINK_INPUT_MESSAGE_SET_SOFT_MUTE,
     PA_SINK_INPUT_MESSAGE_GET_LATENCY,
     PA_SINK_INPUT_MESSAGE_SET_RATE,
     PA_SINK_INPUT_MESSAGE_SET_STATE,
@@ -228,30 +226,26 @@ typedef struct pa_sink_input_new_data {
     pa_sample_spec sample_spec;
     pa_channel_map channel_map;
 
-    pa_cvolume virtual_volume;
-
-    pa_cvolume volume;
+    pa_cvolume virtual_volume, soft_volume;
     pa_bool_t muted:1;
 
     pa_bool_t sample_spec_is_set:1;
     pa_bool_t channel_map_is_set:1;
-    pa_bool_t volume_is_set:1;
+
+    pa_bool_t virtual_volume_is_set:1, soft_volume_is_set:1;
     pa_bool_t muted_is_set:1;
+
+    pa_bool_t virtual_volume_is_absolute:1;
 } pa_sink_input_new_data;
 
 pa_sink_input_new_data* pa_sink_input_new_data_init(pa_sink_input_new_data *data);
 void pa_sink_input_new_data_set_sample_spec(pa_sink_input_new_data *data, const pa_sample_spec *spec);
 void pa_sink_input_new_data_set_channel_map(pa_sink_input_new_data *data, const pa_channel_map *map);
-void pa_sink_input_new_data_set_volume(pa_sink_input_new_data *data, const pa_cvolume *volume);
+void pa_sink_input_new_data_set_soft_volume(pa_sink_input_new_data *data, const pa_cvolume *volume);
+void pa_sink_input_new_data_set_virtual_volume(pa_sink_input_new_data *data, const pa_cvolume *volume);
 void pa_sink_input_new_data_set_muted(pa_sink_input_new_data *data, pa_bool_t mute);
 void pa_sink_input_new_data_done(pa_sink_input_new_data *data);
 
-typedef struct pa_sink_set_input_volume_data {
-  pa_sink_input *sink_input;
-  pa_cvolume virtual_volume;
-  pa_cvolume volume;
-} pa_sink_input_set_volume_data;
-
 /* To be called by the implementing module only */
 
 pa_sink_input* pa_sink_input_new(
diff --git a/src/pulsecore/sink.c b/src/pulsecore/sink.c
index 083dbaa..7735d3a 100644
--- a/src/pulsecore/sink.c
+++ b/src/pulsecore/sink.c
@@ -195,10 +195,10 @@ pa_sink* pa_sink_new(
     s->inputs = pa_idxset_new(NULL, NULL);
     s->n_corked = 0;
 
-    s->volume = data->volume;
+    s->virtual_volume = data->volume;
+    pa_cvolume_reset(&s->soft_volume, s->sample_spec.channels);
     s->base_volume = PA_VOLUME_NORM;
-    s->virtual_volume = s->volume;
-
+    s->n_volume_steps = PA_VOLUME_NORM+1;
     s->muted = data->muted;
     s->refresh_volume = s->refresh_muted = FALSE;
 
@@ -216,8 +216,8 @@ pa_sink* pa_sink_new(
             0);
 
     s->thread_info.inputs = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
-    pa_cvolume_reset(&s->thread_info.soft_volume, s->sample_spec.channels);
-    s->thread_info.soft_muted = FALSE;
+    s->thread_info.soft_volume =  s->soft_volume;
+    s->thread_info.soft_muted = s->muted;
     s->thread_info.state = s->state;
     s->thread_info.rewind_nbytes = 0;
     s->thread_info.rewind_requested = FALSE;
@@ -335,10 +335,17 @@ void pa_sink_put(pa_sink* s) {
     if (!(s->flags & PA_SINK_HW_VOLUME_CTRL)) {
         s->flags |= PA_SINK_DECIBEL_VOLUME;
 
-        s->thread_info.soft_volume = s->volume;
+        s->thread_info.soft_volume = s->soft_volume;
         s->thread_info.soft_muted = s->muted;
     }
 
+    if (s->flags & PA_SINK_DECIBEL_VOLUME)
+        s->n_volume_steps = PA_VOLUME_NORM+1;
+
+    if (s->core->flat_volumes)
+        if (s->flags & PA_SINK_DECIBEL_VOLUME)
+            s->flags |= PA_SINK_FLAT_VOLUME;
+
     pa_assert_se(sink_set_state(s, PA_SINK_IDLE) == 0);
 
     pa_source_put(s->monitor_source);
@@ -911,9 +918,100 @@ pa_usec_t pa_sink_get_latency(pa_sink *s) {
 }
 
 /* Called from main thread */
-void pa_sink_set_volume(pa_sink *s, const pa_cvolume *volume) {
-    pa_bool_t changed;
-    pa_sink_set_volume_data data;
+void pa_sink_update_flat_volume(pa_sink *s, pa_cvolume *new_volume) {
+    pa_sink_input *i;
+    uint32_t idx;
+
+    /* This is called whenever a sink input volume changes and we
+     * might need to fix up the sink volume accordingly. Please note
+     * that we don't actually update the sinks volume here, we only
+     * return how it needs to be updated. The caller should then call
+     * pa_sink_set_flat_volume().*/
+
+    pa_cvolume_mute(new_volume, s->channel_map.channels);
+
+    /* First let's determine the new maximum volume of all inputs
+     * connected to this sink */
+    for (i = PA_SINK_INPUT(pa_idxset_first(s->inputs, &idx)); i; i = PA_SINK_INPUT(pa_idxset_next(s->inputs, &idx))) {
+        unsigned c;
+        pa_cvolume remapped_volume;
+
+        remapped_volume = i->virtual_volume;
+        pa_cvolume_remap(&remapped_volume, &i->channel_map, &s->channel_map);
+
+        for (c = 0; c < new_volume->channels; c++)
+            if (remapped_volume.values[c] > new_volume->values[c])
+                new_volume->values[c] = remapped_volume.values[c];
+    }
+
+    /* Then, let's update the soft volumes of all inputs connected
+     * to this sink */
+    for (i = PA_SINK_INPUT(pa_idxset_first(s->inputs, &idx)); i; i = PA_SINK_INPUT(pa_idxset_next(s->inputs, &idx))) {
+        pa_cvolume remapped_new_volume;
+
+        remapped_new_volume = *new_volume;
+        pa_cvolume_remap(&remapped_new_volume, &s->channel_map, &i->channel_map);
+        pa_sw_cvolume_divide(&i->soft_volume, &i->virtual_volume, &remapped_new_volume);
+
+        /* Hooks have the ability to play games with i->soft_volume */
+        pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_INPUT_SET_VOLUME], i);
+
+        /* We don't issue PA_SINK_INPUT_MESSAGE_SET_VOLUME because
+         * we want the update to have atomically with the sink
+         * volume update, hence we do it within the
+         * pa_sink_set_flat_volume() call below*/
+    }
+}
+
+/* Called from main thread */
+void pa_sink_propagate_flat_volume(pa_sink *s, const pa_cvolume *old_volume) {
+    pa_sink_input *i;
+    uint32_t idx;
+
+    pa_sink_assert_ref(s);
+    pa_assert(PA_SINK_IS_LINKED(s->state));
+    pa_assert(old_volume);
+    pa_assert(s->flags & PA_SINK_FLAT_VOLUME);
+
+    /* This is called whenever the sink volume changes that is not
+     * caused by a sink input volume change. We need to fix up the
+     * sink input volumes accordingly */
+
+    for (i = PA_SINK_INPUT(pa_idxset_first(s->inputs, &idx)); i; i = PA_SINK_INPUT(pa_idxset_next(s->inputs, &idx))) {
+        pa_cvolume remapped_old_volume, remapped_new_volume, fixed_volume;
+        unsigned c;
+
+        remapped_new_volume = s->virtual_volume;
+        pa_cvolume_remap(&remapped_new_volume, &s->channel_map, &i->channel_map);
+
+        remapped_old_volume = *old_volume;
+        pa_cvolume_remap(&remapped_old_volume, &s->channel_map, &i->channel_map);
+
+        for (c = 0; c < i->sample_spec.channels; c++)
+
+            if (remapped_old_volume.values[c] == PA_VOLUME_MUTED)
+                fixed_volume.values[c] = PA_VOLUME_MUTED;
+            else
+                fixed_volume.values[c] = (pa_volume_t)
+                    ((uint64_t) i->virtual_volume.values[c] *
+                     (uint64_t) remapped_new_volume.values[c] /
+                     (uint64_t) remapped_old_volume.values[c]);
+
+        fixed_volume.channels = i->virtual_volume.channels;
+
+        if (!pa_cvolume_equal(&fixed_volume, &i->virtual_volume)) {
+            i->virtual_volume = fixed_volume;
+
+            /* The virtual volume changed, let's tell people so */
+            pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index);
+        }
+    }
+}
+
+/* Called from main thread */
+void pa_sink_set_volume(pa_sink *s, const pa_cvolume *volume, pa_bool_t propagate, pa_bool_t sendmsg) {
+    pa_cvolume old_virtual_volume;
+    pa_bool_t virtual_volume_changed;
 
     pa_sink_assert_ref(s);
     pa_assert(PA_SINK_IS_LINKED(s->state));
@@ -921,39 +1019,45 @@ void pa_sink_set_volume(pa_sink *s, const pa_cvolume *volume) {
     pa_assert(pa_cvolume_valid(volume));
     pa_assert(pa_cvolume_compatible(volume, &s->sample_spec));
 
-    data.sink = s;
-    data.virtual_volume = data.volume = *volume;
+    old_virtual_volume = s->virtual_volume;
+    s->virtual_volume = *volume;
+    virtual_volume_changed = !pa_cvolume_equal(&old_virtual_volume, &s->virtual_volume);
 
-    changed = !pa_cvolume_equal(&data.virtual_volume, &s->virtual_volume) ||
-        !pa_cvolume_equal(&data.volume, &s->volume);
+    /* Propagate this volume change back to the inputs */
+    if (virtual_volume_changed)
+        if (propagate && (s->flags & PA_SINK_FLAT_VOLUME))
+            pa_sink_propagate_flat_volume(s, &old_virtual_volume);
 
-    if (changed) {
-        if (pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_SET_VOLUME], &data) < 0)
-            return;
+    if (s->set_volume) {
+        /* If we have a function set_volume(), then we do not apply a
+         * soft volume by default. However, set_volume() is apply one
+         * to s->soft_volume */
 
-        changed = !pa_cvolume_equal(&data.virtual_volume, &s->virtual_volume); /* from client-side view */
-    }
+        pa_cvolume_reset(&s->soft_volume, s->sample_spec.channels);
+        s->set_volume(s);
 
-    s->volume = data.volume;
-    s->virtual_volume = data.virtual_volume;
+    } else
+        /* If we have no function set_volume(), then the soft volume
+         * becomes the virtual volume */
+        s->soft_volume = s->virtual_volume;
 
-    if (s->set_volume && s->set_volume(s) < 0)
-        s->set_volume = NULL;
+    /* This tells the sink that soft and/or virtual volume changed */
+    if (sendmsg)
+        pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_VOLUME, NULL, 0, NULL) == 0);
 
-    if (!s->set_volume)
-        pa_sink_set_soft_volume(s, &s->volume);
-
-    if (changed)
+    if (virtual_volume_changed)
         pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
 }
 
-/* Called from main thread */
+/* Called from main thread. Only to be called by sink implementor */
 void pa_sink_set_soft_volume(pa_sink *s, const pa_cvolume *volume) {
     pa_sink_assert_ref(s);
     pa_assert(volume);
 
+    s->soft_volume = *volume;
+
     if (PA_SINK_IS_LINKED(s->state))
-        pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_VOLUME, volume, 0, NULL);
+        pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_VOLUME, NULL, 0, NULL) == 0);
     else
         s->thread_info.soft_volume = *volume;
 }
@@ -961,21 +1065,22 @@ void pa_sink_set_soft_volume(pa_sink *s, const pa_cvolume *volume) {
 /* Called from main thread */
 const pa_cvolume *pa_sink_get_volume(pa_sink *s, pa_bool_t force_refresh) {
     pa_sink_assert_ref(s);
-    pa_assert(PA_SINK_IS_LINKED(s->state));
 
     if (s->refresh_volume || force_refresh) {
-        struct pa_cvolume old_volume = s->virtual_volume;
+        struct pa_cvolume old_virtual_volume = s->virtual_volume;
 
-        if (s->get_volume && s->get_volume(s) < 0)
-            s->get_volume = NULL;
+        if (s->get_volume)
+            s->get_volume(s);
 
-        if (!s->get_volume) {
-            pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_VOLUME, &s->volume, 0, NULL);
-            s->virtual_volume = s->volume;
-        }
+        pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_VOLUME, NULL, 0, NULL) == 0);
+
+        if (!pa_cvolume_equal(&old_virtual_volume, &s->virtual_volume)) {
+
+            if (s->flags & PA_SINK_FLAT_VOLUME)
+                pa_sink_propagate_flat_volume(s, &old_virtual_volume);
 
-        if (!pa_cvolume_equal(&old_volume, &s->virtual_volume))
             pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+        }
     }
 
     return &s->virtual_volume;
@@ -983,21 +1088,20 @@ const pa_cvolume *pa_sink_get_volume(pa_sink *s, pa_bool_t force_refresh) {
 
 /* Called from main thread */
 void pa_sink_set_mute(pa_sink *s, pa_bool_t mute) {
-    pa_bool_t changed;
+    pa_bool_t old_muted;
 
     pa_sink_assert_ref(s);
     pa_assert(PA_SINK_IS_LINKED(s->state));
 
-    changed = s->muted != mute;
+    old_muted = s->muted;
     s->muted = mute;
 
-    if (s->set_mute && s->set_mute(s) < 0)
-        s->set_mute = NULL;
+    if (s->set_mute)
+        s->set_mute(s);
 
-    if (!s->set_mute)
-        pa_asyncmsgq_post(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_MUTE, PA_UINT_TO_PTR(mute), 0, NULL, NULL);
+    pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_MUTE, NULL, 0, NULL) == 0);
 
-    if (changed)
+    if (old_muted != s->muted)
         pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
 }
 
@@ -1005,16 +1109,14 @@ void pa_sink_set_mute(pa_sink *s, pa_bool_t mute) {
 pa_bool_t pa_sink_get_mute(pa_sink *s, pa_bool_t force_refresh) {
 
     pa_sink_assert_ref(s);
-    pa_assert(PA_SINK_IS_LINKED(s->state));
 
     if (s->refresh_muted || force_refresh) {
         pa_bool_t old_muted = s->muted;
 
-        if (s->get_mute && s->get_mute(s) < 0)
-            s->get_mute = NULL;
+        if (s->get_mute)
+            s->get_mute(s);
 
-        if (!s->get_mute)
-            pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_MUTE, &s->muted, 0, NULL);
+        pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_MUTE, NULL, 0, NULL) == 0);
 
         if (old_muted != s->muted)
             pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
@@ -1138,6 +1240,22 @@ unsigned pa_sink_check_suspend(pa_sink *s) {
     return ret;
 }
 
+/* Called from the IO thread */
+static void sync_input_volumes_within_thread(pa_sink *s) {
+    pa_sink_input *i;
+    void *state = NULL;
+
+    pa_sink_assert_ref(s);
+
+    while ((i = PA_SINK_INPUT(pa_hashmap_iterate(s->thread_info.inputs, &state, NULL)))) {
+        if (pa_cvolume_equal(&i->thread_info.soft_volume, &i->soft_volume))
+            continue;
+
+        i->thread_info.soft_volume = i->soft_volume;
+        pa_sink_input_request_rewind(i, 0, TRUE, FALSE, FALSE);
+    }
+}
+
 /* Called from IO thread, except when it is not */
 int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk) {
     pa_sink *s = PA_SINK(o);
@@ -1192,7 +1310,9 @@ int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offse
              * slow start, i.e. need some time to buffer client
              * samples before beginning streaming. */
 
-            return 0;
+            /* In flat volume mode we need to update the volume as
+             * well */
+            return o->process_msg(o, PA_SINK_MESSAGE_SET_VOLUME, NULL, 0, NULL);
         }
 
         case PA_SINK_MESSAGE_REMOVE_INPUT: {
@@ -1233,7 +1353,9 @@ int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offse
             pa_sink_invalidate_requested_latency(s);
             pa_sink_request_rewind(s, (size_t) -1);
 
-            return 0;
+            /* In flat volume mode we need to update the volume as
+             * well */
+            return o->process_msg(o, PA_SINK_MESSAGE_SET_VOLUME, NULL, 0, NULL);
         }
 
         case PA_SINK_MESSAGE_START_MOVE: {
@@ -1279,7 +1401,9 @@ int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offse
             pa_log_debug("Requesting rewind due to started move");
             pa_sink_request_rewind(s, (size_t) -1);
 
-            return 0;
+            /* In flat volume mode we need to update the volume as
+             * well */
+            return o->process_msg(o, PA_SINK_MESSAGE_SET_VOLUME, NULL, 0, NULL);
         }
 
         case PA_SINK_MESSAGE_FINISH_MOVE: {
@@ -1323,27 +1447,36 @@ int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offse
                 pa_sink_request_rewind(s, nbytes);
             }
 
-            return 0;
+            /* In flat volume mode we need to update the volume as
+             * well */
+            return o->process_msg(o, PA_SINK_MESSAGE_SET_VOLUME, NULL, 0, NULL);
         }
 
         case PA_SINK_MESSAGE_SET_VOLUME:
-            s->thread_info.soft_volume = *((pa_cvolume*) userdata);
 
-            pa_sink_request_rewind(s, (size_t) -1);
-            return 0;
+            if (!pa_cvolume_equal(&s->thread_info.soft_volume, &s->soft_volume)) {
+                s->thread_info.soft_volume = s->soft_volume;
+                pa_sink_request_rewind(s, (size_t) -1);
+            }
 
-        case PA_SINK_MESSAGE_SET_MUTE:
-            s->thread_info.soft_muted = PA_PTR_TO_UINT(userdata);
+            if (s->flags & PA_SINK_FLAT_VOLUME)
+                sync_input_volumes_within_thread(s);
 
-            pa_sink_request_rewind(s, (size_t) -1);
             return 0;
 
         case PA_SINK_MESSAGE_GET_VOLUME:
-            *((pa_cvolume*) userdata) = s->thread_info.soft_volume;
+            return 0;
+
+        case PA_SINK_MESSAGE_SET_MUTE:
+
+            if (!s->thread_info.soft_muted != s->muted) {
+                s->thread_info.soft_muted = s->muted;
+                pa_sink_request_rewind(s, (size_t) -1);
+            }
+
             return 0;
 
         case PA_SINK_MESSAGE_GET_MUTE:
-            *((pa_bool_t*) userdata) = s->thread_info.soft_muted;
             return 0;
 
         case PA_SINK_MESSAGE_SET_STATE:
@@ -1676,6 +1809,7 @@ void pa_sink_update_latency_range(pa_sink *s, pa_usec_t min_latency, pa_usec_t m
     pa_source_update_latency_range(s->monitor_source, min_latency, max_latency);
 }
 
+/* Called from main context */
 size_t pa_sink_get_max_rewind(pa_sink *s) {
     size_t r;
     pa_sink_assert_ref(s);
@@ -1688,6 +1822,7 @@ size_t pa_sink_get_max_rewind(pa_sink *s) {
     return r;
 }
 
+/* Called from main context */
 size_t pa_sink_get_max_request(pa_sink *s) {
     size_t r;
     pa_sink_assert_ref(s);
diff --git a/src/pulsecore/sink.h b/src/pulsecore/sink.h
index a30245d..b26ca77 100644
--- a/src/pulsecore/sink.h
+++ b/src/pulsecore/sink.h
@@ -71,11 +71,11 @@ struct pa_sink {
     unsigned n_corked;
     pa_source *monitor_source;
 
-    pa_cvolume volume;
-    pa_cvolume virtual_volume;
-    pa_bool_t muted;
+    pa_volume_t base_volume; /* shall be constant */
+    unsigned n_volume_steps; /* shall be constant */
 
-    pa_volume_t base_volume;  /* shall be constant */
+    pa_cvolume virtual_volume, soft_volume;
+    pa_bool_t muted:1;
 
     pa_bool_t refresh_volume:1;
     pa_bool_t refresh_muted:1;
@@ -94,23 +94,23 @@ struct pa_sink {
      * context. If this is NULL a PA_SINK_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. */
-    int (*get_volume)(pa_sink *s);             /* may be NULL */
+    void (*get_volume)(pa_sink *s);             /* may be NULL */
 
     /* Called when the volume shall be changed. Called from main loop
      * context. If this is NULL a PA_SINK_MESSAGE_SET_VOLUME message
      * will be sent to the IO thread instead. */
-    int (*set_volume)(pa_sink *s);             /* dito */
+    void (*set_volume)(pa_sink *s);             /* dito */
 
     /* Called when the mute setting is queried. Called from main loop
      * context. If this is NULL a PA_SINK_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.*/
-    int (*get_mute)(pa_sink *s);               /* dito */
+    void (*get_mute)(pa_sink *s);               /* dito */
 
     /* Called when the mute setting shall be changed. Called from main
      * loop context. If this is NULL a PA_SINK_MESSAGE_SET_MUTE
      * message will be sent to the IO thread instead. */
-    int (*set_mute)(pa_sink *s);               /* dito */
+    void (*set_mute)(pa_sink *s);               /* dito */
 
     /* Called when a rewind request is issued. Called from IO thread
      * context. */
@@ -125,6 +125,7 @@ struct pa_sink {
     struct {
         pa_sink_state_t state;
         pa_hashmap *inputs;
+
         pa_cvolume soft_volume;
         pa_bool_t soft_muted:1;
 
@@ -203,13 +204,7 @@ void pa_sink_new_data_set_volume(pa_sink_new_data *data, const pa_cvolume *volum
 void pa_sink_new_data_set_muted(pa_sink_new_data *data, pa_bool_t mute);
 void pa_sink_new_data_done(pa_sink_new_data *data);
 
-typedef struct pa_sink_set_volume_data {
-  pa_sink *sink;
-  pa_cvolume volume;
-  pa_cvolume virtual_volume;
-} pa_sink_set_volume_data;
-
-/* To be called exclusively by the sink driver, from main context */
+/*** To be called exclusively by the sink driver, from main context */
 
 pa_sink* pa_sink_new(
         pa_core *core,
@@ -228,7 +223,9 @@ void pa_sink_set_latency_range(pa_sink *s, pa_usec_t min_latency, pa_usec_t max_
 void pa_sink_detach(pa_sink *s);
 void pa_sink_attach(pa_sink *s);
 
-/* May be called by everyone, from main context */
+void pa_sink_set_soft_volume(pa_sink *s, const pa_cvolume *volume);
+
+/**** May be called by everyone, from main context */
 
 /* The returned value is supposed to be in the time domain of the sound card! */
 pa_usec_t pa_sink_get_latency(pa_sink *s);
@@ -242,11 +239,13 @@ int pa_sink_update_status(pa_sink*s);
 int pa_sink_suspend(pa_sink *s, pa_bool_t suspend);
 int pa_sink_suspend_all(pa_core *c, pa_bool_t suspend);
 
-void pa_sink_set_volume(pa_sink *sink, const pa_cvolume *volume);
-void pa_sink_set_soft_volume(pa_sink *s, const pa_cvolume *volume);
+void pa_sink_update_flat_volume(pa_sink *s, pa_cvolume *new_volume);
+void pa_sink_propagate_flat_volume(pa_sink *s, const pa_cvolume *old_volume);
+
+void pa_sink_set_volume(pa_sink *sink, const pa_cvolume *volume, pa_bool_t propagate, pa_bool_t sendmsg);
 const pa_cvolume *pa_sink_get_volume(pa_sink *sink, pa_bool_t force_refresh);
 void pa_sink_set_mute(pa_sink *sink, pa_bool_t mute);
-pa_bool_t pa_sink_get_mute(pa_sink *sink, pa_bool_t force_refres);
+pa_bool_t pa_sink_get_mute(pa_sink *sink, pa_bool_t force_refresh);
 
 pa_bool_t pa_sink_update_proplist(pa_sink *s, pa_update_mode_t mode, pa_proplist *p);
 
@@ -260,7 +259,7 @@ pa_queue *pa_sink_move_all_start(pa_sink *s);
 void pa_sink_move_all_finish(pa_sink *s, pa_queue *q);
 void pa_sink_move_all_fail(pa_queue *q);
 
-/* To be called exclusively by the sink driver, from IO context */
+/*** To be called exclusively by the sink driver, from IO context */
 
 void pa_sink_render(pa_sink*s, size_t length, pa_memchunk *result);
 void pa_sink_render_full(pa_sink *s, size_t length, pa_memchunk *result);
@@ -281,7 +280,7 @@ void pa_sink_set_max_request(pa_sink *s, size_t max_request);
 
 void pa_sink_update_latency_range(pa_sink *s, pa_usec_t min_latency, pa_usec_t max_latency);
 
-/* To be called exclusively by sink input drivers, from IO context */
+/*** To be called exclusively by sink input drivers, from IO context */
 
 void pa_sink_request_rewind(pa_sink*s, size_t nbytes);
 
diff --git a/src/pulsecore/sound-file-stream.c b/src/pulsecore/sound-file-stream.c
index b78afca..1be421f 100644
--- a/src/pulsecore/sound-file-stream.c
+++ b/src/pulsecore/sound-file-stream.c
@@ -322,7 +322,7 @@ int pa_play_file(
     data.sink = sink;
     data.driver = __FILE__;
     pa_sink_input_new_data_set_sample_spec(&data, &ss);
-    pa_sink_input_new_data_set_volume(&data, volume);
+    pa_sink_input_new_data_set_virtual_volume(&data, volume);
     pa_proplist_sets(data.proplist, PA_PROP_MEDIA_NAME, pa_path_get_filename(fname));
     pa_proplist_sets(data.proplist, PA_PROP_MEDIA_FILENAME, fname);
 
diff --git a/src/pulsecore/source-output.c b/src/pulsecore/source-output.c
index 1d21ffb..204e06c 100644
--- a/src/pulsecore/source-output.c
+++ b/src/pulsecore/source-output.c
@@ -685,15 +685,15 @@ int pa_source_output_start_move(pa_source_output *o) {
 
     origin = o->source;
 
-    pa_assert_se(pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o->source), PA_SOURCE_MESSAGE_REMOVE_OUTPUT, o, 0, NULL) == 0);
+    pa_idxset_remove_by_data(o->source->outputs, o, NULL);
 
     if (pa_source_output_get_state(o) == PA_SOURCE_OUTPUT_CORKED)
         pa_assert_se(origin->n_corked-- >= 1);
 
-    pa_idxset_remove_by_data(o->source->outputs, o, NULL);
-    o->source = NULL;
+    pa_assert_se(pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o->source), PA_SOURCE_MESSAGE_REMOVE_OUTPUT, o, 0, NULL) == 0);
 
-    pa_source_update_status(origin);
+    pa_source_update_status(o->source);
+    o->source = NULL;
 
     return 0;
 }
@@ -707,6 +707,9 @@ int pa_source_output_finish_move(pa_source_output *o, pa_source *dest) {
     pa_assert(!o->source);
     pa_source_assert_ref(dest);
 
+    if (!pa_source_output_may_move_to(o, dest))
+        return -1;
+
     o->source = dest;
     pa_idxset_put(o->source->outputs, o, NULL);
 
diff --git a/src/pulsecore/source.c b/src/pulsecore/source.c
index 3cddd09..cd6ebc3 100644
--- a/src/pulsecore/source.c
+++ b/src/pulsecore/source.c
@@ -187,10 +187,12 @@ pa_source* pa_source_new(
     s->n_corked = 0;
     s->monitor_of = NULL;
 
-    s->volume = data->volume;
+    s->virtual_volume = data->volume;
+    pa_cvolume_reset(&s->soft_volume, s->sample_spec.channels);
+    s->base_volume = PA_VOLUME_NORM;
+    s->n_volume_steps = PA_VOLUME_NORM+1;
     s->muted = data->muted;
     s->refresh_volume = s->refresh_muted = FALSE;
-    s->base_volume = PA_VOLUME_NORM;
 
     reset_callbacks(s);
     s->userdata = NULL;
@@ -206,8 +208,8 @@ pa_source* pa_source_new(
             0);
 
     s->thread_info.outputs = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
-    pa_cvolume_reset(&s->thread_info.soft_volume, s->sample_spec.channels);
-    s->thread_info.soft_muted = FALSE;
+    s->thread_info.soft_volume = s->soft_volume;
+    s->thread_info.soft_muted = s->muted;
     s->thread_info.state = s->state;
     s->thread_info.max_rewind = 0;
     s->thread_info.requested_latency_valid = FALSE;
@@ -293,10 +295,13 @@ void pa_source_put(pa_source *s) {
     if (!(s->flags & PA_SOURCE_HW_VOLUME_CTRL)) {
         s->flags |= PA_SOURCE_DECIBEL_VOLUME;
 
-        s->thread_info.soft_volume = s->volume;
+        s->thread_info.soft_volume = s->soft_volume;
         s->thread_info.soft_muted = s->muted;
     }
 
+    if (s->flags & PA_SOURCE_DECIBEL_VOLUME)
+        s->n_volume_steps = PA_VOLUME_NORM+1;
+
     pa_assert_se(source_set_state(s, PA_SOURCE_IDLE) == 0);
 
     pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE | PA_SUBSCRIPTION_EVENT_NEW, s->index);
@@ -568,32 +573,38 @@ pa_usec_t pa_source_get_latency(pa_source *s) {
 
 /* Called from main thread */
 void pa_source_set_volume(pa_source *s, const pa_cvolume *volume) {
-    pa_bool_t changed;
+    pa_cvolume old_virtual_volume;
+    pa_bool_t virtual_volume_changed;
 
     pa_source_assert_ref(s);
     pa_assert(PA_SOURCE_IS_LINKED(s->state));
     pa_assert(volume);
+    pa_assert(pa_cvolume_valid(volume));
+    pa_assert(pa_cvolume_compatible(volume, &s->sample_spec));
 
-    changed = !pa_cvolume_equal(volume, &s->volume);
-    s->volume = *volume;
+    old_virtual_volume = s->virtual_volume;
+    s->virtual_volume = *volume;
+    virtual_volume_changed = !pa_cvolume_equal(&old_virtual_volume, &s->virtual_volume);
 
-    if (s->set_volume && s->set_volume(s) < 0)
-        s->set_volume = NULL;
+    if (s->set_volume) {
+        pa_cvolume_reset(&s->soft_volume, s->sample_spec.channels);
+        s->set_volume(s);
+    } else
+        s->soft_volume = s->virtual_volume;
 
-    if (!s->set_volume)
-        pa_source_set_soft_volume(s, volume);
+    pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_SET_VOLUME, NULL, 0, NULL) == 0);
 
-    if (changed)
+    if (virtual_volume_changed)
         pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
 }
 
-/* Called from main thread */
+/* Called from main thread. Only to be called by source implementor */
 void pa_source_set_soft_volume(pa_source *s, const pa_cvolume *volume) {
     pa_source_assert_ref(s);
     pa_assert(volume);
 
     if (PA_SOURCE_IS_LINKED(s->state))
-        pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_SET_VOLUME, volume, 0, NULL);
+        pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_SET_VOLUME, NULL, 0, NULL) == 0);
     else
         s->thread_info.soft_volume = *volume;
 }
@@ -604,38 +615,36 @@ const pa_cvolume *pa_source_get_volume(pa_source *s, pa_bool_t force_refresh) {
     pa_assert(PA_SOURCE_IS_LINKED(s->state));
 
     if (s->refresh_volume || force_refresh) {
-        pa_cvolume old_volume = s->volume;
+        pa_cvolume old_virtual_volume = s->virtual_volume;
 
-        if (s->get_volume && s->get_volume(s) < 0)
-            s->get_volume = NULL;
+        if (s->get_volume)
+            s->get_volume(s);
 
-        if (!s->get_volume)
-            pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_GET_VOLUME, &s->volume, 0, NULL);
+        pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_GET_VOLUME, NULL, 0, NULL) == 0);
 
-        if (!pa_cvolume_equal(&old_volume, &s->volume))
+        if (!pa_cvolume_equal(&old_virtual_volume, &s->virtual_volume))
             pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
     }
 
-    return &s->volume;
+    return &s->virtual_volume;
 }
 
 /* Called from main thread */
 void pa_source_set_mute(pa_source *s, pa_bool_t mute) {
-    pa_bool_t changed;
+    pa_bool_t old_muted;
 
     pa_source_assert_ref(s);
     pa_assert(PA_SOURCE_IS_LINKED(s->state));
 
-    changed = s->muted != mute;
+    old_muted = s->muted;
     s->muted = mute;
 
-    if (s->set_mute && s->set_mute(s) < 0)
-        s->set_mute = NULL;
+    if (s->set_mute)
+        s->set_mute(s);
 
-    if (!s->set_mute)
-        pa_asyncmsgq_post(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_SET_MUTE, PA_UINT_TO_PTR(mute), 0, NULL, NULL);
+    pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_SET_MUTE, NULL, 0, NULL) == 0);
 
-    if (changed)
+    if (old_muted != s->muted)
         pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
 }
 
@@ -648,11 +657,10 @@ pa_bool_t pa_source_get_mute(pa_source *s, pa_bool_t force_refresh) {
     if (s->refresh_muted || force_refresh) {
         pa_bool_t old_muted = s->muted;
 
-        if (s->get_mute && s->get_mute(s) < 0)
-            s->get_mute = NULL;
+        if (s->get_mute)
+            s->get_mute(s);
 
-        if (!s->get_mute)
-            pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_GET_MUTE, &s->muted, 0, NULL);
+        pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_GET_MUTE, NULL, 0, NULL) == 0);
 
         if (old_muted != s->muted)
             pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
@@ -661,6 +669,7 @@ pa_bool_t pa_source_get_mute(pa_source *s, pa_bool_t force_refresh) {
     return s->muted;
 }
 
+/* Called from main thread */
 pa_bool_t pa_source_update_proplist(pa_source *s, pa_update_mode_t mode, pa_proplist *p) {
 
     pa_source_assert_ref(s);
@@ -814,19 +823,17 @@ int pa_source_process_msg(pa_msgobject *object, int code, void *userdata, int64_
         }
 
         case PA_SOURCE_MESSAGE_SET_VOLUME:
-            s->thread_info.soft_volume = *((pa_cvolume*) userdata);
+            s->thread_info.soft_volume = s->soft_volume;
             return 0;
 
-        case PA_SOURCE_MESSAGE_SET_MUTE:
-            s->thread_info.soft_muted = PA_PTR_TO_UINT(userdata);
+        case PA_SOURCE_MESSAGE_GET_VOLUME:
             return 0;
 
-        case PA_SOURCE_MESSAGE_GET_VOLUME:
-            *((pa_cvolume*) userdata) = s->thread_info.soft_volume;
+        case PA_SOURCE_MESSAGE_SET_MUTE:
+            s->thread_info.soft_muted = s->muted;
             return 0;
 
         case PA_SOURCE_MESSAGE_GET_MUTE:
-            *((pa_bool_t*) userdata) = s->thread_info.soft_muted;
             return 0;
 
         case PA_SOURCE_MESSAGE_SET_STATE:
diff --git a/src/pulsecore/source.h b/src/pulsecore/source.h
index 479cade..de23977 100644
--- a/src/pulsecore/source.h
+++ b/src/pulsecore/source.h
@@ -73,10 +73,11 @@ struct pa_source {
     unsigned n_corked;
     pa_sink *monitor_of;                     /* may be NULL */
 
-    pa_cvolume volume;
-    pa_bool_t muted;
-
     pa_volume_t base_volume; /* shall be constant */
+    unsigned n_volume_steps; /* shall be constant */
+
+    pa_cvolume virtual_volume, soft_volume;
+    pa_bool_t muted:1;
 
     pa_bool_t refresh_volume:1;
     pa_bool_t refresh_muted:1;
@@ -95,23 +96,23 @@ struct pa_source {
      * 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. */
-    int (*get_volume)(pa_source *s);         /* dito */
+    void (*get_volume)(pa_source *s);         /* dito */
 
     /* 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. */
-    int (*set_volume)(pa_source *s);         /* dito */
+    void (*set_volume)(pa_source *s);         /* dito */
 
     /* 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.*/
-    int (*get_mute)(pa_source *s);           /* dito */
+    void (*get_mute)(pa_source *s);           /* dito */
 
     /* 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. */
-    int (*set_mute)(pa_source *s);           /* dito */
+    void (*set_mute)(pa_source *s);           /* dito */
 
     /* Called when a the requested latency is changed. Called from IO
      * thread context. */
@@ -122,6 +123,7 @@ struct pa_source {
     struct {
         pa_source_state_t state;
         pa_hashmap *outputs;
+
         pa_cvolume soft_volume;
         pa_bool_t soft_muted:1;
 
@@ -189,7 +191,7 @@ void pa_source_new_data_set_volume(pa_source_new_data *data, const pa_cvolume *v
 void pa_source_new_data_set_muted(pa_source_new_data *data, pa_bool_t mute);
 void pa_source_new_data_done(pa_source_new_data *data);
 
-/* To be called exclusively by the source driver, from main context */
+/*** To be called exclusively by the source driver, from main context */
 
 pa_source* pa_source_new(
         pa_core *core,
@@ -208,7 +210,9 @@ void pa_source_set_latency_range(pa_source *s, pa_usec_t min_latency, pa_usec_t
 void pa_source_detach(pa_source *s);
 void pa_source_attach(pa_source *s);
 
-/* May be called by everyone, from main context */
+void pa_source_set_soft_volume(pa_source *s, const pa_cvolume *volume);
+
+/*** May be called by everyone, from main context */
 
 /* The returned value is supposed to be in the time domain of the sound card! */
 pa_usec_t pa_source_get_latency(pa_source *s);
@@ -222,7 +226,6 @@ int pa_source_suspend(pa_source *s, pa_bool_t suspend);
 int pa_source_suspend_all(pa_core *c, pa_bool_t suspend);
 
 void pa_source_set_volume(pa_source *source, const pa_cvolume *volume);
-void pa_source_set_soft_volume(pa_source *s, const pa_cvolume *volume);
 const pa_cvolume *pa_source_get_volume(pa_source *source, pa_bool_t force_refresh);
 void pa_source_set_mute(pa_source *source, pa_bool_t mute);
 pa_bool_t pa_source_get_mute(pa_source *source, pa_bool_t force_refresh);
@@ -239,7 +242,7 @@ pa_queue *pa_source_move_all_start(pa_source *s);
 void pa_source_move_all_finish(pa_source *s, pa_queue *q);
 void pa_source_move_all_fail(pa_queue *q);
 
-/* To be called exclusively by the source driver, from IO context */
+/*** To be called exclusively by the source driver, from IO context */
 
 void pa_source_post(pa_source*s, const pa_memchunk *chunk);
 void pa_source_post_direct(pa_source*s, pa_source_output *o, const pa_memchunk *chunk);
@@ -255,7 +258,7 @@ pa_usec_t pa_source_get_requested_latency_within_thread(pa_source *s);
 void pa_source_set_max_rewind(pa_source *s, size_t max_rewind);
 void pa_source_update_latency_range(pa_source *s, pa_usec_t min_latency, pa_usec_t max_latency);
 
-/* To be called exclusively by source output drivers, from IO context */
+/*** To be called exclusively by source output drivers, from IO context */
 
 void pa_source_invalidate_requested_latency(pa_source *s);
 

-- 
hooks/post-receive
PulseAudio Sound Server



More information about the pulseaudio-commits mailing list