]> code.delx.au - pulseaudio/blobdiff - src/pulsecore/sink.c
core: add a seperate fixed_latency field for sinks/sources with fixed latency
[pulseaudio] / src / pulsecore / sink.c
index 4f39d6715ec1c626db5cd7a27ac297ab667e8e3e..93800d14f2674acede6d58f2daa749c8621474ee 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
-  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
@@ -33,6 +33,7 @@
 #include <pulse/xmalloc.h>
 #include <pulse/timeval.h>
 #include <pulse/util.h>
+#include <pulse/i18n.h>
 
 #include <pulsecore/sink-input.h>
 #include <pulsecore/namereg.h>
@@ -47,7 +48,9 @@
 
 #define MAX_MIX_CHANNELS 32
 #define MIX_BUFFER_LENGTH (PA_PAGE_SIZE)
-#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_sink, pa_msgobject);
 
@@ -172,6 +175,9 @@ pa_sink* pa_sink_new(
     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, TRUE);
+
     if (pa_hook_fire(&core->hooks[PA_CORE_HOOK_SINK_FIXATE], data) < 0) {
         pa_xfree(s);
         pa_namereg_unregister(core, name);
@@ -203,6 +209,8 @@ pa_sink* pa_sink_new(
     s->muted = data->muted;
     s->refresh_volume = s->refresh_muted = FALSE;
 
+    s->fixed_latency = flags & PA_SINK_DYNAMIC_LATENCY ? 0 : DEFAULT_FIXED_LATENCY;
+
     reset_callbacks(s);
     s->userdata = NULL;
 
@@ -226,8 +234,8 @@ pa_sink* pa_sink_new(
     s->thread_info.max_request = 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;
 
     pa_assert_se(pa_idxset_put(core->sinks, s, &s->index) >= 0);
 
@@ -255,7 +263,7 @@ pa_sink* pa_sink_new(
     pa_proplist_setf(source_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Monitor of %s", dn ? dn : s->name);
     pa_proplist_sets(source_data.proplist, PA_PROP_DEVICE_CLASS, "monitor");
 
-    s->monitor_source = pa_source_new(core, &source_data, PA_SOURCE_LATENCY);
+    s->monitor_source = pa_source_new(core, &source_data, 0);
 
     pa_source_new_data_done(&source_data);
 
@@ -322,6 +330,9 @@ static int sink_set_state(pa_sink *s, pa_sink_state_t state) {
                 pa_sink_input_kill(i);
             else if (i->suspend)
                 i->suspend(i, state == PA_SINK_SUSPENDED);
+
+        if (s->monitor_source)
+            pa_source_sync_suspend(s->monitor_source);
     }
 
     return 0;
@@ -336,8 +347,7 @@ void pa_sink_put(pa_sink* s) {
     /* 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);
 
     if (!(s->flags & PA_SINK_HW_VOLUME_CTRL)) {
         s->flags |= PA_SINK_DECIBEL_VOLUME;
@@ -353,6 +363,17 @@ void pa_sink_put(pa_sink* s) {
         if (s->flags & PA_SINK_DECIBEL_VOLUME)
             s->flags |= PA_SINK_FLAT_VOLUME;
 
+    if (s->flags & PA_SINK_LATENCY)
+        s->monitor_source->flags |= PA_SOURCE_LATENCY;
+
+    if (s->flags & PA_SINK_DYNAMIC_LATENCY) {
+        s->monitor_source->flags |= PA_SOURCE_DYNAMIC_LATENCY;
+        s->fixed_latency = 0;
+    } else if (s->fixed_latency <= 0)
+        s->fixed_latency = DEFAULT_FIXED_LATENCY;
+
+    s->monitor_source->fixed_latency = s->fixed_latency;
+
     pa_assert_se(sink_set_state(s, PA_SINK_IDLE) == 0);
 
     pa_source_put(s->monitor_source);
@@ -502,8 +523,12 @@ pa_queue *pa_sink_move_all_start(pa_sink *s) {
     for (i = PA_SINK_INPUT(pa_idxset_first(s->inputs, &idx)); i; i = n) {
         n = PA_SINK_INPUT(pa_idxset_next(s->inputs, &idx));
 
+        pa_sink_input_ref(i);
+
         if (pa_sink_input_start_move(i) >= 0)
-            pa_queue_push(q, pa_sink_input_ref(i));
+            pa_queue_push(q, i);
+        else
+            pa_sink_input_unref(i);
     }
 
     return q;
@@ -945,6 +970,72 @@ pa_usec_t pa_sink_get_latency(pa_sink *s) {
     return usec;
 }
 
+/* Called from IO thread */
+pa_usec_t pa_sink_get_latency_within_thread(pa_sink *s) {
+    pa_usec_t usec = 0;
+    pa_msgobject *o;
+
+    pa_sink_assert_ref(s);
+    pa_assert(PA_SINK_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_SINK_SUSPENDED)
+        return 0;
+
+    if (!(s->flags & PA_SINK_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_SINK_MESSAGE_GET_LATENCY, &usec, 0, NULL) < 0)
+        return -1;
+
+    return usec;
+}
+
+static void compute_new_soft_volume(pa_sink_input *i, const pa_cvolume *new_volume) {
+    unsigned c;
+
+    pa_sink_input_assert_ref(i);
+    pa_assert(new_volume->channels == i->sample_spec.channels);
+
+    /*
+     * This basically calculates:
+     *
+     * i->relative_volume := i->virtual_volume / new_volume
+     * i->soft_volume := i->relative_volume * i->volume_factor
+     */
+
+    /* The new sink volume passed in here must already be remapped to
+     * the sink input's channel map! */
+
+    i->soft_volume.channels = i->sample_spec.channels;
+
+    for (c = 0; c < i->sample_spec.channels; c++)
+
+        if (new_volume->values[c] <= PA_VOLUME_MUTED)
+            /* We leave i->relative_volume untouched */
+            i->soft_volume.values[c] = PA_VOLUME_MUTED;
+        else {
+            i->relative_volume[c] =
+                pa_sw_volume_to_linear(i->virtual_volume.values[c]) /
+                pa_sw_volume_to_linear(new_volume->values[c]);
+
+            i->soft_volume.values[c] = pa_sw_volume_from_linear(
+                    i->relative_volume[c] *
+                    pa_sw_volume_to_linear(i->volume_factor.values[c]));
+        }
+
+    /* Hooks have the ability to play games with i->soft_volume */
+    pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_SET_VOLUME], i);
+
+    /* We don't copy the soft_volume to the thread_info data
+     * here. That must be done by the caller */
+}
+
 /* Called from main thread */
 void pa_sink_update_flat_volume(pa_sink *s, pa_cvolume *new_volume) {
     pa_sink_input *i;
@@ -959,7 +1050,7 @@ void pa_sink_update_flat_volume(pa_sink *s, pa_cvolume *new_volume) {
      * might need to fix up the sink volume accordingly. Please note
      * that we don't actually update the sinks volume here, we only
      * return how it needs to be updated. The caller should then call
-     * pa_sink_set_flat_volume().*/
+     * pa_sink_set_volume().*/
 
     if (pa_idxset_isempty(s->inputs)) {
         /* In the special case that we have no sink input we leave the
@@ -991,26 +1082,22 @@ void pa_sink_update_flat_volume(pa_sink *s, pa_cvolume *new_volume) {
 
         remapped_new_volume = *new_volume;
         pa_cvolume_remap(&remapped_new_volume, &s->channel_map, &i->channel_map);
-        pa_sw_cvolume_divide(&i->soft_volume, &i->virtual_volume, &remapped_new_volume);
-        pa_sw_cvolume_multiply(&i->soft_volume, &i->soft_volume, &i->volume_factor);
-
-        /* Hooks have the ability to play games with i->soft_volume */
-        pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_INPUT_SET_VOLUME], i);
+        compute_new_soft_volume(i, &remapped_new_volume);
 
-        /* We don't issue PA_SINK_INPUT_MESSAGE_SET_VOLUME because
-         * we want the update to have atomically with the sink
-         * volume update, hence we do it within the
-         * pa_sink_set_flat_volume() call below*/
+        /* We don't copy soft_volume to the thread_info data here
+         * (i.e. issue PA_SINK_INPUT_MESSAGE_SET_VOLUME) because we
+         * want the update to be atomically with the sink volume
+         * update, hence we do it within the pa_sink_set_volume() call
+         * below */
     }
 }
 
 /* Called from main thread */
-void pa_sink_propagate_flat_volume(pa_sink *s, const pa_cvolume *old_volume) {
+void pa_sink_propagate_flat_volume(pa_sink *s) {
     pa_sink_input *i;
     uint32_t idx;
 
     pa_sink_assert_ref(s);
-    pa_assert(old_volume);
     pa_assert(PA_SINK_IS_LINKED(s->state));
     pa_assert(s->flags & PA_SINK_FLAT_VOLUME);
 
@@ -1019,39 +1106,43 @@ void pa_sink_propagate_flat_volume(pa_sink *s, const pa_cvolume *old_volume) {
      * sink input volumes accordingly */
 
     for (i = PA_SINK_INPUT(pa_idxset_first(s->inputs, &idx)); i; i = PA_SINK_INPUT(pa_idxset_next(s->inputs, &idx))) {
-        pa_cvolume remapped_old_volume, remapped_new_volume, fixed_volume;
+        pa_cvolume sink_volume, new_virtual_volume;
         unsigned c;
 
-        remapped_new_volume = s->virtual_volume;
-        pa_cvolume_remap(&remapped_new_volume, &s->channel_map, &i->channel_map);
+        /* This basically calculates i->virtual_volume := i->relative_volume * s->virtual_volume  */
 
-        remapped_old_volume = *old_volume;
-        pa_cvolume_remap(&remapped_old_volume, &s->channel_map, &i->channel_map);
+        sink_volume = s->virtual_volume;
+        pa_cvolume_remap(&sink_volume, &s->channel_map, &i->channel_map);
 
         for (c = 0; c < i->sample_spec.channels; c++)
+            new_virtual_volume.values[c] = pa_sw_volume_from_linear(
+                    i->relative_volume[c] *
+                    pa_sw_volume_to_linear(sink_volume.values[c]));
 
-            if (remapped_old_volume.values[c] == PA_VOLUME_MUTED)
-                fixed_volume.values[c] = PA_VOLUME_MUTED;
-            else
-                fixed_volume.values[c] = (pa_volume_t)
-                    ((uint64_t) i->virtual_volume.values[c] *
-                     (uint64_t) remapped_new_volume.values[c] /
-                     (uint64_t) remapped_old_volume.values[c]);
+        new_virtual_volume.channels = i->sample_spec.channels;
 
-        fixed_volume.channels = i->virtual_volume.channels;
+        if (!pa_cvolume_equal(&new_virtual_volume, &i->virtual_volume)) {
+            i->virtual_volume = new_virtual_volume;
 
-        if (!pa_cvolume_equal(&fixed_volume, &i->virtual_volume)) {
-            i->virtual_volume = fixed_volume;
+            /* Hmm, the soft volume might no longer actually match
+             * what has been chosen as new virtual volume here,
+             * especially when the old volume was
+             * PA_VOLUME_MUTED. Hence let's recalculate the soft
+             * volumes here. */
+            compute_new_soft_volume(i, &sink_volume);
 
             /* The virtual volume changed, let's tell people so */
             pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index);
         }
     }
+
+    /* If the soft_volume of any of the sink inputs got changed, let's
+     * make sure the thread copies are synced up. */
+    pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SYNC_VOLUMES, NULL, 0, NULL) == 0);
 }
 
 /* Called from main thread */
 void pa_sink_set_volume(pa_sink *s, const pa_cvolume *volume, pa_bool_t propagate, pa_bool_t sendmsg) {
-    pa_cvolume old_virtual_volume;
     pa_bool_t virtual_volume_changed;
 
     pa_sink_assert_ref(s);
@@ -1060,14 +1151,13 @@ void pa_sink_set_volume(pa_sink *s, const pa_cvolume *volume, pa_bool_t propagat
     pa_assert(pa_cvolume_valid(volume));
     pa_assert(pa_cvolume_compatible(volume, &s->sample_spec));
 
-    old_virtual_volume = s->virtual_volume;
+    virtual_volume_changed = !pa_cvolume_equal(volume, &s->virtual_volume);
     s->virtual_volume = *volume;
-    virtual_volume_changed = !pa_cvolume_equal(&old_virtual_volume, &s->virtual_volume);
 
     /* Propagate this volume change back to the inputs */
     if (virtual_volume_changed)
         if (propagate && (s->flags & PA_SINK_FLAT_VOLUME))
-            pa_sink_propagate_flat_volume(s, &old_virtual_volume);
+            pa_sink_propagate_flat_volume(s);
 
     if (s->set_volume) {
         /* If we have a function set_volume(), then we do not apply a
@@ -1118,7 +1208,7 @@ const pa_cvolume *pa_sink_get_volume(pa_sink *s, pa_bool_t force_refresh) {
         if (!pa_cvolume_equal(&old_virtual_volume, &s->virtual_volume)) {
 
             if (s->flags & PA_SINK_FLAT_VOLUME)
-                pa_sink_propagate_flat_volume(s, &old_virtual_volume);
+                pa_sink_propagate_flat_volume(s);
 
             pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
         }
@@ -1127,6 +1217,19 @@ const pa_cvolume *pa_sink_get_volume(pa_sink *s, pa_bool_t force_refresh) {
     return &s->virtual_volume;
 }
 
+/* Called from main thread */
+void pa_sink_volume_changed(pa_sink *s, const pa_cvolume *new_volume) {
+    pa_sink_assert_ref(s);
+
+    /* The sink implementor may call this if the volume changed to make sure everyone is notified */
+
+    if (pa_cvolume_equal(&s->virtual_volume, new_volume))
+        return;
+
+    s->virtual_volume = *new_volume;
+    pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+}
+
 /* Called from main thread */
 void pa_sink_set_mute(pa_sink *s, pa_bool_t mute) {
     pa_bool_t old_muted;
@@ -1167,12 +1270,24 @@ pa_bool_t pa_sink_get_mute(pa_sink *s, pa_bool_t force_refresh) {
 }
 
 /* Called from main thread */
-pa_bool_t pa_sink_update_proplist(pa_sink *s, pa_update_mode_t mode, pa_proplist *p) {
+void pa_sink_mute_changed(pa_sink *s, pa_bool_t new_muted) {
+    pa_sink_assert_ref(s);
+
+    /* The sink implementor may call this if the volume changed to make sure everyone is notified */
 
+    if (s->muted == new_muted)
+        return;
+
+    s->muted = new_muted;
+    pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+}
+
+/* Called from main thread */
+pa_bool_t pa_sink_update_proplist(pa_sink *s, pa_update_mode_t mode, pa_proplist *p) {
     pa_sink_assert_ref(s);
-    pa_assert(p);
 
-    pa_proplist_update(s->proplist, mode, p);
+    if (p)
+        pa_proplist_update(s->proplist, mode, p);
 
     if (PA_SINK_IS_LINKED(s->state)) {
         pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_PROPLIST_CHANGED], s);
@@ -1501,9 +1616,13 @@ int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offse
                 pa_sink_request_rewind(s, (size_t) -1);
             }
 
-            if (s->flags & PA_SINK_FLAT_VOLUME)
-                sync_input_volumes_within_thread(s);
+            if (!(s->flags & PA_SINK_FLAT_VOLUME))
+                return 0;
 
+            /* Fall through ... */
+
+        case PA_SINK_MESSAGE_SYNC_VOLUMES:
+            sync_input_volumes_within_thread(s);
             return 0;
 
         case PA_SINK_MESSAGE_GET_VOLUME:
@@ -1521,14 +1640,30 @@ int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offse
         case PA_SINK_MESSAGE_GET_MUTE:
             return 0;
 
-        case PA_SINK_MESSAGE_SET_STATE:
+        case PA_SINK_MESSAGE_SET_STATE: {
+
+            pa_bool_t suspend_change =
+                (s->thread_info.state == PA_SINK_SUSPENDED && PA_SINK_IS_OPENED(PA_PTR_TO_UINT(userdata))) ||
+                (PA_SINK_IS_OPENED(s->thread_info.state) && PA_PTR_TO_UINT(userdata) == PA_SINK_SUSPENDED);
 
             s->thread_info.state = PA_PTR_TO_UINT(userdata);
 
-            if (s->thread_info.state == PA_SINK_SUSPENDED)
+            if (s->thread_info.state == PA_SINK_SUSPENDED) {
+                s->thread_info.rewind_nbytes = 0;
                 s->thread_info.rewind_requested = FALSE;
+            }
+
+            if (suspend_change) {
+                pa_sink_input *i;
+                void *state = NULL;
+
+                while ((i = pa_hashmap_iterate(s->thread_info.inputs, &state, NULL)))
+                    if (i->suspend_within_thread)
+                        i->suspend_within_thread(i, s->thread_info.state == PA_SINK_SUSPENDED);
+            }
 
             return 0;
+        }
 
         case PA_SINK_MESSAGE_DETACH:
 
@@ -1556,7 +1691,7 @@ int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offse
         case PA_SINK_MESSAGE_SET_LATENCY_RANGE: {
             pa_usec_t *r = userdata;
 
-            pa_sink_update_latency_range(s, r[0], r[1]);
+            pa_sink_set_latency_range_within_thread(s, r[0], r[1]);
 
             return 0;
         }
@@ -1580,6 +1715,16 @@ int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offse
             *((size_t*) userdata) = s->thread_info.max_request;
             return 0;
 
+        case PA_SINK_MESSAGE_SET_MAX_REWIND:
+
+            pa_sink_set_max_rewind_within_thread(s, (size_t) offset);
+            return 0;
+
+        case PA_SINK_MESSAGE_SET_MAX_REQUEST:
+
+            pa_sink_set_max_request_within_thread(s, (size_t) offset);
+            return 0;
+
         case PA_SINK_MESSAGE_GET_LATENCY:
         case PA_SINK_MESSAGE_MAX:
             ;
@@ -1596,8 +1741,12 @@ int pa_sink_suspend_all(pa_core *c, pa_bool_t suspend) {
 
     pa_core_assert_ref(c);
 
-    for (sink = PA_SINK(pa_idxset_first(c->sinks, &idx)); sink; sink = PA_SINK(pa_idxset_next(c->sinks, &idx)))
-        ret -= pa_sink_suspend(sink, suspend) < 0;
+    for (sink = PA_SINK(pa_idxset_first(c->sinks, &idx)); sink; sink = PA_SINK(pa_idxset_next(c->sinks, &idx))) {
+        int r;
+
+        if ((r = pa_sink_suspend(sink, suspend)) < 0)
+            ret = r;
+    }
 
     return ret;
 }
@@ -1683,6 +1832,9 @@ pa_usec_t pa_sink_get_requested_latency_within_thread(pa_sink *s) {
 
     pa_sink_assert_ref(s);
 
+    if (!(s->flags & PA_SINK_DYNAMIC_LATENCY))
+        return PA_CLAMP(s->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;
 
@@ -1698,13 +1850,8 @@ pa_usec_t pa_sink_get_requested_latency_within_thread(pa_sink *s) {
         (result == (pa_usec_t) -1 || result > monitor_latency))
         result = monitor_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 (s->thread_info.min_latency > 0 && result < s->thread_info.min_latency)
-            result = s->thread_info.min_latency;
-    }
+    if (result != (pa_usec_t) -1)
+        result = PA_CLAMP(result, s->thread_info.min_latency, s->thread_info.max_latency);
 
     s->thread_info.requested_latency = result;
     s->thread_info.requested_latency_valid = TRUE;
@@ -1726,8 +1873,8 @@ pa_usec_t pa_sink_get_requested_latency(pa_sink *s) {
     return usec;
 }
 
-/* Called from IO thread */
-void pa_sink_set_max_rewind(pa_sink *s, size_t max_rewind) {
+/* Called from IO as well as the main thread -- the latter only before the IO thread started up */
+void pa_sink_set_max_rewind_within_thread(pa_sink *s, size_t max_rewind) {
     pa_sink_input *i;
     void *state = NULL;
 
@@ -1744,11 +1891,21 @@ void pa_sink_set_max_rewind(pa_sink *s, size_t max_rewind) {
     }
 
     if (s->monitor_source)
-        pa_source_set_max_rewind(s->monitor_source, s->thread_info.max_rewind);
+        pa_source_set_max_rewind_within_thread(s->monitor_source, s->thread_info.max_rewind);
 }
 
-/* Called from IO thread */
-void pa_sink_set_max_request(pa_sink *s, size_t max_request) {
+/* Called from main thread */
+void pa_sink_set_max_rewind(pa_sink *s, size_t max_rewind) {
+    pa_sink_assert_ref(s);
+
+    if (PA_SINK_IS_LINKED(s->state))
+        pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_MAX_REWIND, NULL, max_rewind, NULL) == 0);
+    else
+        pa_sink_set_max_rewind_within_thread(s, max_rewind);
+}
+
+/* Called from IO as well as the main thread -- the latter only before the IO thread started up */
+void pa_sink_set_max_request_within_thread(pa_sink *s, size_t max_request) {
     void *state = NULL;
 
     pa_sink_assert_ref(s);
@@ -1766,6 +1923,16 @@ void pa_sink_set_max_request(pa_sink *s, size_t max_request) {
     }
 }
 
+/* Called from main thread */
+void pa_sink_set_max_request(pa_sink *s, size_t max_request) {
+    pa_sink_assert_ref(s);
+
+    if (PA_SINK_IS_LINKED(s->state))
+        pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_MAX_REQUEST, NULL, max_request, NULL) == 0);
+    else
+        pa_sink_set_max_request_within_thread(s, max_request);
+}
+
 /* Called from IO thread */
 void pa_sink_invalidate_requested_latency(pa_sink *s) {
     pa_sink_input *i;
@@ -1773,14 +1940,20 @@ void pa_sink_invalidate_requested_latency(pa_sink *s) {
 
     pa_sink_assert_ref(s);
 
+    if (!(s->flags & PA_SINK_DYNAMIC_LATENCY))
+        return;
+
     s->thread_info.requested_latency_valid = FALSE;
 
-    if (s->update_requested_latency)
-        s->update_requested_latency(s);
+    if (PA_SINK_IS_LINKED(s->thread_info.state)) {
 
-    while ((i = pa_hashmap_iterate(s->thread_info.inputs, &state, NULL)))
-        if (i->update_sink_requested_latency)
-            i->update_sink_requested_latency(i);
+        if (s->update_requested_latency)
+            s->update_requested_latency(s);
+
+        while ((i = pa_hashmap_iterate(s->thread_info.inputs, &state, NULL)))
+            if (i->update_sink_requested_latency)
+                i->update_sink_requested_latency(i);
+    }
 }
 
 /* Called from main thread */
@@ -1788,19 +1961,23 @@ void pa_sink_set_latency_range(pa_sink *s, pa_usec_t min_latency, pa_usec_t max_
     pa_sink_assert_ref(s);
 
     /* min_latency == 0:           no limit
-     * min_latency == (size_t) -1: default limit
      * 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 == (pa_usec_t) -1)
-        max_latency = min_latency;
+    if (max_latency <= 0 ||
+        max_latency > ABSOLUTE_MAX_LATENCY)
+        max_latency = ABSOLUTE_MAX_LATENCY;
 
-    pa_assert(!min_latency || !max_latency ||
-              min_latency <= max_latency);
+    pa_assert(min_latency <= max_latency);
+
+    /* Hmm, let's see if someone forgot to set PA_SINK_DYNAMIC_LATENCY here... */
+    pa_assert((min_latency == ABSOLUTE_MIN_LATENCY &&
+               max_latency == ABSOLUTE_MAX_LATENCY) ||
+              (s->flags & PA_SINK_DYNAMIC_LATENCY));
 
     if (PA_SINK_IS_LINKED(s->state)) {
         pa_usec_t r[2];
@@ -1809,15 +1986,8 @@ void pa_sink_set_latency_range(pa_sink *s, pa_usec_t min_latency, pa_usec_t max_
         r[1] = max_latency;
 
         pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_LATENCY_RANGE, r, 0, NULL) == 0);
-    } else {
-        s->thread_info.min_latency = min_latency;
-        s->thread_info.max_latency = max_latency;
-
-        s->monitor_source->thread_info.min_latency = min_latency;
-        s->monitor_source->thread_info.max_latency = max_latency;
-
-        s->thread_info.requested_latency_valid = s->monitor_source->thread_info.requested_latency_valid = FALSE;
-    }
+    } else
+        pa_sink_set_latency_range_within_thread(s, min_latency, max_latency);
 }
 
 /* Called from main thread */
@@ -1840,22 +2010,34 @@ void pa_sink_get_latency_range(pa_sink *s, pa_usec_t *min_latency, pa_usec_t *ma
 }
 
 /* Called from IO thread */
-void pa_sink_update_latency_range(pa_sink *s, pa_usec_t min_latency, pa_usec_t max_latency) {
-    pa_sink_input *i;
+void pa_sink_set_latency_range_within_thread(pa_sink *s, pa_usec_t min_latency, pa_usec_t max_latency) {
     void *state = NULL;
 
     pa_sink_assert_ref(s);
 
+    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_SINK_DYNAMIC_LATENCY here... */
+    pa_assert((min_latency == ABSOLUTE_MIN_LATENCY &&
+               max_latency == ABSOLUTE_MAX_LATENCY) ||
+              (s->flags & PA_SINK_DYNAMIC_LATENCY));
+
     s->thread_info.min_latency = min_latency;
     s->thread_info.max_latency = max_latency;
 
-    while ((i = pa_hashmap_iterate(s->thread_info.inputs, &state, NULL)))
-        if (i->update_sink_latency_range)
-            i->update_sink_latency_range(i);
+    if (PA_SINK_IS_LINKED(s->thread_info.state)) {
+        pa_sink_input *i;
+
+        while ((i = pa_hashmap_iterate(s->thread_info.inputs, &state, NULL)))
+            if (i->update_sink_latency_range)
+                i->update_sink_latency_range(i);
+    }
 
     pa_sink_invalidate_requested_latency(s);
 
-    pa_source_update_latency_range(s->monitor_source, min_latency, max_latency);
+    pa_source_set_latency_range_within_thread(s->monitor_source, min_latency, max_latency);
 }
 
 /* Called from main context */
@@ -1883,3 +2065,83 @@ size_t pa_sink_get_max_request(pa_sink *s) {
 
     return r;
 }
+
+/* Called from main context */
+pa_bool_t pa_device_init_icon(pa_proplist *p, pa_bool_t is_sink) {
+    const char *ff, *c, *t = NULL, *s = "", *profile, *bus;
+
+    pa_assert(p);
+
+    if (pa_proplist_contains(p, PA_PROP_DEVICE_ICON_NAME))
+        return TRUE;
+
+    if ((ff = pa_proplist_gets(p, PA_PROP_DEVICE_FORM_FACTOR))) {
+
+        if (pa_streq(ff, "microphone"))
+            t = "audio-input-microphone";
+        else if (pa_streq(ff, "webcam"))
+            t = "camera-web";
+        else if (pa_streq(ff, "computer"))
+            t = "computer";
+        else if (pa_streq(ff, "handset"))
+            t = "phone";
+        else if (pa_streq(ff, "portable"))
+            t = "multimedia-player";
+        else if (pa_streq(ff, "tv"))
+            t = "video-display";
+    }
+
+    if (!t)
+        if ((c = pa_proplist_gets(p, PA_PROP_DEVICE_CLASS)))
+            if (pa_streq(c, "modem"))
+                t = "modem";
+
+    if (!t) {
+        if (is_sink)
+            t = "audio-card";
+        else
+            t = "audio-input-microphone";
+    }
+
+    if ((profile = pa_proplist_gets(p, PA_PROP_DEVICE_PROFILE_NAME))) {
+        if (strstr(profile, "analog"))
+            s = "-analog";
+        else if (strstr(profile, "iec958"))
+            s = "-iec958";
+        else if (strstr(profile, "hdmi"))
+            s = "-hdmi";
+    }
+
+    bus = pa_proplist_gets(p, PA_PROP_DEVICE_BUS);
+
+    pa_proplist_setf(p, PA_PROP_DEVICE_ICON_NAME, "%s%s%s%s", t, pa_strempty(s), bus ? "-" : "", pa_strempty(bus));
+
+    return TRUE;
+}
+
+pa_bool_t pa_device_init_description(pa_proplist *p) {
+    const char *s;
+    pa_assert(p);
+
+    if (pa_proplist_contains(p, PA_PROP_DEVICE_DESCRIPTION))
+        return TRUE;
+
+    if ((s = pa_proplist_gets(p, PA_PROP_DEVICE_FORM_FACTOR)))
+        if (pa_streq(s, "internal")) {
+            pa_proplist_sets(p, PA_PROP_DEVICE_DESCRIPTION, _("Internal Audio"));
+            return TRUE;
+        }
+
+    if ((s = pa_proplist_gets(p, PA_PROP_DEVICE_CLASS)))
+        if (pa_streq(s, "modem")) {
+            pa_proplist_sets(p, PA_PROP_DEVICE_DESCRIPTION, _("Modem"));
+            return TRUE;
+        }
+
+    if ((s = pa_proplist_gets(p, PA_PROP_DEVICE_PRODUCT_NAME))) {
+        pa_proplist_sets(p, PA_PROP_DEVICE_DESCRIPTION, s);
+        return TRUE;
+    }
+
+    return FALSE;
+}