Files
wireplumber/modules/module-pipewire/audio-softdsp-endpoint/stream.c
George Kiagiadakis ca8d100b3f meson: temporarily add check to conditionally enable the fade functionality
and in case it's missing, also #define some things to make compilation work,
since this is only available in a pipewire branch right now
2020-02-28 13:25:35 +02:00

834 lines
25 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 <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;
GTask *init_task;
/* Props */
GWeakRef endpoint;
guint id;
gchar *name;
enum pw_direction direction;
/* Stream Proxy */
WpNode *proxy;
WpNode *audio_fade_source;
WpPort *proxy_control_port;
WpPort *audio_fade_source_port;
WpLink *control_link;
WpObjectManager *ports_om;
WpObjectManager *audio_fade_source_ports_om;
GVariantBuilder port_vb;
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,
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
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);
}
}
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);
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);
}
}
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 = wp_audio_stream_get_core (self);
g_autoptr (GError) error = NULL;
GVariantBuilder b;
const struct pw_node_info *info = NULL;
g_autofree gchar *node_id = NULL;
wp_proxy_augment_finish (proxy, res, &error);
if (error) {
g_warning ("WpAudioStream:%p Node proxy failed to augment: %s", self,
error->message);
wp_audio_stream_init_task_finish (self, g_steal_pointer (&error));
return;
}
g_signal_connect_object (proxy, "param",
(GCallback) audio_stream_event_param, self, 0);
wp_proxy_subscribe_params (proxy, 1, SPA_PARAM_Props);
priv->ports_om = wp_object_manager_new ();
/* Get the node id */
info = wp_proxy_get_info (proxy);
node_id = g_strdup_printf ("%u", info->id);
/* set a constraint: the port's "node.id" must match
the stream's underlying node id */
g_variant_builder_init (&b, G_VARIANT_TYPE ("aa{sv}"));
g_variant_builder_open (&b, G_VARIANT_TYPE_VARDICT);
g_variant_builder_add (&b, "{sv}", "type",
g_variant_new_int32 (WP_OBJECT_MANAGER_CONSTRAINT_PW_GLOBAL_PROPERTY));
g_variant_builder_add (&b, "{sv}", "name",
g_variant_new_string (PW_KEY_NODE_ID));
g_variant_builder_add (&b, "{sv}", "value",
g_variant_new_string (node_id));
g_variant_builder_close (&b);
/* declare interest on ports with this constraint */
wp_object_manager_add_interest (priv->ports_om, WP_TYPE_PORT,
g_variant_builder_end (&b),
WP_PROXY_FEATURE_PW_PROXY | WP_PROXY_FEATURE_INFO);
g_signal_connect (priv->ports_om, "objects-changed",
(GCallback) on_ports_changed, self);
g_signal_connect (priv->ports_om, "object-added",
(GCallback) on_ports_added, self);
/* install the object manager */
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));
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);
g_clear_object (&priv->proxy);
g_clear_object (&priv->audio_fade_source_ports_om);
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_autoptr (WpProperties) props = NULL;
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);
g_signal_connect_object (priv->proxy, "notify::info",
(GCallback) wp_audio_stream_event_info, self, 0);
wp_proxy_augment (WP_PROXY (priv->proxy),
WP_PROXY_FEATURES_STANDARD, cancellable,
(GAsyncReadyCallback) on_node_proxy_augmented, self);
}
static gboolean
wp_audio_stream_init_finish (GAsyncInitable *initable, GAsyncResult *result,
GError **error)
{
g_return_val_if_fail (g_task_is_valid (result, initable), FALSE);
return g_task_propagate_boolean (G_TASK (result), error);
}
static void
wp_audio_stream_async_initable_init (gpointer iface, gpointer iface_data)
{
GAsyncInitableIface *ai_iface = iface;
ai_iface->init_async = wp_audio_stream_init_async;
ai_iface->init_finish = wp_audio_stream_init_finish;
}
static void
wp_audio_stream_init (WpAudioStream * self)
{
WpAudioStreamPrivate *priv = wp_audio_stream_get_instance_private (self);
/* Controls */
priv->volume = 1.0;
priv->mute = FALSE;
}
static void
wp_audio_stream_class_init (WpAudioStreamClass * klass)
{
GObjectClass *object_class = (GObjectClass *) klass;
object_class->finalize = wp_audio_stream_finalize;
object_class->set_property = wp_audio_stream_set_property;
object_class->get_property = wp_audio_stream_get_property;
/* Install the properties */
g_object_class_install_property (object_class, PROP_ENDPOINT,
g_param_spec_object ("endpoint", "endpoint",
"The endpoint this audio stream belongs to", WP_TYPE_BASE_ENDPOINT,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_ID,
g_param_spec_uint ("id", "id", "The Id of the audio stream", 0, G_MAXUINT, 0,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_NAME,
g_param_spec_string ("name", "name", "The name of the audio stream", NULL,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_DIRECTION,
g_param_spec_uint ("direction", "direction",
"The direction of the audio stream", 0, 1, 0,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_PROXY_NODE,
g_param_spec_object ("node", "node",
"The node proxy of the stream", WP_TYPE_NODE,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
signals[SIGNAL_CONTROL_CHANGED] = g_signal_new (
"control-changed", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_UINT);
}
WpAudioStream *
wp_audio_stream_new_finish (GObject *initable, GAsyncResult *res,
GError **error)
{
GAsyncInitable *ai = G_ASYNC_INITABLE(initable);
return WP_AUDIO_STREAM (g_async_initable_new_finish(ai, res, error));
}
const char *
wp_audio_stream_get_name (WpAudioStream * self)
{
WpAudioStreamPrivate *priv = wp_audio_stream_get_instance_private (self);
return priv->name;
}
enum pw_direction
wp_audio_stream_get_direction (WpAudioStream * self)
{
WpAudioStreamPrivate *priv = wp_audio_stream_get_instance_private (self);
return priv->direction;
}
WpNode *
wp_audio_stream_get_node (WpAudioStream * self)
{
WpAudioStreamPrivate *priv = wp_audio_stream_get_instance_private (self);
return priv->proxy;
}
const struct pw_node_info *
wp_audio_stream_get_info (WpAudioStream * self)
{
WpAudioStreamPrivate *priv = wp_audio_stream_get_instance_private (self);
return wp_proxy_get_info (WP_PROXY (priv->proxy));
}
static void
port_proxies_foreach_func(gpointer data, gpointer user_data)
{
WpProxy *port = WP_PROXY (data);
WpAudioStream *self = WP_AUDIO_STREAM (user_data);
WpAudioStreamPrivate *priv = wp_audio_stream_get_instance_private (self);
const struct pw_node_info *node_info;
const struct pw_port_info *port_info;
g_autoptr (WpProperties) props = NULL;
const gchar *channel;
uint32_t channel_n = SPA_AUDIO_CHANNEL_UNKNOWN;
node_info = wp_proxy_get_info (WP_PROXY (priv->proxy));
g_return_if_fail (node_info);
port_info = wp_proxy_get_info (port);
g_return_if_fail (port_info);
props = wp_proxy_get_properties (port);
/* 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);
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)));
}
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)
{
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;
}