#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_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_sw_cvolume_multiply(&s->thread_info.current_hw_volume, &s->soft_volume, &s->real_volume);
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;
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_DEFERRED_VOLUME)
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_cvolume volume;
/* disable the monitor in passthrough mode */
- if (s->monitor_source)
+ 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);
/* Called from main context */
void pa_sink_leave_passthrough(pa_sink *s) {
/* Unsuspend monitor */
- if (s->monitor_source)
+ 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_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 */
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. */
-
- /* FIXME: Actually rewinding should be requested before
- * updating the sink requested latency, because updating
- * the requested latency updates also max_rewind of the
- * sink. Now consider this: a sink has a 10 s buffer and
- * nobody has requested anything less. Then a new stream
- * appears while the sink buffer is full. The new stream
- * requests e.g. 100 ms latency. That request is forwarded
- * to the sink, so now max_rewind is 100 ms. When a rewind
- * is requested, the sink will only rewind 100 ms, and the
- * new stream will have to wait about 10 seconds before it
- * becomes audible. */
+ * 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 */
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);
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_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;
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;
return -PA_ERR_NOTIMPLEMENTED;
}
- if (!s->ports)
+ if (!name)
return -PA_ERR_NOENTITY;
if (!(port = pa_hashmap_get(s->ports, name)))
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);