[pulseaudio-commits] [SCM] PulseAudio Sound Server branch, master, updated. v0.9.19-594-g13278bf

Colin Guthrie gitmailer-noreply at 0pointer.de
Sat Oct 16 04:32:34 PDT 2010


This is an automated email from the git hooks/post-receive script. It was
generated because of a push to the "PulseAudio Sound Server" repository.

The master branch has been updated
      from  179b291b18b9a7366955948ce0ddfb2e65a6b66e (commit)

- Log -----------------------------------------------------------------
13278bf intended-roles: Mark devices with a form factor of 'headset' as being appropriate for 'phone' streams
8aa332c alsa-mixer: add profile for Native Instruments Korecontroller
58bdd97 man: sync_volume parameters to manual page
3e53e3b daemon-conf: Add sync volume parameters to daemon-conf
43b3f39 udev-detect: Add sync_volume parameter
1bea955 alsa: Take syncronized HW volume infra into use for alsa-sink
5391daf core: Add infrastructure for synchronizing HW and SW volume changes
-----------------------------------------------------------------------

Summary of changes:
 man/pulse-daemon.conf.5.xml.in                     |   30 ++
 src/Makefile.am                                    |    3 +-
 src/daemon/daemon-conf.c                           |    9 +
 src/daemon/daemon-conf.h                           |    5 +-
 src/daemon/daemon.conf.in                          |    4 +
 src/daemon/main.c                                  |    3 +
 src/modules/alsa/alsa-mixer.c                      |  117 +++++++-
 src/modules/alsa/alsa-mixer.h                      |    9 +-
 src/modules/alsa/alsa-sink.c                       |  130 ++++++++-
 src/modules/alsa/alsa-source.c                     |    2 +-
 .../alsa/mixer/profile-sets/90-pulseaudio.rules    |    1 +
 .../native-instruments-korecontroller.conf         |   85 ++++++
 src/modules/alsa/module-alsa-card.c                |    4 +-
 src/modules/alsa/module-alsa-sink.c                |   10 +-
 src/modules/module-udev-detect.c                   |   18 +-
 src/pulse/def.h                                    |    7 +-
 src/pulsecore/core.c                               |    4 +
 src/pulsecore/core.h                               |    3 +
 src/pulsecore/sink.c                               |  297 ++++++++++++++++++--
 src/pulsecore/sink.h                               |   97 ++++++-
 20 files changed, 776 insertions(+), 62 deletions(-)
 create mode 100644 src/modules/alsa/mixer/profile-sets/native-instruments-korecontroller.conf

-----------------------------------------------------------------------

commit 5391daf8dfcae45c6abb74c0a5ff9b7c50e3c000
Author: Jyri Sarha <jyri.sarha at nokia.com>
Date:   Fri Oct 15 13:05:14 2010 +0300

    core: Add infrastructure for synchronizing HW and SW volume changes
    
    To make concurrent use of SW and HW volume glitchles their application
    needs to be synchronized. For accurate synchronization the HW volume
    needs to be applied in IO thread. This patch adds infrastructure to
    delay the applying of HW volume to match with SW volume timing. To
    avoid synchronization problems this patch moves many of the volume and
    mute related functions from main thread to IO thread. All these
    changes become active only if the sync volume flag for a sink has been
    set. So, for this patch to have any effect it needs to be taken into
    use by sink implementor.
    
    Signed-off-by: Jyri Sarha <jyri.sarha at nokia.com>
    Reviewed-by: Tanu Kaskinen <tanu.kaskinen at digia.com>
    Reviewd-by: Colin Guthrie <cguthrie at mandriva.org>

diff --git a/src/pulse/def.h b/src/pulse/def.h
index 80d2a50..a73e93e 100644
--- a/src/pulse/def.h
+++ b/src/pulse/def.h
@@ -740,11 +740,15 @@ typedef enum pa_sink_flags {
     /**< The latency can be adjusted dynamically depending on the
      * needs of the connected streams. \since 0.9.15 */
 
-    PA_SINK_PASSTHROUGH = 0x0100U
+    PA_SINK_PASSTHROUGH = 0x0100U,
     /**< This sink has support for passthrough mode. The data will be left
      * as is and not reformatted, resampled, mixed.
      * \since 0.9.22*/
 
+    PA_SINK_SYNC_VOLUME = 0x0200U,
+    /**< The HW volume changes are syncronized with SW volume.
+     * \since 0.9.22 */
+
 } pa_sink_flags_t;
 
 /** \cond fulldocs */
@@ -757,6 +761,7 @@ typedef enum pa_sink_flags {
 #define PA_SINK_FLAT_VOLUME PA_SINK_FLAT_VOLUME
 #define PA_SINK_DYNAMIC_LATENCY PA_SINK_DYNAMIC_LATENCY
 #define PA_SINK_PASSTHROUGH PA_SINK_PASSTHROUGH
+#define PA_SINK_SYNC_VOLUME PA_SINK_SYNC_VOLUME
 
 /** \endcond */
 
diff --git a/src/pulsecore/sink.c b/src/pulsecore/sink.c
index ff4cc17..a42a147 100644
--- a/src/pulsecore/sink.c
+++ b/src/pulsecore/sink.c
@@ -34,6 +34,7 @@
 #include <pulse/timeval.h>
 #include <pulse/util.h>
 #include <pulse/i18n.h>
+#include <pulse/rtclock.h>
 
 #include <pulsecore/sink-input.h>
 #include <pulsecore/namereg.h>
@@ -43,6 +44,7 @@
 #include <pulsecore/log.h>
 #include <pulsecore/macro.h>
 #include <pulsecore/play-memblockq.h>
+#include <pulsecore/flist.h>
 
 #include "sink.h"
 
@@ -51,11 +53,29 @@
 #define ABSOLUTE_MIN_LATENCY (500)
 #define ABSOLUTE_MAX_LATENCY (10*PA_USEC_PER_SEC)
 #define DEFAULT_FIXED_LATENCY (250*PA_USEC_PER_MSEC)
+#define VOLUME_CHANGE_SAFETY_MARGIN_DEFAULT (8*PA_USEC_PER_MSEC)
+#define VOLUME_CHANGE_EXTRA_DELAY_DEFAULT (0*PA_USEC_PER_MSEC)
 
 PA_DEFINE_PUBLIC_CLASS(pa_sink, pa_msgobject);
 
+struct pa_sink_volume_change {
+    pa_usec_t at;
+    pa_cvolume hw_volume;
+
+    PA_LLIST_FIELDS(pa_sink_volume_change);
+};
+
+struct sink_message_set_port {
+    pa_device_port *port;
+    int ret;
+};
+
 static void sink_free(pa_object *s);
 
+static void pa_sink_volume_change_push(pa_sink *s);
+static void pa_sink_volume_change_flush(pa_sink *s);
+static void pa_sink_volume_change_rewind(pa_sink *s, size_t nbytes);
+
 pa_sink_new_data* pa_sink_new_data_init(pa_sink_new_data *data) {
     pa_assert(data);
 
@@ -310,6 +330,12 @@ pa_sink* pa_sink_new(
     s->thread_info.max_latency = ABSOLUTE_MAX_LATENCY;
     s->thread_info.fixed_latency = flags & PA_SINK_DYNAMIC_LATENCY ? 0 : DEFAULT_FIXED_LATENCY;
 
+    PA_LLIST_HEAD_INIT(pa_sink_volume_change, s->thread_info.volume_changes);
+    s->thread_info.volume_changes_tail = NULL;
+    pa_sw_cvolume_multiply(&s->thread_info.current_hw_volume, &s->soft_volume, &s->real_volume);
+    s->thread_info.volume_change_safety_margin = VOLUME_CHANGE_SAFETY_MARGIN_DEFAULT;
+    s->thread_info.volume_change_extra_delay = VOLUME_CHANGE_EXTRA_DELAY_DEFAULT;
+
     /* FIXME: This should probably be moved to pa_sink_put() */
     pa_assert_se(pa_idxset_put(core->sinks, s, &s->index) >= 0);
 
@@ -444,12 +470,17 @@ void pa_sink_put(pa_sink* s) {
 
     s->thread_info.soft_volume = s->soft_volume;
     s->thread_info.soft_muted = s->muted;
+    pa_sw_cvolume_multiply(&s->thread_info.current_hw_volume, &s->soft_volume, &s->real_volume);
 
     pa_assert((s->flags & PA_SINK_HW_VOLUME_CTRL) || (s->base_volume == PA_VOLUME_NORM && s->flags & PA_SINK_DECIBEL_VOLUME));
     pa_assert(!(s->flags & PA_SINK_DECIBEL_VOLUME) || s->n_volume_steps == PA_VOLUME_NORM+1);
     pa_assert(!(s->flags & PA_SINK_DYNAMIC_LATENCY) == (s->thread_info.fixed_latency != 0));
     pa_assert(!(s->flags & PA_SINK_LATENCY) == !(s->monitor_source->flags & PA_SOURCE_LATENCY));
     pa_assert(!(s->flags & PA_SINK_DYNAMIC_LATENCY) == !(s->monitor_source->flags & PA_SOURCE_DYNAMIC_LATENCY));
+    pa_assert(!(s->flags & PA_SINK_HW_VOLUME_CTRL) || s->set_volume);
+    pa_assert(!(s->flags & PA_SINK_SYNC_VOLUME) || (s->flags & PA_SINK_HW_VOLUME_CTRL));
+    pa_assert(!(s->flags & PA_SINK_SYNC_VOLUME) || s->write_volume);
+    pa_assert(!(s->flags & PA_SINK_HW_MUTE_CTRL) || s->set_mute);
 
     pa_assert(s->monitor_source->thread_info.fixed_latency == s->thread_info.fixed_latency);
     pa_assert(s->monitor_source->thread_info.min_latency == s->thread_info.min_latency);
@@ -730,9 +761,12 @@ void pa_sink_process_rewind(pa_sink *s, size_t nbytes) {
         pa_sink_input_process_rewind(i, nbytes);
     }
 
-    if (nbytes > 0)
+    if (nbytes > 0) {
         if (s->monitor_source && PA_SOURCE_IS_LINKED(s->monitor_source->thread_info.state))
             pa_source_process_rewind(s->monitor_source, nbytes);
+        if (s->flags & PA_SINK_SYNC_VOLUME)
+            pa_sink_volume_change_rewind(s, nbytes);
+    }
 }
 
 /* Called from IO thread context */
@@ -1459,7 +1493,10 @@ void pa_sink_set_volume(
          * apply one to s->soft_volume */
 
         pa_cvolume_reset(&s->soft_volume, s->sample_spec.channels);
-        s->set_volume(s);
+        if (!(s->flags & PA_SINK_SYNC_VOLUME))
+            s->set_volume(s);
+        else
+            send_msg = TRUE;
 
     } else
         /* If we have no function set_volume(), then the soft volume
@@ -1468,23 +1505,27 @@ void pa_sink_set_volume(
 
     /* This tells the sink that soft and/or virtual volume changed */
     if (send_msg)
-        pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_VOLUME, NULL, 0, NULL) == 0);
+        pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_VOLUME_SYNCED, NULL, 0, NULL) == 0);
 
     if (reference_changed)
         pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
 }
 
-/* Called from main thread. Only to be called by sink implementor */
+/* Called from the io thread if sync volume is used, otherwise from the main thread.
+ * Only to be called by sink implementor */
 void pa_sink_set_soft_volume(pa_sink *s, const pa_cvolume *volume) {
     pa_sink_assert_ref(s);
-    pa_assert_ctl_context();
+    if (s->flags & PA_SINK_SYNC_VOLUME)
+        pa_sink_assert_io_context(s);
+    else
+        pa_assert_ctl_context();
 
     if (!volume)
         pa_cvolume_reset(&s->soft_volume, s->sample_spec.channels);
     else
         s->soft_volume = *volume;
 
-    if (PA_SINK_IS_LINKED(s->state))
+    if (PA_SINK_IS_LINKED(s->state) && !(s->flags & PA_SINK_SYNC_VOLUME))
         pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_VOLUME, NULL, 0, NULL) == 0);
     else
         s->thread_info.soft_volume = s->soft_volume;
@@ -1504,7 +1545,7 @@ static void propagate_real_volume(pa_sink *s, const pa_cvolume *old_real_volume)
      * reference volume and then rebuild the stream volumes based on
      * i->real_ratio which should stay fixed. */
 
-    if (pa_cvolume_equal(old_real_volume, &s->real_volume))
+    if (old_real_volume && pa_cvolume_equal(old_real_volume, &s->real_volume))
         return;
 
     old_reference_volume = s->reference_volume;
@@ -1555,6 +1596,14 @@ static void propagate_real_volume(pa_sink *s, const pa_cvolume *old_real_volume)
         pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
 }
 
+/* Called from io thread */
+void pa_sink_update_volume_and_mute(pa_sink *s) {
+    pa_assert(s);
+    pa_sink_assert_io_context(s);
+
+    pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_UPDATE_VOLUME_AND_MUTE, NULL, 0, NULL, NULL);
+}
+
 /* Called from main thread */
 const pa_cvolume *pa_sink_get_volume(pa_sink *s, pa_bool_t force_refresh) {
     pa_sink_assert_ref(s);
@@ -1566,7 +1615,7 @@ const pa_cvolume *pa_sink_get_volume(pa_sink *s, pa_bool_t force_refresh) {
 
         old_real_volume = s->real_volume;
 
-        if (s->get_volume)
+        if (!(s->flags & PA_SINK_SYNC_VOLUME) && s->get_volume)
             s->get_volume(s);
 
         pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_VOLUME, NULL, 0, NULL) == 0);
@@ -1605,7 +1654,7 @@ void pa_sink_set_mute(pa_sink *s, pa_bool_t mute, pa_bool_t save) {
     s->muted = mute;
     s->save_muted = (old_muted == s->muted && s->save_muted) || save;
 
-    if (s->set_mute)
+    if (!(s->flags & PA_SINK_SYNC_VOLUME) && s->set_mute)
         s->set_mute(s);
 
     pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_MUTE, NULL, 0, NULL) == 0);
@@ -1624,7 +1673,7 @@ pa_bool_t pa_sink_get_mute(pa_sink *s, pa_bool_t force_refresh) {
     if (s->refresh_muted || force_refresh) {
         pa_bool_t old_muted = s->muted;
 
-        if (s->get_mute)
+        if (!(s->flags & PA_SINK_SYNC_VOLUME) && s->get_mute)
             s->get_mute(s);
 
         pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_MUTE, NULL, 0, NULL) == 0);
@@ -1864,7 +1913,7 @@ int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offse
 
             /* In flat volume mode we need to update the volume as
              * well */
-            return o->process_msg(o, PA_SINK_MESSAGE_SET_VOLUME, NULL, 0, NULL);
+            return o->process_msg(o, PA_SINK_MESSAGE_SET_VOLUME_SYNCED, NULL, 0, NULL);
         }
 
         case PA_SINK_MESSAGE_REMOVE_INPUT: {
@@ -1907,7 +1956,7 @@ int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offse
 
             /* In flat volume mode we need to update the volume as
              * well */
-            return o->process_msg(o, PA_SINK_MESSAGE_SET_VOLUME, NULL, 0, NULL);
+            return o->process_msg(o, PA_SINK_MESSAGE_SET_VOLUME_SYNCED, NULL, 0, NULL);
         }
 
         case PA_SINK_MESSAGE_START_MOVE: {
@@ -1952,7 +2001,7 @@ int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offse
 
             /* In flat volume mode we need to update the volume as
              * well */
-            return o->process_msg(o, PA_SINK_MESSAGE_SET_VOLUME, NULL, 0, NULL);
+            return o->process_msg(o, PA_SINK_MESSAGE_SET_VOLUME_SYNCED, NULL, 0, NULL);
         }
 
         case PA_SINK_MESSAGE_FINISH_MOVE: {
@@ -1995,9 +2044,17 @@ int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offse
 
             /* In flat volume mode we need to update the volume as
              * well */
-            return o->process_msg(o, PA_SINK_MESSAGE_SET_VOLUME, NULL, 0, NULL);
+            return o->process_msg(o, PA_SINK_MESSAGE_SET_VOLUME_SYNCED, NULL, 0, NULL);
         }
 
+        case PA_SINK_MESSAGE_SET_VOLUME_SYNCED:
+
+            if (s->flags & PA_SINK_SYNC_VOLUME) {
+                s->set_volume(s);
+                pa_sink_volume_change_push(s);
+            }
+            /* Fall through ... */
+
         case PA_SINK_MESSAGE_SET_VOLUME:
 
             if (!pa_cvolume_equal(&s->thread_info.soft_volume, &s->soft_volume)) {
@@ -2015,6 +2072,19 @@ int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offse
             return 0;
 
         case PA_SINK_MESSAGE_GET_VOLUME:
+
+            if ((s->flags & PA_SINK_SYNC_VOLUME) && s->get_volume) {
+                s->get_volume(s);
+                pa_sink_volume_change_flush(s);
+                pa_sw_cvolume_divide(&s->thread_info.current_hw_volume, &s->real_volume, &s->soft_volume);
+            }
+
+            /* In case sink implementor reset SW volume. */
+            if (!pa_cvolume_equal(&s->thread_info.soft_volume, &s->soft_volume)) {
+                s->thread_info.soft_volume = s->soft_volume;
+                pa_sink_request_rewind(s, (size_t) -1);
+            }
+
             return 0;
 
         case PA_SINK_MESSAGE_SET_MUTE:
@@ -2024,9 +2094,16 @@ 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_SYNC_VOLUME && s->set_mute)
+                s->set_mute(s);
+
             return 0;
 
         case PA_SINK_MESSAGE_GET_MUTE:
+
+            if (s->flags & PA_SINK_SYNC_VOLUME && s->get_mute)
+                s->get_mute(s);
+
             return 0;
 
         case PA_SINK_MESSAGE_SET_STATE: {
@@ -2127,6 +2204,23 @@ int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offse
             pa_sink_set_max_request_within_thread(s, (size_t) offset);
             return 0;
 
+        case PA_SINK_MESSAGE_SET_PORT:
+
+            pa_assert(userdata);
+            if (s->set_port) {
+                struct sink_message_set_port *msg_data = userdata;
+                msg_data->ret = s->set_port(s, msg_data->port);
+            }
+            return 0;
+
+        case PA_SINK_MESSAGE_UPDATE_VOLUME_AND_MUTE:
+            /* This message is sent from IO-thread and handled in main thread. */
+            pa_assert_ctl_context();
+
+            pa_sink_get_volume(s, TRUE);
+            pa_sink_get_mute(s, TRUE);
+            return 0;
+
         case PA_SINK_MESSAGE_GET_LATENCY:
         case PA_SINK_MESSAGE_MAX:
             ;
@@ -2568,7 +2662,7 @@ size_t pa_sink_get_max_request(pa_sink *s) {
 /* Called from main context */
 int pa_sink_set_port(pa_sink *s, const char *name, pa_bool_t save) {
     pa_device_port *port;
-
+    int ret;
     pa_sink_assert_ref(s);
     pa_assert_ctl_context();
 
@@ -2588,7 +2682,15 @@ int pa_sink_set_port(pa_sink *s, const char *name, pa_bool_t save) {
         return 0;
     }
 
-    if ((s->set_port(s, port)) < 0)
+    if (s->flags & PA_SINK_SYNC_VOLUME) {
+        struct sink_message_set_port msg = { .port = port, .ret = 0 };
+        pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_PORT, &msg, 0, NULL) == 0);
+        ret = msg.ret;
+    }
+    else
+        ret = s->set_port(s, port);
+
+    if (ret < 0)
         return -PA_ERR_NOENTITY;
 
     pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
@@ -2760,3 +2862,165 @@ unsigned pa_device_init_priority(pa_proplist *p) {
 
     return priority;
 }
+
+PA_STATIC_FLIST_DECLARE(pa_sink_volume_change, 0, pa_xfree);
+
+/* Called from the IO thread. */
+static pa_sink_volume_change *pa_sink_volume_change_new(pa_sink *s) {
+    pa_sink_volume_change *c;
+    if (!(c = pa_flist_pop(PA_STATIC_FLIST_GET(pa_sink_volume_change))))
+        c = pa_xnew(pa_sink_volume_change, 1);
+
+    PA_LLIST_INIT(pa_sink_volume_change, c);
+    c->at = 0;
+    pa_cvolume_reset(&c->hw_volume, s->sample_spec.channels);
+    return c;
+}
+
+/* Called from the IO thread. */
+static void pa_sink_volume_change_free(pa_sink_volume_change *c) {
+    pa_assert(c);
+    if (pa_flist_push(PA_STATIC_FLIST_GET(pa_sink_volume_change), c) < 0)
+        pa_xfree(c);
+}
+
+/* Called from the IO thread. */
+void pa_sink_volume_change_push(pa_sink *s) {
+    pa_sink_volume_change *c = NULL;
+    pa_sink_volume_change *nc = NULL;
+    uint32_t safety_margin = s->thread_info.volume_change_safety_margin;
+
+    const char *direction = NULL;
+
+    pa_assert(s);
+    nc = pa_sink_volume_change_new(s);
+
+    /* NOTE: There is already more different volumes in pa_sink that I can remember.
+     *       Adding one more volume for HW would get us rid of this, but I am trying
+     *       to survive with the ones we already have. */
+    pa_sw_cvolume_divide(&nc->hw_volume, &s->real_volume, &s->soft_volume);
+
+    if (!s->thread_info.volume_changes && pa_cvolume_equal(&nc->hw_volume, &s->thread_info.current_hw_volume)) {
+        pa_log_debug("Volume not changing");
+        pa_sink_volume_change_free(nc);
+        return;
+    }
+
+    /* Get the latency of the sink */
+    if (PA_MSGOBJECT(s)->process_msg(PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_LATENCY, &nc->at, 0, NULL) < 0)
+        nc->at = 0;
+
+    nc->at += pa_rtclock_now() + s->thread_info.volume_change_extra_delay;
+
+    if (s->thread_info.volume_changes_tail) {
+        for (c = s->thread_info.volume_changes_tail; c; c = c->prev) {
+            /* If volume is going up let's do it a bit late. If it is going
+             * down let's do it a bit early. */
+            if (pa_cvolume_avg(&nc->hw_volume) > pa_cvolume_avg(&c->hw_volume)) {
+                if (nc->at + safety_margin > c->at) {
+                    nc->at += safety_margin;
+                    direction = "up";
+                    break;
+                }
+            }
+            else if (nc->at - safety_margin > c->at) {
+                    nc->at -= safety_margin;
+                    direction = "down";
+                    break;
+            }
+        }
+    }
+
+    if (c == NULL) {
+        if (pa_cvolume_avg(&nc->hw_volume) > pa_cvolume_avg(&s->thread_info.current_hw_volume)) {
+            nc->at += safety_margin;
+            direction = "up";
+        } else {
+            nc->at -= safety_margin;
+            direction = "down";
+        }
+        PA_LLIST_PREPEND(pa_sink_volume_change, s->thread_info.volume_changes, nc);
+    }
+    else {
+        PA_LLIST_INSERT_AFTER(pa_sink_volume_change, s->thread_info.volume_changes, c, nc);
+    }
+
+    pa_log_debug("Volume going %s to %d at %llu", direction, pa_cvolume_avg(&nc->hw_volume), nc->at);
+
+    /* We can ignore volume events that came earlier but should happen later than this. */
+    PA_LLIST_FOREACH(c, nc->next) {
+        pa_log_debug("Volume change to %d at %llu was dropped", pa_cvolume_avg(&c->hw_volume), c->at);
+        pa_sink_volume_change_free(c);
+    }
+    nc->next = NULL;
+    s->thread_info.volume_changes_tail = nc;
+}
+
+/* Called from the IO thread. */
+static void pa_sink_volume_change_flush(pa_sink *s) {
+    pa_sink_volume_change *c = s->thread_info.volume_changes;
+    pa_assert(s);
+    s->thread_info.volume_changes = NULL;
+    s->thread_info.volume_changes_tail = NULL;
+    while (c) {
+        pa_sink_volume_change *next = c->next;
+        pa_sink_volume_change_free(c);
+        c = next;
+    }
+}
+
+/* Called from the IO thread. */
+pa_bool_t pa_sink_volume_change_apply(pa_sink *s, pa_usec_t *usec_to_next) {
+    pa_usec_t now = pa_rtclock_now();
+    pa_bool_t ret = FALSE;
+
+    pa_assert(s);
+    pa_assert(s->write_volume);
+
+    while (s->thread_info.volume_changes && now >= s->thread_info.volume_changes->at) {
+        pa_sink_volume_change *c = s->thread_info.volume_changes;
+        PA_LLIST_REMOVE(pa_sink_volume_change, s->thread_info.volume_changes, c);
+        pa_log_debug("Volume change to %d at %llu was written %llu usec late", pa_cvolume_avg(&c->hw_volume), c->at, now - c->at);
+        ret = TRUE;
+        s->thread_info.current_hw_volume = c->hw_volume;
+        pa_sink_volume_change_free(c);
+    }
+
+    if (s->write_volume && ret)
+        s->write_volume(s);
+
+    if (s->thread_info.volume_changes) {
+        if (usec_to_next)
+            *usec_to_next = s->thread_info.volume_changes->at - now;
+        if (pa_log_ratelimit())
+            pa_log_debug("Next volume change in %lld usec", s->thread_info.volume_changes->at - now);
+    }
+    else {
+        if (usec_to_next)
+            *usec_to_next = 0;
+        s->thread_info.volume_changes_tail = NULL;
+    }
+    return ret;
+}
+
+/* Called from the IO thread. */
+static void pa_sink_volume_change_rewind(pa_sink *s, size_t nbytes) {
+    /* All the queued volume events later than current latency are shifted to happen earlier. */
+    pa_sink_volume_change *c;
+    pa_usec_t rewound = pa_bytes_to_usec(nbytes, &s->sample_spec);
+    pa_usec_t limit;
+
+    /* Get the latency of the sink */
+    if (PA_MSGOBJECT(s)->process_msg(PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_LATENCY, &limit, 0, NULL) < 0)
+        limit = 0;
+
+    limit += pa_rtclock_now() + s->thread_info.volume_change_extra_delay;
+
+    PA_LLIST_FOREACH(c, s->thread_info.volume_changes) {
+        if (c->at > limit) {
+            c->at -= rewound;
+            if (c->at < limit)
+                c->at = limit;
+        }
+    }
+}
diff --git a/src/pulsecore/sink.h b/src/pulsecore/sink.h
index ba547fc..4d569dd 100644
--- a/src/pulsecore/sink.h
+++ b/src/pulsecore/sink.h
@@ -25,6 +25,7 @@
 
 typedef struct pa_sink pa_sink;
 typedef struct pa_device_port pa_device_port;
+typedef struct pa_sink_volume_change pa_sink_volume_change;
 
 #include <inttypes.h>
 
@@ -116,26 +117,72 @@ struct pa_sink {
      * inhibited */
     int (*set_state)(pa_sink *s, pa_sink_state_t state); /* may be NULL */
 
-    /* Callled when the volume is queried. Called from main loop
-     * context. If this is NULL a PA_SINK_MESSAGE_GET_VOLUME message
-     * will be sent to the IO thread instead. If refresh_volume is
-     * FALSE neither this function is called nor a message is sent. */
+    /* Sink drivers that support hardware volume may set this
+     * callback. This is called when the current volume needs to be
+     * re-read from the hardware.
+     *
+     * There are two ways for drivers to implement hardware volume
+     * query: either set this callback or handle
+     * PA_SINK_MESSAGE_GET_VOLUME. The callback implementation or the
+     * message handler must update s->real_volume and s->soft_volume
+     * (using pa_sink_set_soft_volume()) to match the current hardware
+     * volume.
+     *
+     * If PA_SINK_SYNC_VOLUME is not set, then this is called from the
+     * main thread before sending PA_SINK_MESSAGE_GET_VOLUME, so in
+     * this case the driver can choose whether to read the volume from
+     * the hardware in the main thread or in the IO thread.
+     *
+     * If PA_SINK_SYNC_VOLUME is set, then this is called from the IO
+     * thread within the default handler for
+     * PA_SINK_MESSAGE_GET_VOLUME (the main thread is waiting while
+     * the message is being processed), so there's no choice of where
+     * to do the volume reading - it has to be done in the IO thread
+     * always. */
     void (*get_volume)(pa_sink *s);             /* may be NULL */
 
-    /* Called when the volume shall be changed. Called from main loop
-     * context. If this is NULL a PA_SINK_MESSAGE_SET_VOLUME message
-     * will be sent to the IO thread instead. */
+    /* Sink drivers that support hardware volume must set this
+     * callback. This is called when the hardware volume needs to be
+     * updated.
+     *
+     * If PA_SINK_SYNC_VOLUME is not set, then this is called from the
+     * main thread. The callback implementation must set the hardware
+     * volume according to s->real_volume. If the driver can't set the
+     * hardware volume to the exact requested value, it has to update
+     * s->real_volume and/or s->soft_volume so that they together
+     * match the actual hardware volume that was set.
+     *
+     * If PA_SINK_SYNC_VOLUME is set, then this is called from the IO
+     * thread. The callback implementation must not actually set the
+     * hardware volume yet, but it must check how close to the
+     * requested volume the hardware volume can be set, and update
+     * s->real_volume and/or s->soft_volume so that they together
+     * match the actual hardware volume that will be set later in the
+     * write_volume callback. */
     void (*set_volume)(pa_sink *s);             /* dito */
 
-    /* Called when the mute setting is queried. Called from main loop
-     * context. If this is NULL a PA_SINK_MESSAGE_GET_MUTE message
-     * will be sent to the IO thread instead. If refresh_mute is
-     * FALSE neither this function is called nor a message is sent.*/
+    /* Sink drivers that set PA_SINK_SYNC_VOLUME must provide this
+     * callback. This callback is not used with sinks that do not set
+     * PA_SINK_SYNC_VOLUME. This is called from the IO thread when a
+     * pending hardware volume change has to be written to the
+     * hardware. The requested volume is passed to the callback
+     * implementation in s->thread_info.current_hw_volume.
+     *
+     * The call is done inside pa_sink_volume_change_apply(), which is
+     * not called automatically - it is the driver's responsibility to
+     * schedule that function to be called at the right times in the
+     * IO thread. */
+    void (*write_volume)(pa_sink *s);           /* dito */
+
+    /* Called when the mute setting is queried. A PA_SINK_MESSAGE_GET_MUTE
+     * message will also be sent. Called from IO thread if PA_SINK_SYNC_VOLUME
+     * flag is set otherwise from main loop context. If refresh_mute is FALSE
+     * neither this function is called nor a message is sent.*/
     void (*get_mute)(pa_sink *s);               /* dito */
 
-    /* Called when the mute setting shall be changed. Called from main
-     * loop context. If this is NULL a PA_SINK_MESSAGE_SET_MUTE
-     * message will be sent to the IO thread instead. */
+    /* Called when the mute setting shall be changed. A PA_SINK_MESSAGE_SET_MUTE
+     * message will also be sent. Called from IO thread if PA_SINK_SYNC_VOLUME
+     * flag is set otherwise from main loop context. */
     void (*set_mute)(pa_sink *s);               /* dito */
 
     /* Called when a rewind request is issued. Called from IO thread
@@ -188,6 +235,21 @@ struct pa_sink {
          * decided on by the sink, and the clients have no influence
          * in changing it */
         pa_usec_t fixed_latency; /* for sinks with PA_SINK_DYNAMIC_LATENCY this is 0 */
+
+        /* Delayed volume change events are queued here. The events
+         * are stored in expiration order. The one expiring next is in
+         * the head of the list. */
+        PA_LLIST_HEAD(pa_sink_volume_change, volume_changes);
+        pa_sink_volume_change *volume_changes_tail;
+        /* This value is updated in pa_sink_volume_change_apply() and
+         * used only by sinks with PA_SINK_SYNC_VOLUME. */
+        pa_cvolume current_hw_volume;
+
+        /* The amount of usec volume up events are delayed and volume
+         * down events are made earlier. */
+        uint32_t volume_change_safety_margin;
+        /* Usec delay added to all volume change events, may be negative. */
+        int32_t volume_change_extra_delay;
     } thread_info;
 
     void *userdata;
@@ -200,6 +262,7 @@ typedef enum pa_sink_message {
     PA_SINK_MESSAGE_ADD_INPUT,
     PA_SINK_MESSAGE_REMOVE_INPUT,
     PA_SINK_MESSAGE_GET_VOLUME,
+    PA_SINK_MESSAGE_SET_VOLUME_SYNCED,
     PA_SINK_MESSAGE_SET_VOLUME,
     PA_SINK_MESSAGE_SYNC_VOLUMES,
     PA_SINK_MESSAGE_GET_MUTE,
@@ -219,6 +282,8 @@ typedef enum pa_sink_message {
     PA_SINK_MESSAGE_GET_MAX_REQUEST,
     PA_SINK_MESSAGE_SET_MAX_REWIND,
     PA_SINK_MESSAGE_SET_MAX_REQUEST,
+    PA_SINK_MESSAGE_SET_PORT,
+    PA_SINK_MESSAGE_UPDATE_VOLUME_AND_MUTE,
     PA_SINK_MESSAGE_MAX
 } pa_sink_message_t;
 
@@ -349,6 +414,10 @@ void pa_sink_set_max_request_within_thread(pa_sink *s, size_t max_request);
 void pa_sink_set_latency_range_within_thread(pa_sink *s, pa_usec_t min_latency, pa_usec_t max_latency);
 void pa_sink_set_fixed_latency_within_thread(pa_sink *s, pa_usec_t latency);
 
+void pa_sink_update_volume_and_mute(pa_sink *s);
+
+pa_bool_t pa_sink_volume_change_apply(pa_sink *s, pa_usec_t *usec_to_next);
+
 /*** To be called exclusively by sink input drivers, from IO context */
 
 void pa_sink_request_rewind(pa_sink*s, size_t nbytes);

commit 1bea9558297773fd2e2fe4deb43a5adabf90a16e
Author: Jyri Sarha <jyri.sarha at nokia.com>
Date:   Fri Oct 15 13:05:15 2010 +0300

    alsa: Take syncronized HW volume infra into use for alsa-sink
    
    Signed-off-by: Jyri Sarha <jyri.sarha at nokia.com>
    Reviewed-by: Tanu Kaskinen <tanu.kaskinen at digia.com>
    Reviewd-by: Colin Guthrie <cguthrie at mandriva.org>

diff --git a/src/modules/alsa/alsa-mixer.c b/src/modules/alsa/alsa-mixer.c
index 1033bbe..23b22d0 100644
--- a/src/modules/alsa/alsa-mixer.c
+++ b/src/modules/alsa/alsa-mixer.c
@@ -243,6 +243,95 @@ int pa_alsa_fdlist_set_mixer(struct pa_alsa_fdlist *fdl, snd_mixer_t *mixer_hand
     return 0;
 }
 
+struct pa_alsa_mixer_pdata {
+    pa_rtpoll *rtpoll;
+    pa_rtpoll_item *poll_item;
+    snd_mixer_t *mixer;
+};
+
+
+struct pa_alsa_mixer_pdata *pa_alsa_mixer_pdata_new(void) {
+    struct pa_alsa_mixer_pdata *pd;
+
+    pd = pa_xnew0(struct pa_alsa_mixer_pdata, 1);
+
+    return pd;
+}
+
+void pa_alsa_mixer_pdata_free(struct pa_alsa_mixer_pdata *pd) {
+    pa_assert(pd);
+
+    if (pd->poll_item) {
+        pa_rtpoll_item_free(pd->poll_item);
+    }
+
+    pa_xfree(pd);
+}
+
+static int rtpoll_work_cb(pa_rtpoll_item *i) {
+    struct pa_alsa_mixer_pdata *pd;
+    struct pollfd *p;
+    unsigned n_fds;
+    unsigned short revents = 0;
+    int err;
+
+    pd = pa_rtpoll_item_get_userdata(i);
+    pa_assert_fp(pd);
+    pa_assert_fp(i == pd->poll_item);
+
+    p = pa_rtpoll_item_get_pollfd(i, &n_fds);
+
+    if ((err = snd_mixer_poll_descriptors_revents(pd->mixer, p, n_fds, &revents)) < 0) {
+        pa_log_error("Unable to get poll revent: %s", pa_alsa_strerror(err));
+        pa_rtpoll_item_free(i);
+        return -1;
+    }
+
+    if (revents) {
+        snd_mixer_handle_events(pd->mixer);
+        pa_rtpoll_item_free(i);
+        pa_alsa_set_mixer_rtpoll(pd, pd->mixer, pd->rtpoll);
+    }
+
+    return 0;
+}
+
+int pa_alsa_set_mixer_rtpoll(struct pa_alsa_mixer_pdata *pd, snd_mixer_t *mixer, pa_rtpoll *rtp) {
+    pa_rtpoll_item *i;
+    struct pollfd *p;
+    int err, n;
+
+    pa_assert(pd);
+    pa_assert(mixer);
+    pa_assert(rtp);
+
+    if ((n = snd_mixer_poll_descriptors_count(mixer)) < 0) {
+        pa_log("snd_mixer_poll_descriptors_count() failed: %s", pa_alsa_strerror(n));
+        return -1;
+    }
+
+    i = pa_rtpoll_item_new(rtp, PA_RTPOLL_LATE, (unsigned) n);
+
+    p = pa_rtpoll_item_get_pollfd(i, NULL);
+
+    memset(p, 0, sizeof(struct pollfd) * n);
+
+    if ((err = snd_mixer_poll_descriptors(mixer, p, (unsigned) n)) < 0) {
+        pa_log_error("Unable to get poll descriptors: %s", pa_alsa_strerror(err));
+        pa_rtpoll_item_free(i);
+        return -1;
+    }
+
+    pd->rtpoll = rtp;
+    pd->poll_item = i;
+    pd->mixer = mixer;
+
+    pa_rtpoll_item_set_userdata(i, pd);
+    pa_rtpoll_item_set_work_callback(i, rtpoll_work_cb);
+
+    return 0;
+}
+
 static int prepare_mixer(snd_mixer_t *mixer, const char *dev) {
     int err;
 
@@ -671,7 +760,8 @@ int pa_alsa_path_get_mute(pa_alsa_path *p, snd_mixer_t *m, pa_bool_t *muted) {
     return 0;
 }
 
-static int element_set_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_channel_map *cm, pa_cvolume *v) {
+static int element_set_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_channel_map *cm, pa_cvolume *v, pa_bool_t write_to_hw) {
+
     snd_mixer_selem_id_t *sid;
     pa_cvolume rv;
     snd_mixer_elem_t *me;
@@ -720,14 +810,26 @@ static int element_set_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_chann
                  * if the channel is available, ALSA behaves ver
                  * strangely and doesn't fail the call */
                 if (snd_mixer_selem_has_playback_channel(me, c)) {
-                    if ((r = snd_mixer_selem_set_playback_dB(me, c, value, +1)) >= 0)
-                        r = snd_mixer_selem_get_playback_dB(me, c, &value);
+                    if (write_to_hw) {
+                        if ((r = snd_mixer_selem_set_playback_dB(me, c, value, +1)) >= 0)
+                            r = snd_mixer_selem_get_playback_dB(me, c, &value);
+                    } else {
+                        long alsa_val;
+                        if ((r = snd_mixer_selem_ask_playback_dB_vol(me, value, +1, &alsa_val)) >= 0)
+                            r = snd_mixer_selem_ask_playback_vol_dB(me, alsa_val, &value);
+                    }
                 } else
                     r = -1;
             } else {
                 if (snd_mixer_selem_has_capture_channel(me, c)) {
-                    if ((r = snd_mixer_selem_set_capture_dB(me, c, value, +1)) >= 0)
-                        r = snd_mixer_selem_get_capture_dB(me, c, &value);
+                    if (write_to_hw) {
+                        if ((r = snd_mixer_selem_set_capture_dB(me, c, value, +1)) >= 0)
+                            r = snd_mixer_selem_get_capture_dB(me, c, &value);
+                    } else {
+                        long alsa_val;
+                        if ((r = snd_mixer_selem_ask_capture_dB_vol(me, value, +1, &alsa_val)) >= 0)
+                            r = snd_mixer_selem_ask_capture_vol_dB(me, alsa_val, &value);
+                    }
                 } else
                     r = -1;
             }
@@ -782,7 +884,8 @@ static int element_set_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_chann
     return 0;
 }
 
-int pa_alsa_path_set_volume(pa_alsa_path *p, snd_mixer_t *m, const pa_channel_map *cm, pa_cvolume *v) {
+int pa_alsa_path_set_volume(pa_alsa_path *p, snd_mixer_t *m, const pa_channel_map *cm, pa_cvolume *v, pa_bool_t write_to_hw) {
+
     pa_alsa_element *e;
     pa_cvolume rv;
 
@@ -807,7 +910,7 @@ int pa_alsa_path_set_volume(pa_alsa_path *p, snd_mixer_t *m, const pa_channel_ma
         pa_assert(!p->has_dB || e->has_dB);
 
         ev = rv;
-        if (element_set_volume(e, m, cm, &ev) < 0)
+        if (element_set_volume(e, m, cm, &ev, write_to_hw) < 0)
             return -1;
 
         if (!p->has_dB) {
diff --git a/src/modules/alsa/alsa-mixer.h b/src/modules/alsa/alsa-mixer.h
index a0d4fcb..7fb408a 100644
--- a/src/modules/alsa/alsa-mixer.h
+++ b/src/modules/alsa/alsa-mixer.h
@@ -38,6 +38,7 @@
 #include <pulsecore/log.h>
 
 typedef struct pa_alsa_fdlist pa_alsa_fdlist;
+typedef struct pa_alsa_mixer_pdata pa_alsa_mixer_pdata;
 typedef struct pa_alsa_setting pa_alsa_setting;
 typedef struct pa_alsa_option pa_alsa_option;
 typedef struct pa_alsa_element pa_alsa_element;
@@ -202,7 +203,7 @@ int pa_alsa_path_probe(pa_alsa_path *p, snd_mixer_t *m, pa_bool_t ignore_dB);
 void pa_alsa_path_dump(pa_alsa_path *p);
 int pa_alsa_path_get_volume(pa_alsa_path *p, snd_mixer_t *m, const pa_channel_map *cm, pa_cvolume *v);
 int pa_alsa_path_get_mute(pa_alsa_path *path, snd_mixer_t *m, pa_bool_t *muted);
-int pa_alsa_path_set_volume(pa_alsa_path *path, snd_mixer_t *m, const pa_channel_map *cm, pa_cvolume *v);
+int pa_alsa_path_set_volume(pa_alsa_path *path, snd_mixer_t *m, const pa_channel_map *cm, pa_cvolume *v, pa_bool_t write_to_hw);
 int pa_alsa_path_set_mute(pa_alsa_path *path, snd_mixer_t *m, pa_bool_t muted);
 int pa_alsa_path_select(pa_alsa_path *p, snd_mixer_t *m);
 void pa_alsa_path_set_callback(pa_alsa_path *p, snd_mixer_t *m, snd_mixer_elem_callback_t cb, void *userdata);
@@ -279,6 +280,12 @@ pa_alsa_fdlist *pa_alsa_fdlist_new(void);
 void pa_alsa_fdlist_free(pa_alsa_fdlist *fdl);
 int pa_alsa_fdlist_set_mixer(pa_alsa_fdlist *fdl, snd_mixer_t *mixer_handle, pa_mainloop_api* m);
 
+/* Alternative for handling alsa mixer events in io-thread. */
+
+pa_alsa_mixer_pdata *pa_alsa_mixer_pdata_new(void);
+void pa_alsa_mixer_pdata_free(pa_alsa_mixer_pdata *pd);
+int pa_alsa_set_mixer_rtpoll(struct pa_alsa_mixer_pdata *pd, snd_mixer_t *mixer, pa_rtpoll *rtp);
+
 /* Data structure for inclusion in pa_device_port for alsa
  * sinks/sources. This contains nothing that needs to be freed
  * individually */
diff --git a/src/modules/alsa/alsa-sink.c b/src/modules/alsa/alsa-sink.c
index 1108a79..069ee95 100644
--- a/src/modules/alsa/alsa-sink.c
+++ b/src/modules/alsa/alsa-sink.c
@@ -101,6 +101,7 @@ struct userdata {
     snd_pcm_t *pcm_handle;
 
     pa_alsa_fdlist *mixer_fdl;
+    pa_alsa_mixer_pdata *mixer_pd;
     snd_mixer_t *mixer_handle;
     pa_alsa_path_set *mixer_path_set;
     pa_alsa_path *mixer_path;
@@ -1124,7 +1125,7 @@ static int sink_set_state_cb(pa_sink *s, pa_sink_state_t new_state) {
     return 0;
 }
 
-static int mixer_callback(snd_mixer_elem_t *elem, unsigned int mask) {
+static int ctl_mixer_callback(snd_mixer_elem_t *elem, unsigned int mask) {
     struct userdata *u = snd_mixer_elem_get_callback_private(elem);
 
     pa_assert(u);
@@ -1144,6 +1145,24 @@ static int mixer_callback(snd_mixer_elem_t *elem, unsigned int mask) {
     return 0;
 }
 
+static int io_mixer_callback(snd_mixer_elem_t *elem, unsigned int mask) {
+    struct userdata *u = snd_mixer_elem_get_callback_private(elem);
+
+    pa_assert(u);
+    pa_assert(u->mixer_handle);
+
+    if (mask == SND_CTL_EVENT_MASK_REMOVE)
+        return 0;
+
+    if (u->sink->suspend_cause & PA_SUSPEND_SESSION)
+        return 0;
+
+    if (mask & SND_CTL_EVENT_MASK_VALUE)
+        pa_sink_update_volume_and_mute(u->sink);
+
+    return 0;
+}
+
 static void sink_get_volume_cb(pa_sink *s) {
     struct userdata *u = s->userdata;
     pa_cvolume r;
@@ -1175,6 +1194,7 @@ static void sink_set_volume_cb(pa_sink *s) {
     struct userdata *u = s->userdata;
     pa_cvolume r;
     char t[PA_CVOLUME_SNPRINT_MAX];
+    pa_bool_t write_to_hw = (s->flags & PA_SINK_SYNC_VOLUME) ? FALSE : TRUE;
 
     pa_assert(u);
     pa_assert(u->mixer_path);
@@ -1183,7 +1203,7 @@ static void sink_set_volume_cb(pa_sink *s) {
     /* Shift up by the base volume */
     pa_sw_cvolume_divide_scalar(&r, &s->real_volume, s->base_volume);
 
-    if (pa_alsa_path_set_volume(u->mixer_path, u->mixer_handle, &s->channel_map, &r) < 0)
+    if (pa_alsa_path_set_volume(u->mixer_path, u->mixer_handle, &s->channel_map, &r, write_to_hw) < 0)
         return;
 
     /* Shift down by the base volume, so that 0dB becomes maximum volume */
@@ -1223,6 +1243,33 @@ static void sink_set_volume_cb(pa_sink *s) {
     }
 }
 
+static void sink_write_volume_cb(pa_sink *s) {
+    struct userdata *u = s->userdata;
+    pa_cvolume hw_vol = s->thread_info.current_hw_volume;
+
+    pa_assert(u);
+    pa_assert(u->mixer_path);
+    pa_assert(u->mixer_handle);
+    pa_assert(s->flags & PA_SINK_SYNC_VOLUME);
+
+    if (pa_alsa_path_set_volume(u->mixer_path, u->mixer_handle, &s->channel_map, &hw_vol, TRUE) < 0)
+        pa_log_error("Writing HW volume failed");
+    else {
+        pa_cvolume tmp_vol;
+        pa_bool_t accurate_enough;
+        pa_sw_cvolume_divide(&tmp_vol, &hw_vol, &s->thread_info.current_hw_volume);
+        accurate_enough =
+            (pa_cvolume_min(&tmp_vol) >= (PA_VOLUME_NORM - VOLUME_ACCURACY)) &&
+            (pa_cvolume_max(&tmp_vol) <= (PA_VOLUME_NORM + VOLUME_ACCURACY));
+        if (!accurate_enough) {
+            char t[PA_CVOLUME_SNPRINT_MAX];
+            pa_log_debug("Written HW volume did not match with the request %s != %s",
+                         pa_cvolume_snprint(t, sizeof(t), &s->thread_info.current_hw_volume),
+                         pa_cvolume_snprint(t, sizeof(t), &hw_vol));
+        }
+    }
+}
+
 static void sink_get_mute_cb(pa_sink *s) {
     struct userdata *u = s->userdata;
     pa_bool_t b;
@@ -1385,6 +1432,7 @@ static void thread_func(void *userdata) {
 
     for (;;) {
         int ret;
+        pa_usec_t rtpoll_sleep = 0;
 
 #ifdef DEBUG_TIMING
         pa_log_debug("Loop");
@@ -1453,20 +1501,32 @@ static void thread_func(void *userdata) {
 /*                 pa_log_debug("Waking up in %0.2fms (system clock).", (double) cusec / PA_USEC_PER_MSEC); */
 
                 /* We don't trust the conversion, so we wake up whatever comes first */
-                pa_rtpoll_set_timer_relative(u->rtpoll, PA_MIN(sleep_usec, cusec));
+                rtpoll_sleep = PA_MIN(sleep_usec, cusec);
             }
 
             u->after_rewind = FALSE;
 
-        } else if (u->use_tsched)
+        }
+
+        if (u->sink->flags & PA_SINK_SYNC_VOLUME) {
+            pa_usec_t volume_sleep;
+            pa_sink_volume_change_apply(u->sink, &volume_sleep);
+            if (volume_sleep > 0)
+                rtpoll_sleep = MIN(volume_sleep, rtpoll_sleep);
+        }
 
-            /* OK, we're in an invalid state, let's disable our timers */
+        if (rtpoll_sleep > 0)
+            pa_rtpoll_set_timer_relative(u->rtpoll, rtpoll_sleep);
+        else
             pa_rtpoll_set_timer_disabled(u->rtpoll);
 
         /* Hmm, nothing to do. Let's sleep */
         if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0)
             goto fail;
 
+        if (u->sink->flags & PA_SINK_SYNC_VOLUME)
+            pa_sink_volume_change_apply(u->sink, NULL);
+
         if (ret == 0)
             goto finish;
 
@@ -1585,7 +1645,9 @@ fail:
     }
 }
 
-static int setup_mixer(struct userdata *u, pa_bool_t ignore_dB) {
+static int setup_mixer(struct userdata *u, pa_bool_t ignore_dB, pa_bool_t sync_volume) {
+    int (*mixer_callback)(snd_mixer_elem_t *, unsigned int);
+
     pa_assert(u);
 
     if (!u->mixer_handle)
@@ -1651,8 +1713,17 @@ static int setup_mixer(struct userdata *u, pa_bool_t ignore_dB) {
 
         u->sink->get_volume = sink_get_volume_cb;
         u->sink->set_volume = sink_set_volume_cb;
+        u->sink->write_volume = sink_write_volume_cb;
+
+        u->sink->flags |= PA_SINK_HW_VOLUME_CTRL;
+        if (u->mixer_path->has_dB) {
+            u->sink->flags |= PA_SINK_DECIBEL_VOLUME;
+            if (sync_volume) {
+                u->sink->flags |= PA_SINK_SYNC_VOLUME;
+                pa_log_info("Successfully enabled synchronous volume.");
+            }
+        }
 
-        u->sink->flags |= PA_SINK_HW_VOLUME_CTRL | (u->mixer_path->has_dB ? PA_SINK_DECIBEL_VOLUME : 0);
         pa_log_info("Using hardware volume control. Hardware dB scale %s.", u->mixer_path->has_dB ? "supported" : "not supported");
     }
 
@@ -1665,11 +1736,23 @@ static int setup_mixer(struct userdata *u, pa_bool_t ignore_dB) {
         pa_log_info("Using hardware mute control.");
     }
 
-    u->mixer_fdl = pa_alsa_fdlist_new();
+    if (sync_volume) {
+        u->mixer_pd = pa_alsa_mixer_pdata_new();
+        mixer_callback = io_mixer_callback;
 
-    if (pa_alsa_fdlist_set_mixer(u->mixer_fdl, u->mixer_handle, u->core->mainloop) < 0) {
-        pa_log("Failed to initialize file descriptor monitoring");
-        return -1;
+        if (pa_alsa_set_mixer_rtpoll(u->mixer_pd, u->mixer_handle, u->rtpoll) < 0) {
+            pa_log("Failed to initialize file descriptor monitoring");
+            return -1;
+        }
+
+    } else {
+        u->mixer_fdl = pa_alsa_fdlist_new();
+        mixer_callback = ctl_mixer_callback;
+
+        if (pa_alsa_fdlist_set_mixer(u->mixer_fdl, u->mixer_handle, u->core->mainloop) < 0) {
+            pa_log("Failed to initialize file descriptor monitoring");
+            return -1;
+        }
     }
 
     if (u->mixer_path_set)
@@ -1689,7 +1772,7 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca
     uint32_t nfrags, frag_size, buffer_size, tsched_size, tsched_watermark, rewind_safeguard;
     snd_pcm_uframes_t period_frames, buffer_frames, tsched_frames;
     size_t frame_size;
-    pa_bool_t use_mmap = TRUE, b, use_tsched = TRUE, d, ignore_dB = FALSE, namereg_fail = FALSE;
+    pa_bool_t use_mmap = TRUE, b, use_tsched = TRUE, d, ignore_dB = FALSE, namereg_fail = FALSE, sync_volume = FALSE;
     pa_sink_new_data data;
     pa_alsa_profile_set *profile_set = NULL;
 
@@ -1748,6 +1831,11 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca
         goto fail;
     }
 
+    if (pa_modargs_get_value_boolean(ma, "sync_volume", &sync_volume) < 0) {
+        pa_log("Failed to parse sync_volume argument.");
+        goto fail;
+    }
+
     use_tsched = pa_alsa_may_tsched(use_tsched);
 
     u = pa_xnew0(struct userdata, 1);
@@ -1913,6 +2001,18 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca
         goto fail;
     }
 
+    if (pa_modargs_get_value_u32(ma, "sync_volume_safety_margin",
+                                 &u->sink->thread_info.volume_change_safety_margin) < 0) {
+        pa_log("Failed to parse sync_volume_safety_margin parameter");
+        goto fail;
+    }
+
+    if (pa_modargs_get_value_s32(ma, "sync_volume_extra_delay",
+                                 &u->sink->thread_info.volume_change_extra_delay) < 0) {
+        pa_log("Failed to parse sync_volume_extra_delay parameter");
+        goto fail;
+    }
+
     u->sink->parent.process_msg = sink_process_msg;
     if (u->use_tsched)
         u->sink->update_requested_latency = sink_update_requested_latency_cb;
@@ -1969,7 +2069,7 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca
     if (update_sw_params(u) < 0)
         goto fail;
 
-    if (setup_mixer(u, ignore_dB) < 0)
+    if (setup_mixer(u, ignore_dB, sync_volume) < 0)
         goto fail;
 
     pa_alsa_dump(PA_LOG_DEBUG, u->pcm_handle);
@@ -2033,6 +2133,9 @@ static void userdata_free(struct userdata *u) {
     if (u->memchunk.memblock)
         pa_memblock_unref(u->memchunk.memblock);
 
+    if (u->mixer_pd)
+        pa_alsa_mixer_pdata_free(u->mixer_pd);
+
     if (u->alsa_rtpoll_item)
         pa_rtpoll_item_free(u->alsa_rtpoll_item);
 
diff --git a/src/modules/alsa/alsa-source.c b/src/modules/alsa/alsa-source.c
index 5f12675..9795136 100644
--- a/src/modules/alsa/alsa-source.c
+++ b/src/modules/alsa/alsa-source.c
@@ -1108,7 +1108,7 @@ static void source_set_volume_cb(pa_source *s) {
     /* Shift up by the base volume */
     pa_sw_cvolume_divide_scalar(&r, &s->volume, s->base_volume);
 
-    if (pa_alsa_path_set_volume(u->mixer_path, u->mixer_handle, &s->channel_map, &r) < 0)
+    if (pa_alsa_path_set_volume(u->mixer_path, u->mixer_handle, &s->channel_map, &r, TRUE) < 0)
         return;
 
     /* Shift down by the base volume, so that 0dB becomes maximum volume */
diff --git a/src/modules/alsa/module-alsa-card.c b/src/modules/alsa/module-alsa-card.c
index 37b5a17..ebd2f8a 100644
--- a/src/modules/alsa/module-alsa-card.c
+++ b/src/modules/alsa/module-alsa-card.c
@@ -64,7 +64,8 @@ PA_MODULE_USAGE(
         "tsched_buffer_size=<buffer size when using timer based scheduling> "
         "tsched_buffer_watermark=<lower fill watermark> "
         "profile=<profile name> "
-        "ignore_dB=<ignore dB information from the device?>");
+        "ignore_dB=<ignore dB information from the device?> "
+        "sync_volume=<syncronize sw and hw voluchanges in IO-thread?>");
 
 static const char* const valid_modargs[] = {
     "name",
@@ -86,6 +87,7 @@ static const char* const valid_modargs[] = {
     "tsched_buffer_watermark",
     "profile",
     "ignore_dB",
+    "sync_volume",
     NULL
 };
 
diff --git a/src/modules/alsa/module-alsa-sink.c b/src/modules/alsa/module-alsa-sink.c
index a73274f..697fab4 100644
--- a/src/modules/alsa/module-alsa-sink.c
+++ b/src/modules/alsa/module-alsa-sink.c
@@ -54,8 +54,11 @@ PA_MODULE_USAGE(
         "tsched_buffer_size=<buffer size when using timer based scheduling> "
         "tsched_buffer_watermark=<lower fill watermark> "
         "ignore_dB=<ignore dB information from the device?> "
-        "control=<name of mixer control>"
-        "rewind_safeguard=<number of bytes that cannot be rewound");
+        "control=<name of mixer control> "
+        "rewind_safeguard=<number of bytes that cannot be rewound> "
+        "sync_volume=<syncronize sw and hw voluchanges in IO-thread?> "
+        "sync_volume_safety_margin=<usec adjustment depending on volume direction> "
+        "sync_volume_extra_delay=<usec adjustment to HW volume changes>");
 
 static const char* const valid_modargs[] = {
     "name",
@@ -76,6 +79,9 @@ static const char* const valid_modargs[] = {
     "ignore_dB",
     "control",
     "rewind_safeguard",
+    "sync_volume",
+    "sync_volume_safety_margin",
+    "sync_volume_extra_delay",
     NULL
 };
 

commit 43b3f39a2f0f41d6041e3d082f7aaa3f0a565e92
Author: Jyri Sarha <jyri.sarha at nokia.com>
Date:   Fri Oct 15 13:05:16 2010 +0300

    udev-detect: Add sync_volume parameter
    
    Signed-off-by: Jyri Sarha <jyri.sarha at nokia.com>
    Reviewed-by: Tanu Kaskinen <tanu.kaskinen at digia.com>
    Reviewd-by: Colin Guthrie <cguthrie at mandriva.org>

diff --git a/src/modules/module-udev-detect.c b/src/modules/module-udev-detect.c
index f143c28..775094d 100644
--- a/src/modules/module-udev-detect.c
+++ b/src/modules/module-udev-detect.c
@@ -45,7 +45,8 @@ PA_MODULE_VERSION(PACKAGE_VERSION);
 PA_MODULE_LOAD_ONCE(TRUE);
 PA_MODULE_USAGE(
         "tsched=<enable system timer based scheduling mode?> "
-        "ignore_dB=<ignore dB information from the device?>");
+        "ignore_dB=<ignore dB information from the device?> "
+        "sync_volume=<syncronize sw and hw voluchanges in IO-thread?>");
 
 struct device {
     char *path;
@@ -62,6 +63,7 @@ struct userdata {
 
     pa_bool_t use_tsched:1;
     pa_bool_t ignore_dB:1;
+    pa_bool_t sync_volume:1;
 
     struct udev* udev;
     struct udev_monitor *monitor;
@@ -74,6 +76,7 @@ struct userdata {
 static const char* const valid_modargs[] = {
     "tsched",
     "ignore_dB",
+    "sync_volume",
     NULL
 };
 
@@ -386,12 +389,14 @@ static void card_changed(struct userdata *u, struct udev_device *dev) {
                                 "namereg_fail=false "
                                 "tsched=%s "
                                 "ignore_dB=%s "
+                                "sync_volume=%s "
                                 "card_properties=\"module-udev-detect.discovered=1\"",
                                 path_get_card_id(path),
                                 n,
                                 d->card_name,
                                 pa_yes_no(u->use_tsched),
-                                pa_yes_no(u->ignore_dB));
+                                pa_yes_no(u->ignore_dB),
+                                pa_yes_no(u->sync_volume));
     pa_xfree(n);
 
     pa_hashmap_put(u->devices, d->path, d);
@@ -661,7 +666,8 @@ int pa__init(pa_module *m) {
     struct udev_enumerate *enumerate = NULL;
     struct udev_list_entry *item = NULL, *first = NULL;
     int fd;
-    pa_bool_t use_tsched = TRUE, ignore_dB = FALSE;
+    pa_bool_t use_tsched = TRUE, ignore_dB = FALSE, sync_volume = FALSE;
+
 
     pa_assert(m);
 
@@ -687,6 +693,12 @@ int pa__init(pa_module *m) {
     }
     u->ignore_dB = ignore_dB;
 
+    if (pa_modargs_get_value_boolean(ma, "sync_volume", &sync_volume) < 0) {
+        pa_log("Failed to parse sync_volume= argument.");
+        goto fail;
+    }
+    u->sync_volume = sync_volume;
+
     if (!(u->udev = udev_new())) {
         pa_log("Failed to initialize udev library.");
         goto fail;

commit 3e53e3bba31fcf62fc7b50c5be5d1fe3f36955e0
Author: Jyri Sarha <jyri.sarha at nokia.com>
Date:   Fri Oct 15 13:05:17 2010 +0300

    daemon-conf: Add sync volume parameters to daemon-conf
    
    Signed-off-by: Jyri Sarha <jyri.sarha at nokia.com>
    Reviewed-by: Tanu Kaskinen <tanu.kaskinen at digia.com>
    Reviewd-by: Colin Guthrie <cguthrie at mandriva.org>

diff --git a/src/daemon/daemon-conf.c b/src/daemon/daemon-conf.c
index 79dd49a..74e8135 100644
--- a/src/daemon/daemon-conf.c
+++ b/src/daemon/daemon-conf.c
@@ -89,8 +89,11 @@ static const pa_daemon_conf default_conf = {
     .no_cpu_limit = TRUE,
     .disable_shm = FALSE,
     .lock_memory = FALSE,
+    .sync_volume = TRUE,
     .default_n_fragments = 4,
     .default_fragment_size_msec = 25,
+    .sync_volume_safety_margin_usec = 8000,
+    .sync_volume_extra_delay_usec = 0,
     .default_sample_spec = { .format = PA_SAMPLE_S16NE, .rate = 44100, .channels = 2 },
     .default_channel_map = { .channels = 2, .map = { PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT } },
     .shm_size = 0
@@ -508,6 +511,7 @@ int pa_daemon_conf_load(pa_daemon_conf *c, const char *filename) {
         { "enable-shm",                 pa_config_parse_not_bool, &c->disable_shm, NULL },
         { "flat-volumes",               pa_config_parse_bool,     &c->flat_volumes, NULL },
         { "lock-memory",                pa_config_parse_bool,     &c->lock_memory, NULL },
+        { "enable-sync-volume",         pa_config_parse_bool,     &c->sync_volume, NULL },
         { "exit-idle-time",             pa_config_parse_int,      &c->exit_idle_time, NULL },
         { "scache-idle-time",           pa_config_parse_int,      &c->scache_idle_time, NULL },
         { "realtime-priority",          parse_rtprio,             c, NULL },
@@ -523,6 +527,8 @@ int pa_daemon_conf_load(pa_daemon_conf *c, const char *filename) {
         { "default-channel-map",        parse_channel_map,        &ci,  NULL },
         { "default-fragments",          parse_fragments,          c, NULL },
         { "default-fragment-size-msec", parse_fragment_size_msec, c, NULL },
+        { "sync-volume-safety-margin-usec", pa_config_parse_unsigned, &c->sync_volume_safety_margin_usec, NULL },
+        { "sync-volume-extra-delay-usec", pa_config_parse_int, &c->sync_volume_extra_delay_usec, NULL },
         { "nice-level",                 parse_nice_level,         c, NULL },
         { "disable-remixing",           pa_config_parse_bool,     &c->disable_remixing, NULL },
         { "enable-remixing",            pa_config_parse_not_bool, &c->disable_remixing, NULL },
@@ -706,6 +712,7 @@ char *pa_daemon_conf_dump(pa_daemon_conf *c) {
     pa_strbuf_printf(s, "enable-shm = %s\n", pa_yes_no(!c->disable_shm));
     pa_strbuf_printf(s, "flat-volumes = %s\n", pa_yes_no(c->flat_volumes));
     pa_strbuf_printf(s, "lock-memory = %s\n", pa_yes_no(c->lock_memory));
+    pa_strbuf_printf(s, "enable-sync-volume = %s\n", pa_yes_no(c->sync_volume));
     pa_strbuf_printf(s, "exit-idle-time = %i\n", c->exit_idle_time);
     pa_strbuf_printf(s, "scache-idle-time = %i\n", c->scache_idle_time);
     pa_strbuf_printf(s, "dl-search-path = %s\n", pa_strempty(c->dl_search_path));
@@ -722,6 +729,8 @@ char *pa_daemon_conf_dump(pa_daemon_conf *c) {
     pa_strbuf_printf(s, "default-channel-map = %s\n", pa_channel_map_snprint(cm, sizeof(cm), &c->default_channel_map));
     pa_strbuf_printf(s, "default-fragments = %u\n", c->default_n_fragments);
     pa_strbuf_printf(s, "default-fragment-size-msec = %u\n", c->default_fragment_size_msec);
+    pa_strbuf_printf(s, "sync-volume-safety-margin-usec = %u\n", c->sync_volume_safety_margin_usec);
+    pa_strbuf_printf(s, "sync-volume-extra-delay-usec = %d\n", c->sync_volume_extra_delay_usec);
     pa_strbuf_printf(s, "shm-size-bytes = %lu\n", (unsigned long) c->shm_size);
     pa_strbuf_printf(s, "log-meta = %s\n", pa_yes_no(c->log_meta));
     pa_strbuf_printf(s, "log-time = %s\n", pa_yes_no(c->log_time));
diff --git a/src/daemon/daemon-conf.h b/src/daemon/daemon-conf.h
index 41c3c4b..9fd6aba 100644
--- a/src/daemon/daemon-conf.h
+++ b/src/daemon/daemon-conf.h
@@ -75,7 +75,8 @@ typedef struct pa_daemon_conf {
         log_meta,
         log_time,
         flat_volumes,
-        lock_memory;
+        lock_memory,
+        sync_volume;
     pa_server_type_t local_server_type;
     int exit_idle_time,
         scache_idle_time,
@@ -127,6 +128,8 @@ typedef struct pa_daemon_conf {
 #endif
 
     unsigned default_n_fragments, default_fragment_size_msec;
+    unsigned sync_volume_safety_margin_usec;
+    int sync_volume_extra_delay_usec;
     pa_sample_spec default_sample_spec;
     pa_channel_map default_channel_map;
     size_t shm_size;
diff --git a/src/daemon/daemon.conf.in b/src/daemon/daemon.conf.in
index 5de27ed..9beba85 100644
--- a/src/daemon/daemon.conf.in
+++ b/src/daemon/daemon.conf.in
@@ -80,3 +80,7 @@
 
 ; default-fragments = 4
 ; default-fragment-size-msec = 25
+
+; enable-sync-volume = yes
+; sync-volume-safety-margin-usec = 8000
+; sync-volume-extra-delay-usec = 0
diff --git a/src/daemon/main.c b/src/daemon/main.c
index 0e7b54a..f9fcc92 100644
--- a/src/daemon/main.c
+++ b/src/daemon/main.c
@@ -945,6 +945,8 @@ int main(int argc, char *argv[]) {
     c->default_channel_map = conf->default_channel_map;
     c->default_n_fragments = conf->default_n_fragments;
     c->default_fragment_size_msec = conf->default_fragment_size_msec;
+    c->sync_volume_safety_margin_usec = conf->sync_volume_safety_margin_usec;
+    c->sync_volume_extra_delay_usec = conf->sync_volume_extra_delay_usec;
     c->exit_idle_time = conf->exit_idle_time;
     c->scache_idle_time = conf->scache_idle_time;
     c->resample_method = conf->resample_method;
@@ -952,6 +954,7 @@ int main(int argc, char *argv[]) {
     c->realtime_scheduling = !!conf->realtime_scheduling;
     c->disable_remixing = !!conf->disable_remixing;
     c->disable_lfe_remixing = !!conf->disable_lfe_remixing;
+    c->sync_volume = !!conf->sync_volume;
     c->running_as_daemon = !!conf->daemonize;
     c->disallow_exit = conf->disallow_exit;
     c->flat_volumes = conf->flat_volumes;
diff --git a/src/modules/alsa/alsa-sink.c b/src/modules/alsa/alsa-sink.c
index 069ee95..b740d53 100644
--- a/src/modules/alsa/alsa-sink.c
+++ b/src/modules/alsa/alsa-sink.c
@@ -1831,6 +1831,7 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca
         goto fail;
     }
 
+    sync_volume = m->core->sync_volume;
     if (pa_modargs_get_value_boolean(ma, "sync_volume", &sync_volume) < 0) {
         pa_log("Failed to parse sync_volume argument.");
         goto fail;
diff --git a/src/modules/module-udev-detect.c b/src/modules/module-udev-detect.c
index 775094d..2eecc25 100644
--- a/src/modules/module-udev-detect.c
+++ b/src/modules/module-udev-detect.c
@@ -666,7 +666,7 @@ int pa__init(pa_module *m) {
     struct udev_enumerate *enumerate = NULL;
     struct udev_list_entry *item = NULL, *first = NULL;
     int fd;
-    pa_bool_t use_tsched = TRUE, ignore_dB = FALSE, sync_volume = FALSE;
+    pa_bool_t use_tsched = TRUE, ignore_dB = FALSE, sync_volume = m->core->sync_volume;
 
 
     pa_assert(m);
diff --git a/src/pulsecore/core.c b/src/pulsecore/core.c
index f072645..626ae65 100644
--- a/src/pulsecore/core.c
+++ b/src/pulsecore/core.c
@@ -117,6 +117,9 @@ pa_core* pa_core_new(pa_mainloop_api *m, pa_bool_t shared, size_t shm_size) {
     c->default_n_fragments = 4;
     c->default_fragment_size_msec = 25;
 
+    c->sync_volume_safety_margin_usec = 8000;
+    c->sync_volume_extra_delay_usec = 0;
+
     c->module_defer_unload_event = NULL;
     c->scache_auto_unload_event = NULL;
 
@@ -141,6 +144,7 @@ pa_core* pa_core_new(pa_mainloop_api *m, pa_bool_t shared, size_t shm_size) {
     c->realtime_priority = 5;
     c->disable_remixing = FALSE;
     c->disable_lfe_remixing = FALSE;
+    c->sync_volume = TRUE;
     c->resample_method = PA_RESAMPLER_SPEEX_FLOAT_BASE + 3;
 
     for (j = 0; j < PA_CORE_HOOK_MAX; j++)
diff --git a/src/pulsecore/core.h b/src/pulsecore/core.h
index 6088f91..a1215bb 100644
--- a/src/pulsecore/core.h
+++ b/src/pulsecore/core.h
@@ -142,6 +142,8 @@ struct pa_core {
     pa_channel_map default_channel_map;
     pa_sample_spec default_sample_spec;
     unsigned default_n_fragments, default_fragment_size_msec;
+    unsigned sync_volume_safety_margin_usec;
+    int sync_volume_extra_delay_usec;
 
     pa_defer_event *module_defer_unload_event;
 
@@ -165,6 +167,7 @@ struct pa_core {
     pa_bool_t realtime_scheduling:1;
     pa_bool_t disable_remixing:1;
     pa_bool_t disable_lfe_remixing:1;
+    pa_bool_t sync_volume:1;
 
     pa_resample_method_t resample_method;
     int realtime_priority;
diff --git a/src/pulsecore/sink.c b/src/pulsecore/sink.c
index a42a147..136508b 100644
--- a/src/pulsecore/sink.c
+++ b/src/pulsecore/sink.c
@@ -53,8 +53,6 @@
 #define ABSOLUTE_MIN_LATENCY (500)
 #define ABSOLUTE_MAX_LATENCY (10*PA_USEC_PER_SEC)
 #define DEFAULT_FIXED_LATENCY (250*PA_USEC_PER_MSEC)
-#define VOLUME_CHANGE_SAFETY_MARGIN_DEFAULT (8*PA_USEC_PER_MSEC)
-#define VOLUME_CHANGE_EXTRA_DELAY_DEFAULT (0*PA_USEC_PER_MSEC)
 
 PA_DEFINE_PUBLIC_CLASS(pa_sink, pa_msgobject);
 
@@ -333,8 +331,8 @@ pa_sink* pa_sink_new(
     PA_LLIST_HEAD_INIT(pa_sink_volume_change, s->thread_info.volume_changes);
     s->thread_info.volume_changes_tail = NULL;
     pa_sw_cvolume_multiply(&s->thread_info.current_hw_volume, &s->soft_volume, &s->real_volume);
-    s->thread_info.volume_change_safety_margin = VOLUME_CHANGE_SAFETY_MARGIN_DEFAULT;
-    s->thread_info.volume_change_extra_delay = VOLUME_CHANGE_EXTRA_DELAY_DEFAULT;
+    s->thread_info.volume_change_safety_margin = core->sync_volume_safety_margin_usec;
+    s->thread_info.volume_change_extra_delay = core->sync_volume_extra_delay_usec;
 
     /* FIXME: This should probably be moved to pa_sink_put() */
     pa_assert_se(pa_idxset_put(core->sinks, s, &s->index) >= 0);

commit 58bdd97260b8acdc07bedd4cc3112786cb43ddbe
Author: Jyri Sarha <jyri.sarha at nokia.com>
Date:   Fri Oct 15 13:05:18 2010 +0300

    man: sync_volume parameters to manual page
    
    Some wording teaks by Colin Guthrie and others.
    
    Signed-off-by: Jyri Sarha <jyri.sarha at nokia.com>
    Reviewed-by: Tanu Kaskinen <tanu.kaskinen at digia.com>

diff --git a/man/pulse-daemon.conf.5.xml.in b/man/pulse-daemon.conf.5.xml.in
index b5c9e92..0575f56 100644
--- a/man/pulse-daemon.conf.5.xml.in
+++ b/man/pulse-daemon.conf.5.xml.in
@@ -437,6 +437,36 @@ USA.
 
   </section>
 
+  <section name="Default Sync Volume Settings">
+
+    <p>With the flat volume feature enabled, the sink HW volume is set
+    to the same level as the highest volume input stream. Any other streams
+    (with lower volumes) have the appropriate adjustment applied in SW to
+    bring them to the correct overall level. Sadly hadware mixer changes
+    cannot be timed accurately and thus this change of volumes can somtimes
+    cause the resulting output sound to be momentarily too loud or too soft.
+    So to ensure SW and HW volumes are applied concurrently without any
+    glitches, their application needs to be synchronized. The sink
+    implementation needs to support synchornised volumes can use the following
+    parameters to refine the process.</p>
+
+    <option>
+      <p><opt>enable-sync-volume=</opt> Enable sync volume for the sinks that
+      support it. This feature is enabled by default.</p>
+    </option>
+    <option>
+      <p><opt>sync-volume-safety-margin-usec=</opt> The amount of time (in
+      usec) by which the HW volume increases are delayed and HW volume
+      decreases are advanced. Defaults to 8000 usec.</p>
+    </option>
+    <option>
+      <p><opt>sync-volume-extra-delay-usec=</opt> The amount of time (in usec)
+      by which HW volume changes are delayed. Negative values are also allowed.
+      Defaults to 0.</p>
+    </option>
+
+  </section>
+
   <section name="Authors">
     <p>The PulseAudio Developers &lt;@PACKAGE_BUGREPORT@&gt;; PulseAudio is available from <url href="@PACKAGE_URL@"/></p>
   </section>

commit 8aa332c6f5d976a6e8ee3928f280ffda59901837
Author: Daniel Mack <daniel at caiaq.de>
Date:   Fri Oct 15 18:18:43 2010 +0200

    alsa-mixer: add profile for Native Instruments Korecontroller

diff --git a/src/Makefile.am b/src/Makefile.am
index 4061b3c..cfd7f10 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1104,7 +1104,8 @@ dist_alsaprofilesets_DATA = \
 		modules/alsa/mixer/profile-sets/maudio-fasttrack-pro.conf \
 		modules/alsa/mixer/profile-sets/native-instruments-audio4dj.conf \
 		modules/alsa/mixer/profile-sets/native-instruments-audio8dj.conf \
-		modules/alsa/mixer/profile-sets/native-instruments-traktorkontrol-s4.conf
+		modules/alsa/mixer/profile-sets/native-instruments-traktorkontrol-s4.conf \
+		modules/alsa/mixer/profile-sets/native-instruments-korecontroller.conf
 
 if HAVE_UDEV
 dist_udevrules_DATA = \
diff --git a/src/modules/alsa/mixer/profile-sets/90-pulseaudio.rules b/src/modules/alsa/mixer/profile-sets/90-pulseaudio.rules
index 7a425b5..f964b00 100644
--- a/src/modules/alsa/mixer/profile-sets/90-pulseaudio.rules
+++ b/src/modules/alsa/mixer/profile-sets/90-pulseaudio.rules
@@ -23,6 +23,7 @@ KERNEL!="card*", GOTO="pulseaudio_end"
 SUBSYSTEMS=="usb", ATTRS{idVendor}=="17cc", ATTRS{idProduct}=="1978", ENV{PULSE_PROFILE_SET}="native-instruments-audio8dj.conf"
 SUBSYSTEMS=="usb", ATTRS{idVendor}=="17cc", ATTRS{idProduct}=="0839", ENV{PULSE_PROFILE_SET}="native-instruments-audio4dj.conf"
 SUBSYSTEMS=="usb", ATTRS{idVendor}=="17cc", ATTRS{idProduct}=="baff", ENV{PULSE_PROFILE_SET}="native-instruments-traktorkontrol-s4.conf"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="17cc", ATTRS{idProduct}=="4711", ENV{PULSE_PROFILE_SET}="native-instruments-korecontroller.conf"
 SUBSYSTEMS=="usb", ATTRS{idVendor}=="0763", ATTRS{idProduct}=="2012", ENV{PULSE_PROFILE_SET}="maudio-fasttrack-pro.conf"
 
 LABEL="pulseaudio_end"
diff --git a/src/modules/alsa/mixer/profile-sets/native-instruments-korecontroller.conf b/src/modules/alsa/mixer/profile-sets/native-instruments-korecontroller.conf
new file mode 100644
index 0000000..904357d
--- /dev/null
+++ b/src/modules/alsa/mixer/profile-sets/native-instruments-korecontroller.conf
@@ -0,0 +1,85 @@
+# This file is part of PulseAudio.
+#
+# 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.1 of the
+# License, or (at your option) any later version.
+#
+# PulseAudio is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with PulseAudio; if not, write to the Free Software Foundation,
+# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+
+; Native Instruments Kore Controller
+;
+; This card has one stereo pairs of input and two stereo pairs of
+; output, named "Master" and "Headphone". The master channel has
+; an additional Coax S/PDIF connector which is always on.
+;
+; We knowingly only define a subset of the theoretically possible
+; mapping combinations as profiles here.
+;
+; See default.conf for an explanation on the directives used here.
+
+[General]
+auto-profiles = no
+
+[Mapping analog-stereo-master-out]
+description = Analog Stereo Master Channel
+device-strings = hw:%f,0,0
+channel-map = left,right
+
+[Mapping analog-stereo-headphone-out]
+description = Analog Stereo Headphone Channel
+device-strings = hw:%f,0,1
+channel-map = left,right
+direction = output
+
+[Mapping analog-stereo-input]
+description = Analog Stereo
+device-strings = hw:%f,0,0
+channel-map = left,right
+direction = input
+
+[Profile output:analog-stereo-all+input:analog-stereo-all]
+description = Analog Stereo Duplex Master Output, Headphones Output
+output-mappings = analog-stereo-master-out analog-stereo-headphone-out
+input-mappings = analog-stereo-input
+priority = 100
+skip-probe = yes
+
+[Profile output:analog-stereo-master+input:analog-stereo-input]
+description = Analog Stereo Duplex Master Output
+output-mappings = analog-stereo-master-out
+input-mappings = analog-stereo-input
+priority = 40
+skip-probe = yes
+
+[Profile output:analog-stereo-headphone-out+input:analog-stereo-input]
+description = Analog Stereo Headphones Output
+output-mappings = analog-stereo-headphone-out
+input-mappings = analog-stereo-input
+priority = 30
+skip-probe = yes
+
+[Profile output:analog-stereo-master]
+description = Analog Stereo Master Output
+output-mappings = analog-stereo-master-out
+priority = 3
+skip-probe = yes
+
+[Profile output:analog-stereo-headphone]
+description = Analog Stereo Headphones Output
+output-mappings = analog-stereo-headphone-out
+priority = 2
+skip-probe = yes
+
+[Profile input:analog-stereo-input]
+description = Analog Stereo Input
+input-mappings = analog-stereo-input
+priority = 1
+skip-probe = yes

commit 13278bf23407d934b56ba06d460310beb64017eb
Author: Colin Guthrie <cguthrie at mandriva.org>
Date:   Sat Oct 16 12:26:32 2010 +0100

    intended-roles: Mark devices with a form factor of 'headset' as being appropriate for 'phone' streams
    
    This was a result a report by Patrick Ben Koetter relating to his
    Logitech Wireless Headset.

diff --git a/src/pulsecore/sink.c b/src/pulsecore/sink.c
index 136508b..7b4e626 100644
--- a/src/pulsecore/sink.c
+++ b/src/pulsecore/sink.c
@@ -2808,7 +2808,8 @@ pa_bool_t pa_device_init_intended_roles(pa_proplist *p) {
         return TRUE;
 
     if ((s = pa_proplist_gets(p, PA_PROP_DEVICE_FORM_FACTOR)))
-        if (pa_streq(s, "handset") || pa_streq(s, "hands-free")) {
+        if (pa_streq(s, "handset") || pa_streq(s, "hands-free")
+            || pa_streq(s, "headset")) {
             pa_proplist_sets(p, PA_PROP_DEVICE_INTENDED_ROLES, "phone");
             return TRUE;
         }

-- 
hooks/post-receive
PulseAudio Sound Server



More information about the pulseaudio-commits mailing list