modules: replace dsp class with stream, adapter and convert classes

This commit is contained in:
Julian Bouzas
2019-08-13 14:18:34 -04:00
parent 8d58243fa6
commit fbf8de84fa
12 changed files with 1401 additions and 975 deletions

View File

@@ -65,7 +65,9 @@ shared_library(
'wireplumber-module-pw-audio-softdsp-endpoint',
[
'module-pw-audio-softdsp-endpoint.c',
'module-pw-audio-softdsp-endpoint/dsp.c',
'module-pw-audio-softdsp-endpoint/stream.c',
'module-pw-audio-softdsp-endpoint/adapter.c',
'module-pw-audio-softdsp-endpoint/convert.c',
],
c_args : [common_c_args, '-DG_LOG_DOMAIN="m-pw-audio-softdsp-endpoint"'],
install : true,

View File

@@ -246,12 +246,13 @@ emit_endpoint_ports(WpPipewireSimpleEndpoint *self)
/* Build the param profile */
param = spa_format_audio_raw_build(&pod_builder, SPA_PARAM_Format, &format);
param = spa_pod_builder_add_object(&pod_builder,
SPA_TYPE_OBJECT_ParamProfile, SPA_PARAM_Profile,
SPA_PARAM_PROFILE_direction, SPA_POD_Id(self->direction),
SPA_PARAM_PROFILE_format, SPA_POD_Pod(param));
SPA_TYPE_OBJECT_ParamPortConfig, SPA_PARAM_PortConfig,
SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(self->direction),
SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(SPA_PARAM_PORT_CONFIG_MODE_dsp),
SPA_PARAM_PORT_CONFIG_format, SPA_POD_Pod(param));
/* Set the param profile to emit the ports */
pw_node_proxy_set_param(node_proxy, SPA_PARAM_Profile, 0, param);
pw_node_proxy_set_param(node_proxy, SPA_PARAM_PortConfig, 0, param);
}
static void

View File

@@ -103,13 +103,13 @@ on_node_added(WpRemotePipewire *rp, guint id, guint parent_id, gconstpointer p,
/* Make sure the node has properties */
g_return_if_fail(props);
/* Get the name and media_class */
/* Get the media_class */
media_class = spa_dict_lookup(props, "media.class");
/* Make sure the media class is non-dsp audio */
/* Make sure the media class is non-convert audio */
if (!g_str_has_prefix (media_class, "Audio/"))
return;
if (g_str_has_prefix (media_class, "Audio/DSP"))
if (g_str_has_prefix (media_class, "Audio/Convert"))
return;
/* Get the name */

View File

@@ -19,7 +19,9 @@
#include <spa/pod/builder.h>
#include <spa/param/props.h>
#include "module-pw-audio-softdsp-endpoint/dsp.h"
#include "module-pw-audio-softdsp-endpoint/stream.h"
#include "module-pw-audio-softdsp-endpoint/adapter.h"
#include "module-pw-audio-softdsp-endpoint/convert.h"
#define MIN_QUANTUM_SIZE 64
#define MAX_QUANTUM_SIZE 1024
@@ -40,19 +42,12 @@ struct _WpPwAudioSoftdspEndpoint
GTask *init_task;
gboolean init_abort;
/* The remote pipewire */
WpRemotePipewire *remote_pipewire;
/* Direction */
enum pw_direction direction;
/* Proxies */
WpProxyNode *proxy_node;
WpProxyPort *proxy_port;
/* Audio Dsp */
WpPwAudioDsp *converter;
GPtrArray *dsps;
/* Audio Streams */
WpAudioStream *adapter;
GPtrArray *converters;
};
enum {
@@ -108,17 +103,17 @@ endpoint_prepare_link (WpEndpoint * ep, guint32 stream_id,
WpEndpointLink * link, GVariant ** properties, GError ** error)
{
WpPwAudioSoftdspEndpoint *self = WP_PW_AUDIO_SOFTDSP_ENDPOINT (ep);
WpPwAudioDsp *stream = NULL;
WpAudioStream *stream = NULL;
/* Make sure the stream Id is valid */
g_return_val_if_fail(stream_id < self->dsps->len, FALSE);
g_return_val_if_fail(stream_id < self->converters->len, FALSE);
/* Make sure the stream is valid */
stream = g_ptr_array_index (self->dsps, stream_id);
stream = g_ptr_array_index (self->converters, stream_id);
g_return_val_if_fail(stream, FALSE);
/* Prepare the link */
return wp_pw_audio_dsp_prepare_link (stream, properties, error);
return wp_audio_stream_prepare_link (stream, properties, error);
}
static void
@@ -134,27 +129,27 @@ finish_endpoint_creation(WpPwAudioSoftdspEndpoint *self)
}
static void
on_audio_dsp_stream_created(GObject *initable, GAsyncResult *res, gpointer data)
on_audio_convert_created(GObject *initable, GAsyncResult *res, gpointer data)
{
WpPwAudioSoftdspEndpoint *self = data;
WpPwAudioDsp *dsp = NULL;
WpAudioStream *convert = NULL;
guint stream_id = 0;
g_autofree gchar *name = NULL;
/* Get the stream */
dsp = WP_PW_AUDIO_DSP (object_safe_new_finish (self, initable, res,
(WpObjectNewFinishFunc)wp_pw_audio_dsp_new_finish));
if (!dsp)
/* Get the audio convert */
convert = WP_AUDIO_STREAM (object_safe_new_finish (self, initable, res,
(WpObjectNewFinishFunc)wp_audio_stream_new_finish));
if (!convert)
return;
/* Get the stream id */
g_object_get (dsp, "id", &stream_id, "name", &name, NULL);
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->dsps, stream_id, dsp);
g_ptr_array_insert (self->converters, stream_id, convert);
g_debug ("%s:%p Created stream %u %s", G_OBJECT_TYPE_NAME (self), self,
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 */
@@ -163,32 +158,43 @@ on_audio_dsp_stream_created(GObject *initable, GAsyncResult *res, gpointer data)
}
static void
on_audio_dsp_converter_created(GObject *initable, GAsyncResult *res,
on_audio_adapter_created(GObject *initable, GAsyncResult *res,
gpointer data)
{
WpPwAudioSoftdspEndpoint *self = data;
g_autoptr (WpCore) core = wp_endpoint_get_core(WP_ENDPOINT(self));
const struct pw_node_info *target = NULL;
g_autofree gchar *name = NULL;
const struct pw_node_info *adapter_info = NULL;
GVariantDict d;
GVariantIter iter;
const gchar *stream;
int i;
/* Get the proxy dsp converter */
self->converter = WP_PW_AUDIO_DSP (object_safe_new_finish (self, initable,
res, (WpObjectNewFinishFunc)wp_pw_audio_dsp_new_finish));
if (!self->converter)
/* Get the proxy adapter */
self->adapter = WP_AUDIO_STREAM (object_safe_new_finish (self, initable,
res, (WpObjectNewFinishFunc)wp_audio_stream_new_finish));
if (!self->adapter)
return;
/* Get the target and format */
target = wp_pw_audio_dsp_get_info (self->converter);
g_return_if_fail (target);
/* Get the adapter info */
adapter_info = wp_audio_stream_get_info (self->adapter);
g_return_if_fail (adapter_info);
/* Create the audio dsp streams */
/* Give a proper name to this endpoint based on adapter properties */
if (0 == g_strcmp0(spa_dict_lookup (adapter_info->props, "device.api"), "alsa")) {
name = g_strdup_printf ("%s on %s (%s / node %d)",
spa_dict_lookup (adapter_info->props, "api.alsa.pcm.name"),
spa_dict_lookup (adapter_info->props, "api.alsa.card.name"),
spa_dict_lookup (adapter_info->props, "api.alsa.path"),
adapter_info->id);
g_object_set (self, "name", name, NULL);
}
/* Create the audio converters */
g_variant_iter_init (&iter, self->streams);
for (i = 0; g_variant_iter_next (&iter, "&s", &stream); i++) {
wp_pw_audio_dsp_new (WP_ENDPOINT(self), i, stream, self->direction, FALSE,
target, on_audio_dsp_stream_created, self);
wp_audio_convert_new (WP_ENDPOINT(self), i, stream, self->direction,
adapter_info, on_audio_convert_created, self);
/* Register the stream */
g_variant_dict_init (&d, NULL);
@@ -199,84 +205,6 @@ on_audio_dsp_converter_created(GObject *initable, GAsyncResult *res,
self->stream_count = i;
}
static void
on_proxy_node_created(GObject *initable, GAsyncResult *res, gpointer data)
{
WpPwAudioSoftdspEndpoint *self = data;
g_autoptr (WpCore) core = wp_endpoint_get_core(WP_ENDPOINT(self));
g_autofree gchar *name = NULL;
const struct spa_dict *props;
const struct pw_node_info *target = NULL;
/* Get the proxy node */
self->proxy_node = WP_PROXY_NODE (object_safe_new_finish (self, initable,
res, (WpObjectNewFinishFunc)wp_proxy_node_new_finish));
if (!self->proxy_node)
return;
/* Give a proper name to this endpoint based on ALSA properties */
props = wp_proxy_node_get_info (self->proxy_node)->props;
if (0 == g_strcmp0(spa_dict_lookup (props, "device.api"), "alsa")) {
name = g_strdup_printf ("%s on %s (%s / node %d)",
spa_dict_lookup (props, "alsa.pcm.name"),
spa_dict_lookup (props, "alsa.card.name"),
spa_dict_lookup (props, "alsa.device"),
wp_proxy_node_get_info (self->proxy_node)->id);
g_object_set (self, "name", name, NULL);
}
/* Create the converter proxy */
target = wp_proxy_node_get_info (self->proxy_node);
g_return_if_fail (target);
wp_pw_audio_dsp_new (WP_ENDPOINT(self), WP_STREAM_ID_NONE, "master",
self->direction, TRUE, target, on_audio_dsp_converter_created, self);
}
static void
on_proxy_port_created(GObject *initable, GAsyncResult *res, gpointer data)
{
WpPwAudioSoftdspEndpoint *self = data;
struct pw_node_proxy *node_proxy = NULL;
/* Get the proxy port */
self->proxy_port = WP_PROXY_PORT (object_safe_new_finish (self, initable, res,
(WpObjectNewFinishFunc)wp_proxy_port_new_finish));
if (!self->proxy_port)
return;
/* Create the proxy node async */
node_proxy = wp_remote_pipewire_proxy_bind (self->remote_pipewire,
self->global_id, PW_TYPE_INTERFACE_Node);
g_return_if_fail(node_proxy);
wp_proxy_node_new(self->global_id, node_proxy, on_proxy_node_created, self);
}
static void
on_port_added(WpRemotePipewire *rp, guint id, guint parent_id, gconstpointer p,
gpointer d)
{
WpPwAudioSoftdspEndpoint *self = d;
struct pw_port_proxy *port_proxy = NULL;
/* Don't do anything if we are aborting */
if (self->init_abort)
return;
/* Check if it is a node port and handle it */
if (self->global_id != parent_id)
return;
/* Alsa nodes should have 1 port only, so make sure proxy_port is not set */
if (self->proxy_port != 0)
return;
/* Create the proxy port async */
port_proxy = wp_remote_pipewire_proxy_bind (self->remote_pipewire, id,
PW_TYPE_INTERFACE_Port);
g_return_if_fail(port_proxy);
wp_proxy_port_new(id, port_proxy, on_proxy_port_created, self);
}
static void
endpoint_finalize (GObject * object)
{
@@ -284,17 +212,11 @@ endpoint_finalize (GObject * object)
g_clear_pointer(&self->streams, g_variant_unref);
/* Destroy the proxy node */
g_clear_object(&self->proxy_node);
/* Destroy the proxy adapter */
g_clear_object(&self->adapter);
/* Destroy the proxy port */
g_clear_object(&self->proxy_port);
/* Destroy the proxy dsp converter */
g_clear_object(&self->converter);
/* Destroy all the proxy dsp streams */
g_clear_pointer (&self->dsps, g_ptr_array_unref);
/* Destroy all the converters */
g_clear_pointer (&self->converters, g_ptr_array_unref);
/* Destroy the done task */
g_clear_object(&self->init_task);
@@ -345,22 +267,22 @@ endpoint_get_control_value (WpEndpoint * ep, guint32 id)
{
WpPwAudioSoftdspEndpoint *self = WP_PW_AUDIO_SOFTDSP_ENDPOINT (ep);
guint stream_id, control_id;
WpPwAudioDsp *stream = NULL;
WpAudioStream *stream = NULL;
if (id == CONTROL_SELECTED)
return g_variant_new_boolean (self->selected);
wp_pw_audio_dsp_id_decode (id, &stream_id, &control_id);
wp_audio_stream_id_decode (id, &stream_id, &control_id);
/* Check if it is the master stream */
/* Check if it is the adapter (master) */
if (stream_id == WP_STREAM_ID_NONE)
return wp_pw_audio_dsp_get_control_value (self->converter, control_id);
return wp_audio_stream_get_control_value (self->adapter, control_id);
/* Otherwise get the stream_id and control_id */
g_return_val_if_fail (stream_id < self->dsps->len, NULL);
stream = g_ptr_array_index (self->dsps, stream_id);
g_return_val_if_fail (stream_id < self->converters->len, NULL);
stream = g_ptr_array_index (self->converters, stream_id);
g_return_val_if_fail (stream, NULL);
return wp_pw_audio_dsp_get_control_value (stream, control_id);
return wp_audio_stream_get_control_value (stream, control_id);
}
static gboolean
@@ -368,7 +290,7 @@ endpoint_set_control_value (WpEndpoint * ep, guint32 id, GVariant * value)
{
WpPwAudioSoftdspEndpoint *self = WP_PW_AUDIO_SOFTDSP_ENDPOINT (ep);
guint stream_id, control_id;
WpPwAudioDsp *stream = NULL;
WpAudioStream *stream = NULL;
if (id == CONTROL_SELECTED) {
self->selected = g_variant_get_boolean (value);
@@ -376,17 +298,17 @@ endpoint_set_control_value (WpEndpoint * ep, guint32 id, GVariant * value)
return TRUE;
}
wp_pw_audio_dsp_id_decode (id, &stream_id, &control_id);
wp_audio_stream_id_decode (id, &stream_id, &control_id);
/* Check if it is the master stream */
/* Check if it is the adapter (master) */
if (stream_id == WP_STREAM_ID_NONE)
return wp_pw_audio_dsp_set_control_value (self->converter, control_id, value);
return wp_audio_stream_set_control_value (self->adapter, control_id, value);
/* Otherwise get the stream_id and control_id */
g_return_val_if_fail (stream_id < self->dsps->len, FALSE);
stream = g_ptr_array_index (self->dsps, stream_id);
g_return_val_if_fail (stream_id < self->converters->len, FALSE);
stream = g_ptr_array_index (self->converters, stream_id);
g_return_val_if_fail (stream, FALSE);
return wp_pw_audio_dsp_set_control_value (stream, control_id, value);
return wp_audio_stream_set_control_value (stream, control_id, value);
}
static void
@@ -403,17 +325,15 @@ wp_endpoint_init_async (GAsyncInitable *initable, int io_priority,
/* Set the direction */
if (g_str_has_suffix (media_class, "Source"))
self->direction = PW_DIRECTION_INPUT;
else if (g_str_has_suffix (media_class, "Sink"))
self->direction = PW_DIRECTION_OUTPUT;
else if (g_str_has_suffix (media_class, "Sink"))
self->direction = PW_DIRECTION_INPUT;
else
g_critical ("failed to parse direction");
/* Register a port_added callback */
self->remote_pipewire = wp_core_get_global (core, WP_GLOBAL_REMOTE_PIPEWIRE);
g_return_if_fail(self->remote_pipewire);
g_signal_connect_object(self->remote_pipewire, "global-added::port",
(GCallback)on_port_added, self, 0);
/* Create the adapter proxy */
wp_audio_adapter_new (WP_ENDPOINT(self), WP_STREAM_ID_NONE, "master",
self->direction, self->global_id, FALSE, on_audio_adapter_created, self);
/* Register the selected control */
self->selected = FALSE;
@@ -445,7 +365,7 @@ static void
endpoint_init (WpPwAudioSoftdspEndpoint * self)
{
self->init_abort = FALSE;
self->dsps = g_ptr_array_new_with_free_func (g_object_unref);
self->converters = g_ptr_array_new_with_free_func (g_object_unref);
}
static void

View File

@@ -0,0 +1,303 @@
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author Julian Bouzas <julian.bouzas@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
#include <pipewire/pipewire.h>
#include <spa/param/audio/format-utils.h>
#include <spa/pod/builder.h>
#include <spa/param/props.h>
#include "adapter.h"
enum {
PROP_0,
PROP_ADAPTER_ID,
PROP_CONVERT,
};
struct _WpAudioAdapter
{
WpAudioStream parent;
/* The task to signal the proxy is initialized */
GTask *init_task;
gboolean init_abort;
/* Props */
guint adapter_id;
gboolean convert;
/* Proxies */
WpProxyNode *proxy;
};
static GAsyncInitableIface *wp_audio_adapter_parent_interface = NULL;
static void wp_audio_adapter_async_initable_init (gpointer iface,
gpointer iface_data);
G_DEFINE_TYPE_WITH_CODE (WpAudioAdapter, wp_audio_adapter, WP_TYPE_AUDIO_STREAM,
G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE,
wp_audio_adapter_async_initable_init))
typedef GObject* (*WpObjectNewFinishFunc)(GObject *initable, GAsyncResult *res,
GError **error);
static GObject *
object_safe_new_finish(WpAudioAdapter * self, GObject *initable,
GAsyncResult *res, WpObjectNewFinishFunc new_finish_func)
{
GObject *object = NULL;
GError *error = NULL;
/* Return NULL if we are already aborting */
if (self->init_abort)
return NULL;
/* Get the object */
object = G_OBJECT (new_finish_func (initable, res, &error));
g_return_val_if_fail (object, NULL);
/* Check for error */
if (error) {
g_clear_object (&object);
g_warning ("WpAudioAdapter:%p Aborting construction", self);
self->init_abort = TRUE;
g_task_return_error (self->init_task, error);
g_clear_object (&self->init_task);
return NULL;
}
return object;
}
static void
on_audio_adapter_done(WpProxy *proxy, gpointer data)
{
WpAudioAdapter *self = data;
/* Don't do anything if the audio adapter has already been initialized */
if (!self->init_task)
return;
/* Finish the creation of the audio adapter */
g_task_return_boolean (self->init_task, TRUE);
g_clear_object(&self->init_task);
}
static void
on_audio_adapter_proxy_created(GObject *initable, GAsyncResult *res,
gpointer data)
{
WpAudioAdapter *self = data;
enum pw_direction direction =
wp_audio_stream_get_direction ( WP_AUDIO_STREAM (self));
struct pw_node_proxy *pw_proxy = NULL;
uint8_t buf[1024];
struct spa_pod_builder pod_builder = SPA_POD_BUILDER_INIT(buf, sizeof(buf));
struct spa_pod *param;
/* Get the adapter proxy */
self->proxy = WP_PROXY_NODE (object_safe_new_finish (self, initable,
res, (WpObjectNewFinishFunc)wp_proxy_node_new_finish));
if (!self->proxy)
return;
/* Get the pipewire proxy */
pw_proxy = wp_proxy_get_pw_proxy(WP_PROXY(self->proxy));
g_return_if_fail (pw_proxy);
/* Emit the props param */
pw_node_proxy_enum_params (pw_proxy, 0, SPA_PARAM_Props, 0, -1, NULL);
/* Emit the ports */
if (self->convert) {
param = spa_pod_builder_add_object(&pod_builder,
SPA_TYPE_OBJECT_ParamPortConfig, SPA_PARAM_PortConfig,
SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(direction),
SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(SPA_PARAM_PORT_CONFIG_MODE_convert));
pw_node_proxy_set_param(pw_proxy, SPA_PARAM_PortConfig, 0, param);
} else {
struct spa_audio_info_raw format;
spa_zero(format);
format.format = SPA_AUDIO_FORMAT_F32P;
format.flags = 1;
format.rate = 48000;
format.channels = 2;
format.position[0] = SPA_AUDIO_CHANNEL_FL;
format.position[1] = SPA_AUDIO_CHANNEL_FR;
param = spa_format_audio_raw_build(&pod_builder, SPA_PARAM_Format, &format);
param = spa_pod_builder_add_object(&pod_builder,
SPA_TYPE_OBJECT_ParamPortConfig, SPA_PARAM_PortConfig,
SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(direction),
SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(SPA_PARAM_PORT_CONFIG_MODE_dsp),
SPA_PARAM_PORT_CONFIG_format, SPA_POD_Pod(param));
pw_node_proxy_set_param(pw_proxy, SPA_PARAM_PortConfig, 0, param);
}
/* Register a callback to know when all the adapter ports have been emitted */
g_signal_connect_object(self->proxy, "done", (GCallback)on_audio_adapter_done,
self, 0);
wp_proxy_sync (WP_PROXY(self->proxy));
}
static gpointer
wp_audio_adapter_create_proxy (WpAudioStream * as, WpRemotePipewire *rp)
{
WpAudioAdapter * self = WP_AUDIO_ADAPTER (as);
struct pw_node_proxy *proxy = NULL;
/* Create the adapter proxy by binding it */
proxy = wp_remote_pipewire_proxy_bind (rp, self->adapter_id,
PW_TYPE_INTERFACE_Node);
g_return_val_if_fail (proxy, NULL);
wp_proxy_node_new(self->adapter_id, proxy, on_audio_adapter_proxy_created,
self);
return proxy;
}
static gconstpointer
wp_audio_adapter_get_info (WpAudioStream * as)
{
WpAudioAdapter * self = WP_AUDIO_ADAPTER (as);
g_return_val_if_fail (self->proxy, NULL);
/* Return the info */
return wp_proxy_node_get_info (self->proxy);
}
static void
wp_audio_adapter_init_async (GAsyncInitable *initable, int io_priority,
GCancellable *cancellable, GAsyncReadyCallback callback, gpointer data)
{
WpAudioAdapter *self = WP_AUDIO_ADAPTER(initable);
/* Create the async task */
self->init_task = g_task_new (initable, cancellable, callback, data);
/* Call the parent interface */
wp_audio_adapter_parent_interface->init_async (initable, io_priority,
cancellable, callback, data);
}
static void
wp_audio_adapter_async_initable_init (gpointer iface, gpointer iface_data)
{
GAsyncInitableIface *ai_iface = iface;
/* Set the parent interface */
wp_audio_adapter_parent_interface = g_type_interface_peek_parent (iface);
ai_iface->init_async = wp_audio_adapter_init_async;
}
static void
wp_audio_adapter_set_property (GObject * object, guint property_id,
const GValue * value, GParamSpec * pspec)
{
WpAudioAdapter *self = WP_AUDIO_ADAPTER (object);
switch (property_id) {
case PROP_ADAPTER_ID:
self->adapter_id = g_value_get_uint(value);
break;
case PROP_CONVERT:
self->convert = g_value_get_boolean(value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
wp_audio_adapter_get_property (GObject * object, guint property_id,
GValue * value, GParamSpec * pspec)
{
WpAudioAdapter *self = WP_AUDIO_ADAPTER (object);
switch (property_id) {
case PROP_ADAPTER_ID:
g_value_set_uint (value, self->adapter_id);
break;
case PROP_CONVERT:
g_value_set_boolean (value, self->convert);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
wp_audio_adapter_finalize (GObject * object)
{
WpAudioAdapter *self = WP_AUDIO_ADAPTER(object);
/* Destroy the proxy */
g_clear_object(&self->proxy);
G_OBJECT_CLASS (wp_audio_adapter_parent_class)->finalize (object);
}
static void
wp_audio_adapter_init (WpAudioAdapter * self)
{
self->init_abort = FALSE;
}
static void
wp_audio_adapter_class_init (WpAudioAdapterClass * klass)
{
GObjectClass *object_class = (GObjectClass *) klass;
WpAudioStreamClass *audio_stream_class = (WpAudioStreamClass *) klass;
object_class->finalize = wp_audio_adapter_finalize;
object_class->set_property = wp_audio_adapter_set_property;
object_class->get_property = wp_audio_adapter_get_property;
audio_stream_class->create_proxy = wp_audio_adapter_create_proxy;
audio_stream_class->get_info = wp_audio_adapter_get_info;
/* Install the properties */
g_object_class_install_property (object_class, PROP_ADAPTER_ID,
g_param_spec_uint ("adapter-id", "adapter-id", "The Id of the adapter", 0,
G_MAXUINT, 0,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_CONVERT,
g_param_spec_boolean ("convert", "convert", "Do convert only or not",
FALSE,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
}
void
wp_audio_adapter_new (WpEndpoint *endpoint, guint stream_id,
const char *stream_name, enum pw_direction direction, guint adapter_id,
gboolean convert, GAsyncReadyCallback callback, gpointer user_data)
{
g_async_initable_new_async (
WP_TYPE_AUDIO_ADAPTER, G_PRIORITY_DEFAULT, NULL, callback, user_data,
"endpoint", endpoint,
"id", stream_id,
"name", stream_name,
"direction", direction,
"adapter-id", adapter_id,
"convert", convert,
NULL);
}
guint
wp_audio_adapter_get_adapter_id (WpAudioAdapter *self)
{
return self->adapter_id;
}
gboolean
wp_audio_adapter_is_convert (WpAudioAdapter *self)
{
return self->convert;
}

View File

@@ -0,0 +1,32 @@
/* 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
#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 (WpEndpoint *endpoint, guint stream_id,
const char *stream_name, enum pw_direction direction, guint adapter_id,
gboolean convert, GAsyncReadyCallback callback, gpointer user_data);
guint wp_audio_adapter_get_adapter_id (WpAudioAdapter *self);
gboolean wp_audio_adapter_is_convert (WpAudioAdapter *self);
G_END_DECLS
#endif

View File

@@ -0,0 +1,389 @@
/* 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"
enum {
PROP_0,
PROP_TARGET,
};
struct _WpAudioConvert
{
WpAudioStream parent;
/* The task to signal the audio convert is initialized */
GTask *init_task;
gboolean init_abort;
/* Props */
const struct pw_node_info *target;
/* Proxies */
WpProxyNode *proxy;
WpProxyLink *link_proxy;
};
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))
typedef GObject* (*WpObjectNewFinishFunc)(GObject *initable, GAsyncResult *res,
GError **error);
static GObject *
object_safe_new_finish(WpAudioConvert * self, GObject *initable,
GAsyncResult *res, WpObjectNewFinishFunc new_finish_func)
{
GObject *object = NULL;
GError *error = NULL;
/* Return NULL if we are already aborting */
if (self->init_abort)
return NULL;
/* Get the object */
object = G_OBJECT (new_finish_func (initable, res, &error));
g_return_val_if_fail (object, NULL);
/* Check for error */
if (error) {
g_clear_object (&object);
g_warning ("WpAudioConvert:%p Aborting construction", self);
self->init_abort = TRUE;
g_task_return_error (self->init_task, error);
g_clear_object (&self->init_task);
return NULL;
}
return object;
}
static void
on_audio_convert_done(WpProxy *proxy, gpointer data)
{
WpAudioConvert *self = data;
/* Don't do anything if the endpoint has already been initialized */
if (!self->init_task)
return;
/* Finish the creation of the audio convert */
g_task_return_boolean (self->init_task, TRUE);
g_clear_object(&self->init_task);
}
static void
on_proxy_link_created(GObject *initable, GAsyncResult *res, gpointer data)
{
WpAudioConvert *self = data;
/* Get the link */
self->link_proxy = WP_PROXY_LINK (object_safe_new_finish (self, initable,
res, (WpObjectNewFinishFunc)wp_proxy_link_new_finish));
g_return_if_fail (self->link_proxy);
}
static void
on_audio_convert_running(WpAudioConvert *self, WpRemotePipewire *rp)
{
enum pw_direction direction =
wp_audio_stream_get_direction ( WP_AUDIO_STREAM (self));
struct pw_properties *props;
const struct pw_node_info *info = NULL;
struct pw_proxy *proxy = NULL;
/* Return if the node has already been linked */
if (self->link_proxy)
return;
/* Get the info */
info = wp_proxy_node_get_info(self->proxy);
g_return_if_fail (info);
/* Create new properties */
props = pw_properties_new(NULL, NULL);
/* Set the new properties */
pw_properties_set(props, PW_KEY_LINK_PASSIVE, "true");
if (direction == PW_DIRECTION_INPUT) {
pw_properties_setf(props, PW_KEY_LINK_OUTPUT_NODE, "%d", info->id);
pw_properties_setf(props, PW_KEY_LINK_OUTPUT_PORT, "%d", -1);
pw_properties_setf(props, PW_KEY_LINK_INPUT_NODE, "%d", self->target->id);
pw_properties_setf(props, PW_KEY_LINK_INPUT_PORT, "%d", -1);
} else {
pw_properties_setf(props, PW_KEY_LINK_OUTPUT_NODE, "%d", self->target->id);
pw_properties_setf(props, PW_KEY_LINK_OUTPUT_PORT, "%d", -1);
pw_properties_setf(props, PW_KEY_LINK_INPUT_NODE, "%d", info->id);
pw_properties_setf(props, PW_KEY_LINK_INPUT_PORT, "%d", -1);
}
g_debug ("%p linking audio convert to target", self);
/* Create the link */
proxy = wp_remote_pipewire_create_object(rp, "link-factory",
PW_TYPE_INTERFACE_Link, &props->dict);
wp_proxy_link_new (pw_proxy_get_id(proxy), proxy, on_proxy_link_created,
self);
/* Clean up */
pw_properties_free(props);
}
static void
on_audio_convert_idle (WpAudioConvert *self, WpRemotePipewire *rp)
{
/* Clear the proxy */
g_clear_object (&self->link_proxy);
}
static void
on_audio_convert_proxy_created(GObject *initable, GAsyncResult *res,
gpointer data)
{
WpAudioConvert *self = data;
enum pw_direction direction =
wp_audio_stream_get_direction ( WP_AUDIO_STREAM (self));
struct pw_node_proxy *pw_proxy = NULL;
struct spa_audio_info_raw format;
uint8_t buf[1024];
struct spa_pod_builder pod_builder = SPA_POD_BUILDER_INIT(buf, sizeof(buf));
struct spa_pod *param;
/* Get the convert proxy */
self->proxy = WP_PROXY_NODE (object_safe_new_finish (self, initable,
res, (WpObjectNewFinishFunc)wp_proxy_node_new_finish));
if (!self->proxy)
return;
/* Get the pipewire proxy */
pw_proxy = wp_proxy_get_pw_proxy(WP_PROXY(self->proxy));
g_return_if_fail (pw_proxy);
/* Emit the props param */
pw_node_proxy_enum_params (pw_proxy, 0, SPA_PARAM_Props, 0, -1, NULL);
/* Use the default format */
format.format = SPA_AUDIO_FORMAT_F32P;
format.flags = 1;
format.rate = 48000;
format.channels = 2;
format.position[0] = SPA_AUDIO_CHANNEL_FL;
format.position[1] = SPA_AUDIO_CHANNEL_FR;
/* Emit the ports */
param = spa_format_audio_raw_build(&pod_builder, SPA_PARAM_Format, &format);
param = spa_pod_builder_add_object(&pod_builder,
SPA_TYPE_OBJECT_ParamPortConfig, SPA_PARAM_PortConfig,
SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(direction),
SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(SPA_PARAM_PORT_CONFIG_MODE_dsp),
SPA_PARAM_PORT_CONFIG_format, SPA_POD_Pod(param));
pw_node_proxy_set_param(pw_proxy, SPA_PARAM_PortConfig, 0, param);
/* Register a callback to know when all the convert ports have been emitted */
g_signal_connect_object(self->proxy, "done", (GCallback)on_audio_convert_done,
self, 0);
wp_proxy_sync (WP_PROXY(self->proxy));
}
static void
wp_audio_convert_event_info (WpAudioStream * as, gconstpointer i,
WpRemotePipewire *rp)
{
WpAudioConvert * self = WP_AUDIO_CONVERT (as);
const struct pw_node_info *info = i;
/* Handle the different states */
switch (info->state) {
case PW_NODE_STATE_IDLE:
on_audio_convert_idle (self, rp);
break;
case PW_NODE_STATE_RUNNING:
on_audio_convert_running (self, rp);
break;
case PW_NODE_STATE_SUSPENDED:
break;
default:
break;
}
}
static gpointer
wp_audio_convert_create_proxy (WpAudioStream * as, WpRemotePipewire *rp)
{
WpAudioConvert * self = WP_AUDIO_CONVERT (as);
const char *name = wp_audio_stream_get_name (as);
struct pw_properties *props = NULL;
struct pw_node_proxy *proxy = NULL;
/* Create the properties */
g_return_val_if_fail (self->target, NULL);
props = pw_properties_new_dict(self->target->props);
g_return_val_if_fail (props, NULL);
pw_properties_set(props, PW_KEY_NODE_NAME, name);
pw_properties_set(props, PW_KEY_MEDIA_CLASS, "Audio/Convert");
pw_properties_set(props, "factory.name", SPA_NAME_AUDIO_CONVERT);
/* Create the proxy async */
proxy = wp_remote_pipewire_create_object(rp, "spa-node-factory",
PW_TYPE_INTERFACE_Node, &props->dict);
g_return_val_if_fail (proxy, NULL);
wp_proxy_node_new(pw_proxy_get_id((struct pw_proxy *)proxy), proxy,
on_audio_convert_proxy_created, self);
/* Clean up */
pw_properties_free(props);
return proxy;
}
static gconstpointer
wp_audio_convert_get_info (WpAudioStream * as)
{
WpAudioConvert * self = WP_AUDIO_CONVERT (as);
/* Make sure proxy is valid */
if (!self->proxy)
return NULL;
/* Return the info */
return wp_proxy_node_get_info (self->proxy);
}
static void
wp_audio_convert_init_async (GAsyncInitable *initable, int io_priority,
GCancellable *cancellable, GAsyncReadyCallback callback, gpointer data)
{
WpAudioConvert *self = WP_AUDIO_CONVERT (initable);
/* Create the async task */
self->init_task = g_task_new (initable, cancellable, callback, data);
/* Call the parent interface */
wp_audio_convert_parent_interface->init_async (initable, io_priority,
cancellable, callback, data);
}
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_get_pointer(value);
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_pointer (value, (gpointer)self->target);
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);
/* Destroy the init task */
g_clear_object(&self->init_task);
/* Destroy the proxy */
g_clear_object(&self->proxy);
/* Destroy the link proxy */
g_clear_object (&self->link_proxy);
G_OBJECT_CLASS (wp_audio_convert_parent_class)->finalize (object);
}
static void
wp_audio_convert_init (WpAudioConvert * self)
{
self->init_abort = FALSE;
}
static void
wp_audio_convert_class_init (WpAudioConvertClass * klass)
{
GObjectClass *object_class = (GObjectClass *) klass;
WpAudioStreamClass *audio_stream_class = (WpAudioStreamClass *) 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;
audio_stream_class->create_proxy = wp_audio_convert_create_proxy;
audio_stream_class->get_info = wp_audio_convert_get_info;
audio_stream_class->event_info = wp_audio_convert_event_info;
/* Install the properties */
g_object_class_install_property (object_class, PROP_TARGET,
g_param_spec_pointer ("target", "target",
"The target stream info",
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
}
void
wp_audio_convert_new (WpEndpoint *endpoint, guint stream_id,
const char *stream_name, enum pw_direction direction,
const struct pw_node_info *target, 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,
NULL);
}
const struct pw_node_info *
wp_audio_convert_get_target (WpAudioConvert *self)
{
return self->target;
}

View File

@@ -0,0 +1,32 @@
/* 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
#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 (WpEndpoint *endpoint, guint stream_id,
const char *stream_name, enum pw_direction direction,
const struct pw_node_info *target, GAsyncReadyCallback callback,
gpointer user_data);
const struct pw_node_info *wp_audio_convert_get_target (WpAudioConvert *self);
G_END_DECLS
#endif

View File

@@ -1,785 +0,0 @@
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author Julian Bouzas <julian.bouzas@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
#include <pipewire/pipewire.h>
#include <spa/param/audio/format-utils.h>
#include <spa/pod/builder.h>
#include <spa/param/props.h>
#include "dsp.h"
#define MIN_QUANTUM_SIZE 64
#define MAX_QUANTUM_SIZE 1024
enum {
PROP_0,
PROP_ENDPOINT,
PROP_ID,
PROP_NAME,
PROP_DIRECTION,
PROP_CONVERT,
PROP_TARGET,
};
enum {
CONTROL_VOLUME = 0,
CONTROL_MUTE,
N_CONTROLS,
};
struct _WpPwAudioDsp
{
GObject parent;
/* The task to signal the audio dsp is initialized */
GTask *init_task;
gboolean init_abort;
/* The remote pipewire */
WpRemotePipewire *remote_pipewire;
/* Props */
GWeakRef endpoint;
guint id;
gchar *name;
enum pw_direction direction;
gboolean convert;
const struct pw_node_info *target;
/* All ports handled by the port added callback */
GHashTable *handled_ports;
/* Proxies */
WpProxyNode *proxy;
GPtrArray *port_proxies;
WpProxyLink *link_proxy;
/* Listener */
struct spa_hook listener;
/* Volume */
gfloat volume;
gboolean mute;
};
static void wp_pw_audio_dsp_async_initable_init (gpointer iface,
gpointer iface_data);
G_DEFINE_TYPE_WITH_CODE (WpPwAudioDsp, wp_pw_audio_dsp, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE,
wp_pw_audio_dsp_async_initable_init))
typedef GObject* (*WpObjectNewFinishFunc)(GObject *initable, GAsyncResult *res,
GError **error);
static GObject *
object_safe_new_finish(WpPwAudioDsp * self, GObject *initable,
GAsyncResult *res, WpObjectNewFinishFunc new_finish_func)
{
GObject *object = NULL;
GError *error = NULL;
/* Return NULL if we are already aborting */
if (self->init_abort)
return NULL;
/* Get the object */
object = G_OBJECT (new_finish_func (initable, res, &error));
g_return_val_if_fail (object, NULL);
/* Check for error */
if (error) {
g_clear_object (&object);
g_warning ("WpPwAudioDsp:%p Aborting construction", self);
self->init_abort = TRUE;
g_task_return_error (self->init_task, error);
g_clear_object (&self->init_task);
return NULL;
}
return object;
}
guint
wp_pw_audio_dsp_id_encode (guint stream_id, guint control_id)
{
g_return_val_if_fail (control_id < N_CONTROLS, 0);
/* encode NONE as 0 and everything else with +1 */
/* NONE is MAX_UINT, so +1 will do the trick */
stream_id += 1;
/* Encode the stream and control Ids. The first ID is reserved
* for the "selected" control, registered in the endpoint */
return 1 + (stream_id * N_CONTROLS) + control_id;
}
void
wp_pw_audio_dsp_id_decode (guint id, guint *stream_id, guint *control_id)
{
guint s_id, c_id;
g_return_if_fail (id >= 1);
id -= 1;
/* Decode the stream and control Ids */
s_id = (id / N_CONTROLS) - 1;
c_id = id % N_CONTROLS;
/* Set the output params */
if (stream_id)
*stream_id = s_id;
if (control_id)
*control_id = c_id;
}
static void
register_controls (WpPwAudioDsp * self)
{
GVariantDict d;
g_autoptr (WpEndpoint) ep = g_weak_ref_get (&self->endpoint);
g_return_if_fail (ep);
/* Register the volume control */
g_variant_dict_init (&d, NULL);
g_variant_dict_insert (&d, "id", "u",
wp_pw_audio_dsp_id_encode (self->id, CONTROL_VOLUME));
if (self->id != WP_STREAM_ID_NONE)
g_variant_dict_insert (&d, "stream-id", "u", self->id);
g_variant_dict_insert (&d, "name", "s", "volume");
g_variant_dict_insert (&d, "type", "s", "d");
g_variant_dict_insert (&d, "range", "(dd)", 0.0, 1.0);
g_variant_dict_insert (&d, "default-value", "d", self->volume);
wp_endpoint_register_control (ep, g_variant_dict_end (&d));
/* Register the mute control */
g_variant_dict_init (&d, NULL);
g_variant_dict_insert (&d, "id", "u",
wp_pw_audio_dsp_id_encode (self->id, CONTROL_MUTE));
if (self->id != WP_STREAM_ID_NONE)
g_variant_dict_insert (&d, "stream-id", "u", self->id);
g_variant_dict_insert (&d, "name", "s", "mute");
g_variant_dict_insert (&d, "type", "s", "b");
g_variant_dict_insert (&d, "default-value", "b", self->mute);
wp_endpoint_register_control (ep, g_variant_dict_end (&d));
}
static void
on_audio_dsp_port_created(GObject *initable, GAsyncResult *res,
gpointer data)
{
WpPwAudioDsp *self = data;
WpProxyPort *port_proxy = NULL;
/* Get the proxy port */
port_proxy = WP_PROXY_PORT (object_safe_new_finish (self, initable, res,
(WpObjectNewFinishFunc)wp_proxy_port_new_finish));
if (!port_proxy)
return;
/* Add the proxy port to the array */
g_return_if_fail (self->port_proxies);
g_ptr_array_add(self->port_proxies, port_proxy);
}
static void
handled_ports_foreach_func (gpointer key, gpointer value, gpointer data)
{
WpPwAudioDsp *self = data;
const struct pw_node_info *dsp_info = NULL;
struct pw_port_proxy *port_proxy = NULL;
const guint id = GPOINTER_TO_INT (key);
const guint parent_id = GPOINTER_TO_INT (value);
/* Don't do anything if we are aborting */
if (self->init_abort)
return;
/* Get the dsp info */
g_return_if_fail (self->proxy);
dsp_info = wp_proxy_node_get_info(self->proxy);
g_return_if_fail (dsp_info);
/* Skip ports that are not owned by this DSP */
if (dsp_info->id != parent_id)
return;
/* Create the audio dsp port async */
port_proxy = wp_remote_pipewire_proxy_bind (self->remote_pipewire, id,
PW_TYPE_INTERFACE_Port);
g_return_if_fail(port_proxy);
wp_proxy_port_new(id, port_proxy, on_audio_dsp_port_created, self);
}
static void
on_audio_dsp_done(WpProxy *proxy, gpointer data)
{
WpPwAudioDsp *self = data;
g_return_if_fail (self->proxy);
/* Don't do anything if the endpoint has already been initialized */
if (!self->init_task)
return;
/* Create the proxis and sync to trigger this function again */
if (self->port_proxies->len == 0) {
g_hash_table_foreach (self->handled_ports, handled_ports_foreach_func, self);
wp_proxy_sync (WP_PROXY(self->proxy));
return;
}
/* Register the controls */
register_controls (self);
/* Finish the creation of the audio dsp */
g_task_return_boolean (self->init_task, TRUE);
g_clear_object(&self->init_task);
}
static void
on_audio_dsp_port_added(WpRemotePipewire *rp, guint id, guint parent_id,
gconstpointer p, gpointer d)
{
WpPwAudioDsp *self = d;
/* Add the port to the map if it is not already there */
if (!g_hash_table_contains (self->handled_ports, GUINT_TO_POINTER (id)))
g_hash_table_insert (self->handled_ports, GUINT_TO_POINTER(id),
GUINT_TO_POINTER(parent_id));
}
static void
on_proxy_link_created(GObject *initable, GAsyncResult *res, gpointer data)
{
WpPwAudioDsp *self = data;
/* Get the link */
self->link_proxy = WP_PROXY_LINK (object_safe_new_finish (self, initable,
res, (WpObjectNewFinishFunc)wp_proxy_link_new_finish));
g_return_if_fail (self->link_proxy);
}
static void
on_audio_dsp_running(WpPwAudioDsp *self)
{
struct pw_properties *props;
const struct pw_node_info *dsp_info = NULL;
struct pw_proxy *proxy = NULL;
/* Return if the node has already been linked */
if (self->link_proxy)
return;
/* Get the dsp info */
dsp_info = wp_proxy_node_get_info(self->proxy);
g_return_if_fail (dsp_info);
/* Create new properties */
props = pw_properties_new(NULL, NULL);
/* Set the new properties */
pw_properties_set(props, PW_KEY_LINK_PASSIVE, "true");
if (self->direction == PW_DIRECTION_OUTPUT) {
pw_properties_setf(props, PW_KEY_LINK_OUTPUT_NODE, "%d", dsp_info->id);
pw_properties_setf(props, PW_KEY_LINK_OUTPUT_PORT, "%d", -1);
pw_properties_setf(props, PW_KEY_LINK_INPUT_NODE, "%d", self->target->id);
pw_properties_setf(props, PW_KEY_LINK_INPUT_PORT, "%d", -1);
} else {
pw_properties_setf(props, PW_KEY_LINK_OUTPUT_NODE, "%d", self->target->id);
pw_properties_setf(props, PW_KEY_LINK_OUTPUT_PORT, "%d", -1);
pw_properties_setf(props, PW_KEY_LINK_INPUT_NODE, "%d", dsp_info->id);
pw_properties_setf(props, PW_KEY_LINK_INPUT_PORT, "%d", -1);
}
g_debug ("%p linking DSP to node", self);
/* Create the link */
proxy = wp_remote_pipewire_create_object(self->remote_pipewire,
"link-factory", PW_TYPE_INTERFACE_Link, &props->dict);
wp_proxy_link_new (pw_proxy_get_id(proxy), proxy, on_proxy_link_created,
self);
/* Clean up */
pw_properties_free(props);
}
static void
on_audio_dsp_idle (WpPwAudioDsp *self)
{
/* Clear the proxy */
g_clear_object (&self->link_proxy);
}
static void
audio_dsp_event_info (void *data, const struct pw_node_info *info)
{
WpPwAudioDsp *self = data;
/* Handle the different states */
switch (info->state) {
case PW_NODE_STATE_IDLE:
on_audio_dsp_idle (self);
break;
case PW_NODE_STATE_RUNNING:
on_audio_dsp_running (self);
break;
case PW_NODE_STATE_SUSPENDED:
break;
default:
break;
}
}
static void
audio_dsp_event_param (void *object, int seq, uint32_t id,
uint32_t index, uint32_t next, const struct spa_pod *param)
{
WpPwAudioDsp *self = WP_PW_AUDIO_DSP (object);
g_autoptr (WpEndpoint) ep = g_weak_ref_get (&self->endpoint);
switch (id) {
case SPA_PARAM_Props:
{
struct spa_pod_prop *prop;
struct spa_pod_object *obj = (struct spa_pod_object *) param;
float volume = self->volume;
bool mute = self->mute;
SPA_POD_OBJECT_FOREACH(obj, prop) {
switch (prop->key) {
case SPA_PROP_volume:
spa_pod_get_float(&prop->value, &volume);
break;
case SPA_PROP_mute:
spa_pod_get_bool(&prop->value, &mute);
break;
default:
break;
}
}
g_debug ("WpPwAudioDsp:%p param event, vol:(%lf -> %f) mute:(%d -> %d)",
self, self->volume, volume, self->mute, mute);
if (self->volume != volume) {
self->volume = volume;
wp_endpoint_notify_control_value (ep,
wp_pw_audio_dsp_id_encode (self->id, CONTROL_VOLUME));
}
if (self->mute != mute) {
self->mute = mute;
wp_endpoint_notify_control_value (ep,
wp_pw_audio_dsp_id_encode (self->id, CONTROL_MUTE));
}
break;
}
default:
break;
}
}
static const struct pw_node_proxy_events audio_dsp_proxy_events = {
PW_VERSION_NODE_PROXY_EVENTS,
.info = audio_dsp_event_info,
.param = audio_dsp_event_param,
};
static void
on_audio_dsp_proxy_created(GObject *initable, GAsyncResult *res,
gpointer data)
{
WpPwAudioDsp *self = data;
struct pw_node_proxy *pw_proxy = NULL;
struct spa_audio_info_raw format;
uint8_t buf[1024];
struct spa_pod_builder pod_builder = SPA_POD_BUILDER_INIT(buf, sizeof(buf));
struct spa_pod *param;
/* Get the audio dsp proxy */
self->proxy = WP_PROXY_NODE (object_safe_new_finish (self, initable,
res, (WpObjectNewFinishFunc)wp_proxy_node_new_finish));
if (!self->proxy)
return;
/* Add a custom dsp listener */
pw_proxy = wp_proxy_get_pw_proxy(WP_PROXY(self->proxy));
g_return_if_fail (pw_proxy);
pw_node_proxy_add_listener(pw_proxy, &self->listener,
&audio_dsp_proxy_events, self);
/* Emit the props param */
pw_node_proxy_enum_params (pw_proxy, 0, SPA_PARAM_Props, 0, -1, NULL);
if (!self->convert) {
/* Use the default format */
format.format = SPA_AUDIO_FORMAT_F32P;
format.flags = 1;
format.rate = 48000;
format.channels = 2;
format.position[0] = SPA_AUDIO_CHANNEL_FL;
format.position[1] = SPA_AUDIO_CHANNEL_FR;
/* Emit the ports */
param = spa_format_audio_raw_build(&pod_builder, SPA_PARAM_Format, &format);
param = spa_pod_builder_add_object(&pod_builder,
SPA_TYPE_OBJECT_ParamProfile, SPA_PARAM_Profile,
SPA_PARAM_PROFILE_direction, SPA_POD_Id(pw_direction_reverse(self->direction)),
SPA_PARAM_PROFILE_format, SPA_POD_Pod(param));
pw_node_proxy_set_param(pw_proxy, SPA_PARAM_Profile, 0, param);
}
/* Register a callback to know when all the dsp ports have been emitted */
g_signal_connect_object(self->proxy, "done", (GCallback)on_audio_dsp_done,
self, 0);
wp_proxy_sync (WP_PROXY(self->proxy));
}
static void
wp_pw_audio_dsp_finalize (GObject * object)
{
WpPwAudioDsp *self = WP_PW_AUDIO_DSP (object);
/* Props */
g_weak_ref_clear (&self->endpoint);
g_free (self->name);
/* Destroy the init task */
g_clear_object(&self->init_task);
/* Destroy the handled ports map */
g_hash_table_unref(self->handled_ports);
self->handled_ports = NULL;
/* Destroy the proxy dsp */
g_clear_object(&self->proxy);
/* Destroy the proxies port */
if (self->port_proxies) {
g_ptr_array_free(self->port_proxies, TRUE);
self->port_proxies = NULL;
}
/* Destroy the link proxy */
g_clear_object (&self->link_proxy);
G_OBJECT_CLASS (wp_pw_audio_dsp_parent_class)->finalize (object);
}
static void
wp_pw_audio_dsp_set_property (GObject * object, guint property_id,
const GValue * value, GParamSpec * pspec)
{
WpPwAudioDsp *self = WP_PW_AUDIO_DSP (object);
switch (property_id) {
case PROP_ENDPOINT:
g_weak_ref_set (&self->endpoint, g_value_get_object (value));
break;
case PROP_ID:
self->id = g_value_get_uint(value);
break;
case PROP_NAME:
self->name = g_value_dup_string (value);
break;
case PROP_DIRECTION:
self->direction = g_value_get_uint(value);
break;
case PROP_CONVERT:
self->convert = g_value_get_boolean(value);
break;
case PROP_TARGET:
self->target = g_value_get_pointer(value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
wp_pw_audio_dsp_get_property (GObject * object, guint property_id,
GValue * value, GParamSpec * pspec)
{
WpPwAudioDsp *self = WP_PW_AUDIO_DSP (object);
switch (property_id) {
case PROP_ENDPOINT:
g_value_take_object (value, g_weak_ref_get (&self->endpoint));
break;
case PROP_ID:
g_value_set_uint (value, self->id);
break;
case PROP_NAME:
g_value_set_string (value, self->name);
break;
case PROP_DIRECTION:
g_value_set_uint (value, self->direction);
break;
case PROP_CONVERT:
g_value_set_boolean (value, self->convert);
break;
case PROP_TARGET:
g_value_set_pointer (value, (gpointer)self->target);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
wp_pw_audio_dsp_init_async (GAsyncInitable *initable, int io_priority,
GCancellable *cancellable, GAsyncReadyCallback callback, gpointer data)
{
WpPwAudioDsp *self = WP_PW_AUDIO_DSP (initable);
struct pw_properties *props;
struct pw_node_proxy *proxy;
/* Set the remote pipewire */
g_autoptr (WpEndpoint) ep = g_weak_ref_get (&self->endpoint);
g_return_if_fail(ep);
g_autoptr (WpCore) wp_core = wp_endpoint_get_core(ep);
g_return_if_fail(wp_core);
self->remote_pipewire =
wp_core_get_global (wp_core, WP_GLOBAL_REMOTE_PIPEWIRE);
g_return_if_fail(self->remote_pipewire);
/* Create the async task */
self->init_task = g_task_new (initable, cancellable, callback, data);
/* Init the handled ports map */
self->handled_ports = g_hash_table_new (g_direct_hash, g_direct_equal);
/* Init the list of port proxies */
self->port_proxies = g_ptr_array_new_full(4, (GDestroyNotify)g_object_unref);
/* Set the default volume */
self->volume = 1.0;
self->mute = FALSE;
/* Create the properties */
props = pw_properties_new_dict(self->target->props);
g_return_if_fail (props);
/* Set the properties */
pw_properties_set(props, "audio-dsp.name",
self->name ? self->name : "Audio-DSP");
pw_properties_set(props, "audio-dsp.mode", self->convert ? "convert" : NULL);
pw_properties_setf(props, "audio-dsp.direction", "%d", self->direction);
pw_properties_setf(props, "audio-dsp.maxbuffer", "%ld",
MAX_QUANTUM_SIZE * sizeof(float));
/* Register a port_added callback */
g_signal_connect_object(self->remote_pipewire, "global-added::port",
(GCallback)on_audio_dsp_port_added, self, 0);
/* Create the proxy async */
proxy = wp_remote_pipewire_create_object(self->remote_pipewire,
"audio-dsp", PW_TYPE_INTERFACE_Node, &props->dict);
wp_proxy_node_new(pw_proxy_get_id((struct pw_proxy *)proxy), proxy,
on_audio_dsp_proxy_created, self);
/* Clean up */
pw_properties_free(props);
}
static gboolean
wp_pw_audio_dsp_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_pw_audio_dsp_async_initable_init (gpointer iface, gpointer iface_data)
{
GAsyncInitableIface *ai_iface = iface;
ai_iface->init_async = wp_pw_audio_dsp_init_async;
ai_iface->init_finish = wp_pw_audio_dsp_init_finish;
}
static void
wp_pw_audio_dsp_init (WpPwAudioDsp * self)
{
self->init_abort = FALSE;
}
static void
wp_pw_audio_dsp_class_init (WpPwAudioDspClass * klass)
{
GObjectClass *object_class = (GObjectClass *) klass;
object_class->finalize = wp_pw_audio_dsp_finalize;
object_class->set_property = wp_pw_audio_dsp_set_property;
object_class->get_property = wp_pw_audio_dsp_get_property;
/* Install the properties */
g_object_class_install_property (object_class, PROP_ENDPOINT,
g_param_spec_object ("endpoint", "endpoint",
"The endpoint this audio DSP belongs to", WP_TYPE_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 DSP", 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 DSP", NULL,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_DIRECTION,
g_param_spec_uint ("direction", "direction",
"The direction of the audio DSP", 0, 1, 0,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_CONVERT,
g_param_spec_boolean ("convert", "convert",
"Whether the DSP is only in convert mode or not", FALSE,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_TARGET,
g_param_spec_pointer ("target", "target",
"The target node info of the audio DSP",
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
}
void
wp_pw_audio_dsp_new (WpEndpoint *endpoint, guint id, const char *name,
enum pw_direction direction, gboolean convert,
const struct pw_node_info *target, GAsyncReadyCallback callback,
gpointer user_data)
{
g_async_initable_new_async (
wp_pw_audio_dsp_get_type (), G_PRIORITY_DEFAULT, NULL,
callback, user_data,
"endpoint", endpoint,
"id", id,
"name", name,
"direction", direction,
"convert", convert,
"target", target,
NULL);
}
WpPwAudioDsp *
wp_pw_audio_dsp_new_finish (GObject *initable, GAsyncResult *res,
GError **error)
{
GAsyncInitable *ai = G_ASYNC_INITABLE(initable);
return WP_PW_AUDIO_DSP(g_async_initable_new_finish(ai, res, error));
}
const struct pw_node_info *
wp_pw_audio_dsp_get_info (WpPwAudioDsp * self)
{
return wp_proxy_node_get_info(self->proxy);
}
static void
port_proxies_foreach_func(gpointer data, gpointer user_data)
{
GVariantBuilder *b = user_data;
g_variant_builder_add (b, "t", data);
}
gboolean
wp_pw_audio_dsp_prepare_link (WpPwAudioDsp * self, GVariant ** properties,
GError ** error) {
const struct pw_node_info *info = NULL;
GVariantBuilder b, *b_ports;
GVariant *v_ports;
/* Get the proxy node info */
info = wp_proxy_node_get_info(self->proxy);
g_return_val_if_fail (info, FALSE);
/* Create a variant array with all the ports */
b_ports = g_variant_builder_new (G_VARIANT_TYPE ("at"));
g_ptr_array_foreach(self->port_proxies, port_proxies_foreach_func,
b_ports);
v_ports = g_variant_builder_end (b_ports);
/* Set the properties */
g_variant_builder_init (&b, G_VARIANT_TYPE_VARDICT);
g_variant_builder_add (&b, "{sv}", "node-id",
g_variant_new_uint32 (info->id));
g_variant_builder_add (&b, "{sv}", "ports", v_ports);
*properties = g_variant_builder_end (&b);
return TRUE;
}
GVariant *
wp_pw_audio_dsp_get_control_value (WpPwAudioDsp * self, guint32 control_id)
{
switch (control_id) {
case CONTROL_VOLUME:
return g_variant_new_double (self->volume);
case CONTROL_MUTE:
return g_variant_new_boolean (self->mute);
default:
g_warning ("Unknown control id %u", control_id);
return NULL;
}
}
gboolean
wp_pw_audio_dsp_set_control_value (WpPwAudioDsp * self, guint32 control_id,
GVariant * value)
{
char buf[1024];
struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buf, sizeof(buf));
struct pw_node_proxy *pw_proxy = NULL;
float volume;
bool mute;
/* Get the pipewire dsp proxy */
g_return_val_if_fail (self->proxy, FALSE);
pw_proxy = wp_proxy_get_pw_proxy (WP_PROXY(self->proxy));
g_return_val_if_fail (pw_proxy, FALSE);
switch (control_id) {
case CONTROL_VOLUME:
volume = g_variant_get_double (value);
g_debug("WpPwAudioDsp:%p set volume control (%u) value, vol:%f", self,
control_id, volume);
pw_node_proxy_set_param (pw_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),
NULL));
pw_node_proxy_enum_params (pw_proxy, 0, SPA_PARAM_Props, 0, -1,
NULL);
break;
case CONTROL_MUTE:
mute = g_variant_get_boolean (value);
g_debug("WpPwAudioDsp:%p set mute control (%u) value, mute:%d", self,
control_id, mute);
pw_node_proxy_set_param (pw_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),
NULL));
pw_node_proxy_enum_params (pw_proxy, 0, SPA_PARAM_Props, 0, -1,
NULL);
break;
default:
g_warning ("Unknown control id %u", control_id);
return FALSE;
}
return TRUE;
}

View File

@@ -1,36 +0,0 @@
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author Julian Bouzas <julian.bouzas@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
#include <gio/gio.h>
#include <wp/wp.h>
#ifndef __WP_PW_AUDIO_DSP_H__
#define __WP_PW_AUDIO_DSP_H__
G_DECLARE_FINAL_TYPE (WpPwAudioDsp, wp_pw_audio_dsp,
WP_PW, AUDIO_DSP, GObject)
guint wp_pw_audio_dsp_id_encode (guint stream_id, guint control_id);
void wp_pw_audio_dsp_id_decode (guint id, guint *stream_id, guint *control_id);
void wp_pw_audio_dsp_new (WpEndpoint *endpoint, guint id, const char *name,
enum pw_direction direction, gboolean convert,
const struct pw_node_info *target, GAsyncReadyCallback callback,
gpointer user_data);
WpPwAudioDsp * wp_pw_audio_dsp_new_finish (GObject *initable, GAsyncResult *res,
GError **error);
const struct pw_node_info *wp_pw_audio_dsp_get_info (WpPwAudioDsp * self);
gboolean wp_pw_audio_dsp_prepare_link (WpPwAudioDsp * self,
GVariant ** properties, GError ** error);
GVariant * wp_pw_audio_dsp_get_control_value (WpPwAudioDsp * self,
guint32 control_id);
gboolean wp_pw_audio_dsp_set_control_value (WpPwAudioDsp * self,
guint32 control_id, GVariant * value);
#endif

View File

@@ -0,0 +1,520 @@
/* 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 "stream.h"
typedef struct _WpAudioStreamPrivate WpAudioStreamPrivate;
struct _WpAudioStreamPrivate
{
GObject parent;
/* Props */
GWeakRef endpoint;
guint id;
gchar *name;
enum pw_direction direction;
/* Remote Pipewire */
WpRemotePipewire *remote_pipewire;
/* Stream Proxy and Listener */
struct pw_node_proxy *proxy;
struct spa_hook listener;
/* Stream Port Proxies */
GPtrArray *port_proxies;
/* Stream Controls */
gfloat volume;
gboolean mute;
};
enum {
PROP_0,
PROP_ENDPOINT,
PROP_ID,
PROP_NAME,
PROP_DIRECTION,
};
enum {
CONTROL_VOLUME = 0,
CONTROL_MUTE,
N_CONTROLS,
};
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))
guint
wp_audio_stream_id_encode (guint stream_id, guint control_id)
{
g_return_val_if_fail (control_id < N_CONTROLS, 0);
/* encode NONE as 0 and everything else with +1 */
/* NONE is MAX_UINT, so +1 will do the trick */
stream_id += 1;
/* Encode the stream and control Ids. The first ID is reserved
* for the "selected" control, registered in the endpoint */
return 1 + (stream_id * N_CONTROLS) + control_id;
}
void
wp_audio_stream_id_decode (guint id, guint *stream_id, guint *control_id)
{
guint s_id, c_id;
g_return_if_fail (id >= 1);
id -= 1;
/* Decode the stream and control Ids */
s_id = (id / N_CONTROLS) - 1;
c_id = id % N_CONTROLS;
/* Set the output params */
if (stream_id)
*stream_id = s_id;
if (control_id)
*control_id = c_id;
}
static void
on_audio_stream_port_created(GObject *initable, GAsyncResult *res,
gpointer data)
{
WpAudioStream *self = data;
WpAudioStreamPrivate *priv = wp_audio_stream_get_instance_private (self);
WpProxyPort *port_proxy = NULL;
GError *error = NULL;
/* Get the proxy port */
port_proxy = WP_PROXY_PORT (wp_proxy_port_new_finish (initable, res, &error));
if (!port_proxy)
return;
/* Check for error */
if (error) {
g_warning ("WpAudioStream:%p Stream port failed on creation", self);
g_clear_object (&port_proxy);
return;
}
/* Add the proxy port to the array */
g_return_if_fail (priv->port_proxies);
g_ptr_array_add(priv->port_proxies, port_proxy);
}
static void
on_audio_stream_port_added(WpRemotePipewire *rp, guint id, guint parent_id,
gconstpointer p, gpointer d)
{
WpAudioStream *self = d;
const struct pw_node_info *info = NULL;
struct pw_proxy *proxy = NULL;
/* Get the node id */
g_return_if_fail (WP_AUDIO_STREAM_GET_CLASS (self)->get_info);
info = WP_AUDIO_STREAM_GET_CLASS (self)->get_info (self);
if (!info)
return;
/* Skip ports that are not owned by this stream */
if (info->id != parent_id)
return;
/* Create the port proxy async */
proxy = wp_remote_pipewire_proxy_bind (rp, id, PW_TYPE_INTERFACE_Port);
g_return_if_fail(proxy);
wp_proxy_port_new(id, proxy, on_audio_stream_port_created, self);
}
static void
audio_stream_event_info (void *object, const struct pw_node_info *info)
{
WpAudioStream *self = object;
WpAudioStreamPrivate *priv = wp_audio_stream_get_instance_private (self);
/* Let the derived class handle this it is implemented */
if (WP_AUDIO_STREAM_GET_CLASS (self)->event_info)
WP_AUDIO_STREAM_GET_CLASS (self)->event_info (self, info,
priv->remote_pipewire);
}
static void
audio_stream_event_param (void *object, int seq, uint32_t id,
uint32_t index, uint32_t next, const struct spa_pod *param)
{
WpAudioStreamPrivate *priv =
wp_audio_stream_get_instance_private (WP_AUDIO_STREAM (object));
g_autoptr (WpEndpoint) 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);
if (priv->volume != volume) {
priv->volume = volume;
wp_endpoint_notify_control_value (ep,
wp_audio_stream_id_encode (priv->id, CONTROL_VOLUME));
}
break;
case SPA_PROP_mute:
spa_pod_get_bool(&prop->value, &mute);
if (priv->mute != mute) {
priv->mute = mute;
wp_endpoint_notify_control_value (ep,
wp_audio_stream_id_encode (priv->id, CONTROL_MUTE));
}
break;
default:
break;
}
}
break;
}
default:
break;
}
}
static const struct pw_node_proxy_events audio_stream_proxy_events = {
PW_VERSION_NODE_PROXY_EVENTS,
.info = audio_stream_event_info,
.param = audio_stream_event_param,
};
static void
wp_audio_stream_finalize (GObject * object)
{
WpAudioStreamPrivate *priv =
wp_audio_stream_get_instance_private (WP_AUDIO_STREAM (object));
/* Clear the endpoint weak reference */
g_weak_ref_clear (&priv->endpoint);
/* Clear the name */
g_free (priv->name);
priv->name = NULL;
/* Clear the port proxies */
if (priv->port_proxies) {
g_ptr_array_free(priv->port_proxies, TRUE);
priv->port_proxies = NULL;
}
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;
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;
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 (WpEndpoint) ep = g_weak_ref_get (&priv->endpoint);
g_autoptr (WpCore) core = wp_endpoint_get_core (ep);
GVariantDict d;
/* Set the remote pipewire */
priv->remote_pipewire = wp_core_get_global (core, WP_GLOBAL_REMOTE_PIPEWIRE);
/* Init the list of port proxies */
priv->port_proxies = g_ptr_array_new_full(4, (GDestroyNotify)g_object_unref);
/* Register the volume control */
g_variant_dict_init (&d, NULL);
g_variant_dict_insert (&d, "id", "u",
wp_audio_stream_id_encode (priv->id, CONTROL_VOLUME));
if (priv->id != WP_STREAM_ID_NONE)
g_variant_dict_insert (&d, "stream-id", "u", priv->id);
g_variant_dict_insert (&d, "name", "s", "volume");
g_variant_dict_insert (&d, "type", "s", "d");
g_variant_dict_insert (&d, "range", "(dd)", 0.0, 1.0);
g_variant_dict_insert (&d, "default-value", "d", priv->volume);
wp_endpoint_register_control (ep, g_variant_dict_end (&d));
/* Register the mute control */
g_variant_dict_init (&d, NULL);
g_variant_dict_insert (&d, "id", "u",
wp_audio_stream_id_encode (priv->id, CONTROL_MUTE));
if (priv->id != WP_STREAM_ID_NONE)
g_variant_dict_insert (&d, "stream-id", "u", priv->id);
g_variant_dict_insert (&d, "name", "s", "mute");
g_variant_dict_insert (&d, "type", "s", "b");
g_variant_dict_insert (&d, "default-value", "b", priv->mute);
wp_endpoint_register_control (ep, g_variant_dict_end (&d));
/* Create and set the proxy */
g_return_if_fail (WP_AUDIO_STREAM_GET_CLASS (self)->create_proxy);
priv->proxy = WP_AUDIO_STREAM_GET_CLASS (self)->create_proxy (self,
priv->remote_pipewire);
g_return_if_fail (priv->proxy);
/* Add a custom listener */
pw_node_proxy_add_listener(priv->proxy, &priv->listener,
&audio_stream_proxy_events, self);
/* Register a port_added callback */
g_signal_connect_object(priv->remote_pipewire, "global-added::port",
(GCallback)on_audio_stream_port_added, self, 0);
}
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_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));
}
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;
}
gconstpointer
wp_audio_stream_get_info (WpAudioStream * self)
{
const struct pw_node_info *info = NULL;
g_return_val_if_fail (WP_AUDIO_STREAM_GET_CLASS (self)->get_info, NULL);
info = WP_AUDIO_STREAM_GET_CLASS (self)->get_info (self);
return info;
}
static void
port_proxies_foreach_func(gpointer data, gpointer user_data)
{
GVariantBuilder *b = user_data;
g_variant_builder_add (b, "t", data);
}
gboolean
wp_audio_stream_prepare_link (WpAudioStream * self, GVariant ** properties,
GError ** error)
{
WpAudioStreamPrivate *priv = wp_audio_stream_get_instance_private (self);
const struct pw_node_info *info = NULL;
GVariantBuilder b, *b_ports;
GVariant *v_ports;
/* Get the proxy node id */
g_return_val_if_fail (WP_AUDIO_STREAM_GET_CLASS (self)->get_info, FALSE);
info = WP_AUDIO_STREAM_GET_CLASS (self)->get_info (self);
g_return_val_if_fail (info, FALSE);
/* Create a variant array with all the ports */
b_ports = g_variant_builder_new (G_VARIANT_TYPE ("at"));
g_ptr_array_foreach(priv->port_proxies, port_proxies_foreach_func, b_ports);
v_ports = g_variant_builder_end (b_ports);
/* Set the properties */
g_variant_builder_init (&b, G_VARIANT_TYPE_VARDICT);
g_variant_builder_add (&b, "{sv}", "node-id",
g_variant_new_uint32 (info->id));
g_variant_builder_add (&b, "{sv}", "ports", v_ports);
*properties = g_variant_builder_end (&b);
return TRUE;
}
GVariant *
wp_audio_stream_get_control_value (WpAudioStream * self, guint32 control_id)
{
WpAudioStreamPrivate *priv = wp_audio_stream_get_instance_private (self);
switch (control_id) {
case CONTROL_VOLUME:
return g_variant_new_double (priv->volume);
case CONTROL_MUTE:
return g_variant_new_boolean (priv->mute);
default:
g_warning ("Unknown control id %u", control_id);
return NULL;
}
}
gboolean
wp_audio_stream_set_control_value (WpAudioStream * self, guint32 control_id,
GVariant * value)
{
WpAudioStreamPrivate *priv = wp_audio_stream_get_instance_private (self);
char buf[1024];
struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buf, sizeof(buf));
float volume;
bool mute;
/* Make sure the proxy is valid */
g_return_val_if_fail (priv->proxy, FALSE);
/* Set the specific constrol */
switch (control_id) {
case CONTROL_VOLUME:
volume = g_variant_get_double (value);
pw_node_proxy_set_param (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),
NULL));
pw_node_proxy_enum_params (priv->proxy, 0, SPA_PARAM_Props, 0, -1, NULL);
break;
case CONTROL_MUTE:
mute = g_variant_get_boolean (value);
pw_node_proxy_set_param (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),
NULL));
pw_node_proxy_enum_params (priv->proxy, 0, SPA_PARAM_Props, 0, -1, NULL);
break;
default:
g_warning ("Unknown control id %u", control_id);
return FALSE;
}
return TRUE;
}

View File

@@ -0,0 +1,48 @@
/* 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
guint wp_audio_stream_id_encode (guint stream_id, guint control_id);
void wp_audio_stream_id_decode (guint id, guint *stream_id, guint *control_id);
#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;
/* Methods */
gpointer (*create_proxy) (WpAudioStream * self, WpRemotePipewire *rp);
gconstpointer (*get_info) (WpAudioStream * self);
void (*event_info) (WpAudioStream * self, gconstpointer info, WpRemotePipewire *rp);
};
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);
gconstpointer wp_audio_stream_get_info (WpAudioStream * self);
gboolean wp_audio_stream_prepare_link (WpAudioStream * self,
GVariant ** properties, GError ** error);
GVariant * wp_audio_stream_get_control_value (WpAudioStream * self,
guint32 control_id);
gboolean wp_audio_stream_set_control_value (WpAudioStream * self,
guint32 control_id, GVariant * value);
G_END_DECLS
#endif