summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/map-file2
-rw-r--r--src/modules/alsa/alsa-source.c206
-rw-r--r--src/modules/alsa/module-alsa-source.c8
-rw-r--r--src/modules/bluetooth/module-bluetooth-device.c4
-rw-r--r--src/modules/dbus/iface-device.c2
-rw-r--r--src/modules/echo-cancel/module-echo-cancel.c18
-rw-r--r--src/modules/module-virtual-source.c74
-rw-r--r--src/modules/oss/module-oss.c8
-rw-r--r--src/pulse/introspect.c61
-rw-r--r--src/pulse/introspect.h15
-rw-r--r--src/pulse/stream.c38
-rw-r--r--src/pulsecore/cli-command.c2
-rw-r--r--src/pulsecore/native-common.h4
-rw-r--r--src/pulsecore/pdispatch.c7
-rw-r--r--src/pulsecore/protocol-native.c78
-rw-r--r--src/pulsecore/sink.c6
-rw-r--r--src/pulsecore/source-output.c523
-rw-r--r--src/pulsecore/source-output.h74
-rw-r--r--src/pulsecore/source.c993
-rw-r--r--src/pulsecore/source.h50
20 files changed, 1963 insertions, 210 deletions
diff --git a/src/map-file b/src/map-file
index 026ac10f..a9d9aac4 100644
--- a/src/map-file
+++ b/src/map-file
@@ -103,6 +103,8 @@ pa_context_set_sink_port_by_index;
pa_context_set_sink_port_by_name;
pa_context_set_sink_volume_by_index;
pa_context_set_sink_volume_by_name;
+pa_context_set_source_output_mute;
+pa_context_set_source_output_volume;
pa_context_set_source_mute_by_index;
pa_context_set_source_mute_by_name;
pa_context_set_source_port_by_index;
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=<buffer size when using timer based scheduling> "
"tsched_buffer_watermark=<upper 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",
@@ -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=<sample rate> "
"channels=<number of channels> "
"channel_map=<channel map> "
+ "use_volume_sharing=<yes or no> "
+ "force_flat_volume=<yes or no> "
));
#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));
diff --git a/src/pulse/introspect.c b/src/pulse/introspect.c
index a781c14f..d6ad0f50 100644
--- a/src/pulse/introspect.c
+++ b/src/pulse/introspect.c
@@ -1164,7 +1164,7 @@ static void context_get_source_output_info_callback(pa_pdispatch *pd, uint32_t c
while (!pa_tagstruct_eof(t)) {
pa_source_output_info i;
- pa_bool_t corked = FALSE;
+ pa_bool_t mute = FALSE, corked = FALSE, has_volume = FALSE, volume_writable = TRUE;
pa_zero(i);
i.proplist = pa_proplist_new();
@@ -1181,14 +1181,21 @@ static void context_get_source_output_info_callback(pa_pdispatch *pd, uint32_t c
pa_tagstruct_gets(t, &i.resample_method) < 0 ||
pa_tagstruct_gets(t, &i.driver) < 0 ||
(o->context->version >= 13 && pa_tagstruct_get_proplist(t, i.proplist) < 0) ||
- (o->context->version >= 19 && pa_tagstruct_get_boolean(t, &corked) < 0)) {
+ (o->context->version >= 19 && pa_tagstruct_get_boolean(t, &corked) < 0) ||
+ (o->context->version >= 22 && (pa_tagstruct_get_cvolume(t, &i.volume) < 0 ||
+ pa_tagstruct_get_boolean(t, &mute) < 0 ||
+ pa_tagstruct_get_boolean(t, &has_volume) < 0 ||
+ pa_tagstruct_get_boolean(t, &volume_writable) < 0))) {
pa_context_fail(o->context, PA_ERR_PROTOCOL);
pa_proplist_free(i.proplist);
goto finish;
}
+ i.mute = (int) mute;
i.corked = (int) corked;
+ i.has_volume = (int) has_volume;
+ i.volume_writable = (int) volume_writable;
if (o->callback) {
pa_source_output_info_cb_t cb = (pa_source_output_info_cb_t) o->callback;
@@ -1487,6 +1494,56 @@ pa_operation* pa_context_set_source_mute_by_name(pa_context *c, const char *name
return o;
}
+pa_operation* pa_context_set_source_output_volume(pa_context *c, uint32_t idx, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata) {
+ pa_operation *o;
+ pa_tagstruct *t;
+ uint32_t tag;
+
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+ pa_assert(volume);
+
+ PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED);
+ PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY_RETURN_NULL(c, idx != PA_INVALID_INDEX, PA_ERR_INVALID);
+ PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 22, PA_ERR_NOTSUPPORTED);
+ PA_CHECK_VALIDITY_RETURN_NULL(c, pa_cvolume_valid(volume), PA_ERR_INVALID);
+
+ o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
+
+ t = pa_tagstruct_command(c, PA_COMMAND_SET_SOURCE_OUTPUT_VOLUME, &tag);
+ pa_tagstruct_putu32(t, idx);
+ pa_tagstruct_put_cvolume(t, volume);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
+
+ return o;
+}
+
+pa_operation* pa_context_set_source_output_mute(pa_context *c, uint32_t idx, int mute, pa_context_success_cb_t cb, void *userdata) {
+ pa_operation *o;
+ pa_tagstruct *t;
+ uint32_t tag;
+
+ pa_assert(c);
+ pa_assert(PA_REFCNT_VALUE(c) >= 1);
+
+ PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED);
+ PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
+ PA_CHECK_VALIDITY_RETURN_NULL(c, idx != PA_INVALID_INDEX, PA_ERR_INVALID);
+ PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 22, PA_ERR_NOTSUPPORTED);
+
+ o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
+
+ t = pa_tagstruct_command(c, PA_COMMAND_SET_SOURCE_OUTPUT_MUTE, &tag);
+ pa_tagstruct_putu32(t, idx);
+ pa_tagstruct_put_boolean(t, mute);
+ pa_pstream_send_tagstruct(c->pstream, t);
+ pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref);
+
+ return o;
+}
+
/** Sample Cache **/
static void context_get_sample_info_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
diff --git a/src/pulse/introspect.h b/src/pulse/introspect.h
index 65f12d12..84a12b62 100644
--- a/src/pulse/introspect.h
+++ b/src/pulse/introspect.h
@@ -164,9 +164,8 @@
*
* If an application desires to modify the volume of just a single stream
* (commonly one of its own streams), this can be done by setting the volume
- * of its associated sink input, using pa_context_set_sink_input_volume().
- *
- * There is no support for modifying the volume of source outputs.
+ * of its associated sink input or source output, using
+ * pa_context_set_sink_input_volume() or pa_context_set_source_output_volume()
*
* It is also possible to remove sink inputs and source outputs, terminating
* the streams associated with them:
@@ -558,6 +557,10 @@ typedef struct pa_source_output_info {
const char *driver; /**< Driver name */
pa_proplist *proplist; /**< Property list \since 0.9.11 */
int corked; /**< Stream corked \since 1.0 */
+ pa_cvolume volume; /**< The volume of this source output \since 1.0 */
+ int mute; /**< Stream muted \since 1.0 */
+ int has_volume; /**< Stream has volume. If not set, then the meaning of this struct's volume member is unspecified. \since 1.0 */
+ int volume_writable; /**< The volume can be set. If not set, the volume can still change even though clients can't control the volume. \since 1.0 */
} pa_source_output_info;
/** Callback prototype for pa_context_get_source_output_info() and friends*/
@@ -575,6 +578,12 @@ pa_operation* pa_context_move_source_output_by_name(pa_context *c, uint32_t idx,
/** Move the specified source output to a different source. \since 0.9.5 */
pa_operation* pa_context_move_source_output_by_index(pa_context *c, uint32_t idx, uint32_t source_idx, pa_context_success_cb_t cb, void* userdata);
+/** Set the volume of a source output stream \since 1.0 */
+pa_operation* pa_context_set_source_output_volume(pa_context *c, uint32_t idx, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata);
+
+/** Set the mute switch of a source output stream \since 1.0 */
+pa_operation* pa_context_set_source_output_mute(pa_context *c, uint32_t idx, int mute, pa_context_success_cb_t cb, void *userdata);
+
/** Kill a source output. */
pa_operation* pa_context_kill_source_output(pa_context *c, uint32_t idx, pa_context_success_cb_t cb, void *userdata);
diff --git a/src/pulse/stream.c b/src/pulse/stream.c
index 32936848..d60b864b 100644
--- a/src/pulse/stream.c
+++ b/src/pulse/stream.c
@@ -1149,7 +1149,8 @@ static int create_stream(
pa_tagstruct *t;
uint32_t tag;
- pa_bool_t volume_set = FALSE;
+ pa_bool_t volume_set = !!volume;
+ pa_cvolume cv;
uint32_t i;
pa_assert(s);
@@ -1246,9 +1247,18 @@ static int create_stream(
PA_TAG_BOOLEAN, s->corked,
PA_TAG_INVALID);
- if (s->direction == PA_STREAM_PLAYBACK) {
- pa_cvolume cv;
+ if (!volume) {
+ if (pa_sample_spec_valid(&s->sample_spec))
+ volume = pa_cvolume_reset(&cv, s->sample_spec.channels);
+ else {
+ /* This is not really relevant, since no volume was set, and
+ * the real number of channels is embedded in the format_info
+ * structure */
+ volume = pa_cvolume_reset(&cv, PA_CHANNELS_MAX);
+ }
+ }
+ if (s->direction == PA_STREAM_PLAYBACK) {
pa_tagstruct_put(
t,
PA_TAG_U32, s->buffer_attr.tlength,
@@ -1257,19 +1267,6 @@ static int create_stream(
PA_TAG_U32, s->syncid,
PA_TAG_INVALID);
- volume_set = !!volume;
-
- if (!volume) {
- if (pa_sample_spec_valid(&s->sample_spec))
- volume = pa_cvolume_reset(&cv, s->sample_spec.channels);
- else {
- /* This is not really relevant, since no volume was set, and
- * the real number of channels is embedded in the format_info
- * structure */
- volume = pa_cvolume_reset(&cv, PA_CHANNELS_MAX);
- }
- }
-
pa_tagstruct_put_cvolume(t, volume);
} else
pa_tagstruct_putu32(t, s->buffer_attr.fragsize);
@@ -1335,6 +1332,15 @@ static int create_stream(
pa_tagstruct_put_format_info(t, s->req_formats[i]);
}
+ if (s->context->version >= 22 && s->direction == PA_STREAM_RECORD) {
+ pa_tagstruct_put_cvolume(t, volume);
+ pa_tagstruct_put_boolean(t, flags & PA_STREAM_START_MUTED);
+ pa_tagstruct_put_boolean(t, volume_set);
+ pa_tagstruct_put_boolean(t, flags & (PA_STREAM_START_MUTED|PA_STREAM_START_UNMUTED));
+ pa_tagstruct_put_boolean(t, flags & PA_STREAM_RELATIVE_VOLUME);
+ pa_tagstruct_put_boolean(t, flags & (PA_STREAM_PASSTHROUGH));
+ }
+
pa_pstream_send_tagstruct(s->context->pstream, t);
pa_pdispatch_register_reply(s->context->pdispatch, tag, DEFAULT_TIMEOUT, pa_create_stream_callback, s, NULL);
diff --git a/src/pulsecore/cli-command.c b/src/pulsecore/cli-command.c
index 17b0e150..1db19cef 100644
--- a/src/pulsecore/cli-command.c
+++ b/src/pulsecore/cli-command.c
@@ -632,7 +632,7 @@ static int pa_cli_command_source_volume(pa_core *c, pa_tokenizer *t, pa_strbuf *
}
pa_cvolume_set(&cvolume, 1, volume);
- pa_source_set_volume(source, &cvolume, TRUE);
+ pa_source_set_volume(source, &cvolume, TRUE, TRUE);
return 0;
}
diff --git a/src/pulsecore/native-common.h b/src/pulsecore/native-common.h
index f49abb09..5d1ba6a6 100644
--- a/src/pulsecore/native-common.h
+++ b/src/pulsecore/native-common.h
@@ -169,6 +169,10 @@ enum {
PA_COMMAND_SET_SINK_PORT,
PA_COMMAND_SET_SOURCE_PORT,
+ /* Supported since protocol v22 (1.0) */
+ PA_COMMAND_SET_SOURCE_OUTPUT_VOLUME,
+ PA_COMMAND_SET_SOURCE_OUTPUT_MUTE,
+
PA_COMMAND_MAX
};
diff --git a/src/pulsecore/pdispatch.c b/src/pulsecore/pdispatch.c
index 69f5d9ef..9a9ef4e1 100644
--- a/src/pulsecore/pdispatch.c
+++ b/src/pulsecore/pdispatch.c
@@ -184,7 +184,12 @@ static const char *command_names[PA_COMMAND_MAX] = {
/* Supported since protocol v16 (0.9.16) */
[PA_COMMAND_SET_SINK_PORT] = "SET_SINK_PORT",
- [PA_COMMAND_SET_SOURCE_PORT] = "SET_SOURCE_PORT"
+ [PA_COMMAND_SET_SOURCE_PORT] = "SET_SOURCE_PORT",
+
+ /* Supported since protocol v22 (1.0) */
+ [PA_COMMAND_SET_SOURCE_OUTPUT_VOLUME] = "SET_SOURCE_OUTPUT_VOLUME",
+ [PA_COMMAND_SET_SOURCE_OUTPUT_MUTE] = "SET_SOURCE_OUTPUT_MUTE",
+
};
#endif
diff --git a/src/pulsecore/protocol-native.c b/src/pulsecore/protocol-native.c
index 3574ca98..d90b470a 100644
--- a/src/pulsecore/protocol-native.c
+++ b/src/pulsecore/protocol-native.c
@@ -338,10 +338,12 @@ static const pa_pdispatch_cb_t command_table[PA_COMMAND_MAX] = {
[PA_COMMAND_SET_SINK_VOLUME] = command_set_volume,
[PA_COMMAND_SET_SINK_INPUT_VOLUME] = command_set_volume,
[PA_COMMAND_SET_SOURCE_VOLUME] = command_set_volume,
+ [PA_COMMAND_SET_SOURCE_OUTPUT_VOLUME] = command_set_volume,
[PA_COMMAND_SET_SINK_MUTE] = command_set_mute,
[PA_COMMAND_SET_SINK_INPUT_MUTE] = command_set_mute,
[PA_COMMAND_SET_SOURCE_MUTE] = command_set_mute,
+ [PA_COMMAND_SET_SOURCE_OUTPUT_MUTE] = command_set_mute,
[PA_COMMAND_SUSPEND_SINK] = command_suspend,
[PA_COMMAND_SUSPEND_SOURCE] = command_suspend,
@@ -631,10 +633,14 @@ static record_stream* record_stream_new(
pa_channel_map *map,
pa_idxset *formats,
pa_buffer_attr *attr,
+ pa_cvolume *volume,
+ pa_bool_t muted,
+ pa_bool_t muted_set,
pa_source_output_flags_t flags,
pa_proplist *p,
pa_bool_t adjust_latency,
pa_bool_t early_requests,
+ pa_bool_t relative_volume,
pa_bool_t peak_detect,
pa_sink_input *direct_on_input,
int *ret) {
@@ -663,6 +669,15 @@ static record_stream* record_stream_new(
if (formats)
pa_source_output_new_data_set_formats(&data, formats);
data.direct_on_input = direct_on_input;
+ if (volume) {
+ pa_source_output_new_data_set_volume(&data, volume);
+ data.volume_is_absolute = !relative_volume;
+ data.save_volume = TRUE;
+ }
+ if (muted_set) {
+ pa_source_output_new_data_set_muted(&data, muted);
+ data.save_muted = TRUE;
+ }
if (peak_detect)
data.resample_method = PA_RESAMPLER_PEAKS;
data.flags = flags;
@@ -2215,6 +2230,7 @@ static void command_create_record_stream(pa_pdispatch *pd, uint32_t command, uin
pa_channel_map map;
pa_tagstruct *reply;
pa_source *source = NULL;
+ pa_cvolume volume;
pa_bool_t
corked = FALSE,
no_remap = FALSE,
@@ -2224,11 +2240,15 @@ static void command_create_record_stream(pa_pdispatch *pd, uint32_t command, uin
fix_channels = FALSE,
no_move = FALSE,
variable_rate = FALSE,
+ muted = FALSE,
adjust_latency = FALSE,
peak_detect = FALSE,
early_requests = FALSE,
dont_inhibit_auto_suspend = FALSE,
+ volume_set = TRUE,
+ muted_set = FALSE,
fail_on_suspend = FALSE,
+ relative_volume = FALSE,
passthrough = FALSE;
pa_source_output_flags_t flags = 0;
@@ -2333,10 +2353,24 @@ static void command_create_record_stream(pa_pdispatch *pd, uint32_t command, uin
}
pa_idxset_put(formats, format, NULL);
}
+
+ if (pa_tagstruct_get_cvolume(t, &volume) < 0 ||
+ pa_tagstruct_get_boolean(t, &muted) < 0 ||
+ pa_tagstruct_get_boolean(t, &volume_set) < 0 ||
+ pa_tagstruct_get_boolean(t, &muted_set) < 0 ||
+ pa_tagstruct_get_boolean(t, &relative_volume) < 0 ||
+ pa_tagstruct_get_boolean(t, &passthrough) < 0) {
+
+ protocol_error(c);
+ goto finish;
+ }
+
+ CHECK_VALIDITY_GOTO(c->pstream, pa_cvolume_valid(&volume), tag, PA_ERR_INVALID, finish);
}
if (n_formats == 0) {
CHECK_VALIDITY_GOTO(c->pstream, pa_sample_spec_valid(&ss), tag, PA_ERR_INVALID, finish);
+ CHECK_VALIDITY_GOTO(c->pstream, map.channels == ss.channels && volume.channels == ss.channels, tag, PA_ERR_INVALID, finish);
CHECK_VALIDITY_GOTO(c->pstream, pa_channel_map_valid(&map), tag, PA_ERR_INVALID, finish);
} else {
PA_IDXSET_FOREACH(format, formats, i) {
@@ -2386,7 +2420,7 @@ static void command_create_record_stream(pa_pdispatch *pd, uint32_t command, uin
(fail_on_suspend ? PA_SOURCE_OUTPUT_NO_CREATE_ON_SUSPEND|PA_SOURCE_OUTPUT_KILL_ON_SUSPEND : 0) |
(passthrough ? PA_SOURCE_OUTPUT_PASSTHROUGH : 0);
- s = record_stream_new(c, source, &ss, &map, formats, &attr, flags, p, adjust_latency, early_requests, peak_detect, direct_on_input, &ret);
+ s = record_stream_new(c, source, &ss, &map, formats, &attr, volume_set ? &volume : NULL, muted, muted_set, flags, p, adjust_latency, early_requests, relative_volume, peak_detect, direct_on_input, &ret);
CHECK_VALIDITY_GOTO(c->pstream, s, tag, ret, finish);
@@ -3249,12 +3283,20 @@ static void sink_input_fill_tagstruct(pa_native_connection *c, pa_tagstruct *t,
static void source_output_fill_tagstruct(pa_native_connection *c, pa_tagstruct *t, pa_source_output *s) {
pa_sample_spec fixed_ss;
pa_usec_t source_latency;
+ pa_cvolume v;
+ pa_bool_t has_volume = FALSE;
pa_assert(t);
pa_source_output_assert_ref(s);
fixup_sample_spec(c, &fixed_ss, &s->sample_spec);
+ has_volume = pa_source_output_is_volume_readable(s);
+ if (has_volume)
+ pa_source_output_get_volume(s, &v, TRUE);
+ else
+ pa_cvolume_reset(&v, fixed_ss.channels);
+
pa_tagstruct_putu32(t, s->index);
pa_tagstruct_puts(t, pa_strnull(pa_proplist_gets(s->proplist, PA_PROP_MEDIA_NAME)));
pa_tagstruct_putu32(t, s->module ? s->module->index : PA_INVALID_INDEX);
@@ -3270,6 +3312,12 @@ static void source_output_fill_tagstruct(pa_native_connection *c, pa_tagstruct *
pa_tagstruct_put_proplist(t, s->proplist);
if (c->version >= 19)
pa_tagstruct_put_boolean(t, (pa_source_output_get_state(s) == PA_SOURCE_OUTPUT_CORKED));
+ if (c->version >= 22) {
+ pa_tagstruct_put_cvolume(t, &v);
+ pa_tagstruct_put_boolean(t, pa_source_output_get_mute(s));
+ pa_tagstruct_put_boolean(t, has_volume);
+ pa_tagstruct_put_boolean(t, s->volume_writable);
+ }
}
static void scache_fill_tagstruct(pa_native_connection *c, pa_tagstruct *t, pa_scache_entry *e) {
@@ -3564,6 +3612,7 @@ static void command_set_volume(
pa_sink *sink = NULL;
pa_source *source = NULL;
pa_sink_input *si = NULL;
+ pa_source_output *so = NULL;
const char *name = NULL;
const char *client_name;
@@ -3606,11 +3655,15 @@ static void command_set_volume(
si = pa_idxset_get_by_index(c->protocol->core->sink_inputs, idx);
break;
+ case PA_COMMAND_SET_SOURCE_OUTPUT_VOLUME:
+ so = pa_idxset_get_by_index(c->protocol->core->source_outputs, idx);
+ break;
+
default:
pa_assert_not_reached();
}
- CHECK_VALIDITY(c->pstream, si || sink || source, tag, PA_ERR_NOENTITY);
+ CHECK_VALIDITY(c->pstream, si || so || sink || source, tag, PA_ERR_NOENTITY);
client_name = pa_strnull(pa_proplist_gets(c->client->proplist, PA_PROP_APPLICATION_PROCESS_BINARY));
@@ -3623,7 +3676,7 @@ static void command_set_volume(
CHECK_VALIDITY(c->pstream, volume.channels == 1 || pa_cvolume_compatible(&volume, &source->sample_spec), tag, PA_ERR_INVALID);
pa_log_debug("Client %s changes volume of source %s.", client_name, source->name);
- pa_source_set_volume(source, &volume, TRUE);
+ pa_source_set_volume(source, &volume, TRUE, TRUE);
} else if (si) {
CHECK_VALIDITY(c->pstream, si->volume_writable, tag, PA_ERR_BADSTATE);
CHECK_VALIDITY(c->pstream, volume.channels == 1 || pa_cvolume_compatible(&volume, &si->sample_spec), tag, PA_ERR_INVALID);
@@ -3632,6 +3685,13 @@ static void command_set_volume(
client_name,
pa_strnull(pa_proplist_gets(si->proplist, PA_PROP_MEDIA_NAME)));
pa_sink_input_set_volume(si, &volume, TRUE, TRUE);
+ } else if (so) {
+ CHECK_VALIDITY(c->pstream, volume.channels == 1 || pa_cvolume_compatible(&volume, &so->sample_spec), tag, PA_ERR_INVALID);
+
+ pa_log_debug("Client %s changes volume of source output %s.",
+ client_name,
+ pa_strnull(pa_proplist_gets(so->proplist, PA_PROP_MEDIA_NAME)));
+ pa_source_output_set_volume(so, &volume, TRUE, TRUE);
}
pa_pstream_send_simple_ack(c->pstream, tag);
@@ -3650,6 +3710,7 @@ static void command_set_mute(
pa_sink *sink = NULL;
pa_source *source = NULL;
pa_sink_input *si = NULL;
+ pa_source_output *so = NULL;
const char *name = NULL, *client_name;
pa_native_connection_assert_ref(c);
@@ -3692,11 +3753,15 @@ static void command_set_mute(
si = pa_idxset_get_by_index(c->protocol->core->sink_inputs, idx);
break;
+ case PA_COMMAND_SET_SOURCE_OUTPUT_MUTE:
+ so = pa_idxset_get_by_index(c->protocol->core->source_outputs, idx);
+ break;
+
default:
pa_assert_not_reached();
}
- CHECK_VALIDITY(c->pstream, si || sink || source, tag, PA_ERR_NOENTITY);
+ CHECK_VALIDITY(c->pstream, si || so || sink || source, tag, PA_ERR_NOENTITY);
client_name = pa_strnull(pa_proplist_gets(c->client->proplist, PA_PROP_APPLICATION_PROCESS_BINARY));
@@ -3711,6 +3776,11 @@ static void command_set_mute(
client_name,
pa_strnull(pa_proplist_gets(si->proplist, PA_PROP_MEDIA_NAME)));
pa_sink_input_set_mute(si, mute, TRUE);
+ } else if (so) {
+ pa_log_debug("Client %s changes mute of source output %s.",
+ client_name,
+ pa_strnull(pa_proplist_gets(so->proplist, PA_PROP_MEDIA_NAME)));
+ pa_source_output_set_mute(so, mute, TRUE);
}
pa_pstream_send_simple_ack(c->pstream, tag);
diff --git a/src/pulsecore/sink.c b/src/pulsecore/sink.c
index ed38013d..45761a6c 100644
--- a/src/pulsecore/sink.c
+++ b/src/pulsecore/sink.c
@@ -24,9 +24,9 @@
#include <config.h>
#endif
+#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include <stdio.h>
#include <pulse/introspect.h>
#include <pulse/utf8.h>
@@ -2616,6 +2616,7 @@ pa_usec_t pa_sink_get_requested_latency(pa_sink *s) {
return 0;
pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_REQUESTED_LATENCY, &usec, 0, NULL) == 0);
+
return usec;
}
@@ -2873,8 +2874,8 @@ void pa_sink_set_fixed_latency_within_thread(pa_sink *s, pa_usec_t latency) {
/* Called from main context */
size_t pa_sink_get_max_rewind(pa_sink *s) {
size_t r;
- pa_sink_assert_ref(s);
pa_assert_ctl_context();
+ pa_sink_assert_ref(s);
if (!PA_SINK_IS_LINKED(s->state))
return s->thread_info.max_rewind;
@@ -2902,6 +2903,7 @@ size_t pa_sink_get_max_request(pa_sink *s) {
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();
diff --git a/src/pulsecore/source-output.c b/src/pulsecore/source-output.c
index 61e06954..82c6f542 100644
--- a/src/pulsecore/source-output.c
+++ b/src/pulsecore/source-output.c
@@ -45,6 +45,7 @@
PA_DEFINE_PUBLIC_CLASS(pa_source_output, pa_msgobject);
static void source_output_free(pa_object* mo);
+static void set_real_ratio(pa_source_output *o, const pa_cvolume *v);
pa_source_output_new_data* pa_source_output_new_data_init(pa_source_output_new_data *data) {
pa_assert(data);
@@ -52,6 +53,7 @@ pa_source_output_new_data* pa_source_output_new_data_init(pa_source_output_new_d
pa_zero(*data);
data->resample_method = PA_RESAMPLER_INVALID;
data->proplist = pa_proplist_new();
+ data->volume_writable = TRUE;
return data;
}
@@ -82,6 +84,45 @@ pa_bool_t pa_source_output_new_data_is_passthrough(pa_source_output_new_data *da
return FALSE;
}
+void pa_source_output_new_data_set_volume(pa_source_output_new_data *data, const pa_cvolume *volume) {
+ pa_assert(data);
+ pa_assert(data->volume_writable);
+
+ if ((data->volume_is_set = !!volume))
+ data->volume = *volume;
+}
+
+void pa_source_output_new_data_apply_volume_factor(pa_source_output_new_data *data, const pa_cvolume *volume_factor) {
+ pa_assert(data);
+ pa_assert(volume_factor);
+
+ if (data->volume_factor_is_set)
+ pa_sw_cvolume_multiply(&data->volume_factor, &data->volume_factor, volume_factor);
+ else {
+ data->volume_factor_is_set = TRUE;
+ data->volume_factor = *volume_factor;
+ }
+}
+
+void pa_source_output_new_data_apply_volume_factor_source(pa_source_output_new_data *data, const pa_cvolume *volume_factor) {
+ pa_assert(data);
+ pa_assert(volume_factor);
+
+ if (data->volume_factor_source_is_set)
+ pa_sw_cvolume_multiply(&data->volume_factor_source, &data->volume_factor_source, volume_factor);
+ else {
+ data->volume_factor_source_is_set = TRUE;
+ data->volume_factor_source = *volume_factor;
+ }
+}
+
+void pa_source_output_new_data_set_muted(pa_source_output_new_data *data, pa_bool_t mute) {
+ pa_assert(data);
+
+ data->muted_is_set = TRUE;
+ data->muted = !!mute;
+}
+
pa_bool_t pa_source_output_new_data_set_source(pa_source_output_new_data *data, pa_source *s, pa_bool_t save) {
pa_bool_t ret = TRUE;
pa_idxset *formats = NULL;
@@ -167,6 +208,8 @@ static void reset_callbacks(pa_source_output *o) {
o->state_change = NULL;
o->may_move_to = NULL;
o->send_event = NULL;
+ o->volume_changed = NULL;
+ o->mute_changed = NULL;
}
/* Called from main context */
@@ -192,6 +235,9 @@ int pa_source_output_new(
if (data->client)
pa_proplist_update(data->proplist, PA_UPDATE_MERGE, data->client->proplist);
+ if (data->destination_source && (data->destination_source->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER))
+ data->volume_writable = FALSE;
+
if (!data->req_formats) {
/* From this point on, we want to work only with formats, and get back
* to using the sample spec and channel map after all decisions w.r.t.
@@ -250,6 +296,33 @@ int pa_source_output_new(
pa_return_val_if_fail(pa_channel_map_compatible(&data->channel_map, &data->sample_spec), -PA_ERR_INVALID);
+ /* Don't restore (or save) stream volume for passthrough streams */
+ if (!pa_format_info_is_pcm(data->format)) {
+ data->volume_is_set = FALSE;
+ data->volume_factor_is_set = FALSE;
+ }
+
+ if (!data->volume_is_set) {
+ pa_cvolume_reset(&data->volume, data->sample_spec.channels);
+ data->volume_is_absolute = FALSE;
+ data->save_volume = FALSE;
+ }
+
+ pa_return_val_if_fail(pa_cvolume_compatible(&data->volume, &data->sample_spec), -PA_ERR_INVALID);
+
+ if (!data->volume_factor_is_set)
+ pa_cvolume_reset(&data->volume_factor, data->sample_spec.channels);
+
+ pa_return_val_if_fail(pa_cvolume_compatible(&data->volume_factor, &data->sample_spec), -PA_ERR_INVALID);
+
+ if (!data->volume_factor_source_is_set)
+ pa_cvolume_reset(&data->volume_factor_source, data->source->sample_spec.channels);
+
+ pa_return_val_if_fail(pa_cvolume_compatible(&data->volume_factor_source, &data->source->sample_spec), -PA_ERR_INVALID);
+
+ if (!data->muted_is_set)
+ data->muted = FALSE;
+
if (data->flags & PA_SOURCE_OUTPUT_FIX_FORMAT)
data->sample_spec.format = data->source->sample_spec.format;
@@ -266,6 +339,9 @@ int pa_source_output_new(
pa_assert(pa_sample_spec_valid(&data->sample_spec));
pa_assert(pa_channel_map_valid(&data->channel_map));
+ /* Due to the fixing of the sample spec the volume might not match anymore */
+ pa_cvolume_remap(&data->volume, &original_cm, &data->channel_map);
+
if (data->resample_method == PA_RESAMPLER_INVALID)
data->resample_method = core->resample_method;
@@ -324,6 +400,39 @@ int pa_source_output_new(
o->channel_map = data->channel_map;
o->format = pa_format_info_copy(data->format);
+ if (!data->volume_is_absolute && pa_source_flat_volume_enabled(o->source)) {
+ pa_cvolume remapped;
+
+ /* When the 'absolute' bool is not set then we'll treat the volume
+ * as relative to the source volume even in flat volume mode */
+ remapped = data->source->reference_volume;
+ pa_cvolume_remap(&remapped, &data->source->channel_map, &data->channel_map);
+ pa_sw_cvolume_multiply(&o->volume, &data->volume, &remapped);
+ } else
+ o->volume = data->volume;
+
+ o->volume_factor = data->volume_factor;
+ o->volume_factor_source = data->volume_factor_source;
+ o->real_ratio = o->reference_ratio = data->volume;
+ pa_cvolume_reset(&o->soft_volume, o->sample_spec.channels);
+ pa_cvolume_reset(&o->real_ratio, o->sample_spec.channels);
+ o->volume_writable = data->volume_writable;
+ o->save_volume = data->save_volume;
+ o->save_source = data->save_source;
+ o->save_muted = data->save_muted;
+
+ o->muted = data->muted;
+
+ if (data->sync_base) {
+ o->sync_next = data->sync_base->sync_next;
+ o->sync_prev = data->sync_base;
+
+ if (data->sync_base->sync_next)
+ data->sync_base->sync_next->sync_prev = o;
+ data->sync_base->sync_next = o;
+ } else
+ o->sync_next = o->sync_prev = NULL;
+
o->direct_on_input = data->direct_on_input;
reset_callbacks(o);
@@ -333,6 +442,8 @@ int pa_source_output_new(
o->thread_info.attached = FALSE;
o->thread_info.sample_spec = o->sample_spec;
o->thread_info.resampler = resampler;
+ o->thread_info.soft_volume = o->soft_volume;
+ o->thread_info.muted = o->muted;
o->thread_info.requested_source_latency = (pa_usec_t) -1;
o->thread_info.direct_on_input = o->direct_on_input;
@@ -387,6 +498,7 @@ static void update_n_corked(pa_source_output *o, pa_source_output_state_t state)
/* Called from main context */
static void source_output_set_state(pa_source_output *o, pa_source_output_state_t state) {
+ pa_source_output *ssync;
pa_assert(o);
pa_assert_ctl_context();
@@ -398,9 +510,24 @@ static void source_output_set_state(pa_source_output *o, pa_source_output_state_
update_n_corked(o, state);
o->state = state;
+ for (ssync = o->sync_prev; ssync; ssync = ssync->sync_prev) {
+ update_n_corked(ssync, state);
+ ssync->state = state;
+ }
+ for (ssync = o->sync_next; ssync; ssync = ssync->sync_next) {
+ update_n_corked(ssync, state);
+ ssync->state = state;
+ }
+
if (state != PA_SOURCE_OUTPUT_UNLINKED) {
pa_hook_fire(&o->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_STATE_CHANGED], o);
+ for (ssync = o->sync_prev; ssync; ssync = ssync->sync_prev)
+ pa_hook_fire(&o->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_STATE_CHANGED], ssync);
+
+ for (ssync = o->sync_next; ssync; ssync = ssync->sync_next)
+ pa_hook_fire(&o->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_STATE_CHANGED], ssync);
+
if (PA_SOURCE_OUTPUT_IS_LINKED(state))
pa_subscription_post(o->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE, o->index);
}
@@ -424,6 +551,13 @@ void pa_source_output_unlink(pa_source_output*o) {
if (linked)
pa_hook_fire(&o->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK], o);
+ if (o->sync_prev)
+ o->sync_prev->sync_next = o->sync_next;
+ if (o->sync_next)
+ o->sync_next->sync_prev = o->sync_prev;
+
+ o->sync_prev = o->sync_next = NULL;
+
if (o->direct_on_input)
pa_idxset_remove_by_data(o->direct_on_input->direct_outputs, o, NULL);
@@ -439,9 +573,14 @@ void pa_source_output_unlink(pa_source_output*o) {
update_n_corked(o, PA_SOURCE_OUTPUT_UNLINKED);
o->state = PA_SOURCE_OUTPUT_UNLINKED;
- if (linked && o->source)
+ if (linked && o->source) {
+ /* We might need to update the source's volume if we are in flat volume mode. */
+ if (pa_source_flat_volume_enabled(o->source))
+ pa_source_set_volume(o->source, NULL, FALSE, FALSE);
+
if (o->source->asyncmsgq)
pa_assert_se(pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o->source), PA_SOURCE_MESSAGE_REMOVE_OUTPUT, o, 0, NULL) == 0);
+ }
reset_callbacks(o);
@@ -507,6 +646,21 @@ void pa_source_output_put(pa_source_output *o) {
update_n_corked(o, state);
o->state = state;
+ /* We might need to update the source's volume if we are in flat volume mode. */
+ if (pa_source_flat_volume_enabled(o->source))
+ pa_source_set_volume(o->source, NULL, FALSE, o->save_volume);
+ else {
+ if (o->destination_source && (o->destination_source->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER)) {
+ pa_assert(pa_cvolume_is_norm(&o->volume));
+ pa_assert(pa_cvolume_is_norm(&o->reference_ratio));
+ }
+
+ set_real_ratio(o, &o->volume);
+ }
+
+ o->thread_info.soft_volume = o->soft_volume;
+ o->thread_info.muted = o->muted;
+
pa_assert_se(pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o->source), PA_SOURCE_MESSAGE_ADD_OUTPUT, o, 0, NULL) == 0);
pa_subscription_post(o->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_NEW, o->index);
@@ -545,6 +699,8 @@ pa_usec_t pa_source_output_get_latency(pa_source_output *o, pa_usec_t *source_la
/* Called from thread context */
void pa_source_output_push(pa_source_output *o, const pa_memchunk *chunk) {
+ pa_bool_t need_volume_factor_source;
+ pa_bool_t volume_is_norm;
size_t length;
size_t limit, mbs = 0;
@@ -566,6 +722,9 @@ void pa_source_output_push(pa_source_output *o, const pa_memchunk *chunk) {
limit = o->process_rewind ? 0 : o->source->thread_info.max_rewind;
+ volume_is_norm = pa_cvolume_is_norm(&o->thread_info.soft_volume) && !o->thread_info.muted;
+ need_volume_factor_source = !pa_cvolume_is_norm(&o->volume_factor_source);
+
if (limit > 0 && o->source->monitor_of) {
pa_usec_t latency;
size_t n;
@@ -588,6 +747,7 @@ void pa_source_output_push(pa_source_output *o, const pa_memchunk *chunk) {
/* Implement the delay queue */
while ((length = pa_memblockq_get_length(o->thread_info.delay_memblockq)) > limit) {
pa_memchunk qchunk;
+ pa_bool_t nvfs = need_volume_factor_source;
length -= limit;
@@ -598,9 +758,36 @@ void pa_source_output_push(pa_source_output *o, const pa_memchunk *chunk) {
pa_assert(qchunk.length > 0);
- if (!o->thread_info.resampler)
+ /* It might be necessary to adjust the volume here */
+ if (!volume_is_norm) {
+ pa_memchunk_make_writable(&qchunk, 0);
+
+ if (o->thread_info.muted) {
+ pa_silence_memchunk(&qchunk, &o->thread_info.sample_spec);
+ nvfs = FALSE;
+
+ } else if (!o->thread_info.resampler && nvfs) {
+ pa_cvolume v;
+
+ /* If we don't need a resampler we can merge the
+ * post and the pre volume adjustment into one */
+
+ pa_sw_cvolume_multiply(&v, &o->thread_info.soft_volume, &o->volume_factor_source);
+ pa_volume_memchunk(&qchunk, &o->thread_info.sample_spec, &v);
+ nvfs = FALSE;
+
+ } else
+ pa_volume_memchunk(&qchunk, &o->thread_info.sample_spec, &o->thread_info.soft_volume);
+ }
+
+ if (!o->thread_info.resampler) {
+ if (nvfs) {
+ pa_memchunk_make_writable(&qchunk, 0);
+ pa_volume_memchunk(&qchunk, &o->source->sample_spec, &o->volume_factor_source);
+ }
+
o->push(o, &qchunk);
- else {
+ } else {
pa_memchunk rchunk;
if (mbs == 0)
@@ -611,8 +798,14 @@ void pa_source_output_push(pa_source_output *o, const pa_memchunk *chunk) {
pa_resampler_run(o->thread_info.resampler, &qchunk, &rchunk);
- if (rchunk.length > 0)
+ if (rchunk.length > 0) {
+ if (nvfs) {
+ pa_memchunk_make_writable(&rchunk, 0);
+ pa_volume_memchunk(&rchunk, &o->source->sample_spec, &o->volume_factor_source);
+ }
+
o->push(o, &rchunk);
+ }
if (rchunk.memblock)
pa_memblock_unref(rchunk.memblock);
@@ -786,6 +979,85 @@ void pa_source_output_set_name(pa_source_output *o, const char *name) {
}
}
+/* Called from main context */
+void pa_source_output_set_volume(pa_source_output *o, const pa_cvolume *volume, pa_bool_t save, pa_bool_t absolute) {
+ pa_cvolume v;
+
+ pa_source_output_assert_ref(o);
+ pa_assert_ctl_context();
+ pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state));
+ pa_assert(volume);
+ pa_assert(pa_cvolume_valid(volume));
+ pa_assert(volume->channels == 1 || pa_cvolume_compatible(volume, &o->sample_spec));
+ pa_assert(o->volume_writable);
+
+ if (!absolute && pa_source_flat_volume_enabled(o->source)) {
+ v = o->source->reference_volume;
+ pa_cvolume_remap(&v, &o->source->channel_map, &o->channel_map);
+
+ if (pa_cvolume_compatible(volume, &o->sample_spec))
+ volume = pa_sw_cvolume_multiply(&v, &v, volume);
+ else
+ volume = pa_sw_cvolume_multiply_scalar(&v, &v, pa_cvolume_max(volume));
+ } else {
+ if (!pa_cvolume_compatible(volume, &o->sample_spec)) {
+ v = o->volume;
+ volume = pa_cvolume_scale(&v, pa_cvolume_max(volume));
+ }
+ }
+
+ if (pa_cvolume_equal(volume, &o->volume)) {
+ o->save_volume = o->save_volume || save;
+ return;
+ }
+
+ o->volume = *volume;
+ o->save_volume = save;
+
+ if (pa_source_flat_volume_enabled(o->source)) {
+ /* We are in flat volume mode, so let's update all source input
+ * volumes and update the flat volume of the source */
+
+ pa_source_set_volume(o->source, NULL, TRUE, save);
+
+ } else {
+ /* OK, we are in normal volume mode. The volume only affects
+ * ourselves */
+ set_real_ratio(o, volume);
+
+ /* Copy the new soft_volume to the thread_info struct */
+ pa_assert_se(pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o), PA_SOURCE_OUTPUT_MESSAGE_SET_SOFT_VOLUME, NULL, 0, NULL) == 0);
+ }
+
+ /* The volume changed, let's tell people so */
+ if (o->volume_changed)
+ o->volume_changed(o);
+
+ /* The virtual volume changed, let's tell people so */
+ pa_subscription_post(o->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE, o->index);
+}
+
+/* Called from main context */
+static void set_real_ratio(pa_source_output *o, const pa_cvolume *v) {
+ pa_source_output_assert_ref(o);
+ pa_assert_ctl_context();
+ pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state));
+ pa_assert(!v || pa_cvolume_compatible(v, &o->sample_spec));
+
+ /* This basically calculates:
+ *
+ * o->real_ratio := v
+ * o->soft_volume := o->real_ratio * o->volume_factor */
+
+ if (v)
+ o->real_ratio = *v;
+ else
+ pa_cvolume_reset(&o->real_ratio, o->sample_spec.channels);
+
+ pa_sw_cvolume_multiply(&o->soft_volume, &o->real_ratio, &o->volume_factor);
+ /* We don't copy the data to the thread_info data. That's left for someone else to do */
+}
+
/* Called from main or I/O context */
pa_bool_t pa_source_output_is_passthrough(pa_source_output *o) {
pa_source_output_assert_ref(o);
@@ -799,6 +1071,61 @@ pa_bool_t pa_source_output_is_passthrough(pa_source_output *o) {
return FALSE;
}
+/* Called from main context */
+pa_bool_t pa_source_output_is_volume_readable(pa_source_output *o) {
+ pa_source_output_assert_ref(o);
+ pa_assert_ctl_context();
+
+ return !pa_source_output_is_passthrough(o);
+}
+
+/* Called from main context */
+pa_cvolume *pa_source_output_get_volume(pa_source_output *o, pa_cvolume *volume, pa_bool_t absolute) {
+ pa_source_output_assert_ref(o);
+ pa_assert_ctl_context();
+ pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state));
+ pa_assert(pa_source_output_is_volume_readable(o));
+
+ if (absolute || !pa_source_flat_volume_enabled(o->source))
+ *volume = o->volume;
+ else
+ *volume = o->reference_ratio;
+
+ return volume;
+}
+
+/* Called from main context */
+void pa_source_output_set_mute(pa_source_output *o, pa_bool_t mute, pa_bool_t save) {
+ pa_source_output_assert_ref(o);
+ pa_assert_ctl_context();
+ pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state));
+
+ if (!o->muted == !mute) {
+ o->save_muted = o->save_muted || mute;
+ return;
+ }
+
+ o->muted = mute;
+ o->save_muted = save;
+
+ pa_assert_se(pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o), PA_SOURCE_OUTPUT_MESSAGE_SET_SOFT_MUTE, NULL, 0, NULL) == 0);
+
+ /* The mute status changed, let's tell people so */
+ if (o->mute_changed)
+ o->mute_changed(o);
+
+ pa_subscription_post(o->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE, o->index);
+}
+
+/* Called from main context */
+pa_bool_t pa_source_output_get_mute(pa_source_output *o) {
+ pa_source_output_assert_ref(o);
+ pa_assert_ctl_context();
+ pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->state));
+
+ return o->muted;
+}
+
/* Called from main thread */
void pa_source_output_update_proplist(pa_source_output *o, pa_update_mode_t mode, pa_proplist *p) {
pa_source_output_assert_ref(o);
@@ -833,6 +1160,11 @@ pa_bool_t pa_source_output_may_move(pa_source_output *o) {
if (o->direct_on_input)
return FALSE;
+ if (o->sync_next || o->sync_prev) {
+ pa_log_warn("Moving synchronized streams not supported.");
+ return FALSE;
+ }
+
return TRUE;
}
@@ -883,6 +1215,11 @@ int pa_source_output_start_move(pa_source_output *o) {
if (pa_source_output_get_state(o) == PA_SOURCE_OUTPUT_CORKED)
pa_assert_se(origin->n_corked-- >= 1);
+ if (pa_source_flat_volume_enabled(o->source))
+ /* We might need to update the source's volume if we are in flat
+ * volume mode. */
+ pa_source_set_volume(o->source, NULL, FALSE, FALSE);
+
pa_assert_se(pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o->source), PA_SOURCE_MESSAGE_REMOVE_OUTPUT, o, 0, NULL) == 0);
pa_source_update_status(o->source);
@@ -893,6 +1230,156 @@ int pa_source_output_start_move(pa_source_output *o) {
return 0;
}
+/* Called from main context. If it has an origin source that uses volume sharing,
+ * then also the origin source and all streams connected to it need to update
+ * their volume - this function does all that by using recursion. */
+static void update_volume_due_to_moving(pa_source_output *o, pa_source *dest) {
+ pa_cvolume old_volume;
+
+ pa_assert(o);
+ pa_assert(dest);
+ pa_assert(o->source); /* The destination source should already be set. */
+
+ if (o->destination_source && (o->destination_source->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER)) {
+ pa_source *root_source = o->source;
+ pa_source_output *destination_source_output;
+ uint32_t idx;
+
+ while (root_source->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER)
+ root_source = root_source->output_from_master->source;
+
+ if (pa_source_flat_volume_enabled(o->source)) {
+ /* Ok, so the origin source uses volume sharing, and flat volume is
+ * enabled. The volume will have to be updated as follows:
+ *
+ * o->volume := o->source->real_volume
+ * (handled later by pa_source_set_volume)
+ * o->reference_ratio := o->volume / o->source->reference_volume
+ * (handled later by pa_source_set_volume)
+ * o->real_ratio stays unchanged
+ * (streams whose origin source uses volume sharing should
+ * always have real_ratio of 0 dB)
+ * o->soft_volume stays unchanged
+ * (streams whose origin source uses volume sharing should
+ * always have volume_factor as soft_volume, so no change
+ * should be needed) */
+
+ pa_assert(pa_cvolume_is_norm(&o->real_ratio));
+ pa_assert(pa_cvolume_equal(&o->soft_volume, &o->volume_factor));
+
+ /* Notifications will be sent by pa_source_set_volume(). */
+
+ } else {
+ /* Ok, so the origin source uses volume sharing, and flat volume is
+ * disabled. The volume will have to be updated as follows:
+ *
+ * o->volume := 0 dB
+ * o->reference_ratio := 0 dB
+ * o->real_ratio stays unchanged
+ * (streams whose origin source uses volume sharing should
+ * always have real_ratio of 0 dB)
+ * o->soft_volume stays unchanged
+ * (streams whose origin source uses volume sharing should
+ * always have volume_factor as soft_volume, so no change
+ * should be needed) */
+
+ old_volume = o->volume;
+ pa_cvolume_reset(&o->volume, o->volume.channels);
+ pa_cvolume_reset(&o->reference_ratio, o->reference_ratio.channels);
+ pa_assert(pa_cvolume_is_norm(&o->real_ratio));
+ pa_assert(pa_cvolume_equal(&o->soft_volume, &o->volume_factor));
+
+ /* Notify others about the changed source output volume. */
+ if (!pa_cvolume_equal(&o->volume, &old_volume)) {
+ if (o->volume_changed)
+ o->volume_changed(o);
+
+ pa_subscription_post(o->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE, o->index);
+ }
+ }
+
+ /* Additionally, the origin source volume needs updating:
+ *
+ * o->destination_source->reference_volume := root_source->reference_volume
+ * o->destination_source->real_volume := root_source->real_volume
+ * o->destination_source->soft_volume stays unchanged
+ * (sources that use volume sharing should always have
+ * soft_volume of 0 dB) */
+
+ old_volume = o->destination_source->reference_volume;
+
+ o->destination_source->reference_volume = root_source->reference_volume;
+ pa_cvolume_remap(&o->destination_source->reference_volume, &root_source->channel_map, &o->destination_source->channel_map);
+
+ o->destination_source->real_volume = root_source->real_volume;
+ pa_cvolume_remap(&o->destination_source->real_volume, &root_source->channel_map, &o->destination_source->channel_map);
+
+ pa_assert(pa_cvolume_is_norm(&o->destination_source->soft_volume));
+
+ /* Notify others about the changed source volume. If you wonder whether
+ * o->destination_source->set_volume() should be called somewhere, that's not
+ * the case, because sources that use volume sharing shouldn't have any
+ * internal volume that set_volume() would update. If you wonder
+ * whether the thread_info variables should be synced, yes, they
+ * should, and it's done by the PA_SOURCE_MESSAGE_FINISH_MOVE message
+ * handler. */
+ if (!pa_cvolume_equal(&o->destination_source->reference_volume, &old_volume))
+ pa_subscription_post(o->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, o->destination_source->index);
+
+ /* Recursively update origin source outputs. */
+ PA_IDXSET_FOREACH(destination_source_output, o->destination_source->outputs, idx)
+ update_volume_due_to_moving(destination_source_output, dest);
+
+ } else {
+ old_volume = o->volume;
+
+ if (pa_source_flat_volume_enabled(o->source)) {
+ /* Ok, so this is a regular stream, and flat volume is enabled. The
+ * volume will have to be updated as follows:
+ *
+ * o->volume := o->reference_ratio * o->source->reference_volume
+ * o->reference_ratio stays unchanged
+ * o->real_ratio := o->volume / o->source->real_volume
+ * (handled later by pa_source_set_volume)
+ * o->soft_volume := o->real_ratio * o->volume_factor
+ * (handled later by pa_source_set_volume) */
+
+ o->volume = o->source->reference_volume;
+ pa_cvolume_remap(&o->volume, &o->source->channel_map, &o->channel_map);
+ pa_sw_cvolume_multiply(&o->volume, &o->volume, &o->reference_ratio);
+
+ } else {
+ /* Ok, so this is a regular stream, and flat volume is disabled.
+ * The volume will have to be updated as follows:
+ *
+ * o->volume := o->reference_ratio
+ * o->reference_ratio stays unchanged
+ * o->real_ratio := o->reference_ratio
+ * o->soft_volume := o->real_ratio * o->volume_factor */
+
+ o->volume = o->reference_ratio;
+ o->real_ratio = o->reference_ratio;
+ pa_sw_cvolume_multiply(&o->soft_volume, &o->real_ratio, &o->volume_factor);
+ }
+
+ /* Notify others about the changed source output volume. */
+ if (!pa_cvolume_equal(&o->volume, &old_volume)) {
+ /* XXX: In case o->source has flat volume enabled, then real_ratio
+ * and soft_volume are not updated yet. Let's hope that the
+ * callback implementation doesn't care about those variables... */
+ if (o->volume_changed)
+ o->volume_changed(o);
+
+ pa_subscription_post(o->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE, o->index);
+ }
+ }
+
+ /* If o->source == dest, then recursion has finished, and we can finally call
+ * pa_source_set_volume(), which will do the rest of the updates. */
+ if ((o->source == dest) && pa_source_flat_volume_enabled(o->source))
+ pa_source_set_volume(o->source, NULL, FALSE, o->save_volume);
+}
+
/* Called from main context */
int pa_source_output_finish_move(pa_source_output *o, pa_source *dest, pa_bool_t save) {
pa_resampler *new_resampler;
@@ -951,11 +1438,14 @@ int pa_source_output_finish_move(pa_source_output *o, pa_source *dest, pa_bool_t
o->save_source = save;
pa_idxset_put(o->source->outputs, pa_source_output_ref(o), NULL);
+ pa_cvolume_remap(&o->volume_factor_source, &o->channel_map, &o->source->channel_map);
+
if (pa_source_output_get_state(o) == PA_SOURCE_OUTPUT_CORKED)
o->source->n_corked++;
/* Replace resampler */
if (new_resampler != o->thread_info.resampler) {
+
if (o->thread_info.resampler)
pa_resampler_free(o->thread_info.resampler);
o->thread_info.resampler = new_resampler;
@@ -976,6 +1466,8 @@ int pa_source_output_finish_move(pa_source_output *o, pa_source *dest, pa_bool_t
pa_source_update_status(dest);
+ update_volume_due_to_moving(o, dest);
+
pa_assert_se(pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o->source), PA_SOURCE_MESSAGE_ADD_OUTPUT, o, 0, NULL) == 0);
pa_log_debug("Successfully moved source output %i to %s.", o->index, dest->name);
@@ -1075,10 +1567,19 @@ int pa_source_output_process_msg(pa_msgobject *mo, int code, void *userdata, int
pa_resampler_set_output_rate(o->thread_info.resampler, PA_PTR_TO_UINT(userdata));
return 0;
- case PA_SOURCE_OUTPUT_MESSAGE_SET_STATE:
+ case PA_SOURCE_OUTPUT_MESSAGE_SET_STATE: {
+ pa_source_output *ssync;
pa_source_output_set_state_within_thread(o, PA_PTR_TO_UINT(userdata));
+
+ for (ssync = o->thread_info.sync_prev; ssync; ssync = ssync->thread_info.sync_prev)
+ pa_source_output_set_state_within_thread(ssync, PA_PTR_TO_UINT(userdata));
+
+ for (ssync = o->thread_info.sync_next; ssync; ssync = ssync->thread_info.sync_next)
+ pa_source_output_set_state_within_thread(ssync, PA_PTR_TO_UINT(userdata));
+
return 0;
+ }
case PA_SOURCE_OUTPUT_MESSAGE_SET_REQUESTED_LATENCY: {
pa_usec_t *usec = userdata;
@@ -1094,6 +1595,18 @@ int pa_source_output_process_msg(pa_msgobject *mo, int code, void *userdata, int
*r = o->thread_info.requested_source_latency;
return 0;
}
+
+ case PA_SOURCE_OUTPUT_MESSAGE_SET_SOFT_VOLUME:
+ if (!pa_cvolume_equal(&o->thread_info.soft_volume, &o->soft_volume)) {
+ o->thread_info.soft_volume = o->soft_volume;
+ }
+ return 0;
+
+ case PA_SOURCE_OUTPUT_MESSAGE_SET_SOFT_MUTE:
+ if (o->thread_info.muted != o->muted) {
+ o->thread_info.muted = o->muted;
+ }
+ return 0;
}
return -PA_ERR_NOTIMPLEMENTED;
diff --git a/src/pulsecore/source-output.h b/src/pulsecore/source-output.h
index 88de4ae6..4bcddbaf 100644
--- a/src/pulsecore/source-output.h
+++ b/src/pulsecore/source-output.h
@@ -86,10 +86,26 @@ struct pa_source_output {
pa_channel_map channel_map;
pa_format_info *format;
- /* if TRUE then the source we are connected to is worth
- * remembering, i.e. was explicitly chosen by the user and not
- * automatically. module-stream-restore looks for this.*/
- pa_bool_t save_source:1;
+ pa_source_output *sync_prev, *sync_next;
+
+ /* Also see http://pulseaudio.org/wiki/InternalVolumes */
+ pa_cvolume volume; /* The volume clients are informed about */
+ pa_cvolume reference_ratio; /* The ratio of the stream's volume to the source's reference volume */
+ pa_cvolume real_ratio; /* The ratio of the stream's volume to the source's real volume */
+ pa_cvolume volume_factor; /* An internally used volume factor that can be used by modules to apply effects and suchlike without having that visible to the outside */
+ pa_cvolume soft_volume; /* The internal software volume we apply to all PCM data while it passes through. Usually calculated as real_ratio * volume_factor */
+
+ pa_cvolume volume_factor_source; /* A second volume factor in format of the source this stream is connected to */
+
+ pa_bool_t volume_writable:1;
+
+ pa_bool_t muted:1;
+
+ /* if TRUE then the source we are connected to and/or the volume
+ * set is worth remembering, i.e. was explicitly chosen by the
+ * user and not automatically. module-stream-restore looks for
+ * this.*/
+ pa_bool_t save_source:1, save_volume:1, save_muted:1;
pa_resample_method_t requested_resample_method, actual_resample_method;
@@ -118,7 +134,10 @@ struct pa_source_output {
void (*update_source_fixed_latency) (pa_source_output *i); /* may be NULL */
/* If non-NULL this function is called when the output is first
- * connected to a source. Called from IO thread context */
+ * connected to a source or when the rtpoll/asyncmsgq fields
+ * change. You usually don't need to implement this function
+ * unless you rewrite a source that is piggy-backed onto
+ * another. Called from IO thread context */
void (*attach) (pa_source_output *o); /* may be NULL */
/* If non-NULL this function is called when the output is
@@ -134,9 +153,9 @@ struct pa_source_output {
void (*suspend_within_thread) (pa_source_output *o, pa_bool_t b); /* may be NULL */
/* If non-NULL called whenever the source output is moved to a new
- * source. Called from main context after the stream was detached
- * from the old source and before it is attached to the new
- * source. If dest is NULL the move was executed in two
+ * source. Called from main context after the source output has been
+ * detached from the old source and before it has been attached to
+ * the new source. If dest is NULL the move was executed in two
* phases and the second one failed; the stream will be destroyed
* after this call. */
void (*moving) (pa_source_output *o, pa_source *dest); /* may be NULL */
@@ -164,9 +183,20 @@ struct pa_source_output {
* control events. */
void (*send_event)(pa_source_output *o, const char *event, pa_proplist* data);
+ /* If non-NULL this function is called whenever the source output
+ * volume changes. Called from main context */
+ void (*volume_changed)(pa_source_output *o); /* may be NULL */
+
+ /* If non-NULL this function is called whenever the source output
+ * mute status changes. Called from main context */
+ void (*mute_changed)(pa_source_output *o); /* may be NULL */
+
struct {
pa_source_output_state_t state;
+ pa_cvolume soft_volume;
+ pa_bool_t muted:1;
+
pa_bool_t attached:1; /* True only between ->attach() and ->detach() calls */
pa_sample_spec sample_spec;
@@ -177,6 +207,8 @@ struct pa_source_output {
* don't implement rewind() */
pa_memblockq *delay_memblockq;
+ pa_source_output *sync_prev, *sync_next;
+
/* The requested latency for the source */
pa_usec_t requested_source_latency;
@@ -195,6 +227,8 @@ enum {
PA_SOURCE_OUTPUT_MESSAGE_SET_STATE,
PA_SOURCE_OUTPUT_MESSAGE_SET_REQUESTED_LATENCY,
PA_SOURCE_OUTPUT_MESSAGE_GET_REQUESTED_LATENCY,
+ PA_SOURCE_OUTPUT_MESSAGE_SET_SOFT_VOLUME,
+ PA_SOURCE_OUTPUT_MESSAGE_SET_SOFT_MUTE,
PA_SOURCE_OUTPUT_MESSAGE_MAX
};
@@ -219,22 +253,38 @@ typedef struct pa_source_output_new_data {
pa_resample_method_t resample_method;
+ pa_source_output *sync_base;
+
pa_sample_spec sample_spec;
pa_channel_map channel_map;
pa_format_info *format;
pa_idxset *req_formats;
pa_idxset *nego_formats;
+ pa_cvolume volume, volume_factor, volume_factor_source;
+ pa_bool_t muted:1;
+
pa_bool_t sample_spec_is_set:1;
pa_bool_t channel_map_is_set:1;
- pa_bool_t save_source:1;
+ pa_bool_t volume_is_set:1, volume_factor_is_set:1, volume_factor_source_is_set:1;
+ pa_bool_t muted_is_set:1;
+
+ pa_bool_t volume_is_absolute:1;
+
+ pa_bool_t volume_writable:1;
+
+ pa_bool_t save_source:1, save_volume:1, save_muted:1;
} pa_source_output_new_data;
pa_source_output_new_data* pa_source_output_new_data_init(pa_source_output_new_data *data);
void pa_source_output_new_data_set_sample_spec(pa_source_output_new_data *data, const pa_sample_spec *spec);
void pa_source_output_new_data_set_channel_map(pa_source_output_new_data *data, const pa_channel_map *map);
pa_bool_t pa_source_output_new_data_is_passthrough(pa_source_output_new_data *data);
+void pa_source_output_new_data_set_volume(pa_source_output_new_data *data, const pa_cvolume *volume);
+void pa_source_output_new_data_apply_volume_factor(pa_source_output_new_data *data, const pa_cvolume *volume_factor);
+void pa_source_output_new_data_apply_volume_factor_source(pa_source_output_new_data *data, const pa_cvolume *volume_factor);
+void pa_source_output_new_data_set_muted(pa_source_output_new_data *data, pa_bool_t mute);
pa_bool_t pa_source_output_new_data_set_source(pa_source_output_new_data *data, pa_source *s, pa_bool_t save);
pa_bool_t pa_source_output_new_data_set_formats(pa_source_output_new_data *data, pa_idxset *formats);
void pa_source_output_new_data_done(pa_source_output_new_data *data);
@@ -266,7 +316,13 @@ void pa_source_output_kill(pa_source_output*o);
pa_usec_t pa_source_output_get_latency(pa_source_output *o, pa_usec_t *source_latency);
+pa_bool_t pa_source_output_is_volume_readable(pa_source_output *o);
pa_bool_t pa_source_output_is_passthrough(pa_source_output *o);
+void pa_source_output_set_volume(pa_source_output *o, const pa_cvolume *volume, pa_bool_t save, pa_bool_t absolute);
+pa_cvolume *pa_source_output_get_volume(pa_source_output *o, pa_cvolume *volume, pa_bool_t absolute);
+
+void pa_source_output_set_mute(pa_source_output *o, pa_bool_t mute, pa_bool_t save);
+pa_bool_t pa_source_output_get_mute(pa_source_output *o);
void pa_source_output_update_proplist(pa_source_output *o, pa_update_mode_t mode, pa_proplist *p);
diff --git a/src/pulsecore/source.c b/src/pulsecore/source.c
index 587291ad..9c15d3f3 100644
--- a/src/pulsecore/source.c
+++ b/src/pulsecore/source.c
@@ -32,6 +32,7 @@
#include <pulse/xmalloc.h>
#include <pulse/timeval.h>
#include <pulse/util.h>
+#include <pulse/rtclock.h>
#include <pulse/internal.h>
#include <pulsecore/core-util.h>
@@ -40,6 +41,7 @@
#include <pulsecore/core-subscribe.h>
#include <pulsecore/log.h>
#include <pulsecore/sample-util.h>
+#include <pulsecore/flist.h>
#include "source.h"
@@ -49,8 +51,23 @@
PA_DEFINE_PUBLIC_CLASS(pa_source, pa_msgobject);
+struct pa_source_volume_change {
+ pa_usec_t at;
+ pa_cvolume hw_volume;
+
+ PA_LLIST_FIELDS(pa_source_volume_change);
+};
+
+struct source_message_set_port {
+ pa_device_port *port;
+ int ret;
+};
+
static void source_free(pa_object *o);
+static void pa_source_volume_change_push(pa_source *s);
+static void pa_source_volume_change_flush(pa_source *s);
+
pa_source_new_data* pa_source_new_data_init(pa_source_new_data *data) {
pa_assert(data);
@@ -179,8 +196,14 @@ pa_source* pa_source_new(
pa_return_null_if_fail(pa_channel_map_valid(&data->channel_map));
pa_return_null_if_fail(data->channel_map.channels == data->sample_spec.channels);
- if (!data->volume_is_set)
+ /* FIXME: There should probably be a general function for checking whether
+ * the source volume is allowed to be set, like there is for source outputs. */
+ pa_assert(!data->volume_is_set || !(flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER));
+
+ if (!data->volume_is_set) {
pa_cvolume_reset(&data->volume, data->sample_spec.channels);
+ data->save_volume = FALSE;
+ }
pa_return_null_if_fail(pa_cvolume_valid(&data->volume));
pa_return_null_if_fail(pa_cvolume_compatible(&data->volume, &data->sample_spec));
@@ -225,7 +248,7 @@ pa_source* pa_source_new(
s->monitor_of = NULL;
s->output_from_master = NULL;
- s->volume = data->volume;
+ s->reference_volume = s->real_volume = data->volume;
pa_cvolume_reset(&s->soft_volume, s->sample_spec.channels);
s->base_volume = PA_VOLUME_NORM;
s->n_volume_steps = PA_VOLUME_NORM+1;
@@ -280,6 +303,13 @@ pa_source* pa_source_new(
s->thread_info.max_latency = ABSOLUTE_MAX_LATENCY;
s->thread_info.fixed_latency = flags & PA_SOURCE_DYNAMIC_LATENCY ? 0 : DEFAULT_FIXED_LATENCY;
+ PA_LLIST_HEAD_INIT(pa_source_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 = 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_source_put() */
pa_assert_se(pa_idxset_put(core->sources, s, &s->index) >= 0);
if (s->card)
@@ -358,24 +388,59 @@ void pa_source_put(pa_source *s) {
pa_assert_ctl_context();
pa_assert(s->state == PA_SOURCE_INIT);
+ pa_assert(!(s->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER) || s->output_from_master);
/* The following fields must be initialized properly when calling _put() */
pa_assert(s->asyncmsgq);
pa_assert(s->thread_info.min_latency <= s->thread_info.max_latency);
- /* Generally, flags should be initialized via pa_source_new(). As
- * a special exception we allow volume related flags to be set
+ /* Generally, flags should be initialized via pa_source_new(). As a
+ * special exception we allow volume related flags to be set
* between _new() and _put(). */
- if (!(s->flags & PA_SOURCE_HW_VOLUME_CTRL))
+ /* XXX: Currently decibel volume is disabled for all sources that use volume
+ * sharing. When the master source supports decibel volume, it would be good
+ * to have the flag also in the filter source, but currently we don't do that
+ * so that the flags of the filter source never change when it's moved from
+ * a master source to another. One solution for this problem would be to
+ * remove user-visible volume altogether from filter sources when volume
+ * sharing is used, but the current approach was easier to implement... */
+ if (!(s->flags & PA_SOURCE_HW_VOLUME_CTRL) && !(s->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER))
s->flags |= PA_SOURCE_DECIBEL_VOLUME;
+ if ((s->flags & PA_SOURCE_DECIBEL_VOLUME) && s->core->flat_volumes)
+ s->flags |= PA_SOURCE_FLAT_VOLUME;
+
+ if (s->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER) {
+ pa_source *root_source = s->output_from_master->source;
+
+ while (root_source->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER)
+ root_source = root_source->output_from_master->source;
+
+ s->reference_volume = root_source->reference_volume;
+ pa_cvolume_remap(&s->reference_volume, &root_source->channel_map, &s->channel_map);
+
+ s->real_volume = root_source->real_volume;
+ pa_cvolume_remap(&s->real_volume, &root_source->channel_map, &s->channel_map);
+ } else
+ /* We assume that if the sink implementor changed the default
+ * volume he did so in real_volume, because that is the usual
+ * place where he is supposed to place his changes. */
+ s->reference_volume = s->real_volume;
+
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_SOURCE_HW_VOLUME_CTRL) || (s->base_volume == PA_VOLUME_NORM && s->flags & PA_SOURCE_DECIBEL_VOLUME));
+ pa_assert((s->flags & PA_SOURCE_HW_VOLUME_CTRL)
+ || (s->base_volume == PA_VOLUME_NORM
+ && ((s->flags & PA_SOURCE_DECIBEL_VOLUME || (s->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER)))));
pa_assert(!(s->flags & PA_SOURCE_DECIBEL_VOLUME) || s->n_volume_steps == PA_VOLUME_NORM+1);
pa_assert(!(s->flags & PA_SOURCE_DYNAMIC_LATENCY) == (s->thread_info.fixed_latency != 0));
+ pa_assert(!(s->flags & PA_SOURCE_HW_VOLUME_CTRL) || s->set_volume);
+ pa_assert(!(s->flags & PA_SOURCE_SYNC_VOLUME) || (s->flags & PA_SOURCE_HW_VOLUME_CTRL));
+ pa_assert(!(s->flags & PA_SOURCE_SYNC_VOLUME) || s->write_volume);
+ pa_assert(!(s->flags & PA_SOURCE_HW_MUTE_CTRL) || s->set_mute);
pa_assert_se(source_set_state(s, PA_SOURCE_IDLE) == 0);
@@ -529,7 +594,7 @@ int pa_source_suspend(pa_source *s, pa_bool_t suspend, pa_suspend_cause_t cause)
pa_log_debug("Suspend cause of source %s is 0x%04x, %s", s->name, s->suspend_cause, s->suspend_cause ? "suspending" : "resuming");
- if (suspend)
+ if (s->suspend_cause)
return source_set_state(s, PA_SOURCE_SUSPENDED);
else
return source_set_state(s, pa_source_used_by(s) ? PA_SOURCE_RUNNING : PA_SOURCE_IDLE);
@@ -748,7 +813,7 @@ pa_usec_t pa_source_get_latency_within_thread(pa_source *s) {
o = PA_MSGOBJECT(s);
- /* We probably should make this a proper vtable callback instead of going through process_msg() */
+ /* FIXME: We probably should make this a proper vtable callback instead of going through process_msg() */
if (o->process_msg(o, PA_SOURCE_MESSAGE_GET_LATENCY, &usec, 0, NULL) < 0)
return -1;
@@ -756,6 +821,21 @@ pa_usec_t pa_source_get_latency_within_thread(pa_source *s) {
return usec;
}
+/* Called from the main thread (and also from the IO thread while the main
+ * thread is waiting).
+ *
+ * When a source uses volume sharing, it never has the PA_SOURCE_FLAT_VOLUME flag
+ * set. Instead, flat volume mode is detected by checking whether the root source
+ * has the flag set. */
+pa_bool_t pa_source_flat_volume_enabled(pa_source *s) {
+ pa_source_assert_ref(s);
+
+ while (s->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER)
+ s = s->output_from_master->source;
+
+ return (s->flags & PA_SOURCE_FLAT_VOLUME);
+}
+
/* Called from main context */
pa_bool_t pa_source_is_passthrough(pa_source *s) {
@@ -765,59 +845,569 @@ pa_bool_t pa_source_is_passthrough(pa_source *s) {
return (s->monitor_of && pa_sink_is_passthrough(s->monitor_of));
}
+/* Called from main context. */
+static void compute_reference_ratio(pa_source_output *o) {
+ unsigned c = 0;
+ pa_cvolume remapped;
+
+ pa_assert(o);
+ pa_assert(pa_source_flat_volume_enabled(o->source));
+
+ /*
+ * Calculates the reference ratio from the source's reference
+ * volume. This basically calculates:
+ *
+ * o->reference_ratio = o->volume / o->source->reference_volume
+ */
+
+ remapped = o->source->reference_volume;
+ pa_cvolume_remap(&remapped, &o->source->channel_map, &o->channel_map);
+
+ o->reference_ratio.channels = o->sample_spec.channels;
+
+ for (c = 0; c < o->sample_spec.channels; c++) {
+
+ /* We don't update when the source volume is 0 anyway */
+ if (remapped.values[c] <= PA_VOLUME_MUTED)
+ continue;
+
+ /* Don't update the reference ratio unless necessary */
+ if (pa_sw_volume_multiply(
+ o->reference_ratio.values[c],
+ remapped.values[c]) == o->volume.values[c])
+ continue;
+
+ o->reference_ratio.values[c] = pa_sw_volume_divide(
+ o->volume.values[c],
+ remapped.values[c]);
+ }
+}
+
+/* Called from main context. Only called for the root source in volume sharing
+ * cases, except for internal recursive calls. */
+static void compute_reference_ratios(pa_source *s) {
+ uint32_t idx;
+ pa_source_output *o;
+
+ pa_source_assert_ref(s);
+ pa_assert_ctl_context();
+ pa_assert(PA_SOURCE_IS_LINKED(s->state));
+ pa_assert(pa_source_flat_volume_enabled(s));
+
+ PA_IDXSET_FOREACH(o, s->outputs, idx) {
+ compute_reference_ratio(o);
+
+ if (o->destination_source && (o->destination_source->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER))
+ compute_reference_ratios(o->destination_source);
+ }
+}
+
+/* Called from main context. Only called for the root source in volume sharing
+ * cases, except for internal recursive calls. */
+static void compute_real_ratios(pa_source *s) {
+ pa_source_output *o;
+ uint32_t idx;
+
+ pa_source_assert_ref(s);
+ pa_assert_ctl_context();
+ pa_assert(PA_SOURCE_IS_LINKED(s->state));
+ pa_assert(pa_source_flat_volume_enabled(s));
+
+ PA_IDXSET_FOREACH(o, s->outputs, idx) {
+ unsigned c;
+ pa_cvolume remapped;
+
+ if (o->destination_source && (o->destination_source->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER)) {
+ /* The origin source uses volume sharing, so this input's real ratio
+ * is handled as a special case - the real ratio must be 0 dB, and
+ * as a result i->soft_volume must equal i->volume_factor. */
+ pa_cvolume_reset(&o->real_ratio, o->real_ratio.channels);
+ o->soft_volume = o->volume_factor;
+
+ compute_real_ratios(o->destination_source);
+
+ continue;
+ }
+
+ /*
+ * This basically calculates:
+ *
+ * i->real_ratio := i->volume / s->real_volume
+ * i->soft_volume := i->real_ratio * i->volume_factor
+ */
+
+ remapped = s->real_volume;
+ pa_cvolume_remap(&remapped, &s->channel_map, &o->channel_map);
+
+ o->real_ratio.channels = o->sample_spec.channels;
+ o->soft_volume.channels = o->sample_spec.channels;
+
+ for (c = 0; c < o->sample_spec.channels; c++) {
+
+ if (remapped.values[c] <= PA_VOLUME_MUTED) {
+ /* We leave o->real_ratio untouched */
+ o->soft_volume.values[c] = PA_VOLUME_MUTED;
+ continue;
+ }
+
+ /* Don't lose accuracy unless necessary */
+ if (pa_sw_volume_multiply(
+ o->real_ratio.values[c],
+ remapped.values[c]) != o->volume.values[c])
+
+ o->real_ratio.values[c] = pa_sw_volume_divide(
+ o->volume.values[c],
+ remapped.values[c]);
+
+ o->soft_volume.values[c] = pa_sw_volume_multiply(
+ o->real_ratio.values[c],
+ o->volume_factor.values[c]);
+ }
+
+ /* We don't copy the soft_volume to the thread_info data
+ * here. That must be done by the caller */
+ }
+}
+
+static pa_cvolume *cvolume_remap_minimal_impact(
+ pa_cvolume *v,
+ const pa_cvolume *template,
+ const pa_channel_map *from,
+ const pa_channel_map *to) {
+
+ pa_cvolume t;
+
+ pa_assert(v);
+ pa_assert(template);
+ pa_assert(from);
+ pa_assert(to);
+ pa_assert(pa_cvolume_compatible_with_channel_map(v, from));
+ pa_assert(pa_cvolume_compatible_with_channel_map(template, to));
+
+ /* Much like pa_cvolume_remap(), but tries to minimize impact when
+ * mapping from source output to source volumes:
+ *
+ * If template is a possible remapping from v it is used instead
+ * of remapping anew.
+ *
+ * If the channel maps don't match we set an all-channel volume on
+ * the source to ensure that changing a volume on one stream has no
+ * effect that cannot be compensated for in another stream that
+ * does not have the same channel map as the source. */
+
+ if (pa_channel_map_equal(from, to))
+ return v;
+
+ t = *template;
+ if (pa_cvolume_equal(pa_cvolume_remap(&t, to, from), v)) {
+ *v = *template;
+ return v;
+ }
+
+ pa_cvolume_set(v, to->channels, pa_cvolume_max(v));
+ return v;
+}
+
+/* Called from main thread. Only called for the root source in volume sharing
+ * cases, except for internal recursive calls. */
+static void get_maximum_output_volume(pa_source *s, pa_cvolume *max_volume, const pa_channel_map *channel_map) {
+ pa_source_output *o;
+ uint32_t idx;
+
+ pa_source_assert_ref(s);
+ pa_assert(max_volume);
+ pa_assert(channel_map);
+ pa_assert(pa_source_flat_volume_enabled(s));
+
+ PA_IDXSET_FOREACH(o, s->outputs, idx) {
+ pa_cvolume remapped;
+
+ if (o->destination_source && (o->destination_source->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER)) {
+ get_maximum_output_volume(o->destination_source, max_volume, channel_map);
+
+ /* Ignore this output. The origin source uses volume sharing, so this
+ * output's volume will be set to be equal to the root source's real
+ * volume. Obviously this outputs's current volume must not then
+ * affect what the root source's real volume will be. */
+ continue;
+ }
+
+ remapped = o->volume;
+ cvolume_remap_minimal_impact(&remapped, max_volume, &o->channel_map, channel_map);
+ pa_cvolume_merge(max_volume, max_volume, &remapped);
+ }
+}
+
+/* Called from main thread. Only called for the root source in volume sharing
+ * cases, except for internal recursive calls. */
+static pa_bool_t has_outputs(pa_source *s) {
+ pa_source_output *o;
+ uint32_t idx;
+
+ pa_source_assert_ref(s);
+
+ PA_IDXSET_FOREACH(o, s->outputs, idx) {
+ if (!o->destination_source || !(o->destination_source->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER) || has_outputs(o->destination_source))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/* Called from main thread. Only called for the root source in volume sharing
+ * cases, except for internal recursive calls. */
+static void update_real_volume(pa_source *s, const pa_cvolume *new_volume, pa_channel_map *channel_map) {
+ pa_source_output *o;
+ uint32_t idx;
+
+ pa_source_assert_ref(s);
+ pa_assert(new_volume);
+ pa_assert(channel_map);
+
+ s->real_volume = *new_volume;
+ pa_cvolume_remap(&s->real_volume, channel_map, &s->channel_map);
+
+ PA_IDXSET_FOREACH(o, s->outputs, idx) {
+ if (o->destination_source && (o->destination_source->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER)) {
+ if (pa_source_flat_volume_enabled(s)) {
+ pa_cvolume old_volume = o->volume;
+
+ /* Follow the root source's real volume. */
+ o->volume = *new_volume;
+ pa_cvolume_remap(&o->volume, channel_map, &o->channel_map);
+ compute_reference_ratio(o);
+
+ /* The volume changed, let's tell people so */
+ if (!pa_cvolume_equal(&old_volume, &o->volume)) {
+ if (o->volume_changed)
+ o->volume_changed(o);
+
+ pa_subscription_post(o->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE, o->index);
+ }
+ }
+
+ update_real_volume(o->destination_source, new_volume, channel_map);
+ }
+ }
+}
+
+/* Called from main thread. Only called for the root source in shared volume
+ * cases. */
+static void compute_real_volume(pa_source *s) {
+ pa_source_assert_ref(s);
+ pa_assert_ctl_context();
+ pa_assert(PA_SOURCE_IS_LINKED(s->state));
+ pa_assert(pa_source_flat_volume_enabled(s));
+ pa_assert(!(s->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER));
+
+ /* This determines the maximum volume of all streams and sets
+ * s->real_volume accordingly. */
+
+ if (!has_outputs(s)) {
+ /* In the special case that we have no source outputs we leave the
+ * volume unmodified. */
+ update_real_volume(s, &s->reference_volume, &s->channel_map);
+ return;
+ }
+
+ pa_cvolume_mute(&s->real_volume, s->channel_map.channels);
+
+ /* First let's determine the new maximum volume of all outputs
+ * connected to this source */
+ get_maximum_output_volume(s, &s->real_volume, &s->channel_map);
+ update_real_volume(s, &s->real_volume, &s->channel_map);
+
+ /* Then, let's update the real ratios/soft volumes of all outputs
+ * connected to this source */
+ compute_real_ratios(s);
+}
+
+/* Called from main thread. Only called for the root source in shared volume
+ * cases, except for internal recursive calls. */
+static void propagate_reference_volume(pa_source *s) {
+ pa_source_output *o;
+ uint32_t idx;
+
+ pa_source_assert_ref(s);
+ pa_assert_ctl_context();
+ pa_assert(PA_SOURCE_IS_LINKED(s->state));
+ pa_assert(pa_source_flat_volume_enabled(s));
+
+ /* This is called whenever the source volume changes that is not
+ * caused by a source output volume change. We need to fix up the
+ * source output volumes accordingly */
+
+ PA_IDXSET_FOREACH(o, s->outputs, idx) {
+ pa_cvolume old_volume;
+
+ if (o->destination_source && (o->destination_source->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER)) {
+ propagate_reference_volume(o->destination_source);
+
+ /* Since the origin source uses volume sharing, this output's volume
+ * needs to be updated to match the root source's real volume, but
+ * that will be done later in update_shared_real_volume(). */
+ continue;
+ }
+
+ old_volume = o->volume;
+
+ /* This basically calculates:
+ *
+ * o->volume := o->reference_volume * o->reference_ratio */
+
+ o->volume = s->reference_volume;
+ pa_cvolume_remap(&o->volume, &s->channel_map, &o->channel_map);
+ pa_sw_cvolume_multiply(&o->volume, &o->volume, &o->reference_ratio);
+
+ /* The volume changed, let's tell people so */
+ if (!pa_cvolume_equal(&old_volume, &o->volume)) {
+
+ if (o->volume_changed)
+ o->volume_changed(o);
+
+ pa_subscription_post(o->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE, o->index);
+ }
+ }
+}
+
+/* Called from main thread. Only called for the root source in volume sharing
+ * cases, except for internal recursive calls. The return value indicates
+ * whether any reference volume actually changed. */
+static pa_bool_t update_reference_volume(pa_source *s, const pa_cvolume *v, const pa_channel_map *channel_map, pa_bool_t save) {
+ pa_cvolume volume;
+ pa_bool_t reference_volume_changed;
+ pa_source_output *o;
+ uint32_t idx;
+
+ pa_source_assert_ref(s);
+ pa_assert(PA_SOURCE_IS_LINKED(s->state));
+ pa_assert(v);
+ pa_assert(channel_map);
+ pa_assert(pa_cvolume_valid(v));
+
+ volume = *v;
+ pa_cvolume_remap(&volume, channel_map, &s->channel_map);
+
+ reference_volume_changed = !pa_cvolume_equal(&volume, &s->reference_volume);
+ s->reference_volume = volume;
+
+ s->save_volume = (!reference_volume_changed && s->save_volume) || save;
+
+ if (reference_volume_changed)
+ pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+ else if (!(s->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER))
+ /* If the root source's volume doesn't change, then there can't be any
+ * changes in the other source in the source tree either.
+ *
+ * It's probably theoretically possible that even if the root source's
+ * volume changes slightly, some filter source doesn't change its volume
+ * due to rounding errors. If that happens, we still want to propagate
+ * the changed root source volume to the sources connected to the
+ * intermediate source that didn't change its volume. This theoretical
+ * possiblity is the reason why we have that !(s->flags &
+ * PA_SOURCE_SHARE_VOLUME_WITH_MASTER) condition. Probably nobody would
+ * notice even if we returned here FALSE always if
+ * reference_volume_changed is FALSE. */
+ return FALSE;
+
+ PA_IDXSET_FOREACH(o, s->outputs, idx) {
+ if (o->destination_source && (o->destination_source->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER))
+ update_reference_volume(o->destination_source, v, channel_map, FALSE);
+ }
+
+ return TRUE;
+}
+
/* Called from main thread */
void pa_source_set_volume(
pa_source *s,
const pa_cvolume *volume,
+ pa_bool_t send_msg,
pa_bool_t save) {
- pa_bool_t real_changed;
- pa_cvolume old_volume;
+ pa_cvolume new_reference_volume;
+ pa_source *root_source = s;
pa_source_assert_ref(s);
pa_assert_ctl_context();
pa_assert(PA_SOURCE_IS_LINKED(s->state));
- pa_assert(pa_cvolume_valid(volume));
- pa_assert(volume->channels == 1 || pa_cvolume_compatible(volume, &s->sample_spec));
+ pa_assert(!volume || pa_cvolume_valid(volume));
+ pa_assert(volume || pa_source_flat_volume_enabled(s));
+ pa_assert(!volume || volume->channels == 1 || pa_cvolume_compatible(volume, &s->sample_spec));
+
+ /* make sure we don't change the volume when a PASSTHROUGH output is connected */
+ if (pa_source_is_passthrough(s)) {
+ /* FIXME: Need to notify client that volume control is disabled */
+ pa_log_warn("Cannot change volume, Source is monitor of a PASSTHROUGH sink");
+ return;
+ }
- old_volume = s->volume;
+ /* In case of volume sharing, the volume is set for the root source first,
+ * from which it's then propagated to the sharing sources. */
+ while (root_source->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER)
+ root_source = root_source->output_from_master->source;
- if (pa_cvolume_compatible(volume, &s->sample_spec))
- s->volume = *volume;
- else
- pa_cvolume_scale(&s->volume, pa_cvolume_max(volume));
+ /* As a special exception we accept mono volumes on all sources --
+ * even on those with more complex channel maps */
- real_changed = !pa_cvolume_equal(&old_volume, &s->volume);
- s->save_volume = (!real_changed && s->save_volume) || save;
+ if (volume) {
+ if (pa_cvolume_compatible(volume, &s->sample_spec))
+ new_reference_volume = *volume;
+ else {
+ new_reference_volume = s->reference_volume;
+ pa_cvolume_scale(&new_reference_volume, pa_cvolume_max(volume));
+ }
- if (s->set_volume) {
- pa_cvolume_reset(&s->soft_volume, s->sample_spec.channels);
- s->set_volume(s);
- } else
- s->soft_volume = s->volume;
+ pa_cvolume_remap(&new_reference_volume, &s->channel_map, &root_source->channel_map);
+ }
- pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_SET_VOLUME, NULL, 0, NULL) == 0);
+ /* If volume is NULL we synchronize the source's real and reference
+ * volumes with the stream volumes. If it is not NULL we update
+ * the reference_volume with it. */
- if (real_changed)
- pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+ if (volume) {
+ if (update_reference_volume(root_source, &new_reference_volume, &root_source->channel_map, save)) {
+ if (pa_source_flat_volume_enabled(root_source)) {
+ /* OK, propagate this volume change back to the outputs */
+ propagate_reference_volume(root_source);
+
+ /* And now recalculate the real volume */
+ compute_real_volume(root_source);
+ } else
+ update_real_volume(root_source, &root_source->reference_volume, &root_source->channel_map);
+ }
+
+ } else {
+ pa_assert(pa_source_flat_volume_enabled(root_source));
+
+ /* Ok, let's determine the new real volume */
+ compute_real_volume(root_source);
+
+ /* Let's 'push' the reference volume if necessary */
+ pa_cvolume_merge(&new_reference_volume, &s->reference_volume, &root_source->real_volume);
+ update_reference_volume(root_source, &new_reference_volume, &root_source->channel_map, save);
+
+ /* Now that the reference volume is updated, we can update the streams'
+ * reference ratios. */
+ compute_reference_ratios(root_source);
+ }
+
+ if (root_source->set_volume) {
+ /* If we have a function set_volume(), then we do not apply a
+ * soft volume by default. However, set_volume() is free to
+ * apply one to root_source->soft_volume */
+
+ pa_cvolume_reset(&root_source->soft_volume, root_source->sample_spec.channels);
+ if (!(root_source->flags & PA_SOURCE_SYNC_VOLUME))
+ root_source->set_volume(root_source);
+
+ } else
+ /* If we have no function set_volume(), then the soft volume
+ * becomes the real volume */
+ root_source->soft_volume = root_source->real_volume;
+
+ /* This tells the source that soft volume and/or real volume changed */
+ if (send_msg)
+ pa_assert_se(pa_asyncmsgq_send(root_source->asyncmsgq, PA_MSGOBJECT(root_source), PA_SOURCE_MESSAGE_SET_SHARED_VOLUME, NULL, 0, NULL) == 0);
}
-/* Called from main thread. Only to be called by source implementor */
+/* Called from the io thread if sync volume is used, otherwise from the main thread.
+ * Only to be called by source implementor */
void pa_source_set_soft_volume(pa_source *s, const pa_cvolume *volume) {
+
pa_source_assert_ref(s);
- pa_assert_ctl_context();
+ pa_assert(!(s->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER));
+
+ if (s->flags & PA_SOURCE_SYNC_VOLUME)
+ pa_source_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_SOURCE_IS_LINKED(s->state))
+ if (PA_SOURCE_IS_LINKED(s->state) && !(s->flags & PA_SOURCE_SYNC_VOLUME))
pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_SET_VOLUME, NULL, 0, NULL) == 0);
else
s->thread_info.soft_volume = s->soft_volume;
}
+/* Called from the main thread. Only called for the root source in volume sharing
+ * cases, except for internal recursive calls. */
+static void propagate_real_volume(pa_source *s, const pa_cvolume *old_real_volume) {
+ pa_source_output *o;
+ uint32_t idx;
+
+ pa_source_assert_ref(s);
+ pa_assert(old_real_volume);
+ pa_assert_ctl_context();
+ pa_assert(PA_SOURCE_IS_LINKED(s->state));
+
+ /* This is called when the hardware's real volume changes due to
+ * some external event. We copy the real volume into our
+ * reference volume and then rebuild the stream volumes based on
+ * i->real_ratio which should stay fixed. */
+
+ if (!(s->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER)) {
+ if (pa_cvolume_equal(old_real_volume, &s->real_volume))
+ return;
+
+ /* 1. Make the real volume the reference volume */
+ update_reference_volume(s, &s->real_volume, &s->channel_map, TRUE);
+ }
+
+ if (pa_source_flat_volume_enabled(s)) {
+
+ PA_IDXSET_FOREACH(o, s->outputs, idx) {
+ pa_cvolume old_volume = o->volume;
+
+ /* 2. Since the source's reference and real volumes are equal
+ * now our ratios should be too. */
+ o->reference_ratio = o->real_ratio;
+
+ /* 3. Recalculate the new stream reference volume based on the
+ * reference ratio and the sink's reference volume.
+ *
+ * This basically calculates:
+ *
+ * o->volume = s->reference_volume * o->reference_ratio
+ *
+ * This is identical to propagate_reference_volume() */
+ o->volume = s->reference_volume;
+ pa_cvolume_remap(&o->volume, &s->channel_map, &o->channel_map);
+ pa_sw_cvolume_multiply(&o->volume, &o->volume, &o->reference_ratio);
+
+ /* Notify if something changed */
+ if (!pa_cvolume_equal(&old_volume, &o->volume)) {
+
+ if (o->volume_changed)
+ o->volume_changed(o);
+
+ pa_subscription_post(o->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE, o->index);
+ }
+
+ if (o->destination_source && (o->destination_source->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER))
+ propagate_real_volume(o->destination_source, old_real_volume);
+ }
+ }
+
+ /* Something got changed in the hardware. It probably makes sense
+ * to save changed hw settings given that hw volume changes not
+ * triggered by PA are almost certainly done by the user. */
+ if (!(s->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER))
+ s->save_volume = TRUE;
+}
+
+/* Called from io thread */
+void pa_source_update_volume_and_mute(pa_source *s) {
+ pa_assert(s);
+ pa_source_assert_io_context(s);
+
+ pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_UPDATE_VOLUME_AND_MUTE, NULL, 0, NULL, NULL);
+}
+
/* Called from main thread */
const pa_cvolume *pa_source_get_volume(pa_source *s, pa_bool_t force_refresh) {
pa_source_assert_ref(s);
@@ -825,39 +1415,39 @@ const pa_cvolume *pa_source_get_volume(pa_source *s, pa_bool_t force_refresh) {
pa_assert(PA_SOURCE_IS_LINKED(s->state));
if (s->refresh_volume || force_refresh) {
- pa_cvolume old_volume;
+ struct pa_cvolume old_real_volume;
+
+ pa_assert(!(s->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER));
- old_volume = s->volume;
+ old_real_volume = s->real_volume;
- if (s->get_volume)
+ if (!(s->flags & PA_SOURCE_SYNC_VOLUME) && s->get_volume)
s->get_volume(s);
pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_GET_VOLUME, NULL, 0, NULL) == 0);
- if (!pa_cvolume_equal(&old_volume, &s->volume)) {
- s->save_volume = TRUE;
- pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
- }
+ update_real_volume(s, &s->real_volume, &s->channel_map);
+ propagate_real_volume(s, &old_real_volume);
}
- return &s->volume;
+ return &s->reference_volume;
}
-/* Called from main thread */
-void pa_source_volume_changed(pa_source *s, const pa_cvolume *new_volume) {
+/* Called from main thread. In volume sharing cases, only the root source may
+ * call this. */
+void pa_source_volume_changed(pa_source *s, const pa_cvolume *new_real_volume) {
+ pa_cvolume old_real_volume;
+
pa_source_assert_ref(s);
pa_assert_ctl_context();
pa_assert(PA_SOURCE_IS_LINKED(s->state));
+ pa_assert(!(s->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER));
/* The source implementor may call this if the volume changed to make sure everyone is notified */
- if (pa_cvolume_equal(&s->volume, new_volume))
- return;
-
- s->volume = *new_volume;
- s->save_volume = TRUE;
-
- pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+ old_real_volume = s->real_volume;
+ update_real_volume(s, new_real_volume, &s->channel_map);
+ propagate_real_volume(s, &old_real_volume);
}
/* Called from main thread */
@@ -872,7 +1462,7 @@ void pa_source_set_mute(pa_source *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_SOURCE_SYNC_VOLUME) && s->set_mute)
s->set_mute(s);
pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_SET_MUTE, NULL, 0, NULL) == 0);
@@ -883,6 +1473,7 @@ void pa_source_set_mute(pa_source *s, pa_bool_t mute, pa_bool_t save) {
/* Called from main thread */
pa_bool_t pa_source_get_mute(pa_source *s, pa_bool_t force_refresh) {
+
pa_source_assert_ref(s);
pa_assert_ctl_context();
pa_assert(PA_SOURCE_IS_LINKED(s->state));
@@ -890,7 +1481,7 @@ pa_bool_t pa_source_get_mute(pa_source *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_SOURCE_SYNC_VOLUME) && s->get_mute)
s->get_mute(s);
pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_GET_MUTE, NULL, 0, NULL) == 0);
@@ -970,8 +1561,8 @@ void pa_source_set_description(pa_source *s, const char *description) {
/* Called from main thread */
unsigned pa_source_linked_by(pa_source *s) {
pa_source_assert_ref(s);
- pa_assert(PA_SOURCE_IS_LINKED(s->state));
pa_assert_ctl_context();
+ pa_assert(PA_SOURCE_IS_LINKED(s->state));
return pa_idxset_size(s->outputs);
}
@@ -981,8 +1572,8 @@ unsigned pa_source_used_by(pa_source *s) {
unsigned ret;
pa_source_assert_ref(s);
- pa_assert(PA_SOURCE_IS_LINKED(s->state));
pa_assert_ctl_context();
+ pa_assert(PA_SOURCE_IS_LINKED(s->state));
ret = pa_idxset_size(s->outputs);
pa_assert(ret >= s->n_corked);
@@ -1029,6 +1620,39 @@ unsigned pa_source_check_suspend(pa_source *s) {
return ret;
}
+/* Called from the IO thread */
+static void sync_output_volumes_within_thread(pa_source *s) {
+ pa_source_output *o;
+ void *state = NULL;
+
+ pa_source_assert_ref(s);
+ pa_source_assert_io_context(s);
+
+ PA_HASHMAP_FOREACH(o, s->thread_info.outputs, state) {
+ if (pa_cvolume_equal(&o->thread_info.soft_volume, &o->soft_volume))
+ continue;
+
+ o->thread_info.soft_volume = o->soft_volume;
+ //pa_source_output_request_rewind(o, 0, TRUE, FALSE, FALSE);
+ }
+}
+
+/* Called from the IO thread. Only called for the root source in volume sharing
+ * cases, except for internal recursive calls. */
+static void set_shared_volume_within_thread(pa_source *s) {
+ pa_source_output *o;
+ void *state = NULL;
+
+ pa_source_assert_ref(s);
+
+ PA_MSGOBJECT(s)->process_msg(PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_SET_VOLUME_SYNCED, NULL, 0, NULL);
+
+ PA_HASHMAP_FOREACH(o, s->thread_info.outputs, state) {
+ if (o->destination_source && (o->destination_source->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER))
+ set_shared_volume_within_thread(o->destination_source);
+ }
+}
+
/* Called from IO thread, except when it is not */
int pa_source_process_msg(pa_msgobject *object, int code, void *userdata, int64_t offset, pa_memchunk *chunk) {
pa_source *s = PA_SOURCE(object);
@@ -1041,6 +1665,22 @@ int pa_source_process_msg(pa_msgobject *object, int code, void *userdata, int64_
pa_hashmap_put(s->thread_info.outputs, PA_UINT32_TO_PTR(o->index), pa_source_output_ref(o));
+ /* Since the caller sleeps in pa_source_output_put(), we can
+ * safely access data outside of thread_info even though
+ * it is mutable */
+
+ if ((o->thread_info.sync_prev = o->sync_prev)) {
+ pa_assert(o->source == o->thread_info.sync_prev->source);
+ pa_assert(o->sync_prev->sync_next == o);
+ o->thread_info.sync_prev->thread_info.sync_next = o;
+ }
+
+ if ((o->thread_info.sync_next = o->sync_next)) {
+ pa_assert(o->source == o->thread_info.sync_next->source);
+ pa_assert(o->sync_next->sync_prev == o);
+ o->thread_info.sync_next->thread_info.sync_prev = o;
+ }
+
if (o->direct_on_input) {
o->thread_info.direct_on_input = o->direct_on_input;
pa_hashmap_put(o->thread_info.direct_on_input->thread_info.direct_outputs, PA_UINT32_TO_PTR(o->index), o);
@@ -1064,7 +1704,9 @@ int pa_source_process_msg(pa_msgobject *object, int code, void *userdata, int64_
* requested latency. */
pa_source_output_set_requested_latency_within_thread(o, o->thread_info.requested_source_latency);
- return 0;
+ /* In flat volume mode we need to update the volume as
+ * well */
+ return object->process_msg(object, PA_SOURCE_MESSAGE_SET_SHARED_VOLUME, NULL, 0, NULL);
}
case PA_SOURCE_MESSAGE_REMOVE_OUTPUT: {
@@ -1078,6 +1720,23 @@ int pa_source_process_msg(pa_msgobject *object, int code, void *userdata, int64_
pa_assert(o->thread_info.attached);
o->thread_info.attached = FALSE;
+ /* Since the caller sleeps in pa_sink_input_unlink(),
+ * we can safely access data outside of thread_info even
+ * though it is mutable */
+
+ pa_assert(!o->sync_prev);
+ pa_assert(!o->sync_next);
+
+ if (o->thread_info.sync_prev) {
+ o->thread_info.sync_prev->thread_info.sync_next = o->thread_info.sync_prev->sync_next;
+ o->thread_info.sync_prev = NULL;
+ }
+
+ if (o->thread_info.sync_next) {
+ o->thread_info.sync_next->thread_info.sync_prev = o->thread_info.sync_next->sync_prev;
+ o->thread_info.sync_next = NULL;
+ }
+
if (o->thread_info.direct_on_input) {
pa_hashmap_remove(o->thread_info.direct_on_input->thread_info.direct_outputs, PA_UINT32_TO_PTR(o->index));
o->thread_info.direct_on_input = NULL;
@@ -1088,21 +1747,72 @@ int pa_source_process_msg(pa_msgobject *object, int code, void *userdata, int64_
pa_source_invalidate_requested_latency(s, TRUE);
+ /* In flat volume mode we need to update the volume as
+ * well */
+ return object->process_msg(object, PA_SOURCE_MESSAGE_SET_SHARED_VOLUME, NULL, 0, NULL);
+ }
+
+ case PA_SOURCE_MESSAGE_SET_SHARED_VOLUME: {
+ pa_source *root_source = s;
+
+ while (root_source->flags & PA_SOURCE_SHARE_VOLUME_WITH_MASTER)
+ root_source = root_source->output_from_master->source;
+
+ set_shared_volume_within_thread(root_source);
return 0;
}
+ case PA_SOURCE_MESSAGE_SET_VOLUME_SYNCED:
+
+ if (s->flags & PA_SOURCE_SYNC_VOLUME) {
+ s->set_volume(s);
+ pa_source_volume_change_push(s);
+ }
+ /* Fall through ... */
+
case PA_SOURCE_MESSAGE_SET_VOLUME:
- s->thread_info.soft_volume = s->soft_volume;
+
+ if (!pa_cvolume_equal(&s->thread_info.soft_volume, &s->soft_volume)) {
+ s->thread_info.soft_volume = s->soft_volume;
+ }
+
+ /* Fall through ... */
+
+ case PA_SOURCE_MESSAGE_SYNC_VOLUMES:
+ sync_output_volumes_within_thread(s);
return 0;
case PA_SOURCE_MESSAGE_GET_VOLUME:
+
+ if ((s->flags & PA_SOURCE_SYNC_VOLUME) && s->get_volume) {
+ s->get_volume(s);
+ pa_source_volume_change_flush(s);
+ pa_sw_cvolume_divide(&s->thread_info.current_hw_volume, &s->real_volume, &s->soft_volume);
+ }
+
+ /* In case source implementor reset SW volume. */
+ if (!pa_cvolume_equal(&s->thread_info.soft_volume, &s->soft_volume)) {
+ s->thread_info.soft_volume = s->soft_volume;
+ }
+
return 0;
case PA_SOURCE_MESSAGE_SET_MUTE:
- s->thread_info.soft_muted = s->muted;
+
+ if (s->thread_info.soft_muted != s->muted) {
+ s->thread_info.soft_muted = s->muted;
+ }
+
+ if (s->flags & PA_SOURCE_SYNC_VOLUME && s->set_mute)
+ s->set_mute(s);
+
return 0;
case PA_SOURCE_MESSAGE_GET_MUTE:
+
+ if (s->flags & PA_SOURCE_SYNC_VOLUME && s->get_mute)
+ s->get_mute(s);
+
return 0;
case PA_SOURCE_MESSAGE_SET_STATE: {
@@ -1122,7 +1832,6 @@ int pa_source_process_msg(pa_msgobject *object, int code, void *userdata, int64_
o->suspend_within_thread(o, s->thread_info.state == PA_SOURCE_SUSPENDED);
}
-
return 0;
}
@@ -1143,6 +1852,9 @@ int pa_source_process_msg(pa_msgobject *object, int code, void *userdata, int64_
pa_usec_t *usec = userdata;
*usec = pa_source_get_requested_latency_within_thread(s);
+ /* Yes, that's right, the IO thread will see -1 when no
+ * explicit requested latency is configured, the main
+ * thread will see max_latency */
if (*usec == (pa_usec_t) -1)
*usec = s->thread_info.max_latency;
@@ -1196,6 +1908,23 @@ int pa_source_process_msg(pa_msgobject *object, int code, void *userdata, int64_
/* Implementors need to overwrite this implementation! */
return -1;
+ case PA_SOURCE_MESSAGE_SET_PORT:
+
+ pa_assert(userdata);
+ if (s->set_port) {
+ struct source_message_set_port *msg_data = userdata;
+ msg_data->ret = s->set_port(s, msg_data->port);
+ }
+ return 0;
+
+ case PA_SOURCE_MESSAGE_UPDATE_VOLUME_AND_MUTE:
+ /* This message is sent from IO-thread and handled in main thread. */
+ pa_assert_ctl_context();
+
+ pa_source_get_volume(s, TRUE);
+ pa_source_get_mute(s, TRUE);
+ return 0;
+
case PA_SOURCE_MESSAGE_MAX:
;
}
@@ -1205,8 +1934,8 @@ int pa_source_process_msg(pa_msgobject *object, int code, void *userdata, int64_
/* Called from main thread */
int pa_source_suspend_all(pa_core *c, pa_bool_t suspend, pa_suspend_cause_t cause) {
- uint32_t idx;
pa_source *source;
+ uint32_t idx;
int ret = 0;
pa_core_assert_ref(c);
@@ -1552,8 +2281,9 @@ size_t pa_source_get_max_rewind(pa_source *s) {
/* Called from main context */
int pa_source_set_port(pa_source *s, const char *name, pa_bool_t save) {
pa_device_port *port;
+ int ret;
- pa_assert(s);
+ pa_source_assert_ref(s);
pa_assert_ctl_context();
if (!s->set_port) {
@@ -1572,7 +2302,15 @@ int pa_source_set_port(pa_source *s, const char *name, pa_bool_t save) {
return 0;
}
- if ((s->set_port(s, port)) < 0)
+ if (s->flags & PA_SOURCE_SYNC_VOLUME) {
+ struct source_message_set_port msg = { .port = port, .ret = 0 };
+ pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_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_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
@@ -1587,6 +2325,145 @@ int pa_source_set_port(pa_source *s, const char *name, pa_bool_t save) {
return 0;
}
+PA_STATIC_FLIST_DECLARE(pa_source_volume_change, 0, pa_xfree);
+
+/* Called from the IO thread. */
+static pa_source_volume_change *pa_source_volume_change_new(pa_source *s) {
+ pa_source_volume_change *c;
+ if (!(c = pa_flist_pop(PA_STATIC_FLIST_GET(pa_source_volume_change))))
+ c = pa_xnew(pa_source_volume_change, 1);
+
+ PA_LLIST_INIT(pa_source_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_source_volume_change_free(pa_source_volume_change *c) {
+ pa_assert(c);
+ if (pa_flist_push(PA_STATIC_FLIST_GET(pa_source_volume_change), c) < 0)
+ pa_xfree(c);
+}
+
+/* Called from the IO thread. */
+void pa_source_volume_change_push(pa_source *s) {
+ pa_source_volume_change *c = NULL;
+ pa_source_volume_change *nc = NULL;
+ uint32_t safety_margin = s->thread_info.volume_change_safety_margin;
+
+ const char *direction = NULL;
+
+ pa_assert(s);
+ nc = pa_source_volume_change_new(s);
+
+ /* NOTE: There is already more different volumes in pa_source 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_source_volume_change_free(nc);
+ return;
+ }
+
+ nc->at = pa_source_get_latency_within_thread(s);
+ 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_source_volume_change, s->thread_info.volume_changes, nc);
+ }
+ else {
+ PA_LLIST_INSERT_AFTER(pa_source_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), (long long unsigned) 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), (long long unsigned) c->at);
+ pa_source_volume_change_free(c);
+ }
+ nc->next = NULL;
+ s->thread_info.volume_changes_tail = nc;
+}
+
+/* Called from the IO thread. */
+static void pa_source_volume_change_flush(pa_source *s) {
+ pa_source_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_source_volume_change *next = c->next;
+ pa_source_volume_change_free(c);
+ c = next;
+ }
+}
+
+/* Called from the IO thread. */
+pa_bool_t pa_source_volume_change_apply(pa_source *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_source_volume_change *c = s->thread_info.volume_changes;
+ PA_LLIST_REMOVE(pa_source_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), (long long unsigned) c->at, (long long unsigned) (now - c->at));
+ ret = TRUE;
+ s->thread_info.current_hw_volume = c->hw_volume;
+ pa_source_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))
+ pa_log_debug("Next volume change in %lld usec", (long long) (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 main thread */
/* Gets the list of formats supported by the source. The members and idxset must
* be freed by the caller. */
diff --git a/src/pulsecore/source.h b/src/pulsecore/source.h
index 3cdc77ae..902cc036 100644
--- a/src/pulsecore/source.h
+++ b/src/pulsecore/source.h
@@ -24,6 +24,7 @@
***/
typedef struct pa_source pa_source;
+typedef struct pa_source_volume_change pa_source_volume_change;
#include <inttypes.h>
@@ -80,12 +81,15 @@ struct pa_source {
pa_volume_t base_volume; /* shall be constant */
unsigned n_volume_steps; /* shall be constant */
- pa_cvolume volume, soft_volume;
+ /* Also see http://pulseaudio.org/wiki/InternalVolumes */
+ pa_cvolume reference_volume; /* The volume exported and taken as reference base for relative source output volumes */
+ pa_cvolume real_volume; /* The volume that the hardware is configured to */
+ pa_cvolume soft_volume; /* The internal software volume we apply to all PCM data while it passes through */
+
pa_bool_t muted:1;
pa_bool_t refresh_volume:1;
pa_bool_t refresh_muted:1;
-
pa_bool_t save_port:1;
pa_bool_t save_volume:1;
pa_bool_t save_muted:1;
@@ -115,6 +119,19 @@ struct pa_source {
* will be sent to the IO thread instead. */
void (*set_volume)(pa_source *s); /* ditto */
+ /* Source drivers that set PA_SOURCE_SYNC_VOLUME must provide this
+ * callback. This callback is not used with source that do not set
+ * PA_SOURCE_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_source_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_source *s); /* ditto */
+
/* Called when the mute setting is queried. Called from main loop
* context. If this is NULL a PA_SOURCE_MESSAGE_GET_MUTE message
* will be sent to the IO thread instead. If refresh_mute is
@@ -160,6 +177,21 @@ struct pa_source {
pa_usec_t max_latency; /* An upper limit for the latencies */
pa_usec_t fixed_latency; /* for sources with PA_SOURCE_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_source_volume_change, volume_changes);
+ pa_source_volume_change *volume_changes_tail;
+ /* This value is updated in pa_source_volume_change_apply() and
+ * used only by sources with PA_SOURCE_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;
@@ -172,7 +204,10 @@ typedef enum pa_source_message {
PA_SOURCE_MESSAGE_ADD_OUTPUT,
PA_SOURCE_MESSAGE_REMOVE_OUTPUT,
PA_SOURCE_MESSAGE_GET_VOLUME,
+ PA_SOURCE_MESSAGE_SET_SHARED_VOLUME,
+ PA_SOURCE_MESSAGE_SET_VOLUME_SYNCED,
PA_SOURCE_MESSAGE_SET_VOLUME,
+ PA_SOURCE_MESSAGE_SYNC_VOLUMES,
PA_SOURCE_MESSAGE_GET_MUTE,
PA_SOURCE_MESSAGE_SET_MUTE,
PA_SOURCE_MESSAGE_GET_LATENCY,
@@ -186,6 +221,8 @@ typedef enum pa_source_message {
PA_SOURCE_MESSAGE_GET_FIXED_LATENCY,
PA_SOURCE_MESSAGE_GET_MAX_REWIND,
PA_SOURCE_MESSAGE_SET_MAX_REWIND,
+ PA_SOURCE_MESSAGE_SET_PORT,
+ PA_SOURCE_MESSAGE_UPDATE_VOLUME_AND_MUTE,
PA_SOURCE_MESSAGE_MAX
} pa_source_message_t;
@@ -269,11 +306,14 @@ int pa_source_update_status(pa_source*s);
int pa_source_suspend(pa_source *s, pa_bool_t suspend, pa_suspend_cause_t cause);
int pa_source_suspend_all(pa_core *c, pa_bool_t suspend, pa_suspend_cause_t cause);
+/* Use this instead of checking s->flags & PA_SOURCE_FLAT_VOLUME directly. */
+pa_bool_t pa_source_flat_volume_enabled(pa_source *s);
+
/* Is the source in passthrough mode? (that is, is this a monitor source for a sink
* that has a passthrough sink input connected to it. */
pa_bool_t pa_source_is_passthrough(pa_source *s);
-void pa_source_set_volume(pa_source *source, const pa_cvolume *volume, pa_bool_t save);
+void pa_source_set_volume(pa_source *source, const pa_cvolume *volume, pa_bool_t sendmsg, pa_bool_t save);
const pa_cvolume *pa_source_get_volume(pa_source *source, pa_bool_t force_refresh);
void pa_source_set_mute(pa_source *source, pa_bool_t mute, pa_bool_t save);
@@ -315,6 +355,10 @@ void pa_source_set_max_rewind_within_thread(pa_source *s, size_t max_rewind);
void pa_source_set_latency_range_within_thread(pa_source *s, pa_usec_t min_latency, pa_usec_t max_latency);
void pa_source_set_fixed_latency_within_thread(pa_source *s, pa_usec_t latency);
+void pa_source_update_volume_and_mute(pa_source *s);
+
+pa_bool_t pa_source_volume_change_apply(pa_source *s, pa_usec_t *usec_to_next);
+
/*** To be called exclusively by source output drivers, from IO context */
void pa_source_invalidate_requested_latency(pa_source *s, pa_bool_t dynamic);