]> code.delx.au - pulseaudio/blobdiff - src/pulsecore/source-output.c
Fix up some double spaces
[pulseaudio] / src / pulsecore / source-output.c
index 368c9cf9031312db41ebf86eb4fa5348404df41a..0bb8899cda53a0b60b9679dfd5c32d60e72024a9 100644 (file)
@@ -5,7 +5,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
@@ -29,6 +29,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>
 
 #define MEMBLOCKQ_MAXLENGTH (32*1024*1024)
 
 
 #define MEMBLOCKQ_MAXLENGTH (32*1024*1024)
 
-static PA_DEFINE_CHECK_TYPE(pa_source_output, pa_msgobject);
+PA_DEFINE_PUBLIC_CLASS(pa_source_output, pa_msgobject);
 
 static void source_output_free(pa_object* mo);
 
 pa_source_output_new_data* pa_source_output_new_data_init(pa_source_output_new_data *data) {
     pa_assert(data);
 
 
 static void source_output_free(pa_object* mo);
 
 pa_source_output_new_data* pa_source_output_new_data_init(pa_source_output_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();
 
@@ -83,63 +84,75 @@ static void reset_callbacks(pa_source_output *o) {
     o->update_max_rewind = NULL;
     o->update_source_requested_latency = NULL;
     o->update_source_latency_range = NULL;
     o->update_max_rewind = NULL;
     o->update_source_requested_latency = NULL;
     o->update_source_latency_range = NULL;
+    o->update_source_fixed_latency = NULL;
     o->attach = NULL;
     o->detach = NULL;
     o->suspend = NULL;
     o->attach = NULL;
     o->detach = NULL;
     o->suspend = NULL;
-    o->moved = NULL;
+    o->suspend_within_thread = NULL;
+    o->moving = NULL;
     o->kill = NULL;
     o->get_latency = NULL;
     o->state_change = NULL;
     o->kill = NULL;
     o->get_latency = NULL;
     o->state_change = NULL;
+    o->may_move_to = NULL;
+    o->send_event = NULL;
 }
 
 /* Called from main context */
 }
 
 /* Called from main context */
-pa_source_output* pa_source_output_new(
+int pa_source_output_new(
+        pa_source_output**_o,
         pa_core *core,
         pa_core *core,
-        pa_source_output_new_data *data,
-        pa_source_output_flags_t flags) {
+        pa_source_output_new_data *data) {
 
     pa_source_output *o;
     pa_resampler *resampler = NULL;
     char st[PA_SAMPLE_SPEC_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX];
 
     pa_source_output *o;
     pa_resampler *resampler = NULL;
     char st[PA_SAMPLE_SPEC_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX];
+    int r;
+    char *pt;
 
 
+    pa_assert(_o);
     pa_assert(core);
     pa_assert(data);
     pa_assert(core);
     pa_assert(data);
+    pa_assert_ctl_context();
 
 
-    if (pa_hook_fire(&core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_NEW], data) < 0)
-        return NULL;
+    if (data->client)
+        pa_proplist_update(data->proplist, PA_UPDATE_MERGE, data->client->proplist);
 
 
-    pa_return_null_if_fail(!data->driver || pa_utf8_valid(data->driver));
+    if ((r = pa_hook_fire(&core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_NEW], data)) < 0)
+        return r;
 
 
-    if (!data->source)
-        data->source = pa_namereg_get(core, NULL, PA_NAMEREG_SOURCE, TRUE);
+    pa_return_val_if_fail(!data->driver || pa_utf8_valid(data->driver), -PA_ERR_INVALID);
 
 
-    pa_return_null_if_fail(data->source);
-    pa_return_null_if_fail(pa_source_get_state(data->source) != PA_SOURCE_UNLINKED);
+    if (!data->source) {
+        data->source = pa_namereg_get(core, NULL, PA_NAMEREG_SOURCE);
+        data->save_source = FALSE;
+    }
 
 
-    pa_return_null_if_fail(!data->direct_on_input || data->direct_on_input->sink == data->source->monitor_of);
+    pa_return_val_if_fail(data->source, -PA_ERR_NOENTITY);
+    pa_return_val_if_fail(PA_SOURCE_IS_LINKED(pa_source_get_state(data->source)), -PA_ERR_BADSTATE);
+    pa_return_val_if_fail(!data->direct_on_input || data->direct_on_input->sink == data->source->monitor_of, -PA_ERR_INVALID);
 
     if (!data->sample_spec_is_set)
         data->sample_spec = data->source->sample_spec;
 
 
     if (!data->sample_spec_is_set)
         data->sample_spec = data->source->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->source->channel_map.channels == data->sample_spec.channels)
+        if (pa_channel_map_compatible(&data->source->channel_map, &data->sample_spec))
             data->channel_map = data->source->channel_map;
         else
             data->channel_map = data->source->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 (flags & PA_SOURCE_OUTPUT_FIX_FORMAT)
+    if (data->flags & PA_SOURCE_OUTPUT_FIX_FORMAT)
         data->sample_spec.format = data->source->sample_spec.format;
 
         data->sample_spec.format = data->source->sample_spec.format;
 
-    if (flags & PA_SOURCE_OUTPUT_FIX_RATE)
+    if (data->flags & PA_SOURCE_OUTPUT_FIX_RATE)
         data->sample_spec.rate = data->source->sample_spec.rate;
 
         data->sample_spec.rate = data->source->sample_spec.rate;
 
-    if (flags & PA_SOURCE_OUTPUT_FIX_CHANNELS) {
+    if (data->flags & PA_SOURCE_OUTPUT_FIX_CHANNELS) {
         data->sample_spec.channels = data->source->sample_spec.channels;
         data->channel_map = data->source->channel_map;
     }
         data->sample_spec.channels = data->source->sample_spec.channels;
         data->channel_map = data->source->channel_map;
     }
@@ -150,17 +163,23 @@ pa_source_output* pa_source_output_new(
     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 ((r = pa_hook_fire(&core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_FIXATE], data)) < 0)
+        return r;
 
 
-    if (pa_hook_fire(&core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_FIXATE], data) < 0)
-        return NULL;
+    if ((data->flags & PA_SOURCE_OUTPUT_NO_CREATE_ON_SUSPEND) &&
+        pa_source_get_state(data->source) == PA_SOURCE_SUSPENDED) {
+        pa_log("Failed to create source output: source is suspended.");
+        return -PA_ERR_BADSTATE;
+    }
 
     if (pa_idxset_size(data->source->outputs) >= PA_MAX_OUTPUTS_PER_SOURCE) {
         pa_log("Failed to create source output: too many outputs per source.");
 
     if (pa_idxset_size(data->source->outputs) >= PA_MAX_OUTPUTS_PER_SOURCE) {
         pa_log("Failed to create source output: too many outputs per source.");
-        return NULL;
+        return -PA_ERR_TOOLARGE;
     }
 
     }
 
-    if ((flags & PA_SOURCE_OUTPUT_VARIABLE_RATE) ||
+    if ((data->flags & PA_SOURCE_OUTPUT_VARIABLE_RATE) ||
         !pa_sample_spec_equal(&data->sample_spec, &data->source->sample_spec) ||
         !pa_channel_map_equal(&data->channel_map, &data->source->channel_map)) {
 
         !pa_sample_spec_equal(&data->sample_spec, &data->source->sample_spec) ||
         !pa_channel_map_equal(&data->channel_map, &data->source->channel_map)) {
 
@@ -169,14 +188,13 @@ pa_source_output* pa_source_output_new(
                       &data->source->sample_spec, &data->source->channel_map,
                       &data->sample_spec, &data->channel_map,
                       data->resample_method,
                       &data->source->sample_spec, &data->source->channel_map,
                       &data->sample_spec, &data->channel_map,
                       data->resample_method,
-                      ((flags & PA_SOURCE_OUTPUT_VARIABLE_RATE) ? PA_RESAMPLER_VARIABLE_RATE : 0) |
-                      ((flags & PA_SOURCE_OUTPUT_NO_REMAP) ? PA_RESAMPLER_NO_REMAP : 0) |
-                      (core->disable_remixing || (flags & PA_SOURCE_OUTPUT_NO_REMIX) ? PA_RESAMPLER_NO_REMIX : 0)))) {
+                      ((data->flags & PA_SOURCE_OUTPUT_VARIABLE_RATE) ? PA_RESAMPLER_VARIABLE_RATE : 0) |
+                      ((data->flags & PA_SOURCE_OUTPUT_NO_REMAP) ? PA_RESAMPLER_NO_REMAP : 0) |
+                      (core->disable_remixing || (data->flags & PA_SOURCE_OUTPUT_NO_REMIX) ? PA_RESAMPLER_NO_REMIX : 0) |
+                      (core->disable_lfe_remixing ? PA_RESAMPLER_NO_LFE : 0)))) {
             pa_log_warn("Unsupported resampling operation.");
             pa_log_warn("Unsupported resampling operation.");
-            return NULL;
+            return -PA_ERR_NOTSUPPORTED;
         }
         }
-
-        data->resample_method = pa_resampler_get_method(resampler);
     }
 
     o = pa_msgobject_new(pa_source_output);
     }
 
     o = pa_msgobject_new(pa_source_output);
@@ -185,19 +203,23 @@ pa_source_output* pa_source_output_new(
 
     o->core = core;
     o->state = PA_SOURCE_OUTPUT_INIT;
 
     o->core = core;
     o->state = PA_SOURCE_OUTPUT_INIT;
-    o->flags = flags;
+    o->flags = data->flags;
     o->proplist = pa_proplist_copy(data->proplist);
     o->proplist = pa_proplist_copy(data->proplist);
-    o->driver = pa_xstrdup(data->driver);
+    o->driver = pa_xstrdup(pa_path_get_filename(data->driver));
     o->module = data->module;
     o->source = data->source;
     o->module = data->module;
     o->source = data->source;
+    o->destination_source = data->destination_source;
     o->client = data->client;
 
     o->client = data->client;
 
-    o->resample_method = data->resample_method;
+    o->actual_resample_method = resampler ? pa_resampler_get_method(resampler) : PA_RESAMPLER_INVALID;
+    o->requested_resample_method = data->resample_method;
     o->sample_spec = data->sample_spec;
     o->channel_map = data->channel_map;
 
     o->direct_on_input = data->direct_on_input;
 
     o->sample_spec = data->sample_spec;
     o->channel_map = data->channel_map;
 
     o->direct_on_input = data->direct_on_input;
 
+    o->save_source = data->save_source;
+
     reset_callbacks(o);
     o->userdata = NULL;
 
     reset_callbacks(o);
     o->userdata = NULL;
 
@@ -221,55 +243,70 @@ pa_source_output* pa_source_output_new(
     pa_assert_se(pa_idxset_put(core->source_outputs, o, &o->index) == 0);
     pa_assert_se(pa_idxset_put(o->source->outputs, pa_source_output_ref(o), NULL) == 0);
 
     pa_assert_se(pa_idxset_put(core->source_outputs, o, &o->index) == 0);
     pa_assert_se(pa_idxset_put(o->source->outputs, pa_source_output_ref(o), NULL) == 0);
 
+    if (o->client)
+        pa_assert_se(pa_idxset_put(o->client->source_outputs, o, NULL) >= 0);
+
     if (o->direct_on_input)
         pa_assert_se(pa_idxset_put(o->direct_on_input->direct_outputs, o, NULL) == 0);
 
     if (o->direct_on_input)
         pa_assert_se(pa_idxset_put(o->direct_on_input->direct_outputs, o, NULL) == 0);
 
-    pa_log_info("Created output %u \"%s\" on %s with sample spec %s and channel map %s",
+    pt = pa_proplist_to_string_sep(o->proplist, "\n    ");
+    pa_log_info("Created output %u \"%s\" on %s with sample spec %s and channel map %s\n    %s",
                 o->index,
                 pa_strnull(pa_proplist_gets(o->proplist, PA_PROP_MEDIA_NAME)),
                 o->source->name,
                 pa_sample_spec_snprint(st, sizeof(st), &o->sample_spec),
                 o->index,
                 pa_strnull(pa_proplist_gets(o->proplist, PA_PROP_MEDIA_NAME)),
                 o->source->name,
                 pa_sample_spec_snprint(st, sizeof(st), &o->sample_spec),
-                pa_channel_map_snprint(cm, sizeof(cm), &o->channel_map));
+                pa_channel_map_snprint(cm, sizeof(cm), &o->channel_map),
+                pt);
+    pa_xfree(pt);
 
     /* Don't forget to call pa_source_output_put! */
 
 
     /* Don't forget to call pa_source_output_put! */
 
-    return o;
+    *_o = o;
+    return 0;
 }
 
 /* Called from main context */
 static void update_n_corked(pa_source_output *o, pa_source_output_state_t state) {
     pa_assert(o);
 }
 
 /* Called from main context */
 static void update_n_corked(pa_source_output *o, pa_source_output_state_t state) {
     pa_assert(o);
+    pa_assert_ctl_context();
+
+    if (!o->source)
+        return;
 
     if (o->state == PA_SOURCE_OUTPUT_CORKED && state != PA_SOURCE_OUTPUT_CORKED)
         pa_assert_se(o->source->n_corked -- >= 1);
     else if (o->state != PA_SOURCE_OUTPUT_CORKED && state == PA_SOURCE_OUTPUT_CORKED)
         o->source->n_corked++;
 
     if (o->state == PA_SOURCE_OUTPUT_CORKED && state != PA_SOURCE_OUTPUT_CORKED)
         pa_assert_se(o->source->n_corked -- >= 1);
     else if (o->state != PA_SOURCE_OUTPUT_CORKED && state == PA_SOURCE_OUTPUT_CORKED)
         o->source->n_corked++;
-
-    pa_source_update_status(o->source);
 }
 
 /* Called from main context */
 }
 
 /* Called from main context */
-static int source_output_set_state(pa_source_output *o, pa_source_output_state_t state) {
+static void source_output_set_state(pa_source_output *o, pa_source_output_state_t state) {
     pa_assert(o);
     pa_assert(o);
+    pa_assert_ctl_context();
 
     if (o->state == state)
 
     if (o->state == state)
-        return 0;
+        return;
 
     pa_assert_se(pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o), PA_SOURCE_OUTPUT_MESSAGE_SET_STATE, PA_UINT_TO_PTR(state), 0, NULL) == 0);
 
     update_n_corked(o, state);
     o->state = state;
 
 
     pa_assert_se(pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o), PA_SOURCE_OUTPUT_MESSAGE_SET_STATE, PA_UINT_TO_PTR(state), 0, NULL) == 0);
 
     update_n_corked(o, state);
     o->state = state;
 
-    if (state != PA_SOURCE_OUTPUT_UNLINKED)
-        pa_hook_fire(&o->source->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_STATE_CHANGED], o);
+    if (state != PA_SOURCE_OUTPUT_UNLINKED) {
+        pa_hook_fire(&o->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_STATE_CHANGED], o);
 
 
-    return 0;
+        if (PA_SOURCE_OUTPUT_IS_LINKED(state))
+            pa_subscription_post(o->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE, o->index);
+    }
+
+    pa_source_update_status(o->source);
 }
 
 /* Called from main context */
 void pa_source_output_unlink(pa_source_output*o) {
     pa_bool_t linked;
     pa_assert(o);
 }
 
 /* Called from main context */
 void pa_source_output_unlink(pa_source_output*o) {
     pa_bool_t linked;
     pa_assert(o);
+    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 */
@@ -279,29 +316,41 @@ void pa_source_output_unlink(pa_source_output*o) {
     linked = PA_SOURCE_OUTPUT_IS_LINKED(o->state);
 
     if (linked)
     linked = PA_SOURCE_OUTPUT_IS_LINKED(o->state);
 
     if (linked)
-        pa_hook_fire(&o->source->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK], o);
+        pa_hook_fire(&o->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK], o);
 
     if (o->direct_on_input)
         pa_idxset_remove_by_data(o->direct_on_input->direct_outputs, o, NULL);
 
     if (o->direct_on_input)
         pa_idxset_remove_by_data(o->direct_on_input->direct_outputs, o, NULL);
-    pa_idxset_remove_by_data(o->source->core->source_outputs, o, NULL);
-    if (pa_idxset_remove_by_data(o->source->outputs, o, NULL))
-        pa_source_output_unref(o);
+
+    pa_idxset_remove_by_data(o->core->source_outputs, o, NULL);
+
+    if (o->source)
+        if (pa_idxset_remove_by_data(o->source->outputs, o, NULL))
+            pa_source_output_unref(o);
+
+    if (o->client)
+        pa_idxset_remove_by_data(o->client->source_outputs, o, NULL);
 
     update_n_corked(o, PA_SOURCE_OUTPUT_UNLINKED);
     o->state = PA_SOURCE_OUTPUT_UNLINKED;
 
 
     update_n_corked(o, PA_SOURCE_OUTPUT_UNLINKED);
     o->state = PA_SOURCE_OUTPUT_UNLINKED;
 
-    if (linked)
+    if (linked && o->source)
         if (o->source->asyncmsgq)
             pa_assert_se(pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o->source), PA_SOURCE_MESSAGE_REMOVE_OUTPUT, o, 0, NULL) == 0);
 
     reset_callbacks(o);
 
     if (linked) {
         if (o->source->asyncmsgq)
             pa_assert_se(pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o->source), PA_SOURCE_MESSAGE_REMOVE_OUTPUT, o, 0, NULL) == 0);
 
     reset_callbacks(o);
 
     if (linked) {
-        pa_subscription_post(o->source->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_REMOVE, o->index);
-        pa_hook_fire(&o->source->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK_POST], o);
+        pa_subscription_post(o->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_REMOVE, o->index);
+        pa_hook_fire(&o->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK_POST], o);
     }
 
     }
 
-    o->source = NULL;
+    if (o->source) {
+        pa_source_update_status(o->source);
+        o->source = NULL;
+    }
+
+    pa_core_maybe_vacuum(o->core);
+
     pa_source_output_unref(o);
 }
 
     pa_source_output_unref(o);
 }
 
@@ -310,6 +359,7 @@ static void source_output_free(pa_object* mo) {
     pa_source_output *o = PA_SOURCE_OUTPUT(mo);
 
     pa_assert(o);
     pa_source_output *o = PA_SOURCE_OUTPUT(mo);
 
     pa_assert(o);
+    pa_assert_ctl_context();
     pa_assert(pa_source_output_refcnt(o) == 0);
 
     if (PA_SOURCE_OUTPUT_IS_LINKED(o->state))
     pa_assert(pa_source_output_refcnt(o) == 0);
 
     if (PA_SOURCE_OUTPUT_IS_LINKED(o->state))
@@ -317,8 +367,6 @@ static void source_output_free(pa_object* mo) {
 
     pa_log_info("Freeing output %u \"%s\"", o->index, pa_strnull(pa_proplist_gets(o->proplist, PA_PROP_MEDIA_NAME)));
 
 
     pa_log_info("Freeing output %u \"%s\"", o->index, pa_strnull(pa_proplist_gets(o->proplist, PA_PROP_MEDIA_NAME)));
 
-    pa_assert(!o->thread_info.attached);
-
     if (o->thread_info.delay_memblockq)
         pa_memblockq_free(o->thread_info.delay_memblockq);
 
     if (o->thread_info.delay_memblockq)
         pa_memblockq_free(o->thread_info.delay_memblockq);
 
@@ -335,7 +383,9 @@ static void source_output_free(pa_object* mo) {
 /* Called from main context */
 void pa_source_output_put(pa_source_output *o) {
     pa_source_output_state_t state;
 /* Called from main context */
 void pa_source_output_put(pa_source_output *o) {
     pa_source_output_state_t state;
+
     pa_source_output_assert_ref(o);
     pa_source_output_assert_ref(o);
+    pa_assert_ctl_context();
 
     pa_assert(o->state == PA_SOURCE_OUTPUT_INIT);
 
 
     pa_assert(o->state == PA_SOURCE_OUTPUT_INIT);
 
@@ -350,13 +400,16 @@ void pa_source_output_put(pa_source_output *o) {
 
     pa_assert_se(pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o->source), PA_SOURCE_MESSAGE_ADD_OUTPUT, o, 0, NULL) == 0);
 
 
     pa_assert_se(pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o->source), PA_SOURCE_MESSAGE_ADD_OUTPUT, o, 0, NULL) == 0);
 
-    pa_subscription_post(o->source->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_NEW, o->index);
-    pa_hook_fire(&o->source->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_PUT], o);
+    pa_subscription_post(o->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_NEW, o->index);
+    pa_hook_fire(&o->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_PUT], o);
+
+    pa_source_update_status(o->source);
 }
 
 /* Called from main context */
 void pa_source_output_kill(pa_source_output*o) {
     pa_source_output_assert_ref(o);
 }
 
 /* Called from main context */
 void pa_source_output_kill(pa_source_output*o) {
     pa_source_output_assert_ref(o);
+    pa_assert_ctl_context();
     pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state));
 
     o->kill(o);
     pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state));
 
     o->kill(o);
@@ -367,6 +420,7 @@ pa_usec_t pa_source_output_get_latency(pa_source_output *o, pa_usec_t *source_la
     pa_usec_t r[2] = { 0, 0 };
 
     pa_source_output_assert_ref(o);
     pa_usec_t r[2] = { 0, 0 };
 
     pa_source_output_assert_ref(o);
+    pa_assert_ctl_context();
     pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state));
 
     pa_assert_se(pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o), PA_SOURCE_OUTPUT_MESSAGE_GET_LATENCY, r, 0, NULL) == 0);
     pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state));
 
     pa_assert_se(pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o), PA_SOURCE_OUTPUT_MESSAGE_GET_LATENCY, r, 0, NULL) == 0);
@@ -386,6 +440,7 @@ void pa_source_output_push(pa_source_output *o, const pa_memchunk *chunk) {
     size_t limit, mbs = 0;
 
     pa_source_output_assert_ref(o);
     size_t limit, mbs = 0;
 
     pa_source_output_assert_ref(o);
+    pa_source_output_assert_io_context(o);
     pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->thread_info.state));
     pa_assert(chunk);
     pa_assert(pa_frame_aligned(chunk->length, &o->source->sample_spec));
     pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->thread_info.state));
     pa_assert(chunk);
     pa_assert(pa_frame_aligned(chunk->length, &o->source->sample_spec));
@@ -397,11 +452,30 @@ void pa_source_output_push(pa_source_output *o, const pa_memchunk *chunk) {
 
     if (pa_memblockq_push(o->thread_info.delay_memblockq, chunk) < 0) {
         pa_log_debug("Delay queue overflow!");
 
     if (pa_memblockq_push(o->thread_info.delay_memblockq, chunk) < 0) {
         pa_log_debug("Delay queue overflow!");
-        pa_memblockq_seek(o->thread_info.delay_memblockq, chunk->length, PA_SEEK_RELATIVE);
+        pa_memblockq_seek(o->thread_info.delay_memblockq, (int64_t) chunk->length, PA_SEEK_RELATIVE, TRUE);
     }
 
     limit = o->process_rewind ? 0 : o->source->thread_info.max_rewind;
 
     }
 
     limit = o->process_rewind ? 0 : o->source->thread_info.max_rewind;
 
+    if (limit > 0 && o->source->monitor_of) {
+        pa_usec_t latency;
+        size_t n;
+
+        /* Hmm, check the latency for knowing how much of the buffered
+         * data is actually still unplayed and might hence still
+         * change. This is suboptimal. Ideally we'd have a call like
+         * pa_sink_get_changeable_size() or so that tells us how much
+         * of the queued data is actually still changeable. Hence
+         * FIXME! */
+
+        latency = pa_sink_get_latency_within_thread(o->source->monitor_of);
+
+        n = pa_usec_to_bytes(latency, &o->source->sample_spec);
+
+        if (n < limit)
+            limit = n;
+    }
+
     /* Implement the delay queue */
     while ((length = pa_memblockq_get_length(o->thread_info.delay_memblockq)) > limit) {
         pa_memchunk qchunk;
     /* Implement the delay queue */
     while ((length = pa_memblockq_get_length(o->thread_info.delay_memblockq)) > limit) {
         pa_memchunk qchunk;
@@ -441,9 +515,10 @@ void pa_source_output_push(pa_source_output *o, const pa_memchunk *chunk) {
 }
 
 /* Called from thread context */
 }
 
 /* Called from thread context */
-void pa_source_output_process_rewind(pa_source_output *o, size_t nbytes /* in sink sample spec */) {
-    pa_source_output_assert_ref(o);
+void pa_source_output_process_rewind(pa_source_output *o, size_t nbytes /* in source sample spec */) {
 
 
+    pa_source_output_assert_ref(o);
+    pa_source_output_assert_io_context(o);
     pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->thread_info.state));
     pa_assert(pa_frame_aligned(nbytes, &o->source->sample_spec));
 
     pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->thread_info.state));
     pa_assert(pa_frame_aligned(nbytes, &o->source->sample_spec));
 
@@ -468,9 +543,18 @@ void pa_source_output_process_rewind(pa_source_output *o, size_t nbytes /* in si
         pa_memblockq_rewind(o->thread_info.delay_memblockq, nbytes);
 }
 
         pa_memblockq_rewind(o->thread_info.delay_memblockq, nbytes);
 }
 
+/* Called from thread context */
+size_t pa_source_output_get_max_rewind(pa_source_output *o) {
+    pa_source_output_assert_ref(o);
+    pa_source_output_assert_io_context(o);
+
+    return o->thread_info.resampler ? pa_resampler_request(o->thread_info.resampler, o->source->thread_info.max_rewind) : o->source->thread_info.max_rewind;
+}
+
 /* Called from thread context */
 void pa_source_output_update_max_rewind(pa_source_output *o, size_t nbytes  /* in the source's sample spec */) {
     pa_source_output_assert_ref(o);
 /* Called from thread context */
 void pa_source_output_update_max_rewind(pa_source_output *o, size_t nbytes  /* in the source's sample spec */) {
     pa_source_output_assert_ref(o);
+    pa_source_output_assert_io_context(o);
     pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->thread_info.state));
     pa_assert(pa_frame_aligned(nbytes, &o->source->sample_spec));
 
     pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->thread_info.state));
     pa_assert(pa_frame_aligned(nbytes, &o->source->sample_spec));
 
@@ -478,29 +562,19 @@ void pa_source_output_update_max_rewind(pa_source_output *o, size_t nbytes  /* i
         o->update_max_rewind(o, o->thread_info.resampler ? pa_resampler_result(o->thread_info.resampler, nbytes) : nbytes);
 }
 
         o->update_max_rewind(o, o->thread_info.resampler ? pa_resampler_result(o->thread_info.resampler, nbytes) : nbytes);
 }
 
-/* Called from thread context */
-static pa_usec_t fixup_latency(pa_source *s, pa_usec_t usec) {
-    pa_source_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_source_output_set_requested_latency_within_thread(pa_source_output *o, pa_usec_t usec) {
     pa_source_output_assert_ref(o);
 /* Called from thread context */
 pa_usec_t pa_source_output_set_requested_latency_within_thread(pa_source_output *o, pa_usec_t usec) {
     pa_source_output_assert_ref(o);
+    pa_source_output_assert_io_context(o);
+
+    if (!(o->source->flags & PA_SOURCE_DYNAMIC_LATENCY))
+        usec = o->source->thread_info.fixed_latency;
+
+    if (usec != (pa_usec_t) -1)
+        usec = PA_CLAMP(usec, o->source->thread_info.min_latency, o->source->thread_info.max_latency);
 
 
-    usec = fixup_latency(o->source, usec);
     o->thread_info.requested_source_latency = usec;
     o->thread_info.requested_source_latency = usec;
-    pa_source_invalidate_requested_latency(o->source);
+    pa_source_invalidate_requested_latency(o->source, TRUE);
 
     return usec;
 }
 
     return usec;
 }
@@ -508,40 +582,53 @@ pa_usec_t pa_source_output_set_requested_latency_within_thread(pa_source_output
 /* Called from main context */
 pa_usec_t pa_source_output_set_requested_latency(pa_source_output *o, pa_usec_t usec) {
     pa_source_output_assert_ref(o);
 /* Called from main context */
 pa_usec_t pa_source_output_set_requested_latency(pa_source_output *o, pa_usec_t usec) {
     pa_source_output_assert_ref(o);
+    pa_assert_ctl_context();
 
 
-    if (PA_SOURCE_OUTPUT_IS_LINKED(o->state))
+    if (PA_SOURCE_OUTPUT_IS_LINKED(o->state) && o->source) {
         pa_assert_se(pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o), PA_SOURCE_OUTPUT_MESSAGE_SET_REQUESTED_LATENCY, &usec, 0, NULL) == 0);
         pa_assert_se(pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o), PA_SOURCE_OUTPUT_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 source output is not realized yet or is being moved, we
+     * have to touch the thread info data directly */
 
 
-        usec = fixup_latency(o->source, usec);
-        o->thread_info.requested_source_latency = usec;
-        o->source->thread_info.requested_latency_valid = FALSE;
+    if (o->source) {
+        if (!(o->source->flags & PA_SOURCE_DYNAMIC_LATENCY))
+            usec = pa_source_get_fixed_latency(o->source);
+
+        if (usec != (pa_usec_t) -1) {
+            pa_usec_t min_latency, max_latency;
+            pa_source_get_latency_range(o->source, &min_latency, &max_latency);
+            usec = PA_CLAMP(usec, min_latency, max_latency);
+        }
     }
 
     }
 
+    o->thread_info.requested_source_latency = usec;
+
     return usec;
 }
 
 /* Called from main context */
 pa_usec_t pa_source_output_get_requested_latency(pa_source_output *o) {
     return usec;
 }
 
 /* Called from main context */
 pa_usec_t pa_source_output_get_requested_latency(pa_source_output *o) {
-    pa_usec_t usec = 0;
-
     pa_source_output_assert_ref(o);
     pa_source_output_assert_ref(o);
+    pa_assert_ctl_context();
 
 
-    if (PA_SOURCE_OUTPUT_IS_LINKED(o->state))
+    if (PA_SOURCE_OUTPUT_IS_LINKED(o->state) && o->source) {
+        pa_usec_t usec = 0;
         pa_assert_se(pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o), PA_SOURCE_OUTPUT_MESSAGE_GET_REQUESTED_LATENCY, &usec, 0, NULL) == 0);
         pa_assert_se(pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o), PA_SOURCE_OUTPUT_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 = o->thread_info.requested_source_latency;
+        return usec;
+    }
 
 
-    return usec;
+    /* If this source output is not realized yet or is being moved, we
+     * have to touch the thread info data directly */
+
+    return o->thread_info.requested_source_latency;
 }
 
 /* Called from main context */
 void pa_source_output_cork(pa_source_output *o, pa_bool_t b) {
     pa_source_output_assert_ref(o);
 }
 
 /* Called from main context */
 void pa_source_output_cork(pa_source_output *o, pa_bool_t b) {
     pa_source_output_assert_ref(o);
+    pa_assert_ctl_context();
     pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state));
 
     source_output_set_state(o, b ? PA_SOURCE_OUTPUT_CORKED : PA_SOURCE_OUTPUT_RUNNING);
     pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state));
 
     source_output_set_state(o, b ? PA_SOURCE_OUTPUT_CORKED : PA_SOURCE_OUTPUT_RUNNING);
@@ -550,8 +637,9 @@ void pa_source_output_cork(pa_source_output *o, pa_bool_t b) {
 /* Called from main context */
 int pa_source_output_set_rate(pa_source_output *o, uint32_t rate) {
     pa_source_output_assert_ref(o);
 /* Called from main context */
 int pa_source_output_set_rate(pa_source_output *o, uint32_t rate) {
     pa_source_output_assert_ref(o);
+    pa_assert_ctl_context();
     pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state));
     pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state));
-    pa_return_val_if_fail(o->thread_info.resampler, -1);
+    pa_return_val_if_fail(o->thread_info.resampler, -PA_ERR_BADSTATE);
 
     if (o->sample_spec.rate == rate)
         return 0;
 
     if (o->sample_spec.rate == rate)
         return 0;
@@ -560,13 +648,14 @@ int pa_source_output_set_rate(pa_source_output *o, uint32_t rate) {
 
     pa_asyncmsgq_post(o->source->asyncmsgq, PA_MSGOBJECT(o), PA_SOURCE_OUTPUT_MESSAGE_SET_RATE, PA_UINT_TO_PTR(rate), 0, NULL, NULL);
 
 
     pa_asyncmsgq_post(o->source->asyncmsgq, PA_MSGOBJECT(o), PA_SOURCE_OUTPUT_MESSAGE_SET_RATE, PA_UINT_TO_PTR(rate), 0, NULL, NULL);
 
-    pa_subscription_post(o->source->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE, o->index);
+    pa_subscription_post(o->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE, o->index);
     return 0;
 }
 
 /* Called from main context */
 void pa_source_output_set_name(pa_source_output *o, const char *name) {
     const char *old;
     return 0;
 }
 
 /* Called from main context */
 void pa_source_output_set_name(pa_source_output *o, const char *name) {
     const char *old;
+    pa_assert_ctl_context();
     pa_source_output_assert_ref(o);
 
     if (!name && !pa_proplist_contains(o->proplist, PA_PROP_MEDIA_NAME))
     pa_source_output_assert_ref(o);
 
     if (!name && !pa_proplist_contains(o->proplist, PA_PROP_MEDIA_NAME))
@@ -583,47 +672,121 @@ void pa_source_output_set_name(pa_source_output *o, const char *name) {
         pa_proplist_unset(o->proplist, PA_PROP_MEDIA_NAME);
 
     if (PA_SOURCE_OUTPUT_IS_LINKED(o->state)) {
         pa_proplist_unset(o->proplist, PA_PROP_MEDIA_NAME);
 
     if (PA_SOURCE_OUTPUT_IS_LINKED(o->state)) {
-        pa_hook_fire(&o->source->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_PROPLIST_CHANGED], o);
-        pa_subscription_post(o->source->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE, o->index);
+        pa_hook_fire(&o->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_PROPLIST_CHANGED], o);
+        pa_subscription_post(o->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE, o->index);
+    }
+}
+
+/* Called from main thread */
+void pa_source_output_update_proplist(pa_source_output *o, pa_update_mode_t mode, pa_proplist *p) {
+    pa_source_output_assert_ref(o);
+    pa_assert_ctl_context();
+
+    if (p)
+        pa_proplist_update(o->proplist, mode, p);
+
+    if (PA_SOURCE_OUTPUT_IS_LINKED(o->state)) {
+        pa_hook_fire(&o->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_PROPLIST_CHANGED], o);
+        pa_subscription_post(o->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE, o->index);
     }
 }
 
 /* Called from main context */
 pa_resample_method_t pa_source_output_get_resample_method(pa_source_output *o) {
     pa_source_output_assert_ref(o);
     }
 }
 
 /* Called from main context */
 pa_resample_method_t pa_source_output_get_resample_method(pa_source_output *o) {
     pa_source_output_assert_ref(o);
+    pa_assert_ctl_context();
 
 
-    return o->resample_method;
+    return o->actual_resample_method;
 }
 
 /* Called from main context */
 }
 
 /* Called from main context */
-int pa_source_output_move_to(pa_source_output *o, pa_source *dest) {
-    pa_source *origin;
-    pa_resampler *new_resampler;
-    pa_source_output_move_hook_data hook_data;
+pa_bool_t pa_source_output_may_move(pa_source_output *o) {
+    pa_source_output_assert_ref(o);
+    pa_assert_ctl_context();
+    pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state));
 
 
+    if (o->flags & PA_SOURCE_OUTPUT_DONT_MOVE)
+        return FALSE;
+
+    if (o->direct_on_input)
+        return FALSE;
+
+    return TRUE;
+}
+
+/* Called from main context */
+pa_bool_t pa_source_output_may_move_to(pa_source_output *o, pa_source *dest) {
     pa_source_output_assert_ref(o);
     pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state));
     pa_source_assert_ref(dest);
 
     pa_source_output_assert_ref(o);
     pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state));
     pa_source_assert_ref(dest);
 
+    if (dest == o->source)
+        return TRUE;
+
+    if (!pa_source_output_may_move(o))
+        return FALSE;
+
+    if (pa_idxset_size(dest->outputs) >= PA_MAX_OUTPUTS_PER_SOURCE) {
+        pa_log_warn("Failed to move source output: too many outputs per source.");
+        return FALSE;
+    }
+
+    if (o->may_move_to)
+        if (!o->may_move_to(o, dest))
+            return FALSE;
+
+    return TRUE;
+}
+
+/* Called from main context */
+int pa_source_output_start_move(pa_source_output *o) {
+    pa_source *origin;
+    int r;
+
+    pa_source_output_assert_ref(o);
+    pa_assert_ctl_context();
+    pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state));
+    pa_assert(o->source);
+
+    if (!pa_source_output_may_move(o))
+        return -PA_ERR_NOTSUPPORTED;
+
+    if ((r = pa_hook_fire(&o->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MOVE_START], o)) < 0)
+        return r;
+
     origin = o->source;
 
     origin = o->source;
 
-    if (dest == origin)
-        return 0;
+    pa_idxset_remove_by_data(o->source->outputs, o, NULL);
 
 
-    if (o->flags & PA_SOURCE_OUTPUT_DONT_MOVE)
-        return -1;
+    if (pa_source_output_get_state(o) == PA_SOURCE_OUTPUT_CORKED)
+        pa_assert_se(origin->n_corked-- >= 1);
 
 
-    if (o->direct_on_input)
-        return -1;
+    pa_assert_se(pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o->source), PA_SOURCE_MESSAGE_REMOVE_OUTPUT, o, 0, NULL) == 0);
 
 
-    if (pa_idxset_size(dest->outputs) >= PA_MAX_OUTPUTS_PER_SOURCE) {
-        pa_log_warn("Failed to move source output: too many outputs per source.");
+    pa_source_update_status(o->source);
+    o->source = NULL;
+
+    pa_source_output_unref(o);
+
+    return 0;
+}
+
+/* Called from main context */
+int pa_source_output_finish_move(pa_source_output *o, pa_source *dest, pa_bool_t save) {
+    pa_resampler *new_resampler;
+
+    pa_source_output_assert_ref(o);
+    pa_assert_ctl_context();
+    pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state));
+    pa_assert(!o->source);
+    pa_source_assert_ref(dest);
+
+    if (!pa_source_output_may_move_to(o, dest))
         return -1;
         return -1;
-    }
 
     if (o->thread_info.resampler &&
 
     if (o->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_input_sample_spec(o->thread_info.resampler), &dest->sample_spec) &&
+        pa_channel_map_equal(pa_resampler_input_channel_map(o->thread_info.resampler), &dest->channel_map))
 
         /* Try to reuse the old resampler if possible */
         new_resampler = o->thread_info.resampler;
 
         /* Try to reuse the old resampler if possible */
         new_resampler = o->thread_info.resampler;
@@ -635,34 +798,28 @@ int pa_source_output_move_to(pa_source_output *o, pa_source *dest) {
         /* Okey, we need a new resampler for the new source */
 
         if (!(new_resampler = pa_resampler_new(
         /* Okey, we need a new resampler for the new source */
 
         if (!(new_resampler = pa_resampler_new(
-                      dest->core->mempool,
+                      o->core->mempool,
                       &dest->sample_spec, &dest->channel_map,
                       &o->sample_spec, &o->channel_map,
                       &dest->sample_spec, &dest->channel_map,
                       &o->sample_spec, &o->channel_map,
-                      o->resample_method,
+                      o->requested_resample_method,
                       ((o->flags & PA_SOURCE_OUTPUT_VARIABLE_RATE) ? PA_RESAMPLER_VARIABLE_RATE : 0) |
                       ((o->flags & PA_SOURCE_OUTPUT_NO_REMAP) ? PA_RESAMPLER_NO_REMAP : 0) |
                       (o->core->disable_remixing || (o->flags & PA_SOURCE_OUTPUT_NO_REMIX) ? PA_RESAMPLER_NO_REMIX : 0)))) {
             pa_log_warn("Unsupported resampling operation.");
                       ((o->flags & PA_SOURCE_OUTPUT_VARIABLE_RATE) ? PA_RESAMPLER_VARIABLE_RATE : 0) |
                       ((o->flags & PA_SOURCE_OUTPUT_NO_REMAP) ? PA_RESAMPLER_NO_REMAP : 0) |
                       (o->core->disable_remixing || (o->flags & PA_SOURCE_OUTPUT_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.source_output = o;
-    hook_data.destination = dest;
-    pa_hook_fire(&o->source->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MOVE], &hook_data);
+    if (o->moving)
+        o->moving(o, dest);
 
 
-    /* Okey, let's move it */
-    pa_assert_se(pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o->source), PA_SOURCE_MESSAGE_REMOVE_OUTPUT, o, 0, NULL) == 0);
-
-    pa_idxset_remove_by_data(origin->outputs, o, NULL);
-    pa_idxset_put(dest->outputs, o, NULL);
     o->source = dest;
     o->source = dest;
+    o->save_source = save;
+    pa_idxset_put(o->source->outputs, pa_source_output_ref(o), NULL);
 
 
-    if (pa_source_output_get_state(o) == PA_SOURCE_OUTPUT_CORKED) {
-        pa_assert_se(origin->n_corked-- >= 1);
-        dest->n_corked++;
-    }
+    if (pa_source_output_get_state(o) == PA_SOURCE_OUTPUT_CORKED)
+        o->source->n_corked++;
 
     /* Replace resampler */
     if (new_resampler != o->thread_info.resampler) {
 
     /* Replace resampler */
     if (new_resampler != o->thread_info.resampler) {
@@ -683,20 +840,67 @@ int pa_source_output_move_to(pa_source_output *o, pa_source *dest) {
                 &o->source->silence);
     }
 
                 &o->source->silence);
     }
 
-    pa_source_update_status(origin);
     pa_source_update_status(dest);
 
     pa_assert_se(pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o->source), PA_SOURCE_MESSAGE_ADD_OUTPUT, o, 0, NULL) == 0);
 
     pa_source_update_status(dest);
 
     pa_assert_se(pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o->source), PA_SOURCE_MESSAGE_ADD_OUTPUT, o, 0, NULL) == 0);
 
-    if (o->moved)
-        o->moved(o);
+    pa_log_debug("Successfully moved source output %i to %s.", o->index, dest->name);
+
+    /* Notify everyone */
+    pa_hook_fire(&o->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MOVE_FINISH], o);
+    pa_subscription_post(o->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE, o->index);
 
 
-    pa_hook_fire(&o->source->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MOVE_POST], o);
+    return 0;
+}
 
 
-    pa_log_debug("Successfully moved source output %i from %s to %s.", o->index, origin->name, dest->name);
+/* Called from main context */
+void pa_source_output_fail_move(pa_source_output *o) {
 
 
-    /* Notify everyone */
-    pa_subscription_post(o->source->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE, o->index);
+    pa_source_output_assert_ref(o);
+    pa_assert_ctl_context();
+    pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state));
+    pa_assert(!o->source);
+
+    /* Check if someone wants this source output? */
+    if (pa_hook_fire(&o->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MOVE_FAIL], o) == PA_HOOK_STOP)
+        return;
+
+    if (o->moving)
+        o->moving(o, NULL);
+
+    pa_source_output_kill(o);
+}
+
+/* Called from main context */
+int pa_source_output_move_to(pa_source_output *o, pa_source *dest, pa_bool_t save) {
+    int r;
+
+    pa_source_output_assert_ref(o);
+    pa_assert_ctl_context();
+    pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state));
+    pa_assert(o->source);
+    pa_source_assert_ref(dest);
+
+    if (dest == o->source)
+        return 0;
+
+    if (!pa_source_output_may_move_to(o, dest))
+        return -PA_ERR_NOTSUPPORTED;
+
+    pa_source_output_ref(o);
+
+    if ((r = pa_source_output_start_move(o)) < 0) {
+        pa_source_output_unref(o);
+        return r;
+    }
+
+    if ((r = pa_source_output_finish_move(o, dest, save)) < 0) {
+        pa_source_output_fail_move(o);
+        pa_source_output_unref(o);
+        return r;
+    }
+
+    pa_source_output_unref(o);
 
     return 0;
 }
 
     return 0;
 }
@@ -704,6 +908,7 @@ int pa_source_output_move_to(pa_source_output *o, pa_source *dest) {
 /* Called from IO thread context */
 void pa_source_output_set_state_within_thread(pa_source_output *o, pa_source_output_state_t state) {
     pa_source_output_assert_ref(o);
 /* Called from IO thread context */
 void pa_source_output_set_state_within_thread(pa_source_output *o, pa_source_output_state_t state) {
     pa_source_output_assert_ref(o);
+    pa_source_output_assert_io_context(o);
 
     if (state == o->thread_info.state)
         return;
 
     if (state == o->thread_info.state)
         return;
@@ -723,12 +928,9 @@ int pa_source_output_process_msg(pa_msgobject *mo, int code, void *userdata, int
 
         case PA_SOURCE_OUTPUT_MESSAGE_GET_LATENCY: {
             pa_usec_t *r = userdata;
 
         case PA_SOURCE_OUTPUT_MESSAGE_GET_LATENCY: {
             pa_usec_t *r = userdata;
-            pa_usec_t source_usec = 0;
 
             r[0] += pa_bytes_to_usec(pa_memblockq_get_length(o->thread_info.delay_memblockq), &o->source->sample_spec);
 
             r[0] += pa_bytes_to_usec(pa_memblockq_get_length(o->thread_info.delay_memblockq), &o->source->sample_spec);
-
-            if (o->source->parent.process_msg(PA_MSGOBJECT(o->source), PA_SOURCE_MESSAGE_GET_LATENCY, &source_usec, 0, NULL) >= 0)
-                r[1] += source_usec;
+            r[1] += pa_source_get_latency_within_thread(o->source);
 
             return 0;
         }
 
             return 0;
         }
@@ -760,5 +962,34 @@ int pa_source_output_process_msg(pa_msgobject *mo, int code, void *userdata, int
         }
     }
 
         }
     }
 
-    return -1;
+    return -PA_ERR_NOTIMPLEMENTED;
+}
+
+/* Called from main context */
+void pa_source_output_send_event(pa_source_output *o, const char *event, pa_proplist *data) {
+    pa_proplist *pl = NULL;
+    pa_source_output_send_event_hook_data hook_data;
+
+    pa_source_output_assert_ref(o);
+    pa_assert_ctl_context();
+    pa_assert(event);
+
+    if (!o->send_event)
+        return;
+
+    if (!data)
+        data = pl = pa_proplist_new();
+
+    hook_data.source_output = o;
+    hook_data.data = data;
+    hook_data.event = event;
+
+    if (pa_hook_fire(&o->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_SEND_EVENT], &hook_data) < 0)
+        goto finish;
+
+    o->send_event(o, event, data);
+
+finish:
+    if (pl)
+        pa_proplist_free(pl);
 }
 }