modules: remove pipewire module and move algorithms to module-si-adapter

This commit is contained in:
Julian Bouzas
2020-05-11 12:57:55 -04:00
parent 6317599477
commit 4b7bc92e4b
17 changed files with 4 additions and 2499 deletions

View File

@@ -45,24 +45,6 @@ shared_library(
dependencies : [wp_dep, pipewire_dep],
)
shared_library(
'wireplumber-module-pipewire',
[
'module-pipewire.c',
'module-pipewire/algorithms.c',
'module-pipewire/simple-endpoint-link.c',
'module-pipewire/video-endpoint.c',
'module-pipewire/audio-softdsp-endpoint.c',
'module-pipewire/audio-softdsp-endpoint/stream.c',
'module-pipewire/audio-softdsp-endpoint/adapter.c',
'module-pipewire/audio-softdsp-endpoint/convert.c',
],
c_args : [common_c_args, '-DG_LOG_DOMAIN="m-pipewire"'],
install : true,
install_dir : wireplumber_module_dir,
dependencies : [gio_dep, wp_dep, pipewire_dep],
)
shared_library(
'wireplumber-module-config-static-nodes',
[
@@ -107,7 +89,7 @@ shared_library(
'wireplumber-module-si-adapter',
[
'module-si-adapter.c',
'module-pipewire/algorithms.c',
'module-si-adapter/algorithms.c',
],
c_args : [common_c_args, '-DG_LOG_DOMAIN="m-si-adapter"'],
install : true,
@@ -119,7 +101,6 @@ shared_library(
'wireplumber-module-si-convert',
[
'module-si-convert.c',
'module-pipewire/algorithms.c',
],
c_args : [common_c_args, '-DG_LOG_DOMAIN="m-si-convert"'],
install : true,

View File

@@ -1,36 +0,0 @@
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
/**
* module-pipewire provides basic integration between wireplumber and pipewire.
* It provides the pipewire core and remote, connects to pipewire and provides
* the most primitive implementations of WpBaseEndpoint and WpBaseEndpointLink
*/
#include <wp/wp.h>
#include <pipewire/pipewire.h>
void simple_endpoint_link_factory (WpFactory * factory, GType type,
GVariant * properties, GAsyncReadyCallback ready, gpointer user_data);
void
audio_softdsp_endpoint_factory (WpFactory * factory, GType type,
GVariant * properties, GAsyncReadyCallback ready, gpointer user_data);
void
wp_video_endpoint_factory (WpFactory * factory, GType type,
GVariant * properties, GAsyncReadyCallback ready, gpointer user_data);
WP_PLUGIN_EXPORT void
wireplumber__module_init (WpModule * module, WpCore * core, GVariant * args)
{
/* Register simple-endpoint-link and audio-softdsp-endpoint */
wp_factory_new (core, "pipewire-simple-endpoint-link",
simple_endpoint_link_factory);
wp_factory_new (core, "pw-audio-softdsp-endpoint",
audio_softdsp_endpoint_factory);
wp_factory_new (core, "video-endpoint", wp_video_endpoint_factory);
}

View File

@@ -1,544 +0,0 @@
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
/**
* module-pw-audio-softdsp-endpoint provides a WpBaseEndpoint implementation
* that wraps an audio device node in pipewire and plugs a DSP node, as well
* as optional merger+volume nodes that are used as entry points for the
* various streams that this endpoint may have
*/
#include <wp/wp.h>
#include <pipewire/pipewire.h>
#include <pipewire/extensions/session-manager.h>
#include <spa/param/audio/format-utils.h>
#include <spa/pod/builder.h>
#include <spa/param/props.h>
#include <spa/utils/keys.h>
#include "audio-softdsp-endpoint/stream.h"
#include "audio-softdsp-endpoint/adapter.h"
#include "audio-softdsp-endpoint/convert.h"
#define MIN_QUANTUM_SIZE 64
#define MAX_QUANTUM_SIZE 1024
#define CONTROL_SELECTED 0
struct _WpPwAudioSoftdspEndpoint
{
WpBaseEndpoint parent;
/* Properties */
WpNode *node;
GVariant *streams;
char *role;
guint stream_count;
gboolean selected;
/* The task to signal the endpoint is initialized */
GTask *init_task;
/* Audio Streams */
WpAudioStream *adapter;
GPtrArray *converters;
// WpImplEndpoint *impl_ep;
};
enum {
PROP_0,
PROP_PROXY_NODE,
PROP_STREAMS,
PROP_ROLE,
};
static GAsyncInitableIface *async_initable_parent_interface = NULL;
static void endpoint_async_initable_init (gpointer iface,
gpointer iface_data);
G_DECLARE_FINAL_TYPE (WpPwAudioSoftdspEndpoint, endpoint,
WP_PW, AUDIO_SOFTDSP_ENDPOINT, WpBaseEndpoint)
G_DEFINE_TYPE_WITH_CODE (WpPwAudioSoftdspEndpoint, endpoint, WP_TYPE_BASE_ENDPOINT,
G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE,
endpoint_async_initable_init))
static WpProperties *
endpoint_get_properties (WpBaseEndpoint * ep)
{
WpPwAudioSoftdspEndpoint *self = WP_PW_AUDIO_SOFTDSP_ENDPOINT (ep);
return wp_proxy_get_properties (WP_PROXY (self->node));
}
static const char *
endpoint_get_role (WpBaseEndpoint *ep)
{
WpPwAudioSoftdspEndpoint *self = WP_PW_AUDIO_SOFTDSP_ENDPOINT (ep);
return self->role;
}
static guint32
endpoint_get_global_id (WpBaseEndpoint *ep)
{
return SPA_ID_INVALID; //wp_proxy_get_bound_id (WP_PROXY (self->impl_ep));
}
static gboolean
endpoint_prepare_link (WpBaseEndpoint * ep, guint32 stream_id,
WpBaseEndpointLink * link, GVariant ** properties, GError ** error)
{
WpPwAudioSoftdspEndpoint *self = WP_PW_AUDIO_SOFTDSP_ENDPOINT (ep);
WpAudioStream *stream = NULL;
/* Link with the adapter if stream id is none */
if (stream_id == WP_STREAM_ID_NONE)
return wp_audio_stream_prepare_link (self->adapter, properties, error);
/* Make sure the stream Id is valid */
g_return_val_if_fail(stream_id < self->converters->len, FALSE);
/* Make sure the stream is valid */
stream = g_ptr_array_index (self->converters, stream_id);
g_return_val_if_fail(stream, FALSE);
/* Prepare the link */
return wp_audio_stream_prepare_link (stream, properties, error);
}
static void
endpoint_begin_fade (WpBaseEndpoint * ep, guint32 stream_id, guint duration,
gfloat step, guint direction, guint type, GCancellable * cancellable,
GAsyncReadyCallback callback, gpointer data)
{
WpPwAudioSoftdspEndpoint *self = WP_PW_AUDIO_SOFTDSP_ENDPOINT (ep);
WpAudioStream *stream = NULL;
/* Fade the adapter if stream id is none */
if (stream_id == WP_STREAM_ID_NONE) {
wp_audio_stream_begin_fade (self->adapter, duration, step, direction,
type, cancellable, callback, data);
return;
}
/* Make sure the stream Id is valid */
g_return_if_fail(stream_id < self->converters->len);
/* Make sure the stream is valid */
stream = g_ptr_array_index (self->converters, stream_id);
g_return_if_fail(stream);
/* Begin fade */
wp_audio_stream_begin_fade (stream, duration, step, direction, type,
cancellable, callback, data);
}
#if 0
static void
on_exported_control_changed (WpEndpoint * ep, const gchar *id_name,
WpPwAudioSoftdspEndpoint *self)
{
g_autoptr (WpSpaPod) ctrl = NULL;
if (g_strcmp0 (id_name, "volume") == 0) {
gfloat vol;
ctrl = wp_endpoint_get_control (ep, id_name);
wp_spa_pod_get_float (ctrl, &vol);
wp_audio_stream_set_volume (self->adapter, vol);
} else if (g_strcmp0 (id_name, "mute") == 0) {
gboolean m;
ctrl = wp_endpoint_get_control (ep, id_name);
wp_spa_pod_get_boolean (ctrl, &m);
wp_audio_stream_set_mute (self->adapter, m);
}
}
static void
on_adapter_control_changed (WpAudioStream * s, const gchar *id_name,
WpPwAudioSoftdspEndpoint *self)
{
/* block to avoid recursion - WpEndpoint emits the "control-changed"
signal when we change the value here */
g_signal_handlers_block_by_func (self->impl_ep,
on_exported_control_changed, self);
if (g_strcmp0 (id_name, "volume") == 0) {
gfloat vol = wp_audio_stream_get_volume (s);
g_autoptr (WpSpaPod) vol_ctrl = wp_spa_pod_new_float (vol);
wp_endpoint_set_control (WP_ENDPOINT (self->impl_ep), id_name, vol_ctrl);
} else if (g_strcmp0 (id_name, "mute") == 0) {
gboolean m = wp_audio_stream_get_mute (s);
g_autoptr (WpSpaPod) m_ctrl = wp_spa_pod_new_boolean (m);
wp_endpoint_set_control (WP_ENDPOINT (self->impl_ep), id_name, m_ctrl);
}
g_signal_handlers_unblock_by_func (self->impl_ep,
on_exported_control_changed, self);
}
static void
on_endpoint_exported (GObject * impl_ep, GAsyncResult *res, gpointer data)
{
WpPwAudioSoftdspEndpoint *self = WP_PW_AUDIO_SOFTDSP_ENDPOINT (data);
GError *error = NULL;
g_return_if_fail (self->init_task);
/* Get the object */
wp_proxy_augment_finish (WP_PROXY (impl_ep), res, &error);
if (error) {
g_warning ("WpPwAudioSoftdspEndpoint:%p Aborting construction", self);
g_task_return_error (self->init_task, error);
g_clear_object (&self->init_task);
} else {
/* Finish the creation of the endpoint */
g_task_return_boolean (self->init_task, TRUE);
g_clear_object(&self->init_task);
}
}
static void
do_export (WpPwAudioSoftdspEndpoint *self)
{
g_autoptr (WpCore) core = wp_base_endpoint_get_core (WP_BASE_ENDPOINT (self));
g_autoptr (WpProperties) props = NULL;
g_autoptr (WpProperties) extra_props = NULL;
g_autoptr (WpSpaPod) ctrl = NULL;
g_return_if_fail (!self->impl_ep);
self->impl_ep = wp_impl_endpoint_new (core);
wp_impl_endpoint_register_control (self->impl_ep, "volume");
wp_impl_endpoint_register_control (self->impl_ep, "mute");
// wp_impl_endpoint_register_control (self->impl_ep, "channelVolumes");
props = wp_proxy_get_properties (WP_PROXY (self->node));
extra_props = wp_properties_new_empty ();
wp_properties_setf (extra_props, PW_KEY_NODE_ID, "%d",
wp_proxy_get_bound_id (WP_PROXY (self->node)));
wp_properties_set (extra_props, PW_KEY_ENDPOINT_CLIENT_ID,
wp_properties_get (props, PW_KEY_CLIENT_ID));
wp_properties_setf (extra_props, "endpoint.priority", "%d",
wp_base_endpoint_get_priority (WP_BASE_ENDPOINT (self)));
wp_impl_endpoint_update_properties (self->impl_ep, props);
wp_impl_endpoint_update_properties (self->impl_ep, extra_props);
wp_impl_endpoint_set_name (self->impl_ep,
wp_base_endpoint_get_name (WP_BASE_ENDPOINT (self)));
wp_impl_endpoint_set_media_class (self->impl_ep,
wp_base_endpoint_get_media_class (WP_BASE_ENDPOINT (self)));
wp_impl_endpoint_set_direction (self->impl_ep,
wp_base_endpoint_get_direction (WP_BASE_ENDPOINT (self)));
ctrl = wp_spa_pod_new_float (wp_audio_stream_get_volume (self->adapter));
wp_endpoint_set_control (WP_ENDPOINT (self->impl_ep), "volume", ctrl);
ctrl = wp_spa_pod_new_boolean (wp_audio_stream_get_mute (self->adapter));
wp_endpoint_set_control (WP_ENDPOINT (self->impl_ep), "mute", ctrl);
g_signal_connect_object (self->impl_ep, "control-changed",
(GCallback) on_exported_control_changed, self, 0);
g_signal_connect_object (self->adapter, "control-changed",
(GCallback) on_adapter_control_changed, self, 0);
wp_proxy_augment (WP_PROXY (self->impl_ep), WP_PROXY_FEATURE_BOUND,
NULL, on_endpoint_exported, self);
}
#endif
static void
on_audio_convert_created(GObject *initable, GAsyncResult *res, gpointer data)
{
WpPwAudioSoftdspEndpoint *self = data;
g_autoptr (GError) error = NULL;
WpAudioStream *convert = NULL;
guint stream_id = 0;
g_autofree gchar *name = NULL;
/* Get the audio convert */
convert = wp_audio_stream_new_finish (initable, res, &error);
if (error) {
g_warning ("WpPwAudioSoftdspEndpoint:%p Could not create convert: %s\n",
self, error->message);
return;
}
g_return_if_fail (convert);
/* Get the stream id */
g_object_get (convert, "id", &stream_id, "name", &name, NULL);
g_return_if_fail (stream_id >= 0);
/* Set the streams */
g_ptr_array_insert (self->converters, stream_id, convert);
g_debug ("%s:%p Created audio convert %u %s", G_OBJECT_TYPE_NAME (self), self,
stream_id, name);
/* Finish the endpoint creation when all the streams are created */
if (--self->stream_count == 0) {
g_task_return_boolean (self->init_task, TRUE);
g_clear_object(&self->init_task);
}
}
static void
on_audio_adapter_created(GObject *initable, GAsyncResult *res,
gpointer data)
{
WpPwAudioSoftdspEndpoint *self = data;
enum pw_direction direction = wp_base_endpoint_get_direction(WP_BASE_ENDPOINT(self));
g_autoptr (WpCore) core = wp_base_endpoint_get_core(WP_BASE_ENDPOINT(self));
g_autoptr (WpProperties) props = NULL;
g_autoptr (GError) error = NULL;
const struct spa_audio_info_raw *format;
g_autofree gchar *name = NULL;
GVariantDict d;
GVariantIter iter;
const gchar *stream;
guint priority;
int i;
/* Get the proxy adapter */
self->adapter = wp_audio_stream_new_finish (initable, res, &error);
if (error) {
g_warning ("WpPwAudioSoftdspEndpoint:%p Could not create adapter: %s\n",
self, error->message);
return;
}
g_return_if_fail (self->adapter);
props = wp_proxy_get_properties (WP_PROXY (self->node));
/* Set the role */
self->role = g_strdup (wp_properties_get (props, PW_KEY_MEDIA_ROLE));
/* Just finish if no streams need to be created */
if (!self->streams) {
g_task_return_boolean (self->init_task, TRUE);
g_clear_object(&self->init_task);
return;
}
/* Get the adapter format */
format = wp_audio_adapter_get_format (WP_AUDIO_ADAPTER (self->adapter));
g_return_if_fail (format);
/* Create the audio converters */
g_variant_iter_init (&iter, self->streams);
for (i = 0; g_variant_iter_next (&iter, "(&su)", &stream, &priority); i++) {
wp_audio_convert_new (WP_BASE_ENDPOINT(self), i, stream, direction,
self->adapter, format, on_audio_convert_created, self);
/* Register the stream */
g_variant_dict_init (&d, NULL);
g_variant_dict_insert (&d, "id", "u", i);
g_variant_dict_insert (&d, "name", "s", stream);
g_variant_dict_insert (&d, "priority", "u", priority);
wp_base_endpoint_register_stream (WP_BASE_ENDPOINT (self),
g_variant_dict_end (&d));
}
self->stream_count = i;
}
static void
endpoint_finalize (GObject * object)
{
WpPwAudioSoftdspEndpoint *self = WP_PW_AUDIO_SOFTDSP_ENDPOINT (object);
// g_clear_object (&self->impl_ep);
g_clear_pointer(&self->streams, g_variant_unref);
/* Destroy the proxy adapter */
g_clear_object(&self->adapter);
/* Destroy all the converters */
g_clear_pointer (&self->converters, g_ptr_array_unref);
/* Destroy the done task */
g_clear_object(&self->init_task);
g_clear_object(&self->node);
g_free (self->role);
G_OBJECT_CLASS (endpoint_parent_class)->finalize (object);
}
static void
endpoint_set_property (GObject * object, guint property_id,
const GValue * value, GParamSpec * pspec)
{
WpPwAudioSoftdspEndpoint *self = WP_PW_AUDIO_SOFTDSP_ENDPOINT (object);
switch (property_id) {
case PROP_PROXY_NODE:
self->node = g_value_dup_object (value);
break;
case PROP_STREAMS:
self->streams = g_value_dup_variant(value);
break;
case PROP_ROLE:
g_free (self->role);
self->role = g_value_dup_string (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
endpoint_get_property (GObject * object, guint property_id,
GValue * value, GParamSpec * pspec)
{
WpPwAudioSoftdspEndpoint *self = WP_PW_AUDIO_SOFTDSP_ENDPOINT (object);
switch (property_id) {
case PROP_PROXY_NODE:
g_value_set_object (value, self->node);
break;
case PROP_STREAMS:
g_value_set_variant (value, self->streams);
break;
case PROP_ROLE:
g_value_set_string (value, self->role);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
endpoint_init_async (GAsyncInitable *initable, int io_priority,
GCancellable *cancellable, GAsyncReadyCallback callback, gpointer data)
{
WpPwAudioSoftdspEndpoint *self = WP_PW_AUDIO_SOFTDSP_ENDPOINT (initable);
enum pw_direction direction = wp_base_endpoint_get_direction(WP_BASE_ENDPOINT(self));
g_autoptr (WpCore) core = wp_base_endpoint_get_core(WP_BASE_ENDPOINT(self));
GVariantDict d;
/* Create the async task */
self->init_task = g_task_new (initable, cancellable, callback, data);
/* Create the adapter proxy */
wp_audio_adapter_new (WP_BASE_ENDPOINT(self), WP_STREAM_ID_NONE, "master",
direction, self->node, FALSE, on_audio_adapter_created, self);
/* Register the selected control */
self->selected = FALSE;
g_variant_dict_init (&d, NULL);
g_variant_dict_insert (&d, "id", "u", CONTROL_SELECTED);
g_variant_dict_insert (&d, "name", "s", "selected");
g_variant_dict_insert (&d, "type", "s", "b");
g_variant_dict_insert (&d, "default-value", "b", self->selected);
wp_base_endpoint_register_control (WP_BASE_ENDPOINT (self), g_variant_dict_end (&d));
/* Call the parent interface */
async_initable_parent_interface->init_async (initable, io_priority, cancellable,
callback, data);
}
static void
endpoint_async_initable_init (gpointer iface, gpointer iface_data)
{
GAsyncInitableIface *ai_iface = iface;
/* Set the parent interface */
async_initable_parent_interface = g_type_interface_peek_parent (iface);
/* Only set the init_async */
ai_iface->init_async = endpoint_init_async;
}
static void
endpoint_init (WpPwAudioSoftdspEndpoint * self)
{
self->converters = g_ptr_array_new_with_free_func (g_object_unref);
}
static void
endpoint_class_init (WpPwAudioSoftdspEndpointClass * klass)
{
GObjectClass *object_class = (GObjectClass *) klass;
WpBaseEndpointClass *endpoint_class = (WpBaseEndpointClass *) klass;
object_class->finalize = endpoint_finalize;
object_class->set_property = endpoint_set_property;
object_class->get_property = endpoint_get_property;
endpoint_class->get_properties = endpoint_get_properties;
endpoint_class->get_role = endpoint_get_role;
endpoint_class->get_global_id = endpoint_get_global_id;
endpoint_class->prepare_link = endpoint_prepare_link;
endpoint_class->begin_fade = endpoint_begin_fade;
/* Instal the properties */
g_object_class_install_property (object_class, PROP_PROXY_NODE,
g_param_spec_object ("node", "node",
"The node this endpoint refers to", WP_TYPE_NODE,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_STREAMS,
g_param_spec_variant ("streams", "streams",
"The stream names for the streams to create",
G_VARIANT_TYPE ("a(su)"), NULL,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_ROLE,
g_param_spec_string ("role", "role", "The role of the wrapped node", NULL,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
}
void
audio_softdsp_endpoint_factory (WpFactory * factory, GType type, GVariant * properties,
GAsyncReadyCallback ready, gpointer user_data)
{
g_autoptr (WpCore) core = NULL;
const gchar *name, *media_class;
guint direction, priority;
guint64 node;
g_autoptr (GVariant) streams = NULL;
/* Make sure the type is correct */
g_return_if_fail(type == WP_TYPE_BASE_ENDPOINT);
/* Get the Core */
core = wp_factory_get_core(factory);
g_return_if_fail (core);
/* Get the properties */
if (!g_variant_lookup (properties, "name", "&s", &name))
return;
if (!g_variant_lookup (properties, "media-class", "&s", &media_class))
return;
if (!g_variant_lookup (properties, "direction", "u", &direction))
return;
if (!g_variant_lookup (properties, "priority", "u", &priority))
return;
if (!g_variant_lookup (properties, "node", "t", &node))
return;
streams = g_variant_lookup_value (properties, "streams",
G_VARIANT_TYPE ("a(su)"));
/* Create and return the softdsp endpoint object */
g_async_initable_new_async (
endpoint_get_type (), G_PRIORITY_DEFAULT, NULL, ready, user_data,
"core", core,
"name", name,
"media-class", media_class,
"direction", direction,
"priority", priority,
"node", (gpointer) node,
"streams", streams,
NULL);
}

View File

@@ -1,33 +0,0 @@
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author Julian Bouzas <julian.bouzas@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
#ifndef __WIREPLUMBER_AUDIO_ADAPTER_H__
#define __WIREPLUMBER_AUDIO_ADAPTER_H__
#include <gio/gio.h>
#include <wp/wp.h>
#include "stream.h"
G_BEGIN_DECLS
struct spa_audio_info_raw format;
#define WP_TYPE_AUDIO_ADAPTER (wp_audio_adapter_get_type ())
G_DECLARE_FINAL_TYPE (WpAudioAdapter, wp_audio_adapter, WP, AUDIO_ADAPTER,
WpAudioStream)
void wp_audio_adapter_new (WpBaseEndpoint *endpoint, guint stream_id,
const char *stream_name, enum pw_direction direction, WpNode *node,
gboolean convert, GAsyncReadyCallback callback, gpointer user_data);
struct spa_audio_info_raw *wp_audio_adapter_get_format (WpAudioAdapter *self);
G_END_DECLS
#endif

View File

@@ -1,337 +0,0 @@
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author Julian Bouzas <julian.bouzas@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
#include <pipewire/pipewire.h>
#include <spa/utils/names.h>
#include <spa/param/audio/format-utils.h>
#include <spa/pod/builder.h>
#include <spa/param/props.h>
#include "convert.h"
#include "../algorithms.h"
enum {
PROP_0,
PROP_TARGET,
PROP_FORMAT,
};
struct _WpAudioConvert
{
WpAudioStream parent;
/* Props */
WpAudioStream *target;
struct spa_audio_info_raw format;
/* Proxies */
GPtrArray *link_proxies;
};
static GAsyncInitableIface *wp_audio_convert_parent_interface = NULL;
static void wp_audio_convert_async_initable_init (gpointer iface,
gpointer iface_data);
G_DEFINE_TYPE_WITH_CODE (WpAudioConvert, wp_audio_convert, WP_TYPE_AUDIO_STREAM,
G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE,
wp_audio_convert_async_initable_init))
static void
create_link_cb (WpProperties *props, gpointer user_data)
{
WpAudioConvert *self = WP_AUDIO_CONVERT (user_data);
g_autoptr (WpCore) core = NULL;
WpLink *link;
core = wp_audio_stream_get_core (WP_AUDIO_STREAM (self));
g_return_if_fail (core);
/* make the link passive, which means it will not keep
the audioconvert node in the running state if the number of non-passive
links (i.e. the ones linking another endpoint to this one) drops to 0 */
wp_properties_set (props, PW_KEY_LINK_PASSIVE, "1");
/* Create the link */
link = wp_link_new_from_factory (core, "link-factory",
wp_properties_ref (props));
g_return_if_fail (link);
g_ptr_array_add(self->link_proxies, link);
}
static void
on_audio_convert_running(WpAudioConvert *self)
{
g_autoptr (GVariant) src_props = NULL;
g_autoptr (GVariant) sink_props = NULL;
g_autoptr (GError) error = NULL;
enum pw_direction direction =
wp_audio_stream_get_direction (WP_AUDIO_STREAM (self));
g_debug ("%p linking audio convert to target", self);
if (direction == PW_DIRECTION_INPUT) {
wp_audio_stream_prepare_link (WP_AUDIO_STREAM (self), &src_props, &error);
wp_audio_stream_prepare_link (self->target, &sink_props, &error);
} else {
wp_audio_stream_prepare_link (self->target, &src_props, &error);
wp_audio_stream_prepare_link (WP_AUDIO_STREAM (self), &sink_props, &error);
}
multiport_link_create (src_props, sink_props, create_link_cb, self, &error);
}
static void
wp_audio_convert_event_info (WpProxy * proxy, GParamSpec *spec,
WpAudioConvert * 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_ptr_array_set_size (self->link_proxies, 0);
break;
case PW_NODE_STATE_RUNNING:
on_audio_convert_running (self);
break;
case PW_NODE_STATE_SUSPENDED:
break;
default:
break;
}
}
static WpSpaPod *
format_audio_raw_build (const struct spa_audio_info_raw *info)
{
g_autoptr (WpSpaPodBuilder) builder = wp_spa_pod_builder_new_object (
"Format", "Format");
wp_spa_pod_builder_add (builder,
"mediaType", "I", SPA_MEDIA_TYPE_audio,
"mediaSubtype", "I", SPA_MEDIA_SUBTYPE_raw,
"format", "I", info->format,
"rate", "i", info->rate,
"channels", "i", info->channels,
NULL);
if (!SPA_FLAG_IS_SET (info->flags, SPA_AUDIO_FLAG_UNPOSITIONED)) {
/* Build the position array spa pod */
g_autoptr (WpSpaPodBuilder) position_builder = wp_spa_pod_builder_new_array ();
for (guint i = 0; i < info->channels; i++)
wp_spa_pod_builder_add_id (position_builder, info->position[i]);
/* Add the position property */
wp_spa_pod_builder_add_property (builder, "position");
g_autoptr (WpSpaPod) position = wp_spa_pod_builder_end (position_builder);
wp_spa_pod_builder_add_pod (builder, position);
}
return wp_spa_pod_builder_end (builder);
}
static void
on_audio_convert_core_done (WpCore *core, GAsyncResult *res,
WpAudioConvert *self)
{
g_autoptr (GError) error = NULL;
enum pw_direction direction =
wp_audio_stream_get_direction (WP_AUDIO_STREAM (self));
g_autoptr (WpSpaPod) format = NULL;
g_autoptr (WpSpaPod) pod = NULL;
gboolean control;
wp_core_sync_finish (core, res, &error);
if (error) {
g_message("WpAudioConvert:%p initial sync failed: %s", self, error->message);
wp_audio_stream_init_task_finish (WP_AUDIO_STREAM (self),
g_steal_pointer (&error));
return;
}
g_debug ("%s:%p setting format", G_OBJECT_TYPE_NAME (self), self);
format = format_audio_raw_build (&self->format);
/* Only enable control port for input streams */
control =
#if defined(HAVE_AUDIOFADE)
(direction == PW_DIRECTION_INPUT);
#else
FALSE;
#endif
/* Configure audioconvert to be both merger and splitter; this means it will
have an equal number of input and output ports and just passthrough the
same format, but with altered volume.
In the future we need to consider writing a simpler volume node for this,
as doing merge + split is heavy for our needs */
pod = wp_spa_pod_new_object ("PortConfig", "PortConfig",
"direction", "I", pw_direction_reverse(direction),
"mode", "I", SPA_PARAM_PORT_CONFIG_MODE_dsp,
"format", "P", format,
NULL);
wp_audio_stream_set_port_config (WP_AUDIO_STREAM (self), pod);
pod = wp_spa_pod_new_object ("PortConfig", "PortConfig",
"direction", "I", direction,
"mode", "I", SPA_PARAM_PORT_CONFIG_MODE_dsp,
"control", "b", control,
"format", "P", format,
NULL);
wp_audio_stream_set_port_config (WP_AUDIO_STREAM (self), pod);
wp_audio_stream_finish_port_config (WP_AUDIO_STREAM (self));
}
static void
wp_audio_convert_init_async (GAsyncInitable *initable, int io_priority,
GCancellable *cancellable, GAsyncReadyCallback callback, gpointer data)
{
WpAudioConvert *self = WP_AUDIO_CONVERT (initable);
g_autoptr (WpProxy) proxy = NULL;
g_autoptr (WpProperties) props = NULL;
g_autoptr (WpCore) core = wp_audio_stream_get_core (WP_AUDIO_STREAM (self));
WpNode *node;
/* Create the properties */
node = wp_audio_stream_get_node (self->target);
props = wp_properties_copy (wp_proxy_get_properties (WP_PROXY (node)));
wp_properties_setf (props, PW_KEY_OBJECT_PATH, "%s:%s",
wp_properties_get(props, PW_KEY_OBJECT_PATH),
wp_audio_stream_get_name (WP_AUDIO_STREAM (self)));
wp_properties_setf (props, PW_KEY_NODE_NAME, "%s/%s/%s",
SPA_NAME_AUDIO_CONVERT,
wp_properties_get(props, PW_KEY_NODE_NAME),
wp_audio_stream_get_name (WP_AUDIO_STREAM (self)));
wp_properties_set (props, PW_KEY_MEDIA_CLASS, "Audio/Convert");
wp_properties_set (props, SPA_KEY_FACTORY_NAME, SPA_NAME_AUDIO_CONVERT);
/* Create the proxy */
proxy = (WpProxy *) wp_node_new_from_factory (core, "spa-node-factory",
g_steal_pointer (&props));
g_return_if_fail (proxy);
g_object_set (self, "node", proxy, NULL);
g_signal_connect_object (proxy, "notify::info",
(GCallback) wp_audio_convert_event_info, self, 0);
/* Call the parent interface */
wp_audio_convert_parent_interface->init_async (initable, io_priority,
cancellable, callback, data);
/* Register a callback to be called after all the initialization is done */
wp_core_sync (core, NULL,
(GAsyncReadyCallback) on_audio_convert_core_done, self);
}
static void
wp_audio_convert_async_initable_init (gpointer iface, gpointer iface_data)
{
GAsyncInitableIface *ai_iface = iface;
/* Set the parent interface */
wp_audio_convert_parent_interface = g_type_interface_peek_parent (iface);
ai_iface->init_async = wp_audio_convert_init_async;
}
static void
wp_audio_convert_set_property (GObject * object, guint property_id,
const GValue * value, GParamSpec * pspec)
{
WpAudioConvert *self = WP_AUDIO_CONVERT (object);
switch (property_id) {
case PROP_TARGET:
self->target = g_value_dup_object (value);
break;
case PROP_FORMAT: {
const struct spa_audio_info_raw *f = g_value_get_pointer (value);
if (f)
self->format = *f;
else
g_warning ("WpAudioConvert:%p Format needs to be valid", self);
break;
}
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
wp_audio_convert_get_property (GObject * object, guint property_id,
GValue * value, GParamSpec * pspec)
{
WpAudioConvert *self = WP_AUDIO_CONVERT (object);
switch (property_id) {
case PROP_TARGET:
g_value_set_object (value, self->target);
break;
case PROP_FORMAT:
g_value_set_pointer (value, &self->format);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
wp_audio_convert_finalize (GObject * object)
{
WpAudioConvert *self = WP_AUDIO_CONVERT (object);
g_clear_pointer (&self->link_proxies, g_ptr_array_unref);
g_clear_object (&self->target);
G_OBJECT_CLASS (wp_audio_convert_parent_class)->finalize (object);
}
static void
wp_audio_convert_init (WpAudioConvert * self)
{
self->link_proxies = g_ptr_array_new_with_free_func (g_object_unref);
}
static void
wp_audio_convert_class_init (WpAudioConvertClass * klass)
{
GObjectClass *object_class = (GObjectClass *) klass;
object_class->finalize = wp_audio_convert_finalize;
object_class->set_property = wp_audio_convert_set_property;
object_class->get_property = wp_audio_convert_get_property;
/* Install the properties */
g_object_class_install_property (object_class, PROP_TARGET,
g_param_spec_object ("target", "target", "The target stream",
WP_TYPE_AUDIO_STREAM,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_FORMAT,
g_param_spec_pointer ("format", "format", "The accepted format",
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
}
void
wp_audio_convert_new (WpBaseEndpoint *endpoint, guint stream_id,
const char *stream_name, enum pw_direction direction,
WpAudioStream *target, const struct spa_audio_info_raw *format,
GAsyncReadyCallback callback, gpointer user_data)
{
g_async_initable_new_async (
WP_TYPE_AUDIO_CONVERT, G_PRIORITY_DEFAULT, NULL, callback, user_data,
"endpoint", endpoint,
"id", stream_id,
"name", stream_name,
"direction", direction,
"target", target,
"format", format,
NULL);
}

View File

@@ -1,32 +0,0 @@
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author Julian Bouzas <julian.bouzas@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
#ifndef __WIREPLUMBER_AUDIO_CONVERT_H__
#define __WIREPLUMBER_AUDIO_CONVERT_H__
#include <gio/gio.h>
#include <wp/wp.h>
#include "stream.h"
G_BEGIN_DECLS
struct spa_audio_info_raw format;
#define WP_TYPE_AUDIO_CONVERT (wp_audio_convert_get_type ())
G_DECLARE_FINAL_TYPE (WpAudioConvert, wp_audio_convert, WP, AUDIO_CONVERT,
WpAudioStream)
void wp_audio_convert_new (WpBaseEndpoint *endpoint, guint stream_id,
const char *stream_name, enum pw_direction direction,
WpAudioStream *target, const struct spa_audio_info_raw *format,
GAsyncReadyCallback callback, gpointer user_data);
G_END_DECLS
#endif

View File

@@ -1,832 +0,0 @@
/* 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_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;
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, "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, "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_STRING);
}
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 gboolean
port_proxies_fold_func (const GValue *item, GValue *ret, gpointer user_data)
{
WpProxy *port = g_value_get_object (item);
GVariantBuilder *b = g_value_get_pointer (ret);
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_val_if_fail (node_info, TRUE);
port_info = wp_proxy_get_info (port);
g_return_val_if_fail (port_info, TRUE);
props = wp_proxy_get_properties (port);
/* skip control ports */
if (is_stream_control_port (WP_PORT (port)))
return TRUE;
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 (b, "(uuuy)", node_info->id,
port_info->id, channel_n, (guint8) port_info->direction);
return TRUE;
}
gboolean
wp_audio_stream_prepare_link (WpAudioStream * self, GVariant ** properties,
GError ** error)
{
WpAudioStreamPrivate *priv = wp_audio_stream_get_instance_private (self);
GVariantBuilder b;
g_auto (GValue) val = G_VALUE_INIT;
g_autoptr (WpIterator) it = NULL;
/* Create a variant array with all the ports */
g_variant_builder_init (&b, G_VARIANT_TYPE ("a(uuuy)"));
g_value_init (&val, G_TYPE_POINTER);
g_value_set_pointer (&val, &b);
it = wp_object_manager_iterate (priv->ports_om);
wp_iterator_fold (it, port_proxies_fold_func, &val, self);
*properties = g_variant_builder_end (&b);
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;
g_autoptr (WpSpaPod) props = NULL;
/* 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 */
props = wp_spa_pod_new_object (
"Props", "Props",
"audioFadeDuration", "i", duration,
"audioFadeStep", "d", step,
"audioFadeDirection", "I", direction,
"audioFadeType", "I", type,
NULL);
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 WpSpaPod * 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;
}

View File

@@ -1,53 +0,0 @@
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author Julian Bouzas <julian.bouzas@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
#ifndef __WIREPLUMBER_AUDIO_STREAM_H__
#define __WIREPLUMBER_AUDIO_STREAM_H__
#include <gio/gio.h>
#include <wp/wp.h>
G_BEGIN_DECLS
#define WP_TYPE_AUDIO_STREAM (wp_audio_stream_get_type ())
G_DECLARE_DERIVABLE_TYPE (WpAudioStream, wp_audio_stream, WP, AUDIO_STREAM, GObject)
/* The audio stream base class */
struct _WpAudioStreamClass
{
GObjectClass parent_class;
};
WpAudioStream * wp_audio_stream_new_finish (GObject *initable,
GAsyncResult *res, GError **error);
const char *wp_audio_stream_get_name (WpAudioStream * self);
enum pw_direction wp_audio_stream_get_direction (WpAudioStream * self);
WpNode * wp_audio_stream_get_node (WpAudioStream * self);
const struct pw_node_info * wp_audio_stream_get_info (WpAudioStream * self);
gboolean wp_audio_stream_prepare_link (WpAudioStream * self,
GVariant ** properties, GError ** error);
gfloat wp_audio_stream_get_volume (WpAudioStream * self);
gboolean wp_audio_stream_get_mute (WpAudioStream * self);
void wp_audio_stream_set_volume (WpAudioStream * self, gfloat volume);
void wp_audio_stream_set_mute (WpAudioStream * self, gboolean mute);
void wp_audio_stream_begin_fade (WpAudioStream * self, guint duration,
gfloat step, guint direction, guint type, GCancellable * cancellable,
GAsyncReadyCallback callback, gpointer user_data);
/* for subclasses */
WpCore *wp_audio_stream_get_core (WpAudioStream * self);
void wp_audio_stream_init_task_finish (WpAudioStream * self, GError * error);
void wp_audio_stream_set_port_config (WpAudioStream * self,
const WpSpaPod * param);
void wp_audio_stream_finish_port_config (WpAudioStream * self);
G_END_DECLS
#endif

View File

@@ -1,264 +0,0 @@
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
/**
* The simple endpoint link is an implementation of WpBaseEndpointLink that
* expects the two linked endpoints to have nodes in the pipewire graph.
* When asked to create a link, it creates pw_link objects that will link
* the ports of the source node to the ports of the sink node.
*
* The GVariant data that is passed in create must be of type (uau),
* which means a tuple with the following fields:
* - u: a uint32 that is the ID of a node
* - au: an array of uint32 that are the IDs of the ports on this node
*
* Linking endpoints with multiple nodes is not supported by this implementation.
*/
#include <wp/wp.h>
#include <pipewire/pipewire.h>
#include "algorithms.h"
struct _WpPipewireSimpleEndpointLink
{
WpBaseEndpointLink parent;
/* Props */
GWeakRef core;
guint link_count;
/* The task to signal the simple endpoint link is initialized */
GTask *init_task;
/* Handler */
gulong proxy_done_handler_id;
/* The link proxies */
GPtrArray *link_proxies;
};
enum {
PROP_0,
PROP_CORE,
};
static GAsyncInitableIface *wp_simple_endpoint_link_parent_interface = NULL;
static void wp_simple_endpoint_link_async_initable_init (gpointer iface,
gpointer iface_data);
G_DECLARE_FINAL_TYPE (WpPipewireSimpleEndpointLink,
simple_endpoint_link, WP_PIPEWIRE, SIMPLE_ENDPOINT_LINK, WpBaseEndpointLink)
G_DEFINE_TYPE_WITH_CODE (WpPipewireSimpleEndpointLink, simple_endpoint_link,
WP_TYPE_BASE_ENDPOINT_LINK,
G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE,
wp_simple_endpoint_link_async_initable_init))
static void
wp_simple_endpoint_link_init_async (GAsyncInitable *initable, int io_priority,
GCancellable *cancellable, GAsyncReadyCallback callback, gpointer data)
{
WpPipewireSimpleEndpointLink *self =
WP_PIPEWIRE_SIMPLE_ENDPOINT_LINK (initable);
/* Create the async task */
self->init_task = g_task_new (initable, cancellable, callback, data);
/* Call the parent interface */
wp_simple_endpoint_link_parent_interface->init_async (initable,
io_priority, cancellable, callback, data);
}
static void
wp_simple_endpoint_link_async_initable_init (gpointer iface,
gpointer iface_data)
{
GAsyncInitableIface *ai_iface = iface;
/* Set the parent interface */
wp_simple_endpoint_link_parent_interface =
g_type_interface_peek_parent (iface);
/* Only set the init_async */
ai_iface->init_async = wp_simple_endpoint_link_init_async;
}
static void
simple_endpoint_link_init (WpPipewireSimpleEndpointLink * self)
{
/* Init the core weak reference */
g_weak_ref_init (&self->core, NULL);
/* Init the list of link proxies */
self->link_proxies = g_ptr_array_new_full(2, (GDestroyNotify)g_object_unref);
}
static void
simple_endpoint_link_finalize (GObject * object)
{
WpPipewireSimpleEndpointLink *self = WP_PIPEWIRE_SIMPLE_ENDPOINT_LINK(object);
g_clear_object (&self->init_task);
g_clear_pointer (&self->link_proxies, g_ptr_array_unref);
g_weak_ref_clear (&self->core);
G_OBJECT_CLASS (simple_endpoint_link_parent_class)->finalize (object);
}
static void
simple_endpoint_link_set_property (GObject * object, guint property_id,
const GValue * value, GParamSpec * pspec)
{
WpPipewireSimpleEndpointLink *self =
WP_PIPEWIRE_SIMPLE_ENDPOINT_LINK (object);
switch (property_id) {
case PROP_CORE:
g_weak_ref_set (&self->core, g_value_get_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
simple_endpoint_link_get_property (GObject * object, guint property_id,
GValue * value, GParamSpec * pspec)
{
WpPipewireSimpleEndpointLink *self =
WP_PIPEWIRE_SIMPLE_ENDPOINT_LINK (object);
switch (property_id) {
case PROP_CORE:
g_value_take_object (value, g_weak_ref_get (&self->core));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
on_link_augmented (WpProxy *proxy, GAsyncResult *res, gpointer data)
{
WpPipewireSimpleEndpointLink *self = data;
g_autoptr (GError) error = NULL;
wp_proxy_augment_finish (proxy, res, &error);
if (error && self->init_task) {
g_task_return_error (self->init_task, g_steal_pointer (&error));
g_clear_object (&self->init_task);
return;
}
/* Finish the simple endpoint link creation if all links have been created */
if (--self->link_count == 0 && self->init_task) {
g_task_return_boolean (self->init_task, TRUE);
g_clear_object(&self->init_task);
}
}
static void
create_link_cb (WpProperties *props, gpointer user_data)
{
WpPipewireSimpleEndpointLink *self = WP_PIPEWIRE_SIMPLE_ENDPOINT_LINK(user_data);
g_autoptr (WpCore) core = NULL;
WpLink *link;
core = g_weak_ref_get (&self->core);
g_return_if_fail (core);
/* Create the link */
link = wp_link_new_from_factory (core, "link-factory",
wp_properties_ref (props));
g_return_if_fail (link);
g_ptr_array_add (self->link_proxies, link);
/* Wait for the link to be created on the server side */
self->link_count++;
wp_proxy_augment (WP_PROXY (link), WP_PROXY_FEATURE_BOUND, NULL,
(GAsyncReadyCallback) on_link_augmented, self);
}
static gboolean
simple_endpoint_link_create (WpBaseEndpointLink * epl, GVariant * src_data,
GVariant * sink_data, GError ** error)
{
WpPipewireSimpleEndpointLink *self = WP_PIPEWIRE_SIMPLE_ENDPOINT_LINK(epl);
return multiport_link_create (src_data, sink_data, create_link_cb, self, error);
}
static void
simple_endpoint_link_destroy (WpBaseEndpointLink * epl)
{
WpPipewireSimpleEndpointLink *self = WP_PIPEWIRE_SIMPLE_ENDPOINT_LINK(epl);
g_clear_pointer (&self->link_proxies, g_ptr_array_unref);
}
static void
simple_endpoint_link_class_init (WpPipewireSimpleEndpointLinkClass * klass)
{
GObjectClass *object_class = (GObjectClass *) klass;
WpBaseEndpointLinkClass *link_class = (WpBaseEndpointLinkClass *) klass;
object_class->finalize = simple_endpoint_link_finalize;
object_class->set_property = simple_endpoint_link_set_property;
object_class->get_property = simple_endpoint_link_get_property;
link_class->create = simple_endpoint_link_create;
link_class->destroy = simple_endpoint_link_destroy;
g_object_class_install_property (object_class, PROP_CORE,
g_param_spec_object ("core", "core",
"The wireplumber core object this links belongs to", WP_TYPE_CORE,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
}
void
simple_endpoint_link_factory (WpFactory * factory, GType type,
GVariant * properties, GAsyncReadyCallback ready, gpointer data)
{
g_autoptr(WpCore) core = NULL;
guint64 src, sink;
guint src_stream, sink_stream;
gboolean keep;
/* Make sure the type is an endpoint link */
g_return_if_fail (type == WP_TYPE_BASE_ENDPOINT_LINK);
/* Get the Core */
core = wp_factory_get_core (factory);
g_return_if_fail (core);
/* Get the properties */
if (!g_variant_lookup (properties, "src", "t", &src))
return;
if (!g_variant_lookup (properties, "src-stream", "u", &src_stream))
return;
if (!g_variant_lookup (properties, "sink", "t", &sink))
return;
if (!g_variant_lookup (properties, "sink-stream", "u", &sink_stream))
return;
if (!g_variant_lookup (properties, "keep", "b", &keep))
return;
/* Create the endpoint link */
g_async_initable_new_async (
simple_endpoint_link_get_type (), G_PRIORITY_DEFAULT, NULL, ready, data,
"src", (gpointer)src,
"src-stream", src_stream,
"sink", (gpointer)sink,
"sink-stream", sink_stream,
"keep", keep,
"core", core,
NULL);
}

View File

@@ -1,340 +0,0 @@
/* WirePlumber
*
* Copyright © 2020 Collabora Ltd.
* @author Julian Bouzas <julian.bouzas@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
#include <wp/wp.h>
#include <pipewire/pipewire.h>
struct _WpVideoEndpoint
{
WpBaseEndpoint parent;
/* Properties */
WpNode *node;
char *role;
/* The task to signal the endpoint is initialized */
GTask *init_task;
WpObjectManager *ports_om;
};
enum {
PROP_0,
PROP_PROXY_NODE,
PROP_ROLE,
};
static GAsyncInitableIface *async_initable_parent_interface = NULL;
static void wp_video_endpoint_async_initable_init (gpointer iface,
gpointer iface_data);
G_DECLARE_FINAL_TYPE (WpVideoEndpoint, wp_video_endpoint, WP, VIDEO_ENDPOINT,
WpBaseEndpoint)
G_DEFINE_TYPE_WITH_CODE (WpVideoEndpoint, wp_video_endpoint,
WP_TYPE_BASE_ENDPOINT, G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE,
wp_video_endpoint_async_initable_init))
static WpProperties *
wp_video_endpoint_get_properties (WpBaseEndpoint * ep)
{
WpVideoEndpoint *self = WP_VIDEO_ENDPOINT (ep);
return wp_proxy_get_properties (WP_PROXY (self->node));
}
static const char *
wp_video_endpoint_get_role (WpBaseEndpoint *ep)
{
WpVideoEndpoint *self = WP_VIDEO_ENDPOINT (ep);
return self->role;
}
static guint32
wp_video_endpoint_get_global_id (WpBaseEndpoint *ep)
{
WpVideoEndpoint *self = WP_VIDEO_ENDPOINT (ep);
return wp_proxy_get_bound_id (WP_PROXY (self->node));
}
static gboolean
port_proxies_fold_func (const GValue *item, GValue *ret, gpointer user_data)
{
WpProxy *port = g_value_get_object (item);
GVariantBuilder *b = g_value_get_pointer (ret);
WpVideoEndpoint *self = WP_VIDEO_ENDPOINT (user_data);
const struct pw_node_info *node_info;
const struct pw_port_info *port_info;
node_info = wp_proxy_get_info (WP_PROXY (self->node));
g_return_val_if_fail (node_info, TRUE);
port_info = wp_proxy_get_info (port);
g_return_val_if_fail (port_info, TRUE);
/* tuple format:
uint32 node_id;
uint32 port_id;
uint32 channel; // always 0 for video
uint8 direction; // enum spa_direction
*/
g_variant_builder_add (b, "(uuuy)", node_info->id,
port_info->id, 0, (guint8) port_info->direction);
return TRUE;
}
static gboolean
wp_video_endpoint_prepare_link (WpBaseEndpoint * ep, guint32 stream_id,
WpBaseEndpointLink * link, GVariant ** properties, GError ** error)
{
WpVideoEndpoint *self = WP_VIDEO_ENDPOINT (ep);
GVariantBuilder b;
g_auto (GValue) val = G_VALUE_INIT;
g_autoptr (WpIterator) it = NULL;
/* Create a variant array with all the ports */
g_variant_builder_init (&b, G_VARIANT_TYPE ("a(uuuy)"));
g_value_init (&val, G_TYPE_POINTER);
g_value_set_pointer (&val, &b);
it = wp_object_manager_iterate (self->ports_om);
wp_iterator_fold (it, port_proxies_fold_func, &val, self);
*properties = g_variant_builder_end (&b);
return TRUE;
}
static void
wp_video_endpoint_begin_fade (WpBaseEndpoint * ep, guint32 stream_id,
guint duration, gfloat step, guint direction, guint type,
GCancellable * cancellable, GAsyncReadyCallback callback, gpointer data)
{
/* TODO: apply a video fade effect */
g_autoptr (GTask) task = g_task_new (ep, cancellable, callback, data);
g_task_return_boolean (task, TRUE);
}
static void
wp_video_endpoint_finalize (GObject * object)
{
WpVideoEndpoint *self = WP_VIDEO_ENDPOINT (object);
g_clear_object (&self->init_task);
/* Destroy the done task */
g_clear_object (&self->init_task);
/* Props */
g_clear_object (&self->node);
g_free (self->role);
G_OBJECT_CLASS (wp_video_endpoint_parent_class)->finalize (object);
}
static void
wp_video_endpoint_set_property (GObject * object, guint property_id,
const GValue * value, GParamSpec * pspec)
{
WpVideoEndpoint *self = WP_VIDEO_ENDPOINT (object);
switch (property_id) {
case PROP_PROXY_NODE:
self->node = g_value_dup_object (value);
break;
case PROP_ROLE:
g_free (self->role);
self->role = g_value_dup_string (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
wp_video_endpoint_get_property (GObject * object, guint property_id,
GValue * value, GParamSpec * pspec)
{
WpVideoEndpoint *self = WP_VIDEO_ENDPOINT (object);
switch (property_id) {
case PROP_PROXY_NODE:
g_value_set_object (value, self->node);
break;
case PROP_ROLE:
g_value_set_string (value, self->role);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
wp_video_endpoint_init_task_finish (WpVideoEndpoint * self, GError * err)
{
g_autoptr (GError) error = err;
if (!self->init_task)
return;
if (error)
g_task_return_error (self->init_task, g_steal_pointer (&error));
else
g_task_return_boolean (self->init_task, TRUE);
g_clear_object (&self->init_task);
}
static void
on_ports_changed (WpObjectManager *om, WpVideoEndpoint *self)
{
wp_video_endpoint_init_task_finish (self, NULL);
g_signal_handlers_disconnect_by_func (self->ports_om, on_ports_changed, self);
}
static void
on_node_proxy_augmented (WpProxy * proxy, GAsyncResult * res,
WpVideoEndpoint * self)
{
g_autoptr (WpCore) core = wp_base_endpoint_get_core (WP_BASE_ENDPOINT(self));
guint32 id;
g_autofree gchar *id_str = NULL;
GVariantBuilder b;
/* Get the bound id */
id = wp_proxy_get_bound_id (proxy);
id_str = g_strdup_printf ("%u", id);
/* Create the ports object manager and set the port constrains */
self->ports_om = wp_object_manager_new ();
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 (id_str));
g_variant_builder_close (&b);
wp_object_manager_add_interest (self->ports_om, WP_TYPE_PORT,
g_variant_builder_end (&b),
WP_PROXY_FEATURE_PW_PROXY | WP_PROXY_FEATURE_INFO);
/* Add a callback to know when ports have been changed */
g_signal_connect (self->ports_om, "objects-changed",
(GCallback) on_ports_changed, self);
/* Install the object manager */
wp_core_install_object_manager (core, self->ports_om);
}
static void
wp_video_endpoint_init_async (GAsyncInitable *initable, int io_priority,
GCancellable *cancellable, GAsyncReadyCallback callback, gpointer data)
{
WpVideoEndpoint *self = WP_VIDEO_ENDPOINT (initable);
/* Create the async task */
self->init_task = g_task_new (initable, cancellable, callback, data);
/* Augment the proxy */
wp_proxy_augment (WP_PROXY (self->node), WP_PROXY_FEATURES_STANDARD,
cancellable, (GAsyncReadyCallback) on_node_proxy_augmented, self);
/* Call the parent interface */
async_initable_parent_interface->init_async (initable, io_priority, cancellable,
callback, data);
}
static void
wp_video_endpoint_async_initable_init (gpointer iface, gpointer iface_data)
{
GAsyncInitableIface *ai_iface = iface;
/* Set the parent interface */
async_initable_parent_interface = g_type_interface_peek_parent (iface);
/* Only set the init_async */
ai_iface->init_async = wp_video_endpoint_init_async;
}
static void
wp_video_endpoint_init (WpVideoEndpoint * self)
{
}
static void
wp_video_endpoint_class_init (WpVideoEndpointClass * klass)
{
GObjectClass *object_class = (GObjectClass *) klass;
WpBaseEndpointClass *base_endpoint_class = (WpBaseEndpointClass *) klass;
object_class->finalize = wp_video_endpoint_finalize;
object_class->set_property = wp_video_endpoint_set_property;
object_class->get_property = wp_video_endpoint_get_property;
base_endpoint_class->get_properties = wp_video_endpoint_get_properties;
base_endpoint_class->get_role = wp_video_endpoint_get_role;
base_endpoint_class->get_global_id = wp_video_endpoint_get_global_id;
base_endpoint_class->prepare_link = wp_video_endpoint_prepare_link;
base_endpoint_class->begin_fade = wp_video_endpoint_begin_fade;
/* Instal the properties */
g_object_class_install_property (object_class, PROP_PROXY_NODE,
g_param_spec_object ("node", "node",
"The node this endpoint refers to", WP_TYPE_NODE,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_ROLE,
g_param_spec_string ("role", "role", "The role of the wrapped node", NULL,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
}
void
wp_video_endpoint_factory (WpFactory * factory, GType type,
GVariant * properties, GAsyncReadyCallback ready, gpointer user_data)
{
g_autoptr (WpCore) core = NULL;
const gchar *name, *media_class;
guint direction, priority;
guint64 node;
/* Make sure the type is correct */
g_return_if_fail(type == WP_TYPE_BASE_ENDPOINT);
/* Get the Core */
core = wp_factory_get_core(factory);
g_return_if_fail (core);
/* Get the properties */
if (!g_variant_lookup (properties, "name", "&s", &name))
return;
if (!g_variant_lookup (properties, "media-class", "&s", &media_class))
return;
if (!g_variant_lookup (properties, "direction", "u", &direction))
return;
if (!g_variant_lookup (properties, "priority", "u", &priority))
return;
if (!g_variant_lookup (properties, "node", "t", &node))
return;
/* Create and return the video endpoint object */
g_async_initable_new_async (
wp_video_endpoint_get_type (), G_PRIORITY_DEFAULT, NULL, ready, user_data,
"core", core,
"name", name,
"media-class", media_class,
"direction", direction,
"priority", priority,
"node", (gpointer) node,
NULL);
}

View File

@@ -14,7 +14,7 @@
#include <spa/param/audio/raw.h>
#include <spa/param/param.h>
#include "module-pipewire/algorithms.h"
#include "module-si-adapter/algorithms.h"
enum {
STEP_VERIFY_CONFIG = WP_TRANSITION_STEP_CUSTOM_START,

View File

@@ -14,8 +14,6 @@
#include <spa/param/audio/raw.h>
#include <spa/param/param.h>
#include "module-pipewire/algorithms.h"
enum {
STEP_VERIFY_CONFIG = WP_TRANSITION_STEP_CUSTOM_START,
STEP_CREATE_NODE,

View File

@@ -13,9 +13,6 @@ create-session video
# the client-device pipewire module is needed for libwireplumber-module-monitor
load-pipewire-module libpipewire-module-client-device
# Basic pipewire integration - do not remove
load-module C libwireplumber-module-pipewire
# Grants access to security confined clients
load-module C libwireplumber-module-client-permissions

View File

@@ -14,7 +14,7 @@
#include <spa/param/audio/format.h>
#include <spa/param/audio/format-utils.h>
#include "../../modules/module-pipewire/algorithms.h"
#include "../../modules/module-si-adapter/algorithms.h"
static void
test_choose_sensible_raw_audio_format (void)

View File

@@ -14,7 +14,7 @@ test(
executable('test-algorithms',
[
'algorithms.c',
'../../modules/module-pipewire/algorithms.c'
'../../modules/module-si-adapter/algorithms.c'
],
dependencies: common_deps, c_args: common_args),
env: common_env,