Files
wireplumber/modules/module-pipewire/video-endpoint.c
2020-03-03 13:27:15 -05:00

335 lines
9.5 KiB
C

/* WirePlumber
*
* Copyright © 2020 Collabora Ltd.
* @author Julian Bouzas <julian.bouzas@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
#include <wp/wp.h>
#include <pipewire/pipewire.h>
struct _WpVideoEndpoint
{
WpBaseEndpoint parent;
/* Properties */
WpNode *node;
char *role;
/* The task to signal the endpoint is initialized */
GTask *init_task;
GVariantBuilder port_vb;
WpObjectManager *ports_om;
};
enum {
PROP_0,
PROP_PROXY_NODE,
PROP_ROLE,
};
static GAsyncInitableIface *async_initable_parent_interface = NULL;
static void wp_video_endpoint_async_initable_init (gpointer iface,
gpointer iface_data);
G_DECLARE_FINAL_TYPE (WpVideoEndpoint, wp_video_endpoint, WP, VIDEO_ENDPOINT,
WpBaseEndpoint)
G_DEFINE_TYPE_WITH_CODE (WpVideoEndpoint, wp_video_endpoint,
WP_TYPE_BASE_ENDPOINT, G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE,
wp_video_endpoint_async_initable_init))
static WpProperties *
wp_video_endpoint_get_properties (WpBaseEndpoint * ep)
{
WpVideoEndpoint *self = WP_VIDEO_ENDPOINT (ep);
return wp_proxy_get_properties (WP_PROXY (self->node));
}
static const char *
wp_video_endpoint_get_role (WpBaseEndpoint *ep)
{
WpVideoEndpoint *self = WP_VIDEO_ENDPOINT (ep);
return self->role;
}
static guint32
wp_video_endpoint_get_global_id (WpBaseEndpoint *ep)
{
WpVideoEndpoint *self = WP_VIDEO_ENDPOINT (ep);
return wp_proxy_get_bound_id (WP_PROXY (self->node));
}
static void
port_proxies_foreach_func (gpointer data, gpointer user_data)
{
WpProxy *port = WP_PROXY (data);
WpVideoEndpoint *self = WP_VIDEO_ENDPOINT (user_data);
const struct pw_node_info *node_info;
const struct pw_port_info *port_info;
node_info = wp_proxy_get_info (WP_PROXY (self->node));
g_return_if_fail (node_info);
port_info = wp_proxy_get_info (port);
g_return_if_fail (port_info);
/* tuple format:
uint32 node_id;
uint32 port_id;
uint32 channel; // always 0 for video
uint8 direction; // enum spa_direction
*/
g_variant_builder_add (&self->port_vb, "(uuuy)", node_info->id,
port_info->id, 0, (guint8) port_info->direction);
}
static gboolean
wp_video_endpoint_prepare_link (WpBaseEndpoint * ep, guint32 stream_id,
WpBaseEndpointLink * link, GVariant ** properties, GError ** error)
{
WpVideoEndpoint *self = WP_VIDEO_ENDPOINT (ep);
g_autoptr (GPtrArray) port_proxies = wp_object_manager_get_objects (
self->ports_om, 0);
/* Create a variant array with all the ports */
g_variant_builder_init (&self->port_vb, G_VARIANT_TYPE ("a(uuuy)"));
g_ptr_array_foreach (port_proxies, port_proxies_foreach_func, self);
*properties = g_variant_builder_end (&self->port_vb);
return TRUE;
}
static void
wp_video_endpoint_begin_fade (WpBaseEndpoint * ep, guint32 stream_id,
guint duration, gfloat step, guint direction, guint type,
GCancellable * cancellable, GAsyncReadyCallback callback, gpointer data)
{
/* TODO: apply a video fade effect */
g_autoptr (GTask) task = g_task_new (ep, cancellable, callback, data);
g_task_return_boolean (task, TRUE);
}
static void
wp_video_endpoint_finalize (GObject * object)
{
WpVideoEndpoint *self = WP_VIDEO_ENDPOINT (object);
g_clear_object (&self->init_task);
/* Destroy the done task */
g_clear_object (&self->init_task);
/* Props */
g_clear_object (&self->node);
g_free (self->role);
G_OBJECT_CLASS (wp_video_endpoint_parent_class)->finalize (object);
}
static void
wp_video_endpoint_set_property (GObject * object, guint property_id,
const GValue * value, GParamSpec * pspec)
{
WpVideoEndpoint *self = WP_VIDEO_ENDPOINT (object);
switch (property_id) {
case PROP_PROXY_NODE:
self->node = g_value_dup_object (value);
break;
case PROP_ROLE:
g_free (self->role);
self->role = g_value_dup_string (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
wp_video_endpoint_get_property (GObject * object, guint property_id,
GValue * value, GParamSpec * pspec)
{
WpVideoEndpoint *self = WP_VIDEO_ENDPOINT (object);
switch (property_id) {
case PROP_PROXY_NODE:
g_value_set_object (value, self->node);
break;
case PROP_ROLE:
g_value_set_string (value, self->role);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
wp_video_endpoint_init_task_finish (WpVideoEndpoint * self, GError * err)
{
g_autoptr (GError) error = err;
if (!self->init_task)
return;
if (error)
g_task_return_error (self->init_task, g_steal_pointer (&error));
else
g_task_return_boolean (self->init_task, TRUE);
g_clear_object (&self->init_task);
}
static void
on_ports_changed (WpObjectManager *om, WpVideoEndpoint *self)
{
wp_video_endpoint_init_task_finish (self, NULL);
g_signal_handlers_disconnect_by_func (self->ports_om, on_ports_changed, self);
}
static void
on_node_proxy_augmented (WpProxy * proxy, GAsyncResult * res,
WpVideoEndpoint * self)
{
g_autoptr (WpCore) core = wp_base_endpoint_get_core (WP_BASE_ENDPOINT(self));
guint32 id;
g_autofree gchar *id_str = NULL;
GVariantBuilder b;
/* Get the bound id */
id = wp_proxy_get_bound_id (proxy);
id_str = g_strdup_printf ("%u", id);
/* Create the ports object manager and set the port constrains */
self->ports_om = wp_object_manager_new ();
g_variant_builder_init (&b, G_VARIANT_TYPE ("aa{sv}"));
g_variant_builder_open (&b, G_VARIANT_TYPE_VARDICT);
g_variant_builder_add (&b, "{sv}", "type",
g_variant_new_int32 (WP_OBJECT_MANAGER_CONSTRAINT_PW_GLOBAL_PROPERTY));
g_variant_builder_add (&b, "{sv}", "name",
g_variant_new_string (PW_KEY_NODE_ID));
g_variant_builder_add (&b, "{sv}", "value",
g_variant_new_string (id_str));
g_variant_builder_close (&b);
wp_object_manager_add_interest (self->ports_om, WP_TYPE_PORT,
g_variant_builder_end (&b),
WP_PROXY_FEATURE_PW_PROXY | WP_PROXY_FEATURE_INFO);
/* Add a callback to know when ports have been changed */
g_signal_connect (self->ports_om, "objects-changed",
(GCallback) on_ports_changed, self);
/* Install the object manager */
wp_core_install_object_manager (core, self->ports_om);
}
static void
wp_video_endpoint_init_async (GAsyncInitable *initable, int io_priority,
GCancellable *cancellable, GAsyncReadyCallback callback, gpointer data)
{
WpVideoEndpoint *self = WP_VIDEO_ENDPOINT (initable);
/* Create the async task */
self->init_task = g_task_new (initable, cancellable, callback, data);
/* Augment the proxy */
wp_proxy_augment (WP_PROXY (self->node), WP_PROXY_FEATURES_STANDARD,
cancellable, (GAsyncReadyCallback) on_node_proxy_augmented, self);
/* Call the parent interface */
async_initable_parent_interface->init_async (initable, io_priority, cancellable,
callback, data);
}
static void
wp_video_endpoint_async_initable_init (gpointer iface, gpointer iface_data)
{
GAsyncInitableIface *ai_iface = iface;
/* Set the parent interface */
async_initable_parent_interface = g_type_interface_peek_parent (iface);
/* Only set the init_async */
ai_iface->init_async = wp_video_endpoint_init_async;
}
static void
wp_video_endpoint_init (WpVideoEndpoint * self)
{
}
static void
wp_video_endpoint_class_init (WpVideoEndpointClass * klass)
{
GObjectClass *object_class = (GObjectClass *) klass;
WpBaseEndpointClass *base_endpoint_class = (WpBaseEndpointClass *) klass;
object_class->finalize = wp_video_endpoint_finalize;
object_class->set_property = wp_video_endpoint_set_property;
object_class->get_property = wp_video_endpoint_get_property;
base_endpoint_class->get_properties = wp_video_endpoint_get_properties;
base_endpoint_class->get_role = wp_video_endpoint_get_role;
base_endpoint_class->get_global_id = wp_video_endpoint_get_global_id;
base_endpoint_class->prepare_link = wp_video_endpoint_prepare_link;
base_endpoint_class->begin_fade = wp_video_endpoint_begin_fade;
/* Instal the properties */
g_object_class_install_property (object_class, PROP_PROXY_NODE,
g_param_spec_object ("node", "node",
"The node this endpoint refers to", WP_TYPE_NODE,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_ROLE,
g_param_spec_string ("role", "role", "The role of the wrapped node", NULL,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
}
void
wp_video_endpoint_factory (WpFactory * factory, GType type,
GVariant * properties, GAsyncReadyCallback ready, gpointer user_data)
{
g_autoptr (WpCore) core = NULL;
const gchar *name, *media_class;
guint direction, priority;
guint64 node;
/* Make sure the type is correct */
g_return_if_fail(type == WP_TYPE_BASE_ENDPOINT);
/* Get the Core */
core = wp_factory_get_core(factory);
g_return_if_fail (core);
/* Get the properties */
if (!g_variant_lookup (properties, "name", "&s", &name))
return;
if (!g_variant_lookup (properties, "media-class", "&s", &media_class))
return;
if (!g_variant_lookup (properties, "direction", "u", &direction))
return;
if (!g_variant_lookup (properties, "priority", "u", &priority))
return;
if (!g_variant_lookup (properties, "node", "t", &node))
return;
/* Create and return the video endpoint object */
g_async_initable_new_async (
wp_video_endpoint_get_type (), G_PRIORITY_DEFAULT, NULL, ready, user_data,
"core", core,
"name", name,
"media-class", media_class,
"direction", direction,
"priority", priority,
"node", (gpointer) node,
NULL);
}