wireplumber/lib/wp/endpoint.c

1134 lines
32 KiB
C
Raw Normal View History

/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
2019-05-31 12:13:01 +03:00
* SPDX-License-Identifier: MIT
*/
2019-05-21 18:58:30 +03:00
/**
* SECTION: Endpoint
*
* An endpoint is an abstraction layer that represents a physical place where
* audio can be routed to/from.
*
* Examples of endpoints on a desktop-like system:
* * Laptop speakers
* * Laptop webcam
* * USB microphone
* * Docking station stereo jack port
* * USB 5.1 Digital audio output
*
* Examples of endpoints on a car:
* * Driver seat speakers
* * Front right seat microphone array
* * Rear left seat headphones
* * Bluetooth phone gateway
* * All speakers
*
* In ALSA terms, an endpoint may be representing an ALSA subdevice 1-to-1
* (therefore a single alsa-source/alsa-sink node in pipewire),
* but it may as well be representing a part of this subdevice (for instance,
* only the front stereo channels, or only the rear stereo), or it may represent
* a combination of devices (for instance, playing to all speakers of a system
* while they are plugged on different sound cards).
*
* An endpoint is not necessarily tied to a device that is present on this
* system using ALSA or V4L. It may also represent a hardware device that
* can be accessed in some hardware-specific path and is not accessible to
* applications through pipewire. In this case, the endpoint can only used
* for controlling the hardware, or - if the appropriate EndpointLink object
* is also implemented - it can be used to route media from some other
* hardware endpoint.
*
* ## Streams
*
* An endpoint can contain multiple streams, which represent different,
* controllable paths that can be used to reach this endpoint.
* Streams can be used to implement grouping of applications based on their
* role or other things.
*
* Examples of streams on an audio output endpoint: "multimedia", "radio",
* "phone". In this example, an audio player would be routed through the
* "multimedia" stream, for instance, while a voip app would be routed through
* "phone". This would allow lowering the volume of the audio player while the
* call is in progress by using the standard volume control of the "multimedia"
* stream.
*
* Examples of streams on an audio capture endpoint: "standard",
* "voice recognition". In this example, the "standard" capture gives a
* real-time capture from the microphone, while "voice recognition" gives a
* slightly delayed and DSP-optimized for speech input, which can be used
* as input in a voice recognition engine.
*
* A stream is described as a dictionary GVariant (a{sv}) with the following
* standard keys available:
* "id": the id of the stream
* "name": the name of the stream
*
* ## Controls
*
* An endpoint can have multiple controls, which can control anything in the
* path of media. Typically, audio streams have volume and mute controls, while
* video streams have hue, brightness, contrast, etc... Controls can be linked
* to a specific stream, but may as well be global and apply to all streams
* of the endpoint. This can be used to implement a master volume, for instance.
*
* A control is described as a dictionary GVariant (a{sv}) with the following
* standard keys available:
* "id": the id of the control
* "stream-id": the id of the stream that this control applies to
* "name": the name of the control
* "type": a GVariant type string
* "range": a tuple (min, max)
2019-05-25 23:19:28 +03:00
* "default-value": the default value
2019-05-21 18:58:30 +03:00
*/
#include "endpoint.h"
#include "error.h"
#include "factory.h"
#include "private.h"
typedef struct _WpEndpointPrivate WpEndpointPrivate;
struct _WpEndpointPrivate
{
gchar *name;
gchar media_class[40];
guint direction;
2019-10-23 06:51:11 +02:00
guint64 creation_time;
GPtrArray *streams;
GPtrArray *controls;
GPtrArray *links;
GWeakRef core;
};
enum {
PROP_0,
PROP_CORE,
PROP_NAME,
PROP_MEDIA_CLASS,
PROP_DIRECTION,
2019-10-23 06:51:11 +02:00
PROP_CREATION_TIME,
};
enum {
SIGNAL_NOTIFY_CONTROL_VALUE,
NUM_SIGNALS
};
static guint32 signals[NUM_SIGNALS];
static void wp_endpoint_async_initable_init (gpointer iface,
gpointer iface_data);
G_DEFINE_ABSTRACT_TYPE_WITH_CODE (WpEndpoint, wp_endpoint, G_TYPE_OBJECT,
G_ADD_PRIVATE (WpEndpoint)
G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE,
wp_endpoint_async_initable_init))
static void
wp_endpoint_init_async (GAsyncInitable *initable, int io_priority,
GCancellable *cancellable, GAsyncReadyCallback callback, gpointer data)
{
}
static gboolean
wp_endpoint_init_finish (GAsyncInitable *initable, GAsyncResult *result,
GError **error)
{
g_return_val_if_fail (g_task_is_valid (result, initable), FALSE);
return g_task_propagate_boolean (G_TASK (result), error);
}
static void
wp_endpoint_async_initable_init (gpointer iface, gpointer iface_data)
{
GAsyncInitableIface *ai_iface = iface;
ai_iface->init_async = wp_endpoint_init_async;
ai_iface->init_finish = wp_endpoint_init_finish;
}
static void
wp_endpoint_init (WpEndpoint * self)
{
WpEndpointPrivate *priv = wp_endpoint_get_instance_private (self);
g_weak_ref_init (&priv->core, NULL);
2019-10-23 06:51:11 +02:00
priv->creation_time = (guint64) g_get_monotonic_time ();
priv->streams =
g_ptr_array_new_with_free_func ((GDestroyNotify) g_variant_unref);
priv->controls =
g_ptr_array_new_with_free_func ((GDestroyNotify) g_variant_unref);
priv->links = g_ptr_array_new_with_free_func (g_object_unref);
}
static void
wp_endpoint_dispose (GObject * object)
{
WpEndpointPrivate *priv =
wp_endpoint_get_instance_private (WP_ENDPOINT (object));
gint i;
/* wp_endpoint_link_destroy removes elements from the array,
* so traversing in reverse order is faster and less complicated */
for (i = priv->links->len - 1; i >= 0; i--) {
wp_endpoint_link_destroy (g_ptr_array_index (priv->links, i));
}
G_OBJECT_CLASS (wp_endpoint_parent_class)->dispose (object);
}
static void
wp_endpoint_finalize (GObject * object)
{
WpEndpointPrivate *priv =
wp_endpoint_get_instance_private (WP_ENDPOINT (object));
g_debug ("%s:%p destroyed: %s", G_OBJECT_TYPE_NAME (object), object,
priv->name);
g_ptr_array_unref (priv->streams);
g_ptr_array_unref (priv->controls);
g_ptr_array_unref (priv->links);
g_free (priv->name);
g_weak_ref_clear (&priv->core);
G_OBJECT_CLASS (wp_endpoint_parent_class)->finalize (object);
}
static void
wp_endpoint_set_property (GObject * object, guint property_id,
const GValue * value, GParamSpec * pspec)
{
WpEndpointPrivate *priv =
wp_endpoint_get_instance_private (WP_ENDPOINT (object));
switch (property_id) {
case PROP_CORE:
g_weak_ref_set (&priv->core, g_value_get_object (value));
break;
case PROP_NAME:
priv->name = g_value_dup_string (value);
break;
case PROP_MEDIA_CLASS:
strncpy (priv->media_class, g_value_get_string (value),
sizeof (priv->media_class) - 1);
break;
case PROP_DIRECTION:
priv->direction = g_value_get_uint(value);
break;
2019-10-23 06:51:11 +02:00
case PROP_CREATION_TIME:
priv->creation_time = g_value_get_uint64(value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
wp_endpoint_get_property (GObject * object, guint property_id, GValue * value,
GParamSpec * pspec)
{
WpEndpointPrivate *priv =
wp_endpoint_get_instance_private (WP_ENDPOINT (object));
switch (property_id) {
case PROP_CORE:
g_value_take_object (value, g_weak_ref_get (&priv->core));
break;
case PROP_NAME:
g_value_set_string (value, priv->name);
break;
case PROP_MEDIA_CLASS:
g_value_set_string (value, priv->media_class);
break;
case PROP_DIRECTION:
g_value_set_uint (value, priv->direction);
break;
2019-10-23 06:51:11 +02:00
case PROP_CREATION_TIME:
g_value_set_uint64 (value, priv->creation_time);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
wp_endpoint_class_init (WpEndpointClass * klass)
{
GObjectClass * object_class = (GObjectClass *) klass;
object_class->dispose = wp_endpoint_dispose;
object_class->finalize = wp_endpoint_finalize;
object_class->get_property = wp_endpoint_get_property;
object_class->set_property = wp_endpoint_set_property;
g_object_class_install_property (object_class, PROP_CORE,
g_param_spec_object ("core", "core", "The wireplumber core",
WP_TYPE_CORE,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
2019-05-21 18:58:30 +03:00
/**
* WpEndpoint::name:
* The name of the endpoint.
*/
g_object_class_install_property (object_class, PROP_NAME,
g_param_spec_string ("name", "name", "The name of the endpoint", NULL,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
2019-05-21 18:58:30 +03:00
/**
* WpEndpoint::media-class:
* The media class describes the type of media that this endpoint handles.
* This should be the same as PipeWire media class strings.
* For instance:
* * Audio/Sink
* * Audio/Source
* * Video/Source
* * Stream/Audio/Source
*/
g_object_class_install_property (object_class, PROP_MEDIA_CLASS,
g_param_spec_string ("media-class", "media-class",
"The media class of the endpoint", NULL,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
signals[SIGNAL_NOTIFY_CONTROL_VALUE] = g_signal_new ("notify-control-value",
G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL,
G_TYPE_NONE, 1, G_TYPE_UINT);
/**
* WpEndpoint::direction:
* The direction of the endpoint: input = 0, output = 1.
*/
g_object_class_install_property (object_class, PROP_DIRECTION,
g_param_spec_uint ("direction", "direction",
"The direction of the endpoint", 0, 1, 0,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
2019-10-23 06:51:11 +02:00
/**
* WpEndpoint::creation-time:
* The creation time of the endpoint in monolitic time
*/
g_object_class_install_property (object_class, PROP_CREATION_TIME,
g_param_spec_uint64 ("creation-time", "creation-time",
"The time that this endpoint was created, in monotonic time",
0, G_MAXUINT64, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
}
/**
* wp_endpoint_new_finish:
* @initable: the #GAsyncInitable from the callback
* @res: the #GAsyncResult from the callback
* @error: return location for errors, or NULL to ignore
*
* Finishes the async construction of #WpEndpoint.
*/
WpEndpoint *
2019-07-10 11:15:33 -04:00
wp_endpoint_new_finish (GObject *initable, GAsyncResult *res, GError **error)
{
GAsyncInitable *ai = G_ASYNC_INITABLE(initable);
return WP_ENDPOINT(g_async_initable_new_finish(ai, res, error));
}
/**
* wp_endpoint_register:
* @self: the endpoint
*
* Registers the endpoint on the #WpCore.
*/
void
wp_endpoint_register (WpEndpoint * self)
{
WpEndpointPrivate *priv;
g_autoptr (WpCore) core = NULL;
g_return_if_fail (WP_IS_ENDPOINT (self));
priv = wp_endpoint_get_instance_private (self);
core = g_weak_ref_get (&priv->core);
g_return_if_fail (core != NULL);
2019-05-26 12:36:20 +03:00
g_info ("WpEndpoint:%p registering '%s' (%s)", self, priv->name,
priv->media_class);
lib: introduce WpObjectManager * rework how global objects are stored in the core * rework how users get notified about global objects and proxies of remote global objects The purpose of this change is to have a class that can manage objects that are registered in the core or signalled through the registry. This object can declare interest on certain types of global objects and only keep & signal those objects that it is interested in. Additionally, it can prepare proxy features and asynchronously deliver an 'objects-changed' signal, which is basically telling us that the list of objects has changed. This is useful to simplify port proxies management in WpAudioStream. Now the stream object can declare that it is interested in ports that have "node.id" == X and the object manager will only maintain a list of those. Additionally, it will emit the 'objects-changed' signal when the list of ports is complete, so there is no reason to do complex operations and core syncs in the WpAudioStream class in order to figure out when the list of ports is ready. As a side effect, this also reduces resource management. Now we don't construct a WpProxy for every global that pipewire reports; we only construct proxies when there is interest in them! Another interesting side effect is that we can now register an object manager at any point in time and get immediately notified about remote globals that already exist. i.e. when you register an object manager that is interested in nodes, it will be immediately notified about all the existing nodes in the graph. This is useful to avoid race conditions between connecting the signal and objects beting created in pipewire
2019-11-13 15:44:23 +02:00
wp_core_register_object (core, g_object_ref (self));
}
/**
* wp_endpoint_unregister:
* @self: the endpoint
*
* Unregisters the endpoint from the session manager, if it was registered
* and the session manager object still exists
*/
void
wp_endpoint_unregister (WpEndpoint * self)
{
WpEndpointPrivate *priv;
g_autoptr (WpCore) core = NULL;
g_return_if_fail (WP_IS_ENDPOINT (self));
priv = wp_endpoint_get_instance_private (self);
/* unlink before unregistering so that policy modules
* can find dangling unlinked endpoints */
for (gint i = priv->links->len - 1; i >= 0; i--)
wp_endpoint_link_destroy (g_ptr_array_index (priv->links, i));
core = g_weak_ref_get (&priv->core);
if (core) {
2019-05-26 12:36:20 +03:00
g_info ("WpEndpoint:%p unregistering '%s' (%s)", self, priv->name,
priv->media_class);
lib: introduce WpObjectManager * rework how global objects are stored in the core * rework how users get notified about global objects and proxies of remote global objects The purpose of this change is to have a class that can manage objects that are registered in the core or signalled through the registry. This object can declare interest on certain types of global objects and only keep & signal those objects that it is interested in. Additionally, it can prepare proxy features and asynchronously deliver an 'objects-changed' signal, which is basically telling us that the list of objects has changed. This is useful to simplify port proxies management in WpAudioStream. Now the stream object can declare that it is interested in ports that have "node.id" == X and the object manager will only maintain a list of those. Additionally, it will emit the 'objects-changed' signal when the list of ports is complete, so there is no reason to do complex operations and core syncs in the WpAudioStream class in order to figure out when the list of ports is ready. As a side effect, this also reduces resource management. Now we don't construct a WpProxy for every global that pipewire reports; we only construct proxies when there is interest in them! Another interesting side effect is that we can now register an object manager at any point in time and get immediately notified about remote globals that already exist. i.e. when you register an object manager that is interested in nodes, it will be immediately notified about all the existing nodes in the graph. This is useful to avoid race conditions between connecting the signal and objects beting created in pipewire
2019-11-13 15:44:23 +02:00
wp_core_remove_object (core, self);
}
}
/**
* wp_endpoint_get_core:
* @self: the endpoint
*
* Returns: (transfer full): the core on which this endpoint is registered
*/
WpCore *
wp_endpoint_get_core (WpEndpoint * self)
{
WpEndpointPrivate *priv;
g_return_val_if_fail (WP_IS_ENDPOINT (self), NULL);
priv = wp_endpoint_get_instance_private (self);
return g_weak_ref_get (&priv->core);
}
const gchar *
wp_endpoint_get_name (WpEndpoint * self)
{
WpEndpointPrivate *priv;
g_return_val_if_fail (WP_IS_ENDPOINT (self), NULL);
priv = wp_endpoint_get_instance_private (self);
return priv->name;
}
const gchar *
wp_endpoint_get_media_class (WpEndpoint * self)
{
WpEndpointPrivate *priv;
g_return_val_if_fail (WP_IS_ENDPOINT (self), NULL);
priv = wp_endpoint_get_instance_private (self);
return priv->media_class;
}
guint
wp_endpoint_get_direction (WpEndpoint * self)
{
WpEndpointPrivate *priv;
g_return_val_if_fail (WP_IS_ENDPOINT (self), -1);
priv = wp_endpoint_get_instance_private (self);
return priv->direction;
}
2019-10-23 06:51:11 +02:00
guint64
wp_endpoint_get_creation_time (WpEndpoint * self)
{
WpEndpointPrivate *priv;
g_return_val_if_fail (WP_IS_ENDPOINT (self), -1);
priv = wp_endpoint_get_instance_private (self);
return priv->creation_time;
}
2019-11-22 09:08:11 -05:00
WpProperties *
wp_endpoint_get_properties (WpEndpoint * self)
{
g_return_val_if_fail (WP_IS_ENDPOINT (self), NULL);
if (WP_ENDPOINT_GET_CLASS (self)->get_properties)
return WP_ENDPOINT_GET_CLASS (self)->get_properties (self);
return NULL;
}
2019-12-03 08:48:36 -05:00
const char *
wp_endpoint_get_role (WpEndpoint * self)
{
g_return_val_if_fail (WP_IS_ENDPOINT (self), NULL);
if (WP_ENDPOINT_GET_CLASS (self)->get_role)
return WP_ENDPOINT_GET_CLASS (self)->get_role (self);
return NULL;
}
2019-05-21 18:58:30 +03:00
/**
* wp_endpoint_register_stream:
* @self: the endpoint
* @stream: (transfer floating): a dictionary GVariant with the stream info
*/
void
wp_endpoint_register_stream (WpEndpoint * self, GVariant * stream)
{
WpEndpointPrivate *priv;
g_return_if_fail (WP_IS_ENDPOINT (self));
g_return_if_fail (g_variant_is_of_type (stream, G_VARIANT_TYPE_VARDICT));
priv = wp_endpoint_get_instance_private (self);
g_ptr_array_add (priv->streams, g_variant_ref_sink (stream));
}
GVariant *
wp_endpoint_get_stream (WpEndpoint * self, guint32 stream_id)
{
WpEndpointPrivate *priv;
guint32 id;
gint i;
g_return_val_if_fail (WP_IS_ENDPOINT (self), NULL);
priv = wp_endpoint_get_instance_private (self);
for (i = 0; i < priv->streams->len; i++) {
GVariant *v = g_ptr_array_index (priv->streams, i);
if (g_variant_lookup (v, "id", "u", &id) && id == stream_id) {
return g_variant_ref (v);
}
}
return NULL;
}
2019-05-21 18:58:30 +03:00
/**
* wp_endpoint_list_streams:
* @self: the endpoint
*
* Returns: (transfer floating): a floating GVariant that contains an array of
* dictionaries (aa{sv}) where each dictionary contains information about
* a single stream
*/
GVariant *
wp_endpoint_list_streams (WpEndpoint * self)
{
WpEndpointPrivate *priv;
g_return_val_if_fail (WP_IS_ENDPOINT (self), NULL);
priv = wp_endpoint_get_instance_private (self);
return g_variant_new_array (G_VARIANT_TYPE_VARDICT,
(GVariant * const *) priv->streams->pdata, priv->streams->len);
}
guint32
wp_endpoint_find_stream (WpEndpoint * self, const gchar * name)
{
WpEndpointPrivate *priv;
const gchar *tmp = NULL;
guint32 id = WP_STREAM_ID_NONE;
gint i;
g_return_val_if_fail (WP_IS_ENDPOINT (self), WP_STREAM_ID_NONE);
g_return_val_if_fail (name != NULL, WP_STREAM_ID_NONE);
priv = wp_endpoint_get_instance_private (self);
for (i = 0; i < priv->streams->len; i++) {
GVariant *v = g_ptr_array_index (priv->streams, i);
if (g_variant_lookup (v, "name", "&s", &tmp) && !g_strcmp0 (tmp, name)) {
/* found, return the id */
g_variant_lookup (v, "id", "u", &id);
break;
}
}
return id;
}
2019-05-21 18:58:30 +03:00
/**
* wp_endpoint_register_control:
* @self: the endpoint
* @control: (transfer floating): a dictionary GVariant with the control info
*/
void
wp_endpoint_register_control (WpEndpoint * self, GVariant * control)
{
WpEndpointPrivate *priv;
g_return_if_fail (WP_IS_ENDPOINT (self));
g_return_if_fail (g_variant_is_of_type (control, G_VARIANT_TYPE_VARDICT));
priv = wp_endpoint_get_instance_private (self);
g_ptr_array_add (priv->controls, g_variant_ref_sink (control));
}
GVariant *
wp_endpoint_get_control (WpEndpoint * self, guint32 control_id)
{
WpEndpointPrivate *priv;
guint32 id;
gint i;
g_return_val_if_fail (WP_IS_ENDPOINT (self), NULL);
priv = wp_endpoint_get_instance_private (self);
for (i = 0; i < priv->controls->len; i++) {
GVariant *v = g_ptr_array_index (priv->controls, i);
if (g_variant_lookup (v, "id", "u", &id) && id == control_id) {
return g_variant_ref (v);
}
}
return NULL;
}
2019-05-21 18:58:30 +03:00
/**
* wp_endpoint_list_controls:
* @self: the endpoint
*
* Returns: (transfer floating): a floating GVariant that contains an array of
* dictionaries (aa{sv}) where each dictionary contains information about
* a single control
*/
GVariant *
wp_endpoint_list_controls (WpEndpoint * self)
{
WpEndpointPrivate *priv;
g_return_val_if_fail (WP_IS_ENDPOINT (self), NULL);
priv = wp_endpoint_get_instance_private (self);
return g_variant_new_array (G_VARIANT_TYPE_VARDICT,
(GVariant * const *) priv->controls->pdata, priv->controls->len);
}
guint32
wp_endpoint_find_control (WpEndpoint * self, guint32 stream_id,
const gchar * name)
{
WpEndpointPrivate *priv;
const gchar *tmp = NULL;
guint32 tmp_id = WP_STREAM_ID_NONE;
guint32 id = WP_CONTROL_ID_NONE;
gint i;
g_return_val_if_fail (WP_IS_ENDPOINT (self), WP_CONTROL_ID_NONE);
g_return_val_if_fail (name != NULL, WP_CONTROL_ID_NONE);
priv = wp_endpoint_get_instance_private (self);
for (i = 0; i < priv->controls->len; i++) {
GVariant *v = g_ptr_array_index (priv->controls, i);
/*
* if the stream-id exists, it must match @stream_id
* if it doesn't exist, then @stream_id must be NONE
*/
if (g_variant_lookup (v, "stream-id", "u", &tmp_id)) {
if (stream_id != tmp_id)
continue;
} else if (stream_id != WP_STREAM_ID_NONE) {
continue;
}
if (g_variant_lookup (v, "name", "&s", &tmp) && !g_strcmp0 (tmp, name)) {
/* found, return the id */
g_variant_lookup (v, "id", "u", &id);
break;
}
}
return id;
}
2019-05-21 18:58:30 +03:00
/**
* wp_endpoint_get_control_value: (virtual get_control_value)
* @self: the endpoint
* @control_id: the id of the control to set
*
2019-05-25 23:19:28 +03:00
* Returns a GVariant that holds the value of the control. The type
* should be the same type specified in the control variant's "type" field.
2019-05-21 18:58:30 +03:00
*
* On error, NULL will be returned.
*
* Returns: (transfer floating) (nullable): a dictionary GVariant containing
* the control value
*/
GVariant *
wp_endpoint_get_control_value (WpEndpoint * self, guint32 control_id)
{
g_return_val_if_fail (WP_IS_ENDPOINT (self), NULL);
if (WP_ENDPOINT_GET_CLASS (self)->get_control_value)
return WP_ENDPOINT_GET_CLASS (self)->get_control_value (self, control_id);
else
return NULL;
}
2019-05-21 18:58:30 +03:00
/**
* wp_endpoint_set_control_value: (virtual set_control_value)
* @self: the endpoint
* @control_id: the id of the control to set
* @value: (transfer none): the value to set on the control
*
* Sets the @value on the specified control. The implementation should
* call wp_endpoint_notify_control_value() if the value has been changed
* in order to signal the change.
*
* Returns: TRUE on success, FALSE on failure
*/
gboolean
wp_endpoint_set_control_value (WpEndpoint * self, guint32 control_id,
GVariant * value)
{
gboolean ret = FALSE;
g_return_val_if_fail (WP_IS_ENDPOINT (self), FALSE);
if (WP_ENDPOINT_GET_CLASS (self)->set_control_value)
ret = WP_ENDPOINT_GET_CLASS (self)->set_control_value (self, control_id,
value);
if (g_variant_is_floating (value))
g_variant_unref (value);
return ret;
}
2019-05-21 18:58:30 +03:00
/**
* wp_endpoint_notify_control_value:
* @self: the endpoint
* @control_id: the id of the control
*
* Emits the "notify-control-value" signal so that others can be informed
* about a value change in some of the controls. This is meant to be used
* by subclasses only.
*/
void
wp_endpoint_notify_control_value (WpEndpoint * self, guint32 control_id)
{
g_return_if_fail (WP_IS_ENDPOINT (self));
g_signal_emit (self, signals[SIGNAL_NOTIFY_CONTROL_VALUE], 0, control_id);
}
2019-05-21 18:58:30 +03:00
/**
* wp_endpoint_is_linked:
* @self: the endpoint
*
* Returns: TRUE if there is at least one link associated with this endpoint
*/
gboolean
wp_endpoint_is_linked (WpEndpoint * self)
{
WpEndpointPrivate *priv;
g_return_val_if_fail (WP_IS_ENDPOINT (self), FALSE);
priv = wp_endpoint_get_instance_private (self);
return (priv->links->len > 0);
}
2019-05-21 18:58:30 +03:00
/**
* wp_endpoint_get_links:
* @self: the endpoint
*
* Returns: (transfer none) (element-type WpEndpointLink): an array of
* #WpEndpointLink objects that are currently associated with this endpoint
*/
GPtrArray *
wp_endpoint_get_links (WpEndpoint * self)
{
WpEndpointPrivate *priv;
g_return_val_if_fail (WP_IS_ENDPOINT (self), NULL);
priv = wp_endpoint_get_instance_private (self);
return priv->links;
}
2019-07-08 09:53:55 -04:00
/**
* wp_endpoint_unlink:
* @self: the endpoint
*
* Unlinks all the endpoints linked to this endpoint
*/
void
wp_endpoint_unlink (WpEndpoint * self)
{
WpEndpointPrivate *priv;
gint i;
g_return_if_fail (WP_IS_ENDPOINT (self));
priv = wp_endpoint_get_instance_private (self);
for (i = priv->links->len - 1; i >= 0; i--)
wp_endpoint_link_destroy (g_ptr_array_index (priv->links, i));
}
typedef struct _WpEndpointLinkPrivate WpEndpointLinkPrivate;
struct _WpEndpointLinkPrivate
{
2019-06-28 12:33:00 -04:00
GWeakRef src;
guint32 src_stream;
2019-06-28 12:33:00 -04:00
GWeakRef sink;
guint32 sink_stream;
2019-12-02 09:55:57 -05:00
gboolean keep;
};
2019-06-28 12:33:00 -04:00
enum {
LINKPROP_0,
LINKPROP_SRC,
LINKPROP_SRC_STREAM,
LINKPROP_SINK,
LINKPROP_SINK_STREAM,
2019-12-02 09:55:57 -05:00
LINKPROP_KEEP,
2019-06-28 12:33:00 -04:00
};
static void wp_endpoint_link_async_initable_init (gpointer iface,
gpointer iface_data);
G_DEFINE_ABSTRACT_TYPE_WITH_CODE (WpEndpointLink, wp_endpoint_link, G_TYPE_OBJECT,
G_ADD_PRIVATE (WpEndpointLink)
G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE,
wp_endpoint_link_async_initable_init))
static void
2019-06-28 12:33:00 -04:00
endpoint_link_finalize (GObject * object)
{
2019-06-28 12:33:00 -04:00
WpEndpointLinkPrivate *priv =
wp_endpoint_link_get_instance_private (WP_ENDPOINT_LINK (object));
/* Clear the endpoint weak reaferences */
g_weak_ref_clear(&priv->src);
g_weak_ref_clear(&priv->sink);
G_OBJECT_CLASS (wp_endpoint_link_parent_class)->finalize(object);
}
static void
2019-06-28 12:33:00 -04:00
endpoint_link_set_property (GObject * object, guint property_id,
const GValue * value, GParamSpec * pspec)
{
2019-06-28 12:33:00 -04:00
WpEndpointLinkPrivate *priv =
wp_endpoint_link_get_instance_private (WP_ENDPOINT_LINK (object));
switch (property_id) {
case LINKPROP_SRC:
g_weak_ref_set (&priv->src, g_value_get_object (value));
break;
case LINKPROP_SRC_STREAM:
priv->src_stream = g_value_get_uint(value);
break;
case LINKPROP_SINK:
g_weak_ref_set (&priv->sink, g_value_get_object (value));
break;
case LINKPROP_SINK_STREAM:
priv->sink_stream = g_value_get_uint(value);
break;
2019-12-02 09:55:57 -05:00
case LINKPROP_KEEP:
priv->keep = g_value_get_boolean (value);
break;
2019-06-28 12:33:00 -04:00
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
2019-06-28 12:33:00 -04:00
static void
endpoint_link_get_property (GObject * object, guint property_id,
GValue * value, GParamSpec * pspec)
{
2019-06-28 12:33:00 -04:00
WpEndpointLinkPrivate *priv =
wp_endpoint_link_get_instance_private (WP_ENDPOINT_LINK (object));
2019-06-28 12:33:00 -04:00
switch (property_id) {
case LINKPROP_SRC:
g_value_take_object (value, g_weak_ref_get (&priv->src));
break;
case LINKPROP_SRC_STREAM:
g_value_set_uint (value, priv->src_stream);
break;
case LINKPROP_SINK:
g_value_take_object (value, g_weak_ref_get (&priv->sink));
break;
case LINKPROP_SINK_STREAM:
g_value_set_uint (value, priv->sink_stream);
break;
2019-12-02 09:55:57 -05:00
case LINKPROP_KEEP:
g_value_set_boolean (value, priv->keep);
break;
2019-06-28 12:33:00 -04:00
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
2019-06-28 12:33:00 -04:00
static void
wp_endpoint_link_init_async (GAsyncInitable *initable, int io_priority,
GCancellable *cancellable, GAsyncReadyCallback callback, gpointer data)
{
WpEndpointLink *link = WP_ENDPOINT_LINK(initable);
WpEndpointLinkPrivate *priv =
wp_endpoint_link_get_instance_private (WP_ENDPOINT_LINK (initable));
g_autoptr (WpEndpoint) src = g_weak_ref_get (&priv->src);
g_autoptr (WpEndpoint) sink = g_weak_ref_get (&priv->sink);
g_autoptr (GVariant) src_props = NULL;
g_autoptr (GVariant) sink_props = NULL;
WpEndpointPrivate *endpoint_priv;
/* Prepare the endpoints */
if (!WP_ENDPOINT_GET_CLASS (src)->prepare_link (src, priv->src_stream, link,
&src_props, NULL)) {
g_critical ("Failed to prepare link on source endpoint");
2019-06-28 12:33:00 -04:00
return;
}
if (!WP_ENDPOINT_GET_CLASS (sink)->prepare_link (sink, priv->sink_stream,
link, &sink_props, NULL)) {
g_critical ("Failed to prepare link on sink endpoint");
2019-06-28 12:33:00 -04:00
return;
}
/* Create the link */
g_return_if_fail (WP_ENDPOINT_LINK_GET_CLASS (link)->create);
if (!WP_ENDPOINT_LINK_GET_CLASS (link)->create (link, src_props,
sink_props, NULL)) {
g_critical ("Failed to create link in src and sink endpoints");
2019-06-28 12:33:00 -04:00
return;
}
/* Register the link on the endpoints */
endpoint_priv = wp_endpoint_get_instance_private (src);
g_ptr_array_add (endpoint_priv->links, g_object_ref (link));
endpoint_priv = wp_endpoint_get_instance_private (sink);
g_ptr_array_add (endpoint_priv->links, g_object_ref (link));
}
static gboolean
wp_endpoint_link_init_finish (GAsyncInitable *initable, GAsyncResult *result,
GError **error)
{
g_return_val_if_fail (g_task_is_valid (result, initable), FALSE);
return g_task_propagate_boolean (G_TASK (result), error);
}
static void
wp_endpoint_link_async_initable_init (gpointer iface, gpointer iface_data)
{
GAsyncInitableIface *ai_iface = iface;
ai_iface->init_async = wp_endpoint_link_init_async;
ai_iface->init_finish = wp_endpoint_link_init_finish;
}
static void
wp_endpoint_link_init (WpEndpointLink * self)
{
WpEndpointLinkPrivate *priv = wp_endpoint_link_get_instance_private (self);
/* Init the endpoint weak references */
g_weak_ref_init (&priv->src, NULL);
g_weak_ref_init (&priv->sink, NULL);
}
static void
wp_endpoint_link_class_init (WpEndpointLinkClass * klass)
{
GObjectClass *object_class = (GObjectClass *) klass;
object_class->finalize = endpoint_link_finalize;
object_class->set_property = endpoint_link_set_property;
object_class->get_property = endpoint_link_get_property;
g_object_class_install_property (object_class, LINKPROP_SRC,
g_param_spec_object ("src", "src", "The src endpoint", WP_TYPE_ENDPOINT,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, LINKPROP_SRC_STREAM,
g_param_spec_uint ("src-stream", "src-stream", "The src stream",
0, G_MAXUINT, 0,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, LINKPROP_SINK,
g_param_spec_object ("sink", "sink", "The sink endpoint", WP_TYPE_ENDPOINT,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, LINKPROP_SINK_STREAM,
g_param_spec_uint ("sink-stream", "sink-stream", "The sink stream",
0, G_MAXUINT, 0,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
2019-12-02 09:55:57 -05:00
g_object_class_install_property (object_class, LINKPROP_KEEP,
g_param_spec_boolean ("keep", "keep", "If we want to keep the link",
FALSE,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
}
2019-07-10 11:15:33 -04:00
/**
* wp_endpoint_link_get_source_endpoint:
* @self: the endpoint
*
* Gets the source endpoint of the link
*
* Returns: (transfer full): the source endpoint
*/
WpEndpoint *
wp_endpoint_link_get_source_endpoint (WpEndpointLink * self)
{
WpEndpointLinkPrivate *priv;
g_return_val_if_fail (WP_IS_ENDPOINT_LINK (self), NULL);
priv = wp_endpoint_link_get_instance_private (self);
2019-06-28 12:33:00 -04:00
return g_weak_ref_get (&priv->src);
}
guint32
wp_endpoint_link_get_source_stream (WpEndpointLink * self)
{
WpEndpointLinkPrivate *priv;
g_return_val_if_fail (WP_IS_ENDPOINT_LINK (self), 0);
priv = wp_endpoint_link_get_instance_private (self);
return priv->src_stream;
}
2019-07-10 11:15:33 -04:00
/**
* wp_endpoint_link_get_sink_endpoint:
* @self: the endpoint
*
* Gets the sink endpoint of the link
*
* Returns: (transfer full): the sink endpoint
*/
WpEndpoint *
wp_endpoint_link_get_sink_endpoint (WpEndpointLink * self)
{
WpEndpointLinkPrivate *priv;
g_return_val_if_fail (WP_IS_ENDPOINT_LINK (self), NULL);
priv = wp_endpoint_link_get_instance_private (self);
2019-06-28 12:33:00 -04:00
return g_weak_ref_get (&priv->sink);
}
guint32
wp_endpoint_link_get_sink_stream (WpEndpointLink * self)
{
WpEndpointLinkPrivate *priv;
g_return_val_if_fail (WP_IS_ENDPOINT_LINK (self), 0);
priv = wp_endpoint_link_get_instance_private (self);
return priv->sink_stream;
}
2019-12-02 09:55:57 -05:00
gboolean
wp_endpoint_link_is_kept (WpEndpointLink * self)
{
WpEndpointLinkPrivate *priv;
g_return_val_if_fail (WP_IS_ENDPOINT_LINK (self), 0);
priv = wp_endpoint_link_get_instance_private (self);
return priv->keep;
}
2019-06-28 12:33:00 -04:00
void
wp_endpoint_link_new (WpCore * core, WpEndpoint * src, guint32 src_stream,
2019-12-02 09:55:57 -05:00
WpEndpoint * sink, guint32 sink_stream, gboolean keep,
GAsyncReadyCallback ready, gpointer data)
{
const gchar *src_factory = NULL, *sink_factory = NULL;
2019-06-28 12:33:00 -04:00
GVariantBuilder b;
g_autoptr (GVariant) link_props = NULL;
2019-06-28 12:33:00 -04:00
g_return_if_fail (WP_IS_ENDPOINT (src));
g_return_if_fail (WP_IS_ENDPOINT (sink));
g_return_if_fail (WP_ENDPOINT_GET_CLASS (src)->prepare_link);
g_return_if_fail (WP_ENDPOINT_GET_CLASS (sink)->prepare_link);
/* find the factory */
if (WP_ENDPOINT_GET_CLASS (src)->get_endpoint_link_factory)
src_factory = WP_ENDPOINT_GET_CLASS (src)->get_endpoint_link_factory (src);
if (WP_ENDPOINT_GET_CLASS (sink)->get_endpoint_link_factory)
sink_factory = WP_ENDPOINT_GET_CLASS (sink)->get_endpoint_link_factory (sink);
if (src_factory || sink_factory) {
if (src_factory && sink_factory && strcmp (src_factory, sink_factory) != 0) {
2019-06-28 12:33:00 -04:00
g_critical ("It is not possible to link endpoints that both specify"
"different custom link factories");
return;
} else if (sink_factory)
src_factory = sink_factory;
} else {
src_factory = "pipewire-simple-endpoint-link";
}
2019-06-28 12:33:00 -04:00
/* Build the properties */
g_variant_builder_init (&b, G_VARIANT_TYPE_VARDICT);
g_variant_builder_add (&b, "{sv}", "src",
g_variant_new_uint64 ((guint64)src));
g_variant_builder_add (&b, "{sv}", "src-stream",
g_variant_new_uint32 (src_stream));
g_variant_builder_add (&b, "{sv}", "sink",
g_variant_new_uint64 ((guint64)sink));
g_variant_builder_add (&b, "{sv}", "sink-stream",
g_variant_new_uint32 (sink_stream));
2019-12-02 09:55:57 -05:00
g_variant_builder_add (&b, "{sv}", "keep",
g_variant_new_boolean (keep));
2019-06-28 12:33:00 -04:00
link_props = g_variant_builder_end (&b);
/* Create the link object async */
wp_factory_make (core, src_factory, WP_TYPE_ENDPOINT_LINK, link_props, ready,
data);
}
2019-06-28 12:33:00 -04:00
WpEndpointLink *
wp_endpoint_link_new_finish (GObject *initable, GAsyncResult *res,
GError **error)
{
GAsyncInitable *ai = G_ASYNC_INITABLE(initable);
return WP_ENDPOINT_LINK(g_async_initable_new_finish(ai, res, error));
}
void
wp_endpoint_link_destroy (WpEndpointLink * self)
{
WpEndpointLinkPrivate *priv;
WpEndpointPrivate *endpoint_priv;
2019-06-28 12:33:00 -04:00
g_autoptr (WpEndpoint) src = NULL;
g_autoptr (WpEndpoint) sink = NULL;
g_return_if_fail (WP_IS_ENDPOINT_LINK (self));
g_return_if_fail (WP_ENDPOINT_LINK_GET_CLASS (self)->destroy);
priv = wp_endpoint_link_get_instance_private (self);
2019-06-28 12:33:00 -04:00
src = g_weak_ref_get (&priv->src);
sink = g_weak_ref_get (&priv->sink);
WP_ENDPOINT_LINK_GET_CLASS (self)->destroy (self);
2019-06-28 12:33:00 -04:00
if (src && WP_ENDPOINT_GET_CLASS (src)->release_link)
WP_ENDPOINT_GET_CLASS (src)->release_link (src, self);
if (sink && WP_ENDPOINT_GET_CLASS (sink)->release_link)
WP_ENDPOINT_GET_CLASS (sink)->release_link (sink, self);
2019-06-28 12:33:00 -04:00
if (src) {
endpoint_priv = wp_endpoint_get_instance_private (src);
g_ptr_array_remove_fast (endpoint_priv->links, self);
}
if (sink) {
endpoint_priv = wp_endpoint_get_instance_private (sink);
g_ptr_array_remove_fast (endpoint_priv->links, self);
}
}