From: Jyri Sarha <jyri.sarha@xxxxxxxxx> --- 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