
There are 3 kinds of WpProxy objects: * the ones that are created as a result of binding a global from the registry * the ones that are created as a result of calling into a remote factory (wp_node_new_from_factory, etc...) * the ones that are a local implementation of an object (WpImplNode, etc...) and are exported Previously the object manager was only able to track the first kind. With these changes we can now also have globals associated with WpProxies that were created earlier (and caused the creation of the global). This saves some resources and reduces round-trips (in case client code wants to change properties of an object that is locally implemented, it shouldn't need to do a round-trip through the server)
523 lines
15 KiB
C
523 lines
15 KiB
C
/* WirePlumber
|
|
*
|
|
* Copyright © 2019 Collabora Ltd.
|
|
* @author Julian Bouzas <julian.bouzas@collabora.com>
|
|
*
|
|
* SPDX-License-Identifier: MIT
|
|
*/
|
|
|
|
#include <spa/param/props.h>
|
|
#include <pipewire/pipewire.h>
|
|
#include <spa/debug/types.h>
|
|
#include <spa/pod/builder.h>
|
|
#include <spa/pod/iter.h>
|
|
#include <spa/param/audio/type-info.h>
|
|
|
|
#include "stream.h"
|
|
|
|
typedef struct _WpAudioStreamPrivate WpAudioStreamPrivate;
|
|
struct _WpAudioStreamPrivate
|
|
{
|
|
GObject parent;
|
|
|
|
GTask *init_task;
|
|
|
|
/* Props */
|
|
GWeakRef endpoint;
|
|
guint id;
|
|
gchar *name;
|
|
enum pw_direction direction;
|
|
|
|
/* Stream Proxy */
|
|
WpNode *proxy;
|
|
|
|
WpObjectManager *ports_om;
|
|
GVariantBuilder port_vb;
|
|
gboolean port_config_done;
|
|
|
|
/* Stream Controls */
|
|
gfloat volume;
|
|
gboolean mute;
|
|
};
|
|
|
|
enum {
|
|
PROP_0,
|
|
PROP_ENDPOINT,
|
|
PROP_ID,
|
|
PROP_NAME,
|
|
PROP_DIRECTION,
|
|
PROP_PROXY_NODE,
|
|
};
|
|
|
|
enum {
|
|
SIGNAL_CONTROL_CHANGED,
|
|
N_SIGNALS,
|
|
};
|
|
|
|
static guint32 signals[N_SIGNALS] = {0};
|
|
|
|
static void wp_audio_stream_async_initable_init (gpointer iface, gpointer iface_data);
|
|
|
|
G_DEFINE_ABSTRACT_TYPE_WITH_CODE (WpAudioStream, wp_audio_stream, G_TYPE_OBJECT,
|
|
G_ADD_PRIVATE (WpAudioStream)
|
|
G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, wp_audio_stream_async_initable_init))
|
|
|
|
static void
|
|
audio_stream_event_param (WpProxy *proxy, int seq, uint32_t id,
|
|
uint32_t index, uint32_t next, const struct spa_pod *param,
|
|
WpAudioStream *self)
|
|
{
|
|
WpAudioStreamPrivate *priv = wp_audio_stream_get_instance_private (self);
|
|
g_autoptr (WpBaseEndpoint) ep = g_weak_ref_get (&priv->endpoint);
|
|
|
|
switch (id) {
|
|
case SPA_PARAM_Props:
|
|
{
|
|
struct spa_pod_prop *prop;
|
|
struct spa_pod_object *obj = (struct spa_pod_object *) param;
|
|
float volume = priv->volume;
|
|
bool mute = priv->mute;
|
|
|
|
SPA_POD_OBJECT_FOREACH(obj, prop) {
|
|
switch (prop->key) {
|
|
case SPA_PROP_volume:
|
|
spa_pod_get_float(&prop->value, &volume);
|
|
priv->volume = volume;
|
|
g_signal_emit (self, signals[SIGNAL_CONTROL_CHANGED], 0,
|
|
WP_ENDPOINT_CONTROL_VOLUME);
|
|
break;
|
|
case SPA_PROP_mute:
|
|
spa_pod_get_bool(&prop->value, &mute);
|
|
priv->mute = mute;
|
|
g_signal_emit (self, signals[SIGNAL_CONTROL_CHANGED], 0,
|
|
WP_ENDPOINT_CONTROL_MUTE);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
on_ports_changed (WpObjectManager *om, WpAudioStream *self)
|
|
{
|
|
WpAudioStreamPrivate *priv = wp_audio_stream_get_instance_private (self);
|
|
|
|
if (priv->port_config_done) {
|
|
g_debug ("%s:%p port config done", G_OBJECT_TYPE_NAME (self), self);
|
|
wp_audio_stream_init_task_finish (self, NULL);
|
|
g_signal_handlers_disconnect_by_func (priv->ports_om, on_ports_changed,
|
|
self);
|
|
}
|
|
}
|
|
|
|
static void
|
|
on_node_proxy_augmented (WpProxy * proxy, GAsyncResult * res,
|
|
WpAudioStream * self)
|
|
{
|
|
WpAudioStreamPrivate *priv = wp_audio_stream_get_instance_private (self);
|
|
g_autoptr (GError) error = NULL;
|
|
g_autoptr (WpCore) core = NULL;
|
|
GVariantBuilder b;
|
|
const struct pw_node_info *info = NULL;
|
|
g_autofree gchar *node_id = NULL;
|
|
|
|
wp_proxy_augment_finish (proxy, res, &error);
|
|
if (error) {
|
|
g_warning ("WpAudioStream:%p Node proxy failed to augment: %s", self,
|
|
error->message);
|
|
wp_audio_stream_init_task_finish (self, g_steal_pointer (&error));
|
|
return;
|
|
}
|
|
|
|
g_signal_connect_object (proxy, "param",
|
|
(GCallback) audio_stream_event_param, self, 0);
|
|
wp_proxy_subscribe_params (proxy, 1, SPA_PARAM_Props);
|
|
|
|
priv->ports_om = wp_object_manager_new ();
|
|
|
|
/* Get the node id */
|
|
info = wp_proxy_get_info (proxy);
|
|
node_id = g_strdup_printf ("%u", info->id);
|
|
|
|
/* set a constraint: the port's "node.id" must match
|
|
the stream's underlying node id */
|
|
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 (node_id));
|
|
g_variant_builder_close (&b);
|
|
|
|
/* declare interest on ports with this constraint */
|
|
wp_object_manager_add_interest (priv->ports_om, WP_TYPE_PORT,
|
|
g_variant_builder_end (&b),
|
|
WP_PROXY_FEATURE_PW_PROXY | WP_PROXY_FEATURE_INFO);
|
|
|
|
g_signal_connect (priv->ports_om, "objects-changed",
|
|
(GCallback) on_ports_changed, self);
|
|
|
|
/* install the object manager */
|
|
g_object_get (proxy, "core", &core, NULL);
|
|
wp_core_install_object_manager (core, priv->ports_om);
|
|
}
|
|
|
|
static void
|
|
wp_audio_stream_finalize (GObject * object)
|
|
{
|
|
WpAudioStreamPrivate *priv =
|
|
wp_audio_stream_get_instance_private (WP_AUDIO_STREAM (object));
|
|
|
|
g_clear_object (&priv->proxy);
|
|
g_clear_object (&priv->ports_om);
|
|
|
|
/* Clear the endpoint weak reference */
|
|
g_weak_ref_clear (&priv->endpoint);
|
|
|
|
/* Clear the name */
|
|
g_clear_pointer (&priv->name, g_free);
|
|
|
|
g_clear_object (&priv->init_task);
|
|
|
|
G_OBJECT_CLASS (wp_audio_stream_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
wp_audio_stream_set_property (GObject * object, guint property_id,
|
|
const GValue * value, GParamSpec * pspec)
|
|
{
|
|
WpAudioStreamPrivate *priv =
|
|
wp_audio_stream_get_instance_private (WP_AUDIO_STREAM (object));
|
|
|
|
switch (property_id) {
|
|
case PROP_ENDPOINT:
|
|
g_weak_ref_set (&priv->endpoint, g_value_get_object (value));
|
|
break;
|
|
case PROP_ID:
|
|
priv->id = g_value_get_uint(value);
|
|
break;
|
|
case PROP_NAME:
|
|
priv->name = g_value_dup_string (value);
|
|
break;
|
|
case PROP_DIRECTION:
|
|
priv->direction = g_value_get_uint(value);
|
|
break;
|
|
case PROP_PROXY_NODE:
|
|
priv->proxy = g_value_dup_object (value);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
wp_audio_stream_get_property (GObject * object, guint property_id,
|
|
GValue * value, GParamSpec * pspec)
|
|
{
|
|
WpAudioStreamPrivate *priv =
|
|
wp_audio_stream_get_instance_private (WP_AUDIO_STREAM (object));
|
|
|
|
switch (property_id) {
|
|
case PROP_ENDPOINT:
|
|
g_value_take_object (value, g_weak_ref_get (&priv->endpoint));
|
|
break;
|
|
case PROP_ID:
|
|
g_value_set_uint (value, priv->id);
|
|
break;
|
|
case PROP_NAME:
|
|
g_value_set_string (value, priv->name);
|
|
break;
|
|
case PROP_DIRECTION:
|
|
g_value_set_uint (value, priv->direction);
|
|
break;
|
|
case PROP_PROXY_NODE:
|
|
g_value_set_object (value, priv->proxy);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
wp_audio_stream_init_async (GAsyncInitable *initable, int io_priority,
|
|
GCancellable *cancellable, GAsyncReadyCallback callback, gpointer data)
|
|
{
|
|
WpAudioStream *self = WP_AUDIO_STREAM(initable);
|
|
WpAudioStreamPrivate *priv = wp_audio_stream_get_instance_private (self);
|
|
g_autoptr (WpBaseEndpoint) ep = g_weak_ref_get (&priv->endpoint);
|
|
g_autoptr (WpCore) core = wp_audio_stream_get_core (self);
|
|
|
|
g_debug ("WpBaseEndpoint:%p init stream %s (%s:%p)", ep, priv->name,
|
|
G_OBJECT_TYPE_NAME (self), self);
|
|
|
|
priv->init_task = g_task_new (initable, cancellable, callback, data);
|
|
|
|
g_return_if_fail (priv->proxy);
|
|
wp_proxy_augment (WP_PROXY (priv->proxy),
|
|
WP_PROXY_FEATURES_STANDARD, cancellable,
|
|
(GAsyncReadyCallback) on_node_proxy_augmented, self);
|
|
}
|
|
|
|
static gboolean
|
|
wp_audio_stream_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_audio_stream_async_initable_init (gpointer iface, gpointer iface_data)
|
|
{
|
|
GAsyncInitableIface *ai_iface = iface;
|
|
|
|
ai_iface->init_async = wp_audio_stream_init_async;
|
|
ai_iface->init_finish = wp_audio_stream_init_finish;
|
|
}
|
|
|
|
static void
|
|
wp_audio_stream_init (WpAudioStream * self)
|
|
{
|
|
WpAudioStreamPrivate *priv = wp_audio_stream_get_instance_private (self);
|
|
|
|
/* Controls */
|
|
priv->volume = 1.0;
|
|
priv->mute = FALSE;
|
|
}
|
|
|
|
static void
|
|
wp_audio_stream_class_init (WpAudioStreamClass * klass)
|
|
{
|
|
GObjectClass *object_class = (GObjectClass *) klass;
|
|
|
|
object_class->finalize = wp_audio_stream_finalize;
|
|
object_class->set_property = wp_audio_stream_set_property;
|
|
object_class->get_property = wp_audio_stream_get_property;
|
|
|
|
/* Install the properties */
|
|
g_object_class_install_property (object_class, PROP_ENDPOINT,
|
|
g_param_spec_object ("endpoint", "endpoint",
|
|
"The endpoint this audio stream belongs to", WP_TYPE_BASE_ENDPOINT,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
|
|
g_object_class_install_property (object_class, PROP_ID,
|
|
g_param_spec_uint ("id", "id", "The Id of the audio stream", 0, G_MAXUINT, 0,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
|
|
g_object_class_install_property (object_class, PROP_NAME,
|
|
g_param_spec_string ("name", "name", "The name of the audio stream", NULL,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
|
|
g_object_class_install_property (object_class, PROP_DIRECTION,
|
|
g_param_spec_uint ("direction", "direction",
|
|
"The direction of the audio stream", 0, 1, 0,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
|
|
g_object_class_install_property (object_class, PROP_PROXY_NODE,
|
|
g_param_spec_object ("node", "node",
|
|
"The node proxy of the stream", WP_TYPE_NODE,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
|
|
|
|
signals[SIGNAL_CONTROL_CHANGED] = g_signal_new (
|
|
"control-changed", G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_UINT);
|
|
}
|
|
|
|
WpAudioStream *
|
|
wp_audio_stream_new_finish (GObject *initable, GAsyncResult *res,
|
|
GError **error)
|
|
{
|
|
GAsyncInitable *ai = G_ASYNC_INITABLE(initable);
|
|
return WP_AUDIO_STREAM (g_async_initable_new_finish(ai, res, error));
|
|
}
|
|
|
|
const char *
|
|
wp_audio_stream_get_name (WpAudioStream * self)
|
|
{
|
|
WpAudioStreamPrivate *priv = wp_audio_stream_get_instance_private (self);
|
|
|
|
return priv->name;
|
|
}
|
|
|
|
enum pw_direction
|
|
wp_audio_stream_get_direction (WpAudioStream * self)
|
|
{
|
|
WpAudioStreamPrivate *priv = wp_audio_stream_get_instance_private (self);
|
|
|
|
return priv->direction;
|
|
}
|
|
|
|
WpNode *
|
|
wp_audio_stream_get_node (WpAudioStream * self)
|
|
{
|
|
WpAudioStreamPrivate *priv = wp_audio_stream_get_instance_private (self);
|
|
|
|
return priv->proxy;
|
|
}
|
|
|
|
const struct pw_node_info *
|
|
wp_audio_stream_get_info (WpAudioStream * self)
|
|
{
|
|
WpAudioStreamPrivate *priv = wp_audio_stream_get_instance_private (self);
|
|
|
|
return wp_proxy_get_info (WP_PROXY (priv->proxy));
|
|
}
|
|
|
|
static void
|
|
port_proxies_foreach_func(gpointer data, gpointer user_data)
|
|
{
|
|
WpProxy *port = WP_PROXY (data);
|
|
WpAudioStream *self = WP_AUDIO_STREAM (user_data);
|
|
WpAudioStreamPrivate *priv = wp_audio_stream_get_instance_private (self);
|
|
const struct pw_node_info *node_info;
|
|
const struct pw_port_info *port_info;
|
|
g_autoptr (WpProperties) props = NULL;
|
|
const gchar *channel;
|
|
uint32_t channel_n = SPA_AUDIO_CHANNEL_UNKNOWN;
|
|
|
|
node_info = wp_proxy_get_info (WP_PROXY (priv->proxy));
|
|
g_return_if_fail (node_info);
|
|
|
|
port_info = wp_proxy_get_info (port);
|
|
g_return_if_fail (port_info);
|
|
|
|
props = wp_proxy_get_properties (port);
|
|
channel = wp_properties_get (props, PW_KEY_AUDIO_CHANNEL);
|
|
if (channel) {
|
|
const struct spa_type_info *t = spa_type_audio_channel;
|
|
for (; t && t->name; t++) {
|
|
const char *name = t->name + strlen(SPA_TYPE_INFO_AUDIO_CHANNEL_BASE);
|
|
if (!g_strcmp0 (channel, name)) {
|
|
channel_n = t->type;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* tuple format:
|
|
uint32 node_id;
|
|
uint32 port_id;
|
|
uint32 channel; // enum spa_audio_channel
|
|
uint8 direction; // enum spa_direction
|
|
*/
|
|
g_variant_builder_add (&priv->port_vb, "(uuuy)", node_info->id,
|
|
port_info->id, channel_n, (guint8) port_info->direction);
|
|
}
|
|
|
|
gboolean
|
|
wp_audio_stream_prepare_link (WpAudioStream * self, GVariant ** properties,
|
|
GError ** error)
|
|
{
|
|
WpAudioStreamPrivate *priv = wp_audio_stream_get_instance_private (self);
|
|
g_autoptr (GPtrArray) port_proxies =
|
|
wp_object_manager_get_objects (priv->ports_om, 0);
|
|
|
|
/* Create a variant array with all the ports */
|
|
g_variant_builder_init (&priv->port_vb, G_VARIANT_TYPE ("a(uuuy)"));
|
|
g_ptr_array_foreach (port_proxies, port_proxies_foreach_func, self);
|
|
*properties = g_variant_builder_end (&priv->port_vb);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gfloat
|
|
wp_audio_stream_get_volume (WpAudioStream * self)
|
|
{
|
|
WpAudioStreamPrivate *priv = wp_audio_stream_get_instance_private (self);
|
|
return priv->volume;
|
|
}
|
|
|
|
gboolean
|
|
wp_audio_stream_get_mute (WpAudioStream * self)
|
|
{
|
|
WpAudioStreamPrivate *priv = wp_audio_stream_get_instance_private (self);
|
|
return priv->mute;
|
|
}
|
|
|
|
void
|
|
wp_audio_stream_set_volume (WpAudioStream * self, gfloat volume)
|
|
{
|
|
WpAudioStreamPrivate *priv = wp_audio_stream_get_instance_private (self);
|
|
char buf[1024];
|
|
struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buf, sizeof(buf));
|
|
|
|
/* Make sure the proxy is valid */
|
|
g_return_if_fail (priv->proxy);
|
|
|
|
wp_proxy_set_param (WP_PROXY (priv->proxy),
|
|
SPA_PARAM_Props, 0,
|
|
spa_pod_builder_add_object (&b,
|
|
SPA_TYPE_OBJECT_Props, SPA_PARAM_Props,
|
|
SPA_PROP_volume, SPA_POD_Float(volume)));
|
|
}
|
|
|
|
void
|
|
wp_audio_stream_set_mute (WpAudioStream * self, gboolean mute)
|
|
{
|
|
WpAudioStreamPrivate *priv = wp_audio_stream_get_instance_private (self);
|
|
char buf[1024];
|
|
struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buf, sizeof(buf));
|
|
|
|
/* Make sure the proxy is valid */
|
|
g_return_if_fail (priv->proxy);
|
|
|
|
wp_proxy_set_param (WP_PROXY (priv->proxy),
|
|
SPA_PARAM_Props, 0,
|
|
spa_pod_builder_add_object (&b,
|
|
SPA_TYPE_OBJECT_Props, SPA_PARAM_Props,
|
|
SPA_PROP_mute, SPA_POD_Bool(mute)));
|
|
}
|
|
|
|
WpCore *
|
|
wp_audio_stream_get_core (WpAudioStream * self)
|
|
{
|
|
WpAudioStreamPrivate *priv = wp_audio_stream_get_instance_private (self);
|
|
g_autoptr (WpBaseEndpoint) ep = NULL;
|
|
g_autoptr (WpCore) core = NULL;
|
|
|
|
ep = g_weak_ref_get (&priv->endpoint);
|
|
core = wp_base_endpoint_get_core (ep);
|
|
return g_steal_pointer (&core);
|
|
}
|
|
|
|
void
|
|
wp_audio_stream_init_task_finish (WpAudioStream * self, GError * err)
|
|
{
|
|
WpAudioStreamPrivate *priv = wp_audio_stream_get_instance_private (self);
|
|
g_autoptr (GError) error = err;
|
|
|
|
if (!priv->init_task)
|
|
return;
|
|
|
|
if (error)
|
|
g_task_return_error (priv->init_task, g_steal_pointer (&error));
|
|
else
|
|
g_task_return_boolean (priv->init_task, TRUE);
|
|
|
|
g_clear_object (&priv->init_task);
|
|
}
|
|
|
|
void
|
|
wp_audio_stream_set_port_config (WpAudioStream * self,
|
|
const struct spa_pod * param)
|
|
{
|
|
WpAudioStreamPrivate *priv = wp_audio_stream_get_instance_private (self);
|
|
|
|
wp_proxy_set_param (WP_PROXY (priv->proxy), SPA_PARAM_PortConfig, 0, param);
|
|
}
|
|
|
|
void
|
|
wp_audio_stream_finish_port_config (WpAudioStream * self)
|
|
{
|
|
WpAudioStreamPrivate *priv = wp_audio_stream_get_instance_private (self);
|
|
priv->port_config_done = TRUE;
|
|
}
|