modules: remove pipewire module and move algorithms to module-si-adapter
This commit is contained in:
@@ -45,24 +45,6 @@ shared_library(
|
|||||||
dependencies : [wp_dep, pipewire_dep],
|
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(
|
shared_library(
|
||||||
'wireplumber-module-config-static-nodes',
|
'wireplumber-module-config-static-nodes',
|
||||||
[
|
[
|
||||||
@@ -107,7 +89,7 @@ shared_library(
|
|||||||
'wireplumber-module-si-adapter',
|
'wireplumber-module-si-adapter',
|
||||||
[
|
[
|
||||||
'module-si-adapter.c',
|
'module-si-adapter.c',
|
||||||
'module-pipewire/algorithms.c',
|
'module-si-adapter/algorithms.c',
|
||||||
],
|
],
|
||||||
c_args : [common_c_args, '-DG_LOG_DOMAIN="m-si-adapter"'],
|
c_args : [common_c_args, '-DG_LOG_DOMAIN="m-si-adapter"'],
|
||||||
install : true,
|
install : true,
|
||||||
@@ -119,7 +101,6 @@ shared_library(
|
|||||||
'wireplumber-module-si-convert',
|
'wireplumber-module-si-convert',
|
||||||
[
|
[
|
||||||
'module-si-convert.c',
|
'module-si-convert.c',
|
||||||
'module-pipewire/algorithms.c',
|
|
||||||
],
|
],
|
||||||
c_args : [common_c_args, '-DG_LOG_DOMAIN="m-si-convert"'],
|
c_args : [common_c_args, '-DG_LOG_DOMAIN="m-si-convert"'],
|
||||||
install : true,
|
install : true,
|
||||||
|
@@ -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);
|
|
||||||
}
|
|
@@ -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);
|
|
||||||
}
|
|
@@ -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
|
|
@@ -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);
|
|
||||||
}
|
|
@@ -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
|
|
@@ -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;
|
|
||||||
}
|
|
@@ -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
|
|
@@ -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);
|
|
||||||
}
|
|
@@ -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);
|
|
||||||
}
|
|
@@ -14,7 +14,7 @@
|
|||||||
#include <spa/param/audio/raw.h>
|
#include <spa/param/audio/raw.h>
|
||||||
#include <spa/param/param.h>
|
#include <spa/param/param.h>
|
||||||
|
|
||||||
#include "module-pipewire/algorithms.h"
|
#include "module-si-adapter/algorithms.h"
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
STEP_VERIFY_CONFIG = WP_TRANSITION_STEP_CUSTOM_START,
|
STEP_VERIFY_CONFIG = WP_TRANSITION_STEP_CUSTOM_START,
|
||||||
|
@@ -14,8 +14,6 @@
|
|||||||
#include <spa/param/audio/raw.h>
|
#include <spa/param/audio/raw.h>
|
||||||
#include <spa/param/param.h>
|
#include <spa/param/param.h>
|
||||||
|
|
||||||
#include "module-pipewire/algorithms.h"
|
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
STEP_VERIFY_CONFIG = WP_TRANSITION_STEP_CUSTOM_START,
|
STEP_VERIFY_CONFIG = WP_TRANSITION_STEP_CUSTOM_START,
|
||||||
STEP_CREATE_NODE,
|
STEP_CREATE_NODE,
|
||||||
|
@@ -13,9 +13,6 @@ create-session video
|
|||||||
# the client-device pipewire module is needed for libwireplumber-module-monitor
|
# the client-device pipewire module is needed for libwireplumber-module-monitor
|
||||||
load-pipewire-module libpipewire-module-client-device
|
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
|
# Grants access to security confined clients
|
||||||
load-module C libwireplumber-module-client-permissions
|
load-module C libwireplumber-module-client-permissions
|
||||||
|
|
||||||
|
@@ -14,7 +14,7 @@
|
|||||||
#include <spa/param/audio/format.h>
|
#include <spa/param/audio/format.h>
|
||||||
#include <spa/param/audio/format-utils.h>
|
#include <spa/param/audio/format-utils.h>
|
||||||
|
|
||||||
#include "../../modules/module-pipewire/algorithms.h"
|
#include "../../modules/module-si-adapter/algorithms.h"
|
||||||
|
|
||||||
static void
|
static void
|
||||||
test_choose_sensible_raw_audio_format (void)
|
test_choose_sensible_raw_audio_format (void)
|
||||||
|
@@ -14,7 +14,7 @@ test(
|
|||||||
executable('test-algorithms',
|
executable('test-algorithms',
|
||||||
[
|
[
|
||||||
'algorithms.c',
|
'algorithms.c',
|
||||||
'../../modules/module-pipewire/algorithms.c'
|
'../../modules/module-si-adapter/algorithms.c'
|
||||||
],
|
],
|
||||||
dependencies: common_deps, c_args: common_args),
|
dependencies: common_deps, c_args: common_args),
|
||||||
env: common_env,
|
env: common_env,
|
||||||
|
Reference in New Issue
Block a user