wireplumber/modules/module-pipewire/audio-softdsp-endpoint/stream.c

522 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 */
WpProxyNode *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_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->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_FEATURE_PW_PROXY | WP_PROXY_FEATURE_INFO, NULL,
(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 ("proxy-node", "proxy-node",
"The node proxy of the stream", WP_TYPE_PROXY_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;
}
WpProxyNode *
wp_audio_stream_get_proxy_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;
}