mirror of
https://gitlab.freedesktop.org/pipewire/pipewire
synced 2024-09-30 05:05:27 +00:00
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:
parent
a6035dc4c0
commit
02e6f9fbca
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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],
|
||||
)
|
||||
|
|
473
src/modules/module-fallback-sink.c
Normal file
473
src/modules/module-fallback-sink.c
Normal 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,
|
||||
®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;
|
||||
}
|
Loading…
Reference in a new issue