summaryrefslogtreecommitdiffstats
path: root/src/modules/module-fallback-sink.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/modules/module-fallback-sink.c')
-rw-r--r--src/modules/module-fallback-sink.c473
1 files changed, 473 insertions, 0 deletions
diff --git a/src/modules/module-fallback-sink.c b/src/modules/module-fallback-sink.c
new file mode 100644
index 0000000..9a277d1
--- /dev/null
+++ b/src/modules/module-fallback-sink.c
@@ -0,0 +1,473 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "config.h"
+
+#include <spa/utils/result.h>
+#include <spa/utils/string.h>
+#include <spa/param/audio/raw.h>
+
+#include <pipewire/impl.h>
+#include <pipewire/i18n.h>
+
+/** \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=<str> ] " \
+ "[ sink.description=<str> ] ")
+
+static const struct spa_dict_item module_props[] = {
+ { PW_KEY_MODULE_AUTHOR, "Pauli Virtanen <pav@iki.fi>" },
+ { 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(void *data, uint32_t id)
+{
+ 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 = sink_proxy_bound,
+ .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,
+ &registry_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;
+}