diff --git a/modules/meson.build b/modules/meson.build index 5db780e60317809add3259babc9c6271779ba3ed..adefe23ba33fd166fe6825e940882f413b8a2db6 100644 --- a/modules/meson.build +++ b/modules/meson.build @@ -25,7 +25,7 @@ shared_library( c_args : [common_c_args, '-DG_LOG_DOMAIN="m-pipewire"'], install : true, install_dir : wireplumber_module_dir, - dependencies : [wp_dep, pipewire_dep], + dependencies : [gio_dep, wp_dep, pipewire_dep], ) shared_library( @@ -47,7 +47,7 @@ shared_library( c_args : [common_c_args, '-DG_LOG_DOMAIN="m-pw-audio-softdsp-endpoint"'], install : true, install_dir : wireplumber_module_dir, - dependencies : [wp_dep, pipewire_dep], + dependencies : [gio_dep, wp_dep, pipewire_dep], ) shared_library( diff --git a/modules/module-pipewire.c b/modules/module-pipewire.c index a329fb8b9d13d8fa4107e34d6a80e08bafc51cf8..831e4f966bebd6354569b94f883e6f31eeca2bc7 100644 --- a/modules/module-pipewire.c +++ b/modules/module-pipewire.c @@ -14,12 +14,11 @@ #include <wp/wp.h> #include <pipewire/pipewire.h> -#include <spa/param/audio/format-utils.h> void remote_endpoint_init (WpCore * core, struct pw_core * pw_core, struct pw_remote * remote); -gpointer simple_endpoint_factory (WpFactory * factory, GType type, - GVariant * properties); +void simple_endpoint_factory (WpFactory * factory, GType type, + GVariant * properties, GAsyncReadyCallback ready, gpointer user_data); gpointer simple_endpoint_link_factory (WpFactory * factory, GType type, GVariant * properties); @@ -27,158 +26,40 @@ struct module_data { WpModule *module; WpRemotePipewire *remote_pipewire; - GHashTable *client_nodes_info; -}; - -struct endpoint_info -{ - gchar *name; - gchar *media_class; - struct pw_proxy *proxy; -}; - -struct proxy_info -{ - const struct module_data *data; - uint32_t node_id; - WpProxyPort *proxy_port; }; static void -endpoint_info_destroy(gpointer p) +on_endpoint_created(GObject *initable, GAsyncResult *res, gpointer d) { - struct endpoint_info *ei = p; - - /* Free the name */ - g_free (ei->name); - - /* Free the media class */ - g_free (ei->media_class); - - /* Clean up */ - g_slice_free (struct endpoint_info, p); -} - -static void -proxy_info_destroy(gpointer p) -{ - struct proxy_info *pi = p; - - /* Unref the proxy port */ - g_clear_object (&pi->proxy_port); - - /* Clean up */ - g_slice_free (struct proxy_info, p); -} - -static void -unregister_endpoint (WpProxy* wp_proxy, WpEndpoint *endpoint) -{ - g_return_if_fail(WP_IS_PROXY(wp_proxy)); - g_return_if_fail(WP_IS_ENDPOINT(endpoint)); - - /* Unregister the endpoint */ - wp_endpoint_unregister(endpoint); -} - -static void -proxy_node_created(GObject *initable, GAsyncResult *res, gpointer d) -{ - struct proxy_info *pi = d; - const struct module_data *data = pi->data; - g_autoptr (WpCore) core = wp_module_get_core (data->module); - g_autoptr (WpProxyNode) proxy_node = NULL; - struct endpoint_info *ei = NULL; g_autoptr (WpEndpoint) endpoint = NULL; - g_autoptr (GVariant) endpoint_props = NULL; - GVariantBuilder b; - - /* Get the proxy */ - proxy_node = wp_proxy_node_new_finish(initable, res, NULL); - if (!proxy_node) - return; + guint global_id = 0; - /* Get the client node info */ - ei = g_hash_table_lookup(data->client_nodes_info, - GINT_TO_POINTER(pi->node_id)); - if (!ei) + /* Get the endpoint */ + endpoint = wp_endpoint_new_finish(initable, res, NULL); + if (!endpoint) return; - /* Set the properties */ - g_variant_builder_init (&b, G_VARIANT_TYPE_VARDICT); - g_variant_builder_add (&b, "{sv}", - "name", ei->name ? g_variant_new_string (ei->name) : - g_variant_new_take_string ( - g_strdup_printf ("Stream %u", pi->node_id))); - g_variant_builder_add (&b, "{sv}", - "media-class", g_variant_new_string (ei->media_class)); - g_variant_builder_add (&b, "{sv}", - "proxy-node", g_variant_new_uint64 ((guint64) proxy_node)); - g_variant_builder_add (&b, "{sv}", - "proxy-port", g_variant_new_uint64 ((guint64) pi->proxy_port)); - endpoint_props = g_variant_builder_end (&b); - - /* Create the endpoint */ - endpoint = wp_factory_make (core, "pipewire-simple-endpoint", - WP_TYPE_ENDPOINT, endpoint_props); + /* Get the endpoint global id */ + g_object_get (endpoint, "global-id", &global_id, NULL); + g_debug ("Created client endpoint for global id %d", global_id); /* Register the endpoint */ wp_endpoint_register (endpoint); - - /* Set destroy handler to unregister endpoint when the proxy is detroyed */ - g_signal_connect (proxy_node, "destroyed", G_CALLBACK(unregister_endpoint), - endpoint); - - /* Clean up */ - proxy_info_destroy (pi); } static void -proxy_port_created(GObject *initable, GAsyncResult *res, gpointer d) -{ - struct proxy_info *pi = d; - const struct module_data *data = pi->data; - struct endpoint_info *ei = NULL; - WpProxyPort *proxy_port = NULL; - - /* Get the proxy port */ - proxy_port = wp_proxy_port_new_finish(initable, res, NULL); - if (!proxy_port) - return; - - /* Forward the proxy port */ - pi->proxy_port = proxy_port; - - /* Get the node proxy */ - ei = g_hash_table_lookup(data->client_nodes_info, - GINT_TO_POINTER(pi->node_id)); - if (!ei) - return; - - /* Create the proxy node asynchronically */ - wp_proxy_node_new(pi->node_id, ei->proxy, proxy_node_created, pi); -} - -static void -handle_node (WpRemotePipewire *rp, guint id, guint parent_id, gconstpointer p, +on_node_added (WpRemotePipewire *rp, guint id, guint parent_id, gconstpointer p, gpointer d) { struct module_data *data = d; const struct spa_dict *props = p; - struct endpoint_info *ei = NULL; - const gchar *name; - const gchar *media_class; - struct pw_proxy *proxy; - struct spa_audio_info_raw format = { 0, }; - struct spa_pod *param; - struct spa_pod_builder pod_builder = { 0, }; - char buf[1024]; + g_autoptr (WpCore) core = wp_module_get_core (data->module); + const gchar *name, *media_class; + GVariantBuilder b; + g_autoptr (GVariant) endpoint_props = NULL; /* Make sure the node has properties */ - if (!props) { - g_warning("node has no properties, skipping..."); - return; - } + g_return_if_fail(props); /* Get the media_class */ media_class = spa_dict_lookup(props, "media.class"); @@ -192,67 +73,20 @@ handle_node (WpRemotePipewire *rp, guint id, guint parent_id, gconstpointer p, if (!name) name = spa_dict_lookup (props, "node.name"); - g_debug ("found stream node: id:%u ; name:%s ; media_class:%s", id, name, - media_class); - - /* Bind the proxy */ - proxy = wp_remote_pipewire_proxy_bind(rp, id, PW_TYPE_INTERFACE_Node); - if (!proxy) - return; - - /* TODO: Assume all clients have this format for now */ - format.format = SPA_AUDIO_FORMAT_F32P; - format.flags = 1; - format.rate = 48000; - format.channels = 1; - format.position[0] = 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); - - /* Create the endpoint info */ - ei = g_slice_new0 (struct endpoint_info); - ei->name = g_strdup(name); - ei->media_class = g_strdup(media_class); - ei->proxy = proxy; - - /* Insert the client node info in the hash table */ - g_hash_table_insert(data->client_nodes_info, GINT_TO_POINTER (id), ei); -} - -static void -handle_port(WpRemotePipewire *rp, guint id, guint parent_id, gconstpointer p, - gpointer d) -{ - struct module_data *data = d; - struct proxy_info *pi = NULL; - struct pw_proxy *proxy = NULL; - - /* Only handle ports whose parent is an alsa node */ - if (!g_hash_table_contains(data->client_nodes_info, - GINT_TO_POINTER (parent_id))) - return; - - /* Bind the proxy */ - proxy = wp_remote_pipewire_proxy_bind (rp, id, PW_TYPE_INTERFACE_Port); - if (!proxy) - return; - - /* Create the port info */ - pi = g_slice_new0 (struct proxy_info); - pi->data = data; - pi->node_id = parent_id; - pi->proxy_port = NULL; + /* Set the properties */ + g_variant_builder_init (&b, G_VARIANT_TYPE_VARDICT); + g_variant_builder_add (&b, "{sv}", + "name", name ? g_variant_new_string (name) : + g_variant_new_take_string (g_strdup_printf ("Stream %u", id))); + g_variant_builder_add (&b, "{sv}", + "media-class", g_variant_new_string (media_class)); + g_variant_builder_add (&b, "{sv}", + "global-id", g_variant_new_uint32 (id)); + endpoint_props = g_variant_builder_end (&b); - /* Create the proxy port asynchronically */ - wp_proxy_port_new(id, proxy, proxy_port_created, pi); + /* Create the endpoint async */ + wp_factory_make_async (core, "pipewire-simple-endpoint", WP_TYPE_ENDPOINT, + endpoint_props, on_endpoint_created, data); } static void @@ -264,10 +98,6 @@ module_destroy (gpointer d) data->module = NULL; data->remote_pipewire = NULL; - /* Destroy the hash table */ - g_hash_table_unref (data->client_nodes_info); - data->client_nodes_info = NULL; - /* Clean up */ g_slice_free (struct module_data, data); } @@ -292,22 +122,20 @@ wireplumber__module_init (WpModule * module, WpCore * core, GVariant * args) data = g_slice_new0 (struct module_data); data->module = module; data->remote_pipewire = rp; - data->client_nodes_info = g_hash_table_new_full (g_direct_hash, - g_direct_equal, NULL, endpoint_info_destroy); /* Set the module destroy callback */ wp_module_set_destroy_callback (module, module_destroy, data); - /* Register the global addded callbacks */ - g_signal_connect(rp, "global-added::node", (GCallback)handle_node, data); - g_signal_connect(rp, "global-added::port", (GCallback)handle_port, data); + /* Register the global added/removed callbacks */ + g_signal_connect(rp, "global-added::node", (GCallback)on_node_added, data); /* Init remoted endpoint */ g_object_get (rp, "pw-core", &pw_core, "pw-remote", &pw_remote, NULL); remote_endpoint_init (core, pw_core, pw_remote); /* Register simple-endpoint and simple-endpoint-link */ - wp_factory_new (core, "pipewire-simple-endpoint", simple_endpoint_factory); + wp_factory_new_async (core, "pipewire-simple-endpoint", + simple_endpoint_factory); wp_factory_new (core, "pipewire-simple-endpoint-link", simple_endpoint_link_factory); } diff --git a/modules/module-pipewire/simple-endpoint.c b/modules/module-pipewire/simple-endpoint.c index 6f9aa6937ccf98941396b3a048f926b0b55827e2..d3d33766c2a6df2f162b08bffa02bfd6f730b477 100644 --- a/modules/module-pipewire/simple-endpoint.c +++ b/modules/module-pipewire/simple-endpoint.c @@ -13,6 +13,8 @@ * other arbitrary node that does not need any kind of internal management. */ +#include <spa/param/audio/format-utils.h> + #include <wp/wp.h> #include <pipewire/pipewire.h> #include <spa/pod/parser.h> @@ -22,10 +24,19 @@ struct _WpPipewireSimpleEndpoint { WpEndpoint parent; - /* Proxy */ + /* The global-id this endpoint refers to */ + guint global_id; + + /* The task to signal the endpoint is initialized */ + GTask *init_task; + + /* The remote pipewire */ + WpRemotePipewire *remote_pipewire; + + /* Proxies */ WpProxyNode *proxy_node; - WpProxyPort *proxy_port; struct spa_hook node_proxy_listener; + GPtrArray *proxies_port; /* controls cache */ gfloat volume; @@ -34,8 +45,7 @@ struct _WpPipewireSimpleEndpoint enum { PROP_0, - PROP_NODE_PROXY, - PROP_PORT_PROXY + PROP_GLOBAL_ID, }; enum { @@ -43,10 +53,17 @@ enum { CONTROL_MUTE, }; +static GAsyncInitableIface *wp_simple_endpoint_parent_interface = NULL; +static void wp_simple_endpoint_async_initable_init (gpointer iface, + gpointer iface_data); + G_DECLARE_FINAL_TYPE (WpPipewireSimpleEndpoint, simple_endpoint, WP_PIPEWIRE, SIMPLE_ENDPOINT, WpEndpoint) -G_DEFINE_TYPE (WpPipewireSimpleEndpoint, simple_endpoint, WP_TYPE_ENDPOINT) +G_DEFINE_TYPE_WITH_CODE (WpPipewireSimpleEndpoint, simple_endpoint, + WP_TYPE_ENDPOINT, + G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, + wp_simple_endpoint_async_initable_init)) static void node_proxy_param (void *object, int seq, uint32_t id, @@ -100,21 +117,122 @@ static const struct pw_node_proxy_events node_node_proxy_events = { }; static void -simple_endpoint_init (WpPipewireSimpleEndpoint * self) +on_proxy_node_destroyed (WpProxy* wp_proxy, WpEndpoint *endpoint) { + g_return_if_fail(WP_IS_ENDPOINT(endpoint)); + + /* Unregister the endpoint */ + wp_endpoint_unregister(endpoint); } static void -simple_endpoint_constructed (GObject * object) +on_all_ports_done(WpProxy *proxy, gpointer data) { - WpPipewireSimpleEndpoint *self = WP_PIPEWIRE_SIMPLE_ENDPOINT (object); + WpPipewireSimpleEndpoint *self = data; + + /* Don't do anything if the endpoint has already been initialized */ + if (!self->init_task) + return; + + /* Set destroy handler to unregister endpoint on proxy_node destruction */ + g_signal_connect (self->proxy_node, "destroyed", + G_CALLBACK(on_proxy_node_destroyed), WP_ENDPOINT(self)); + + /* Finish the creation of the endpoint */ + g_task_return_boolean (self->init_task, TRUE); + g_clear_object(&self->init_task); +} + +static void +on_proxy_port_created(GObject *initable, GAsyncResult *res, gpointer data) +{ + WpPipewireSimpleEndpoint *self = data; + WpProxyPort *proxy_port = NULL; + + /* Get the proxy port */ + proxy_port = wp_proxy_port_new_finish(initable, res, NULL); + g_return_if_fail (proxy_port); + + /* Add the proxy port to the array */ + g_return_if_fail (self->proxies_port); + g_ptr_array_add(self->proxies_port, proxy_port); + + /* Register the done callback */ + g_signal_connect(self->proxy_node, "done", (GCallback)on_all_ports_done, + self); + wp_proxy_sync (WP_PROXY(self->proxy_node)); +} + +static void +on_port_added(WpRemotePipewire *rp, guint id, guint parent_id, gconstpointer p, + gpointer d) +{ + WpPipewireSimpleEndpoint *self = d; + struct pw_port_proxy *port_proxy = NULL; + + /* Only handle ports owned by this endpoint */ + if (parent_id != self->global_id) + return; + + /* Create the proxy port async */ + port_proxy = wp_remote_pipewire_proxy_bind (self->remote_pipewire, id, + PW_TYPE_INTERFACE_Port); + g_return_if_fail(port_proxy); + wp_proxy_port_new(id, port_proxy, on_proxy_port_created, self); +} + +static void +emit_endpoint_ports(WpPipewireSimpleEndpoint *self) +{ + struct pw_node_proxy* node_proxy = NULL; + struct spa_audio_info_raw format = { 0, }; + struct spa_pod *param; + struct spa_pod_builder pod_builder = { 0, }; + char buf[1024]; + + /* Get the pipewire node proxy */ + node_proxy = wp_proxy_get_pw_proxy(WP_PROXY(self->proxy_node)); + g_return_if_fail (node_proxy); + + /* TODO: Assume all clients have this format for now */ + format.format = SPA_AUDIO_FORMAT_F32P; + format.flags = 1; + format.rate = 48000; + format.channels = 2; + format.position[0] = 0; + format.position[1] = 0; + + /* Build the param 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)); + + /* Set the param profile to emit the ports */ + pw_node_proxy_set_param(node_proxy, SPA_PARAM_Profile, 0, param); +} + +static void +on_proxy_node_created(GObject *initable, GAsyncResult *res, gpointer data) +{ + WpPipewireSimpleEndpoint *self = data; GVariantDict d; uint32_t ids[1] = { SPA_PARAM_Props }; uint32_t n_ids = 1; struct pw_node_proxy *node_proxy = NULL; + /* Get the proxy node */ + self->proxy_node = wp_proxy_node_new_finish(initable, res, NULL); + g_return_if_fail (self->proxy_node); + + /* Emit the ports */ + emit_endpoint_ports(self); + /* Add a custom node proxy event listener */ node_proxy = wp_proxy_get_pw_proxy(WP_PROXY(self->proxy_node)); + g_return_if_fail (node_proxy); pw_node_proxy_add_listener (node_proxy, &self->node_proxy_listener, &node_node_proxy_events, self); pw_node_proxy_subscribe_params (node_proxy, ids, n_ids); @@ -143,8 +261,54 @@ simple_endpoint_constructed (GObject * object) g_variant_dict_insert (&d, "default-value", "b", FALSE); wp_endpoint_register_control (WP_ENDPOINT (self), g_variant_dict_end (&d)); } +} + +static void +wp_simple_endpoint_init_async (GAsyncInitable *initable, int io_priority, + GCancellable *cancellable, GAsyncReadyCallback callback, gpointer data) +{ + WpPipewireSimpleEndpoint *self = WP_PIPEWIRE_SIMPLE_ENDPOINT (initable); + g_autoptr (WpCore) core = wp_endpoint_get_core(WP_ENDPOINT(self)); + struct pw_node_proxy *node_proxy = NULL; + + /* Create the async task */ + self->init_task = g_task_new (initable, cancellable, callback, data); + + /* Init the proxies_port array */ + self->proxies_port = g_ptr_array_new_full(2, (GDestroyNotify)g_object_unref); + + /* Register a port_added callback */ + self->remote_pipewire = wp_core_get_global (core, WP_GLOBAL_REMOTE_PIPEWIRE); + g_return_if_fail(self->remote_pipewire); + g_signal_connect(self->remote_pipewire, "global-added::port", + (GCallback)on_port_added, self); + + /* Create the proxy node async */ + node_proxy = wp_remote_pipewire_proxy_bind (self->remote_pipewire, + self->global_id, PW_TYPE_INTERFACE_Node); + g_return_if_fail(node_proxy); + wp_proxy_node_new(self->global_id, node_proxy, on_proxy_node_created, self); + + /* Call the parent interface */ + wp_simple_endpoint_parent_interface->init_async (initable, io_priority, + cancellable, callback, data); +} + +static void +wp_simple_endpoint_async_initable_init (gpointer iface, gpointer iface_data) +{ + GAsyncInitableIface *ai_iface = iface; + + /* Set the parent interface */ + wp_simple_endpoint_parent_interface = g_type_interface_peek_parent (iface); + + /* Only set the init_async */ + ai_iface->init_async = wp_simple_endpoint_init_async; +} - G_OBJECT_CLASS (simple_endpoint_parent_class)->constructed (object); +static void +simple_endpoint_init (WpPipewireSimpleEndpoint * self) +{ } static void @@ -152,12 +316,18 @@ simple_endpoint_finalize (GObject * object) { WpPipewireSimpleEndpoint *self = WP_PIPEWIRE_SIMPLE_ENDPOINT (object); - /* Unref the proxy node */ - if (self->proxy_node) { - g_object_unref(self->proxy_node); - self->proxy_node = NULL; + /* Destroy the proxies port */ + if (self->proxies_port) { + g_ptr_array_free(self->proxies_port, TRUE); + self->proxies_port = NULL; } + /* Destroy the proxy node */ + g_clear_object(&self->proxy_node); + + /* Destroy the done task */ + g_clear_object(&self->init_task); + G_OBJECT_CLASS (simple_endpoint_parent_class)->finalize (object); } @@ -168,13 +338,8 @@ simple_endpoint_set_property (GObject * object, guint property_id, WpPipewireSimpleEndpoint *self = WP_PIPEWIRE_SIMPLE_ENDPOINT (object); switch (property_id) { - case PROP_NODE_PROXY: - g_clear_object(&self->proxy_node); - self->proxy_node = g_value_dup_object(value); - break; - case PROP_PORT_PROXY: - g_clear_object(&self->proxy_port); - self->proxy_port = g_value_dup_object(value); + case PROP_GLOBAL_ID: + self->global_id = g_value_get_uint(value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); @@ -189,11 +354,8 @@ simple_endpoint_get_property (GObject * object, guint property_id, WpPipewireSimpleEndpoint *self = WP_PIPEWIRE_SIMPLE_ENDPOINT (object); switch (property_id) { - case PROP_NODE_PROXY: - g_value_set_object (value, self->proxy_node); - break; - case PROP_PORT_PROXY: - g_value_set_object (value, self->proxy_port); + case PROP_GLOBAL_ID: + g_value_set_uint (value, self->global_id); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); @@ -206,16 +368,14 @@ simple_endpoint_prepare_link (WpEndpoint * ep, guint32 stream_id, WpEndpointLink * link, GVariant ** properties, GError ** error) { WpPipewireSimpleEndpoint *self = WP_PIPEWIRE_SIMPLE_ENDPOINT (ep); - uint32_t node_id = wp_proxy_get_global_id(WP_PROXY(self->proxy_node)); - uint32_t port_id = wp_proxy_get_global_id(WP_PROXY(self->proxy_port)); GVariantBuilder b; /* Set the properties */ g_variant_builder_init (&b, G_VARIANT_TYPE_VARDICT); g_variant_builder_add (&b, "{sv}", "node-id", - g_variant_new_uint32 (node_id)); + g_variant_new_uint32 (self->global_id)); g_variant_builder_add (&b, "{sv}", "node-port-id", - g_variant_new_uint32 (port_id)); + g_variant_new_uint32 (-1)); *properties = g_variant_builder_end (&b); return TRUE; @@ -294,7 +454,6 @@ simple_endpoint_class_init (WpPipewireSimpleEndpointClass * klass) GObjectClass *object_class = (GObjectClass *) klass; WpEndpointClass *endpoint_class = (WpEndpointClass *) klass; - object_class->constructed = simple_endpoint_constructed; object_class->finalize = simple_endpoint_finalize; object_class->set_property = simple_endpoint_set_property; object_class->get_property = simple_endpoint_get_property; @@ -303,48 +462,40 @@ simple_endpoint_class_init (WpPipewireSimpleEndpointClass * klass) endpoint_class->get_control_value = simple_endpoint_get_control_value; endpoint_class->set_control_value = simple_endpoint_set_control_value; - g_object_class_install_property (object_class, PROP_NODE_PROXY, - g_param_spec_object ("node-proxy", "node-proxy", - "Pointer to the node proxy of the client", WP_TYPE_PROXY_NODE, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); - g_object_class_install_property (object_class, PROP_PORT_PROXY, - g_param_spec_object ("port-proxy", "port-proxy", - "Pointer to the port proxy of the client", WP_TYPE_PROXY_PORT, + g_object_class_install_property (object_class, PROP_GLOBAL_ID, + g_param_spec_uint ("global-id", "global-id", + "The global Id this endpoint refers to", 0, G_MAXUINT, 0, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); } -gpointer +void simple_endpoint_factory (WpFactory * factory, GType type, - GVariant * properties) + GVariant * properties, GAsyncReadyCallback ready, gpointer user_data) { g_autoptr (WpCore) core = NULL; - guint64 proxy_node; - guint64 proxy_port; - const gchar *name; - const gchar *media_class; + const gchar *name, *media_class; + guint global_id; - g_return_val_if_fail (type == WP_TYPE_ENDPOINT, NULL); - g_return_val_if_fail (properties != NULL, NULL); - g_return_val_if_fail (g_variant_is_of_type (properties, - G_VARIANT_TYPE_VARDICT), NULL); + /* Make sure the type is correct */ + g_return_if_fail (type == WP_TYPE_ENDPOINT); + /* Get the Core */ core = wp_factory_get_core (factory); - g_return_val_if_fail (core != NULL, NULL); + g_return_if_fail (core); + /* Get the properties */ if (!g_variant_lookup (properties, "name", "&s", &name)) - return NULL; + return; if (!g_variant_lookup (properties, "media-class", "&s", &media_class)) - return NULL; - if (!g_variant_lookup (properties, "proxy-node", "t", &proxy_node)) - return NULL; - if (!g_variant_lookup (properties, "proxy-port", "t", &proxy_port)) - return NULL; + return; + if (!g_variant_lookup (properties, "global-id", "u", &global_id)) + return; - return g_object_new (simple_endpoint_get_type (), + g_async_initable_new_async ( + simple_endpoint_get_type (), G_PRIORITY_DEFAULT, NULL, ready, user_data, "core", core, "name", name, "media-class", media_class, - "node-proxy", (gpointer) proxy_node, - "port-proxy", (gpointer) proxy_port, + "global-id", global_id, NULL); } diff --git a/modules/module-pw-alsa-udev.c b/modules/module-pw-alsa-udev.c index c98cd76cf713d263b30236bdd813e1bbb8cc141a..576e1e2cd141b38a86dad3d99c14f520cc9b5fe5 100644 --- a/modules/module-pw-alsa-udev.c +++ b/modules/module-pw-alsa-udev.c @@ -19,143 +19,37 @@ struct impl { WpModule *module; WpRemotePipewire *remote_pipewire; - GHashTable *alsa_nodes_info; }; -struct endpoint_info -{ - gchar *name; - gchar *media_class; -}; - -struct proxy_info -{ - const struct impl *impl; - uint32_t node_id; - WpProxyPort *proxy_port; -}; - -static void -endpoint_info_destroy(gpointer p) -{ - struct endpoint_info *ei = p; - - /* Free the name */ - g_free (ei->name); - - /* Free the media class */ - g_free (ei->media_class); - - /* Clean up */ - g_slice_free (struct endpoint_info, p); -} - static void -proxy_info_destroy(gpointer p) +on_endpoint_created(GObject *initable, GAsyncResult *res, gpointer d) { - struct proxy_info *pi = p; - - /* Unref the proxy port */ - g_clear_object (&pi->proxy_port); - - /* Clean up */ - g_slice_free (struct proxy_info, p); -} - -static void -unregister_endpoint (WpProxy* wp_proxy, WpEndpoint *endpoint) -{ - g_return_if_fail(WP_IS_PROXY(wp_proxy)); - g_return_if_fail(WP_IS_ENDPOINT(endpoint)); - - /* Unregister the endpoint */ - wp_endpoint_unregister(endpoint); -} - -static void -proxy_node_created(GObject *initable, GAsyncResult *res, gpointer data) -{ - struct proxy_info *pi = data; - const struct impl *impl = pi->impl; - g_autoptr (WpCore) core = wp_module_get_core (impl->module); - g_autoptr(WpProxyNode) proxy_node = NULL; - struct endpoint_info *ei = NULL; - GVariantBuilder b; - g_autoptr (GVariant) endpoint_props = NULL; g_autoptr (WpEndpoint) endpoint = NULL; + guint global_id = 0; - /* Get the proxy */ - proxy_node = wp_proxy_node_new_finish(initable, res, NULL); - if (!proxy_node) - return; - - /* Get the alsa node info */ - ei = g_hash_table_lookup(impl->alsa_nodes_info, GINT_TO_POINTER(pi->node_id)); - if (!ei) + /* Get the endpoint */ + endpoint = wp_endpoint_new_finish(initable, res, NULL); + if (!endpoint) return; - /* Build the 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", - pi->node_id, ei->name))); - g_variant_builder_add (&b, "{sv}", - "media-class", g_variant_new_string (ei->media_class)); - g_variant_builder_add (&b, "{sv}", - "proxy-node", g_variant_new_uint64 ((guint64) proxy_node)); - g_variant_builder_add (&b, "{sv}", - "proxy-port", g_variant_new_uint64 ((guint64) pi->proxy_port)); - endpoint_props = g_variant_builder_end (&b); - - /* Create and register the endpoint */ - endpoint = wp_factory_make (core, "pw-audio-softdsp-endpoint", - WP_TYPE_ENDPOINT, endpoint_props); + /* Get the endpoint global id */ + g_object_get (endpoint, "global-id", &global_id, NULL); + g_debug ("Created alsa endpoint for global id %d", global_id); /* Register the endpoint */ wp_endpoint_register (endpoint); - - /* Set destroy handler to unregister endpoint when the proxy is detroyed */ - g_signal_connect (proxy_node, "destroyed", G_CALLBACK(unregister_endpoint), - endpoint); - - /* Clean up */ - proxy_info_destroy (pi); -} - -static void -proxy_port_created(GObject *initable, GAsyncResult *res, gpointer data) -{ - struct proxy_info *pi = data; - const struct impl *impl = pi->impl; - WpProxyPort *proxy_port = NULL; - struct pw_proxy *proxy = NULL; - - /* Get the proxy port */ - proxy_port = wp_proxy_port_new_finish(initable, res, NULL); - if (!proxy_port) - return; - - /* Forward the proxy port */ - pi->proxy_port = proxy_port; - - /* Get the node proxy */ - proxy = wp_remote_pipewire_proxy_bind (impl->remote_pipewire, pi->node_id, - PW_TYPE_INTERFACE_Node); - if (!proxy) - return; - - /* Create the proxy node asynchronically */ - wp_proxy_node_new(pi->node_id, proxy, proxy_node_created, pi); } static void -handle_node(WpRemotePipewire *rp, guint id, guint parent_id, gconstpointer p, +on_node_added(WpRemotePipewire *rp, guint id, guint parent_id, gconstpointer p, gpointer d) { struct impl *impl = d; const struct spa_dict *props = p; - const gchar *media_class = NULL, *name = NULL; - struct endpoint_info *ei = NULL; + g_autoptr (WpCore) core = wp_module_get_core (impl->module); + const gchar *name, *media_class; + GVariantBuilder b; + g_autoptr (GVariant) endpoint_props = NULL; /* Make sure the node has properties */ g_return_if_fail(props); @@ -170,40 +64,20 @@ handle_node(WpRemotePipewire *rp, guint id, guint parent_id, gconstpointer p, if (g_str_has_prefix (media_class, "Audio/DSP")) return; - /* Create the endpoint info */ - ei = g_slice_new0 (struct endpoint_info); - ei->name = g_strdup(name); - ei->media_class = g_strdup(media_class); - - /* Insert the alsa node info in the hash table */ - g_hash_table_insert(impl->alsa_nodes_info, GINT_TO_POINTER (id), ei); -} - -static void -handle_port(WpRemotePipewire *rp, guint id, guint parent_id, gconstpointer p, - gpointer d) -{ - struct impl *impl = d; - struct proxy_info *pi = NULL; - struct pw_proxy *proxy = NULL; - - /* Only handle ports whose parent is an alsa node */ - if (!g_hash_table_contains(impl->alsa_nodes_info, GINT_TO_POINTER (parent_id))) - return; - - /* Get the port proxy */ - proxy = wp_remote_pipewire_proxy_bind (rp, id, PW_TYPE_INTERFACE_Port); - if (!proxy) - return; - - /* Create the port info */ - pi = g_slice_new0 (struct proxy_info); - pi->impl = impl; - pi->node_id = parent_id; - pi->proxy_port = NULL; + /* 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 ( + "Endpoint %u: %s", id, name))); + g_variant_builder_add (&b, "{sv}", + "media-class", g_variant_new_string (media_class)); + g_variant_builder_add (&b, "{sv}", + "global-id", g_variant_new_uint32 (id)); + endpoint_props = g_variant_builder_end (&b); - /* Create the proxy port asynchronically */ - wp_proxy_port_new(id, proxy, proxy_port_created, pi); + /* Create the endpoint async */ + wp_factory_make_async (core, "pw-audio-softdsp-endpoint", WP_TYPE_ENDPOINT, + endpoint_props, on_endpoint_created, impl); } static void @@ -215,10 +89,6 @@ module_destroy (gpointer data) impl->module = NULL; impl->remote_pipewire = NULL; - /* Destroy the hash table */ - g_hash_table_unref (impl->alsa_nodes_info); - impl->alsa_nodes_info = NULL; - /* Clean up */ g_slice_free (struct impl, impl); } @@ -241,13 +111,10 @@ wireplumber__module_init (WpModule * module, WpCore * core, GVariant * args) impl = g_slice_new0(struct impl); impl->module = module; impl->remote_pipewire = rp; - impl->alsa_nodes_info = g_hash_table_new_full (g_direct_hash, - g_direct_equal, NULL, endpoint_info_destroy); /* Set destroy callback for impl */ wp_module_set_destroy_callback (module, module_destroy, impl); /* Register the global addded callbacks */ - g_signal_connect(rp, "global-added::node", (GCallback)handle_node, impl); - g_signal_connect(rp, "global-added::port", (GCallback)handle_port, impl); + g_signal_connect(rp, "global-added::node", (GCallback)on_node_added, impl); } diff --git a/modules/module-pw-audio-softdsp-endpoint.c b/modules/module-pw-audio-softdsp-endpoint.c index c728dce55b3193b51f6720b35751f89fd5879886..f901508ea9f68adc83e26193800a6dd7c736fcc8 100644 --- a/modules/module-pw-audio-softdsp-endpoint.c +++ b/modules/module-pw-audio-softdsp-endpoint.c @@ -26,6 +26,12 @@ struct _WpPwAudioSoftdspEndpoint { WpEndpoint parent; + /* The global-id this endpoint refers to */ + guint global_id; + + /* The task to signal the endpoint is initialized */ + GTask *init_task; + /* The remote pipewire */ WpRemotePipewire *remote_pipewire; @@ -36,28 +42,25 @@ struct _WpPwAudioSoftdspEndpoint /* Direction */ enum pw_direction direction; - /* Proxy */ + /* Proxies */ WpProxyNode *proxy_node; WpProxyPort *proxy_port; - - /* DSP port id */ - uint32_t dsp_port_id; + WpProxyNode *proxy_dsp; + GPtrArray *proxies_dsp_port; /* Volume */ gfloat master_volume; gboolean master_mute; - /* TODO: This needs to use the new proxy API */ - struct pw_node_proxy *dsp_proxy; + /* DSP */ + uint32_t dsp_port_id; struct spa_hook dsp_listener; - struct pw_node_info *dsp_info; struct pw_proxy *link_proxy; }; enum { PROP_0, - PROP_NODE_PROXY, - PROP_PORT_PROXY, + PROP_GLOBAL_ID, }; enum { @@ -66,26 +69,33 @@ enum { CONTROL_SELECTED, }; +static GAsyncInitableIface *wp_endpoint_parent_interface = NULL; +static void wp_endpoint_async_initable_init (gpointer iface, + gpointer iface_data); + G_DECLARE_FINAL_TYPE (WpPwAudioSoftdspEndpoint, endpoint, WP_PW, AUDIO_SOFTDSP_ENDPOINT, WpEndpoint) -G_DEFINE_TYPE (WpPwAudioSoftdspEndpoint, endpoint, WP_TYPE_ENDPOINT) +G_DEFINE_TYPE_WITH_CODE (WpPwAudioSoftdspEndpoint, endpoint, WP_TYPE_ENDPOINT, + G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, + wp_endpoint_async_initable_init)) static gboolean endpoint_prepare_link (WpEndpoint * ep, guint32 stream_id, WpEndpointLink * link, GVariant ** properties, GError ** error) { WpPwAudioSoftdspEndpoint *self = WP_PW_AUDIO_SOFTDSP_ENDPOINT (ep); + const struct pw_node_info *dsp_info = NULL; GVariantBuilder b; - /* Make sure dsp info is valid */ - if (!self->dsp_info) - return FALSE; + /* Get the dsp info */ + dsp_info = wp_proxy_node_get_info(self->proxy_dsp); + g_return_val_if_fail (dsp_info, 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_new_uint32 (dsp_info->id)); g_variant_builder_add (&b, "{sv}", "node-port-id", g_variant_new_uint32 (self->dsp_port_id)); *properties = g_variant_builder_end (&b); @@ -98,15 +108,18 @@ on_dsp_running(WpPwAudioSoftdspEndpoint *self) { struct pw_properties *props; const struct pw_node_info *node_info = NULL; + const struct pw_node_info *dsp_info = NULL; /* Return if the node has already been linked */ - if (self->link_proxy) - return; + g_return_if_fail (!self->link_proxy); /* Get the node info */ node_info = wp_proxy_node_get_info(self->proxy_node); - if (!node_info) - return; + g_return_if_fail (node_info); + + /* Get the dsp info */ + dsp_info = wp_proxy_node_get_info(self->proxy_dsp); + g_return_if_fail (dsp_info); /* Create new properties */ props = pw_properties_new(NULL, NULL); @@ -114,14 +127,14 @@ on_dsp_running(WpPwAudioSoftdspEndpoint *self) /* 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_NODE_ID, "%d", dsp_info->id); pw_properties_setf(props, PW_LINK_OUTPUT_PORT_ID, "%d", -1); pw_properties_setf(props, PW_LINK_INPUT_NODE_ID, "%d", 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", 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_NODE_ID, "%d", dsp_info->id); pw_properties_setf(props, PW_LINK_INPUT_PORT_ID, "%d", -1); } @@ -150,9 +163,6 @@ 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: @@ -221,32 +231,88 @@ static const struct pw_node_proxy_events dsp_node_events = { }; static void -emit_audio_dsp_node (WpPwAudioSoftdspEndpoint *self) +on_proxy_node_destroyed (WpProxy* wp_proxy, WpEndpoint *endpoint) { - struct pw_properties *props; - const char *dsp_name = NULL; + g_return_if_fail(WP_IS_ENDPOINT(endpoint)); + + /* Unregister the endpoint */ + wp_endpoint_unregister(endpoint); +} + +static void +on_proxy_dsp_done(WpProxy *proxy, gpointer data) +{ + WpPwAudioSoftdspEndpoint *self = data; + + /* Don't do anything if the endpoint has already been initialized */ + if (!self->init_task) + return; + + /* Set destroy handler to unregister endpoint on proxy_node destruction */ + g_signal_connect (self->proxy_node, "destroyed", + G_CALLBACK(on_proxy_node_destroyed), WP_ENDPOINT(self)); + + /* Finish the creation of the endpoint */ + g_task_return_boolean (self->init_task, TRUE); + g_clear_object(&self->init_task); +} + +static void +on_proxy_dsp_created(GObject *initable, GAsyncResult *res, gpointer data) +{ + WpPwAudioSoftdspEndpoint *self = data; + struct pw_node_proxy *dsp_proxy = NULL; + const struct spa_audio_info_raw *port_format; + struct spa_audio_info_raw format; uint8_t buf[1024]; struct spa_pod_builder pod_builder = { 0, }; struct spa_pod *param; - const struct pw_node_info *node_info; - const struct spa_audio_info_raw *port_format; - struct spa_audio_info_raw format; - /* Get the node info */ - node_info = wp_proxy_node_get_info(self->proxy_node); - if (!node_info) - return; + /* Get the proxy dsp */ + self->proxy_dsp = wp_proxy_node_new_finish(initable, res, NULL); + g_return_if_fail (self->proxy_dsp); + + /* Add a custom dsp listener */ + dsp_proxy = wp_proxy_get_pw_proxy(WP_PROXY(self->proxy_dsp)); + g_return_if_fail (dsp_proxy); + pw_node_proxy_add_listener(dsp_proxy, &self->dsp_listener, + &dsp_node_events, self); + + /* Emit the props param */ + pw_node_proxy_enum_params (dsp_proxy, 0, SPA_PARAM_Props, 0, -1, NULL); /* Get the port format */ port_format = wp_proxy_port_get_format(self->proxy_port); - if (!port_format) - return; + g_return_if_fail (port_format); format = *port_format; + /* Build the param 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_reverse(self->direction)), + SPA_PARAM_PROFILE_format, SPA_POD_Pod(param)); + + /* Set the param profile to emit the dsp ports */ + pw_node_proxy_set_param(dsp_proxy, SPA_PARAM_Profile, 0, param); +} + +static void +emit_audio_dsp_node (WpPwAudioSoftdspEndpoint *self) +{ + struct pw_properties *props; + const char *dsp_name = NULL; + struct pw_node_proxy *dsp_proxy = NULL; + const struct pw_node_info *node_info; + + /* Get the node info */ + node_info = wp_proxy_node_get_info(self->proxy_node); + g_return_if_fail (node_info); + /* Get the properties */ props = pw_properties_new_dict(node_info->props); - if (!props) - return; + g_return_if_fail (props); /* Get the DSP name */ dsp_name = pw_properties_get(props, "device.nick"); @@ -259,87 +325,25 @@ emit_audio_dsp_node (WpPwAudioSoftdspEndpoint *self) pw_properties_setf(props, "audio-dsp.maxbuffer", "%ld", MAX_QUANTUM_SIZE * sizeof(float)); - /* Set the DSP proxy and listener */ - self->dsp_proxy = wp_remote_pipewire_create_object(self->remote_pipewire, + /* Create the proxy dsp async */ + dsp_proxy = wp_remote_pipewire_create_object(self->remote_pipewire, "audio-dsp", PW_TYPE_INTERFACE_Node, &props->dict); - pw_node_proxy_add_listener(self->dsp_proxy, &self->dsp_listener, - &dsp_node_events, self); - pw_node_proxy_enum_params (self->dsp_proxy, 0, SPA_PARAM_Props, 0, -1, NULL); - - /* Set DSP proxy params */ - 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_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); + wp_proxy_node_new(pw_proxy_get_id((struct pw_proxy *)dsp_proxy), dsp_proxy, + on_proxy_dsp_created, self); /* Clean up */ pw_properties_free(props); } static void -handle_port(WpRemotePipewire *rp, guint id, guint parent_id, gconstpointer p, - gpointer d) -{ - WpPwAudioSoftdspEndpoint *self = d; - const struct spa_dict *props = p; - const char *direction_prop = NULL; - enum pw_direction direction; - - /* Make sure the dsp port is not already set*/ - if (self->dsp_port_id != 0) - return; - - /* Make sure the port has porperties */ - if (!props) - return; - - /* Only handle ports owned by this endpoint */ - if (!self->dsp_info || self->dsp_info->id != parent_id) - return; - - /* Get the direction property */ - direction_prop = spa_dict_lookup(props, "port.direction"); - if (!direction_prop) - return; - direction = - !strcmp(direction_prop, "out") ? PW_DIRECTION_OUTPUT : PW_DIRECTION_INPUT; - - /* Only handle ports with the oposit direction of the endpoint */ - if (self->direction == direction) - return; - - /* Set the dsp port id */ - self->dsp_port_id = id; -} - -static void -endpoint_constructed (GObject * object) +on_proxy_node_created(GObject *initable, GAsyncResult *res, gpointer data) { - WpPwAudioSoftdspEndpoint *self = WP_PW_AUDIO_SOFTDSP_ENDPOINT (object); - g_autoptr (WpCore) core = wp_endpoint_get_core(WP_ENDPOINT(self)); - const gchar *media_class = wp_endpoint_get_media_class (WP_ENDPOINT (self)); + WpPwAudioSoftdspEndpoint *self = data; GVariantDict d; - /* Set Remote Pipewire */ - self->remote_pipewire = wp_core_get_global (core, WP_GLOBAL_REMOTE_PIPEWIRE); - if (!self->remote_pipewire) - g_critical ("failed to get remote pipewire "); - - /* Register the global-added::port callback in remote pipewire */ - g_signal_connect(self->remote_pipewire, "global-added::port", - (GCallback)handle_port, self); - - /* Set the direction */ - if (g_str_has_suffix (media_class, "Source")) - self->direction = PW_DIRECTION_INPUT; - else if (g_str_has_suffix (media_class, "Sink")) - self->direction = PW_DIRECTION_OUTPUT; - else - g_critical ("failed to parse direction"); + /* Get the proxy node */ + self->proxy_node = wp_proxy_node_new_finish(initable, res, NULL); + g_return_if_fail (self->proxy_node); /* Emit the audio DSP node */ emit_audio_dsp_node(self); @@ -373,37 +377,143 @@ endpoint_constructed (GObject * object) g_variant_dict_insert (&d, "type", "s", "b"); g_variant_dict_insert (&d, "default-value", "b", self->selected); wp_endpoint_register_control (WP_ENDPOINT (self), g_variant_dict_end (&d)); +} + +static void +on_proxy_port_created(GObject *initable, GAsyncResult *res, gpointer data) +{ + WpPwAudioSoftdspEndpoint *self = data; + struct pw_node_proxy *node_proxy = NULL; + + /* Get the proxy port */ + self->proxy_port = wp_proxy_port_new_finish(initable, res, NULL); + g_return_if_fail (self->proxy_port); - G_OBJECT_CLASS (endpoint_parent_class)->constructed (object); + /* Create the proxy node async */ + node_proxy = wp_remote_pipewire_proxy_bind (self->remote_pipewire, + self->global_id, PW_TYPE_INTERFACE_Node); + g_return_if_fail(node_proxy); + wp_proxy_node_new(self->global_id, node_proxy, on_proxy_node_created, self); } static void -endpoint_finalize (GObject * object) +handle_node_port(WpPwAudioSoftdspEndpoint *self, guint id, guint parent_id, + const struct spa_dict *props) { - WpPwAudioSoftdspEndpoint *self = WP_PW_AUDIO_SOFTDSP_ENDPOINT (object); + struct pw_port_proxy *port_proxy = NULL; - /* Set to NULL remote pipewire as we don't own the reference */ - self->remote_pipewire = NULL; + /* Alsa nodes should have 1 port only, so make sure proxy_port is not set */ + if (self->proxy_port != 0) + return; - /* Unref the proxy node */ - g_clear_object (&self->proxy_node); + /* Create the proxy port async */ + port_proxy = wp_remote_pipewire_proxy_bind (self->remote_pipewire, id, + PW_TYPE_INTERFACE_Port); + g_return_if_fail(port_proxy); + wp_proxy_port_new(id, port_proxy, on_proxy_port_created, self); +} - /* Unref the proxy port */ - g_clear_object (&self->proxy_port); +static void +on_proxy_dsp_port_created(GObject *initable, GAsyncResult *res, gpointer data) +{ + WpPwAudioSoftdspEndpoint *self = data; + WpProxyPort *proxy_dsp_port = NULL; - /* Clear the dsp info */ - if (self->dsp_info) { - pw_node_info_free(self->dsp_info); - self->dsp_info = NULL; + /* Get the proxy dsp port */ + proxy_dsp_port = wp_proxy_port_new_finish(initable, res, NULL); + g_return_if_fail (proxy_dsp_port); + + /* Add the proxy dsp port to the array */ + g_return_if_fail (self->proxies_dsp_port); + g_ptr_array_add(self->proxies_dsp_port, proxy_dsp_port); + + /* Register a callback to know when all the dsp ports have been emitted */ + g_signal_connect(self->proxy_dsp, "done", (GCallback)on_proxy_dsp_done, self); + wp_proxy_sync (WP_PROXY(self->proxy_dsp)); +} + +static void +handle_dsp_port(WpPwAudioSoftdspEndpoint *self, guint id, guint parent_id, + const struct spa_dict *props) +{ + const char *direction_prop = NULL; + struct pw_port_proxy *port_proxy = NULL; + enum pw_direction direction; + + /* Create the proxy dsp port async */ + port_proxy = wp_remote_pipewire_proxy_bind (self->remote_pipewire, id, + PW_TYPE_INTERFACE_Port); + g_return_if_fail(port_proxy); + wp_proxy_port_new(id, port_proxy, on_proxy_dsp_port_created, self); + + /* Make sure the port has porperties */ + g_return_if_fail(props); + + /* TODO: For now we only handle 1 DSP port */ + if (self->dsp_port_id != 0) + return; + + /* Get the direction property */ + direction_prop = spa_dict_lookup(props, "port.direction"); + if (!direction_prop) + return; + direction = + !strcmp(direction_prop, "out") ? PW_DIRECTION_OUTPUT : PW_DIRECTION_INPUT; + + /* Only handle ports with the opposite direction of the endpoint */ + if (self->direction == direction) + return; + + /* Set the dsp port id */ + self->dsp_port_id = id; +} + +static void +on_port_added(WpRemotePipewire *rp, guint id, guint parent_id, gconstpointer p, + gpointer d) +{ + WpPwAudioSoftdspEndpoint *self = d; + const struct spa_dict *props = p; + const struct pw_node_info *dsp_info = NULL; + + /* Check if it is a node port and handle it */ + if (self->global_id == parent_id) { + handle_node_port(self, id, parent_id, props); + return; } - /* Destroy the dsp_proxy */ - if (self->dsp_proxy) { - spa_hook_remove (&self->dsp_listener); - pw_proxy_destroy ((struct pw_proxy *) self->dsp_proxy); - self->dsp_proxy = NULL; + /* Otherwise, check if it is a dsp port and handle it */ + if (!self->proxy_dsp) + return; + dsp_info = wp_proxy_node_get_info (self->proxy_dsp); + if (!dsp_info || dsp_info->id != parent_id) + return; + handle_dsp_port(self, id, parent_id, props); +} + +static void +endpoint_finalize (GObject * object) +{ + WpPwAudioSoftdspEndpoint *self = WP_PW_AUDIO_SOFTDSP_ENDPOINT (object); + + /* Destroy the proxies port */ + if (self->proxies_dsp_port) { + g_ptr_array_free(self->proxies_dsp_port, TRUE); + self->proxies_dsp_port = NULL; } + /* Destroy the proxy node */ + g_clear_object(&self->proxy_node); + + /* Destroy the proxy port */ + g_clear_object(&self->proxy_port); + + /* Destroy the proxy dsp */ + g_clear_object(&self->proxy_dsp); + + /* Destroy the done task */ + g_clear_object(&self->init_task); + G_OBJECT_CLASS (endpoint_parent_class)->finalize (object); } @@ -414,13 +524,8 @@ endpoint_set_property (GObject * object, guint property_id, WpPwAudioSoftdspEndpoint *self = WP_PW_AUDIO_SOFTDSP_ENDPOINT (object); switch (property_id) { - case PROP_NODE_PROXY: - g_clear_object(&self->proxy_node); - self->proxy_node = g_value_dup_object(value); - break; - case PROP_PORT_PROXY: - g_clear_object(&self->proxy_port); - self->proxy_port = g_value_dup_object(value); + case PROP_GLOBAL_ID: + self->global_id = g_value_get_uint(value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); @@ -435,11 +540,8 @@ endpoint_get_property (GObject * object, guint property_id, WpPwAudioSoftdspEndpoint *self = WP_PW_AUDIO_SOFTDSP_ENDPOINT (object); switch (property_id) { - case PROP_NODE_PROXY: - g_value_set_object (value, self->proxy_node); - break; - case PROP_PORT_PROXY: - g_value_set_object (value, self->proxy_port); + case PROP_GLOBAL_ID: + g_value_set_uint (value, self->global_id); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); @@ -472,14 +574,14 @@ endpoint_set_control_value (WpEndpoint * ep, guint32 control_id, WpPwAudioSoftdspEndpoint *self = WP_PW_AUDIO_SOFTDSP_ENDPOINT (ep); char buf[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buf, sizeof(buf)); + struct pw_node_proxy *dsp_proxy = NULL; float volume; bool mute; - if (!self->dsp_proxy) { - g_debug("WpEndpoint:%p too early to set control, dsp is not created yet", - self); - return FALSE; - } + /* Get the pipewire dsp proxy */ + g_return_val_if_fail (self->proxy_dsp, FALSE); + dsp_proxy = wp_proxy_get_pw_proxy (WP_PROXY(self->proxy_dsp)); + g_return_val_if_fail (dsp_proxy, FALSE); switch (control_id) { case CONTROL_VOLUME: @@ -488,13 +590,13 @@ endpoint_set_control_value (WpEndpoint * ep, guint32 control_id, g_debug("WpEndpoint:%p set volume control (%u) value, vol:%f", self, control_id, volume); - pw_node_proxy_set_param (self->dsp_proxy, + pw_node_proxy_set_param (dsp_proxy, SPA_PARAM_Props, 0, spa_pod_builder_add_object (&b, SPA_TYPE_OBJECT_Props, SPA_PARAM_Props, SPA_PROP_volume, SPA_POD_Float(volume), NULL)); - pw_node_proxy_enum_params (self->dsp_proxy, 0, SPA_PARAM_Props, 0, -1, + pw_node_proxy_enum_params (dsp_proxy, 0, SPA_PARAM_Props, 0, -1, NULL); break; @@ -504,13 +606,13 @@ endpoint_set_control_value (WpEndpoint * ep, guint32 control_id, g_debug("WpEndpoint:%p set mute control (%u) value, mute:%d", self, control_id, mute); - pw_node_proxy_set_param (self->dsp_proxy, + pw_node_proxy_set_param (dsp_proxy, SPA_PARAM_Props, 0, spa_pod_builder_add_object (&b, SPA_TYPE_OBJECT_Props, SPA_PARAM_Props, SPA_PROP_mute, SPA_POD_Bool(mute), NULL)); - pw_node_proxy_enum_params (self->dsp_proxy, 0, SPA_PARAM_Props, 0, -1, + pw_node_proxy_enum_params (dsp_proxy, 0, SPA_PARAM_Props, 0, -1, NULL); break; @@ -527,6 +629,51 @@ endpoint_set_control_value (WpEndpoint * ep, guint32 control_id, return TRUE; } +static void +wp_endpoint_init_async (GAsyncInitable *initable, int io_priority, + GCancellable *cancellable, GAsyncReadyCallback callback, gpointer data) +{ + WpPwAudioSoftdspEndpoint *self = WP_PW_AUDIO_SOFTDSP_ENDPOINT (initable); + g_autoptr (WpCore) core = wp_endpoint_get_core(WP_ENDPOINT(self)); + const gchar *media_class = wp_endpoint_get_media_class (WP_ENDPOINT (self)); + + /* Create the async task */ + self->init_task = g_task_new (initable, cancellable, callback, data); + + /* Init the proxies_dsp_port array */ + self->proxies_dsp_port = g_ptr_array_new_full(4, (GDestroyNotify)g_object_unref); + + /* Set the direction */ + if (g_str_has_suffix (media_class, "Source")) + self->direction = PW_DIRECTION_INPUT; + else if (g_str_has_suffix (media_class, "Sink")) + self->direction = PW_DIRECTION_OUTPUT; + else + g_critical ("failed to parse direction"); + + /* Register a port_added callback */ + self->remote_pipewire = wp_core_get_global (core, WP_GLOBAL_REMOTE_PIPEWIRE); + g_return_if_fail(self->remote_pipewire); + g_signal_connect(self->remote_pipewire, "global-added::port", + (GCallback)on_port_added, self); + + /* Call the parent interface */ + wp_endpoint_parent_interface->init_async (initable, io_priority, cancellable, + callback, data); +} + +static void +wp_endpoint_async_initable_init (gpointer iface, gpointer iface_data) +{ + GAsyncInitableIface *ai_iface = iface; + + /* Set the parent interface */ + wp_endpoint_parent_interface = g_type_interface_peek_parent (iface); + + /* Only set the init_async */ + ai_iface->init_async = wp_endpoint_init_async; +} + static void endpoint_init (WpPwAudioSoftdspEndpoint * self) { @@ -538,7 +685,6 @@ endpoint_class_init (WpPwAudioSoftdspEndpointClass * klass) GObjectClass *object_class = (GObjectClass *) klass; WpEndpointClass *endpoint_class = (WpEndpointClass *) klass; - object_class->constructed = endpoint_constructed; object_class->finalize = endpoint_finalize; object_class->set_property = endpoint_set_property; object_class->get_property = endpoint_get_property; @@ -548,48 +694,42 @@ endpoint_class_init (WpPwAudioSoftdspEndpointClass * klass) endpoint_class->set_control_value = endpoint_set_control_value; /* Instal the properties */ - g_object_class_install_property (object_class, PROP_NODE_PROXY, - g_param_spec_object ("node-proxy", "node-proxy", - "Pointer to the node proxy of the device", WP_TYPE_PROXY_NODE, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); - g_object_class_install_property (object_class, PROP_PORT_PROXY, - g_param_spec_object ("port-proxy", "port-proxy", - "Pointer to the port proxy of the device", WP_TYPE_PROXY_PORT, + g_object_class_install_property (object_class, PROP_GLOBAL_ID, + g_param_spec_uint ("global-id", "global-id", + "The global Id this endpoint refers to", 0, G_MAXUINT, 0, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); } -static gpointer -endpoint_factory (WpFactory * factory, GType type, GVariant * properties) +void +endpoint_factory (WpFactory * factory, GType type, GVariant * properties, + GAsyncReadyCallback ready, gpointer user_data) { g_autoptr (WpCore) core = NULL; - const gchar *name = NULL; - const gchar *media_class = NULL; - guint64 proxy_node, proxy_port; + const gchar *name, *media_class; + guint global_id; /* Make sure the type is correct */ - g_return_val_if_fail(type == WP_TYPE_ENDPOINT, NULL); + g_return_if_fail(type == WP_TYPE_ENDPOINT); /* Get the Core */ core = wp_factory_get_core(factory); - g_return_val_if_fail (core, NULL); + g_return_if_fail (core); /* Get the properties */ if (!g_variant_lookup (properties, "name", "&s", &name)) - return NULL; + return; if (!g_variant_lookup (properties, "media-class", "&s", &media_class)) - return NULL; - if (!g_variant_lookup (properties, "proxy-node", "t", &proxy_node)) - return NULL; - if (!g_variant_lookup (properties, "proxy-port", "t", &proxy_port)) - return NULL; + return; + if (!g_variant_lookup (properties, "global-id", "u", &global_id)) + return; /* Create and return the softdsp endpoint object */ - return g_object_new (endpoint_get_type (), + g_async_initable_new_async ( + endpoint_get_type (), G_PRIORITY_DEFAULT, NULL, ready, user_data, "core", core, "name", name, "media-class", media_class, - "node-proxy", (gpointer) proxy_node, - "port-proxy", (gpointer) proxy_port, + "global-id", global_id, NULL); } @@ -597,5 +737,5 @@ void wireplumber__module_init (WpModule * module, WpCore * core, GVariant * args) { /* Register the softdsp endpoint */ - wp_factory_new (core, "pw-audio-softdsp-endpoint", endpoint_factory); + wp_factory_new_async (core, "pw-audio-softdsp-endpoint", endpoint_factory); }