pulseaudio/src/pulsecore/core.c
2024-06-25 17:03:12 +08:00

787 lines
24 KiB
C

/***
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;
}