]> code.delx.au - pulseaudio/blobdiff - src/pulsecore/source.c
sample-util: move volume code to separate file
[pulseaudio] / src / pulsecore / source.c
index c0d6d9ea3318589186e4c31d36502d4c6434e2ee..8aa07f5e58c805873aa9bb1efc865a90975a0b30 100644 (file)
@@ -6,7 +6,7 @@
 
   PulseAudio is free software; you can redistribute it and/or modify
   it under the terms of the GNU Lesser General Public License as published
 
   PulseAudio is free software; you can redistribute it and/or modify
   it under the terms of the GNU Lesser General Public License as published
-  by the Free Software Foundation; either version 2 of the License,
+  by the Free Software Foundation; either version 2.1 of the License,
   or (at your option) any later version.
 
   PulseAudio is distributed in the hope that it will be useful, but
   or (at your option) any later version.
 
   PulseAudio is distributed in the hope that it will be useful, but
@@ -33,6 +33,7 @@
 #include <pulse/timeval.h>
 #include <pulse/util.h>
 
 #include <pulse/timeval.h>
 #include <pulse/util.h>
 
+#include <pulsecore/core-util.h>
 #include <pulsecore/source-output.h>
 #include <pulsecore/namereg.h>
 #include <pulsecore/core-subscribe.h>
 #include <pulsecore/source-output.h>
 #include <pulsecore/namereg.h>
 #include <pulsecore/core-subscribe.h>
@@ -41,7 +42,9 @@
 
 #include "source.h"
 
 
 #include "source.h"
 
-#define DEFAULT_MIN_LATENCY (4*PA_USEC_PER_MSEC)
+#define ABSOLUTE_MIN_LATENCY (500)
+#define ABSOLUTE_MAX_LATENCY (10*PA_USEC_PER_SEC)
+#define DEFAULT_FIXED_LATENCY (250*PA_USEC_PER_MSEC)
 
 static PA_DEFINE_CHECK_TYPE(pa_source, pa_msgobject);
 
 
 static PA_DEFINE_CHECK_TYPE(pa_source, pa_msgobject);
 
@@ -50,7 +53,7 @@ static void source_free(pa_object *o);
 pa_source_new_data* pa_source_new_data_init(pa_source_new_data *data) {
     pa_assert(data);
 
 pa_source_new_data* pa_source_new_data_init(pa_source_new_data *data) {
     pa_assert(data);
 
-    memset(data, 0, sizeof(*data));
+    pa_zero(*data);
     data->proplist = pa_proplist_new();
 
     return data;
     data->proplist = pa_proplist_new();
 
     return data;
@@ -91,11 +94,29 @@ void pa_source_new_data_set_muted(pa_source_new_data *data, pa_bool_t mute) {
     data->muted = !!mute;
 }
 
     data->muted = !!mute;
 }
 
+void pa_source_new_data_set_port(pa_source_new_data *data, const char *port) {
+    pa_assert(data);
+
+    pa_xfree(data->active_port);
+    data->active_port = pa_xstrdup(port);
+}
+
 void pa_source_new_data_done(pa_source_new_data *data) {
     pa_assert(data);
 
 void pa_source_new_data_done(pa_source_new_data *data) {
     pa_assert(data);
 
-    pa_xfree(data->name);
     pa_proplist_free(data->proplist);
     pa_proplist_free(data->proplist);
+
+    if (data->ports) {
+        pa_device_port *p;
+
+        while ((p = pa_hashmap_steal_first(data->ports)))
+            pa_device_port_free(p);
+
+        pa_hashmap_free(data->ports, NULL, NULL);
+    }
+
+    pa_xfree(data->name);
+    pa_xfree(data->active_port);
 }
 
 /* Called from main context */
 }
 
 /* Called from main context */
@@ -108,6 +129,7 @@ static void reset_callbacks(pa_source *s) {
     s->get_mute = NULL;
     s->set_mute = NULL;
     s->update_requested_latency = NULL;
     s->get_mute = NULL;
     s->set_mute = NULL;
     s->update_requested_latency = NULL;
+    s->set_port = NULL;
 }
 
 /* Called from main context */
 }
 
 /* Called from main context */
@@ -124,10 +146,12 @@ pa_source* pa_source_new(
     pa_assert(core);
     pa_assert(data);
     pa_assert(data->name);
     pa_assert(core);
     pa_assert(data);
     pa_assert(data->name);
+    pa_assert_ctl_context();
 
     s = pa_msgobject_new(pa_source);
 
     if (!(name = pa_namereg_register(core, data->name, PA_NAMEREG_SOURCE, s, data->namereg_fail))) {
 
     s = pa_msgobject_new(pa_source);
 
     if (!(name = pa_namereg_register(core, data->name, PA_NAMEREG_SOURCE, s, data->namereg_fail))) {
+        pa_log_debug("Failed to register name %s.", data->name);
         pa_xfree(s);
         return NULL;
     }
         pa_xfree(s);
         return NULL;
     }
@@ -140,6 +164,8 @@ pa_source* pa_source_new(
         return NULL;
     }
 
         return NULL;
     }
 
+    /* FIXME, need to free s here on failure */
+
     pa_return_null_if_fail(!data->driver || pa_utf8_valid(data->driver));
     pa_return_null_if_fail(data->name && pa_utf8_valid(data->name) && data->name[0]);
 
     pa_return_null_if_fail(!data->driver || pa_utf8_valid(data->driver));
     pa_return_null_if_fail(data->name && pa_utf8_valid(data->name) && data->name[0]);
 
@@ -155,7 +181,7 @@ pa_source* pa_source_new(
         pa_cvolume_reset(&data->volume, data->sample_spec.channels);
 
     pa_return_null_if_fail(pa_cvolume_valid(&data->volume));
         pa_cvolume_reset(&data->volume, data->sample_spec.channels);
 
     pa_return_null_if_fail(pa_cvolume_valid(&data->volume));
-    pa_return_null_if_fail(data->volume.channels == data->sample_spec.channels);
+    pa_return_null_if_fail(pa_cvolume_compatible(&data->volume, &data->sample_spec));
 
     if (!data->muted_is_set)
         data->muted = FALSE;
 
     if (!data->muted_is_set)
         data->muted = FALSE;
@@ -163,6 +189,10 @@ pa_source* pa_source_new(
     if (data->card)
         pa_proplist_update(data->proplist, PA_UPDATE_MERGE, data->card->proplist);
 
     if (data->card)
         pa_proplist_update(data->proplist, PA_UPDATE_MERGE, data->card->proplist);
 
+    pa_device_init_description(data->proplist);
+    pa_device_init_icon(data->proplist, FALSE);
+    pa_device_init_intended_roles(data->proplist);
+
     if (pa_hook_fire(&core->hooks[PA_CORE_HOOK_SOURCE_FIXATE], data) < 0) {
         pa_xfree(s);
         pa_namereg_unregister(core, name);
     if (pa_hook_fire(&core->hooks[PA_CORE_HOOK_SOURCE_FIXATE], data) < 0) {
         pa_xfree(s);
         pa_namereg_unregister(core, name);
@@ -175,6 +205,7 @@ pa_source* pa_source_new(
     s->core = core;
     s->state = PA_SOURCE_INIT;
     s->flags = flags;
     s->core = core;
     s->state = PA_SOURCE_INIT;
     s->flags = flags;
+    s->suspend_cause = 0;
     s->name = pa_xstrdup(name);
     s->proplist = pa_proplist_copy(data->proplist);
     s->driver = pa_xstrdup(pa_path_get_filename(data->driver));
     s->name = pa_xstrdup(name);
     s->proplist = pa_proplist_copy(data->proplist);
     s->driver = pa_xstrdup(pa_path_get_filename(data->driver));
@@ -188,7 +219,7 @@ pa_source* pa_source_new(
     s->n_corked = 0;
     s->monitor_of = NULL;
 
     s->n_corked = 0;
     s->monitor_of = NULL;
 
-    s->virtual_volume = data->volume;
+    s->volume = data->volume;
     pa_cvolume_reset(&s->soft_volume, s->sample_spec.channels);
     s->base_volume = PA_VOLUME_NORM;
     s->n_volume_steps = PA_VOLUME_NORM+1;
     pa_cvolume_reset(&s->soft_volume, s->sample_spec.channels);
     s->base_volume = PA_VOLUME_NORM;
     s->n_volume_steps = PA_VOLUME_NORM+1;
@@ -199,7 +230,30 @@ pa_source* pa_source_new(
     s->userdata = NULL;
 
     s->asyncmsgq = NULL;
     s->userdata = NULL;
 
     s->asyncmsgq = NULL;
-    s->rtpoll = NULL;
+
+    /* As a minor optimization we just steal the list instead of
+     * copying it here */
+    s->ports = data->ports;
+    data->ports = NULL;
+
+    s->active_port = NULL;
+    s->save_port = FALSE;
+
+    if (data->active_port && s->ports)
+        if ((s->active_port = pa_hashmap_get(s->ports, data->active_port)))
+            s->save_port = data->save_port;
+
+    if (!s->active_port && s->ports) {
+        void *state;
+        pa_device_port *p;
+
+        PA_HASHMAP_FOREACH(p, s->ports, state)
+            if (!s->active_port || p->priority > s->active_port->priority)
+                s->active_port = p;
+    }
+
+    s->save_volume = data->save_volume;
+    s->save_muted = data->save_muted;
 
     pa_silence_memchunk_get(
             &core->silence_cache,
 
     pa_silence_memchunk_get(
             &core->silence_cache,
@@ -208,6 +262,7 @@ pa_source* pa_source_new(
             &s->sample_spec,
             0);
 
             &s->sample_spec,
             0);
 
+    s->thread_info.rtpoll = NULL;
     s->thread_info.outputs = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
     s->thread_info.soft_volume = s->soft_volume;
     s->thread_info.soft_muted = s->muted;
     s->thread_info.outputs = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
     s->thread_info.soft_volume = s->soft_volume;
     s->thread_info.soft_muted = s->muted;
@@ -215,8 +270,9 @@ pa_source* pa_source_new(
     s->thread_info.max_rewind = 0;
     s->thread_info.requested_latency_valid = FALSE;
     s->thread_info.requested_latency = 0;
     s->thread_info.max_rewind = 0;
     s->thread_info.requested_latency_valid = FALSE;
     s->thread_info.requested_latency = 0;
-    s->thread_info.min_latency = DEFAULT_MIN_LATENCY;
-    s->thread_info.max_latency = 0;
+    s->thread_info.min_latency = ABSOLUTE_MIN_LATENCY;
+    s->thread_info.max_latency = ABSOLUTE_MAX_LATENCY;
+    s->thread_info.fixed_latency = flags & PA_SOURCE_DYNAMIC_LATENCY ? 0 : DEFAULT_FIXED_LATENCY;
 
     pa_assert_se(pa_idxset_put(core->sources, s, &s->index) >= 0);
 
 
     pa_assert_se(pa_idxset_put(core->sources, s, &s->index) >= 0);
 
@@ -242,6 +298,7 @@ static int source_set_state(pa_source *s, pa_source_state_t state) {
     pa_source_state_t original_state;
 
     pa_assert(s);
     pa_source_state_t original_state;
 
     pa_assert(s);
+    pa_assert_ctl_context();
 
     if (s->state == state)
         return 0;
 
     if (s->state == state)
         return 0;
@@ -278,39 +335,41 @@ static int source_set_state(pa_source *s, pa_source_state_t state) {
 
         /* We're suspending or resuming, tell everyone about it */
 
 
         /* We're suspending or resuming, tell everyone about it */
 
-        for (o = PA_SOURCE_OUTPUT(pa_idxset_first(s->outputs, &idx)); o; o = PA_SOURCE_OUTPUT(pa_idxset_next(s->outputs, &idx)))
+        PA_IDXSET_FOREACH(o, s->outputs, idx)
             if (s->state == PA_SOURCE_SUSPENDED &&
             if (s->state == PA_SOURCE_SUSPENDED &&
-                (o->flags & PA_SOURCE_OUTPUT_FAIL_ON_SUSPEND))
+                (o->flags & PA_SOURCE_OUTPUT_KILL_ON_SUSPEND))
                 pa_source_output_kill(o);
             else if (o->suspend)
                 o->suspend(o, state == PA_SOURCE_SUSPENDED);
     }
 
                 pa_source_output_kill(o);
             else if (o->suspend)
                 o->suspend(o, state == PA_SOURCE_SUSPENDED);
     }
 
-
     return 0;
 }
 
 /* Called from main context */
 void pa_source_put(pa_source *s) {
     pa_source_assert_ref(s);
     return 0;
 }
 
 /* Called from main context */
 void pa_source_put(pa_source *s) {
     pa_source_assert_ref(s);
+    pa_assert_ctl_context();
 
     pa_assert(s->state == PA_SOURCE_INIT);
 
     /* The following fields must be initialized properly when calling _put() */
     pa_assert(s->asyncmsgq);
 
     pa_assert(s->state == PA_SOURCE_INIT);
 
     /* The following fields must be initialized properly when calling _put() */
     pa_assert(s->asyncmsgq);
-    pa_assert(s->rtpoll);
-    pa_assert(!s->thread_info.min_latency || !s->thread_info.max_latency ||
-              s->thread_info.min_latency <= s->thread_info.max_latency);
+    pa_assert(s->thread_info.min_latency <= s->thread_info.max_latency);
+
+    /* Generally, flags should be initialized via pa_source_new(). As
+     * a special exception we allow volume related flags to be set
+     * between _new() and _put(). */
 
 
-    if (!(s->flags & PA_SOURCE_HW_VOLUME_CTRL)) {
+    if (!(s->flags & PA_SOURCE_HW_VOLUME_CTRL))
         s->flags |= PA_SOURCE_DECIBEL_VOLUME;
 
         s->flags |= PA_SOURCE_DECIBEL_VOLUME;
 
-        s->thread_info.soft_volume = s->soft_volume;
-        s->thread_info.soft_muted = s->muted;
-    }
+    s->thread_info.soft_volume = s->soft_volume;
+    s->thread_info.soft_muted = s->muted;
 
 
-    if (s->flags & PA_SOURCE_DECIBEL_VOLUME)
-        s->n_volume_steps = PA_VOLUME_NORM+1;
+    pa_assert((s->flags & PA_SOURCE_HW_VOLUME_CTRL) || (s->base_volume == PA_VOLUME_NORM && s->flags & PA_SOURCE_DECIBEL_VOLUME));
+    pa_assert(!(s->flags & PA_SOURCE_DECIBEL_VOLUME) || s->n_volume_steps == PA_VOLUME_NORM+1);
+    pa_assert(!(s->flags & PA_SOURCE_DYNAMIC_LATENCY) == (s->thread_info.fixed_latency != 0));
 
     pa_assert_se(source_set_state(s, PA_SOURCE_IDLE) == 0);
 
 
     pa_assert_se(source_set_state(s, PA_SOURCE_IDLE) == 0);
 
@@ -324,6 +383,7 @@ void pa_source_unlink(pa_source *s) {
     pa_source_output *o, *j = NULL;
 
     pa_assert(s);
     pa_source_output *o, *j = NULL;
 
     pa_assert(s);
+    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. */
@@ -365,6 +425,7 @@ static void source_free(pa_object *o) {
     pa_source *s = PA_SOURCE(o);
 
     pa_assert(s);
     pa_source *s = PA_SOURCE(o);
 
     pa_assert(s);
+    pa_assert_ctl_context();
     pa_assert(pa_source_refcnt(s) == 0);
 
     if (PA_SOURCE_IS_LINKED(s->state))
     pa_assert(pa_source_refcnt(s) == 0);
 
     if (PA_SOURCE_IS_LINKED(s->state))
@@ -388,26 +449,52 @@ static void source_free(pa_object *o) {
     if (s->proplist)
         pa_proplist_free(s->proplist);
 
     if (s->proplist)
         pa_proplist_free(s->proplist);
 
+    if (s->ports) {
+        pa_device_port *p;
+
+        while ((p = pa_hashmap_steal_first(s->ports)))
+            pa_device_port_free(p);
+
+        pa_hashmap_free(s->ports, NULL, NULL);
+    }
+
     pa_xfree(s);
 }
 
     pa_xfree(s);
 }
 
-/* Called from main context */
+/* Called from main context, and not while the IO thread is active, please */
 void pa_source_set_asyncmsgq(pa_source *s, pa_asyncmsgq *q) {
     pa_source_assert_ref(s);
 void pa_source_set_asyncmsgq(pa_source *s, pa_asyncmsgq *q) {
     pa_source_assert_ref(s);
+    pa_assert_ctl_context();
 
     s->asyncmsgq = q;
 }
 
 
     s->asyncmsgq = q;
 }
 
-/* Called from main context */
+/* Called from main context, and not while the IO thread is active, please */
+void pa_source_update_flags(pa_source *s, pa_source_flags_t mask, pa_source_flags_t value) {
+    pa_source_assert_ref(s);
+    pa_assert_ctl_context();
+
+    if (mask == 0)
+        return;
+
+    /* For now, allow only a minimal set of flags to be changed. */
+    pa_assert((mask & ~(PA_SOURCE_DYNAMIC_LATENCY|PA_SOURCE_LATENCY)) == 0);
+
+    s->flags = (s->flags & ~mask) | (value & mask);
+}
+
+/* Called from IO context, or before _put() from main context */
 void pa_source_set_rtpoll(pa_source *s, pa_rtpoll *p) {
     pa_source_assert_ref(s);
 void pa_source_set_rtpoll(pa_source *s, pa_rtpoll *p) {
     pa_source_assert_ref(s);
+    pa_source_assert_io_context(s);
 
 
-    s->rtpoll = p;
+    s->thread_info.rtpoll = p;
 }
 
 /* Called from main context */
 int pa_source_update_status(pa_source*s) {
     pa_source_assert_ref(s);
 }
 
 /* Called from main context */
 int pa_source_update_status(pa_source*s) {
     pa_source_assert_ref(s);
+    pa_assert_ctl_context();
     pa_assert(PA_SOURCE_IS_LINKED(s->state));
 
     if (s->state == PA_SOURCE_SUSPENDED)
     pa_assert(PA_SOURCE_IS_LINKED(s->state));
 
     if (s->state == PA_SOURCE_SUSPENDED)
@@ -417,9 +504,24 @@ int pa_source_update_status(pa_source*s) {
 }
 
 /* Called from main context */
 }
 
 /* Called from main context */
-int pa_source_suspend(pa_source *s, pa_bool_t suspend) {
+int pa_source_suspend(pa_source *s, pa_bool_t suspend, pa_suspend_cause_t cause) {
     pa_source_assert_ref(s);
     pa_source_assert_ref(s);
+    pa_assert_ctl_context();
     pa_assert(PA_SOURCE_IS_LINKED(s->state));
     pa_assert(PA_SOURCE_IS_LINKED(s->state));
+    pa_assert(cause != 0);
+
+    if (s->monitor_of)
+        return -PA_ERR_NOTSUPPORTED;
+
+    if (suspend)
+        s->suspend_cause |= cause;
+    else
+        s->suspend_cause &= ~cause;
+
+    if ((pa_source_get_state(s) == PA_SOURCE_SUSPENDED) == !!s->suspend_cause)
+        return 0;
+
+    pa_log_debug("Suspend cause of source %s is 0x%04x, %s", s->name, s->suspend_cause, s->suspend_cause ? "suspending" : "resuming");
 
     if (suspend)
         return source_set_state(s, PA_SOURCE_SUSPENDED);
 
     if (suspend)
         return source_set_state(s, PA_SOURCE_SUSPENDED);
@@ -428,21 +530,45 @@ int pa_source_suspend(pa_source *s, pa_bool_t suspend) {
 }
 
 /* Called from main context */
 }
 
 /* Called from main context */
-pa_queue *pa_source_move_all_start(pa_source *s) {
-    pa_queue *q;
+int pa_source_sync_suspend(pa_source *s) {
+    pa_sink_state_t state;
+
+    pa_source_assert_ref(s);
+    pa_assert_ctl_context();
+    pa_assert(PA_SOURCE_IS_LINKED(s->state));
+    pa_assert(s->monitor_of);
+
+    state = pa_sink_get_state(s->monitor_of);
+
+    if (state == PA_SINK_SUSPENDED)
+        return source_set_state(s, PA_SOURCE_SUSPENDED);
+
+    pa_assert(PA_SINK_IS_OPENED(state));
+
+    return source_set_state(s, pa_source_used_by(s) ? PA_SOURCE_RUNNING : PA_SOURCE_IDLE);
+}
+
+/* Called from main context */
+pa_queue *pa_source_move_all_start(pa_source *s, pa_queue *q) {
     pa_source_output *o, *n;
     uint32_t idx;
 
     pa_source_assert_ref(s);
     pa_source_output *o, *n;
     uint32_t idx;
 
     pa_source_assert_ref(s);
+    pa_assert_ctl_context();
     pa_assert(PA_SOURCE_IS_LINKED(s->state));
 
     pa_assert(PA_SOURCE_IS_LINKED(s->state));
 
-    q = pa_queue_new();
+    if (!q)
+        q = pa_queue_new();
 
     for (o = PA_SOURCE_OUTPUT(pa_idxset_first(s->outputs, &idx)); o; o = n) {
         n = PA_SOURCE_OUTPUT(pa_idxset_next(s->outputs, &idx));
 
 
     for (o = PA_SOURCE_OUTPUT(pa_idxset_first(s->outputs, &idx)); o; o = n) {
         n = PA_SOURCE_OUTPUT(pa_idxset_next(s->outputs, &idx));
 
+        pa_source_output_ref(o);
+
         if (pa_source_output_start_move(o) >= 0)
         if (pa_source_output_start_move(o) >= 0)
-            pa_queue_push(q, pa_source_output_ref(o));
+            pa_queue_push(q, o);
+        else
+            pa_source_output_unref(o);
     }
 
     return q;
     }
 
     return q;
@@ -453,12 +579,13 @@ void pa_source_move_all_finish(pa_source *s, pa_queue *q, pa_bool_t save) {
     pa_source_output *o;
 
     pa_source_assert_ref(s);
     pa_source_output *o;
 
     pa_source_assert_ref(s);
+    pa_assert_ctl_context();
     pa_assert(PA_SOURCE_IS_LINKED(s->state));
     pa_assert(q);
 
     while ((o = PA_SOURCE_OUTPUT(pa_queue_pop(q)))) {
         if (pa_source_output_finish_move(o, s, save) < 0)
     pa_assert(PA_SOURCE_IS_LINKED(s->state));
     pa_assert(q);
 
     while ((o = PA_SOURCE_OUTPUT(pa_queue_pop(q)))) {
         if (pa_source_output_finish_move(o, s, save) < 0)
-            pa_source_output_kill(o);
+            pa_source_output_fail_move(o);
 
         pa_source_output_unref(o);
     }
 
         pa_source_output_unref(o);
     }
@@ -469,13 +596,13 @@ void pa_source_move_all_finish(pa_source *s, pa_queue *q, pa_bool_t save) {
 /* Called from main context */
 void pa_source_move_all_fail(pa_queue *q) {
     pa_source_output *o;
 /* Called from main context */
 void pa_source_move_all_fail(pa_queue *q) {
     pa_source_output *o;
+
+    pa_assert_ctl_context();
     pa_assert(q);
 
     while ((o = PA_SOURCE_OUTPUT(pa_queue_pop(q)))) {
     pa_assert(q);
 
     while ((o = PA_SOURCE_OUTPUT(pa_queue_pop(q)))) {
-        if (pa_hook_fire(&o->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MOVE_FAIL], o) == PA_HOOK_OK) {
-            pa_source_output_kill(o);
-            pa_source_output_unref(o);
-        }
+        pa_source_output_fail_move(o);
+        pa_source_output_unref(o);
     }
 
     pa_queue_free(q, NULL, NULL);
     }
 
     pa_queue_free(q, NULL, NULL);
@@ -487,17 +614,18 @@ void pa_source_process_rewind(pa_source *s, size_t nbytes) {
     void *state = NULL;
 
     pa_source_assert_ref(s);
     void *state = NULL;
 
     pa_source_assert_ref(s);
+    pa_source_assert_io_context(s);
     pa_assert(PA_SOURCE_IS_LINKED(s->thread_info.state));
 
     pa_assert(PA_SOURCE_IS_LINKED(s->thread_info.state));
 
-    if (s->thread_info.state == PA_SOURCE_SUSPENDED)
+    if (nbytes <= 0)
         return;
 
         return;
 
-    if (nbytes <= 0)
+    if (s->thread_info.state == PA_SOURCE_SUSPENDED)
         return;
 
     pa_log_debug("Processing rewind...");
 
         return;
 
     pa_log_debug("Processing rewind...");
 
-    while ((o = pa_hashmap_iterate(s->thread_info.outputs, &state, NULL))) {
+    PA_HASHMAP_FOREACH(o, s->thread_info.outputs, state) {
         pa_source_output_assert_ref(o);
         pa_source_output_process_rewind(o, nbytes);
     }
         pa_source_output_assert_ref(o);
         pa_source_output_process_rewind(o, nbytes);
     }
@@ -509,6 +637,7 @@ void pa_source_post(pa_source*s, const pa_memchunk *chunk) {
     void *state = NULL;
 
     pa_source_assert_ref(s);
     void *state = NULL;
 
     pa_source_assert_ref(s);
+    pa_source_assert_io_context(s);
     pa_assert(PA_SOURCE_IS_LINKED(s->thread_info.state));
     pa_assert(chunk);
 
     pa_assert(PA_SOURCE_IS_LINKED(s->thread_info.state));
     pa_assert(chunk);
 
@@ -548,6 +677,7 @@ void pa_source_post(pa_source*s, const pa_memchunk *chunk) {
 /* Called from IO thread context */
 void pa_source_post_direct(pa_source*s, pa_source_output *o, const pa_memchunk *chunk) {
     pa_source_assert_ref(s);
 /* Called from IO thread context */
 void pa_source_post_direct(pa_source*s, pa_source_output *o, const pa_memchunk *chunk) {
     pa_source_assert_ref(s);
+    pa_source_assert_io_context(s);
     pa_assert(PA_SOURCE_IS_LINKED(s->thread_info.state));
     pa_source_output_assert_ref(o);
     pa_assert(o->thread_info.direct_on_input);
     pa_assert(PA_SOURCE_IS_LINKED(s->thread_info.state));
     pa_source_output_assert_ref(o);
     pa_assert(o->thread_info.direct_on_input);
@@ -579,6 +709,7 @@ pa_usec_t pa_source_get_latency(pa_source *s) {
     pa_usec_t usec;
 
     pa_source_assert_ref(s);
     pa_usec_t usec;
 
     pa_source_assert_ref(s);
+    pa_assert_ctl_context();
     pa_assert(PA_SOURCE_IS_LINKED(s->state));
 
     if (s->state == PA_SOURCE_SUSPENDED)
     pa_assert(PA_SOURCE_IS_LINKED(s->state));
 
     if (s->state == PA_SOURCE_SUSPENDED)
@@ -592,73 +723,132 @@ pa_usec_t pa_source_get_latency(pa_source *s) {
     return usec;
 }
 
     return usec;
 }
 
+/* Called from IO thread */
+pa_usec_t pa_source_get_latency_within_thread(pa_source *s) {
+    pa_usec_t usec = 0;
+    pa_msgobject *o;
+
+    pa_source_assert_ref(s);
+    pa_source_assert_io_context(s);
+    pa_assert(PA_SOURCE_IS_LINKED(s->thread_info.state));
+
+    /* The returned value is supposed to be in the time domain of the sound card! */
+
+    if (s->thread_info.state == PA_SOURCE_SUSPENDED)
+        return 0;
+
+    if (!(s->flags & PA_SOURCE_LATENCY))
+        return 0;
+
+    o = PA_MSGOBJECT(s);
+
+    /* We probably should make this a proper vtable callback instead of going through process_msg() */
+
+    if (o->process_msg(o, PA_SOURCE_MESSAGE_GET_LATENCY, &usec, 0, NULL) < 0)
+        return -1;
+
+    return usec;
+}
+
 /* Called from main thread */
 /* Called from main thread */
-void pa_source_set_volume(pa_source *s, const pa_cvolume *volume) {
-    pa_cvolume old_virtual_volume;
-    pa_bool_t virtual_volume_changed;
+void pa_source_set_volume(
+        pa_source *s,
+        const pa_cvolume *volume,
+        pa_bool_t save) {
+
+    pa_bool_t real_changed;
 
     pa_source_assert_ref(s);
 
     pa_source_assert_ref(s);
+    pa_assert_ctl_context();
     pa_assert(PA_SOURCE_IS_LINKED(s->state));
     pa_assert(PA_SOURCE_IS_LINKED(s->state));
-    pa_assert(volume);
     pa_assert(pa_cvolume_valid(volume));
     pa_assert(pa_cvolume_compatible(volume, &s->sample_spec));
 
     pa_assert(pa_cvolume_valid(volume));
     pa_assert(pa_cvolume_compatible(volume, &s->sample_spec));
 
-    old_virtual_volume = s->virtual_volume;
-    s->virtual_volume = *volume;
-    virtual_volume_changed = !pa_cvolume_equal(&old_virtual_volume, &s->virtual_volume);
+    real_changed = !pa_cvolume_equal(volume, &s->volume);
+    s->volume = *volume;
+    s->save_volume = (!real_changed && s->save_volume) || save;
 
     if (s->set_volume) {
         pa_cvolume_reset(&s->soft_volume, s->sample_spec.channels);
         s->set_volume(s);
     } else
 
     if (s->set_volume) {
         pa_cvolume_reset(&s->soft_volume, s->sample_spec.channels);
         s->set_volume(s);
     } else
-        s->soft_volume = s->virtual_volume;
+        s->soft_volume = s->volume;
 
     pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_SET_VOLUME, NULL, 0, NULL) == 0);
 
 
     pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_SET_VOLUME, NULL, 0, NULL) == 0);
 
-    if (virtual_volume_changed)
+    if (real_changed)
         pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
 }
 
 /* Called from main thread. Only to be called by source implementor */
 void pa_source_set_soft_volume(pa_source *s, const pa_cvolume *volume) {
     pa_source_assert_ref(s);
         pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
 }
 
 /* Called from main thread. Only to be called by source implementor */
 void pa_source_set_soft_volume(pa_source *s, const pa_cvolume *volume) {
     pa_source_assert_ref(s);
-    pa_assert(volume);
+    pa_assert_ctl_context();
+
+    if (!volume)
+        pa_cvolume_reset(&s->soft_volume, s->sample_spec.channels);
+    else
+        s->soft_volume = *volume;
 
     if (PA_SOURCE_IS_LINKED(s->state))
         pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_SET_VOLUME, NULL, 0, NULL) == 0);
     else
 
     if (PA_SOURCE_IS_LINKED(s->state))
         pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_SET_VOLUME, NULL, 0, NULL) == 0);
     else
-        s->thread_info.soft_volume = *volume;
+        s->thread_info.soft_volume = s->soft_volume;
 }
 
 /* Called from main thread */
 const pa_cvolume *pa_source_get_volume(pa_source *s, pa_bool_t force_refresh) {
     pa_source_assert_ref(s);
 }
 
 /* Called from main thread */
 const pa_cvolume *pa_source_get_volume(pa_source *s, pa_bool_t force_refresh) {
     pa_source_assert_ref(s);
+    pa_assert_ctl_context();
     pa_assert(PA_SOURCE_IS_LINKED(s->state));
 
     if (s->refresh_volume || force_refresh) {
     pa_assert(PA_SOURCE_IS_LINKED(s->state));
 
     if (s->refresh_volume || force_refresh) {
-        pa_cvolume old_virtual_volume = s->virtual_volume;
+        pa_cvolume old_volume;
+
+        old_volume = s->volume;
 
         if (s->get_volume)
             s->get_volume(s);
 
         pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_GET_VOLUME, NULL, 0, NULL) == 0);
 
 
         if (s->get_volume)
             s->get_volume(s);
 
         pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_GET_VOLUME, NULL, 0, NULL) == 0);
 
-        if (!pa_cvolume_equal(&old_virtual_volume, &s->virtual_volume))
+        if (!pa_cvolume_equal(&old_volume, &s->volume)) {
+            s->save_volume = TRUE;
             pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
             pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+        }
     }
 
     }
 
-    return &s->virtual_volume;
+    return &s->volume;
 }
 
 /* Called from main thread */
 }
 
 /* Called from main thread */
-void pa_source_set_mute(pa_source *s, pa_bool_t mute) {
+void pa_source_volume_changed(pa_source *s, const pa_cvolume *new_volume) {
+    pa_source_assert_ref(s);
+    pa_assert_ctl_context();
+    pa_assert(PA_SOURCE_IS_LINKED(s->state));
+
+    /* The source implementor may call this if the volume changed to make sure everyone is notified */
+
+    if (pa_cvolume_equal(&s->volume, new_volume))
+        return;
+
+    s->volume = *new_volume;
+    s->save_volume = TRUE;
+
+    pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+}
+
+/* Called from main thread */
+void pa_source_set_mute(pa_source *s, pa_bool_t mute, pa_bool_t save) {
     pa_bool_t old_muted;
 
     pa_source_assert_ref(s);
     pa_bool_t old_muted;
 
     pa_source_assert_ref(s);
+    pa_assert_ctl_context();
     pa_assert(PA_SOURCE_IS_LINKED(s->state));
 
     old_muted = s->muted;
     s->muted = mute;
     pa_assert(PA_SOURCE_IS_LINKED(s->state));
 
     old_muted = s->muted;
     s->muted = mute;
+    s->save_muted = (old_muted == s->muted && s->save_muted) || save;
 
     if (s->set_mute)
         s->set_mute(s);
 
     if (s->set_mute)
         s->set_mute(s);
@@ -671,8 +861,8 @@ void pa_source_set_mute(pa_source *s, pa_bool_t mute) {
 
 /* Called from main thread */
 pa_bool_t pa_source_get_mute(pa_source *s, pa_bool_t force_refresh) {
 
 /* Called from main thread */
 pa_bool_t pa_source_get_mute(pa_source *s, pa_bool_t force_refresh) {
-
     pa_source_assert_ref(s);
     pa_source_assert_ref(s);
+    pa_assert_ctl_context();
     pa_assert(PA_SOURCE_IS_LINKED(s->state));
 
     if (s->refresh_muted || force_refresh) {
     pa_assert(PA_SOURCE_IS_LINKED(s->state));
 
     if (s->refresh_muted || force_refresh) {
@@ -683,19 +873,43 @@ pa_bool_t pa_source_get_mute(pa_source *s, pa_bool_t force_refresh) {
 
         pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_GET_MUTE, NULL, 0, NULL) == 0);
 
 
         pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_GET_MUTE, NULL, 0, NULL) == 0);
 
-        if (old_muted != s->muted)
+        if (old_muted != s->muted) {
+            s->save_muted = TRUE;
+
             pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
             pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+
+            /* Make sure the soft mute status stays in sync */
+            pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_SET_MUTE, NULL, 0, NULL) == 0);
+        }
     }
 
     return s->muted;
 }
 
     }
 
     return s->muted;
 }
 
+/* Called from main thread */
+void pa_source_mute_changed(pa_source *s, pa_bool_t new_muted) {
+    pa_source_assert_ref(s);
+    pa_assert_ctl_context();
+    pa_assert(PA_SOURCE_IS_LINKED(s->state));
+
+    /* The source implementor may call this if the mute state changed to make sure everyone is notified */
+
+    if (s->muted == new_muted)
+        return;
+
+    s->muted = new_muted;
+    s->save_muted = TRUE;
+
+    pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+}
+
 /* Called from main thread */
 pa_bool_t pa_source_update_proplist(pa_source *s, pa_update_mode_t mode, pa_proplist *p) {
     pa_source_assert_ref(s);
 /* Called from main thread */
 pa_bool_t pa_source_update_proplist(pa_source *s, pa_update_mode_t mode, pa_proplist *p) {
     pa_source_assert_ref(s);
-    pa_assert(p);
+    pa_assert_ctl_context();
 
 
-    pa_proplist_update(s->proplist, mode, p);
+    if (p)
+        pa_proplist_update(s->proplist, mode, p);
 
     if (PA_SOURCE_IS_LINKED(s->state)) {
         pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SOURCE_PROPLIST_CHANGED], s);
 
     if (PA_SOURCE_IS_LINKED(s->state)) {
         pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SOURCE_PROPLIST_CHANGED], s);
@@ -706,16 +920,18 @@ pa_bool_t pa_source_update_proplist(pa_source *s, pa_update_mode_t mode, pa_prop
 }
 
 /* Called from main thread */
 }
 
 /* Called from main thread */
+/* FIXME -- this should be dropped and be merged into pa_source_update_proplist() */
 void pa_source_set_description(pa_source *s, const char *description) {
     const char *old;
     pa_source_assert_ref(s);
 void pa_source_set_description(pa_source *s, const char *description) {
     const char *old;
     pa_source_assert_ref(s);
+    pa_assert_ctl_context();
 
     if (!description && !pa_proplist_contains(s->proplist, PA_PROP_DEVICE_DESCRIPTION))
         return;
 
     old = pa_proplist_gets(s->proplist, PA_PROP_DEVICE_DESCRIPTION);
 
 
     if (!description && !pa_proplist_contains(s->proplist, PA_PROP_DEVICE_DESCRIPTION))
         return;
 
     old = pa_proplist_gets(s->proplist, PA_PROP_DEVICE_DESCRIPTION);
 
-    if (old && description && !strcmp(old, description))
+    if (old && description && pa_streq(old, description))
         return;
 
     if (description)
         return;
 
     if (description)
@@ -733,6 +949,7 @@ void pa_source_set_description(pa_source *s, const char *description) {
 unsigned pa_source_linked_by(pa_source *s) {
     pa_source_assert_ref(s);
     pa_assert(PA_SOURCE_IS_LINKED(s->state));
 unsigned pa_source_linked_by(pa_source *s) {
     pa_source_assert_ref(s);
     pa_assert(PA_SOURCE_IS_LINKED(s->state));
+    pa_assert_ctl_context();
 
     return pa_idxset_size(s->outputs);
 }
 
     return pa_idxset_size(s->outputs);
 }
@@ -743,6 +960,7 @@ unsigned pa_source_used_by(pa_source *s) {
 
     pa_source_assert_ref(s);
     pa_assert(PA_SOURCE_IS_LINKED(s->state));
 
     pa_source_assert_ref(s);
     pa_assert(PA_SOURCE_IS_LINKED(s->state));
+    pa_assert_ctl_context();
 
     ret = pa_idxset_size(s->outputs);
     pa_assert(ret >= s->n_corked);
 
     ret = pa_idxset_size(s->outputs);
     pa_assert(ret >= s->n_corked);
@@ -757,13 +975,14 @@ unsigned pa_source_check_suspend(pa_source *s) {
     uint32_t idx;
 
     pa_source_assert_ref(s);
     uint32_t idx;
 
     pa_source_assert_ref(s);
+    pa_assert_ctl_context();
 
     if (!PA_SOURCE_IS_LINKED(s->state))
         return 0;
 
     ret = 0;
 
 
     if (!PA_SOURCE_IS_LINKED(s->state))
         return 0;
 
     ret = 0;
 
-    for (o = PA_SOURCE_OUTPUT(pa_idxset_first(s->outputs, &idx)); o; o = PA_SOURCE_OUTPUT(pa_idxset_next(s->outputs, &idx))) {
+    PA_IDXSET_FOREACH(o, s->outputs, idx) {
         pa_source_output_state_t st;
 
         st = pa_source_output_get_state(o);
         pa_source_output_state_t st;
 
         st = pa_source_output_get_state(o);
@@ -838,7 +1057,7 @@ int pa_source_process_msg(pa_msgobject *object, int code, void *userdata, int64_
             if (pa_hashmap_remove(s->thread_info.outputs, PA_UINT32_TO_PTR(o->index)))
                 pa_source_output_unref(o);
 
             if (pa_hashmap_remove(s->thread_info.outputs, PA_UINT32_TO_PTR(o->index)))
                 pa_source_output_unref(o);
 
-            pa_source_invalidate_requested_latency(s);
+            pa_source_invalidate_requested_latency(s, TRUE);
 
             return 0;
         }
 
             return 0;
         }
@@ -857,9 +1076,26 @@ int pa_source_process_msg(pa_msgobject *object, int code, void *userdata, int64_
         case PA_SOURCE_MESSAGE_GET_MUTE:
             return 0;
 
         case PA_SOURCE_MESSAGE_GET_MUTE:
             return 0;
 
-        case PA_SOURCE_MESSAGE_SET_STATE:
+        case PA_SOURCE_MESSAGE_SET_STATE: {
+
+            pa_bool_t suspend_change =
+                (s->thread_info.state == PA_SOURCE_SUSPENDED && PA_SOURCE_IS_OPENED(PA_PTR_TO_UINT(userdata))) ||
+                (PA_SOURCE_IS_OPENED(s->thread_info.state) && PA_PTR_TO_UINT(userdata) == PA_SOURCE_SUSPENDED);
+
             s->thread_info.state = PA_PTR_TO_UINT(userdata);
             s->thread_info.state = PA_PTR_TO_UINT(userdata);
+
+            if (suspend_change) {
+                pa_source_output *o;
+                void *state = NULL;
+
+                while ((o = pa_hashmap_iterate(s->thread_info.outputs, &state, NULL)))
+                    if (o->suspend_within_thread)
+                        o->suspend_within_thread(o, s->thread_info.state == PA_SOURCE_SUSPENDED);
+            }
+
+
             return 0;
             return 0;
+        }
 
         case PA_SOURCE_MESSAGE_DETACH:
 
 
         case PA_SOURCE_MESSAGE_DETACH:
 
@@ -887,7 +1123,7 @@ int pa_source_process_msg(pa_msgobject *object, int code, void *userdata, int64_
         case PA_SOURCE_MESSAGE_SET_LATENCY_RANGE: {
             pa_usec_t *r = userdata;
 
         case PA_SOURCE_MESSAGE_SET_LATENCY_RANGE: {
             pa_usec_t *r = userdata;
 
-            pa_source_update_latency_range(s, r[0], r[1]);
+            pa_source_set_latency_range_within_thread(s, r[0], r[1]);
 
             return 0;
         }
 
             return 0;
         }
@@ -901,11 +1137,26 @@ int pa_source_process_msg(pa_msgobject *object, int code, void *userdata, int64_
             return 0;
         }
 
             return 0;
         }
 
+        case PA_SOURCE_MESSAGE_GET_FIXED_LATENCY:
+
+            *((pa_usec_t*) userdata) = s->thread_info.fixed_latency;
+            return 0;
+
+        case PA_SOURCE_MESSAGE_SET_FIXED_LATENCY:
+
+            pa_source_set_fixed_latency_within_thread(s, (pa_usec_t) offset);
+            return 0;
+
         case PA_SOURCE_MESSAGE_GET_MAX_REWIND:
 
             *((size_t*) userdata) = s->thread_info.max_rewind;
             return 0;
 
         case PA_SOURCE_MESSAGE_GET_MAX_REWIND:
 
             *((size_t*) userdata) = s->thread_info.max_rewind;
             return 0;
 
+        case PA_SOURCE_MESSAGE_SET_MAX_REWIND:
+
+            pa_source_set_max_rewind_within_thread(s, (size_t) offset);
+            return 0;
+
         case PA_SOURCE_MESSAGE_GET_LATENCY:
 
             if (s->monitor_of) {
         case PA_SOURCE_MESSAGE_GET_LATENCY:
 
             if (s->monitor_of) {
@@ -924,15 +1175,24 @@ int pa_source_process_msg(pa_msgobject *object, int code, void *userdata, int64_
 }
 
 /* Called from main thread */
 }
 
 /* Called from main thread */
-int pa_source_suspend_all(pa_core *c, pa_bool_t suspend) {
+int pa_source_suspend_all(pa_core *c, pa_bool_t suspend, pa_suspend_cause_t cause) {
     uint32_t idx;
     pa_source *source;
     int ret = 0;
 
     pa_core_assert_ref(c);
     uint32_t idx;
     pa_source *source;
     int ret = 0;
 
     pa_core_assert_ref(c);
+    pa_assert_ctl_context();
+    pa_assert(cause != 0);
 
 
-    for (source = PA_SOURCE(pa_idxset_first(c->sources, &idx)); source; source = PA_SOURCE(pa_idxset_next(c->sources, &idx)))
-        ret -= pa_source_suspend(source, suspend) < 0;
+    for (source = PA_SOURCE(pa_idxset_first(c->sources, &idx)); source; source = PA_SOURCE(pa_idxset_next(c->sources, &idx))) {
+        int r;
+
+        if (source->monitor_of)
+            continue;
+
+        if ((r = pa_source_suspend(source, suspend, cause)) < 0)
+            ret = r;
+    }
 
     return ret;
 }
 
     return ret;
 }
@@ -940,6 +1200,7 @@ int pa_source_suspend_all(pa_core *c, pa_bool_t suspend) {
 /* Called from main thread */
 void pa_source_detach(pa_source *s) {
     pa_source_assert_ref(s);
 /* Called from main thread */
 void pa_source_detach(pa_source *s) {
     pa_source_assert_ref(s);
+    pa_assert_ctl_context();
     pa_assert(PA_SOURCE_IS_LINKED(s->state));
 
     pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_DETACH, NULL, 0, NULL) == 0);
     pa_assert(PA_SOURCE_IS_LINKED(s->state));
 
     pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_DETACH, NULL, 0, NULL) == 0);
@@ -948,6 +1209,7 @@ void pa_source_detach(pa_source *s) {
 /* Called from main thread */
 void pa_source_attach(pa_source *s) {
     pa_source_assert_ref(s);
 /* Called from main thread */
 void pa_source_attach(pa_source *s) {
     pa_source_assert_ref(s);
+    pa_assert_ctl_context();
     pa_assert(PA_SOURCE_IS_LINKED(s->state));
 
     pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_ATTACH, NULL, 0, NULL) == 0);
     pa_assert(PA_SOURCE_IS_LINKED(s->state));
 
     pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_ATTACH, NULL, 0, NULL) == 0);
@@ -959,9 +1221,10 @@ void pa_source_detach_within_thread(pa_source *s) {
     void *state = NULL;
 
     pa_source_assert_ref(s);
     void *state = NULL;
 
     pa_source_assert_ref(s);
+    pa_source_assert_io_context(s);
     pa_assert(PA_SOURCE_IS_LINKED(s->thread_info.state));
 
     pa_assert(PA_SOURCE_IS_LINKED(s->thread_info.state));
 
-    while ((o = pa_hashmap_iterate(s->thread_info.outputs, &state, NULL)))
+    PA_HASHMAP_FOREACH(o, s->thread_info.outputs, state)
         if (o->detach)
             o->detach(o);
 }
         if (o->detach)
             o->detach(o);
 }
@@ -972,9 +1235,10 @@ void pa_source_attach_within_thread(pa_source *s) {
     void *state = NULL;
 
     pa_source_assert_ref(s);
     void *state = NULL;
 
     pa_source_assert_ref(s);
+    pa_source_assert_io_context(s);
     pa_assert(PA_SOURCE_IS_LINKED(s->thread_info.state));
 
     pa_assert(PA_SOURCE_IS_LINKED(s->thread_info.state));
 
-    while ((o = pa_hashmap_iterate(s->thread_info.outputs, &state, NULL)))
+    PA_HASHMAP_FOREACH(o, s->thread_info.outputs, state)
         if (o->attach)
             o->attach(o);
 }
         if (o->attach)
             o->attach(o);
 }
@@ -986,35 +1250,37 @@ pa_usec_t pa_source_get_requested_latency_within_thread(pa_source *s) {
     void *state = NULL;
 
     pa_source_assert_ref(s);
     void *state = NULL;
 
     pa_source_assert_ref(s);
+    pa_source_assert_io_context(s);
+
+    if (!(s->flags & PA_SOURCE_DYNAMIC_LATENCY))
+        return PA_CLAMP(s->thread_info.fixed_latency, s->thread_info.min_latency, s->thread_info.max_latency);
 
     if (s->thread_info.requested_latency_valid)
         return s->thread_info.requested_latency;
 
 
     if (s->thread_info.requested_latency_valid)
         return s->thread_info.requested_latency;
 
-    while ((o = pa_hashmap_iterate(s->thread_info.outputs, &state, NULL)))
-
+    PA_HASHMAP_FOREACH(o, s->thread_info.outputs, state)
         if (o->thread_info.requested_source_latency != (pa_usec_t) -1 &&
             (result == (pa_usec_t) -1 || result > o->thread_info.requested_source_latency))
             result = o->thread_info.requested_source_latency;
 
         if (o->thread_info.requested_source_latency != (pa_usec_t) -1 &&
             (result == (pa_usec_t) -1 || result > o->thread_info.requested_source_latency))
             result = o->thread_info.requested_source_latency;
 
-    if (result != (pa_usec_t) -1) {
-        if (s->thread_info.max_latency > 0 && result > s->thread_info.max_latency)
-            result = s->thread_info.max_latency;
+    if (result != (pa_usec_t) -1)
+        result = PA_CLAMP(result, s->thread_info.min_latency, s->thread_info.max_latency);
 
 
-        if (s->thread_info.min_latency > 0 && result < s->thread_info.min_latency)
-            result = s->thread_info.min_latency;
+    if (PA_SOURCE_IS_LINKED(s->thread_info.state)) {
+        /* Only cache this if we are fully set up */
+        s->thread_info.requested_latency = result;
+        s->thread_info.requested_latency_valid = TRUE;
     }
 
     }
 
-    s->thread_info.requested_latency = result;
-    s->thread_info.requested_latency_valid = TRUE;
-
     return result;
 }
 
 /* Called from main thread */
 pa_usec_t pa_source_get_requested_latency(pa_source *s) {
     return result;
 }
 
 /* Called from main thread */
 pa_usec_t pa_source_get_requested_latency(pa_source *s) {
-    pa_usec_t usec;
+    pa_usec_t usec = 0;
 
     pa_source_assert_ref(s);
 
     pa_source_assert_ref(s);
+    pa_assert_ctl_context();
     pa_assert(PA_SOURCE_IS_LINKED(s->state));
 
     if (s->state == PA_SOURCE_SUSPENDED)
     pa_assert(PA_SOURCE_IS_LINKED(s->state));
 
     if (s->state == PA_SOURCE_SUSPENDED)
@@ -1026,59 +1292,84 @@ pa_usec_t pa_source_get_requested_latency(pa_source *s) {
 }
 
 /* Called from IO thread */
 }
 
 /* Called from IO thread */
-void pa_source_set_max_rewind(pa_source *s, size_t max_rewind) {
+void pa_source_set_max_rewind_within_thread(pa_source *s, size_t max_rewind) {
     pa_source_output *o;
     void *state = NULL;
 
     pa_source_assert_ref(s);
     pa_source_output *o;
     void *state = NULL;
 
     pa_source_assert_ref(s);
+    pa_source_assert_io_context(s);
 
     if (max_rewind == s->thread_info.max_rewind)
         return;
 
     s->thread_info.max_rewind = max_rewind;
 
 
     if (max_rewind == s->thread_info.max_rewind)
         return;
 
     s->thread_info.max_rewind = max_rewind;
 
-    if (PA_SOURCE_IS_LINKED(s->thread_info.state)) {
-        while ((o = pa_hashmap_iterate(s->thread_info.outputs, &state, NULL)))
+    if (PA_SOURCE_IS_LINKED(s->thread_info.state))
+        PA_HASHMAP_FOREACH(o, s->thread_info.outputs, state)
             pa_source_output_update_max_rewind(o, s->thread_info.max_rewind);
             pa_source_output_update_max_rewind(o, s->thread_info.max_rewind);
-    }
 }
 
 }
 
-void pa_source_invalidate_requested_latency(pa_source *s) {
+/* Called from main thread */
+void pa_source_set_max_rewind(pa_source *s, size_t max_rewind) {
+    pa_source_assert_ref(s);
+    pa_assert_ctl_context();
+
+    if (PA_SOURCE_IS_LINKED(s->state))
+        pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_SET_MAX_REWIND, NULL, max_rewind, NULL) == 0);
+    else
+        pa_source_set_max_rewind_within_thread(s, max_rewind);
+}
+
+/* Called from IO thread */
+void pa_source_invalidate_requested_latency(pa_source *s, pa_bool_t dynamic) {
     pa_source_output *o;
     void *state = NULL;
 
     pa_source_assert_ref(s);
     pa_source_output *o;
     void *state = NULL;
 
     pa_source_assert_ref(s);
+    pa_source_assert_io_context(s);
 
 
-    s->thread_info.requested_latency_valid = FALSE;
+    if ((s->flags & PA_SOURCE_DYNAMIC_LATENCY))
+        s->thread_info.requested_latency_valid = FALSE;
+    else if (dynamic)
+        return;
 
 
-    if (s->update_requested_latency)
-        s->update_requested_latency(s);
+    if (PA_SOURCE_IS_LINKED(s->thread_info.state)) {
 
 
-    while ((o = pa_hashmap_iterate(s->thread_info.outputs, &state, NULL)))
-        if (o->update_source_requested_latency)
-            o->update_source_requested_latency(o);
+        if (s->update_requested_latency)
+            s->update_requested_latency(s);
+
+        while ((o = pa_hashmap_iterate(s->thread_info.outputs, &state, NULL)))
+            if (o->update_source_requested_latency)
+                o->update_source_requested_latency(o);
+    }
 
     if (s->monitor_of)
 
     if (s->monitor_of)
-        pa_sink_invalidate_requested_latency(s->monitor_of);
+        pa_sink_invalidate_requested_latency(s->monitor_of, dynamic);
 }
 
 }
 
+/* Called from main thread */
 void pa_source_set_latency_range(pa_source *s, pa_usec_t min_latency, pa_usec_t max_latency) {
     pa_source_assert_ref(s);
 void pa_source_set_latency_range(pa_source *s, pa_usec_t min_latency, pa_usec_t max_latency) {
     pa_source_assert_ref(s);
+    pa_assert_ctl_context();
 
     /* min_latency == 0:           no limit
 
     /* min_latency == 0:           no limit
-     * min_latency == (size_t) -1: default limit
      * min_latency anything else:  specified limit
      *
      * Similar for max_latency */
 
      * min_latency anything else:  specified limit
      *
      * Similar for max_latency */
 
-    if (min_latency == (pa_usec_t) -1)
-        min_latency = DEFAULT_MIN_LATENCY;
+    if (min_latency < ABSOLUTE_MIN_LATENCY)
+        min_latency = ABSOLUTE_MIN_LATENCY;
+
+    if (max_latency <= 0 ||
+        max_latency > ABSOLUTE_MAX_LATENCY)
+        max_latency = ABSOLUTE_MAX_LATENCY;
 
 
-    if (max_latency == (pa_usec_t) -1)
-        max_latency = min_latency;
+    pa_assert(min_latency <= max_latency);
 
 
-    pa_assert(!min_latency || !max_latency ||
-              min_latency <= max_latency);
+    /* Hmm, let's see if someone forgot to set PA_SOURCE_DYNAMIC_LATENCY here... */
+    pa_assert((min_latency == ABSOLUTE_MIN_LATENCY &&
+               max_latency == ABSOLUTE_MAX_LATENCY) ||
+              (s->flags & PA_SOURCE_DYNAMIC_LATENCY));
 
     if (PA_SOURCE_IS_LINKED(s->state)) {
         pa_usec_t r[2];
 
     if (PA_SOURCE_IS_LINKED(s->state)) {
         pa_usec_t r[2];
@@ -1087,16 +1378,14 @@ void pa_source_set_latency_range(pa_source *s, pa_usec_t min_latency, pa_usec_t
         r[1] = max_latency;
 
         pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_SET_LATENCY_RANGE, r, 0, NULL) == 0);
         r[1] = max_latency;
 
         pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_SET_LATENCY_RANGE, r, 0, NULL) == 0);
-    } else {
-        s->thread_info.min_latency = min_latency;
-        s->thread_info.max_latency = max_latency;
-
-        s->thread_info.requested_latency_valid = FALSE;
-    }
+    } else
+        pa_source_set_latency_range_within_thread(s, min_latency, max_latency);
 }
 
 }
 
+/* Called from main thread */
 void pa_source_get_latency_range(pa_source *s, pa_usec_t *min_latency, pa_usec_t *max_latency) {
    pa_source_assert_ref(s);
 void pa_source_get_latency_range(pa_source *s, pa_usec_t *min_latency, pa_usec_t *max_latency) {
    pa_source_assert_ref(s);
+   pa_assert_ctl_context();
    pa_assert(min_latency);
    pa_assert(max_latency);
 
    pa_assert(min_latency);
    pa_assert(max_latency);
 
@@ -1113,28 +1402,114 @@ void pa_source_get_latency_range(pa_source *s, pa_usec_t *min_latency, pa_usec_t
    }
 }
 
    }
 }
 
-/* Called from IO thread */
-void pa_source_update_latency_range(pa_source *s, pa_usec_t min_latency, pa_usec_t max_latency) {
-    pa_source_output *o;
-    void *state = NULL;
-
+/* Called from IO thread, and from main thread before pa_source_put() is called */
+void pa_source_set_latency_range_within_thread(pa_source *s, pa_usec_t min_latency, pa_usec_t max_latency) {
     pa_source_assert_ref(s);
     pa_source_assert_ref(s);
+    pa_source_assert_io_context(s);
 
 
-    pa_assert(!min_latency || !max_latency ||
-              min_latency <= max_latency);
+    pa_assert(min_latency >= ABSOLUTE_MIN_LATENCY);
+    pa_assert(max_latency <= ABSOLUTE_MAX_LATENCY);
+    pa_assert(min_latency <= max_latency);
+
+    /* Hmm, let's see if someone forgot to set PA_SOURCE_DYNAMIC_LATENCY here... */
+    pa_assert((min_latency == ABSOLUTE_MIN_LATENCY &&
+               max_latency == ABSOLUTE_MAX_LATENCY) ||
+              (s->flags & PA_SOURCE_DYNAMIC_LATENCY) ||
+              s->monitor_of);
+
+    if (s->thread_info.min_latency == min_latency &&
+        s->thread_info.max_latency == max_latency)
+        return;
 
     s->thread_info.min_latency = min_latency;
     s->thread_info.max_latency = max_latency;
 
 
     s->thread_info.min_latency = min_latency;
     s->thread_info.max_latency = max_latency;
 
-    while ((o = pa_hashmap_iterate(s->thread_info.outputs, &state, NULL)))
-        if (o->update_source_latency_range)
-            o->update_source_latency_range(o);
+    if (PA_SOURCE_IS_LINKED(s->thread_info.state)) {
+        pa_source_output *o;
+        void *state = NULL;
 
 
-    pa_source_invalidate_requested_latency(s);
+        PA_HASHMAP_FOREACH(o, s->thread_info.outputs, state)
+            if (o->update_source_latency_range)
+                o->update_source_latency_range(o);
+    }
+
+    pa_source_invalidate_requested_latency(s, FALSE);
+}
+
+/* Called from main thread, before the source is put */
+void pa_source_set_fixed_latency(pa_source *s, pa_usec_t latency) {
+    pa_source_assert_ref(s);
+    pa_assert_ctl_context();
+
+    if (s->flags & PA_SOURCE_DYNAMIC_LATENCY) {
+        pa_assert(latency == 0);
+        return;
+    }
+
+    if (latency < ABSOLUTE_MIN_LATENCY)
+        latency = ABSOLUTE_MIN_LATENCY;
+
+    if (latency > ABSOLUTE_MAX_LATENCY)
+        latency = ABSOLUTE_MAX_LATENCY;
+
+    if (PA_SOURCE_IS_LINKED(s->state))
+        pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_SET_FIXED_LATENCY, NULL, (int64_t) latency, NULL) == 0);
+    else
+        s->thread_info.fixed_latency = latency;
+}
+
+/* Called from main thread */
+pa_usec_t pa_source_get_fixed_latency(pa_source *s) {
+    pa_usec_t latency;
+
+    pa_source_assert_ref(s);
+    pa_assert_ctl_context();
+
+    if (s->flags & PA_SOURCE_DYNAMIC_LATENCY)
+        return 0;
+
+    if (PA_SOURCE_IS_LINKED(s->state))
+        pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_GET_FIXED_LATENCY, &latency, 0, NULL) == 0);
+    else
+        latency = s->thread_info.fixed_latency;
+
+    return latency;
+}
+
+/* Called from IO thread */
+void pa_source_set_fixed_latency_within_thread(pa_source *s, pa_usec_t latency) {
+    pa_source_assert_ref(s);
+    pa_source_assert_io_context(s);
+
+    if (s->flags & PA_SOURCE_DYNAMIC_LATENCY) {
+        pa_assert(latency == 0);
+        return;
+    }
+
+    pa_assert(latency >= ABSOLUTE_MIN_LATENCY);
+    pa_assert(latency <= ABSOLUTE_MAX_LATENCY);
+
+    if (s->thread_info.fixed_latency == latency)
+        return;
+
+    s->thread_info.fixed_latency = latency;
+
+    if (PA_SOURCE_IS_LINKED(s->thread_info.state)) {
+        pa_source_output *o;
+        void *state = NULL;
+
+        PA_HASHMAP_FOREACH(o, s->thread_info.outputs, state)
+            if (o->update_source_fixed_latency)
+                o->update_source_fixed_latency(o);
+    }
+
+    pa_source_invalidate_requested_latency(s, FALSE);
 }
 
 }
 
+/* Called from main thread */
 size_t pa_source_get_max_rewind(pa_source *s) {
     size_t r;
 size_t pa_source_get_max_rewind(pa_source *s) {
     size_t r;
+    pa_assert_ctl_context();
     pa_source_assert_ref(s);
 
     if (!PA_SOURCE_IS_LINKED(s->state))
     pa_source_assert_ref(s);
 
     if (!PA_SOURCE_IS_LINKED(s->state))
@@ -1144,3 +1519,39 @@ size_t pa_source_get_max_rewind(pa_source *s) {
 
     return r;
 }
 
     return r;
 }
+
+/* Called from main context */
+int pa_source_set_port(pa_source *s, const char *name, pa_bool_t save) {
+    pa_device_port *port;
+
+    pa_assert(s);
+    pa_assert_ctl_context();
+
+    if (!s->set_port) {
+        pa_log_debug("set_port() operation not implemented for source %u \"%s\"", s->index, s->name);
+        return -PA_ERR_NOTIMPLEMENTED;
+    }
+
+    if (!s->ports)
+        return -PA_ERR_NOENTITY;
+
+    if (!(port = pa_hashmap_get(s->ports, name)))
+        return -PA_ERR_NOENTITY;
+
+    if (s->active_port == port) {
+        s->save_port = s->save_port || save;
+        return 0;
+    }
+
+    if ((s->set_port(s, port)) < 0)
+        return -PA_ERR_NOENTITY;
+
+    pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+
+    pa_log_info("Changed port of source %u \"%s\" to %s", s->index, s->name, port->name);
+
+    s->active_port = port;
+    s->save_port = save;
+
+    return 0;
+}