#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#ifdef TIZEN_PCM_DUMP
+#include <time.h>
+#endif
#include <pulse/introspect.h>
#include <pulse/format.h>
#include <pulsecore/macro.h>
#include <pulsecore/play-memblockq.h>
#include <pulsecore/flist.h>
+#ifdef __TIZEN__
+#include <pulsecore/proplist-util.h>
+#endif
#include "sink.h"
#define MAX_MIX_CHANNELS 32
-#define MIX_BUFFER_LENGTH (PA_PAGE_SIZE)
+#define MIX_BUFFER_LENGTH (pa_page_size())
#define ABSOLUTE_MIN_LATENCY (500)
#define ABSOLUTE_MAX_LATENCY (10*PA_USEC_PER_SEC)
#define DEFAULT_FIXED_LATENCY (250*PA_USEC_PER_MSEC)
PA_LLIST_FIELDS(pa_sink_volume_change);
};
-struct sink_message_set_port {
- pa_device_port *port;
- int ret;
+struct set_state_data {
+ pa_sink_state_t state;
+ pa_suspend_cause_t suspend_cause;
};
static void sink_free(pa_object *s);
static void pa_sink_volume_change_flush(pa_sink *s);
static void pa_sink_volume_change_rewind(pa_sink *s, size_t nbytes);
+#ifdef TIZEN_PCM_DUMP
+static void pa_sink_write_pcm_dump(pa_sink *s, pa_memchunk *chunk)
+{
+ char *dump_time = NULL, *dump_path_surfix = NULL;
+ const char *s_device_api_str, *card_name_str, *device_idx_str;
+ struct timeval now;
+ struct tm tm;
+ char datetime[7];
+
+ /* open file for dump pcm */
+ if (s->core->pcm_dump & PA_PCM_DUMP_SINK && !s->pcm_dump_fp && s->state == PA_SINK_RUNNING) {
+ pa_gettimeofday(&now);
+ localtime_r(&now.tv_sec, &tm);
+ memset(&datetime[0], 0x00, sizeof(datetime));
+ strftime(&datetime[0], sizeof(datetime), "%H%M%S", &tm);
+ dump_time = pa_sprintf_malloc("%s.%03ld", &datetime[0], now.tv_usec / 1000);
+
+ if ((s_device_api_str = pa_proplist_gets(s->proplist, PA_PROP_DEVICE_API))) {
+ if (pa_streq(s_device_api_str, "alsa")) {
+ card_name_str = pa_proplist_gets(s->proplist, "alsa.card_name");
+ device_idx_str = pa_proplist_gets(s->proplist, "alsa.device");
+ dump_path_surfix = pa_sprintf_malloc("%s.%s", pa_strnull(card_name_str), pa_strnull(device_idx_str));
+ } else {
+ dump_path_surfix = pa_sprintf_malloc("%s", s_device_api_str);
+ }
+ } else {
+ dump_path_surfix = pa_sprintf_malloc("%s", s->name);
+ }
+
+ s->dump_path = pa_sprintf_malloc("%s_%s_pa-sink%d-%s_%dch_%d.raw", PA_PCM_DUMP_PATH_PREFIX, pa_strempty(dump_time),
+ s->index, pa_strempty(dump_path_surfix), s->sample_spec.channels, s->sample_spec.rate);
+ if (s->dump_path) {
+ s->pcm_dump_fp = fopen(s->dump_path, "w");
+ if (!s->pcm_dump_fp)
+ pa_log_warn("%s open failed", s->dump_path);
+ else
+ pa_log_info("%s opened", s->dump_path);
+ }
+ pa_xfree(dump_time);
+ pa_xfree(dump_path_surfix);
+ /* close file for dump pcm when config is changed */
+ } else if (~s->core->pcm_dump & PA_PCM_DUMP_SINK && s->pcm_dump_fp) {
+ fclose(s->pcm_dump_fp);
+ pa_log_info("%s closed", s->dump_path);
+ pa_xfree(s->dump_path);
+ s->pcm_dump_fp = NULL;
+ }
+
+ /* dump pcm */
+ if (s->pcm_dump_fp) {
+ void *ptr = NULL;
+
+ ptr = pa_memblock_acquire(chunk->memblock);
+ if (ptr)
+ fwrite((uint8_t *)ptr + chunk->index, 1, chunk->length, s->pcm_dump_fp);
+ else
+ pa_log_warn("pa_memblock_acquire is failed. ptr is NULL");
+
+ pa_memblock_release(chunk->memblock);
+ }
+}
+#endif
+
pa_sink_new_data* pa_sink_new_data_init(pa_sink_new_data *data) {
pa_assert(data);
data->alternate_sample_rate = alternate_sample_rate;
}
+void pa_sink_new_data_set_avoid_resampling(pa_sink_new_data *data, bool avoid_resampling) {
+ pa_assert(data);
+
+ data->avoid_resampling_is_set = true;
+ data->avoid_resampling = avoid_resampling;
+}
+
void pa_sink_new_data_set_volume(pa_sink_new_data *data, const pa_cvolume *volume) {
pa_assert(data);
static void reset_callbacks(pa_sink *s) {
pa_assert(s);
- s->set_state = NULL;
+ s->set_state_in_main_thread = NULL;
+ s->set_state_in_io_thread = NULL;
s->get_volume = NULL;
s->set_volume = NULL;
s->write_volume = NULL;
s->set_port = NULL;
s->get_formats = NULL;
s->set_formats = NULL;
- s->update_rate = NULL;
+ s->reconfigure = NULL;
}
/* Called from main context */
s->flags = flags;
s->priority = 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));
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;
- }
+ if (data->avoid_resampling_is_set)
+ s->avoid_resampling = data->avoid_resampling;
+ else
+ s->avoid_resampling = s->core->avoid_resampling;
+#ifdef __TIZEN__
+ s->selected_sample_format = s->sample_spec.format;
+ s->selected_sample_rate = s->sample_spec.rate;
+#endif
s->inputs = pa_idxset_new(NULL, NULL);
s->n_corked = 0;
s->active_port = pa_device_port_find_best(s->ports);
if (s->active_port)
- s->latency_offset = s->active_port->latency_offset;
+ s->port_latency_offset = s->active_port->latency_offset;
else
- s->latency_offset = 0;
+ s->port_latency_offset = 0;
s->save_volume = data->save_volume;
s->save_muted = data->save_muted;
+#ifdef TIZEN_PCM_DUMP
+ s->pcm_dump_fp = NULL;
+ s->dump_path = NULL;
+#endif
pa_silence_memchunk_get(
&core->silence_cache,
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);
+ pa_sw_cvolume_divide(&s->thread_info.current_hw_volume, &s->real_volume, &s->soft_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;
+ s->thread_info.port_latency_offset = s->port_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_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);
+ pa_source_new_data_set_avoid_resampling(&source_data, s->avoid_resampling);
source_data.name = pa_sprintf_malloc("%s.monitor", name);
source_data.driver = data->driver;
source_data.module = data->module;
}
/* Called from main context */
-static int sink_set_state(pa_sink *s, pa_sink_state_t state) {
- int ret;
- bool suspend_change;
- pa_sink_state_t original_state;
+static int sink_set_state(pa_sink *s, pa_sink_state_t state, pa_suspend_cause_t suspend_cause) {
+ int ret = 0;
+ bool state_changed;
+ bool suspend_cause_changed;
+ bool suspending;
+ bool resuming;
+ pa_sink_state_t old_state;
+ pa_suspend_cause_t old_suspend_cause;
pa_assert(s);
pa_assert_ctl_context();
- if (s->state == state)
+ state_changed = state != s->state;
+ suspend_cause_changed = suspend_cause != s->suspend_cause;
+
+ if (!state_changed && !suspend_cause_changed)
return 0;
- original_state = s->state;
+ suspending = PA_SINK_IS_OPENED(s->state) && state == PA_SINK_SUSPENDED;
+ resuming = s->state == PA_SINK_SUSPENDED && PA_SINK_IS_OPENED(state);
+
+ /* If we are resuming, suspend_cause must be 0. */
+ pa_assert(!resuming || !suspend_cause);
+
+ /* Here's something to think about: what to do with the suspend cause if
+ * resuming the sink fails? The old suspend cause will be incorrect, so we
+ * can't use that. On the other hand, if we set no suspend cause (as is the
+ * case currently), then it looks strange to have a sink suspended without
+ * any cause. It might be a good idea to add a new "resume failed" suspend
+ * cause, or it might just add unnecessary complexity, given that the
+ * current approach of not setting any suspend cause works well enough. */
+
+ if (s->set_state_in_main_thread) {
+ if ((ret = s->set_state_in_main_thread(s, state, suspend_cause)) < 0) {
+ /* set_state_in_main_thread() is allowed to fail only when resuming. */
+ pa_assert(resuming);
+
+ /* If resuming fails, we set the state to SUSPENDED and
+ * suspend_cause to 0. */
+ state = PA_SINK_SUSPENDED;
+ suspend_cause = 0;
+ state_changed = false;
+ suspend_cause_changed = suspend_cause != s->suspend_cause;
+ resuming = false;
+
+ /* We know the state isn't changing. If the suspend cause isn't
+ * changing either, then there's nothing more to do. */
+ if (!suspend_cause_changed)
+ return ret;
+ }
+ }
- suspend_change =
- (original_state == PA_SINK_SUSPENDED && PA_SINK_IS_OPENED(state)) ||
- (PA_SINK_IS_OPENED(original_state) && state == PA_SINK_SUSPENDED);
+ if (s->asyncmsgq) {
+ struct set_state_data data = { .state = state, .suspend_cause = suspend_cause };
- if (s->set_state)
- if ((ret = s->set_state(s, state)) < 0)
- return ret;
+ if ((ret = pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_STATE, &data, 0, NULL)) < 0) {
+ /* SET_STATE is allowed to fail only when resuming. */
+ pa_assert(resuming);
- if (s->asyncmsgq)
- if ((ret = pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_STATE, PA_UINT_TO_PTR(state), 0, NULL)) < 0) {
+ if (s->set_state_in_main_thread)
+ s->set_state_in_main_thread(s, PA_SINK_SUSPENDED, 0);
- if (s->set_state)
- s->set_state(s, original_state);
+ /* If resuming fails, we set the state to SUSPENDED and
+ * suspend_cause to 0. */
+ state = PA_SINK_SUSPENDED;
+ suspend_cause = 0;
+ state_changed = false;
+ suspend_cause_changed = suspend_cause != s->suspend_cause;
+ resuming = false;
- return ret;
+ /* We know the state isn't changing. If the suspend cause isn't
+ * changing either, then there's nothing more to do. */
+ if (!suspend_cause_changed)
+ return ret;
}
+ }
+
+#ifdef TIZEN_PCM_DUMP
+ /* close file for dump pcm */
+ if (s->pcm_dump_fp && (s->core->pcm_dump & PA_PCM_DUMP_SEPARATED) && suspending) {
+ fclose(s->pcm_dump_fp);
+ pa_log_info("%s closed", s->dump_path);
+ pa_xfree(s->dump_path);
+ s->pcm_dump_fp = NULL;
+ }
+#endif
+ old_suspend_cause = s->suspend_cause;
+ if (suspend_cause_changed) {
+ char old_cause_buf[PA_SUSPEND_CAUSE_TO_STRING_BUF_SIZE];
+ char new_cause_buf[PA_SUSPEND_CAUSE_TO_STRING_BUF_SIZE];
+
+ pa_log_debug("%s: suspend_cause: %s -> %s", s->name, pa_suspend_cause_to_string(s->suspend_cause, old_cause_buf),
+ pa_suspend_cause_to_string(suspend_cause, new_cause_buf));
+ s->suspend_cause = suspend_cause;
+ }
- s->state = state;
+ old_state = s->state;
+ if (state_changed) {
+ pa_log_debug("%s: state: %s -> %s", s->name, pa_sink_state_to_string(s->state), pa_sink_state_to_string(state));
+ s->state = state;
- 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);
+ /* If we enter UNLINKED state, then we don't send change notifications.
+ * pa_sink_unlink() will send unlink notifications instead. */
+ if (state != PA_SINK_UNLINKED) {
+ 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);
+ }
}
- if (suspend_change) {
+ if (suspending || resuming || suspend_cause_changed) {
pa_sink_input *i;
uint32_t idx;
(i->flags & PA_SINK_INPUT_KILL_ON_SUSPEND))
pa_sink_input_kill(i);
else if (i->suspend)
- i->suspend(i, state == PA_SINK_SUSPENDED);
-
- if (s->monitor_source)
- pa_source_sync_suspend(s->monitor_source);
+ i->suspend(i, old_state, old_suspend_cause);
}
- return 0;
+ if ((suspending || resuming || suspend_cause_changed) && s->monitor_source && state != PA_SINK_UNLINKED)
+ pa_source_sync_suspend(s->monitor_source);
+
+ return ret;
}
void pa_sink_set_get_volume_callback(pa_sink *s, pa_sink_cb_t cb) {
pa_assert_ctl_context();
pa_assert(s->state == PA_SINK_INIT);
- pa_assert(!(s->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER) || s->input_to_master);
+ pa_assert(!(s->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER) || pa_sink_is_filter(s));
/* The following fields must be initialized properly when calling _put() */
pa_assert(s->asyncmsgq);
pa_cvolume_remap(&s->real_volume, &root_sink->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. */
+ * volume they did so in real_volume, because that is the usual
+ * place where they are supposed to place their 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_sw_cvolume_divide(&s->thread_info.current_hw_volume, &s->real_volume, &s->soft_volume);
pa_assert((s->flags & PA_SINK_HW_VOLUME_CTRL)
|| (s->base_volume == PA_VOLUME_NORM
&& ((s->flags & PA_SINK_DECIBEL_VOLUME || (s->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)))));
pa_assert(!(s->flags & PA_SINK_DECIBEL_VOLUME) || s->n_volume_steps == PA_VOLUME_NORM+1);
- pa_assert(!(s->flags & PA_SINK_DYNAMIC_LATENCY) == (s->thread_info.fixed_latency != 0));
+ 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->monitor_source->thread_info.max_latency == s->thread_info.max_latency);
if (s->suspend_cause)
- pa_assert_se(sink_set_state(s, PA_SINK_SUSPENDED) == 0);
+ pa_assert_se(sink_set_state(s, PA_SINK_SUSPENDED, s->suspend_cause) == 0);
else
- pa_assert_se(sink_set_state(s, PA_SINK_IDLE) == 0);
+ pa_assert_se(sink_set_state(s, PA_SINK_IDLE, 0) == 0);
pa_source_put(s->monitor_source);
pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK | PA_SUBSCRIPTION_EVENT_NEW, s->index);
pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_PUT], s);
+
+ /* It's good to fire the SINK_PUT hook before updating the default sink,
+ * because module-switch-on-connect will set the new sink as the default
+ * sink, and if we were to call pa_core_update_default_sink() before that,
+ * the default sink might change twice, causing unnecessary stream moving. */
+
+ pa_core_update_default_sink(s->core);
+
+ pa_core_move_streams_to_newly_available_preferred_sink(s->core, s);
}
/* Called from main context */
bool linked;
pa_sink_input *i, PA_UNUSED *j = NULL;
- pa_assert(s);
+ pa_sink_assert_ref(s);
pa_assert_ctl_context();
/* Please note that pa_sink_unlink() does more than simply
* reversing pa_sink_put(). It also undoes the registrations
* already done in pa_sink_new()! */
- /* All operations here shall be idempotent, i.e. pa_sink_unlink()
- * may be called multiple times on the same sink without bad
- * effects. */
+ if (s->unlink_requested)
+ return;
+
+ s->unlink_requested = true;
linked = PA_SINK_IS_LINKED(s->state);
pa_namereg_unregister(s->core, s->name);
pa_idxset_remove_by_data(s->core->sinks, s, NULL);
+ pa_core_update_default_sink(s->core);
+
+ if (linked && s->core->rescue_streams)
+ pa_sink_move_streams_to_default_sink(s->core, s, false);
+
if (s->card)
pa_idxset_remove_by_data(s->card->sinks, s, NULL);
j = i;
}
+ /* Unlink monitor source before unlinking the sink */
+ if (s->monitor_source)
+ pa_source_unlink(s->monitor_source);
+
if (linked)
- sink_set_state(s, PA_SINK_UNLINKED);
+ /* It's important to keep the suspend cause unchanged when unlinking,
+ * because if we remove the SESSION suspend cause here, the alsa sink
+ * will sync its volume with the hardware while another user is
+ * active, messing up the volume for that other user. */
+ sink_set_state(s, PA_SINK_UNLINKED, s->suspend_cause);
else
s->state = PA_SINK_UNLINKED;
reset_callbacks(s);
- if (s->monitor_source)
- pa_source_unlink(s->monitor_source);
-
if (linked) {
pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK | PA_SUBSCRIPTION_EVENT_REMOVE, s->index);
pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_UNLINK_POST], s);
pa_assert(s);
pa_assert_ctl_context();
pa_assert(pa_sink_refcnt(s) == 0);
-
- if (PA_SINK_IS_LINKED(s->state))
- pa_sink_unlink(s);
+ pa_assert(!PA_SINK_IS_LINKED(s->state));
pa_log_info("Freeing sink %u \"%s\"", s->index, s->name);
+ pa_sink_volume_change_flush(s);
+
if (s->monitor_source) {
pa_source_unref(s->monitor_source);
s->monitor_source = NULL;
if (s->ports)
pa_hashmap_free(s->ports);
+#ifdef TIZEN_PCM_DUMP
+ /* close file for dump pcm */
+ if (s->pcm_dump_fp) {
+ fclose(s->pcm_dump_fp);
+ pa_log_info("%s closed", s->dump_path);
+ pa_xfree(s->dump_path);
+ s->pcm_dump_fp = NULL;
+ }
+#endif
pa_xfree(s);
}
if (s->state == PA_SINK_SUSPENDED)
return 0;
- 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, bool is_dirty) {
- pa_atomic_store(&s->mixer_dirty, is_dirty ? 1 : 0);
+ return sink_set_state(s, pa_sink_used_by(s) ? PA_SINK_RUNNING : PA_SINK_IDLE, 0);
}
/* Called from main context */
int pa_sink_suspend(pa_sink *s, bool suspend, pa_suspend_cause_t cause) {
+ pa_suspend_cause_t merged_cause;
+
pa_sink_assert_ref(s);
pa_assert_ctl_context();
pa_assert(PA_SINK_IS_LINKED(s->state));
pa_assert(cause != 0);
- if (suspend) {
- s->suspend_cause |= cause;
- s->monitor_source->suspend_cause |= cause;
- } else {
- s->suspend_cause &= ~cause;
- 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_log_debug("Suspend cause of sink %s is 0x%04x, %s", s->name, s->suspend_cause, s->suspend_cause ? "suspending" : "resuming");
+ if (suspend)
+ merged_cause = s->suspend_cause | cause;
+ else
+ merged_cause = s->suspend_cause & ~cause;
- if (s->suspend_cause)
- return sink_set_state(s, PA_SINK_SUSPENDED);
+ if (merged_cause)
+ return sink_set_state(s, PA_SINK_SUSPENDED, merged_cause);
else
- return sink_set_state(s, pa_sink_used_by(s) ? PA_SINK_RUNNING : PA_SINK_IDLE);
+ return sink_set_state(s, pa_sink_used_by(s) ? PA_SINK_RUNNING : PA_SINK_IDLE, 0);
}
/* Called from main context */
pa_assert(q);
while ((i = PA_SINK_INPUT(pa_queue_pop(q)))) {
- if (pa_sink_input_finish_move(i, s, save) < 0)
- pa_sink_input_fail_move(i);
+ if (PA_SINK_INPUT_IS_LINKED(i->state)) {
+ if (pa_sink_input_finish_move(i, s, save) < 0)
+ pa_sink_input_fail_move(i);
+ }
pa_sink_input_unref(i);
}
PA_HASHMAP_FOREACH(i, s->thread_info.inputs, state) {
size_t uf = i->thread_info.underrun_for_sink;
- if (uf == 0)
- continue;
- if (uf >= left_to_play) {
- if (pa_sink_input_process_underrun(i))
- continue;
+
+ /* Propagate down the filter tree */
+ if (i->origin_sink) {
+ size_t filter_result, left_to_play_origin;
+
+ /* The recursive call works in the origin sink domain ... */
+ left_to_play_origin = pa_convert_size(left_to_play, &i->sink->sample_spec, &i->origin_sink->sample_spec);
+
+ /* .. and returns the time to sleep before waking up. We need the
+ * underrun duration for comparisons, so we undo the subtraction on
+ * the return value... */
+ filter_result = left_to_play_origin - pa_sink_process_input_underruns(i->origin_sink, left_to_play_origin);
+
+ /* ... and convert it back to the master sink domain */
+ filter_result = pa_convert_size(filter_result, &i->origin_sink->sample_spec, &i->sink->sample_spec);
+
+ /* Remember the longest underrun so far */
+ if (filter_result > result)
+ result = filter_result;
}
- else if (uf > result)
+
+ if (uf == 0) {
+ /* No underrun here, move on */
+ continue;
+ } else if (uf >= left_to_play) {
+ /* The sink has possibly consumed all the data the sink input provided */
+ pa_sink_input_process_underrun(i);
+ } else if (uf > result) {
+ /* Remember the longest underrun so far */
result = uf;
+ }
}
if (result > 0)
- pa_log_debug("Found underrun %ld bytes ago (%ld bytes ahead in playback buffer)", (long) result, (long) left_to_play - result);
+ pa_log_debug("%s: Found underrun %ld bytes ago (%ld bytes ahead in playback buffer)", s->name,
+ (long) result, (long) left_to_play - result);
return left_to_play - result;
}
pa_log_debug("Processing rewind...");
if (s->flags & PA_SINK_DEFERRED_VOLUME)
pa_sink_volume_change_rewind(s, nbytes);
+#ifdef TIZEN_PCM_DUMP
+ /* rewind pcm */
+ if (s->pcm_dump_fp)
+ fseeko(s->pcm_dump_fp, (off_t)nbytes * (-1), SEEK_CUR);
+#endif
}
PA_HASHMAP_FOREACH(i, s->thread_info.inputs, state) {
inputs_drop(s, info, n, result);
+#ifdef TIZEN_PCM_DUMP
+ pa_sink_write_pcm_dump(s, result);
+#endif
pa_sink_unref(s);
}
inputs_drop(s, info, n, target);
+#ifdef TIZEN_PCM_DUMP
+ pa_sink_write_pcm_dump(s, target);
+#endif
pa_sink_unref(s);
}
}
/* Called from main thread */
-int pa_sink_update_rate(pa_sink *s, uint32_t rate, bool passthrough) {
- int ret = -1;
- uint32_t desired_rate = rate;
+void pa_sink_reconfigure(pa_sink *s, pa_sample_spec *spec, bool passthrough) {
+ pa_sample_spec desired_spec;
uint32_t default_rate = s->default_sample_rate;
uint32_t alternate_rate = s->alternate_sample_rate;
uint32_t idx;
pa_sink_input *i;
bool default_rate_is_usable = false;
bool alternate_rate_is_usable = false;
+ bool avoid_resampling = s->avoid_resampling;
- if (rate == s->sample_spec.rate)
- return 0;
+ if (pa_sample_spec_equal(spec, &s->sample_spec))
+ return;
- if (!s->update_rate)
- return -1;
+ if (!s->reconfigure)
+ return;
- if (PA_UNLIKELY(default_rate == alternate_rate && !passthrough)) {
+#ifndef __TIZEN__
+ if (PA_UNLIKELY(default_rate == alternate_rate && !passthrough && !avoid_resampling)) {
pa_log_debug("Default and alternate sample rates are the same, so there is no point in switching.");
- return -1;
+ return;
}
+#endif
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 -1;
+ pa_log_info("Cannot update sample spec, SINK_IS_RUNNING, will keep using %s and %u Hz",
+ pa_sample_format_to_string(s->sample_spec.format), s->sample_spec.rate);
+ return;
}
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 -1;
+ pa_log_info("Cannot update sample spec, monitor source is RUNNING");
+ return;
}
}
- if (PA_UNLIKELY(!pa_sample_rate_valid(desired_rate)))
- return -1;
+ if (PA_UNLIKELY(!pa_sample_spec_valid(spec)))
+ return;
+
+ desired_spec = s->sample_spec;
+
+#ifdef __TIZEN__
+ if (!avoid_resampling) {
+ default_rate = alternate_rate = s->selected_sample_rate;
+ desired_spec.format = s->selected_sample_format;
+ }
+#endif
+ if (passthrough) {
+ /* We have to try to use the sink input format and rate */
+ desired_spec.format = spec->format;
+ desired_spec.rate = spec->rate;
+
+ } else if (avoid_resampling) {
+ /* We just try to set the sink input's sample rate if it's not too low */
+ if (spec->rate >= default_rate || spec->rate >= alternate_rate)
+ desired_spec.rate = spec->rate;
+ desired_spec.format = spec->format;
+
+ } else if (default_rate == spec->rate || alternate_rate == spec->rate) {
+ /* We can directly try to use this rate */
+ desired_spec.rate = spec->rate;
+
+ }
- if (!passthrough && default_rate != desired_rate && alternate_rate != desired_rate) {
- if (default_rate % 11025 == 0 && desired_rate % 11025 == 0)
+ if (desired_spec.rate != spec->rate) {
+ /* See if we can pick a rate that results in less resampling effort */
+ if (default_rate % 11025 == 0 && spec->rate % 11025 == 0)
default_rate_is_usable = true;
- if (default_rate % 4000 == 0 && desired_rate % 4000 == 0)
+ if (default_rate % 4000 == 0 && spec->rate % 4000 == 0)
default_rate_is_usable = true;
- if (alternate_rate && alternate_rate % 11025 == 0 && desired_rate % 11025 == 0)
+ if (alternate_rate % 11025 == 0 && spec->rate % 11025 == 0)
alternate_rate_is_usable = true;
- if (alternate_rate && alternate_rate % 4000 == 0 && desired_rate % 4000 == 0)
+ if (alternate_rate % 4000 == 0 && spec->rate % 4000 == 0)
alternate_rate_is_usable = true;
if (alternate_rate_is_usable && !default_rate_is_usable)
- desired_rate = alternate_rate;
+ desired_spec.rate = alternate_rate;
else
- desired_rate = default_rate;
+ desired_spec.rate = default_rate;
}
- if (desired_rate == s->sample_spec.rate)
- return -1;
+ if (pa_sample_spec_equal(&desired_spec, &s->sample_spec) && passthrough == pa_sink_is_passthrough(s))
+#ifdef __TIZEN__
+ {
+ pa_log_info("desired spec is same as sink->sample_spec");
+ return;
+ }
+#else
+ return;
+#endif
if (!passthrough && pa_sink_used_by(s) > 0)
- return -1;
+ return;
- pa_log_debug("Suspending sink %s due to changing the sample rate.", s->name);
+ pa_log_debug("Suspending sink %s due to changing format, desired format = %s rate = %u",
+ s->name, pa_sample_format_to_string(desired_spec.format), desired_spec.rate);
pa_sink_suspend(s, true, PA_SUSPEND_INTERNAL);
- if (s->update_rate(s, desired_rate) >= 0) {
- /* 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");
+ s->reconfigure(s, &desired_spec, passthrough);
- PA_IDXSET_FOREACH(i, s->inputs, idx) {
- if (i->state == PA_SINK_INPUT_CORKED)
- pa_sink_input_update_rate(i);
- }
+ /* update monitor source as well */
+ if (s->monitor_source && !passthrough)
+ pa_source_reconfigure(s->monitor_source, &s->sample_spec, false);
+ pa_log_info("Reconfigured successfully");
- ret = 0;
+ PA_IDXSET_FOREACH(i, s->inputs, idx) {
+ if (i->state == PA_SINK_INPUT_CORKED)
+ pa_sink_input_update_resampler(i);
}
pa_sink_suspend(s, false, PA_SUSPEND_INTERNAL);
-
- return ret;
}
/* Called from main thread */
pa_usec_t pa_sink_get_latency(pa_sink *s) {
- pa_usec_t usec = 0;
+ int64_t usec = 0;
pa_sink_assert_ref(s);
pa_assert_ctl_context();
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
+ /* the return value 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;
+ if (-s->port_latency_offset <= usec)
+ usec += s->port_latency_offset;
else
usec = 0;
- return usec;
+ return (pa_usec_t)usec;
}
/* Called from IO thread */
-pa_usec_t pa_sink_get_latency_within_thread(pa_sink *s) {
- pa_usec_t usec = 0;
+int64_t pa_sink_get_latency_within_thread(pa_sink *s, bool allow_negative) {
+ int64_t usec = 0;
pa_msgobject *o;
pa_sink_assert_ref(s);
/* FIXME: We probably should make this a proper vtable callback instead of going through process_msg() */
- if (o->process_msg(o, PA_SINK_MESSAGE_GET_LATENCY, &usec, 0, NULL) < 0)
- return -1;
+ o->process_msg(o, PA_SINK_MESSAGE_GET_LATENCY, &usec, 0, NULL);
- /* 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
+ /* If allow_negative is false, the call should only return positive values, */
+ usec += s->thread_info.port_latency_offset;
+ if (!allow_negative && usec < 0)
usec = 0;
return usec;
}
/* Called from main context */
+bool pa_sink_is_filter(pa_sink *s) {
+ pa_sink_assert_ref(s);
+
+ return (s->input_to_master != NULL);
+}
+
+/* Called from main context */
bool pa_sink_is_passthrough(pa_sink *s) {
pa_sink_input *alt_i;
uint32_t idx;
void pa_sink_enter_passthrough(pa_sink *s) {
pa_cvolume volume;
+ /* The sink implementation is reconfigured for passthrough in
+ * pa_sink_reconfigure(). This function sets the PA core objects to
+ * passthrough mode. */
+
/* 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_cvolume_set(&volume, s->sample_spec.channels, PA_MIN(s->base_volume, PA_VOLUME_NORM));
pa_sink_set_volume(s, &volume, true, false);
+
+ pa_log_debug("Suspending/Restarting sink %s to enter passthrough mode", s->name);
}
/* Called from main context */
pa_cvolume_init(&s->saved_volume);
s->saved_save_volume = false;
+
}
/* Called from main context. */
PA_IDXSET_FOREACH(i, s->inputs, idx) {
compute_reference_ratio(i);
- if (i->origin_sink && (i->origin_sink->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER))
+ if (i->origin_sink && (i->origin_sink->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)
+ && PA_SINK_IS_LINKED(i->origin_sink->state))
compute_reference_ratios(i->origin_sink);
}
}
pa_cvolume_reset(&i->real_ratio, i->real_ratio.channels);
i->soft_volume = i->volume_factor;
- compute_real_ratios(i->origin_sink);
+ if (PA_SINK_IS_LINKED(i->origin_sink->state))
+ compute_real_ratios(i->origin_sink);
continue;
}
pa_cvolume remapped;
if (i->origin_sink && (i->origin_sink->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)) {
- get_maximum_input_volume(i->origin_sink, max_volume, channel_map);
+ if (PA_SINK_IS_LINKED(i->origin_sink->state))
+ get_maximum_input_volume(i->origin_sink, max_volume, channel_map);
/* Ignore this input. The origin sink uses volume sharing, so this
* input's volume will be set to be equal to the root sink's real
compute_reference_ratio(i);
}
- update_real_volume(i->origin_sink, new_volume, channel_map);
+ if (PA_SINK_IS_LINKED(i->origin_sink->state))
+ update_real_volume(i->origin_sink, new_volume, channel_map);
}
}
}
pa_cvolume new_volume;
if (i->origin_sink && (i->origin_sink->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)) {
- propagate_reference_volume(i->origin_sink);
+ if (PA_SINK_IS_LINKED(i->origin_sink->state))
+ propagate_reference_volume(i->origin_sink);
/* Since the origin sink uses volume sharing, this input's volume
* needs to be updated to match the root sink's real volume, but
- * that will be done later in update_shared_real_volume(). */
+ * that will be done later in update_real_volume(). */
continue;
}
return false;
PA_IDXSET_FOREACH(i, s->inputs, idx) {
- if (i->origin_sink && (i->origin_sink->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER))
+ if (i->origin_sink && (i->origin_sink->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)
+ && PA_SINK_IS_LINKED(i->origin_sink->state))
update_reference_volume(i->origin_sink, v, channel_map, false);
}
pa_sw_cvolume_multiply(&new_volume, &new_volume, &i->reference_ratio);
pa_sink_input_set_volume_direct(i, &new_volume);
- if (i->origin_sink && (i->origin_sink->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER))
+ if (i->origin_sink && (i->origin_sink->flags & PA_SINK_SHARE_VOLUME_WITH_MASTER)
+ && PA_SINK_IS_LINKED(i->origin_sink->state))
propagate_real_volume(i->origin_sink, old_real_volume);
}
}
}
/* Called from main thread */
+#ifdef __TIZEN__
+void pa_sink_update_proplist(pa_sink *s, pa_update_mode_t mode, pa_proplist *p) {
+#else
bool pa_sink_update_proplist(pa_sink *s, pa_update_mode_t mode, pa_proplist *p) {
+#endif
pa_sink_assert_ref(s);
pa_assert_ctl_context();
pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
}
+#ifndef __TIZEN__
return true;
+#endif
+}
+
+#ifdef __TIZEN__
+/* Called from main thread */
+void pa_sink_update_proplist_remote_access_permission(pa_sink *s, bool allowed) {
+ pa_proplist* p = NULL;
+
+ pa_sink_assert_ref(s);
+ pa_assert_ctl_context();
+
+ p = pa_proplist_new();
+
+ if (pa_proplist_set_remote_access_permission(p, allowed) == 0)
+ pa_sink_update_proplist(s, PA_UPDATE_REPLACE, p);
+ else
+ pa_log_error("set remote access permission %d on proplist %p failed", allowed, p);
+
+ pa_proplist_free(p);
}
+#endif /* __TIZEN__ */
/* Called from main thread */
/* FIXME -- this should be dropped and be merged into pa_sink_update_proplist() */
}
/* Called from main thread */
-unsigned pa_sink_check_suspend(pa_sink *s) {
+unsigned pa_sink_check_suspend(pa_sink *s, pa_sink_input *ignore_input, pa_source_output *ignore_output) {
unsigned ret;
pa_sink_input *i;
uint32_t idx;
ret = 0;
PA_IDXSET_FOREACH(i, s->inputs, idx) {
- pa_sink_input_state_t st;
-
- st = pa_sink_input_get_state(i);
+ if (i == ignore_input)
+ continue;
/* 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
* towards our busy status.
*/
- if (!PA_SINK_INPUT_IS_LINKED(st))
+ if (!PA_SINK_INPUT_IS_LINKED(i->state))
continue;
- if (st == PA_SINK_INPUT_CORKED)
+ if (i->state == PA_SINK_INPUT_CORKED)
continue;
if (i->flags & PA_SINK_INPUT_DONT_INHIBIT_AUTO_SUSPEND)
}
if (s->monitor_source)
- ret += pa_source_check_suspend(s->monitor_source);
+ ret += pa_source_check_suspend(s->monitor_source, ignore_output);
return ret;
}
+const char *pa_sink_state_to_string(pa_sink_state_t state) {
+ switch (state) {
+ case PA_SINK_INIT: return "INIT";
+ case PA_SINK_IDLE: return "IDLE";
+ case PA_SINK_RUNNING: return "RUNNING";
+ case PA_SINK_SUSPENDED: return "SUSPENDED";
+ case PA_SINK_UNLINKED: return "UNLINKED";
+ case PA_SINK_INVALID_STATE: return "INVALID_STATE";
+ }
+
+ pa_assert_not_reached();
+}
+
/* Called from the IO thread */
static void sync_input_volumes_within_thread(pa_sink *s) {
pa_sink_input *i;
i->thread_info.sync_next->thread_info.sync_prev = i;
}
- pa_assert(!i->thread_info.attached);
- i->thread_info.attached = true;
-
- if (i->attach)
- i->attach(i);
+ pa_sink_input_attach(i);
pa_sink_input_set_state_within_thread(i, i->state);
* sink input handling a few lines down at
* PA_SINK_MESSAGE_START_MOVE, too. */
- if (i->detach)
- i->detach(i);
+ pa_sink_input_detach(i);
pa_sink_input_set_state_within_thread(i, i->state);
- pa_assert(i->thread_info.attached);
- i->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 */
* same as the read index. */
/* Get the latency of the sink */
- usec = pa_sink_get_latency_within_thread(s);
+ usec = pa_sink_get_latency_within_thread(s, false);
sink_nbytes = pa_usec_to_bytes(usec, &s->sample_spec);
total_nbytes = sink_nbytes + pa_memblockq_get_length(i->thread_info.render_memblockq);
}
}
- if (i->detach)
- i->detach(i);
-
- pa_assert(i->thread_info.attached);
- i->thread_info.attached = false;
+ pa_sink_input_detach(i);
/* Let's remove the sink input ...*/
pa_hashmap_remove_and_free(s->thread_info.inputs, PA_UINT32_TO_PTR(i->index));
pa_hashmap_put(s->thread_info.inputs, PA_UINT32_TO_PTR(i->index), pa_sink_input_ref(i));
- pa_assert(!i->thread_info.attached);
- i->thread_info.attached = true;
-
- if (i->attach)
- i->attach(i);
+ pa_sink_input_attach(i);
if (i->thread_info.state != PA_SINK_INPUT_CORKED) {
pa_usec_t usec = 0;
* rewind. */
/* Get the latency of the sink */
- usec = pa_sink_get_latency_within_thread(s);
+ usec = pa_sink_get_latency_within_thread(s, false);
nbytes = pa_usec_to_bytes(usec, &s->sample_spec);
if (nbytes > 0)
return 0;
case PA_SINK_MESSAGE_SET_STATE: {
-
+ struct set_state_data *data = userdata;
bool suspend_change =
- (s->thread_info.state == PA_SINK_SUSPENDED && PA_SINK_IS_OPENED(PA_PTR_TO_UINT(userdata))) ||
- (PA_SINK_IS_OPENED(s->thread_info.state) && PA_PTR_TO_UINT(userdata) == PA_SINK_SUSPENDED);
+ (s->thread_info.state == PA_SINK_SUSPENDED && PA_SINK_IS_OPENED(data->state)) ||
+ (PA_SINK_IS_OPENED(s->thread_info.state) && data->state == PA_SINK_SUSPENDED);
+
+ if (s->set_state_in_io_thread) {
+ int r;
- s->thread_info.state = PA_PTR_TO_UINT(userdata);
+ if ((r = s->set_state_in_io_thread(s, data->state, data->suspend_cause)) < 0)
+ return r;
+ }
+
+ s->thread_info.state = data->state;
if (s->thread_info.state == PA_SINK_SUSPENDED) {
s->thread_info.rewind_nbytes = 0;
pa_sink_set_max_request_within_thread(s, (size_t) offset);
return 0;
- case PA_SINK_MESSAGE_SET_PORT:
-
- pa_assert(userdata);
- if (s->set_port) {
- struct sink_message_set_port *msg_data = userdata;
- msg_data->ret = s->set_port(s, msg_data->port);
- }
- return 0;
-
case PA_SINK_MESSAGE_UPDATE_VOLUME_AND_MUTE:
/* This message is sent from IO-thread and handled in main thread. */
pa_assert_ctl_context();
pa_sink_get_mute(s, true);
return 0;
- case PA_SINK_MESSAGE_SET_LATENCY_OFFSET:
- s->thread_info.latency_offset = offset;
+ case PA_SINK_MESSAGE_SET_PORT_LATENCY_OFFSET:
+ s->thread_info.port_latency_offset = offset;
return 0;
case PA_SINK_MESSAGE_GET_LATENCY:
pa_assert(PA_SINK_IS_LINKED(s->thread_info.state));
PA_HASHMAP_FOREACH(i, s->thread_info.inputs, state)
- if (i->detach)
- i->detach(i);
+ pa_sink_input_detach(i);
if (s->monitor_source)
pa_source_detach_within_thread(s->monitor_source);
pa_assert(PA_SINK_IS_LINKED(s->thread_info.state));
PA_HASHMAP_FOREACH(i, s->thread_info.inputs, state)
- if (i->attach)
- i->attach(i);
+ pa_sink_input_attach(i);
if (s->monitor_source)
pa_source_attach_within_thread(s->monitor_source);
}
/* Called from main context */
-void pa_sink_set_latency_offset(pa_sink *s, int64_t offset) {
+void pa_sink_set_port_latency_offset(pa_sink *s, int64_t offset) {
pa_sink_assert_ref(s);
- s->latency_offset = offset;
+ s->port_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);
+ pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_PORT_LATENCY_OFFSET, NULL, offset, NULL) == 0);
else
- s->thread_info.latency_offset = offset;
+ s->thread_info.port_latency_offset = offset;
+
+ pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_PORT_LATENCY_OFFSET_CHANGED], s);
}
/* Called from main context */
/* Called from main context */
int pa_sink_set_port(pa_sink *s, const char *name, bool save) {
pa_device_port *port;
- int ret;
pa_sink_assert_ref(s);
pa_assert_ctl_context();
return 0;
}
- 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;
- }
- else
- ret = s->set_port(s, port);
+ s->port_changing = true;
- if (ret < 0)
+ if (s->set_port(s, port) < 0)
return -PA_ERR_NOENTITY;
pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
s->active_port = port;
s->save_port = save;
- pa_sink_set_latency_offset(s, s->active_port->latency_offset);
+ pa_sink_set_port_latency_offset(s, s->active_port->latency_offset);
+
+ /* The active port affects the default sink selection. */
+ pa_core_update_default_sink(s->core);
pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_PORT_CHANGED], s);
+ s->port_changing = false;
+
return 0;
}
pa_assert(p);
+ /* JACK sinks and sources get very high priority so that we'll switch the
+ * default devices automatically when jackd starts and
+ * module-jackdbus-detect creates the jack sink and source. */
+ if ((s = pa_proplist_gets(p, PA_PROP_DEVICE_API))) {
+ if (pa_streq(s, "jack"))
+ priority += 10000;
+ }
+
if ((s = pa_proplist_gets(p, PA_PROP_DEVICE_CLASS))) {
if (pa_streq(s, "sound"))
if ((s = pa_proplist_gets(p, PA_PROP_DEVICE_FORM_FACTOR))) {
- if (pa_streq(s, "internal"))
+ if (pa_streq(s, "headphone"))
priority += 900;
+ else if (pa_streq(s, "hifi"))
+ priority += 600;
else if (pa_streq(s, "speaker"))
priority += 500;
- else if (pa_streq(s, "headphone"))
- priority += 400;
+ else if (pa_streq(s, "portable"))
+ priority += 450;
}
if ((s = pa_proplist_gets(p, PA_PROP_DEVICE_BUS))) {
- if (pa_streq(s, "pci"))
+ if (pa_streq(s, "bluetooth"))
priority += 50;
else if (pa_streq(s, "usb"))
priority += 40;
- else if (pa_streq(s, "bluetooth"))
+ else if (pa_streq(s, "pci"))
priority += 30;
}
if ((s = pa_proplist_gets(p, PA_PROP_DEVICE_PROFILE_NAME))) {
- if (pa_startswith(s, "analog-"))
+ if (pa_startswith(s, "analog-")) {
priority += 9;
+
+ /* If an analog device has an intended role of "phone", it probably
+ * co-exists with another device that is meant for everything else,
+ * and that other device should have higher priority than the phone
+ * device. */
+ if (pa_str_in_list_spaces(pa_proplist_gets(p, PA_PROP_DEVICE_INTENDED_ROLES), "phone"))
+ priority -= 1;
+ }
else if (pa_startswith(s, "iec958-"))
- priority += 8;
+ priority += 7;
}
return priority;
void pa_sink_volume_change_push(pa_sink *s) {
pa_sink_volume_change *c = NULL;
pa_sink_volume_change *nc = NULL;
+ pa_sink_volume_change *pc = NULL;
uint32_t safety_margin = s->thread_info.volume_change_safety_margin;
const char *direction = NULL;
return;
}
- nc->at = pa_sink_get_latency_within_thread(s);
+ nc->at = pa_sink_get_latency_within_thread(s, false);
nc->at += pa_rtclock_now() + s->thread_info.volume_change_extra_delay;
if (s->thread_info.volume_changes_tail) {
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_LLIST_FOREACH_SAFE(c, pc, 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_sink_volume_change_free(c);
}
pa_sink_volume_change *c;
pa_volume_t prev_vol = pa_cvolume_avg(&s->thread_info.current_hw_volume);
pa_usec_t rewound = pa_bytes_to_usec(nbytes, &s->sample_spec);
- pa_usec_t limit = pa_sink_get_latency_within_thread(s);
+ pa_usec_t limit = pa_sink_get_latency_within_thread(s, false);
pa_log_debug("latency = %lld", (long long) limit);
limit += pa_rtclock_now() + s->thread_info.volume_change_extra_delay;
return out_formats;
}
+/* Called from the main thread */
+void pa_sink_set_sample_format(pa_sink *s, pa_sample_format_t format) {
+ pa_sample_format_t old_format;
+
+ pa_assert(s);
+ pa_assert(pa_sample_format_valid(format));
+
+ old_format = s->sample_spec.format;
+ if (old_format == format)
+ return;
+
+ pa_log_info("%s: format: %s -> %s",
+ s->name, pa_sample_format_to_string(old_format), pa_sample_format_to_string(format));
+
+ s->sample_spec.format = format;
+
+ pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK | PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+}
+
+/* Called from the main thread */
+void pa_sink_set_sample_rate(pa_sink *s, uint32_t rate) {
+ uint32_t old_rate;
+
+ pa_assert(s);
+ pa_assert(pa_sample_rate_valid(rate));
+
+ old_rate = s->sample_spec.rate;
+ if (old_rate == rate)
+ return;
+
+ pa_log_info("%s: rate: %u -> %u", s->name, old_rate, rate);
+
+ s->sample_spec.rate = rate;
+
+ pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK | PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
+}
+
/* Called from the main thread. */
void pa_sink_set_reference_volume_direct(pa_sink *s, const pa_cvolume *volume) {
pa_cvolume old_volume;
pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index);
pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_VOLUME_CHANGED], s);
}
+
+void pa_sink_move_streams_to_default_sink(pa_core *core, pa_sink *old_sink, bool default_sink_changed) {
+ pa_sink_input *i;
+ uint32_t idx;
+
+ pa_assert(core);
+ pa_assert(old_sink);
+
+ if (core->state == PA_CORE_SHUTDOWN)
+ return;
+
+ if (core->default_sink == NULL || core->default_sink->unlink_requested)
+ return;
+
+ if (old_sink == core->default_sink)
+ return;
+
+ PA_IDXSET_FOREACH(i, old_sink->inputs, idx) {
+ if (!PA_SINK_INPUT_IS_LINKED(i->state))
+ continue;
+
+ if (!i->sink)
+ continue;
+
+ /* Don't move sink-inputs which connect filter sinks to their target sinks */
+ if (i->origin_sink)
+ continue;
+
+ /* If default_sink_changed is false, the old sink became unavailable, so all streams must be moved. */
+ if (pa_safe_streq(old_sink->name, i->preferred_sink) && default_sink_changed)
+ continue;
+
+ if (!pa_sink_input_may_move_to(i, core->default_sink))
+ continue;
+
+ if (default_sink_changed)
+ pa_log_info("The sink input %u \"%s\" is moving to %s due to change of the default sink.",
+ i->index, pa_strnull(pa_proplist_gets(i->proplist, PA_PROP_APPLICATION_NAME)), core->default_sink->name);
+ else
+ pa_log_info("The sink input %u \"%s\" is moving to %s, because the old sink became unavailable.",
+ i->index, pa_strnull(pa_proplist_gets(i->proplist, PA_PROP_APPLICATION_NAME)), core->default_sink->name);
+
+ pa_sink_input_move_to(i, core->default_sink, false);
+ }
+}