[pulseaudio-discuss] [PATCH 2/5] alsa-sink: Take syncronized HW volume infra into use

Jyri Sarha oku at iki.fi
Sun Feb 28 05:13:06 PST 2010


From: Jyri Sarha <jyri.sarha at nokia.com>

---
 src/modules/alsa/alsa-mixer.c       |   38 +++++++++++++---
 src/modules/alsa/alsa-mixer.h       |    2 +-
 src/modules/alsa/alsa-sink.c        |   81 +++++++++++++++++++++++++++++++---
 src/modules/alsa/alsa-source.c      |    2 +-
 src/modules/alsa/module-alsa-sink.c |    8 +++-
 5 files changed, 113 insertions(+), 18 deletions(-)

diff --git a/src/modules/alsa/alsa-mixer.c b/src/modules/alsa/alsa-mixer.c
index 93f2ed0..0e77a4b 100644
--- a/src/modules/alsa/alsa-mixer.c
+++ b/src/modules/alsa/alsa-mixer.c
@@ -671,7 +671,13 @@ 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 +726,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 +800,13 @@ 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 +831,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..ce95edf 100644
--- a/src/modules/alsa/alsa-mixer.h
+++ b/src/modules/alsa/alsa-mixer.h
@@ -202,7 +202,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);
diff --git a/src/modules/alsa/alsa-sink.c b/src/modules/alsa/alsa-sink.c
index 581b943..45ba24a 100644
--- a/src/modules/alsa/alsa-sink.c
+++ b/src/modules/alsa/alsa-sink.c
@@ -1160,6 +1160,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);
@@ -1168,7 +1169,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 */
@@ -1208,6 +1209,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("Writting 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;
@@ -1373,6 +1401,7 @@ static void thread_func(void *userdata) {
 
     for (;;) {
         int ret;
+        pa_usec_t rtpoll_sleep = 0;
 
 #ifdef DEBUG_TIMING
         pa_log_debug("Loop");
@@ -1439,21 +1468,33 @@ 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->first = FALSE;
             u->after_rewind = FALSE;
 
-        } else if (u->use_tsched)
+        }
 
-            /* OK, we're in an invalid state, let's disable our timers */
+        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);
+        }
+
+        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;
 
@@ -1572,7 +1613,7 @@ 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) {
     pa_assert(u);
 
     if (!u->mixer_handle)
@@ -1631,8 +1672,15 @@ 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;
+
+        if (sync_volume) {
+            u->sink->flags |= PA_SINK_HW_VOLUME_CTRL |
+                (u->mixer_path->has_dB ? (PA_SINK_DECIBEL_VOLUME | PA_SINK_SYNC_VOLUME) : 0);
+            pa_log_info("Successfully enabled synchronous volume.");
+        } else
+            u->sink->flags |= PA_SINK_HW_VOLUME_CTRL | (u->mixer_path->has_dB ? PA_SINK_DECIBEL_VOLUME : 0);
 
-        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");
     }
 
@@ -1669,7 +1717,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;
     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;
+    pa_bool_t use_mmap = TRUE, b, use_tsched = TRUE, d, ignore_dB = FALSE, sync_volume = FALSE;
     pa_sink_new_data data;
     pa_alsa_profile_set *profile_set = NULL;
 
@@ -1722,6 +1770,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);
@@ -1873,6 +1926,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;
@@ -1924,7 +1989,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);
diff --git a/src/modules/alsa/alsa-source.c b/src/modules/alsa/alsa-source.c
index 1022d0d..c4adc80 100644
--- a/src/modules/alsa/alsa-source.c
+++ b/src/modules/alsa/alsa-source.c
@@ -1105,7 +1105,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-sink.c b/src/modules/alsa/module-alsa-sink.c
index 3aa89b2..d4074b9 100644
--- a/src/modules/alsa/module-alsa-sink.c
+++ b/src/modules/alsa/module-alsa-sink.c
@@ -54,7 +54,10 @@ 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>");
+        "control=<name of mixer control> "
+        "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",
@@ -74,6 +77,9 @@ static const char* const valid_modargs[] = {
     "tsched_buffer_watermark",
     "ignore_dB",
     "control",
+    "sync_volume",
+    "sync_volume_safety_margin",
+    "sync_volume_extra_delay",
     NULL
 };
 
-- 
1.7.0




More information about the pulseaudio-discuss mailing list