/* WirePlumber * * Copyright © 2019 Collabora Ltd. * @author George Kiagiadakis * * SPDX-License-Identifier: MIT */ /** * module-pipewire provides basic integration between wireplumber and pipewire. * It provides the pipewire core and remote, connects to pipewire and provides * the most primitive implementations of WpEndpoint and WpEndpointLink */ #include #include #include #include "module-pipewire/loop-source.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); gpointer simple_endpoint_link_factory (WpFactory * factory, GType type, GVariant * properties); struct module_data { WpModule *module; struct pw_core *core; struct pw_remote *remote; struct spa_hook remote_listener; struct pw_registry_proxy *registry_proxy; struct spa_hook registry_listener; }; 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; 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); wp_endpoint_register (endpoint, core); } } static const struct pw_registry_proxy_events registry_events = { PW_VERSION_REGISTRY_PROXY_EVENTS, .global = registry_global, }; static void on_remote_state_changed (void *d, enum pw_remote_state old_state, enum pw_remote_state new_state, const char *error) { struct module_data *data = d; struct pw_core_proxy *core_proxy; g_debug ("remote state changed, old:%s new:%s", pw_remote_state_as_string (old_state), pw_remote_state_as_string (new_state)); switch (new_state) { case PW_REMOTE_STATE_CONNECTED: core_proxy = pw_remote_get_core_proxy (data->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, &data->registry_listener, ®istry_events, data); break; case PW_REMOTE_STATE_UNCONNECTED: { g_autoptr (WpCore) core = wp_module_get_core (data->module); if (core) { g_message ("disconnected from PipeWire"); g_main_loop_quit (wp_core_get_global (core, g_quark_from_string ("main-loop"))); } break; } case PW_REMOTE_STATE_ERROR: { g_autoptr (WpCore) core = wp_module_get_core (data->module); if (core) { g_message ("PipeWire remote error: %s", error); g_main_loop_quit (wp_core_get_global (core, g_quark_from_string ("main-loop"))); } break; } default: break; } } static const struct pw_remote_events remote_events = { PW_VERSION_REMOTE_EVENTS, .state_changed = on_remote_state_changed, }; static gboolean connect_in_idle (struct pw_remote *remote) { pw_remote_connect (remote); return G_SOURCE_REMOVE; } static void module_destroy (gpointer d) { struct module_data *data = d; pw_remote_destroy (data->remote); pw_core_destroy (data->core); g_slice_free (struct module_data, data); } void wireplumber__module_init (WpModule * module, WpCore * core, GVariant * args) { GSource *source; struct module_data *data; pw_init (NULL, NULL); data = g_slice_new0 (struct module_data); data->module = module; wp_module_set_destroy_callback (module, module_destroy, data); source = wp_loop_source_new (); g_source_attach (source, NULL); data->core = pw_core_new (WP_LOOP_SOURCE (source)->loop, NULL, 0); wp_core_register_global (core, WP_GLOBAL_PW_CORE, data->core, NULL); data->remote = pw_remote_new (data->core, NULL, 0); pw_remote_add_listener (data->remote, &data->remote_listener, &remote_events, data); wp_core_register_global (core, WP_GLOBAL_PW_REMOTE, data->remote, NULL); remote_endpoint_init (core, data->core, data->remote); wp_factory_new (core, "pipewire-simple-endpoint", simple_endpoint_factory); wp_factory_new (core, "pipewire-simple-endpoint-link", simple_endpoint_link_factory); g_idle_add ((GSourceFunc) connect_in_idle, data->remote); }