LADI
/
spa
1
Fork 0

module-fallback-sink: add dynamically appearing fallback sink

Add a module for a fallback dummy sink, which appears dynamically when
no other sinks are present.

Enable it for pipewire-pulse, because Pulseaudio will also show
dynamically a dummy sink.
This commit is contained in:
Pauli Virtanen 2022-02-12 18:36:36 +02:00 committed by Wim Taymans
parent a6035dc4c0
commit 02e6f9fbca
5 changed files with 488 additions and 0 deletions

View File

@ -59,6 +59,7 @@ List of known modules:
- \subpage page_module_echo_cancel
- \subpage page_module_example_sink
- \subpage page_module_example_source
- \subpage page_module_fallback_sink
- \subpage page_module_filter_chain
- \subpage page_module_link_factory
- \subpage page_module_loopback

View File

@ -2,6 +2,7 @@ src/daemon/pipewire.c
src/daemon/pipewire.desktop.in
src/modules/module-protocol-pulse/modules/module-tunnel-sink.c
src/modules/module-protocol-pulse/modules/module-tunnel-source.c
src/modules/module-fallback-sink.c
src/modules/module-pulse-tunnel.c
src/modules/module-zeroconf-discover.c
src/tools/pw-cat.c

View File

@ -68,6 +68,9 @@ context.modules = [
}
}
}
# Pulseaudio shows a fallback null sink, if no other sinks are present
{ name = libpipewire-module-fallback-sink, args = { sink.name = "auto_null" } }
]
# Extra modules can be loaded here. Setup in default.pa can be moved here

View File

@ -10,6 +10,7 @@ module_sources = [
'module-echo-cancel.c',
'module-example-sink.c',
'module-example-source.c',
'module-fallback-sink.c',
'module-filter-chain.c',
'module-link-factory.c',
'module-loopback.c',
@ -498,3 +499,12 @@ pipewire_module_x11_bell = shared_library('pipewire-module-x11-bell',
)
endif
summary({'x11-bell': build_module_x11_bell}, bool_yn: true, section: 'Optional Modules')
pipewire_module_fallback_sink = shared_library('pipewire-module-fallback-sink',
[ 'module-fallback-sink.c' ],
include_directories : [configinc],
install : true,
install_dir : modules_install_dir,
install_rpath: modules_install_dir,
dependencies : [mathlib, dl_lib, rt_lib, pipewire_dep],
)

View File

@ -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;
}