Merge commit 'origin/master-tx'
[profile/ivi/pulseaudio-panda.git] / src / pulsecore / sink-input.c
index 9a3c49c..a5f9635 100644 (file)
@@ -6,7 +6,7 @@
 
   PulseAudio is free software; you can redistribute it and/or modify
   it under the terms of the GNU Lesser General Public License as published
 
   PulseAudio is free software; you can redistribute it and/or modify
   it under the terms of the GNU Lesser General Public License as published
-  by the Free Software Foundation; either version 2 of the License,
+  by the Free Software Foundation; either version 2.1 of the License,
   or (at your option) any later version.
 
   PulseAudio is distributed in the hope that it will be useful, but
   or (at your option) any later version.
 
   PulseAudio is distributed in the hope that it will be useful, but
@@ -30,6 +30,7 @@
 
 #include <pulse/utf8.h>
 #include <pulse/xmalloc.h>
 
 #include <pulse/utf8.h>
 #include <pulse/xmalloc.h>
+#include <pulse/util.h>
 
 #include <pulsecore/sample-util.h>
 #include <pulsecore/core-subscribe.h>
 
 #include <pulsecore/sample-util.h>
 #include <pulsecore/core-subscribe.h>
@@ -75,7 +76,19 @@ void pa_sink_input_new_data_set_volume(pa_sink_input_new_data *data, const pa_cv
     pa_assert(data);
 
     if ((data->volume_is_set = !!volume))
     pa_assert(data);
 
     if ((data->volume_is_set = !!volume))
-        data->volume = data->virtual_volume = *volume;
+        data->volume = *volume;
+}
+
+void pa_sink_input_new_data_apply_volume_factor(pa_sink_input_new_data *data, const pa_cvolume *volume_factor) {
+    pa_assert(data);
+    pa_assert(volume_factor);
+
+    if (data->volume_factor_is_set)
+        pa_sw_cvolume_multiply(&data->volume_factor, &data->volume_factor, volume_factor);
+    else {
+        data->volume_factor_is_set = TRUE;
+        data->volume_factor = *volume_factor;
+    }
 }
 
 void pa_sink_input_new_data_set_muted(pa_sink_input_new_data *data, pa_bool_t mute) {
 }
 
 void pa_sink_input_new_data_set_muted(pa_sink_input_new_data *data, pa_bool_t mute) {
@@ -104,14 +117,18 @@ static void reset_callbacks(pa_sink_input *i) {
     i->attach = NULL;
     i->detach = NULL;
     i->suspend = NULL;
     i->attach = NULL;
     i->detach = NULL;
     i->suspend = NULL;
-    i->moved = NULL;
+    i->suspend_within_thread = NULL;
+    i->moving = NULL;
     i->kill = NULL;
     i->get_latency = NULL;
     i->state_change = NULL;
     i->kill = NULL;
     i->get_latency = NULL;
     i->state_change = NULL;
+    i->may_move_to = NULL;
+    i->send_event = NULL;
 }
 
 /* Called from main context */
 }
 
 /* Called from main context */
-pa_sink_input* pa_sink_input_new(
+int pa_sink_input_new(
+        pa_sink_input **_i,
         pa_core *core,
         pa_sink_input_new_data *data,
         pa_sink_input_flags_t flags) {
         pa_core *core,
         pa_sink_input_new_data *data,
         pa_sink_input_flags_t flags) {
@@ -119,44 +136,59 @@ pa_sink_input* pa_sink_input_new(
     pa_sink_input *i;
     pa_resampler *resampler = NULL;
     char st[PA_SAMPLE_SPEC_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX];
     pa_sink_input *i;
     pa_resampler *resampler = NULL;
     char st[PA_SAMPLE_SPEC_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX];
+    pa_channel_map original_cm;
+    int r;
 
 
+    pa_assert(_i);
     pa_assert(core);
     pa_assert(data);
 
     pa_assert(core);
     pa_assert(data);
 
-    if (pa_hook_fire(&core->hooks[PA_CORE_HOOK_SINK_INPUT_NEW], data) < 0)
-        return NULL;
+    if (data->client)
+        pa_proplist_update(data->proplist, PA_UPDATE_MERGE, data->client->proplist);
+
+    if ((r = pa_hook_fire(&core->hooks[PA_CORE_HOOK_SINK_INPUT_NEW], data)) < 0)
+        return r;
 
 
-    pa_return_null_if_fail(!data->driver || pa_utf8_valid(data->driver));
+    pa_return_val_if_fail(!data->driver || pa_utf8_valid(data->driver), -PA_ERR_INVALID);
 
 
-    if (!data->sink)
-        data->sink = pa_namereg_get(core, NULL, PA_NAMEREG_SINK, TRUE);
+    if (!data->sink) {
+        data->sink = pa_namereg_get(core, NULL, PA_NAMEREG_SINK);
+        data->save_sink = FALSE;
+    }
 
 
-    pa_return_null_if_fail(data->sink);
-    pa_return_null_if_fail(pa_sink_get_state(data->sink) != PA_SINK_UNLINKED);
-    pa_return_null_if_fail(!data->sync_base || (data->sync_base->sink == data->sink && pa_sink_input_get_state(data->sync_base) == PA_SINK_INPUT_CORKED));
+    pa_return_val_if_fail(data->sink, -PA_ERR_NOENTITY);
+    pa_return_val_if_fail(PA_SINK_IS_LINKED(pa_sink_get_state(data->sink)), -PA_ERR_BADSTATE);
+    pa_return_val_if_fail(!data->sync_base || (data->sync_base->sink == data->sink && pa_sink_input_get_state(data->sync_base) == PA_SINK_INPUT_CORKED), -PA_ERR_INVALID);
 
     if (!data->sample_spec_is_set)
         data->sample_spec = data->sink->sample_spec;
 
 
     if (!data->sample_spec_is_set)
         data->sample_spec = data->sink->sample_spec;
 
-    pa_return_null_if_fail(pa_sample_spec_valid(&data->sample_spec));
+    pa_return_val_if_fail(pa_sample_spec_valid(&data->sample_spec), -PA_ERR_INVALID);
 
     if (!data->channel_map_is_set) {
 
     if (!data->channel_map_is_set) {
-        if (data->sink->channel_map.channels == data->sample_spec.channels)
+        if (pa_channel_map_compatible(&data->sink->channel_map, &data->sample_spec))
             data->channel_map = data->sink->channel_map;
         else
             data->channel_map = data->sink->channel_map;
         else
-            pa_return_null_if_fail(pa_channel_map_init_auto(&data->channel_map, data->sample_spec.channels, PA_CHANNEL_MAP_DEFAULT));
+            pa_channel_map_init_extend(&data->channel_map, data->sample_spec.channels, PA_CHANNEL_MAP_DEFAULT);
     }
 
     }
 
-    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);
+    pa_return_val_if_fail(pa_channel_map_valid(&data->channel_map), -PA_ERR_INVALID);
+    pa_return_val_if_fail(pa_channel_map_compatible(&data->channel_map, &data->sample_spec), -PA_ERR_INVALID);
 
 
-    if (!data->volume_is_set)
+    if (!data->volume_is_set) {
         pa_cvolume_reset(&data->volume, data->sample_spec.channels);
         pa_cvolume_reset(&data->volume, data->sample_spec.channels);
+        data->volume_is_absolute = FALSE;
+        data->save_volume = FALSE;
+    }
 
 
-    pa_return_null_if_fail(pa_cvolume_valid(&data->volume));
-    pa_return_null_if_fail(data->volume.channels == data->sample_spec.channels);
+    pa_return_val_if_fail(pa_cvolume_valid(&data->volume), -PA_ERR_INVALID);
+    pa_return_val_if_fail(pa_cvolume_compatible(&data->volume, &data->sample_spec), -PA_ERR_INVALID);
 
 
-    data->virtual_volume = data->volume;
+    if (!data->volume_factor_is_set)
+        pa_cvolume_reset(&data->volume_factor, data->sample_spec.channels);
+
+    pa_return_val_if_fail(pa_cvolume_valid(&data->volume_factor), -PA_ERR_INVALID);
+    pa_return_val_if_fail(pa_cvolume_compatible(&data->volume_factor, &data->sample_spec), -PA_ERR_INVALID);
 
     if (!data->muted_is_set)
         data->muted = FALSE;
 
     if (!data->muted_is_set)
         data->muted = FALSE;
@@ -167,6 +199,8 @@ pa_sink_input* pa_sink_input_new(
     if (flags & PA_SINK_INPUT_FIX_RATE)
         data->sample_spec.rate = data->sink->sample_spec.rate;
 
     if (flags & PA_SINK_INPUT_FIX_RATE)
         data->sample_spec.rate = data->sink->sample_spec.rate;
 
+    original_cm = data->channel_map;
+
     if (flags & PA_SINK_INPUT_FIX_CHANNELS) {
         data->sample_spec.channels = data->sink->sample_spec.channels;
         data->channel_map = data->sink->channel_map;
     if (flags & PA_SINK_INPUT_FIX_CHANNELS) {
         data->sample_spec.channels = data->sink->sample_spec.channels;
         data->channel_map = data->sink->channel_map;
@@ -176,20 +210,25 @@ pa_sink_input* pa_sink_input_new(
     pa_assert(pa_channel_map_valid(&data->channel_map));
 
     /* Due to the fixing of the sample spec the volume might not match anymore */
     pa_assert(pa_channel_map_valid(&data->channel_map));
 
     /* Due to the fixing of the sample spec the volume might not match anymore */
-    if (data->volume.channels != data->sample_spec.channels)
-        pa_cvolume_set(&data->volume, data->sample_spec.channels, pa_cvolume_avg(&data->volume));
+    pa_cvolume_remap(&data->volume, &original_cm, &data->channel_map);
 
     if (data->resample_method == PA_RESAMPLER_INVALID)
         data->resample_method = core->resample_method;
 
 
     if (data->resample_method == PA_RESAMPLER_INVALID)
         data->resample_method = core->resample_method;
 
-    pa_return_null_if_fail(data->resample_method < PA_RESAMPLER_MAX);
+    pa_return_val_if_fail(data->resample_method < PA_RESAMPLER_MAX, -PA_ERR_INVALID);
 
 
-    if (pa_hook_fire(&core->hooks[PA_CORE_HOOK_SINK_INPUT_FIXATE], data) < 0)
-        return NULL;
+    if ((r = pa_hook_fire(&core->hooks[PA_CORE_HOOK_SINK_INPUT_FIXATE], data)) < 0)
+        return r;
+
+    if ((flags & PA_SINK_INPUT_FAIL_ON_SUSPEND) &&
+        pa_sink_get_state(data->sink) == PA_SINK_SUSPENDED) {
+        pa_log_warn("Failed to create sink input: sink is suspended.");
+        return -PA_ERR_BADSTATE;
+    }
 
     if (pa_idxset_size(data->sink->inputs) >= PA_MAX_INPUTS_PER_SINK) {
         pa_log_warn("Failed to create sink input: too many inputs per sink.");
 
     if (pa_idxset_size(data->sink->inputs) >= PA_MAX_INPUTS_PER_SINK) {
         pa_log_warn("Failed to create sink input: too many inputs per sink.");
-        return NULL;
+        return -PA_ERR_TOOLARGE;
     }
 
     if ((flags & PA_SINK_INPUT_VARIABLE_RATE) ||
     }
 
     if ((flags & PA_SINK_INPUT_VARIABLE_RATE) ||
@@ -206,10 +245,8 @@ pa_sink_input* pa_sink_input_new(
                       (core->disable_remixing || (flags & PA_SINK_INPUT_NO_REMIX) ? PA_RESAMPLER_NO_REMIX : 0) |
                       (core->disable_lfe_remixing ? PA_RESAMPLER_NO_LFE : 0)))) {
             pa_log_warn("Unsupported resampling operation.");
                       (core->disable_remixing || (flags & PA_SINK_INPUT_NO_REMIX) ? PA_RESAMPLER_NO_REMIX : 0) |
                       (core->disable_lfe_remixing ? PA_RESAMPLER_NO_LFE : 0)))) {
             pa_log_warn("Unsupported resampling operation.");
-            return NULL;
+            return -PA_ERR_NOTSUPPORTED;
         }
         }
-
-        data->resample_method = pa_resampler_get_method(resampler);
     }
 
     i = pa_msgobject_new(pa_sink_input);
     }
 
     i = pa_msgobject_new(pa_sink_input);
@@ -220,17 +257,32 @@ pa_sink_input* pa_sink_input_new(
     i->state = PA_SINK_INPUT_INIT;
     i->flags = flags;
     i->proplist = pa_proplist_copy(data->proplist);
     i->state = PA_SINK_INPUT_INIT;
     i->flags = flags;
     i->proplist = pa_proplist_copy(data->proplist);
-    i->driver = pa_xstrdup(data->driver);
+    i->driver = pa_xstrdup(pa_path_get_filename(data->driver));
     i->module = data->module;
     i->sink = data->sink;
     i->client = data->client;
 
     i->module = data->module;
     i->sink = data->sink;
     i->client = data->client;
 
-    i->resample_method = data->resample_method;
+    i->requested_resample_method = data->resample_method;
+    i->actual_resample_method = resampler ? pa_resampler_get_method(resampler) : PA_RESAMPLER_INVALID;
     i->sample_spec = data->sample_spec;
     i->channel_map = data->channel_map;
 
     i->sample_spec = data->sample_spec;
     i->channel_map = data->channel_map;
 
-    i->virtual_volume = data->virtual_volume;
-    i->volume = data->volume;
+    if ((i->sink->flags & PA_SINK_FLAT_VOLUME) && !data->volume_is_absolute) {
+        /* When the 'absolute' bool is not set then we'll treat the volume
+         * as relative to the sink volume even in flat volume mode */
+
+        pa_cvolume v = data->sink->reference_volume;
+        pa_cvolume_remap(&v, &data->sink->channel_map, &data->channel_map);
+        pa_sw_cvolume_multiply(&i->virtual_volume, &data->volume, &v);
+    } else
+        i->virtual_volume = data->volume;
+
+    i->volume_factor = data->volume_factor;
+    pa_cvolume_init(&i->soft_volume);
+    memset(i->relative_volume, 0, sizeof(i->relative_volume));
+    i->save_volume = data->save_volume;
+    i->save_sink = data->save_sink;
+    i->save_muted = data->save_muted;
 
     i->muted = data->muted;
 
 
     i->muted = data->muted;
 
@@ -254,11 +306,12 @@ pa_sink_input* pa_sink_input_new(
     pa_atomic_store(&i->thread_info.drained, 1);
     i->thread_info.sample_spec = i->sample_spec;
     i->thread_info.resampler = resampler;
     pa_atomic_store(&i->thread_info.drained, 1);
     i->thread_info.sample_spec = i->sample_spec;
     i->thread_info.resampler = resampler;
-    i->thread_info.volume = i->volume;
+    i->thread_info.soft_volume = i->soft_volume;
     i->thread_info.muted = i->muted;
     i->thread_info.requested_sink_latency = (pa_usec_t) -1;
     i->thread_info.rewrite_nbytes = 0;
     i->thread_info.rewrite_flush = FALSE;
     i->thread_info.muted = i->muted;
     i->thread_info.requested_sink_latency = (pa_usec_t) -1;
     i->thread_info.rewrite_nbytes = 0;
     i->thread_info.rewrite_flush = FALSE;
+    i->thread_info.dont_rewind_render = FALSE;
     i->thread_info.underrun_for = (uint64_t) -1;
     i->thread_info.playing_for = 0;
     i->thread_info.direct_outputs = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
     i->thread_info.underrun_for = (uint64_t) -1;
     i->thread_info.playing_for = 0;
     i->thread_info.direct_outputs = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
@@ -273,8 +326,11 @@ pa_sink_input* pa_sink_input_new(
             0,
             &i->sink->silence);
 
             0,
             &i->sink->silence);
 
-    pa_assert_se(pa_idxset_put(core->sink_inputs, pa_sink_input_ref(i), &i->index) == 0);
-    pa_assert_se(pa_idxset_put(i->sink->inputs, i, NULL) == 0);
+    pa_assert_se(pa_idxset_put(core->sink_inputs, i, &i->index) == 0);
+    pa_assert_se(pa_idxset_put(i->sink->inputs, pa_sink_input_ref(i), NULL) == 0);
+
+    if (i->client)
+        pa_assert_se(pa_idxset_put(i->client->sink_inputs, i, NULL) >= 0);
 
     pa_log_info("Created input %u \"%s\" on %s with sample spec %s and channel map %s",
                 i->index,
 
     pa_log_info("Created input %u \"%s\" on %s with sample spec %s and channel map %s",
                 i->index,
@@ -285,23 +341,25 @@ pa_sink_input* pa_sink_input_new(
 
     /* Don't forget to call pa_sink_input_put! */
 
 
     /* Don't forget to call pa_sink_input_put! */
 
-    return i;
+    *_i = i;
+    return 0;
 }
 
 /* Called from main context */
 static void update_n_corked(pa_sink_input *i, pa_sink_input_state_t state) {
     pa_assert(i);
 
 }
 
 /* Called from main context */
 static void update_n_corked(pa_sink_input *i, pa_sink_input_state_t state) {
     pa_assert(i);
 
+    if (!i->sink)
+        return;
+
     if (i->state == PA_SINK_INPUT_CORKED && state != PA_SINK_INPUT_CORKED)
         pa_assert_se(i->sink->n_corked -- >= 1);
     else if (i->state != PA_SINK_INPUT_CORKED && state == PA_SINK_INPUT_CORKED)
         i->sink->n_corked++;
     if (i->state == PA_SINK_INPUT_CORKED && state != PA_SINK_INPUT_CORKED)
         pa_assert_se(i->sink->n_corked -- >= 1);
     else if (i->state != PA_SINK_INPUT_CORKED && state == PA_SINK_INPUT_CORKED)
         i->sink->n_corked++;
-
-    pa_sink_update_status(i->sink);
 }
 
 /* Called from main context */
 }
 
 /* Called from main context */
-static int sink_input_set_state(pa_sink_input *i, pa_sink_input_state_t state) {
+static void sink_input_set_state(pa_sink_input *i, pa_sink_input_state_t state) {
     pa_sink_input *ssync;
     pa_assert(i);
 
     pa_sink_input *ssync;
     pa_assert(i);
 
@@ -309,7 +367,7 @@ static int sink_input_set_state(pa_sink_input *i, pa_sink_input_state_t state) {
         state = PA_SINK_INPUT_RUNNING;
 
     if (i->state == state)
         state = PA_SINK_INPUT_RUNNING;
 
     if (i->state == state)
-        return 0;
+        return;
 
     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);
 
 
     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);
 
@@ -326,16 +384,16 @@ static int sink_input_set_state(pa_sink_input *i, pa_sink_input_state_t state) {
     }
 
     if (state != PA_SINK_INPUT_UNLINKED) {
     }
 
     if (state != PA_SINK_INPUT_UNLINKED) {
-        pa_hook_fire(&i->sink->core->hooks[PA_CORE_HOOK_SINK_INPUT_STATE_CHANGED], i);
+        pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_STATE_CHANGED], i);
 
         for (ssync = i->sync_prev; ssync; ssync = ssync->sync_prev)
 
         for (ssync = i->sync_prev; ssync; ssync = ssync->sync_prev)
-            pa_hook_fire(&i->sink->core->hooks[PA_CORE_HOOK_SINK_INPUT_STATE_CHANGED], ssync);
+            pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_STATE_CHANGED], ssync);
 
         for (ssync = i->sync_next; ssync; ssync = ssync->sync_next)
 
         for (ssync = i->sync_next; ssync; ssync = ssync->sync_next)
-            pa_hook_fire(&i->sink->core->hooks[PA_CORE_HOOK_SINK_INPUT_STATE_CHANGED], ssync);
+            pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_STATE_CHANGED], ssync);
     }
 
     }
 
-    return 0;
+    pa_sink_update_status(i->sink);
 }
 
 /* Called from main context */
 }
 
 /* Called from main context */
@@ -352,7 +410,7 @@ void pa_sink_input_unlink(pa_sink_input *i) {
     linked = PA_SINK_INPUT_IS_LINKED(i->state);
 
     if (linked)
     linked = PA_SINK_INPUT_IS_LINKED(i->state);
 
     if (linked)
-        pa_hook_fire(&i->sink->core->hooks[PA_CORE_HOOK_SINK_INPUT_UNLINK], i);
+        pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_UNLINK], i);
 
     if (i->sync_prev)
         i->sync_prev->sync_next = i->sync_next;
 
     if (i->sync_prev)
         i->sync_prev->sync_next = i->sync_next;
@@ -361,9 +419,14 @@ void pa_sink_input_unlink(pa_sink_input *i) {
 
     i->sync_prev = i->sync_next = NULL;
 
 
     i->sync_prev = i->sync_next = NULL;
 
-    pa_idxset_remove_by_data(i->sink->core->sink_inputs, i, NULL);
-    if (pa_idxset_remove_by_data(i->sink->inputs, i, NULL))
-        pa_sink_input_unref(i);
+    pa_idxset_remove_by_data(i->core->sink_inputs, i, NULL);
+
+    if (i->sink)
+        if (pa_idxset_remove_by_data(i->sink->inputs, i, NULL))
+            pa_sink_input_unref(i);
+
+    if (i->client)
+        pa_idxset_remove_by_data(i->client->sink_inputs, i, NULL);
 
     while ((o = pa_idxset_first(i->direct_outputs, NULL))) {
         pa_assert(o != p);
 
     while ((o = pa_idxset_first(i->direct_outputs, NULL))) {
         pa_assert(o != p);
@@ -374,18 +437,32 @@ void pa_sink_input_unlink(pa_sink_input *i) {
     update_n_corked(i, PA_SINK_INPUT_UNLINKED);
     i->state = PA_SINK_INPUT_UNLINKED;
 
     update_n_corked(i, PA_SINK_INPUT_UNLINKED);
     i->state = PA_SINK_INPUT_UNLINKED;
 
-    if (linked)
+    if (linked && i->sink) {
+        /* We might need to update the sink's volume if we are in flat volume mode. */
+        if (i->sink->flags & PA_SINK_FLAT_VOLUME) {
+            pa_cvolume new_volume;
+            pa_sink_update_flat_volume(i->sink, &new_volume);
+            pa_sink_set_volume(i->sink, &new_volume, FALSE, FALSE, FALSE, FALSE);
+        }
+
         if (i->sink->asyncmsgq)
             pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i->sink), PA_SINK_MESSAGE_REMOVE_INPUT, i, 0, NULL) == 0);
         if (i->sink->asyncmsgq)
             pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i->sink), PA_SINK_MESSAGE_REMOVE_INPUT, i, 0, NULL) == 0);
+    }
 
     reset_callbacks(i);
 
     if (linked) {
 
     reset_callbacks(i);
 
     if (linked) {
-        pa_subscription_post(i->sink->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_REMOVE, i->index);
-        pa_hook_fire(&i->sink->core->hooks[PA_CORE_HOOK_SINK_INPUT_UNLINK_POST], i);
+        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);
     }
 
     }
 
-    i->sink = NULL;
+    if (i->sink) {
+        pa_sink_update_status(i->sink);
+        i->sink = NULL;
+    }
+
+    pa_core_maybe_vacuum(i->core);
+
     pa_sink_input_unref(i);
 }
 
     pa_sink_input_unref(i);
 }
 
@@ -434,18 +511,28 @@ void pa_sink_input_put(pa_sink_input *i) {
     pa_assert(i->process_rewind);
     pa_assert(i->kill);
 
     pa_assert(i->process_rewind);
     pa_assert(i->kill);
 
-    i->thread_info.volume = i->volume;
-    i->thread_info.muted = i->muted;
-
     state = i->flags & PA_SINK_INPUT_START_CORKED ? PA_SINK_INPUT_CORKED : PA_SINK_INPUT_RUNNING;
 
     update_n_corked(i, state);
     i->state = state;
 
     state = i->flags & PA_SINK_INPUT_START_CORKED ? PA_SINK_INPUT_CORKED : PA_SINK_INPUT_RUNNING;
 
     update_n_corked(i, state);
     i->state = state;
 
+    /* We might need to update the sink's volume if we are in flat volume mode. */
+    if (i->sink->flags & PA_SINK_FLAT_VOLUME) {
+        pa_cvolume new_volume;
+        pa_sink_update_flat_volume(i->sink, &new_volume);
+        pa_sink_set_volume(i->sink, &new_volume, FALSE, FALSE, FALSE, FALSE);
+    } else
+        pa_sink_input_set_relative_volume(i, &i->virtual_volume);
+
+    i->thread_info.soft_volume = i->soft_volume;
+    i->thread_info.muted = i->muted;
+
     pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i->sink), PA_SINK_MESSAGE_ADD_INPUT, i, 0, NULL) == 0);
 
     pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i->sink), PA_SINK_MESSAGE_ADD_INPUT, i, 0, NULL) == 0);
 
-    pa_subscription_post(i->sink->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_NEW, i->index);
-    pa_hook_fire(&i->sink->core->hooks[PA_CORE_HOOK_SINK_INPUT_PUT], i);
+    pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_NEW, i->index);
+    pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_PUT], i);
+
+    pa_sink_update_status(i->sink);
 }
 
 /* Called from main context */
 }
 
 /* Called from main context */
@@ -475,7 +562,7 @@ pa_usec_t pa_sink_input_get_latency(pa_sink_input *i, pa_usec_t *sink_latency) {
 }
 
 /* Called from thread context */
 }
 
 /* Called from thread context */
-int pa_sink_input_peek(pa_sink_input *i, size_t slength /* in sink frames */, pa_memchunk *chunk, pa_cvolume *volume) {
+void pa_sink_input_peek(pa_sink_input *i, size_t slength /* in sink frames */, pa_memchunk *chunk, pa_cvolume *volume) {
     pa_bool_t do_volume_adj_here;
     pa_bool_t volume_is_norm;
     size_t block_size_max_sink, block_size_max_sink_input;
     pa_bool_t do_volume_adj_here;
     pa_bool_t volume_is_norm;
     size_t block_size_max_sink, block_size_max_sink_input;
@@ -489,18 +576,15 @@ int pa_sink_input_peek(pa_sink_input *i, size_t slength /* in sink frames */, pa
 
 /*     pa_log_debug("peek"); */
 
 
 /*     pa_log_debug("peek"); */
 
-    if (!i->pop)
-        return -1;
-
     pa_assert(i->thread_info.state == PA_SINK_INPUT_RUNNING ||
               i->thread_info.state == PA_SINK_INPUT_CORKED ||
               i->thread_info.state == PA_SINK_INPUT_DRAINED);
 
     block_size_max_sink_input = i->thread_info.resampler ?
         pa_resampler_max_block_size(i->thread_info.resampler) :
     pa_assert(i->thread_info.state == PA_SINK_INPUT_RUNNING ||
               i->thread_info.state == PA_SINK_INPUT_CORKED ||
               i->thread_info.state == PA_SINK_INPUT_DRAINED);
 
     block_size_max_sink_input = i->thread_info.resampler ?
         pa_resampler_max_block_size(i->thread_info.resampler) :
-        pa_frame_align(pa_mempool_block_size_max(i->sink->core->mempool), &i->sample_spec);
+        pa_frame_align(pa_mempool_block_size_max(i->core->mempool), &i->sample_spec);
 
 
-    block_size_max_sink = pa_frame_align(pa_mempool_block_size_max(i->sink->core->mempool), &i->sink->sample_spec);
+    block_size_max_sink = pa_frame_align(pa_mempool_block_size_max(i->core->mempool), &i->sink->sample_spec);
 
     /* Default buffer size */
     if (slength <= 0)
 
     /* Default buffer size */
     if (slength <= 0)
@@ -525,7 +609,7 @@ int pa_sink_input_peek(pa_sink_input *i, size_t slength /* in sink frames */, pa
      * it after and leave it for the sink code */
 
     do_volume_adj_here = !pa_channel_map_equal(&i->channel_map, &i->sink->channel_map);
      * it after and leave it for the sink code */
 
     do_volume_adj_here = !pa_channel_map_equal(&i->channel_map, &i->sink->channel_map);
-    volume_is_norm = pa_cvolume_is_norm(&i->thread_info.volume) && !i->thread_info.muted;
+    volume_is_norm = pa_cvolume_is_norm(&i->thread_info.soft_volume) && !i->thread_info.muted;
 
     while (!pa_memblockq_is_readable(i->thread_info.render_memblockq)) {
         pa_memchunk tchunk;
 
     while (!pa_memblockq_is_readable(i->thread_info.render_memblockq)) {
         pa_memchunk tchunk;
@@ -540,7 +624,7 @@ int pa_sink_input_peek(pa_sink_input *i, size_t slength /* in sink frames */, pa
              * data, so let's just hand out silence */
             pa_atomic_store(&i->thread_info.drained, 1);
 
              * data, so let's just hand out silence */
             pa_atomic_store(&i->thread_info.drained, 1);
 
-            pa_memblockq_seek(i->thread_info.render_memblockq, (int64_t) slength, PA_SEEK_RELATIVE);
+            pa_memblockq_seek(i->thread_info.render_memblockq, (int64_t) slength, PA_SEEK_RELATIVE, TRUE);
             i->thread_info.playing_for = 0;
             if (i->thread_info.underrun_for != (uint64_t) -1)
                 i->thread_info.underrun_for += ilength;
             i->thread_info.playing_for = 0;
             if (i->thread_info.underrun_for != (uint64_t) -1)
                 i->thread_info.underrun_for += ilength;
@@ -571,7 +655,7 @@ int pa_sink_input_peek(pa_sink_input *i, size_t slength /* in sink frames */, pa
                 if (i->thread_info.muted)
                     pa_silence_memchunk(&wchunk, &i->thread_info.sample_spec);
                 else
                 if (i->thread_info.muted)
                     pa_silence_memchunk(&wchunk, &i->thread_info.sample_spec);
                 else
-                    pa_volume_memchunk(&wchunk, &i->thread_info.sample_spec, &i->thread_info.volume);
+                    pa_volume_memchunk(&wchunk, &i->thread_info.sample_spec, &i->thread_info.soft_volume);
             }
 
             if (!i->thread_info.resampler)
             }
 
             if (!i->thread_info.resampler)
@@ -617,9 +701,7 @@ int pa_sink_input_peek(pa_sink_input *i, size_t slength /* in sink frames */, pa
         /* We've both the same channel map, so let's have the sink do the adjustment for us*/
         pa_cvolume_mute(volume, i->sink->sample_spec.channels);
     else
         /* We've both the same channel map, so let's have the sink do the adjustment for us*/
         pa_cvolume_mute(volume, i->sink->sample_spec.channels);
     else
-        *volume = i->thread_info.volume;
-
-    return 0;
+        *volume = i->thread_info.soft_volume;
 }
 
 /* Called from thread context */
 }
 
 /* Called from thread context */
@@ -648,7 +730,7 @@ void pa_sink_input_process_rewind(pa_sink_input *i, size_t nbytes /* in sink sam
 
     lbq = pa_memblockq_get_length(i->thread_info.render_memblockq);
 
 
     lbq = pa_memblockq_get_length(i->thread_info.render_memblockq);
 
-    if (nbytes > 0) {
+    if (nbytes > 0 && !i->thread_info.dont_rewind_render) {
         pa_log_debug("Have to rewind %lu bytes on render memblockq.", (unsigned long) nbytes);
         pa_memblockq_rewind(i->thread_info.render_memblockq, nbytes);
     }
         pa_log_debug("Have to rewind %lu bytes on render memblockq.", (unsigned long) nbytes);
         pa_memblockq_rewind(i->thread_info.render_memblockq, nbytes);
     }
@@ -687,7 +769,7 @@ void pa_sink_input_process_rewind(pa_sink_input *i, size_t nbytes /* in sink sam
 
             if (amount > 0)
                 /* Ok, now update the write pointer */
 
             if (amount > 0)
                 /* Ok, now update the write pointer */
-                pa_memblockq_seek(i->thread_info.render_memblockq, - ((int64_t) amount), PA_SEEK_RELATIVE);
+                pa_memblockq_seek(i->thread_info.render_memblockq, - ((int64_t) amount), PA_SEEK_RELATIVE, TRUE);
 
             if (i->thread_info.rewrite_flush)
                 pa_memblockq_silence(i->thread_info.render_memblockq);
 
             if (i->thread_info.rewrite_flush)
                 pa_memblockq_silence(i->thread_info.render_memblockq);
@@ -704,6 +786,7 @@ void pa_sink_input_process_rewind(pa_sink_input *i, size_t nbytes /* in sink sam
 
     i->thread_info.rewrite_nbytes = 0;
     i->thread_info.rewrite_flush = FALSE;
 
     i->thread_info.rewrite_nbytes = 0;
     i->thread_info.rewrite_flush = FALSE;
+    i->thread_info.dont_rewind_render = FALSE;
 }
 
 /* Called from thread context */
 }
 
 /* Called from thread context */
@@ -729,26 +812,15 @@ void pa_sink_input_update_max_request(pa_sink_input *i, size_t nbytes  /* in the
 }
 
 /* Called from thread context */
 }
 
 /* Called from thread context */
-static pa_usec_t fixup_latency(pa_sink *s, pa_usec_t usec) {
-    pa_sink_assert_ref(s);
-
-    if (usec == (pa_usec_t) -1)
-        return usec;
-
-    if (s->thread_info.max_latency > 0 && usec > s->thread_info.max_latency)
-        usec = s->thread_info.max_latency;
-
-    if (s->thread_info.min_latency > 0 && usec < s->thread_info.min_latency)
-        usec = s->thread_info.min_latency;
-
-    return usec;
-}
-
-/* Called from thread context */
 pa_usec_t pa_sink_input_set_requested_latency_within_thread(pa_sink_input *i, pa_usec_t usec) {
     pa_sink_input_assert_ref(i);
 
 pa_usec_t pa_sink_input_set_requested_latency_within_thread(pa_sink_input *i, pa_usec_t usec) {
     pa_sink_input_assert_ref(i);
 
-    usec = fixup_latency(i->sink, usec);
+    if (!(i->sink->flags & PA_SINK_DYNAMIC_LATENCY))
+        usec = i->sink->fixed_latency;
+
+    if (usec != (pa_usec_t) -1)
+        usec = PA_CLAMP(usec, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency);
+
     i->thread_info.requested_sink_latency = usec;
     pa_sink_invalidate_requested_latency(i->sink);
 
     i->thread_info.requested_sink_latency = usec;
     pa_sink_invalidate_requested_latency(i->sink);
 
@@ -759,71 +831,160 @@ pa_usec_t pa_sink_input_set_requested_latency_within_thread(pa_sink_input *i, pa
 pa_usec_t pa_sink_input_set_requested_latency(pa_sink_input *i, pa_usec_t usec) {
     pa_sink_input_assert_ref(i);
 
 pa_usec_t pa_sink_input_set_requested_latency(pa_sink_input *i, pa_usec_t usec) {
     pa_sink_input_assert_ref(i);
 
-    if (PA_SINK_INPUT_IS_LINKED(i->state))
+    if (PA_SINK_INPUT_IS_LINKED(i->state) && i->sink) {
         pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_SET_REQUESTED_LATENCY, &usec, 0, NULL) == 0);
         pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_SET_REQUESTED_LATENCY, &usec, 0, NULL) == 0);
-    else
-        /* If this sink input is not realized yet, we have to touch
-         * the thread info data directly */
+        return usec;
+    }
+
+    /* If this sink input is not realized yet or we are being moved,
+     * we have to touch the thread info data directly */
 
 
-        i->thread_info.requested_sink_latency = usec;
+    if (i->sink) {
+        if (!(i->sink->flags & PA_SINK_DYNAMIC_LATENCY))
+            usec = i->sink->fixed_latency;
+
+        if (usec != (pa_usec_t) -1) {
+            pa_usec_t min_latency, max_latency;
+            pa_sink_get_latency_range(i->sink, &min_latency, &max_latency);
+            usec =  PA_CLAMP(usec, min_latency, max_latency);
+        }
+    }
+
+    i->thread_info.requested_sink_latency = usec;
 
     return usec;
 }
 
 /* Called from main context */
 pa_usec_t pa_sink_input_get_requested_latency(pa_sink_input *i) {
 
     return usec;
 }
 
 /* Called from main context */
 pa_usec_t pa_sink_input_get_requested_latency(pa_sink_input *i) {
-    pa_usec_t usec = 0;
-
     pa_sink_input_assert_ref(i);
 
     pa_sink_input_assert_ref(i);
 
-    if (PA_SINK_INPUT_IS_LINKED(i->state))
+    if (PA_SINK_INPUT_IS_LINKED(i->state) && i->sink) {
+        pa_usec_t usec = 0;
         pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_GET_REQUESTED_LATENCY, &usec, 0, NULL) == 0);
         pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_GET_REQUESTED_LATENCY, &usec, 0, NULL) == 0);
-    else
-        /* If this sink input is not realized yet, we have to touch
-         * the thread info data directly */
-        usec = i->thread_info.requested_sink_latency;
+        return usec;
+    }
 
 
-    return usec;
+    /* If this sink input is not realized yet or we are being moved,
+     * we have to touch the thread info data directly */
+
+    return i->thread_info.requested_sink_latency;
 }
 
 /* Called from main context */
 }
 
 /* Called from main context */
-void pa_sink_input_set_volume(pa_sink_input *i, const pa_cvolume *volume) {
-    pa_sink_input_set_volume_data data;
+void pa_sink_input_set_volume(pa_sink_input *i, const pa_cvolume *volume, pa_bool_t save, pa_bool_t absolute) {
+    pa_cvolume v;
 
     pa_sink_input_assert_ref(i);
     pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
     pa_assert(volume);
 
     pa_sink_input_assert_ref(i);
     pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
     pa_assert(volume);
+    pa_assert(pa_cvolume_valid(volume));
+    pa_assert(pa_cvolume_compatible(volume, &i->sample_spec));
 
 
-    data.sink_input = i;
-    data.virtual_volume = *volume;
-    data.volume = *volume;
-
-    if (pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_SET_VOLUME], &data) < 0) {
-        return;
+    if ((i->sink->flags & PA_SINK_FLAT_VOLUME) && !absolute) {
+        v = i->sink->reference_volume;
+        pa_cvolume_remap(&v, &i->sink->channel_map, &i->channel_map);
+        volume = pa_sw_cvolume_multiply(&v, &v, volume);
     }
 
     }
 
-    if (!pa_cvolume_equal(&i->volume, &data.volume)) {
-        i->volume = data.volume;
-       pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_SET_VOLUME, &data.volume, 0, NULL) == 0);
+    if (pa_cvolume_equal(volume, &i->virtual_volume))
         return;
         return;
-    }
 
 
-    if (!pa_cvolume_equal(&i->virtual_volume, &data.virtual_volume)) {
-        i->virtual_volume = data.virtual_volume;
-        pa_subscription_post(i->sink->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index);
+    i->virtual_volume = *volume;
+    i->save_volume = save;
+
+    if (i->sink->flags & PA_SINK_FLAT_VOLUME) {
+        pa_cvolume new_volume;
+
+        /* We are in flat volume mode, so let's update all sink input
+         * volumes and update the flat volume of the sink */
+
+        pa_sink_update_flat_volume(i->sink, &new_volume);
+        pa_sink_set_volume(i->sink, &new_volume, FALSE, TRUE, FALSE, FALSE);
+
+    } else {
+
+        /* OK, we are in normal volume mode. The volume only affects
+         * ourselves */
+        pa_sink_input_set_relative_volume(i, volume);
+
+        /* Hooks have the ability to play games with i->soft_volume */
+        pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_SET_VOLUME], i);
+
+        /* 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 virtual volume changed, let's tell people so */
+    pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index);
 }
 
 /* Called from main context */
 }
 
 /* Called from main context */
-const pa_cvolume *pa_sink_input_get_volume(pa_sink_input *i) {
+pa_cvolume *pa_sink_input_get_volume(pa_sink_input *i, pa_cvolume *volume, pa_bool_t absolute) {
     pa_sink_input_assert_ref(i);
     pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
 
     pa_sink_input_assert_ref(i);
     pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
 
-    return &i->virtual_volume;
+    if ((i->sink->flags & PA_SINK_FLAT_VOLUME) && !absolute) {
+        pa_cvolume v = i->sink->reference_volume;
+        pa_cvolume_remap(&v, &i->sink->channel_map, &i->channel_map);
+        pa_sw_cvolume_divide(volume, &i->virtual_volume, &v);
+    } else
+        *volume = i->virtual_volume;
+
+    return volume;
 }
 
 /* Called from main context */
 }
 
 /* Called from main context */
-void pa_sink_input_set_mute(pa_sink_input *i, pa_bool_t mute) {
+pa_cvolume *pa_sink_input_get_relative_volume(pa_sink_input *i, pa_cvolume *v) {
+    unsigned c;
+
+    pa_sink_input_assert_ref(i);
+    pa_assert(v);
+    pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
+
+    /* This always returns the relative volume. Converts the float
+     * version into a pa_cvolume */
+
+    v->channels = i->sample_spec.channels;
+
+    for (c = 0; c < v->channels; c++)
+        v->values[c] = pa_sw_volume_from_linear(i->relative_volume[c]);
+
+    return v;
+}
+
+/* Called from main context */
+void pa_sink_input_set_relative_volume(pa_sink_input *i, const pa_cvolume *v) {
+    unsigned c;
+    pa_cvolume _v;
+
+    pa_sink_input_assert_ref(i);
+    pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
+    pa_assert(!v || pa_cvolume_compatible(v, &i->sample_spec));
+
+    if (!v)
+        v = pa_cvolume_reset(&_v, i->sample_spec.channels);
+
+    /* This basically calculates:
+     *
+     * i->relative_volume := v
+     * i->soft_volume := i->relative_volume * i->volume_factor */
+
+    i->soft_volume.channels = i->sample_spec.channels;
+
+    for (c = 0; c < i->sample_spec.channels; c++) {
+        i->relative_volume[c] = pa_sw_volume_to_linear(v->values[c]);
+
+        i->soft_volume.values[c] = pa_sw_volume_from_linear(
+                i->relative_volume[c] *
+                pa_sw_volume_to_linear(i->volume_factor.values[c]));
+    }
+
+    /* We don't copy the data to the thread_info data. That's left for someone else to do */
+}
+
+/* Called from main context */
+void pa_sink_input_set_mute(pa_sink_input *i, pa_bool_t mute, pa_bool_t save) {
     pa_assert(i);
     pa_sink_input_assert_ref(i);
     pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
     pa_assert(i);
     pa_sink_input_assert_ref(i);
     pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
@@ -832,9 +993,10 @@ void pa_sink_input_set_mute(pa_sink_input *i, pa_bool_t mute) {
         return;
 
     i->muted = mute;
         return;
 
     i->muted = mute;
+    i->save_muted = save;
 
 
-    pa_asyncmsgq_post(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_SET_MUTE, PA_UINT_TO_PTR(mute), 0, NULL, NULL);
-    pa_subscription_post(i->sink->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index);
+    pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_SET_SOFT_MUTE, NULL, 0, NULL) == 0);
+    pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index);
 }
 
 /* Called from main context */
 }
 
 /* Called from main context */
@@ -845,6 +1007,19 @@ pa_bool_t pa_sink_input_get_mute(pa_sink_input *i) {
     return i->muted;
 }
 
     return i->muted;
 }
 
+/* Called from main thread */
+void pa_sink_input_update_proplist(pa_sink_input *i, pa_update_mode_t mode, pa_proplist *p) {
+    pa_sink_input_assert_ref(i);
+
+    if (p)
+        pa_proplist_update(i->proplist, mode, p);
+
+    if (PA_SINK_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 */
 void pa_sink_input_cork(pa_sink_input *i, pa_bool_t b) {
     pa_sink_input_assert_ref(i);
 /* Called from main context */
 void pa_sink_input_cork(pa_sink_input *i, pa_bool_t b) {
     pa_sink_input_assert_ref(i);
@@ -857,7 +1032,7 @@ void pa_sink_input_cork(pa_sink_input *i, pa_bool_t b) {
 int pa_sink_input_set_rate(pa_sink_input *i, uint32_t rate) {
     pa_sink_input_assert_ref(i);
     pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
 int pa_sink_input_set_rate(pa_sink_input *i, uint32_t rate) {
     pa_sink_input_assert_ref(i);
     pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
-    pa_return_val_if_fail(i->thread_info.resampler, -1);
+    pa_return_val_if_fail(i->thread_info.resampler, -PA_ERR_BADSTATE);
 
     if (i->sample_spec.rate == rate)
         return 0;
 
     if (i->sample_spec.rate == rate)
         return 0;
@@ -866,7 +1041,7 @@ int pa_sink_input_set_rate(pa_sink_input *i, uint32_t rate) {
 
     pa_asyncmsgq_post(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_SET_RATE, PA_UINT_TO_PTR(rate), 0, NULL, NULL);
 
 
     pa_asyncmsgq_post(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_SET_RATE, PA_UINT_TO_PTR(rate), 0, NULL, NULL);
 
-    pa_subscription_post(i->sink->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index);
+    pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index);
     return 0;
 }
 
     return 0;
 }
 
@@ -889,8 +1064,8 @@ void pa_sink_input_set_name(pa_sink_input *i, const char *name) {
         pa_proplist_unset(i->proplist, PA_PROP_MEDIA_NAME);
 
     if (PA_SINK_INPUT_IS_LINKED(i->state)) {
         pa_proplist_unset(i->proplist, PA_PROP_MEDIA_NAME);
 
     if (PA_SINK_INPUT_IS_LINKED(i->state)) {
-        pa_hook_fire(&i->sink->core->hooks[PA_CORE_HOOK_SINK_INPUT_PROPLIST_CHANGED], i);
-        pa_subscription_post(i->sink->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index);
+        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);
     }
 }
 
     }
 }
 
@@ -898,48 +1073,120 @@ void pa_sink_input_set_name(pa_sink_input *i, const char *name) {
 pa_resample_method_t pa_sink_input_get_resample_method(pa_sink_input *i) {
     pa_sink_input_assert_ref(i);
 
 pa_resample_method_t pa_sink_input_get_resample_method(pa_sink_input *i) {
     pa_sink_input_assert_ref(i);
 
-    return i->resample_method;
+    return i->actual_resample_method;
 }
 
 /* Called from main context */
 }
 
 /* Called from main context */
-int pa_sink_input_move_to(pa_sink_input *i, pa_sink *dest) {
-    pa_resampler *new_resampler;
-    pa_sink *origin;
-    pa_sink_input_move_hook_data hook_data;
-    pa_source_output *o, *p = NULL;
-
+pa_bool_t pa_sink_input_may_move(pa_sink_input *i) {
     pa_sink_input_assert_ref(i);
     pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
     pa_sink_input_assert_ref(i);
     pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
-    pa_sink_assert_ref(dest);
-
-    origin = i->sink;
-
-    if (dest == origin)
-        return 0;
 
     if (i->flags & PA_SINK_INPUT_DONT_MOVE)
 
     if (i->flags & PA_SINK_INPUT_DONT_MOVE)
-        return -1;
+        return FALSE;
 
     if (i->sync_next || i->sync_prev) {
         pa_log_warn("Moving synchronised streams not supported.");
 
     if (i->sync_next || i->sync_prev) {
         pa_log_warn("Moving synchronised streams not supported.");
-        return -1;
+        return FALSE;
     }
 
     }
 
+    return TRUE;
+}
+
+/* Called from main context */
+pa_bool_t pa_sink_input_may_move_to(pa_sink_input *i, pa_sink *dest) {
+    pa_sink_input_assert_ref(i);
+    pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
+    pa_sink_assert_ref(dest);
+
+    if (dest == i->sink)
+        return TRUE;
+
+    if (!pa_sink_input_may_move(i))
+        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.");
     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 -1;
+        return FALSE;
     }
 
     }
 
+    if (i->may_move_to)
+        if (!i->may_move_to(i, dest))
+            return FALSE;
+
+    return TRUE;
+}
+
+/* Called from main context */
+int pa_sink_input_start_move(pa_sink_input *i) {
+    pa_source_output *o, *p = NULL;
+    pa_sink *origin;
+    int r;
+
+    pa_sink_input_assert_ref(i);
+    pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
+    pa_assert(i->sink);
+
+    if (!pa_sink_input_may_move(i))
+        return -PA_ERR_NOTSUPPORTED;
+
+    if ((r = pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_START], i)) < 0)
+        return r;
+
+    origin = i->sink;
+
     /* Kill directly connected outputs */
     while ((o = pa_idxset_first(i->direct_outputs, NULL))) {
         pa_assert(o != p);
         pa_source_output_kill(o);
         p = o;
     }
     /* Kill directly connected outputs */
     while ((o = pa_idxset_first(i->direct_outputs, NULL))) {
         pa_assert(o != p);
         pa_source_output_kill(o);
         p = o;
     }
+    pa_assert(pa_idxset_isempty(i->direct_outputs));
+
+    pa_idxset_remove_by_data(i->sink->inputs, i, NULL);
+
+    if (pa_sink_input_get_state(i) == PA_SINK_INPUT_CORKED)
+        pa_assert_se(i->sink->n_corked-- >= 1);
+
+    if (i->sink->flags & PA_SINK_FLAT_VOLUME) {
+        pa_cvolume new_volume;
+
+        /* Make the virtual volume relative */
+        pa_sink_input_get_relative_volume(i, &i->virtual_volume);
+
+        /* And reset the the relative volume */
+        pa_sink_input_set_relative_volume(i, NULL);
+
+        /* We might need to update the sink's volume if we are in flat
+         * volume mode. */
+        pa_sink_update_flat_volume(i->sink, &new_volume);
+        pa_sink_set_volume(i->sink, &new_volume, FALSE, FALSE, FALSE, FALSE);
+    }
+
+    pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i->sink), PA_SINK_MESSAGE_START_MOVE, i, 0, NULL) == 0);
+
+    pa_sink_update_status(i->sink);
+    i->sink = NULL;
+
+    pa_sink_input_unref(i);
+
+    return 0;
+}
+
+/* Called from main context */
+int pa_sink_input_finish_move(pa_sink_input *i, pa_sink *dest, pa_bool_t save) {
+    pa_resampler *new_resampler;
+
+    pa_sink_input_assert_ref(i);
+    pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
+    pa_assert(!i->sink);
+    pa_sink_assert_ref(dest);
+
+    if (!pa_sink_input_may_move_to(i, dest))
+        return -PA_ERR_NOTSUPPORTED;
 
     if (i->thread_info.resampler &&
 
     if (i->thread_info.resampler &&
-        pa_sample_spec_equal(&origin->sample_spec, &dest->sample_spec) &&
-        pa_channel_map_equal(&origin->channel_map, &dest->channel_map))
+        pa_sample_spec_equal(pa_resampler_output_sample_spec(i->thread_info.resampler), &dest->sample_spec) &&
+        pa_channel_map_equal(pa_resampler_output_channel_map(i->thread_info.resampler), &dest->channel_map))
 
         /* Try to reuse the old resampler if possible */
         new_resampler = i->thread_info.resampler;
 
         /* Try to reuse the old resampler if possible */
         new_resampler = i->thread_info.resampler;
@@ -951,33 +1198,28 @@ int pa_sink_input_move_to(pa_sink_input *i, pa_sink *dest) {
         /* Okey, we need a new resampler for the new sink */
 
         if (!(new_resampler = pa_resampler_new(
         /* Okey, we need a new resampler for the new sink */
 
         if (!(new_resampler = pa_resampler_new(
-                      dest->core->mempool,
+                      i->core->mempool,
                       &i->sample_spec, &i->channel_map,
                       &dest->sample_spec, &dest->channel_map,
                       &i->sample_spec, &i->channel_map,
                       &dest->sample_spec, &dest->channel_map,
-                      i->resample_method,
+                      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)))) {
             pa_log_warn("Unsupported resampling operation.");
                       ((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)))) {
             pa_log_warn("Unsupported resampling operation.");
-            return -1;
+            return -PA_ERR_NOTSUPPORTED;
         }
     } else
         new_resampler = NULL;
 
         }
     } else
         new_resampler = NULL;
 
-    hook_data.sink_input = i;
-    hook_data.destination = dest;
-    pa_hook_fire(&i->sink->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE], &hook_data);
-
-    pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i->sink), PA_SINK_MESSAGE_START_MOVE, i, 0, NULL) == 0);
+    if (i->moving)
+        i->moving(i, dest);
 
 
-    pa_idxset_remove_by_data(origin->inputs, i, NULL);
-    pa_idxset_put(dest->inputs, i, NULL);
     i->sink = dest;
     i->sink = dest;
+    i->save_sink = save;
+    pa_idxset_put(dest->inputs, pa_sink_input_ref(i), NULL);
 
 
-    if (pa_sink_input_get_state(i) == PA_SINK_INPUT_CORKED) {
-        pa_assert_se(origin->n_corked-- >= 1);
-        dest->n_corked++;
-    }
+    if (pa_sink_input_get_state(i) == PA_SINK_INPUT_CORKED)
+        i->sink->n_corked++;
 
     /* Replace resampler and render queue */
     if (new_resampler != i->thread_info.resampler) {
 
     /* Replace resampler and render queue */
     if (new_resampler != i->thread_info.resampler) {
@@ -998,21 +1240,60 @@ int pa_sink_input_move_to(pa_sink_input *i, pa_sink *dest) {
                 0,
                 &i->sink->silence);
     }
                 0,
                 &i->sink->silence);
     }
-
-    pa_sink_update_status(origin);
     pa_sink_update_status(dest);
 
     pa_sink_update_status(dest);
 
-    pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i->sink), PA_SINK_MESSAGE_FINISH_MOVE, i, 0, NULL) == 0);
+    if (i->sink->flags & PA_SINK_FLAT_VOLUME) {
+        pa_cvolume new_volume;
 
 
-    if (i->moved)
-        i->moved(i);
+        /* Make relative volume absolute again */
+        pa_cvolume t = dest->reference_volume;
+        pa_cvolume_remap(&t, &dest->channel_map, &i->channel_map);
+        pa_sw_cvolume_multiply(&i->virtual_volume, &i->virtual_volume, &t);
 
 
-    pa_hook_fire(&i->sink->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_POST], i);
+        /* We might need to update the sink's volume if we are in flat volume mode. */
+        pa_sink_update_flat_volume(i->sink, &new_volume);
+        pa_sink_set_volume(i->sink, &new_volume, FALSE, FALSE, FALSE, FALSE);
+    }
+
+    pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i->sink), PA_SINK_MESSAGE_FINISH_MOVE, i, 0, NULL) == 0);
 
 
-    pa_log_debug("Successfully moved sink input %i from %s to %s.", i->index, origin->name, dest->name);
+    pa_log_debug("Successfully moved sink input %i to %s.", i->index, dest->name);
 
     /* Notify everyone */
 
     /* Notify everyone */
-    pa_subscription_post(i->sink->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index);
+    pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_FINISH], i);
+    pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index);
+
+    return 0;
+}
+
+/* Called from main context */
+int pa_sink_input_move_to(pa_sink_input *i, pa_sink *dest, pa_bool_t save) {
+    int r;
+
+    pa_sink_input_assert_ref(i);
+    pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
+    pa_assert(i->sink);
+    pa_sink_assert_ref(dest);
+
+    if (dest == i->sink)
+        return 0;
+
+    if (!pa_sink_input_may_move_to(i, dest))
+        return -PA_ERR_NOTSUPPORTED;
+
+    pa_sink_input_ref(i);
+
+    if ((r = pa_sink_input_start_move(i)) < 0) {
+        pa_sink_input_unref(i);
+        return r;
+    }
+
+    if ((r = pa_sink_input_finish_move(i, dest, save)) < 0) {
+        pa_sink_input_unref(i);
+        return r;
+    }
+
+    pa_sink_input_unref(i);
 
     return 0;
 }
 
     return 0;
 }
@@ -1043,7 +1324,7 @@ 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 */
 
         /* 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);
+        pa_sink_input_request_rewind(i, 0, TRUE, TRUE, FALSE);
 
     } else if (uncorking) {
 
 
     } else if (uncorking) {
 
@@ -1054,7 +1335,7 @@ 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. */
 
         /* 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);
+        pa_sink_input_request_rewind(i, 0, FALSE, TRUE, TRUE);
     }
 }
 
     }
 }
 
@@ -1065,24 +1346,25 @@ int pa_sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int64_t
 
     switch (code) {
 
 
     switch (code) {
 
-        case PA_SINK_INPUT_MESSAGE_SET_VOLUME:
-            i->thread_info.volume = *((pa_cvolume*) userdata);
-            pa_sink_input_request_rewind(i, 0, TRUE, FALSE);
+        case PA_SINK_INPUT_MESSAGE_SET_SOFT_VOLUME:
+            if (!pa_cvolume_equal(&i->thread_info.soft_volume, &i->soft_volume)) {
+                i->thread_info.soft_volume = i->soft_volume;
+                pa_sink_input_request_rewind(i, 0, TRUE, FALSE, FALSE);
+            }
             return 0;
 
             return 0;
 
-        case PA_SINK_INPUT_MESSAGE_SET_MUTE:
-            i->thread_info.muted = PA_PTR_TO_UINT(userdata);
-            pa_sink_input_request_rewind(i, 0, TRUE, FALSE);
+        case PA_SINK_INPUT_MESSAGE_SET_SOFT_MUTE:
+            if (i->thread_info.muted != i->muted) {
+                i->thread_info.muted = i->muted;
+                pa_sink_input_request_rewind(i, 0, TRUE, FALSE, FALSE);
+            }
             return 0;
 
         case PA_SINK_INPUT_MESSAGE_GET_LATENCY: {
             pa_usec_t *r = userdata;
             return 0;
 
         case PA_SINK_INPUT_MESSAGE_GET_LATENCY: {
             pa_usec_t *r = userdata;
-            pa_usec_t sink_usec = 0;
 
             r[0] += pa_bytes_to_usec(pa_memblockq_get_length(i->thread_info.render_memblockq), &i->sink->sample_spec);
 
             r[0] += pa_bytes_to_usec(pa_memblockq_get_length(i->thread_info.render_memblockq), &i->sink->sample_spec);
-
-            if (i->sink->parent.process_msg(PA_MSGOBJECT(i->sink), PA_SINK_MESSAGE_GET_LATENCY, &sink_usec, 0, NULL) >= 0)
-                r[1] += sink_usec;
+            r[1] += pa_sink_get_latency_within_thread(i->sink);
 
             return 0;
         }
 
             return 0;
         }
@@ -1123,7 +1405,7 @@ int pa_sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int64_t
         }
     }
 
         }
     }
 
-    return -1;
+    return -PA_ERR_NOTIMPLEMENTED;
 }
 
 /* Called from main thread */
 }
 
 /* Called from main thread */
@@ -1147,7 +1429,7 @@ pa_bool_t pa_sink_input_safe_to_remove(pa_sink_input *i) {
 }
 
 /* Called from IO context */
 }
 
 /* Called from IO context */
-void pa_sink_input_request_rewind(pa_sink_input *i, size_t nbytes  /* in our sample spec */, pa_bool_t rewrite, pa_bool_t flush) {
+void pa_sink_input_request_rewind(pa_sink_input *i, size_t nbytes  /* in our sample spec */, pa_bool_t rewrite, pa_bool_t flush, pa_bool_t dont_rewind_render) {
     size_t lbq;
 
     /* If 'rewrite' is TRUE the sink is rewound as far as requested
     size_t lbq;
 
     /* If 'rewrite' is TRUE the sink is rewound as far as requested
@@ -1158,10 +1440,13 @@ void pa_sink_input_request_rewind(pa_sink_input *i, size_t nbytes  /* in our sam
      * If 'rewrite' is FALSE the sink is rewound as far as requested
      * and possible and the already rendered data is dropped so that
      * in the next iteration we read new data from the
      * If 'rewrite' is FALSE the sink is rewound as far as requested
      * and possible and the already rendered data is dropped so that
      * in the next iteration we read new data from the
-     * implementor. This implies 'flush' is TRUE. */
+     * implementor. This implies 'flush' is TRUE.  If
+     * dont_rewind_render is TRUE then the render memblockq is not
+     * rewound. */
 
     pa_sink_input_assert_ref(i);
 
     pa_sink_input_assert_ref(i);
-    pa_assert(i->thread_info.rewrite_nbytes == 0);
+
+    nbytes = PA_MAX(i->thread_info.rewrite_nbytes, nbytes);
 
 /*     pa_log_debug("request rewrite %lu", (unsigned long) nbytes); */
 
 
 /*     pa_log_debug("request rewrite %lu", (unsigned long) nbytes); */
 
@@ -1170,6 +1455,7 @@ void pa_sink_input_request_rewind(pa_sink_input *i, size_t nbytes  /* in our sam
         return;
 
     pa_assert(rewrite || flush);
         return;
 
     pa_assert(rewrite || flush);
+    pa_assert(!dont_rewind_render || !rewrite);
 
     /* Calculate how much we can rewind locally without having to
      * touch the sink */
 
     /* Calculate how much we can rewind locally without having to
      * touch the sink */
@@ -1189,26 +1475,37 @@ void pa_sink_input_request_rewind(pa_sink_input *i, size_t nbytes  /* in our sam
             nbytes = pa_resampler_request(i->thread_info.resampler, nbytes);
     }
 
             nbytes = pa_resampler_request(i->thread_info.resampler, nbytes);
     }
 
-    if (rewrite) {
-        /* Make sure to not overwrite over underruns */
-        if (nbytes > i->thread_info.playing_for)
-            nbytes = (size_t) i->thread_info.playing_for;
+    if (i->thread_info.rewrite_nbytes != (size_t) -1) {
+        if (rewrite) {
+            /* Make sure to not overwrite over underruns */
+            if (nbytes > i->thread_info.playing_for)
+                nbytes = (size_t) i->thread_info.playing_for;
 
 
-        i->thread_info.rewrite_nbytes = nbytes;
-    } else
-        i->thread_info.rewrite_nbytes = (size_t) -1;
+            i->thread_info.rewrite_nbytes = nbytes;
+        } else
+            i->thread_info.rewrite_nbytes = (size_t) -1;
+    }
 
 
-    i->thread_info.rewrite_flush = flush && i->thread_info.rewrite_nbytes != 0;
+    i->thread_info.rewrite_flush =
+        i->thread_info.rewrite_flush ||
+        (flush && i->thread_info.rewrite_nbytes != 0);
 
 
-    /* Transform to sink domain */
-    if (i->thread_info.resampler)
-        nbytes = pa_resampler_result(i->thread_info.resampler, nbytes);
+    i->thread_info.dont_rewind_render =
+        i->thread_info.dont_rewind_render ||
+        dont_rewind_render;
 
 
-    if (nbytes > lbq)
-        pa_sink_request_rewind(i->sink, nbytes - lbq);
-    else
-        /* This call will make sure process_rewind() is called later */
-        pa_sink_request_rewind(i->sink, 0);
+    if (nbytes != (size_t) -1) {
+
+        /* Transform to sink domain */
+        if (i->thread_info.resampler)
+            nbytes = pa_resampler_result(i->thread_info.resampler, nbytes);
+
+        if (nbytes > lbq)
+            pa_sink_request_rewind(i->sink, nbytes - lbq);
+        else
+            /* This call will make sure process_rewind() is called later */
+            pa_sink_request_rewind(i->sink, 0);
+    }
 }
 
 /* Called from main context */
 }
 
 /* Called from main context */
@@ -1217,11 +1514,39 @@ pa_memchunk* pa_sink_input_get_silence(pa_sink_input *i, pa_memchunk *ret) {
     pa_assert(ret);
 
     pa_silence_memchunk_get(
     pa_assert(ret);
 
     pa_silence_memchunk_get(
-                &i->sink->core->silence_cache,
-                i->sink->core->mempool,
+                &i->core->silence_cache,
+                i->core->mempool,
                 ret,
                 &i->sample_spec,
                 i->thread_info.resampler ? pa_resampler_max_block_size(i->thread_info.resampler) : 0);
 
     return ret;
 }
                 ret,
                 &i->sample_spec,
                 i->thread_info.resampler ? pa_resampler_max_block_size(i->thread_info.resampler) : 0);
 
     return ret;
 }
+
+/* Called from main context */
+void pa_sink_input_send_event(pa_sink_input *i, const char *event, pa_proplist *data) {
+    pa_proplist *pl = NULL;
+    pa_sink_input_send_event_hook_data hook_data;
+
+    pa_sink_input_assert_ref(i);
+    pa_assert(event);
+
+    if (!i->send_event)
+        return;
+
+    if (!data)
+        data = pl = pa_proplist_new();
+
+    hook_data.sink_input = i;
+    hook_data.data = data;
+    hook_data.event = event;
+
+    if (pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_SEND_EVENT], &hook_data) < 0)
+        goto finish;
+
+    i->send_event(i, event, data);
+
+finish:
+    if (pl)
+        pa_proplist_free(pl);
+}