diff --git a/modules/meson.build b/modules/meson.build index 96d5b843..3cc521af 100644 --- a/modules/meson.build +++ b/modules/meson.build @@ -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( diff --git a/modules/module-config-endpoint.c b/modules/module-config-endpoint.c new file mode 100644 index 00000000..a8f7e959 --- /dev/null +++ b/modules/module-config-endpoint.c @@ -0,0 +1,39 @@ +/* WirePlumber + * + * Copyright © 2019 Collabora Ltd. + * @author Julian Bouzas + * + * SPDX-License-Identifier: MIT + */ + +#include + +#include + +#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); +} diff --git a/modules/module-config-endpoint/context.c b/modules/module-config-endpoint/context.c new file mode 100644 index 00000000..202eaa5a --- /dev/null +++ b/modules/module-config-endpoint/context.c @@ -0,0 +1,296 @@ +/* WirePlumber + * + * Copyright © 2019 Collabora Ltd. + * @author Julian Bouzas + * + * SPDX-License-Identifier: MIT + */ + +#include + +#include + +#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); +} diff --git a/modules/module-config-endpoint/context.h b/modules/module-config-endpoint/context.h new file mode 100644 index 00000000..b09e98ab --- /dev/null +++ b/modules/module-config-endpoint/context.h @@ -0,0 +1,24 @@ +/* WirePlumber + * + * Copyright © 2019 Collabora Ltd. + * @author Julian Bouzas + * + * SPDX-License-Identifier: MIT + */ + +#ifndef __WIREPLUMBER_CONFIG_ENDPOINT_CONTEXT_H__ +#define __WIREPLUMBER_CONFIG_ENDPOINT_CONTEXT_H__ + +#include + +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 diff --git a/modules/module-config-endpoint/parser-endpoint.c b/modules/module-config-endpoint/parser-endpoint.c new file mode 100644 index 00000000..9477d8e3 --- /dev/null +++ b/modules/module-config-endpoint/parser-endpoint.c @@ -0,0 +1,272 @@ +/* WirePlumber + * + * Copyright © 2019 Collabora Ltd. + * @author Julian Bouzas + * + * SPDX-License-Identifier: MIT + */ + +#include + +#include + +#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; +} diff --git a/modules/module-config-endpoint/parser-endpoint.h b/modules/module-config-endpoint/parser-endpoint.h new file mode 100644 index 00000000..13028ac2 --- /dev/null +++ b/modules/module-config-endpoint/parser-endpoint.h @@ -0,0 +1,40 @@ +/* WirePlumber + * + * Copyright © 2019 Collabora Ltd. + * @author Julian Bouzas + * + * SPDX-License-Identifier: MIT + */ + +#ifndef __WIREPLUMBER_PARSER_ENDPOINT_H__ +#define __WIREPLUMBER_PARSER_ENDPOINT_H__ + +#include + +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 diff --git a/modules/module-config-endpoint/parser-streams.c b/modules/module-config-endpoint/parser-streams.c new file mode 100644 index 00000000..b1c718aa --- /dev/null +++ b/modules/module-config-endpoint/parser-streams.c @@ -0,0 +1,223 @@ +/* WirePlumber + * + * Copyright © 2019 Collabora Ltd. + * @author Julian Bouzas + * + * SPDX-License-Identifier: MIT + */ + +#include + +#include + +#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; +} diff --git a/modules/module-config-endpoint/parser-streams.h b/modules/module-config-endpoint/parser-streams.h new file mode 100644 index 00000000..75c6209b --- /dev/null +++ b/modules/module-config-endpoint/parser-streams.h @@ -0,0 +1,44 @@ +/* WirePlumber + * + * Copyright © 2019 Collabora Ltd. + * @author Julian Bouzas + * + * SPDX-License-Identifier: MIT + */ + +#ifndef __WIREPLUMBER_PARSER_STREAMS_H__ +#define __WIREPLUMBER_PARSER_STREAMS_H__ + +#include + +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 diff --git a/modules/module-pipewire/audio-softdsp-endpoint.c b/modules/module-pipewire/audio-softdsp-endpoint.c index b688f7af..2a0e3d1e 100644 --- a/modules/module-pipewire/audio-softdsp-endpoint.c +++ b/modules/module-pipewire/audio-softdsp-endpoint.c @@ -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 ( diff --git a/src/wireplumber.conf b/src/wireplumber.conf index 4ee82968..e16d6247 100644 --- a/src/wireplumber.conf +++ b/src/wireplumber.conf @@ -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 diff --git a/src/wireplumber/audio-sink.endpoint b/src/wireplumber/audio-sink.endpoint new file mode 100644 index 00000000..458555ca --- /dev/null +++ b/src/wireplumber/audio-sink.endpoint @@ -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" diff --git a/src/wireplumber/audio-source.endpoint b/src/wireplumber/audio-source.endpoint new file mode 100644 index 00000000..7b0e7ce3 --- /dev/null +++ b/src/wireplumber/audio-source.endpoint @@ -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" diff --git a/src/wireplumber/stream-input-audio.endpoint b/src/wireplumber/stream-input-audio.endpoint new file mode 100644 index 00000000..635b2a5c --- /dev/null +++ b/src/wireplumber/stream-input-audio.endpoint @@ -0,0 +1,9 @@ +[match-node] +priotity = 0 +properties = [ + { name = "media.class", value = "Stream/Input/Audio" }, +] + +[endpoint] +direction = "input" +type = "pw-audio-softdsp-endpoint" diff --git a/src/wireplumber/stream-output-audio.endpoint b/src/wireplumber/stream-output-audio.endpoint new file mode 100644 index 00000000..6170ee78 --- /dev/null +++ b/src/wireplumber/stream-output-audio.endpoint @@ -0,0 +1,9 @@ +[match-node] +priotity = 0 +properties = [ + { name = "media.class", value = "Stream/Output/Audio" }, +] + +[endpoint] +direction = "output" +type = "pw-audio-softdsp-endpoint" diff --git a/tests/modules/config-endpoint.c b/tests/modules/config-endpoint.c new file mode 100644 index 00000000..6d944a6c --- /dev/null +++ b/tests/modules/config-endpoint.c @@ -0,0 +1,183 @@ +/* WirePlumber + * + * Copyright © 2019 Collabora Ltd. + * @author Julian Bouzas + * + * SPDX-License-Identifier: MIT + */ + +#include + +#include + +#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 (); +} diff --git a/tests/modules/config-endpoint/basic/audio-source.endpoint b/tests/modules/config-endpoint/basic/audio-source.endpoint new file mode 100644 index 00000000..d842dac8 --- /dev/null +++ b/tests/modules/config-endpoint/basic/audio-source.endpoint @@ -0,0 +1,9 @@ +[match-node] +properties = [ + { name = "media.class", value = "Audio/Source" }, +] + +[endpoint] +direction = "output" +type = "wp-endpoint-audiotestsrc" +streams = "default.streams" diff --git a/tests/modules/config-endpoint/basic/default.streams b/tests/modules/config-endpoint/basic/default.streams new file mode 100644 index 00000000..50cd23c7 --- /dev/null +++ b/tests/modules/config-endpoint/basic/default.streams @@ -0,0 +1,15 @@ +[[streams]] +name = "0" +priority = 0 + +[[streams]] +name = "2" +priority = 2 + +[[streams]] +name = "3" +priority = 3 + +[[streams]] +name = "4" +priority = 4 diff --git a/tests/modules/config-endpoint/endpoint-audiotestsrc.c b/tests/modules/config-endpoint/endpoint-audiotestsrc.c new file mode 100644 index 00000000..5b950892 --- /dev/null +++ b/tests/modules/config-endpoint/endpoint-audiotestsrc.c @@ -0,0 +1,239 @@ +/* WirePlumber + * + * Copyright © 2019 Collabora Ltd. + * @author Julian Bouzas + * + * SPDX-License-Identifier: MIT + */ + +#include + +#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); +} diff --git a/tests/modules/config-endpoint/endpoint-audiotestsrc.h b/tests/modules/config-endpoint/endpoint-audiotestsrc.h new file mode 100644 index 00000000..5fa96dd7 --- /dev/null +++ b/tests/modules/config-endpoint/endpoint-audiotestsrc.h @@ -0,0 +1,24 @@ +/* WirePlumber + * + * Copyright © 2019 Collabora Ltd. + * @author Julian Bouzas + * + * SPDX-License-Identifier: MIT + */ + +#ifndef __WIREPLUMBER_ENDPOINT_AUDIOTESTSRC_H__ +#define __WIREPLUMBER_ENDPOINT_AUDIOTESTSRC_H__ + +#include + +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 diff --git a/tests/modules/meson.build b/tests/modules/meson.build index 6303163c..151b93c4 100644 --- a/tests/modules/meson.build +++ b/tests/modules/meson.build @@ -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',