diff --git a/modules/module-pipewire.c b/modules/module-pipewire.c index fbcf04fcc552ae97d10006cde8fdb68735c8a2d6..b7a9ebd882c58f53cb433cfc27f8ca93e4910ffa 100644 --- a/modules/module-pipewire.c +++ b/modules/module-pipewire.c @@ -14,6 +14,7 @@ #include <wp/wp.h> #include <pipewire/pipewire.h> +#include <spa/param/audio/format-utils.h> #include "module-pipewire/loop-source.h" @@ -50,6 +51,10 @@ registry_global (void * d, uint32_t id, uint32_t parent_id, g_autoptr (GVariant) endpoint_props = NULL; g_autoptr (WpCore) core = NULL; g_autoptr (WpEndpoint) endpoint = NULL; + struct spa_audio_info_raw format = { 0, }; + struct spa_pod *param; + struct spa_pod_builder pod_builder = { 0, }; + char buf[1024]; /* listen for client "Stream" nodes and create endpoints for them */ if (type == PW_TYPE_INTERFACE_Node && @@ -66,7 +71,26 @@ registry_global (void * d, uint32_t id, uint32_t parent_id, proxy = pw_registry_proxy_bind (data->registry_proxy, id, type, PW_VERSION_NODE, 0); + /* TODO: we need to get this from the EnumFormat event */ + format.format = SPA_AUDIO_FORMAT_F32P; + format.flags = 1; + format.rate = 48000; + format.channels = 2; + format.position[0] = 0; + format.position[1] = 0; + + /* Set the profile */ + spa_pod_builder_init(&pod_builder, buf, sizeof(buf)); + param = spa_format_audio_raw_build(&pod_builder, SPA_PARAM_Format, &format); + param = spa_pod_builder_add_object(&pod_builder, + SPA_TYPE_OBJECT_ParamProfile, SPA_PARAM_Profile, + SPA_PARAM_PROFILE_direction, SPA_POD_Id(PW_DIRECTION_OUTPUT), + SPA_PARAM_PROFILE_format, SPA_POD_Pod(param)); + pw_node_proxy_set_param((struct pw_node_proxy*)proxy, + SPA_PARAM_Profile, 0, param); + g_variant_builder_init (&b, G_VARIANT_TYPE_VARDICT); + g_variant_builder_add (&b, "{sv}", "node-id", g_variant_new_uint32 (id)); g_variant_builder_add (&b, "{sv}", "name", name ? g_variant_new_string (name) : g_variant_new_take_string (g_strdup_printf ("Stream %u", id))); diff --git a/modules/module-pipewire/port.h b/modules/module-pipewire/port.h new file mode 100644 index 0000000000000000000000000000000000000000..5fcf98bffabf2ba07c235f599223171ba8176ff7 --- /dev/null +++ b/modules/module-pipewire/port.h @@ -0,0 +1,30 @@ +/* WirePlumber + * + * Copyright © 2019 Collabora Ltd. + * @author Julian Bouzas <julian.bouzas@collabora.com> + * + * SPDX-License-Identifier: MIT + */ + +#include <wp/wp.h> +#include <pipewire/pipewire.h> +#include <spa/param/audio/format-utils.h> + +struct _WpPort +{ + struct spa_list l; + + /* Port proxy and listener */ + struct pw_proxy *proxy; + struct spa_hook listener; + + /* Port info */ + uint32_t id; + uint32_t parent_id; + enum pw_direction direction; + uint32_t media_type; + uint32_t media_subtype; + struct pw_port_info *info; + struct spa_audio_info_raw format; +}; +typedef struct _WpPort WpPort; \ No newline at end of file diff --git a/modules/module-pipewire/simple-endpoint-link.c b/modules/module-pipewire/simple-endpoint-link.c index 512120d52b5ac334b48a70660cf1e5793107cf1c..0db10c3bc8893dce1d6a1687c9ebd067c46ec13b 100644 --- a/modules/module-pipewire/simple-endpoint-link.c +++ b/modules/module-pipewire/simple-endpoint-link.c @@ -26,6 +26,9 @@ struct _WpPipewireSimpleEndpointLink { WpEndpointLink parent; + + /* The core proxy */ + struct pw_core_proxy *core_proxy; }; G_DECLARE_FINAL_TYPE (WpPipewireSimpleEndpointLink, @@ -40,10 +43,36 @@ simple_endpoint_link_init (WpPipewireSimpleEndpointLink * self) } static gboolean -simple_endpoint_link_create (WpEndpointLink * self, GVariant * src_data, +simple_endpoint_link_create (WpEndpointLink * epl, GVariant * src_data, GVariant * sink_data, GError ** error) { - /* TODO create pw_links based on the nodes & ports described in src/sink_data */ + WpPipewireSimpleEndpointLink *self = WP_PIPEWIRE_SIMPLE_ENDPOINT_LINK(epl); + struct pw_properties *props; + guint32 output_node_id, input_node_id, output_port_id, input_port_id; + + /* Get the node ids and port ids */ + if (!g_variant_lookup (src_data, "node-id", "u", &output_node_id)) + return FALSE; + if (!g_variant_lookup (src_data, "node-port-id", "u", &output_port_id)) + return FALSE; + if (!g_variant_lookup (sink_data, "node-id", "u", &input_node_id)) + return FALSE; + if (!g_variant_lookup (sink_data, "node-port-id", "u", &input_port_id)) + return FALSE; + + /* Create the properties */ + props = pw_properties_new(NULL, NULL); + pw_properties_setf(props, PW_LINK_OUTPUT_NODE_ID, "%d", output_node_id); + pw_properties_setf(props, PW_LINK_OUTPUT_PORT_ID, "%d", output_port_id); + pw_properties_setf(props, PW_LINK_INPUT_NODE_ID, "%d", input_node_id); + pw_properties_setf(props, PW_LINK_INPUT_PORT_ID, "%d", input_port_id); + + /* Create the link */ + pw_core_proxy_create_object(self->core_proxy, "link-factory", + PW_TYPE_INTERFACE_Link, PW_VERSION_LINK, &props->dict, 0); + + /* Clean up */ + pw_properties_free(props); return TRUE; } @@ -67,7 +96,37 @@ gpointer simple_endpoint_link_factory (WpFactory * factory, GType type, GVariant * properties) { + WpCore *wp_core = NULL; + struct pw_remote *remote; + + /* Make sure the type is an endpoint link */ if (type != WP_TYPE_ENDPOINT_LINK) return NULL; - return g_object_new (simple_endpoint_link_get_type (), NULL); + + /* Get the WirePlumber core */ + wp_core = wp_factory_get_core(factory); + if (!wp_core) { + g_warning("failed to get wireplumbe core. Skipping..."); + return NULL; + } + + /* Get the remote */ + remote = wp_core_get_global(wp_core, WP_GLOBAL_PW_REMOTE); + if (!remote) { + g_warning("failed to get core remote. Skipping..."); + return NULL; + } + + /* Create the endpoint link */ + WpPipewireSimpleEndpointLink *epl = g_object_new ( + simple_endpoint_link_get_type (), NULL); + + /* Set the core proxy */ + epl->core_proxy = pw_remote_get_core_proxy(remote); + if (!epl->core_proxy) { + g_warning("failed to get core proxy. Skipping..."); + return NULL; + } + + return epl; } diff --git a/modules/module-pipewire/simple-endpoint.c b/modules/module-pipewire/simple-endpoint.c index 7f94cd30fff53816862d858f846cf7d4233ab631..12d1e4e5b84d41ee92be9048cfd3eeaf0e9f7b79 100644 --- a/modules/module-pipewire/simple-endpoint.c +++ b/modules/module-pipewire/simple-endpoint.c @@ -18,12 +18,19 @@ #include <spa/pod/parser.h> #include <spa/param/props.h> +#include "port.h" + struct _WpPipewireSimpleEndpoint { WpEndpoint parent; + + /* Node */ struct pw_node_proxy *node; struct spa_hook proxy_listener; struct spa_hook node_proxy_listener; + + /* Info */ + struct pw_node_info *info; /* controls cache */ gfloat volume; @@ -45,19 +52,6 @@ G_DECLARE_FINAL_TYPE (WpPipewireSimpleEndpoint, G_DEFINE_TYPE (WpPipewireSimpleEndpoint, simple_endpoint, WP_TYPE_ENDPOINT) -static void -node_proxy_destroy (void *data) -{ - WpPipewireSimpleEndpoint *self = WP_PIPEWIRE_SIMPLE_ENDPOINT (data); - self->node = NULL; - wp_endpoint_unregister (WP_ENDPOINT (self)); -} - -static const struct pw_proxy_events node_proxy_events = { - PW_VERSION_PROXY_EVENTS, - .destroy = node_proxy_destroy, -}; - static void node_proxy_param (void *object, int seq, uint32_t id, uint32_t index, uint32_t next, const struct spa_pod *param) @@ -104,8 +98,15 @@ node_proxy_param (void *object, int seq, uint32_t id, } } +static void node_proxy_info(void *object, const struct pw_node_info *info) +{ + WpPipewireSimpleEndpoint *self = WP_PIPEWIRE_SIMPLE_ENDPOINT (object); + self->info = pw_node_info_update(self->info, info); +} + static const struct pw_node_proxy_events node_node_proxy_events = { PW_VERSION_NODE_PROXY_EVENTS, + .info = node_proxy_info, .param = node_proxy_param, }; @@ -114,6 +115,21 @@ simple_endpoint_init (WpPipewireSimpleEndpoint * self) { } +static void +node_proxy_destroy (void *data) +{ + WpPipewireSimpleEndpoint *self = WP_PIPEWIRE_SIMPLE_ENDPOINT (data); + self->node = NULL; + + wp_endpoint_unregister (WP_ENDPOINT (self)); +} + +static const struct pw_proxy_events node_proxy_events = { + PW_VERSION_PROXY_EVENTS, + .destroy = node_proxy_destroy, +}; + + static void simple_endpoint_constructed (GObject * object) { @@ -124,6 +140,7 @@ simple_endpoint_constructed (GObject * object) pw_proxy_add_listener ((struct pw_proxy *) self->node, &self->proxy_listener, &node_proxy_events, self); + pw_node_proxy_add_listener (self->node, &self->node_proxy_listener, &node_node_proxy_events, self); pw_node_proxy_subscribe_params (self->node, ids, n_ids); @@ -202,11 +219,26 @@ simple_endpoint_get_property (GObject * object, guint property_id, } static gboolean -simple_endpoint_prepare_link (WpEndpoint * self, guint32 stream_id, +simple_endpoint_prepare_link (WpEndpoint * ep, guint32 stream_id, WpEndpointLink * link, GVariant ** properties, GError ** error) { - /* TODO: verify that the remote end supports the same media type */ - /* TODO: fill @properties with (node id, array(port ids)) */ + WpPipewireSimpleEndpoint *self = WP_PIPEWIRE_SIMPLE_ENDPOINT (ep); + GVariantBuilder b; + + /* TODO: Since the linking with a 1 port client works when passing -1 as + * a port parameter, there is no need to find the port and set it in the + * properties. However, we need to add logic here and select the correct + * port in case the client has more than 1 port */ + + /* Set the port format here */ + + /* Set the properties */ + g_variant_builder_init (&b, G_VARIANT_TYPE_VARDICT); + g_variant_builder_add (&b, "{sv}", "node-id", + g_variant_new_uint32 (self->info->id)); + g_variant_builder_add (&b, "{sv}", "node-port-id", + g_variant_new_uint32 (-1)); + *properties = g_variant_builder_end (&b); return TRUE; } @@ -299,7 +331,6 @@ gpointer simple_endpoint_factory (WpFactory * factory, GType type, GVariant * properties) { - WpPipewireSimpleEndpoint *ep; guint64 proxy; const gchar *name; const gchar *media_class; @@ -316,11 +347,9 @@ simple_endpoint_factory (WpFactory * factory, GType type, if (!g_variant_lookup (properties, "node-proxy", "t", &proxy)) return NULL; - ep = g_object_new (simple_endpoint_get_type (), + return g_object_new (simple_endpoint_get_type (), "name", name, "media-class", media_class, "node-proxy", (gpointer) proxy, NULL); - - return ep; } diff --git a/modules/module-pw-alsa-udev.c b/modules/module-pw-alsa-udev.c index e04818b92c428aae31a1c8c072ac18c46fd32e64..8e35aa7592d28c8b24b9b9099b680b51c6a14ee7 100644 --- a/modules/module-pw-alsa-udev.c +++ b/modules/module-pw-alsa-udev.c @@ -13,26 +13,155 @@ #include <wp/wp.h> #include <pipewire/pipewire.h> +#include <spa/param/audio/format-utils.h> + +#include "module-pipewire/port.h" + +typedef void (*WpDoneCallback)(gpointer); + +struct done_data { + WpDoneCallback callback; + gpointer data; + GDestroyNotify data_destroy; +}; struct impl { WpCore *wp_core; + /* Remote */ struct pw_remote *remote; struct spa_hook remote_listener; + /* Core */ + struct pw_core_proxy *core_proxy; + struct spa_hook core_listener; + int core_seq; + GQueue *done_queue; + + /* Registry */ struct pw_registry_proxy *registry_proxy; struct spa_hook registry_listener; + + /* Ports */ + struct spa_list port_list; }; +struct endpoint_info { + struct impl *impl; + uint32_t id; + uint32_t parent_id; + gchar *name; + gchar *media_class; +}; + +static void endpoint_info_destroy(gpointer p) { + struct endpoint_info *ei = p; + if (ei->name) { + g_free (ei->name); + ei->name = NULL; + } + if (ei->media_class) { + g_free (ei->media_class); + ei->media_class = NULL; + } + g_slice_free (struct endpoint_info, p); +} + +static void done_data_destroy(gpointer p) { + struct done_data *dd = p; + if (dd->data_destroy) { + dd->data_destroy(dd->data); + dd->data = NULL; + } + g_slice_free (struct done_data, dd); +} + +static void sync_core_with_callabck(struct impl* impl, + WpDoneCallback callback, gpointer data, GDestroyNotify data_destroy) { + struct done_data *dd = g_new0(struct done_data, 1); + + /* Set the data */ + dd->callback = callback; + dd->data = data; + dd->data_destroy = data_destroy; + + /* Add the data to the queue */ + g_queue_push_tail (impl->done_queue, dd); + + /* Sync the core */ + impl->core_seq = pw_core_proxy_sync(impl->core_proxy, 0, impl->core_seq); +} + +static void create_endpoint(gpointer p) { + struct endpoint_info *ei = p; + struct spa_proxy *proxy = NULL; + GVariantBuilder b; + g_autoptr(GVariant) endpoint_props = NULL; + WpEndpoint *endpoint = NULL; + + /* Make sure the endpoint info is valid */ + if (!ei) + return; + + /* Register the proxy */ + proxy = pw_registry_proxy_bind (ei->impl->registry_proxy, + ei->id, PW_TYPE_INTERFACE_Node, PW_VERSION_NODE, 0); + + /* Build the GVariant properties for the endpoint */ + g_variant_builder_init (&b, G_VARIANT_TYPE_VARDICT); + g_variant_builder_add (&b, "{sv}", "name", + g_variant_new_take_string (g_strdup_printf ("Endpoint %u: %s", ei->id, + ei->name))); + g_variant_builder_add (&b, "{sv}", + "media-class", g_variant_new_string (ei->media_class)); + g_variant_builder_add (&b, "{sv}", + "node-proxy", g_variant_new_uint64 ((guint64) proxy)); + g_variant_builder_add (&b, "{sv}", + "port-list", g_variant_new_uint64 ((guint64) &ei->impl->port_list)); + endpoint_props = g_variant_builder_end (&b); + + /* Create and register the endpoint */ + endpoint = wp_factory_make (ei->impl->wp_core, "pw-audio-softdsp-endpoint", + WP_TYPE_ENDPOINT, endpoint_props); + wp_endpoint_register (endpoint, ei->impl->wp_core); +} + +static void enum_format_and_create_endpoint(gpointer p) { + struct endpoint_info *ei = p, *ei_copy = NULL; + WpPort *port = NULL; + + /* Make sure the endpoint info is valid */ + if (!ei) + return; + + /* Find the unique alsa port */ + spa_list_for_each(port, &ei->impl->port_list, l) { + if (port->parent_id == ei->id) + break; + } + + /* Emit the port EnumFormat */ + pw_port_proxy_enum_params((struct pw_port_proxy*)port->proxy, 0, + SPA_PARAM_EnumFormat, 0, -1, NULL); + + /* Copy endpoint info */ + ei_copy = g_new0(struct endpoint_info, 1); + ei_copy->impl = ei->impl; + ei_copy->id = ei->id; + ei_copy->name = g_strdup(ei->name); + ei_copy->media_class = g_strdup(ei->media_class); + + /* Forward the endpoint creation until the port EnumFormat is emitted */ + sync_core_with_callabck(ei->impl, create_endpoint, ei_copy, + endpoint_info_destroy); +} + static void handle_node(struct impl *impl, uint32_t id, uint32_t parent_id, const struct spa_dict *props) { - const gchar *media_class = NULL, *node_name = NULL; - struct spa_proxy *proxy = NULL; - GVariantBuilder b; - g_autoptr(GVariant) endpoint_props = NULL; - g_autoptr (WpEndpoint) endpoint = NULL; + struct endpoint_info *ei = NULL; + const gchar *media_class = NULL, *name = NULL; /* Make sure the node has properties */ if (!props) { @@ -40,33 +169,101 @@ handle_node(struct impl *impl, uint32_t id, uint32_t parent_id, return; } - /* Make sure the media class is audio */ - /* FIXME: need to handle only alsa nodes */ + /* Get the name and media_class */ + name = spa_dict_lookup(props, "node.name"); media_class = spa_dict_lookup(props, "media.class"); + + /* Make sure the media class is non-dsp audio */ if (!g_str_has_prefix (media_class, "Audio/")) return; + if (g_str_has_prefix (media_class, "Audio/DSP")) + return; + + /* Create the endpoint info */ + ei = g_new0(struct endpoint_info, 1); + ei->impl = impl; + ei->id = id; + ei->name = g_strdup(name); + ei->media_class = g_strdup(media_class); + + /* Delay the creation of the endpoint until all ports have been created */ + sync_core_with_callabck(impl, enum_format_and_create_endpoint, ei, + endpoint_info_destroy); +} + +static void port_event_info(void *data, const struct pw_port_info *info) +{ + WpPort *port = data; + port->info = pw_port_info_update(port->info, info); +} + +static void port_event_param(void *data, int seq, uint32_t id, uint32_t index, + uint32_t next, const struct spa_pod *param) +{ + WpPort *port = data; + + /* Only handle EnumFormat */ + if (id != SPA_PARAM_EnumFormat) + return; + + /* Parse the format */ + if (spa_format_parse(param, &port->media_type, &port->media_subtype) < 0) + return; + + /* Only handle RAW audio types */ + if (port->media_type != SPA_MEDIA_TYPE_audio || + port->media_subtype != SPA_MEDIA_SUBTYPE_raw) + return; + + /* Parse the raw audio format */ + spa_pod_fixate((struct spa_pod*)param); + spa_format_audio_raw_parse(param, &port->format); +} + +static const struct pw_port_proxy_events port_events = { + PW_VERSION_PORT_PROXY_EVENTS, + .info = port_event_info, + .param = port_event_param, +}; + +static void +handle_port(struct impl *impl, uint32_t id, uint32_t parent_id, + const struct spa_dict *props) +{ + struct pw_proxy *proxy; + WpPort *port; + const char *direction_prop; + + /* Make sure the port has porperties */ + if (!props) + return; - /* Get the device name */ - node_name = spa_dict_lookup(props, "node.name"); + /* Get the direction property */ + direction_prop = spa_dict_lookup(props, "port.direction"); + if (!direction_prop) + return; /* Get the proxy */ proxy = pw_registry_proxy_bind (impl->registry_proxy, id, - PW_TYPE_INTERFACE_Node, PW_VERSION_NODE, 0); + PW_TYPE_INTERFACE_Port, PW_VERSION_NODE, sizeof(WpPort)); + if (!proxy) + return; - /* Build the GVariant properties for the endpoint */ - g_variant_builder_init (&b, G_VARIANT_TYPE_VARDICT); - g_variant_builder_add (&b, "{sv}", "name", - g_variant_new_take_string (g_strdup_printf ("Endpoint %u: %s", id, node_name))); - g_variant_builder_add (&b, "{sv}", - "media-class", g_variant_new_string (media_class)); - g_variant_builder_add (&b, "{sv}", - "node-proxy", g_variant_new_uint64 ((guint64) proxy)); - endpoint_props = g_variant_builder_end (&b); + /* Get the port */ + port = pw_proxy_get_user_data(proxy); - /* Create the endpoint */ - endpoint = wp_factory_make (impl->wp_core, "pw-audio-softdsp-endpoint", - WP_TYPE_ENDPOINT, endpoint_props); - wp_endpoint_register (endpoint, impl->wp_core); + /* Set the info */ + port->id = id; + port->parent_id = parent_id; + port->direction = + !strcmp(direction_prop, "out") ? PW_DIRECTION_OUTPUT : PW_DIRECTION_INPUT; + + /* Set the proxy and listener */ + port->proxy = proxy; + pw_proxy_add_proxy_listener(proxy, &port->listener, &port_events, port); + + /* Add the port to the list */ + spa_list_append(&impl->port_list, &port->l); } static void @@ -76,12 +273,15 @@ registry_global(void *data,uint32_t id, uint32_t parent_id, { struct impl *impl = data; - /* Only handle nodes */ switch (type) { case PW_TYPE_INTERFACE_Node: handle_node(impl, id, parent_id, props); break; + case PW_TYPE_INTERFACE_Port: + handle_port(impl, id, parent_id, props); + break; + default: break; } @@ -92,16 +292,35 @@ static const struct pw_registry_proxy_events registry_events = { .global = registry_global, }; +static void core_done(void *d, uint32_t id, int seq) +{ + struct impl * impl = d; + struct done_data * dd = NULL; + + /* Process all the done_data queue */ + while ((dd = g_queue_pop_head(impl->done_queue))) { + if (dd->callback) + dd->callback(dd->data); + done_data_destroy(dd); + } +} + +static const struct pw_core_proxy_events core_events = { + PW_VERSION_CORE_EVENTS, + .done = core_done +}; + static void on_state_changed(void *_data, enum pw_remote_state old, enum pw_remote_state state, const char *error) { struct impl *impl = _data; - struct pw_core_proxy *core_proxy; switch (state) { case PW_REMOTE_STATE_CONNECTED: - core_proxy = pw_remote_get_core_proxy (impl->remote); - impl->registry_proxy = pw_core_proxy_get_registry (core_proxy, + impl->core_proxy = pw_remote_get_core_proxy (impl->remote); + pw_core_proxy_add_listener(impl->core_proxy, &impl->core_listener, + &core_events, impl); + impl->registry_proxy = pw_core_proxy_get_registry (impl->core_proxy, PW_TYPE_INTERFACE_Registry, PW_VERSION_REGISTRY, 0); pw_registry_proxy_add_listener(impl->registry_proxy, &impl->registry_listener, ®istry_events, impl); @@ -121,6 +340,7 @@ static void module_destroy (gpointer data) { struct impl *impl = data; + g_queue_free_full(impl->done_queue, done_data_destroy); g_slice_free (struct impl, impl); } @@ -136,10 +356,13 @@ wireplumber__module_init (WpModule * module, WpCore * core, GVariant * args) struct impl *impl = g_new0(struct impl, 1); impl->wp_core = core; impl->remote = wp_core_get_global(core, WP_GLOBAL_PW_REMOTE); + impl->done_queue = g_queue_new(); + spa_list_init(&impl->port_list); /* Set destroy callback for impl */ wp_module_set_destroy_callback (module, module_destroy, impl); /* Add a state changed listener */ - pw_remote_add_listener(impl->remote, &impl->remote_listener, &remote_events, impl); + pw_remote_add_listener(impl->remote, &impl->remote_listener, &remote_events, + impl); } diff --git a/modules/module-pw-audio-softdsp-endpoint.c b/modules/module-pw-audio-softdsp-endpoint.c index 052eff2fee350c8007c0d41658907a7fce2532b6..19d469b7d8a2f261bca086dfb7d1a4f21bbe15d6 100644 --- a/modules/module-pw-audio-softdsp-endpoint.c +++ b/modules/module-pw-audio-softdsp-endpoint.c @@ -15,10 +15,44 @@ #include <wp/wp.h> #include <pipewire/pipewire.h> +#include <spa/param/audio/format-utils.h> -struct _WpPwAudioSoftdspEndpoint -{ +#include "module-pipewire/port.h" + +#define MIN_QUANTUM_SIZE 64 +#define MAX_QUANTUM_SIZE 1024 + +struct _WpPwAudioSoftdspEndpoint { WpEndpoint parent; + + /* The core proxy */ + struct pw_core_proxy *core_proxy; + + /* Node proxy and listener */ + struct pw_proxy *node_proxy; + struct spa_hook listener; + struct spa_hook proxy_listener; + + /* Node info */ + struct pw_node_info *node_info; + uint32_t media_type; + uint32_t media_subtype; + struct spa_audio_info_raw format; + enum pw_direction direction; + + /* DSP proxy and listener */ + struct pw_proxy *dsp_proxy; + struct spa_hook dsp_listener; + + /* DSP info */ + struct pw_node_info *dsp_info; + + /* Link proxy and listener */ + struct pw_proxy *link_proxy; + + /* The all port list reference */ + /* TODO: make it thread safe */ + struct spa_list *port_list; }; G_DECLARE_FINAL_TYPE (WpPwAudioSoftdspEndpoint, endpoint, @@ -31,44 +65,325 @@ endpoint_init (WpPwAudioSoftdspEndpoint * self) { } +static void +endpoint_finalize (GObject * object) +{ + WpPwAudioSoftdspEndpoint *self = WP_PW_AUDIO_SOFTDSP_ENDPOINT (object); + + /* Remove and destroy the node_proxy */ + if (self->node_proxy) { + spa_hook_remove (&self->listener); + pw_proxy_destroy ((struct pw_proxy *) self->node_proxy); + } + + /* Remove and destroy the dsp_proxy */ + if (self->dsp_proxy) { + spa_hook_remove (&self->dsp_listener); + pw_proxy_destroy ((struct pw_proxy *) self->dsp_proxy); + } + + G_OBJECT_CLASS (endpoint_parent_class)->finalize (object); +} + static gboolean -endpoint_prepare_link (WpEndpoint * self, guint32 stream_id, +endpoint_prepare_link (WpEndpoint * ep, guint32 stream_id, WpEndpointLink * link, GVariant ** properties, GError ** error) { + WpPwAudioSoftdspEndpoint *self = WP_PW_AUDIO_SOFTDSP_ENDPOINT (ep); + WpPort *port = NULL, *node_port = NULL, *dsp_port = NULL; + GVariantBuilder b; + + /* Find the node port */ + spa_list_for_each(port, self->port_list, l) { + if (self->node_info->id == port->parent_id) { + node_port = port; + break; + } + } + if (!node_port) + return FALSE; + + /* Find the first dsp port with the same direction as the node port */ + spa_list_for_each(port, self->port_list, l) { + if (self->dsp_info->id == port->parent_id + && port->direction == node_port->direction) { + dsp_port = port; + break; + } + } + if (!dsp_port) + return FALSE; + + /* Set the properties */ + g_variant_builder_init (&b, G_VARIANT_TYPE_VARDICT); + g_variant_builder_add (&b, "{sv}", "node-id", + g_variant_new_uint32 (self->dsp_info->id)); + g_variant_builder_add (&b, "{sv}", "node-port-id", + g_variant_new_uint32 (dsp_port->id)); + *properties = g_variant_builder_end (&b); + return TRUE; } static void endpoint_class_init (WpPwAudioSoftdspEndpointClass * klass) { + GObjectClass *object_class = (GObjectClass *) klass; WpEndpointClass *endpoint_class = (WpEndpointClass *) klass; + object_class->finalize = endpoint_finalize; + endpoint_class->prepare_link = endpoint_prepare_link; } +static void on_dsp_running(WpPwAudioSoftdspEndpoint *self) +{ + struct pw_properties *props; + + /* Return if the node has already been linked */ + if (self->link_proxy) + return; + + /* Create new properties */ + props = pw_properties_new(NULL, NULL); + + /* Set the new properties */ + pw_properties_set(props, PW_LINK_PROP_PASSIVE, "true"); + if (self->direction == PW_DIRECTION_OUTPUT) { + pw_properties_setf(props, PW_LINK_OUTPUT_NODE_ID, "%d", self->dsp_info->id); + pw_properties_setf(props, PW_LINK_OUTPUT_PORT_ID, "%d", -1); + pw_properties_setf(props, PW_LINK_INPUT_NODE_ID, "%d", self->node_info->id); + pw_properties_setf(props, PW_LINK_INPUT_PORT_ID, "%d", -1); + } else { + pw_properties_setf(props, PW_LINK_OUTPUT_NODE_ID, "%d", self->node_info->id); + pw_properties_setf(props, PW_LINK_OUTPUT_PORT_ID, "%d", -1); + pw_properties_setf(props, PW_LINK_INPUT_NODE_ID, "%d", self->dsp_info->id); + pw_properties_setf(props, PW_LINK_INPUT_PORT_ID, "%d", -1); + } + + /* Create the link */ + self->link_proxy = pw_core_proxy_create_object(self->core_proxy, + "link-factory", PW_TYPE_INTERFACE_Link, PW_VERSION_LINK, &props->dict, 0); + + /* Clean up */ + pw_properties_free(props); +} + +static void dsp_node_event_info(void *data, const struct pw_node_info *info) +{ + WpPwAudioSoftdspEndpoint *self = data; + + /* Set dsp info */ + self->dsp_info = pw_node_info_update(self->dsp_info, info); + + /* Handle the different states */ + switch (info->state) { + case PW_NODE_STATE_IDLE: + break; + case PW_NODE_STATE_RUNNING: + on_dsp_running(self); + break; + case PW_NODE_STATE_SUSPENDED: + break; + default: + break; + } +} + +static const struct pw_node_proxy_events dsp_node_events = { + PW_VERSION_NODE_PROXY_EVENTS, + .info = dsp_node_event_info, +}; + +static void emit_audio_dsp_node(WpPwAudioSoftdspEndpoint *self) +{ + struct pw_properties *props; + const char *dsp_name = NULL; + uint8_t buf[1024]; + struct spa_pod_builder pod_builder = { 0, }; + struct spa_pod *param; + + /* Return if the node has been already emitted */ + if (self->dsp_proxy) + return; + + /* Get the properties */ + props = pw_properties_new_dict(self->node_info->props); + if (!props) + return; + + /* Get the DSP name */ + dsp_name = pw_properties_get(props, "device.nick"); + if (!dsp_name) + dsp_name = self->node_info->name; + + /* Set the properties */ + pw_properties_set(props, "audio-dsp.name", dsp_name); + pw_properties_setf(props, "audio-dsp.direction", "%d", self->direction); + pw_properties_setf(props, "audio-dsp.maxbuffer", "%ld", MAX_QUANTUM_SIZE * sizeof(float)); + + /* Set the DSP proxy and listener */ + self->dsp_proxy = pw_core_proxy_create_object(self->core_proxy, "audio-dsp", + PW_TYPE_INTERFACE_Node, PW_VERSION_NODE, &props->dict, 0); + pw_proxy_add_proxy_listener(self->dsp_proxy, &self->dsp_listener, + &dsp_node_events, self); + + /* Set DSP proxy params */ + spa_pod_builder_init(&pod_builder, buf, sizeof(buf)); + param = spa_format_audio_raw_build(&pod_builder, SPA_PARAM_Format, &self->format); + param = spa_pod_builder_add_object(&pod_builder, + SPA_TYPE_OBJECT_ParamProfile, SPA_PARAM_Profile, + SPA_PARAM_PROFILE_direction, SPA_POD_Id(pw_direction_reverse(self->direction)), + SPA_PARAM_PROFILE_format, SPA_POD_Pod(param)); + pw_node_proxy_set_param((struct pw_node_proxy*)self->dsp_proxy, + SPA_PARAM_Profile, 0, param); + + /* Clean up */ + pw_properties_free(props); +} + +static void node_event_info(void *data, const struct pw_node_info *info) +{ + WpPwAudioSoftdspEndpoint *self = data; + WpPort *port = NULL; + + /* Set the node info */ + self->node_info = pw_node_info_update(self->node_info, info); + + /* Find the node port */ + spa_list_for_each(port, self->port_list, l) { + if (port->parent_id == self->node_info->id) + break; + } + + /* Set the format using the port format */ + self->format.format = port->format.format; + self->format.flags = port->format.flags; + self->format.rate = port->format.rate; + self->format.channels = port->format.channels; + for (int i = 0; i < port->format.channels; ++i) + self->format.position[i] = port->format.position[i]; + + /* Emit the audio DSP node */ + emit_audio_dsp_node(self); + + /* TODO: Handle the different states */ + switch (info->state) { + case PW_NODE_STATE_IDLE: + break; + case PW_NODE_STATE_RUNNING: + break; + case PW_NODE_STATE_SUSPENDED: + break; + default: + break; + } +} + +static void node_proxy_destroy(void *data) +{ + WpPwAudioSoftdspEndpoint *self = data; + + /* Clear node_info */ + if (self->node_info) + pw_node_info_free(self->node_info); + + /* Clear dsp_info */ + if (self->dsp_info) + pw_node_info_free(self->dsp_info); + + wp_endpoint_unregister (WP_ENDPOINT (self)); +} + +static const struct pw_proxy_events node_proxy_events = { + PW_VERSION_PROXY_EVENTS, + .destroy = node_proxy_destroy, +}; + +static const struct pw_node_proxy_events node_events = { + PW_VERSION_NODE_PROXY_EVENTS, + .info = node_event_info, +}; + static gpointer endpoint_factory (WpFactory * factory, GType type, GVariant * properties) { + WpCore *wp_core = NULL; + struct pw_remote *remote; const gchar *name = NULL; const gchar *media_class = NULL; + guint64 proxy, port_list; + /* Make sure the type is not the base class type */ if (type != WP_TYPE_ENDPOINT) return NULL; + /* Get the WirePlumber core */ + wp_core = wp_factory_get_core(factory); + if (!wp_core) { + g_warning("failed to get wireplumbe core. Skipping..."); + return NULL; + } + + /* Get the remote */ + remote = wp_core_get_global(wp_core, WP_GLOBAL_PW_REMOTE); + if (!remote) { + g_warning("failed to get core remote. Skipping..."); + return NULL; + } + /* Get the name and media-class */ if (!g_variant_lookup (properties, "name", "&s", &name)) return NULL; if (!g_variant_lookup (properties, "media-class", "&s", &media_class)) return NULL; + if (!g_variant_lookup (properties, "node-proxy", "t", &proxy)) + return NULL; + if (!g_variant_lookup (properties, "port-list", "t", &port_list)) + return NULL; - return g_object_new (endpoint_get_type (), + /* Create the softdsp endpoint object */ + WpPwAudioSoftdspEndpoint *ep = g_object_new (endpoint_get_type (), "name", name, "media-class", media_class, NULL); + if (!ep) + return NULL; + + /* Set the direction */ + if (g_str_has_suffix(media_class, "Source")) { + ep->direction = PW_DIRECTION_INPUT; + } else if (g_str_has_suffix(media_class, "Sink")) { + ep->direction = PW_DIRECTION_OUTPUT; + } else { + g_warning("failed to parse direction. Skipping..."); + return NULL; + } + + /* Set the port list reference */ + ep->port_list = (gpointer) port_list; + + /* Set the core proxy */ + ep->core_proxy = pw_remote_get_core_proxy(remote); + if (!ep->core_proxy) { + g_warning("failed to get core proxy. Skipping..."); + return NULL; + } + + /* Set the node proxy and listener */ + ep->node_proxy = (gpointer) proxy; + pw_proxy_add_listener (ep->node_proxy, &ep->listener, &node_proxy_events, + ep); + pw_proxy_add_proxy_listener(ep->node_proxy, &ep->proxy_listener, + &node_events, ep); + + /* Return the object */ + return ep; } void wireplumber__module_init (WpModule * module, WpCore * core, GVariant * args) { + /* Register the softdsp endpoint */ wp_factory_new (core, "pw-audio-softdsp-endpoint", endpoint_factory); }