#include <config.h>
#endif
+#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include <stdio.h>
#include <pulse/introspect.h>
+#include <pulse/format.h>
#include <pulse/utf8.h>
#include <pulse/xmalloc.h>
#include <pulse/timeval.h>
#include <pulse/util.h>
-#include <pulse/i18n.h>
#include <pulse/rtclock.h>
#include <pulse/internal.h>
+#include <pulsecore/i18n.h>
#include <pulsecore/sink-input.h>
#include <pulsecore/namereg.h>
#include <pulsecore/core-util.h>
#include <pulsecore/sample-util.h>
+#include <pulsecore/mix.h>
#include <pulsecore/core-subscribe.h>
#include <pulsecore/log.h>
#include <pulsecore/macro.h>
pa_zero(*data);
data->proplist = pa_proplist_new();
+ data->ports = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
return data;
}
data->channel_map = *map;
}
+void pa_sink_new_data_set_alternate_sample_rate(pa_sink_new_data *data, const uint32_t alternate_sample_rate) {
+ pa_assert(data);
+
+ data->alternate_sample_rate_is_set = TRUE;
+ data->alternate_sample_rate = alternate_sample_rate;
+}
+
void pa_sink_new_data_set_volume(pa_sink_new_data *data, const pa_cvolume *volume) {
pa_assert(data);
pa_proplist_free(data->proplist);
- if (data->ports) {
- pa_device_port *p;
-
- while ((p = pa_hashmap_steal_first(data->ports)))
- pa_device_port_free(p);
-
- pa_hashmap_free(data->ports, NULL, NULL);
- }
+ if (data->ports)
+ pa_device_port_hashmap_free(data->ports);
pa_xfree(data->name);
pa_xfree(data->active_port);
}
-pa_device_port *pa_device_port_new(const char *name, const char *description, size_t extra) {
- pa_device_port *p;
-
- pa_assert(name);
-
- p = pa_xmalloc(PA_ALIGN(sizeof(pa_device_port)) + extra);
- p->name = pa_xstrdup(name);
- p->description = pa_xstrdup(description);
-
- p->priority = 0;
-
- return p;
-}
-
-void pa_device_port_free(pa_device_port *p) {
- pa_assert(p);
-
- pa_xfree(p->name);
- pa_xfree(p->description);
- pa_xfree(p);
-}
/* Called from main context */
static void reset_callbacks(pa_sink *s) {
s->set_state = NULL;
s->get_volume = NULL;
s->set_volume = NULL;
+ s->write_volume = NULL;
s->get_mute = NULL;
s->set_mute = NULL;
s->request_rewind = NULL;
s->update_requested_latency = NULL;
s->set_port = NULL;
s->get_formats = NULL;
+ s->set_formats = NULL;
+ s->update_rate = NULL;
}
/* Called from main context */
s->state = PA_SINK_INIT;
s->flags = flags;
s->priority = 0;
- s->suspend_cause = 0;
+ s->suspend_cause = data->suspend_cause;
+ pa_sink_set_mixer_dirty(s, FALSE);
s->name = pa_xstrdup(name);
s->proplist = pa_proplist_copy(data->proplist);
s->driver = pa_xstrdup(pa_path_get_filename(data->driver));
s->sample_spec = data->sample_spec;
s->channel_map = data->channel_map;
+ s->default_sample_rate = s->sample_spec.rate;
+
+ if (data->alternate_sample_rate_is_set)
+ s->alternate_sample_rate = data->alternate_sample_rate;
+ else
+ s->alternate_sample_rate = s->core->alternate_sample_rate;
+
+ if (s->sample_spec.rate == s->alternate_sample_rate) {
+ pa_log_warn("Default and alternate sample rates are the same.");
+ s->alternate_sample_rate = 0;
+ }
s->inputs = pa_idxset_new(NULL, NULL);
s->n_corked = 0;
s->active_port = NULL;
s->save_port = FALSE;
- if (data->active_port && s->ports)
+ if (data->active_port)
if ((s->active_port = pa_hashmap_get(s->ports, data->active_port)))
s->save_port = data->save_port;
- if (!s->active_port && s->ports) {
+ if (!s->active_port) {
void *state;
pa_device_port *p;
s->active_port = p;
}
+ if (s->active_port)
+ s->latency_offset = s->active_port->latency_offset;
+ else
+ s->latency_offset = 0;
+
s->save_volume = data->save_volume;
s->save_muted = data->save_muted;
PA_LLIST_HEAD_INIT(pa_sink_volume_change, s->thread_info.volume_changes);
s->thread_info.volume_changes_tail = NULL;
pa_sw_cvolume_multiply(&s->thread_info.current_hw_volume, &s->soft_volume, &s->real_volume);
- s->thread_info.volume_change_safety_margin = core->sync_volume_safety_margin_usec;
- s->thread_info.volume_change_extra_delay = core->sync_volume_extra_delay_usec;
+ s->thread_info.volume_change_safety_margin = core->deferred_volume_safety_margin_usec;
+ s->thread_info.volume_change_extra_delay = core->deferred_volume_extra_delay_usec;
+ s->thread_info.latency_offset = s->latency_offset;
/* FIXME: This should probably be moved to pa_sink_put() */
pa_assert_se(pa_idxset_put(core->sinks, s, &s->index) >= 0);
pa_source_new_data_init(&source_data);
pa_source_new_data_set_sample_spec(&source_data, &s->sample_spec);
pa_source_new_data_set_channel_map(&source_data, &s->channel_map);
+ pa_source_new_data_set_alternate_sample_rate(&source_data, s->alternate_sample_rate);
source_data.name = pa_sprintf_malloc("%s.monitor", name);
source_data.driver = data->driver;
source_data.module = data->module;
s->state = state;
- if (state != PA_SINK_UNLINKED) { /* if we enter UNLINKED state pa_sink_unlink() will fire the apropriate events */
+ if (state != PA_SINK_UNLINKED) { /* if we enter UNLINKED state pa_sink_unlink() will fire the appropriate events */
pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_STATE_CHANGED], s);
pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK | PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
}
return 0;
}
+void pa_sink_set_get_volume_callback(pa_sink *s, pa_sink_cb_t cb) {
+ pa_assert(s);
+
+ s->get_volume = cb;
+}
+
+void pa_sink_set_set_volume_callback(pa_sink *s, pa_sink_cb_t cb) {
+ pa_sink_flags_t flags;
+
+ pa_assert(s);
+ pa_assert(!s->write_volume || cb);
+
+ s->set_volume = cb;
+
+ /* Save the current flags so we can tell if they've changed */
+ flags = s->flags;
+
+ if (cb) {
+ /* The sink implementor is responsible for setting decibel volume support */
+ s->flags |= PA_SINK_HW_VOLUME_CTRL;
+ } else {
+ s->flags &= ~PA_SINK_HW_VOLUME_CTRL;
+ /* See note below in pa_sink_put() about volume sharing and decibel volumes */
+ pa_sink_enable_decibel_volume(s, !(s->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER));
+ }
+
+ /* If the flags have changed after init, let any clients know via a change event */
+ if (s->state != PA_SINK_INIT && flags != s->flags)
+ pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+}
+
+void pa_sink_set_write_volume_callback(pa_sink *s, pa_sink_cb_t cb) {
+ pa_sink_flags_t flags;
+
+ pa_assert(s);
+ pa_assert(!cb || s->set_volume);
+
+ s->write_volume = cb;
+
+ /* Save the current flags so we can tell if they've changed */
+ flags = s->flags;
+
+ if (cb)
+ s->flags |= PA_SINK_DEFERRED_VOLUME;
+ else
+ s->flags &= ~PA_SINK_DEFERRED_VOLUME;
+
+ /* If the flags have changed after init, let any clients know via a change event */
+ if (s->state != PA_SINK_INIT && flags != s->flags)
+ pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+}
+
+void pa_sink_set_get_mute_callback(pa_sink *s, pa_sink_cb_t cb) {
+ pa_assert(s);
+
+ s->get_mute = cb;
+}
+
+void pa_sink_set_set_mute_callback(pa_sink *s, pa_sink_cb_t cb) {
+ pa_sink_flags_t flags;
+
+ pa_assert(s);
+
+ s->set_mute = cb;
+
+ /* Save the current flags so we can tell if they've changed */
+ flags = s->flags;
+
+ if (cb)
+ s->flags |= PA_SINK_HW_MUTE_CTRL;
+ else
+ s->flags &= ~PA_SINK_HW_MUTE_CTRL;
+
+ /* If the flags have changed after init, let any clients know via a change event */
+ if (s->state != PA_SINK_INIT && flags != s->flags)
+ pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+}
+
+static void enable_flat_volume(pa_sink *s, pa_bool_t enable) {
+ pa_sink_flags_t flags;
+
+ pa_assert(s);
+
+ /* Always follow the overall user preference here */
+ enable = enable && s->core->flat_volumes;
+
+ /* Save the current flags so we can tell if they've changed */
+ flags = s->flags;
+
+ if (enable)
+ s->flags |= PA_SINK_FLAT_VOLUME;
+ else
+ s->flags &= ~PA_SINK_FLAT_VOLUME;
+
+ /* If the flags have changed after init, let any clients know via a change event */
+ if (s->state != PA_SINK_INIT && flags != s->flags)
+ pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+}
+
+void pa_sink_enable_decibel_volume(pa_sink *s, pa_bool_t enable) {
+ pa_sink_flags_t flags;
+
+ pa_assert(s);
+
+ /* Save the current flags so we can tell if they've changed */
+ flags = s->flags;
+
+ if (enable) {
+ s->flags |= PA_SINK_DECIBEL_VOLUME;
+ enable_flat_volume(s, TRUE);
+ } else {
+ s->flags &= ~PA_SINK_DECIBEL_VOLUME;
+ enable_flat_volume(s, FALSE);
+ }
+
+ /* If the flags have changed after init, let any clients know via a change event */
+ if (s->state != PA_SINK_INIT && flags != s->flags)
+ pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+}
+
/* Called from main context */
void pa_sink_put(pa_sink* s) {
pa_sink_assert_ref(s);
pa_assert(s->thread_info.min_latency <= s->thread_info.max_latency);
/* Generally, flags should be initialized via pa_sink_new(). As a
- * special exception we allow volume related flags to be set
- * between _new() and _put(). */
+ * special exception we allow some volume related flags to be set
+ * between _new() and _put() by the callback setter functions above.
+ *
+ * Thus we implement a couple safeguards here which ensure the above
+ * setters were used (or at least the implementor made manual changes
+ * in a compatible way).
+ *
+ * Note: All of these flags set here can change over the life time
+ * of the sink. */
+ pa_assert(!(s->flags & PA_SINK_HW_VOLUME_CTRL) || s->set_volume);
+ pa_assert(!(s->flags & PA_SINK_DEFERRED_VOLUME) || s->write_volume);
+ pa_assert(!(s->flags & PA_SINK_HW_MUTE_CTRL) || s->set_mute);
/* XXX: Currently decibel volume is disabled for all sinks that use volume
* sharing. When the master sink supports decibel volume, it would be good
* a master sink to another. One solution for this problem would be to
* remove user-visible volume altogether from filter sinks when volume
* sharing is used, but the current approach was easier to implement... */
+ /* We always support decibel volumes in software, otherwise we leave it to
+ * the sink implementor to set this flag as needed.
+ *
+ * Note: This flag can also change over the life time of the sink. */
if (!(s->flags & PA_SINK_HW_VOLUME_CTRL) && !(s->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER))
- s->flags |= PA_SINK_DECIBEL_VOLUME;
+ pa_sink_enable_decibel_volume(s, TRUE);
- if ((s->flags & PA_SINK_DECIBEL_VOLUME) && s->core->flat_volumes)
- s->flags |= PA_SINK_FLAT_VOLUME;
+ /* If the sink implementor support DB volumes by itself, we should always
+ * try and enable flat volumes too */
+ if ((s->flags & PA_SINK_DECIBEL_VOLUME))
+ enable_flat_volume(s, TRUE);
if (s->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER) {
- pa_sink *root_sink = s->input_to_master->sink;
+ pa_sink *root_sink = pa_sink_get_master(s);
- while (root_sink->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)
- root_sink = root_sink->input_to_master->sink;
+ pa_assert(root_sink);
s->reference_volume = root_sink->reference_volume;
pa_cvolume_remap(&s->reference_volume, &root_sink->channel_map, &s->channel_map);
pa_assert(!(s->flags & PA_SINK_DYNAMIC_LATENCY) == (s->thread_info.fixed_latency != 0));
pa_assert(!(s->flags & PA_SINK_LATENCY) == !(s->monitor_source->flags & PA_SOURCE_LATENCY));
pa_assert(!(s->flags & PA_SINK_DYNAMIC_LATENCY) == !(s->monitor_source->flags & PA_SOURCE_DYNAMIC_LATENCY));
- pa_assert(!(s->flags & PA_SINK_HW_VOLUME_CTRL) || s->set_volume);
- pa_assert(!(s->flags & PA_SINK_SYNC_VOLUME) || (s->flags & PA_SINK_HW_VOLUME_CTRL));
- pa_assert(!(s->flags & PA_SINK_SYNC_VOLUME) || s->write_volume);
- pa_assert(!(s->flags & PA_SINK_HW_MUTE_CTRL) || s->set_mute);
pa_assert(s->monitor_source->thread_info.fixed_latency == s->thread_info.fixed_latency);
pa_assert(s->monitor_source->thread_info.min_latency == s->thread_info.min_latency);
pa_assert(s->monitor_source->thread_info.max_latency == s->thread_info.max_latency);
- pa_assert_se(sink_set_state(s, PA_SINK_IDLE) == 0);
+ if (s->suspend_cause)
+ pa_assert_se(sink_set_state(s, PA_SINK_SUSPENDED) == 0);
+ else
+ pa_assert_se(sink_set_state(s, PA_SINK_IDLE) == 0);
pa_source_put(s->monitor_source);
if (s->proplist)
pa_proplist_free(s->proplist);
- if (s->ports) {
- pa_device_port *p;
-
- while ((p = pa_hashmap_steal_first(s->ports)))
- pa_device_port_free(p);
-
- pa_hashmap_free(s->ports, NULL, NULL);
- }
+ if (s->ports)
+ pa_device_port_hashmap_free(s->ports);
pa_xfree(s);
}
return sink_set_state(s, pa_sink_used_by(s) ? PA_SINK_RUNNING : PA_SINK_IDLE);
}
+/* Called from any context - must be threadsafe */
+void pa_sink_set_mixer_dirty(pa_sink *s, pa_bool_t is_dirty)
+{
+ pa_atomic_store(&s->mixer_dirty, is_dirty ? 1 : 0);
+}
+
/* Called from main context */
int pa_sink_suspend(pa_sink *s, pa_bool_t suspend, pa_suspend_cause_t cause) {
pa_sink_assert_ref(s);
s->monitor_source->suspend_cause &= ~cause;
}
+ if (!(s->suspend_cause & PA_SUSPEND_SESSION) && (pa_atomic_load(&s->mixer_dirty) != 0)) {
+ /* This might look racy but isn't: If somebody sets mixer_dirty exactly here,
+ it'll be handled just fine. */
+ pa_sink_set_mixer_dirty(s, FALSE);
+ pa_log_debug("Mixer is now accessible. Updating alsa mixer settings.");
+ if (s->active_port && s->set_port) {
+ if (s->flags & PA_SINK_DEFERRED_VOLUME) {
+ struct sink_message_set_port msg = { .port = s->active_port, .ret = 0 };
+ pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_PORT, &msg, 0, NULL) == 0);
+ }
+ else
+ s->set_port(s, s->active_port);
+ }
+ else {
+ if (s->set_mute)
+ s->set_mute(s);
+ if (s->set_volume)
+ s->set_volume(s);
+ }
+ }
+
if ((pa_sink_get_state(s) == PA_SINK_SUSPENDED) == !!s->suspend_cause)
return 0;
pa_sink_input_unref(i);
}
- pa_queue_free(q, NULL, NULL);
+ pa_queue_free(q, NULL);
}
/* Called from main context */
pa_sink_input_unref(i);
}
- pa_queue_free(q, NULL, NULL);
+ pa_queue_free(q, NULL);
}
/* Called from IO thread context */
s->thread_info.rewind_nbytes = 0;
s->thread_info.rewind_requested = FALSE;
- if (s->thread_info.state == PA_SINK_SUSPENDED)
- return;
-
if (nbytes > 0) {
pa_log_debug("Processing rewind...");
- if (s->flags & PA_SINK_SYNC_VOLUME)
+ if (s->flags & PA_SINK_DEFERRED_VOLUME)
pa_sink_volume_change_rewind(s, nbytes);
}
pa_sink_unref(s);
}
+/* Called from main thread */
+pa_bool_t pa_sink_update_rate(pa_sink *s, uint32_t rate, pa_bool_t passthrough)
+{
+ if (s->update_rate) {
+ uint32_t desired_rate = rate;
+ uint32_t default_rate = s->default_sample_rate;
+ uint32_t alternate_rate = s->alternate_sample_rate;
+ uint32_t idx;
+ pa_sink_input *i;
+ pa_bool_t use_alternate = FALSE;
+
+ if (PA_UNLIKELY(default_rate == alternate_rate)) {
+ pa_log_warn("Default and alternate sample rates are the same.");
+ return FALSE;
+ }
+
+ if (PA_SINK_IS_RUNNING(s->state)) {
+ pa_log_info("Cannot update rate, SINK_IS_RUNNING, will keep using %u Hz",
+ s->sample_spec.rate);
+ return FALSE;
+ }
+
+ if (s->monitor_source) {
+ if (PA_SOURCE_IS_RUNNING(s->monitor_source->state) == TRUE) {
+ pa_log_info("Cannot update rate, monitor source is RUNNING");
+ return FALSE;
+ }
+ }
+
+ if (PA_UNLIKELY (desired_rate < 8000 ||
+ desired_rate > PA_RATE_MAX))
+ return FALSE;
+
+ if (!passthrough) {
+ pa_assert(default_rate % 4000 || default_rate % 11025);
+ pa_assert(alternate_rate % 4000 || alternate_rate % 11025);
+
+ if (default_rate % 4000) {
+ /* default is a 11025 multiple */
+ if ((alternate_rate % 4000 == 0) && (desired_rate % 4000 == 0))
+ use_alternate=TRUE;
+ } else {
+ /* default is 4000 multiple */
+ if ((alternate_rate % 11025 == 0) && (desired_rate % 11025 == 0))
+ use_alternate=TRUE;
+ }
+
+ if (use_alternate)
+ desired_rate = alternate_rate;
+ else
+ desired_rate = default_rate;
+ } else {
+ desired_rate = rate; /* use stream sampling rate, discard default/alternate settings */
+ }
+
+ if (desired_rate == s->sample_spec.rate)
+ return FALSE;
+
+ if (!passthrough && pa_sink_used_by(s) > 0)
+ return FALSE;
+
+ pa_log_debug("Suspending sink %s due to changing the sample rate.", s->name);
+ pa_sink_suspend(s, TRUE, PA_SUSPEND_IDLE); /* needed before rate update, will be resumed automatically */
+
+ if (s->update_rate(s, desired_rate) == TRUE) {
+ /* update monitor source as well */
+ if (s->monitor_source && !passthrough)
+ pa_source_update_rate(s->monitor_source, desired_rate, FALSE);
+ pa_log_info("Changed sampling rate successfully");
+
+ PA_IDXSET_FOREACH(i, s->inputs, idx) {
+ if (i->state == PA_SINK_INPUT_CORKED)
+ pa_sink_input_update_rate(i);
+ }
+
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
/* Called from main thread */
pa_usec_t pa_sink_get_latency(pa_sink *s) {
pa_usec_t usec = 0;
pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_LATENCY, &usec, 0, NULL) == 0);
+ /* usec is unsigned, so check that the offset can be added to usec without
+ * underflowing. */
+ if (-s->latency_offset <= (int64_t) usec)
+ usec += s->latency_offset;
+ else
+ usec = 0;
+
return usec;
}
if (o->process_msg(o, PA_SINK_MESSAGE_GET_LATENCY, &usec, 0, NULL) < 0)
return -1;
+ /* usec is unsigned, so check that the offset can be added to usec without
+ * underflowing. */
+ if (-s->thread_info.latency_offset <= (int64_t) usec)
+ usec += s->thread_info.latency_offset;
+ else
+ usec = 0;
+
return usec;
}
pa_bool_t pa_sink_flat_volume_enabled(pa_sink *s) {
pa_sink_assert_ref(s);
- while (s->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)
+ s = pa_sink_get_master(s);
+
+ if (PA_LIKELY(s))
+ return (s->flags & PA_SINK_FLAT_VOLUME);
+ else
+ return FALSE;
+}
+
+/* Called from the main thread (and also from the IO thread while the main
+ * thread is waiting). */
+pa_sink *pa_sink_get_master(pa_sink *s) {
+ pa_sink_assert_ref(s);
+
+ while (s && (s->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)) {
+ if (PA_UNLIKELY(!s->input_to_master))
+ return NULL;
+
s = s->input_to_master->sink;
+ }
- return (s->flags & PA_SINK_FLAT_VOLUME);
+ return s;
}
/* Called from main context */
if (pa_idxset_size(s->inputs) == 1) {
alt_i = pa_idxset_first(s->inputs, &idx);
- if (!pa_format_info_is_pcm(alt_i->format))
+ if (pa_sink_input_is_passthrough(alt_i))
return TRUE;
}
return FALSE;
}
+/* Called from main context */
+void pa_sink_enter_passthrough(pa_sink *s) {
+ pa_cvolume volume;
+
+ /* disable the monitor in passthrough mode */
+ if (s->monitor_source) {
+ pa_log_debug("Suspending monitor source %s, because the sink is entering the passthrough mode.", s->monitor_source->name);
+ pa_source_suspend(s->monitor_source, TRUE, PA_SUSPEND_PASSTHROUGH);
+ }
+
+ /* set the volume to NORM */
+ s->saved_volume = *pa_sink_get_volume(s, TRUE);
+ s->saved_save_volume = s->save_volume;
+
+ pa_cvolume_set(&volume, s->sample_spec.channels, PA_MIN(s->base_volume, PA_VOLUME_NORM));
+ pa_sink_set_volume(s, &volume, TRUE, FALSE);
+}
+
+/* Called from main context */
+void pa_sink_leave_passthrough(pa_sink *s) {
+ /* Unsuspend monitor */
+ if (s->monitor_source) {
+ pa_log_debug("Resuming monitor source %s, because the sink is leaving the passthrough mode.", s->monitor_source->name);
+ pa_source_suspend(s->monitor_source, FALSE, PA_SUSPEND_PASSTHROUGH);
+ }
+
+ /* Restore sink volume to what it was before we entered passthrough mode */
+ pa_sink_set_volume(s, &s->saved_volume, TRUE, s->saved_save_volume);
+
+ pa_cvolume_init(&s->saved_volume);
+ s->saved_save_volume = FALSE;
+}
+
/* Called from main context. */
static void compute_reference_ratio(pa_sink_input *i) {
unsigned c = 0;
* due to rounding errors. If that happens, we still want to propagate
* the changed root sink volume to the sinks connected to the
* intermediate sink that didn't change its volume. This theoretical
- * possiblity is the reason why we have that !(s->flags &
+ * possibility is the reason why we have that !(s->flags &
* PA_SINK_SHARE_VOLUME_WITH_MASTER) condition. Probably nobody would
* notice even if we returned here FALSE always if
* reference_volume_changed is FALSE. */
pa_bool_t save) {
pa_cvolume new_reference_volume;
- pa_sink *root_sink = s;
+ pa_sink *root_sink;
pa_sink_assert_ref(s);
pa_assert_ctl_context();
pa_assert(volume || pa_sink_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 input is connected */
- if (pa_sink_is_passthrough(s)) {
- /* FIXME: Need to notify client that volume control is disabled */
+ /* make sure we don't change the volume when a PASSTHROUGH input is connected ...
+ * ... *except* if we're being invoked to reset the volume to ensure 0 dB gain */
+ if (pa_sink_is_passthrough(s) && (!volume || !pa_cvolume_is_norm(volume))) {
pa_log_warn("Cannot change volume, Sink is connected to PASSTHROUGH input");
return;
}
/* In case of volume sharing, the volume is set for the root sink first,
* from which it's then propagated to the sharing sinks. */
- while (root_sink->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)
- root_sink = root_sink->input_to_master->sink;
+ root_sink = pa_sink_get_master(s);
+
+ if (PA_UNLIKELY(!root_sink))
+ return;
/* As a special exception we accept mono volumes on all sinks --
* even on those with more complex channel maps */
}
pa_cvolume_remap(&new_reference_volume, &s->channel_map, &root_sink->channel_map);
- }
-
- /* If volume is NULL we synchronize the sink's real and reference
- * volumes with the stream volumes. If it is not NULL we update
- * the reference_volume with it. */
- if (volume) {
if (update_reference_volume(root_sink, &new_reference_volume, &root_sink->channel_map, save)) {
if (pa_sink_flat_volume_enabled(root_sink)) {
/* OK, propagate this volume change back to the inputs */
}
} else {
+ /* If volume is NULL we synchronize the sink's real and
+ * reference volumes with the stream volumes. */
+
pa_assert(pa_sink_flat_volume_enabled(root_sink));
/* Ok, let's determine the new real volume */
/* Let's 'push' the reference volume if necessary */
pa_cvolume_merge(&new_reference_volume, &s->reference_volume, &root_sink->real_volume);
+ /* If the sink and it's root don't have the same number of channels, we need to remap */
+ if (s != root_sink && !pa_channel_map_equal(&s->channel_map, &root_sink->channel_map))
+ pa_cvolume_remap(&new_reference_volume, &s->channel_map, &root_sink->channel_map);
update_reference_volume(root_sink, &new_reference_volume, &root_sink->channel_map, save);
/* Now that the reference volume is updated, we can update the streams'
* apply one to root_sink->soft_volume */
pa_cvolume_reset(&root_sink->soft_volume, root_sink->sample_spec.channels);
- if (!(root_sink->flags & PA_SINK_SYNC_VOLUME))
+ if (!(root_sink->flags & PA_SINK_DEFERRED_VOLUME))
root_sink->set_volume(root_sink);
} else
/* Called from the io thread if sync volume is used, otherwise from the main thread.
* Only to be called by sink implementor */
void pa_sink_set_soft_volume(pa_sink *s, const pa_cvolume *volume) {
+
pa_sink_assert_ref(s);
pa_assert(!(s->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER));
- if (s->flags & PA_SINK_SYNC_VOLUME)
+
+ if (s->flags & PA_SINK_DEFERRED_VOLUME)
pa_sink_assert_io_context(s);
else
pa_assert_ctl_context();
else
s->soft_volume = *volume;
- if (PA_SINK_IS_LINKED(s->state) && !(s->flags & PA_SINK_SYNC_VOLUME))
+ if (PA_SINK_IS_LINKED(s->state) && !(s->flags & PA_SINK_DEFERRED_VOLUME))
pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_VOLUME, NULL, 0, NULL) == 0);
else
s->thread_info.soft_volume = s->soft_volume;
old_real_volume = s->real_volume;
- if (!(s->flags & PA_SINK_SYNC_VOLUME) && s->get_volume)
+ if (!(s->flags & PA_SINK_DEFERRED_VOLUME) && s->get_volume)
s->get_volume(s);
pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_VOLUME, NULL, 0, NULL) == 0);
s->muted = mute;
s->save_muted = (old_muted == s->muted && s->save_muted) || save;
- if (!(s->flags & PA_SINK_SYNC_VOLUME) && s->set_mute)
+ if (!(s->flags & PA_SINK_DEFERRED_VOLUME) && s->set_mute)
s->set_mute(s);
pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_MUTE, NULL, 0, NULL) == 0);
if (s->refresh_muted || force_refresh) {
pa_bool_t old_muted = s->muted;
- if (!(s->flags & PA_SINK_SYNC_VOLUME) && s->get_mute)
+ if (!(s->flags & PA_SINK_DEFERRED_VOLUME) && s->get_mute)
s->get_mute(s);
pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_MUTE, NULL, 0, NULL) == 0);
ret = pa_idxset_size(s->inputs);
/* We add in the number of streams connected to us here. Please
- * note the asymmmetry to pa_sink_used_by()! */
+ * note the asymmetry to pa_sink_used_by()! */
if (s->monitor_source)
ret += pa_source_linked_by(s->monitor_source);
/* We do not assert here. It is perfectly valid for a sink input to
* be in the INIT state (i.e. created, marked done but not yet put)
* and we should not care if it's unlinked as it won't contribute
- * towarards our busy status.
+ * towards our busy status.
*/
if (!PA_SINK_INPUT_IS_LINKED(st))
continue;
pa_sink_input_set_state_within_thread(i, i->state);
- /* The requested latency of the sink input needs to be
- * fixed up and then configured on the sink */
+ /* The requested latency of the sink input needs to be fixed up and
+ * then configured on the sink. If this causes the sink latency to
+ * go down, the sink implementor is responsible for doing a rewind
+ * in the update_requested_latency() callback to ensure that the
+ * sink buffer doesn't contain more data than what the new latency
+ * allows.
+ *
+ * XXX: Does it really make sense to push this responsibility to
+ * the sink implementors? Wouldn't it be better to do it once in
+ * the core than many times in the modules? */
if (i->thread_info.requested_sink_latency != (pa_usec_t) -1)
pa_sink_input_set_requested_latency_within_thread(i, i->thread_info.requested_sink_latency);
/* We don't rewind here automatically. This is left to the
* sink input implementor because some sink inputs need a
* slow start, i.e. need some time to buffer client
- * samples before beginning streaming. */
+ * samples before beginning streaming.
+ *
+ * XXX: Does it really make sense to push this functionality to
+ * the sink implementors? Wouldn't it be better to do it once in
+ * the core than many times in the modules? */
/* In flat volume mode we need to update the volume as
* well */
/* If you change anything here, make sure to change the
* sink input handling a few lines down at
- * PA_SINK_MESSAGE_PREPAPRE_MOVE, too. */
+ * PA_SINK_MESSAGE_START_MOVE, too. */
if (i->detach)
i->detach(i);
pa_usec_t usec = 0;
size_t sink_nbytes, total_nbytes;
+ /* The old sink probably has some audio from this
+ * stream in its buffer. We want to "take it back" as
+ * much as possible and play it to the new sink. We
+ * don't know at this point how much the old sink can
+ * rewind. We have to pick something, and that
+ * something is the full latency of the old sink here.
+ * So we rewind the stream buffer by the sink latency
+ * amount, which may be more than what we should
+ * rewind. This can result in a chunk of audio being
+ * played both to the old sink and the new sink.
+ *
+ * FIXME: Fix this code so that we don't have to make
+ * guesses about how much the sink will actually be
+ * able to rewind. If someone comes up with a solution
+ * for this, something to note is that the part of the
+ * latency that the old sink couldn't rewind should
+ * ideally be compensated after the stream has moved
+ * to the new sink by adding silence. The new sink
+ * most likely can't start playing the moved stream
+ * immediately, and that gap should be removed from
+ * the "compensation silence" (at least at the time of
+ * writing this, the move finish code will actually
+ * already take care of dropping the new sink's
+ * unrewindable latency, so taking into account the
+ * unrewindable latency of the old sink is the only
+ * problem).
+ *
+ * The render_memblockq contents are discarded,
+ * because when the sink changes, the format of the
+ * audio stored in the render_memblockq may change
+ * too, making the stored audio invalid. FIXME:
+ * However, the read and write indices are moved back
+ * the same amount, so if they are not the same now,
+ * they won't be the same after the rewind either. If
+ * the write index of the render_memblockq is ahead of
+ * the read index, then the render_memblockq will feed
+ * the new sink some silence first, which it shouldn't
+ * do. The write index should be flushed to be the
+ * same as the read index. */
+
/* Get the latency of the sink */
usec = pa_sink_get_latency_within_thread(s);
sink_nbytes = pa_usec_to_bytes(usec, &s->sample_spec);
if (i->attach)
i->attach(i);
- if (i->thread_info.requested_sink_latency != (pa_usec_t) -1)
- pa_sink_input_set_requested_latency_within_thread(i, i->thread_info.requested_sink_latency);
-
- pa_sink_input_update_max_rewind(i, s->thread_info.max_rewind);
- pa_sink_input_update_max_request(i, s->thread_info.max_request);
-
if (i->thread_info.state != PA_SINK_INPUT_CORKED) {
pa_usec_t usec = 0;
size_t nbytes;
+ /* In the ideal case the new sink would start playing
+ * the stream immediately. That requires the sink to
+ * be able to rewind all of its latency, which usually
+ * isn't possible, so there will probably be some gap
+ * before the moved stream becomes audible. We then
+ * have two possibilities: 1) start playing the stream
+ * from where it is now, or 2) drop the unrewindable
+ * latency of the sink from the stream. With option 1
+ * we won't lose any audio but the stream will have a
+ * pause. With option 2 we may lose some audio but the
+ * stream time will be somewhat in sync with the wall
+ * clock. Lennart seems to have chosen option 2 (one
+ * of the reasons might have been that option 1 is
+ * actually much harder to implement), so we drop the
+ * latency of the new sink from the moved stream and
+ * hope that the sink will undo most of that in the
+ * rewind. */
+
/* Get the latency of the sink */
usec = pa_sink_get_latency_within_thread(s);
nbytes = pa_usec_to_bytes(usec, &s->sample_spec);
pa_sink_request_rewind(s, nbytes);
}
+ /* Updating the requested sink latency has to be done
+ * after the sink rewind request, not before, because
+ * otherwise the sink may limit the rewind amount
+ * needlessly. */
+
+ if (i->thread_info.requested_sink_latency != (pa_usec_t) -1)
+ pa_sink_input_set_requested_latency_within_thread(i, i->thread_info.requested_sink_latency);
+
+ pa_sink_input_update_max_rewind(i, s->thread_info.max_rewind);
+ pa_sink_input_update_max_request(i, s->thread_info.max_request);
+
return o->process_msg(o, PA_SINK_MESSAGE_SET_SHARED_VOLUME, NULL, 0, NULL);
}
case PA_SINK_MESSAGE_SET_SHARED_VOLUME: {
- pa_sink *root_sink = s;
+ pa_sink *root_sink = pa_sink_get_master(s);
- while (root_sink->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)
- root_sink = root_sink->input_to_master->sink;
+ if (PA_LIKELY(root_sink))
+ set_shared_volume_within_thread(root_sink);
- set_shared_volume_within_thread(root_sink);
return 0;
}
case PA_SINK_MESSAGE_SET_VOLUME_SYNCED:
- if (s->flags & PA_SINK_SYNC_VOLUME) {
+ if (s->flags & PA_SINK_DEFERRED_VOLUME) {
s->set_volume(s);
pa_sink_volume_change_push(s);
}
case PA_SINK_MESSAGE_GET_VOLUME:
- if ((s->flags & PA_SINK_SYNC_VOLUME) && s->get_volume) {
+ if ((s->flags & PA_SINK_DEFERRED_VOLUME) && s->get_volume) {
s->get_volume(s);
pa_sink_volume_change_flush(s);
pa_sw_cvolume_divide(&s->thread_info.current_hw_volume, &s->real_volume, &s->soft_volume);
pa_sink_request_rewind(s, (size_t) -1);
}
- if (s->flags & PA_SINK_SYNC_VOLUME && s->set_mute)
+ if (s->flags & PA_SINK_DEFERRED_VOLUME && s->set_mute)
s->set_mute(s);
return 0;
case PA_SINK_MESSAGE_GET_MUTE:
- if (s->flags & PA_SINK_SYNC_VOLUME && s->get_mute)
+ if (s->flags & PA_SINK_DEFERRED_VOLUME && s->get_mute)
s->get_mute(s);
return 0;
/* This message is sent from IO-thread and handled in main thread. */
pa_assert_ctl_context();
+ /* Make sure we're not messing with main thread when no longer linked */
+ if (!PA_SINK_IS_LINKED(s->state))
+ return 0;
+
pa_sink_get_volume(s, TRUE);
pa_sink_get_mute(s, TRUE);
return 0;
+ case PA_SINK_MESSAGE_SET_LATENCY_OFFSET:
+ s->thread_info.latency_offset = offset;
+ return 0;
+
case PA_SINK_MESSAGE_GET_LATENCY:
case PA_SINK_MESSAGE_MAX:
;
pa_sink_assert_io_context(s);
pa_assert(PA_SINK_IS_LINKED(s->thread_info.state));
- if (s->thread_info.state == PA_SINK_SUSPENDED)
- return;
-
if (nbytes == (size_t) -1)
nbytes = s->thread_info.max_rewind;
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;
}
pa_source_set_fixed_latency_within_thread(s->monitor_source, latency);
}
+/* Called from main context */
+void pa_sink_set_latency_offset(pa_sink *s, int64_t offset) {
+ pa_sink_assert_ref(s);
+
+ s->latency_offset = offset;
+
+ if (PA_SINK_IS_LINKED(s->state))
+ pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_LATENCY_OFFSET, NULL, offset, NULL) == 0);
+ else
+ s->thread_info.latency_offset = offset;
+}
+
/* 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;
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();
return -PA_ERR_NOTIMPLEMENTED;
}
- if (!s->ports)
+ if (!name)
return -PA_ERR_NOENTITY;
if (!(port = pa_hashmap_get(s->ports, name)))
return 0;
}
- if (s->flags & PA_SINK_SYNC_VOLUME) {
+ if (s->flags & PA_SINK_DEFERRED_VOLUME) {
struct sink_message_set_port msg = { .port = port, .ret = 0 };
pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_PORT, &msg, 0, NULL) == 0);
ret = msg.ret;
s->active_port = port;
s->save_port = save;
+ pa_sink_set_latency_offset(s, s->active_port->latency_offset);
+
pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_PORT_CHANGED], s);
return 0;
if ((s = pa_proplist_gets(p, PA_PROP_DEVICE_FORM_FACTOR)))
if (pa_streq(s, "internal"))
- d = _("Internal Audio");
+ d = _("Built-in Audio");
if (!d)
if ((s = pa_proplist_gets(p, PA_PROP_DEVICE_CLASS)))
k = pa_proplist_gets(p, PA_PROP_DEVICE_PROFILE_DESCRIPTION);
if (d && k)
- pa_proplist_setf(p, PA_PROP_DEVICE_DESCRIPTION, _("%s %s"), d, k);
+ pa_proplist_setf(p, PA_PROP_DEVICE_DESCRIPTION, "%s %s", d, k);
else if (d)
pa_proplist_sets(p, PA_PROP_DEVICE_DESCRIPTION, d);
/* Called from the IO thread. */
pa_bool_t pa_sink_volume_change_apply(pa_sink *s, pa_usec_t *usec_to_next) {
- pa_usec_t now = pa_rtclock_now();
+ pa_usec_t now;
pa_bool_t ret = FALSE;
pa_assert(s);
+
+ if (!s->thread_info.volume_changes || !PA_SINK_IS_LINKED(s->state)) {
+ if (usec_to_next)
+ *usec_to_next = 0;
+ return ret;
+ }
+
pa_assert(s->write_volume);
+ now = pa_rtclock_now();
+
while (s->thread_info.volume_changes && now >= s->thread_info.volume_changes->at) {
pa_sink_volume_change *c = s->thread_info.volume_changes;
PA_LLIST_REMOVE(pa_sink_volume_change, s->thread_info.volume_changes, c);
pa_sink_volume_change_free(c);
}
- if (s->write_volume && ret)
+ if (ret)
s->write_volume(s);
if (s->thread_info.volume_changes) {
return ret;
}
+/* Called from the main thread */
+/* Allows an external source to set what formats a sink supports if the sink
+ * permits this. The function makes a copy of the formats on success. */
+pa_bool_t pa_sink_set_formats(pa_sink *s, pa_idxset *formats) {
+ pa_assert(s);
+ pa_assert(formats);
+
+ if (s->set_formats)
+ /* Sink supports setting formats -- let's give it a shot */
+ return s->set_formats(s, formats);
+ else
+ /* Sink doesn't support setting this -- bail out */
+ return FALSE;
+}
+
+/* Called from the main thread */
+/* Checks if the sink can accept this format */
+pa_bool_t pa_sink_check_format(pa_sink *s, pa_format_info *f)
+{
+ pa_idxset *formats = NULL;
+ pa_bool_t ret = FALSE;
+
+ pa_assert(s);
+ pa_assert(f);
+
+ formats = pa_sink_get_formats(s);
+
+ if (formats) {
+ pa_format_info *finfo_device;
+ uint32_t i;
+
+ PA_IDXSET_FOREACH(finfo_device, formats, i) {
+ if (pa_format_info_is_compatible(finfo_device, f)) {
+ ret = TRUE;
+ break;
+ }
+ }
+
+ pa_idxset_free(formats, (pa_free2_cb_t) pa_format_info_free2, NULL);
+ }
+
+ return ret;
+}
+
/* Called from the main thread */
/* Calculates the intersection between formats supported by the sink and
* in_formats, and returns these, in the order of the sink's formats. */
pa_idxset* pa_sink_check_formats(pa_sink *s, pa_idxset *in_formats) {
- pa_idxset *out_formats = pa_idxset_new(NULL, NULL), *sink_formats;
+ pa_idxset *out_formats = pa_idxset_new(NULL, NULL), *sink_formats = NULL;
pa_format_info *f_sink, *f_in;
uint32_t i, j;
}
done:
+ if (sink_formats)
+ pa_idxset_free(sink_formats, (pa_free2_cb_t) pa_format_info_free2, NULL);
+
return out_formats;
}