Skip to content
Snippets Groups Projects
Commit b376c5d7 authored by George Kiagiadakis's avatar George Kiagiadakis
Browse files

modules: add a basic default session implementation as a module

parent 02c69fd2
No related branches found
No related tags found
No related merge requests found
......@@ -20,4 +20,5 @@ gnome = import('gnome')
wp_lib_include_dir = include_directories('lib')
subdir('lib')
subdir('modules')
subdir('src')
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],
)
/* 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));
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment