[pulseaudio-commits] [SCM] PulseAudio Sound Server branch, master, updated. v0.9.16-test4-106-g8c31974

Lennart Poettering gitmailer-noreply at 0pointer.de
Tue Aug 18 18:00:40 PDT 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  8f928b2e572cd7bf26517afddd62ceecb78edfdc (commit)

- Log -----------------------------------------------------------------
8c31974 sink: volume handling rework, new flat volume logic
5207e19 match: document how broken the module-match logic is
cfef930 volume: introduce pa_cvolume_{inc|dec}()
1421eff volume: use PA_VOLUME_MAX instead of (pa_volume_t) -1
d6f598a udev: allow passing of ignore_dB= parameter to alsa modules
24e5828 source: rework volume handling
2838b78 macro: extend comments a bit
a69b729 voltest: extend test to verify correctness of _multiply() and _divide()
2223a9f dbus: never return DBUS_HANDLER_RESULT_HANDLED in filter callbacks, since other callbacks might stell want to have the messages
ef01baf volume: round properly when showing human readable volume percentages
96f01b8 volume: simplify volume multiplifactions, do them in integer only
d634555 volume: introduce pa_cvolume_min() and pa_cvolume_min_mask()
-----------------------------------------------------------------------

Summary of changes:
 src/map-file                                    |    4 +
 src/modules/alsa/alsa-sink.c                    |   39 ++-
 src/modules/alsa/alsa-source.c                  |   37 ++-
 src/modules/bluetooth/module-bluetooth-device.c |    8 +-
 src/modules/module-console-kit.c                |    2 -
 src/modules/module-device-restore.c             |    2 +-
 src/modules/module-hal-detect.c                 |    3 -
 src/modules/module-lirc.c                       |    6 +-
 src/modules/module-match.c                      |    5 +-
 src/modules/module-mmkbd-evdev.c                |    6 +-
 src/modules/module-tunnel.c                     |    4 +-
 src/modules/module-udev-detect.c                |   27 ++-
 src/modules/oss/module-oss.c                    |   16 +-
 src/modules/raop/module-raop-sink.c             |    6 +-
 src/modules/reserve-monitor.c                   |   24 +--
 src/modules/reserve.c                           |   25 +--
 src/pulse/volume.c                              |   89 +++++-
 src/pulse/volume.h                              |   18 +
 src/pulsecore/cli-command.c                     |    4 +-
 src/pulsecore/cli-text.c                        |    6 +-
 src/pulsecore/core.h                            |    1 -
 src/pulsecore/macro.h                           |    8 +-
 src/pulsecore/protocol-native.c                 |    4 +-
 src/pulsecore/sink-input.c                      |  164 ++++-------
 src/pulsecore/sink-input.h                      |   15 +-
 src/pulsecore/sink.c                            |  381 ++++++++++++++--------
 src/pulsecore/sink.h                            |   12 +-
 src/pulsecore/source.c                          |   45 ++--
 src/pulsecore/source.h                          |    2 +-
 src/tests/voltest.c                             |   36 ++-
 30 files changed, 586 insertions(+), 413 deletions(-)

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

commit d634555a3e3e2e35d95da6bca9464c02627d02fd
Author: Lennart Poettering <lennart at poettering.net>
Date:   Wed Aug 19 00:56:16 2009 +0200

    volume: introduce pa_cvolume_min() and pa_cvolume_min_mask()

diff --git a/src/map-file b/src/map-file
index 4f20c48..d6122a4 100644
--- a/src/map-file
+++ b/src/map-file
@@ -131,6 +131,8 @@ pa_cvolume_init;
 pa_cvolume_max;
 pa_cvolume_max_mask;
 pa_cvolume_merge;
+pa_cvolume_min;
+pa_cvolume_min_mask;
 pa_cvolume_remap;
 pa_cvolume_scale;
 pa_cvolume_scale_mask;
diff --git a/src/pulse/volume.c b/src/pulse/volume.c
index d7fb247..e353572 100644
--- a/src/pulse/volume.c
+++ b/src/pulse/volume.c
@@ -126,7 +126,7 @@ pa_volume_t pa_cvolume_avg_mask(const pa_cvolume *a, const pa_channel_map *cm, p
 }
 
 pa_volume_t pa_cvolume_max(const pa_cvolume *a) {
-    pa_volume_t m = 0;
+    pa_volume_t m = PA_VOLUME_MUTED;
     unsigned c;
 
     pa_assert(a);
@@ -139,8 +139,22 @@ pa_volume_t pa_cvolume_max(const pa_cvolume *a) {
     return m;
 }
 
+pa_volume_t pa_cvolume_min(const pa_cvolume *a) {
+    pa_volume_t m = (pa_volume_t) -1;
+    unsigned c;
+
+    pa_assert(a);
+    pa_return_val_if_fail(pa_cvolume_valid(a), PA_VOLUME_MUTED);
+
+    for (c = 0; c < a->channels; c++)
+        if (m == (pa_volume_t) -1 || a->values[c] < m)
+            m = a->values[c];
+
+    return m;
+}
+
 pa_volume_t pa_cvolume_max_mask(const pa_cvolume *a, const pa_channel_map *cm, pa_channel_position_mask_t mask) {
-    pa_volume_t m = 0;
+    pa_volume_t m = PA_VOLUME_MUTED;
     unsigned c, n;
 
     pa_assert(a);
@@ -162,6 +176,29 @@ pa_volume_t pa_cvolume_max_mask(const pa_cvolume *a, const pa_channel_map *cm, p
     return m;
 }
 
+pa_volume_t pa_cvolume_min_mask(const pa_cvolume *a, const pa_channel_map *cm, pa_channel_position_mask_t mask) {
+    pa_volume_t m = (pa_volume_t) -1;
+    unsigned c, n;
+
+    pa_assert(a);
+
+    if (!cm)
+        return pa_cvolume_min(a);
+
+    pa_return_val_if_fail(pa_cvolume_compatible_with_channel_map(a, cm), PA_VOLUME_MUTED);
+
+    for (c = n = 0; c < a->channels; c++) {
+
+        if (!(PA_CHANNEL_POSITION_MASK(cm->map[c]) & mask))
+            continue;
+
+        if (m == (pa_volume_t) -1 || a->values[c] < m)
+            m = a->values[c];
+    }
+
+    return m;
+}
+
 pa_volume_t pa_sw_volume_multiply(pa_volume_t a, pa_volume_t b) {
     return pa_sw_volume_from_linear(pa_sw_volume_to_linear(a) * pa_sw_volume_to_linear(b));
 }
diff --git a/src/pulse/volume.h b/src/pulse/volume.h
index 3881da2..349ca49 100644
--- a/src/pulse/volume.h
+++ b/src/pulse/volume.h
@@ -195,6 +195,16 @@ pa_volume_t pa_cvolume_max(const pa_cvolume *a) PA_GCC_PURE;
  * \since 0.9.16 */
 pa_volume_t pa_cvolume_max_mask(const pa_cvolume *a, const pa_channel_map *cm, pa_channel_position_mask_t mask) PA_GCC_PURE;
 
+/** Return the minimum volume of all channels. \since 0.9.16 */
+pa_volume_t pa_cvolume_min(const pa_cvolume *a) PA_GCC_PURE;
+
+/** Return the minimum volume of all channels that are included in the
+ * specified channel map with the specified channel position mask. If
+ * cm is NULL this call is identical to pa_cvolume_min(). If no
+ * channel is selected the returned value will be PA_VOLUME_MUTED.
+ * \since 0.9.16 */
+pa_volume_t pa_cvolume_min_mask(const pa_cvolume *a, const pa_channel_map *cm, pa_channel_position_mask_t mask) PA_GCC_PURE;
+
 /** Return TRUE when the passed cvolume structure is valid, FALSE otherwise */
 int pa_cvolume_valid(const pa_cvolume *v) PA_GCC_PURE;
 

commit 96f01b822a9be366ac45dc963b5b0b3b852aa236
Author: Lennart Poettering <lennart at poettering.net>
Date:   Wed Aug 19 00:57:58 2009 +0200

    volume: simplify volume multiplifactions, do them in integer only

diff --git a/src/pulse/volume.c b/src/pulse/volume.c
index e353572..0d40237 100644
--- a/src/pulse/volume.c
+++ b/src/pulse/volume.c
@@ -200,16 +200,18 @@ pa_volume_t pa_cvolume_min_mask(const pa_cvolume *a, const pa_channel_map *cm, p
 }
 
 pa_volume_t pa_sw_volume_multiply(pa_volume_t a, pa_volume_t b) {
-    return pa_sw_volume_from_linear(pa_sw_volume_to_linear(a) * pa_sw_volume_to_linear(b));
+
+    /* cbrt((a/PA_VOLUME_NORM)^3*(b/PA_VOLUME_NORM)^3)*PA_VOLUME_NORM = a*b/PA_VOLUME_NORM */
+
+    return (pa_volume_t) (((uint64_t) a * (uint64_t) b + (uint64_t) PA_VOLUME_NORM / 2ULL) / (uint64_t) PA_VOLUME_NORM);
 }
 
 pa_volume_t pa_sw_volume_divide(pa_volume_t a, pa_volume_t b) {
-    double v = pa_sw_volume_to_linear(b);
 
-    if (v <= 0)
+    if (b <= PA_VOLUME_MUTED)
         return 0;
 
-    return pa_sw_volume_from_linear(pa_sw_volume_to_linear(a) / v);
+    return (pa_volume_t) (((uint64_t) a * (uint64_t) PA_VOLUME_NORM + (uint64_t) b / 2ULL) / (uint64_t) b);
 }
 
 /* Amplitude, not power */

commit ef01baf613b5f2cedd1a64b883a79d93965dc219
Author: Lennart Poettering <lennart at poettering.net>
Date:   Wed Aug 19 00:58:20 2009 +0200

    volume: round properly when showing human readable volume percentages

diff --git a/src/pulse/volume.c b/src/pulse/volume.c
index 0d40237..ee86938 100644
--- a/src/pulse/volume.c
+++ b/src/pulse/volume.c
@@ -292,7 +292,7 @@ char *pa_cvolume_snprint(char *s, size_t l, const pa_cvolume *c) {
         l -= pa_snprintf(e, l, "%s%u: %3u%%",
                       first ? "" : " ",
                       channel,
-                      (c->values[channel]*100)/PA_VOLUME_NORM);
+                      (c->values[channel]*100+PA_VOLUME_NORM/2)/PA_VOLUME_NORM);
 
         e = strchr(e, 0);
         first = FALSE;
@@ -312,7 +312,7 @@ char *pa_volume_snprint(char *s, size_t l, pa_volume_t v) {
         return s;
     }
 
-    pa_snprintf(s, l, "%3u%%", (v*100)/PA_VOLUME_NORM);
+    pa_snprintf(s, l, "%3u%%", (v*100+PA_VOLUME_NORM/2)/PA_VOLUME_NORM);
     return s;
 }
 

commit 2223a9f9384ca76691f85d0faf4cdd72924f0976
Author: Lennart Poettering <lennart at poettering.net>
Date:   Wed Aug 19 00:59:23 2009 +0200

    dbus: never return DBUS_HANDLER_RESULT_HANDLED in filter callbacks, since other callbacks might stell want to have the messages

diff --git a/src/modules/module-console-kit.c b/src/modules/module-console-kit.c
index a666073..103f5c4 100644
--- a/src/modules/module-console-kit.c
+++ b/src/modules/module-console-kit.c
@@ -187,7 +187,6 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *message, vo
         }
 
         add_session(u, path);
-        return DBUS_HANDLER_RESULT_HANDLED;
 
     } else if (dbus_message_is_signal(message, "org.freedesktop.ConsoleKit.Seat", "SessionRemoved")) {
 
@@ -202,7 +201,6 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *message, vo
         }
 
         remove_session(u, path);
-        return DBUS_HANDLER_RESULT_HANDLED;
     }
 
 finish:
diff --git a/src/modules/module-hal-detect.c b/src/modules/module-hal-detect.c
index ec370d6..6034d0e 100644
--- a/src/modules/module-hal-detect.c
+++ b/src/modules/module-hal-detect.c
@@ -623,8 +623,6 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *message, vo
 
         }
 
-        return DBUS_HANDLER_RESULT_HANDLED;
-
     } else if (dbus_message_is_signal(message, "org.pulseaudio.Server", "DirtyGiveUpMessage")) {
         /* We use this message to avoid a dirty race condition when we
            get an ACLAdded message before the previously owning PA
@@ -668,7 +666,6 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *message, vo
             /* Yes, we don't check the UDI for validity, but hopefully HAL will */
             device_added_cb(u->context, udi);
 
-        return DBUS_HANDLER_RESULT_HANDLED;
     }
 
 finish:

commit a69b7294145e7dfed6ede8e3d8aa01d7e8509142
Author: Lennart Poettering <lennart at poettering.net>
Date:   Wed Aug 19 01:02:32 2009 +0200

    voltest: extend test to verify correctness of _multiply() and _divide()

diff --git a/src/modules/reserve-monitor.c b/src/modules/reserve-monitor.c
index 97cb9b9..ab453e6 100644
--- a/src/modules/reserve-monitor.c
+++ b/src/modules/reserve-monitor.c
@@ -64,7 +64,6 @@ static DBusHandlerResult filter_handler(
 	DBusMessage *s,
 	void *userdata) {
 
-	DBusMessage *reply;
 	rm_monitor *m;
 	DBusError error;
 
@@ -105,31 +104,10 @@ static DBusHandlerResult filter_handler(
 		}
 	}
 
-	return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
-
 invalid:
-	if (!(reply = dbus_message_new_error(
-		      s,
-		      DBUS_ERROR_INVALID_ARGS,
-		      "Invalid arguments")))
-		goto oom;
-
-	if (!dbus_connection_send(c, reply, NULL))
-		goto oom;
-
-	dbus_message_unref(reply);
-
 	dbus_error_free(&error);
 
-	return DBUS_HANDLER_RESULT_HANDLED;
-
-oom:
-	if (reply)
-		dbus_message_unref(reply);
-
-	dbus_error_free(&error);
-
-	return DBUS_HANDLER_RESULT_NEED_MEMORY;
+	return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
 }
 
 int rm_watch(
diff --git a/src/modules/reserve.c b/src/modules/reserve.c
index 5597f17..b4c168c 100644
--- a/src/modules/reserve.c
+++ b/src/modules/reserve.c
@@ -291,7 +291,6 @@ static DBusHandlerResult filter_handler(
 	DBusMessage *m,
 	void *userdata) {
 
-	DBusMessage *reply;
 	rd_device *d;
 	DBusError error;
 
@@ -323,35 +322,13 @@ static DBusHandlerResult filter_handler(
 				rd_release(d);
 			}
 
-			return DBUS_HANDLER_RESULT_HANDLED;
 		}
 	}
 
-	return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
-
 invalid:
-	if (!(reply = dbus_message_new_error(
-		      m,
-		      DBUS_ERROR_INVALID_ARGS,
-		      "Invalid arguments")))
-		goto oom;
-
-	if (!dbus_connection_send(c, reply, NULL))
-		goto oom;
-
-	dbus_message_unref(reply);
-
-	dbus_error_free(&error);
-
-	return DBUS_HANDLER_RESULT_HANDLED;
-
-oom:
-	if (reply)
-		dbus_message_unref(reply);
-
 	dbus_error_free(&error);
 
-	return DBUS_HANDLER_RESULT_NEED_MEMORY;
+	return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
 }
 
 
diff --git a/src/tests/voltest.c b/src/tests/voltest.c
index 64aec5c..551f7ec 100644
--- a/src/tests/voltest.c
+++ b/src/tests/voltest.c
@@ -33,6 +33,8 @@ int main(int argc, char *argv[]) {
     pa_cvolume cv;
     float b;
     pa_channel_map map;
+    pa_volume_t md = 0;
+    unsigned mdn = 0;
 
     printf("Attenuation of sample 1 against 32767: %g dB\n", 20.0*log10(1.0/32767.0));
     printf("Smallest possible attenutation > 0 applied to 32767: %li\n", lrint(32767.0*pa_sw_volume_to_linear(1)));
@@ -85,16 +87,48 @@ int main(int argc, char *argv[]) {
                 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" : "");
             }
 
-    for (v = PA_VOLUME_MUTED; v <= PA_VOLUME_NORM*2; v += 1) {
+    for (v = PA_VOLUME_MUTED; v <= PA_VOLUME_NORM*2; v += 51) {
 
         double l = pa_sw_volume_to_linear(v);
         pa_volume_t k = pa_sw_volume_from_linear(l);
         double db = pa_sw_volume_to_dB(v);
         pa_volume_t r = pa_sw_volume_from_dB(db);
+        pa_volume_t w;
 
         pa_assert(k == v);
         pa_assert(r == v);
+
+        for (w = PA_VOLUME_MUTED; w < PA_VOLUME_NORM*2; w += 37) {
+
+            double t = pa_sw_volume_to_linear(w);
+            double db2 = pa_sw_volume_to_dB(w);
+            pa_volume_t p, p1, p2;
+            double q, qq;
+
+            p = pa_sw_volume_multiply(v, w);
+            qq = db + db2;
+            p2 = pa_sw_volume_from_dB(qq);
+            q = l*t;
+            p1 = pa_sw_volume_from_linear(q);
+
+            if (p2 > p && p2 - p > md)
+                md = p2 - p;
+            if (p2 < p && p - p2 > md)
+                md = p - p2;
+            if (p1 > p && p1 - p > md)
+                md = p1 - p;
+            if (p1 < p && p - p1 > md)
+                md = p - p1;
+
+            if (p1 != p || p2 != p)
+                mdn++;
+        }
     }
 
+    printf("max deviation: %lu n=%lu\n", (unsigned long) md, (unsigned long) mdn);
+
+    pa_assert(md <= 1);
+    pa_assert(mdn <= 251);
+
     return 0;
 }

commit 2838b78e59ee7c8ea42fec6880cc4c2b2a2c9485
Author: Lennart Poettering <lennart at poettering.net>
Date:   Wed Aug 19 01:03:09 2009 +0200

    macro: extend comments a bit

diff --git a/src/pulsecore/macro.h b/src/pulsecore/macro.h
index 3c560bc..ce88c1b 100644
--- a/src/pulsecore/macro.h
+++ b/src/pulsecore/macro.h
@@ -80,10 +80,10 @@ static inline size_t PA_PAGE_ALIGN(size_t l) {
 
 #define PA_ELEMENTSOF(x) (sizeof(x)/sizeof((x)[0]))
 
-/* The users of PA_MIN and PA_MAX should be aware that these macros on
- * non-GCC executed code with side effects twice. It is thus
- * considered misuse to use code with side effects as arguments to MIN
- * and MAX. */
+/* The users of PA_MIN and PA_MAX, PA_CLAMP, PA_ROUND_UP should be
+ * aware that these macros on non-GCC executed code with side effects
+ * twice. It is thus considered misuse to use code with side effects
+ * as arguments to MIN and MAX. */
 
 #ifdef __GNUC__
 #define PA_MAX(a,b)                             \

commit 24e582808c18d6866d8c10f8f0320b1af0ab758b
Author: Lennart Poettering <lennart at poettering.net>
Date:   Wed Aug 19 01:35:43 2009 +0200

    source: rework volume handling
    
    - drop the 'virtual_' prefix from s->virtual_volume since we don't
      distuingish between reference and real volumes for sources
    
    - introduce an accuracy for source volumes: if the hardware can control
      the volume "close enough" don't necessarily adjust the rest in
      software unless it is beyond a certain threshold. This should save a
      little bit of CPU at the expensive of a bit of accuracy in volume
      handling.
    
    - other minor cleanups

diff --git a/src/modules/alsa/alsa-source.c b/src/modules/alsa/alsa-source.c
index 9a51f85..7da3755 100644
--- a/src/modules/alsa/alsa-source.c
+++ b/src/modules/alsa/alsa-source.c
@@ -65,6 +65,8 @@
 #define TSCHED_MIN_SLEEP_USEC (10*PA_USEC_PER_MSEC)          /* 10ms */
 #define TSCHED_MIN_WAKEUP_USEC (4*PA_USEC_PER_MSEC)          /* 4ms */
 
+#define VOLUME_ACCURACY (PA_VOLUME_NORM/100)
+
 struct userdata {
     pa_core *core;
     pa_module *module;
@@ -987,15 +989,11 @@ static void source_get_volume_cb(pa_source *s) {
     if (pa_cvolume_equal(&u->hardware_volume, &r))
         return;
 
-    s->virtual_volume = u->hardware_volume = r;
-
-    if (u->mixer_path->has_dB) {
-        pa_cvolume reset;
+    s->volume = u->hardware_volume = r;
 
-        /* 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);
-    }
+    /* Hmm, so the hardware volume changed, let's reset our software volume */
+    if (u->mixer_path->has_dB)
+        pa_source_set_soft_volume(s, NULL);
 }
 
 static void source_set_volume_cb(pa_source *s) {
@@ -1008,7 +1006,7 @@ static void source_set_volume_cb(pa_source *s) {
     pa_assert(u->mixer_handle);
 
     /* Shift up by the base volume */
-    pa_sw_cvolume_divide_scalar(&r, &s->virtual_volume, s->base_volume);
+    pa_sw_cvolume_divide_scalar(&r, &s->volume, s->base_volume);
 
     if (pa_alsa_path_set_volume(u->mixer_path, u->mixer_handle, &s->channel_map, &r) < 0)
         return;
@@ -1019,13 +1017,26 @@ static void source_set_volume_cb(pa_source *s) {
     u->hardware_volume = r;
 
     if (u->mixer_path->has_dB) {
+        pa_cvolume new_soft_volume;
+        pa_bool_t accurate_enough;
 
         /* Match exactly what the user requested by software */
-        pa_sw_cvolume_divide(&s->soft_volume, &s->virtual_volume, &u->hardware_volume);
+        pa_sw_cvolume_divide(&new_soft_volume, &s->volume, &u->hardware_volume);
+
+        /* If the adjustment to do in software is only minimal we
+         * can skip it. That saves us CPU at the expense of a bit of
+         * accuracy */
+        accurate_enough =
+            (pa_cvolume_min(&new_soft_volume) >= (PA_VOLUME_NORM - VOLUME_ACCURACY)) &&
+            (pa_cvolume_max(&new_soft_volume) <= (PA_VOLUME_NORM + VOLUME_ACCURACY));
 
-        pa_log_debug("Requested volume: %s", pa_cvolume_snprint(t, sizeof(t), &s->virtual_volume));
+        pa_log_debug("Requested volume: %s", pa_cvolume_snprint(t, sizeof(t), &s->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), &s->soft_volume));
+        pa_log_debug("Calculated software volume: %s (accurate-enough=%s)", pa_cvolume_snprint(t, sizeof(t), &new_soft_volume),
+                     pa_yes_no(accurate_enough));
+
+        if (!accurate_enough)
+            s->soft_volume = new_soft_volume;
 
     } else {
         pa_log_debug("Wrote hardware volume: %s", pa_cvolume_snprint(t, sizeof(t), &r));
@@ -1033,7 +1044,7 @@ static void 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->virtual_volume = r;
+        s->volume = r;
     }
 }
 
diff --git a/src/modules/bluetooth/module-bluetooth-device.c b/src/modules/bluetooth/module-bluetooth-device.c
index d6321fc..395ec83 100644
--- a/src/modules/bluetooth/module-bluetooth-device.c
+++ b/src/modules/bluetooth/module-bluetooth-device.c
@@ -1500,12 +1500,12 @@ static void source_set_volume_cb(pa_source *s) {
     if (u->profile != PROFILE_HSP)
         return;
 
-    gain = (pa_cvolume_max(&s->virtual_volume) * 15) / PA_VOLUME_NORM;
+    gain = (pa_cvolume_max(&s->volume) * 15) / PA_VOLUME_NORM;
 
     if (gain > 15)
         gain = 15;
 
-    pa_cvolume_set(&s->virtual_volume, u->sample_spec.channels, (pa_volume_t) (gain * PA_VOLUME_NORM / 15));
+    pa_cvolume_set(&s->volume, u->sample_spec.channels, (pa_volume_t) (gain * PA_VOLUME_NORM / 15));
 
     pa_assert_se(m = dbus_message_new_method_call("org.bluez", u->path, "org.bluez.Headset", "SetMicrophoneGain"));
     pa_assert_se(dbus_message_append_args(m, DBUS_TYPE_UINT16, &gain, DBUS_TYPE_INVALID));
diff --git a/src/modules/oss/module-oss.c b/src/modules/oss/module-oss.c
index c44b882..0848d43 100644
--- a/src/modules/oss/module-oss.c
+++ b/src/modules/oss/module-oss.c
@@ -848,11 +848,11 @@ static void source_get_volume(pa_source *s) {
     pa_assert(u->mixer_devmask & (SOUND_MASK_IGAIN|SOUND_MASK_RECLEV));
 
     if (u->mixer_devmask & SOUND_MASK_IGAIN)
-        if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_IGAIN, &s->sample_spec, &s->virtual_volume) >= 0)
+        if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_IGAIN, &s->sample_spec, &s->volume) >= 0)
             return;
 
     if (u->mixer_devmask & SOUND_MASK_RECLEV)
-        if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_RECLEV, &s->sample_spec, &s->virtual_volume) >= 0)
+        if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_RECLEV, &s->sample_spec, &s->volume) >= 0)
             return;
 
     pa_log_info("Device doesn't support reading mixer settings: %s", pa_cstrerror(errno));
@@ -866,11 +866,11 @@ static void source_set_volume(pa_source *s) {
     pa_assert(u->mixer_devmask & (SOUND_MASK_IGAIN|SOUND_MASK_RECLEV));
 
     if (u->mixer_devmask & SOUND_MASK_IGAIN)
-        if (pa_oss_set_volume(u->mixer_fd, SOUND_MIXER_WRITE_IGAIN, &s->sample_spec, &s->virtual_volume) >= 0)
+        if (pa_oss_set_volume(u->mixer_fd, SOUND_MIXER_WRITE_IGAIN, &s->sample_spec, &s->volume) >= 0)
             return;
 
     if (u->mixer_devmask & SOUND_MASK_RECLEV)
-        if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_WRITE_RECLEV, &s->sample_spec, &s->virtual_volume) >= 0)
+        if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_WRITE_RECLEV, &s->sample_spec, &s->volume) >= 0)
             return;
 
     pa_log_info("Device doesn't support writing mixer settings: %s", pa_cstrerror(errno));
diff --git a/src/pulsecore/source.c b/src/pulsecore/source.c
index 46f049e..8aa07f5 100644
--- a/src/pulsecore/source.c
+++ b/src/pulsecore/source.c
@@ -181,7 +181,7 @@ pa_source* pa_source_new(
         pa_cvolume_reset(&data->volume, data->sample_spec.channels);
 
     pa_return_null_if_fail(pa_cvolume_valid(&data->volume));
-    pa_return_null_if_fail(data->volume.channels == data->sample_spec.channels);
+    pa_return_null_if_fail(pa_cvolume_compatible(&data->volume, &data->sample_spec));
 
     if (!data->muted_is_set)
         data->muted = FALSE;
@@ -219,7 +219,7 @@ pa_source* pa_source_new(
     s->n_corked = 0;
     s->monitor_of = NULL;
 
-    s->virtual_volume = data->volume;
+    s->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;
@@ -751,31 +751,32 @@ pa_usec_t pa_source_get_latency_within_thread(pa_source *s) {
 }
 
 /* Called from main thread */
-void pa_source_set_volume(pa_source *s, const pa_cvolume *volume, pa_bool_t save) {
-    pa_cvolume old_virtual_volume;
-    pa_bool_t virtual_volume_changed;
+void pa_source_set_volume(
+        pa_source *s,
+        const pa_cvolume *volume,
+        pa_bool_t save) {
+
+    pa_bool_t real_changed;
 
     pa_source_assert_ref(s);
     pa_assert_ctl_context();
     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));
 
-    old_virtual_volume = s->virtual_volume;
-    s->virtual_volume = *volume;
-    virtual_volume_changed = !pa_cvolume_equal(&old_virtual_volume, &s->virtual_volume);
-    s->save_volume = (!virtual_volume_changed && s->save_volume) || save;
+    real_changed = !pa_cvolume_equal(volume, &s->volume);
+    s->volume = *volume;
+    s->save_volume = (!real_changed && s->save_volume) || save;
 
     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;
+        s->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 (virtual_volume_changed)
+    if (real_changed)
         pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
 }
 
@@ -783,12 +784,16 @@ void pa_source_set_volume(pa_source *s, const pa_cvolume *volume, pa_bool_t save
 void pa_source_set_soft_volume(pa_source *s, const pa_cvolume *volume) {
     pa_source_assert_ref(s);
     pa_assert_ctl_context();
-    pa_assert(volume);
+
+    if (!volume)
+        pa_cvolume_reset(&s->soft_volume, s->sample_spec.channels);
+    else
+        s->soft_volume = *volume;
 
     if (PA_SOURCE_IS_LINKED(s->state))
         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;
+        s->thread_info.soft_volume = s->soft_volume;
 }
 
 /* Called from main thread */
@@ -798,20 +803,22 @@ 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_virtual_volume = s->virtual_volume;
+        pa_cvolume old_volume;
+
+        old_volume = s->volume;
 
         if (s->get_volume)
             s->get_volume(s);
 
         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_virtual_volume, &s->virtual_volume)) {
+        if (!pa_cvolume_equal(&old_volume, &s->volume)) {
             s->save_volume = TRUE;
             pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
         }
     }
 
-    return &s->virtual_volume;
+    return &s->volume;
 }
 
 /* Called from main thread */
@@ -822,10 +829,10 @@ void pa_source_volume_changed(pa_source *s, const pa_cvolume *new_volume) {
 
     /* The source implementor may call this if the volume changed to make sure everyone is notified */
 
-    if (pa_cvolume_equal(&s->virtual_volume, new_volume))
+    if (pa_cvolume_equal(&s->volume, new_volume))
         return;
 
-    s->virtual_volume = *new_volume;
+    s->volume = *new_volume;
     s->save_volume = TRUE;
 
     pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
diff --git a/src/pulsecore/source.h b/src/pulsecore/source.h
index 6f33de0..7b3e495 100644
--- a/src/pulsecore/source.h
+++ b/src/pulsecore/source.h
@@ -79,7 +79,7 @@ struct pa_source {
     pa_volume_t base_volume; /* shall be constant */
     unsigned n_volume_steps; /* shall be constant */
 
-    pa_cvolume virtual_volume, soft_volume;
+    pa_cvolume volume, soft_volume;
     pa_bool_t muted:1;
 
     pa_bool_t refresh_volume:1;

commit d6f598ab3e1cdb71dc3b408592d06bba23f53a71
Author: Lennart Poettering <lennart at poettering.net>
Date:   Wed Aug 19 02:29:59 2009 +0200

    udev: allow passing of ignore_dB= parameter to alsa modules

diff --git a/src/modules/alsa/alsa-sink.c b/src/modules/alsa/alsa-sink.c
index a91b4b8..1253836 100644
--- a/src/modules/alsa/alsa-sink.c
+++ b/src/modules/alsa/alsa-sink.c
@@ -68,6 +68,8 @@
 #define TSCHED_MIN_SLEEP_USEC (10*PA_USEC_PER_MSEC)               /* 10ms -- Sleep at least 10ms on each iteration */
 #define TSCHED_MIN_WAKEUP_USEC (4*PA_USEC_PER_MSEC)               /* 4ms  -- Wakeup at least this long before the buffer runs empty*/
 
+#define VOLUME_ACCURACY (PA_VOLUME_NORM/100)  /* don't require volume adjustments to be perfectly correct. don't necessarily extend granularity in software unless the differences get greater than this level */
+
 struct userdata {
     pa_core *core;
     pa_module *module;
@@ -1034,15 +1036,11 @@ static void sink_get_volume_cb(pa_sink *s) {
     if (pa_cvolume_equal(&u->hardware_volume, &r))
         return;
 
-    s->virtual_volume = u->hardware_volume = r;
-
-    if (u->mixer_path->has_dB) {
-        pa_cvolume reset;
+    s->real_volume = u->hardware_volume = r;
 
-        /* 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);
-    }
+    /* Hmm, so the hardware volume changed, let's reset our software volume */
+    if (u->mixer_path->has_dB)
+        pa_sink_set_soft_volume(s, NULL);
 }
 
 static void sink_set_volume_cb(pa_sink *s) {
@@ -1055,7 +1053,7 @@ static void sink_set_volume_cb(pa_sink *s) {
     pa_assert(u->mixer_handle);
 
     /* Shift up by the base volume */
-    pa_sw_cvolume_divide_scalar(&r, &s->virtual_volume, s->base_volume);
+    pa_sw_cvolume_divide_scalar(&r, &s->real_volume, s->base_volume);
 
     if (pa_alsa_path_set_volume(u->mixer_path, u->mixer_handle, &s->channel_map, &r) < 0)
         return;
@@ -1066,13 +1064,26 @@ static void sink_set_volume_cb(pa_sink *s) {
     u->hardware_volume = r;
 
     if (u->mixer_path->has_dB) {
+        pa_cvolume new_soft_volume;
+        pa_bool_t accurate_enough;
 
         /* Match exactly what the user requested by software */
-        pa_sw_cvolume_divide(&s->soft_volume, &s->virtual_volume, &u->hardware_volume);
+        pa_sw_cvolume_divide(&new_soft_volume, &s->real_volume, &u->hardware_volume);
+
+        /* If the adjustment to do in software is only minimal we
+         * can skip it. That saves us CPU at the expense of a bit of
+         * accuracy */
+        accurate_enough =
+            (pa_cvolume_min(&new_soft_volume) >= (PA_VOLUME_NORM - VOLUME_ACCURACY)) &&
+            (pa_cvolume_max(&new_soft_volume) <= (PA_VOLUME_NORM + VOLUME_ACCURACY));
 
-        pa_log_debug("Requested volume: %s", pa_cvolume_snprint(t, sizeof(t), &s->virtual_volume));
+        pa_log_debug("Requested volume: %s", pa_cvolume_snprint(t, sizeof(t), &s->real_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), &s->soft_volume));
+        pa_log_debug("Calculated software volume: %s (accurate-enough=%s)", pa_cvolume_snprint(t, sizeof(t), &new_soft_volume),
+                     pa_yes_no(accurate_enough));
+
+        if (!accurate_enough)
+            s->soft_volume = new_soft_volume;
 
     } else {
         pa_log_debug("Wrote hardware volume: %s", pa_cvolume_snprint(t, sizeof(t), &r));
@@ -1080,7 +1091,7 @@ static void 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->virtual_volume = r;
+        s->real_volume = r;
     }
 }
 
diff --git a/src/modules/bluetooth/module-bluetooth-device.c b/src/modules/bluetooth/module-bluetooth-device.c
index 395ec83..4e23862 100644
--- a/src/modules/bluetooth/module-bluetooth-device.c
+++ b/src/modules/bluetooth/module-bluetooth-device.c
@@ -1476,12 +1476,12 @@ static void sink_set_volume_cb(pa_sink *s) {
     if (u->profile != PROFILE_HSP)
         return;
 
-    gain = (pa_cvolume_max(&s->virtual_volume) * 15) / PA_VOLUME_NORM;
+    gain = (pa_cvolume_max(&s->real_volume) * 15) / PA_VOLUME_NORM;
 
     if (gain > 15)
         gain = 15;
 
-    pa_cvolume_set(&s->virtual_volume, u->sample_spec.channels, (pa_volume_t) (gain * PA_VOLUME_NORM / 15));
+    pa_cvolume_set(&s->real_volume, u->sample_spec.channels, (pa_volume_t) (gain * PA_VOLUME_NORM / 15));
 
     pa_assert_se(m = dbus_message_new_method_call("org.bluez", u->path, "org.bluez.Headset", "SetSpeakerGain"));
     pa_assert_se(dbus_message_append_args(m, DBUS_TYPE_UINT16, &gain, DBUS_TYPE_INVALID));
diff --git a/src/modules/module-lirc.c b/src/modules/module-lirc.c
index 06efeb8..2bb8014 100644
--- a/src/modules/module-lirc.c
+++ b/src/modules/module-lirc.c
@@ -133,7 +133,7 @@ static void io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io_event
                                     cv.values[i] = PA_VOLUME_MAX;
                             }
 
-                            pa_sink_set_volume(s, &cv, TRUE, TRUE, TRUE, TRUE);
+                            pa_sink_set_volume(s, &cv, TRUE, TRUE);
                             break;
 
                         case DOWN:
@@ -144,7 +144,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, TRUE, TRUE, TRUE, TRUE);
+                            pa_sink_set_volume(s, &cv, TRUE, TRUE);
                             break;
 
                         case MUTE:
diff --git a/src/modules/module-udev-detect.c b/src/modules/module-udev-detect.c
index 11de1cc..0b30fd5 100644
--- a/src/modules/module-udev-detect.c
+++ b/src/modules/module-udev-detect.c
@@ -39,6 +39,9 @@ PA_MODULE_AUTHOR("Lennart Poettering");
 PA_MODULE_DESCRIPTION("Detect available audio hardware and load matching drivers");
 PA_MODULE_VERSION(PACKAGE_VERSION);
 PA_MODULE_LOAD_ONCE(TRUE);
+PA_MODULE_USAGE(
+        "tsched=<enable system timer based scheduling mode?> "
+        "ignore_dB=<ignore dB information from the device?>");
 
 struct device {
     char *path;
@@ -50,7 +53,9 @@ struct device {
 struct userdata {
     pa_core *core;
     pa_hashmap *devices;
-    pa_bool_t use_tsched;
+
+    pa_bool_t use_tsched:1;
+    pa_bool_t ignore_dB:1;
 
     struct udev* udev;
     struct udev_monitor *monitor;
@@ -62,6 +67,7 @@ struct userdata {
 
 static const char* const valid_modargs[] = {
     "tsched",
+    "ignore_dB",
     NULL
 };
 
@@ -140,12 +146,14 @@ static void card_changed(struct userdata *u, struct udev_device *dev) {
     args = pa_sprintf_malloc("device_id=\"%s\" "
                              "name=\"%s\" "
                              "card_name=\"%s\" "
-                             "tsched=%i "
+                             "tsched=%s "
+                             "ignore_dB=%s "
                              "card_properties=\"module-udev-detect.discovered=1\"",
                              path_get_card_id(path),
                              n,
                              card_name,
-                             (int) u->use_tsched);
+                             pa_yes_no(u->use_tsched),
+                             pa_yes_no(u->ignore_dB));
 
     pa_log_debug("Loading module-alsa-card with arguments '%s'", args);
     m = pa_module_load(u->core, "module-alsa-card", args);
@@ -364,6 +372,7 @@ int pa__init(pa_module *m) {
     struct udev_enumerate *enumerate = NULL;
     struct udev_list_entry *item = NULL, *first = NULL;
     int fd;
+    pa_bool_t use_tsched = TRUE, ignore_dB = FALSE;
 
     pa_assert(m);
 
@@ -375,13 +384,19 @@ int pa__init(pa_module *m) {
     m->userdata = u = pa_xnew0(struct userdata, 1);
     u->core = m->core;
     u->devices = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
-    u->use_tsched = TRUE;
     u->inotify_fd = -1;
 
-    if (pa_modargs_get_value_boolean(ma, "tsched", &u->use_tsched) < 0) {
-        pa_log("Failed to parse tsched argument.");
+    if (pa_modargs_get_value_boolean(ma, "tsched", &use_tsched) < 0) {
+        pa_log("Failed to parse tsched= argument.");
+        goto fail;
+    }
+    u->use_tsched = use_tsched;
+
+    if (pa_modargs_get_value_boolean(ma, "ignore_dB", &ignore_dB) < 0) {
+        pa_log("Failed to parse ignore_dB= argument.");
         goto fail;
     }
+    u->ignore_dB = ignore_dB;
 
     if (!(u->udev = udev_new())) {
         pa_log("Failed to initialize udev library.");

commit 1421eff0b69f6b0173835afe6b857d39e719d1d0
Author: Lennart Poettering <lennart at poettering.net>
Date:   Wed Aug 19 02:31:11 2009 +0200

    volume: use PA_VOLUME_MAX instead of (pa_volume_t) -1

diff --git a/src/pulse/volume.c b/src/pulse/volume.c
index ee86938..3dcf315 100644
--- a/src/pulse/volume.c
+++ b/src/pulse/volume.c
@@ -140,14 +140,14 @@ pa_volume_t pa_cvolume_max(const pa_cvolume *a) {
 }
 
 pa_volume_t pa_cvolume_min(const pa_cvolume *a) {
-    pa_volume_t m = (pa_volume_t) -1;
+    pa_volume_t m = PA_VOLUME_MAX;
     unsigned c;
 
     pa_assert(a);
     pa_return_val_if_fail(pa_cvolume_valid(a), PA_VOLUME_MUTED);
 
     for (c = 0; c < a->channels; c++)
-        if (m == (pa_volume_t) -1 || a->values[c] < m)
+        if (a->values[c] < m)
             m = a->values[c];
 
     return m;
@@ -177,7 +177,7 @@ pa_volume_t pa_cvolume_max_mask(const pa_cvolume *a, const pa_channel_map *cm, p
 }
 
 pa_volume_t pa_cvolume_min_mask(const pa_cvolume *a, const pa_channel_map *cm, pa_channel_position_mask_t mask) {
-    pa_volume_t m = (pa_volume_t) -1;
+    pa_volume_t m = PA_VOLUME_MAX;
     unsigned c, n;
 
     pa_assert(a);
@@ -192,7 +192,7 @@ pa_volume_t pa_cvolume_min_mask(const pa_cvolume *a, const pa_channel_map *cm, p
         if (!(PA_CHANNEL_POSITION_MASK(cm->map[c]) & mask))
             continue;
 
-        if (m == (pa_volume_t) -1 || a->values[c] < m)
+        if (a->values[c] < m)
             m = a->values[c];
     }
 

commit cfef930036572e2770a4c17e57f139737a99444a
Author: Lennart Poettering <lennart at poettering.net>
Date:   Wed Aug 19 02:32:36 2009 +0200

    volume: introduce pa_cvolume_{inc|dec}()

diff --git a/src/map-file b/src/map-file
index d6122a4..95b2803 100644
--- a/src/map-file
+++ b/src/map-file
@@ -123,10 +123,12 @@ pa_cvolume_avg_mask;
 pa_cvolume_channels_equal_to;
 pa_cvolume_compatible;
 pa_cvolume_compatible_with_channel_map;
+pa_cvolume_dec;
 pa_cvolume_equal;
 pa_cvolume_get_balance;
 pa_cvolume_get_fade;
 pa_cvolume_get_position;
+pa_cvolume_inc;
 pa_cvolume_init;
 pa_cvolume_max;
 pa_cvolume_max_mask;
diff --git a/src/pulse/volume.c b/src/pulse/volume.c
index 3dcf315..234c3f7 100644
--- a/src/pulse/volume.c
+++ b/src/pulse/volume.c
@@ -876,3 +876,37 @@ pa_cvolume* pa_cvolume_merge(pa_cvolume *dest, const pa_cvolume *a, const pa_cvo
 
     return dest;
 }
+
+pa_cvolume* pa_cvolume_inc(pa_cvolume *v, pa_volume_t inc) {
+    pa_volume_t m;
+
+    pa_assert(v);
+
+    pa_return_val_if_fail(pa_cvolume_valid(v), NULL);
+
+    m = pa_cvolume_max(v);
+
+    if (m >= PA_VOLUME_MAX - inc)
+        m = PA_VOLUME_MAX;
+    else
+        m += inc;
+
+    return pa_cvolume_scale(v, m);
+}
+
+pa_cvolume* pa_cvolume_dec(pa_cvolume *v, pa_volume_t dec) {
+    pa_volume_t m;
+
+    pa_assert(v);
+
+    pa_return_val_if_fail(pa_cvolume_valid(v), NULL);
+
+    m = pa_cvolume_max(v);
+
+    if (m <= PA_VOLUME_MUTED + dec)
+        m = PA_VOLUME_MUTED;
+    else
+        m -= dec;
+
+    return pa_cvolume_scale(v, m);
+}
diff --git a/src/pulse/volume.h b/src/pulse/volume.h
index 349ca49..543b0af 100644
--- a/src/pulse/volume.h
+++ b/src/pulse/volume.h
@@ -345,6 +345,14 @@ pa_volume_t pa_cvolume_get_position(pa_cvolume *cv, const pa_channel_map *map, p
  * and dest may point to the same structure. \since 0.9.16 */
 pa_cvolume* pa_cvolume_merge(pa_cvolume *dest, const pa_cvolume *a, const pa_cvolume *b);
 
+/** Increase the volume passed in by 'inc'. The proportions between
+ * the channels are kept. \since 0.9.16 */
+pa_cvolume* pa_cvolume_inc(pa_cvolume *v, pa_volume_t inc);
+
+/** Increase the volume passed in by 'inc'. The proportions between
+ * the channels are kept. \since 0.9.16 */
+pa_cvolume* pa_cvolume_dec(pa_cvolume *v, pa_volume_t dec);
+
 PA_C_DECL_END
 
 #endif

commit 5207e191424675df74059aaf30f9b1292a05cb5d
Author: Lennart Poettering <lennart at poettering.net>
Date:   Wed Aug 19 02:37:35 2009 +0200

    match: document how broken the module-match logic is

diff --git a/src/modules/module-match.c b/src/modules/module-match.c
index 625f2a8..14e0112 100644
--- a/src/modules/module-match.c
+++ b/src/modules/module-match.c
@@ -243,6 +243,9 @@ int pa__init(pa_module*m) {
     if (load_rules(u, pa_modargs_get_value(ma, "table", NULL)) < 0)
         goto fail;
 
+    /* FIXME: Doing this asynchronously is just broken. This needs to
+     * use a hook! */
+
     u->subscription = pa_subscription_new(m->core, PA_SUBSCRIPTION_MASK_SINK_INPUT, callback, u);
 
     pa_modargs_free(ma);

commit 8c31974f56ebbbfc1a4978150026acf77c32689e
Author: Lennart Poettering <lennart at poettering.net>
Date:   Wed Aug 19 02:55:02 2009 +0200

    sink: volume handling rework, new flat volume logic
    
    - We now implement a logic where the sink maintains two distinct
      volumes: the 'reference' volume which is shown to the users, and the
      'real' volume, which is configured to the hardware. The latter is
      configured to the max of all streams. Volume changes on sinks are
      propagated back to the streams proportional to the reference volume
      change. Volume changes on sink inputs are forwarded to the sink by
      'pushing' the volume if necessary.
    
      This renames the old 'virtual_volume' to 'real_volume'. The
      'reference_volume' is now the one exposed to users.
    
      By this logic the sink volume visible to the user, will always be the
      "upper" boundary for everything that is played. Saved/restored stream
      volumes are measured relative to this boundary, the factor here is
      always < 1.0.
    
    - introduce accuracy for sink volumes, similar to the accuracy we
      already have for source volumes.
    
    - other cleanups.

diff --git a/src/modules/alsa/alsa-sink.c b/src/modules/alsa/alsa-sink.c
index 1253836..e3707ae 100644
--- a/src/modules/alsa/alsa-sink.c
+++ b/src/modules/alsa/alsa-sink.c
@@ -1009,7 +1009,7 @@ static int mixer_callback(snd_mixer_elem_t *elem, unsigned int mask) {
         return 0;
 
     if (mask & SND_CTL_EVENT_MASK_VALUE) {
-        pa_sink_get_volume(u->sink, TRUE, FALSE);
+        pa_sink_get_volume(u->sink, TRUE);
         pa_sink_get_mute(u->sink, TRUE);
     }
 
diff --git a/src/modules/module-device-restore.c b/src/modules/module-device-restore.c
index 120b762..da6c966 100644
--- a/src/modules/module-device-restore.c
+++ b/src/modules/module-device-restore.c
@@ -218,7 +218,7 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3
 
         if (sink->save_volume) {
             entry.channel_map = sink->channel_map;
-            entry.volume = *pa_sink_get_volume(sink, FALSE, TRUE);
+            entry.volume = *pa_sink_get_volume(sink, FALSE);
             entry.volume_valid = TRUE;
         }
 
diff --git a/src/modules/module-lirc.c b/src/modules/module-lirc.c
index 2bb8014..fdfdc79 100644
--- a/src/modules/module-lirc.c
+++ b/src/modules/module-lirc.c
@@ -120,7 +120,7 @@ static void io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io_event
                     pa_log("Failed to get sink '%s'", u->sink_name);
                 else {
                     int i;
-                    pa_cvolume cv = *pa_sink_get_volume(s, FALSE, FALSE);
+                    pa_cvolume cv = *pa_sink_get_volume(s, FALSE);
 
 #define DELTA (PA_VOLUME_NORM/20)
 
diff --git a/src/modules/module-match.c b/src/modules/module-match.c
index 14e0112..0bd781d 100644
--- a/src/modules/module-match.c
+++ b/src/modules/module-match.c
@@ -216,7 +216,7 @@ static void callback(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, v
                 pa_cvolume cv;
                 pa_log_debug("changing volume of sink input '%s' to 0x%03x", n, r->volume);
                 pa_cvolume_set(&cv, si->sample_spec.channels, r->volume);
-                pa_sink_input_set_volume(si, &cv, TRUE, TRUE);
+                pa_sink_input_set_volume(si, &cv, TRUE, FALSE);
             }
         }
     }
diff --git a/src/modules/module-mmkbd-evdev.c b/src/modules/module-mmkbd-evdev.c
index b30fae5..7be4870 100644
--- a/src/modules/module-mmkbd-evdev.c
+++ b/src/modules/module-mmkbd-evdev.c
@@ -102,7 +102,7 @@ static void io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io_event
                     pa_log("Failed to get sink '%s'", u->sink_name);
                 else {
                     int i;
-                    pa_cvolume cv = *pa_sink_get_volume(s, FALSE, FALSE);
+                    pa_cvolume cv = *pa_sink_get_volume(s, FALSE);
 
 #define DELTA (PA_VOLUME_NORM/20)
 
@@ -115,7 +115,7 @@ static void io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io_event
                                     cv.values[i] = PA_VOLUME_MAX;
                             }
 
-                            pa_sink_set_volume(s, &cv, TRUE, TRUE, TRUE, TRUE);
+                            pa_sink_set_volume(s, &cv, TRUE, TRUE);
                             break;
 
                         case DOWN:
@@ -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_MUTED;
                             }
 
-                            pa_sink_set_volume(s, &cv, TRUE, TRUE, TRUE, TRUE);
+                            pa_sink_set_volume(s, &cv, TRUE, TRUE);
                             break;
 
                         case MUTE_TOGGLE:
diff --git a/src/modules/module-tunnel.c b/src/modules/module-tunnel.c
index eaccea4..5ccb81d 100644
--- a/src/modules/module-tunnel.c
+++ b/src/modules/module-tunnel.c
@@ -1162,7 +1162,7 @@ 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->virtual_volume))
+        pa_cvolume_equal(&volume, &u->sink->real_volume))
         return;
 
     pa_sink_volume_changed(u->sink, &volume);
@@ -1763,7 +1763,7 @@ static void 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->virtual_volume);
+    pa_tagstruct_put_cvolume(t, &sink->real_volume);
     pa_pstream_send_tagstruct(u->pstream, t);
 }
 
diff --git a/src/modules/oss/module-oss.c b/src/modules/oss/module-oss.c
index 0848d43..7153626 100644
--- a/src/modules/oss/module-oss.c
+++ b/src/modules/oss/module-oss.c
@@ -812,11 +812,11 @@ static void sink_get_volume(pa_sink *s) {
     pa_assert(u->mixer_devmask & (SOUND_MASK_VOLUME|SOUND_MASK_PCM));
 
     if (u->mixer_devmask & SOUND_MASK_VOLUME)
-        if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_VOLUME, &s->sample_spec, &s->virtual_volume) >= 0)
+        if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_VOLUME, &s->sample_spec, &s->real_volume) >= 0)
             return;
 
     if (u->mixer_devmask & SOUND_MASK_PCM)
-        if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_PCM, &s->sample_spec, &s->virtual_volume) >= 0)
+        if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_PCM, &s->sample_spec, &s->real_volume) >= 0)
             return;
 
     pa_log_info("Device doesn't support reading mixer settings: %s", pa_cstrerror(errno));
@@ -830,11 +830,11 @@ static void sink_set_volume(pa_sink *s) {
     pa_assert(u->mixer_devmask & (SOUND_MASK_VOLUME|SOUND_MASK_PCM));
 
     if (u->mixer_devmask & SOUND_MASK_VOLUME)
-        if (pa_oss_set_volume(u->mixer_fd, SOUND_MIXER_WRITE_VOLUME, &s->sample_spec, &s->virtual_volume) >= 0)
+        if (pa_oss_set_volume(u->mixer_fd, SOUND_MIXER_WRITE_VOLUME, &s->sample_spec, &s->real_volume) >= 0)
             return;
 
     if (u->mixer_devmask & SOUND_MASK_PCM)
-        if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_WRITE_PCM, &s->sample_spec, &s->virtual_volume) >= 0)
+        if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_WRITE_PCM, &s->sample_spec, &s->real_volume) >= 0)
             return;
 
     pa_log_info("Device doesn't support writing mixer settings: %s", pa_cstrerror(errno));
diff --git a/src/modules/raop/module-raop-sink.c b/src/modules/raop/module-raop-sink.c
index 9699132..ac48ab1 100644
--- a/src/modules/raop/module-raop-sink.c
+++ b/src/modules/raop/module-raop-sink.c
@@ -283,15 +283,15 @@ static void sink_set_volume_cb(pa_sink *s) {
     /* Calculate the max volume of all channels.
        We'll use this as our (single) volume on the APEX device and emulate
        any variation in channel volumes in software */
-    v = pa_cvolume_max(&s->virtual_volume);
+    v = pa_cvolume_max(&s->real_volume);
 
     /* Create a pa_cvolume version of our single value */
     pa_cvolume_set(&hw, s->sample_spec.channels, v);
 
     /* Perform any software manipulation of the volume needed */
-    pa_sw_cvolume_divide(&s->soft_volume, &s->virtual_volume, &hw);
+    pa_sw_cvolume_divide(&s->soft_volume, &s->real_volume, &hw);
 
-    pa_log_debug("Requested volume: %s", pa_cvolume_snprint(t, sizeof(t), &s->virtual_volume));
+    pa_log_debug("Requested volume: %s", pa_cvolume_snprint(t, sizeof(t), &s->real_volume));
     pa_log_debug("Got hardware volume: %s", pa_cvolume_snprint(t, sizeof(t), &hw));
     pa_log_debug("Calculated software volume: %s", pa_cvolume_snprint(t, sizeof(t), &s->soft_volume));
 
diff --git a/src/pulsecore/cli-command.c b/src/pulsecore/cli-command.c
index e2c3c06..6ec7464 100644
--- a/src/pulsecore/cli-command.c
+++ b/src/pulsecore/cli-command.c
@@ -530,7 +530,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, TRUE, TRUE, TRUE, TRUE);
+    pa_sink_set_volume(sink, &cvolume, TRUE, TRUE);
     return 0;
 }
 
@@ -1586,7 +1586,7 @@ static int pa_cli_command_dump(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_b
             nl = 1;
         }
 
-        pa_strbuf_printf(buf, "set-sink-volume %s 0x%03x\n", sink->name, pa_cvolume_avg(pa_sink_get_volume(sink, FALSE, TRUE)));
+        pa_strbuf_printf(buf, "set-sink-volume %s 0x%03x\n", sink->name, pa_cvolume_avg(pa_sink_get_volume(sink, FALSE)));
         pa_strbuf_printf(buf, "set-sink-mute %s %s\n", sink->name, pa_yes_no(pa_sink_get_mute(sink, FALSE)));
         pa_strbuf_printf(buf, "suspend-sink %s %s\n", sink->name, pa_yes_no(pa_sink_get_state(sink) == PA_SINK_SUSPENDED));
     }
diff --git a/src/pulsecore/cli-text.c b/src/pulsecore/cli-text.c
index a553099..c7a178d 100644
--- a/src/pulsecore/cli-text.c
+++ b/src/pulsecore/cli-text.c
@@ -262,10 +262,10 @@ char *pa_sink_list_to_string(pa_core *c) {
             sink->suspend_cause & PA_SUSPEND_APPLICATION ? "APPLICATION " : "",
             sink->suspend_cause & PA_SUSPEND_IDLE ? "IDLE " : "",
             sink->suspend_cause & PA_SUSPEND_SESSION ? "SESSION" : "",
-            pa_cvolume_snprint(cv, sizeof(cv), pa_sink_get_volume(sink, FALSE, FALSE)),
+            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, FALSE)) : "",
-            pa_cvolume_get_balance(pa_sink_get_volume(sink, FALSE, FALSE), &sink->channel_map),
+            sink->flags & PA_SINK_DECIBEL_VOLUME ? pa_sw_cvolume_snprint_dB(cvdb, sizeof(cvdb), 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) : "",
diff --git a/src/pulsecore/core.h b/src/pulsecore/core.h
index e7abd61..f6ec712 100644
--- a/src/pulsecore/core.h
+++ b/src/pulsecore/core.h
@@ -83,7 +83,6 @@ typedef enum pa_core_hook {
     PA_CORE_HOOK_SINK_INPUT_MOVE_FAIL,
     PA_CORE_HOOK_SINK_INPUT_STATE_CHANGED,
     PA_CORE_HOOK_SINK_INPUT_PROPLIST_CHANGED,
-    PA_CORE_HOOK_SINK_INPUT_SET_VOLUME,
     PA_CORE_HOOK_SINK_INPUT_SEND_EVENT,
     PA_CORE_HOOK_SOURCE_OUTPUT_NEW,
     PA_CORE_HOOK_SOURCE_OUTPUT_FIXATE,
diff --git a/src/pulsecore/protocol-native.c b/src/pulsecore/protocol-native.c
index 280707e..b1285e1 100644
--- a/src/pulsecore/protocol-native.c
+++ b/src/pulsecore/protocol-native.c
@@ -2840,7 +2840,7 @@ static void sink_fill_tagstruct(pa_native_connection *c, pa_tagstruct *t, pa_sin
         PA_TAG_SAMPLE_SPEC, &fixed_ss,
         PA_TAG_CHANNEL_MAP, &sink->channel_map,
         PA_TAG_U32, sink->module ? sink->module->index : PA_INVALID_INDEX,
-        PA_TAG_CVOLUME, pa_sink_get_volume(sink, FALSE, FALSE),
+        PA_TAG_CVOLUME, pa_sink_get_volume(sink, FALSE),
         PA_TAG_BOOLEAN, pa_sink_get_mute(sink, FALSE),
         PA_TAG_U32, sink->monitor_source ? sink->monitor_source->index : PA_INVALID_INDEX,
         PA_TAG_STRING, sink->monitor_source ? sink->monitor_source->name : NULL,
@@ -3388,7 +3388,7 @@ static void command_set_volume(
 
     if (sink) {
         pa_log_debug("Client %s changes volume of sink %s.", client_name, sink->name);
-        pa_sink_set_volume(sink, &volume, TRUE, TRUE, TRUE, TRUE);
+        pa_sink_set_volume(sink, &volume, TRUE, TRUE);
     } else if (source) {
         pa_log_debug("Client %s changes volume of sink %s.", client_name, source->name);
         pa_source_set_volume(source, &volume, TRUE);
diff --git a/src/pulsecore/sink-input.c b/src/pulsecore/sink-input.c
index f6d9ac7..a29334f 100644
--- a/src/pulsecore/sink-input.c
+++ b/src/pulsecore/sink-input.c
@@ -47,6 +47,7 @@
 static PA_DEFINE_CHECK_TYPE(pa_sink_input, pa_msgobject);
 
 static void sink_input_free(pa_object *o);
+static void set_real_ratio(pa_sink_input *i, const pa_cvolume *v);
 
 pa_sink_input_new_data* pa_sink_input_new_data_init(pa_sink_input_new_data *data) {
     pa_assert(data);
@@ -270,18 +271,20 @@ int pa_sink_input_new(
     i->channel_map = data->channel_map;
 
     if ((i->sink->flags & PA_SINK_FLAT_VOLUME) && !data->volume_is_absolute) {
+        pa_cvolume remapped;
+
         /* When the 'absolute' bool is not set then we'll treat the volume
          * as relative to the sink volume even in flat volume mode */
-
-        pa_cvolume v = data->sink->reference_volume;
-        pa_cvolume_remap(&v, &data->sink->channel_map, &data->channel_map);
-        pa_sw_cvolume_multiply(&i->virtual_volume, &data->volume, &v);
+        remapped = data->sink->reference_volume;
+        pa_cvolume_remap(&remapped, &data->sink->channel_map, &data->channel_map);
+        pa_sw_cvolume_multiply(&i->volume, &data->volume, &remapped);
     } else
-        i->virtual_volume = data->volume;
+        i->volume = data->volume;
 
     i->volume_factor = data->volume_factor;
-    pa_cvolume_init(&i->soft_volume);
-    memset(i->relative_volume, 0, sizeof(i->relative_volume));
+    i->real_ratio = i->reference_ratio = data->volume;
+    pa_cvolume_reset(&i->soft_volume, i->sample_spec.channels);
+    pa_cvolume_reset(&i->real_ratio, i->sample_spec.channels);
     i->save_volume = data->save_volume;
     i->save_sink = data->save_sink;
     i->save_muted = data->save_muted;
@@ -445,11 +448,8 @@ void pa_sink_input_unlink(pa_sink_input *i) {
 
     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, FALSE, FALSE);
-        }
+        if (i->sink->flags & PA_SINK_FLAT_VOLUME)
+            pa_sink_set_volume(i->sink, NULL, 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);
@@ -526,12 +526,10 @@ void pa_sink_input_put(pa_sink_input *i) {
     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, FALSE, FALSE);
-    } else
-        pa_sink_input_set_relative_volume(i, &i->virtual_volume);
+    if (i->sink->flags & PA_SINK_FLAT_VOLUME)
+        pa_sink_set_volume(i->sink, NULL, FALSE, i->save_volume);
+    else
+        set_real_ratio(i, &i->volume);
 
     i->thread_info.soft_volume = i->soft_volume;
     i->thread_info.muted = i->muted;
@@ -910,6 +908,27 @@ pa_usec_t pa_sink_input_get_requested_latency(pa_sink_input *i) {
 }
 
 /* Called from main context */
+static void set_real_ratio(pa_sink_input *i, const pa_cvolume *v) {
+    pa_sink_input_assert_ref(i);
+    pa_assert_ctl_context();
+    pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
+    pa_assert(!v || pa_cvolume_compatible(v, &i->sample_spec));
+
+    /* This basically calculates:
+     *
+     * i->real_ratio := v
+     * i->soft_volume := i->real_ratio * i->volume_factor */
+
+    if (v)
+        i->real_ratio = *v;
+    else
+        pa_cvolume_reset(&i->real_ratio, i->sample_spec.channels);
+
+    pa_sw_cvolume_multiply(&i->soft_volume, &i->real_ratio, &i->volume_factor);
+    /* We don't copy the data to the thread_info data. That's left for someone else to do */
+}
+
+/* Called from main context */
 void pa_sink_input_set_volume(pa_sink_input *i, const pa_cvolume *volume, pa_bool_t save, pa_bool_t absolute) {
     pa_cvolume v;
 
@@ -926,29 +945,24 @@ void pa_sink_input_set_volume(pa_sink_input *i, const pa_cvolume *volume, pa_boo
         volume = pa_sw_cvolume_multiply(&v, &v, volume);
     }
 
-    if (pa_cvolume_equal(volume, &i->virtual_volume))
+    if (pa_cvolume_equal(volume, &i->volume)) {
+        i->save_volume = i->save_volume || save;
         return;
+    }
 
-    i->virtual_volume = *volume;
+    i->volume = *volume;
     i->save_volume = save;
 
-    if (i->sink->flags & PA_SINK_FLAT_VOLUME) {
-        pa_cvolume new_volume;
-
+    if (i->sink->flags & PA_SINK_FLAT_VOLUME)
         /* We are in flat volume mode, so let's update all sink input
          * volumes and update the flat volume of the sink */
 
-        pa_sink_update_flat_volume(i->sink, &new_volume);
-        pa_sink_set_volume(i->sink, &new_volume, FALSE, TRUE, FALSE, FALSE);
-
-    } else {
+        pa_sink_set_volume(i->sink, NULL, TRUE, save);
 
+    else {
         /* OK, we are in normal volume mode. The volume only affects
          * ourselves */
-        pa_sink_input_set_relative_volume(i, 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);
+        set_real_ratio(i, volume);
 
         /* Copy the new soft_volume to the thread_info struct */
         pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_SET_SOFT_VOLUME, NULL, 0, NULL) == 0);
@@ -964,68 +978,15 @@ pa_cvolume *pa_sink_input_get_volume(pa_sink_input *i, pa_cvolume *volume, pa_bo
     pa_assert_ctl_context();
     pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
 
-    if ((i->sink->flags & PA_SINK_FLAT_VOLUME) && !absolute) {
-        pa_cvolume v = i->sink->reference_volume;
-        pa_cvolume_remap(&v, &i->sink->channel_map, &i->channel_map);
-        pa_sw_cvolume_divide(volume, &i->virtual_volume, &v);
-    } else
-        *volume = i->virtual_volume;
+    if (absolute || !(i->sink->flags & PA_SINK_FLAT_VOLUME))
+        *volume = i->volume;
+    else
+        *volume = i->reference_ratio;
 
     return volume;
 }
 
 /* Called from main context */
-pa_cvolume *pa_sink_input_get_relative_volume(pa_sink_input *i, pa_cvolume *v) {
-    unsigned c;
-
-    pa_sink_input_assert_ref(i);
-    pa_assert_ctl_context();
-    pa_assert(v);
-    pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
-
-    /* This always returns the relative volume. Converts the float
-     * version into a pa_cvolume */
-
-    v->channels = i->sample_spec.channels;
-
-    for (c = 0; c < v->channels; c++)
-        v->values[c] = pa_sw_volume_from_linear(i->relative_volume[c]);
-
-    return v;
-}
-
-/* Called from main context */
-void pa_sink_input_set_relative_volume(pa_sink_input *i, const pa_cvolume *v) {
-    unsigned c;
-    pa_cvolume _v;
-
-    pa_sink_input_assert_ref(i);
-    pa_assert_ctl_context();
-    pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
-    pa_assert(!v || pa_cvolume_compatible(v, &i->sample_spec));
-
-    if (!v)
-        v = pa_cvolume_reset(&_v, i->sample_spec.channels);
-
-    /* This basically calculates:
-     *
-     * i->relative_volume := v
-     * i->soft_volume := i->relative_volume * i->volume_factor */
-
-    i->soft_volume.channels = i->sample_spec.channels;
-
-    for (c = 0; c < i->sample_spec.channels; c++) {
-        i->relative_volume[c] = pa_sw_volume_to_linear(v->values[c]);
-
-        i->soft_volume.values[c] = pa_sw_volume_from_linear(
-                i->relative_volume[c] *
-                pa_sw_volume_to_linear(i->volume_factor.values[c]));
-    }
-
-    /* We don't copy the data to the thread_info data. That's left for someone else to do */
-}
-
-/* Called from main context */
 void pa_sink_input_set_mute(pa_sink_input *i, pa_bool_t mute, pa_bool_t save) {
     pa_sink_input_assert_ref(i);
     pa_assert_ctl_context();
@@ -1198,20 +1159,10 @@ int pa_sink_input_start_move(pa_sink_input *i) {
     if (pa_sink_input_get_state(i) == PA_SINK_INPUT_CORKED)
         pa_assert_se(i->sink->n_corked-- >= 1);
 
-    if (i->sink->flags & PA_SINK_FLAT_VOLUME) {
-        pa_cvolume new_volume;
-
-        /* Make the virtual volume relative */
-        pa_sink_input_get_relative_volume(i, &i->virtual_volume);
-
-        /* And reset the the relative volume */
-        pa_sink_input_set_relative_volume(i, NULL);
-
+    if (i->sink->flags & PA_SINK_FLAT_VOLUME)
         /* We might need to update the sink's volume if we are in flat
          * volume mode. */
-        pa_sink_update_flat_volume(i->sink, &new_volume);
-        pa_sink_set_volume(i->sink, &new_volume, FALSE, FALSE, FALSE, FALSE);
-    }
+        pa_sink_set_volume(i->sink, NULL, FALSE, FALSE);
 
     pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i->sink), PA_SINK_MESSAGE_START_MOVE, i, 0, NULL) == 0);
 
@@ -1295,16 +1246,15 @@ int pa_sink_input_finish_move(pa_sink_input *i, pa_sink *dest, pa_bool_t save) {
     pa_sink_update_status(dest);
 
     if (i->sink->flags & PA_SINK_FLAT_VOLUME) {
-        pa_cvolume new_volume;
+        pa_cvolume remapped;
 
-        /* Make relative volume absolute again */
-        pa_cvolume t = dest->reference_volume;
-        pa_cvolume_remap(&t, &dest->channel_map, &i->channel_map);
-        pa_sw_cvolume_multiply(&i->virtual_volume, &i->virtual_volume, &t);
+        /* Make relative volumes absolute */
+        remapped = dest->reference_volume;
+        pa_cvolume_remap(&remapped, &dest->channel_map, &i->channel_map);
+        pa_sw_cvolume_multiply(&i->volume, &i->reference_ratio, &remapped);
 
         /* We might need to update the sink's volume if we are in flat volume mode. */
-        pa_sink_update_flat_volume(i->sink, &new_volume);
-        pa_sink_set_volume(i->sink, &new_volume, FALSE, FALSE, FALSE, FALSE);
+        pa_sink_set_volume(i->sink, NULL, FALSE, i->save_volume);
     }
 
     pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i->sink), PA_SINK_MESSAGE_FINISH_MOVE, i, 0, NULL) == 0);
diff --git a/src/pulsecore/sink-input.h b/src/pulsecore/sink-input.h
index b5502c4..ea0f8c0 100644
--- a/src/pulsecore/sink-input.h
+++ b/src/pulsecore/sink-input.h
@@ -94,10 +94,12 @@ struct pa_sink_input {
     pa_sink_input *sync_prev, *sync_next;
 
     /* Also see http://pulseaudio.org/wiki/InternalVolumes */
-    pa_cvolume virtual_volume;  /* The volume clients are informed about */
-    pa_cvolume volume_factor;   /* An internally used volume factor that can be used by modules to apply effects and suchlike without having that visible to the outside */
-    double relative_volume[PA_CHANNELS_MAX]; /* The calculated volume relative to the sink volume as linear factors. */
-    pa_cvolume soft_volume;     /* The internal software volume we apply to all PCM data while it passes through. Usually calculated as relative_volume * volume_factor  */
+    pa_cvolume volume;             /* The volume clients are informed about */
+    pa_cvolume reference_ratio;    /* The ratio of the stream's volume to the sink's reference volume */
+    pa_cvolume real_ratio;         /* The ratio of the stream's volume to the sink's real volume */
+    pa_cvolume volume_factor;      /* An internally used volume factor that can be used by modules to apply effects and suchlike without having that visible to the outside */
+    pa_cvolume soft_volume;        /* The internal software volume we apply to all PCM data while it passes through. Usually calculated as real_ratio * volume_factor */
+
     pa_bool_t muted:1;
 
     /* if TRUE then the source we are connected to and/or the volume
@@ -325,8 +327,6 @@ pa_usec_t pa_sink_input_get_latency(pa_sink_input *i, pa_usec_t *sink_latency);
 void pa_sink_input_set_volume(pa_sink_input *i, const pa_cvolume *volume, pa_bool_t save, pa_bool_t absolute);
 pa_cvolume *pa_sink_input_get_volume(pa_sink_input *i, pa_cvolume *volume, pa_bool_t absolute);
 
-pa_cvolume *pa_sink_input_get_relative_volume(pa_sink_input *i, pa_cvolume *v);
-
 void pa_sink_input_set_mute(pa_sink_input *i, pa_bool_t mute, pa_bool_t save);
 pa_bool_t pa_sink_input_get_mute(pa_sink_input *i);
 
@@ -369,9 +369,6 @@ pa_bool_t pa_sink_input_safe_to_remove(pa_sink_input *i);
 
 pa_memchunk* pa_sink_input_get_silence(pa_sink_input *i, pa_memchunk *ret);
 
-/* To be used by sink.c only */
-void pa_sink_input_set_relative_volume(pa_sink_input *i, const pa_cvolume *v);
-
 #define pa_sink_input_assert_io_context(s) \
     pa_assert(pa_thread_mq_get() || !PA_SINK_INPUT_IS_LINKED((s)->state))
 
diff --git a/src/pulsecore/sink.c b/src/pulsecore/sink.c
index 717584f..1cce8e6 100644
--- a/src/pulsecore/sink.c
+++ b/src/pulsecore/sink.c
@@ -212,7 +212,7 @@ pa_sink* pa_sink_new(
         pa_cvolume_reset(&data->volume, data->sample_spec.channels);
 
     pa_return_null_if_fail(pa_cvolume_valid(&data->volume));
-    pa_return_null_if_fail(data->volume.channels == data->sample_spec.channels);
+    pa_return_null_if_fail(pa_cvolume_compatible(&data->volume, &data->sample_spec));
 
     if (!data->muted_is_set)
         data->muted = FALSE;
@@ -249,7 +249,7 @@ pa_sink* pa_sink_new(
     s->inputs = pa_idxset_new(NULL, NULL);
     s->n_corked = 0;
 
-    s->reference_volume = s->virtual_volume = data->volume;
+    s->reference_volume = s->real_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;
@@ -434,6 +434,11 @@ void pa_sink_put(pa_sink* s) {
     if ((s->flags & PA_SINK_DECIBEL_VOLUME) && s->core->flat_volumes)
         s->flags |= PA_SINK_FLAT_VOLUME;
 
+    /* We assume that if the sink implementor changed the default
+     * volume he did so in real_volume, because that is the usual
+     * place where he is supposed to place his changes.  */
+    s->reference_volume = s->real_volume;
+
     s->thread_info.soft_volume = s->soft_volume;
     s->thread_info.soft_muted = s->muted;
 
@@ -1212,105 +1217,144 @@ pa_usec_t pa_sink_get_latency_within_thread(pa_sink *s) {
     return usec;
 }
 
-static void compute_new_soft_volume(pa_sink_input *i, const pa_cvolume *new_volume) {
-    unsigned c;
+/* Called from main context */
+static void compute_reference_ratios(pa_sink *s) {
+    uint32_t idx;
+    pa_sink_input *i;
+
+    pa_sink_assert_ref(s);
+    pa_assert_ctl_context();
+    pa_assert(PA_SINK_IS_LINKED(s->state));
+    pa_assert(s->flags & PA_SINK_FLAT_VOLUME);
 
-    pa_sink_input_assert_ref(i);
-    pa_assert(new_volume->channels == i->sample_spec.channels);
+    PA_IDXSET_FOREACH(i, s->inputs, idx) {
+        unsigned c;
+        pa_cvolume remapped;
 
-    /*
-     * This basically calculates:
-     *
-     * i->relative_volume := i->virtual_volume / new_volume
-     * i->soft_volume := i->relative_volume * i->volume_factor
-     */
+        /*
+         * Calculates the reference volume from the sink's reference
+         * volume. This basically calculates:
+         *
+         * i->reference_ratio = i->volume / s->reference_volume
+         */
 
-    /* The new sink volume passed in here must already be remapped to
-     * the sink input's channel map! */
+        remapped = s->reference_volume;
+        pa_cvolume_remap(&remapped, &s->channel_map, &i->channel_map);
 
-    i->soft_volume.channels = i->sample_spec.channels;
+        i->reference_ratio.channels = i->sample_spec.channels;
 
-    for (c = 0; c < i->sample_spec.channels; c++)
+        for (c = 0; c < i->sample_spec.channels; c++) {
 
-        if (new_volume->values[c] <= PA_VOLUME_MUTED)
-            /* We leave i->relative_volume untouched */
-            i->soft_volume.values[c] = PA_VOLUME_MUTED;
-        else {
-            i->relative_volume[c] =
-                pa_sw_volume_to_linear(i->virtual_volume.values[c]) /
-                pa_sw_volume_to_linear(new_volume->values[c]);
+            /* We don't update when the sink volume is 0 anyway */
+            if (remapped.values[c] <= PA_VOLUME_MUTED)
+                continue;
 
-            i->soft_volume.values[c] = pa_sw_volume_from_linear(
-                    i->relative_volume[c] *
-                    pa_sw_volume_to_linear(i->volume_factor.values[c]));
+            /* Don't update the reference ratio unless necessary */
+            if (pa_sw_volume_multiply(
+                        i->reference_ratio.values[c],
+                        remapped.values[c]) == i->volume.values[c])
+                continue;
+
+            i->reference_ratio.values[c] = pa_sw_volume_divide(
+                    i->volume.values[c],
+                    remapped.values[c]);
         }
+    }
+}
+
+/* Called from main context */
+static void compute_real_ratios(pa_sink *s) {
+    pa_sink_input *i;
+    uint32_t idx;
 
-    /* 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_sink_assert_ref(s);
+    pa_assert_ctl_context();
+    pa_assert(PA_SINK_IS_LINKED(s->state));
+    pa_assert(s->flags & PA_SINK_FLAT_VOLUME);
 
-    /* We don't copy the soft_volume to the thread_info data
-     * here. That must be done by the caller */
+    PA_IDXSET_FOREACH(i, s->inputs, idx) {
+        unsigned c;
+        pa_cvolume remapped;
+
+        /*
+         * This basically calculates:
+         *
+         * i->real_ratio := i->volume / s->real_volume
+         * i->soft_volume := i->real_ratio * i->volume_factor
+         */
+
+        remapped = s->real_volume;
+        pa_cvolume_remap(&remapped, &s->channel_map, &i->channel_map);
+
+        i->real_ratio.channels = i->sample_spec.channels;
+        i->soft_volume.channels = i->sample_spec.channels;
+
+        for (c = 0; c < i->sample_spec.channels; c++) {
+
+            if (remapped.values[c] <= PA_VOLUME_MUTED) {
+                /* We leave i->real_ratio untouched */
+                i->soft_volume.values[c] = PA_VOLUME_MUTED;
+                continue;
+            }
+
+            /* Don't lose accuracy unless necessary */
+            if (pa_sw_volume_multiply(
+                        i->real_ratio.values[c],
+                        remapped.values[c]) != i->volume.values[c])
+
+                i->real_ratio.values[c] = pa_sw_volume_divide(
+                        i->volume.values[c],
+                        remapped.values[c]);
+
+            i->soft_volume.values[c] = pa_sw_volume_multiply(
+                    i->real_ratio.values[c],
+                    i->volume_factor.values[c]);
+        }
+
+        /* We don't copy the soft_volume to the thread_info data
+         * here. That must be done by the caller */
+    }
 }
 
 /* Called from main thread */
-void pa_sink_update_flat_volume(pa_sink *s, pa_cvolume *new_volume) {
+static void compute_real_volume(pa_sink *s) {
     pa_sink_input *i;
     uint32_t idx;
 
     pa_sink_assert_ref(s);
     pa_assert_ctl_context();
-    pa_assert(new_volume);
     pa_assert(PA_SINK_IS_LINKED(s->state));
     pa_assert(s->flags & PA_SINK_FLAT_VOLUME);
 
-    /* This is called whenever a sink input volume changes or a sink
-     * input is added/removed 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_volume().*/
+    /* This determines the maximum volume of all streams and sets
+     * s->real_volume accordingly. */
 
     if (pa_idxset_isempty(s->inputs)) {
         /* In the special case that we have no sink input we leave the
          * volume unmodified. */
-        *new_volume = s->reference_volume;
+        s->real_volume = s->reference_volume;
         return;
     }
 
-    pa_cvolume_mute(new_volume, s->channel_map.channels);
+    pa_cvolume_mute(&s->real_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);
+    PA_IDXSET_FOREACH(i, s->inputs, idx) {
+        pa_cvolume remapped;
 
-        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];
+        remapped = i->volume;
+        pa_cvolume_remap(&remapped, &i->channel_map, &s->channel_map);
+        pa_cvolume_merge(&s->real_volume, &s->real_volume, &remapped);
     }
 
-    /* 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);
-        compute_new_soft_volume(i, &remapped_new_volume);
-
-        /* We don't copy soft_volume to the thread_info data here
-         * (i.e. issue PA_SINK_INPUT_MESSAGE_SET_VOLUME) because we
-         * want the update to be atomically with the sink volume
-         * update, hence we do it within the pa_sink_set_volume() call
-         * below */
-    }
+    /* Then, let's update the real ratios/soft volumes of all inputs
+     * connected to this sink */
+    compute_real_ratios(s);
 }
 
 /* Called from main thread */
-void pa_sink_propagate_flat_volume(pa_sink *s) {
+static void propagate_reference_volume(pa_sink *s) {
     pa_sink_input *i;
     uint32_t idx;
 
@@ -1323,64 +1367,77 @@ void pa_sink_propagate_flat_volume(pa_sink *s) {
      * 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 sink_volume, new_virtual_volume;
-        unsigned c;
-
-        /* This basically calculates i->virtual_volume := i->relative_volume * s->virtual_volume  */
-
-        sink_volume = s->virtual_volume;
-        pa_cvolume_remap(&sink_volume, &s->channel_map, &i->channel_map);
-
-        for (c = 0; c < i->sample_spec.channels; c++)
-            new_virtual_volume.values[c] = pa_sw_volume_from_linear(
-                    i->relative_volume[c] *
-                    pa_sw_volume_to_linear(sink_volume.values[c]));
+    PA_IDXSET_FOREACH(i, s->inputs, idx) {
+        pa_cvolume old_volume, remapped;
 
-        new_virtual_volume.channels = i->sample_spec.channels;
+        old_volume = i->volume;
 
-        if (!pa_cvolume_equal(&new_virtual_volume, &i->virtual_volume)) {
-            i->virtual_volume = new_virtual_volume;
+        /* This basically calculates:
+         *
+         * i->volume := s->reference_volume * i->reference_ratio  */
 
-            /* Hmm, the soft volume might no longer actually match
-             * what has been chosen as new virtual volume here,
-             * especially when the old volume was
-             * PA_VOLUME_MUTED. Hence let's recalculate the soft
-             * volumes here. */
-            compute_new_soft_volume(i, &sink_volume);
+        remapped = s->reference_volume;
+        pa_cvolume_remap(&remapped, &s->channel_map, &i->channel_map);
+        pa_sw_cvolume_multiply(&i->volume, &remapped, &i->reference_ratio);
 
-            /* The virtual volume changed, let's tell people so */
+        /* The reference volume changed, let's tell people so */
+        if (!pa_cvolume_equal(&old_volume, &i->volume))
             pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index);
-        }
     }
-
-    /* If the soft_volume of any of the sink inputs got changed, let's
-     * make sure the thread copies are synced up. */
-    pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SYNC_VOLUMES, NULL, 0, NULL) == 0);
 }
 
 /* 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_bool_t become_reference, pa_bool_t save) {
-    pa_bool_t virtual_volume_changed;
+void pa_sink_set_volume(
+        pa_sink *s,
+        const pa_cvolume *volume,
+        pa_bool_t sendmsg,
+        pa_bool_t save) {
+
+    pa_cvolume old_reference_volume;
+    pa_bool_t reference_changed;
 
     pa_sink_assert_ref(s);
     pa_assert_ctl_context();
     pa_assert(PA_SINK_IS_LINKED(s->state));
-    pa_assert(volume);
-    pa_assert(pa_cvolume_valid(volume));
-    pa_assert(pa_cvolume_compatible(volume, &s->sample_spec));
+    pa_assert(!volume || pa_cvolume_valid(volume));
+    pa_assert(!volume || pa_cvolume_compatible(volume, &s->sample_spec));
+    pa_assert(volume || (s->flags & PA_SINK_FLAT_VOLUME));
+
+    /* If volume is NULL we synchronize the sink's real and reference
+     * volumes with the stream volumes. If it is not NULL we update
+     * the reference_volume with it. */
+
+    old_reference_volume = s->reference_volume;
+
+    if (volume) {
+
+        s->reference_volume = *volume;
+
+        if (s->flags & PA_SINK_FLAT_VOLUME) {
+            /* OK, propagate this volume change back to the inputs */
+            propagate_reference_volume(s);
+
+            /* And now recalculate the real volume */
+            compute_real_volume(s);
+        } else
+            s->real_volume = s->reference_volume;
+
+    } else {
+        pa_assert(s->flags & PA_SINK_FLAT_VOLUME);
+
+        /* Ok, let's determine the new real volume */
+        compute_real_volume(s);
 
-    virtual_volume_changed = !pa_cvolume_equal(volume, &s->virtual_volume);
-    s->virtual_volume = *volume;
-    s->save_volume = (!virtual_volume_changed && s->save_volume) || save;
+        /* Let's 'push' the reference volume if necessary */
+        pa_cvolume_merge(&s->reference_volume, &s->reference_volume, &s->real_volume);
 
-    if (become_reference)
-        s->reference_volume = s->virtual_volume;
+        /* We need to fix the reference ratios of all streams now that
+         * we changed the reference volume */
+        compute_reference_ratios(s);
+    }
 
-    /* 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);
+    reference_changed = !pa_cvolume_equal(&old_reference_volume, &s->reference_volume);
+    s->save_volume = (!reference_changed && s->save_volume) || save;
 
     if (s->set_volume) {
         /* If we have a function set_volume(), then we do not apply a
@@ -1393,13 +1450,13 @@ void pa_sink_set_volume(pa_sink *s, const pa_cvolume *volume, pa_bool_t propagat
     } else
         /* If we have no function set_volume(), then the soft volume
          * becomes the virtual volume */
-        s->soft_volume = s->virtual_volume;
+        s->soft_volume = s->real_volume;
 
     /* 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 (virtual_volume_changed)
+    if (reference_changed)
         pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
 }
 
@@ -1407,67 +1464,114 @@ void pa_sink_set_volume(pa_sink *s, const pa_cvolume *volume, pa_bool_t propagat
 void pa_sink_set_soft_volume(pa_sink *s, const pa_cvolume *volume) {
     pa_sink_assert_ref(s);
     pa_assert_ctl_context();
-    pa_assert(volume);
 
-    s->soft_volume = *volume;
+    if (!volume)
+        pa_cvolume_reset(&s->soft_volume, s->sample_spec.channels);
+    else
+        s->soft_volume = *volume;
 
     if (PA_SINK_IS_LINKED(s->state))
         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;
+        s->thread_info.soft_volume = s->soft_volume;
 }
 
-/* Called from main thread */
-const pa_cvolume *pa_sink_get_volume(pa_sink *s, pa_bool_t force_refresh, pa_bool_t reference) {
+static void propagate_real_volume(pa_sink *s, const pa_cvolume *old_real_volume) {
+    pa_sink_input *i;
+    uint32_t idx;
+    pa_cvolume old_reference_volume;
+
     pa_sink_assert_ref(s);
     pa_assert_ctl_context();
     pa_assert(PA_SINK_IS_LINKED(s->state));
 
-    if (s->refresh_volume || force_refresh) {
-        struct pa_cvolume old_virtual_volume = s->virtual_volume;
+    /* This is called when the hardware's real volume changes due to
+     * some external event. We copy the real volume into our
+     * reference volume and then rebuild the stream volumes based on
+     * i->real_ratio which should stay fixed. */
 
-        if (s->get_volume)
-            s->get_volume(s);
+    if (pa_cvolume_equal(old_real_volume, &s->real_volume))
+        return;
 
-        pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_VOLUME, NULL, 0, NULL) == 0);
+    old_reference_volume = s->reference_volume;
 
-        if (!pa_cvolume_equal(&old_virtual_volume, &s->virtual_volume)) {
+    /* 1. Make the real volume the reference volume */
+    s->reference_volume = s->real_volume;
 
-            s->reference_volume = s->virtual_volume;
+    if (s->flags & PA_SINK_FLAT_VOLUME) {
 
-            /* Something got changed in the hardware. It probably
-             * makes sense to save changed hw settings given that hw
-             * volume changes not triggered by PA are almost certainly
-             * done by the user. */
-            s->save_volume = TRUE;
+        PA_IDXSET_FOREACH(i, s->inputs, idx) {
+            pa_cvolume old_volume, remapped;
 
-            if (s->flags & PA_SINK_FLAT_VOLUME)
-                pa_sink_propagate_flat_volume(s);
+            old_volume = i->volume;
 
-            pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+            /* 2. Since the sink's reference and real volumes are equal
+             * now our ratios should be too. */
+            i->reference_ratio = i->real_ratio;
+
+            /* 3. Recalculate the new stream reference volume based on the
+             * reference ratio and the sink's reference volume.
+             *
+             * This basically calculates:
+             *
+             * i->volume = s->reference_volume * i->reference_ratio
+             *
+             * This is identical to propagate_reference_volume() */
+            remapped = s->reference_volume;
+            pa_cvolume_remap(&remapped, &s->channel_map, &i->channel_map);
+            pa_sw_cvolume_multiply(&i->volume, &remapped, &i->reference_ratio);
+
+            /* Notify if something changed */
+            if (!pa_cvolume_equal(&old_volume, &i->volume))
+                pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index);
         }
     }
 
-    return reference ? &s->reference_volume : &s->virtual_volume;
+    /* Something got changed in the hardware. It probably makes sense
+     * to save changed hw settings given that hw volume changes not
+     * triggered by PA are almost certainly done by the user. */
+    s->save_volume = TRUE;
+
+    if (!pa_cvolume_equal(&old_reference_volume, &s->reference_volume))
+        pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
 }
 
 /* Called from main thread */
-void pa_sink_volume_changed(pa_sink *s, const pa_cvolume *new_volume) {
+const pa_cvolume *pa_sink_get_volume(pa_sink *s, pa_bool_t force_refresh) {
     pa_sink_assert_ref(s);
     pa_assert_ctl_context();
     pa_assert(PA_SINK_IS_LINKED(s->state));
 
-    /* The sink implementor may call this if the volume changed to make sure everyone is notified */
-    if (pa_cvolume_equal(&s->virtual_volume, new_volume))
-        return;
+    if (s->refresh_volume || force_refresh) {
+        struct pa_cvolume old_real_volume;
 
-    s->reference_volume = s->virtual_volume = *new_volume;
-    s->save_volume = TRUE;
+        old_real_volume = s->real_volume;
 
-    if (s->flags & PA_SINK_FLAT_VOLUME)
-        pa_sink_propagate_flat_volume(s);
+        if (s->get_volume)
+            s->get_volume(s);
 
-    pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+        pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_VOLUME, NULL, 0, NULL) == 0);
+
+        propagate_real_volume(s, &old_real_volume);
+    }
+
+    return &s->reference_volume;
+}
+
+/* Called from main thread */
+void pa_sink_volume_changed(pa_sink *s, const pa_cvolume *new_real_volume) {
+    pa_cvolume old_real_volume;
+
+    pa_sink_assert_ref(s);
+    pa_assert_ctl_context();
+    pa_assert(PA_SINK_IS_LINKED(s->state));
+
+    /* The sink implementor may call this if the volume changed to make sure everyone is notified */
+
+    old_real_volume = s->real_volume;
+    s->real_volume = *new_real_volume;
+
+    propagate_real_volume(s, &old_real_volume);
 }
 
 /* Called from main thread */
@@ -1516,7 +1620,6 @@ pa_bool_t pa_sink_get_mute(pa_sink *s, pa_bool_t force_refresh) {
         }
     }
 
-
     return s->muted;
 }
 
diff --git a/src/pulsecore/sink.h b/src/pulsecore/sink.h
index 3cd7e59..936d1c2 100644
--- a/src/pulsecore/sink.h
+++ b/src/pulsecore/sink.h
@@ -90,9 +90,10 @@ struct pa_sink {
     unsigned n_volume_steps; /* shall be constant */
 
     /* Also see http://pulseaudio.org/wiki/InternalVolumes */
-    pa_cvolume virtual_volume;   /* The volume clients are informed about */
-    pa_cvolume reference_volume; /* The volume taken as refernce base for relative sink input volumes */
+    pa_cvolume reference_volume; /* The volume exported and taken as reference base for relative sink input volumes */
+    pa_cvolume real_volume;      /* The volume that the hardware is configured to  */
     pa_cvolume soft_volume;      /* The internal software volume we apply to all PCM data while it passes through */
+
     pa_bool_t muted:1;
 
     pa_bool_t refresh_volume:1;
@@ -303,11 +304,8 @@ int pa_sink_update_status(pa_sink*s);
 int pa_sink_suspend(pa_sink *s, pa_bool_t suspend, pa_suspend_cause_t cause);
 int pa_sink_suspend_all(pa_core *c, pa_bool_t suspend, pa_suspend_cause_t cause);
 
-void pa_sink_update_flat_volume(pa_sink *s, pa_cvolume *new_volume);
-void pa_sink_propagate_flat_volume(pa_sink *s);
-
-void pa_sink_set_volume(pa_sink *sink, const pa_cvolume *volume, pa_bool_t propagate, pa_bool_t sendmsg, pa_bool_t become_reference, pa_bool_t save);
-const pa_cvolume *pa_sink_get_volume(pa_sink *sink, pa_bool_t force_refresh, pa_bool_t reference);
+void pa_sink_set_volume(pa_sink *sink, const pa_cvolume *volume, pa_bool_t sendmsg, pa_bool_t save);
+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 save);
 pa_bool_t pa_sink_get_mute(pa_sink *sink, pa_bool_t force_refresh);

-- 
hooks/post-receive
PulseAudio Sound Server



More information about the pulseaudio-commits mailing list