| /*** | |
| This file is part of PulseAudio. | |
| Copyright 2004-2006 Lennart Poettering | |
| Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB | |
| PulseAudio is free software; you can redistribute it and/or modify | |
| it under the terms of the GNU Lesser General Public License as published | |
| by the Free Software Foundation; either version 2.1 of the License, | |
| or (at your option) any later version. | |
| PulseAudio is distributed in the hope that it will be useful, but | |
| WITHOUT ANY WARRANTY; without even the implied warranty of | |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| General Public License for more details. | |
| You should have received a copy of the GNU Lesser General Public License | |
| along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. | |
| ***/ | |
| #ifdef HAVE_CONFIG_H | |
| #include <config.h> | |
| #endif | |
| #include <stdlib.h> | |
| #include <stdio.h> | |
| #include <signal.h> | |
| #include <pulse/rtclock.h> | |
| #include <pulse/timeval.h> | |
| #include <pulse/xmalloc.h> | |
| #include <pulsecore/module.h> | |
| #include <pulsecore/core-rtclock.h> | |
| #include <pulsecore/core-util.h> | |
| #include <pulsecore/message-handler.h> | |
| #include <pulsecore/core-scache.h> | |
| #include <pulsecore/core-subscribe.h> | |
| #include <pulsecore/random.h> | |
| #include <pulsecore/log.h> | |
| #include <pulsecore/macro.h> | |
| #include <pulsecore/strbuf.h> | |
| #include <pulsecore/namereg.h> | |
| #include "core.h" | |
| PA_DEFINE_PUBLIC_CLASS(pa_core, pa_msgobject); | |
| static int core_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk) { | |
| pa_core *c = PA_CORE(o); | |
| pa_core_assert_ref(c); | |
| switch (code) { | |
| case PA_CORE_MESSAGE_UNLOAD_MODULE: | |
| pa_module_unload(userdata, true); | |
| return 0; | |
| default: | |
| return -1; | |
| } | |
| } | |
| static void core_free(pa_object *o); | |
| /* Returns a list of handlers. */ | |
| static char *message_handler_list(pa_core *c) { | |
| pa_json_encoder *encoder; | |
| void *state = NULL; | |
| struct pa_message_handler *handler; | |
| encoder = pa_json_encoder_new(); | |
| pa_json_encoder_begin_element_array(encoder); | |
| PA_HASHMAP_FOREACH(handler, c->message_handlers, state) { | |
| pa_json_encoder_begin_element_object(encoder); | |
| pa_json_encoder_add_member_string(encoder, "name", handler->object_path); | |
| pa_json_encoder_add_member_string(encoder, "description", handler->description); | |
| pa_json_encoder_end_object(encoder); | |
| } | |
| pa_json_encoder_end_array(encoder); | |
| return pa_json_encoder_to_string_free(encoder); | |
| } | |
| static int core_message_handler(const char *object_path, const char *message, const pa_json_object *parameters, char **response, void *userdata) { | |
| pa_core *c = userdata; | |
| pa_assert(c); | |
| pa_assert(message); | |
| pa_assert(response); | |
| pa_assert(pa_safe_streq(object_path, "/core")); | |
| if (pa_streq(message, "list-handlers")) { | |
| *response = message_handler_list(c); | |
| return PA_OK; | |
| } | |
| return -PA_ERR_NOTIMPLEMENTED; | |
| } | |
| pa_core* pa_core_new(pa_mainloop_api *m, bool shared, bool enable_memfd, size_t shm_size) { | |
| pa_core* c; | |
| pa_mempool *pool; | |
| pa_mem_type_t type; | |
| int j; | |
| pa_assert(m); | |
| if (shared) { | |
| type = (enable_memfd) ? PA_MEM_TYPE_SHARED_MEMFD : PA_MEM_TYPE_SHARED_POSIX; | |
| if (!(pool = pa_mempool_new(type, shm_size, false))) { | |
| pa_log_warn("Failed to allocate %s memory pool. Falling back to a normal memory pool.", | |
| pa_mem_type_to_string(type)); | |
| shared = false; | |
| } | |
| } | |
| if (!shared) { | |
| if (!(pool = pa_mempool_new(PA_MEM_TYPE_PRIVATE, shm_size, false))) { | |
| pa_log("pa_mempool_new() failed."); | |
| return NULL; | |
| } | |
| } | |
| c = pa_msgobject_new(pa_core); | |
| c->parent.parent.free = core_free; | |
| c->parent.process_msg = core_process_msg; | |
| c->state = PA_CORE_STARTUP; | |
| c->mainloop = m; | |
| c->clients = pa_idxset_new(NULL, NULL); | |
| c->cards = pa_idxset_new(NULL, NULL); | |
| c->sinks = pa_idxset_new(NULL, NULL); | |
| c->sources = pa_idxset_new(NULL, NULL); | |
| c->sink_inputs = pa_idxset_new(NULL, NULL); | |
| c->source_outputs = pa_idxset_new(NULL, NULL); | |
| c->modules = pa_idxset_new(NULL, NULL); | |
| c->scache = pa_idxset_new(NULL, NULL); | |
| c->namereg = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); | |
| c->shared = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); | |
| c->message_handlers = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); | |
| pa_message_handler_register(c, "/core", "Core message handler", core_message_handler, (void *) c); | |
| c->default_source = NULL; | |
| c->default_sink = NULL; | |
| c->configured_default_source = NULL; | |
| c->configured_default_sink = NULL; | |
| c->policy_default_source = NULL; | |
| c->policy_default_sink = NULL; | |
| c->default_sample_spec.format = PA_SAMPLE_S16NE; | |
| c->default_sample_spec.rate = 44100; | |
| c->default_sample_spec.channels = 2; | |
| pa_channel_map_init_extend(&c->default_channel_map, c->default_sample_spec.channels, PA_CHANNEL_MAP_DEFAULT); | |
| c->default_n_fragments = 4; | |
| c->default_fragment_size_msec = 25; | |
| c->deferred_volume_safety_margin_usec = 8000; | |
| c->deferred_volume_extra_delay_usec = 0; | |
| c->module_defer_unload_event = NULL; | |
| c->modules_pending_unload = pa_hashmap_new(NULL, NULL); | |
| c->subscription_defer_event = NULL; | |
| PA_LLIST_HEAD_INIT(pa_subscription, c->subscriptions); | |
| PA_LLIST_HEAD_INIT(pa_subscription_event, c->subscription_event_queue); | |
| c->subscription_event_last = NULL; | |
| c->mempool = pool; | |
| c->shm_size = shm_size; | |
| pa_silence_cache_init(&c->silence_cache); | |
| c->exit_event = NULL; | |
| c->scache_auto_unload_event = NULL; | |
| c->exit_idle_time = -1; | |
| c->scache_idle_time = 20; | |
| c->flat_volumes = true; | |
| c->disallow_module_loading = false; | |
| c->disallow_exit = false; | |
| c->running_as_daemon = false; | |
| c->realtime_scheduling = false; | |
| c->realtime_priority = 5; | |
| c->disable_remixing = false; | |
| c->remixing_use_all_sink_channels = true; | |
| c->remixing_produce_lfe = false; | |
| c->remixing_consume_lfe = false; | |
| c->lfe_crossover_freq = 0; | |
| c->deferred_volume = true; | |
| c->resample_method = PA_RESAMPLER_SPEEX_FLOAT_BASE + 1; | |
| for (j = 0; j < PA_CORE_HOOK_MAX; j++) | |
| pa_hook_init(&c->hooks[j], c); | |
| pa_random(&c->cookie, sizeof(c->cookie)); | |
| #ifdef SIGPIPE | |
| pa_check_signal_is_blocked(SIGPIPE); | |
| #endif | |
| return c; | |
| } | |
| static void core_free(pa_object *o) { | |
| pa_core *c = PA_CORE(o); | |
| int j; | |
| pa_assert(c); | |
| c->state = PA_CORE_SHUTDOWN; | |
| /* Note: All modules and samples in the cache should be unloaded before | |
| * we get here */ | |
| pa_assert(pa_idxset_isempty(c->scache)); | |
| pa_idxset_free(c->scache, NULL); | |
| pa_assert(pa_idxset_isempty(c->modules)); | |
| pa_idxset_free(c->modules, NULL); | |
| pa_assert(pa_idxset_isempty(c->clients)); | |
| pa_idxset_free(c->clients, NULL); | |
| pa_assert(pa_idxset_isempty(c->cards)); | |
| pa_idxset_free(c->cards, NULL); | |
| pa_assert(pa_idxset_isempty(c->sinks)); | |
| pa_idxset_free(c->sinks, NULL); | |
| pa_assert(pa_idxset_isempty(c->sources)); | |
| pa_idxset_free(c->sources, NULL); | |
| pa_assert(pa_idxset_isempty(c->source_outputs)); | |
| pa_idxset_free(c->source_outputs, NULL); | |
| pa_assert(pa_idxset_isempty(c->sink_inputs)); | |
| pa_idxset_free(c->sink_inputs, NULL); | |
| pa_assert(pa_hashmap_isempty(c->namereg)); | |
| pa_hashmap_free(c->namereg); | |
| pa_assert(pa_hashmap_isempty(c->shared)); | |
| pa_hashmap_free(c->shared); | |
| pa_message_handler_unregister(c, "/core"); | |
| pa_assert(pa_hashmap_isempty(c->message_handlers)); | |
| pa_hashmap_free(c->message_handlers); | |
| pa_assert(pa_hashmap_isempty(c->modules_pending_unload)); | |
| pa_hashmap_free(c->modules_pending_unload); | |
| pa_subscription_free_all(c); | |
| if (c->exit_event) | |
| c->mainloop->time_free(c->exit_event); | |
| pa_assert(!c->default_source); | |
| pa_assert(!c->default_sink); | |
| pa_xfree(c->configured_default_source); | |
| pa_xfree(c->configured_default_sink); | |
| pa_xfree(c->policy_default_source); | |
| pa_xfree(c->policy_default_sink); | |
| pa_silence_cache_done(&c->silence_cache); | |
| pa_mempool_unref(c->mempool); | |
| for (j = 0; j < PA_CORE_HOOK_MAX; j++) | |
| pa_hook_done(&c->hooks[j]); | |
| pa_xfree(c); | |
| } | |
| static bool is_sink_available(pa_core *core, const char *sink_name) { | |
| pa_sink *sink; | |
| if (!(sink = pa_namereg_get(core, sink_name, PA_NAMEREG_SINK))) | |
| return false; | |
| if (!PA_SINK_IS_LINKED(sink->state)) | |
| return false; | |
| if (sink->active_port && sink->active_port->available == PA_AVAILABLE_NO) | |
| return false; | |
| return true; | |
| } | |
| static bool is_source_available(pa_core *core, const char *source_name) { | |
| pa_source *source; | |
| if (!(source = pa_namereg_get(core, source_name, PA_NAMEREG_SOURCE))) | |
| return false; | |
| if (!PA_SOURCE_IS_LINKED(source->state)) | |
| return false; | |
| if (source->active_port && source->active_port->available == PA_AVAILABLE_NO) | |
| return false; | |
| return true; | |
| } | |
| void pa_core_set_configured_default_sink(pa_core *core, const char *sink) { | |
| char *old_sink; | |
| pa_assert(core); | |
| old_sink = pa_xstrdup(core->configured_default_sink); | |
| /* The default sink was overwritten by the policy default sink, but the user is | |
| * now setting a new default manually. Clear the policy default sink. */ | |
| if (core->policy_default_sink && is_sink_available(core, core->policy_default_sink)) { | |
| pa_xfree(core->policy_default_sink); | |
| core->policy_default_sink = NULL; | |
| } else if (pa_safe_streq(sink, old_sink)) | |
| goto finish; | |
| pa_xfree(core->configured_default_sink); | |
| core->configured_default_sink = pa_xstrdup(sink); | |
| if (!pa_safe_streq(sink, old_sink)) { | |
| pa_log_info("configured_default_sink: %s -> %s", | |
| old_sink ? old_sink : "(unset)", sink ? sink : "(unset)"); | |
| } | |
| pa_subscription_post(core, PA_SUBSCRIPTION_EVENT_SERVER | PA_SUBSCRIPTION_EVENT_CHANGE, PA_INVALID_INDEX); | |
| pa_core_update_default_sink(core); | |
| finish: | |
| pa_xfree(old_sink); | |
| } | |
| void pa_core_set_configured_default_source(pa_core *core, const char *source) { | |
| char *old_source; | |
| pa_assert(core); | |
| old_source = pa_xstrdup(core->configured_default_source); | |
| /* The default source was overwritten by the policy default source, but the user is | |
| * now setting a new default manually. Clear the policy default source. */ | |
| if (core->policy_default_source && is_source_available(core, core->policy_default_source)) { | |
| pa_xfree(core->policy_default_source); | |
| core->policy_default_source = NULL; | |
| } else if (pa_safe_streq(source, old_source)) | |
| goto finish; | |
| pa_xfree(core->configured_default_source); | |
| core->configured_default_source = pa_xstrdup(source); | |
| if (!pa_safe_streq(source, old_source)) { | |
| pa_log_info("configured_default_source: %s -> %s", | |
| old_source ? old_source : "(unset)", source ? source : "(unset)"); | |
| } | |
| pa_subscription_post(core, PA_SUBSCRIPTION_EVENT_SERVER | PA_SUBSCRIPTION_EVENT_CHANGE, PA_INVALID_INDEX); | |
| pa_core_update_default_source(core); | |
| finish: | |
| pa_xfree(old_source); | |
| } | |
| void pa_core_set_policy_default_sink(pa_core *core, const char *sink) { | |
| char *old_sink; | |
| pa_assert(core); | |
| old_sink = pa_xstrdup(core->policy_default_sink); | |
| if (pa_safe_streq(sink, old_sink)) | |
| goto finish; | |
| pa_xfree(core->policy_default_sink); | |
| core->policy_default_sink = pa_xstrdup(sink); | |
| pa_log_info("policy_default_sink: %s -> %s", | |
| old_sink ? old_sink : "(unset)", sink ? sink : "(unset)"); | |
| pa_subscription_post(core, PA_SUBSCRIPTION_EVENT_SERVER | PA_SUBSCRIPTION_EVENT_CHANGE, PA_INVALID_INDEX); | |
| pa_core_update_default_sink(core); | |
| finish: | |
| pa_xfree(old_sink); | |
| } | |
| void pa_core_set_policy_default_source(pa_core *core, const char *source) { | |
| char *old_source; | |
| pa_assert(core); | |
| old_source = pa_xstrdup(core->policy_default_source); | |
| if (pa_safe_streq(source, old_source)) | |
| goto finish; | |
| pa_xfree(core->policy_default_source); | |
| core->policy_default_source = pa_xstrdup(source); | |
| pa_log_info("policy_default_source: %s -> %s", | |
| old_source ? old_source : "(unset)", source ? source : "(unset)"); | |
| pa_subscription_post(core, PA_SUBSCRIPTION_EVENT_SERVER | PA_SUBSCRIPTION_EVENT_CHANGE, PA_INVALID_INDEX); | |
| pa_core_update_default_source(core); | |
| finish: | |
| pa_xfree(old_source); | |
| } | |
| /* a < b -> return -1 | |
| * a == b -> return 0 | |
| * a > b -> return 1 */ | |
| static int compare_sinks(pa_sink *a, pa_sink *b) { | |
| pa_core *core; | |
| core = a->core; | |
| /* Available sinks always beat unavailable sinks. */ | |
| if (a->active_port && a->active_port->available == PA_AVAILABLE_NO | |
| && (!b->active_port || b->active_port->available != PA_AVAILABLE_NO)) | |
| return -1; | |
| if (b->active_port && b->active_port->available == PA_AVAILABLE_NO | |
| && (!a->active_port || a->active_port->available != PA_AVAILABLE_NO)) | |
| return 1; | |
| /* The policy default sink is preferred over any other sink. */ | |
| if (pa_safe_streq(b->name, core->policy_default_sink)) | |
| return -1; | |
| if (pa_safe_streq(a->name, core->policy_default_sink)) | |
| return 1; | |
| /* The configured default sink is preferred over any other sink | |
| * except the policy default sink. */ | |
| if (pa_safe_streq(b->name, core->configured_default_sink)) | |
| return -1; | |
| if (pa_safe_streq(a->name, core->configured_default_sink)) | |
| return 1; | |
| if (a->priority < b->priority) | |
| return -1; | |
| if (a->priority > b->priority) | |
| return 1; | |
| /* It's hard to find any difference between these sinks, but maybe one of | |
| * them is already the default sink? If so, it's best to keep it as the | |
| * default to avoid changing the routing for no good reason. */ | |
| if (b == core->default_sink) | |
| return -1; | |
| if (a == core->default_sink) | |
| return 1; | |
| return 0; | |
| } | |
| void pa_core_update_default_sink(pa_core *core) { | |
| pa_sink *best = NULL; | |
| pa_sink *sink; | |
| uint32_t idx; | |
| pa_sink *old_default_sink; | |
| pa_assert(core); | |
| PA_IDXSET_FOREACH(sink, core->sinks, idx) { | |
| if (!PA_SINK_IS_LINKED(sink->state)) | |
| continue; | |
| if (!best) { | |
| best = sink; | |
| continue; | |
| } | |
| if (compare_sinks(sink, best) > 0) | |
| best = sink; | |
| } | |
| old_default_sink = core->default_sink; | |
| if (best == old_default_sink) | |
| return; | |
| core->default_sink = best; | |
| pa_log_info("default_sink: %s -> %s", | |
| old_default_sink ? old_default_sink->name : "(unset)", best ? best->name : "(unset)"); | |
| /* If the default sink changed, it may be that the default source has to be | |
| * changed too, because monitor sources are prioritized partly based on the | |
| * priorities of the monitored sinks. */ | |
| pa_core_update_default_source(core); | |
| pa_subscription_post(core, PA_SUBSCRIPTION_EVENT_SERVER | PA_SUBSCRIPTION_EVENT_CHANGE, PA_INVALID_INDEX); | |
| pa_hook_fire(&core->hooks[PA_CORE_HOOK_DEFAULT_SINK_CHANGED], core->default_sink); | |
| /* try to move the streams from old_default_sink to the new default_sink conditionally */ | |
| if (old_default_sink) | |
| pa_sink_move_streams_to_default_sink(core, old_default_sink, true); | |
| } | |
| /* a < b -> return -1 | |
| * a == b -> return 0 | |
| * a > b -> return 1 */ | |
| static int compare_sources(pa_source *a, pa_source *b) { | |
| pa_core *core; | |
| core = a->core; | |
| /* Available sources always beat unavailable sources. */ | |
| if (a->active_port && a->active_port->available == PA_AVAILABLE_NO | |
| && (!b->active_port || b->active_port->available != PA_AVAILABLE_NO)) | |
| return -1; | |
| if (b->active_port && b->active_port->available == PA_AVAILABLE_NO | |
| && (!a->active_port || a->active_port->available != PA_AVAILABLE_NO)) | |
| return 1; | |
| /* The policy default source is preferred over any other source. */ | |
| if (pa_safe_streq(b->name, core->policy_default_source)) | |
| return -1; | |
| if (pa_safe_streq(a->name, core->policy_default_source)) | |
| return 1; | |
| /* The configured default source is preferred over any other source | |
| * except the policy default source. */ | |
| if (pa_safe_streq(b->name, core->configured_default_source)) | |
| return -1; | |
| if (pa_safe_streq(a->name, core->configured_default_source)) | |
| return 1; | |
| /* Monitor sources lose to non-monitor sources. */ | |
| if (a->monitor_of && !b->monitor_of) | |
| return -1; | |
| if (!a->monitor_of && b->monitor_of) | |
| return 1; | |
| if (a->priority < b->priority) | |
| return -1; | |
| if (a->priority > b->priority) | |
| return 1; | |
| /* If the sources are monitors, we can compare the monitored sinks. */ | |
| if (a->monitor_of) | |
| return compare_sinks(a->monitor_of, b->monitor_of); | |
| /* It's hard to find any difference between these sources, but maybe one of | |
| * them is already the default source? If so, it's best to keep it as the | |
| * default to avoid changing the routing for no good reason. */ | |
| if (b == core->default_source) | |
| return -1; | |
| if (a == core->default_source) | |
| return 1; | |
| return 0; | |
| } | |
| void pa_core_update_default_source(pa_core *core) { | |
| pa_source *best = NULL; | |
| pa_source *source; | |
| uint32_t idx; | |
| pa_source *old_default_source; | |
| pa_assert(core); | |
| PA_IDXSET_FOREACH(source, core->sources, idx) { | |
| if (!PA_SOURCE_IS_LINKED(source->state)) | |
| continue; | |
| if (!best) { | |
| best = source; | |
| continue; | |
| } | |
| if (compare_sources(source, best) > 0) | |
| best = source; | |
| } | |
| old_default_source = core->default_source; | |
| if (best == old_default_source) | |
| return; | |
| core->default_source = best; | |
| pa_log_info("default_source: %s -> %s", | |
| old_default_source ? old_default_source->name : "(unset)", best ? best->name : "(unset)"); | |
| pa_subscription_post(core, PA_SUBSCRIPTION_EVENT_SERVER | PA_SUBSCRIPTION_EVENT_CHANGE, PA_INVALID_INDEX); | |
| pa_hook_fire(&core->hooks[PA_CORE_HOOK_DEFAULT_SOURCE_CHANGED], core->default_source); | |
| /* try to move the streams from old_default_source to the new default_source conditionally */ | |
| if (old_default_source) | |
| pa_source_move_streams_to_default_source(core, old_default_source, true); | |
| } | |
| void pa_core_set_exit_idle_time(pa_core *core, int time) { | |
| pa_assert(core); | |
| if (time == core->exit_idle_time) | |
| return; | |
| pa_log_info("exit_idle_time: %i -> %i", core->exit_idle_time, time); | |
| core->exit_idle_time = time; | |
| } | |
| static void exit_callback(pa_mainloop_api *m, pa_time_event *e, const struct timeval *t, void *userdata) { | |
| pa_core *c = userdata; | |
| pa_assert(c->exit_event == e); | |
| pa_log_info("We are idle, quitting..."); | |
| pa_core_exit(c, true, 0); | |
| } | |
| void pa_core_check_idle(pa_core *c) { | |
| pa_assert(c); | |
| if (!c->exit_event && | |
| c->exit_idle_time >= 0 && | |
| pa_idxset_size(c->clients) == 0) { | |
| c->exit_event = pa_core_rttime_new(c, pa_rtclock_now() + c->exit_idle_time * PA_USEC_PER_SEC, exit_callback, c); | |
| } else if (c->exit_event && pa_idxset_size(c->clients) > 0) { | |
| c->mainloop->time_free(c->exit_event); | |
| c->exit_event = NULL; | |
| } | |
| } | |
| int pa_core_exit(pa_core *c, bool force, int retval) { | |
| pa_assert(c); | |
| if (c->disallow_exit && !force) | |
| return -1; | |
| c->mainloop->quit(c->mainloop, retval); | |
| return 0; | |
| } | |
| void pa_core_maybe_vacuum(pa_core *c) { | |
| pa_assert(c); | |
| if (pa_idxset_isempty(c->sink_inputs) && pa_idxset_isempty(c->source_outputs)) { | |
| pa_log_debug("Hmm, no streams around, trying to vacuum."); | |
| } else { | |
| pa_sink *si; | |
| pa_source *so; | |
| uint32_t idx; | |
| idx = 0; | |
| PA_IDXSET_FOREACH(si, c->sinks, idx) | |
| if (si->state != PA_SINK_SUSPENDED) | |
| return; | |
| idx = 0; | |
| PA_IDXSET_FOREACH(so, c->sources, idx) | |
| if (so->state != PA_SOURCE_SUSPENDED) | |
| return; | |
| pa_log_info("All sinks and sources are suspended, vacuuming memory"); | |
| } | |
| pa_mempool_vacuum(c->mempool); | |
| } | |
| pa_time_event* pa_core_rttime_new(pa_core *c, pa_usec_t usec, pa_time_event_cb_t cb, void *userdata) { | |
| struct timeval tv; | |
| pa_assert(c); | |
| pa_assert(c->mainloop); | |
| return c->mainloop->time_new(c->mainloop, pa_timeval_rtstore(&tv, usec, true), cb, userdata); | |
| } | |
| void pa_core_rttime_restart(pa_core *c, pa_time_event *e, pa_usec_t usec) { | |
| struct timeval tv; | |
| pa_assert(c); | |
| pa_assert(c->mainloop); | |
| c->mainloop->time_restart(e, pa_timeval_rtstore(&tv, usec, true)); | |
| } | |
| void pa_core_move_streams_to_newly_available_preferred_sink(pa_core *c, pa_sink *s) { | |
| pa_sink_input *si; | |
| uint32_t idx; | |
| pa_assert(c); | |
| pa_assert(s); | |
| PA_IDXSET_FOREACH(si, c->sink_inputs, idx) { | |
| if (si->sink == s) | |
| continue; | |
| if (!si->sink) | |
| continue; | |
| /* Skip this sink input if it is connecting a filter sink to | |
| * the master */ | |
| if (si->origin_sink) | |
| continue; | |
| /* It might happen that a stream and a sink are set up at the | |
| same time, in which case we want to make sure we don't | |
| interfere with that */ | |
| if (!PA_SINK_INPUT_IS_LINKED(si->state)) | |
| continue; | |
| if (pa_safe_streq(si->preferred_sink, s->name)) | |
| pa_sink_input_move_to(si, s, false); | |
| } | |
| } | |
| void pa_core_move_streams_to_newly_available_preferred_source(pa_core *c, pa_source *s) { | |
| pa_source_output *so; | |
| uint32_t idx; | |
| pa_assert(c); | |
| pa_assert(s); | |
| PA_IDXSET_FOREACH(so, c->source_outputs, idx) { | |
| if (so->source == s) | |
| continue; | |
| if (so->direct_on_input) | |
| continue; | |
| if (!so->source) | |
| continue; | |
| /* Skip this source output if it is connecting a filter source to | |
| * the master */ | |
| if (so->destination_source) | |
| continue; | |
| /* It might happen that a stream and a source are set up at the | |
| same time, in which case we want to make sure we don't | |
| interfere with that */ | |
| if (!PA_SOURCE_OUTPUT_IS_LINKED(so->state)) | |
| continue; | |
| if (pa_safe_streq(so->preferred_source, s->name)) | |
| pa_source_output_move_to(so, s, false); | |
| } | |
| } | |
| /* Helper macro to reduce repetition in pa_suspend_cause_to_string(). | |
| * Parameters: | |
| * char *p: the current position in the write buffer | |
| * bool first: is cause_to_check the first cause to be written? | |
| * pa_suspend_cause_t cause_bitfield: the causes given to pa_suspend_cause_to_string() | |
| * pa_suspend_cause_t cause_to_check: the cause whose presence in cause_bitfield is to be checked | |
| */ | |
| #define CHECK_CAUSE(p, first, cause_bitfield, cause_to_check) \ | |
| if (cause_bitfield & PA_SUSPEND_##cause_to_check) { \ | |
| size_t len = sizeof(#cause_to_check) - 1; \ | |
| if (!first) { \ | |
| *p = '|'; \ | |
| p++; \ | |
| } \ | |
| first = false; \ | |
| memcpy(p, #cause_to_check, len); \ | |
| p += len; \ | |
| } | |
| const char *pa_suspend_cause_to_string(pa_suspend_cause_t cause_bitfield, char buf[PA_SUSPEND_CAUSE_TO_STRING_BUF_SIZE]) { | |
| char *p = buf; | |
| bool first = true; | |
| CHECK_CAUSE(p, first, cause_bitfield, USER); | |
| CHECK_CAUSE(p, first, cause_bitfield, APPLICATION); | |
| CHECK_CAUSE(p, first, cause_bitfield, IDLE); | |
| CHECK_CAUSE(p, first, cause_bitfield, SESSION); | |
| CHECK_CAUSE(p, first, cause_bitfield, PASSTHROUGH); | |
| CHECK_CAUSE(p, first, cause_bitfield, INTERNAL); | |
| CHECK_CAUSE(p, first, cause_bitfield, UNAVAILABLE); | |
| if (p == buf) { | |
| memcpy(p, "(none)", 6); | |
| p += 6; | |
| } | |
| *p = 0; | |
| return buf; | |
| } |