From b376c5d7c42c37be62363245513f27e1648c49a8 Mon Sep 17 00:00:00 2001 From: George Kiagiadakis <george.kiagiadakis@collabora.com> Date: Fri, 26 Apr 2019 12:39:35 +0300 Subject: [PATCH] modules: add a basic default session implementation as a module --- meson.build | 1 + modules/meson.build | 13 ++ modules/module-default-session.c | 334 +++++++++++++++++++++++++++++++ 3 files changed, 348 insertions(+) create mode 100644 modules/meson.build create mode 100644 modules/module-default-session.c diff --git a/meson.build b/meson.build index 3886b8b5..36da2f10 100644 --- a/meson.build +++ b/meson.build @@ -20,4 +20,5 @@ gnome = import('gnome') wp_lib_include_dir = include_directories('lib') subdir('lib') +subdir('modules') subdir('src') diff --git a/modules/meson.build b/modules/meson.build new file mode 100644 index 00000000..39db6467 --- /dev/null +++ b/modules/meson.build @@ -0,0 +1,13 @@ +common_c_args = [ + '-D_GNU_SOURCE', + '-DG_LOG_USE_STRUCTURED', +] + +shared_library( + 'wireplumber-module-default-session', + ['module-default-session.c'], + c_args : [common_c_args, '-DG_LOG_DOMAIN="wireplumber-module-default-session"'], + install : true, + #install_dir : modules_install_dir, + dependencies : [wp_dep, pipewire_dep], +) diff --git a/modules/module-default-session.c b/modules/module-default-session.c new file mode 100644 index 00000000..c9f0ebac --- /dev/null +++ b/modules/module-default-session.c @@ -0,0 +1,334 @@ +/* WirePlumber + * + * Copyright © 2019 Collabora Ltd. + * @author George Kiagiadakis <george.kiagiadakis@collabora.com> + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#include <wp/plugin.h> +#include <wp/session.h> +#include <pipewire/pipewire.h> +#include <spa/param/audio/format-utils.h> + +#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 <george.kiagiadakis@collabora.com>", + .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)); +} -- GitLab