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

834 lines
25 KiB
C
Raw Normal View History

/* 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 <spa/utils/names.h>
#include "stream.h"
#if !defined(HAVE_AUDIOFADE)
# define SPA_PROP_audioFadeDuration 0x30001
# define SPA_PROP_audioFadeStep 0x30002
# define SPA_PROP_audioFadeDirection 0x30003
# define SPA_PROP_audioFadeType 0x30004
# define SPA_NAME_CONTROL_AUDIO_FADE_SOURCE "control.audio.fade.source"
#endif
typedef struct _WpAudioStreamPrivate WpAudioStreamPrivate;
struct _WpAudioStreamPrivate
{
GObject parent;
2019-08-29 21:21:33 +03:00
GTask *init_task;
/* Props */
GWeakRef endpoint;
guint id;
gchar *name;
enum pw_direction direction;
2019-08-29 21:21:33 +03:00
/* Stream Proxy */
WpNode *proxy;
WpNode *audio_fade_source;
WpPort *proxy_control_port;
WpPort *audio_fade_source_port;
WpLink *control_link;
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
WpObjectManager *ports_om;
WpObjectManager *audio_fade_source_ports_om;
GVariantBuilder port_vb;
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
gboolean port_config_done;
gboolean port_control_pending;
/* Stream Controls */
gfloat volume;
gboolean mute;
/* Fade */
GTask *fade_task;
GSource *fade_source;
};
enum {
PROP_0,
PROP_ENDPOINT,
PROP_ID,
PROP_NAME,
PROP_DIRECTION,
2019-08-29 21:21:33 +03:00
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
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)
{
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
wp_audio_stream_sync_done (WpCore *core, GAsyncResult *res, gpointer data)
{
WpAudioStream *self = WP_AUDIO_STREAM (data);
WpAudioStreamPrivate *priv = wp_audio_stream_get_instance_private (self);
/* finish stream creation when ports are done */
if (priv->port_config_done && !priv->port_control_pending)
wp_audio_stream_init_task_finish (self, NULL);
}
static void
wp_audio_stream_link_control_ports (WpAudioStream *self)
{
WpAudioStreamPrivate *priv = wp_audio_stream_get_instance_private (self);
g_autoptr (WpCore) core = NULL;
g_autoptr (WpProperties) props = NULL;
guint32 in_node_id, out_node_id, in_port_id, out_port_id;
/* just return if the link was already created */
if (priv->control_link)
return;
g_return_if_fail (priv->proxy);
g_return_if_fail (priv->audio_fade_source);
g_return_if_fail (priv->proxy_control_port);
g_return_if_fail (priv->audio_fade_source_port);
/* get the info */
in_node_id = wp_proxy_get_bound_id (WP_PROXY (priv->proxy));
out_node_id = wp_proxy_get_bound_id (WP_PROXY (priv->audio_fade_source));
in_port_id = wp_proxy_get_bound_id (WP_PROXY (priv->proxy_control_port));
out_port_id = wp_proxy_get_bound_id (WP_PROXY (priv->audio_fade_source_port));
/* create the properties */
props = wp_properties_new_empty ();
wp_properties_setf(props, PW_KEY_LINK_OUTPUT_NODE, "%u", out_node_id);
wp_properties_setf(props, PW_KEY_LINK_OUTPUT_PORT, "%u", out_port_id);
wp_properties_setf(props, PW_KEY_LINK_INPUT_NODE, "%u", in_node_id);
wp_properties_setf(props, PW_KEY_LINK_INPUT_PORT, "%u", in_port_id);
/* create the link */
g_debug ("%s:%p linking stream control ports: (%d) %d -> (%d) %d\n",
G_OBJECT_TYPE_NAME (self), self, out_node_id, out_port_id, in_node_id,
in_port_id);
core = wp_audio_stream_get_core (self);
priv->control_link = wp_link_new_from_factory (core, "link-factory",
g_steal_pointer (&props));
g_return_if_fail (priv->control_link);
}
static void
wp_audio_stream_event_info (WpProxy * proxy, GParamSpec *spec,
WpAudioStream * self)
{
WpAudioStreamPrivate *priv = wp_audio_stream_get_instance_private (self);
const struct pw_node_info *info = wp_proxy_get_info (proxy);
/* handle the different states */
switch (info->state) {
case PW_NODE_STATE_IDLE:
g_clear_object (&priv->control_link);
break;
case PW_NODE_STATE_RUNNING:
if (priv->audio_fade_source)
wp_audio_stream_link_control_ports (self);
break;
case PW_NODE_STATE_SUSPENDED:
break;
default:
break;
}
}
static void
on_audio_fade_source_ports_added (WpObjectManager *om, WpPort *port,
WpAudioStream *self)
{
WpAudioStreamPrivate *priv = wp_audio_stream_get_instance_private (self);
g_autoptr (WpCore) core = wp_audio_stream_get_core (self);
const struct pw_port_info *info = wp_proxy_get_info (WP_PROXY (port));
g_return_if_fail (info);
g_return_if_fail (info->direction == PW_DIRECTION_OUTPUT);
if (!priv->audio_fade_source_port) {
priv->audio_fade_source_port = g_object_ref (port);
g_signal_handlers_disconnect_by_func (priv->audio_fade_source_ports_om,
on_audio_fade_source_ports_added, self);
/* set the pending flag to false and sync */
priv->port_control_pending = FALSE;
wp_core_sync (core, NULL, (GAsyncReadyCallback)wp_audio_stream_sync_done,
self);
}
}
static void
on_audio_fade_source_augmented (WpProxy * proxy, GAsyncResult * res,
WpAudioStream * self)
{
WpAudioStreamPrivate *priv = wp_audio_stream_get_instance_private (self);
g_autoptr (WpCore) core = wp_audio_stream_get_core (self);
g_autoptr (GError) error = NULL;
g_autofree gchar *node_id = NULL;
GVariantBuilder b;
/* Get the audmented node */
wp_proxy_augment_finish (proxy, res, &error);
if (error) {
g_warning ("WpAudioStream:%p audio-fade-source failed to augment: %s", self,
error->message);
wp_audio_stream_init_task_finish (self, g_steal_pointer (&error));
return;
}
/* Get the node id */
node_id = g_strdup_printf ("%u", wp_proxy_get_bound_id (proxy));
/* Create the audio fade source ports object manager */
priv->audio_fade_source_ports_om = wp_object_manager_new ();
/* 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->audio_fade_source_ports_om,
WP_TYPE_PORT,
g_variant_builder_end (&b),
WP_PROXY_FEATURES_STANDARD);
/* Add a handler to be triggered when ports are added */
g_signal_connect (priv->audio_fade_source_ports_om, "object-added",
(GCallback) on_audio_fade_source_ports_added, self);
/* install the object manager */
wp_core_install_object_manager (core, priv->audio_fade_source_ports_om);
}
static gboolean
is_stream_control_port (WpPort *port)
{
const struct pw_port_info *info = wp_proxy_get_info (WP_PROXY (port));
const char *port_name;
g_return_val_if_fail (info, FALSE);
if (info->direction == PW_DIRECTION_OUTPUT)
return FALSE;
port_name = spa_dict_lookup (info->props, PW_KEY_PORT_NAME);
if (g_strcmp0 (port_name, "control") != 0)
return FALSE;
return TRUE;
}
static void
on_ports_added (WpObjectManager *om, WpPort *port, WpAudioStream *self)
{
WpAudioStreamPrivate *priv = wp_audio_stream_get_instance_private (self);
g_autoptr (WpCore) core = wp_audio_stream_get_core (self);
g_autoptr (WpProperties) props = NULL;
/* only handle control ports */
if (!is_stream_control_port (port))
return;
if (!priv->proxy_control_port) {
priv->proxy_control_port = g_object_ref (port);
g_signal_handlers_disconnect_by_func (priv->ports_om, on_ports_added, self);
/* set the pending flag so that the stream does not finish yet */
priv->port_control_pending = TRUE;
/* create the audio-fade-source node */
props = wp_properties_new_empty ();
wp_properties_setf (props, PW_KEY_NODE_NAME, "%s",
SPA_NAME_CONTROL_AUDIO_FADE_SOURCE);
wp_properties_set (props, SPA_KEY_LIBRARY_NAME, "control/libspa-control");
wp_properties_set (props, SPA_KEY_FACTORY_NAME,
SPA_NAME_CONTROL_AUDIO_FADE_SOURCE);
priv->audio_fade_source = wp_node_new_from_factory (core,
"spa-node-factory", g_steal_pointer (&props));
g_return_if_fail (priv->audio_fade_source);
wp_proxy_augment (WP_PROXY (priv->audio_fade_source),
WP_PROXY_FEATURES_STANDARD, NULL,
(GAsyncReadyCallback) on_audio_fade_source_augmented, self);
}
}
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
static void
on_ports_changed (WpObjectManager *om, WpAudioStream *self)
{
WpAudioStreamPrivate *priv = wp_audio_stream_get_instance_private (self);
g_autoptr (WpCore) core = wp_audio_stream_get_core (self);
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
if (priv->port_config_done) {
g_debug ("%s:%p port config done", G_OBJECT_TYPE_NAME (self), self);
g_signal_handlers_disconnect_by_func (priv->ports_om, on_ports_changed,
self);
/* sync before finishing */
wp_core_sync (core, NULL, (GAsyncReadyCallback)wp_audio_stream_sync_done,
self);
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
}
}
2019-08-29 21:21:33 +03:00
static void
on_node_proxy_augmented (WpProxy * proxy, GAsyncResult * res,
WpAudioStream * self)
{
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
WpAudioStreamPrivate *priv = wp_audio_stream_get_instance_private (self);
g_autoptr (WpCore) core = wp_audio_stream_get_core (self);
2019-08-29 21:21:33 +03:00
g_autoptr (GError) error = NULL;
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
GVariantBuilder b;
const struct pw_node_info *info = NULL;
g_autofree gchar *node_id = NULL;
2019-08-29 21:21:33 +03:00
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);
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
priv->ports_om = wp_object_manager_new ();
/* Get the node id */
info = wp_proxy_get_info (proxy);
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
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,
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
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);
g_signal_connect (priv->ports_om, "object-added",
(GCallback) on_ports_added, self);
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
/* install the object manager */
wp_core_install_object_manager (core, priv->ports_om);
2019-08-29 21:21:33 +03:00
}
static void
wp_audio_stream_finalize (GObject * object)
{
WpAudioStreamPrivate *priv =
wp_audio_stream_get_instance_private (WP_AUDIO_STREAM (object));
if (priv->fade_source)
g_source_destroy (priv->fade_source);
g_clear_pointer (&priv->fade_source, g_source_unref);
g_clear_object (&priv->fade_task);
g_clear_object (&priv->control_link);
g_clear_object (&priv->audio_fade_source_port);
g_clear_object (&priv->proxy_control_port);
g_clear_object (&priv->audio_fade_source);
2019-12-10 19:34:30 -05:00
g_clear_object (&priv->proxy);
g_clear_object (&priv->audio_fade_source_ports_om);
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
g_clear_object (&priv->ports_om);
/* 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-29 21:21:33 +03:00
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;
2019-08-29 21:21:33 +03:00
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;
2019-08-29 21:21:33 +03:00
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_autoptr (WpProperties) props = NULL;
g_debug ("WpBaseEndpoint:%p init stream %s (%s:%p)", ep, priv->name,
2019-08-29 21:21:33 +03:00
G_OBJECT_TYPE_NAME (self), self);
2019-08-29 21:21:33 +03:00
priv->init_task = g_task_new (initable, cancellable, callback, data);
g_return_if_fail (priv->proxy);
g_signal_connect_object (priv->proxy, "notify::info",
(GCallback) wp_audio_stream_event_info, self, 0);
2019-08-29 21:21:33 +03:00
wp_proxy_augment (WP_PROXY (priv->proxy),
WP_PROXY_FEATURES_STANDARD, cancellable,
2019-08-29 21:21:33 +03:00
(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));
2019-08-29 21:21:33 +03:00
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,
2019-08-29 21:21:33 +03:00
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)
{
2019-08-29 21:21:33 +03:00
WpAudioStreamPrivate *priv = wp_audio_stream_get_instance_private (self);
2019-08-29 21:21:33 +03:00
return priv->proxy;
}
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_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);
/* skip control ports */
if (is_stream_control_port (WP_PORT (port)))
return;
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);
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
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)"));
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
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)));
}
2019-08-29 21:21:33 +03:00
static gboolean
fade_timeout_callback (gpointer user_data)
{
WpAudioStream *self = user_data;
WpAudioStreamPrivate *priv = wp_audio_stream_get_instance_private (self);
g_return_val_if_fail (priv->fade_task, G_SOURCE_REMOVE);
g_task_return_boolean (priv->fade_task, TRUE);
g_clear_object (&priv->fade_task);
return G_SOURCE_REMOVE;
}
void
wp_audio_stream_begin_fade (WpAudioStream * self, guint duration,
gfloat step, guint direction, guint type, GCancellable * cancellable,
GAsyncReadyCallback callback, gpointer user_data)
{
WpAudioStreamPrivate *priv = wp_audio_stream_get_instance_private (self);
g_autoptr (WpCore) core = wp_audio_stream_get_core (self);
GTask *task = NULL;
struct spa_pod *props;
uint8_t buffer[1024];
struct spa_pod_builder b = { 0 };
/* Create the fade callback */
task = g_task_new (self, cancellable, callback, user_data);
/* Just trigger the callback if the stream does not have a control node */
if (!priv->audio_fade_source) {
g_task_return_boolean (task, TRUE);
g_clear_object (&task);
return;
}
/* Destroy the pending source */
if (priv->fade_source)
g_source_destroy (priv->fade_source);
g_clear_pointer (&priv->fade_source, g_source_unref);
/* Finish the pending task */
if (priv->fade_task) {
g_task_return_boolean (priv->fade_task, FALSE);
g_clear_object (&priv->fade_task);
}
/* Set the new task */
priv->fade_task = task;
/* Send the fade */
spa_pod_builder_init(&b, buffer, sizeof(buffer));
props = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Props, 0,
SPA_PROP_audioFadeDuration, SPA_POD_Int(duration),
SPA_PROP_audioFadeStep, SPA_POD_Double(step),
SPA_PROP_audioFadeDirection, SPA_POD_Id(direction),
SPA_PROP_audioFadeType, SPA_POD_Id(type));
wp_proxy_set_param (WP_PROXY (priv->audio_fade_source), SPA_PARAM_Props, 0,
props);
/* TODO: for now we always trigger the callback after 1 second, which should
* be enough for the fade to finish. However, we should trigger the callback
* when channelmix finishes processing the control sequence, via node event
* param for example */
wp_core_timeout_add (core, &priv->fade_source, 1000, fade_timeout_callback,
g_object_ref (self), g_object_unref);
}
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 (WpBaseEndpoint) ep = NULL;
2019-08-29 21:21:33 +03:00
g_autoptr (WpCore) core = NULL;
ep = g_weak_ref_get (&priv->endpoint);
core = wp_base_endpoint_get_core (ep);
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_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);
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
priv->port_config_done = TRUE;
2019-08-29 21:21:33 +03:00
}