diff --git a/modules/module-pipewire.c b/modules/module-pipewire.c index 6e615b1d5c1cdf7cd9ac6dfba3530cb9c1d5eca2..55269f710871825355e563b2c410fa145780238b 100644 --- a/modules/module-pipewire.c +++ b/modules/module-pipewire.c @@ -27,150 +27,258 @@ struct module_data { WpModule *module; + /* Registry */ struct pw_registry_proxy *registry_proxy; struct spa_hook registry_listener; - struct pw_core_proxy *core_proxy; - struct spa_hook core_listener; - GQueue *done_queue; + /* Client nodes info */ + GHashTable *client_nodes_info; }; +struct endpoint_info +{ + gchar *name; + gchar *media_class; + const struct pw_proxy *proxy; +}; -typedef void (*WpDoneCallback)(gpointer, gpointer); - -struct done_data +struct proxy_info { - WpDoneCallback callback; - gpointer data; - GDestroyNotify data_destroy; + const struct module_data *data; + uint32_t node_id; + WpProxyPort *proxy_port; }; static void -done_data_destroy(gpointer p) +endpoint_info_destroy(gpointer p) { - struct done_data *dd = p; - if (dd->data_destroy) { - dd->data_destroy(dd->data); - dd->data = NULL; + struct endpoint_info *ei = p; + + /* Free the name */ + if (ei->name) { + g_free (ei->name); + ei->name = NULL; + } + + /* Free the media class */ + if (ei->media_class) { + g_free (ei->media_class); + ei->media_class = NULL; } - g_slice_free (struct done_data, dd); + + /* Clean up */ + g_slice_free (struct endpoint_info, p); } static void -sync_core_with_callback(struct module_data* impl, WpDoneCallback callback, - gpointer data, GDestroyNotify data_destroy) +proxy_info_destroy(gpointer p) { - struct done_data *dd = g_new0(struct done_data, 1); + struct proxy_info *pi = p; - /* 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); + /* Unref the proxy port */ + if (pi->proxy_port) { + g_object_unref (pi->proxy_port); + pi->proxy_port = NULL; + } - /* Sync the core */ - pw_core_proxy_sync(impl->core_proxy, 0, 0); + /* Clean up */ + g_slice_free (struct proxy_info, p); } static void -core_done(void *d, uint32_t id, int seq) +proxy_node_created(GObject *initable, GAsyncResult *res, gpointer d) { - struct module_data * 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(impl, dd->data); - done_data_destroy(dd); - } -} + struct proxy_info *pi = d; + const struct module_data *data = pi->data; + g_autoptr (WpCore) core = wp_module_get_core (data->module); + WpProxyNode *proxy_node = NULL; + struct endpoint_info *ei = NULL; + WpEndpoint *endpoint = NULL; + g_autoptr (GVariant) endpoint_props = NULL; + GVariantBuilder b; -static const struct pw_core_proxy_events core_events = { - PW_VERSION_CORE_EVENTS, - .done = core_done -}; + /* Get the proxy */ + proxy_node = wp_proxy_node_new_finish(initable, res, NULL); + if (!proxy_node) + return; + + /* Register the proxy node */ + wp_proxy_register(WP_PROXY(proxy_node)); + + /* Get the client node info */ + ei = g_hash_table_lookup(data->client_nodes_info, + GINT_TO_POINTER(pi->node_id)); + if (!ei) + 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) + g_object_ref(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); + + /* Register the endpoint */ + wp_endpoint_register (endpoint, core); + + /* Clean up */ + proxy_info_destroy (pi); +} static void -register_endpoint (struct module_data* data, WpEndpoint *ep) +proxy_port_created(GObject *initable, GAsyncResult *res, gpointer d) { - g_autoptr (WpCore) core = NULL; - core = wp_module_get_core (data->module); - g_return_if_fail (core != NULL); - wp_endpoint_register (ep, core); + struct proxy_info *pi = d; + const struct module_data *data = pi->data; + g_autoptr (WpCore) core = wp_module_get_core (data->module); + 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; + + /* Register the proxy port */ + wp_proxy_register(WP_PROXY(proxy_port)); + + /* Forward the proxy port */ + pi->proxy_port = proxy_port; + + /* Get the node proxy */ + proxy = pw_registry_proxy_bind (data->registry_proxy, pi->node_id, + PW_TYPE_INTERFACE_Node, PW_VERSION_NODE, 0); + if (!proxy) + return; + + /* Create the proxy node asynchronically */ + wp_proxy_node_new(core, proxy, proxy_node_created, pi); } static void -registry_global (void * d, uint32_t id, uint32_t parent_id, - uint32_t permissions, uint32_t type, uint32_t version, +handle_node (struct module_data *data, uint32_t id, uint32_t parent_id, const struct spa_dict * props) { - struct module_data *data = d; + struct endpoint_info *ei = NULL; const gchar *name; const gchar *media_class; struct pw_proxy *proxy; - GVariantBuilder b; - 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 && - props && (media_class = spa_dict_lookup(props, "media.class")) && - g_str_has_prefix (media_class, "Stream/")) - { - name = spa_dict_lookup (props, "media.name"); - 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); - - 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))); - 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); - - core = wp_module_get_core (data->module); - g_return_if_fail (core != NULL); - - endpoint = wp_factory_make (core, "pipewire-simple-endpoint", - WP_TYPE_ENDPOINT, endpoint_props); - sync_core_with_callback (data, (WpDoneCallback) register_endpoint, - g_steal_pointer (&endpoint), g_object_unref); + /* Make sure the node has properties */ + if (!props) { + g_warning("node has no properties, skipping..."); + return; + } + + /* Get the media_class */ + media_class = spa_dict_lookup(props, "media.class"); + + /* Only handle client Stream nodes */ + if (!g_str_has_prefix (media_class, "Stream/")) + return; + + /* Get the name */ + name = spa_dict_lookup (props, "media.name"); + 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); + + /* Get the proxy */ + proxy = pw_registry_proxy_bind (data->registry_proxy, id, + PW_TYPE_INTERFACE_Node, PW_VERSION_NODE, 0); + + /* 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_new0(struct endpoint_info, 1); + 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(struct module_data *data, uint32_t id, uint32_t parent_id, + const struct spa_dict *props) +{ + g_autoptr (WpCore) core = wp_module_get_core (data->module); + 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; + + /* Get the port proxy */ + proxy = pw_registry_proxy_bind (data->registry_proxy, id, + PW_TYPE_INTERFACE_Port, PW_VERSION_PORT, 0); + if (!proxy) + return; + + /* Create the port info */ + pi = g_new0(struct proxy_info, 1); + pi->data = data; + pi->node_id = parent_id; + pi->proxy_port = NULL; + + /* Create the proxy port asynchronically */ + wp_proxy_port_new(core, proxy, proxy_port_created, pi); +} + +static void +registry_global(void *d, uint32_t id, uint32_t parent_id, + uint32_t permissions, uint32_t type, uint32_t version, + const struct spa_dict *props) +{ + struct module_data *data = d; + + switch (type) { + case PW_TYPE_INTERFACE_Node: + handle_node(data, id, parent_id, props); + break; + + case PW_TYPE_INTERFACE_Port: + handle_port(data, id, parent_id, props); + break; + + default: + break; } } @@ -188,9 +296,7 @@ on_remote_connected (WpRemote *remote, WpRemoteState state, g_object_get (remote, "pw-remote", &pw_remote, NULL); - core_proxy = data->core_proxy = pw_remote_get_core_proxy (pw_remote); - pw_core_proxy_add_listener(data->core_proxy, &data->core_listener, - &core_events, data); + core_proxy = pw_remote_get_core_proxy (pw_remote); data->registry_proxy = pw_core_proxy_get_registry (core_proxy, PW_TYPE_INTERFACE_Registry, PW_VERSION_REGISTRY, 0); pw_registry_proxy_add_listener(data->registry_proxy, @@ -202,7 +308,13 @@ module_destroy (gpointer d) { struct module_data *data = d; - g_queue_free_full(data->done_queue, done_data_destroy); + /* Destroy the hash table */ + if (data->client_nodes_info) { + g_hash_table_destroy(data->client_nodes_info); + data->client_nodes_info = NULL; + } + + /* Clean up */ g_slice_free (struct module_data, data); } @@ -221,9 +333,12 @@ wireplumber__module_init (WpModule * module, WpCore * core, GVariant * args) return; } + /* Create the module data */ data = g_slice_new0 (struct module_data); data->module = module; - data->done_queue = g_queue_new(); + data->client_nodes_info = g_hash_table_new_full (g_direct_hash, + g_direct_equal, NULL, endpoint_info_destroy); + wp_module_set_destroy_callback (module, module_destroy, data); g_signal_connect (remote, "state-changed::connected", diff --git a/modules/module-pipewire/simple-endpoint.c b/modules/module-pipewire/simple-endpoint.c index fb2568155e15f51042edcf587a37bd136cf94f5b..7a44ecb073da28c7dcb3ae2006726d5b5584e9a4 100644 --- a/modules/module-pipewire/simple-endpoint.c +++ b/modules/module-pipewire/simple-endpoint.c @@ -22,14 +22,11 @@ struct _WpPipewireSimpleEndpoint { WpEndpoint parent; - /* Node */ - struct pw_node_proxy *node; - struct spa_hook proxy_listener; + /* Proxy */ + WpProxyNode *proxy_node; + WpProxyPort *proxy_port; struct spa_hook node_proxy_listener; - /* Info */ - struct pw_node_info *info; - /* controls cache */ gfloat volume; gboolean mute; @@ -38,6 +35,7 @@ struct _WpPipewireSimpleEndpoint enum { PROP_0, PROP_NODE_PROXY, + PROP_PORT_PROXY }; enum { @@ -96,15 +94,8 @@ 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, }; @@ -113,21 +104,6 @@ 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) { @@ -135,13 +111,13 @@ simple_endpoint_constructed (GObject * object) GVariantDict d; uint32_t ids[1] = { SPA_PARAM_Props }; uint32_t n_ids = 1; + struct pw_node_proxy *node_proxy = NULL; - 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, + /* Add a custom node proxy event listener */ + node_proxy = wp_proxy_get_pw_proxy(WP_PROXY(self->proxy_node)); + pw_node_proxy_add_listener (node_proxy, &self->node_proxy_listener, &node_node_proxy_events, self); - pw_node_proxy_subscribe_params (self->node, ids, n_ids); + pw_node_proxy_subscribe_params (node_proxy, ids, n_ids); g_variant_dict_init (&d, NULL); g_variant_dict_insert (&d, "id", "u", 0); @@ -176,9 +152,10 @@ simple_endpoint_finalize (GObject * object) { WpPipewireSimpleEndpoint *self = WP_PIPEWIRE_SIMPLE_ENDPOINT (object); - if (self->node) { - spa_hook_remove (&self->proxy_listener); - pw_proxy_destroy ((struct pw_proxy *) self->node); + /* Unref the proxy node */ + if (self->proxy_node) { + g_object_unref(self->proxy_node); + self->proxy_node = NULL; } G_OBJECT_CLASS (simple_endpoint_parent_class)->finalize (object); @@ -192,7 +169,12 @@ simple_endpoint_set_property (GObject * object, guint property_id, switch (property_id) { case PROP_NODE_PROXY: - self->node = g_value_get_pointer (value); + g_clear_object(&self->proxy_node); + self->proxy_node = g_value_get_object(value); + break; + case PROP_PORT_PROXY: + g_clear_object(&self->proxy_port); + self->proxy_port = g_value_get_object(value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); @@ -208,7 +190,10 @@ simple_endpoint_get_property (GObject * object, guint property_id, switch (property_id) { case PROP_NODE_PROXY: - g_value_set_pointer (value, self->node); + g_value_set_object (value, self->proxy_node); + break; + case PROP_PORT_PROXY: + g_value_set_object (value, self->proxy_port); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); @@ -221,6 +206,7 @@ simple_endpoint_prepare_link (WpEndpoint * ep, guint32 stream_id, WpEndpointLink * link, GVariant ** properties, GError ** error) { WpPipewireSimpleEndpoint *self = WP_PIPEWIRE_SIMPLE_ENDPOINT (ep); + const struct pw_node_info *node_info = NULL; GVariantBuilder b; /* TODO: Since the linking with a 1 port client works when passing -1 as @@ -228,12 +214,15 @@ simple_endpoint_prepare_link (WpEndpoint * ep, guint32 stream_id, * 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 */ + /* Get the node info */ + node_info = wp_proxy_node_get_info(self->proxy_node); + if (!node_info) + 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->info->id)); + g_variant_new_uint32 (node_info->id)); g_variant_builder_add (&b, "{sv}", "node-port-id", g_variant_new_uint32 (-1)); *properties = g_variant_builder_end (&b); @@ -266,6 +255,10 @@ simple_endpoint_set_control_value (WpEndpoint * ep, guint32 control_id, struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buf, sizeof(buf)); float volume; bool mute; + struct pw_node_proxy *node_proxy = NULL; + + /* Get the node proxy */ + node_proxy = wp_proxy_get_pw_proxy(WP_PROXY(self->proxy_node)); switch (control_id) { case CONTROL_VOLUME: @@ -274,7 +267,7 @@ simple_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->node, + pw_node_proxy_set_param (node_proxy, SPA_PARAM_Props, 0, spa_pod_builder_add_object (&b, SPA_TYPE_OBJECT_Props, SPA_PARAM_Props, @@ -288,7 +281,7 @@ simple_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->node, + pw_node_proxy_set_param (node_proxy, SPA_PARAM_Props, 0, spa_pod_builder_add_object (&b, SPA_TYPE_OBJECT_Props, SPA_PARAM_Props, @@ -320,8 +313,12 @@ simple_endpoint_class_init (WpPipewireSimpleEndpointClass * klass) endpoint_class->set_control_value = simple_endpoint_set_control_value; g_object_class_install_property (object_class, PROP_NODE_PROXY, - g_param_spec_pointer ("node-proxy", "node-proxy", - "Pointer to the pw_node_proxy* to wrap", + 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_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); } @@ -329,7 +326,8 @@ gpointer simple_endpoint_factory (WpFactory * factory, GType type, GVariant * properties) { - guint64 proxy; + guint64 proxy_node; + guint64 proxy_port; const gchar *name; const gchar *media_class; @@ -342,12 +340,15 @@ simple_endpoint_factory (WpFactory * factory, GType type, return NULL; if (!g_variant_lookup (properties, "media-class", "&s", &media_class)) return NULL; - if (!g_variant_lookup (properties, "node-proxy", "t", &proxy)) + 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 g_object_new (simple_endpoint_get_type (), "name", name, "media-class", media_class, - "node-proxy", (gpointer) proxy, + "node-proxy", (gpointer) proxy_node, + "port-proxy", (gpointer) proxy_port, NULL); } diff --git a/modules/module-pw-alsa-udev.c b/modules/module-pw-alsa-udev.c index b88e0fcbe803c73bff2e4094dd0ac582884f45c3..ebf221b842a315b8eb185ee88cbefae4aa74749d 100644 --- a/modules/module-pw-alsa-udev.c +++ b/modules/module-pw-alsa-udev.c @@ -100,7 +100,7 @@ proxy_node_created(GObject *initable, GAsyncResult *res, gpointer data) /* Get the alsa node info */ ei = g_hash_table_lookup(impl->alsa_nodes_info, GINT_TO_POINTER(pi->node_id)); - if (!data) + if (!ei) return; /* Build the properties for the endpoint */ diff --git a/modules/module-pw-audio-softdsp-endpoint.c b/modules/module-pw-audio-softdsp-endpoint.c index 75ff94d06f24fa83a5fa45d255c9cfcca0ba88b7..3e8d684be2617065db155fa1c8ea644503eeebfa 100644 --- a/modules/module-pw-audio-softdsp-endpoint.c +++ b/modules/module-pw-audio-softdsp-endpoint.c @@ -509,7 +509,7 @@ endpoint_class_init (WpPwAudioSoftdspEndpointClass * klass) 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 ptoxy of the device", WP_TYPE_PROXY_PORT, + "Pointer to the port proxy of the device", WP_TYPE_PROXY_PORT, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); }