summaryrefslogtreecommitdiffstats
path: root/src/pulsecore/sink.c
diff options
context:
space:
mode:
authorTanu Kaskinen <tanu.kaskinen@digia.com>2011-02-24 16:16:38 +0200
committerColin Guthrie <cguthrie@mandriva.org>2011-02-26 10:37:20 +0000
commitc9c88fb8dcbd35774e7aaae09e057e1b8bcdc3d4 (patch)
treee7172312a1200124979985a2c6b3096e21a3a4a3 /src/pulsecore/sink.c
parentdbdb4607b02bf312255da980e3b01aa6733c0800 (diff)
Implement the "volume sharing" feature.
When we have a filter sink that does some processing, currently the benefits of the flat volume feature are not really available. That's because if you have a music player that is connected to the filter sink, the hardware sink doesn't have any idea of the music player's stream volume. This problem is solved by this "volume sharing" feature. The volume sharing feature works so that the filter sinks that want to avoid the previously described problem declare that they don't want to have independent volume, but they follow the master sink volume instead. The PA_SINK_SHARE_VOLUME_WITH_MASTER sink flag is used for that declaration. Then the volume logic is changed so that the hardware sink calculates its real volume using also the streams connected to the filter sink in addition to the streams that are connected directly to the hardware sink. Basically we're trying to create an illusion that from volume point of view all streams are connected directly to the hardware sink. For that illusion to work, the volumes of the filter sinks and their virtual streams have to be managed carefully according to a set of rules: If a filter sink follows the hardware sink volume, then the filter sink's * reference_volume always equals the hw sink's reference_volume * real_volume always equals the hw sink's real_volume * soft_volume is always 0dB (ie. no soft volume) If a filter sink doesn't follow the hardware sink volume, then the filter sink's * reference_volume can be whatever (completely independent from the hw sink) * real_volume always equals reference_volume * soft_volume always equals real_volume (and reference_volume) If a filter sink follows the hardware sink volume, and the hardware sink supports flat volume, then the filter sink's virtual stream's * volume always equals the hw sink's real_volume * reference_ratio is calculated normally from the stream volume and the hw sink's reference_volume * real_ratio always equals 0dB (follows from the first point) * soft_volume always equals volume_factor (follows from the previous point) If a filter sink follows the hardware sink volume, and the hardware sink doesn't support flat volume, then the filter sink's virtual stream's * volume is always 0dB * reference_ratio is always 0dB * real_ratio is always 0dB * soft_volume always equals volume_factor If a filter sink doesn't follow the hardware sink volume, then the filter sink's virtual stream is handled as a regular stream. Since the volumes of the virtual streams are controlled by a set of rules, the user is not allowed to change the virtual streams' volumes. It would probably also make sense to forbid changing the filter sinks' volume, but that's not strictly necessary, and currently changing a filter sink's volume changes actually the hardware sink's volume, and from there it propagates to all filter sinks ("funny" effects are expected when adjusting a single channel in cases where all sinks don't have the same channel maps). This patch is based on the work of Marc-André Lureau, who did the initial implementation for Pulseaudio 0.9.15.
Diffstat (limited to 'src/pulsecore/sink.c')
-rw-r--r--src/pulsecore/sink.c555
1 files changed, 395 insertions, 160 deletions
diff --git a/src/pulsecore/sink.c b/src/pulsecore/sink.c
index 773123d8..d713be11 100644
--- a/src/pulsecore/sink.c
+++ b/src/pulsecore/sink.c
@@ -226,8 +226,14 @@ pa_sink* pa_sink_new(
pa_return_null_if_fail(pa_channel_map_valid(&data->channel_map));
pa_return_null_if_fail(data->channel_map.channels == data->sample_spec.channels);
- if (!data->volume_is_set)
+ /* FIXME: There should probably be a general function for checking whether
+ * the sink volume is allowed to be set, like there is for sink inputs. */
+ pa_assert(!data->volume_is_set || !(flags & PA_SINK_SHARE_VOLUME_WITH_MASTER));
+
+ if (!data->volume_is_set) {
pa_cvolume_reset(&data->volume, data->sample_spec.channels);
+ data->save_volume = FALSE;
+ }
pa_return_null_if_fail(pa_cvolume_valid(&data->volume));
pa_return_null_if_fail(pa_cvolume_compatible(&data->volume, &data->sample_spec));
@@ -447,6 +453,7 @@ void pa_sink_put(pa_sink* s) {
pa_assert_ctl_context();
pa_assert(s->state == PA_SINK_INIT);
+ pa_assert(!(s->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER) || s->input_to_master);
/* The following fields must be initialized properly when calling _put() */
pa_assert(s->asyncmsgq);
@@ -456,22 +463,43 @@ void pa_sink_put(pa_sink* s) {
* special exception we allow volume related flags to be set
* between _new() and _put(). */
- if (!(s->flags & PA_SINK_HW_VOLUME_CTRL))
+ /* XXX: Currently decibel volume is disabled for all sinks that use volume
+ * sharing. When the master sink supports decibel volume, it would be good
+ * to have the flag also in the filter sink, but currently we don't do that
+ * so that the flags of the filter sink never change when it's moved from
+ * a master sink to another. One solution for this problem would be to
+ * remove user-visible volume altogether from filter sinks when volume
+ * sharing is used, but the current approach was easier to implement... */
+ if (!(s->flags & PA_SINK_HW_VOLUME_CTRL) && !(s->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER))
s->flags |= PA_SINK_DECIBEL_VOLUME;
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;
+ if (s->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER) {
+ pa_sink *root_sink = s->input_to_master->sink;
+
+ while (root_sink->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)
+ root_sink = root_sink->input_to_master->sink;
+
+ s->reference_volume = root_sink->reference_volume;
+ pa_cvolume_remap(&s->reference_volume, &root_sink->channel_map, &s->channel_map);
+
+ s->real_volume = root_sink->real_volume;
+ pa_cvolume_remap(&s->real_volume, &root_sink->channel_map, &s->channel_map);
+ } else
+ /* 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;
pa_sw_cvolume_multiply(&s->thread_info.current_hw_volume, &s->soft_volume, &s->real_volume);
- pa_assert((s->flags & PA_SINK_HW_VOLUME_CTRL) || (s->base_volume == PA_VOLUME_NORM && s->flags & PA_SINK_DECIBEL_VOLUME));
+ pa_assert((s->flags & PA_SINK_HW_VOLUME_CTRL)
+ || (s->base_volume == PA_VOLUME_NORM
+ && ((s->flags & PA_SINK_DECIBEL_VOLUME || (s->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)))));
pa_assert(!(s->flags & PA_SINK_DECIBEL_VOLUME) || s->n_volume_steps == PA_VOLUME_NORM+1);
pa_assert(!(s->flags & PA_SINK_DYNAMIC_LATENCY) == (s->thread_info.fixed_latency != 0));
pa_assert(!(s->flags & PA_SINK_LATENCY) == !(s->monitor_source->flags & PA_SOURCE_LATENCY));
@@ -1196,47 +1224,61 @@ pa_usec_t pa_sink_get_latency_within_thread(pa_sink *s) {
return usec;
}
-static pa_cvolume* cvolume_remap_minimal_impact(
- pa_cvolume *v,
- const pa_cvolume *template,
- const pa_channel_map *from,
- const pa_channel_map *to) {
+/* Called from the main thread (and also from the IO thread while the main
+ * thread is waiting).
+ *
+ * When a sink uses volume sharing, it never has the PA_SINK_FLAT_VOLUME flag
+ * set. Instead, flat volume mode is detected by checking whether the root sink
+ * has the flag set. */
+pa_bool_t pa_sink_flat_volume_enabled(pa_sink *s) {
+ pa_sink_assert_ref(s);
- pa_cvolume t;
+ while (s->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)
+ s = s->input_to_master->sink;
- pa_assert(v);
- pa_assert(template);
- pa_assert(from);
- pa_assert(to);
+ return (s->flags & PA_SINK_FLAT_VOLUME);
+}
- pa_return_val_if_fail(pa_cvolume_compatible_with_channel_map(v, from), NULL);
- pa_return_val_if_fail(pa_cvolume_compatible_with_channel_map(template, to), NULL);
+/* Called from main context. */
+static void compute_reference_ratio(pa_sink_input *i) {
+ unsigned c = 0;
+ pa_cvolume remapped;
- /* Much like pa_cvolume_remap(), but tries to minimize impact when
- * mapping from sink input to sink volumes:
- *
- * If template is a possible remapping from v it is used instead
- * of remapping anew.
+ pa_assert(i);
+ pa_assert(pa_sink_flat_volume_enabled(i->sink));
+
+ /*
+ * Calculates the reference ratio from the sink's reference
+ * volume. This basically calculates:
*
- * If the channel maps don't match we set an all-channel volume on
- * the sink to ensure that changing a volume on one stream has no
- * effect that cannot be compensated for in another stream that
- * does not have the same channel map as the sink. */
+ * i->reference_ratio = i->volume / i->sink->reference_volume
+ */
- if (pa_channel_map_equal(from, to))
- return v;
+ remapped = i->sink->reference_volume;
+ pa_cvolume_remap(&remapped, &i->sink->channel_map, &i->channel_map);
- t = *template;
- if (pa_cvolume_equal(pa_cvolume_remap(&t, to, from), v)) {
- *v = *template;
- return v;
- }
+ i->reference_ratio.channels = i->sample_spec.channels;
- pa_cvolume_set(v, to->channels, pa_cvolume_max(v));
- return v;
+ for (c = 0; c < i->sample_spec.channels; c++) {
+
+ /* We don't update when the sink volume is 0 anyway */
+ if (remapped.values[c] <= PA_VOLUME_MUTED)
+ continue;
+
+ /* 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 */
+/* Called from main context. Only called for the root sink in volume sharing
+ * cases, except for internal recursive calls. */
static void compute_reference_ratios(pa_sink *s) {
uint32_t idx;
pa_sink_input *i;
@@ -1244,44 +1286,18 @@ static void compute_reference_ratios(pa_sink *s) {
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_assert(pa_sink_flat_volume_enabled(s));
PA_IDXSET_FOREACH(i, s->inputs, idx) {
- unsigned c;
- pa_cvolume remapped;
-
- /*
- * Calculates the reference volume from the sink's reference
- * volume. This basically calculates:
- *
- * i->reference_ratio = i->volume / s->reference_volume
- */
-
- remapped = s->reference_volume;
- pa_cvolume_remap(&remapped, &s->channel_map, &i->channel_map);
-
- i->reference_ratio.channels = i->sample_spec.channels;
-
- for (c = 0; c < i->sample_spec.channels; c++) {
+ compute_reference_ratio(i);
- /* We don't update when the sink volume is 0 anyway */
- if (remapped.values[c] <= PA_VOLUME_MUTED)
- continue;
-
- /* 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]);
- }
+ if (i->origin_sink && (i->origin_sink->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER))
+ compute_reference_ratios(i->origin_sink);
}
}
-/* Called from main context */
+/* Called from main context. Only called for the root sink in volume sharing
+ * cases, except for internal recursive calls. */
static void compute_real_ratios(pa_sink *s) {
pa_sink_input *i;
uint32_t idx;
@@ -1289,12 +1305,24 @@ static void compute_real_ratios(pa_sink *s) {
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_assert(pa_sink_flat_volume_enabled(s));
PA_IDXSET_FOREACH(i, s->inputs, idx) {
unsigned c;
pa_cvolume remapped;
+ if (i->origin_sink && (i->origin_sink->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)) {
+ /* The origin sink uses volume sharing, so this input's real ratio
+ * is handled as a special case - the real ratio must be 0 dB, and
+ * as a result i->soft_volume must equal i->volume_factor. */
+ pa_cvolume_reset(&i->real_ratio, i->real_ratio.channels);
+ i->soft_volume = i->volume_factor;
+
+ compute_real_ratios(i->origin_sink);
+
+ continue;
+ }
+
/*
* This basically calculates:
*
@@ -1335,23 +1363,144 @@ static void compute_real_ratios(pa_sink *s) {
}
}
-/* Called from main thread */
-static void compute_real_volume(pa_sink *s) {
+static pa_cvolume *cvolume_remap_minimal_impact(
+ pa_cvolume *v,
+ const pa_cvolume *template,
+ const pa_channel_map *from,
+ const pa_channel_map *to) {
+
+ pa_cvolume t;
+
+ pa_assert(v);
+ pa_assert(template);
+ pa_assert(from);
+ pa_assert(to);
+ pa_assert(pa_cvolume_compatible_with_channel_map(v, from));
+ pa_assert(pa_cvolume_compatible_with_channel_map(template, to));
+
+ /* Much like pa_cvolume_remap(), but tries to minimize impact when
+ * mapping from sink input to sink volumes:
+ *
+ * If template is a possible remapping from v it is used instead
+ * of remapping anew.
+ *
+ * If the channel maps don't match we set an all-channel volume on
+ * the sink to ensure that changing a volume on one stream has no
+ * effect that cannot be compensated for in another stream that
+ * does not have the same channel map as the sink. */
+
+ if (pa_channel_map_equal(from, to))
+ return v;
+
+ t = *template;
+ if (pa_cvolume_equal(pa_cvolume_remap(&t, to, from), v)) {
+ *v = *template;
+ return v;
+ }
+
+ pa_cvolume_set(v, to->channels, pa_cvolume_max(v));
+ return v;
+}
+
+/* Called from main thread. Only called for the root sink in volume sharing
+ * cases, except for internal recursive calls. */
+static void get_maximum_input_volume(pa_sink *s, pa_cvolume *max_volume, const pa_channel_map *channel_map) {
pa_sink_input *i;
uint32_t idx;
pa_sink_assert_ref(s);
+ pa_assert(max_volume);
+ pa_assert(channel_map);
+ pa_assert(pa_sink_flat_volume_enabled(s));
+
+ PA_IDXSET_FOREACH(i, s->inputs, idx) {
+ pa_cvolume remapped;
+
+ if (i->origin_sink && (i->origin_sink->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)) {
+ get_maximum_input_volume(i->origin_sink, max_volume, channel_map);
+
+ /* Ignore this input. The origin sink uses volume sharing, so this
+ * input's volume will be set to be equal to the root sink's real
+ * volume. Obviously this input's current volume must not then
+ * affect what the root sink's real volume will be. */
+ continue;
+ }
+
+ remapped = i->volume;
+ cvolume_remap_minimal_impact(&remapped, max_volume, &i->channel_map, channel_map);
+ pa_cvolume_merge(max_volume, max_volume, &remapped);
+ }
+}
+
+/* Called from main thread. Only called for the root sink in volume sharing
+ * cases, except for internal recursive calls. */
+static pa_bool_t has_inputs(pa_sink *s) {
+ pa_sink_input *i;
+ uint32_t idx;
+
+ pa_sink_assert_ref(s);
+
+ PA_IDXSET_FOREACH(i, s->inputs, idx) {
+ if (!i->origin_sink || !(i->origin_sink->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER) || has_inputs(i->origin_sink))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/* Called from main thread. Only called for the root sink in volume sharing
+ * cases, except for internal recursive calls. */
+static void update_real_volume(pa_sink *s, const pa_cvolume *new_volume, pa_channel_map *channel_map) {
+ pa_sink_input *i;
+ uint32_t idx;
+
+ pa_sink_assert_ref(s);
+ pa_assert(new_volume);
+ pa_assert(channel_map);
+
+ s->real_volume = *new_volume;
+ pa_cvolume_remap(&s->real_volume, channel_map, &s->channel_map);
+
+ PA_IDXSET_FOREACH(i, s->inputs, idx) {
+ if (i->origin_sink && (i->origin_sink->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)) {
+ if (pa_sink_flat_volume_enabled(s)) {
+ pa_cvolume old_volume = i->volume;
+
+ /* Follow the root sink's real volume. */
+ i->volume = *new_volume;
+ pa_cvolume_remap(&i->volume, channel_map, &i->channel_map);
+ compute_reference_ratio(i);
+
+ /* The volume changed, let's tell people so */
+ if (!pa_cvolume_equal(&old_volume, &i->volume)) {
+ if (i->volume_changed)
+ i->volume_changed(i);
+
+ pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index);
+ }
+ }
+
+ update_real_volume(i->origin_sink, new_volume, channel_map);
+ }
+ }
+}
+
+/* Called from main thread. Only called for the root sink in shared volume
+ * cases. */
+static void compute_real_volume(pa_sink *s) {
+ 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_assert(pa_sink_flat_volume_enabled(s));
+ pa_assert(!(s->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER));
/* 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
+ if (!has_inputs(s)) {
+ /* In the special case that we have no sink inputs we leave the
* volume unmodified. */
- s->real_volume = s->reference_volume;
+ update_real_volume(s, &s->reference_volume, &s->channel_map);
return;
}
@@ -1359,20 +1508,16 @@ static void compute_real_volume(pa_sink *s) {
/* First let's determine the new maximum volume of all inputs
* connected to this sink */
- PA_IDXSET_FOREACH(i, s->inputs, idx) {
- pa_cvolume remapped;
-
- remapped = i->volume;
- cvolume_remap_minimal_impact(&remapped, &s->real_volume, &i->channel_map, &s->channel_map);
- pa_cvolume_merge(&s->real_volume, &s->real_volume, &remapped);
- }
+ get_maximum_input_volume(s, &s->real_volume, &s->channel_map);
+ update_real_volume(s, &s->real_volume, &s->channel_map);
/* Then, let's update the real ratios/soft volumes of all inputs
* connected to this sink */
compute_real_ratios(s);
}
-/* Called from main thread */
+/* Called from main thread. Only called for the root sink in shared volume
+ * cases, except for internal recursive calls. */
static void propagate_reference_volume(pa_sink *s) {
pa_sink_input *i;
uint32_t idx;
@@ -1380,14 +1525,23 @@ static void propagate_reference_volume(pa_sink *s) {
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_assert(pa_sink_flat_volume_enabled(s));
/* This is called whenever the sink volume changes that is not
* caused by a sink input volume change. We need to fix up the
* sink input volumes accordingly */
PA_IDXSET_FOREACH(i, s->inputs, idx) {
- pa_cvolume old_volume, remapped;
+ pa_cvolume old_volume;
+
+ if (i->origin_sink && (i->origin_sink->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)) {
+ propagate_reference_volume(i->origin_sink);
+
+ /* Since the origin sink uses volume sharing, this input's volume
+ * needs to be updated to match the root sink's real volume, but
+ * that will be done later in update_shared_real_volume(). */
+ continue;
+ }
old_volume = i->volume;
@@ -1395,9 +1549,9 @@ static void propagate_reference_volume(pa_sink *s) {
*
* i->volume := s->reference_volume * i->reference_ratio */
- remapped = s->reference_volume;
- pa_cvolume_remap(&remapped, &s->channel_map, &i->channel_map);
- pa_sw_cvolume_multiply(&i->volume, &remapped, &i->reference_ratio);
+ i->volume = s->reference_volume;
+ pa_cvolume_remap(&i->volume, &s->channel_map, &i->channel_map);
+ pa_sw_cvolume_multiply(&i->volume, &i->volume, &i->reference_ratio);
/* The volume changed, let's tell people so */
if (!pa_cvolume_equal(&old_volume, &i->volume)) {
@@ -1410,6 +1564,54 @@ static void propagate_reference_volume(pa_sink *s) {
}
}
+/* Called from main thread. Only called for the root sink in volume sharing
+ * cases, except for internal recursive calls. The return value indicates
+ * whether any reference volume actually changed. */
+static pa_bool_t update_reference_volume(pa_sink *s, const pa_cvolume *v, const pa_channel_map *channel_map, pa_bool_t save) {
+ pa_cvolume volume;
+ pa_bool_t reference_volume_changed;
+ pa_sink_input *i;
+ uint32_t idx;
+
+ pa_sink_assert_ref(s);
+ pa_assert(PA_SINK_IS_LINKED(s->state));
+ pa_assert(v);
+ pa_assert(channel_map);
+ pa_assert(pa_cvolume_valid(v));
+
+ volume = *v;
+ pa_cvolume_remap(&volume, channel_map, &s->channel_map);
+
+ reference_volume_changed = !pa_cvolume_equal(&volume, &s->reference_volume);
+ s->reference_volume = volume;
+
+ s->save_volume = (!reference_volume_changed && s->save_volume) || save;
+
+ if (reference_volume_changed)
+ pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+ else if (!(s->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER))
+ /* If the root sink's volume doesn't change, then there can't be any
+ * changes in the other sinks in the sink tree either.
+ *
+ * It's probably theoretically possible that even if the root sink's
+ * volume changes slightly, some filter sink doesn't change its volume
+ * due to rounding errors. If that happens, we still want to propagate
+ * the changed root sink volume to the sinks connected to the
+ * intermediate sink that didn't change its volume. This theoretical
+ * possiblity is the reason why we have that !(s->flags &
+ * PA_SINK_SHARE_VOLUME_WITH_MASTER) condition. Probably nobody would
+ * notice even if we returned here FALSE always if
+ * reference_volume_changed is FALSE. */
+ return FALSE;
+
+ PA_IDXSET_FOREACH(i, s->inputs, idx) {
+ if (i->origin_sink && (i->origin_sink->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER))
+ update_reference_volume(i->origin_sink, v, channel_map, FALSE);
+ }
+
+ return TRUE;
+}
+
/* Called from main thread */
void pa_sink_set_volume(
pa_sink *s,
@@ -1417,14 +1619,14 @@ void pa_sink_set_volume(
pa_bool_t send_msg,
pa_bool_t save) {
- pa_cvolume old_reference_volume;
- pa_bool_t reference_changed;
+ pa_cvolume new_reference_volume;
+ pa_sink *root_sink = s;
pa_sink_assert_ref(s);
pa_assert_ctl_context();
pa_assert(PA_SINK_IS_LINKED(s->state));
pa_assert(!volume || pa_cvolume_valid(volume));
- pa_assert(volume || (s->flags & PA_SINK_FLAT_VOLUME));
+ pa_assert(volume || pa_sink_flat_volume_enabled(s));
pa_assert(!volume || volume->channels == 1 || pa_cvolume_compatible(volume, &s->sample_spec));
/* make sure we don't change the volume when a PASSTHROUGH input is connected */
@@ -1445,76 +1647,82 @@ void pa_sink_set_volume(
}
}
+ /* In case of volume sharing, the volume is set for the root sink first,
+ * from which it's then propagated to the sharing sinks. */
+ while (root_sink->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)
+ root_sink = root_sink->input_to_master->sink;
+
/* As a special exception we accept mono volumes on all sinks --
* even on those with more complex channel maps */
+ if (volume) {
+ if (pa_cvolume_compatible(volume, &s->sample_spec))
+ new_reference_volume = *volume;
+ else {
+ new_reference_volume = s->reference_volume;
+ pa_cvolume_scale(&new_reference_volume, pa_cvolume_max(volume));
+ }
+
+ pa_cvolume_remap(&new_reference_volume, &s->channel_map, &root_sink->channel_map);
+ }
+
/* 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) {
-
- if (pa_cvolume_compatible(volume, &s->sample_spec))
- s->reference_volume = *volume;
- else
- pa_cvolume_scale(&s->reference_volume, pa_cvolume_max(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;
+ if (update_reference_volume(root_sink, &new_reference_volume, &root_sink->channel_map, save)) {
+ if (pa_sink_flat_volume_enabled(root_sink)) {
+ /* OK, propagate this volume change back to the inputs */
+ propagate_reference_volume(root_sink);
+
+ /* And now recalculate the real volume */
+ compute_real_volume(root_sink);
+ } else
+ update_real_volume(root_sink, &root_sink->reference_volume, &root_sink->channel_map);
+ }
} else {
- pa_assert(s->flags & PA_SINK_FLAT_VOLUME);
+ pa_assert(pa_sink_flat_volume_enabled(root_sink));
/* Ok, let's determine the new real volume */
- compute_real_volume(s);
+ compute_real_volume(root_sink);
/* Let's 'push' the reference volume if necessary */
- pa_cvolume_merge(&s->reference_volume, &s->reference_volume, &s->real_volume);
+ pa_cvolume_merge(&new_reference_volume, &s->reference_volume, &root_sink->real_volume);
+ update_reference_volume(root_sink, &new_reference_volume, &root_sink->channel_map, save);
- /* We need to fix the reference ratios of all streams now that
- * we changed the reference volume */
- compute_reference_ratios(s);
+ /* Now that the reference volume is updated, we can update the streams'
+ * reference ratios. */
+ compute_reference_ratios(root_sink);
}
- 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 (root_sink->set_volume) {
/* If we have a function set_volume(), then we do not apply a
* soft volume by default. However, set_volume() is free to
- * apply one to s->soft_volume */
+ * apply one to root_sink->soft_volume */
- pa_cvolume_reset(&s->soft_volume, s->sample_spec.channels);
- if (!(s->flags & PA_SINK_SYNC_VOLUME))
- s->set_volume(s);
+ pa_cvolume_reset(&root_sink->soft_volume, root_sink->sample_spec.channels);
+ if (!(root_sink->flags & PA_SINK_SYNC_VOLUME))
+ root_sink->set_volume(root_sink);
else
send_msg = TRUE;
} else
/* If we have no function set_volume(), then the soft volume
- * becomes the virtual volume */
- s->soft_volume = s->real_volume;
+ * becomes the real volume */
+ root_sink->soft_volume = root_sink->real_volume;
- /* This tells the sink that soft and/or virtual volume changed */
+ /* This tells the sink that soft volume and/or real volume changed */
if (send_msg)
- pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_VOLUME_SYNCED, NULL, 0, NULL) == 0);
-
- if (reference_changed)
- pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+ pa_assert_se(pa_asyncmsgq_send(root_sink->asyncmsgq, PA_MSGOBJECT(root_sink), PA_SINK_MESSAGE_SET_SHARED_VOLUME, NULL, 0, NULL) == 0);
}
/* Called from the io thread if sync volume is used, otherwise from the main thread.
* Only to be called by sink implementor */
void pa_sink_set_soft_volume(pa_sink *s, const pa_cvolume *volume) {
pa_sink_assert_ref(s);
+ pa_assert(!(s->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER));
if (s->flags & PA_SINK_SYNC_VOLUME)
pa_sink_assert_io_context(s);
else
@@ -1531,12 +1739,14 @@ void pa_sink_set_soft_volume(pa_sink *s, const pa_cvolume *volume) {
s->thread_info.soft_volume = s->soft_volume;
}
+/* Called from the main thread. Only called for the root sink in volume sharing
+ * cases, except for internal recursive calls. */
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(old_real_volume);
pa_assert_ctl_context();
pa_assert(PA_SINK_IS_LINKED(s->state));
@@ -1545,20 +1755,18 @@ static void propagate_real_volume(pa_sink *s, const pa_cvolume *old_real_volume)
* reference volume and then rebuild the stream volumes based on
* i->real_ratio which should stay fixed. */
- if (old_real_volume && pa_cvolume_equal(old_real_volume, &s->real_volume))
- return;
-
- old_reference_volume = s->reference_volume;
+ if (!(s->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)) {
+ if (pa_cvolume_equal(old_real_volume, &s->real_volume))
+ return;
- /* 1. Make the real volume the reference volume */
- s->reference_volume = s->real_volume;
+ /* 1. Make the real volume the reference volume */
+ update_reference_volume(s, &s->real_volume, &s->channel_map, TRUE);
+ }
- if (s->flags & PA_SINK_FLAT_VOLUME) {
+ if (pa_sink_flat_volume_enabled(s)) {
PA_IDXSET_FOREACH(i, s->inputs, idx) {
- pa_cvolume old_volume, remapped;
-
- old_volume = i->volume;
+ pa_cvolume old_volume = i->volume;
/* 2. Since the sink's reference and real volumes are equal
* now our ratios should be too. */
@@ -1572,9 +1780,9 @@ static void propagate_real_volume(pa_sink *s, const pa_cvolume *old_real_volume)
* 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);
+ i->volume = s->reference_volume;
+ pa_cvolume_remap(&i->volume, &s->channel_map, &i->channel_map);
+ pa_sw_cvolume_multiply(&i->volume, &i->volume, &i->reference_ratio);
/* Notify if something changed */
if (!pa_cvolume_equal(&old_volume, &i->volume)) {
@@ -1584,16 +1792,17 @@ static void propagate_real_volume(pa_sink *s, const pa_cvolume *old_real_volume)
pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index);
}
+
+ if (i->origin_sink && (i->origin_sink->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER))
+ propagate_real_volume(i->origin_sink, old_real_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);
+ if (!(s->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER))
+ s->save_volume = TRUE;
}
/* Called from io thread */
@@ -1613,6 +1822,8 @@ const pa_cvolume *pa_sink_get_volume(pa_sink *s, pa_bool_t force_refresh) {
if (s->refresh_volume || force_refresh) {
struct pa_cvolume old_real_volume;
+ pa_assert(!(s->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER));
+
old_real_volume = s->real_volume;
if (!(s->flags & PA_SINK_SYNC_VOLUME) && s->get_volume)
@@ -1620,25 +1831,27 @@ const pa_cvolume *pa_sink_get_volume(pa_sink *s, pa_bool_t force_refresh) {
pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_VOLUME, NULL, 0, NULL) == 0);
+ update_real_volume(s, &s->real_volume, &s->channel_map);
propagate_real_volume(s, &old_real_volume);
}
return &s->reference_volume;
}
-/* Called from main thread */
+/* Called from main thread. In volume sharing cases, only the root sink may
+ * call this. */
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));
+ pa_assert(!(s->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER));
/* 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;
-
+ update_real_volume(s, new_real_volume, &s->channel_map);
propagate_real_volume(s, &old_real_volume);
}
@@ -1853,10 +2066,27 @@ static void sync_input_volumes_within_thread(pa_sink *s) {
if (!pa_atomic_load(&i->before_ramping_v))
i->thread_info.soft_volume = i->soft_volume;
+
pa_sink_input_request_rewind(i, 0, TRUE, FALSE, FALSE);
}
}
+/* Called from the IO thread. Only called for the root sink in volume sharing
+ * cases, except for internal recursive calls. */
+static void set_shared_volume_within_thread(pa_sink *s) {
+ pa_sink_input *i = NULL;
+ void *state = NULL;
+
+ pa_sink_assert_ref(s);
+
+ PA_MSGOBJECT(s)->process_msg(PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_VOLUME_SYNCED, NULL, 0, NULL);
+
+ PA_HASHMAP_FOREACH(i, s->thread_info.inputs, state) {
+ if (i->origin_sink && (i->origin_sink->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER))
+ set_shared_volume_within_thread(i->origin_sink);
+ }
+}
+
/* Called from IO thread, except when it is not */
int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk) {
pa_sink *s = PA_SINK(o);
@@ -1913,7 +2143,7 @@ int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offse
/* In flat volume mode we need to update the volume as
* well */
- return o->process_msg(o, PA_SINK_MESSAGE_SET_VOLUME_SYNCED, NULL, 0, NULL);
+ return o->process_msg(o, PA_SINK_MESSAGE_SET_SHARED_VOLUME, NULL, 0, NULL);
}
case PA_SINK_MESSAGE_REMOVE_INPUT: {
@@ -1956,7 +2186,7 @@ int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offse
/* In flat volume mode we need to update the volume as
* well */
- return o->process_msg(o, PA_SINK_MESSAGE_SET_VOLUME_SYNCED, NULL, 0, NULL);
+ return o->process_msg(o, PA_SINK_MESSAGE_SET_SHARED_VOLUME, NULL, 0, NULL);
}
case PA_SINK_MESSAGE_START_MOVE: {
@@ -2001,7 +2231,7 @@ int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offse
/* In flat volume mode we need to update the volume as
* well */
- return o->process_msg(o, PA_SINK_MESSAGE_SET_VOLUME_SYNCED, NULL, 0, NULL);
+ return o->process_msg(o, PA_SINK_MESSAGE_SET_SHARED_VOLUME, NULL, 0, NULL);
}
case PA_SINK_MESSAGE_FINISH_MOVE: {
@@ -2042,9 +2272,17 @@ int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offse
pa_sink_request_rewind(s, nbytes);
}
- /* In flat volume mode we need to update the volume as
- * well */
- return o->process_msg(o, PA_SINK_MESSAGE_SET_VOLUME_SYNCED, NULL, 0, NULL);
+ return o->process_msg(o, PA_SINK_MESSAGE_SET_SHARED_VOLUME, NULL, 0, NULL);
+ }
+
+ case PA_SINK_MESSAGE_SET_SHARED_VOLUME: {
+ pa_sink *root_sink = s;
+
+ while (root_sink->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)
+ root_sink = root_sink->input_to_master->sink;
+
+ set_shared_volume_within_thread(root_sink);
+ return 0;
}
case PA_SINK_MESSAGE_SET_VOLUME_SYNCED:
@@ -2062,9 +2300,6 @@ int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offse
pa_sink_request_rewind(s, (size_t) -1);
}
- if (!(s->flags & PA_SINK_FLAT_VOLUME))
- return 0;
-
/* Fall through ... */
case PA_SINK_MESSAGE_SYNC_VOLUMES: