summaryrefslogtreecommitdiffstats
path: root/src/pulsecore/core.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 16:03:18 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 16:03:18 +0000
commit2dd5bc6a074165ddfbd57c4bd52c2d2dac8f47a1 (patch)
tree465b29cb405d3af0b0ad50c78e1dccc636594fec /src/pulsecore/core.c
parentInitial commit. (diff)
downloadpulseaudio-upstream.tar.xz
pulseaudio-upstream.zip
Adding upstream version 14.2.upstream/14.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/pulsecore/core.c')
-rw-r--r--src/pulsecore/core.c632
1 files changed, 632 insertions, 0 deletions
diff --git a/src/pulsecore/core.c b/src/pulsecore/core.c
new file mode 100644
index 0000000..c28c531
--- /dev/null
+++ b/src/pulsecore/core.c
@@ -0,0 +1,632 @@
+/***
+ 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/core-scache.h>
+#include <pulsecore/core-subscribe.h>
+#include <pulsecore/random.h>
+#include <pulsecore/log.h>
+#include <pulsecore/macro.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);
+
+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);
+
+ c->default_source = NULL;
+ c->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_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_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);
+}
+
+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);
+
+ if (pa_safe_streq(sink, old_sink))
+ goto finish;
+
+ pa_xfree(core->configured_default_sink);
+ core->configured_default_sink = pa_xstrdup(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);
+
+ if (pa_safe_streq(source, old_source))
+ goto finish;
+
+ pa_xfree(core->configured_default_source);
+ core->configured_default_source = pa_xstrdup(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);
+}
+
+/* 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 configured default sink is preferred over any other 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 configured default source is preferred over any other 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;
+}