mirror of
https://gitlab.freedesktop.org/pipewire/wireplumber.git
synced 2026-05-15 09:18:07 +02:00
previously, we would only remove the endpoint associated with the node, but not the monitor endpoint... to implement that, we now store the node's associated session items in the node's qdata instead of a local hash table, so that we can store an arbitrary ammount of session items per node
368 lines
12 KiB
C
368 lines
12 KiB
C
/* WirePlumber
|
|
*
|
|
* Copyright © 2019 Collabora Ltd.
|
|
* @author Julian Bouzas <julian.bouzas@collabora.com>
|
|
*
|
|
* SPDX-License-Identifier: MIT
|
|
*/
|
|
|
|
#include <pipewire/pipewire.h>
|
|
|
|
#include <wp/wp.h>
|
|
|
|
#include "parser-endpoint.h"
|
|
#include "parser-streams.h"
|
|
#include "context.h"
|
|
|
|
G_DEFINE_QUARK (wp-module-config-endpoint-context-session, session);
|
|
G_DEFINE_QUARK (wp-module-config-endpoint-context-monitor, monitor);
|
|
G_DEFINE_QUARK (wp-module-config-endpoint-context-endpoint, endpoint);
|
|
|
|
struct _WpConfigEndpointContext
|
|
{
|
|
GObject parent;
|
|
|
|
WpObjectManager *sessions_om;
|
|
WpObjectManager *nodes_om;
|
|
};
|
|
|
|
enum {
|
|
SIGNAL_ENDPOINT_CREATED,
|
|
N_SIGNALS
|
|
};
|
|
|
|
static guint signals[N_SIGNALS];
|
|
|
|
G_DEFINE_TYPE (WpConfigEndpointContext, wp_config_endpoint_context,
|
|
WP_TYPE_PLUGIN)
|
|
|
|
static const struct WpParserStreamsData *
|
|
get_streams_data (WpConfiguration *config, const char *file_name)
|
|
{
|
|
g_autoptr (WpConfigParser) parser = NULL;
|
|
|
|
g_return_val_if_fail (config, 0);
|
|
g_return_val_if_fail (file_name, 0);
|
|
|
|
/* Get the streams parser */
|
|
parser = wp_configuration_get_parser (config, WP_PARSER_STREAMS_EXTENSION);
|
|
if (!parser)
|
|
return 0;
|
|
|
|
/* Get the streams data */
|
|
return wp_config_parser_get_matched_data (parser, (gpointer)file_name);
|
|
}
|
|
|
|
static void endpoint_activate_finish_cb (WpSessionItem * ep, GAsyncResult * res,
|
|
WpConfigEndpointContext * self);
|
|
|
|
static void
|
|
endpoint_export_finish_cb (WpSessionItem * ep, GAsyncResult * res,
|
|
WpConfigEndpointContext * self)
|
|
{
|
|
g_autoptr (GObject) node = NULL;
|
|
WpSessionItem * monitor = NULL;
|
|
g_autoptr (GError) error = NULL;
|
|
|
|
if (!wp_session_item_export_finish (ep, res, &error)) {
|
|
wp_warning_object (self, "failed to export endpoint: %s", error->message);
|
|
return;
|
|
}
|
|
|
|
/* Activate monitor, if there is one and if ep is not the monitor itself */
|
|
node = wp_session_item_get_associated_proxy (ep, WP_TYPE_NODE);
|
|
if (node)
|
|
monitor = g_object_get_qdata (node, monitor_quark ());
|
|
if (monitor && ep != monitor)
|
|
wp_session_item_activate (monitor,
|
|
(GAsyncReadyCallback) endpoint_activate_finish_cb, self);
|
|
|
|
/* Emit the signal */
|
|
g_signal_emit (self, signals[SIGNAL_ENDPOINT_CREATED], 0, ep);
|
|
}
|
|
|
|
static void
|
|
endpoint_activate_finish_cb (WpSessionItem * ep, GAsyncResult * res,
|
|
WpConfigEndpointContext * self)
|
|
{
|
|
WpSession * session = NULL;
|
|
g_autoptr (GError) error = NULL;
|
|
gboolean activate_ret = wp_session_item_activate_finish (ep, res, &error);
|
|
if (!activate_ret) {
|
|
wp_warning_object (self, "failed to activate endpoint: %s", error->message);
|
|
return;
|
|
}
|
|
|
|
/* Get the session */
|
|
session = g_object_get_qdata (G_OBJECT (ep), session_quark ());
|
|
g_return_if_fail (session);
|
|
|
|
wp_session_item_export (ep, WP_SESSION (session),
|
|
(GAsyncReadyCallback) endpoint_export_finish_cb, self);
|
|
}
|
|
|
|
static void
|
|
on_node_added (WpObjectManager *om, WpProxy *proxy, gpointer d)
|
|
{
|
|
WpConfigEndpointContext *self = d;
|
|
g_autoptr (WpCore) core = wp_plugin_get_core (WP_PLUGIN (self));
|
|
g_autoptr (WpConfiguration) config = wp_configuration_get_instance (core);
|
|
g_autoptr (WpProperties) props = wp_proxy_get_properties (proxy);
|
|
g_autoptr (WpSessionItem) ep = NULL;
|
|
g_autoptr (WpSessionItem) streams_ep = NULL;
|
|
g_autoptr (WpSession) session = NULL;
|
|
g_autoptr (WpConfigParser) parser = NULL;
|
|
const struct WpParserEndpointData *endpoint_data = NULL;
|
|
const struct WpParserStreamsData *streams_data = NULL;
|
|
WpDirection direction = WP_DIRECTION_INPUT;
|
|
|
|
/* Skip nodes with no media class (JACK Clients) */
|
|
if (!wp_properties_get (props, PW_KEY_MEDIA_CLASS))
|
|
return;
|
|
|
|
/* Get the endpoint configuration data */
|
|
parser = wp_configuration_get_parser (config, WP_PARSER_ENDPOINT_EXTENSION);
|
|
endpoint_data = wp_config_parser_get_matched_data (parser, proxy);
|
|
if (!endpoint_data)
|
|
return;
|
|
|
|
wp_info_object (self, "node %u " WP_OBJECT_FORMAT " matches %s",
|
|
wp_proxy_get_bound_id (proxy), WP_OBJECT_ARGS (proxy),
|
|
endpoint_data->filename);
|
|
|
|
/* Get the session */
|
|
session = wp_object_manager_lookup (self->sessions_om, WP_TYPE_SESSION,
|
|
WP_CONSTRAINT_TYPE_PW_PROPERTY, "session.name", "=s",
|
|
endpoint_data->e.session, NULL);
|
|
if (!session) {
|
|
wp_warning_object (self, "could not find session for endpoint");
|
|
return;
|
|
}
|
|
|
|
/* Get the streams data */
|
|
streams_data = endpoint_data->e.streams ?
|
|
get_streams_data (config, endpoint_data->e.streams) : NULL;
|
|
|
|
/* Create the endpoint */
|
|
ep = wp_session_item_make (core, endpoint_data->e.type);
|
|
if (!ep) {
|
|
wp_warning_object (self, "could not create endpoint of type %s",
|
|
endpoint_data->e.type);
|
|
return;
|
|
}
|
|
|
|
/* Configure the endpoint */
|
|
{
|
|
g_auto (GVariantBuilder) b =
|
|
G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT);
|
|
g_variant_builder_add (&b, "{sv}", "node",
|
|
g_variant_new_uint64 ((guint64) proxy));
|
|
|
|
if (endpoint_data->e.c.name)
|
|
g_variant_builder_add (&b, "{sv}", "name",
|
|
g_variant_new_string (endpoint_data->e.c.name));
|
|
|
|
if (endpoint_data->e.c.media_class)
|
|
g_variant_builder_add (&b, "{sv}", "media-class",
|
|
g_variant_new_string (endpoint_data->e.c.media_class));
|
|
|
|
if (endpoint_data->e.c.role)
|
|
g_variant_builder_add (&b, "{sv}", "role",
|
|
g_variant_new_string (endpoint_data->e.c.role));
|
|
|
|
g_variant_builder_add (&b, "{sv}", "priority",
|
|
g_variant_new_uint32 (endpoint_data->e.c.priority));
|
|
|
|
g_variant_builder_add (&b, "{sv}", "enable-control-port",
|
|
g_variant_new_boolean (endpoint_data->e.c.enable_control_port));
|
|
|
|
g_variant_builder_add (&b, "{sv}", "enable-monitor",
|
|
g_variant_new_boolean (endpoint_data->e.c.enable_monitor));
|
|
|
|
g_variant_builder_add (&b, "{sv}", "preferred-n-channels",
|
|
g_variant_new_uint32 (endpoint_data->e.c.preferred_n_channels));
|
|
|
|
wp_session_item_configure (ep, g_variant_builder_end (&b));
|
|
}
|
|
|
|
/* Get the endpoint direction */
|
|
{
|
|
g_autoptr (GVariant) ep_config = wp_session_item_get_configuration (ep);
|
|
if (!g_variant_lookup (ep_config, "direction", "y", &direction)) {
|
|
wp_warning_object (self, "could not get endpoint direction");
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* TODO: for now we always create softdsp audio endpoints if streams data is
|
|
* valid. However, this will need to change once we have video endpoints. */
|
|
if (streams_data) {
|
|
/* Create the steams endpoint */
|
|
streams_ep = wp_session_item_make (core, "si-audio-softdsp-endpoint");
|
|
|
|
/* Configure the streams endpoint */
|
|
{
|
|
g_auto (GVariantBuilder) b =
|
|
G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT);
|
|
g_variant_builder_add (&b, "{sv}", "adapter",
|
|
g_variant_new_uint64 ((guint64) ep));
|
|
wp_session_item_configure (streams_ep, g_variant_builder_end (&b));
|
|
}
|
|
|
|
/* Add the streams */
|
|
for (guint i = 0; i < streams_data->n_streams; i++) {
|
|
const struct WpParserStreamsStreamData *sd = streams_data->streams + i;
|
|
g_autoptr (WpSessionItem) stream =
|
|
wp_session_item_make (core, "si-convert");
|
|
{
|
|
g_auto (GVariantBuilder) b =
|
|
G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT);
|
|
g_variant_builder_add (&b, "{sv}", "target",
|
|
g_variant_new_uint64 ((guint64) ep));
|
|
g_variant_builder_add (&b, "{sv}", "name",
|
|
g_variant_new_string (sd->name));
|
|
g_variant_builder_add (&b, "{sv}", "enable-control-port",
|
|
g_variant_new_boolean (sd->enable_control_port));
|
|
wp_session_item_configure (stream, g_variant_builder_end (&b));
|
|
}
|
|
|
|
wp_session_bin_add (WP_SESSION_BIN (streams_ep), g_steal_pointer (&stream));
|
|
}
|
|
}
|
|
|
|
/* Create monitor endpoint if input direction and enable_monitor is true */
|
|
if (endpoint_data->e.c.enable_monitor && direction == WP_DIRECTION_INPUT) {
|
|
g_autoptr (WpSessionItem) monitor_ep =
|
|
wp_session_item_make (core, "si-monitor-endpoint");
|
|
|
|
g_auto (GVariantBuilder) b =
|
|
G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT);
|
|
g_return_if_fail (ep);
|
|
g_return_if_fail (monitor_ep);
|
|
g_variant_builder_add (&b, "{sv}", "adapter",
|
|
g_variant_new_uint64 ((guint64) ep));
|
|
wp_session_item_configure (monitor_ep, g_variant_builder_end (&b));
|
|
|
|
/* Set session */
|
|
g_object_set_qdata_full (G_OBJECT (monitor_ep), session_quark (),
|
|
g_object_ref (session), g_object_unref);
|
|
|
|
/* Keep a reference in the proxy */
|
|
g_object_set_qdata_full (G_OBJECT (proxy), monitor_quark (),
|
|
g_steal_pointer (&monitor_ep), g_object_unref);
|
|
}
|
|
|
|
/* Set session */
|
|
g_object_set_qdata_full (
|
|
G_OBJECT (streams_data ? streams_ep : ep), session_quark (),
|
|
g_steal_pointer (&session), g_object_unref);
|
|
|
|
/* Activate endpoint */
|
|
wp_session_item_activate (streams_data ? streams_ep : ep,
|
|
(GAsyncReadyCallback) endpoint_activate_finish_cb, self);
|
|
|
|
/* Insert the endpoint */
|
|
g_object_set_qdata_full (G_OBJECT (proxy), endpoint_quark (),
|
|
streams_data ? g_steal_pointer (&streams_ep) : g_steal_pointer (&ep),
|
|
g_object_unref);
|
|
}
|
|
|
|
static void
|
|
on_node_removed (WpObjectManager *om, WpProxy *proxy, gpointer d)
|
|
{
|
|
/* Remove the endpoint and its monitor, if any */
|
|
g_object_set_qdata (G_OBJECT (proxy), monitor_quark (), NULL);
|
|
g_object_set_qdata (G_OBJECT (proxy), endpoint_quark (), NULL);
|
|
}
|
|
|
|
static void
|
|
remove_all_nodes (const GValue *item, gpointer data)
|
|
{
|
|
WpProxy *p = g_value_get_object (item);
|
|
on_node_removed (NULL, p, NULL);
|
|
}
|
|
|
|
static void
|
|
wp_config_endpoint_context_activate (WpPlugin * plugin)
|
|
{
|
|
WpConfigEndpointContext *self = WP_CONFIG_ENDPOINT_CONTEXT (plugin);
|
|
g_autoptr (WpCore) core = wp_plugin_get_core (plugin);
|
|
g_return_if_fail (core);
|
|
g_autoptr (WpConfiguration) config = wp_configuration_get_instance (core);
|
|
g_return_if_fail (config);
|
|
|
|
/* Add the endpoint and streams parsers */
|
|
wp_configuration_add_extension (config, WP_PARSER_ENDPOINT_EXTENSION,
|
|
WP_TYPE_PARSER_ENDPOINT);
|
|
wp_configuration_add_extension (config, WP_PARSER_STREAMS_EXTENSION,
|
|
WP_TYPE_PARSER_STREAMS);
|
|
|
|
/* Parse the files */
|
|
wp_configuration_reload (config, WP_PARSER_ENDPOINT_EXTENSION);
|
|
wp_configuration_reload (config, WP_PARSER_STREAMS_EXTENSION);
|
|
|
|
/* Install the session object manager */
|
|
self->sessions_om = wp_object_manager_new ();
|
|
wp_object_manager_add_interest (self->sessions_om, WP_TYPE_SESSION, NULL);
|
|
wp_object_manager_request_proxy_features (self->sessions_om, WP_TYPE_SESSION,
|
|
WP_SESSION_FEATURES_STANDARD);
|
|
wp_core_install_object_manager (core, self->sessions_om);
|
|
|
|
/* Handle node-added signal and install the nodes object manager */
|
|
self->nodes_om = wp_object_manager_new ();
|
|
wp_object_manager_add_interest (self->nodes_om, WP_TYPE_NODE, NULL);
|
|
wp_object_manager_request_proxy_features (self->nodes_om, WP_TYPE_NODE,
|
|
WP_PROXY_FEATURES_STANDARD);
|
|
g_signal_connect_object (self->nodes_om, "object-added",
|
|
G_CALLBACK (on_node_added), self, 0);
|
|
g_signal_connect_object (self->nodes_om, "object-removed",
|
|
G_CALLBACK (on_node_removed), self, 0);
|
|
wp_core_install_object_manager (core, self->nodes_om);
|
|
}
|
|
|
|
static void
|
|
wp_config_endpoint_context_deactivate (WpPlugin *plugin)
|
|
{
|
|
WpConfigEndpointContext *self = WP_CONFIG_ENDPOINT_CONTEXT (plugin);
|
|
|
|
g_autoptr (WpCore) core = wp_plugin_get_core (plugin);
|
|
if (core) {
|
|
g_autoptr (WpConfiguration) config = wp_configuration_get_instance (core);
|
|
wp_configuration_remove_extension (config, WP_PARSER_ENDPOINT_EXTENSION);
|
|
wp_configuration_remove_extension (config, WP_PARSER_STREAMS_EXTENSION);
|
|
}
|
|
|
|
{
|
|
g_autoptr (WpIterator) it = wp_object_manager_iterate (self->nodes_om);
|
|
wp_iterator_foreach (it, remove_all_nodes, NULL);
|
|
}
|
|
g_clear_object (&self->sessions_om);
|
|
g_clear_object (&self->nodes_om);
|
|
}
|
|
|
|
static void
|
|
wp_config_endpoint_context_init (WpConfigEndpointContext *self)
|
|
{
|
|
}
|
|
|
|
static void
|
|
wp_config_endpoint_context_class_init (WpConfigEndpointContextClass *klass)
|
|
{
|
|
WpPluginClass *plugin_class = (WpPluginClass *) klass;
|
|
|
|
plugin_class->activate = wp_config_endpoint_context_activate;
|
|
plugin_class->deactivate = wp_config_endpoint_context_deactivate;
|
|
|
|
/* Signals */
|
|
signals[SIGNAL_ENDPOINT_CREATED] = g_signal_new ("endpoint-created",
|
|
G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL,
|
|
G_TYPE_NONE, 1, WP_TYPE_SESSION_ITEM);
|
|
}
|
|
|
|
WpConfigEndpointContext *
|
|
wp_config_endpoint_context_new (WpModule * module)
|
|
{
|
|
return g_object_new (wp_config_endpoint_context_get_type (),
|
|
"module", module,
|
|
NULL);
|
|
}
|