From dffc4d18d3a9f608f8b316f1e7057d13978ef44f Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Tue, 17 May 2011 22:31:10 +0100 Subject: capture: Implement per-stream volume control for capture streams. This piggy backs onto the previous changes for protocol 22 and thus does not bump the version. This and the previous commits should be seen as mostly atomic. Apologies for any bisecting issues this causes (although I would expect these to be minimal) --- src/modules/alsa/alsa-source.c | 206 +++++++++++++++++++----- src/modules/alsa/module-alsa-source.c | 8 +- src/modules/bluetooth/module-bluetooth-device.c | 4 +- src/modules/dbus/iface-device.c | 2 +- src/modules/echo-cancel/module-echo-cancel.c | 18 +-- src/modules/module-virtual-source.c | 74 ++++----- src/modules/oss/module-oss.c | 8 +- 7 files changed, 214 insertions(+), 106 deletions(-) (limited to 'src/modules') diff --git a/src/modules/alsa/alsa-source.c b/src/modules/alsa/alsa-source.c index 3355fbdc..adec8f64 100644 --- a/src/modules/alsa/alsa-source.c +++ b/src/modules/alsa/alsa-source.c @@ -93,6 +93,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; @@ -114,8 +115,8 @@ struct userdata { pa_usec_t watermark_dec_not_before; - char *device_name; - char *control_device; + char *device_name; /* name of the PCM device */ + char *control_device; /* name of the control device */ pa_bool_t use_mmap:1, use_tsched:1; @@ -185,10 +186,10 @@ static int reserve_init(struct userdata *u, const char *dname) { if (pa_in_system_mode()) return 0; - /* We are resuming, try to lock the device */ if (!(rname = pa_alsa_get_reserve_name(dname))) return 0; + /* We are resuming, try to lock the device */ u->reserve = pa_reserve_wrapper_get(u->core, rname); pa_xfree(rname); @@ -238,10 +239,10 @@ static int reserve_monitor_init(struct userdata *u, const char *dname) { if (pa_in_system_mode()) return 0; - /* We are resuming, try to lock the device */ if (!(rname = pa_alsa_get_reserve_name(dname))) return 0; + /* We are resuming, try to lock the device */ u->monitor = pa_reserve_monitor_wrapper_get(u->core, rname); pa_xfree(rname); @@ -256,6 +257,7 @@ static int reserve_monitor_init(struct userdata *u, const char *dname) { static void fix_min_sleep_wakeup(struct userdata *u) { size_t max_use, max_use_2; + pa_assert(u); pa_assert(u->use_tsched); @@ -350,7 +352,7 @@ restart: u->watermark_dec_not_before = now + TSCHED_WATERMARK_VERIFY_AFTER_USEC; } -static pa_usec_t hw_sleep_time(struct userdata *u, pa_usec_t *sleep_usec, pa_usec_t*process_usec) { +static void hw_sleep_time(struct userdata *u, pa_usec_t *sleep_usec, pa_usec_t*process_usec) { pa_usec_t wm, usec; pa_assert(sleep_usec); @@ -378,8 +380,6 @@ static pa_usec_t hw_sleep_time(struct userdata *u, pa_usec_t *sleep_usec, pa_use (unsigned long) (*sleep_usec / PA_USEC_PER_MSEC), (unsigned long) (*process_usec / PA_USEC_PER_MSEC)); #endif - - return usec; } static int try_recover(struct userdata *u, const char *call, int err) { @@ -444,9 +444,9 @@ static size_t check_left_to_record(struct userdata *u, size_t n_bytes, pa_bool_t else if (left_to_record > u->watermark_dec_threshold) { reset_not_before = FALSE; - /* We decrease the watermark only if have actually been - * woken up by a timeout. If something else woke us up - * it's too easy to fulfill the deadlines... */ + /* We decrease the watermark only if have actually + * been woken up by a timeout. If something else woke + * us up it's too easy to fulfill the deadlines... */ if (on_timeout) decrease_watermark(u); @@ -521,6 +521,7 @@ static int mmap_read(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled break; } + if (++j > 10) { #ifdef DEBUG_TIMING pa_log_debug("Not filling up, because already too many iterations."); @@ -536,15 +537,14 @@ static int mmap_read(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled #endif for (;;) { + pa_memchunk chunk; + void *p; int err; const snd_pcm_channel_area_t *areas; snd_pcm_uframes_t offset, frames; - pa_memchunk chunk; - void *p; snd_pcm_sframes_t sframes; frames = (snd_pcm_uframes_t) (n_bytes / u->frame_size); - /* pa_log_debug("%lu frames to read", (unsigned long) frames); */ if (PA_UNLIKELY((err = pa_alsa_safe_mmap_begin(u->pcm_handle, &areas, &offset, &frames, u->hwbuf_size, &u->source->sample_spec)) < 0)) { @@ -822,6 +822,7 @@ static int build_pollfd(struct userdata *u) { return 0; } +/* Called from IO context */ static int suspend(struct userdata *u) { pa_assert(u); pa_assert(u->pcm_handle); @@ -842,6 +843,7 @@ static int suspend(struct userdata *u) { return 0; } +/* Called from IO context */ static int update_sw_params(struct userdata *u) { snd_pcm_uframes_t avail_min; int err; @@ -894,6 +896,7 @@ static int update_sw_params(struct userdata *u) { return 0; } +/* Called from IO context */ static int unsuspend(struct userdata *u) { pa_sample_spec ss; int err; @@ -971,6 +974,7 @@ fail: return -PA_ERR_IO; } +/* Called from IO context */ static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { struct userdata *u = PA_SOURCE(o)->userdata; @@ -993,6 +997,7 @@ static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t off case PA_SOURCE_SUSPENDED: { int r; + pa_assert(PA_SOURCE_IS_OPENED(u->source->thread_info.state)); if ((r = suspend(u)) < 0) @@ -1049,7 +1054,7 @@ static int source_set_state_cb(pa_source *s, pa_source_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); @@ -1069,6 +1074,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->source->suspend_cause & PA_SUSPEND_SESSION) + return 0; + + if (mask & SND_CTL_EVENT_MASK_VALUE) + pa_source_update_volume_and_mute(u->source); + + return 0; +} + static void source_get_volume_cb(pa_source *s) { struct userdata *u = s->userdata; pa_cvolume r; @@ -1095,7 +1118,7 @@ static void source_get_volume_cb(pa_source *s) { if (pa_cvolume_equal(&u->hardware_volume, &r)) return; - s->volume = u->hardware_volume = r; + s->real_volume = u->hardware_volume = r; /* Hmm, so the hardware volume changed, let's reset our software volume */ if (u->mixer_path->has_dB) @@ -1106,15 +1129,16 @@ static void source_set_volume_cb(pa_source *s) { struct userdata *u = s->userdata; pa_cvolume r; char vol_str_pcnt[PA_CVOLUME_SNPRINT_MAX]; + pa_bool_t write_to_hw = (s->flags & PA_SOURCE_SYNC_VOLUME) ? FALSE : TRUE; pa_assert(u); pa_assert(u->mixer_path); pa_assert(u->mixer_handle); /* Shift up by the base volume */ - pa_sw_cvolume_divide_scalar(&r, &s->volume, s->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, TRUE) < 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 */ @@ -1128,7 +1152,7 @@ static void source_set_volume_cb(pa_source *s) { char vol_str_db[PA_SW_CVOLUME_SNPRINT_DB_MAX]; /* Match exactly what the user requested by software */ - pa_sw_cvolume_divide(&new_soft_volume, &s->volume, &u->hardware_volume); + pa_sw_cvolume_divide(&new_soft_volume, &s->real_volume, &u->hardware_volume); /* If the adjustment to do in software is only minimal we * can skip it. That saves us CPU at the expense of a bit of @@ -1137,8 +1161,8 @@ static void source_set_volume_cb(pa_source *s) { (pa_cvolume_min(&new_soft_volume) >= (PA_VOLUME_NORM - VOLUME_ACCURACY)) && (pa_cvolume_max(&new_soft_volume) <= (PA_VOLUME_NORM + VOLUME_ACCURACY)); - pa_log_debug("Requested volume: %s", pa_cvolume_snprint(vol_str_pcnt, sizeof(vol_str_pcnt), &s->volume)); - pa_log_debug(" in dB: %s", pa_sw_cvolume_snprint_dB(vol_str_db, sizeof(vol_str_db), &s->volume)); + pa_log_debug("Requested volume: %s", pa_cvolume_snprint(vol_str_pcnt, sizeof(vol_str_pcnt), &s->real_volume)); + pa_log_debug(" in dB: %s", pa_sw_cvolume_snprint_dB(vol_str_db, sizeof(vol_str_db), &s->real_volume)); pa_log_debug("Got hardware volume: %s", pa_cvolume_snprint(vol_str_pcnt, sizeof(vol_str_pcnt), &u->hardware_volume)); pa_log_debug(" in dB: %s", pa_sw_cvolume_snprint_dB(vol_str_db, sizeof(vol_str_db), &u->hardware_volume)); pa_log_debug("Calculated software volume: %s (accurate-enough=%s)", @@ -1155,7 +1179,49 @@ static void source_set_volume_cb(pa_source *s) { /* We can't match exactly what the user requested, hence let's * at least tell the user about it */ - s->volume = r; + s->real_volume = r; + } +} + +static void source_write_volume_cb(pa_source *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_SOURCE_SYNC_VOLUME); + + /* Shift up by the base volume */ + pa_sw_cvolume_divide_scalar(&hw_vol, &hw_vol, s->base_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; + + /* Shift down by the base volume, so that 0dB becomes maximum volume */ + pa_sw_cvolume_multiply_scalar(&hw_vol, &hw_vol, s->base_volume); + + 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) { + union { + char db[2][PA_SW_CVOLUME_SNPRINT_DB_MAX]; + char pcnt[2][PA_CVOLUME_SNPRINT_MAX]; + } vol; + + pa_log_debug("Written HW volume did not match with the request: %s (request) != %s", + pa_cvolume_snprint(vol.pcnt[0], sizeof(vol.pcnt[0]), &s->thread_info.current_hw_volume), + pa_cvolume_snprint(vol.pcnt[1], sizeof(vol.pcnt[1]), &hw_vol)); + pa_log_debug(" in dB: %s (request) != %s", + pa_sw_cvolume_snprint_dB(vol.db[0], sizeof(vol.db[0]), &s->thread_info.current_hw_volume), + pa_sw_cvolume_snprint_dB(vol.db[1], sizeof(vol.db[1]), &hw_vol)); + } } } @@ -1220,7 +1286,9 @@ static int source_set_port_cb(pa_source *s, pa_device_port *p) { static void source_update_requested_latency_cb(pa_source *s) { struct userdata *u = s->userdata; pa_assert(u); - pa_assert(u->use_tsched); + pa_assert(u->use_tsched); /* only when timer scheduling is used + * we can dynamically adjust the + * latency */ if (!u->pcm_handle) return; @@ -1243,6 +1311,7 @@ static void thread_func(void *userdata) { for (;;) { int ret; + pa_usec_t rtpoll_sleep = 0; #ifdef DEBUG_TIMING pa_log_debug("Loop"); @@ -1291,17 +1360,29 @@ 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); } - } else if (u->use_tsched) + } + + if (u->source->flags & PA_SOURCE_SYNC_VOLUME) { + pa_usec_t volume_sleep; + pa_source_volume_change_apply(u->source, &volume_sleep); + if (volume_sleep > 0) + rtpoll_sleep = PA_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->source->flags & PA_SOURCE_SYNC_VOLUME) + pa_source_volume_change_apply(u->source, NULL); + if (ret == 0) goto finish; @@ -1419,7 +1500,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) @@ -1475,8 +1556,17 @@ static int setup_mixer(struct userdata *u, pa_bool_t ignore_dB) { u->source->get_volume = source_get_volume_cb; u->source->set_volume = source_set_volume_cb; + u->source->write_volume = source_write_volume_cb; + + u->source->flags |= PA_SOURCE_HW_VOLUME_CTRL; + if (u->mixer_path->has_dB) { + u->source->flags |= PA_SOURCE_DECIBEL_VOLUME; + if (sync_volume) { + u->source->flags |= PA_SOURCE_SYNC_VOLUME; + pa_log_info("Successfully enabled synchronous volume."); + } + } - u->source->flags |= PA_SOURCE_HW_VOLUME_CTRL | (u->mixer_path->has_dB ? PA_SOURCE_DECIBEL_VOLUME : 0); pa_log_info("Using hardware volume control. Hardware dB scale %s.", u->mixer_path->has_dB ? "supported" : "not supported"); } @@ -1489,17 +1579,31 @@ 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 (u->source->flags & (PA_SOURCE_HW_VOLUME_CTRL|PA_SOURCE_HW_MUTE_CTRL)) { + int (*mixer_callback)(snd_mixer_elem_t *, unsigned int); + if (u->source->flags & PA_SOURCE_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 (u->mixer_path_set) - pa_alsa_path_set_set_callback(u->mixer_path_set, u->mixer_handle, mixer_callback, u); - else - pa_alsa_path_set_callback(u->mixer_path, u->mixer_handle, mixer_callback, u); + 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) + pa_alsa_path_set_set_callback(u->mixer_path_set, u->mixer_handle, mixer_callback, u); + else + pa_alsa_path_set_callback(u->mixer_path, u->mixer_handle, mixer_callback, u); + } return 0; } @@ -1513,7 +1617,7 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p 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, namereg_fail = FALSE; + pa_bool_t use_mmap = TRUE, b, use_tsched = TRUE, d, ignore_dB = FALSE, namereg_fail = FALSE, sync_volume = FALSE; pa_source_new_data data; pa_alsa_profile_set *profile_set = NULL; @@ -1523,7 +1627,7 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p ss = m->core->default_sample_spec; map = m->core->default_channel_map; if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_ALSA) < 0) { - pa_log("Failed to parse sample specification"); + pa_log("Failed to parse sample specification and channel map"); goto fail; } @@ -1557,7 +1661,7 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p } if (pa_modargs_get_value_boolean(ma, "tsched", &use_tsched) < 0) { - pa_log("Failed to parse timer_scheduling argument."); + pa_log("Failed to parse tsched argument."); goto fail; } @@ -1566,6 +1670,12 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p 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; + } + use_tsched = pa_alsa_may_tsched(use_tsched); u = pa_xnew0(struct userdata, 1); @@ -1728,6 +1838,18 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p goto fail; } + if (pa_modargs_get_value_u32(ma, "sync_volume_safety_margin", + &u->source->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->source->thread_info.volume_change_extra_delay) < 0) { + pa_log("Failed to parse sync_volume_extra_delay parameter"); + goto fail; + } + u->source->parent.process_msg = source_process_msg; if (u->use_tsched) u->source->update_requested_latency = source_update_requested_latency_cb; @@ -1776,7 +1898,7 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p 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); @@ -1785,6 +1907,7 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p pa_log("Failed to create thread."); goto fail; } + /* Get initial mixer settings */ if (data.volume_is_set) { if (u->source->set_volume) @@ -1836,6 +1959,9 @@ static void userdata_free(struct userdata *u) { if (u->source) pa_source_unref(u->source); + 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/module-alsa-source.c b/src/modules/alsa/module-alsa-source.c index 90ffea57..478a2e8c 100644 --- a/src/modules/alsa/module-alsa-source.c +++ b/src/modules/alsa/module-alsa-source.c @@ -79,7 +79,10 @@ PA_MODULE_USAGE( "tsched_buffer_size= " "tsched_buffer_watermark= " "ignore_dB= " - "control="); + "control=" + "sync_volume= " + "sync_volume_safety_margin= " + "sync_volume_extra_delay="); static const char* const valid_modargs[] = { "name", @@ -100,6 +103,9 @@ static const char* const valid_modargs[] = { "tsched_buffer_watermark", "ignore_dB", "control", + "sync_volume", + "sync_volume_safety_margin", + "sync_volume_extra_delay", NULL }; diff --git a/src/modules/bluetooth/module-bluetooth-device.c b/src/modules/bluetooth/module-bluetooth-device.c index 1ff752a9..0639afd6 100644 --- a/src/modules/bluetooth/module-bluetooth-device.c +++ b/src/modules/bluetooth/module-bluetooth-device.c @@ -1868,7 +1868,7 @@ static void source_set_volume_cb(pa_source *s) { pa_assert(u->source == s); pa_assert(u->profile == PROFILE_HSP); - gain = (pa_cvolume_max(&s->volume) * HSP_MAX_GAIN) / PA_VOLUME_NORM; + gain = (pa_cvolume_max(&s->real_volume) * HSP_MAX_GAIN) / PA_VOLUME_NORM; if (gain > HSP_MAX_GAIN) gain = HSP_MAX_GAIN; @@ -1879,7 +1879,7 @@ static void source_set_volume_cb(pa_source *s) { if (volume < PA_VOLUME_NORM) volume++; - pa_cvolume_set(&s->volume, u->sample_spec.channels, volume); + pa_cvolume_set(&s->real_volume, u->sample_spec.channels, volume); pa_assert_se(m = dbus_message_new_method_call("org.bluez", u->path, "org.bluez.Headset", "SetMicrophoneGain")); pa_assert_se(dbus_message_append_args(m, DBUS_TYPE_UINT16, &gain, DBUS_TYPE_INVALID)); diff --git a/src/modules/dbus/iface-device.c b/src/modules/dbus/iface-device.c index c5ba88e0..652790f0 100644 --- a/src/modules/dbus/iface-device.c +++ b/src/modules/dbus/iface-device.c @@ -447,7 +447,7 @@ static void handle_set_volume(DBusConnection *conn, DBusMessage *msg, DBusMessag if (d->type == DEVICE_TYPE_SINK) pa_sink_set_volume(d->sink, &new_vol, TRUE, TRUE); else - pa_source_set_volume(d->source, &new_vol, TRUE); + pa_source_set_volume(d->source, &new_vol, TRUE, TRUE); pa_dbus_send_empty_reply(conn, msg); } diff --git a/src/modules/echo-cancel/module-echo-cancel.c b/src/modules/echo-cancel/module-echo-cancel.c index 3d22ef84..e5ee5b13 100644 --- a/src/modules/echo-cancel/module-echo-cancel.c +++ b/src/modules/echo-cancel/module-echo-cancel.c @@ -526,8 +526,7 @@ static void source_set_volume_cb(pa_source *s) { !PA_SOURCE_OUTPUT_IS_LINKED(pa_source_output_get_state(u->source_output))) return; - /* FIXME, no volume control in source_output, set volume at the master */ - pa_source_set_volume(u->source_output->source, &s->volume, TRUE); + pa_source_output_set_volume(u->source_output, &s->real_volume, s->save_volume, TRUE); } /* Called from main context */ @@ -546,6 +545,7 @@ static void sink_set_volume_cb(pa_sink *s) { static void source_get_volume_cb(pa_source *s) { struct userdata *u; + pa_cvolume v; pa_source_assert_ref(s); pa_assert_se(u = s->userdata); @@ -554,18 +554,16 @@ static void source_get_volume_cb(pa_source *s) { !PA_SOURCE_OUTPUT_IS_LINKED(pa_source_output_get_state(u->source_output))) return; - /* FIXME, no volume control in source_output, get the info from the master */ - pa_source_get_volume(u->source_output->source, TRUE); + pa_source_output_get_volume(u->source_output, &v, TRUE); - if (pa_cvolume_equal(&s->volume,&u->source_output->source->volume)) + if (pa_cvolume_equal(&s->real_volume, &v)) /* no change */ return; - s->volume = u->source_output->source->volume; + s->real_volume = v; pa_source_set_soft_volume(s, NULL); } - /* Called from main context */ static void source_set_mute_cb(pa_source *s) { struct userdata *u; @@ -577,8 +575,7 @@ static void source_set_mute_cb(pa_source *s) { !PA_SOURCE_OUTPUT_IS_LINKED(pa_source_output_get_state(u->source_output))) return; - /* FIXME, no volume control in source_output, set mute at the master */ - pa_source_set_mute(u->source_output->source, TRUE, TRUE); + pa_source_output_set_mute(u->source_output, s->muted, s->save_muted); } /* Called from main context */ @@ -606,8 +603,7 @@ static void source_get_mute_cb(pa_source *s) { !PA_SOURCE_OUTPUT_IS_LINKED(pa_source_output_get_state(u->source_output))) return; - /* FIXME, no volume control in source_output, get the info from the master */ - pa_source_get_mute(u->source_output->source, TRUE); + pa_source_output_get_mute(u->source_output); } /* must be called from the input thread context */ diff --git a/src/modules/module-virtual-source.c b/src/modules/module-virtual-source.c index 170fa4e0..680e71a1 100644 --- a/src/modules/module-virtual-source.c +++ b/src/modules/module-virtual-source.c @@ -61,6 +61,8 @@ PA_MODULE_USAGE( "rate= " "channels= " "channel_map= " + "use_volume_sharing= " + "force_flat_volume= " )); #define MEMBLOCKQ_MAXLENGTH (16*1024*1024) @@ -96,6 +98,8 @@ static const char* const valid_modargs[] = { "rate", "channels", "channel_map", + "use_volume_sharing", + "force_flat_volume", NULL }; @@ -241,32 +245,9 @@ static void source_set_volume_cb(pa_source *s) { !PA_SOURCE_OUTPUT_IS_LINKED(pa_source_output_get_state(u->source_output))) return; - /* FIXME, no volume control in source_output, set volume at the master */ - pa_source_set_volume(u->source_output->source, &s->volume, TRUE); + pa_source_output_set_volume(u->source_output, &s->real_volume, s->save_volume, TRUE); } -static void source_get_volume_cb(pa_source *s) { - struct userdata *u; - - pa_source_assert_ref(s); - pa_assert_se(u = s->userdata); - - if (!PA_SOURCE_IS_LINKED(pa_source_get_state(s)) || - !PA_SOURCE_OUTPUT_IS_LINKED(pa_source_output_get_state(u->source_output))) - return; - - /* FIXME, no volume control in source_output, get the info from the master */ - pa_source_get_volume(u->source_output->source, TRUE); - - if (pa_cvolume_equal(&s->volume,&u->source_output->source->volume)) - /* no change */ - return; - - s->volume = u->source_output->source->volume; - pa_source_set_soft_volume(s, NULL); -} - - /* Called from main context */ static void source_set_mute_cb(pa_source *s) { struct userdata *u; @@ -278,23 +259,7 @@ static void source_set_mute_cb(pa_source *s) { !PA_SOURCE_OUTPUT_IS_LINKED(pa_source_output_get_state(u->source_output))) return; - /* FIXME, no volume control in source_output, set mute at the master */ - pa_source_set_mute(u->source_output->source, TRUE, TRUE); -} - -/* Called from main context */ -static void source_get_mute_cb(pa_source *s) { - struct userdata *u; - - pa_source_assert_ref(s); - pa_assert_se(u = s->userdata); - - if (!PA_SOURCE_IS_LINKED(pa_source_get_state(s)) || - !PA_SOURCE_OUTPUT_IS_LINKED(pa_source_output_get_state(u->source_output))) - return; - - /* FIXME, no volume control in source_output, get the info from the master */ - pa_source_get_mute(u->source_output->source, TRUE); + pa_source_output_set_mute(u->source_output, s->muted, s->save_muted); } /* Called from input thread context */ @@ -538,6 +503,8 @@ int pa__init(pa_module*m) { pa_source *master=NULL; pa_source_output_new_data source_output_data; pa_source_new_data source_data; + pa_bool_t use_volume_sharing = FALSE; + pa_bool_t force_flat_volume = FALSE; /* optional for uplink_sink */ pa_sink_new_data sink_data; @@ -565,6 +532,20 @@ int pa__init(pa_module*m) { goto fail; } + if (pa_modargs_get_value_boolean(ma, "use_volume_sharing", &use_volume_sharing) < 0) { + pa_log("use_volume_sharing= expects a boolean argument"); + goto fail; + } + + if (pa_modargs_get_value_boolean(ma, "force_flat_volume", &force_flat_volume) < 0) { + pa_log("force_flat_volume= expects a boolean argument"); + goto fail; + } + + if (use_volume_sharing && force_flat_volume) { + pa_log("Flat volume can't be forced when using volume sharing."); + goto fail; + } u = pa_xnew0(struct userdata, 1); if (!u) { @@ -605,9 +586,10 @@ int pa__init(pa_module*m) { pa_proplist_setf(source_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Virtual Source %s on %s", source_data.name, z ? z : master->name); } - u->source = pa_source_new(m->core, &source_data, - PA_SOURCE_HW_MUTE_CTRL|PA_SOURCE_HW_VOLUME_CTRL|PA_SOURCE_DECIBEL_VOLUME| - (master->flags & (PA_SOURCE_LATENCY|PA_SOURCE_DYNAMIC_LATENCY))); + u->source = pa_source_new(m->core, &source_data, (master->flags & (PA_SOURCE_LATENCY|PA_SOURCE_DYNAMIC_LATENCY)) + | (use_volume_sharing ? PA_SOURCE_SHARE_VOLUME_WITH_MASTER : 0) + | (force_flat_volume ? PA_SOURCE_FLAT_VOLUME : 0)); + pa_source_new_data_done(&source_data); if (!u->source) { @@ -618,10 +600,8 @@ int pa__init(pa_module*m) { u->source->parent.process_msg = source_process_msg_cb; u->source->set_state = source_set_state_cb; u->source->update_requested_latency = source_update_requested_latency_cb; - u->source->set_volume = source_set_volume_cb; + u->source->set_volume = use_volume_sharing ? NULL : source_set_volume_cb; u->source->set_mute = source_set_mute_cb; - u->source->get_volume = source_get_volume_cb; - u->source->get_mute = source_get_mute_cb; u->source->userdata = u; pa_source_set_asyncmsgq(u->source, master->asyncmsgq); diff --git a/src/modules/oss/module-oss.c b/src/modules/oss/module-oss.c index 84dbbdaf..dea5628f 100644 --- a/src/modules/oss/module-oss.c +++ b/src/modules/oss/module-oss.c @@ -848,11 +848,11 @@ static void source_get_volume(pa_source *s) { pa_assert(u->mixer_devmask & (SOUND_MASK_IGAIN|SOUND_MASK_RECLEV)); if (u->mixer_devmask & SOUND_MASK_IGAIN) - if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_IGAIN, &s->sample_spec, &s->volume) >= 0) + if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_IGAIN, &s->sample_spec, &s->real_volume) >= 0) return; if (u->mixer_devmask & SOUND_MASK_RECLEV) - if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_RECLEV, &s->sample_spec, &s->volume) >= 0) + if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_RECLEV, &s->sample_spec, &s->real_volume) >= 0) return; pa_log_info("Device doesn't support reading mixer settings: %s", pa_cstrerror(errno)); @@ -866,11 +866,11 @@ static void source_set_volume(pa_source *s) { pa_assert(u->mixer_devmask & (SOUND_MASK_IGAIN|SOUND_MASK_RECLEV)); if (u->mixer_devmask & SOUND_MASK_IGAIN) - if (pa_oss_set_volume(u->mixer_fd, SOUND_MIXER_WRITE_IGAIN, &s->sample_spec, &s->volume) >= 0) + if (pa_oss_set_volume(u->mixer_fd, SOUND_MIXER_WRITE_IGAIN, &s->sample_spec, &s->real_volume) >= 0) return; if (u->mixer_devmask & SOUND_MASK_RECLEV) - if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_WRITE_RECLEV, &s->sample_spec, &s->volume) >= 0) + if (pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_WRITE_RECLEV, &s->sample_spec, &s->real_volume) >= 0) return; pa_log_info("Device doesn't support writing mixer settings: %s", pa_cstrerror(errno)); -- cgit