modules: add config endpoint module

This commit is contained in:
Julian Bouzas
2019-12-11 14:50:45 -05:00
parent 50f06baf3a
commit acda80d77c
20 changed files with 1478 additions and 20 deletions

View File

@@ -43,26 +43,17 @@ shared_library(
)
shared_library(
'wireplumber-module-pw-audio-client',
'wireplumber-module-config-endpoint',
[
'module-pw-audio-client.c',
'module-config-endpoint/parser-endpoint.c',
'module-config-endpoint/parser-streams.c',
'module-config-endpoint/context.c',
'module-config-endpoint.c',
],
c_args : [common_c_args, '-DG_LOG_DOMAIN="m-pw-audio-client"'],
c_args : [common_c_args, '-DG_LOG_DOMAIN="m-config-endpoint"'],
install : true,
install_dir : wireplumber_module_dir,
dependencies : [wp_dep, pipewire_dep],
)
shared_library(
'wireplumber-module-pw-alsa-udev',
[
'module-pw-alsa-udev.c',
],
c_args : [common_c_args, '-DG_LOG_DOMAIN="m-pw-alsa-udev"'],
install : true,
install_dir : wireplumber_module_dir,
dependencies : [wp_dep, pipewire_dep],
dependencies : [wp_dep, wptoml_dep, pipewire_dep],
)
shared_library(

View File

@@ -0,0 +1,39 @@
/* 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 "module-config-endpoint/context.h"
struct module_data
{
WpConfigEndpointContext *ctx;
};
static void
module_destroy (gpointer d)
{
struct module_data *data = d;
g_clear_object (&data->ctx);
g_slice_free (struct module_data, data);
}
void
wireplumber__module_init (WpModule * module, WpCore * core, GVariant * args)
{
struct module_data *data;
/* Create the module data */
data = g_slice_new0 (struct module_data);
data->ctx = wp_config_endpoint_context_new (core);
/* Set the module destroy callback */
wp_module_set_destroy_callback (module, module_destroy, data);
}

View File

@@ -0,0 +1,296 @@
/* 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"
struct _WpConfigEndpointContext
{
GObject parent;
/* Props */
GWeakRef core;
WpObjectManager *om;
GHashTable *registered_endpoints;
};
enum {
PROP_0,
PROP_CORE,
};
enum {
SIGNAL_ENDPOINT_CREATED,
N_SIGNALS
};
static guint signals[N_SIGNALS];
G_DEFINE_TYPE (WpConfigEndpointContext, wp_config_endpoint_context,
G_TYPE_OBJECT)
static void
on_endpoint_created (GObject *initable, GAsyncResult *res, gpointer d)
{
WpConfigEndpointContext *self = d;
g_autoptr (WpBaseEndpoint) endpoint = NULL;
g_autoptr (WpProxy) proxy = NULL;
guint global_id = 0;
GError *error = NULL;
/* Get the endpoint */
endpoint = wp_base_endpoint_new_finish (initable, res, &error);
if (error) {
g_warning ("Failed to create endpoint: %s", error->message);
return;
}
/* Get the endpoint global id */
g_object_get (endpoint, "proxy-node", &proxy, NULL);
global_id = wp_proxy_get_global_id (proxy);
/* Register the endpoint and add it to the table */
wp_base_endpoint_register (endpoint);
g_hash_table_insert (self->registered_endpoints, GUINT_TO_POINTER (global_id),
g_object_ref (endpoint));
/* Emit the endpoint-created signal */
g_signal_emit (self, signals[SIGNAL_ENDPOINT_CREATED], 0, endpoint);
}
static GVariant *
create_streams_variant (WpConfiguration *config, const char *streams)
{
g_autoptr (WpConfigParser) parser = NULL;
const struct WpParserStreamsData *streams_data = NULL;
g_autoptr (GVariantBuilder) ba = NULL;
if (!streams || !config)
return NULL;
/* Get the streams parser */
parser = wp_configuration_get_parser (config, WP_PARSER_STREAMS_EXTENSION);
if (!parser)
return NULL;
/* Get the streams data */
streams_data = wp_config_parser_get_matched_data (parser, (gpointer)streams);
if (!streams_data || streams_data->n_streams <= 0)
return NULL;
/* Build the variant array with the stream name and priority */
ba = g_variant_builder_new (G_VARIANT_TYPE ("a(su)"));
g_variant_builder_init (ba, G_VARIANT_TYPE_ARRAY);
for (guint i = 0; i < streams_data->n_streams; i++)
g_variant_builder_add (ba, "(su)", streams_data->streams[i].name,
streams_data->streams[i].priority);
return g_variant_new ("a(su)", ba);
}
static void
on_node_added (WpObjectManager *om, WpProxy *proxy, gpointer d)
{
WpConfigEndpointContext *self = d;
g_autoptr (WpCore) core = g_weak_ref_get (&self->core);
g_autoptr (WpConfiguration) config = wp_configuration_get_instance (core);
WpProxyNode *proxy_node = WP_PROXY_NODE (proxy);
g_autoptr (WpProperties) props = wp_proxy_node_get_properties (proxy_node);
g_autoptr (WpConfigParser) parser = NULL;
const struct WpParserEndpointData *endpoint_data = NULL;
GVariantBuilder b;
g_autoptr (GVariant) endpoint_props = NULL;
const char *media_class = NULL, *name = NULL;
g_autoptr (GVariant) streams_variant = NULL;
/* Get the linked and ep streams data */
parser = wp_configuration_get_parser (config, WP_PARSER_ENDPOINT_EXTENSION);
endpoint_data = wp_config_parser_get_matched_data (parser, proxy_node);
if (!endpoint_data)
return;
/* Set the name if it is null */
name = endpoint_data->e.name;
if (!name)
name = wp_properties_get (props, PW_KEY_NODE_NAME);
/* Set the media class if it is null */
media_class = endpoint_data->e.media_class;
if (!media_class)
media_class = wp_properties_get (props, PW_KEY_MEDIA_CLASS);
/* Create the streams variant */
streams_variant = create_streams_variant (config, endpoint_data->e.streams);
/* Set the properties */
g_variant_builder_init (&b, G_VARIANT_TYPE_VARDICT);
g_variant_builder_add (&b, "{sv}",
"name", g_variant_new_take_string (g_strdup_printf ("%s", name)));
g_variant_builder_add (&b, "{sv}",
"media-class", g_variant_new_string (media_class));
g_variant_builder_add (&b, "{sv}",
"direction", g_variant_new_uint32 (endpoint_data->e.direction));
g_variant_builder_add (&b, "{sv}",
"proxy-node", g_variant_new_uint64 ((guint64) proxy));
if (streams_variant)
g_variant_builder_add (&b, "{sv}", "streams",
g_steal_pointer (&streams_variant));
endpoint_props = g_variant_builder_end (&b);
/* Create the endpoint async */
wp_factory_make (core, endpoint_data->e.type, WP_TYPE_BASE_ENDPOINT,
endpoint_props, on_endpoint_created, self);
}
static void
on_node_removed (WpObjectManager *om, WpProxy *proxy, gpointer d)
{
WpConfigEndpointContext *self = d;
WpBaseEndpoint *endpoint = NULL;
guint32 id = wp_proxy_get_global_id (proxy);
/* Get the endpoint */
endpoint = g_hash_table_lookup (self->registered_endpoints,
GUINT_TO_POINTER(id));
if (!endpoint)
return;
/* Unregister the endpoint and remove it from the table */
wp_base_endpoint_unregister (endpoint);
g_hash_table_remove (self->registered_endpoints, GUINT_TO_POINTER(id));
}
static void
wp_config_endpoint_context_constructed (GObject * object)
{
WpConfigEndpointContext *self = WP_CONFIG_ENDPOINT_CONTEXT (object);
g_autoptr (WpCore) core = g_weak_ref_get (&self->core);
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 object manager */
wp_core_install_object_manager (core, self->om);
G_OBJECT_CLASS (wp_config_endpoint_context_parent_class)->constructed (object);
}
static void
wp_config_endpoint_context_set_property (GObject * object, guint property_id,
const GValue * value, GParamSpec * pspec)
{
WpConfigEndpointContext *self = WP_CONFIG_ENDPOINT_CONTEXT (object);
switch (property_id) {
case PROP_CORE:
g_weak_ref_set (&self->core, g_value_get_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
wp_config_endpoint_context_get_property (GObject * object, guint property_id,
GValue * value, GParamSpec * pspec)
{
WpConfigEndpointContext *self = WP_CONFIG_ENDPOINT_CONTEXT (object);
switch (property_id) {
case PROP_CORE:
g_value_take_object (value, g_weak_ref_get (&self->core));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
wp_config_endpoint_context_finalize (GObject *object)
{
WpConfigEndpointContext *self = WP_CONFIG_ENDPOINT_CONTEXT (object);
g_autoptr (WpCore) core = g_weak_ref_get (&self->core);
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_weak_ref_clear (&self->core);
g_clear_object (&self->om);
g_clear_pointer (&self->registered_endpoints, g_hash_table_unref);
G_OBJECT_CLASS (wp_config_endpoint_context_parent_class)->finalize (object);
}
static void
wp_config_endpoint_context_init (WpConfigEndpointContext *self)
{
self->om = wp_object_manager_new ();
self->registered_endpoints = g_hash_table_new_full (g_direct_hash,
g_direct_equal, NULL, (GDestroyNotify) g_object_unref);
/* Only handle augmented nodes with info set */
wp_object_manager_add_proxy_interest (self->om, PW_TYPE_INTERFACE_Node, NULL,
WP_PROXY_FEATURE_INFO);
/* Register the global added/removed callbacks */
g_signal_connect(self->om, "object-added",
(GCallback) on_node_added, self);
g_signal_connect(self->om, "object-removed",
(GCallback) on_node_removed, self);
}
static void
wp_config_endpoint_context_class_init (WpConfigEndpointContextClass *klass)
{
GObjectClass *object_class = (GObjectClass *) klass;
object_class->constructed = wp_config_endpoint_context_constructed;
object_class->finalize = wp_config_endpoint_context_finalize;
object_class->set_property = wp_config_endpoint_context_set_property;
object_class->get_property = wp_config_endpoint_context_get_property;
/* Properties */
g_object_class_install_property (object_class, PROP_CORE,
g_param_spec_object ("core", "core", "The wireplumber core",
WP_TYPE_CORE,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
/* 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_ENDPOINT);
}
WpConfigEndpointContext *
wp_config_endpoint_context_new (WpCore *core)
{
return g_object_new (wp_config_endpoint_context_get_type (),
"core", core,
NULL);
}

View File

@@ -0,0 +1,24 @@
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author Julian Bouzas <julian.bouzas@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
#ifndef __WIREPLUMBER_CONFIG_ENDPOINT_CONTEXT_H__
#define __WIREPLUMBER_CONFIG_ENDPOINT_CONTEXT_H__
#include <wp/wp.h>
G_BEGIN_DECLS
#define WP_TYPE_CONFIG_ENDPOINT_CONTEXT (wp_config_endpoint_context_get_type ())
G_DECLARE_FINAL_TYPE (WpConfigEndpointContext, wp_config_endpoint_context,
WP, CONFIG_ENDPOINT_CONTEXT, GObject);
WpConfigEndpointContext * wp_config_endpoint_context_new (WpCore *core);
G_END_DECLS
#endif

View File

@@ -0,0 +1,272 @@
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author Julian Bouzas <julian.bouzas@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
#include <wptoml/wptoml.h>
#include <pipewire/pipewire.h>
#include "parser-endpoint.h"
struct _WpParserEndpoint
{
GObject parent;
GPtrArray *datas;
};
static void wp_parser_endpoint_config_parser_init (gpointer iface,
gpointer iface_data);
G_DEFINE_TYPE_WITH_CODE (WpParserEndpoint, wp_parser_endpoint,
G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (WP_TYPE_CONFIG_PARSER,
wp_parser_endpoint_config_parser_init))
static void
wp_parser_endpoint_data_destroy (gpointer p)
{
struct WpParserEndpointData *data = p;
/* Free the strings */
g_clear_pointer (&data->mn.props, wp_properties_unref);
g_clear_pointer (&data->e.name, g_free);
g_clear_pointer (&data->e.media_class, g_free);
g_clear_pointer (&data->e.props, wp_properties_unref);
g_clear_pointer (&data->e.type, g_free);
g_clear_pointer (&data->e.streams, g_free);
g_slice_free (struct WpParserEndpointData, data);
}
static void
parse_properties_for_each (const WpTomlTable *table, gpointer user_data)
{
WpProperties *props = user_data;
g_return_if_fail (props);
/* Skip unparsed tables */
if (!table)
return;
/* Parse the name and value */
g_autofree gchar *name = wp_toml_table_get_string (table, "name");
g_autofree gchar *value = wp_toml_table_get_string (table, "value");
/* Set the property */
if (name && value)
wp_properties_set (props, name, value);
}
static WpProperties *
parse_properties (WpTomlTable *table, const char *name)
{
WpProperties *props = wp_properties_new_empty ();
g_autoptr (WpTomlTableArray) properties = NULL;
properties = wp_toml_table_get_array_table (table, name);
if (properties)
wp_toml_table_array_for_each (properties, parse_properties_for_each, props);
return props;
}
static guint
parse_endpoint_direction (const char *direction)
{
if (g_strcmp0 (direction, "input") == 0)
return PW_DIRECTION_INPUT;
else if (g_strcmp0 (direction, "output") == 0)
return PW_DIRECTION_OUTPUT;
g_return_val_if_reached (PW_DIRECTION_INPUT);
}
static struct WpParserEndpointData *
wp_parser_endpoint_data_new (const gchar *location)
{
g_autoptr (WpTomlFile) file = NULL;
g_autoptr (WpTomlTable) table = NULL, mn = NULL, e = NULL;
g_autoptr (WpTomlArray) streams = NULL;
struct WpParserEndpointData *res = NULL;
g_autofree char *direction = NULL;
/* File format:
* ------------
* [match-node]
* priority (uint32)
* properties (WpProperties)
*
* [endpoint]
* name (string)
* media_class (string)
* direction (string)
* priority (uint32)
* properties (WpProperties)
* type (string)
* streams (string)
*/
/* Get the TOML file */
file = wp_toml_file_new (location);
if (!file)
return NULL;
/* Get the file table */
table = wp_toml_file_get_table (file);
if (!table)
return NULL;
/* Create the endpoint data */
res = g_slice_new0(struct WpParserEndpointData);
/* Get the match-node table */
mn = wp_toml_table_get_table (table, "match-node");
if (!mn)
goto error;
/* Get the priority from the match-node table */
res->mn.priority = 0;
wp_toml_table_get_uint32 (mn, "priority", &res->mn.priority);
/* Get the match node properties */
res->mn.props = parse_properties (mn, "properties");
/* Get the endpoint table */
e = wp_toml_table_get_table (table, "endpoint");
if (!e)
goto error;
/* Get the name from the endpoint table */
res->e.name = wp_toml_table_get_string (e, "name");
/* Get the media class from the endpoint table */
res->e.media_class = wp_toml_table_get_string (e, "media_class");
/* Get the direction from the endpoint table */
direction = wp_toml_table_get_string (e, "direction");
if (!direction)
goto error;
res->e.direction = parse_endpoint_direction (direction);
/* Get the priority from the endpoint table */
res->mn.priority = 0;
wp_toml_table_get_uint32 (e, "priority", &res->e.priority);
/* Get the endpoint properties */
res->e.props = parse_properties (e, "properties");
/* Get the endpoint type */
res->e.type = wp_toml_table_get_string (e, "type");
if (!res->e.type)
goto error;
/* Get the endpoint streams */
res->e.streams = wp_toml_table_get_string (e, "streams");
return res;
error:
g_clear_pointer (&res, wp_parser_endpoint_data_destroy);
return NULL;
}
static gint
compare_datas_func (gconstpointer a, gconstpointer b)
{
struct WpParserEndpointData *da = *(struct WpParserEndpointData *const *)a;
struct WpParserEndpointData *db = *(struct WpParserEndpointData *const *)b;
return db->mn.priority - da->mn.priority;
}
static gboolean
wp_parser_endpoint_add_file (WpConfigParser *parser,
const gchar *name)
{
WpParserEndpoint *self = WP_PARSER_ENDPOINT (parser);
struct WpParserEndpointData *data;
/* Parse the file */
data = wp_parser_endpoint_data_new (name);
if (!data) {
g_warning ("Failed to parse configuration file '%s'", name);
return FALSE;
}
/* Add the data to the array */
g_ptr_array_add(self->datas, data);
/* Sort the array by priority */
g_ptr_array_sort(self->datas, compare_datas_func);
return TRUE;
}
static gconstpointer
wp_parser_endpoint_get_matched_data (WpConfigParser *parser, gpointer data)
{
WpParserEndpoint *self = WP_PARSER_ENDPOINT (parser);
WpProxyNode *node = WP_PROXY_NODE (data);
const struct WpParserEndpointData *d = NULL;
g_autoptr (WpProperties) props = NULL;
g_return_val_if_fail (node, NULL);
props = wp_proxy_node_get_properties (node);
/* Find the first data that matches node */
for (guint i = 0; i < self->datas->len; i++) {
d = g_ptr_array_index(self->datas, i);
if (wp_properties_matches (props, d->mn.props))
return d;
}
return NULL;
}
static void
wp_parser_endpoint_reset (WpConfigParser *parser)
{
WpParserEndpoint *self = WP_PARSER_ENDPOINT (parser);
g_ptr_array_set_size (self->datas, 0);
}
static void
wp_parser_endpoint_config_parser_init (gpointer iface, gpointer iface_data)
{
WpConfigParserInterface *cp_iface = iface;
cp_iface->add_file = wp_parser_endpoint_add_file;
cp_iface->get_matched_data = wp_parser_endpoint_get_matched_data;
cp_iface->reset = wp_parser_endpoint_reset;
}
static void
wp_parser_endpoint_finalize (GObject * object)
{
WpParserEndpoint *self = WP_PARSER_ENDPOINT (object);
g_clear_pointer (&self->datas, g_ptr_array_unref);
G_OBJECT_CLASS (wp_parser_endpoint_parent_class)->finalize (object);
}
static void
wp_parser_endpoint_init (WpParserEndpoint * self)
{
self->datas = g_ptr_array_new_with_free_func(
wp_parser_endpoint_data_destroy);
}
static void
wp_parser_endpoint_class_init (WpParserEndpointClass * klass)
{
GObjectClass *object_class = (GObjectClass *) klass;
object_class->finalize = wp_parser_endpoint_finalize;
}

View File

@@ -0,0 +1,40 @@
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author Julian Bouzas <julian.bouzas@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
#ifndef __WIREPLUMBER_PARSER_ENDPOINT_H__
#define __WIREPLUMBER_PARSER_ENDPOINT_H__
#include <wp/wp.h>
G_BEGIN_DECLS
#define WP_PARSER_ENDPOINT_EXTENSION "endpoint"
struct WpParserEndpointData {
struct MatchNode {
guint priority;
WpProperties *props;
} mn;
struct Endpoint {
char *name;
char *media_class;
guint direction;
guint priority;
WpProperties *props;
char *type;
char *streams;
} e;
};
#define WP_TYPE_PARSER_ENDPOINT (wp_parser_endpoint_get_type ())
G_DECLARE_FINAL_TYPE (WpParserEndpoint, wp_parser_endpoint,
WP, PARSER_ENDPOINT, GObject)
G_END_DECLS
#endif

View File

@@ -0,0 +1,223 @@
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author Julian Bouzas <julian.bouzas@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
#include <wptoml/wptoml.h>
#include <pipewire/pipewire.h>
#include "parser-streams.h"
struct _WpParserStreams
{
GObject parent;
GPtrArray *datas;
};
const struct WpParserStreamsStreamData *
wp_parser_streams_find_stream (const struct WpParserStreamsData *data,
const char *name)
{
for (guint i = 0; i < data->n_streams; i++) {
const struct WpParserStreamsStreamData *s = data->streams + i;
if (g_strcmp0 (s->name, name) == 0)
return s;
}
return NULL;
}
const struct WpParserStreamsStreamData *
wp_parser_streams_get_lowest_stream (const struct WpParserStreamsData *data)
{
const struct WpParserStreamsStreamData *res = NULL;
guint lowest = G_MAXUINT;
for (guint i = 0; i < data->n_streams; i++) {
const struct WpParserStreamsStreamData *s = data->streams + i;
if (s->priority < lowest) {
lowest = s->priority;
res = s;
}
}
return res;
}
static void wp_parser_streams_config_parser_init (gpointer iface,
gpointer iface_data);
G_DEFINE_TYPE_WITH_CODE (WpParserStreams, wp_parser_streams,
G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (WP_TYPE_CONFIG_PARSER,
wp_parser_streams_config_parser_init))
static void
wp_parser_streams_data_destroy (gpointer p)
{
struct WpParserStreamsData *data = p;
/* Clear the location */
g_clear_pointer (&data->location, g_free);
/* Clear the streams */
for (guint i = 0; i < data->n_streams; i++) {
struct WpParserStreamsStreamData *s = data->streams + i;
g_clear_pointer (&s->name, g_free);
}
data->n_streams = 0;
g_slice_free (struct WpParserStreamsData, data);
}
static void
streams_for_each (const WpTomlTable *table, gpointer user_data)
{
struct WpParserStreamsData *data = user_data;
struct WpParserStreamsStreamData *stream = NULL;
g_return_if_fail (data);
/* Make sure we don't parse more MAX_STREAMS streams */
if (data->n_streams >= MAX_STREAMS)
return;
/* Skip unparsed tables */
if (!table)
return;
/* Parse the mandatory name */
stream = data->streams + data->n_streams;
stream->name = wp_toml_table_get_string (table, "name");
if (!stream->name)
return;
/* Parse the optional priority */
stream->priority = 0;
wp_toml_table_get_uint32 (table, "priority", &stream->priority);
/* Increment the number of streams */
data->n_streams++;
}
static struct WpParserStreamsData *
wp_parser_streams_data_new (const gchar *location)
{
g_autoptr (WpTomlFile) file = NULL;
g_autoptr (WpTomlTable) table = NULL;
g_autoptr (WpTomlTableArray) streams = NULL;
struct WpParserStreamsData *res = NULL;
/* File format:
* ------------
* [[streams]]
* name (string)
* priority (uint32)
*/
/* Get the TOML file */
file = wp_toml_file_new (location);
if (!file)
return NULL;
/* Get the file table */
table = wp_toml_file_get_table (file);
if (!table)
return NULL;
/* Create the streams data */
res = g_slice_new0 (struct WpParserStreamsData);
/* Set the location */
res->location = g_strdup (location);
/* Parse the streams */
res->n_streams = 0;
streams = wp_toml_table_get_array_table (table, "streams");
if (streams)
wp_toml_table_array_for_each (streams, streams_for_each, res);
return res;
}
static gboolean
wp_parser_streams_add_file (WpConfigParser *parser,
const gchar *name)
{
WpParserStreams *self = WP_PARSER_STREAMS (parser);
struct WpParserStreamsData *data;
/* Parse the file */
data = wp_parser_streams_data_new (name);
if (!data) {
g_warning ("Failed to parse configuration file '%s'", name);
return FALSE;
}
/* Add the data to the array */
g_ptr_array_add(self->datas, data);
return TRUE;
}
static gconstpointer
wp_parser_streams_get_matched_data (WpConfigParser *parser, gpointer data)
{
WpParserStreams *self = WP_PARSER_STREAMS (parser);
const char *location = data;
const struct WpParserStreamsData *d = NULL;
/* Find the first data that matches location */
for (guint i = 0; i < self->datas->len; i++) {
d = g_ptr_array_index(self->datas, i);
if (g_strrstr (d->location, location))
return d;
}
return NULL;
}
static void
wp_parser_streams_reset (WpConfigParser *parser)
{
WpParserStreams *self = WP_PARSER_STREAMS (parser);
g_ptr_array_set_size (self->datas, 0);
}
static void
wp_parser_streams_config_parser_init (gpointer iface,
gpointer iface_data)
{
WpConfigParserInterface *cp_iface = iface;
cp_iface->add_file = wp_parser_streams_add_file;
cp_iface->get_matched_data = wp_parser_streams_get_matched_data;
cp_iface->reset = wp_parser_streams_reset;
}
static void
wp_parser_streams_finalize (GObject * object)
{
WpParserStreams *self = WP_PARSER_STREAMS (object);
g_clear_pointer (&self->datas, g_ptr_array_unref);
G_OBJECT_CLASS (wp_parser_streams_parent_class)->finalize (object);
}
static void
wp_parser_streams_init (WpParserStreams * self)
{
self->datas = g_ptr_array_new_with_free_func (wp_parser_streams_data_destroy);
}
static void
wp_parser_streams_class_init (WpParserStreamsClass * klass)
{
GObjectClass *object_class = (GObjectClass *) klass;
object_class->finalize = wp_parser_streams_finalize;
}

View File

@@ -0,0 +1,44 @@
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author Julian Bouzas <julian.bouzas@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
#ifndef __WIREPLUMBER_PARSER_STREAMS_H__
#define __WIREPLUMBER_PARSER_STREAMS_H__
#include <wp/wp.h>
G_BEGIN_DECLS
#define WP_PARSER_STREAMS_EXTENSION "streams"
/* For simplicity, we limit the number of streams */
#define MAX_STREAMS 32
struct WpParserStreamsStreamData {
char *name;
guint priority;
};
struct WpParserStreamsData {
char *location;
struct WpParserStreamsStreamData streams[MAX_STREAMS];
guint n_streams;
};
/* Helpers */
const struct WpParserStreamsStreamData *wp_parser_streams_find_stream (
const struct WpParserStreamsData *data, const char *name);
const struct WpParserStreamsStreamData *wp_parser_streams_get_lowest_stream (
const struct WpParserStreamsData *data);
#define WP_TYPE_PARSER_STREAMS (wp_parser_streams_get_type ())
G_DECLARE_FINAL_TYPE (WpParserStreams, wp_parser_streams,
WP, PARSER_STREAMS, GObject);
G_END_DECLS
#endif

View File

@@ -298,6 +298,7 @@ on_audio_adapter_created(GObject *initable, GAsyncResult *res,
GVariantDict d;
GVariantIter iter;
const gchar *stream;
guint priority;
int i;
/* Get the proxy adapter */
@@ -323,7 +324,7 @@ on_audio_adapter_created(GObject *initable, GAsyncResult *res,
/* Create the audio converters */
g_variant_iter_init (&iter, self->streams);
for (i = 0; g_variant_iter_next (&iter, "&s", &stream); i++) {
for (i = 0; g_variant_iter_next (&iter, "(&su)", &stream, &priority); i++) {
wp_audio_convert_new (WP_BASE_ENDPOINT(self), i, stream, direction,
self->adapter, format, on_audio_convert_created, self);
@@ -331,7 +332,9 @@ on_audio_adapter_created(GObject *initable, GAsyncResult *res,
g_variant_dict_init (&d, NULL);
g_variant_dict_insert (&d, "id", "u", i);
g_variant_dict_insert (&d, "name", "s", stream);
wp_base_endpoint_register_stream (WP_BASE_ENDPOINT (self), g_variant_dict_end (&d));
g_variant_dict_insert (&d, "priority", "u", priority);
wp_base_endpoint_register_stream (WP_BASE_ENDPOINT (self),
g_variant_dict_end (&d));
}
self->stream_count = i;
}
@@ -478,7 +481,7 @@ endpoint_class_init (WpPwAudioSoftdspEndpointClass * klass)
g_object_class_install_property (object_class, PROP_STREAMS,
g_param_spec_variant ("streams", "streams",
"The stream names for the streams to create",
G_VARIANT_TYPE ("as"), NULL,
G_VARIANT_TYPE ("a(su)"), NULL,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_ROLE,
@@ -513,7 +516,7 @@ audio_softdsp_endpoint_factory (WpFactory * factory, GType type, GVariant * prop
if (!g_variant_lookup (properties, "proxy-node", "t", &node))
return;
streams = g_variant_lookup_value (properties, "streams",
G_VARIANT_TYPE ("as"));
G_VARIANT_TYPE ("a(su)"));
/* Create and return the softdsp endpoint object */
g_async_initable_new_async (

View File

@@ -31,5 +31,8 @@ load-module C libwireplumber-module-monitor {
"factory": <"api.v4l2.enum.udev">
}
# Implements endpoint creation based on TOML configuration files
load-module C libwireplumber-module-config-endpoint
# Implements linking clients to devices based on TOML configuration files
load-module C libwireplumber-module-config-policy

View File

@@ -0,0 +1,10 @@
[match-node]
priotity = 0
properties = [
{ name = "media.class", value = "Audio/Sink" },
]
[endpoint]
direction = "input"
type = "pw-audio-softdsp-endpoint"
streams = "default.streams"

View File

@@ -0,0 +1,10 @@
[match-node]
priotity = 0
properties = [
{ name = "media.class", value = "Audio/Source" },
]
[endpoint]
direction = "output"
type = "pw-audio-softdsp-endpoint"
streams = "default.streams"

View File

@@ -0,0 +1,9 @@
[match-node]
priotity = 0
properties = [
{ name = "media.class", value = "Stream/Input/Audio" },
]
[endpoint]
direction = "input"
type = "pw-audio-softdsp-endpoint"

View File

@@ -0,0 +1,9 @@
[match-node]
priotity = 0
properties = [
{ name = "media.class", value = "Stream/Output/Audio" },
]
[endpoint]
direction = "output"
type = "pw-audio-softdsp-endpoint"

View File

@@ -0,0 +1,183 @@
/* 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 "config-endpoint/endpoint-audiotestsrc.h"
#include "../wp/test-server.h"
#include "../../modules/module-config-endpoint/context.h"
typedef struct {
WpTestServer server;
GMutex mutex;
GCond cond;
gboolean created;
GThread *loop_thread;
GMainContext *context;
GMainLoop *loop;
WpCore *core;
} TestConfigEndpointFixture;
void wait_for_created (TestConfigEndpointFixture *self)
{
g_mutex_lock (&self->mutex);
while (!self->created)
g_cond_wait (&self->cond, &self->mutex);
self->created = FALSE;
g_mutex_unlock (&self->mutex);
}
void signal_created (TestConfigEndpointFixture *self)
{
g_mutex_lock (&self->mutex);
self->created = TRUE;
g_cond_signal (&self->cond);
g_mutex_unlock (&self->mutex);
}
static void
create_audiotestsrc (TestConfigEndpointFixture *self)
{
pw_thread_loop_lock (self->server.thread_loop);
pw_core_add_spa_lib (self->server.core, "audiotestsrc",
"audiotestsrc/libspa-audiotestsrc");
if (!pw_module_load (self->server.core, "libpipewire-module-spa-node",
"audiotestsrc", NULL)) {
pw_thread_loop_unlock (self->server.thread_loop);
g_test_skip ("audiotestsrc SPA plugin is not installed");
return;
}
pw_thread_loop_unlock (self->server.thread_loop);
}
static void
on_connected (WpCore *core, enum pw_remote_state new_state,
TestConfigEndpointFixture *self)
{
/* Register the wp-endpoint-audiotestsrc */
wp_factory_new (self->core, "wp-endpoint-audiotestsrc",
wp_endpoint_audiotestsrc_factory);
/* Create the audiotestsrc node */
create_audiotestsrc (self);
/* Signal we are done */
signal_created (self);
}
static void *
loop_thread_start (void *d)
{
TestConfigEndpointFixture *self = d;
/* Create the main loop using the default thread context */
self->context = g_main_context_get_thread_default ();
self->loop = g_main_loop_new (self->context, FALSE);
/* Create the server */
wp_test_server_setup (&self->server);
/* Create the core and connect to the server */
g_autoptr (WpProperties) props = NULL;
props = wp_properties_new (PW_KEY_REMOTE_NAME, self->server.name, NULL);
self->core = wp_core_new (self->context, props);
g_signal_connect (self->core, "remote-state-changed::connected",
(GCallback) on_connected, self);
wp_core_connect (self->core);
/* Run the main loop */
g_main_loop_run (self->loop);
/* Clean up */
g_clear_object (&self->core);
wp_test_server_teardown (&self->server);
g_clear_pointer (&self->loop, g_main_loop_unref);
return NULL;
}
static void
config_endpoint_setup (TestConfigEndpointFixture *self, gconstpointer data)
{
/* Data */
g_mutex_init (&self->mutex);
g_cond_init (&self->cond);
self->created = FALSE;
/* Initialize main loop, server and core in a thread */
self->loop_thread = g_thread_new("loop-thread", &loop_thread_start, self);
/* Wait for everything to be created */
wait_for_created (self);
}
static gboolean
loop_thread_stop (gpointer data)
{
TestConfigEndpointFixture *self = data;
g_main_loop_quit (self->loop);
return G_SOURCE_REMOVE;
}
static void
config_endpoint_teardown (TestConfigEndpointFixture *self, gconstpointer data)
{
/* Stop the main loop and wait until it is done */
g_autoptr (GSource) source = g_idle_source_new ();
g_source_set_callback (source, loop_thread_stop, self, NULL);
g_source_attach (source, self->context);
g_thread_join (self->loop_thread);
/* Data */
g_mutex_clear (&self->mutex);
g_cond_clear (&self->cond);
self->created = FALSE;
}
static void
on_audiotestsrc_created (WpConfigEndpointContext *ctx, WpEndpoint *ep,
TestConfigEndpointFixture *f)
{
g_assert_nonnull (ep);
signal_created (f);
}
static void
basic (TestConfigEndpointFixture *f, gconstpointer data)
{
/* Set the configuration path */
g_autoptr (WpConfiguration) config = wp_configuration_get_instance (f->core);
g_assert_nonnull (config);
wp_configuration_add_path (config, "config-endpoint/basic");
/* Create the context and handle the endpoint-created callback */
g_autoptr (WpConfigEndpointContext) ctx =
wp_config_endpoint_context_new (f->core);
g_assert_nonnull (ctx);
g_signal_connect (ctx, "endpoint-created",
(GCallback) on_audiotestsrc_created, f);
/* Wait for the endpoint to be created */
wait_for_created (f);
}
int
main (int argc, char *argv[])
{
g_test_init (&argc, &argv, NULL);
pw_init (NULL, NULL);
g_test_add ("/modules/config-endpoint/basic", TestConfigEndpointFixture,
NULL, config_endpoint_setup, basic, config_endpoint_teardown);
return g_test_run ();
}

View File

@@ -0,0 +1,9 @@
[match-node]
properties = [
{ name = "media.class", value = "Audio/Source" },
]
[endpoint]
direction = "output"
type = "wp-endpoint-audiotestsrc"
streams = "default.streams"

View File

@@ -0,0 +1,15 @@
[[streams]]
name = "0"
priority = 0
[[streams]]
name = "2"
priority = 2
[[streams]]
name = "3"
priority = 3
[[streams]]
name = "4"
priority = 4

View File

@@ -0,0 +1,239 @@
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author Julian Bouzas <julian.bouzas@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
#include <wp/wp.h>
#include "endpoint-audiotestsrc.h"
struct _WpEndpointAudiotestsrc
{
WpBaseEndpoint parent;
GTask *init_task;
/* Props */
WpProxyNode *proxy_node;
GVariant *streams;
};
enum {
PROP_0,
PROP_PROXY_NODE,
PROP_STREAMS,
};
static GAsyncInitableIface *wp_endpoint_audiotestsrc_parent_interface = NULL;
static void wp_endpoint_audiotestsrc_async_initable_init (gpointer iface,
gpointer iface_data);
G_DEFINE_TYPE_WITH_CODE (WpEndpointAudiotestsrc, wp_endpoint_audiotestsrc,
WP_TYPE_BASE_ENDPOINT,
G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE,
wp_endpoint_audiotestsrc_async_initable_init))
static WpProperties *
wp_endpoint_audiotestsrc_get_properties (WpBaseEndpoint * ep)
{
WpEndpointAudiotestsrc *self = WP_ENDPOINT_AUDIOTESTSRC (ep);
return wp_proxy_node_get_properties (self->proxy_node);
}
static const char *
wp_endpoint_audiotestsrc_get_role (WpBaseEndpoint * ep)
{
return NULL;
}
static gboolean
wp_endpoint_audiotestsrc_prepare_link (WpBaseEndpoint * ep, guint32 stream_id,
WpBaseEndpointLink * link, GVariant ** properties, GError ** error)
{
return TRUE;
}
static const char *
wp_endpoint_audiotestsrc_get_endpoint_link_factory (WpBaseEndpoint * ep)
{
return NULL;
}
static void
wp_endpoint_audiotestsrc_constructed (GObject * object)
{
WpEndpointAudiotestsrc *self = WP_ENDPOINT_AUDIOTESTSRC (object);
GVariantDict d;
GVariantIter iter;
const gchar *stream;
guint priority;
int i;
if (self->streams) {
g_variant_iter_init (&iter, self->streams);
for (i = 0; g_variant_iter_next (&iter, "(&su)", &stream, &priority); i++) {
g_variant_dict_init (&d, NULL);
g_variant_dict_insert (&d, "id", "u", i);
g_variant_dict_insert (&d, "name", "s", stream);
g_variant_dict_insert (&d, "priority", "u", priority);
wp_base_endpoint_register_stream (WP_BASE_ENDPOINT (self), g_variant_dict_end (&d));
}
}
G_OBJECT_CLASS (wp_endpoint_audiotestsrc_parent_class)->constructed (object);
}
static void
wp_endpoint_audiotestsrc_set_property (GObject * object, guint property_id,
const GValue * value, GParamSpec * pspec)
{
WpEndpointAudiotestsrc *self = WP_ENDPOINT_AUDIOTESTSRC (object);
switch (property_id) {
case PROP_PROXY_NODE:
self->proxy_node = g_value_dup_object (value);
break;
case PROP_STREAMS:
self->streams = g_value_dup_variant(value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
wp_endpoint_audiotestsrc_get_property (GObject * object, guint property_id,
GValue * value, GParamSpec * pspec)
{
WpEndpointAudiotestsrc *self = WP_ENDPOINT_AUDIOTESTSRC (object);
switch (property_id) {
case PROP_PROXY_NODE:
g_value_set_object (value, self->proxy_node);
break;
case PROP_STREAMS:
g_value_set_variant (value, self->streams);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
wp_endpoint_audiotestsrc_finalize (GObject * object)
{
WpEndpointAudiotestsrc *self = WP_ENDPOINT_AUDIOTESTSRC (object);
g_clear_object(&self->proxy_node);
g_clear_pointer(&self->streams, g_variant_unref);
G_OBJECT_CLASS (wp_endpoint_audiotestsrc_parent_class)->finalize (object);
}
static void
wp_endpoint_audiotestsrc_finish_creation (WpCore *core, GAsyncResult *res,
WpEndpointAudiotestsrc *self)
{
g_task_return_boolean (self->init_task, TRUE);
g_clear_object (&self->init_task);
}
static void
wp_endpoint_audiotestsrc_init_async (GAsyncInitable *initable, int io_priority,
GCancellable *cancellable, GAsyncReadyCallback callback, gpointer data)
{
WpEndpointAudiotestsrc *self = WP_ENDPOINT_AUDIOTESTSRC (initable);
self->init_task = g_task_new (initable, cancellable, callback, data);
wp_endpoint_audiotestsrc_parent_interface->init_async (initable, io_priority,
cancellable, callback, data);
g_autoptr (WpCore) core = wp_base_endpoint_get_core (WP_BASE_ENDPOINT(self));
if (core)
wp_core_sync (core, NULL,
(GAsyncReadyCallback) wp_endpoint_audiotestsrc_finish_creation, self);
}
static void
wp_endpoint_audiotestsrc_async_initable_init (gpointer iface,
gpointer iface_data)
{
GAsyncInitableIface *ai_iface = iface;
wp_endpoint_audiotestsrc_parent_interface =
g_type_interface_peek_parent (iface);
ai_iface->init_async = wp_endpoint_audiotestsrc_init_async;
}
static void
wp_endpoint_audiotestsrc_init (WpEndpointAudiotestsrc * self)
{
}
static void
wp_endpoint_audiotestsrc_class_init (WpEndpointAudiotestsrcClass * klass)
{
GObjectClass *object_class = (GObjectClass *) klass;
WpBaseEndpointClass *endpoint_class = (WpBaseEndpointClass *) klass;
object_class->constructed = wp_endpoint_audiotestsrc_constructed;
object_class->finalize = wp_endpoint_audiotestsrc_finalize;
object_class->set_property = wp_endpoint_audiotestsrc_set_property;
object_class->get_property = wp_endpoint_audiotestsrc_get_property;
endpoint_class->get_properties = wp_endpoint_audiotestsrc_get_properties;
endpoint_class->get_role = wp_endpoint_audiotestsrc_get_role;
endpoint_class->prepare_link = wp_endpoint_audiotestsrc_prepare_link;
endpoint_class->get_endpoint_link_factory =
wp_endpoint_audiotestsrc_get_endpoint_link_factory;
g_object_class_install_property (object_class, PROP_PROXY_NODE,
g_param_spec_object ("proxy-node", "proxy-node",
"The node this endpoint refers to", WP_TYPE_PROXY_NODE,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_STREAMS,
g_param_spec_variant ("streams", "streams",
"The stream names for the streams to register",
G_VARIANT_TYPE ("a(su)"), NULL,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
}
void
wp_endpoint_audiotestsrc_factory (WpFactory * factory, GType type,
GVariant * properties, GAsyncReadyCallback ready, gpointer data)
{
g_autoptr (WpCore) core = NULL;
const gchar *name, *media_class;
guint direction;
guint64 node;
g_autoptr (GVariant) streams = NULL;
core = wp_factory_get_core(factory);
g_return_if_fail (core);
if (!g_variant_lookup (properties, "name", "&s", &name))
return;
if (!g_variant_lookup (properties, "media-class", "&s", &media_class))
return;
if (!g_variant_lookup (properties, "direction", "u", &direction))
return;
if (!g_variant_lookup (properties, "proxy-node", "t", &node))
return;
streams = g_variant_lookup_value (properties, "streams",
G_VARIANT_TYPE ("a(su)"));
g_async_initable_new_async (wp_endpoint_audiotestsrc_get_type (),
G_PRIORITY_DEFAULT, NULL, ready, data,
"core", core,
"name", name,
"media-class", media_class,
"direction", direction,
"proxy-node", (gpointer) node,
"streams", streams,
NULL);
}

View File

@@ -0,0 +1,24 @@
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author Julian Bouzas <julian.bouzas@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
#ifndef __WIREPLUMBER_ENDPOINT_AUDIOTESTSRC_H__
#define __WIREPLUMBER_ENDPOINT_AUDIOTESTSRC_H__
#include <wp/wp.h>
G_BEGIN_DECLS
G_DECLARE_FINAL_TYPE (WpEndpointAudiotestsrc, wp_endpoint_audiotestsrc, WP,
ENDPOINT_AUDIOTESTSRC, WpBaseEndpoint)
void wp_endpoint_audiotestsrc_factory (WpFactory * factory, GType type,
GVariant * properties, GAsyncReadyCallback ready, gpointer data);
G_END_DECLS
#endif

View File

@@ -15,6 +15,21 @@ test(
env: common_env,
)
test(
'test-config-endpoint',
executable('test-config-endpoint',
[
'config-endpoint.c',
'config-endpoint/endpoint-audiotestsrc.c',
'../../modules/module-config-endpoint/parser-endpoint.c',
'../../modules/module-config-endpoint/parser-streams.c',
'../../modules/module-config-endpoint/context.c',
],
dependencies: common_deps + [wptoml_dep]),
env: common_env,
workdir : meson.current_source_dir(),
)
test(
'test-config-policy',
executable('test-config-policy',