sink, source: Rework reconfiguration logic to apply to more than rate
[platform/upstream/pulseaudio.git] / src / pulsecore / sink-input.c
index fb2a893..05fe2c0 100644 (file)
@@ -15,9 +15,7 @@
   General Public License for more details.
 
   You should have received a copy of the GNU Lesser General Public License
-  along with PulseAudio; if not, write to the Free Software
-  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
-  USA.
+  along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
 ***/
 
 #ifdef HAVE_CONFIG_H
@@ -46,7 +44,7 @@
 /* #define SINK_INPUT_DEBUG */
 
 #define MEMBLOCKQ_MAXLENGTH (32*1024*1024)
-#define CONVERT_BUFFER_LENGTH (PA_PAGE_SIZE)
+#define CONVERT_BUFFER_LENGTH (pa_page_size())
 
 PA_DEFINE_PUBLIC_CLASS(pa_sink_input, pa_msgobject);
 
@@ -179,7 +177,7 @@ void pa_sink_input_new_data_set_muted(pa_sink_input_new_data *data, bool mute) {
     pa_assert(data);
 
     data->muted_is_set = true;
-    data->muted = !!mute;
+    data->muted = mute;
 }
 
 bool pa_sink_input_new_data_set_sink(pa_sink_input_new_data *data, pa_sink *s, bool save) {
@@ -412,7 +410,7 @@ int pa_sink_input_new(
            module-suspend-on-idle can resume a sink */
 
         pa_log_info("Trying to change sample rate");
-        if (pa_sink_update_rate(data->sink, data->sample_spec.rate, pa_sink_input_new_data_is_passthrough(data)) >= 0)
+        if (pa_sink_reconfigure(data->sink, &data->sample_spec, pa_sink_input_new_data_is_passthrough(data)) >= 0)
             pa_log_info("Rate changed to %u Hz", data->sink->sample_spec.rate);
     }
 
@@ -453,10 +451,12 @@ int pa_sink_input_new(
                           core->mempool,
                           &data->sample_spec, &data->channel_map,
                           &data->sink->sample_spec, &data->sink->channel_map,
+                          core->lfe_crossover_freq,
                           data->resample_method,
                           ((data->flags & PA_SINK_INPUT_VARIABLE_RATE) ? PA_RESAMPLER_VARIABLE_RATE : 0) |
                           ((data->flags & PA_SINK_INPUT_NO_REMAP) ? PA_RESAMPLER_NO_REMAP : 0) |
                           (core->disable_remixing || (data->flags & PA_SINK_INPUT_NO_REMIX) ? PA_RESAMPLER_NO_REMIX : 0) |
+                          (core->remixing_use_all_sink_channels ? 0 : PA_RESAMPLER_NO_FILL_SINK) |
                           (core->disable_lfe_remixing ? PA_RESAMPLER_NO_LFE : 0)))) {
                 pa_log_warn("Unsupported resampling operation.");
                 return -PA_ERR_NOTSUPPORTED;
@@ -604,14 +604,26 @@ static void sink_input_set_state(pa_sink_input *i, pa_sink_input_state_t state)
     if (i->state == state)
         return;
 
-    if (i->state == PA_SINK_INPUT_CORKED && state == PA_SINK_INPUT_RUNNING && pa_sink_used_by(i->sink) == 0 &&
-        !pa_sample_spec_equal(&i->sample_spec, &i->sink->sample_spec)) {
-        /* We were uncorked and the sink was not playing anything -- let's try
-         * to update the sample rate to avoid resampling */
-        pa_sink_update_rate(i->sink, i->sample_spec.rate, pa_sink_input_is_passthrough(i));
-    }
+    if (i->sink) {
+        if (i->state == PA_SINK_INPUT_CORKED && state == PA_SINK_INPUT_RUNNING && pa_sink_used_by(i->sink) == 0 &&
+            !pa_sample_spec_equal(&i->sample_spec, &i->sink->sample_spec)) {
+            /* We were uncorked and the sink was not playing anything -- let's try
+             * to update the sample rate to avoid resampling */
+            pa_sink_reconfigure(i->sink, &i->sample_spec, pa_sink_input_is_passthrough(i));
+        }
+
+        pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_SET_STATE, PA_UINT_TO_PTR(state), 0, NULL) == 0);
+    } else {
+        /* If the sink is not valid, pa_sink_input_set_state_within_thread() must be called directly */
+
+        pa_sink_input_set_state_within_thread(i, state);
 
-    pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_SET_STATE, PA_UINT_TO_PTR(state), 0, NULL) == 0);
+        for (ssync = i->thread_info.sync_prev; ssync; ssync = ssync->thread_info.sync_prev)
+            pa_sink_input_set_state_within_thread(ssync, state);
+
+        for (ssync = i->thread_info.sync_next; ssync; ssync = ssync->thread_info.sync_next)
+            pa_sink_input_set_state_within_thread(ssync, state);
+    }
 
     update_n_corked(i, state);
     i->state = state;
@@ -638,15 +650,16 @@ static void sink_input_set_state(pa_sink_input *i, pa_sink_input_state_t state)
             pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index);
     }
 
-    pa_sink_update_status(i->sink);
+    if (i->sink)
+        pa_sink_update_status(i->sink);
 }
 
 /* Called from main context */
 void pa_sink_input_unlink(pa_sink_input *i) {
     bool linked;
-    pa_source_output *o, *p = NULL;
+    pa_source_output *o, PA_UNUSED *p = NULL;
 
-    pa_assert(i);
+    pa_sink_input_assert_ref(i);
     pa_assert_ctl_context();
 
     /* See pa_sink_unlink() for a couple of comments how this function
@@ -698,11 +711,6 @@ void pa_sink_input_unlink(pa_sink_input *i) {
 
     reset_callbacks(i);
 
-    if (linked) {
-        pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_REMOVE, i->index);
-        pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_UNLINK_POST], i);
-    }
-
     if (i->sink) {
         if (PA_SINK_IS_LINKED(pa_sink_get_state(i->sink)))
             pa_sink_update_status(i->sink);
@@ -710,6 +718,11 @@ void pa_sink_input_unlink(pa_sink_input *i) {
         i->sink = NULL;
     }
 
+    if (linked) {
+        pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_REMOVE, i->index);
+        pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_UNLINK_POST], i);
+    }
+
     pa_core_maybe_vacuum(i->core);
 
     pa_sink_input_unref(i);
@@ -722,9 +735,7 @@ static void sink_input_free(pa_object *o) {
     pa_assert(i);
     pa_assert_ctl_context();
     pa_assert(pa_sink_input_refcnt(i) == 0);
-
-    if (PA_SINK_INPUT_IS_LINKED(i->state))
-        pa_sink_input_unlink(i);
+    pa_assert(!PA_SINK_INPUT_IS_LINKED(i->state));
 
     pa_log_info("Freeing input %u \"%s\"", i->index,
                 i->proplist ? pa_strnull(pa_proplist_gets(i->proplist, PA_PROP_MEDIA_NAME)) : "");
@@ -1107,9 +1118,9 @@ void pa_sink_input_process_rewind(pa_sink_input *i, size_t nbytes /* in sink sam
             if (i->thread_info.rewrite_flush)
                 pa_memblockq_silence(i->thread_info.render_memblockq);
 
-            /* And reset the resampler */
+            /* And rewind the resampler */
             if (i->thread_info.resampler)
-                pa_resampler_reset(i->thread_info.resampler);
+                pa_resampler_rewind(i->thread_info.resampler, amount);
         }
     }
 
@@ -1260,7 +1271,7 @@ void pa_sink_input_set_volume(pa_sink_input *i, const pa_cvolume *volume, bool s
         return;
     }
 
-    i->volume = *volume;
+    pa_sink_input_set_volume_direct(i, volume);
     i->save_volume = save;
 
     if (pa_sink_flat_volume_enabled(i->sink)) {
@@ -1273,18 +1284,11 @@ void pa_sink_input_set_volume(pa_sink_input *i, const pa_cvolume *volume, bool s
         /* OK, we are in normal volume mode. The volume only affects
          * ourselves */
         set_real_ratio(i, volume);
-        i->reference_ratio = i->volume;
+        pa_sink_input_set_reference_ratio(i, &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);
     }
-
-    /* The volume changed, let's tell people so */
-    if (i->volume_changed)
-        i->volume_changed(i);
-
-    /* The virtual volume changed, let's tell people so */
-    pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index);
 }
 
 void pa_sink_input_add_volume_factor(pa_sink_input *i, const char *key, const pa_cvolume *volume_factor) {
@@ -1324,13 +1328,9 @@ int pa_sink_input_remove_volume_factor(pa_sink_input *i, const char *key) {
     pa_assert_ctl_context();
     pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
 
-    v = pa_hashmap_remove(i->volume_factor_items, key);
-
-    if (!v)
+    if (pa_hashmap_remove_and_free(i->volume_factor_items, key) < 0)
         return -1;
 
-    volume_factor_entry_free(v);
-
     switch (pa_hashmap_size(i->volume_factor_items)) {
         case 0:
             pa_cvolume_reset(&i->volume_factor, i->sample_spec.channels);
@@ -1410,16 +1410,22 @@ pa_cvolume *pa_sink_input_get_volume(pa_sink_input *i, pa_cvolume *volume, bool
 
 /* Called from main context */
 void pa_sink_input_set_mute(pa_sink_input *i, bool mute, bool save) {
+    bool old_mute;
+
     pa_sink_input_assert_ref(i);
     pa_assert_ctl_context();
     pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
 
-    if (!i->muted == !mute) {
-        i->save_muted = i->save_muted || mute;
+    old_mute = i->muted;
+
+    if (mute == old_mute) {
+        i->save_muted |= save;
         return;
     }
 
     i->muted = mute;
+    pa_log_debug("The mute of sink input %u changed from %s to %s.", i->index, pa_yes_no(old_mute), pa_yes_no(mute));
+
     i->save_muted = save;
 
     pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_SET_SOFT_MUTE, NULL, 0, NULL) == 0);
@@ -1429,28 +1435,122 @@ void pa_sink_input_set_mute(pa_sink_input *i, bool mute, bool save) {
         i->mute_changed(i);
 
     pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index);
+    pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_MUTE_CHANGED], i);
 }
 
-/* Called from main context */
-bool pa_sink_input_get_mute(pa_sink_input *i) {
-    pa_sink_input_assert_ref(i);
-    pa_assert_ctl_context();
-    pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
+void pa_sink_input_set_property(pa_sink_input *i, const char *key, const char *value) {
+    char *old_value = NULL;
+    const char *new_value;
+
+    pa_assert(i);
+    pa_assert(key);
+
+    if (pa_proplist_contains(i->proplist, key)) {
+        old_value = pa_xstrdup(pa_proplist_gets(i->proplist, key));
+        if (value && old_value && pa_streq(value, old_value))
+            goto finish;
+
+        if (!old_value)
+            old_value = pa_xstrdup("(data)");
+    } else {
+        if (!value)
+            goto finish;
+
+        old_value = pa_xstrdup("(unset)");
+    }
+
+    if (value) {
+        pa_proplist_sets(i->proplist, key, value);
+        new_value = value;
+    } else {
+        pa_proplist_unset(i->proplist, key);
+        new_value = "(unset)";
+    }
 
-    return i->muted;
+    if (PA_SINK_INPUT_IS_LINKED(i->state)) {
+        pa_log_debug("Sink input %u: proplist[%s]: %s -> %s", i->index, key, old_value, new_value);
+        pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_PROPLIST_CHANGED], i);
+        pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT | PA_SUBSCRIPTION_EVENT_CHANGE, i->index);
+    }
+
+finish:
+    pa_xfree(old_value);
+}
+
+void pa_sink_input_set_property_arbitrary(pa_sink_input *i, const char *key, const uint8_t *value, size_t nbytes) {
+    const uint8_t *old_value;
+    size_t old_nbytes;
+    const char *old_value_str;
+    const char *new_value_str;
+
+    pa_assert(i);
+    pa_assert(key);
+
+    if (pa_proplist_get(i->proplist, key, (const void **) &old_value, &old_nbytes) >= 0) {
+        if (value && nbytes == old_nbytes && !memcmp(value, old_value, nbytes))
+            return;
+
+        old_value_str = "(data)";
+
+    } else {
+        if (!value)
+            return;
+
+        old_value_str = "(unset)";
+    }
+
+    if (value) {
+        pa_proplist_set(i->proplist, key, value, nbytes);
+        new_value_str = "(data)";
+    } else {
+        pa_proplist_unset(i->proplist, key);
+        new_value_str = "(unset)";
+    }
+
+    if (PA_SINK_INPUT_IS_LINKED(i->state)) {
+        pa_log_debug("Sink input %u: proplist[%s]: %s -> %s", i->index, key, old_value_str, new_value_str);
+        pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_PROPLIST_CHANGED], i);
+        pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT | PA_SUBSCRIPTION_EVENT_CHANGE, i->index);
+    }
 }
 
 /* Called from main thread */
 void pa_sink_input_update_proplist(pa_sink_input *i, pa_update_mode_t mode, pa_proplist *p) {
+    void *state;
+    const char *key;
+    const uint8_t *value;
+    size_t nbytes;
+
     pa_sink_input_assert_ref(i);
+    pa_assert(p);
     pa_assert_ctl_context();
 
-    if (p)
-        pa_proplist_update(i->proplist, mode, p);
+    switch (mode) {
+        case PA_UPDATE_SET:
+            /* Delete everything that is not in p. */
+            for (state = NULL; (key = pa_proplist_iterate(i->proplist, &state));) {
+                if (!pa_proplist_contains(p, key))
+                    pa_sink_input_set_property(i, key, NULL);
+            }
 
-    if (PA_SINK_INPUT_IS_LINKED(i->state)) {
-        pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_PROPLIST_CHANGED], i);
-        pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index);
+            /* Fall through. */
+        case PA_UPDATE_REPLACE:
+            for (state = NULL; (key = pa_proplist_iterate(p, &state));) {
+                pa_proplist_get(p, key, (const void **) &value, &nbytes);
+                pa_sink_input_set_property_arbitrary(i, key, value, nbytes);
+            }
+
+            break;
+        case PA_UPDATE_MERGE:
+            for (state = NULL; (key = pa_proplist_iterate(p, &state));) {
+                if (pa_proplist_contains(i->proplist, key))
+                    continue;
+
+                pa_proplist_get(p, key, (const void **) &value, &nbytes);
+                pa_sink_input_set_property_arbitrary(i, key, value, nbytes);
+            }
+
+            break;
     }
 }
 
@@ -1475,38 +1575,18 @@ int pa_sink_input_set_rate(pa_sink_input *i, uint32_t rate) {
 
     i->sample_spec.rate = rate;
 
-    pa_asyncmsgq_post(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_SET_RATE, PA_UINT_TO_PTR(rate), 0, NULL, NULL);
+    if (i->sink)
+        pa_asyncmsgq_post(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_SET_RATE, PA_UINT_TO_PTR(rate), 0, NULL, NULL);
+    else {
+        i->thread_info.sample_spec.rate = rate;
+        pa_resampler_set_input_rate(i->thread_info.resampler, rate);
+    }
 
     pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index);
     return 0;
 }
 
 /* Called from main context */
-void pa_sink_input_set_name(pa_sink_input *i, const char *name) {
-    const char *old;
-    pa_sink_input_assert_ref(i);
-    pa_assert_ctl_context();
-
-    if (!name && !pa_proplist_contains(i->proplist, PA_PROP_MEDIA_NAME))
-        return;
-
-    old = pa_proplist_gets(i->proplist, PA_PROP_MEDIA_NAME);
-
-    if (old && name && pa_streq(old, name))
-        return;
-
-    if (name)
-        pa_proplist_sets(i->proplist, PA_PROP_MEDIA_NAME, name);
-    else
-        pa_proplist_unset(i->proplist, PA_PROP_MEDIA_NAME);
-
-    if (PA_SINK_INPUT_IS_LINKED(i->state)) {
-        pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_PROPLIST_CHANGED], i);
-        pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index);
-    }
-}
-
-/* Called from main context */
 pa_resample_method_t pa_sink_input_get_resample_method(pa_sink_input *i) {
     pa_sink_input_assert_ref(i);
     pa_assert_ctl_context();
@@ -1532,7 +1612,7 @@ bool pa_sink_input_may_move(pa_sink_input *i) {
 }
 
 static bool find_filter_sink_input(pa_sink_input *target, pa_sink *s) {
-    int i = 0;
+    unsigned PA_UNUSED i = 0;
     while (s && s->input_to_master) {
         if (s->input_to_master == target)
             return true;
@@ -1542,6 +1622,22 @@ static bool find_filter_sink_input(pa_sink_input *target, pa_sink *s) {
     return false;
 }
 
+static bool is_filter_sink_moving(pa_sink_input *i) {
+    pa_sink *sink = i->sink;
+
+    if (!sink)
+        return false;
+
+    while (sink->input_to_master) {
+        sink = sink->input_to_master->sink;
+
+        if (!sink)
+            return true;
+    }
+
+    return false;
+}
+
 /* Called from main context */
 bool pa_sink_input_may_move_to(pa_sink_input *i, pa_sink *dest) {
     pa_sink_input_assert_ref(i);
@@ -1552,6 +1648,9 @@ bool pa_sink_input_may_move_to(pa_sink_input *i, pa_sink *dest) {
     if (dest == i->sink)
         return true;
 
+    if (dest->unlink_requested)
+        return false;
+
     if (!pa_sink_input_may_move(i))
         return false;
 
@@ -1561,6 +1660,16 @@ bool pa_sink_input_may_move_to(pa_sink_input *i, pa_sink *dest) {
         return false;
     }
 
+    /* If this sink input is connected to a filter sink that itself is moving,
+     * then don't allow the move. Moving requires sending a message to the IO
+     * thread of the old sink, and if the old sink is a filter sink that is
+     * moving, there's no IO thread associated to the old sink. */
+    if (is_filter_sink_moving(i)) {
+        pa_log_debug("Can't move input from filter sink %s, because the filter sink itself is currently moving.",
+                     i->sink->name);
+        return false;
+    }
+
     if (pa_idxset_size(dest->inputs) >= PA_MAX_INPUTS_PER_SINK) {
         pa_log_warn("Failed to move sink input: too many inputs per sink.");
         return false;
@@ -1578,7 +1687,7 @@ bool pa_sink_input_may_move_to(pa_sink_input *i, pa_sink *dest) {
 
 /* Called from main context */
 int pa_sink_input_start_move(pa_sink_input *i) {
-    pa_source_output *o, *p = NULL;
+    pa_source_output *o, PA_UNUSED *p = NULL;
     struct volume_factor_entry *v;
     void *state = NULL;
     int r;
@@ -1594,6 +1703,8 @@ int pa_sink_input_start_move(pa_sink_input *i) {
     if ((r = pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_START], i)) < 0)
         return r;
 
+    pa_log_debug("Starting to move sink input %u from '%s'", (unsigned) i->index, i->sink->name);
+
     /* Kill directly connected outputs */
     while ((o = pa_idxset_first(i->direct_outputs, NULL))) {
         pa_assert(o != p);
@@ -1635,7 +1746,7 @@ int pa_sink_input_start_move(pa_sink_input *i) {
  * then also the origin sink and all streams connected to it need to update
  * their volume - this function does all that by using recursion. */
 static void update_volume_due_to_moving(pa_sink_input *i, pa_sink *dest) {
-    pa_cvolume old_volume;
+    pa_cvolume new_volume;
 
     pa_assert(i);
     pa_assert(dest);
@@ -1684,19 +1795,11 @@ static void update_volume_due_to_moving(pa_sink_input *i, pa_sink *dest) {
              *          always have volume_factor as soft_volume, so no change
              *          should be needed) */
 
-            old_volume = i->volume;
-            pa_cvolume_reset(&i->volume, i->volume.channels);
-            pa_cvolume_reset(&i->reference_ratio, i->reference_ratio.channels);
+            pa_cvolume_reset(&new_volume, i->volume.channels);
+            pa_sink_input_set_volume_direct(i, &new_volume);
+            pa_sink_input_set_reference_ratio(i, &new_volume);
             pa_assert(pa_cvolume_is_norm(&i->real_ratio));
             pa_assert(pa_cvolume_equal(&i->soft_volume, &i->volume_factor));
-
-            /* Notify others about the changed sink input volume. */
-            if (!pa_cvolume_equal(&i->volume, &old_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);
-            }
         }
 
         /* Additionally, the origin sink volume needs updating:
@@ -1707,33 +1810,27 @@ static void update_volume_due_to_moving(pa_sink_input *i, pa_sink *dest) {
          *         (sinks that use volume sharing should always have
          *          soft_volume of 0 dB) */
 
-        old_volume = i->origin_sink->reference_volume;
-
-        i->origin_sink->reference_volume = root_sink->reference_volume;
-        pa_cvolume_remap(&i->origin_sink->reference_volume, &root_sink->channel_map, &i->origin_sink->channel_map);
+        new_volume = root_sink->reference_volume;
+        pa_cvolume_remap(&new_volume, &root_sink->channel_map, &i->origin_sink->channel_map);
+        pa_sink_set_reference_volume_direct(i->origin_sink, &new_volume);
 
         i->origin_sink->real_volume = root_sink->real_volume;
         pa_cvolume_remap(&i->origin_sink->real_volume, &root_sink->channel_map, &i->origin_sink->channel_map);
 
         pa_assert(pa_cvolume_is_norm(&i->origin_sink->soft_volume));
 
-        /* Notify others about the changed sink volume. If you wonder whether
-         * i->origin_sink->set_volume() should be called somewhere, that's not
-         * the case, because sinks that use volume sharing shouldn't have any
-         * internal volume that set_volume() would update. If you wonder
-         * whether the thread_info variables should be synced, yes, they
-         * should, and it's done by the PA_SINK_MESSAGE_FINISH_MOVE message
-         * handler. */
-        if (!pa_cvolume_equal(&i->origin_sink->reference_volume, &old_volume))
-            pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, i->origin_sink->index);
+        /* If you wonder whether i->origin_sink->set_volume() should be called
+         * somewhere, that's not the case, because sinks that use volume
+         * sharing shouldn't have any internal volume that set_volume() would
+         * update. If you wonder whether the thread_info variables should be
+         * synced, yes, they should, and it's done by the
+         * PA_SINK_MESSAGE_FINISH_MOVE message handler. */
 
         /* Recursively update origin sink inputs. */
         PA_IDXSET_FOREACH(origin_sink_input, i->origin_sink->inputs, idx)
             update_volume_due_to_moving(origin_sink_input, dest);
 
     } else {
-        old_volume = i->volume;
-
         if (pa_sink_flat_volume_enabled(i->sink)) {
             /* Ok, so this is a regular stream, and flat volume is enabled. The
              * volume will have to be updated as follows:
@@ -1745,9 +1842,10 @@ static void update_volume_due_to_moving(pa_sink_input *i, pa_sink *dest) {
              *     i->soft_volume := i->real_ratio * i->volume_factor
              *         (handled later by pa_sink_set_volume) */
 
-            i->volume = i->sink->reference_volume;
-            pa_cvolume_remap(&i->volume, &i->sink->channel_map, &i->channel_map);
-            pa_sw_cvolume_multiply(&i->volume, &i->volume, &i->reference_ratio);
+            new_volume = i->sink->reference_volume;
+            pa_cvolume_remap(&new_volume, &i->sink->channel_map, &i->channel_map);
+            pa_sw_cvolume_multiply(&new_volume, &new_volume, &i->reference_ratio);
+            pa_sink_input_set_volume_direct(i, &new_volume);
 
         } else {
             /* Ok, so this is a regular stream, and flat volume is disabled.
@@ -1758,21 +1856,10 @@ static void update_volume_due_to_moving(pa_sink_input *i, pa_sink *dest) {
              *     i->real_ratio := i->reference_ratio
              *     i->soft_volume := i->real_ratio * i->volume_factor */
 
-            i->volume = i->reference_ratio;
+            pa_sink_input_set_volume_direct(i, &i->reference_ratio);
             i->real_ratio = i->reference_ratio;
             pa_sw_cvolume_multiply(&i->soft_volume, &i->real_ratio, &i->volume_factor);
         }
-
-        /* Notify others about the changed sink input volume. */
-        if (!pa_cvolume_equal(&i->volume, &old_volume)) {
-            /* XXX: In case i->sink has flat volume enabled, then real_ratio
-             * and soft_volume are not updated yet. Let's hope that the
-             * callback implementation doesn't care about those variables... */
-            if (i->volume_changed)
-                i->volume_changed(i);
-
-            pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index);
-        }
     }
 
     /* If i->sink == dest, then recursion has finished, and we can finally call
@@ -1813,7 +1900,7 @@ int pa_sink_input_finish_move(pa_sink_input *i, pa_sink *dest, bool save) {
            SINK_INPUT_MOVE_FINISH hook */
 
         pa_log_info("Trying to change sample rate");
-        if (pa_sink_update_rate(dest, i->sample_spec.rate, pa_sink_input_is_passthrough(i)) >= 0)
+        if (pa_sink_reconfigure(dest, &i->sample_spec, pa_sink_input_is_passthrough(i)) >= 0)
             pa_log_info("Rate changed to %u Hz", dest->sample_spec.rate);
     }
 
@@ -1904,12 +1991,11 @@ int pa_sink_input_move_to(pa_sink_input *i, pa_sink *dest, bool save) {
     return 0;
 }
 
-/* Called from IO thread context */
+/* Called from IO thread context except when cork() is called without a valid sink. */
 void pa_sink_input_set_state_within_thread(pa_sink_input *i, pa_sink_input_state_t state) {
     bool corking, uncorking;
 
     pa_sink_input_assert_ref(i);
-    pa_sink_input_assert_io_context(i);
 
     if (state == i->thread_info.state)
         return;
@@ -1930,7 +2016,8 @@ void pa_sink_input_set_state_within_thread(pa_sink_input *i, pa_sink_input_state
 
         /* This will tell the implementing sink input driver to rewind
          * so that the unplayed already mixed data is not lost */
-        pa_sink_input_request_rewind(i, 0, true, true, false);
+        if (i->sink)
+            pa_sink_input_request_rewind(i, 0, true, true, false);
 
         /* Set the corked state *after* requesting rewind */
         i->thread_info.state = state;
@@ -1948,7 +2035,8 @@ void pa_sink_input_set_state_within_thread(pa_sink_input *i, pa_sink_input_state
 
         /* OK, we're being uncorked. Make sure we're not rewound when
          * the hw buffer is remixed and request a remix. */
-        pa_sink_input_request_rewind(i, 0, false, true, true);
+        if (i->sink)
+            pa_sink_input_request_rewind(i, 0, false, true, true);
     } else
         /* We may not be corking or uncorking, but we still need to set the state. */
         i->thread_info.state = state;
@@ -1979,7 +2067,7 @@ int pa_sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int64_t
             pa_usec_t *r = userdata;
 
             r[0] += pa_bytes_to_usec(pa_memblockq_get_length(i->thread_info.render_memblockq), &i->sink->sample_spec);
-            r[1] += pa_sink_get_latency_within_thread(i->sink);
+            r[1] += pa_sink_get_latency_within_thread(i->sink, false);
 
             return 0;
         }
@@ -2049,8 +2137,8 @@ bool pa_sink_input_safe_to_remove(pa_sink_input *i) {
 void pa_sink_input_request_rewind(
         pa_sink_input *i,
         size_t nbytes  /* in our sample spec */,
-        bool rewrite,
-        bool flush,
+        bool rewrite,  /* rewrite what we have, or get fresh data? */
+        bool flush,    /* flush render memblockq? */
         bool dont_rewind_render) {
 
     size_t lbq;
@@ -2207,10 +2295,12 @@ int pa_sink_input_update_rate(pa_sink_input *i) {
         new_resampler = pa_resampler_new(i->core->mempool,
                                      &i->sample_spec, &i->channel_map,
                                      &i->sink->sample_spec, &i->sink->channel_map,
+                                     i->core->lfe_crossover_freq,
                                      i->requested_resample_method,
                                      ((i->flags & PA_SINK_INPUT_VARIABLE_RATE) ? PA_RESAMPLER_VARIABLE_RATE : 0) |
                                      ((i->flags & PA_SINK_INPUT_NO_REMAP) ? PA_RESAMPLER_NO_REMAP : 0) |
                                      (i->core->disable_remixing || (i->flags & PA_SINK_INPUT_NO_REMIX) ? PA_RESAMPLER_NO_REMIX : 0) |
+                                     (i->core->remixing_use_all_sink_channels ? 0 : PA_RESAMPLER_NO_FILL_SINK) |
                                      (i->core->disable_lfe_remixing ? PA_RESAMPLER_NO_LFE : 0));
 
         if (!new_resampler) {
@@ -2249,3 +2339,77 @@ int pa_sink_input_update_rate(pa_sink_input *i) {
 
     return 0;
 }
+
+/* Called from the IO thread. */
+void pa_sink_input_attach(pa_sink_input *i) {
+    pa_assert(i);
+    pa_assert(!i->thread_info.attached);
+
+    i->thread_info.attached = true;
+
+    if (i->attach)
+        i->attach(i);
+}
+
+/* Called from the IO thread. */
+void pa_sink_input_detach(pa_sink_input *i) {
+    pa_assert(i);
+
+    if (!i->thread_info.attached)
+        return;
+
+    i->thread_info.attached = false;
+
+    if (i->detach)
+        i->detach(i);
+}
+
+/* Called from the main thread. */
+void pa_sink_input_set_volume_direct(pa_sink_input *i, const pa_cvolume *volume) {
+    pa_cvolume old_volume;
+    char old_volume_str[PA_CVOLUME_SNPRINT_VERBOSE_MAX];
+    char new_volume_str[PA_CVOLUME_SNPRINT_VERBOSE_MAX];
+
+    pa_assert(i);
+    pa_assert(volume);
+
+    old_volume = i->volume;
+
+    if (pa_cvolume_equal(volume, &old_volume))
+        return;
+
+    i->volume = *volume;
+    pa_log_debug("The volume of sink input %u changed from %s to %s.", i->index,
+                 pa_cvolume_snprint_verbose(old_volume_str, sizeof(old_volume_str), &old_volume, &i->channel_map, true),
+                 pa_cvolume_snprint_verbose(new_volume_str, sizeof(new_volume_str), volume, &i->channel_map, true));
+
+    if (i->volume_changed)
+        i->volume_changed(i);
+
+    pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index);
+    pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_VOLUME_CHANGED], i);
+}
+
+/* Called from the main thread. */
+void pa_sink_input_set_reference_ratio(pa_sink_input *i, const pa_cvolume *ratio) {
+    pa_cvolume old_ratio;
+    char old_ratio_str[PA_CVOLUME_SNPRINT_VERBOSE_MAX];
+    char new_ratio_str[PA_CVOLUME_SNPRINT_VERBOSE_MAX];
+
+    pa_assert(i);
+    pa_assert(ratio);
+
+    old_ratio = i->reference_ratio;
+
+    if (pa_cvolume_equal(ratio, &old_ratio))
+        return;
+
+    i->reference_ratio = *ratio;
+
+    if (!PA_SINK_INPUT_IS_LINKED(i->state))
+        return;
+
+    pa_log_debug("Sink input %u reference ratio changed from %s to %s.", i->index,
+                 pa_cvolume_snprint_verbose(old_ratio_str, sizeof(old_ratio_str), &old_ratio, &i->channel_map, true),
+                 pa_cvolume_snprint_verbose(new_ratio_str, sizeof(new_ratio_str), ratio, &i->channel_map, true));
+}