]> code.delx.au - pulseaudio/commitdiff
sink: volume handling rework, new flat volume logic
authorLennart Poettering <lennart@poettering.net>
Wed, 19 Aug 2009 00:55:02 +0000 (02:55 +0200)
committerLennart Poettering <lennart@poettering.net>
Wed, 19 Aug 2009 00:55:02 +0000 (02:55 +0200)
- 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.

16 files changed:
src/modules/alsa/alsa-sink.c
src/modules/module-device-restore.c
src/modules/module-lirc.c
src/modules/module-match.c
src/modules/module-mmkbd-evdev.c
src/modules/module-tunnel.c
src/modules/oss/module-oss.c
src/modules/raop/module-raop-sink.c
src/pulsecore/cli-command.c
src/pulsecore/cli-text.c
src/pulsecore/core.h
src/pulsecore/protocol-native.c
src/pulsecore/sink-input.c
src/pulsecore/sink-input.h
src/pulsecore/sink.c
src/pulsecore/sink.h

index 12538368aa947c16845d5f6a070ca5ce0a8dff5e..e3707ae7e3d660b52025a9bd93aaea54d1ce0281 100644 (file)
@@ -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);
     }
 
index 120b762c143eb65f1af047204a827c919ef80d02..da6c96661df28af606bfa93f057d8cc575ab7d58 100644 (file)
@@ -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;
         }
 
index 2bb8014da3f9341d65a23ce2c676901c8520bc30..fdfdc797372dabecfdb408889e729d1396c684e2 100644 (file)
@@ -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)
 
index 14e01127f10999cf2e25303e76304481c772c367..0bd781d2b2beae0a441e42b26bc996abf08ea324 100644 (file)
@@ -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);
             }
         }
     }
index b30fae5122fb6c493f99d3c9f7d6f6a7c27750db..7be487000dc89778f2cffac5439ae64a46155446 100644 (file)
@@ -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:
index eaccea4e58f257e81b310d8d2e9210c227e1afe2..5ccb81d0659c2c3cac989d781a222e9641375e94 100644 (file)
@@ -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);
 }
 
index 0848d43b780852e2682495beca409caf9288cdc7..71536260237dcffd1f6a26db511f7d759feba302 100644 (file)
@@ -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));
index 9699132df3981a871b53667a43dc6b1627ebf49d..ac48ab105f7aa0f7e1975916f0fe34e7dcc188f5 100644 (file)
@@ -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));
 
index e2c3c066c3c009a75e8d9883f6805109061b9b01..6ec746475c0f187efa3bf65081aff39df4848a25 100644 (file)
@@ -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));
     }
index a553099180140841f6dc53f954d52dc3818b9ce1..c7a178d6c2ffe2cd6d528ace5664c6ac6a6b37ed 100644 (file)
@@ -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) : "",
index e7abd61b9a357fc48df53b883e2efca7e9167d3a..f6ec7122f86694728d3f80fc993c82686fe64dca 100644 (file)
@@ -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,
index 280707e2c5b2486746315dfa1577df84928bb5b1..b1285e1551f6458835f2611b978d88279bf20511 100644 (file)
@@ -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);
index f6d9ac7364d1efd0cd0ece9e606f488497c9990d..a29334f953bbdb788f901236fd43bc77c0bbab78 100644 (file)
@@ -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;
@@ -909,6 +907,27 @@ pa_usec_t pa_sink_input_get_requested_latency(pa_sink_input *i) {
     return i->thread_info.requested_sink_latency;
 }
 
+/* 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,67 +978,14 @@ 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);
@@ -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);
index b5502c4520f5df9d846a96cd43f8fb2cf1d143a0..ea0f8c0ecf775b7e3f1a06124ec00eeddeecbae8 100644 (file)
@@ -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))
 
index 717584f2ff4f58b37bd926aa3103ba5ab2a9d143..1cce8e6b688295e442ea428f068f4fcce49b01c4 100644 (file)
@@ -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;
 }
 
index 3cd7e59d16732501a93eaa5ad125e0baf9fe11f6..936d1c2a1db4fc97b2bdfd4fb8ad607bb480796e 100644 (file)
@@ -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);