/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include "config.h" #include #include #include #include #include /** \page page_module_fallback_sink PipeWire Module: Fallback Sink * * Fallback sink, which appear dynamically when no other sinks are * present. This is only useful for Pulseaudio compatibility. */ #define NAME "fallback-sink" #define DEFAULT_SINK_NAME "auto_null" #define DEFAULT_SINK_DESCRIPTION _("Dummy Output") PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); #define PW_LOG_TOPIC_DEFAULT mod_topic #define MODULE_USAGE ("( sink.name= ) " \ "( sink.description= ) ") static const struct spa_dict_item module_props[] = { { PW_KEY_MODULE_AUTHOR, "Pauli Virtanen " }, { PW_KEY_MODULE_DESCRIPTION, "Dynamically appearing fallback sink" }, { PW_KEY_MODULE_USAGE, MODULE_USAGE }, { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, }; struct bitmap { uint8_t *data; size_t size; size_t items; }; struct impl { struct pw_context *context; struct pw_impl_module *module; struct spa_hook module_listener; struct pw_core *core; struct pw_registry *registry; struct pw_proxy *sink; struct spa_hook core_listener; struct spa_hook core_proxy_listener; struct spa_hook registry_listener; struct spa_hook sink_listener; struct pw_properties *properties; struct bitmap sink_ids; struct bitmap fallback_sink_ids; int check_seq; unsigned int do_disconnect:1; unsigned int scheduled:1; }; static int bitmap_add(struct bitmap *map, uint32_t i) { const uint32_t pos = (i >> 3); const uint8_t mask = 1 << (i & 0x7); if (pos >= map->size) { size_t new_size = map->size + pos + 16; void *p; p = realloc(map->data, new_size); if (!p) return -errno; memset((uint8_t*)p + map->size, 0, new_size - map->size); map->data = p; map->size = new_size; } if (map->data[pos] & mask) return 1; map->data[pos] |= mask; ++map->items; return 0; } static bool bitmap_remove(struct bitmap *map, uint32_t i) { const uint32_t pos = (i >> 3); const uint8_t mask = 1 << (i & 0x7); if (pos >= map->size) return false; if (!(map->data[pos] & mask)) return false; map->data[pos] &= ~mask; --map->items; return true; } static void bitmap_free(struct bitmap *map) { free(map->data); spa_zero(*map); } static int add_id(struct bitmap *map, uint32_t id) { int res; if (id == SPA_ID_INVALID) return -EINVAL; if ((res = bitmap_add(map, id)) < 0) pw_log_error("%s", spa_strerror(res)); return res; } static void reschedule_check(struct impl *impl) { if (!impl->scheduled) return; impl->check_seq = pw_core_sync(impl->core, 0, impl->check_seq); } static void schedule_check(struct impl *impl) { if (impl->scheduled) return; impl->scheduled = true; impl->check_seq = pw_core_sync(impl->core, 0, impl->check_seq); } static void sink_proxy_removed(void *data) { struct impl *impl = data; pw_proxy_destroy(impl->sink); } static void sink_proxy_bound_props(void *data, uint32_t id, const struct spa_dict *props) { struct impl *impl = data; add_id(&impl->sink_ids, id); add_id(&impl->fallback_sink_ids, id); reschedule_check(impl); schedule_check(impl); } static void sink_proxy_destroy(void *data) { struct impl *impl = data; pw_log_debug("fallback dummy sink destroyed"); spa_hook_remove(&impl->sink_listener); impl->sink = NULL; } static const struct pw_proxy_events sink_proxy_events = { PW_VERSION_PROXY_EVENTS, .removed = sink_proxy_removed, .bound_props = sink_proxy_bound_props, .destroy = sink_proxy_destroy, }; static int sink_create(struct impl *impl) { if (impl->sink) return 0; pw_log_info("creating fallback dummy sink"); impl->sink = pw_core_create_object(impl->core, "adapter", PW_TYPE_INTERFACE_Node, PW_VERSION_NODE, impl->properties ? &impl->properties->dict : NULL, 0); if (impl->sink == NULL) return -errno; pw_proxy_add_listener(impl->sink, &impl->sink_listener, &sink_proxy_events, impl); return 0; } static void sink_destroy(struct impl *impl) { if (!impl->sink) return; pw_log_info("removing fallback dummy sink"); pw_proxy_destroy(impl->sink); } static void check_sinks(struct impl *impl) { int res; pw_log_debug("seeing %zu sink(s), %zu fallback sink(s)", impl->sink_ids.items, impl->fallback_sink_ids.items); if (impl->sink_ids.items > impl->fallback_sink_ids.items) { sink_destroy(impl); } else { if ((res = sink_create(impl)) < 0) pw_log_error("error creating sink: %s", spa_strerror(res)); } } static void registry_event_global(void *data, uint32_t id, uint32_t permissions, const char *type, uint32_t version, const struct spa_dict *props) { struct impl *impl = data; const char *str; reschedule_check(impl); if (!props) return; if (!spa_streq(type, PW_TYPE_INTERFACE_Node)) return; str = spa_dict_lookup(props, PW_KEY_MEDIA_CLASS); if (!(spa_streq(str, "Audio/Sink") || spa_streq(str, "Audio/Sink/Virtual"))) return; add_id(&impl->sink_ids, id); schedule_check(impl); } static void registry_event_global_remove(void *data, uint32_t id) { struct impl *impl = data; reschedule_check(impl); bitmap_remove(&impl->fallback_sink_ids, id); if (bitmap_remove(&impl->sink_ids, id)) schedule_check(impl); } static const struct pw_registry_events registry_events = { PW_VERSION_REGISTRY_EVENTS, .global = registry_event_global, .global_remove = registry_event_global_remove, }; static void core_done(void *data, uint32_t id, int seq) { struct impl *impl = data; if (seq == impl->check_seq) { impl->scheduled = false; check_sinks(impl); } } static const struct pw_core_events core_events = { PW_VERSION_CORE_EVENTS, .done = core_done, }; static void core_proxy_removed(void *data) { struct impl *impl = data; if (impl->registry) { spa_hook_remove(&impl->registry_listener); pw_proxy_destroy((struct pw_proxy*)impl->registry); impl->registry = NULL; } } static void core_proxy_destroy(void *data) { struct impl *impl = data; spa_hook_remove(&impl->core_listener); spa_hook_remove(&impl->core_proxy_listener); impl->core = NULL; } static const struct pw_proxy_events core_proxy_events = { PW_VERSION_PROXY_EVENTS, .destroy = core_proxy_destroy, .removed = core_proxy_removed, }; static void impl_destroy(struct impl *impl) { sink_destroy(impl); if (impl->registry) { spa_hook_remove(&impl->registry_listener); pw_proxy_destroy((struct pw_proxy*)impl->registry); impl->registry = NULL; } if (impl->core) { spa_hook_remove(&impl->core_listener); spa_hook_remove(&impl->core_proxy_listener); if (impl->do_disconnect) pw_core_disconnect(impl->core); impl->core = NULL; } if (impl->properties) { pw_properties_free(impl->properties); impl->properties = NULL; } bitmap_free(&impl->sink_ids); bitmap_free(&impl->fallback_sink_ids); free(impl); } static void module_destroy(void *data) { struct impl *impl = data; spa_hook_remove(&impl->module_listener); impl_destroy(impl); } static const struct pw_impl_module_events module_events = { PW_VERSION_IMPL_MODULE_EVENTS, .destroy = module_destroy, }; SPA_EXPORT int pipewire__module_init(struct pw_impl_module *module, const char *args) { struct pw_context *context = pw_impl_module_get_context(module); struct pw_properties *props = NULL; struct impl *impl = NULL; const char *str; int res; PW_LOG_TOPIC_INIT(mod_topic); impl = calloc(1, sizeof(struct impl)); if (impl == NULL) goto error_errno; pw_log_debug("module %p: new %s", impl, args); if (args == NULL) args = ""; impl->module = module; impl->context = context; props = pw_properties_new_string(args); if (props == NULL) goto error_errno; impl->properties = pw_properties_new(NULL, NULL); if (impl->properties == NULL) goto error_errno; if ((str = pw_properties_get(props, "sink.name")) == NULL) str = DEFAULT_SINK_NAME; pw_properties_set(impl->properties, PW_KEY_NODE_NAME, str); if ((str = pw_properties_get(props, "sink.description")) == NULL) str = DEFAULT_SINK_DESCRIPTION; pw_properties_set(impl->properties, PW_KEY_NODE_DESCRIPTION, str); pw_properties_setf(impl->properties, SPA_KEY_AUDIO_RATE, "%u", 48000); pw_properties_setf(impl->properties, SPA_KEY_AUDIO_CHANNELS, "%u", 2); pw_properties_set(impl->properties, SPA_KEY_AUDIO_POSITION, "FL,FR"); pw_properties_set(impl->properties, PW_KEY_MEDIA_CLASS, "Audio/Sink"); pw_properties_set(impl->properties, PW_KEY_FACTORY_NAME, "support.null-audio-sink"); pw_properties_set(impl->properties, PW_KEY_NODE_VIRTUAL, "true"); pw_properties_set(impl->properties, "monitor.channel-volumes", "true"); impl->core = pw_context_get_object(impl->context, PW_TYPE_INTERFACE_Core); if (impl->core == NULL) { str = pw_properties_get(props, PW_KEY_REMOTE_NAME); impl->core = pw_context_connect(impl->context, pw_properties_new( PW_KEY_REMOTE_NAME, str, NULL), 0); impl->do_disconnect = true; } if (impl->core == NULL) { res = -errno; pw_log_error("can't connect: %m"); goto error; } pw_proxy_add_listener((struct pw_proxy*)impl->core, &impl->core_proxy_listener, &core_proxy_events, impl); pw_core_add_listener(impl->core, &impl->core_listener, &core_events, impl); impl->registry = pw_core_get_registry(impl->core, PW_VERSION_REGISTRY, 0); if (impl->registry == NULL) goto error_errno; pw_registry_add_listener(impl->registry, &impl->registry_listener, ®istry_events, impl); pw_impl_module_add_listener(module, &impl->module_listener, &module_events, impl); pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props)); schedule_check(impl); pw_properties_free(props); return 0; error_errno: res = -errno; error: if (props) pw_properties_free(props); if (impl) impl_destroy(impl); return res; }