pipewire/src/modules/module-fallback-sink.c
Wim Taymans 07e6f44e58 modules: clean up USAGE arguments
use () to mark optional arguments to avoid confusion with arrays.
Add some more optional arguments.
2023-03-22 16:35:55 +01:00

453 lines
9.9 KiB
C

/* PipeWire */
/* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */
/* SPDX-License-Identifier: MIT */
#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_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,
&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;
}