]> code.delx.au - pulseaudio/blobdiff - src/pulsecore/source-output.c
namereg: select default sink by priority
[pulseaudio] / src / pulsecore / source-output.c
index 5c36937a2836020be162261cbd63caeba21ba824..1509807b29bc2c6264ee9575ac0f5757417385ac 100644 (file)
@@ -1,5 +1,3 @@
-/* $Id$ */
-
 /***
   This file is part of PulseAudio.
 
 /***
   This file is part of PulseAudio.
 
@@ -7,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
@@ -31,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();
 
@@ -76,66 +75,83 @@ void pa_source_output_new_data_done(pa_source_output_new_data *data) {
     pa_proplist_free(data->proplist);
 }
 
     pa_proplist_free(data->proplist);
 }
 
+/* Called from main context */
 static void reset_callbacks(pa_source_output *o) {
     pa_assert(o);
 
     o->push = NULL;
     o->process_rewind = NULL;
     o->update_max_rewind = NULL;
 static void reset_callbacks(pa_source_output *o) {
     pa_assert(o);
 
     o->push = NULL;
     o->process_rewind = 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;
 }
 
 }
 
-pa_source_output* pa_source_output_new(
+/* Called from main context */
+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;
 
 
+    pa_assert(_o);
     pa_assert(core);
     pa_assert(data);
     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 (pa_hook_fire(&core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_NEW], data) < 0)
-        return NULL;
+    if ((r = pa_hook_fire(&core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_NEW], data)) < 0)
+        return r;
 
 
-    pa_return_null_if_fail(!data->driver || pa_utf8_valid(data->driver));
+    pa_return_val_if_fail(!data->driver || pa_utf8_valid(data->driver), -PA_ERR_INVALID);
 
 
-    if (!data->source)
-        data->source = pa_namereg_get(core, NULL, PA_NAMEREG_SOURCE, 1);
+    if (!data->source) {
+        data->source = pa_namereg_get(core, NULL, PA_NAMEREG_SOURCE);
+        data->save_source = FALSE;
+    }
 
 
-    pa_return_null_if_fail(data->source);
-    pa_return_null_if_fail(pa_source_get_state(data->source) != PA_SOURCE_UNLINKED);
+    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;
     }
@@ -146,17 +162,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 (pa_hook_fire(&core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_FIXATE], data) < 0)
-        return NULL;
+    if ((r = pa_hook_fire(&core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_FIXATE], data)) < 0)
+        return r;
+
+    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)) {
 
@@ -165,14 +187,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);
@@ -181,17 +202,22 @@ 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->client = data->client;
 
     o->module = data->module;
     o->source = data->source;
     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->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;
 
@@ -200,6 +226,7 @@ pa_source_output* pa_source_output_new(
     o->thread_info.sample_spec = o->sample_spec;
     o->thread_info.resampler = resampler;
     o->thread_info.requested_source_latency = (pa_usec_t) -1;
     o->thread_info.sample_spec = o->sample_spec;
     o->thread_info.resampler = resampler;
     o->thread_info.requested_source_latency = (pa_usec_t) -1;
+    o->thread_info.direct_on_input = o->direct_on_input;
 
     o->thread_info.delay_memblockq = pa_memblockq_new(
             0,
 
     o->thread_info.delay_memblockq = pa_memblockq_new(
             0,
@@ -214,6 +241,12 @@ 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);
+
     pa_log_info("Created output %u \"%s\" on %s with sample spec %s and channel map %s",
                 o->index,
                 pa_strnull(pa_proplist_gets(o->proplist, PA_PROP_MEDIA_NAME)),
     pa_log_info("Created output %u \"%s\" on %s with sample spec %s and channel map %s",
                 o->index,
                 pa_strnull(pa_proplist_gets(o->proplist, PA_PROP_MEDIA_NAME)),
@@ -223,41 +256,48 @@ pa_source_output* pa_source_output_new(
 
     /* 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);
 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);
 }
 
 }
 
-static int source_output_set_state(pa_source_output *o, pa_source_output_state_t state) {
+/* Called from main context */
+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;
 
 
-    if (pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o), PA_SOURCE_OUTPUT_MESSAGE_SET_STATE, PA_UINT_TO_PTR(state), 0, NULL) < 0)
-        return -1;
+    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)
 
     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);
+        pa_hook_fire(&o->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_STATE_CHANGED], o);
 
 
-    return 0;
+    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);
 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 */
@@ -267,34 +307,50 @@ 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);
 
 
-    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);
+    if (o->direct_on_input)
+        pa_idxset_remove_by_data(o->direct_on_input->direct_outputs, o, NULL);
+
+    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)
         if (o->source->asyncmsgq)
-            pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o->source), PA_SOURCE_MESSAGE_REMOVE_OUTPUT, o, 0, NULL);
+            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) {
 
     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);
 }
 
+/* Called from main context */
 static void source_output_free(pa_object* mo) {
     pa_source_output *o = PA_SOURCE_OUTPUT(mo);
 
     pa_assert(o);
 static void source_output_free(pa_object* mo) {
     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))
@@ -302,8 +358,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);
 
@@ -317,45 +371,58 @@ static void source_output_free(pa_object* mo) {
     pa_xfree(o);
 }
 
     pa_xfree(o);
 }
 
+/* Called from main context */
 void pa_source_output_put(pa_source_output *o) {
     pa_source_output_state_t state;
 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);
+
+    /* The following fields must be initialized properly */
     pa_assert(o->push);
     pa_assert(o->push);
+    pa_assert(o->kill);
 
     state = o->flags & PA_SOURCE_OUTPUT_START_CORKED ? PA_SOURCE_OUTPUT_CORKED : PA_SOURCE_OUTPUT_RUNNING;
 
     update_n_corked(o, state);
     o->state = state;
 
 
     state = o->flags & PA_SOURCE_OUTPUT_START_CORKED ? PA_SOURCE_OUTPUT_CORKED : PA_SOURCE_OUTPUT_RUNNING;
 
     update_n_corked(o, state);
     o->state = state;
 
-    pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o->source), PA_SOURCE_MESSAGE_ADD_OUTPUT, o, 0, NULL);
+    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);
 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));
 
     pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state));
 
-    if (o->kill)
-        o->kill(o);
+    o->kill(o);
 }
 
 }
 
-pa_usec_t pa_source_output_get_latency(pa_source_output *o) {
-    pa_usec_t r = 0;
+/* Called from main context */
+pa_usec_t pa_source_output_get_latency(pa_source_output *o, pa_usec_t *source_latency) {
+    pa_usec_t r[2] = { 0, 0 };
 
     pa_source_output_assert_ref(o);
 
     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));
 
-    if (pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o), PA_SOURCE_OUTPUT_MESSAGE_GET_LATENCY, &r, 0, NULL) < 0)
-        r = 0;
+    pa_assert_se(pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o), PA_SOURCE_OUTPUT_MESSAGE_GET_LATENCY, r, 0, NULL) == 0);
 
     if (o->get_latency)
 
     if (o->get_latency)
-        r += o->get_latency(o);
+        r[0] += o->get_latency(o);
+
+    if (source_latency)
+        *source_latency = r[1];
 
 
-    return r;
+    return r[0];
 }
 
 /* Called from thread context */
 }
 
 /* Called from thread context */
@@ -364,22 +431,42 @@ 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));
 
-    if (!o->push || o->state == PA_SOURCE_OUTPUT_CORKED)
+    if (!o->push || o->thread_info.state == PA_SOURCE_OUTPUT_CORKED)
         return;
 
         return;
 
-    pa_assert(o->state == PA_SOURCE_OUTPUT_RUNNING);
+    pa_assert(o->thread_info.state == PA_SOURCE_OUTPUT_RUNNING);
 
     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;
@@ -409,7 +496,8 @@ void pa_source_output_push(pa_source_output *o, const pa_memchunk *chunk) {
             if (rchunk.length > 0)
                 o->push(o, &rchunk);
 
             if (rchunk.length > 0)
                 o->push(o, &rchunk);
 
-            pa_memblock_unref(rchunk.memblock);
+            if (rchunk.memblock)
+                pa_memblock_unref(rchunk.memblock);
         }
 
         pa_memblock_unref(qchunk.memblock);
         }
 
         pa_memblock_unref(qchunk.memblock);
@@ -418,10 +506,11 @@ 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_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state));
+    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));
 
     if (nbytes <= 0)
     pa_assert(pa_frame_aligned(nbytes, &o->source->sample_spec));
 
     if (nbytes <= 0)
@@ -445,9 +534,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));
 
@@ -455,75 +553,84 @@ 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);
 }
 
-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->max_latency > 0 && usec > s->max_latency)
-        usec = s->max_latency;
-
-    if (s->min_latency > 0 && usec < s->min_latency)
-        usec = s->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);
 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;
 
 
-    usec = fixup_latency(o->source, usec);
+    if (usec != (pa_usec_t) -1)
+        usec = PA_CLAMP(usec, o->source->thread_info.min_latency, o->source->thread_info.max_latency);
 
     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;
 }
 
+/* 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_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) && 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);
+        return usec;
+    }
 
 
-    usec = fixup_latency(o->source, usec);
+    /* If this source output is not realized yet or is being moved, we
+     * have to touch the thread info data directly */
 
 
-    if (PA_SOURCE_OUTPUT_IS_LINKED(o->state))
-        pa_asyncmsgq_post(o->source->asyncmsgq, PA_MSGOBJECT(o), PA_SOURCE_OUTPUT_MESSAGE_SET_REQUESTED_LATENCY, NULL, (int64_t) usec, NULL, NULL);
-    else {
-        /* If this sink input is not realized yet, we have to touch
-         * the thread info data directly */
-        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;
 }
 
     return usec;
 }
 
+/* Called from main context */
 pa_usec_t pa_source_output_get_requested_latency(pa_source_output *o) {
 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))
-        pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o), PA_SOURCE_OUTPUT_MESSAGE_GET_REQUESTED_LATENCY, &usec, 0, NULL);
-    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;
+    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);
+        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);
 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);
 }
 
+/* Called from main context */
 int pa_source_output_set_rate(pa_source_output *o, uint32_t rate) {
     pa_source_output_assert_ref(o);
 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;
@@ -532,12 +639,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;
 }
 
     return 0;
 }
 
+/* Called from main context */
 void pa_source_output_set_name(pa_source_output *o, const char *name) {
     const char *old;
 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))
@@ -554,42 +663,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);
 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;
 }
 
 }
 
-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;
+/* Called from main context */
+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);
 
-    origin = o->source;
-
-    if (dest == origin)
-        return 0;
+    if (dest == o->source)
+        return TRUE;
 
 
-    if (o->flags & PA_SOURCE_OUTPUT_DONT_MOVE)
-        return -1;
+    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.");
 
     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 -1;
+        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;
+
+    pa_idxset_remove_by_data(o->source->outputs, o, NULL);
+
+    if (pa_source_output_get_state(o) == PA_SOURCE_OUTPUT_CORKED)
+        pa_assert_se(origin->n_corked-- >= 1);
+
+    pa_assert_se(pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o->source), PA_SOURCE_MESSAGE_REMOVE_OUTPUT, o, 0, NULL) == 0);
+
+    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;
+
     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;
@@ -601,34 +789,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);
-
-    /* Okey, let's move it */
-    pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o->source), PA_SOURCE_MESSAGE_REMOVE_OUTPUT, o, 0, NULL);
+    if (o->moving)
+        o->moving(o, dest);
 
 
-    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) {
@@ -649,26 +831,75 @@ 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_source_update_status(dest);
 
-    pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o->source), PA_SOURCE_MESSAGE_ADD_OUTPUT, o, 0, NULL);
+    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);
 
 
-    pa_hook_fire(&o->source->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MOVE_POST], o);
+    /* 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_log_debug("Successfully moved source output %i from %s to %s.", o->index, origin->name, dest->name);
+    return 0;
+}
 
 
-    /* Notify everyone */
-    pa_subscription_post(o->source->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE, o->index);
+/* Called from main context */
+void pa_source_output_fail_move(pa_source_output *o) {
+
+    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;
 }
 
+/* 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);
 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;
@@ -679,19 +910,19 @@ void pa_source_output_set_state_within_thread(pa_source_output *o, pa_source_out
     o->thread_info.state = state;
 }
 
     o->thread_info.state = state;
 }
 
-/* Called from thread context */
+/* Called from IO thread context, except when it is not */
 int pa_source_output_process_msg(pa_msgobject *mo, int code, void *userdata, int64_t offset, pa_memchunk* chunk) {
     pa_source_output *o = PA_SOURCE_OUTPUT(mo);
 int pa_source_output_process_msg(pa_msgobject *mo, int code, void *userdata, int64_t offset, pa_memchunk* chunk) {
     pa_source_output *o = PA_SOURCE_OUTPUT(mo);
-
     pa_source_output_assert_ref(o);
     pa_source_output_assert_ref(o);
-    pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->thread_info.state));
 
     switch (code) {
 
         case PA_SOURCE_OUTPUT_MESSAGE_GET_LATENCY: {
             pa_usec_t *r = userdata;
 
 
     switch (code) {
 
         case PA_SOURCE_OUTPUT_MESSAGE_GET_LATENCY: {
             pa_usec_t *r = userdata;
 
-            *r += 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);
+            r[1] += pa_source_get_latency_within_thread(o->source);
+
             return 0;
         }
 
             return 0;
         }
 
@@ -706,12 +937,15 @@ int pa_source_output_process_msg(pa_msgobject *mo, int code, void *userdata, int
             pa_source_output_set_state_within_thread(o, PA_PTR_TO_UINT(userdata));
             return 0;
 
             pa_source_output_set_state_within_thread(o, PA_PTR_TO_UINT(userdata));
             return 0;
 
-        case PA_SOURCE_OUTPUT_MESSAGE_SET_REQUESTED_LATENCY:
+        case PA_SOURCE_OUTPUT_MESSAGE_SET_REQUESTED_LATENCY: {
+            pa_usec_t *usec = userdata;
+
+            *usec = pa_source_output_set_requested_latency_within_thread(o, *usec);
 
 
-            pa_source_output_set_requested_latency_within_thread(o, (pa_usec_t) offset);
             return 0;
             return 0;
+        }
 
 
-        case PA_SINK_INPUT_MESSAGE_GET_REQUESTED_LATENCY: {
+        case PA_SOURCE_OUTPUT_MESSAGE_GET_REQUESTED_LATENCY: {
             pa_usec_t *r = userdata;
 
             *r = o->thread_info.requested_source_latency;
             pa_usec_t *r = userdata;
 
             *r = o->thread_info.requested_source_latency;
@@ -719,5 +953,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);
 }
 }