2019-08-13 14:18:34 -04:00
|
|
|
|
/* 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 "stream.h"
|
|
|
|
|
|
|
|
|
|
|
|
typedef struct _WpAudioStreamPrivate WpAudioStreamPrivate;
|
|
|
|
|
|
struct _WpAudioStreamPrivate
|
|
|
|
|
|
{
|
|
|
|
|
|
GObject parent;
|
|
|
|
|
|
|
2019-08-29 21:21:33 +03:00
|
|
|
|
GTask *init_task;
|
|
|
|
|
|
|
2019-08-13 14:18:34 -04:00
|
|
|
|
/* Props */
|
|
|
|
|
|
GWeakRef endpoint;
|
|
|
|
|
|
guint id;
|
|
|
|
|
|
gchar *name;
|
|
|
|
|
|
enum pw_direction direction;
|
|
|
|
|
|
|
2019-08-29 21:21:33 +03:00
|
|
|
|
/* Stream Proxy */
|
|
|
|
|
|
WpProxyNode *proxy;
|
2019-08-13 14:18:34 -04:00
|
|
|
|
|
|
|
|
|
|
/* Stream Port Proxies */
|
|
|
|
|
|
GPtrArray *port_proxies;
|
|
|
|
|
|
|
|
|
|
|
|
/* Stream Controls */
|
|
|
|
|
|
gfloat volume;
|
|
|
|
|
|
gboolean mute;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
enum {
|
|
|
|
|
|
PROP_0,
|
|
|
|
|
|
PROP_ENDPOINT,
|
|
|
|
|
|
PROP_ID,
|
|
|
|
|
|
PROP_NAME,
|
|
|
|
|
|
PROP_DIRECTION,
|
2019-08-29 21:21:33 +03:00
|
|
|
|
PROP_PROXY_NODE,
|
2019-08-13 14:18:34 -04:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-08-29 21:21:33 +03:00
|
|
|
|
/* called once after all the ports are augmented with INFO */
|
2019-08-13 14:18:34 -04:00
|
|
|
|
static void
|
2019-08-29 21:21:33 +03:00
|
|
|
|
on_all_ports_augmented (WpProxy *proxy, GAsyncResult *res, WpAudioStream *self)
|
2019-08-13 14:18:34 -04:00
|
|
|
|
{
|
2019-08-29 21:21:33 +03:00
|
|
|
|
g_autoptr (GError) error = NULL;
|
2019-08-13 14:18:34 -04:00
|
|
|
|
|
2019-08-29 21:21:33 +03:00
|
|
|
|
wp_proxy_sync_finish (proxy, res, &error);
|
|
|
|
|
|
if (error) {
|
|
|
|
|
|
g_warning ("WpAudioStream:%p second sync failed: %s", self,
|
|
|
|
|
|
error->message);
|
|
|
|
|
|
wp_audio_stream_init_task_finish (self, g_steal_pointer (&error));
|
2019-08-13 14:18:34 -04:00
|
|
|
|
return;
|
2019-08-29 21:21:33 +03:00
|
|
|
|
}
|
2019-08-13 14:18:34 -04:00
|
|
|
|
|
2019-08-29 21:21:33 +03:00
|
|
|
|
g_debug ("%s:%p second sync done", G_OBJECT_TYPE_NAME (self), self);
|
|
|
|
|
|
|
|
|
|
|
|
wp_audio_stream_init_task_finish (self, NULL);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* called multiple times after on_port_config_done */
|
|
|
|
|
|
static void
|
|
|
|
|
|
on_audio_stream_port_augmented (WpProxy *port_proxy, GAsyncResult *res,
|
|
|
|
|
|
WpAudioStream *self)
|
|
|
|
|
|
{
|
|
|
|
|
|
WpAudioStreamPrivate *priv = wp_audio_stream_get_instance_private (self);
|
|
|
|
|
|
g_autoptr (GError) error = NULL;
|
|
|
|
|
|
|
|
|
|
|
|
wp_proxy_augment_finish (port_proxy, res, &error);
|
2019-08-13 14:18:34 -04:00
|
|
|
|
if (error) {
|
2019-08-29 21:21:33 +03:00
|
|
|
|
g_warning ("WpAudioStream:%p Stream port failed to augment: %s", self,
|
|
|
|
|
|
error->message);
|
|
|
|
|
|
wp_audio_stream_init_task_finish (self, g_steal_pointer (&error));
|
2019-08-13 14:18:34 -04:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Add the proxy port to the array */
|
2019-08-29 21:21:33 +03:00
|
|
|
|
g_ptr_array_add(priv->port_proxies, g_object_ref (port_proxy));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* called once after we have all the ports added */
|
|
|
|
|
|
static void
|
|
|
|
|
|
on_port_config_done (WpProxy *proxy, GAsyncResult *res, WpAudioStream *self)
|
|
|
|
|
|
{
|
|
|
|
|
|
WpAudioStreamPrivate *priv = wp_audio_stream_get_instance_private (self);
|
|
|
|
|
|
g_autoptr (GError) error = NULL;
|
|
|
|
|
|
|
|
|
|
|
|
wp_proxy_sync_finish (proxy, res, &error);
|
|
|
|
|
|
if (error) {
|
|
|
|
|
|
g_warning ("WpAudioStream:%p port config sync failed: %s", self,
|
|
|
|
|
|
error->message);
|
|
|
|
|
|
wp_audio_stream_init_task_finish (self, g_steal_pointer (&error));
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
g_debug ("%s:%p port config done", G_OBJECT_TYPE_NAME (self), self);
|
|
|
|
|
|
|
|
|
|
|
|
wp_proxy_sync (WP_PROXY (priv->proxy), NULL,
|
|
|
|
|
|
(GAsyncReadyCallback) on_all_ports_augmented, self);
|
2019-08-13 14:18:34 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2019-08-29 21:21:33 +03:00
|
|
|
|
/* called multiple times after we set the PortConfig */
|
2019-08-13 14:18:34 -04:00
|
|
|
|
static void
|
2019-09-07 17:55:46 +03:00
|
|
|
|
on_audio_stream_port_added (WpCore *core, WpProxy *proxy, WpAudioStream *self)
|
2019-08-13 14:18:34 -04:00
|
|
|
|
{
|
2019-08-29 21:21:33 +03:00
|
|
|
|
WpAudioStreamPrivate *priv = wp_audio_stream_get_instance_private (self);
|
|
|
|
|
|
g_autoptr (WpProperties) props = wp_proxy_get_global_properties (proxy);
|
2019-08-13 14:18:34 -04:00
|
|
|
|
const struct pw_node_info *info = NULL;
|
2019-08-19 13:53:45 +03:00
|
|
|
|
const char *s;
|
|
|
|
|
|
guint node_id = 0;
|
2019-08-13 14:18:34 -04:00
|
|
|
|
|
|
|
|
|
|
/* Get the node id */
|
2019-08-29 21:21:33 +03:00
|
|
|
|
info = wp_proxy_node_get_info (priv->proxy);
|
2019-08-13 14:18:34 -04:00
|
|
|
|
if (!info)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
2019-08-29 21:21:33 +03:00
|
|
|
|
if ((s = wp_properties_get (props, PW_KEY_NODE_ID)))
|
2019-08-19 13:53:45 +03:00
|
|
|
|
node_id = atoi(s);
|
|
|
|
|
|
|
2019-08-13 14:18:34 -04:00
|
|
|
|
/* Skip ports that are not owned by this stream */
|
2019-08-19 13:53:45 +03:00
|
|
|
|
if (info->id != node_id)
|
2019-08-13 14:18:34 -04:00
|
|
|
|
return;
|
|
|
|
|
|
|
2019-08-29 21:21:33 +03:00
|
|
|
|
wp_proxy_augment (proxy, WP_PROXY_FEATURE_PW_PROXY | WP_PROXY_FEATURE_INFO,
|
|
|
|
|
|
NULL, (GAsyncReadyCallback) on_audio_stream_port_augmented, self);
|
2019-08-13 14:18:34 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
2019-08-29 21:21:33 +03:00
|
|
|
|
audio_stream_event_param (WpProxy *proxy, int seq, uint32_t id,
|
|
|
|
|
|
uint32_t index, uint32_t next, const struct spa_pod *param,
|
|
|
|
|
|
WpAudioStream *self)
|
2019-08-13 14:18:34 -04:00
|
|
|
|
{
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-08-29 21:21:33 +03:00
|
|
|
|
static void
|
|
|
|
|
|
on_node_proxy_augmented (WpProxy * proxy, GAsyncResult * res,
|
|
|
|
|
|
WpAudioStream * self)
|
|
|
|
|
|
{
|
|
|
|
|
|
g_autoptr (GError) error = 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);
|
|
|
|
|
|
}
|
2019-08-13 14:18:34 -04:00
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
|
wp_audio_stream_finalize (GObject * object)
|
|
|
|
|
|
{
|
|
|
|
|
|
WpAudioStreamPrivate *priv =
|
|
|
|
|
|
wp_audio_stream_get_instance_private (WP_AUDIO_STREAM (object));
|
|
|
|
|
|
|
|
|
|
|
|
/* Clear the endpoint weak reference */
|
|
|
|
|
|
g_weak_ref_clear (&priv->endpoint);
|
|
|
|
|
|
|
|
|
|
|
|
/* Clear the name */
|
2019-08-29 21:21:33 +03:00
|
|
|
|
g_clear_pointer (&priv->name, g_free);
|
2019-08-13 14:18:34 -04:00
|
|
|
|
|
|
|
|
|
|
/* Clear the port proxies */
|
2019-08-29 21:21:33 +03:00
|
|
|
|
g_clear_pointer (&priv->port_proxies, g_ptr_array_unref);
|
|
|
|
|
|
|
|
|
|
|
|
g_clear_object (&priv->init_task);
|
2019-08-13 14:18:34 -04:00
|
|
|
|
|
|
|
|
|
|
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;
|
2019-08-29 21:21:33 +03:00
|
|
|
|
case PROP_PROXY_NODE:
|
|
|
|
|
|
priv->proxy = g_value_dup_object (value);
|
|
|
|
|
|
break;
|
2019-08-13 14:18:34 -04:00
|
|
|
|
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;
|
2019-08-29 21:21:33 +03:00
|
|
|
|
case PROP_PROXY_NODE:
|
|
|
|
|
|
g_value_set_object (value, priv->proxy);
|
|
|
|
|
|
break;
|
2019-08-13 14:18:34 -04:00
|
|
|
|
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);
|
2019-09-07 17:55:46 +03:00
|
|
|
|
g_autoptr (WpCore) core = wp_audio_stream_get_core (self);
|
2019-08-13 14:18:34 -04:00
|
|
|
|
GVariantDict d;
|
|
|
|
|
|
|
2019-08-29 21:21:33 +03:00
|
|
|
|
g_debug ("WpEndpoint:%p init stream %s (%s:%p)", ep, priv->name,
|
|
|
|
|
|
G_OBJECT_TYPE_NAME (self), self);
|
2019-08-13 14:18:34 -04:00
|
|
|
|
|
2019-08-29 21:21:33 +03:00
|
|
|
|
priv->init_task = g_task_new (initable, cancellable, callback, data);
|
2019-08-13 14:18:34 -04:00
|
|
|
|
|
|
|
|
|
|
/* 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);
|
2019-08-29 21:21:33 +03:00
|
|
|
|
wp_proxy_augment (WP_PROXY (priv->proxy),
|
|
|
|
|
|
WP_PROXY_FEATURE_PW_PROXY | WP_PROXY_FEATURE_INFO, NULL,
|
|
|
|
|
|
(GAsyncReadyCallback) on_node_proxy_augmented, self);
|
2019-08-13 14:18:34 -04:00
|
|
|
|
|
|
|
|
|
|
/* Register a port_added callback */
|
2019-09-07 17:55:46 +03:00
|
|
|
|
g_signal_connect_object(core, "remote-global-added::port",
|
|
|
|
|
|
(GCallback) on_audio_stream_port_added, self, 0);
|
2019-08-13 14:18:34 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
2019-08-29 21:21:33 +03:00
|
|
|
|
priv->port_proxies = g_ptr_array_new_full(4, (GDestroyNotify)g_object_unref);
|
2019-08-13 14:18:34 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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));
|
2019-08-29 21:21:33 +03:00
|
|
|
|
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));
|
2019-08-13 14:18:34 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-08-29 21:21:33 +03:00
|
|
|
|
WpProxyNode *
|
|
|
|
|
|
wp_audio_stream_get_proxy_node (WpAudioStream * self)
|
2019-08-13 14:18:34 -04:00
|
|
|
|
{
|
2019-08-29 21:21:33 +03:00
|
|
|
|
WpAudioStreamPrivate *priv = wp_audio_stream_get_instance_private (self);
|
2019-08-13 14:18:34 -04:00
|
|
|
|
|
2019-08-29 21:21:33 +03:00
|
|
|
|
return priv->proxy;
|
|
|
|
|
|
}
|
2019-08-13 14:18:34 -04:00
|
|
|
|
|
2019-08-29 21:21:33 +03:00
|
|
|
|
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);
|
2019-08-13 14:18:34 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
|
port_proxies_foreach_func(gpointer data, gpointer user_data)
|
|
|
|
|
|
{
|
|
|
|
|
|
GVariantBuilder *b = user_data;
|
|
|
|
|
|
g_variant_builder_add (b, "t", data);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
gboolean
|
|
|
|
|
|
wp_audio_stream_prepare_link (WpAudioStream * self, GVariant ** properties,
|
|
|
|
|
|
GError ** error)
|
|
|
|
|
|
{
|
|
|
|
|
|
WpAudioStreamPrivate *priv = wp_audio_stream_get_instance_private (self);
|
|
|
|
|
|
const struct pw_node_info *info = NULL;
|
|
|
|
|
|
GVariantBuilder b, *b_ports;
|
|
|
|
|
|
GVariant *v_ports;
|
|
|
|
|
|
|
|
|
|
|
|
/* Get the proxy node id */
|
2019-08-29 21:21:33 +03:00
|
|
|
|
info = wp_proxy_node_get_info (priv->proxy);
|
2019-08-13 14:18:34 -04:00
|
|
|
|
g_return_val_if_fail (info, FALSE);
|
|
|
|
|
|
|
|
|
|
|
|
/* Create a variant array with all the ports */
|
|
|
|
|
|
b_ports = g_variant_builder_new (G_VARIANT_TYPE ("at"));
|
|
|
|
|
|
g_ptr_array_foreach(priv->port_proxies, port_proxies_foreach_func, b_ports);
|
|
|
|
|
|
v_ports = g_variant_builder_end (b_ports);
|
|
|
|
|
|
|
|
|
|
|
|
/* Set the properties */
|
|
|
|
|
|
g_variant_builder_init (&b, G_VARIANT_TYPE_VARDICT);
|
|
|
|
|
|
g_variant_builder_add (&b, "{sv}", "node-id",
|
|
|
|
|
|
g_variant_new_uint32 (info->id));
|
|
|
|
|
|
g_variant_builder_add (&b, "{sv}", "ports", v_ports);
|
|
|
|
|
|
*properties = g_variant_builder_end (&b);
|
|
|
|
|
|
|
|
|
|
|
|
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);
|
2019-08-29 21:21:33 +03:00
|
|
|
|
wp_proxy_node_set_param (priv->proxy,
|
2019-08-13 14:18:34 -04:00
|
|
|
|
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);
|
2019-08-29 21:21:33 +03:00
|
|
|
|
wp_proxy_node_set_param (priv->proxy,
|
2019-08-13 14:18:34 -04:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
2019-08-29 21:21:33 +03:00
|
|
|
|
|
2019-09-07 17:55:46 +03:00
|
|
|
|
WpCore *
|
|
|
|
|
|
wp_audio_stream_get_core (WpAudioStream * self)
|
2019-08-29 21:21:33 +03:00
|
|
|
|
{
|
|
|
|
|
|
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);
|
2019-09-07 17:55:46 +03:00
|
|
|
|
return g_steal_pointer (&core);
|
2019-08-29 21:21:33 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
wp_proxy_sync (WP_PROXY (priv->proxy), NULL,
|
|
|
|
|
|
(GAsyncReadyCallback) on_port_config_done, self);
|
|
|
|
|
|
}
|