]> code.delx.au - pulseaudio/blobdiff - src/pulsecore/sink-input.c
Merge remote branch 'mkbosmans/rate-adjustment'
[pulseaudio] / src / pulsecore / sink-input.c
index 34217c86726f39db3479e1b9530686703094ef40..065fd2d310a3ea3efc8344b7c557b6215b657422 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
 #include <pulsecore/play-memblockq.h>
 #include <pulsecore/namereg.h>
 #include <pulsecore/core-util.h>
 #include <pulsecore/play-memblockq.h>
 #include <pulsecore/namereg.h>
 #include <pulsecore/core-util.h>
+#include <pulse/timeval.h>
 
 #include "sink-input.h"
 
 #define MEMBLOCKQ_MAXLENGTH (32*1024*1024)
 #define CONVERT_BUFFER_LENGTH (PA_PAGE_SIZE)
 
 
 #include "sink-input.h"
 
 #define MEMBLOCKQ_MAXLENGTH (32*1024*1024)
 #define CONVERT_BUFFER_LENGTH (PA_PAGE_SIZE)
 
-static PA_DEFINE_CHECK_TYPE(pa_sink_input, pa_msgobject);
+PA_DEFINE_PUBLIC_CLASS(pa_sink_input, pa_msgobject);
 
 static void sink_input_free(pa_object *o);
 
 static void sink_input_free(pa_object *o);
+static void set_real_ratio(pa_sink_input *i, const pa_cvolume *v);
+static void sink_input_set_ramping_info(pa_sink_input* i, pa_volume_t  pre_virtual_volume, pa_volume_t target_virtual_volume, pa_usec_t t);
+static void sink_input_set_ramping_info_for_mute(pa_sink_input* i, pa_bool_t mute, pa_usec_t t);
+static void sink_input_volume_ramping(pa_sink_input* i, pa_memchunk* chunk);
+static void sink_input_rewind_ramp_info(pa_sink_input *i, size_t nbytes);
+static void sink_input_release_envelope(pa_sink_input *i);
+
+static int check_passthrough_connection(pa_sink_input_flags_t flags, pa_sink *dest) {
+
+    if (dest->flags & PA_SINK_PASSTHROUGH) {
+
+        if (pa_idxset_size(dest->inputs) > 0) {
+
+            pa_sink_input *alt_i;
+            uint32_t idx;
+
+            alt_i = pa_idxset_first(dest->inputs, &idx);
+
+            /* only need to check the first input is not PASSTHROUGH */
+            if (alt_i->flags & PA_SINK_INPUT_PASSTHROUGH) {
+                pa_log_warn("Sink is already connected to PASSTHROUGH input");
+                return -PA_ERR_BUSY;
+            }
+
+            /* Current inputs are PCM, check new input is not PASSTHROUGH */
+            if (flags & PA_SINK_INPUT_PASSTHROUGH) {
+                pa_log_warn("Sink is already connected, cannot accept new PASSTHROUGH INPUT");
+                return -PA_ERR_BUSY;
+            }
+        }
+
+    } else {
+         if (flags & PA_SINK_INPUT_PASSTHROUGH) {
+             pa_log_warn("Cannot connect PASSTHROUGH sink input to sink without PASSTHROUGH capabilities");
+             return -PA_ERR_INVALID;
+         }
+    }
+    return PA_OK;
+}
 
 pa_sink_input_new_data* pa_sink_input_new_data_init(pa_sink_input_new_data *data) {
     pa_assert(data);
 
 
 pa_sink_input_new_data* pa_sink_input_new_data_init(pa_sink_input_new_data *data) {
     pa_assert(data);
 
-    memset(data, 0, sizeof(*data));
+    pa_zero(*data);
     data->resample_method = PA_RESAMPLER_INVALID;
     data->proplist = pa_proplist_new();
 
     data->resample_method = PA_RESAMPLER_INVALID;
     data->proplist = pa_proplist_new();
 
@@ -91,6 +131,18 @@ void pa_sink_input_new_data_apply_volume_factor(pa_sink_input_new_data *data, co
     }
 }
 
     }
 }
 
+void pa_sink_input_new_data_apply_volume_factor_sink(pa_sink_input_new_data *data, const pa_cvolume *volume_factor) {
+    pa_assert(data);
+    pa_assert(volume_factor);
+
+    if (data->volume_factor_sink_is_set)
+        pa_sw_cvolume_multiply(&data->volume_factor_sink, &data->volume_factor_sink, volume_factor);
+    else {
+        data->volume_factor_sink_is_set = TRUE;
+        data->volume_factor_sink = *volume_factor;
+    }
+}
+
 void pa_sink_input_new_data_set_muted(pa_sink_input_new_data *data, pa_bool_t mute) {
     pa_assert(data);
 
 void pa_sink_input_new_data_set_muted(pa_sink_input_new_data *data, pa_bool_t mute) {
     pa_assert(data);
 
@@ -114,33 +166,38 @@ static void reset_callbacks(pa_sink_input *i) {
     i->update_max_request = NULL;
     i->update_sink_requested_latency = NULL;
     i->update_sink_latency_range = NULL;
     i->update_max_request = NULL;
     i->update_sink_requested_latency = NULL;
     i->update_sink_latency_range = NULL;
+    i->update_sink_fixed_latency = NULL;
     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->may_move_to = NULL;
     i->send_event = NULL;
     i->kill = NULL;
     i->get_latency = NULL;
     i->state_change = NULL;
     i->may_move_to = NULL;
     i->send_event = NULL;
+    i->volume_changed = NULL;
+    i->mute_changed = NULL;
 }
 
 /* Called from main context */
 int pa_sink_input_new(
         pa_sink_input **_i,
         pa_core *core,
 }
 
 /* Called from main context */
 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_sink_input_new_data *data) {
 
     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_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;
+    char *pt;
 
     pa_assert(_i);
     pa_assert(core);
     pa_assert(data);
 
     pa_assert(_i);
     pa_assert(core);
     pa_assert(data);
+    pa_assert_ctl_context();
 
     if (data->client)
         pa_proplist_update(data->proplist, PA_UPDATE_MERGE, data->client->proplist);
 
     if (data->client)
         pa_proplist_update(data->proplist, PA_UPDATE_MERGE, data->client->proplist);
@@ -158,7 +215,9 @@ int pa_sink_input_new(
     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);
     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);
-    pa_return_val_if_fail(!(flags & PA_SINK_INPUT_FAIL_ON_SUSPEND) || pa_sink_get_state(data->sink) != PA_SINK_SUSPENDED, -PA_ERR_BADSTATE);
+
+    r = check_passthrough_connection(data->flags, data->sink);
+    pa_return_val_if_fail(r == PA_OK, r);
 
     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;
@@ -172,44 +231,38 @@ int pa_sink_input_new(
             pa_channel_map_init_extend(&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_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) {
     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->sink->flags & PA_SINK_FLAT_VOLUME) {
-            data->volume = *pa_sink_get_volume(data->sink, FALSE);
-            pa_cvolume_remap(&data->volume, &data->sink->channel_map, &data->channel_map);
-            data->volume_is_absolute = TRUE;
-        } else {
-            pa_cvolume_reset(&data->volume, data->sample_spec.channels);
-            data->volume_is_absolute = FALSE;
-        }
-
+        pa_cvolume_reset(&data->volume, data->sample_spec.channels);
+        data->volume_is_absolute = FALSE;
         data->save_volume = FALSE;
     }
 
         data->save_volume = FALSE;
     }
 
-    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);
 
     if (!data->volume_factor_is_set)
         pa_cvolume_reset(&data->volume_factor, data->sample_spec.channels);
 
     pa_return_val_if_fail(pa_cvolume_compatible(&data->volume, &data->sample_spec), -PA_ERR_INVALID);
 
     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);
 
     pa_return_val_if_fail(pa_cvolume_compatible(&data->volume_factor, &data->sample_spec), -PA_ERR_INVALID);
 
+    if (!data->volume_factor_sink_is_set)
+        pa_cvolume_reset(&data->volume_factor_sink, data->sink->sample_spec.channels);
+
+    pa_return_val_if_fail(pa_cvolume_compatible(&data->volume_factor_sink, &data->sink->sample_spec), -PA_ERR_INVALID);
+
     if (!data->muted_is_set)
         data->muted = FALSE;
 
     if (!data->muted_is_set)
         data->muted = FALSE;
 
-    if (flags & PA_SINK_INPUT_FIX_FORMAT)
+    if (data->flags & PA_SINK_INPUT_FIX_FORMAT)
         data->sample_spec.format = data->sink->sample_spec.format;
 
         data->sample_spec.format = data->sink->sample_spec.format;
 
-    if (flags & PA_SINK_INPUT_FIX_RATE)
+    if (data->flags & PA_SINK_INPUT_FIX_RATE)
         data->sample_spec.rate = data->sink->sample_spec.rate;
 
     original_cm = data->channel_map;
 
         data->sample_spec.rate = data->sink->sample_spec.rate;
 
     original_cm = data->channel_map;
 
-    if (flags & PA_SINK_INPUT_FIX_CHANNELS) {
+    if (data->flags & PA_SINK_INPUT_FIX_CHANNELS) {
         data->sample_spec.channels = data->sink->sample_spec.channels;
         data->channel_map = data->sink->channel_map;
     }
         data->sample_spec.channels = data->sink->sample_spec.channels;
         data->channel_map = data->sink->channel_map;
     }
@@ -228,12 +281,18 @@ int pa_sink_input_new(
     if ((r = pa_hook_fire(&core->hooks[PA_CORE_HOOK_SINK_INPUT_FIXATE], data)) < 0)
         return r;
 
     if ((r = pa_hook_fire(&core->hooks[PA_CORE_HOOK_SINK_INPUT_FIXATE], data)) < 0)
         return r;
 
+    if ((data->flags & PA_SINK_INPUT_NO_CREATE_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.");
         return -PA_ERR_TOOLARGE;
     }
 
     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 -PA_ERR_TOOLARGE;
     }
 
-    if ((flags & PA_SINK_INPUT_VARIABLE_RATE) ||
+    if ((data->flags & PA_SINK_INPUT_VARIABLE_RATE) ||
         !pa_sample_spec_equal(&data->sample_spec, &data->sink->sample_spec) ||
         !pa_channel_map_equal(&data->channel_map, &data->sink->channel_map)) {
 
         !pa_sample_spec_equal(&data->sample_spec, &data->sink->sample_spec) ||
         !pa_channel_map_equal(&data->channel_map, &data->sink->channel_map)) {
 
@@ -242,9 +301,9 @@ int pa_sink_input_new(
                       &data->sample_spec, &data->channel_map,
                       &data->sink->sample_spec, &data->sink->channel_map,
                       data->resample_method,
                       &data->sample_spec, &data->channel_map,
                       &data->sink->sample_spec, &data->sink->channel_map,
                       data->resample_method,
-                      ((flags & PA_SINK_INPUT_VARIABLE_RATE) ? PA_RESAMPLER_VARIABLE_RATE : 0) |
-                      ((flags & PA_SINK_INPUT_NO_REMAP) ? PA_RESAMPLER_NO_REMAP : 0) |
-                      (core->disable_remixing || (flags & PA_SINK_INPUT_NO_REMIX) ? PA_RESAMPLER_NO_REMIX : 0) |
+                      ((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->disable_lfe_remixing ? PA_RESAMPLER_NO_LFE : 0)))) {
             pa_log_warn("Unsupported resampling operation.");
             return -PA_ERR_NOTSUPPORTED;
                       (core->disable_lfe_remixing ? PA_RESAMPLER_NO_LFE : 0)))) {
             pa_log_warn("Unsupported resampling operation.");
             return -PA_ERR_NOTSUPPORTED;
@@ -257,7 +316,7 @@ int pa_sink_input_new(
 
     i->core = core;
     i->state = PA_SINK_INPUT_INIT;
 
     i->core = core;
     i->state = PA_SINK_INPUT_INIT;
-    i->flags = flags;
+    i->flags = data->flags;
     i->proplist = pa_proplist_copy(data->proplist);
     i->driver = pa_xstrdup(pa_path_get_filename(data->driver));
     i->module = data->module;
     i->proplist = pa_proplist_copy(data->proplist);
     i->driver = pa_xstrdup(pa_path_get_filename(data->driver));
     i->module = data->module;
@@ -270,18 +329,21 @@ int pa_sink_input_new(
     i->channel_map = data->channel_map;
 
     if ((i->sink->flags & PA_SINK_FLAT_VOLUME) && !data->volume_is_absolute) {
     i->channel_map = data->channel_map;
 
     if ((i->sink->flags & PA_SINK_FLAT_VOLUME) && !data->volume_is_absolute) {
+        pa_cvolume remapped;
+
         /* When the 'absolute' bool is not set then we'll treat the volume
          * as relative to the sink volume even in flat volume mode */
         /* 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 t = *pa_sink_get_volume(data->sink, FALSE);
-        pa_cvolume_remap(&t, &data->sink->channel_map, &data->channel_map);
-
-        pa_sw_cvolume_multiply(&i->virtual_volume, &data->volume, &t);
+        remapped = data->sink->reference_volume;
+        pa_cvolume_remap(&remapped, &data->sink->channel_map, &data->channel_map);
+        pa_sw_cvolume_multiply(&i->volume, &data->volume, &remapped);
     } else
     } else
-        i->virtual_volume = data->volume;
+        i->volume = data->volume;
 
     i->volume_factor = data->volume_factor;
 
     i->volume_factor = data->volume_factor;
-    pa_cvolume_init(&i->soft_volume);
+    i->volume_factor_sink = data->volume_factor_sink;
+    i->real_ratio = i->reference_ratio = data->volume;
+    pa_cvolume_reset(&i->soft_volume, i->sample_spec.channels);
+    pa_cvolume_reset(&i->real_ratio, i->sample_spec.channels);
     i->save_volume = data->save_volume;
     i->save_sink = data->save_sink;
     i->save_muted = data->save_muted;
     i->save_volume = data->save_volume;
     i->save_sink = data->save_sink;
     i->save_muted = data->save_muted;
@@ -303,6 +365,16 @@ int pa_sink_input_new(
     reset_callbacks(i);
     i->userdata = NULL;
 
     reset_callbacks(i);
     i->userdata = NULL;
 
+    /* Set Ramping info */
+    i->thread_info.ramp_info.is_ramping = FALSE;
+    i->thread_info.ramp_info.envelope_dead = TRUE;
+    i->thread_info.ramp_info.envelope = NULL;
+    i->thread_info.ramp_info.item = NULL;
+    i->thread_info.ramp_info.envelope_dying = 0;
+
+    pa_atomic_store(&i->before_ramping_v, 0);
+    pa_atomic_store(&i->before_ramping_m, 0);
+
     i->thread_info.state = i->state;
     i->thread_info.attached = FALSE;
     pa_atomic_store(&i->thread_info.drained, 1);
     i->thread_info.state = i->state;
     i->thread_info.attached = FALSE;
     pa_atomic_store(&i->thread_info.drained, 1);
@@ -328,18 +400,21 @@ int 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);
 
 
     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",
+    pt = pa_proplist_to_string_sep(i->proplist, "\n    ");
+    pa_log_info("Created input %u \"%s\" on %s with sample spec %s and channel map %s\n    %s",
                 i->index,
                 pa_strnull(pa_proplist_gets(i->proplist, PA_PROP_MEDIA_NAME)),
                 i->sink->name,
                 pa_sample_spec_snprint(st, sizeof(st), &i->sample_spec),
                 i->index,
                 pa_strnull(pa_proplist_gets(i->proplist, PA_PROP_MEDIA_NAME)),
                 i->sink->name,
                 pa_sample_spec_snprint(st, sizeof(st), &i->sample_spec),
-                pa_channel_map_snprint(cm, sizeof(cm), &i->channel_map));
+                pa_channel_map_snprint(cm, sizeof(cm), &i->channel_map),
+                pt);
+    pa_xfree(pt);
 
     /* Don't forget to call pa_sink_input_put! */
 
 
     /* Don't forget to call pa_sink_input_put! */
 
@@ -350,6 +425,7 @@ int pa_sink_input_new(
 /* 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);
+    pa_assert_ctl_context();
 
     if (!i->sink)
         return;
 
     if (!i->sink)
         return;
@@ -364,6 +440,7 @@ static void update_n_corked(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);
 static void sink_input_set_state(pa_sink_input *i, pa_sink_input_state_t state) {
     pa_sink_input *ssync;
     pa_assert(i);
+    pa_assert_ctl_context();
 
     if (state == PA_SINK_INPUT_DRAINED)
         state = PA_SINK_INPUT_RUNNING;
 
     if (state == PA_SINK_INPUT_DRAINED)
         state = PA_SINK_INPUT_RUNNING;
@@ -393,6 +470,9 @@ static void sink_input_set_state(pa_sink_input *i, pa_sink_input_state_t state)
 
         for (ssync = i->sync_next; ssync; ssync = ssync->sync_next)
             pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_STATE_CHANGED], ssync);
 
         for (ssync = i->sync_next; ssync; ssync = ssync->sync_next)
             pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_STATE_CHANGED], ssync);
+
+        if (PA_SINK_INPUT_IS_LINKED(state))
+            pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index);
     }
 
     pa_sink_update_status(i->sink);
     }
 
     pa_sink_update_status(i->sink);
@@ -402,7 +482,9 @@ static void sink_input_set_state(pa_sink_input *i, pa_sink_input_state_t state)
 void pa_sink_input_unlink(pa_sink_input *i) {
     pa_bool_t linked;
     pa_source_output *o, *p =  NULL;
 void pa_sink_input_unlink(pa_sink_input *i) {
     pa_bool_t linked;
     pa_source_output *o, *p =  NULL;
+
     pa_assert(i);
     pa_assert(i);
+    pa_assert_ctl_context();
 
     /* See pa_sink_unlink() for a couple of comments how this function
      * works */
 
     /* See pa_sink_unlink() for a couple of comments how this function
      * works */
@@ -441,11 +523,8 @@ void pa_sink_input_unlink(pa_sink_input *i) {
 
     if (linked && i->sink) {
         /* We might need to update the sink's volume if we are in flat volume mode. */
 
     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);
-        }
+        if (i->sink->flags & PA_SINK_FLAT_VOLUME)
+            pa_sink_set_volume(i->sink, NULL, 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);
@@ -463,6 +542,8 @@ void pa_sink_input_unlink(pa_sink_input *i) {
         i->sink = NULL;
     }
 
         i->sink = NULL;
     }
 
+    pa_core_maybe_vacuum(i->core);
+
     pa_sink_input_unref(i);
 }
 
     pa_sink_input_unref(i);
 }
 
@@ -471,6 +552,7 @@ static void sink_input_free(pa_object *o) {
     pa_sink_input* i = PA_SINK_INPUT(o);
 
     pa_assert(i);
     pa_sink_input* i = PA_SINK_INPUT(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_assert(pa_sink_input_refcnt(i) == 0);
 
     if (PA_SINK_INPUT_IS_LINKED(i->state))
@@ -478,7 +560,16 @@ static void sink_input_free(pa_object *o) {
 
     pa_log_info("Freeing input %u \"%s\"", i->index, pa_strnull(pa_proplist_gets(i->proplist, PA_PROP_MEDIA_NAME)));
 
 
     pa_log_info("Freeing input %u \"%s\"", i->index, pa_strnull(pa_proplist_gets(i->proplist, PA_PROP_MEDIA_NAME)));
 
-    pa_assert(!i->thread_info.attached);
+    /* Side note: this function must be able to destruct properly any
+     * kind of sink input in any state, even those which are
+     * "half-moved" or are connected to sinks that have no asyncmsgq
+     * and are hence half-destructed themselves! */
+
+    if (i->thread_info.ramp_info.envelope) {
+        pa_log_debug ("Freeing envelope\n");
+        pa_envelope_free(i->thread_info.ramp_info.envelope);
+        i->thread_info.ramp_info.envelope = NULL;
+    }
 
     if (i->thread_info.render_memblockq)
         pa_memblockq_free(i->thread_info.render_memblockq);
 
     if (i->thread_info.render_memblockq)
         pa_memblockq_free(i->thread_info.render_memblockq);
@@ -502,7 +593,9 @@ static void sink_input_free(pa_object *o) {
 /* Called from main context */
 void pa_sink_input_put(pa_sink_input *i) {
     pa_sink_input_state_t state;
 /* Called from main context */
 void pa_sink_input_put(pa_sink_input *i) {
     pa_sink_input_state_t state;
+
     pa_sink_input_assert_ref(i);
     pa_sink_input_assert_ref(i);
+    pa_assert_ctl_context();
 
     pa_assert(i->state == PA_SINK_INPUT_INIT);
 
 
     pa_assert(i->state == PA_SINK_INPUT_INIT);
 
@@ -517,12 +610,10 @@ void pa_sink_input_put(pa_sink_input *i) {
     i->state = state;
 
     /* We might need to update the sink's volume if we are in flat volume mode. */
     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);
-    } else
-        pa_sw_cvolume_multiply(&i->soft_volume, &i->virtual_volume, &i->volume_factor);
+    if (i->sink->flags & PA_SINK_FLAT_VOLUME)
+        pa_sink_set_volume(i->sink, NULL, FALSE, i->save_volume);
+    else
+        set_real_ratio(i, &i->volume);
 
     i->thread_info.soft_volume = i->soft_volume;
     i->thread_info.muted = i->muted;
 
     i->thread_info.soft_volume = i->soft_volume;
     i->thread_info.muted = i->muted;
@@ -538,6 +629,7 @@ void pa_sink_input_put(pa_sink_input *i) {
 /* Called from main context */
 void pa_sink_input_kill(pa_sink_input*i) {
     pa_sink_input_assert_ref(i);
 /* Called from main context */
 void pa_sink_input_kill(pa_sink_input*i) {
     pa_sink_input_assert_ref(i);
+    pa_assert_ctl_context();
     pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
 
     i->kill(i);
     pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
 
     i->kill(i);
@@ -548,6 +640,7 @@ pa_usec_t pa_sink_input_get_latency(pa_sink_input *i, pa_usec_t *sink_latency) {
     pa_usec_t r[2] = { 0, 0 };
 
     pa_sink_input_assert_ref(i);
     pa_usec_t r[2] = { 0, 0 };
 
     pa_sink_input_assert_ref(i);
+    pa_assert_ctl_context();
     pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
 
     pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_GET_LATENCY, r, 0, NULL) == 0);
     pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
 
     pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_GET_LATENCY, r, 0, NULL) == 0);
@@ -563,12 +656,14 @@ pa_usec_t pa_sink_input_get_latency(pa_sink_input *i, pa_usec_t *sink_latency) {
 
 /* Called from thread context */
 void pa_sink_input_peek(pa_sink_input *i, size_t slength /* in sink frames */, pa_memchunk *chunk, pa_cvolume *volume) {
 
 /* Called from thread context */
 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 do_volume_adj_here, need_volume_factor_sink;
     pa_bool_t volume_is_norm;
     pa_bool_t volume_is_norm;
+    pa_bool_t ramping;
     size_t block_size_max_sink, block_size_max_sink_input;
     size_t ilength;
 
     pa_sink_input_assert_ref(i);
     size_t block_size_max_sink, block_size_max_sink_input;
     size_t ilength;
 
     pa_sink_input_assert_ref(i);
+    pa_sink_input_assert_io_context(i);
     pa_assert(PA_SINK_INPUT_IS_LINKED(i->thread_info.state));
     pa_assert(pa_frame_aligned(slength, &i->sink->sample_spec));
     pa_assert(chunk);
     pa_assert(PA_SINK_INPUT_IS_LINKED(i->thread_info.state));
     pa_assert(pa_frame_aligned(slength, &i->sink->sample_spec));
     pa_assert(chunk);
@@ -608,8 +703,9 @@ void pa_sink_input_peek(pa_sink_input *i, size_t slength /* in sink frames */, p
      * to adjust the volume *before* we resample. Otherwise we can do
      * it after and leave it for the sink code */
 
      * to adjust the volume *before* we resample. Otherwise we can do
      * it after and leave it for the sink code */
 
-    do_volume_adj_here = !pa_channel_map_equal(&i->channel_map, &i->sink->channel_map);
+    do_volume_adj_here = !pa_channel_map_equal(&i->channel_map, &i->sink->channel_map) || i->thread_info.ramp_info.is_ramping;
     volume_is_norm = pa_cvolume_is_norm(&i->thread_info.soft_volume) && !i->thread_info.muted;
     volume_is_norm = pa_cvolume_is_norm(&i->thread_info.soft_volume) && !i->thread_info.muted;
+    need_volume_factor_sink = !pa_cvolume_is_norm(&i->volume_factor_sink);
 
     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;
@@ -624,7 +720,7 @@ void pa_sink_input_peek(pa_sink_input *i, size_t slength /* in sink frames */, p
              * 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;
@@ -641,6 +737,7 @@ void pa_sink_input_peek(pa_sink_input *i, size_t slength /* in sink frames */, p
 
         while (tchunk.length > 0) {
             pa_memchunk wchunk;
 
         while (tchunk.length > 0) {
             pa_memchunk wchunk;
+            pa_bool_t nvfs = need_volume_factor_sink;
 
             wchunk = tchunk;
             pa_memblock_ref(wchunk.memblock);
 
             wchunk = tchunk;
             pa_memblock_ref(wchunk.memblock);
@@ -649,24 +746,48 @@ void pa_sink_input_peek(pa_sink_input *i, size_t slength /* in sink frames */, p
                 wchunk.length = block_size_max_sink_input;
 
             /* It might be necessary to adjust the volume here */
                 wchunk.length = block_size_max_sink_input;
 
             /* It might be necessary to adjust the volume here */
-            if (do_volume_adj_here && !volume_is_norm) {
+            if (do_volume_adj_here && !volume_is_norm && !i->thread_info.ramp_info.is_ramping) {
                 pa_memchunk_make_writable(&wchunk, 0);
 
                 pa_memchunk_make_writable(&wchunk, 0);
 
-                if (i->thread_info.muted)
+                if (i->thread_info.muted) {
                     pa_silence_memchunk(&wchunk, &i->thread_info.sample_spec);
                     pa_silence_memchunk(&wchunk, &i->thread_info.sample_spec);
-                else
+                    nvfs = FALSE;
+
+                } else if (!i->thread_info.resampler && nvfs) {
+                    pa_cvolume v;
+
+                    /* If we don't need a resampler we can merge the
+                     * post and the pre volume adjustment into one */
+
+                    pa_sw_cvolume_multiply(&v, &i->thread_info.soft_volume, &i->volume_factor_sink);
+                    pa_volume_memchunk(&wchunk, &i->thread_info.sample_spec, &v);
+                    nvfs = FALSE;
+
+                } else
                     pa_volume_memchunk(&wchunk, &i->thread_info.sample_spec, &i->thread_info.soft_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) {
+
+                if (nvfs) {
+                    pa_memchunk_make_writable(&wchunk, 0);
+                    pa_volume_memchunk(&wchunk, &i->sink->sample_spec, &i->volume_factor_sink);
+                }
+
                 pa_memblockq_push_align(i->thread_info.render_memblockq, &wchunk);
                 pa_memblockq_push_align(i->thread_info.render_memblockq, &wchunk);
-            else {
+            else {
                 pa_memchunk rchunk;
                 pa_resampler_run(i->thread_info.resampler, &wchunk, &rchunk);
 
 /*                 pa_log_debug("pushing %lu", (unsigned long) rchunk.length); */
 
                 if (rchunk.memblock) {
                 pa_memchunk rchunk;
                 pa_resampler_run(i->thread_info.resampler, &wchunk, &rchunk);
 
 /*                 pa_log_debug("pushing %lu", (unsigned long) rchunk.length); */
 
                 if (rchunk.memblock) {
+
+                    if (nvfs) {
+                        pa_memchunk_make_writable(&rchunk, 0);
+                        pa_volume_memchunk(&rchunk, &i->sink->sample_spec, &i->volume_factor_sink);
+                    }
+
                     pa_memblockq_push_align(i->thread_info.render_memblockq, &rchunk);
                     pa_memblock_unref(rchunk.memblock);
                 }
                     pa_memblockq_push_align(i->thread_info.render_memblockq, &rchunk);
                     pa_memblock_unref(rchunk.memblock);
                 }
@@ -691,6 +812,23 @@ void pa_sink_input_peek(pa_sink_input *i, size_t slength /* in sink frames */, p
     if (chunk->length > block_size_max_sink)
         chunk->length = block_size_max_sink;
 
     if (chunk->length > block_size_max_sink)
         chunk->length = block_size_max_sink;
 
+    ramping = i->thread_info.ramp_info.is_ramping;
+    if (ramping)
+        sink_input_volume_ramping(i, chunk);
+
+    if (!i->thread_info.ramp_info.envelope_dead) {
+        i->thread_info.ramp_info.envelope_dying += chunk->length;
+        pa_log_debug("Envelope dying is %d, chunk length is %zu, dead thresholder is %lu\n", i->thread_info.ramp_info.envelope_dying,
+                chunk->length,
+                i->sink->thread_info.max_rewind + pa_envelope_length(i->thread_info.ramp_info.envelope));
+
+        if (i->thread_info.ramp_info.envelope_dying >= (int32_t) (i->sink->thread_info.max_rewind + pa_envelope_length(i->thread_info.ramp_info.envelope))) {
+            pa_log_debug("RELEASE Envelop");
+            i->thread_info.ramp_info.envelope_dead = TRUE;
+            sink_input_release_envelope(i);
+        }
+    }
+
     /* Let's see if we had to apply the volume adjustment ourselves,
      * or if this can be done by the sink for us */
 
     /* Let's see if we had to apply the volume adjustment ourselves,
      * or if this can be done by the sink for us */
 
@@ -706,8 +844,9 @@ void pa_sink_input_peek(pa_sink_input *i, size_t slength /* in sink frames */, p
 
 /* Called from thread context */
 void pa_sink_input_drop(pa_sink_input *i, size_t nbytes /* in sink sample spec */) {
 
 /* Called from thread context */
 void pa_sink_input_drop(pa_sink_input *i, size_t nbytes /* in sink sample spec */) {
-    pa_sink_input_assert_ref(i);
 
 
+    pa_sink_input_assert_ref(i);
+    pa_sink_input_assert_io_context(i);
     pa_assert(PA_SINK_INPUT_IS_LINKED(i->thread_info.state));
     pa_assert(pa_frame_aligned(nbytes, &i->sink->sample_spec));
     pa_assert(nbytes > 0);
     pa_assert(PA_SINK_INPUT_IS_LINKED(i->thread_info.state));
     pa_assert(pa_frame_aligned(nbytes, &i->sink->sample_spec));
     pa_assert(nbytes > 0);
@@ -721,8 +860,9 @@ void pa_sink_input_drop(pa_sink_input *i, size_t nbytes /* in sink sample spec *
 void pa_sink_input_process_rewind(pa_sink_input *i, size_t nbytes /* in sink sample spec */) {
     size_t lbq;
     pa_bool_t called = FALSE;
 void pa_sink_input_process_rewind(pa_sink_input *i, size_t nbytes /* in sink sample spec */) {
     size_t lbq;
     pa_bool_t called = FALSE;
-    pa_sink_input_assert_ref(i);
 
 
+    pa_sink_input_assert_ref(i);
+    pa_sink_input_assert_io_context(i);
     pa_assert(PA_SINK_INPUT_IS_LINKED(i->thread_info.state));
     pa_assert(pa_frame_aligned(nbytes, &i->sink->sample_spec));
 
     pa_assert(PA_SINK_INPUT_IS_LINKED(i->thread_info.state));
     pa_assert(pa_frame_aligned(nbytes, &i->sink->sample_spec));
 
@@ -733,6 +873,7 @@ void pa_sink_input_process_rewind(pa_sink_input *i, size_t nbytes /* in sink sam
     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);
     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);
+        sink_input_rewind_ramp_info(i, nbytes);
     }
 
     if (i->thread_info.rewrite_nbytes == (size_t) -1) {
     }
 
     if (i->thread_info.rewrite_nbytes == (size_t) -1) {
@@ -740,7 +881,7 @@ void pa_sink_input_process_rewind(pa_sink_input *i, size_t nbytes /* in sink sam
         /* We were asked to drop all buffered data, and rerequest new
          * data from implementor the next time push() is called */
 
         /* We were asked to drop all buffered data, and rerequest new
          * data from implementor the next time push() is called */
 
-        pa_memblockq_flush_write(i->thread_info.render_memblockq);
+        pa_memblockq_flush_write(i->thread_info.render_memblockq, TRUE);
 
     } else if (i->thread_info.rewrite_nbytes > 0) {
         size_t max_rewrite, amount;
 
     } else if (i->thread_info.rewrite_nbytes > 0) {
         size_t max_rewrite, amount;
@@ -769,7 +910,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);
@@ -789,9 +930,29 @@ void pa_sink_input_process_rewind(pa_sink_input *i, size_t nbytes /* in sink sam
     i->thread_info.dont_rewind_render = FALSE;
 }
 
     i->thread_info.dont_rewind_render = FALSE;
 }
 
+/* Called from thread context */
+size_t pa_sink_input_get_max_rewind(pa_sink_input *i) {
+    pa_sink_input_assert_ref(i);
+    pa_sink_input_assert_io_context(i);
+
+    return i->thread_info.resampler ? pa_resampler_request(i->thread_info.resampler, i->sink->thread_info.max_rewind) : i->sink->thread_info.max_rewind;
+}
+
+/* Called from thread context */
+size_t pa_sink_input_get_max_request(pa_sink_input *i) {
+    pa_sink_input_assert_ref(i);
+    pa_sink_input_assert_io_context(i);
+
+    /* We're not verifying the status here, to allow this to be called
+     * in the state change handler between _INIT and _RUNNING */
+
+    return i->thread_info.resampler ? pa_resampler_request(i->thread_info.resampler, i->sink->thread_info.max_request) : i->sink->thread_info.max_request;
+}
+
 /* Called from thread context */
 void pa_sink_input_update_max_rewind(pa_sink_input *i, size_t nbytes  /* in the sink's sample spec */) {
     pa_sink_input_assert_ref(i);
 /* Called from thread context */
 void pa_sink_input_update_max_rewind(pa_sink_input *i, size_t nbytes  /* in the sink's sample spec */) {
     pa_sink_input_assert_ref(i);
+    pa_sink_input_assert_io_context(i);
     pa_assert(PA_SINK_INPUT_IS_LINKED(i->thread_info.state));
     pa_assert(pa_frame_aligned(nbytes, &i->sink->sample_spec));
 
     pa_assert(PA_SINK_INPUT_IS_LINKED(i->thread_info.state));
     pa_assert(pa_frame_aligned(nbytes, &i->sink->sample_spec));
 
@@ -804,6 +965,7 @@ void pa_sink_input_update_max_rewind(pa_sink_input *i, size_t nbytes  /* in the
 /* Called from thread context */
 void pa_sink_input_update_max_request(pa_sink_input *i, size_t nbytes  /* in the sink's sample spec */) {
     pa_sink_input_assert_ref(i);
 /* Called from thread context */
 void pa_sink_input_update_max_request(pa_sink_input *i, size_t nbytes  /* in the sink's sample spec */) {
     pa_sink_input_assert_ref(i);
+    pa_sink_input_assert_io_context(i);
     pa_assert(PA_SINK_INPUT_IS_LINKED(i->thread_info.state));
     pa_assert(pa_frame_aligned(nbytes, &i->sink->sample_spec));
 
     pa_assert(PA_SINK_INPUT_IS_LINKED(i->thread_info.state));
     pa_assert(pa_frame_aligned(nbytes, &i->sink->sample_spec));
 
@@ -811,29 +973,19 @@ void pa_sink_input_update_max_request(pa_sink_input *i, size_t nbytes  /* in the
         i->update_max_request(i, i->thread_info.resampler ? pa_resampler_request(i->thread_info.resampler, nbytes) : nbytes);
 }
 
         i->update_max_request(i, i->thread_info.resampler ? pa_resampler_request(i->thread_info.resampler, nbytes) : nbytes);
 }
 
-/* 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);
 /* 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_sink_input_assert_io_context(i);
+
+    if (!(i->sink->flags & PA_SINK_DYNAMIC_LATENCY))
+        usec = i->sink->thread_info.fixed_latency;
+
+    if (usec != (pa_usec_t) -1)
+        usec = PA_CLAMP(usec, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency);
 
 
-    usec = fixup_latency(i->sink, usec);
     i->thread_info.requested_sink_latency = usec;
     i->thread_info.requested_sink_latency = usec;
-    pa_sink_invalidate_requested_latency(i->sink);
+    pa_sink_invalidate_requested_latency(i->sink, TRUE);
 
     return usec;
 }
 
     return usec;
 }
@@ -841,100 +993,105 @@ pa_usec_t pa_sink_input_set_requested_latency_within_thread(pa_sink_input *i, pa
 /* Called from main context */
 pa_usec_t pa_sink_input_set_requested_latency(pa_sink_input *i, pa_usec_t usec) {
     pa_sink_input_assert_ref(i);
 /* Called from main context */
 pa_usec_t pa_sink_input_set_requested_latency(pa_sink_input *i, pa_usec_t usec) {
     pa_sink_input_assert_ref(i);
+    pa_assert_ctl_context();
 
 
-    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 */
+
+    if (i->sink) {
+        if (!(i->sink->flags & PA_SINK_DYNAMIC_LATENCY))
+            usec = pa_sink_get_fixed_latency(i->sink);
+
+        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;
+    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);
+    pa_assert_ctl_context();
 
 
-    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_bool_t save) {
+static void set_real_ratio(pa_sink_input *i, const pa_cvolume *v) {
     pa_sink_input_assert_ref(i);
     pa_sink_input_assert_ref(i);
+    pa_assert_ctl_context();
     pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
     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));
-
-    if (pa_cvolume_equal(volume, &i->virtual_volume))
-        return;
-
-    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_assert(!v || pa_cvolume_compatible(v, &i->sample_spec));
 
 
-        pa_sink_update_flat_volume(i->sink, &new_volume);
-        pa_sink_set_volume(i->sink, &new_volume, FALSE, TRUE);
+    /* This basically calculates:
+     *
+     * i->real_ratio := v
+     * i->soft_volume := i->real_ratio * i->volume_factor */
 
 
-    } else {
+    if (v)
+        i->real_ratio = *v;
+    else
+        pa_cvolume_reset(&i->real_ratio, i->sample_spec.channels);
 
 
-        /* OK, we are in normal volume mode. The volume only affects
-         * ourselves */
-        pa_sw_cvolume_multiply(&i->soft_volume, volume, &i->volume_factor);
+    pa_sw_cvolume_multiply(&i->soft_volume, &i->real_ratio, &i->volume_factor);
+    /* We don't copy the data to the thread_info data. That's left for someone else to do */
+}
 
 
-        /* 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);
+/* Called from main context */
+void pa_sink_input_set_volume(pa_sink_input *i, const pa_cvolume *volume, pa_bool_t save, pa_bool_t absolute) {
 
 
-        pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_SET_SOFT_VOLUME, NULL, 0, NULL) == 0);
-    }
+    /* Do not allow for volume changes for non-audio types */
+    if (i->flags & PA_SINK_INPUT_PASSTHROUGH)
+        return;
 
 
-    /* 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);
+    /* test ramping -> return pa_sink_input_set_volume_with_ramping(i, volume, save, absolute, 2000 * PA_USEC_PER_MSEC); */
+    return pa_sink_input_set_volume_with_ramping(i, volume, save, absolute, 0);
 }
 
 /* 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_sink_input_assert_ref(i);
+    pa_assert_ctl_context();
     pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
 
     pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
 
-    return &i->virtual_volume;
+    if (absolute || !(i->sink->flags & PA_SINK_FLAT_VOLUME))
+        *volume = i->volume;
+    else
+        *volume = i->reference_ratio;
+
+    return volume;
 }
 
 /* Called from main context */
 void pa_sink_input_set_mute(pa_sink_input *i, pa_bool_t mute, pa_bool_t save) {
 }
 
 /* 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));
-
-    if (!i->muted == !mute)
-        return;
-
-    i->muted = 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);
-    pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index);
+    /* test ramping -> return pa_sink_input_set_mute_with_ramping(i, mute, save, 2000 * PA_USEC_PER_MSEC); */
+    return pa_sink_input_set_mute_with_ramping(i, mute, save, 0);
 }
 
 /* Called from main context */
 pa_bool_t pa_sink_input_get_mute(pa_sink_input *i) {
     pa_sink_input_assert_ref(i);
 }
 
 /* Called from main context */
 pa_bool_t 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));
 
     return i->muted;
     pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
 
     return i->muted;
@@ -943,11 +1100,12 @@ pa_bool_t pa_sink_input_get_mute(pa_sink_input *i) {
 /* 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);
 /* 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);
+    pa_assert_ctl_context();
 
     if (p)
         pa_proplist_update(i->proplist, mode, p);
 
 
     if (p)
         pa_proplist_update(i->proplist, mode, p);
 
-    if (PA_SINK_IS_LINKED(i->state)) {
+    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);
     }
         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);
     }
@@ -956,6 +1114,7 @@ void pa_sink_input_update_proplist(pa_sink_input *i, pa_update_mode_t mode, pa_p
 /* 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);
+    pa_assert_ctl_context();
     pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
 
     sink_input_set_state(i, b ? PA_SINK_INPUT_CORKED : PA_SINK_INPUT_RUNNING);
     pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
 
     sink_input_set_state(i, b ? PA_SINK_INPUT_CORKED : PA_SINK_INPUT_RUNNING);
@@ -964,6 +1123,7 @@ void pa_sink_input_cork(pa_sink_input *i, pa_bool_t b) {
 /* Called from main context */
 int pa_sink_input_set_rate(pa_sink_input *i, uint32_t rate) {
     pa_sink_input_assert_ref(i);
 /* Called from main context */
 int pa_sink_input_set_rate(pa_sink_input *i, uint32_t rate) {
     pa_sink_input_assert_ref(i);
+    pa_assert_ctl_context();
     pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
     pa_return_val_if_fail(i->thread_info.resampler, -PA_ERR_BADSTATE);
 
     pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
     pa_return_val_if_fail(i->thread_info.resampler, -PA_ERR_BADSTATE);
 
@@ -982,13 +1142,14 @@ int pa_sink_input_set_rate(pa_sink_input *i, uint32_t rate) {
 void pa_sink_input_set_name(pa_sink_input *i, const char *name) {
     const char *old;
     pa_sink_input_assert_ref(i);
 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 (!name && !pa_proplist_contains(i->proplist, PA_PROP_MEDIA_NAME))
         return;
 
     old = pa_proplist_gets(i->proplist, PA_PROP_MEDIA_NAME);
 
-    if (old && name && !strcmp(old, name))
+    if (old && name && pa_streq(old, name))
         return;
 
     if (name)
         return;
 
     if (name)
@@ -1005,6 +1166,7 @@ void pa_sink_input_set_name(pa_sink_input *i, const char *name) {
 /* Called from main context */
 pa_resample_method_t pa_sink_input_get_resample_method(pa_sink_input *i) {
     pa_sink_input_assert_ref(i);
 /* 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();
 
     return i->actual_resample_method;
 }
 
     return i->actual_resample_method;
 }
@@ -1012,13 +1174,14 @@ pa_resample_method_t pa_sink_input_get_resample_method(pa_sink_input *i) {
 /* Called from main context */
 pa_bool_t pa_sink_input_may_move(pa_sink_input *i) {
     pa_sink_input_assert_ref(i);
 /* Called from main context */
 pa_bool_t pa_sink_input_may_move(pa_sink_input *i) {
     pa_sink_input_assert_ref(i);
+    pa_assert_ctl_context();
     pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
 
     if (i->flags & PA_SINK_INPUT_DONT_MOVE)
         return FALSE;
 
     if (i->sync_next || i->sync_prev) {
     pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
 
     if (i->flags & PA_SINK_INPUT_DONT_MOVE)
         return FALSE;
 
     if (i->sync_next || i->sync_prev) {
-        pa_log_warn("Moving synchronised streams not supported.");
+        pa_log_warn("Moving synchronized streams not supported.");
         return FALSE;
     }
 
         return FALSE;
     }
 
@@ -1028,6 +1191,7 @@ pa_bool_t pa_sink_input_may_move(pa_sink_input *i) {
 /* 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);
 /* 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_ctl_context();
     pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
     pa_sink_assert_ref(dest);
 
     pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
     pa_sink_assert_ref(dest);
 
@@ -1042,6 +1206,9 @@ pa_bool_t pa_sink_input_may_move_to(pa_sink_input *i, pa_sink *dest) {
         return FALSE;
     }
 
         return FALSE;
     }
 
+    if (check_passthrough_connection(i->flags, dest) < 0)
+        return FALSE;
+
     if (i->may_move_to)
         if (!i->may_move_to(i, dest))
             return FALSE;
     if (i->may_move_to)
         if (!i->may_move_to(i, dest))
             return FALSE;
@@ -1052,10 +1219,10 @@ pa_bool_t 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;
 /* 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);
     int r;
 
     pa_sink_input_assert_ref(i);
+    pa_assert_ctl_context();
     pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
     pa_assert(i->sink);
 
     pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
     pa_assert(i->sink);
 
@@ -1065,8 +1232,6 @@ 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;
 
     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);
     /* Kill directly connected outputs */
     while ((o = pa_idxset_first(i->direct_outputs, NULL))) {
         pa_assert(o != p);
@@ -1080,24 +1245,19 @@ int pa_sink_input_start_move(pa_sink_input *i) {
     if (pa_sink_input_get_state(i) == PA_SINK_INPUT_CORKED)
         pa_assert_se(i->sink->n_corked-- >= 1);
 
     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 absolute volume relative */
-        i->virtual_volume = i->soft_volume;
-        i->soft_volume = i->volume_factor;
-
+    if (i->sink->flags & PA_SINK_FLAT_VOLUME)
         /* We might need to update the sink's volume if we are in flat
          * volume mode. */
         /* 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);
-    }
+        pa_sink_set_volume(i->sink, NULL, 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);
 
     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);
+    pa_cvolume_remap(&i->volume_factor_sink, &i->sink->channel_map, &i->channel_map);
     i->sink = NULL;
 
     i->sink = NULL;
 
+    pa_sink_input_unref(i);
+
     return 0;
 }
 
     return 0;
 }
 
@@ -1106,6 +1266,7 @@ 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_resampler *new_resampler;
 
     pa_sink_input_assert_ref(i);
+    pa_assert_ctl_context();
     pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
     pa_assert(!i->sink);
     pa_sink_assert_ref(dest);
     pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
     pa_assert(!i->sink);
     pa_sink_assert_ref(dest);
@@ -1140,9 +1301,14 @@ int pa_sink_input_finish_move(pa_sink_input *i, pa_sink *dest, pa_bool_t save) {
     } else
         new_resampler = NULL;
 
     } else
         new_resampler = NULL;
 
+    if (i->moving)
+        i->moving(i, dest);
+
     i->sink = dest;
     i->save_sink = save;
     i->sink = dest;
     i->save_sink = save;
-    pa_idxset_put(dest->inputs, i, NULL);
+    pa_idxset_put(dest->inputs, pa_sink_input_ref(i), NULL);
+
+    pa_cvolume_remap(&i->volume_factor_sink, &i->channel_map, &i->sink->channel_map);
 
     if (pa_sink_input_get_state(i) == PA_SINK_INPUT_CORKED)
         i->sink->n_corked++;
 
     if (pa_sink_input_get_state(i) == PA_SINK_INPUT_CORKED)
         i->sink->n_corked++;
@@ -1166,20 +1332,18 @@ int pa_sink_input_finish_move(pa_sink_input *i, pa_sink *dest, pa_bool_t save) {
                 0,
                 &i->sink->silence);
     }
                 0,
                 &i->sink->silence);
     }
-
     pa_sink_update_status(dest);
 
     if (i->sink->flags & PA_SINK_FLAT_VOLUME) {
     pa_sink_update_status(dest);
 
     if (i->sink->flags & PA_SINK_FLAT_VOLUME) {
-        pa_cvolume new_volume;
+        pa_cvolume remapped;
 
 
-        /* Make relative volume absolute again */
-        pa_cvolume t = dest->virtual_volume;
-        pa_cvolume_remap(&t, &dest->channel_map, &i->channel_map);
-        pa_sw_cvolume_multiply(&i->virtual_volume, &i->virtual_volume, &t);
+        /* Make relative volumes absolute */
+        remapped = dest->reference_volume;
+        pa_cvolume_remap(&remapped, &dest->channel_map, &i->channel_map);
+        pa_sw_cvolume_multiply(&i->volume, &i->reference_ratio, &remapped);
 
         /* We might need to update the sink's volume if we are in flat volume mode. */
 
         /* 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);
+        pa_sink_set_volume(i->sink, NULL, FALSE, i->save_volume);
     }
 
     pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i->sink), PA_SINK_MESSAGE_FINISH_MOVE, i, 0, NULL) == 0);
     }
 
     pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i->sink), PA_SINK_MESSAGE_FINISH_MOVE, i, 0, NULL) == 0);
@@ -1187,20 +1351,40 @@ int pa_sink_input_finish_move(pa_sink_input *i, pa_sink *dest, pa_bool_t save) {
     pa_log_debug("Successfully moved sink input %i to %s.", i->index, dest->name);
 
     /* Notify everyone */
     pa_log_debug("Successfully moved sink input %i to %s.", i->index, dest->name);
 
     /* Notify everyone */
-    if (i->moved)
-        i->moved(i);
-
     pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_FINISH], i);
     pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_FINISH], i);
+
+    if (i->volume_changed)
+        i->volume_changed(i);
+
     pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index);
 
     return 0;
 }
 
     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_fail_move(pa_sink_input *i) {
+
+    pa_sink_input_assert_ref(i);
+    pa_assert_ctl_context();
+    pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
+    pa_assert(!i->sink);
+
+    /* Check if someone wants this sink input? */
+    if (pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_FAIL], i) == PA_HOOK_STOP)
+        return;
+
+    if (i->moving)
+        i->moving(i, NULL);
+
+    pa_sink_input_kill(i);
+}
+
 /* 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);
 /* 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_ctl_context();
     pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
     pa_assert(i->sink);
     pa_sink_assert_ref(dest);
     pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
     pa_assert(i->sink);
     pa_sink_assert_ref(dest);
@@ -1211,11 +1395,20 @@ int pa_sink_input_move_to(pa_sink_input *i, pa_sink *dest, pa_bool_t save) {
     if (!pa_sink_input_may_move_to(i, dest))
         return -PA_ERR_NOTSUPPORTED;
 
     if (!pa_sink_input_may_move_to(i, dest))
         return -PA_ERR_NOTSUPPORTED;
 
-    if ((r = pa_sink_input_start_move(i)) < 0)
+    pa_sink_input_ref(i);
+
+    if ((r = pa_sink_input_start_move(i)) < 0) {
+        pa_sink_input_unref(i);
         return r;
         return r;
+    }
 
 
-    if ((r = pa_sink_input_finish_move(i, dest, save)) < 0)
+    if ((r = pa_sink_input_finish_move(i, dest, save)) < 0) {
+        pa_sink_input_fail_move(i);
+        pa_sink_input_unref(i);
         return r;
         return r;
+    }
+
+    pa_sink_input_unref(i);
 
     return 0;
 }
 
     return 0;
 }
@@ -1223,7 +1416,9 @@ int pa_sink_input_move_to(pa_sink_input *i, pa_sink *dest, pa_bool_t save) {
 /* Called from IO thread context */
 void pa_sink_input_set_state_within_thread(pa_sink_input *i, pa_sink_input_state_t state) {
     pa_bool_t corking, uncorking;
 /* Called from IO thread context */
 void pa_sink_input_set_state_within_thread(pa_sink_input *i, pa_sink_input_state_t state) {
     pa_bool_t corking, uncorking;
+
     pa_sink_input_assert_ref(i);
     pa_sink_input_assert_ref(i);
+    pa_sink_input_assert_io_context(i);
 
     if (state == i->thread_info.state)
         return;
 
     if (state == i->thread_info.state)
         return;
@@ -1269,27 +1464,32 @@ int pa_sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int64_t
     switch (code) {
 
         case PA_SINK_INPUT_MESSAGE_SET_SOFT_VOLUME:
     switch (code) {
 
         case PA_SINK_INPUT_MESSAGE_SET_SOFT_VOLUME:
+            if (pa_atomic_load(&i->before_ramping_v))
+                i->thread_info.future_soft_volume = i->soft_volume;
+
             if (!pa_cvolume_equal(&i->thread_info.soft_volume, &i->soft_volume)) {
             if (!pa_cvolume_equal(&i->thread_info.soft_volume, &i->soft_volume)) {
-                i->thread_info.soft_volume = i->soft_volume;
+                if (!pa_atomic_load(&i->before_ramping_v))
+                    i->thread_info.soft_volume = i->soft_volume;
                 pa_sink_input_request_rewind(i, 0, TRUE, FALSE, FALSE);
             }
             return 0;
 
         case PA_SINK_INPUT_MESSAGE_SET_SOFT_MUTE:
                 pa_sink_input_request_rewind(i, 0, TRUE, FALSE, FALSE);
             }
             return 0;
 
         case PA_SINK_INPUT_MESSAGE_SET_SOFT_MUTE:
+            if (pa_atomic_load(&i->before_ramping_m))
+                i->thread_info.future_muted = i->muted;
+
             if (i->thread_info.muted != i->muted) {
             if (i->thread_info.muted != i->muted) {
-                i->thread_info.muted = i->muted;
+                if (!pa_atomic_load(&i->before_ramping_m))
+                    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;
                 pa_sink_input_request_rewind(i, 0, TRUE, FALSE, FALSE);
             }
             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;
         }
@@ -1328,6 +1528,26 @@ int pa_sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int64_t
             *r = i->thread_info.requested_sink_latency;
             return 0;
         }
             *r = i->thread_info.requested_sink_latency;
             return 0;
         }
+
+        case PA_SINK_INPUT_MESSAGE_SET_ENVELOPE: {
+            if (!i->thread_info.ramp_info.envelope)
+                i->thread_info.ramp_info.envelope = pa_envelope_new(&i->sink->sample_spec);
+
+            if (i->thread_info.ramp_info.envelope && i->thread_info.ramp_info.item) {
+                pa_envelope_remove(i->thread_info.ramp_info.envelope, i->thread_info.ramp_info.item);
+                i->thread_info.ramp_info.item = NULL;
+            }
+
+            i->thread_info.ramp_info.item = pa_envelope_add(i->thread_info.ramp_info.envelope, &i->using_def);
+            i->thread_info.ramp_info.is_ramping = TRUE;
+            i->thread_info.ramp_info.envelope_dead = FALSE;
+            i->thread_info.ramp_info.envelope_dying = 0;
+
+            if (i->thread_info.ramp_info.envelope)
+                pa_envelope_restart(i->thread_info.ramp_info.envelope);
+
+            return 0;
+        }
     }
 
     return -PA_ERR_NOTIMPLEMENTED;
     }
 
     return -PA_ERR_NOTIMPLEMENTED;
@@ -1336,6 +1556,7 @@ int pa_sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int64_t
 /* Called from main thread */
 pa_sink_input_state_t pa_sink_input_get_state(pa_sink_input *i) {
     pa_sink_input_assert_ref(i);
 /* Called from main thread */
 pa_sink_input_state_t pa_sink_input_get_state(pa_sink_input *i) {
     pa_sink_input_assert_ref(i);
+    pa_assert_ctl_context();
 
     if (i->state == PA_SINK_INPUT_RUNNING || i->state == PA_SINK_INPUT_DRAINED)
         return pa_atomic_load(&i->thread_info.drained) ? PA_SINK_INPUT_DRAINED : PA_SINK_INPUT_RUNNING;
 
     if (i->state == PA_SINK_INPUT_RUNNING || i->state == PA_SINK_INPUT_DRAINED)
         return pa_atomic_load(&i->thread_info.drained) ? PA_SINK_INPUT_DRAINED : PA_SINK_INPUT_RUNNING;
@@ -1346,6 +1567,7 @@ pa_sink_input_state_t pa_sink_input_get_state(pa_sink_input *i) {
 /* Called from IO context */
 pa_bool_t pa_sink_input_safe_to_remove(pa_sink_input *i) {
     pa_sink_input_assert_ref(i);
 /* Called from IO context */
 pa_bool_t pa_sink_input_safe_to_remove(pa_sink_input *i) {
     pa_sink_input_assert_ref(i);
+    pa_sink_input_assert_io_context(i);
 
     if (PA_SINK_INPUT_IS_LINKED(i->thread_info.state))
         return pa_memblockq_is_empty(i->thread_info.render_memblockq);
 
     if (PA_SINK_INPUT_IS_LINKED(i->thread_info.state))
         return pa_memblockq_is_empty(i->thread_info.render_memblockq);
@@ -1354,7 +1576,13 @@ 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, pa_bool_t dont_rewind_render) {
+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
@@ -1369,18 +1597,20 @@ void pa_sink_input_request_rewind(pa_sink_input *i, size_t nbytes  /* in our sam
      * dont_rewind_render is TRUE then the render memblockq is not
      * rewound. */
 
      * dont_rewind_render is TRUE then the render memblockq is not
      * rewound. */
 
-    pa_sink_input_assert_ref(i);
+    /* nbytes = 0 means maximum rewind request */
 
 
-    nbytes = PA_MAX(i->thread_info.rewrite_nbytes, nbytes);
-
-/*     pa_log_debug("request rewrite %lu", (unsigned long) nbytes); */
+    pa_sink_input_assert_ref(i);
+    pa_sink_input_assert_io_context(i);
+    pa_assert(rewrite || flush);
+    pa_assert(!dont_rewind_render || !rewrite);
 
     /* We don't take rewind requests while we are corked */
     if (i->thread_info.state == PA_SINK_INPUT_CORKED)
         return;
 
 
     /* We don't take rewind requests while we are corked */
     if (i->thread_info.state == PA_SINK_INPUT_CORKED)
         return;
 
-    pa_assert(rewrite || flush);
-    pa_assert(!dont_rewind_render || !rewrite);
+    nbytes = PA_MAX(i->thread_info.rewrite_nbytes, nbytes);
+
+    /* pa_log_debug("request rewrite %zu", nbytes); */
 
     /* 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 */
@@ -1400,6 +1630,7 @@ 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);
     }
 
+    /* Remember how much we actually want to rewrite */
     if (i->thread_info.rewrite_nbytes != (size_t) -1) {
         if (rewrite) {
             /* Make sure to not overwrite over underruns */
     if (i->thread_info.rewrite_nbytes != (size_t) -1) {
         if (rewrite) {
             /* Make sure to not overwrite over underruns */
@@ -1436,8 +1667,11 @@ void pa_sink_input_request_rewind(pa_sink_input *i, size_t nbytes  /* in our sam
 /* Called from main context */
 pa_memchunk* pa_sink_input_get_silence(pa_sink_input *i, pa_memchunk *ret) {
     pa_sink_input_assert_ref(i);
 /* Called from main context */
 pa_memchunk* pa_sink_input_get_silence(pa_sink_input *i, pa_memchunk *ret) {
     pa_sink_input_assert_ref(i);
+    pa_assert_ctl_context();
     pa_assert(ret);
 
     pa_assert(ret);
 
+    /* FIXME: Shouldn't access resampler object from main context! */
+
     pa_silence_memchunk_get(
                 &i->core->silence_cache,
                 i->core->mempool,
     pa_silence_memchunk_get(
                 &i->core->silence_cache,
                 i->core->mempool,
@@ -1454,6 +1688,7 @@ void pa_sink_input_send_event(pa_sink_input *i, const char *event, pa_proplist *
     pa_sink_input_send_event_hook_data hook_data;
 
     pa_sink_input_assert_ref(i);
     pa_sink_input_send_event_hook_data hook_data;
 
     pa_sink_input_assert_ref(i);
+    pa_assert_ctl_context();
     pa_assert(event);
 
     if (!i->send_event)
     pa_assert(event);
 
     if (!i->send_event)
@@ -1475,3 +1710,237 @@ finish:
     if (pl)
         pa_proplist_free(pl);
 }
     if (pl)
         pa_proplist_free(pl);
 }
+
+/* Called from IO context */
+static void sink_input_volume_ramping(pa_sink_input* i, pa_memchunk* chunk) {
+    pa_assert(i);
+    pa_assert(chunk);
+    pa_assert(chunk->memblock);
+    pa_assert(i->thread_info.ramp_info.is_ramping);
+
+    /* Volume is adjusted with ramping effect here */
+    pa_envelope_apply(i->thread_info.ramp_info.envelope, chunk);
+
+    if (pa_envelope_is_finished(i->thread_info.ramp_info.envelope)) {
+        i->thread_info.ramp_info.is_ramping = FALSE;
+        if (pa_atomic_load(&i->before_ramping_v)) {
+            i->thread_info.soft_volume = i->thread_info.future_soft_volume;
+            pa_atomic_store(&i->before_ramping_v, 0);
+        }
+        else if (pa_atomic_load(&i->before_ramping_m)) {
+            i->thread_info.muted = i->thread_info.future_muted;
+            pa_atomic_store(&i->before_ramping_m, 0);
+        }
+    }
+}
+
+/*
+ * Called from main context
+ * This function should be called inside pa_sink_input_set_volume_with_ramping
+ * should be called after soft_volume of sink_input and sink are all adjusted
+ */
+static void sink_input_set_ramping_info(pa_sink_input* i, pa_volume_t  pre_virtual_volume, pa_volume_t target_virtual_volume, pa_usec_t t) {
+
+    int32_t target_abs_vol, target_apply_vol, pre_apply_vol;
+    pa_assert(i);
+
+    pa_log_debug("Sink input's soft volume is %d= %f ", pa_cvolume_avg(&i->soft_volume), pa_sw_volume_to_linear(pa_cvolume_avg(&i->soft_volume)));
+
+    /* Calculation formula are target_abs_vol := i->soft_volume
+     *                                   target_apply_vol := lrint(pa_sw_volume_to_linear(target_abs_vol) * 0x10000)
+     *                                   pre_apply_vol := ( previous_virtual_volume / target_virtual_volume ) * target_apply_vol
+     *
+     * Will do volume adjustment inside pa_sink_input_peek
+     */
+    target_abs_vol = pa_cvolume_avg(&i->soft_volume);
+    target_apply_vol = (int32_t) lrint(pa_sw_volume_to_linear(target_abs_vol) * 0x10000);
+    pre_apply_vol = (int32_t) ((pa_sw_volume_to_linear(pre_virtual_volume) / pa_sw_volume_to_linear(target_virtual_volume)) * target_apply_vol);
+
+    i->using_def.n_points = 2;
+    i->using_def.points_x[0] = 0;
+    i->using_def.points_x[1] = t;
+    i->using_def.points_y.i[0] = pre_apply_vol;
+    i->using_def.points_y.i[1] = target_apply_vol;
+    i->using_def.points_y.f[0] = ((float) i->using_def.points_y.i[0]) /0x10000;
+    i->using_def.points_y.f[1] = ((float) i->using_def.points_y.i[1]) /0x10000;
+
+    pa_log_debug("Volume Ramping: Point 1 is %d=%f, Point 2 is %d=%f\n", i->using_def.points_y.i[0], i->using_def.points_y.f[0],
+                                   i->using_def.points_y.i[1], i->using_def.points_y.f[1]);
+
+    pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_SET_ENVELOPE, NULL, 0, NULL) == 0);
+}
+
+/* Called from main context */
+static void sink_input_set_ramping_info_for_mute(pa_sink_input* i, pa_bool_t mute, pa_usec_t t) {
+
+    int32_t cur_vol;
+    pa_assert(i);
+
+    i->using_def.n_points = 2;
+    i->using_def.points_x[0] = 0;
+    i->using_def.points_x[1] = t;
+    cur_vol = (int32_t) lrint( pa_sw_volume_to_linear(pa_cvolume_avg(&i->soft_volume)) * 0x10000);
+
+    if (mute) {
+        i->using_def.points_y.i[0] = cur_vol;
+        i->using_def.points_y.i[1] = 0;
+    } else {
+        i->using_def.points_y.i[0] = 0;
+        i->using_def.points_y.i[1] = cur_vol;
+    }
+
+    i->using_def.points_y.f[0] = ((float) i->using_def.points_y.i[0]) /0x10000;
+    i->using_def.points_y.f[1] = ((float) i->using_def.points_y.i[1]) /0x10000;
+
+    pa_log_debug("Mute Ramping: Point 1 is %d=%f, Point 2 is %d=%f\n", i->using_def.points_y.i[0], i->using_def.points_y.f[0],
+                   i->using_def.points_y.i[1], i->using_def.points_y.f[1]);
+
+    pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_SET_ENVELOPE, NULL, 0, NULL) == 0);
+}
+
+/* Called from IO context */
+static void sink_input_release_envelope(pa_sink_input *i) {
+    pa_assert(i);
+    pa_assert(!i->thread_info.ramp_info.is_ramping);
+    pa_assert(i->thread_info.ramp_info.envelope_dead);
+
+    pa_envelope_free(i->thread_info.ramp_info.envelope);
+    i->thread_info.ramp_info.envelope = NULL;
+    i->thread_info.ramp_info.item = NULL;
+}
+
+/* Called from IO context */
+static void sink_input_rewind_ramp_info(pa_sink_input *i, size_t nbytes) {
+    pa_assert(i);
+
+    if (!i->thread_info.ramp_info.envelope_dead) {
+        int32_t envelope_length;
+
+        pa_assert(i->thread_info.ramp_info.envelope);
+
+        envelope_length = pa_envelope_length(i->thread_info.ramp_info.envelope);
+
+        if (i->thread_info.ramp_info.envelope_dying > envelope_length) {
+            if ((int32_t) (i->thread_info.ramp_info.envelope_dying - nbytes) < envelope_length) {
+                pa_log_debug("Envelope Become Alive");
+                pa_envelope_rewind(i->thread_info.ramp_info.envelope, envelope_length - (i->thread_info.ramp_info.envelope_dying - nbytes));
+                i->thread_info.ramp_info.is_ramping = TRUE;
+            }
+        } else if (i->thread_info.ramp_info.envelope_dying < envelope_length) {
+            if ((i->thread_info.ramp_info.envelope_dying - (ssize_t) nbytes) <= 0) {
+                pa_log_debug("Envelope Restart");
+                pa_envelope_restart(i->thread_info.ramp_info.envelope);
+            }
+            else {
+                pa_log_debug("Envelope Simple Rewind");
+                pa_envelope_rewind(i->thread_info.ramp_info.envelope, nbytes);
+            }
+        }
+
+        i->thread_info.ramp_info.envelope_dying -= nbytes;
+        if (i->thread_info.ramp_info.envelope_dying <= 0)
+            i->thread_info.ramp_info.envelope_dying = 0;
+    }
+}
+
+void pa_sink_input_set_volume_with_ramping(pa_sink_input *i, const pa_cvolume *volume, pa_bool_t save, pa_bool_t absolute, pa_usec_t t){
+    pa_cvolume v;
+    pa_volume_t previous_virtual_volume, target_virtual_volume;
+
+    pa_sink_input_assert_ref(i);
+    pa_assert_ctl_context();
+    pa_assert(PA_SINK_INPUT_IS_LINKED(i->state));
+    pa_assert(volume);
+    pa_assert(pa_cvolume_valid(volume));
+    pa_assert(volume->channels == 1 || pa_cvolume_compatible(volume, &i->sample_spec));
+
+    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);
+
+        if (pa_cvolume_compatible(volume, &i->sample_spec))
+            volume = pa_sw_cvolume_multiply(&v, &v, volume);
+        else
+            volume = pa_sw_cvolume_multiply_scalar(&v, &v, pa_cvolume_max(volume));
+    } else {
+
+        if (!pa_cvolume_compatible(volume, &i->sample_spec)) {
+            v = i->volume;
+            volume = pa_cvolume_scale(&v, pa_cvolume_max(volume));
+        }
+    }
+
+    if (pa_cvolume_equal(volume, &i->volume)) {
+        i->save_volume = i->save_volume || save;
+        return;
+    }
+
+    previous_virtual_volume = pa_cvolume_avg(&i->volume);
+    target_virtual_volume = pa_cvolume_avg(volume);
+
+    if (t > 0 && target_virtual_volume > 0)
+        pa_log_debug("SetVolumeWithRamping: Virtual Volume From %u=%f to %u=%f\n", previous_virtual_volume, pa_sw_volume_to_linear(previous_virtual_volume),
+                                             target_virtual_volume, pa_sw_volume_to_linear(target_virtual_volume));
+
+    i->volume = *volume;
+    i->save_volume = save;
+
+    /* Set this flag before the following code modify i->thread_info.soft_volume */
+    if (t > 0 && target_virtual_volume > 0)
+        pa_atomic_store(&i->before_ramping_v, 1);
+
+    if (i->sink->flags & PA_SINK_FLAT_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_set_volume(i->sink, NULL, TRUE, save);
+
+    } else {
+        /* OK, we are in normal volume mode. The volume only affects
+         * ourselves */
+        set_real_ratio(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);
+    }
+
+    if (t > 0 && target_virtual_volume > 0)
+        sink_input_set_ramping_info(i, previous_virtual_volume, target_virtual_volume, t);
+
+    /* 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_set_mute_with_ramping(pa_sink_input *i, pa_bool_t mute, pa_bool_t save, pa_usec_t t){
+
+    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;
+        return;
+    }
+
+    i->muted = mute;
+    i->save_muted = save;
+
+    /* Set this flag before the following code modify i->thread_info.muted, otherwise distortion will be heard */
+    if (t > 0)
+        pa_atomic_store(&i->before_ramping_m, 1);
+
+    pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_SET_SOFT_MUTE, NULL, 0, NULL) == 0);
+
+    if (t > 0)
+        sink_input_set_ramping_info_for_mute(i, mute, t);
+
+    /* The mute status changed, let's tell people so */
+    if (i->mute_changed)
+        i->mute_changed(i);
+
+    pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index);
+}