Rename endpoint concept to virtual session item

This commit is contained in:
Julian Bouzas
2022-12-11 18:45:43 -05:00
parent 9004362cda
commit fb855b00cf
20 changed files with 284 additions and 278 deletions

View File

@@ -389,49 +389,51 @@ files and are placed under ``wireplumber.conf.d/``. More on this below.
* wp_settings_apply_rule () is WpSettings API for rules.
* *wireplumber.endpoints*
* *wireplumber.virtuals*
Endpoints are a way of grouping different kinds of clients or
Virtual session items are a way of grouping different kinds of clients or
applications(for example Music, Voice, Navigation, Gaming etc).
The actual grouping is done based on the `media.role` of the client
stream node.
Endpoints allows for that actions to be taken up at group level rather than
at individual stream level, which can be cumbersome.
Virtual session items allows for that actions to be taken up at group level
rather than at individual stream level, which can be cumbersome.
For example imagine the following scenarios.
* Incoming Navigation message needs to duck the volume of
Audio playback(all the apps playing audio).
* Incoming voice/voip call needs to stop(cork) the Audio playback.
Endpoints realize this functionality with ease.
Virtual session items realize this functionality with ease.
* *Defining Endpoints*
* *Defining Virtual session items*
Example::
endpoints = {
endpoint.capture = {
virtual-items = {
virtual-item.capture = {
media.class = "Audio/Source"
role = "Capture"
}
endpoint.multimedia = {
virtual-item.multimedia = {
media.class = "Audio/Sink"
role = "Multimedia"
}
endpoint.navigation = {
virtual-item.navigation = {
media.class = "Audio/Sink"
role = "Navigation"
}
This example creates 3 endpoints, with names ``endpoint.capture``,
``endpoint.multimedia`` and ``endpoint.navigation`` and assigned roles
``Capture``, ``Multimedia`` and ``Navigation`` respectively.
This example creates 3 virtual session items, with names
``virtual-item.capture``, ``virtual-item.multimedia`` and
``virtual-item.navigation`` and assigned roles ``Capture``, ``Multimedia``
and ``Navigation`` respectively.
First end point has a media class of ``Audio/Source`` used for capture and rest of the endpoints have ``Audio/Sink`` media class, and so are only
used for playback.
First virtual item has a media class of ``Audio/Source`` used for capture
and rest of the virtual items have ``Audio/Sink`` media class, and so are
only used for playback.
* *Endpoints config*
* *Virtual session items config*
Example::
@@ -456,15 +458,15 @@ files and are placed under ``wireplumber.conf.d/``. More on this below.
The above example defines actions for both ``Multimedia`` and ``Navigation``
roles. Since the Navigation role has more priority than the Multimedia
role, when a client connects to the Navigation endpoint, it will ``duck``
the volume of all Multimedia clients. If Multiple Navigation clients want
to play audio, their audio will be mixed.
role, when a client connects to the Navigation virtual session item, it
will ``duck`` the volume of all Multimedia clients. If Multiple Navigation
clients want to play audio, their audio will be mixed.
Possible values of actions are: ``mix`` (Mixes audio),
``duck`` (Mixes and lowers the audio volume) or ``cork`` (Pauses audio).
Endpoints are not used for desktop use cases, it is more suitable for
embedded use cases.
Virtual session items are not used for desktop use cases, it is more suitable
for embedded use cases.
* *Split Configuration files*

View File

@@ -62,16 +62,16 @@ Native API clients
pw-cat
^^^^^^
Using the default endpoint:
Using the default device:
.. code:: console
$ wpctl status # verify the default endpoints
$ wpctl status # verify the default device
$ pw-record test.wav
$ pw-play test.wav
Using a non-default endpoint:
Using a non-default device:
.. code:: console
@@ -84,19 +84,18 @@ or
.. code:: console
$ wpctl status # find the capture & playback endpoint ids
$ pw-record --target <endpoint_id> test.wav
$ pw-play --target <endpoint_id> test.wav
$ wpctl status # find the capture & playback node ids
$ pw-record --target <node_id> test.wav
$ pw-play --target <node_id> test.wav
.. note::
node ids and endpoint ids can be used interchangeably when specifying
targets in all use cases
node ids can be used interchangeably when specifying targets in all use cases
video-play
^^^^^^^^^^
Using the default endpoint:
Using the default device:
.. code:: console
@@ -104,13 +103,13 @@ Using the default endpoint:
$ ./build/src/examples/video-play
Using a non-default endpoint:
Using a non-default device:
.. code:: console
$ wpctl status # find the endpoint id from the list
$ wpctl status # find the device node id from the list
$ cd path/to/pipewire-source-dir
$ ./build/src/examples/video-play <endpoint_id>
$ ./build/src/examples/video-play <node_id>
.. tip::
@@ -123,11 +122,11 @@ PulseAudio compat API clients
pacat
^^^^^
Using the default endpoint:
Using the default device:
.. code:: console
$ wpctl status # verify the default endpoints
$ wpctl status # verify the default device
$ parecord test.wav
$ paplay test.wav
@@ -158,29 +157,29 @@ aplay / arecord
``pipewire-alsa/conf/50-pipewire.conf`` in your ``~/.asoundrc``
(or anywhere else, system-wide, where libasound can read it)
Using the default endpoint:
Using the default device:
.. code:: console
$ wpctl status # verify the default endpoints
$ wpctl status # verify the default devices
$ arecord -D pipewire -f S16_LE -r 48000 test.wav
$ aplay -D pipewire test.wav
Using a non-default endpoint:
Using a non-default device:
.. code:: console
$ wpctl status # find the capture & playback endpoint ids
$ PIPEWIRE_NODE=<endpoint_id> arecord -D pipewire -f S16_LE -r 48000 test.wav
$ PIPEWIRE_NODE=<endpoint_id> aplay -D pipewire test.wav
$ wpctl status # find the capture & playback node ids
$ PIPEWIRE_NODE=<node_id> arecord -D pipewire -f S16_LE -r 48000 test.wav
$ PIPEWIRE_NODE=<node_id> aplay -D pipewire test.wav
or
.. code:: console
$ wpctl status # find the capture & playback endpoint ids
$ arecord -D pipewire:NODE=<endpoint_id> -f S16_LE -r 48000 test.wav
$ aplay -D pipewire:NODE=<endpoint_id> test.wav
$ wpctl status # find the capture & playback device node ids
$ arecord -D pipewire:NODE=<node_id> -f S16_LE -r 48000 test.wav
$ aplay -D pipewire:NODE=<node_id> test.wav
JACK compat API clients
@@ -201,15 +200,14 @@ jack_simple_client
.. code:: console
$ wpctl status # find the target endpoint id
$ wpctl inspect <endpoint_id> # find the node.id
$ wpctl status # find the target device node id
$ wpctl inspect <node_id> # find the node.id
$ PIPEWIRE_NODE=<node_id> pw-jack jack_simple_client
.. note::
The JACK layer is not controlled by the session manager, it creates its own
links; which is why it is required to specify a node id (endpoint id will not
work)
links; which is why it is required to specify a node id.
Device Reservation
------------------
@@ -263,7 +261,7 @@ with JACK
3. Wait a few seconds and run ``wpctl status`` to inspect
- The devices taken by JACK should no longer be available
- There should be two *JACK System* endpoints (sink & source)
- There should be two *JACK System* nodes (sink & source)
4. Run an audio client on PipeWire (ex ``pw-play test.wav``)
@@ -277,7 +275,7 @@ with JACK
6. Wait a few seconds and run ``wpctl status`` to inspect
- The devices that were release by JACK should again be available
- There should be no *JACK System* endpoint
- There should be no *JACK System* nodes
.. note::

View File

@@ -76,11 +76,11 @@ shared_library(
)
shared_library(
'wireplumber-module-si-audio-endpoint',
'wireplumber-module-si-audio-virtual',
[
'module-si-audio-endpoint.c',
'module-si-audio-virtual.c',
],
c_args : [common_c_args, '-DG_LOG_DOMAIN="m-si-audio-endpoint"'],
c_args : [common_c_args, '-DG_LOG_DOMAIN="m-si-audio-virtual"'],
install : true,
install_dir : wireplumber_module_dir,
dependencies : [wp_dep, pipewire_dep],

View File

@@ -175,13 +175,6 @@ local Feature = {
Node = {
PORTS = (1 << 16),
},
Session = {
ENDPOINTS = (1 << 16),
LINKS = (1 << 17),
},
Endpoint = {
STREAMS = (1 << 16),
},
Metadata = {
DATA = (1 << 16),
},

View File

@@ -12,9 +12,9 @@
#include <spa/param/audio/raw.h>
#include <spa/param/param.h>
#define SI_FACTORY_NAME "si-audio-endpoint"
#define SI_FACTORY_NAME "si-audio-virtual"
struct _WpSiAudioEndpoint
struct _WpSiAudioVirtual
{
WpSessionItem parent;
@@ -31,26 +31,26 @@ struct _WpSiAudioEndpoint
WpSiAdapter *adapter;
};
static void si_audio_endpoint_linkable_init (WpSiLinkableInterface * iface);
static void si_audio_endpoint_adapter_init (WpSiAdapterInterface * iface);
static void si_audio_virtual_linkable_init (WpSiLinkableInterface * iface);
static void si_audio_virtual_adapter_init (WpSiAdapterInterface * iface);
G_DECLARE_FINAL_TYPE(WpSiAudioEndpoint, si_audio_endpoint, WP,
SI_AUDIO_ENDPOINT, WpSessionItem)
G_DEFINE_TYPE_WITH_CODE (WpSiAudioEndpoint, si_audio_endpoint,
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_endpoint_linkable_init)
G_IMPLEMENT_INTERFACE (WP_TYPE_SI_ADAPTER, si_audio_endpoint_adapter_init))
si_audio_virtual_linkable_init)
G_IMPLEMENT_INTERFACE (WP_TYPE_SI_ADAPTER, si_audio_virtual_adapter_init))
static void
si_audio_endpoint_init (WpSiAudioEndpoint * self)
si_audio_virtual_init (WpSiAudioVirtual * self)
{
}
static void
si_audio_endpoint_reset (WpSessionItem * item)
si_audio_virtual_reset (WpSessionItem * item)
{
WpSiAudioEndpoint *self = WP_SI_AUDIO_ENDPOINT (item);
WpSiAudioVirtual *self = WP_SI_AUDIO_VIRTUAL (item);
/* deactivate first */
wp_object_deactivate (WP_OBJECT (self),
@@ -64,18 +64,18 @@ si_audio_endpoint_reset (WpSessionItem * item)
self->priority = 0;
self->disable_dsp = FALSE;
WP_SESSION_ITEM_CLASS (si_audio_endpoint_parent_class)->reset (item);
WP_SESSION_ITEM_CLASS (si_audio_virtual_parent_class)->reset (item);
}
static gboolean
si_audio_endpoint_configure (WpSessionItem * item, WpProperties *p)
si_audio_virtual_configure (WpSessionItem * item, WpProperties *p)
{
WpSiAudioEndpoint *self = WP_SI_AUDIO_ENDPOINT (item);
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_endpoint_reset (item);
si_audio_virtual_reset (item);
str = wp_properties_get (si_props, "name");
if (!str)
@@ -116,18 +116,18 @@ si_audio_endpoint_configure (WpSessionItem * item, WpProperties *p)
}
static gpointer
si_audio_endpoint_get_associated_proxy (WpSessionItem * item, GType proxy_type)
si_audio_virtual_get_associated_proxy (WpSessionItem * item, GType proxy_type)
{
WpSiAudioEndpoint *self = WP_SI_AUDIO_ENDPOINT (item);
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_endpoint_disable_active (WpSessionItem *si)
si_audio_virtual_disable_active (WpSessionItem *si)
{
WpSiAudioEndpoint *self = WP_SI_AUDIO_ENDPOINT (si);
WpSiAudioVirtual *self = WP_SI_AUDIO_VIRTUAL (si);
g_clear_object (&self->adapter);
g_clear_object (&self->node);
@@ -136,9 +136,9 @@ si_audio_endpoint_disable_active (WpSessionItem *si)
}
static void
si_audio_endpoint_disable_exported (WpSessionItem *si)
si_audio_virtual_disable_exported (WpSessionItem *si)
{
WpSiAudioEndpoint *self = WP_SI_AUDIO_ENDPOINT (si);
WpSiAudioVirtual *self = WP_SI_AUDIO_VIRTUAL (si);
wp_object_update_features (WP_OBJECT (self), 0,
WP_SESSION_ITEM_FEATURE_EXPORTED);
@@ -148,7 +148,7 @@ static void
on_adapter_activate_done (WpObject * adapter, GAsyncResult * res,
WpTransition * transition)
{
WpSiAudioEndpoint *self = wp_transition_get_source_object (transition);
WpSiAudioVirtual *self = wp_transition_get_source_object (transition);
g_autoptr (GError) error = NULL;
if (!wp_object_activate_finish (adapter, res, &error)) {
@@ -163,7 +163,7 @@ on_adapter_activate_done (WpObject * adapter, GAsyncResult * res,
static void
on_adapter_port_state_changed (WpSiAdapter *item,
WpSiAdapterPortsState old_state, WpSiAdapterPortsState new_state,
WpSiAudioEndpoint *self)
WpSiAudioVirtual *self)
{
g_signal_emit_by_name (self, "adapter-ports-state-changed", old_state,
new_state);
@@ -173,7 +173,7 @@ static void
on_node_activate_done (WpObject * node, GAsyncResult * res,
WpTransition * transition)
{
WpSiAudioEndpoint *self = wp_transition_get_source_object (transition);
WpSiAudioVirtual *self = wp_transition_get_source_object (transition);
g_autoptr (GError) error = NULL;
g_autoptr (WpCore) core = NULL;
g_autoptr (WpProperties) props = NULL;
@@ -190,7 +190,7 @@ on_node_activate_done (WpObject * node, GAsyncResult * res,
if (!self->adapter) {
wp_transition_return_error (transition,
g_error_new (WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT,
"si-audio-endpoint: could not create si-audio-adapter"));
"si-audio-virtual: could not create si-audio-adapter"));
}
/* Forward adapter-ports-state-changed signal */
@@ -210,7 +210,7 @@ on_node_activate_done (WpObject * node, GAsyncResult * res,
g_steal_pointer (&props))) {
wp_transition_return_error (transition,
g_error_new (WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT,
"si-audio-endpoint: could not configure si-audio-adapter"));
"si-audio-virtual: could not configure si-audio-adapter"));
}
/* activate adapter */
@@ -219,12 +219,12 @@ on_node_activate_done (WpObject * node, GAsyncResult * res,
}
static void
si_audio_endpoint_enable_active (WpSessionItem *si, WpTransition *transition)
si_audio_virtual_enable_active (WpSessionItem *si, WpTransition *transition)
{
WpSiAudioEndpoint *self = WP_SI_AUDIO_ENDPOINT (si);
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 Endpoint", self->role,
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");
@@ -232,7 +232,7 @@ si_audio_endpoint_enable_active (WpSessionItem *si, WpTransition *transition)
if (!wp_session_item_is_configured (si)) {
wp_transition_return_error (transition,
g_error_new (WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT,
"si-audio-endpoint: item is not configured"));
"si-audio-virtual: item is not configured"));
return;
}
@@ -244,12 +244,12 @@ si_audio_endpoint_enable_active (WpSessionItem *si, WpTransition *transition)
PW_KEY_FACTORY_NAME, "support.null-audio-sink",
PW_KEY_NODE_DESCRIPTION, desc,
"monitor.channel-volumes", "true",
"wireplumber.is-endpoint", "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-endpoint: could not create null-audio-sink node"));
"si-audio-virtual: could not create null-audio-sink node"));
return;
}
@@ -260,84 +260,84 @@ si_audio_endpoint_enable_active (WpSessionItem *si, WpTransition *transition)
}
static void
si_audio_endpoint_enable_exported (WpSessionItem *si, WpTransition *transition)
si_audio_virtual_enable_exported (WpSessionItem *si, WpTransition *transition)
{
WpSiAudioEndpoint *self = WP_SI_AUDIO_ENDPOINT (si);
WpSiAudioVirtual *self = WP_SI_AUDIO_VIRTUAL (si);
wp_object_update_features (WP_OBJECT (self),
WP_SESSION_ITEM_FEATURE_EXPORTED, 0);
}
static void
si_audio_endpoint_class_init (WpSiAudioEndpointClass * klass)
si_audio_virtual_class_init (WpSiAudioVirtualClass * klass)
{
WpSessionItemClass *si_class = (WpSessionItemClass *) klass;
si_class->reset = si_audio_endpoint_reset;
si_class->configure = si_audio_endpoint_configure;
si_class->get_associated_proxy = si_audio_endpoint_get_associated_proxy;
si_class->disable_active = si_audio_endpoint_disable_active;
si_class->disable_exported = si_audio_endpoint_disable_exported;
si_class->enable_active = si_audio_endpoint_enable_active;
si_class->enable_exported = si_audio_endpoint_enable_exported;
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_endpoint_get_ports (WpSiLinkable * item, const gchar * context)
si_audio_virtual_get_ports (WpSiLinkable * item, const gchar * context)
{
WpSiAudioEndpoint *self = WP_SI_AUDIO_ENDPOINT (item);
WpSiAudioVirtual *self = WP_SI_AUDIO_VIRTUAL (item);
return wp_si_linkable_get_ports (WP_SI_LINKABLE (self->adapter), context);
}
static void
si_audio_endpoint_linkable_init (WpSiLinkableInterface * iface)
si_audio_virtual_linkable_init (WpSiLinkableInterface * iface)
{
iface->get_ports = si_audio_endpoint_get_ports;
iface->get_ports = si_audio_virtual_get_ports;
}
static WpSiAdapterPortsState
si_audio_endpoint_get_ports_state (WpSiAdapter * item)
si_audio_virtual_get_ports_state (WpSiAdapter * item)
{
WpSiAudioEndpoint *self = WP_SI_AUDIO_ENDPOINT (item);
WpSiAudioVirtual *self = WP_SI_AUDIO_VIRTUAL (item);
return wp_si_adapter_get_ports_state (self->adapter);
}
static WpSpaPod *
si_audio_endpoint_get_ports_format (WpSiAdapter * item, const gchar **mode)
si_audio_virtual_get_ports_format (WpSiAdapter * item, const gchar **mode)
{
WpSiAudioEndpoint *self = WP_SI_AUDIO_ENDPOINT (item);
WpSiAudioVirtual *self = WP_SI_AUDIO_VIRTUAL (item);
return wp_si_adapter_get_ports_format (self->adapter, mode);
}
static void
si_audio_endpoint_set_ports_format (WpSiAdapter * item, WpSpaPod *f,
si_audio_virtual_set_ports_format (WpSiAdapter * item, WpSpaPod *f,
const gchar *mode, GAsyncReadyCallback callback, gpointer data)
{
WpSiAudioEndpoint *self = WP_SI_AUDIO_ENDPOINT (item);
WpSiAudioVirtual *self = WP_SI_AUDIO_VIRTUAL (item);
wp_si_adapter_set_ports_format (self->adapter, f, mode, callback, data);
}
static gboolean
si_audio_endpoint_set_ports_format_finish (WpSiAdapter * item,
si_audio_virtual_set_ports_format_finish (WpSiAdapter * item,
GAsyncResult * res, GError ** error)
{
WpSiAudioEndpoint *self = WP_SI_AUDIO_ENDPOINT (item);
WpSiAudioVirtual *self = WP_SI_AUDIO_VIRTUAL (item);
return wp_si_adapter_set_ports_format_finish (self->adapter, res, error);
}
static void
si_audio_endpoint_adapter_init (WpSiAdapterInterface * iface)
si_audio_virtual_adapter_init (WpSiAdapterInterface * iface)
{
iface->get_ports_state = si_audio_endpoint_get_ports_state;
iface->get_ports_format = si_audio_endpoint_get_ports_format;
iface->set_ports_format = si_audio_endpoint_set_ports_format;
iface->set_ports_format_finish = si_audio_endpoint_set_ports_format_finish;
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 gboolean
wireplumber__module_init (WpCore * core, GVariant * args, GError ** error)
{
wp_si_factory_register (core, wp_si_factory_new_simple (SI_FACTORY_NAME,
si_audio_endpoint_get_type ()));
si_audio_virtual_get_type ()));
return TRUE;
}

View File

@@ -550,10 +550,10 @@ configure_and_link_adapters (WpSiStandardLink *self, WpTransition *transition)
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-endpoint") && !in->is_device)
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-endpoint") && !out->is_device)
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");

View File

@@ -131,7 +131,7 @@ wireplumber.components = [
}
{
name = libwireplumber-module-si-audio-endpoint,
name = libwireplumber-module-si-audio-virtual,
type = module
## default priority for session item modules
priority = 130

View File

@@ -1,48 +1,48 @@
## The WirePlumber endpoint configuration
## The WirePlumber virtual item configuration
endpoints = {
## The list of endpoints to create
virtual-items = {
## The list of virtual items to create
# endpoint.capture = {
# virtual-item.capture = {
# media.class = "Audio/Source"
# role = "Capture"
# }
# endpoint.multimedia = {
# virtual-item.multimedia = {
# media.class = "Audio/Sink"
# role = "Multimedia"
# }
# endpoint.speech_low = {
# virtual-item.speech_low = {
# media.class = "Audio/Sink"
# role = "Speech-Low"
# }
# endpoint.custom_low = {
# virtual-item.custom_low = {
# media.class = "Audio/Sink"
# role = "Custom-Low"
# }
# endpoint.navigation = {
# virtual-item.navigation = {
# media.class = "Audio/Sink"
# role = "Navigation"
# }
# endpoint.speech_high = {
# virtual-item.speech_high = {
# media.class = "Audio/Sink"
# role = "Speech-High"
# }
# endpoint.custom_high = {
# virtual-item.custom_high = {
# media.class = "Audio/Sink"
# role = "Custom-High"
# }
# endpoint.communication = {
# virtual-item.communication = {
# media.class = "Audio/Sink"
# role = "Communication"
# }
# endpoint.emergency = {
# virtual-item.emergency = {
# media.class = "Audio/Sink"
# role = "Emergency"
# }
}
endpoint-roles = {
## The list of endpoint roles to use
virtual-item-roles = {
## The list of virtual item roles to use
# Capture = {
# alias = [ "Multimedia", "Music", "Voice", "Capture" ]

View File

@@ -0,0 +1,42 @@
-- WirePlumber
--
-- Copyright © 2021 Collabora Ltd.
-- @author Julian Bouzas <julian.bouzas@collabora.com>
--
-- SPDX-License-Identifier: MIT
-- Receive script arguments from config.lua
local defaults = {}
defaults.virtual_items = Json.Object {}
local config = {}
config.virtual_items = Conf.get_section (
"virtual-items", defaults.virtual_items):parse ()
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

View File

@@ -12,8 +12,8 @@ node_om = ObjectManager {
Interest {
type = "node",
Constraint { "media.class", "matches", "Audio/Sink", type = "pw-global" },
-- Do not consider endpoints created by WirePlumber
Constraint { "wireplumber.is-endpoint", "!", true, type = "pw" },
-- Do not consider virtual items created by WirePlumber
Constraint { "wireplumber.is-virtual", "!", true, type = "pw" },
-- or the fallback sink itself
Constraint { "wireplumber.is-fallback", "!", true, type = "pw" },
}

View File

@@ -22,8 +22,7 @@ function checkLinkable (si, om, handle_nonstreams)
end
-- Determine if we can handle item by this policy
if om:lookup { type = "SiEndpoint" } and
si_props ["item.factory.name"] == "si-audio-adapter" then
if si_props ["item.factory.name"] == "si-audio-virtual" then
return false
end

View File

@@ -74,7 +74,7 @@ AsyncEventHook {
EventInterest {
Constraint { "event.type", "=", "node-added" },
Constraint { "media.class", "#", "Audio/*", type = "pw-global" },
Constraint { "wireplumber.is-endpoint", "-", type = "pw" },
Constraint { "wireplumber.is-virtual", "-", type = "pw" },
},
},
steps = {
@@ -144,7 +144,7 @@ SimpleEventHook {
EventInterest {
Constraint { "event.type", "=", "node-removed" },
Constraint { "media.class", "#", "Audio/*", type = "pw-global" },
Constraint { "wireplumber.is-endpoint", "-", type = "pw" },
Constraint { "wireplumber.is-virtual", "-", type = "pw" },
},
},
execute = function (event)

View File

@@ -13,7 +13,7 @@ local config = {}
config.duck_level = Conf.get_value_float ("wireplumber.settings",
"policy.default.duck-level", defaults.duck_level)
config.roles = Conf.get_section (
"endpoint-roles", defaults.roles):parse ()
"virtual-item-roles", defaults.roles):parse ()
function findRole(role)
if role and not config.roles[role] then
@@ -50,32 +50,38 @@ end
function restoreVolume(role, media_class)
if not mixer_api then return end
local ep = endpoints_om:lookup {
local si_v = virtuals_om:lookup {
Constraint { "media.role", "=", role, type = "pw" },
Constraint { "media.class", "=", media_class, type = "pw" },
}
if ep and ep.properties["node.id"] then
Log.debug(ep, "restore role " .. role)
mixer_api:call("set-volume", ep.properties["node.id"], {
monitorVolume = 1.0,
})
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 duck(role, media_class)
if not mixer_api then return end
local ep = endpoints_om:lookup {
local si_v = virtuals_om:lookup {
Constraint { "media.role", "=", role, type = "pw" },
Constraint { "media.class", "=", media_class, type = "pw" },
}
if ep and ep.properties["node.id"] then
Log.debug(ep, "duck role " .. role)
mixer_api:call("set-volume", ep.properties["node.id"], {
monitorVolume = config.duck_level,
})
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
@@ -98,7 +104,7 @@ function rescan()
["Video/Source"] = {},
}
Log.info("Rescan endpoint links")
Log.info("Rescan virtual links")
-- deactivate all links if suspend playback metadata is present
local suspend = getSuspendPlaybackMetadata()
@@ -193,7 +199,7 @@ end
silinks_om = ObjectManager {
Interest {
type = "SiLink",
Constraint { "is.policy.endpoint.client.link", "=", true },
Constraint { "is.policy.virtual.client.link", "=", true },
},
}
silinks_om:connect("objects-changed", maybeRescan)
@@ -202,10 +208,12 @@ silinks_om:activate()
-- enable ducking if mixer-api is loaded
mixer_api = Plugin.find("mixer-api")
if mixer_api then
endpoints_om = ObjectManager {
Interest { type = "endpoint" },
virtuals_om = ObjectManager { Interest { type = "SiLinkable",
Constraint {
"item.factory.name", "=", "si-audio-virtual", type = "pw-global" },
}
}
endpoints_om:activate()
virtuals_om:activate()
end
metadata_om = ObjectManager {

View File

@@ -12,7 +12,7 @@ defaults.roles = Json.Object {}
local config = {}
config.roles = Conf.get_section (
"endpoint-roles", defaults.roles):parse ()
"virtual-item-roles", defaults.roles):parse ()
local self = {}
self.scanning = false
@@ -73,7 +73,7 @@ function findRole(role, tmc)
return role
end
function findTargetEndpoint (node, media_class, role)
function findTargetVirtual (node, media_class, role)
local target_class_assoc = {
["Stream/Input/Audio"] = "Audio/Source",
["Stream/Output/Audio"] = "Audio/Sink",
@@ -89,9 +89,9 @@ function findTargetEndpoint (node, media_class, role)
return nil
end
-- find highest priority endpoint by role
-- find highest priority virtual by role
media_role = findRole(role, target_media_class)
for si_target_ep in endpoints_om:iterate {
for si_target_ep in virtuals_om:iterate {
Constraint { "role", "=", media_role, type = "pw-global" },
Constraint { "media.class", "=", target_media_class, type = "pw-global" },
} do
@@ -132,7 +132,7 @@ function createLink (si, si_target_ep)
["in.item"] = in_item,
["out.item.port.context"] = "output",
["in.item.port.context"] = "input",
["is.policy.endpoint.client.link"] = true,
["is.policy.virtual.client.link"] = true,
["media.role"] = target_ep_props["role"],
["target.media.class"] = target_ep_props["media.class"],
["item.plugged.usec"] = si_props["item.plugged.usec"],
@@ -159,7 +159,7 @@ function checkLinkable (si)
end
-- Determine if we can handle item by this policy
if endpoints_om:get_n_objects () == 0 then
if virtuals_om:get_n_objects () == 0 then
Log.debug (si, "item won't be handled by this policy")
return false
end
@@ -178,10 +178,10 @@ function handleLinkable (si)
Log.info (si, "handling item " .. tostring(node.properties["node.name"]) ..
" with role " .. media_role)
-- find proper target endpoint
local si_target_ep = findTargetEndpoint (node, media_class, media_role)
-- find proper target virtual
local si_target_ep = findTargetVirtual (node, media_class, media_role)
if not si_target_ep then
Log.info (si, "... target endpoint not found")
Log.info (si, "... target virtual not found")
return
end
@@ -191,11 +191,11 @@ function handleLinkable (si)
local in_id = tonumber(link.properties["in.item.id"])
if out_id == si.id or in_id == si.id then
local is_out = out_id == si.id and true or false
for peer_ep in endpoints_om:iterate() do
for peer_ep in virtuals_om:iterate() do
if peer_ep.id == (is_out and in_id or out_id) then
if peer_ep.id == si_target_ep.id then
Log.info (si, "... already linked to proper target endpoint")
Log.info (si, "... already linked to proper target virtual")
return
end
@@ -237,7 +237,11 @@ function unhandleLinkable (si)
end
end
endpoints_om = ObjectManager { Interest { type = "SiEndpoint" }}
virtuals_om = ObjectManager { Interest { type = "SiLinkable",
Constraint {
"item.factory.name", "=", "si-audio-virtual", type = "pw-global" },
}
}
linkables_om = ObjectManager { Interest { type = "SiLinkable",
-- only handle si-audio-adapter and si-node
Constraint {
@@ -248,7 +252,7 @@ linkables_om = ObjectManager { Interest { type = "SiLinkable",
}
links_om = ObjectManager { Interest { type = "SiLink",
-- only handle links created by this policy
Constraint { "is.policy.endpoint.client.link", "=", true, type = "pw-global" },
Constraint { "is.policy.virtual.client.link", "=", true, type = "pw-global" },
} }
linkables_om:connect("objects-changed", function (om)
@@ -259,6 +263,6 @@ linkables_om:connect("object-removed", function (om, si)
unhandleLinkable (si)
end)
endpoints_om:activate()
virtuals_om:activate()
linkables_om:activate()
links_om:activate()

View File

@@ -17,9 +17,9 @@ self.scanning = false
self.pending_rescan = false
function rescan ()
-- check endpoints and register new links
for si_ep in endpoints_om:iterate() do
handleEndpoint (si_ep)
-- check virtuals and register new links
for si_v in virtuals_om:iterate() do
handleVirtual (si_v)
end
end
@@ -111,7 +111,7 @@ function createLink (si_ep, si_target)
["out.item.port.context"] = "output",
["in.item.port.context"] = "input",
["passive"] = true,
["is.policy.endpoint.device.link"] = true,
["is.policy.virtual.device.link"] = true,
} then
Log.warning (si_link, "failed to configure si-standard-link")
return
@@ -131,8 +131,8 @@ function createLink (si_ep, si_target)
end)
end
function handleEndpoint (si_ep)
Log.info (si_ep, "handling endpoint " .. si_ep.properties["name"])
function handleVirtual (si_ep)
Log.info (si_ep, "handling virtual " .. si_ep.properties["name"])
-- find proper target item
local si_target = findUndefinedTarget (si_ep)
@@ -192,7 +192,11 @@ function unhandleLinkable (si)
end
default_nodes = Plugin.find("default-nodes-api")
endpoints_om = ObjectManager { Interest { type = "SiEndpoint" }}
virtuals_om = ObjectManager { Interest { type = "SiLinkable",
Constraint {
"item.factory.name", "=", "si-audio-virtual", type = "pw-global" },
}
}
linkables_om = ObjectManager {
Interest {
type = "SiLinkable",
@@ -206,7 +210,7 @@ links_om = ObjectManager {
Interest {
type = "SiLink",
-- only handle links created by this policy
Constraint { "is.policy.endpoint.device.link", "=", true, type = "pw-global" },
Constraint { "is.policy.virtual.device.link", "=", true, type = "pw-global" },
}
}
@@ -221,7 +225,7 @@ linkables_om:connect("objects-changed", function (om)
scheduleRescan ()
end)
endpoints_om:connect("object-added", function (om)
virtuals_om:connect("object-added", function (om)
scheduleRescan ()
end)
@@ -229,6 +233,6 @@ linkables_om:connect("object-removed", function (om, si)
unhandleLinkable (si)
end)
endpoints_om:activate()
virtuals_om:activate()
linkables_om:activate()
links_om:activate()

View File

@@ -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
local defaults = {}
defaults.endpoints = Json.Object {}
local config = {}
config.endpoints = Conf.get_section (
"endpoints", defaults.endpoints):parse ()
function createEndpoint (factory_name, properties)
-- create endpoint
local ep = SessionItem ( factory_name )
if not ep then
Log.warning (ep, "could not create endpoint of type " .. factory_name)
return
end
-- configure endpoint
if not ep:configure(properties) then
Log.warning(ep, "failed to configure endpoint " .. properties.name)
return
end
-- activate and register endpoint
ep:activate (Features.ALL, function (item)
item:register ()
Log.info(item, "registered endpoint " .. properties.name)
end)
end
for name, properties in pairs(config.endpoints) do
properties["name"] = name
createEndpoint ("si-audio-endpoint", properties)
end

View File

@@ -38,8 +38,8 @@ test(
)
test(
'test-si-audio-endpoint',
executable('test-si-audio-endpoint', 'si-audio-endpoint.c',
'test-si-audio-virtual',
executable('test-si-audio-virtual', 'si-audio-virtual.c',
dependencies: common_deps, c_args: common_args),
env: common_env,
)

View File

@@ -13,7 +13,7 @@ typedef struct {
} TestFixture;
static void
test_si_audio_endpoint_setup (TestFixture * f, gconstpointer user_data)
test_si_audio_virtual_setup (TestFixture * f, gconstpointer user_data)
{
wp_base_test_fixture_setup (&f->base, 0);
@@ -36,22 +36,22 @@ test_si_audio_endpoint_setup (TestFixture * f, gconstpointer user_data)
{
g_autoptr (GError) error = NULL;
wp_core_load_component (f->base.core,
"libwireplumber-module-si-audio-endpoint", "module", NULL, &error);
"libwireplumber-module-si-audio-virtual", "module", NULL, &error);
g_assert_no_error (error);
}
}
static void
test_si_audio_endpoint_teardown (TestFixture * f, gconstpointer user_data)
test_si_audio_virtual_teardown (TestFixture * f, gconstpointer user_data)
{
wp_base_test_fixture_teardown (&f->base);
}
static void
test_si_audio_endpoint_configure_activate (TestFixture * f,
test_si_audio_virtual_configure_activate (TestFixture * f,
gconstpointer user_data)
{
g_autoptr (WpSessionItem) endpoint = NULL;
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")) {
@@ -59,53 +59,53 @@ test_si_audio_endpoint_configure_activate (TestFixture * f,
return;
}
/* create endpoint */
/* create item */
endpoint = wp_session_item_make (f->base.core, "si-audio-endpoint");
g_assert_nonnull (endpoint);
item = wp_session_item_make (f->base.core, "si-audio-virtual");
g_assert_nonnull (item);
/* configure endpoint */
/* configure item */
{
WpProperties *props = wp_properties_new_empty ();
wp_properties_set (props, "name", "endpoint");
wp_properties_set (props, "name", "virtual");
wp_properties_set (props, "media.class", "Audio/Source");
g_assert_true (wp_session_item_configure (endpoint, props));
g_assert_true (wp_session_item_is_configured (endpoint));
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 (endpoint);
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 ("endpoint", ==, str);
g_assert_cmpstr ("virtual", ==, str);
str = wp_properties_get (props, "direction");
g_assert_nonnull (str);
g_assert_cmpstr ("1", ==, str);
str = wp_properties_get (props, "item.factory.name");
g_assert_nonnull (str);
g_assert_cmpstr ("si-audio-endpoint", ==, str);
g_assert_cmpstr ("si-audio-virtual", ==, str);
}
/* activate endpoint */
/* activate item */
wp_object_activate (WP_OBJECT (endpoint), WP_SESSION_ITEM_FEATURE_ACTIVE,
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 (endpoint)), ==,
g_assert_cmphex (wp_object_get_active_features (WP_OBJECT (item)), ==,
WP_SESSION_ITEM_FEATURE_ACTIVE);
/* reset */
wp_session_item_reset (endpoint);
g_assert_false (wp_session_item_is_configured (endpoint));
wp_session_item_reset (item);
g_assert_false (wp_session_item_is_configured (item));
}
static void
test_si_audio_endpoint_export (TestFixture * f, gconstpointer user_data)
test_si_audio_virtual_export (TestFixture * f, gconstpointer user_data)
{
g_autoptr (WpSessionItem) endpoint = NULL;
g_autoptr (WpSessionItem) item = NULL;
g_autoptr (WpObjectManager) clients_om = NULL;
g_autoptr (WpClient) self_client = NULL;
@@ -115,8 +115,6 @@ test_si_audio_endpoint_export (TestFixture * f, gconstpointer user_data)
return;
}
/* find self_client, to be used for verifying endpoint.client.id */
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,
@@ -128,28 +126,28 @@ test_si_audio_endpoint_export (TestFixture * f, gconstpointer user_data)
self_client = wp_object_manager_lookup (clients_om, WP_TYPE_CLIENT, NULL);
g_assert_nonnull (self_client);
/* create endpoint */
/* create item */
endpoint = wp_session_item_make (f->base.core, "si-audio-endpoint");
g_assert_nonnull (endpoint);
item = wp_session_item_make (f->base.core, "si-audio-virtual");
g_assert_nonnull (item);
/* configure endpoint */
/* configure item */
{
WpProperties *props = wp_properties_new_empty ();
wp_properties_set (props, "name", "endpoint");
wp_properties_set (props, "name", "virtual");
wp_properties_set (props, "media.class", "Audio/Source");
g_assert_true (wp_session_item_configure (endpoint, props));
g_assert_true (wp_session_item_is_configured (endpoint));
g_assert_true (wp_session_item_configure (item, props));
g_assert_true (wp_session_item_is_configured (item));
}
/* activate endpoint */
/* activate item */
{
wp_object_activate (WP_OBJECT (endpoint),
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 (endpoint)), ==,
g_assert_cmphex (wp_object_get_active_features (WP_OBJECT (item)), ==,
WP_SESSION_ITEM_FEATURE_ACTIVE | WP_SESSION_ITEM_FEATURE_EXPORTED);
}
@@ -157,7 +155,7 @@ test_si_audio_endpoint_export (TestFixture * f, gconstpointer user_data)
g_autoptr (WpNode) n = NULL;
g_autoptr (WpProperties) props = NULL;
n = wp_session_item_get_associated_proxy (endpoint, WP_TYPE_NODE);
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);
@@ -167,8 +165,8 @@ test_si_audio_endpoint_export (TestFixture * f, gconstpointer user_data)
}
/* reset */
wp_session_item_reset (endpoint);
g_assert_false (wp_session_item_is_configured (endpoint));
wp_session_item_reset (item);
g_assert_false (wp_session_item_is_configured (item));
}
gint
@@ -178,18 +176,18 @@ main (gint argc, gchar *argv[])
wp_init (WP_INIT_ALL);
/* configure-activate */
g_test_add ("/modules/si-audio-endpoint/configure-activate",
g_test_add ("/modules/si-audio-virtual/configure-activate",
TestFixture, NULL,
test_si_audio_endpoint_setup,
test_si_audio_endpoint_configure_activate,
test_si_audio_endpoint_teardown);
test_si_audio_virtual_setup,
test_si_audio_virtual_configure_activate,
test_si_audio_virtual_teardown);
/* export */
g_test_add ("/modules/si-audio-endpoint/export",
g_test_add ("/modules/si-audio-virtual/export",
TestFixture, NULL,
test_si_audio_endpoint_setup,
test_si_audio_endpoint_export,
test_si_audio_endpoint_teardown);
test_si_audio_virtual_setup,
test_si_audio_virtual_export,
test_si_audio_virtual_teardown);
return g_test_run ();
}

View File

@@ -112,13 +112,13 @@ test_si_standard_link_main (TestFixture * f, gconstpointer user_data)
{
g_autoptr (WpSessionItem) link = NULL;
/* skip the test if audiotestsrc endpoint could not be loaded */
/* skip the test if audiotestsrc could not be loaded */
if (!f->src_item) {
g_test_skip ("The pipewire audiotestsrc factory was not found");
return;
}
/* skip the test if null-audio-sink endpoint could not be loaded */
/* skip the test if null-audio-sink could not be loaded */
if (!f->sink_item) {
g_test_skip ("The pipewire null-audio-sink factory was not found");
return;

View File

@@ -209,7 +209,7 @@ load_components (ScriptRunnerFixture *f, gconstpointer argv)
load_component (f, "si-audio-adapter", "module");
load_component (f, "si-standard-link", "module");
load_component (f, "si-audio-endpoint", "module");
load_component (f, "si-audio-virtual", "module");
load_component (f, "metadata", "module");
load_component (f, "default-nodes-api", "module");