741 lines
21 KiB
C
741 lines
21 KiB
C
/* 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,
|
|
PROP_FORMAT,
|
|
};
|
|
|
|
enum {
|
|
CONTROL_VOLUME = 0,
|
|
CONTROL_MUTE,
|
|
N_CONTROLS,
|
|
};
|
|
|
|
struct _WpPwAudioDsp
|
|
{
|
|
GObject parent;
|
|
|
|
/* The task to signal the audio dsp is initialized */
|
|
GTask *init_task;
|
|
|
|
/* 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;
|
|
const struct spa_audio_info_raw *format;
|
|
|
|
/* All ports handled by the port added callback */
|
|
GHashTable *handled_ports;
|
|
|
|
/* Proxies */
|
|
WpProxyNode *proxy;
|
|
GPtrArray *port_proxies;
|
|
struct pw_proxy *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))
|
|
|
|
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_new_finish(initable, res, NULL);
|
|
g_return_if_fail (port_proxy);
|
|
|
|
/* 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);
|
|
|
|
/* 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_audio_dsp_running(WpPwAudioDsp *self)
|
|
{
|
|
struct pw_properties *props;
|
|
const struct pw_node_info *dsp_info = 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_LINK_PROP_PASSIVE, "true");
|
|
if (self->direction == PW_DIRECTION_OUTPUT) {
|
|
pw_properties_setf(props, PW_LINK_OUTPUT_NODE_ID, "%d", dsp_info->id);
|
|
pw_properties_setf(props, PW_LINK_OUTPUT_PORT_ID, "%d", -1);
|
|
pw_properties_setf(props, PW_LINK_INPUT_NODE_ID, "%d", self->target->id);
|
|
pw_properties_setf(props, PW_LINK_INPUT_PORT_ID, "%d", -1);
|
|
} else {
|
|
pw_properties_setf(props, PW_LINK_OUTPUT_NODE_ID, "%d", self->target->id);
|
|
pw_properties_setf(props, PW_LINK_OUTPUT_PORT_ID, "%d", -1);
|
|
pw_properties_setf(props, PW_LINK_INPUT_NODE_ID, "%d", dsp_info->id);
|
|
pw_properties_setf(props, PW_LINK_INPUT_PORT_ID, "%d", -1);
|
|
}
|
|
|
|
g_debug ("%p linking DSP to node", self);
|
|
|
|
/* Create the link */
|
|
self->link_proxy = wp_remote_pipewire_create_object(self->remote_pipewire,
|
|
"link-factory", PW_TYPE_INTERFACE_Link, &props->dict);
|
|
|
|
/* Clean up */
|
|
pw_properties_free(props);
|
|
}
|
|
|
|
static void
|
|
on_audio_dsp_idle (WpPwAudioDsp *self)
|
|
{
|
|
if (self->link_proxy != NULL) {
|
|
pw_proxy_destroy (self->link_proxy);
|
|
self->link_proxy = NULL;
|
|
}
|
|
}
|
|
|
|
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 = { 0, };
|
|
struct spa_pod *param;
|
|
|
|
/* Get the audio dsp proxy */
|
|
self->proxy = wp_proxy_node_new_finish(initable, res, NULL);
|
|
g_return_if_fail (self->proxy);
|
|
|
|
/* 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) {
|
|
/* Get the port format */
|
|
g_return_if_fail (self->format);
|
|
format = *self->format;
|
|
|
|
/* Emit the ports */
|
|
spa_pod_builder_init(&pod_builder, buf, sizeof(buf));
|
|
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;
|
|
}
|
|
|
|
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;
|
|
case PROP_FORMAT:
|
|
self->format = 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;
|
|
case PROP_FORMAT:
|
|
g_value_set_pointer (value, (gpointer)self->format);
|
|
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)
|
|
{
|
|
}
|
|
|
|
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));
|
|
g_object_class_install_property (object_class, PROP_FORMAT,
|
|
g_param_spec_pointer ("format", "format",
|
|
"The format of the audio DSP ports",
|
|
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, const struct spa_audio_info_raw *format,
|
|
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,
|
|
"format", format,
|
|
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;
|
|
}
|