/* WirePlumber * * Copyright © 2019 Collabora Ltd. * @author George Kiagiadakis * * SPDX-License-Identifier: LGPL-2.1-or-later */ #include #include #include #include #define MIN_QUANTUM_SIZE 64 #define MAX_QUANTUM_SIZE 1024 G_DECLARE_FINAL_TYPE (DefaultSession, session, DEFAULT, SESSION, WpSession) G_DECLARE_FINAL_TYPE (DefaultSessionPlugin, plugin, DEFAULT, SESSION_PLUGIN, WpPlugin) /* DefaultSession */ struct _DefaultSession { WpSession parent; WpProxy *device_node; struct pw_node_proxy *dsp_proxy; struct spa_audio_info_raw format; guint32 media_type; guint32 session_id; }; G_DEFINE_TYPE (DefaultSession, session, WP_TYPE_SESSION) static void session_init (DefaultSession * self) { } static void session_finalize (GObject * obj) { DefaultSession *self = DEFAULT_SESSION (obj); G_OBJECT_CLASS (session_parent_class)->finalize (obj); } static void session_class_init (DefaultSessionClass * klass) { GObjectClass *object_class = (GObjectClass *) klass; object_class->finalize = session_finalize; } static DefaultSession * session_new (WpProxy * device_node, guint32 type, WpSessionDirection dir) { DefaultSession *sess = g_object_new (session_get_type (), "direction", dir, NULL); sess->device_node = device_node; sess->media_type = type; return sess; } /* DefaultSessionPlugin */ struct _DefaultSessionPlugin { WpPlugin parent; }; G_DEFINE_TYPE (DefaultSessionPlugin, plugin, WP_TYPE_PLUGIN) static void device_node_destroyed (WpProxy * device_node, DefaultSession * session) { g_autoptr (WpObject) core = NULL; g_autoptr (WpSessionRegistry) sr = NULL; g_info ("Proxy %u destroyed - unregistering session %u", wp_proxy_get_id (device_node), session->session_id); core = wp_proxy_get_core (device_node); sr = wp_object_get_interface (core, WP_TYPE_SESSION_REGISTRY); g_return_if_fail (sr != NULL); wp_session_registry_unregister_session (sr, session->session_id); } static gboolean handle_node (WpPlugin * self, WpProxy * proxy) { g_autoptr (WpObject) core = NULL; g_autoptr (WpSessionRegistry) sr = NULL; g_autoptr (GError) error = NULL; g_autoptr (DefaultSession) session = NULL; const gchar *media_class, *ptr; WpSessionDirection direction; guint32 media_type; guint32 sess_id; ptr = media_class = wp_pipewire_properties_get ( WP_PIPEWIRE_PROPERTIES (proxy), "media.class"); if (g_str_has_prefix (ptr, "Audio/")) { ptr += strlen ("Audio/"); media_type = SPA_MEDIA_TYPE_audio; } else if (g_str_has_prefix (ptr, "Video/")) { ptr += strlen ("Video/"); media_type = SPA_MEDIA_TYPE_video; } else { goto out; } if (strcmp (ptr, "Sink") == 0) direction = WP_SESSION_DIRECTION_OUTPUT; else if (strcmp (ptr, "Source") == 0) direction = WP_SESSION_DIRECTION_INPUT; else goto out; g_info ("Creating session for node %u (%s), media.class = '%s'", wp_proxy_get_id (proxy), wp_proxy_get_spa_type_string (proxy), media_class); session = session_new (proxy, media_type, direction); core = wp_plugin_get_core (self); sr = wp_object_get_interface (core, WP_TYPE_SESSION_REGISTRY); g_return_val_if_fail (sr != NULL, FALSE); if ((sess_id = wp_session_registry_register_session (sr, WP_SESSION (session), &error)) == -1) { g_warning ("Error registering session: %s", error->message); return FALSE; } session->session_id = sess_id; g_object_set_data_full (G_OBJECT (proxy), "module-default-session.session", g_object_ref (session), g_object_unref); g_signal_connect_object (proxy, "destroyed", (GCallback) device_node_destroyed, session, 0); return TRUE; out: g_message ("Unrecognized media.class '%s' - not handling proxy %u (%s)", media_class, wp_proxy_get_id (proxy), wp_proxy_get_spa_type_string (proxy)); return FALSE; } static gboolean plug_dsp (WpProxy * node) { DefaultSession *session; g_autoptr (WpObject) core = NULL; g_autoptr (WpPipewireObjects) pw_objects = NULL; struct pw_core_proxy *core_proxy; g_autoptr (WpPipewireProperties) pw_props = NULL; struct pw_properties *props; const char *name; enum pw_direction reverse_direction; uint8_t buf[1024]; struct spa_pod_builder b = { 0, }; struct spa_pod *param; session = g_object_get_data (G_OBJECT (node), "module-default-session.session"); g_return_val_if_fail (session->media_type == SPA_MEDIA_TYPE_audio, G_SOURCE_REMOVE); g_info ("making audio dsp for session %u", session->session_id); core = wp_proxy_get_core (node); pw_objects = wp_object_get_interface (core, WP_TYPE_PIPEWIRE_OBJECTS); core_proxy = pw_remote_get_core_proxy (wp_pipewire_objects_get_pw_remote (pw_objects)); pw_props = wp_object_get_interface (WP_OBJECT (node), WP_TYPE_PIPEWIRE_PROPERTIES); props = pw_properties_new_dict ( wp_pipewire_properties_get_as_spa_dict (pw_props)); if ((name = pw_properties_get (props, "device.nick")) == NULL) name = "unnamed"; pw_properties_set (props, "audio-dsp.name", name); pw_properties_setf (props, "audio-dsp.direction", "%d", wp_session_get_direction (WP_SESSION (session))); pw_properties_setf (props, "audio-dsp.maxbuffer", "%ld", MAX_QUANTUM_SIZE * sizeof (float)); session->dsp_proxy = pw_core_proxy_create_object (core_proxy, "audio-dsp", PW_TYPE_INTERFACE_Node, PW_VERSION_NODE, &props->dict, 0); pw_properties_free (props); reverse_direction = (wp_session_get_direction (WP_SESSION (session)) == WP_SESSION_DIRECTION_INPUT) ? PW_DIRECTION_OUTPUT : PW_DIRECTION_INPUT; spa_pod_builder_init (&b, buf, sizeof (buf)); param = spa_format_audio_raw_build (&b, SPA_PARAM_Format, &session->format); param = spa_pod_builder_add_object (&b, SPA_TYPE_OBJECT_ParamProfile, SPA_PARAM_Profile, SPA_PARAM_PROFILE_direction, SPA_POD_Id (reverse_direction), SPA_PARAM_PROFILE_format, SPA_POD_Pod (param)); pw_node_proxy_set_param ((struct pw_node_proxy *) session->dsp_proxy, SPA_PARAM_Profile, 0, param); return G_SOURCE_REMOVE; } static void audio_port_enum_params_done (GObject * proxy, GAsyncResult * res, gpointer data) { g_autoptr (GError) error = NULL; g_autoptr (GPtrArray) params = NULL; DefaultSession *session; struct spa_audio_info_raw info = { 0, }; guint32 media_type, media_subtype; guint i; g_debug ("done enumerating port %u params", wp_proxy_get_id (WP_PROXY (proxy))); params = wp_proxy_enum_params_finish (WP_PROXY (proxy), res, &error); if (!params) { g_warning ("%s", error->message); return; } session = g_object_get_data (proxy, "module-default-session.session"); for (i = 0; i < params->len; i++) { struct spa_pod *param = g_ptr_array_index (params, i); if (spa_format_parse(param, &media_type, &media_subtype) < 0) return; if (media_type != SPA_MEDIA_TYPE_audio || media_subtype != SPA_MEDIA_SUBTYPE_raw) return; spa_pod_fixate (param); if (spa_format_audio_raw_parse (param, &info) < 0) return; if (info.channels > session->format.channels) session->format = info; } g_idle_add_full (G_PRIORITY_DEFAULT_IDLE, (GSourceFunc) plug_dsp, g_object_ref (proxy), g_object_unref); } static gboolean handle_audio_port (WpPlugin * self, WpProxy * proxy) { wp_proxy_enum_params (proxy, SPA_PARAM_EnumFormat, audio_port_enum_params_done, NULL); return TRUE; } static gboolean handle_pw_proxy (WpPlugin * self, WpProxy * proxy) { g_autoptr (WpObject) core = NULL; g_autoptr (WpProxy) parent = NULL; g_autoptr (WpProxyRegistry) reg = NULL; DefaultSession *session; if (wp_proxy_get_spa_type (proxy) != PW_TYPE_INTERFACE_Port && wp_proxy_get_spa_type (proxy) != PW_TYPE_INTERFACE_Node) return FALSE; core = wp_plugin_get_core (self); reg = wp_object_get_interface (core, WP_TYPE_PROXY_REGISTRY); parent = wp_proxy_registry_get_proxy (reg, wp_proxy_get_parent_id (proxy)); if (wp_proxy_get_spa_type (parent) == PW_TYPE_INTERFACE_Device && wp_proxy_get_spa_type (proxy) == PW_TYPE_INTERFACE_Node) { g_debug ("handling node %u (parent device %u)", wp_proxy_get_id (proxy), wp_proxy_get_id (parent)); return handle_node (self, proxy); } else if (wp_proxy_get_spa_type (parent) == PW_TYPE_INTERFACE_Node && wp_proxy_get_spa_type (proxy) == PW_TYPE_INTERFACE_Port && (session = g_object_get_data (G_OBJECT (parent), "module-default-session.session")) && session->media_type == SPA_MEDIA_TYPE_audio) { g_debug ("handling audio port %u (parent node %u)", wp_proxy_get_id (proxy), wp_proxy_get_id (parent)); return handle_audio_port (self, proxy); } return FALSE; } static void plugin_init (DefaultSessionPlugin * self) { } static void plugin_class_init (DefaultSessionPluginClass * klass) { WpPluginClass *plugin_class = WP_PLUGIN_CLASS (klass); plugin_class->handle_pw_proxy = handle_pw_proxy; } static const WpPluginMetadata plugin_metadata = { .rank = WP_PLUGIN_RANK_UPSTREAM, .name = "default-session", .description = "Provides the default WpSession implementation", .author = "George Kiagiadakis ", .license = "LGPL-2.1-or-later", .version = "0.1", .origin = "https://gitlab.freedesktop.org/gkiagia/wireplumber" }; void WP_MODULE_INIT_SYMBOL (WpPluginRegistry * registry) { wp_plugin_registry_register_static (registry, plugin_get_type (), &plugin_metadata, sizeof (plugin_metadata)); }