+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);
+
+ /* Now that the reference volume is updated, we can update the streams'
+ * reference ratios. */
+ compute_reference_ratios(root_source);
+ }
+
+ if (root_source->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 root_source->soft_volume */
+
+ pa_cvolume_reset(&root_source->soft_volume, root_source->sample_spec.channels);
+ if (!(root_source->flags & PA_SOURCE_DEFERRED_VOLUME))
+ root_source->set_volume(root_source);
+
+ } else
+ /* If we have no function set_volume(), then the soft volume
+ * becomes the real volume */
+ root_source->soft_volume = root_source->real_volume;
+
+ /* This tells the source that soft volume and/or real volume changed */
+ if (send_msg)
+ pa_assert_se(pa_asyncmsgq_send(root_source->asyncmsgq, PA_MSGOBJECT(root_source), PA_SOURCE_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 source implementor */
+void pa_source_set_soft_volume(pa_source *s, const pa_cvolume *volume) {
+
+ pa_source_assert_ref(s);
+ pa_assert(!(s->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER));
+
+ if (s->flags & PA_SOURCE_DEFERRED_VOLUME)
+ pa_source_assert_io_context(s);
+ else
+ pa_assert_ctl_context();
+
+ if (!volume)
+ pa_cvolume_reset(&s->soft_volume, s->sample_spec.channels);
+ else
+ s->soft_volume = *volume;
+
+ if (PA_SOURCE_IS_LINKED(s->state) && !(s->flags & PA_SOURCE_DEFERRED_VOLUME))
+ pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_SET_VOLUME, NULL, 0, NULL) == 0);
+ else
+ s->thread_info.soft_volume = s->soft_volume;
+}
+
+/* Called from the main thread. Only called for the root source in volume sharing
+ * cases, except for internal recursive calls. */
+static void propagate_real_volume(pa_source *s, const pa_cvolume *old_real_volume) {
+ pa_source_output *o;
+ uint32_t idx;
+
+ pa_source_assert_ref(s);
+ pa_assert(old_real_volume);
+ pa_assert_ctl_context();
+ pa_assert(PA_SOURCE_IS_LINKED(s->state));
+
+ /* 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->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER)) {
+ if (pa_cvolume_equal(old_real_volume, &s->real_volume))
+ return;
+
+ /* 1. Make the real volume the reference volume */
+ update_reference_volume(s, &s->real_volume, &s->channel_map, true);
+ }
+
+ if (pa_source_flat_volume_enabled(s)) {
+
+ PA_IDXSET_FOREACH(o, s->outputs, idx) {
+ pa_cvolume old_volume = o->volume;
+
+ /* 2. Since the source's reference and real volumes are equal
+ * now our ratios should be too. */
+ o->reference_ratio = o->real_ratio;
+
+ /* 3. Recalculate the new stream reference volume based on the
+ * reference ratio and the sink's reference volume.
+ *
+ * This basically calculates:
+ *
+ * o->volume = s->reference_volume * o->reference_ratio
+ *
+ * This is identical to propagate_reference_volume() */
+ o->volume = s->reference_volume;
+ pa_cvolume_remap(&o->volume, &s->channel_map, &o->channel_map);
+ pa_sw_cvolume_multiply(&o->volume, &o->volume, &o->reference_ratio);
+
+ /* Notify if something changed */
+ if (!pa_cvolume_equal(&old_volume, &o->volume)) {
+
+ if (o->volume_changed)
+ o->volume_changed(o);
+
+ pa_subscription_post(o->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE, o->index);
+ }
+
+ if (o->destination_source && (o->destination_source->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER))
+ propagate_real_volume(o->destination_source, 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. */
+ if (!(s->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER))
+ s->save_volume = true;
+}
+
+/* Called from io thread */
+void pa_source_update_volume_and_mute(pa_source *s) {
+ pa_assert(s);
+ pa_source_assert_io_context(s);
+
+ pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_UPDATE_VOLUME_AND_MUTE, NULL, 0, NULL, NULL);
+}
+
+/* Called from main thread */
+const pa_cvolume *pa_source_get_volume(pa_source *s, bool force_refresh) {
+ pa_source_assert_ref(s);
+ pa_assert_ctl_context();
+ pa_assert(PA_SOURCE_IS_LINKED(s->state));
+
+ if (s->refresh_volume || force_refresh) {
+ struct pa_cvolume old_real_volume;
+
+ pa_assert(!(s->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER));
+
+ old_real_volume = s->real_volume;
+
+ if (!(s->flags & PA_SOURCE_DEFERRED_VOLUME) && s->get_volume)
+ s->get_volume(s);
+
+ pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_GET_VOLUME, NULL, 0, NULL) == 0);
+
+ 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. In volume sharing cases, only the root source may
+ * call this. */
+void pa_source_volume_changed(pa_source *s, const pa_cvolume *new_real_volume) {
+ pa_cvolume old_real_volume;
+
+ pa_source_assert_ref(s);
+ pa_assert_ctl_context();
+ pa_assert(PA_SOURCE_IS_LINKED(s->state));
+ pa_assert(!(s->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER));
+
+ /* The source implementor may call this if the volume changed to make sure everyone is notified */
+
+ old_real_volume = s->real_volume;
+ update_real_volume(s, new_real_volume, &s->channel_map);
+ propagate_real_volume(s, &old_real_volume);
+}
+
+/* Called from main thread */
+void pa_source_set_mute(pa_source *s, bool mute, bool save) {
+ bool old_muted;
+
+ pa_source_assert_ref(s);
+ pa_assert_ctl_context();
+ pa_assert(PA_SOURCE_IS_LINKED(s->state));
+
+ old_muted = s->muted;
+ s->muted = mute;
+ s->save_muted = (old_muted == s->muted && s->save_muted) || save;
+
+ if (!(s->flags & PA_SOURCE_DEFERRED_VOLUME) && s->set_mute)
+ s->set_mute(s);
+
+ pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_SET_MUTE, NULL, 0, NULL) == 0);
+
+ if (old_muted != s->muted)
+ pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+}
+
+/* Called from main thread */
+bool pa_source_get_mute(pa_source *s, bool force_refresh) {
+
+ pa_source_assert_ref(s);
+ pa_assert_ctl_context();
+ pa_assert(PA_SOURCE_IS_LINKED(s->state));
+
+ if (s->refresh_muted || force_refresh) {
+ bool old_muted = s->muted;
+
+ if (!(s->flags & PA_SOURCE_DEFERRED_VOLUME) && s->get_mute)
+ s->get_mute(s);
+
+ pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_GET_MUTE, NULL, 0, NULL) == 0);
+
+ if (old_muted != s->muted) {
+ s->save_muted = true;
+
+ pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+
+ /* Make sure the soft mute status stays in sync */
+ pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_SET_MUTE, NULL, 0, NULL) == 0);
+ }
+ }
+
+ return s->muted;
+}
+
+/* Called from main thread */
+void pa_source_mute_changed(pa_source *s, bool new_muted) {