Newer
Older
/* 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>

George Kiagiadakis
committed
#include <spa/debug/types.h>
#include <spa/pod/builder.h>
#include <spa/pod/iter.h>

George Kiagiadakis
committed
#include <spa/param/audio/type-info.h>
#include "stream.h"
typedef struct _WpAudioStreamPrivate WpAudioStreamPrivate;
struct _WpAudioStreamPrivate
{
GObject parent;
/* Props */
GWeakRef endpoint;
guint id;
gchar *name;
enum pw_direction direction;
/* Stream Proxy */
WpProxyNode *proxy;
GVariantBuilder port_vb;
/* Stream Controls */
gfloat volume;
gboolean mute;
};
enum {
PROP_0,
PROP_ENDPOINT,
PROP_ID,
PROP_NAME,
PROP_DIRECTION,
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
};
enum {
CONTROL_VOLUME = 0,
CONTROL_MUTE,
N_CONTROLS,
};
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))
guint
wp_audio_stream_id_encode (guint stream_id, guint control_id)
{
g_return_val_if_fail (control_id < N_CONTROLS, 0);
/* encode NONE as 0 and everything else with +1 */
/* NONE is MAX_UINT, so +1 will do the trick */
stream_id += 1;
/* Encode the stream and control Ids. The first ID is reserved
* for the "selected" control, registered in the endpoint */
return 1 + (stream_id * N_CONTROLS) + control_id;
}
void
wp_audio_stream_id_decode (guint id, guint *stream_id, guint *control_id)
{
guint s_id, c_id;
g_return_if_fail (id >= 1);
id -= 1;
/* Decode the stream and control Ids */
s_id = (id / N_CONTROLS) - 1;
c_id = id % N_CONTROLS;
/* Set the output params */
if (stream_id)
*stream_id = s_id;
if (control_id)
*control_id = c_id;
}
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)
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
{
WpAudioStreamPrivate *priv = wp_audio_stream_get_instance_private (self);
g_autoptr (WpEndpoint) 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);
if (priv->volume != volume) {
priv->volume = volume;
wp_endpoint_notify_control_value (ep,
wp_audio_stream_id_encode (priv->id, CONTROL_VOLUME));
}
break;
case SPA_PROP_mute:
spa_pod_get_bool(&prop->value, &mute);
if (priv->mute != mute) {
priv->mute = mute;
wp_endpoint_notify_control_value (ep,
wp_audio_stream_id_encode (priv->id, 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 (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_node_subscribe_params (WP_PROXY_NODE (proxy), 1, SPA_PARAM_Props);
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
priv->ports_om = wp_object_manager_new ();
/* Get the node id */
info = wp_proxy_node_get_info (WP_PROXY_NODE (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_proxy_interest (priv->ports_om, PW_TYPE_INTERFACE_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->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;
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
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 (WpEndpoint) ep = g_weak_ref_get (&priv->endpoint);
g_autoptr (WpCore) core = wp_audio_stream_get_core (self);
GVariantDict d;
g_debug ("WpEndpoint:%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);
/* Register the volume control */
g_variant_dict_init (&d, NULL);
g_variant_dict_insert (&d, "id", "u",
wp_audio_stream_id_encode (priv->id, CONTROL_VOLUME));
if (priv->id != WP_STREAM_ID_NONE)
g_variant_dict_insert (&d, "stream-id", "u", priv->id);
g_variant_dict_insert (&d, "name", "s", "volume");
g_variant_dict_insert (&d, "type", "s", "d");
g_variant_dict_insert (&d, "range", "(dd)", 0.0, 1.0);
g_variant_dict_insert (&d, "default-value", "d", priv->volume);
wp_endpoint_register_control (ep, g_variant_dict_end (&d));
/* Register the mute control */
g_variant_dict_init (&d, NULL);
g_variant_dict_insert (&d, "id", "u",
wp_audio_stream_id_encode (priv->id, CONTROL_MUTE));
if (priv->id != WP_STREAM_ID_NONE)
g_variant_dict_insert (&d, "stream-id", "u", priv->id);
g_variant_dict_insert (&d, "name", "s", "mute");
g_variant_dict_insert (&d, "type", "s", "b");
g_variant_dict_insert (&d, "default-value", "b", priv->mute);
wp_endpoint_register_control (ep, g_variant_dict_end (&d));
g_return_if_fail (priv->proxy);
wp_proxy_augment (WP_PROXY (priv->proxy),
WP_PROXY_FEATURE_PW_PROXY | WP_PROXY_FEATURE_INFO, NULL,
(GAsyncReadyCallback) on_node_proxy_augmented, self);
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
}
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_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 ("proxy-node", "proxy-node",
"The node proxy of the stream", WP_TYPE_PROXY_NODE,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
}
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;
}
WpProxyNode *
wp_audio_stream_get_proxy_node (WpAudioStream * self)
WpAudioStreamPrivate *priv = wp_audio_stream_get_instance_private (self);
const struct pw_node_info *
wp_audio_stream_get_info (WpAudioStream * self)
{
WpAudioStreamPrivate *priv = wp_audio_stream_get_instance_private (self);
return wp_proxy_node_get_info (priv->proxy);
}
static void
port_proxies_foreach_func(gpointer data, gpointer user_data)
{
WpAudioStream *self = user_data;
WpAudioStreamPrivate *priv = wp_audio_stream_get_instance_private (self);

George Kiagiadakis
committed
WpProxyPort *port = data;
const struct pw_node_info *node_info;
const struct pw_port_info *port_info;

George Kiagiadakis
committed
g_autoptr (WpProperties) props = NULL;
const gchar *channel;
uint32_t channel_n = SPA_AUDIO_CHANNEL_UNKNOWN;

George Kiagiadakis
committed
node_info = wp_proxy_node_get_info (priv->proxy);
g_return_if_fail (node_info);

George Kiagiadakis
committed
port_info = wp_proxy_port_get_info (port);
g_return_if_fail (port_info);

George Kiagiadakis
committed
props = wp_proxy_port_get_properties (port);

George Kiagiadakis
committed
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);
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
return TRUE;
}
GVariant *
wp_audio_stream_get_control_value (WpAudioStream * self, guint32 control_id)
{
WpAudioStreamPrivate *priv = wp_audio_stream_get_instance_private (self);
switch (control_id) {
case CONTROL_VOLUME:
return g_variant_new_double (priv->volume);
case CONTROL_MUTE:
return g_variant_new_boolean (priv->mute);
default:
g_warning ("Unknown control id %u", control_id);
return NULL;
}
}
gboolean
wp_audio_stream_set_control_value (WpAudioStream * self, guint32 control_id,
GVariant * value)
{
WpAudioStreamPrivate *priv = wp_audio_stream_get_instance_private (self);
char buf[1024];
struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buf, sizeof(buf));
float volume;
bool mute;
/* Make sure the proxy is valid */
g_return_val_if_fail (priv->proxy, FALSE);
/* Set the specific constrol */
switch (control_id) {
case CONTROL_VOLUME:
volume = g_variant_get_double (value);
wp_proxy_node_set_param (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),
NULL));
break;
case CONTROL_MUTE:
mute = g_variant_get_boolean (value);
wp_proxy_node_set_param (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),
NULL));
break;
default:
g_warning ("Unknown control id %u", control_id);
return FALSE;
}
return TRUE;
}
WpCore *
wp_audio_stream_get_core (WpAudioStream * self)
{
WpAudioStreamPrivate *priv = wp_audio_stream_get_instance_private (self);
g_autoptr (WpEndpoint) ep = NULL;
g_autoptr (WpCore) core = NULL;
ep = g_weak_ref_get (&priv->endpoint);
core = wp_endpoint_get_core (ep);
return g_steal_pointer (&core);
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
}
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_node_set_param (priv->proxy, SPA_PARAM_PortConfig, 0, param);

George Kiagiadakis
committed
}
void
wp_audio_stream_finish_port_config (WpAudioStream * self)
{
WpAudioStreamPrivate *priv = wp_audio_stream_get_instance_private (self);