+ pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_GET_LATENCY, &usec, 0, NULL) == 0);
+
+ /* usec is unsigned, so check that the offset can be added to usec without
+ * underflowing. */
+ if (-s->latency_offset <= (int64_t) usec)
+ usec += s->latency_offset;
+ else
+ usec = 0;
+
+ return usec;
+}
+
+/* Called from IO thread */
+pa_usec_t pa_source_get_latency_within_thread(pa_source *s) {
+ pa_usec_t usec = 0;
+ pa_msgobject *o;
+
+ pa_source_assert_ref(s);
+ pa_source_assert_io_context(s);
+ pa_assert(PA_SOURCE_IS_LINKED(s->thread_info.state));
+
+ /* The returned value is supposed to be in the time domain of the sound card! */
+
+ if (s->thread_info.state == PA_SOURCE_SUSPENDED)
+ return 0;
+
+ if (!(s->flags & PA_SOURCE_LATENCY))
+ return 0;
+
+ o = PA_MSGOBJECT(s);
+
+ /* FIXME: We probably should make this a proper vtable callback instead of going through process_msg() */
+
+ if (o->process_msg(o, PA_SOURCE_MESSAGE_GET_LATENCY, &usec, 0, NULL) < 0)
+ return -1;
+
+ /* usec is unsigned, so check that the offset can be added to usec without
+ * underflowing. */
+ if (-s->thread_info.latency_offset <= (int64_t) usec)
+ usec += s->thread_info.latency_offset;
+ else
+ usec = 0;
+
+ return usec;
+}
+
+/* Called from the main thread (and also from the IO thread while the main
+ * thread is waiting).
+ *
+ * When a source uses volume sharing, it never has the PA_SOURCE_FLAT_VOLUME flag
+ * set. Instead, flat volume mode is detected by checking whether the root source
+ * has the flag set. */
+bool pa_source_flat_volume_enabled(pa_source *s) {
+ pa_source_assert_ref(s);
+
+ s = pa_source_get_master(s);
+
+ if (PA_LIKELY(s))
+ return (s->flags & PA_SOURCE_FLAT_VOLUME);
+ else
+ return false;
+}
+
+/* Called from the main thread (and also from the IO thread while the main
+ * thread is waiting). */
+pa_source *pa_source_get_master(pa_source *s) {
+ pa_source_assert_ref(s);
+
+ while (s && (s->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER)) {
+ if (PA_UNLIKELY(!s->output_from_master))
+ return NULL;
+
+ s = s->output_from_master->source;
+ }
+
+ return s;
+}
+
+/* Called from main context */
+bool pa_source_is_passthrough(pa_source *s) {
+
+ pa_source_assert_ref(s);
+
+ /* NB Currently only monitor sources support passthrough mode */
+ return (s->monitor_of && pa_sink_is_passthrough(s->monitor_of));
+}
+
+/* Called from main context */
+void pa_source_enter_passthrough(pa_source *s) {
+ pa_cvolume volume;
+
+ /* set the volume to NORM */
+ s->saved_volume = *pa_source_get_volume(s, true);
+ s->saved_save_volume = s->save_volume;
+
+ pa_cvolume_set(&volume, s->sample_spec.channels, PA_MIN(s->base_volume, PA_VOLUME_NORM));
+ pa_source_set_volume(s, &volume, true, false);
+}
+
+/* Called from main context */
+void pa_source_leave_passthrough(pa_source *s) {
+ /* Restore source volume to what it was before we entered passthrough mode */
+ pa_source_set_volume(s, &s->saved_volume, true, s->saved_save_volume);
+
+ pa_cvolume_init(&s->saved_volume);
+ s->saved_save_volume = false;
+}
+
+/* Called from main context. */
+static void compute_reference_ratio(pa_source_output *o) {
+ unsigned c = 0;
+ pa_cvolume remapped;
+
+ pa_assert(o);
+ pa_assert(pa_source_flat_volume_enabled(o->source));
+
+ /*
+ * Calculates the reference ratio from the source's reference
+ * volume. This basically calculates:
+ *
+ * o->reference_ratio = o->volume / o->source->reference_volume
+ */
+
+ remapped = o->source->reference_volume;
+ pa_cvolume_remap(&remapped, &o->source->channel_map, &o->channel_map);
+
+ o->reference_ratio.channels = o->sample_spec.channels;
+
+ for (c = 0; c < o->sample_spec.channels; c++) {
+
+ /* We don't update when the source 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(
+ o->reference_ratio.values[c],
+ remapped.values[c]) == o->volume.values[c])
+ continue;
+
+ o->reference_ratio.values[c] = pa_sw_volume_divide(
+ o->volume.values[c],
+ remapped.values[c]);
+ }
+}
+
+/* Called from main context. Only called for the root source in volume sharing
+ * cases, except for internal recursive calls. */
+static void compute_reference_ratios(pa_source *s) {
+ uint32_t idx;
+ pa_source_output *o;
+
+ pa_source_assert_ref(s);
+ pa_assert_ctl_context();
+ pa_assert(PA_SOURCE_IS_LINKED(s->state));
+ pa_assert(pa_source_flat_volume_enabled(s));
+
+ PA_IDXSET_FOREACH(o, s->outputs, idx) {
+ compute_reference_ratio(o);
+
+ if (o->destination_source && (o->destination_source->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER))
+ compute_reference_ratios(o->destination_source);
+ }
+}
+
+/* Called from main context. Only called for the root source in volume sharing
+ * cases, except for internal recursive calls. */
+static void compute_real_ratios(pa_source *s) {
+ pa_source_output *o;
+ uint32_t idx;
+
+ pa_source_assert_ref(s);
+ pa_assert_ctl_context();
+ pa_assert(PA_SOURCE_IS_LINKED(s->state));
+ pa_assert(pa_source_flat_volume_enabled(s));
+
+ PA_IDXSET_FOREACH(o, s->outputs, idx) {
+ unsigned c;
+ pa_cvolume remapped;
+
+ if (o->destination_source && (o->destination_source->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER)) {
+ /* The origin source 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(&o->real_ratio, o->real_ratio.channels);
+ o->soft_volume = o->volume_factor;
+
+ compute_real_ratios(o->destination_source);
+
+ continue;
+ }
+
+ /*
+ * 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, &o->channel_map);
+
+ o->real_ratio.channels = o->sample_spec.channels;
+ o->soft_volume.channels = o->sample_spec.channels;
+
+ for (c = 0; c < o->sample_spec.channels; c++) {
+
+ if (remapped.values[c] <= PA_VOLUME_MUTED) {
+ /* We leave o->real_ratio untouched */
+ o->soft_volume.values[c] = PA_VOLUME_MUTED;
+ continue;
+ }
+
+ /* Don't lose accuracy unless necessary */
+ if (pa_sw_volume_multiply(
+ o->real_ratio.values[c],
+ remapped.values[c]) != o->volume.values[c])
+
+ o->real_ratio.values[c] = pa_sw_volume_divide(
+ o->volume.values[c],
+ remapped.values[c]);
+
+ o->soft_volume.values[c] = pa_sw_volume_multiply(
+ o->real_ratio.values[c],
+ o->volume_factor.values[c]);
+ }
+
+ /* We don't copy the soft_volume to the thread_info data
+ * here. That must be done by the caller */
+ }
+}
+
+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 source output to source 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 source 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 source. */
+
+ 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 source in volume sharing
+ * cases, except for internal recursive calls. */
+static void get_maximum_output_volume(pa_source *s, pa_cvolume *max_volume, const pa_channel_map *channel_map) {
+ pa_source_output *o;
+ uint32_t idx;
+
+ pa_source_assert_ref(s);
+ pa_assert(max_volume);
+ pa_assert(channel_map);
+ pa_assert(pa_source_flat_volume_enabled(s));
+
+ PA_IDXSET_FOREACH(o, s->outputs, idx) {
+ pa_cvolume remapped;
+
+ if (o->destination_source && (o->destination_source->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER)) {
+ get_maximum_output_volume(o->destination_source, max_volume, channel_map);
+
+ /* Ignore this output. The origin source uses volume sharing, so this
+ * output's volume will be set to be equal to the root source's real
+ * volume. Obviously this output's current volume must not then
+ * affect what the root source's real volume will be. */
+ continue;
+ }
+
+ remapped = o->volume;
+ cvolume_remap_minimal_impact(&remapped, max_volume, &o->channel_map, channel_map);
+ pa_cvolume_merge(max_volume, max_volume, &remapped);
+ }
+}
+
+/* Called from main thread. Only called for the root source in volume sharing
+ * cases, except for internal recursive calls. */
+static bool has_outputs(pa_source *s) {
+ pa_source_output *o;
+ uint32_t idx;
+
+ pa_source_assert_ref(s);
+
+ PA_IDXSET_FOREACH(o, s->outputs, idx) {
+ if (!o->destination_source || !(o->destination_source->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER) || has_outputs(o->destination_source))
+ return true;
+ }
+
+ return false;
+}
+
+/* Called from main thread. Only called for the root source in volume sharing
+ * cases, except for internal recursive calls. */
+static void update_real_volume(pa_source *s, const pa_cvolume *new_volume, pa_channel_map *channel_map) {
+ pa_source_output *o;
+ uint32_t idx;
+
+ pa_source_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(o, s->outputs, idx) {
+ if (o->destination_source && (o->destination_source->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER)) {
+ if (pa_source_flat_volume_enabled(s)) {
+ pa_cvolume new_output_volume;
+
+ /* Follow the root source's real volume. */
+ new_output_volume = *new_volume;
+ pa_cvolume_remap(&new_output_volume, channel_map, &o->channel_map);
+ pa_source_output_set_volume_direct(o, &new_output_volume);
+ compute_reference_ratio(o);
+ }
+
+ update_real_volume(o->destination_source, new_volume, channel_map);
+ }
+ }
+}
+
+/* Called from main thread. Only called for the root source in shared volume
+ * cases. */
+static void compute_real_volume(pa_source *s) {
+ pa_source_assert_ref(s);
+ pa_assert_ctl_context();
+ pa_assert(PA_SOURCE_IS_LINKED(s->state));
+ pa_assert(pa_source_flat_volume_enabled(s));
+ pa_assert(!(s->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER));
+
+ /* This determines the maximum volume of all streams and sets
+ * s->real_volume accordingly. */
+
+ if (!has_outputs(s)) {
+ /* In the special case that we have no source outputs we leave the
+ * volume unmodified. */
+ update_real_volume(s, &s->reference_volume, &s->channel_map);
+ return;
+ }
+
+ pa_cvolume_mute(&s->real_volume, s->channel_map.channels);
+
+ /* First let's determine the new maximum volume of all outputs
+ * connected to this source */
+ get_maximum_output_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 outputs
+ * connected to this source */
+ compute_real_ratios(s);
+}
+
+/* Called from main thread. Only called for the root source in shared volume
+ * cases, except for internal recursive calls. */
+static void propagate_reference_volume(pa_source *s) {
+ pa_source_output *o;
+ uint32_t idx;
+
+ pa_source_assert_ref(s);
+ pa_assert_ctl_context();
+ pa_assert(PA_SOURCE_IS_LINKED(s->state));
+ pa_assert(pa_source_flat_volume_enabled(s));
+
+ /* This is called whenever the source volume changes that is not
+ * caused by a source output volume change. We need to fix up the
+ * source output volumes accordingly */
+
+ PA_IDXSET_FOREACH(o, s->outputs, idx) {
+ pa_cvolume new_volume;
+
+ if (o->destination_source && (o->destination_source->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER)) {
+ propagate_reference_volume(o->destination_source);
+
+ /* Since the origin source uses volume sharing, this output's volume
+ * needs to be updated to match the root source's real volume, but
+ * that will be done later in update_shared_real_volume(). */
+ continue;
+ }
+
+ /* This basically calculates:
+ *
+ * o->volume := o->reference_volume * o->reference_ratio */
+
+ new_volume = s->reference_volume;
+ pa_cvolume_remap(&new_volume, &s->channel_map, &o->channel_map);
+ pa_sw_cvolume_multiply(&new_volume, &new_volume, &o->reference_ratio);
+ pa_source_output_set_volume_direct(o, &new_volume);
+ }
+}
+
+/* Called from main thread. Only called for the root source in volume sharing
+ * cases, except for internal recursive calls. The return value indicates
+ * whether any reference volume actually changed. */
+static bool update_reference_volume(pa_source *s, const pa_cvolume *v, const pa_channel_map *channel_map, bool save) {
+ pa_cvolume volume;
+ bool reference_volume_changed;
+ pa_source_output *o;
+ uint32_t idx;
+
+ pa_source_assert_ref(s);
+ pa_assert(PA_SOURCE_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);
+ pa_source_set_reference_volume_direct(s, &volume);
+
+ s->save_volume = (!reference_volume_changed && s->save_volume) || save;
+
+ if (!reference_volume_changed && !(s->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER))
+ /* If the root source's volume doesn't change, then there can't be any
+ * changes in the other source in the source tree either.
+ *
+ * It's probably theoretically possible that even if the root source's
+ * volume changes slightly, some filter source doesn't change its volume
+ * due to rounding errors. If that happens, we still want to propagate
+ * the changed root source volume to the sources connected to the
+ * intermediate source that didn't change its volume. This theoretical
+ * possibility is the reason why we have that !(s->flags &
+ * PA_SOURCE_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(o, s->outputs, idx) {
+ if (o->destination_source && (o->destination_source->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER))
+ update_reference_volume(o->destination_source, v, channel_map, false);
+ }
+
+ return true;
+}
+
+/* Called from main thread */
+void pa_source_set_volume(
+ pa_source *s,
+ const pa_cvolume *volume,
+ bool send_msg,
+ bool save) {
+
+ pa_cvolume new_reference_volume;
+ pa_source *root_source;
+
+ pa_source_assert_ref(s);
+ pa_assert_ctl_context();
+ pa_assert(PA_SOURCE_IS_LINKED(s->state));
+ pa_assert(!volume || pa_cvolume_valid(volume));
+ pa_assert(volume || pa_source_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 in PASSTHROUGH mode ...
+ * ... *except* if we're being invoked to reset the volume to ensure 0 dB gain */
+ if (pa_source_is_passthrough(s) && (!volume || !pa_cvolume_is_norm(volume))) {
+ pa_log_warn("Cannot change volume, source is monitor of a PASSTHROUGH sink");
+ return;
+ }
+
+ /* In case of volume sharing, the volume is set for the root source first,
+ * from which it's then propagated to the sharing sources. */
+ root_source = pa_source_get_master(s);
+
+ if (PA_UNLIKELY(!root_source))
+ return;
+
+ /* As a special exception we accept mono volumes on all sources --
+ * 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_source->channel_map);
+
+ if (update_reference_volume(root_source, &new_reference_volume, &root_source->channel_map, save)) {
+ if (pa_source_flat_volume_enabled(root_source)) {
+ /* OK, propagate this volume change back to the outputs */
+ propagate_reference_volume(root_source);
+
+ /* And now recalculate the real volume */
+ compute_real_volume(root_source);
+ } else
+ update_real_volume(root_source, &root_source->reference_volume, &root_source->channel_map);
+ }
+
+ } else {
+ /* If volume is NULL we synchronize the source's real and
+ * reference volumes with the stream volumes. */
+
+ pa_assert(pa_source_flat_volume_enabled(root_source));
+
+ /* Ok, let's determine the new real volume */
+ compute_real_volume(root_source);
+
+ /* Let's 'push' the reference volume if necessary */
+ pa_cvolume_merge(&new_reference_volume, &s->reference_volume, &root_source->real_volume);
+ /* If the source and its root don't have the same number of channels, we need to remap */
+ if (s != root_source && !pa_channel_map_equal(&s->channel_map, &root_source->channel_map))
+ pa_cvolume_remap(&new_reference_volume, &s->channel_map, &root_source->channel_map);
+ update_reference_volume(root_source, &new_reference_volume, &root_source->channel_map, save);