Merge branch 'async-endpoints' into 'master'
Async endpoints See merge request gkiagia/wireplumber!13
This commit is contained in:
@@ -113,7 +113,37 @@ enum {
|
||||
|
||||
static guint32 signals[NUM_SIGNALS];
|
||||
|
||||
G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (WpEndpoint, wp_endpoint, G_TYPE_OBJECT)
|
||||
static void wp_endpoint_async_initable_init (gpointer iface,
|
||||
gpointer iface_data);
|
||||
|
||||
G_DEFINE_ABSTRACT_TYPE_WITH_CODE (WpEndpoint, wp_endpoint, G_TYPE_OBJECT,
|
||||
G_ADD_PRIVATE (WpEndpoint)
|
||||
G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE,
|
||||
wp_endpoint_async_initable_init))
|
||||
|
||||
static void
|
||||
wp_endpoint_init_async (GAsyncInitable *initable, int io_priority,
|
||||
GCancellable *cancellable, GAsyncReadyCallback callback, gpointer data)
|
||||
{
|
||||
}
|
||||
|
||||
static gboolean
|
||||
wp_endpoint_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_endpoint_async_initable_init (gpointer iface, gpointer iface_data)
|
||||
{
|
||||
GAsyncInitableIface *ai_iface = iface;
|
||||
|
||||
ai_iface->init_async = wp_endpoint_init_async;
|
||||
ai_iface->init_finish = wp_endpoint_init_finish;
|
||||
}
|
||||
|
||||
static void
|
||||
wp_endpoint_init (WpEndpoint * self)
|
||||
@@ -253,6 +283,22 @@ wp_endpoint_class_init (WpEndpointClass * klass)
|
||||
G_TYPE_NONE, 1, G_TYPE_UINT);
|
||||
}
|
||||
|
||||
/**
|
||||
* wp_endpoint_new_finish:
|
||||
* @initable: the #GAsyncInitable from the callback
|
||||
* @res: the #GAsyncResult from the callback
|
||||
* @error: return location for errors, or NULL to ignore
|
||||
*
|
||||
* Finishes the async construction of #WpEndpoint.
|
||||
*/
|
||||
WpEndpoint *
|
||||
wp_endpoint_new_finish (GObject *initable, GAsyncResult *res,
|
||||
GError **error)
|
||||
{
|
||||
GAsyncInitable *ai = G_ASYNC_INITABLE(initable);
|
||||
return WP_ENDPOINT(g_async_initable_new_finish(ai, res, error));
|
||||
}
|
||||
|
||||
/**
|
||||
* wp_endpoint_register:
|
||||
* @self: the endpoint
|
||||
|
@@ -9,6 +9,8 @@
|
||||
#ifndef __WIREPLUMBER_ENDPOINT_H__
|
||||
#define __WIREPLUMBER_ENDPOINT_H__
|
||||
|
||||
#include <gio/gio.h>
|
||||
|
||||
#include "core.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
@@ -37,6 +39,8 @@ struct _WpEndpointClass
|
||||
const gchar * (*get_endpoint_link_factory) (WpEndpoint * self);
|
||||
};
|
||||
|
||||
WpEndpoint * wp_endpoint_new_finish (GObject *initable, GAsyncResult *res,
|
||||
GError **error);
|
||||
void wp_endpoint_register (WpEndpoint * self);
|
||||
void wp_endpoint_unregister (WpEndpoint * self);
|
||||
GPtrArray * wp_endpoint_find (WpCore * core, const gchar * media_class_lookup);
|
||||
|
@@ -15,7 +15,10 @@ struct _WpFactory
|
||||
GWeakRef core;
|
||||
gchar *name;
|
||||
GQuark name_quark;
|
||||
WpFactoryFunc create_object;
|
||||
union {
|
||||
WpFactoryFunc sync;
|
||||
WpFactoryAsyncFunc async;
|
||||
} create_object;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE (WpFactory, wp_factory, G_TYPE_OBJECT)
|
||||
@@ -45,19 +48,29 @@ wp_factory_class_init (WpFactoryClass * klass)
|
||||
object_class->finalize = wp_factory_finalize;
|
||||
}
|
||||
|
||||
WpFactory *
|
||||
wp_factory_new (WpCore * core, const gchar * name, WpFactoryFunc func)
|
||||
static
|
||||
WpFactory * create_factory (WpCore * core, const gchar * name)
|
||||
{
|
||||
WpFactory *f = NULL;
|
||||
|
||||
g_return_val_if_fail (name != NULL && *name != '\0', NULL);
|
||||
g_return_val_if_fail (func != NULL, NULL);
|
||||
|
||||
f = g_object_new (WP_TYPE_FACTORY, NULL);
|
||||
g_weak_ref_init (&f->core, core);
|
||||
f->name = g_strdup (name);
|
||||
f->name_quark = g_quark_from_string (f->name);
|
||||
f->create_object = func;
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
WpFactory *
|
||||
wp_factory_new (WpCore * core, const gchar * name, WpFactoryFunc func)
|
||||
{
|
||||
WpFactory *f = NULL;
|
||||
g_return_val_if_fail (func, NULL);
|
||||
|
||||
f = create_factory(core, name);
|
||||
f->create_object.sync = func;
|
||||
|
||||
g_info ("WpFactory:%p new factory: %s", f, name);
|
||||
|
||||
@@ -66,6 +79,23 @@ wp_factory_new (WpCore * core, const gchar * name, WpFactoryFunc func)
|
||||
return f;
|
||||
}
|
||||
|
||||
WpFactory *
|
||||
wp_factory_new_async (WpCore * core, const gchar * name,
|
||||
WpFactoryAsyncFunc func)
|
||||
{
|
||||
WpFactory *f = NULL;
|
||||
g_return_val_if_fail (func, NULL);
|
||||
|
||||
f = create_factory(core, name);
|
||||
f->create_object.async = func;
|
||||
|
||||
g_info ("WpFactory:%p new async factory: %s", f, name);
|
||||
|
||||
wp_core_register_global (core, WP_GLOBAL_FACTORY, f, g_object_unref);
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
const gchar *
|
||||
wp_factory_get_name (WpFactory * self)
|
||||
{
|
||||
@@ -89,7 +119,18 @@ wp_factory_create_object (WpFactory * self, GType type, GVariant * properties)
|
||||
{
|
||||
g_debug ("WpFactory:%p (%s) create object of type %s", self, self->name,
|
||||
g_type_name (type));
|
||||
return self->create_object (self, type, properties);
|
||||
|
||||
return self->create_object.sync (self, type, properties);
|
||||
}
|
||||
|
||||
void
|
||||
wp_factory_create_object_async (WpFactory * self, GType type,
|
||||
GVariant * properties, GAsyncReadyCallback ready, gpointer user_data)
|
||||
{
|
||||
g_debug ("WpFactory:%p (%s) create object async of type %s", self, self->name,
|
||||
g_type_name (type));
|
||||
|
||||
self->create_object.async (self, type, properties, ready, user_data);
|
||||
}
|
||||
|
||||
struct find_factory_data
|
||||
@@ -127,3 +168,12 @@ wp_factory_make (WpCore * core, const gchar * name, GType type,
|
||||
if (!f) return NULL;
|
||||
return wp_factory_create_object (f, type, properties);
|
||||
}
|
||||
|
||||
void
|
||||
wp_factory_make_async (WpCore * core, const gchar * name, GType type,
|
||||
GVariant * properties, GAsyncReadyCallback ready, gpointer user_data)
|
||||
{
|
||||
WpFactory *f = wp_factory_find (core, name);
|
||||
if (!f) return;
|
||||
wp_factory_create_object_async (f, type, properties, ready, user_data);
|
||||
}
|
||||
|
@@ -9,6 +9,8 @@
|
||||
#ifndef __WIREPLUMBER_FACTORY_H__
|
||||
#define __WIREPLUMBER_FACTORY_H__
|
||||
|
||||
#include <gio/gio.h>
|
||||
|
||||
#include "core.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
@@ -18,18 +20,26 @@ G_DECLARE_FINAL_TYPE (WpFactory, wp_factory, WP, FACTORY, GObject)
|
||||
|
||||
typedef gpointer (*WpFactoryFunc) (WpFactory * self, GType type,
|
||||
GVariant * properties);
|
||||
typedef void (*WpFactoryAsyncFunc) (WpFactory * self, GType type,
|
||||
GVariant * properties, GAsyncReadyCallback ready, gpointer user_data);
|
||||
|
||||
WpFactory * wp_factory_new (WpCore * core, const gchar * name,
|
||||
WpFactoryFunc func);
|
||||
WpFactory * wp_factory_new_async (WpCore * core, const gchar * name,
|
||||
WpFactoryAsyncFunc func);
|
||||
|
||||
const gchar * wp_factory_get_name (WpFactory * self);
|
||||
WpCore * wp_factory_get_core (WpFactory * self);
|
||||
gpointer wp_factory_create_object (WpFactory * self, GType type,
|
||||
GVariant * properties);
|
||||
void wp_factory_create_object_async (WpFactory * self, GType type,
|
||||
GVariant * properties, GAsyncReadyCallback ready, gpointer user_data);
|
||||
|
||||
WpFactory * wp_factory_find (WpCore * core, const gchar * name);
|
||||
gpointer wp_factory_make (WpCore * core, const gchar * name, GType type,
|
||||
GVariant * properties);
|
||||
void wp_factory_make_async (WpCore * core, const gchar * name, GType type,
|
||||
GVariant * properties, GAsyncReadyCallback ready, gpointer user_data);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
|
@@ -36,7 +36,7 @@ shared_library(
|
||||
c_args : [common_c_args, '-DG_LOG_DOMAIN="m-pipewire"'],
|
||||
install : true,
|
||||
install_dir : wireplumber_module_dir,
|
||||
dependencies : [wp_dep, pipewire_dep],
|
||||
dependencies : [gio_dep, wp_dep, pipewire_dep],
|
||||
)
|
||||
|
||||
shared_library(
|
||||
@@ -58,7 +58,7 @@ shared_library(
|
||||
c_args : [common_c_args, '-DG_LOG_DOMAIN="m-pw-audio-softdsp-endpoint"'],
|
||||
install : true,
|
||||
install_dir : wireplumber_module_dir,
|
||||
dependencies : [wp_dep, pipewire_dep],
|
||||
dependencies : [gio_dep, wp_dep, pipewire_dep],
|
||||
)
|
||||
|
||||
shared_library(
|
||||
|
@@ -14,12 +14,11 @@
|
||||
|
||||
#include <wp/wp.h>
|
||||
#include <pipewire/pipewire.h>
|
||||
#include <spa/param/audio/format-utils.h>
|
||||
|
||||
void remote_endpoint_init (WpCore * core, struct pw_core * pw_core,
|
||||
struct pw_remote * remote);
|
||||
gpointer simple_endpoint_factory (WpFactory * factory, GType type,
|
||||
GVariant * properties);
|
||||
void simple_endpoint_factory (WpFactory * factory, GType type,
|
||||
GVariant * properties, GAsyncReadyCallback ready, gpointer user_data);
|
||||
gpointer simple_endpoint_link_factory (WpFactory * factory, GType type,
|
||||
GVariant * properties);
|
||||
|
||||
@@ -27,158 +26,40 @@ struct module_data
|
||||
{
|
||||
WpModule *module;
|
||||
WpRemotePipewire *remote_pipewire;
|
||||
GHashTable *client_nodes_info;
|
||||
};
|
||||
|
||||
struct endpoint_info
|
||||
{
|
||||
gchar *name;
|
||||
gchar *media_class;
|
||||
struct pw_proxy *proxy;
|
||||
};
|
||||
|
||||
struct proxy_info
|
||||
{
|
||||
const struct module_data *data;
|
||||
uint32_t node_id;
|
||||
WpProxyPort *proxy_port;
|
||||
};
|
||||
|
||||
static void
|
||||
endpoint_info_destroy(gpointer p)
|
||||
on_endpoint_created(GObject *initable, GAsyncResult *res, gpointer d)
|
||||
{
|
||||
struct endpoint_info *ei = p;
|
||||
|
||||
/* Free the name */
|
||||
g_free (ei->name);
|
||||
|
||||
/* Free the media class */
|
||||
g_free (ei->media_class);
|
||||
|
||||
/* Clean up */
|
||||
g_slice_free (struct endpoint_info, p);
|
||||
}
|
||||
|
||||
static void
|
||||
proxy_info_destroy(gpointer p)
|
||||
{
|
||||
struct proxy_info *pi = p;
|
||||
|
||||
/* Unref the proxy port */
|
||||
g_clear_object (&pi->proxy_port);
|
||||
|
||||
/* Clean up */
|
||||
g_slice_free (struct proxy_info, p);
|
||||
}
|
||||
|
||||
static void
|
||||
unregister_endpoint (WpProxy* wp_proxy, WpEndpoint *endpoint)
|
||||
{
|
||||
g_return_if_fail(WP_IS_PROXY(wp_proxy));
|
||||
g_return_if_fail(WP_IS_ENDPOINT(endpoint));
|
||||
|
||||
/* Unregister the endpoint */
|
||||
wp_endpoint_unregister(endpoint);
|
||||
}
|
||||
|
||||
static void
|
||||
proxy_node_created(GObject *initable, GAsyncResult *res, gpointer d)
|
||||
{
|
||||
struct proxy_info *pi = d;
|
||||
const struct module_data *data = pi->data;
|
||||
g_autoptr (WpCore) core = wp_module_get_core (data->module);
|
||||
g_autoptr (WpProxyNode) proxy_node = NULL;
|
||||
struct endpoint_info *ei = NULL;
|
||||
g_autoptr (WpEndpoint) endpoint = NULL;
|
||||
g_autoptr (GVariant) endpoint_props = NULL;
|
||||
GVariantBuilder b;
|
||||
guint global_id = 0;
|
||||
|
||||
/* Get the proxy */
|
||||
proxy_node = wp_proxy_node_new_finish(initable, res, NULL);
|
||||
if (!proxy_node)
|
||||
/* Get the endpoint */
|
||||
endpoint = wp_endpoint_new_finish(initable, res, NULL);
|
||||
if (!endpoint)
|
||||
return;
|
||||
|
||||
/* Get the client node info */
|
||||
ei = g_hash_table_lookup(data->client_nodes_info,
|
||||
GINT_TO_POINTER(pi->node_id));
|
||||
if (!ei)
|
||||
return;
|
||||
|
||||
/* Set the properties */
|
||||
g_variant_builder_init (&b, G_VARIANT_TYPE_VARDICT);
|
||||
g_variant_builder_add (&b, "{sv}",
|
||||
"name", ei->name ? g_variant_new_string (ei->name) :
|
||||
g_variant_new_take_string (
|
||||
g_strdup_printf ("Stream %u", pi->node_id)));
|
||||
g_variant_builder_add (&b, "{sv}",
|
||||
"media-class", g_variant_new_string (ei->media_class));
|
||||
g_variant_builder_add (&b, "{sv}",
|
||||
"proxy-node", g_variant_new_uint64 ((guint64) proxy_node));
|
||||
g_variant_builder_add (&b, "{sv}",
|
||||
"proxy-port", g_variant_new_uint64 ((guint64) pi->proxy_port));
|
||||
endpoint_props = g_variant_builder_end (&b);
|
||||
|
||||
/* Create the endpoint */
|
||||
endpoint = wp_factory_make (core, "pipewire-simple-endpoint",
|
||||
WP_TYPE_ENDPOINT, endpoint_props);
|
||||
/* Get the endpoint global id */
|
||||
g_object_get (endpoint, "global-id", &global_id, NULL);
|
||||
g_debug ("Created client endpoint for global id %d", global_id);
|
||||
|
||||
/* Register the endpoint */
|
||||
wp_endpoint_register (endpoint);
|
||||
|
||||
/* Set destroy handler to unregister endpoint when the proxy is detroyed */
|
||||
g_signal_connect (proxy_node, "destroyed", G_CALLBACK(unregister_endpoint),
|
||||
endpoint);
|
||||
|
||||
/* Clean up */
|
||||
proxy_info_destroy (pi);
|
||||
}
|
||||
|
||||
static void
|
||||
proxy_port_created(GObject *initable, GAsyncResult *res, gpointer d)
|
||||
{
|
||||
struct proxy_info *pi = d;
|
||||
const struct module_data *data = pi->data;
|
||||
struct endpoint_info *ei = NULL;
|
||||
WpProxyPort *proxy_port = NULL;
|
||||
|
||||
/* Get the proxy port */
|
||||
proxy_port = wp_proxy_port_new_finish(initable, res, NULL);
|
||||
if (!proxy_port)
|
||||
return;
|
||||
|
||||
/* Forward the proxy port */
|
||||
pi->proxy_port = proxy_port;
|
||||
|
||||
/* Get the node proxy */
|
||||
ei = g_hash_table_lookup(data->client_nodes_info,
|
||||
GINT_TO_POINTER(pi->node_id));
|
||||
if (!ei)
|
||||
return;
|
||||
|
||||
/* Create the proxy node asynchronically */
|
||||
wp_proxy_node_new(pi->node_id, ei->proxy, proxy_node_created, pi);
|
||||
}
|
||||
|
||||
static void
|
||||
handle_node (WpRemotePipewire *rp, guint id, guint parent_id, gconstpointer p,
|
||||
on_node_added (WpRemotePipewire *rp, guint id, guint parent_id, gconstpointer p,
|
||||
gpointer d)
|
||||
{
|
||||
struct module_data *data = d;
|
||||
const struct spa_dict *props = p;
|
||||
struct endpoint_info *ei = NULL;
|
||||
const gchar *name;
|
||||
const gchar *media_class;
|
||||
struct pw_proxy *proxy;
|
||||
struct spa_audio_info_raw format = { 0, };
|
||||
struct spa_pod *param;
|
||||
struct spa_pod_builder pod_builder = { 0, };
|
||||
char buf[1024];
|
||||
g_autoptr (WpCore) core = wp_module_get_core (data->module);
|
||||
const gchar *name, *media_class;
|
||||
GVariantBuilder b;
|
||||
g_autoptr (GVariant) endpoint_props = NULL;
|
||||
|
||||
/* Make sure the node has properties */
|
||||
if (!props) {
|
||||
g_warning("node has no properties, skipping...");
|
||||
return;
|
||||
}
|
||||
g_return_if_fail(props);
|
||||
|
||||
/* Get the media_class */
|
||||
media_class = spa_dict_lookup(props, "media.class");
|
||||
@@ -192,67 +73,20 @@ handle_node (WpRemotePipewire *rp, guint id, guint parent_id, gconstpointer p,
|
||||
if (!name)
|
||||
name = spa_dict_lookup (props, "node.name");
|
||||
|
||||
g_debug ("found stream node: id:%u ; name:%s ; media_class:%s", id, name,
|
||||
media_class);
|
||||
/* Set the properties */
|
||||
g_variant_builder_init (&b, G_VARIANT_TYPE_VARDICT);
|
||||
g_variant_builder_add (&b, "{sv}",
|
||||
"name", name ? g_variant_new_string (name) :
|
||||
g_variant_new_take_string (g_strdup_printf ("Stream %u", id)));
|
||||
g_variant_builder_add (&b, "{sv}",
|
||||
"media-class", g_variant_new_string (media_class));
|
||||
g_variant_builder_add (&b, "{sv}",
|
||||
"global-id", g_variant_new_uint32 (id));
|
||||
endpoint_props = g_variant_builder_end (&b);
|
||||
|
||||
/* Bind the proxy */
|
||||
proxy = wp_remote_pipewire_proxy_bind(rp, id, PW_TYPE_INTERFACE_Node);
|
||||
if (!proxy)
|
||||
return;
|
||||
|
||||
/* TODO: Assume all clients have this format for now */
|
||||
format.format = SPA_AUDIO_FORMAT_F32P;
|
||||
format.flags = 1;
|
||||
format.rate = 48000;
|
||||
format.channels = 1;
|
||||
format.position[0] = 0;
|
||||
|
||||
/* Set the profile */
|
||||
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_OUTPUT),
|
||||
SPA_PARAM_PROFILE_format, SPA_POD_Pod(param));
|
||||
pw_node_proxy_set_param((struct pw_node_proxy*)proxy,
|
||||
SPA_PARAM_Profile, 0, param);
|
||||
|
||||
/* Create the endpoint info */
|
||||
ei = g_slice_new0 (struct endpoint_info);
|
||||
ei->name = g_strdup(name);
|
||||
ei->media_class = g_strdup(media_class);
|
||||
ei->proxy = proxy;
|
||||
|
||||
/* Insert the client node info in the hash table */
|
||||
g_hash_table_insert(data->client_nodes_info, GINT_TO_POINTER (id), ei);
|
||||
}
|
||||
|
||||
static void
|
||||
handle_port(WpRemotePipewire *rp, guint id, guint parent_id, gconstpointer p,
|
||||
gpointer d)
|
||||
{
|
||||
struct module_data *data = d;
|
||||
struct proxy_info *pi = NULL;
|
||||
struct pw_proxy *proxy = NULL;
|
||||
|
||||
/* Only handle ports whose parent is an alsa node */
|
||||
if (!g_hash_table_contains(data->client_nodes_info,
|
||||
GINT_TO_POINTER (parent_id)))
|
||||
return;
|
||||
|
||||
/* Bind the proxy */
|
||||
proxy = wp_remote_pipewire_proxy_bind (rp, id, PW_TYPE_INTERFACE_Port);
|
||||
if (!proxy)
|
||||
return;
|
||||
|
||||
/* Create the port info */
|
||||
pi = g_slice_new0 (struct proxy_info);
|
||||
pi->data = data;
|
||||
pi->node_id = parent_id;
|
||||
pi->proxy_port = NULL;
|
||||
|
||||
/* Create the proxy port asynchronically */
|
||||
wp_proxy_port_new(id, proxy, proxy_port_created, pi);
|
||||
/* Create the endpoint async */
|
||||
wp_factory_make_async (core, "pipewire-simple-endpoint", WP_TYPE_ENDPOINT,
|
||||
endpoint_props, on_endpoint_created, data);
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -264,10 +98,6 @@ module_destroy (gpointer d)
|
||||
data->module = NULL;
|
||||
data->remote_pipewire = NULL;
|
||||
|
||||
/* Destroy the hash table */
|
||||
g_hash_table_unref (data->client_nodes_info);
|
||||
data->client_nodes_info = NULL;
|
||||
|
||||
/* Clean up */
|
||||
g_slice_free (struct module_data, data);
|
||||
}
|
||||
@@ -292,22 +122,20 @@ wireplumber__module_init (WpModule * module, WpCore * core, GVariant * args)
|
||||
data = g_slice_new0 (struct module_data);
|
||||
data->module = module;
|
||||
data->remote_pipewire = rp;
|
||||
data->client_nodes_info = g_hash_table_new_full (g_direct_hash,
|
||||
g_direct_equal, NULL, endpoint_info_destroy);
|
||||
|
||||
/* Set the module destroy callback */
|
||||
wp_module_set_destroy_callback (module, module_destroy, data);
|
||||
|
||||
/* Register the global addded callbacks */
|
||||
g_signal_connect(rp, "global-added::node", (GCallback)handle_node, data);
|
||||
g_signal_connect(rp, "global-added::port", (GCallback)handle_port, data);
|
||||
/* Register the global added/removed callbacks */
|
||||
g_signal_connect(rp, "global-added::node", (GCallback)on_node_added, data);
|
||||
|
||||
/* Init remoted endpoint */
|
||||
g_object_get (rp, "pw-core", &pw_core, "pw-remote", &pw_remote, NULL);
|
||||
remote_endpoint_init (core, pw_core, pw_remote);
|
||||
|
||||
/* Register simple-endpoint and simple-endpoint-link */
|
||||
wp_factory_new (core, "pipewire-simple-endpoint", simple_endpoint_factory);
|
||||
wp_factory_new_async (core, "pipewire-simple-endpoint",
|
||||
simple_endpoint_factory);
|
||||
wp_factory_new (core, "pipewire-simple-endpoint-link",
|
||||
simple_endpoint_link_factory);
|
||||
}
|
||||
|
@@ -48,24 +48,46 @@ simple_endpoint_link_create (WpEndpointLink * epl, GVariant * src_data,
|
||||
{
|
||||
WpPipewireSimpleEndpointLink *self = WP_PIPEWIRE_SIMPLE_ENDPOINT_LINK(epl);
|
||||
struct pw_properties *props;
|
||||
guint32 output_node_id, input_node_id, output_port_id, input_port_id;
|
||||
guint32 output_node_id, input_node_id;
|
||||
GVariant *src_ports, *sink_ports;
|
||||
GVariantIter *out_iter, *in_iter;
|
||||
guint64 out_ptr, in_ptr;
|
||||
|
||||
/* Get the node ids and port ids */
|
||||
if (!g_variant_lookup (src_data, "node-id", "u", &output_node_id))
|
||||
return FALSE;
|
||||
if (!g_variant_lookup (src_data, "node-port-id", "u", &output_port_id))
|
||||
src_ports = g_variant_lookup_value (src_data, "ports", G_VARIANT_TYPE_ARRAY);
|
||||
if (!src_ports)
|
||||
return FALSE;
|
||||
if (!g_variant_lookup (sink_data, "node-id", "u", &input_node_id))
|
||||
return FALSE;
|
||||
if (!g_variant_lookup (sink_data, "node-port-id", "u", &input_port_id))
|
||||
sink_ports = g_variant_lookup_value (sink_data, "ports", G_VARIANT_TYPE_ARRAY);
|
||||
if (!sink_ports)
|
||||
return FALSE;
|
||||
|
||||
/* Link all the output ports with the input ports */
|
||||
g_variant_get (src_ports, "at", &out_iter);
|
||||
while (g_variant_iter_loop (out_iter, "t", &out_ptr)) {
|
||||
WpProxyPort *out_p = (gpointer)out_ptr;
|
||||
enum pw_direction out_direction = wp_proxy_port_get_info(out_p)->direction;
|
||||
guint out_id = wp_proxy_get_global_id(WP_PROXY(out_p));
|
||||
if (out_direction == PW_DIRECTION_INPUT)
|
||||
continue;
|
||||
|
||||
g_variant_get (sink_ports, "at", &in_iter);
|
||||
while (g_variant_iter_loop (in_iter, "t", &in_ptr)) {
|
||||
WpProxyPort *in_p = (gpointer)in_ptr;
|
||||
enum pw_direction in_direction = wp_proxy_port_get_info(in_p)->direction;
|
||||
guint in_id = wp_proxy_get_global_id(WP_PROXY(in_p));
|
||||
if (in_direction == PW_DIRECTION_OUTPUT)
|
||||
continue;
|
||||
|
||||
/* Create the properties */
|
||||
props = pw_properties_new(NULL, NULL);
|
||||
pw_properties_setf(props, PW_LINK_OUTPUT_NODE_ID, "%d", output_node_id);
|
||||
pw_properties_setf(props, PW_LINK_OUTPUT_PORT_ID, "%d", output_port_id);
|
||||
pw_properties_setf(props, PW_LINK_OUTPUT_PORT_ID, "%d", out_id);
|
||||
pw_properties_setf(props, PW_LINK_INPUT_NODE_ID, "%d", input_node_id);
|
||||
pw_properties_setf(props, PW_LINK_INPUT_PORT_ID, "%d", input_port_id);
|
||||
pw_properties_setf(props, PW_LINK_INPUT_PORT_ID, "%d", in_id);
|
||||
|
||||
/* Create the link */
|
||||
pw_core_proxy_create_object(self->core_proxy, "link-factory",
|
||||
@@ -73,6 +95,10 @@ simple_endpoint_link_create (WpEndpointLink * epl, GVariant * src_data,
|
||||
|
||||
/* Clean up */
|
||||
pw_properties_free(props);
|
||||
}
|
||||
g_variant_iter_free (in_iter);
|
||||
}
|
||||
g_variant_iter_free (out_iter);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
@@ -13,6 +13,8 @@
|
||||
* other arbitrary node that does not need any kind of internal management.
|
||||
*/
|
||||
|
||||
#include <spa/param/audio/format-utils.h>
|
||||
|
||||
#include <wp/wp.h>
|
||||
#include <pipewire/pipewire.h>
|
||||
#include <spa/pod/parser.h>
|
||||
@@ -22,10 +24,19 @@ struct _WpPipewireSimpleEndpoint
|
||||
{
|
||||
WpEndpoint parent;
|
||||
|
||||
/* Proxy */
|
||||
/* The global-id this endpoint refers to */
|
||||
guint global_id;
|
||||
|
||||
/* The task to signal the endpoint is initialized */
|
||||
GTask *init_task;
|
||||
|
||||
/* The remote pipewire */
|
||||
WpRemotePipewire *remote_pipewire;
|
||||
|
||||
/* Proxies */
|
||||
WpProxyNode *proxy_node;
|
||||
WpProxyPort *proxy_port;
|
||||
struct spa_hook node_proxy_listener;
|
||||
GPtrArray *proxies_port;
|
||||
|
||||
/* controls cache */
|
||||
gfloat volume;
|
||||
@@ -34,8 +45,7 @@ struct _WpPipewireSimpleEndpoint
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_NODE_PROXY,
|
||||
PROP_PORT_PROXY
|
||||
PROP_GLOBAL_ID,
|
||||
};
|
||||
|
||||
enum {
|
||||
@@ -43,10 +53,17 @@ enum {
|
||||
CONTROL_MUTE,
|
||||
};
|
||||
|
||||
static GAsyncInitableIface *wp_simple_endpoint_parent_interface = NULL;
|
||||
static void wp_simple_endpoint_async_initable_init (gpointer iface,
|
||||
gpointer iface_data);
|
||||
|
||||
G_DECLARE_FINAL_TYPE (WpPipewireSimpleEndpoint,
|
||||
simple_endpoint, WP_PIPEWIRE, SIMPLE_ENDPOINT, WpEndpoint)
|
||||
|
||||
G_DEFINE_TYPE (WpPipewireSimpleEndpoint, simple_endpoint, WP_TYPE_ENDPOINT)
|
||||
G_DEFINE_TYPE_WITH_CODE (WpPipewireSimpleEndpoint, simple_endpoint,
|
||||
WP_TYPE_ENDPOINT,
|
||||
G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE,
|
||||
wp_simple_endpoint_async_initable_init))
|
||||
|
||||
static void
|
||||
node_proxy_param (void *object, int seq, uint32_t id,
|
||||
@@ -100,21 +117,122 @@ static const struct pw_node_proxy_events node_node_proxy_events = {
|
||||
};
|
||||
|
||||
static void
|
||||
simple_endpoint_init (WpPipewireSimpleEndpoint * self)
|
||||
on_proxy_node_destroyed (WpProxy* wp_proxy, WpEndpoint *endpoint)
|
||||
{
|
||||
g_return_if_fail(WP_IS_ENDPOINT(endpoint));
|
||||
|
||||
/* Unregister the endpoint */
|
||||
wp_endpoint_unregister(endpoint);
|
||||
}
|
||||
|
||||
static void
|
||||
simple_endpoint_constructed (GObject * object)
|
||||
on_all_ports_done(WpProxy *proxy, gpointer data)
|
||||
{
|
||||
WpPipewireSimpleEndpoint *self = WP_PIPEWIRE_SIMPLE_ENDPOINT (object);
|
||||
WpPipewireSimpleEndpoint *self = data;
|
||||
|
||||
/* Don't do anything if the endpoint has already been initialized */
|
||||
if (!self->init_task)
|
||||
return;
|
||||
|
||||
/* Set destroy handler to unregister endpoint on proxy_node destruction */
|
||||
g_signal_connect (self->proxy_node, "destroyed",
|
||||
G_CALLBACK(on_proxy_node_destroyed), WP_ENDPOINT(self));
|
||||
|
||||
/* Finish the creation of the endpoint */
|
||||
g_task_return_boolean (self->init_task, TRUE);
|
||||
g_clear_object(&self->init_task);
|
||||
}
|
||||
|
||||
static void
|
||||
on_proxy_port_created(GObject *initable, GAsyncResult *res, gpointer data)
|
||||
{
|
||||
WpPipewireSimpleEndpoint *self = data;
|
||||
WpProxyPort *proxy_port = NULL;
|
||||
|
||||
/* Get the proxy port */
|
||||
proxy_port = wp_proxy_port_new_finish(initable, res, NULL);
|
||||
g_return_if_fail (proxy_port);
|
||||
|
||||
/* Add the proxy port to the array */
|
||||
g_return_if_fail (self->proxies_port);
|
||||
g_ptr_array_add(self->proxies_port, proxy_port);
|
||||
|
||||
/* Register the done callback */
|
||||
g_signal_connect(self->proxy_node, "done", (GCallback)on_all_ports_done,
|
||||
self);
|
||||
wp_proxy_sync (WP_PROXY(self->proxy_node));
|
||||
}
|
||||
|
||||
static void
|
||||
on_port_added(WpRemotePipewire *rp, guint id, guint parent_id, gconstpointer p,
|
||||
gpointer d)
|
||||
{
|
||||
WpPipewireSimpleEndpoint *self = d;
|
||||
struct pw_port_proxy *port_proxy = NULL;
|
||||
|
||||
/* Only handle ports owned by this endpoint */
|
||||
if (parent_id != self->global_id)
|
||||
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
|
||||
emit_endpoint_ports(WpPipewireSimpleEndpoint *self)
|
||||
{
|
||||
struct pw_node_proxy* node_proxy = NULL;
|
||||
struct spa_audio_info_raw format = { 0, };
|
||||
struct spa_pod *param;
|
||||
struct spa_pod_builder pod_builder = { 0, };
|
||||
char buf[1024];
|
||||
|
||||
/* Get the pipewire node proxy */
|
||||
node_proxy = wp_proxy_get_pw_proxy(WP_PROXY(self->proxy_node));
|
||||
g_return_if_fail (node_proxy);
|
||||
|
||||
/* TODO: Assume all clients have this format for now */
|
||||
format.format = SPA_AUDIO_FORMAT_F32P;
|
||||
format.flags = 1;
|
||||
format.rate = 48000;
|
||||
format.channels = 2;
|
||||
format.position[0] = 0;
|
||||
format.position[1] = 0;
|
||||
|
||||
/* Build the param profile */
|
||||
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_OUTPUT),
|
||||
SPA_PARAM_PROFILE_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);
|
||||
}
|
||||
|
||||
static void
|
||||
on_proxy_node_created(GObject *initable, GAsyncResult *res, gpointer data)
|
||||
{
|
||||
WpPipewireSimpleEndpoint *self = data;
|
||||
GVariantDict d;
|
||||
uint32_t ids[1] = { SPA_PARAM_Props };
|
||||
uint32_t n_ids = 1;
|
||||
struct pw_node_proxy *node_proxy = NULL;
|
||||
|
||||
/* Get the proxy node */
|
||||
self->proxy_node = wp_proxy_node_new_finish(initable, res, NULL);
|
||||
g_return_if_fail (self->proxy_node);
|
||||
|
||||
/* Emit the ports */
|
||||
emit_endpoint_ports(self);
|
||||
|
||||
/* Add a custom node proxy event listener */
|
||||
node_proxy = wp_proxy_get_pw_proxy(WP_PROXY(self->proxy_node));
|
||||
g_return_if_fail (node_proxy);
|
||||
pw_node_proxy_add_listener (node_proxy, &self->node_proxy_listener,
|
||||
&node_node_proxy_events, self);
|
||||
pw_node_proxy_subscribe_params (node_proxy, ids, n_ids);
|
||||
@@ -143,8 +261,54 @@ simple_endpoint_constructed (GObject * object)
|
||||
g_variant_dict_insert (&d, "default-value", "b", FALSE);
|
||||
wp_endpoint_register_control (WP_ENDPOINT (self), g_variant_dict_end (&d));
|
||||
}
|
||||
}
|
||||
|
||||
G_OBJECT_CLASS (simple_endpoint_parent_class)->constructed (object);
|
||||
static void
|
||||
wp_simple_endpoint_init_async (GAsyncInitable *initable, int io_priority,
|
||||
GCancellable *cancellable, GAsyncReadyCallback callback, gpointer data)
|
||||
{
|
||||
WpPipewireSimpleEndpoint *self = WP_PIPEWIRE_SIMPLE_ENDPOINT (initable);
|
||||
g_autoptr (WpCore) core = wp_endpoint_get_core(WP_ENDPOINT(self));
|
||||
struct pw_node_proxy *node_proxy = NULL;
|
||||
|
||||
/* Create the async task */
|
||||
self->init_task = g_task_new (initable, cancellable, callback, data);
|
||||
|
||||
/* Init the proxies_port array */
|
||||
self->proxies_port = g_ptr_array_new_full(2, (GDestroyNotify)g_object_unref);
|
||||
|
||||
/* 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(self->remote_pipewire, "global-added::port",
|
||||
(GCallback)on_port_added, self);
|
||||
|
||||
/* 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);
|
||||
|
||||
/* Call the parent interface */
|
||||
wp_simple_endpoint_parent_interface->init_async (initable, io_priority,
|
||||
cancellable, callback, data);
|
||||
}
|
||||
|
||||
static void
|
||||
wp_simple_endpoint_async_initable_init (gpointer iface, gpointer iface_data)
|
||||
{
|
||||
GAsyncInitableIface *ai_iface = iface;
|
||||
|
||||
/* Set the parent interface */
|
||||
wp_simple_endpoint_parent_interface = g_type_interface_peek_parent (iface);
|
||||
|
||||
/* Only set the init_async */
|
||||
ai_iface->init_async = wp_simple_endpoint_init_async;
|
||||
}
|
||||
|
||||
static void
|
||||
simple_endpoint_init (WpPipewireSimpleEndpoint * self)
|
||||
{
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -152,12 +316,18 @@ simple_endpoint_finalize (GObject * object)
|
||||
{
|
||||
WpPipewireSimpleEndpoint *self = WP_PIPEWIRE_SIMPLE_ENDPOINT (object);
|
||||
|
||||
/* Unref the proxy node */
|
||||
if (self->proxy_node) {
|
||||
g_object_unref(self->proxy_node);
|
||||
self->proxy_node = NULL;
|
||||
/* Destroy the proxies port */
|
||||
if (self->proxies_port) {
|
||||
g_ptr_array_free(self->proxies_port, TRUE);
|
||||
self->proxies_port = NULL;
|
||||
}
|
||||
|
||||
/* Destroy the proxy node */
|
||||
g_clear_object(&self->proxy_node);
|
||||
|
||||
/* Destroy the done task */
|
||||
g_clear_object(&self->init_task);
|
||||
|
||||
G_OBJECT_CLASS (simple_endpoint_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
@@ -168,13 +338,8 @@ simple_endpoint_set_property (GObject * object, guint property_id,
|
||||
WpPipewireSimpleEndpoint *self = WP_PIPEWIRE_SIMPLE_ENDPOINT (object);
|
||||
|
||||
switch (property_id) {
|
||||
case PROP_NODE_PROXY:
|
||||
g_clear_object(&self->proxy_node);
|
||||
self->proxy_node = g_value_dup_object(value);
|
||||
break;
|
||||
case PROP_PORT_PROXY:
|
||||
g_clear_object(&self->proxy_port);
|
||||
self->proxy_port = g_value_dup_object(value);
|
||||
case PROP_GLOBAL_ID:
|
||||
self->global_id = g_value_get_uint(value);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
@@ -189,11 +354,8 @@ simple_endpoint_get_property (GObject * object, guint property_id,
|
||||
WpPipewireSimpleEndpoint *self = WP_PIPEWIRE_SIMPLE_ENDPOINT (object);
|
||||
|
||||
switch (property_id) {
|
||||
case PROP_NODE_PROXY:
|
||||
g_value_set_object (value, self->proxy_node);
|
||||
break;
|
||||
case PROP_PORT_PROXY:
|
||||
g_value_set_object (value, self->proxy_port);
|
||||
case PROP_GLOBAL_ID:
|
||||
g_value_set_uint (value, self->global_id);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
@@ -201,21 +363,31 @@ simple_endpoint_get_property (GObject * object, guint property_id,
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
proxies_port_foreach_func(gpointer data, gpointer user_data)
|
||||
{
|
||||
GVariantBuilder *b = user_data;
|
||||
g_variant_builder_add (b, "t", data);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
simple_endpoint_prepare_link (WpEndpoint * ep, guint32 stream_id,
|
||||
WpEndpointLink * link, GVariant ** properties, GError ** error)
|
||||
{
|
||||
WpPipewireSimpleEndpoint *self = WP_PIPEWIRE_SIMPLE_ENDPOINT (ep);
|
||||
uint32_t node_id = wp_proxy_get_global_id(WP_PROXY(self->proxy_node));
|
||||
uint32_t port_id = wp_proxy_get_global_id(WP_PROXY(self->proxy_port));
|
||||
GVariantBuilder b;
|
||||
GVariantBuilder b, *b_ports;
|
||||
GVariant *v_ports;
|
||||
|
||||
/* Create a variant array with all the ports */
|
||||
b_ports = g_variant_builder_new (G_VARIANT_TYPE ("at"));
|
||||
g_ptr_array_foreach(self->proxies_port, proxies_port_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 (node_id));
|
||||
g_variant_builder_add (&b, "{sv}", "node-port-id",
|
||||
g_variant_new_uint32 (port_id));
|
||||
g_variant_new_uint32 (self->global_id));
|
||||
g_variant_builder_add (&b, "{sv}", "ports", v_ports);
|
||||
*properties = g_variant_builder_end (&b);
|
||||
|
||||
return TRUE;
|
||||
@@ -294,7 +466,6 @@ simple_endpoint_class_init (WpPipewireSimpleEndpointClass * klass)
|
||||
GObjectClass *object_class = (GObjectClass *) klass;
|
||||
WpEndpointClass *endpoint_class = (WpEndpointClass *) klass;
|
||||
|
||||
object_class->constructed = simple_endpoint_constructed;
|
||||
object_class->finalize = simple_endpoint_finalize;
|
||||
object_class->set_property = simple_endpoint_set_property;
|
||||
object_class->get_property = simple_endpoint_get_property;
|
||||
@@ -303,48 +474,40 @@ simple_endpoint_class_init (WpPipewireSimpleEndpointClass * klass)
|
||||
endpoint_class->get_control_value = simple_endpoint_get_control_value;
|
||||
endpoint_class->set_control_value = simple_endpoint_set_control_value;
|
||||
|
||||
g_object_class_install_property (object_class, PROP_NODE_PROXY,
|
||||
g_param_spec_object ("node-proxy", "node-proxy",
|
||||
"Pointer to the node proxy of the client", WP_TYPE_PROXY_NODE,
|
||||
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
|
||||
g_object_class_install_property (object_class, PROP_PORT_PROXY,
|
||||
g_param_spec_object ("port-proxy", "port-proxy",
|
||||
"Pointer to the port proxy of the client", WP_TYPE_PROXY_PORT,
|
||||
g_object_class_install_property (object_class, PROP_GLOBAL_ID,
|
||||
g_param_spec_uint ("global-id", "global-id",
|
||||
"The global Id this endpoint refers to", 0, G_MAXUINT, 0,
|
||||
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
|
||||
}
|
||||
|
||||
gpointer
|
||||
void
|
||||
simple_endpoint_factory (WpFactory * factory, GType type,
|
||||
GVariant * properties)
|
||||
GVariant * properties, GAsyncReadyCallback ready, gpointer user_data)
|
||||
{
|
||||
g_autoptr (WpCore) core = NULL;
|
||||
guint64 proxy_node;
|
||||
guint64 proxy_port;
|
||||
const gchar *name;
|
||||
const gchar *media_class;
|
||||
const gchar *name, *media_class;
|
||||
guint global_id;
|
||||
|
||||
g_return_val_if_fail (type == WP_TYPE_ENDPOINT, NULL);
|
||||
g_return_val_if_fail (properties != NULL, NULL);
|
||||
g_return_val_if_fail (g_variant_is_of_type (properties,
|
||||
G_VARIANT_TYPE_VARDICT), NULL);
|
||||
/* Make sure the type is correct */
|
||||
g_return_if_fail (type == WP_TYPE_ENDPOINT);
|
||||
|
||||
/* Get the Core */
|
||||
core = wp_factory_get_core (factory);
|
||||
g_return_val_if_fail (core != NULL, NULL);
|
||||
g_return_if_fail (core);
|
||||
|
||||
/* Get the properties */
|
||||
if (!g_variant_lookup (properties, "name", "&s", &name))
|
||||
return NULL;
|
||||
return;
|
||||
if (!g_variant_lookup (properties, "media-class", "&s", &media_class))
|
||||
return NULL;
|
||||
if (!g_variant_lookup (properties, "proxy-node", "t", &proxy_node))
|
||||
return NULL;
|
||||
if (!g_variant_lookup (properties, "proxy-port", "t", &proxy_port))
|
||||
return NULL;
|
||||
return;
|
||||
if (!g_variant_lookup (properties, "global-id", "u", &global_id))
|
||||
return;
|
||||
|
||||
return g_object_new (simple_endpoint_get_type (),
|
||||
g_async_initable_new_async (
|
||||
simple_endpoint_get_type (), G_PRIORITY_DEFAULT, NULL, ready, user_data,
|
||||
"core", core,
|
||||
"name", name,
|
||||
"media-class", media_class,
|
||||
"node-proxy", (gpointer) proxy_node,
|
||||
"port-proxy", (gpointer) proxy_port,
|
||||
"global-id", global_id,
|
||||
NULL);
|
||||
}
|
||||
|
@@ -19,143 +19,37 @@ struct impl
|
||||
{
|
||||
WpModule *module;
|
||||
WpRemotePipewire *remote_pipewire;
|
||||
GHashTable *alsa_nodes_info;
|
||||
};
|
||||
|
||||
struct endpoint_info
|
||||
{
|
||||
gchar *name;
|
||||
gchar *media_class;
|
||||
};
|
||||
|
||||
struct proxy_info
|
||||
{
|
||||
const struct impl *impl;
|
||||
uint32_t node_id;
|
||||
WpProxyPort *proxy_port;
|
||||
};
|
||||
|
||||
static void
|
||||
endpoint_info_destroy(gpointer p)
|
||||
on_endpoint_created(GObject *initable, GAsyncResult *res, gpointer d)
|
||||
{
|
||||
struct endpoint_info *ei = p;
|
||||
|
||||
/* Free the name */
|
||||
g_free (ei->name);
|
||||
|
||||
/* Free the media class */
|
||||
g_free (ei->media_class);
|
||||
|
||||
/* Clean up */
|
||||
g_slice_free (struct endpoint_info, p);
|
||||
}
|
||||
|
||||
static void
|
||||
proxy_info_destroy(gpointer p)
|
||||
{
|
||||
struct proxy_info *pi = p;
|
||||
|
||||
/* Unref the proxy port */
|
||||
g_clear_object (&pi->proxy_port);
|
||||
|
||||
/* Clean up */
|
||||
g_slice_free (struct proxy_info, p);
|
||||
}
|
||||
|
||||
static void
|
||||
unregister_endpoint (WpProxy* wp_proxy, WpEndpoint *endpoint)
|
||||
{
|
||||
g_return_if_fail(WP_IS_PROXY(wp_proxy));
|
||||
g_return_if_fail(WP_IS_ENDPOINT(endpoint));
|
||||
|
||||
/* Unregister the endpoint */
|
||||
wp_endpoint_unregister(endpoint);
|
||||
}
|
||||
|
||||
static void
|
||||
proxy_node_created(GObject *initable, GAsyncResult *res, gpointer data)
|
||||
{
|
||||
struct proxy_info *pi = data;
|
||||
const struct impl *impl = pi->impl;
|
||||
g_autoptr (WpCore) core = wp_module_get_core (impl->module);
|
||||
g_autoptr(WpProxyNode) proxy_node = NULL;
|
||||
struct endpoint_info *ei = NULL;
|
||||
GVariantBuilder b;
|
||||
g_autoptr (GVariant) endpoint_props = NULL;
|
||||
g_autoptr (WpEndpoint) endpoint = NULL;
|
||||
guint global_id = 0;
|
||||
|
||||
/* Get the proxy */
|
||||
proxy_node = wp_proxy_node_new_finish(initable, res, NULL);
|
||||
if (!proxy_node)
|
||||
/* Get the endpoint */
|
||||
endpoint = wp_endpoint_new_finish(initable, res, NULL);
|
||||
if (!endpoint)
|
||||
return;
|
||||
|
||||
/* Get the alsa node info */
|
||||
ei = g_hash_table_lookup(impl->alsa_nodes_info, GINT_TO_POINTER(pi->node_id));
|
||||
if (!ei)
|
||||
return;
|
||||
|
||||
/* Build the properties for the endpoint */
|
||||
g_variant_builder_init (&b, G_VARIANT_TYPE_VARDICT);
|
||||
g_variant_builder_add (&b, "{sv}", "name",
|
||||
g_variant_new_take_string (g_strdup_printf ("Endpoint %u: %s",
|
||||
pi->node_id, ei->name)));
|
||||
g_variant_builder_add (&b, "{sv}",
|
||||
"media-class", g_variant_new_string (ei->media_class));
|
||||
g_variant_builder_add (&b, "{sv}",
|
||||
"proxy-node", g_variant_new_uint64 ((guint64) proxy_node));
|
||||
g_variant_builder_add (&b, "{sv}",
|
||||
"proxy-port", g_variant_new_uint64 ((guint64) pi->proxy_port));
|
||||
endpoint_props = g_variant_builder_end (&b);
|
||||
|
||||
/* Create and register the endpoint */
|
||||
endpoint = wp_factory_make (core, "pw-audio-softdsp-endpoint",
|
||||
WP_TYPE_ENDPOINT, endpoint_props);
|
||||
/* Get the endpoint global id */
|
||||
g_object_get (endpoint, "global-id", &global_id, NULL);
|
||||
g_debug ("Created alsa endpoint for global id %d", global_id);
|
||||
|
||||
/* Register the endpoint */
|
||||
wp_endpoint_register (endpoint);
|
||||
|
||||
/* Set destroy handler to unregister endpoint when the proxy is detroyed */
|
||||
g_signal_connect (proxy_node, "destroyed", G_CALLBACK(unregister_endpoint),
|
||||
endpoint);
|
||||
|
||||
/* Clean up */
|
||||
proxy_info_destroy (pi);
|
||||
}
|
||||
|
||||
static void
|
||||
proxy_port_created(GObject *initable, GAsyncResult *res, gpointer data)
|
||||
{
|
||||
struct proxy_info *pi = data;
|
||||
const struct impl *impl = pi->impl;
|
||||
WpProxyPort *proxy_port = NULL;
|
||||
struct pw_proxy *proxy = NULL;
|
||||
|
||||
/* Get the proxy port */
|
||||
proxy_port = wp_proxy_port_new_finish(initable, res, NULL);
|
||||
if (!proxy_port)
|
||||
return;
|
||||
|
||||
/* Forward the proxy port */
|
||||
pi->proxy_port = proxy_port;
|
||||
|
||||
/* Get the node proxy */
|
||||
proxy = wp_remote_pipewire_proxy_bind (impl->remote_pipewire, pi->node_id,
|
||||
PW_TYPE_INTERFACE_Node);
|
||||
if (!proxy)
|
||||
return;
|
||||
|
||||
/* Create the proxy node asynchronically */
|
||||
wp_proxy_node_new(pi->node_id, proxy, proxy_node_created, pi);
|
||||
}
|
||||
|
||||
static void
|
||||
handle_node(WpRemotePipewire *rp, guint id, guint parent_id, gconstpointer p,
|
||||
on_node_added(WpRemotePipewire *rp, guint id, guint parent_id, gconstpointer p,
|
||||
gpointer d)
|
||||
{
|
||||
struct impl *impl = d;
|
||||
const struct spa_dict *props = p;
|
||||
const gchar *media_class = NULL, *name = NULL;
|
||||
struct endpoint_info *ei = NULL;
|
||||
g_autoptr (WpCore) core = wp_module_get_core (impl->module);
|
||||
const gchar *name, *media_class;
|
||||
GVariantBuilder b;
|
||||
g_autoptr (GVariant) endpoint_props = NULL;
|
||||
|
||||
/* Make sure the node has properties */
|
||||
g_return_if_fail(props);
|
||||
@@ -170,40 +64,20 @@ handle_node(WpRemotePipewire *rp, guint id, guint parent_id, gconstpointer p,
|
||||
if (g_str_has_prefix (media_class, "Audio/DSP"))
|
||||
return;
|
||||
|
||||
/* Create the endpoint info */
|
||||
ei = g_slice_new0 (struct endpoint_info);
|
||||
ei->name = g_strdup(name);
|
||||
ei->media_class = g_strdup(media_class);
|
||||
/* Set the properties */
|
||||
g_variant_builder_init (&b, G_VARIANT_TYPE_VARDICT);
|
||||
g_variant_builder_add (&b, "{sv}",
|
||||
"name", g_variant_new_take_string (g_strdup_printf (
|
||||
"Endpoint %u: %s", id, name)));
|
||||
g_variant_builder_add (&b, "{sv}",
|
||||
"media-class", g_variant_new_string (media_class));
|
||||
g_variant_builder_add (&b, "{sv}",
|
||||
"global-id", g_variant_new_uint32 (id));
|
||||
endpoint_props = g_variant_builder_end (&b);
|
||||
|
||||
/* Insert the alsa node info in the hash table */
|
||||
g_hash_table_insert(impl->alsa_nodes_info, GINT_TO_POINTER (id), ei);
|
||||
}
|
||||
|
||||
static void
|
||||
handle_port(WpRemotePipewire *rp, guint id, guint parent_id, gconstpointer p,
|
||||
gpointer d)
|
||||
{
|
||||
struct impl *impl = d;
|
||||
struct proxy_info *pi = NULL;
|
||||
struct pw_proxy *proxy = NULL;
|
||||
|
||||
/* Only handle ports whose parent is an alsa node */
|
||||
if (!g_hash_table_contains(impl->alsa_nodes_info, GINT_TO_POINTER (parent_id)))
|
||||
return;
|
||||
|
||||
/* Get the port proxy */
|
||||
proxy = wp_remote_pipewire_proxy_bind (rp, id, PW_TYPE_INTERFACE_Port);
|
||||
if (!proxy)
|
||||
return;
|
||||
|
||||
/* Create the port info */
|
||||
pi = g_slice_new0 (struct proxy_info);
|
||||
pi->impl = impl;
|
||||
pi->node_id = parent_id;
|
||||
pi->proxy_port = NULL;
|
||||
|
||||
/* Create the proxy port asynchronically */
|
||||
wp_proxy_port_new(id, proxy, proxy_port_created, pi);
|
||||
/* Create the endpoint async */
|
||||
wp_factory_make_async (core, "pw-audio-softdsp-endpoint", WP_TYPE_ENDPOINT,
|
||||
endpoint_props, on_endpoint_created, impl);
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -215,10 +89,6 @@ module_destroy (gpointer data)
|
||||
impl->module = NULL;
|
||||
impl->remote_pipewire = NULL;
|
||||
|
||||
/* Destroy the hash table */
|
||||
g_hash_table_unref (impl->alsa_nodes_info);
|
||||
impl->alsa_nodes_info = NULL;
|
||||
|
||||
/* Clean up */
|
||||
g_slice_free (struct impl, impl);
|
||||
}
|
||||
@@ -241,13 +111,10 @@ wireplumber__module_init (WpModule * module, WpCore * core, GVariant * args)
|
||||
impl = g_slice_new0(struct impl);
|
||||
impl->module = module;
|
||||
impl->remote_pipewire = rp;
|
||||
impl->alsa_nodes_info = g_hash_table_new_full (g_direct_hash,
|
||||
g_direct_equal, NULL, endpoint_info_destroy);
|
||||
|
||||
/* Set destroy callback for impl */
|
||||
wp_module_set_destroy_callback (module, module_destroy, impl);
|
||||
|
||||
/* Register the global addded callbacks */
|
||||
g_signal_connect(rp, "global-added::node", (GCallback)handle_node, impl);
|
||||
g_signal_connect(rp, "global-added::port", (GCallback)handle_port, impl);
|
||||
g_signal_connect(rp, "global-added::node", (GCallback)on_node_added, impl);
|
||||
}
|
||||
|
@@ -26,6 +26,12 @@ struct _WpPwAudioSoftdspEndpoint
|
||||
{
|
||||
WpEndpoint parent;
|
||||
|
||||
/* The global-id this endpoint refers to */
|
||||
guint global_id;
|
||||
|
||||
/* The task to signal the endpoint is initialized */
|
||||
GTask *init_task;
|
||||
|
||||
/* The remote pipewire */
|
||||
WpRemotePipewire *remote_pipewire;
|
||||
|
||||
@@ -36,28 +42,24 @@ struct _WpPwAudioSoftdspEndpoint
|
||||
/* Direction */
|
||||
enum pw_direction direction;
|
||||
|
||||
/* Proxy */
|
||||
/* Proxies */
|
||||
WpProxyNode *proxy_node;
|
||||
WpProxyPort *proxy_port;
|
||||
|
||||
/* DSP port id */
|
||||
uint32_t dsp_port_id;
|
||||
WpProxyNode *proxy_dsp;
|
||||
GPtrArray *proxies_dsp_port;
|
||||
|
||||
/* Volume */
|
||||
gfloat master_volume;
|
||||
gboolean master_mute;
|
||||
|
||||
/* TODO: This needs to use the new proxy API */
|
||||
struct pw_node_proxy *dsp_proxy;
|
||||
/* DSP */
|
||||
struct spa_hook dsp_listener;
|
||||
struct pw_node_info *dsp_info;
|
||||
struct pw_proxy *link_proxy;
|
||||
};
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_NODE_PROXY,
|
||||
PROP_PORT_PROXY,
|
||||
PROP_GLOBAL_ID,
|
||||
};
|
||||
|
||||
enum {
|
||||
@@ -66,28 +68,48 @@ enum {
|
||||
CONTROL_SELECTED,
|
||||
};
|
||||
|
||||
static GAsyncInitableIface *wp_endpoint_parent_interface = NULL;
|
||||
static void wp_endpoint_async_initable_init (gpointer iface,
|
||||
gpointer iface_data);
|
||||
|
||||
G_DECLARE_FINAL_TYPE (WpPwAudioSoftdspEndpoint, endpoint,
|
||||
WP_PW, AUDIO_SOFTDSP_ENDPOINT, WpEndpoint)
|
||||
|
||||
G_DEFINE_TYPE (WpPwAudioSoftdspEndpoint, endpoint, WP_TYPE_ENDPOINT)
|
||||
G_DEFINE_TYPE_WITH_CODE (WpPwAudioSoftdspEndpoint, endpoint, WP_TYPE_ENDPOINT,
|
||||
G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE,
|
||||
wp_endpoint_async_initable_init))
|
||||
|
||||
static void
|
||||
proxies_dsp_port_foreach_func(gpointer data, gpointer user_data)
|
||||
{
|
||||
GVariantBuilder *b = user_data;
|
||||
g_variant_builder_add (b, "t", data);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
endpoint_prepare_link (WpEndpoint * ep, guint32 stream_id,
|
||||
WpEndpointLink * link, GVariant ** properties, GError ** error)
|
||||
{
|
||||
WpPwAudioSoftdspEndpoint *self = WP_PW_AUDIO_SOFTDSP_ENDPOINT (ep);
|
||||
GVariantBuilder b;
|
||||
const struct pw_node_info *dsp_info = NULL;
|
||||
GVariantBuilder b, *b_ports;
|
||||
GVariant *v_ports;
|
||||
|
||||
/* Make sure dsp info is valid */
|
||||
if (!self->dsp_info)
|
||||
return FALSE;
|
||||
/* Get the dsp info */
|
||||
dsp_info = wp_proxy_node_get_info(self->proxy_dsp);
|
||||
g_return_val_if_fail (dsp_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->proxies_dsp_port, proxies_dsp_port_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 (self->dsp_info->id));
|
||||
g_variant_builder_add (&b, "{sv}", "node-port-id",
|
||||
g_variant_new_uint32 (self->dsp_port_id));
|
||||
g_variant_new_uint32 (dsp_info->id));
|
||||
g_variant_builder_add (&b, "{sv}", "ports", v_ports);
|
||||
*properties = g_variant_builder_end (&b);
|
||||
|
||||
return TRUE;
|
||||
@@ -98,15 +120,18 @@ on_dsp_running(WpPwAudioSoftdspEndpoint *self)
|
||||
{
|
||||
struct pw_properties *props;
|
||||
const struct pw_node_info *node_info = NULL;
|
||||
const struct pw_node_info *dsp_info = NULL;
|
||||
|
||||
/* Return if the node has already been linked */
|
||||
if (self->link_proxy)
|
||||
return;
|
||||
g_return_if_fail (!self->link_proxy);
|
||||
|
||||
/* Get the node info */
|
||||
node_info = wp_proxy_node_get_info(self->proxy_node);
|
||||
if (!node_info)
|
||||
return;
|
||||
g_return_if_fail (node_info);
|
||||
|
||||
/* Get the dsp info */
|
||||
dsp_info = wp_proxy_node_get_info(self->proxy_dsp);
|
||||
g_return_if_fail (dsp_info);
|
||||
|
||||
/* Create new properties */
|
||||
props = pw_properties_new(NULL, NULL);
|
||||
@@ -114,14 +139,14 @@ on_dsp_running(WpPwAudioSoftdspEndpoint *self)
|
||||
/* 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", self->dsp_info->id);
|
||||
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", node_info->id);
|
||||
pw_properties_setf(props, PW_LINK_INPUT_PORT_ID, "%d", -1);
|
||||
} else {
|
||||
pw_properties_setf(props, PW_LINK_OUTPUT_NODE_ID, "%d", node_info->id);
|
||||
pw_properties_setf(props, PW_LINK_OUTPUT_PORT_ID, "%d", -1);
|
||||
pw_properties_setf(props, PW_LINK_INPUT_NODE_ID, "%d", self->dsp_info->id);
|
||||
pw_properties_setf(props, PW_LINK_INPUT_NODE_ID, "%d", dsp_info->id);
|
||||
pw_properties_setf(props, PW_LINK_INPUT_PORT_ID, "%d", -1);
|
||||
}
|
||||
|
||||
@@ -150,9 +175,6 @@ dsp_node_event_info (void *data, const struct pw_node_info *info)
|
||||
{
|
||||
WpPwAudioSoftdspEndpoint *self = data;
|
||||
|
||||
/* Set dsp info */
|
||||
self->dsp_info = pw_node_info_update(self->dsp_info, info);
|
||||
|
||||
/* Handle the different states */
|
||||
switch (info->state) {
|
||||
case PW_NODE_STATE_IDLE:
|
||||
@@ -220,33 +242,89 @@ static const struct pw_node_proxy_events dsp_node_events = {
|
||||
.param = dsp_node_event_param,
|
||||
};
|
||||
|
||||
static void
|
||||
on_proxy_node_destroyed (WpProxy* wp_proxy, WpEndpoint *endpoint)
|
||||
{
|
||||
g_return_if_fail(WP_IS_ENDPOINT(endpoint));
|
||||
|
||||
/* Unregister the endpoint */
|
||||
wp_endpoint_unregister(endpoint);
|
||||
}
|
||||
|
||||
static void
|
||||
on_proxy_dsp_done(WpProxy *proxy, gpointer data)
|
||||
{
|
||||
WpPwAudioSoftdspEndpoint *self = data;
|
||||
|
||||
/* Don't do anything if the endpoint has already been initialized */
|
||||
if (!self->init_task)
|
||||
return;
|
||||
|
||||
/* Set destroy handler to unregister endpoint on proxy_node destruction */
|
||||
g_signal_connect (self->proxy_node, "destroyed",
|
||||
G_CALLBACK(on_proxy_node_destroyed), WP_ENDPOINT(self));
|
||||
|
||||
/* Finish the creation of the endpoint */
|
||||
g_task_return_boolean (self->init_task, TRUE);
|
||||
g_clear_object(&self->init_task);
|
||||
}
|
||||
|
||||
static void
|
||||
on_proxy_dsp_created(GObject *initable, GAsyncResult *res, gpointer data)
|
||||
{
|
||||
WpPwAudioSoftdspEndpoint *self = data;
|
||||
struct pw_node_proxy *dsp_proxy = NULL;
|
||||
const struct spa_audio_info_raw *port_format;
|
||||
struct spa_audio_info_raw format;
|
||||
uint8_t buf[1024];
|
||||
struct spa_pod_builder pod_builder = { 0, };
|
||||
struct spa_pod *param;
|
||||
|
||||
/* Get the proxy dsp */
|
||||
self->proxy_dsp = wp_proxy_node_new_finish(initable, res, NULL);
|
||||
g_return_if_fail (self->proxy_dsp);
|
||||
|
||||
/* Add a custom dsp listener */
|
||||
dsp_proxy = wp_proxy_get_pw_proxy(WP_PROXY(self->proxy_dsp));
|
||||
g_return_if_fail (dsp_proxy);
|
||||
pw_node_proxy_add_listener(dsp_proxy, &self->dsp_listener,
|
||||
&dsp_node_events, self);
|
||||
|
||||
/* Emit the props param */
|
||||
pw_node_proxy_enum_params (dsp_proxy, 0, SPA_PARAM_Props, 0, -1, NULL);
|
||||
|
||||
/* Get the port format */
|
||||
port_format = wp_proxy_port_get_format(self->proxy_port);
|
||||
g_return_if_fail (port_format);
|
||||
format = *port_format;
|
||||
|
||||
/* Build the param profile */
|
||||
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));
|
||||
|
||||
/* Set the param profile to emit the dsp ports */
|
||||
pw_node_proxy_set_param(dsp_proxy, SPA_PARAM_Profile, 0, param);
|
||||
}
|
||||
|
||||
static void
|
||||
emit_audio_dsp_node (WpPwAudioSoftdspEndpoint *self)
|
||||
{
|
||||
struct pw_properties *props;
|
||||
const char *dsp_name = NULL;
|
||||
uint8_t buf[1024];
|
||||
struct spa_pod_builder pod_builder = { 0, };
|
||||
struct spa_pod *param;
|
||||
struct pw_node_proxy *dsp_proxy = NULL;
|
||||
const struct pw_node_info *node_info;
|
||||
const struct spa_audio_info_raw *port_format;
|
||||
struct spa_audio_info_raw format;
|
||||
|
||||
/* Get the node info */
|
||||
node_info = wp_proxy_node_get_info(self->proxy_node);
|
||||
if (!node_info)
|
||||
return;
|
||||
|
||||
/* Get the port format */
|
||||
port_format = wp_proxy_port_get_format(self->proxy_port);
|
||||
if (!port_format)
|
||||
return;
|
||||
format = *port_format;
|
||||
g_return_if_fail (node_info);
|
||||
|
||||
/* Get the properties */
|
||||
props = pw_properties_new_dict(node_info->props);
|
||||
if (!props)
|
||||
return;
|
||||
g_return_if_fail (props);
|
||||
|
||||
/* Get the DSP name */
|
||||
dsp_name = pw_properties_get(props, "device.nick");
|
||||
@@ -259,87 +337,25 @@ emit_audio_dsp_node (WpPwAudioSoftdspEndpoint *self)
|
||||
pw_properties_setf(props, "audio-dsp.maxbuffer", "%ld",
|
||||
MAX_QUANTUM_SIZE * sizeof(float));
|
||||
|
||||
/* Set the DSP proxy and listener */
|
||||
self->dsp_proxy = wp_remote_pipewire_create_object(self->remote_pipewire,
|
||||
/* Create the proxy dsp async */
|
||||
dsp_proxy = wp_remote_pipewire_create_object(self->remote_pipewire,
|
||||
"audio-dsp", PW_TYPE_INTERFACE_Node, &props->dict);
|
||||
pw_node_proxy_add_listener(self->dsp_proxy, &self->dsp_listener,
|
||||
&dsp_node_events, self);
|
||||
pw_node_proxy_enum_params (self->dsp_proxy, 0, SPA_PARAM_Props, 0, -1, NULL);
|
||||
|
||||
/* Set DSP proxy params */
|
||||
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((struct pw_node_proxy*)self->dsp_proxy,
|
||||
SPA_PARAM_Profile, 0, param);
|
||||
wp_proxy_node_new(pw_proxy_get_id((struct pw_proxy *)dsp_proxy), dsp_proxy,
|
||||
on_proxy_dsp_created, self);
|
||||
|
||||
/* Clean up */
|
||||
pw_properties_free(props);
|
||||
}
|
||||
|
||||
static void
|
||||
handle_port(WpRemotePipewire *rp, guint id, guint parent_id, gconstpointer p,
|
||||
gpointer d)
|
||||
on_proxy_node_created(GObject *initable, GAsyncResult *res, gpointer data)
|
||||
{
|
||||
WpPwAudioSoftdspEndpoint *self = d;
|
||||
const struct spa_dict *props = p;
|
||||
const char *direction_prop = NULL;
|
||||
enum pw_direction direction;
|
||||
|
||||
/* Make sure the dsp port is not already set*/
|
||||
if (self->dsp_port_id != 0)
|
||||
return;
|
||||
|
||||
/* Make sure the port has porperties */
|
||||
if (!props)
|
||||
return;
|
||||
|
||||
/* Only handle ports owned by this endpoint */
|
||||
if (!self->dsp_info || self->dsp_info->id != parent_id)
|
||||
return;
|
||||
|
||||
/* Get the direction property */
|
||||
direction_prop = spa_dict_lookup(props, "port.direction");
|
||||
if (!direction_prop)
|
||||
return;
|
||||
direction =
|
||||
!strcmp(direction_prop, "out") ? PW_DIRECTION_OUTPUT : PW_DIRECTION_INPUT;
|
||||
|
||||
/* Only handle ports with the oposit direction of the endpoint */
|
||||
if (self->direction == direction)
|
||||
return;
|
||||
|
||||
/* Set the dsp port id */
|
||||
self->dsp_port_id = id;
|
||||
}
|
||||
|
||||
static void
|
||||
endpoint_constructed (GObject * object)
|
||||
{
|
||||
WpPwAudioSoftdspEndpoint *self = WP_PW_AUDIO_SOFTDSP_ENDPOINT (object);
|
||||
g_autoptr (WpCore) core = wp_endpoint_get_core(WP_ENDPOINT(self));
|
||||
const gchar *media_class = wp_endpoint_get_media_class (WP_ENDPOINT (self));
|
||||
WpPwAudioSoftdspEndpoint *self = data;
|
||||
GVariantDict d;
|
||||
|
||||
/* Set Remote Pipewire */
|
||||
self->remote_pipewire = wp_core_get_global (core, WP_GLOBAL_REMOTE_PIPEWIRE);
|
||||
if (!self->remote_pipewire)
|
||||
g_critical ("failed to get remote pipewire ");
|
||||
|
||||
/* Register the global-added::port callback in remote pipewire */
|
||||
g_signal_connect(self->remote_pipewire, "global-added::port",
|
||||
(GCallback)handle_port, self);
|
||||
|
||||
/* 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
|
||||
g_critical ("failed to parse direction");
|
||||
/* Get the proxy node */
|
||||
self->proxy_node = wp_proxy_node_new_finish(initable, res, NULL);
|
||||
g_return_if_fail (self->proxy_node);
|
||||
|
||||
/* Emit the audio DSP node */
|
||||
emit_audio_dsp_node(self);
|
||||
@@ -373,8 +389,95 @@ endpoint_constructed (GObject * object)
|
||||
g_variant_dict_insert (&d, "type", "s", "b");
|
||||
g_variant_dict_insert (&d, "default-value", "b", self->selected);
|
||||
wp_endpoint_register_control (WP_ENDPOINT (self), g_variant_dict_end (&d));
|
||||
}
|
||||
|
||||
G_OBJECT_CLASS (endpoint_parent_class)->constructed (object);
|
||||
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_new_finish(initable, res, NULL);
|
||||
g_return_if_fail (self->proxy_port);
|
||||
|
||||
/* 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
|
||||
handle_node_port(WpPwAudioSoftdspEndpoint *self, guint id, guint parent_id,
|
||||
const struct spa_dict *props)
|
||||
{
|
||||
struct pw_port_proxy *port_proxy = NULL;
|
||||
|
||||
/* 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
|
||||
on_proxy_dsp_port_created(GObject *initable, GAsyncResult *res, gpointer data)
|
||||
{
|
||||
WpPwAudioSoftdspEndpoint *self = data;
|
||||
WpProxyPort *proxy_dsp_port = NULL;
|
||||
|
||||
/* Get the proxy dsp port */
|
||||
proxy_dsp_port = wp_proxy_port_new_finish(initable, res, NULL);
|
||||
g_return_if_fail (proxy_dsp_port);
|
||||
|
||||
/* Add the proxy dsp port to the array */
|
||||
g_return_if_fail (self->proxies_dsp_port);
|
||||
g_ptr_array_add(self->proxies_dsp_port, proxy_dsp_port);
|
||||
|
||||
/* Register a callback to know when all the dsp ports have been emitted */
|
||||
g_signal_connect(self->proxy_dsp, "done", (GCallback)on_proxy_dsp_done, self);
|
||||
wp_proxy_sync (WP_PROXY(self->proxy_dsp));
|
||||
}
|
||||
|
||||
static void
|
||||
handle_dsp_port(WpPwAudioSoftdspEndpoint *self, guint id, guint parent_id,
|
||||
const struct spa_dict *props)
|
||||
{
|
||||
struct pw_port_proxy *port_proxy = NULL;
|
||||
|
||||
/* Create the proxy 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_proxy_dsp_port_created, self);
|
||||
}
|
||||
|
||||
static void
|
||||
on_port_added(WpRemotePipewire *rp, guint id, guint parent_id, gconstpointer p,
|
||||
gpointer d)
|
||||
{
|
||||
WpPwAudioSoftdspEndpoint *self = d;
|
||||
const struct spa_dict *props = p;
|
||||
const struct pw_node_info *dsp_info = NULL;
|
||||
|
||||
/* Check if it is a node port and handle it */
|
||||
if (self->global_id == parent_id) {
|
||||
handle_node_port(self, id, parent_id, props);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Otherwise, check if it is a dsp port and handle it */
|
||||
if (!self->proxy_dsp)
|
||||
return;
|
||||
dsp_info = wp_proxy_node_get_info (self->proxy_dsp);
|
||||
if (!dsp_info || dsp_info->id != parent_id)
|
||||
return;
|
||||
handle_dsp_port(self, id, parent_id, props);
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -382,27 +485,23 @@ endpoint_finalize (GObject * object)
|
||||
{
|
||||
WpPwAudioSoftdspEndpoint *self = WP_PW_AUDIO_SOFTDSP_ENDPOINT (object);
|
||||
|
||||
/* Set to NULL remote pipewire as we don't own the reference */
|
||||
self->remote_pipewire = NULL;
|
||||
|
||||
/* Unref the proxy node */
|
||||
g_clear_object (&self->proxy_node);
|
||||
|
||||
/* Unref the proxy port */
|
||||
g_clear_object (&self->proxy_port);
|
||||
|
||||
/* Clear the dsp info */
|
||||
if (self->dsp_info) {
|
||||
pw_node_info_free(self->dsp_info);
|
||||
self->dsp_info = NULL;
|
||||
/* Destroy the proxies port */
|
||||
if (self->proxies_dsp_port) {
|
||||
g_ptr_array_free(self->proxies_dsp_port, TRUE);
|
||||
self->proxies_dsp_port = NULL;
|
||||
}
|
||||
|
||||
/* Destroy the dsp_proxy */
|
||||
if (self->dsp_proxy) {
|
||||
spa_hook_remove (&self->dsp_listener);
|
||||
pw_proxy_destroy ((struct pw_proxy *) self->dsp_proxy);
|
||||
self->dsp_proxy = NULL;
|
||||
}
|
||||
/* Destroy the proxy node */
|
||||
g_clear_object(&self->proxy_node);
|
||||
|
||||
/* Destroy the proxy port */
|
||||
g_clear_object(&self->proxy_port);
|
||||
|
||||
/* Destroy the proxy dsp */
|
||||
g_clear_object(&self->proxy_dsp);
|
||||
|
||||
/* Destroy the done task */
|
||||
g_clear_object(&self->init_task);
|
||||
|
||||
G_OBJECT_CLASS (endpoint_parent_class)->finalize (object);
|
||||
}
|
||||
@@ -414,13 +513,8 @@ endpoint_set_property (GObject * object, guint property_id,
|
||||
WpPwAudioSoftdspEndpoint *self = WP_PW_AUDIO_SOFTDSP_ENDPOINT (object);
|
||||
|
||||
switch (property_id) {
|
||||
case PROP_NODE_PROXY:
|
||||
g_clear_object(&self->proxy_node);
|
||||
self->proxy_node = g_value_dup_object(value);
|
||||
break;
|
||||
case PROP_PORT_PROXY:
|
||||
g_clear_object(&self->proxy_port);
|
||||
self->proxy_port = g_value_dup_object(value);
|
||||
case PROP_GLOBAL_ID:
|
||||
self->global_id = g_value_get_uint(value);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
@@ -435,11 +529,8 @@ endpoint_get_property (GObject * object, guint property_id,
|
||||
WpPwAudioSoftdspEndpoint *self = WP_PW_AUDIO_SOFTDSP_ENDPOINT (object);
|
||||
|
||||
switch (property_id) {
|
||||
case PROP_NODE_PROXY:
|
||||
g_value_set_object (value, self->proxy_node);
|
||||
break;
|
||||
case PROP_PORT_PROXY:
|
||||
g_value_set_object (value, self->proxy_port);
|
||||
case PROP_GLOBAL_ID:
|
||||
g_value_set_uint (value, self->global_id);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
@@ -472,14 +563,14 @@ endpoint_set_control_value (WpEndpoint * ep, guint32 control_id,
|
||||
WpPwAudioSoftdspEndpoint *self = WP_PW_AUDIO_SOFTDSP_ENDPOINT (ep);
|
||||
char buf[1024];
|
||||
struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buf, sizeof(buf));
|
||||
struct pw_node_proxy *dsp_proxy = NULL;
|
||||
float volume;
|
||||
bool mute;
|
||||
|
||||
if (!self->dsp_proxy) {
|
||||
g_debug("WpEndpoint:%p too early to set control, dsp is not created yet",
|
||||
self);
|
||||
return FALSE;
|
||||
}
|
||||
/* Get the pipewire dsp proxy */
|
||||
g_return_val_if_fail (self->proxy_dsp, FALSE);
|
||||
dsp_proxy = wp_proxy_get_pw_proxy (WP_PROXY(self->proxy_dsp));
|
||||
g_return_val_if_fail (dsp_proxy, FALSE);
|
||||
|
||||
switch (control_id) {
|
||||
case CONTROL_VOLUME:
|
||||
@@ -488,13 +579,13 @@ endpoint_set_control_value (WpEndpoint * ep, guint32 control_id,
|
||||
g_debug("WpEndpoint:%p set volume control (%u) value, vol:%f", self,
|
||||
control_id, volume);
|
||||
|
||||
pw_node_proxy_set_param (self->dsp_proxy,
|
||||
pw_node_proxy_set_param (dsp_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 (self->dsp_proxy, 0, SPA_PARAM_Props, 0, -1,
|
||||
pw_node_proxy_enum_params (dsp_proxy, 0, SPA_PARAM_Props, 0, -1,
|
||||
NULL);
|
||||
break;
|
||||
|
||||
@@ -504,13 +595,13 @@ endpoint_set_control_value (WpEndpoint * ep, guint32 control_id,
|
||||
g_debug("WpEndpoint:%p set mute control (%u) value, mute:%d", self,
|
||||
control_id, mute);
|
||||
|
||||
pw_node_proxy_set_param (self->dsp_proxy,
|
||||
pw_node_proxy_set_param (dsp_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 (self->dsp_proxy, 0, SPA_PARAM_Props, 0, -1,
|
||||
pw_node_proxy_enum_params (dsp_proxy, 0, SPA_PARAM_Props, 0, -1,
|
||||
NULL);
|
||||
break;
|
||||
|
||||
@@ -527,6 +618,51 @@ endpoint_set_control_value (WpEndpoint * ep, guint32 control_id,
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
wp_endpoint_init_async (GAsyncInitable *initable, int io_priority,
|
||||
GCancellable *cancellable, GAsyncReadyCallback callback, gpointer data)
|
||||
{
|
||||
WpPwAudioSoftdspEndpoint *self = WP_PW_AUDIO_SOFTDSP_ENDPOINT (initable);
|
||||
g_autoptr (WpCore) core = wp_endpoint_get_core(WP_ENDPOINT(self));
|
||||
const gchar *media_class = wp_endpoint_get_media_class (WP_ENDPOINT (self));
|
||||
|
||||
/* Create the async task */
|
||||
self->init_task = g_task_new (initable, cancellable, callback, data);
|
||||
|
||||
/* Init the proxies_dsp_port array */
|
||||
self->proxies_dsp_port = g_ptr_array_new_full(4, (GDestroyNotify)g_object_unref);
|
||||
|
||||
/* 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
|
||||
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(self->remote_pipewire, "global-added::port",
|
||||
(GCallback)on_port_added, self);
|
||||
|
||||
/* Call the parent interface */
|
||||
wp_endpoint_parent_interface->init_async (initable, io_priority, cancellable,
|
||||
callback, data);
|
||||
}
|
||||
|
||||
static void
|
||||
wp_endpoint_async_initable_init (gpointer iface, gpointer iface_data)
|
||||
{
|
||||
GAsyncInitableIface *ai_iface = iface;
|
||||
|
||||
/* Set the parent interface */
|
||||
wp_endpoint_parent_interface = g_type_interface_peek_parent (iface);
|
||||
|
||||
/* Only set the init_async */
|
||||
ai_iface->init_async = wp_endpoint_init_async;
|
||||
}
|
||||
|
||||
static void
|
||||
endpoint_init (WpPwAudioSoftdspEndpoint * self)
|
||||
{
|
||||
@@ -538,7 +674,6 @@ endpoint_class_init (WpPwAudioSoftdspEndpointClass * klass)
|
||||
GObjectClass *object_class = (GObjectClass *) klass;
|
||||
WpEndpointClass *endpoint_class = (WpEndpointClass *) klass;
|
||||
|
||||
object_class->constructed = endpoint_constructed;
|
||||
object_class->finalize = endpoint_finalize;
|
||||
object_class->set_property = endpoint_set_property;
|
||||
object_class->get_property = endpoint_get_property;
|
||||
@@ -548,48 +683,42 @@ endpoint_class_init (WpPwAudioSoftdspEndpointClass * klass)
|
||||
endpoint_class->set_control_value = endpoint_set_control_value;
|
||||
|
||||
/* Instal the properties */
|
||||
g_object_class_install_property (object_class, PROP_NODE_PROXY,
|
||||
g_param_spec_object ("node-proxy", "node-proxy",
|
||||
"Pointer to the node proxy of the device", WP_TYPE_PROXY_NODE,
|
||||
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
|
||||
g_object_class_install_property (object_class, PROP_PORT_PROXY,
|
||||
g_param_spec_object ("port-proxy", "port-proxy",
|
||||
"Pointer to the port proxy of the device", WP_TYPE_PROXY_PORT,
|
||||
g_object_class_install_property (object_class, PROP_GLOBAL_ID,
|
||||
g_param_spec_uint ("global-id", "global-id",
|
||||
"The global Id this endpoint refers to", 0, G_MAXUINT, 0,
|
||||
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
|
||||
}
|
||||
|
||||
static gpointer
|
||||
endpoint_factory (WpFactory * factory, GType type, GVariant * properties)
|
||||
void
|
||||
endpoint_factory (WpFactory * factory, GType type, GVariant * properties,
|
||||
GAsyncReadyCallback ready, gpointer user_data)
|
||||
{
|
||||
g_autoptr (WpCore) core = NULL;
|
||||
const gchar *name = NULL;
|
||||
const gchar *media_class = NULL;
|
||||
guint64 proxy_node, proxy_port;
|
||||
const gchar *name, *media_class;
|
||||
guint global_id;
|
||||
|
||||
/* Make sure the type is correct */
|
||||
g_return_val_if_fail(type == WP_TYPE_ENDPOINT, NULL);
|
||||
g_return_if_fail(type == WP_TYPE_ENDPOINT);
|
||||
|
||||
/* Get the Core */
|
||||
core = wp_factory_get_core(factory);
|
||||
g_return_val_if_fail (core, NULL);
|
||||
g_return_if_fail (core);
|
||||
|
||||
/* Get the properties */
|
||||
if (!g_variant_lookup (properties, "name", "&s", &name))
|
||||
return NULL;
|
||||
return;
|
||||
if (!g_variant_lookup (properties, "media-class", "&s", &media_class))
|
||||
return NULL;
|
||||
if (!g_variant_lookup (properties, "proxy-node", "t", &proxy_node))
|
||||
return NULL;
|
||||
if (!g_variant_lookup (properties, "proxy-port", "t", &proxy_port))
|
||||
return NULL;
|
||||
return;
|
||||
if (!g_variant_lookup (properties, "global-id", "u", &global_id))
|
||||
return;
|
||||
|
||||
/* Create and return the softdsp endpoint object */
|
||||
return g_object_new (endpoint_get_type (),
|
||||
g_async_initable_new_async (
|
||||
endpoint_get_type (), G_PRIORITY_DEFAULT, NULL, ready, user_data,
|
||||
"core", core,
|
||||
"name", name,
|
||||
"media-class", media_class,
|
||||
"node-proxy", (gpointer) proxy_node,
|
||||
"port-proxy", (gpointer) proxy_port,
|
||||
"global-id", global_id,
|
||||
NULL);
|
||||
}
|
||||
|
||||
@@ -597,5 +726,5 @@ void
|
||||
wireplumber__module_init (WpModule * module, WpCore * core, GVariant * args)
|
||||
{
|
||||
/* Register the softdsp endpoint */
|
||||
wp_factory_new (core, "pw-audio-softdsp-endpoint", endpoint_factory);
|
||||
wp_factory_new_async (core, "pw-audio-softdsp-endpoint", endpoint_factory);
|
||||
}
|
||||
|
Reference in New Issue
Block a user