mirror of
https://gitlab.freedesktop.org/pipewire/wireplumber.git
synced 2026-05-01 04:37:58 +02:00
Calling sync() in loops may end up looping forever if the operating system's scheduling allows pipewire to reply to this sync() before wireplumber's event loop has a chance to become idle. In order for the new ports to appear, the object manager that monitors them needs to emit "objects-changed", which only ever happens in an idle callback. If the reply to sync() arrives before the idle callback, it gets prioritized and processed, which causes another sync(), and so on... Since setting PortFormat in the adapter always changes the ports, watching for "ports-changed" feels like a better solution. Still, there is more room for improvement.
573 lines
18 KiB
C
573 lines
18 KiB
C
/* WirePlumber
|
|
*
|
|
* Copyright © 2020 Collabora Ltd.
|
|
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
|
|
*
|
|
* SPDX-License-Identifier: MIT
|
|
*/
|
|
|
|
#include <wp/wp.h>
|
|
#include <pipewire/keys.h>
|
|
#include <pipewire/properties.h>
|
|
|
|
#include <spa/param/format.h>
|
|
#include <spa/param/audio/raw.h>
|
|
#include <spa/param/param.h>
|
|
|
|
#include "module-si-audio-adapter/audio-utils.h"
|
|
|
|
#define SI_FACTORY_NAME "si-audio-adapter"
|
|
|
|
struct _WpSiAudioAdapter
|
|
{
|
|
WpSessionItem parent;
|
|
|
|
/* configuration */
|
|
WpNode *node;
|
|
gchar name[96];
|
|
gchar media_class[32];
|
|
gboolean control_port;
|
|
gboolean monitor;
|
|
WpDirection direction;
|
|
gboolean is_device;
|
|
gboolean dont_remix;
|
|
gboolean is_autoconnect;
|
|
WpSpaPod *format;
|
|
gchar mode[32];
|
|
GTask *format_task;
|
|
};
|
|
|
|
static void si_audio_adapter_linkable_init (WpSiLinkableInterface * iface);
|
|
static void si_audio_adapter_adapter_init (WpSiAdapterInterface * iface);
|
|
|
|
G_DECLARE_FINAL_TYPE(WpSiAudioAdapter, si_audio_adapter, WP, SI_AUDIO_ADAPTER,
|
|
WpSessionItem)
|
|
G_DEFINE_TYPE_WITH_CODE (WpSiAudioAdapter, si_audio_adapter,
|
|
WP_TYPE_SESSION_ITEM,
|
|
G_IMPLEMENT_INTERFACE (WP_TYPE_SI_LINKABLE, si_audio_adapter_linkable_init)
|
|
G_IMPLEMENT_INTERFACE (WP_TYPE_SI_ADAPTER, si_audio_adapter_adapter_init))
|
|
|
|
static void
|
|
si_audio_adapter_init (WpSiAudioAdapter * self)
|
|
{
|
|
}
|
|
|
|
static void
|
|
si_audio_adapter_reset (WpSessionItem * item)
|
|
{
|
|
WpSiAudioAdapter *self = WP_SI_AUDIO_ADAPTER (item);
|
|
|
|
/* deactivate first */
|
|
wp_object_deactivate (WP_OBJECT (self), WP_SESSION_ITEM_FEATURE_ACTIVE);
|
|
|
|
/* reset */
|
|
g_clear_object (&self->node);
|
|
self->name[0] = '\0';
|
|
self->media_class[0] = '\0';
|
|
self->control_port = FALSE;
|
|
self->monitor = FALSE;
|
|
self->direction = WP_DIRECTION_INPUT;
|
|
self->is_device = FALSE;
|
|
self->dont_remix = FALSE;
|
|
self->is_autoconnect = FALSE;
|
|
if (self->format_task) {
|
|
g_task_return_new_error (self->format_task, WP_DOMAIN_LIBRARY,
|
|
WP_LIBRARY_ERROR_OPERATION_FAILED,
|
|
"item deactivated before format set");
|
|
g_clear_object (&self->format_task);
|
|
}
|
|
g_clear_pointer (&self->format, wp_spa_pod_unref);
|
|
self->mode[0] = '\0';
|
|
|
|
WP_SESSION_ITEM_CLASS (si_audio_adapter_parent_class)->reset (item);
|
|
}
|
|
|
|
static gboolean
|
|
si_audio_adapter_configure (WpSessionItem * item, WpProperties *p)
|
|
{
|
|
WpSiAudioAdapter *self = WP_SI_AUDIO_ADAPTER (item);
|
|
g_autoptr (WpProperties) si_props = wp_properties_ensure_unique_owner (p);
|
|
WpNode *node = NULL;
|
|
g_autoptr (WpProperties) node_props = NULL;
|
|
const gchar *str;
|
|
|
|
/* reset previous config */
|
|
si_audio_adapter_reset (item);
|
|
|
|
str = wp_properties_get (si_props, "node");
|
|
if (!str || sscanf(str, "%p", &node) != 1 || !WP_IS_NODE (node))
|
|
return FALSE;
|
|
|
|
node_props = wp_pipewire_object_get_properties (WP_PIPEWIRE_OBJECT (node));
|
|
|
|
str = wp_properties_get (node_props, PW_KEY_STREAM_DONT_REMIX);
|
|
self->dont_remix = str && pw_properties_parse_bool (str);
|
|
|
|
str = wp_properties_get (node_props, PW_KEY_NODE_AUTOCONNECT);
|
|
self->is_autoconnect = str && pw_properties_parse_bool (str);
|
|
|
|
str = wp_properties_get (si_props, "name");
|
|
if (str) {
|
|
strncpy (self->name, str, sizeof (self->name) - 1);
|
|
} else {
|
|
str = wp_properties_get (node_props, PW_KEY_NODE_NAME);
|
|
if (G_LIKELY (str))
|
|
strncpy (self->name, str, sizeof (self->name) - 1);
|
|
else
|
|
strncpy (self->name, "Unknown", sizeof (self->name) - 1);
|
|
wp_properties_set (si_props, "name", self->name);
|
|
}
|
|
|
|
str = wp_properties_get (si_props, "media.class");
|
|
if (str) {
|
|
strncpy (self->media_class, str, sizeof (self->media_class) - 1);
|
|
} else {
|
|
str = wp_properties_get (node_props, PW_KEY_MEDIA_CLASS);
|
|
if (G_LIKELY (str))
|
|
strncpy (self->media_class, str, sizeof (self->media_class) - 1);
|
|
else
|
|
strncpy (self->media_class, "Unknown", sizeof (self->media_class) - 1);
|
|
wp_properties_set (si_props, "media.class", self->media_class);
|
|
}
|
|
self->is_device = strstr (self->media_class, "Stream") == NULL;
|
|
|
|
if (strstr (self->media_class, "Source") ||
|
|
strstr (self->media_class, "Output"))
|
|
self->direction = WP_DIRECTION_OUTPUT;
|
|
wp_properties_setf (si_props, "direction", "%u", self->direction);
|
|
|
|
str = wp_properties_get (si_props, "enable.control.port");
|
|
if (str && sscanf(str, "%u", &self->control_port) != 1)
|
|
return FALSE;
|
|
if (!str)
|
|
wp_properties_setf (si_props, "enable.control.port", "%u",
|
|
self->control_port);
|
|
|
|
str = wp_properties_get (si_props, "enable.monitor");
|
|
if (str && sscanf(str, "%u", &self->monitor) != 1)
|
|
return FALSE;
|
|
if (!str)
|
|
wp_properties_setf (si_props, "enable.monitor", "%u", self->monitor);
|
|
|
|
self->node = g_object_ref (node);
|
|
|
|
wp_properties_set (si_props, "si.factory.name", SI_FACTORY_NAME);
|
|
wp_properties_setf (si_props, "is.device", "%u", self->is_device);
|
|
wp_properties_setf (si_props, "dont.remix", "%u", self->dont_remix);
|
|
wp_properties_setf (si_props, "is.autoconnect", "%u", self->is_autoconnect);
|
|
wp_session_item_set_properties (WP_SESSION_ITEM (self),
|
|
g_steal_pointer (&si_props));
|
|
return TRUE;
|
|
}
|
|
|
|
static gpointer
|
|
si_audio_adapter_get_associated_proxy (WpSessionItem * item, GType proxy_type)
|
|
{
|
|
WpSiAudioAdapter *self = WP_SI_AUDIO_ADAPTER (item);
|
|
|
|
if (proxy_type == WP_TYPE_NODE)
|
|
return self->node ? g_object_ref (self->node) : NULL;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
si_audio_adapter_disable_active (WpSessionItem *si)
|
|
{
|
|
WpSiAudioAdapter *self = WP_SI_AUDIO_ADAPTER (si);
|
|
|
|
wp_object_deactivate (WP_OBJECT (self->node), WP_NODE_FEATURE_PORTS);
|
|
|
|
wp_object_update_features (WP_OBJECT (self), 0,
|
|
WP_SESSION_ITEM_FEATURE_ACTIVE);
|
|
}
|
|
|
|
static WpSpaPod *
|
|
format_audio_raw_build (const struct spa_audio_info_raw *info)
|
|
{
|
|
g_autoptr (WpSpaPodBuilder) builder = wp_spa_pod_builder_new_object (
|
|
"Spa:Pod:Object:Param:Format", "Format");
|
|
wp_spa_pod_builder_add (builder,
|
|
"mediaType", "K", "audio",
|
|
"mediaSubtype", "K", "raw",
|
|
"format", "I", info->format,
|
|
"rate", "i", info->rate,
|
|
"channels", "i", info->channels,
|
|
NULL);
|
|
|
|
if (!SPA_FLAG_IS_SET (info->flags, SPA_AUDIO_FLAG_UNPOSITIONED)) {
|
|
/* Build the position array spa pod */
|
|
g_autoptr (WpSpaPodBuilder) position_builder = wp_spa_pod_builder_new_array ();
|
|
for (guint i = 0; i < info->channels; i++)
|
|
wp_spa_pod_builder_add_id (position_builder, info->position[i]);
|
|
|
|
/* Add the position property */
|
|
wp_spa_pod_builder_add_property (builder, "position");
|
|
g_autoptr (WpSpaPod) position = wp_spa_pod_builder_end (position_builder);
|
|
wp_spa_pod_builder_add_pod (builder, position);
|
|
}
|
|
|
|
return wp_spa_pod_builder_end (builder);
|
|
}
|
|
|
|
static void
|
|
on_format_set (GObject *obj, GAsyncResult * res, gpointer p)
|
|
{
|
|
WpTransition *transition = p;
|
|
WpSiAudioAdapter *self = wp_transition_get_source_object (transition);
|
|
g_autoptr (GError) error = NULL;
|
|
|
|
wp_si_adapter_set_ports_format_finish (WP_SI_ADAPTER (self), res, &error);
|
|
if (error) {
|
|
wp_transition_return_error (transition, g_steal_pointer (&error));
|
|
return;
|
|
}
|
|
|
|
wp_object_update_features (WP_OBJECT (self),
|
|
WP_SESSION_ITEM_FEATURE_ACTIVE, 0);
|
|
}
|
|
|
|
static void
|
|
on_node_enum_format_done (WpPipewireObject * proxy, GAsyncResult * res,
|
|
WpTransition * transition)
|
|
{
|
|
WpSiAudioAdapter *self = wp_transition_get_source_object (transition);
|
|
g_autoptr (WpIterator) formats = NULL;
|
|
g_autoptr (GError) error = NULL;
|
|
g_autoptr (WpSpaPod) format = NULL;
|
|
struct spa_audio_info_raw spa_format;
|
|
|
|
formats = wp_pipewire_object_enum_params_finish (proxy, res, &error);
|
|
if (error) {
|
|
wp_transition_return_error (transition, g_steal_pointer (&error));
|
|
return;
|
|
}
|
|
|
|
/* chose sensible format */
|
|
if (!choose_sensible_raw_audio_format (formats, 34, &spa_format)) {
|
|
wp_transition_return_error (transition,
|
|
g_error_new (WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
|
|
"failed to choose a sensible audio format"));
|
|
return;
|
|
}
|
|
|
|
/* set the chosen format on the node */
|
|
format = format_audio_raw_build (&spa_format);
|
|
wp_pipewire_object_set_param (WP_PIPEWIRE_OBJECT (self->node), "Format", 0,
|
|
wp_spa_pod_ref (format));
|
|
|
|
/* set chosen format in the ports */
|
|
wp_si_adapter_set_ports_format (WP_SI_ADAPTER (self), wp_spa_pod_ref (format),
|
|
"dsp", on_format_set, transition);
|
|
}
|
|
|
|
static void
|
|
on_node_ports_changed (WpObject * node, WpSiAudioAdapter *self)
|
|
{
|
|
/* finish the task started by _set_ports_format() */
|
|
if (self->format_task && wp_node_get_n_ports (self->node) > 0) {
|
|
g_autoptr (GTask) t = g_steal_pointer (&self->format_task);
|
|
g_task_return_boolean (t, TRUE);
|
|
}
|
|
}
|
|
|
|
static void
|
|
on_feature_ports_ready (WpObject * node, GAsyncResult * res,
|
|
WpTransition * transition)
|
|
{
|
|
WpSiAudioAdapter *self = wp_transition_get_source_object (transition);
|
|
g_autoptr (GError) error = NULL;
|
|
|
|
if (!wp_object_activate_finish (node, res, &error)) {
|
|
wp_transition_return_error (transition, g_steal_pointer (&error));
|
|
return;
|
|
}
|
|
|
|
g_signal_connect_object (node, "ports-changed",
|
|
(GCallback) on_node_ports_changed, self, 0);
|
|
|
|
/* If device node, enum available formats and set one of them */
|
|
if (self->is_device || self->dont_remix || !self->is_autoconnect)
|
|
wp_pipewire_object_enum_params (WP_PIPEWIRE_OBJECT (self->node),
|
|
"EnumFormat", NULL, NULL,
|
|
(GAsyncReadyCallback) on_node_enum_format_done, transition);
|
|
|
|
/* Otherwise just finish activating */
|
|
else
|
|
wp_object_update_features (WP_OBJECT (self),
|
|
WP_SESSION_ITEM_FEATURE_ACTIVE, 0);
|
|
}
|
|
|
|
static void
|
|
si_audio_adapter_enable_active (WpSessionItem *si, WpTransition *transition)
|
|
{
|
|
WpSiAudioAdapter *self = WP_SI_AUDIO_ADAPTER (si);
|
|
|
|
if (!wp_session_item_is_configured (si)) {
|
|
wp_transition_return_error (transition,
|
|
g_error_new (WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT,
|
|
"si-audio-adapter: item is not configured"));
|
|
return;
|
|
}
|
|
|
|
if (!(wp_object_get_active_features (WP_OBJECT (self->node))
|
|
& WP_PIPEWIRE_OBJECT_FEATURES_MINIMAL)) {
|
|
wp_transition_return_error (transition,
|
|
g_error_new (WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT,
|
|
"si-audio-adapter: node minimal feature not enabled"));
|
|
return;
|
|
}
|
|
|
|
/* enable ports feature */
|
|
wp_object_activate (WP_OBJECT (self->node), WP_NODE_FEATURE_PORTS,
|
|
NULL, (GAsyncReadyCallback) on_feature_ports_ready, transition);
|
|
}
|
|
|
|
static WpObjectFeatures
|
|
si_audio_adapter_get_supported_features (WpObject * self)
|
|
{
|
|
return WP_SESSION_ITEM_FEATURE_ACTIVE;
|
|
}
|
|
|
|
static void
|
|
si_audio_adapter_class_init (WpSiAudioAdapterClass * klass)
|
|
{
|
|
WpObjectClass * wpobject_class = (WpObjectClass *) klass;
|
|
WpSessionItemClass *si_class = (WpSessionItemClass *) klass;
|
|
|
|
wpobject_class->get_supported_features =
|
|
si_audio_adapter_get_supported_features;
|
|
|
|
si_class->reset = si_audio_adapter_reset;
|
|
si_class->configure = si_audio_adapter_configure;
|
|
si_class->get_associated_proxy = si_audio_adapter_get_associated_proxy;
|
|
si_class->disable_active = si_audio_adapter_disable_active;
|
|
si_class->enable_active = si_audio_adapter_enable_active;
|
|
}
|
|
|
|
static WpSpaPod *
|
|
si_audio_adapter_get_ports_format (WpSiAdapter * item, const gchar **mode)
|
|
{
|
|
WpSiAudioAdapter *self = WP_SI_AUDIO_ADAPTER (item);
|
|
if (mode)
|
|
*mode = self->mode;
|
|
return self->format ? wp_spa_pod_ref (self->format) : NULL;
|
|
}
|
|
|
|
static gboolean
|
|
parse_adapter_format (WpSpaPod *format, gint *channels,
|
|
WpSpaPod **position)
|
|
{
|
|
g_autoptr (WpSpaPodParser) parser = NULL;
|
|
guint32 t = 0, s = 0, f = 0;
|
|
gint r = 0, c = 0;
|
|
g_autoptr (WpSpaPod) p = NULL;
|
|
|
|
g_return_val_if_fail (format, FALSE);
|
|
parser = wp_spa_pod_parser_new_object (format, NULL);
|
|
g_return_val_if_fail (parser, FALSE);
|
|
|
|
if (!wp_spa_pod_parser_get (parser, "mediaType", "I", &t, NULL) ||
|
|
!wp_spa_pod_parser_get (parser, "mediaSubtype", "I", &s, NULL) ||
|
|
!wp_spa_pod_parser_get (parser, "format", "I", &f, NULL) ||
|
|
!wp_spa_pod_parser_get (parser, "rate", "i", &r, NULL) ||
|
|
!wp_spa_pod_parser_get (parser, "channels", "i", &c, NULL))
|
|
return FALSE;
|
|
|
|
/* position is optional */
|
|
wp_spa_pod_parser_get (parser, "position", "P", &p, NULL);
|
|
|
|
if (channels)
|
|
*channels = c;
|
|
if (position)
|
|
*position = p ? wp_spa_pod_ref (p) : NULL;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static WpSpaPod *
|
|
build_adapter_format (WpSiAudioAdapter * self, WpSpaPod *format)
|
|
{
|
|
g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (self));
|
|
g_autoptr (WpSpaPodBuilder) b = NULL;
|
|
g_autoptr (WpProperties) props = NULL;
|
|
const gchar *rate_str = NULL;
|
|
gint channels = 2;
|
|
g_autoptr (WpSpaPod) position = NULL;
|
|
|
|
/* get the default clock rate */
|
|
g_return_val_if_fail (core, NULL);
|
|
props = wp_core_get_remote_properties (core);
|
|
g_return_val_if_fail (props, NULL);
|
|
rate_str = wp_properties_get (props, "default.clock.rate");
|
|
|
|
/* get channels and position */
|
|
if (format && !parse_adapter_format (format, &channels, &position))
|
|
return NULL;
|
|
if (!format) {
|
|
g_autoptr (WpSpaPodBuilder) b = wp_spa_pod_builder_new_array ();
|
|
wp_spa_pod_builder_add_id (b, SPA_AUDIO_CHANNEL_FL);
|
|
wp_spa_pod_builder_add_id (b, SPA_AUDIO_CHANNEL_FR);
|
|
position = wp_spa_pod_builder_end (b);
|
|
}
|
|
|
|
b = wp_spa_pod_builder_new_object ("Spa:Pod:Object:Param:Format", "Format");
|
|
wp_spa_pod_builder_add_property (b, "mediaType");
|
|
wp_spa_pod_builder_add_id (b, SPA_MEDIA_TYPE_audio);
|
|
wp_spa_pod_builder_add_property (b, "mediaSubtype");
|
|
wp_spa_pod_builder_add_id (b, SPA_MEDIA_SUBTYPE_raw);
|
|
wp_spa_pod_builder_add_property (b, "format");
|
|
wp_spa_pod_builder_add_id (b, SPA_AUDIO_FORMAT_F32P);
|
|
wp_spa_pod_builder_add_property (b, "rate");
|
|
wp_spa_pod_builder_add_int (b, rate_str ? atoi (rate_str) : 48000);
|
|
wp_spa_pod_builder_add_property (b, "channels");
|
|
wp_spa_pod_builder_add_int (b, channels);
|
|
if (position) {
|
|
wp_spa_pod_builder_add_property (b, "position");
|
|
wp_spa_pod_builder_add_pod (b, position);
|
|
}
|
|
return wp_spa_pod_builder_end (b);
|
|
}
|
|
|
|
static void
|
|
si_audio_adapter_set_ports_format (WpSiAdapter * item, WpSpaPod *f,
|
|
const gchar *mode, GAsyncReadyCallback callback, gpointer data)
|
|
{
|
|
WpSiAudioAdapter *self = WP_SI_AUDIO_ADAPTER (item);
|
|
g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (self));
|
|
g_autoptr (WpSpaPod) format = f;
|
|
g_autoptr (WpSpaPod) new_format = NULL;
|
|
guint32 active = 0;
|
|
|
|
g_return_if_fail (core);
|
|
|
|
/* cancel previous task if any */
|
|
if (self->format_task) {
|
|
g_autoptr (GTask) t = g_steal_pointer (&self->format_task);
|
|
g_task_return_new_error (t, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT,
|
|
"setting new format before previous done");
|
|
}
|
|
|
|
/* create the new task */
|
|
g_return_if_fail (!self->format_task);
|
|
self->format_task = g_task_new (self, NULL, callback, data);
|
|
|
|
/* build new format */
|
|
new_format = build_adapter_format (self, format);
|
|
if (!new_format) {
|
|
g_autoptr (GTask) t = g_steal_pointer (&self->format_task);
|
|
g_task_return_new_error (t, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT,
|
|
"failed to parse format");
|
|
return;
|
|
}
|
|
|
|
active = wp_object_get_active_features (WP_OBJECT (self->node));
|
|
if (G_UNLIKELY (!(active & WP_NODE_FEATURE_PORTS))) {
|
|
g_autoptr (GTask) t = g_steal_pointer (&self->format_task);
|
|
g_task_return_new_error (t, WP_DOMAIN_LIBRARY,
|
|
WP_LIBRARY_ERROR_OPERATION_FAILED,
|
|
"node feature ports is not enabled, aborting set format operation");
|
|
return;
|
|
}
|
|
|
|
/* set format and mode */
|
|
g_clear_pointer (&self->format, wp_spa_pod_unref);
|
|
self->format = g_steal_pointer (&new_format);
|
|
strncpy (self->mode, mode ? mode : "dsp", sizeof (self->mode) - 1);
|
|
|
|
/* configure DSP with chosen format */
|
|
wp_pipewire_object_set_param (WP_PIPEWIRE_OBJECT (self->node),
|
|
"PortConfig", 0, wp_spa_pod_new_object (
|
|
"Spa:Pod:Object:Param:PortConfig", "PortConfig",
|
|
"direction", "I", self->direction,
|
|
"mode", "K", self->mode,
|
|
"monitor", "b", self->monitor,
|
|
"control", "b", self->control_port,
|
|
"format", "P", self->format,
|
|
NULL));
|
|
|
|
/* the task finishes with new ports being emitted -> on_node_ports_changed */
|
|
}
|
|
|
|
static gboolean
|
|
si_audio_adapter_set_ports_format_finish (WpSiAdapter * item,
|
|
GAsyncResult * res, GError ** error)
|
|
{
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
}
|
|
|
|
static void
|
|
si_audio_adapter_adapter_init (WpSiAdapterInterface * iface)
|
|
{
|
|
iface->get_ports_format = si_audio_adapter_get_ports_format;
|
|
iface->set_ports_format = si_audio_adapter_set_ports_format;
|
|
iface->set_ports_format_finish = si_audio_adapter_set_ports_format_finish;
|
|
}
|
|
|
|
static GVariant *
|
|
si_audio_adapter_get_ports (WpSiLinkable * item, const gchar * context)
|
|
{
|
|
WpSiAudioAdapter *self = WP_SI_AUDIO_ADAPTER (item);
|
|
g_auto (GVariantBuilder) b = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_ARRAY);
|
|
g_autoptr (WpIterator) it = NULL;
|
|
g_auto (GValue) val = G_VALUE_INIT;
|
|
WpDirection direction = self->direction;
|
|
guint32 node_id;
|
|
|
|
/* context can only be NULL or "reverse" */
|
|
if (!g_strcmp0 (context, "reverse")) {
|
|
direction = (self->direction == WP_DIRECTION_INPUT) ?
|
|
WP_DIRECTION_OUTPUT : WP_DIRECTION_INPUT;
|
|
}
|
|
else if (context != NULL) {
|
|
/* on any other context, return an empty list of ports */
|
|
return g_variant_new_array (G_VARIANT_TYPE ("(uuu)"), NULL, 0);
|
|
}
|
|
|
|
g_variant_builder_init (&b, G_VARIANT_TYPE ("a(uuu)"));
|
|
node_id = wp_proxy_get_bound_id (WP_PROXY (self->node));
|
|
|
|
for (it = wp_node_new_ports_iterator (self->node);
|
|
wp_iterator_next (it, &val);
|
|
g_value_unset (&val))
|
|
{
|
|
WpPort *port = g_value_get_object (&val);
|
|
g_autoptr (WpProperties) props = NULL;
|
|
const gchar *channel;
|
|
guint32 port_id, channel_id = 0;
|
|
|
|
if (wp_port_get_direction (port) != direction)
|
|
continue;
|
|
|
|
port_id = wp_proxy_get_bound_id (WP_PROXY (port));
|
|
props = wp_pipewire_object_get_properties (WP_PIPEWIRE_OBJECT (port));
|
|
|
|
/* try to find the audio channel; if channel is NULL, this will silently
|
|
leave the channel_id to its default value, 0 */
|
|
channel = wp_properties_get (props, PW_KEY_AUDIO_CHANNEL);
|
|
if (channel) {
|
|
WpSpaIdValue idval = wp_spa_id_value_from_short_name (
|
|
"Spa:Enum:AudioChannel", channel);
|
|
if (idval)
|
|
channel_id = wp_spa_id_value_number (idval);
|
|
}
|
|
|
|
g_variant_builder_add (&b, "(uuu)", node_id, port_id, channel_id);
|
|
}
|
|
|
|
return g_variant_builder_end (&b);
|
|
}
|
|
|
|
static void
|
|
si_audio_adapter_linkable_init (WpSiLinkableInterface * iface)
|
|
{
|
|
iface->get_ports = si_audio_adapter_get_ports;
|
|
}
|
|
|
|
WP_PLUGIN_EXPORT gboolean
|
|
wireplumber__module_init (WpCore * core, GVariant * args, GError ** error)
|
|
{
|
|
wp_si_factory_register (core, wp_si_factory_new_simple (SI_FACTORY_NAME,
|
|
si_audio_adapter_get_type ()));
|
|
return TRUE;
|
|
}
|