mirror of
https://gitlab.freedesktop.org/pipewire/wireplumber.git
synced 2026-05-05 17:08:01 +02:00
remove "virtual items" scripts, m-si-audio-virtual and related tests
This commit is contained in:
parent
f6b77c7456
commit
5948539551
15 changed files with 7 additions and 1143 deletions
|
|
@ -68,16 +68,6 @@ shared_library(
|
|||
dependencies : [wp_dep, pipewire_dep],
|
||||
)
|
||||
|
||||
shared_library(
|
||||
'wireplumber-module-si-audio-virtual',
|
||||
[
|
||||
'module-si-audio-virtual.c',
|
||||
],
|
||||
install : true,
|
||||
install_dir : wireplumber_module_dir,
|
||||
dependencies : [wp_dep, pipewire_dep],
|
||||
)
|
||||
|
||||
shared_library(
|
||||
'wireplumber-module-si-node',
|
||||
[
|
||||
|
|
|
|||
|
|
@ -1,368 +0,0 @@
|
|||
/* WirePlumber
|
||||
*
|
||||
* Copyright © 2020 Collabora Ltd.
|
||||
* @author Julian Bouzas <julian.bouzas@collabora.com>
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <wp/wp.h>
|
||||
#include <pipewire/pipewire.h>
|
||||
#include <spa/param/format.h>
|
||||
#include <spa/param/audio/raw.h>
|
||||
#include <spa/param/param.h>
|
||||
|
||||
WP_DEFINE_LOCAL_LOG_TOPIC ("m-si-audio-virtual")
|
||||
|
||||
#define SI_FACTORY_NAME "si-audio-virtual"
|
||||
|
||||
struct _WpSiAudioVirtual
|
||||
{
|
||||
WpSessionItem parent;
|
||||
|
||||
/* configuration */
|
||||
gchar name[96];
|
||||
gchar media_class[32];
|
||||
WpDirection direction;
|
||||
gchar role[32];
|
||||
guint priority;
|
||||
gboolean disable_dsp;
|
||||
|
||||
/* activation */
|
||||
WpNode *node;
|
||||
WpSiAdapter *adapter;
|
||||
};
|
||||
|
||||
static void si_audio_virtual_linkable_init (WpSiLinkableInterface * iface);
|
||||
static void si_audio_virtual_adapter_init (WpSiAdapterInterface * iface);
|
||||
|
||||
G_DECLARE_FINAL_TYPE(WpSiAudioVirtual, si_audio_virtual, WP,
|
||||
SI_AUDIO_VIRTUAL, WpSessionItem)
|
||||
G_DEFINE_TYPE_WITH_CODE (WpSiAudioVirtual, si_audio_virtual,
|
||||
WP_TYPE_SESSION_ITEM,
|
||||
G_IMPLEMENT_INTERFACE (WP_TYPE_SI_LINKABLE,
|
||||
si_audio_virtual_linkable_init)
|
||||
G_IMPLEMENT_INTERFACE (WP_TYPE_SI_ADAPTER, si_audio_virtual_adapter_init))
|
||||
|
||||
static void
|
||||
si_audio_virtual_init (WpSiAudioVirtual * self)
|
||||
{
|
||||
}
|
||||
|
||||
static void
|
||||
si_audio_virtual_reset (WpSessionItem * item)
|
||||
{
|
||||
WpSiAudioVirtual *self = WP_SI_AUDIO_VIRTUAL (item);
|
||||
|
||||
/* deactivate first */
|
||||
wp_object_deactivate (WP_OBJECT (self),
|
||||
WP_SESSION_ITEM_FEATURE_ACTIVE | WP_SESSION_ITEM_FEATURE_EXPORTED);
|
||||
|
||||
/* reset */
|
||||
self->name[0] = '\0';
|
||||
self->media_class[0] = '\0';
|
||||
self->direction = WP_DIRECTION_INPUT;
|
||||
self->role[0] = '\0';
|
||||
self->priority = 0;
|
||||
self->disable_dsp = FALSE;
|
||||
|
||||
WP_SESSION_ITEM_CLASS (si_audio_virtual_parent_class)->reset (item);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
si_audio_virtual_configure (WpSessionItem * item, WpProperties *p)
|
||||
{
|
||||
WpSiAudioVirtual *self = WP_SI_AUDIO_VIRTUAL (item);
|
||||
g_autoptr (WpProperties) si_props = wp_properties_ensure_unique_owner (p);
|
||||
const gchar *str;
|
||||
|
||||
/* reset previous config */
|
||||
si_audio_virtual_reset (item);
|
||||
|
||||
str = wp_properties_get (si_props, "name");
|
||||
if (!str)
|
||||
return FALSE;
|
||||
strncpy (self->name, str, sizeof (self->name) - 1);
|
||||
|
||||
str = wp_properties_get (si_props, "media.class");
|
||||
if (!str)
|
||||
return FALSE;
|
||||
strncpy (self->media_class, str, sizeof (self->media_class) - 1);
|
||||
|
||||
if (strstr (self->media_class, "Source") ||
|
||||
strstr (self->media_class, "Output"))
|
||||
self->direction = WP_DIRECTION_OUTPUT;
|
||||
wp_properties_set (si_props, "item.node.direction",
|
||||
self->direction == WP_DIRECTION_OUTPUT ? "output" : "input");
|
||||
|
||||
str = wp_properties_get (si_props, "role");
|
||||
if (str) {
|
||||
strncpy (self->role, str, sizeof (self->role) - 1);
|
||||
} else {
|
||||
strncpy (self->role, "Unknown", sizeof (self->role) - 1);
|
||||
wp_properties_set (si_props, "role", self->role);
|
||||
}
|
||||
|
||||
str = wp_properties_get (si_props, "priority");
|
||||
if (str && sscanf(str, "%u", &self->priority) != 1)
|
||||
return FALSE;
|
||||
if (!str)
|
||||
wp_properties_setf (si_props, "priority", "%u", self->priority);
|
||||
|
||||
str = wp_properties_get (si_props, "item.features.no-dsp");
|
||||
self->disable_dsp = str && pw_properties_parse_bool (str);
|
||||
|
||||
/* We always want virtual sources to autoconnect */
|
||||
wp_properties_set (si_props, PW_KEY_NODE_AUTOCONNECT, "true");
|
||||
wp_properties_set (si_props, "media.type", "Audio");
|
||||
|
||||
wp_properties_set (si_props, "item.factory.name", SI_FACTORY_NAME);
|
||||
wp_session_item_set_properties (WP_SESSION_ITEM (self),
|
||||
g_steal_pointer (&si_props));
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gpointer
|
||||
si_audio_virtual_get_associated_proxy (WpSessionItem * item, GType proxy_type)
|
||||
{
|
||||
WpSiAudioVirtual *self = WP_SI_AUDIO_VIRTUAL (item);
|
||||
|
||||
return wp_session_item_get_associated_proxy (
|
||||
WP_SESSION_ITEM (self->adapter), proxy_type);
|
||||
}
|
||||
|
||||
static void
|
||||
si_audio_virtual_disable_active (WpSessionItem *si)
|
||||
{
|
||||
WpSiAudioVirtual *self = WP_SI_AUDIO_VIRTUAL (si);
|
||||
|
||||
g_clear_object (&self->adapter);
|
||||
g_clear_object (&self->node);
|
||||
wp_object_update_features (WP_OBJECT (self), 0,
|
||||
WP_SESSION_ITEM_FEATURE_ACTIVE);
|
||||
}
|
||||
|
||||
static void
|
||||
si_audio_virtual_disable_exported (WpSessionItem *si)
|
||||
{
|
||||
WpSiAudioVirtual *self = WP_SI_AUDIO_VIRTUAL (si);
|
||||
|
||||
wp_object_update_features (WP_OBJECT (self), 0,
|
||||
WP_SESSION_ITEM_FEATURE_EXPORTED);
|
||||
}
|
||||
|
||||
static void
|
||||
on_adapter_activate_done (WpObject * adapter, GAsyncResult * res,
|
||||
WpTransition * transition)
|
||||
{
|
||||
WpSiAudioVirtual *self = wp_transition_get_source_object (transition);
|
||||
g_autoptr (GError) error = NULL;
|
||||
|
||||
if (!wp_object_activate_finish (adapter, res, &error)) {
|
||||
wp_transition_return_error (transition, g_steal_pointer (&error));
|
||||
return;
|
||||
}
|
||||
|
||||
wp_object_update_features (WP_OBJECT (self),
|
||||
WP_SESSION_ITEM_FEATURE_ACTIVE, 0);
|
||||
}
|
||||
|
||||
static void
|
||||
on_adapter_port_state_changed (WpSiAdapter *item,
|
||||
WpSiAdapterPortsState old_state, WpSiAdapterPortsState new_state,
|
||||
WpSiAudioVirtual *self)
|
||||
{
|
||||
g_signal_emit_by_name (self, "adapter-ports-state-changed", old_state,
|
||||
new_state);
|
||||
}
|
||||
|
||||
static void
|
||||
on_node_activate_done (WpObject * node, GAsyncResult * res,
|
||||
WpTransition * transition)
|
||||
{
|
||||
WpSiAudioVirtual *self = wp_transition_get_source_object (transition);
|
||||
g_autoptr (GError) error = NULL;
|
||||
g_autoptr (WpCore) core = NULL;
|
||||
g_autoptr (WpProperties) props = NULL;
|
||||
|
||||
if (!wp_object_activate_finish (node, res, &error)) {
|
||||
wp_transition_return_error (transition, g_steal_pointer (&error));
|
||||
return;
|
||||
}
|
||||
|
||||
/* create adapter */
|
||||
core = wp_object_get_core (WP_OBJECT (self));
|
||||
self->adapter = WP_SI_ADAPTER (wp_session_item_make (core,
|
||||
"si-audio-adapter"));
|
||||
if (!self->adapter) {
|
||||
wp_transition_return_error (transition,
|
||||
g_error_new (WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT,
|
||||
"si-audio-virtual: could not create si-audio-adapter"));
|
||||
}
|
||||
|
||||
/* Set node.id and node.name properties in this session item */
|
||||
{
|
||||
g_autoptr (WpProperties) si_props = wp_session_item_get_properties (
|
||||
WP_SESSION_ITEM (self));
|
||||
g_autoptr (WpProperties) new_props = wp_properties_new_empty ();
|
||||
guint32 node_id = wp_proxy_get_bound_id (WP_PROXY (node));
|
||||
wp_properties_setf (new_props, "node.id", "%u", node_id);
|
||||
wp_properties_set (new_props, "node.name",
|
||||
wp_pipewire_object_get_property (WP_PIPEWIRE_OBJECT (node),
|
||||
PW_KEY_NODE_NAME));
|
||||
wp_properties_update (si_props, new_props);
|
||||
wp_session_item_set_properties (WP_SESSION_ITEM (self),
|
||||
g_steal_pointer (&si_props));
|
||||
}
|
||||
|
||||
/* Forward adapter-ports-state-changed signal */
|
||||
g_signal_connect_object (self->adapter, "adapter-ports-state-changed",
|
||||
G_CALLBACK (on_adapter_port_state_changed), self, 0);
|
||||
|
||||
/* configure adapter */
|
||||
props = wp_properties_new_empty ();
|
||||
wp_properties_setf (props, "item.node", "%p", node);
|
||||
wp_properties_set (props, "name", self->name);
|
||||
wp_properties_set (props, "media.class", "Audio/Sink");
|
||||
wp_properties_set (props, "item.features.no-format", "true");
|
||||
wp_properties_set (props, "item.features.monitor", "true");
|
||||
if (self->disable_dsp)
|
||||
wp_properties_set (props, "item.features.no-dsp", "true");
|
||||
if (!wp_session_item_configure (WP_SESSION_ITEM (self->adapter),
|
||||
g_steal_pointer (&props))) {
|
||||
wp_transition_return_error (transition,
|
||||
g_error_new (WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT,
|
||||
"si-audio-virtual: could not configure si-audio-adapter"));
|
||||
}
|
||||
|
||||
/* activate adapter */
|
||||
wp_object_activate (WP_OBJECT (self->adapter), WP_SESSION_ITEM_FEATURE_ACTIVE,
|
||||
NULL, (GAsyncReadyCallback) on_adapter_activate_done, transition);
|
||||
}
|
||||
|
||||
static void
|
||||
si_audio_virtual_enable_active (WpSessionItem *si, WpTransition *transition)
|
||||
{
|
||||
WpSiAudioVirtual *self = WP_SI_AUDIO_VIRTUAL (si);
|
||||
g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (self));
|
||||
g_autofree gchar *name = g_strdup_printf ("control.%s", self->name);
|
||||
g_autofree gchar *desc = g_strdup_printf ("%s %s Virtual", self->role,
|
||||
(self->direction == WP_DIRECTION_OUTPUT) ? "Capture" : "Playback");
|
||||
g_autofree gchar *media = g_strdup_printf ("Audio/%s",
|
||||
(self->direction == WP_DIRECTION_OUTPUT) ? "Source" : "Sink");
|
||||
const gchar *passive =
|
||||
(self->direction == WP_DIRECTION_OUTPUT) ? "in" : "out";
|
||||
|
||||
if (!wp_session_item_is_configured (si)) {
|
||||
wp_transition_return_error (transition,
|
||||
g_error_new (WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT,
|
||||
"si-audio-virtual: item is not configured"));
|
||||
return;
|
||||
}
|
||||
|
||||
/* create the node */
|
||||
self->node = wp_node_new_from_factory (core, "adapter",
|
||||
wp_properties_new (
|
||||
PW_KEY_NODE_NAME, name,
|
||||
PW_KEY_MEDIA_CLASS, media,
|
||||
PW_KEY_FACTORY_NAME, "support.null-audio-sink",
|
||||
PW_KEY_NODE_DESCRIPTION, desc,
|
||||
PW_KEY_NODE_AUTOCONNECT, "true",
|
||||
PW_KEY_NODE_PASSIVE, passive,
|
||||
"monitor.channel-volumes", "true",
|
||||
"wireplumber.is-virtual", "true",
|
||||
NULL));
|
||||
if (!self->node) {
|
||||
wp_transition_return_error (transition,
|
||||
g_error_new (WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT,
|
||||
"si-audio-virtual: could not create null-audio-sink node"));
|
||||
return;
|
||||
}
|
||||
|
||||
/* activate node */
|
||||
wp_object_activate (WP_OBJECT (self->node),
|
||||
WP_PIPEWIRE_OBJECT_FEATURES_MINIMAL | WP_NODE_FEATURE_PORTS, NULL,
|
||||
(GAsyncReadyCallback) on_node_activate_done, transition);
|
||||
}
|
||||
|
||||
static void
|
||||
si_audio_virtual_enable_exported (WpSessionItem *si, WpTransition *transition)
|
||||
{
|
||||
WpSiAudioVirtual *self = WP_SI_AUDIO_VIRTUAL (si);
|
||||
|
||||
wp_object_update_features (WP_OBJECT (self),
|
||||
WP_SESSION_ITEM_FEATURE_EXPORTED, 0);
|
||||
}
|
||||
|
||||
static void
|
||||
si_audio_virtual_class_init (WpSiAudioVirtualClass * klass)
|
||||
{
|
||||
WpSessionItemClass *si_class = (WpSessionItemClass *) klass;
|
||||
|
||||
si_class->reset = si_audio_virtual_reset;
|
||||
si_class->configure = si_audio_virtual_configure;
|
||||
si_class->get_associated_proxy = si_audio_virtual_get_associated_proxy;
|
||||
si_class->disable_active = si_audio_virtual_disable_active;
|
||||
si_class->disable_exported = si_audio_virtual_disable_exported;
|
||||
si_class->enable_active = si_audio_virtual_enable_active;
|
||||
si_class->enable_exported = si_audio_virtual_enable_exported;
|
||||
}
|
||||
|
||||
static GVariant *
|
||||
si_audio_virtual_get_ports (WpSiLinkable * item, const gchar * context)
|
||||
{
|
||||
WpSiAudioVirtual *self = WP_SI_AUDIO_VIRTUAL (item);
|
||||
return wp_si_linkable_get_ports (WP_SI_LINKABLE (self->adapter), context);
|
||||
}
|
||||
|
||||
static void
|
||||
si_audio_virtual_linkable_init (WpSiLinkableInterface * iface)
|
||||
{
|
||||
iface->get_ports = si_audio_virtual_get_ports;
|
||||
}
|
||||
|
||||
static WpSiAdapterPortsState
|
||||
si_audio_virtual_get_ports_state (WpSiAdapter * item)
|
||||
{
|
||||
WpSiAudioVirtual *self = WP_SI_AUDIO_VIRTUAL (item);
|
||||
return wp_si_adapter_get_ports_state (self->adapter);
|
||||
}
|
||||
|
||||
static WpSpaPod *
|
||||
si_audio_virtual_get_ports_format (WpSiAdapter * item, const gchar **mode)
|
||||
{
|
||||
WpSiAudioVirtual *self = WP_SI_AUDIO_VIRTUAL (item);
|
||||
return wp_si_adapter_get_ports_format (self->adapter, mode);
|
||||
}
|
||||
|
||||
static void
|
||||
si_audio_virtual_set_ports_format (WpSiAdapter * item, WpSpaPod *f,
|
||||
const gchar *mode, GAsyncReadyCallback callback, gpointer data)
|
||||
{
|
||||
WpSiAudioVirtual *self = WP_SI_AUDIO_VIRTUAL (item);
|
||||
wp_si_adapter_set_ports_format (self->adapter, f, mode, callback, data);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
si_audio_virtual_set_ports_format_finish (WpSiAdapter * item,
|
||||
GAsyncResult * res, GError ** error)
|
||||
{
|
||||
WpSiAudioVirtual *self = WP_SI_AUDIO_VIRTUAL (item);
|
||||
return wp_si_adapter_set_ports_format_finish (self->adapter, res, error);
|
||||
}
|
||||
|
||||
static void
|
||||
si_audio_virtual_adapter_init (WpSiAdapterInterface * iface)
|
||||
{
|
||||
iface->get_ports_state = si_audio_virtual_get_ports_state;
|
||||
iface->get_ports_format = si_audio_virtual_get_ports_format;
|
||||
iface->set_ports_format = si_audio_virtual_set_ports_format;
|
||||
iface->set_ports_format_finish = si_audio_virtual_set_ports_format_finish;
|
||||
}
|
||||
|
||||
WP_PLUGIN_EXPORT GObject *
|
||||
wireplumber__module_init (WpCore * core, WpSpaJson * args, GError ** error)
|
||||
{
|
||||
return G_OBJECT (wp_si_factory_new_simple (SI_FACTORY_NAME,
|
||||
si_audio_virtual_get_type ()));
|
||||
}
|
||||
|
|
@ -544,13 +544,6 @@ configure_and_link_adapters (WpSiStandardLink *self, WpTransition *transition)
|
|||
str = wp_session_item_get_property (WP_SESSION_ITEM (in->si), "item.node.type");
|
||||
in->is_device = !g_strcmp0 (str, "device");
|
||||
|
||||
str = wp_session_item_get_property (WP_SESSION_ITEM (out->si), "item.factory.name");
|
||||
out->is_device = (str && !g_strcmp0 (str, "si-audio-virtual") && !in->is_device)
|
||||
|| out->is_device;
|
||||
str = wp_session_item_get_property (WP_SESSION_ITEM (in->si), "item.factory.name");
|
||||
in->is_device = (str && !g_strcmp0 (str, "si-audio-virtual") && !out->is_device)
|
||||
|| in->is_device;
|
||||
|
||||
str = wp_session_item_get_property (WP_SESSION_ITEM (out->si), "stream.dont-remix");
|
||||
out->dont_remix = str && pw_properties_parse_bool (str);
|
||||
str = wp_session_item_get_property (WP_SESSION_ITEM (in->si), "stream.dont-remix");
|
||||
|
|
|
|||
|
|
@ -232,10 +232,6 @@ wireplumber.components = [
|
|||
name = libwireplumber-module-si-standard-link, type = module
|
||||
provides = si.standard-link
|
||||
}
|
||||
{
|
||||
name = libwireplumber-module-si-audio-virtual, type = module
|
||||
provides = si.audio-virtual
|
||||
}
|
||||
|
||||
## API to access default nodes from scripts
|
||||
{
|
||||
|
|
@ -546,11 +542,6 @@ wireplumber.components = [
|
|||
name = node/filter-forward-format.lua, type = script/lua
|
||||
provides = hooks.filter.forward-format
|
||||
}
|
||||
{
|
||||
name = node/create-virtual-item.lua, type = script/lua
|
||||
provides = script.create-role-items
|
||||
requires = [ si.audio-virtual ]
|
||||
}
|
||||
{
|
||||
type = virtual, provides = policy.node
|
||||
requires = [ hooks.node.create-session-item ]
|
||||
|
|
@ -642,7 +633,6 @@ wireplumber.components = [
|
|||
{
|
||||
type = virtual, provides = policy.role-priority-system
|
||||
requires = [ policy.standard,
|
||||
script.create-role-items,
|
||||
policy.linking.role-priority-system ]
|
||||
}
|
||||
## Load targets
|
||||
|
|
|
|||
|
|
@ -1,95 +0,0 @@
|
|||
## The WirePlumber virtual item configuration
|
||||
|
||||
virtual-items = {
|
||||
## The list of virtual items to create
|
||||
|
||||
# virtual-item.capture = {
|
||||
# media.class = "Audio/Source"
|
||||
# role = "Capture"
|
||||
# }
|
||||
# virtual-item.multimedia = {
|
||||
# media.class = "Audio/Sink"
|
||||
# role = "Multimedia"
|
||||
# }
|
||||
# virtual-item.speech_low = {
|
||||
# media.class = "Audio/Sink"
|
||||
# role = "Speech-Low"
|
||||
# }
|
||||
# virtual-item.custom_low = {
|
||||
# media.class = "Audio/Sink"
|
||||
# role = "Custom-Low"
|
||||
# }
|
||||
# virtual-item.navigation = {
|
||||
# media.class = "Audio/Sink"
|
||||
# role = "Navigation"
|
||||
# }
|
||||
# virtual-item.speech_high = {
|
||||
# media.class = "Audio/Sink"
|
||||
# role = "Speech-High"
|
||||
# }
|
||||
# virtual-item.custom_high = {
|
||||
# media.class = "Audio/Sink"
|
||||
# role = "Custom-High"
|
||||
# }
|
||||
# virtual-item.communication = {
|
||||
# media.class = "Audio/Sink"
|
||||
# role = "Communication"
|
||||
# }
|
||||
# virtual-item.emergency = {
|
||||
# media.class = "Audio/Sink"
|
||||
# role = "Emergency"
|
||||
# }
|
||||
}
|
||||
|
||||
virtual-item-roles = {
|
||||
## The list of virtual item roles to use
|
||||
|
||||
# Capture = {
|
||||
# alias = [ "Multimedia", "Music", "Voice", "Capture" ]
|
||||
# priority = 25
|
||||
# action.default = "cork"
|
||||
# action.capture = "mix"
|
||||
# media.class = "Audio/Source"
|
||||
# }
|
||||
# Multimedia = {
|
||||
# alias = [ "Movie" "Music" "Game" ]
|
||||
# priority = 25
|
||||
# action.default = "cork"
|
||||
# }
|
||||
# Speech-Low = {
|
||||
# priority = 30
|
||||
# action.default = "cork"
|
||||
# action.Speech-Low = "mix"
|
||||
# }
|
||||
# Custom-Low = {
|
||||
# priority = 35
|
||||
# action.default = "cork"
|
||||
# action.Custom-Low = "mix"
|
||||
# }
|
||||
# Navigation = {
|
||||
# priority = 50
|
||||
# action.default = "duck"
|
||||
# action.Navigation = "mix"
|
||||
# }
|
||||
# Speech-High = {
|
||||
# priority = 60
|
||||
# action.default = "cork"
|
||||
# action.Speech-High = "mix"
|
||||
# }
|
||||
# Custom-High = {
|
||||
# priority = 65
|
||||
# action.default = "cork"
|
||||
# action.Custom-High = "mix"
|
||||
# }
|
||||
# Communication = {
|
||||
# priority = 75
|
||||
# action.default = "cork"
|
||||
# action.Communication = "mix"
|
||||
# }
|
||||
# Emergency = {
|
||||
# alias = [ "Alert" ]
|
||||
# priority = 99
|
||||
# action.default = "cork"
|
||||
# action.Emergency = "mix"
|
||||
# }
|
||||
}
|
||||
|
|
@ -37,11 +37,6 @@ end
|
|||
function cutils.getTargetDirection (properties)
|
||||
local target_direction = nil
|
||||
|
||||
-- retrun same direction for si-audio-virtual session items
|
||||
if properties ["item.factory.name"] == "si-audio-virtual" then
|
||||
return properties ["item.node.direction"]
|
||||
end
|
||||
|
||||
if properties ["item.node.direction"] == "output" or
|
||||
(properties ["item.node.direction"] == "input" and
|
||||
cutils.parseBool (properties ["stream.capture.sink"])) then
|
||||
|
|
|
|||
|
|
@ -249,19 +249,11 @@ function lutils.canLink (properties, si_target)
|
|||
properties ["item.factory.name"] == "si-audio-adapter"
|
||||
end
|
||||
|
||||
if properties ["item.factory.name"] == "si-audio-virtual" then
|
||||
-- virtual nodes must have the same direction, unless the target is monitor
|
||||
if properties ["item.node.direction"] ~= target_props ["item.node.direction"]
|
||||
and not isMonitor (target_props) then
|
||||
return false
|
||||
end
|
||||
else
|
||||
-- nodes must have opposite direction, or otherwise they must be both input
|
||||
-- and the target must have a monitor (so the target will be used as a source)
|
||||
if properties ["item.node.direction"] == target_props ["item.node.direction"]
|
||||
and not isMonitor (target_props) then
|
||||
return false
|
||||
end
|
||||
-- nodes must have opposite direction, or otherwise they must be both input
|
||||
-- and the target must have a monitor (so the target will be used as a source)
|
||||
if properties ["item.node.direction"] == target_props ["item.node.direction"]
|
||||
and not isMonitor (target_props) then
|
||||
return false
|
||||
end
|
||||
|
||||
-- check link group
|
||||
|
|
|
|||
|
|
@ -1,121 +0,0 @@
|
|||
-- WirePlumber
|
||||
--
|
||||
-- Copyright © 2022 Collabora Ltd.
|
||||
--
|
||||
-- SPDX-License-Identifier: MIT
|
||||
--
|
||||
-- Select the virtual target based on roles
|
||||
|
||||
lutils = require ("linking-utils")
|
||||
log = Log.open_topic ("s-linking")
|
||||
|
||||
config = {}
|
||||
config.roles = Conf.get_section_as_object ("virtual-item-roles")
|
||||
|
||||
function findRole(role, tmc)
|
||||
if role and not config.roles[role] then
|
||||
-- find the role with matching alias
|
||||
for r, p in pairs(config.roles) do
|
||||
-- default media class can be overridden in the role config data
|
||||
mc = p["media.class"] or "Audio/Sink"
|
||||
if (type(p.alias) == "table" and tmc == mc) then
|
||||
for i = 1, #(p.alias), 1 do
|
||||
if role == p.alias[i] then
|
||||
return r
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- otherwise get the lowest priority role
|
||||
local lowest_priority_p = nil
|
||||
local lowest_priority_r = nil
|
||||
for r, p in pairs(config.roles) do
|
||||
mc = p["media.class"] or "Audio/Sink"
|
||||
if tmc == mc and (lowest_priority_p == nil or
|
||||
p.priority < lowest_priority_p.priority) then
|
||||
lowest_priority_p = p
|
||||
lowest_priority_r = r
|
||||
end
|
||||
end
|
||||
return lowest_priority_r
|
||||
end
|
||||
return role
|
||||
end
|
||||
|
||||
SimpleEventHook {
|
||||
name = "linking/find-virtual-target",
|
||||
interests = {
|
||||
EventInterest {
|
||||
Constraint { "event.type", "=", "select-target" },
|
||||
},
|
||||
},
|
||||
execute = function (event)
|
||||
local source, om, si, si_props, si_flags, target =
|
||||
lutils:unwrap_select_target_event (event)
|
||||
local target_class_assoc = {
|
||||
["Stream/Input/Audio"] = "Audio/Source",
|
||||
["Stream/Output/Audio"] = "Audio/Sink",
|
||||
["Stream/Input/Video"] = "Video/Source",
|
||||
}
|
||||
local node = si:get_associated_proxy ("node")
|
||||
local highest_priority = -1
|
||||
local target = nil
|
||||
local role = node.properties["media.role"] or "Default"
|
||||
|
||||
-- bypass the hook if the target is already picked up
|
||||
if target then
|
||||
return
|
||||
end
|
||||
|
||||
-- dont use virtual target for any si-audio-virtual
|
||||
if si_props ["item.factory.name"] == "si-audio-virtual" then
|
||||
return
|
||||
end
|
||||
|
||||
log:info (si, string.format ("handling item %d: %s (%s)", si.id,
|
||||
tostring (si_props ["node.name"]), tostring (si_props ["node.id"])))
|
||||
|
||||
-- get target media class
|
||||
local target_media_class = target_class_assoc[si_props ["media.class"]]
|
||||
if not target_media_class then
|
||||
log:info (si, "target media class not found")
|
||||
return
|
||||
end
|
||||
|
||||
-- find highest priority virtual by role
|
||||
local media_role = findRole (role, target_media_class)
|
||||
if media_role == nil then
|
||||
log:info (si, "media role not found")
|
||||
return
|
||||
end
|
||||
|
||||
for si_virtual in om:iterate {
|
||||
Constraint { "role", "=", media_role, type = "pw-global" },
|
||||
Constraint { "media.class", "=", target_media_class, type = "pw-global" },
|
||||
Constraint { "item.factory.name", "=", "si-audio-virtual", type = "pw-global" },
|
||||
} do
|
||||
local priority = tonumber(si_virtual.properties["priority"])
|
||||
if priority > highest_priority then
|
||||
highest_priority = priority
|
||||
target = si_virtual
|
||||
end
|
||||
end
|
||||
|
||||
local can_passthrough, passthrough_compatible
|
||||
if target then
|
||||
passthrough_compatible, can_passthrough =
|
||||
lutils.checkPassthroughCompatibility (si, target)
|
||||
|
||||
if not passthrough_compatible then
|
||||
target = nil
|
||||
end
|
||||
end
|
||||
|
||||
-- set target
|
||||
if target ~= nil then
|
||||
si_flags.can_passthrough = can_passthrough
|
||||
event:set_data ("target", target)
|
||||
end
|
||||
end
|
||||
}:register ()
|
||||
|
|
@ -124,8 +124,8 @@ AsyncEventHook {
|
|||
log:debug (si_link, "registered link between "
|
||||
.. tostring (si) .. " and " .. tostring (target))
|
||||
|
||||
-- only activate non virtual links because virtual links activation is
|
||||
-- handled by rescan-virtual-links.lua
|
||||
-- only activate non media role links because their activation is
|
||||
-- handled by rescan-media-role-links.lua
|
||||
if not is_media_role_link then
|
||||
si_link:activate (Feature.SessionItem.ACTIVE, function (l, e)
|
||||
if e then
|
||||
|
|
|
|||
|
|
@ -1,251 +0,0 @@
|
|||
-- WirePlumber
|
||||
--
|
||||
-- Copyright © 2023 Collabora Ltd.
|
||||
-- @author Julian Bouzas <george.kiagiadakis@collabora.com>
|
||||
--
|
||||
-- SPDX-License-Identifier: MIT
|
||||
|
||||
lutils = require ("linking-utils")
|
||||
log = Log.open_topic ("s-linking")
|
||||
|
||||
defaults = {}
|
||||
defaults.duck_level = 0.3
|
||||
|
||||
config = {}
|
||||
config.duck_level = defaults.duck_level -- FIXME
|
||||
config.roles = Conf.get_section_as_object ("virtual-item-roles")
|
||||
|
||||
-- enable ducking if mixer-api is loaded
|
||||
mixer_api = Plugin.find("mixer-api")
|
||||
|
||||
function findRole (role)
|
||||
if role and not config.roles[role] then
|
||||
for r, p in pairs(config.roles) do
|
||||
if type(p.alias) == "table" then
|
||||
for i = 1, #(p.alias), 1 do
|
||||
if role == p.alias[i] then
|
||||
return r
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return role
|
||||
end
|
||||
|
||||
function getRolePriority (role)
|
||||
local r = role and config.roles[role] or nil
|
||||
return r and r.priority or 0
|
||||
end
|
||||
|
||||
function getAction (dominant_role, other_role)
|
||||
-- default to "mix" if the role is not configured
|
||||
if not dominant_role or not config.roles[dominant_role] then
|
||||
return "mix"
|
||||
end
|
||||
|
||||
local role_config = config.roles[dominant_role]
|
||||
return role_config["action." .. other_role]
|
||||
or role_config["action.default"]
|
||||
or "mix"
|
||||
end
|
||||
|
||||
function restoreVolume (om, role, media_class)
|
||||
if not mixer_api then return end
|
||||
|
||||
local si_v = om:lookup {
|
||||
type = "SiLinkable",
|
||||
Constraint { "item.factory.name", "=", "si-audio-virtual", type = "pw-global" },
|
||||
Constraint { "media.role", "=", role, type = "pw" },
|
||||
Constraint { "media.class", "=", media_class, type = "pw" },
|
||||
}
|
||||
|
||||
if si_v then
|
||||
local n = si_v:get_associated_proxy ("node")
|
||||
if n then
|
||||
log:debug(si_v, "restore role " .. role)
|
||||
mixer_api:call("set-volume", n["bound-id"], {
|
||||
monitorVolume = 1.0,
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function duckVolume (om, role, media_class)
|
||||
if not mixer_api then return end
|
||||
|
||||
local si_v = om:lookup {
|
||||
type = "SiLinkable",
|
||||
Constraint { "item.factory.name", "=", "si-audio-virtual", type = "pw-global" },
|
||||
Constraint { "media.role", "=", role, type = "pw" },
|
||||
Constraint { "media.class", "=", media_class, type = "pw" },
|
||||
}
|
||||
|
||||
if si_v then
|
||||
local n = si_v:get_associated_proxy ("node")
|
||||
if n then
|
||||
log:debug(si_v, "duck role " .. role)
|
||||
mixer_api:call("set-volume", n["bound-id"], {
|
||||
monitorVolume = config.duck_level,
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function getSuspendPlaybackFromMetadata (om)
|
||||
local suspend = false
|
||||
local metadata = om:lookup {
|
||||
type = "metadata",
|
||||
Constraint { "metadata.name", "=", "default" },
|
||||
}
|
||||
if metadata then
|
||||
local value = metadata:find(0, "suspend.playback")
|
||||
if value then
|
||||
suspend = value == "1" and true or false
|
||||
end
|
||||
end
|
||||
return suspend
|
||||
end
|
||||
|
||||
AsyncEventHook {
|
||||
name = "linking/rescan-virtual-links",
|
||||
interests = {
|
||||
EventInterest {
|
||||
-- on virtual client link added and removed
|
||||
Constraint { "event.type", "c", "session-item-added", "session-item-removed" },
|
||||
Constraint { "event.session-item.interface", "=", "link" },
|
||||
Constraint { "is.virtual.client.link", "=", true },
|
||||
},
|
||||
EventInterest {
|
||||
-- on default metadata suspend.playback changed
|
||||
Constraint { "event.type", "=", "metadata-changed" },
|
||||
Constraint { "metadata.name", "=", "default" },
|
||||
Constraint { "event.subject.key", "=", "suspend.playback" },
|
||||
}
|
||||
},
|
||||
steps = {
|
||||
start = {
|
||||
next = "none",
|
||||
execute = function (event, transition)
|
||||
local source = event:get_source ()
|
||||
local om = source:call ("get-object-manager", "session-item")
|
||||
local metadata_om = source:call ("get-object-manager", "metadata")
|
||||
local suspend = getSuspendPlaybackFromMetadata (metadata_om)
|
||||
local pending_activations = 0
|
||||
local links = {
|
||||
["Audio/Source"] = {},
|
||||
["Audio/Sink"] = {},
|
||||
["Video/Source"] = {},
|
||||
}
|
||||
|
||||
-- gather info about links
|
||||
log:info ("Rescanning virtual si-standard-link links...")
|
||||
for silink in om:iterate {
|
||||
type = "SiLink",
|
||||
Constraint { "is.virtual.client.link", "=", true },
|
||||
} do
|
||||
|
||||
-- deactivate all links if suspend playback metadata is present
|
||||
if suspend then
|
||||
silink:deactivate (Feature.SessionItem.ACTIVE)
|
||||
end
|
||||
|
||||
local props = silink.properties
|
||||
local role = props["media.role"]
|
||||
local target_class = props["target.media.class"]
|
||||
local plugged = props["item.plugged.usec"]
|
||||
local active = ((silink:get_active_features() & Feature.SessionItem.ACTIVE) ~= 0)
|
||||
if links[target_class] then
|
||||
table.insert(links[target_class], {
|
||||
silink = silink,
|
||||
role = findRole (role),
|
||||
active = active,
|
||||
priority = getRolePriority (role),
|
||||
plugged = plugged and tonumber(plugged) or 0
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
local function onVirtualLinkActivated (l, e)
|
||||
local si_id = tonumber (l.properties ["main.item.id"])
|
||||
local target_id = tonumber (l.properties ["target.item.id"])
|
||||
local si_flags = lutils:get_flags (si_id)
|
||||
|
||||
if e then
|
||||
log:warning (l, "failed to activate virtual si-standard-link: " .. e)
|
||||
if si_flags ~= nil then
|
||||
si_flags.peer_id = nil
|
||||
end
|
||||
l:remove ()
|
||||
else
|
||||
log:info (l, "virtual si-standard-link activated successfully")
|
||||
si_flags.failed_peer_id = nil
|
||||
if si_flags.peer_id == nil then
|
||||
si_flags.peer_id = target_id
|
||||
end
|
||||
si_flags.failed_count = 0
|
||||
end
|
||||
|
||||
-- advance only when all pending activations are completed
|
||||
pending_activations = pending_activations - 1
|
||||
if pending_activations <= 0 then
|
||||
log:info ("All virtual si-standard-links activated")
|
||||
transition:advance ()
|
||||
end
|
||||
end
|
||||
|
||||
local function compareLinks(l1, l2)
|
||||
return (l1.priority > l2.priority) or
|
||||
((l1.priority == l2.priority) and (l1.plugged > l2.plugged))
|
||||
end
|
||||
|
||||
for media_class, v in pairs(links) do
|
||||
-- sort on priority and stream creation time
|
||||
table.sort(v, compareLinks)
|
||||
|
||||
-- apply actions
|
||||
local first_link = v[1]
|
||||
if first_link then
|
||||
for i = 2, #v, 1 do
|
||||
local action = getAction(first_link.role, v[i].role)
|
||||
if action == "cork" then
|
||||
if v[i].active then
|
||||
v[i].silink:deactivate(Feature.SessionItem.ACTIVE)
|
||||
end
|
||||
elseif action == "mix" then
|
||||
if not v[i].active and not suspend then
|
||||
pending_activations = pending_activations + 1
|
||||
v[i].silink:activate (Feature.SessionItem.ACTIVE,
|
||||
onVirtualLinkActivated)
|
||||
end
|
||||
restoreVolume(om, v[i].role, media_class)
|
||||
elseif action == "duck" then
|
||||
if not v[i].active and not suspend then
|
||||
pending_activations = pending_activations + 1
|
||||
v[i].silink:activate (Feature.SessionItem.ACTIVE,
|
||||
onVirtualLinkActivated)
|
||||
end
|
||||
duckVolume (om, v[i].role, media_class)
|
||||
else
|
||||
log:warning("Unknown action: " .. action)
|
||||
end
|
||||
end
|
||||
|
||||
if not first_link.active and not suspend then
|
||||
pending_activations = pending_activations + 1
|
||||
first_link.silink:activate(Feature.SessionItem.ACTIVE,
|
||||
onVirtualLinkActivated)
|
||||
end
|
||||
restoreVolume (om, first_link.role, media_class)
|
||||
end
|
||||
end
|
||||
|
||||
-- just advance transition if no pending activations are needed
|
||||
if pending_activations <= 0 then
|
||||
log:info ("All virtual si-standard-links rescanned")
|
||||
transition:advance ()
|
||||
end
|
||||
end,
|
||||
},
|
||||
},
|
||||
}:register ()
|
||||
|
|
@ -43,11 +43,6 @@ end
|
|||
function checkLinkable (si, om, handle_nonstreams)
|
||||
local si_props = si.properties
|
||||
|
||||
-- Always handle si-audio-virtual session items
|
||||
if si_props ["item.factory.name"] == "si-audio-virtual" then
|
||||
return true, si_props
|
||||
end
|
||||
|
||||
-- For the rest of them, only handle stream session items
|
||||
if not si_props or (si_props ["item.node.type"] ~= "stream"
|
||||
and not handle_nonstreams) then
|
||||
|
|
|
|||
|
|
@ -1,42 +0,0 @@
|
|||
-- WirePlumber
|
||||
--
|
||||
-- Copyright © 2021 Collabora Ltd.
|
||||
-- @author Julian Bouzas <julian.bouzas@collabora.com>
|
||||
--
|
||||
-- SPDX-License-Identifier: MIT
|
||||
|
||||
-- Receive script arguments from config.lua
|
||||
|
||||
-- creates the virtual items defined in the JSON(virtual.conf)
|
||||
|
||||
log = Log.open_topic ("s-node")
|
||||
|
||||
config = {}
|
||||
config.virtual_items = Conf.get_section_as_object ("virtual-items")
|
||||
|
||||
function createVirtualItem (factory_name, properties)
|
||||
-- create virtual item
|
||||
local si_v = SessionItem ( factory_name )
|
||||
if not si_v then
|
||||
log:warning (si_v, "could not create virtual item of type " .. factory_name)
|
||||
return
|
||||
end
|
||||
|
||||
-- configure virtual item
|
||||
if not si_v:configure(properties) then
|
||||
log:warning(si_v, "failed to configure virtual item " .. properties.name)
|
||||
return
|
||||
end
|
||||
|
||||
-- activate and register virtual item
|
||||
si_v:activate (Features.ALL, function (item)
|
||||
item:register ()
|
||||
log:info(item, "registered virtual item " .. properties.name)
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
for name, properties in pairs(config.virtual_items) do
|
||||
properties["name"] = name
|
||||
createVirtualItem ("si-audio-virtual", properties)
|
||||
end
|
||||
|
|
@ -40,13 +40,6 @@ test(
|
|||
env: common_env,
|
||||
)
|
||||
|
||||
test(
|
||||
'test-si-audio-virtual',
|
||||
executable('test-si-audio-virtual', 'si-audio-virtual.c',
|
||||
dependencies: common_deps),
|
||||
env: common_env,
|
||||
)
|
||||
|
||||
test(
|
||||
'test-si-standard-link',
|
||||
executable('test-si-standard-link', 'si-standard-link.c',
|
||||
|
|
|
|||
|
|
@ -1,206 +0,0 @@
|
|||
/* WirePlumber
|
||||
*
|
||||
* Copyright © 2020 Collabora Ltd.
|
||||
* @author Julian Bouzas <julian.bouzas@collabora.com>
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "../common/base-test-fixture.h"
|
||||
|
||||
typedef struct {
|
||||
WpBaseTestFixture base;
|
||||
} TestFixture;
|
||||
|
||||
static void
|
||||
on_plugin_loaded (WpCore * core, GAsyncResult * res, TestFixture *f)
|
||||
{
|
||||
gboolean loaded;
|
||||
GError *error = NULL;
|
||||
|
||||
loaded = wp_core_load_component_finish (core, res, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert_true (loaded);
|
||||
|
||||
g_main_loop_quit (f->base.loop);
|
||||
}
|
||||
|
||||
static void
|
||||
test_si_audio_virtual_setup (TestFixture * f, gconstpointer user_data)
|
||||
{
|
||||
wp_base_test_fixture_setup (&f->base, 0);
|
||||
|
||||
/* load modules */
|
||||
{
|
||||
g_autoptr (WpTestServerLocker) lock =
|
||||
wp_test_server_locker_new (&f->base.server);
|
||||
|
||||
g_assert_nonnull (pw_context_load_module (f->base.server.context,
|
||||
"libpipewire-module-spa-node-factory", NULL, NULL));
|
||||
g_assert_nonnull (pw_context_load_module (f->base.server.context,
|
||||
"libpipewire-module-adapter", NULL, NULL));
|
||||
}
|
||||
{
|
||||
wp_core_load_component (f->base.core,
|
||||
"libwireplumber-module-si-audio-adapter", "module", NULL, NULL, NULL,
|
||||
(GAsyncReadyCallback) on_plugin_loaded, f);
|
||||
g_main_loop_run (f->base.loop);
|
||||
}
|
||||
{
|
||||
wp_core_load_component (f->base.core,
|
||||
"libwireplumber-module-si-audio-virtual", "module", NULL, NULL, NULL,
|
||||
(GAsyncReadyCallback) on_plugin_loaded, f);
|
||||
g_main_loop_run (f->base.loop);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
test_si_audio_virtual_teardown (TestFixture * f, gconstpointer user_data)
|
||||
{
|
||||
wp_base_test_fixture_teardown (&f->base);
|
||||
}
|
||||
|
||||
static void
|
||||
test_si_audio_virtual_configure_activate (TestFixture * f,
|
||||
gconstpointer user_data)
|
||||
{
|
||||
g_autoptr (WpSessionItem) item = NULL;
|
||||
|
||||
/* skip the test if null-audio-sink factory is not installed */
|
||||
if (!test_is_spa_lib_installed (&f->base, "support.null-audio-sink")) {
|
||||
g_test_skip ("The pipewire null-audio-sink factory was not found");
|
||||
return;
|
||||
}
|
||||
|
||||
/* create item */
|
||||
|
||||
item = wp_session_item_make (f->base.core, "si-audio-virtual");
|
||||
g_assert_nonnull (item);
|
||||
|
||||
/* configure item */
|
||||
|
||||
{
|
||||
WpProperties *props = wp_properties_new_empty ();
|
||||
wp_properties_set (props, "name", "virtual");
|
||||
wp_properties_set (props, "media.class", "Audio/Source");
|
||||
g_assert_true (wp_session_item_configure (item, props));
|
||||
g_assert_true (wp_session_item_is_configured (item));
|
||||
}
|
||||
|
||||
{
|
||||
const gchar *str = NULL;
|
||||
g_autoptr (WpProperties) props = wp_session_item_get_properties (item);
|
||||
g_assert_nonnull (props);
|
||||
str = wp_properties_get (props, "name");
|
||||
g_assert_nonnull (str);
|
||||
g_assert_cmpstr ("virtual", ==, str);
|
||||
str = wp_properties_get (props, "item.node.direction");
|
||||
g_assert_nonnull (str);
|
||||
g_assert_cmpstr ("output", ==, str);
|
||||
str = wp_properties_get (props, "item.factory.name");
|
||||
g_assert_nonnull (str);
|
||||
g_assert_cmpstr ("si-audio-virtual", ==, str);
|
||||
}
|
||||
|
||||
/* activate item */
|
||||
|
||||
wp_object_activate (WP_OBJECT (item), WP_SESSION_ITEM_FEATURE_ACTIVE,
|
||||
NULL, (GAsyncReadyCallback) test_object_activate_finish_cb, f);
|
||||
g_main_loop_run (f->base.loop);
|
||||
g_assert_cmphex (wp_object_get_active_features (WP_OBJECT (item)), ==,
|
||||
WP_SESSION_ITEM_FEATURE_ACTIVE);
|
||||
|
||||
/* reset */
|
||||
wp_session_item_reset (item);
|
||||
g_assert_false (wp_session_item_is_configured (item));
|
||||
}
|
||||
|
||||
static void
|
||||
test_si_audio_virtual_export (TestFixture * f, gconstpointer user_data)
|
||||
{
|
||||
g_autoptr (WpSessionItem) item = NULL;
|
||||
g_autoptr (WpObjectManager) clients_om = NULL;
|
||||
g_autoptr (WpClient) self_client = NULL;
|
||||
|
||||
/* skip the test if null-audio-sink factory is not installed */
|
||||
if (!test_is_spa_lib_installed (&f->base, "support.null-audio-sink")) {
|
||||
g_test_skip ("The pipewire null-audio-sink factory was not found");
|
||||
return;
|
||||
}
|
||||
|
||||
clients_om = wp_object_manager_new ();
|
||||
wp_object_manager_add_interest (clients_om, WP_TYPE_CLIENT, NULL);
|
||||
wp_object_manager_request_object_features (clients_om,
|
||||
WP_TYPE_CLIENT, WP_PROXY_FEATURE_BOUND);
|
||||
g_signal_connect_swapped (clients_om, "objects-changed",
|
||||
G_CALLBACK (g_main_loop_quit), f->base.loop);
|
||||
wp_core_install_object_manager (f->base.core, clients_om);
|
||||
g_main_loop_run (f->base.loop);
|
||||
self_client = wp_object_manager_lookup (clients_om, WP_TYPE_CLIENT, NULL);
|
||||
g_assert_nonnull (self_client);
|
||||
|
||||
/* create item */
|
||||
|
||||
item = wp_session_item_make (f->base.core, "si-audio-virtual");
|
||||
g_assert_nonnull (item);
|
||||
|
||||
/* configure item */
|
||||
{
|
||||
WpProperties *props = wp_properties_new_empty ();
|
||||
wp_properties_set (props, "name", "virtual");
|
||||
wp_properties_set (props, "media.class", "Audio/Source");
|
||||
g_assert_true (wp_session_item_configure (item, props));
|
||||
g_assert_true (wp_session_item_is_configured (item));
|
||||
}
|
||||
|
||||
/* activate item */
|
||||
|
||||
{
|
||||
wp_object_activate (WP_OBJECT (item),
|
||||
WP_SESSION_ITEM_FEATURE_ACTIVE | WP_SESSION_ITEM_FEATURE_EXPORTED,
|
||||
NULL, (GAsyncReadyCallback) test_object_activate_finish_cb, f);
|
||||
g_main_loop_run (f->base.loop);
|
||||
g_assert_cmphex (wp_object_get_active_features (WP_OBJECT (item)), ==,
|
||||
WP_SESSION_ITEM_FEATURE_ACTIVE | WP_SESSION_ITEM_FEATURE_EXPORTED);
|
||||
}
|
||||
|
||||
{
|
||||
g_autoptr (WpNode) n = NULL;
|
||||
g_autoptr (WpProperties) props = NULL;
|
||||
|
||||
n = wp_session_item_get_associated_proxy (item, WP_TYPE_NODE);
|
||||
g_assert_nonnull (n);
|
||||
props = wp_pipewire_object_get_properties (WP_PIPEWIRE_OBJECT (n));
|
||||
g_assert_nonnull (props);
|
||||
|
||||
g_assert_cmpstr (wp_properties_get (props, "media.class"), ==,
|
||||
"Audio/Source");
|
||||
}
|
||||
|
||||
/* reset */
|
||||
wp_session_item_reset (item);
|
||||
g_assert_false (wp_session_item_is_configured (item));
|
||||
}
|
||||
|
||||
gint
|
||||
main (gint argc, gchar *argv[])
|
||||
{
|
||||
g_test_init (&argc, &argv, NULL);
|
||||
wp_init (WP_INIT_ALL);
|
||||
|
||||
/* configure-activate */
|
||||
g_test_add ("/modules/si-audio-virtual/configure-activate",
|
||||
TestFixture, NULL,
|
||||
test_si_audio_virtual_setup,
|
||||
test_si_audio_virtual_configure_activate,
|
||||
test_si_audio_virtual_teardown);
|
||||
|
||||
/* export */
|
||||
g_test_add ("/modules/si-audio-virtual/export",
|
||||
TestFixture, NULL,
|
||||
test_si_audio_virtual_setup,
|
||||
test_si_audio_virtual_export,
|
||||
test_si_audio_virtual_teardown);
|
||||
|
||||
return g_test_run ();
|
||||
}
|
||||
|
|
@ -229,7 +229,6 @@ load_components (ScriptRunnerFixture *f, gconstpointer argv)
|
|||
|
||||
load_component (f, "libwireplumber-module-si-audio-adapter", "module");
|
||||
load_component (f, "libwireplumber-module-si-standard-link", "module");
|
||||
load_component (f, "libwireplumber-module-si-audio-virtual", "module");
|
||||
|
||||
load_component (f, "default-nodes/apply-default-node.lua", "script/lua");
|
||||
load_component (f, "default-nodes/state-default-nodes.lua", "script/lua");
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue