
If one link fails, the activation transition will return, but then other links will continue to call the callback and try to access the now invalid activation transition. With this change, the callback is bound to the lifetime of the transition and will stop being called after the transition returns Fixes #76
680 lines
22 KiB
C
680 lines
22 KiB
C
/* WirePlumber
|
|
*
|
|
* Copyright © 2020 Collabora Ltd.
|
|
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
|
|
*
|
|
* SPDX-License-Identifier: MIT
|
|
*/
|
|
|
|
#include <wp/wp.h>
|
|
#include <pipewire/pipewire.h>
|
|
#include <spa/debug/types.h>
|
|
#include <spa/param/audio/type-info.h>
|
|
|
|
#define SI_FACTORY_NAME "si-standard-link"
|
|
|
|
struct _WpSiStandardLink
|
|
{
|
|
WpSessionItem parent;
|
|
|
|
/* configuration */
|
|
GWeakRef out_item;
|
|
GWeakRef in_item;
|
|
const gchar *out_item_port_context;
|
|
const gchar *in_item_port_context;
|
|
gboolean passive;
|
|
gboolean passthrough;
|
|
|
|
/* activate */
|
|
GPtrArray *node_links;
|
|
guint n_async_ops_wait;
|
|
};
|
|
|
|
static void si_standard_link_link_init (WpSiLinkInterface * iface);
|
|
|
|
G_DECLARE_FINAL_TYPE (WpSiStandardLink, si_standard_link, WP, SI_STANDARD_LINK,
|
|
WpSessionItem)
|
|
G_DEFINE_TYPE_WITH_CODE (WpSiStandardLink, si_standard_link,
|
|
WP_TYPE_SESSION_ITEM,
|
|
G_IMPLEMENT_INTERFACE (WP_TYPE_SI_LINK, si_standard_link_link_init))
|
|
|
|
static void
|
|
si_standard_link_init (WpSiStandardLink * self)
|
|
{
|
|
g_weak_ref_init (&self->out_item, NULL);
|
|
g_weak_ref_init (&self->in_item, NULL);
|
|
}
|
|
|
|
static void
|
|
si_standard_link_reset (WpSessionItem * item)
|
|
{
|
|
WpSiStandardLink *self = WP_SI_STANDARD_LINK (item);
|
|
|
|
/* deactivate first */
|
|
wp_object_deactivate (WP_OBJECT (self),
|
|
WP_SESSION_ITEM_FEATURE_ACTIVE | WP_SESSION_ITEM_FEATURE_EXPORTED);
|
|
|
|
/* reset */
|
|
g_weak_ref_set (&self->out_item, NULL);
|
|
g_weak_ref_set (&self->in_item, NULL);
|
|
self->out_item_port_context = NULL;
|
|
self->in_item_port_context = NULL;
|
|
self->passive = FALSE;
|
|
self->passthrough = FALSE;
|
|
|
|
WP_SESSION_ITEM_CLASS (si_standard_link_parent_class)->reset (item);
|
|
}
|
|
|
|
static WpSessionItem *
|
|
get_and_validate_item (WpProperties * props, const gchar *key)
|
|
{
|
|
WpSessionItem *res = NULL;
|
|
const gchar *str = NULL;
|
|
|
|
str = wp_properties_get (props, key);
|
|
if (!str || sscanf(str, "%p", &res) != 1 || !WP_IS_SI_LINKABLE (res) ||
|
|
!(wp_object_get_active_features (WP_OBJECT (res)) &
|
|
WP_SESSION_ITEM_FEATURE_ACTIVE))
|
|
return NULL;
|
|
|
|
return res;
|
|
}
|
|
|
|
static gboolean
|
|
si_standard_link_configure (WpSessionItem * item, WpProperties * p)
|
|
{
|
|
WpSiStandardLink *self = WP_SI_STANDARD_LINK (item);
|
|
g_autoptr (WpProperties) si_props = wp_properties_ensure_unique_owner (p);
|
|
WpSessionItem *out_item, *in_item;
|
|
const gchar *str;
|
|
|
|
/* reset previous config */
|
|
si_standard_link_reset (item);
|
|
|
|
out_item = get_and_validate_item (si_props, "out.item");
|
|
if (!out_item)
|
|
return FALSE;
|
|
wp_properties_setf (si_props, "out.item.id", "%u",
|
|
wp_session_item_get_id (out_item));
|
|
|
|
in_item = get_and_validate_item (si_props, "in.item");
|
|
if (!in_item)
|
|
return FALSE;
|
|
wp_properties_setf (si_props, "in.item.id", "%u",
|
|
wp_session_item_get_id (in_item));
|
|
|
|
self->out_item_port_context = wp_properties_get (si_props,
|
|
"out.item.port.context");
|
|
|
|
self->in_item_port_context = wp_properties_get (si_props,
|
|
"in.item.port.context");
|
|
|
|
str = wp_properties_get (si_props, "passive");
|
|
self->passive = str && pw_properties_parse_bool (str);
|
|
|
|
str = wp_properties_get (si_props, "passthrough");
|
|
self->passthrough = str && pw_properties_parse_bool (str);
|
|
|
|
g_weak_ref_set(&self->out_item, out_item);
|
|
g_weak_ref_set(&self->in_item, in_item);
|
|
|
|
wp_properties_set (si_props, "item.factory.name", SI_FACTORY_NAME);
|
|
wp_session_item_set_properties (WP_SESSION_ITEM (self),
|
|
g_steal_pointer (&si_props));
|
|
return TRUE;
|
|
}
|
|
|
|
static gpointer
|
|
si_standard_link_get_associated_proxy (WpSessionItem * item, GType proxy_type)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
si_standard_link_disable_active (WpSessionItem *si)
|
|
{
|
|
WpSiStandardLink *self = WP_SI_STANDARD_LINK (si);
|
|
g_autoptr (WpSessionItem) si_out = g_weak_ref_get (&self->out_item);
|
|
g_autoptr (WpSessionItem) si_in = g_weak_ref_get (&self->in_item);
|
|
WpSiAcquisition *out_acquisition, *in_acquisition;
|
|
|
|
if (si_out) {
|
|
out_acquisition = wp_si_linkable_get_acquisition (
|
|
WP_SI_LINKABLE (si_out));
|
|
if (out_acquisition)
|
|
wp_si_acquisition_release (out_acquisition, WP_SI_LINK (self),
|
|
WP_SI_LINKABLE (si_out));
|
|
}
|
|
if (si_in) {
|
|
in_acquisition = wp_si_linkable_get_acquisition (WP_SI_LINKABLE (si_in));
|
|
if (in_acquisition)
|
|
wp_si_acquisition_release (in_acquisition, WP_SI_LINK (self),
|
|
WP_SI_LINKABLE (si_in));
|
|
}
|
|
|
|
g_clear_pointer (&self->node_links, g_ptr_array_unref);
|
|
self->n_async_ops_wait = 0;
|
|
|
|
wp_object_update_features (WP_OBJECT (self), 0,
|
|
WP_SESSION_ITEM_FEATURE_ACTIVE);
|
|
}
|
|
|
|
static void
|
|
on_link_activated (WpObject * proxy, GAsyncResult * res,
|
|
WpTransition * transition)
|
|
{
|
|
WpSiStandardLink *self = wp_transition_get_source_object (transition);
|
|
g_autoptr (GError) error = NULL;
|
|
|
|
if (!wp_object_activate_finish (proxy, res, &error)) {
|
|
wp_transition_return_error (transition, g_steal_pointer (&error));
|
|
return;
|
|
}
|
|
|
|
self->n_async_ops_wait--;
|
|
if (self->n_async_ops_wait == 0)
|
|
wp_object_update_features (WP_OBJECT (self),
|
|
WP_SESSION_ITEM_FEATURE_ACTIVE, 0);
|
|
}
|
|
|
|
struct port
|
|
{
|
|
guint32 node_id;
|
|
guint32 port_id;
|
|
guint32 channel;
|
|
gboolean visited;
|
|
};
|
|
|
|
static inline bool
|
|
channel_is_aux(guint32 channel)
|
|
{
|
|
return channel >= SPA_AUDIO_CHANNEL_START_Aux &&
|
|
channel <= SPA_AUDIO_CHANNEL_LAST_Aux;
|
|
}
|
|
|
|
static inline int
|
|
score_ports(struct port *out, struct port *in)
|
|
{
|
|
int score = 0;
|
|
|
|
if (out->channel == in->channel)
|
|
score += 100;
|
|
else if ((out->channel == SPA_AUDIO_CHANNEL_SL && in->channel == SPA_AUDIO_CHANNEL_RL) ||
|
|
(out->channel == SPA_AUDIO_CHANNEL_RL && in->channel == SPA_AUDIO_CHANNEL_SL) ||
|
|
(out->channel == SPA_AUDIO_CHANNEL_SR && in->channel == SPA_AUDIO_CHANNEL_RR) ||
|
|
(out->channel == SPA_AUDIO_CHANNEL_RR && in->channel == SPA_AUDIO_CHANNEL_SR))
|
|
score += 60;
|
|
else if ((out->channel == SPA_AUDIO_CHANNEL_FC && in->channel == SPA_AUDIO_CHANNEL_MONO) ||
|
|
(out->channel == SPA_AUDIO_CHANNEL_MONO && in->channel == SPA_AUDIO_CHANNEL_FC))
|
|
score += 50;
|
|
else if (in->channel == SPA_AUDIO_CHANNEL_UNKNOWN ||
|
|
in->channel == SPA_AUDIO_CHANNEL_MONO ||
|
|
out->channel == SPA_AUDIO_CHANNEL_UNKNOWN ||
|
|
out->channel == SPA_AUDIO_CHANNEL_MONO)
|
|
score += 10;
|
|
else if (channel_is_aux(in->channel) != channel_is_aux(out->channel))
|
|
score += 7;
|
|
if (score > 0 && !in->visited)
|
|
score += 5;
|
|
if (score <= 10)
|
|
score = 0;
|
|
return score;
|
|
}
|
|
|
|
static gboolean
|
|
create_links (WpSiStandardLink * self, WpTransition * transition,
|
|
GVariant * out_ports, GVariant * in_ports)
|
|
{
|
|
g_autoptr (GArray) in_ports_arr = NULL;
|
|
g_autoptr (WpCore) core = NULL;
|
|
struct port out_port = {0};
|
|
struct port *in_port;
|
|
GVariantIter *iter = NULL;
|
|
guint i;
|
|
|
|
/* tuple format:
|
|
uint32 node_id;
|
|
uint32 port_id;
|
|
uint32 channel; // enum spa_audio_channel
|
|
*/
|
|
if (!out_ports || !g_variant_is_of_type (out_ports, G_VARIANT_TYPE("a(uuu)")))
|
|
return FALSE;
|
|
if (!in_ports || !g_variant_is_of_type (in_ports, G_VARIANT_TYPE("a(uuu)")))
|
|
return FALSE;
|
|
|
|
core = wp_object_get_core (WP_OBJECT (self));
|
|
g_return_val_if_fail (core, FALSE);
|
|
|
|
self->n_async_ops_wait = 0;
|
|
self->node_links = g_ptr_array_new_with_free_func (g_object_unref);
|
|
|
|
i = g_variant_n_children (in_ports);
|
|
if (i == 0)
|
|
return FALSE;
|
|
|
|
/* transfer the in ports to an array so that we can
|
|
mark them when they are linked */
|
|
in_ports_arr = g_array_sized_new (FALSE, TRUE, sizeof (struct port), i + 1);
|
|
g_array_set_size (in_ports_arr, i + 1);
|
|
g_variant_get (in_ports, "a(uuu)", &iter);
|
|
i = 0;
|
|
do {
|
|
in_port = &g_array_index (in_ports_arr, struct port, i++);
|
|
} while (g_variant_iter_loop (iter, "(uuu)", &in_port->node_id,
|
|
&in_port->port_id, &in_port->channel));
|
|
g_variant_iter_free (iter);
|
|
|
|
/* now loop over the out ports and figure out where they should be linked */
|
|
g_variant_get (out_ports, "a(uuu)", &iter);
|
|
while (g_variant_iter_loop (iter, "(uuu)", &out_port.node_id,
|
|
&out_port.port_id, &out_port.channel))
|
|
{
|
|
int best_score = 0;
|
|
struct port *best_port = NULL;
|
|
WpProperties *props = NULL;
|
|
WpLink *link;
|
|
|
|
for (i = 0; i < in_ports_arr->len - 1; i++) {
|
|
in_port = &g_array_index (in_ports_arr, struct port, i);
|
|
int score = score_ports (&out_port, in_port);
|
|
if (score > best_score) {
|
|
best_score = score;
|
|
best_port = in_port;
|
|
}
|
|
}
|
|
|
|
/* not all output ports have to be linked ... */
|
|
if (!best_port || best_port->visited)
|
|
continue;
|
|
|
|
best_port->visited = TRUE;
|
|
|
|
/* Create the properties */
|
|
props = wp_properties_new_empty ();
|
|
wp_properties_setf (props, PW_KEY_LINK_OUTPUT_NODE, "%u", out_port.node_id);
|
|
wp_properties_setf (props, PW_KEY_LINK_OUTPUT_PORT, "%u", out_port.port_id);
|
|
wp_properties_setf (props, PW_KEY_LINK_INPUT_NODE, "%u", best_port->node_id);
|
|
wp_properties_setf (props, PW_KEY_LINK_INPUT_PORT, "%u", best_port->port_id);
|
|
if (self->passive)
|
|
wp_properties_set (props, PW_KEY_LINK_PASSIVE, "true");
|
|
|
|
wp_debug_object (self, "create pw link: %u:%u (%s) -> %u:%u (%s)",
|
|
out_port.node_id, out_port.port_id,
|
|
spa_debug_type_find_name (spa_type_audio_channel, out_port.channel),
|
|
best_port->node_id, best_port->port_id,
|
|
spa_debug_type_find_name (spa_type_audio_channel, best_port->channel));
|
|
|
|
/* create the link */
|
|
link = wp_link_new_from_factory (core, "link-factory", props);
|
|
g_ptr_array_add (self->node_links, link);
|
|
|
|
/* activate to ensure it is created without errors */
|
|
self->n_async_ops_wait++;
|
|
wp_object_activate_closure (WP_OBJECT (link),
|
|
WP_PIPEWIRE_OBJECT_FEATURES_MINIMAL, NULL,
|
|
g_cclosure_new_object (
|
|
(GCallback) on_link_activated, G_OBJECT (transition)));
|
|
}
|
|
g_variant_iter_free (iter);
|
|
return self->node_links->len > 0;
|
|
}
|
|
|
|
static void
|
|
get_ports_and_create_links (WpSiStandardLink *self, WpTransition *transition)
|
|
{
|
|
g_autoptr (WpSiLinkable) si_out = NULL;
|
|
g_autoptr (WpSiLinkable) si_in = NULL;
|
|
g_autoptr (GVariant) out_ports = NULL;
|
|
g_autoptr (GVariant) in_ports = NULL;
|
|
|
|
si_out = WP_SI_LINKABLE (g_weak_ref_get (&self->out_item));
|
|
si_in = WP_SI_LINKABLE (g_weak_ref_get (&self->in_item));
|
|
|
|
g_return_if_fail (si_out);
|
|
g_return_if_fail (si_in);
|
|
|
|
out_ports = wp_si_linkable_get_ports (si_out, self->out_item_port_context);
|
|
in_ports = wp_si_linkable_get_ports (si_in, self->in_item_port_context);
|
|
|
|
if (!create_links (self, transition, out_ports, in_ports))
|
|
wp_transition_return_error (transition, g_error_new (WP_DOMAIN_LIBRARY,
|
|
WP_LIBRARY_ERROR_INVARIANT,
|
|
"Failed to create links because of wrong ports"));
|
|
}
|
|
|
|
static void
|
|
on_adapters_ready (GObject *obj, GAsyncResult * res, gpointer p)
|
|
{
|
|
WpTransition *transition = p;
|
|
WpSiStandardLink *self = wp_transition_get_source_object (transition);
|
|
g_autoptr (GError) error = NULL;
|
|
|
|
wp_si_adapter_set_ports_format_finish (WP_SI_ADAPTER (obj), res, &error);
|
|
if (error) {
|
|
wp_transition_return_error (transition, g_steal_pointer (&error));
|
|
return;
|
|
}
|
|
|
|
/* create links */
|
|
get_ports_and_create_links (self, transition);
|
|
}
|
|
|
|
struct adapter
|
|
{
|
|
WpSiAdapter *si;
|
|
gboolean is_device;
|
|
gboolean dont_remix;
|
|
gboolean unpositioned;
|
|
gboolean no_dsp;
|
|
WpSpaPod *fmt;
|
|
const gchar *mode;
|
|
};
|
|
|
|
static void
|
|
adapter_free (struct adapter *a)
|
|
{
|
|
g_clear_object (&a->si);
|
|
g_clear_pointer (&a->fmt, wp_spa_pod_unref);
|
|
g_slice_free (struct adapter, a);
|
|
}
|
|
|
|
static void
|
|
configure_adapter (WpSiStandardLink *self, WpTransition *transition,
|
|
struct adapter *main, struct adapter *other)
|
|
{
|
|
/* configure other to have the same format with main, if necessary */
|
|
if (!main->no_dsp && !other->dont_remix && !other->unpositioned && !main->unpositioned) {
|
|
/* if formats are the same, no need to reconfigure */
|
|
if (other->fmt && !g_strcmp0 (main->mode, other->mode)
|
|
&& wp_spa_pod_equal (main->fmt, other->fmt))
|
|
get_ports_and_create_links (self, transition);
|
|
else
|
|
wp_si_adapter_set_ports_format (other->si, wp_spa_pod_ref (main->fmt),
|
|
"dsp", on_adapters_ready, transition);
|
|
} else if (main->no_dsp) {
|
|
/* if formats are the same, no need to reconfigure */
|
|
if (other->fmt && !g_strcmp0 (other->mode, "convert")
|
|
&& wp_spa_pod_equal (main->fmt, other->fmt))
|
|
get_ports_and_create_links (self, transition);
|
|
else
|
|
wp_si_adapter_set_ports_format (other->si, wp_spa_pod_ref (main->fmt),
|
|
"convert", on_adapters_ready, transition);
|
|
} else {
|
|
/* dont_remix or unpositioned case */
|
|
if (other->fmt)
|
|
get_ports_and_create_links (self, transition);
|
|
else
|
|
wp_si_adapter_set_ports_format (other->si, NULL,
|
|
"dsp", on_adapters_ready, transition);
|
|
}
|
|
}
|
|
|
|
static void
|
|
on_main_adapter_ready (GObject *obj, GAsyncResult * res, gpointer p)
|
|
{
|
|
WpTransition *transition = p;
|
|
WpSiStandardLink *self = wp_transition_get_source_object (transition);
|
|
g_autoptr (GError) error = NULL;
|
|
struct adapter *main, *other;
|
|
|
|
wp_si_adapter_set_ports_format_finish (WP_SI_ADAPTER (obj), res, &error);
|
|
if (error) {
|
|
wp_transition_return_error (transition, g_steal_pointer (&error));
|
|
return;
|
|
}
|
|
|
|
main = g_object_get_data (G_OBJECT (transition), "adapter_main");
|
|
other = g_object_get_data (G_OBJECT (transition), "adapter_other");
|
|
|
|
if (self->passthrough) {
|
|
wp_si_adapter_set_ports_format (other->si, NULL, "passthrough",
|
|
on_adapters_ready, transition);
|
|
} else {
|
|
/* get the up-to-date formats */
|
|
g_clear_pointer (&main->fmt, wp_spa_pod_unref);
|
|
g_clear_pointer (&other->fmt, wp_spa_pod_unref);
|
|
main->fmt = wp_si_adapter_get_ports_format (main->si, &main->mode);
|
|
other->fmt = wp_si_adapter_get_ports_format (other->si, &other->mode);
|
|
|
|
/* now configure other based on main */
|
|
configure_adapter (self, transition, main, other);
|
|
}
|
|
}
|
|
|
|
static void
|
|
configure_and_link_adapters (WpSiStandardLink *self, WpTransition *transition)
|
|
{
|
|
struct adapter *out, *in, *main, *other;
|
|
const gchar *str = NULL;
|
|
|
|
out = g_slice_new0 (struct adapter);
|
|
in = g_slice_new0 (struct adapter);
|
|
out->si = WP_SI_ADAPTER (g_weak_ref_get (&self->out_item));
|
|
in->si = WP_SI_ADAPTER (g_weak_ref_get (&self->in_item));
|
|
g_return_if_fail (out->si);
|
|
g_return_if_fail (in->si);
|
|
|
|
str = wp_session_item_get_property (WP_SESSION_ITEM (out->si), "item.node.type");
|
|
out->is_device = !g_strcmp0 (str, "device");
|
|
str = wp_session_item_get_property (WP_SESSION_ITEM (in->si), "item.node.type");
|
|
in->is_device = !g_strcmp0 (str, "device");
|
|
|
|
str = wp_session_item_get_property (WP_SESSION_ITEM (out->si), "item.factory.name");
|
|
out->is_device = (str && !g_strcmp0 (str, "si-audio-endpoint") && !in->is_device)
|
|
|| out->is_device;
|
|
str = wp_session_item_get_property (WP_SESSION_ITEM (in->si), "item.factory.name");
|
|
in->is_device = (str && !g_strcmp0 (str, "si-audio-endpoint") && !out->is_device)
|
|
|| in->is_device;
|
|
|
|
str = wp_session_item_get_property (WP_SESSION_ITEM (out->si), "stream.dont-remix");
|
|
out->dont_remix = str && pw_properties_parse_bool (str);
|
|
str = wp_session_item_get_property (WP_SESSION_ITEM (in->si), "stream.dont-remix");
|
|
in->dont_remix = str && pw_properties_parse_bool (str);
|
|
|
|
str = wp_session_item_get_property (WP_SESSION_ITEM (out->si), "item.node.unpositioned");
|
|
out->unpositioned = str && pw_properties_parse_bool (str);
|
|
str = wp_session_item_get_property (WP_SESSION_ITEM (in->si), "item.node.unpositioned");
|
|
in->unpositioned = str && pw_properties_parse_bool (str);
|
|
|
|
str = wp_session_item_get_property (WP_SESSION_ITEM (out->si), "item.features.no-dsp");
|
|
out->no_dsp = str && pw_properties_parse_bool (str);
|
|
str = wp_session_item_get_property (WP_SESSION_ITEM (in->si), "item.features.no-dsp");
|
|
in->no_dsp = str && pw_properties_parse_bool (str);
|
|
|
|
wp_debug_object (self, "out [device:%d, dont_remix %d, unpos %d], "
|
|
"in: [device %d, dont_remix %d, unpos %d]",
|
|
out->is_device, out->dont_remix, out->unpositioned,
|
|
in->is_device, in->dont_remix, in->unpositioned);
|
|
|
|
/* we always use out->si format, unless in->si is device */
|
|
if (!out->is_device && in->is_device) {
|
|
main = in;
|
|
other = out;
|
|
} else {
|
|
main = out;
|
|
other = in;
|
|
}
|
|
|
|
/* always configure both adapters in passthrough mode
|
|
if this is a passthrough link */
|
|
if (self->passthrough) {
|
|
g_object_set_data_full (G_OBJECT (transition), "adapter_main", main,
|
|
(GDestroyNotify) adapter_free);
|
|
g_object_set_data_full (G_OBJECT (transition), "adapter_other", other,
|
|
(GDestroyNotify) adapter_free);
|
|
wp_si_adapter_set_ports_format (main->si, NULL, "passthrough",
|
|
on_main_adapter_ready, transition);
|
|
return;
|
|
}
|
|
|
|
main->fmt = wp_si_adapter_get_ports_format (main->si, &main->mode);
|
|
other->fmt = wp_si_adapter_get_ports_format (other->si, &other->mode);
|
|
|
|
if (main->fmt)
|
|
/* ideally, configure other based on main */
|
|
configure_adapter (self, transition, main, other);
|
|
else if (other->fmt)
|
|
/* if main is not configured but other is, do it the other way around */
|
|
configure_adapter (self, transition, other, main);
|
|
else {
|
|
/* no adapter configured, let's configure main first */
|
|
g_object_set_data_full (G_OBJECT (transition), "adapter_main", main,
|
|
(GDestroyNotify) adapter_free);
|
|
g_object_set_data_full (G_OBJECT (transition), "adapter_other", other,
|
|
(GDestroyNotify) adapter_free);
|
|
wp_si_adapter_set_ports_format (main->si, NULL,
|
|
main->no_dsp ? "passthrough" : "dsp", on_main_adapter_ready, transition);
|
|
return;
|
|
}
|
|
|
|
adapter_free (main);
|
|
adapter_free (other);
|
|
}
|
|
|
|
static void
|
|
si_standard_link_do_link (WpSiStandardLink *self, WpTransition *transition)
|
|
{
|
|
g_autoptr (WpSessionItem) si_out = g_weak_ref_get (&self->out_item);
|
|
g_autoptr (WpSessionItem) si_in = g_weak_ref_get (&self->in_item);
|
|
|
|
if (WP_IS_SI_ADAPTER (si_out) && WP_IS_SI_ADAPTER (si_in))
|
|
configure_and_link_adapters (self, transition);
|
|
else if (!WP_IS_SI_ADAPTER (si_out) && !WP_IS_SI_ADAPTER (si_in))
|
|
get_ports_and_create_links (self, transition);
|
|
else
|
|
wp_transition_return_error (transition, g_error_new (WP_DOMAIN_LIBRARY,
|
|
WP_LIBRARY_ERROR_INVARIANT,
|
|
"Adapters cannot be linked with non-adapters"));
|
|
}
|
|
|
|
static void
|
|
on_item_acquired (WpSiAcquisition * acq, GAsyncResult * res,
|
|
WpTransition * transition)
|
|
{
|
|
WpSiStandardLink *self = wp_transition_get_source_object (transition);
|
|
g_autoptr (GError) error = NULL;
|
|
|
|
if (!wp_si_acquisition_acquire_finish (acq, res, &error)) {
|
|
wp_transition_return_error (transition, g_steal_pointer (&error));
|
|
return;
|
|
}
|
|
|
|
self->n_async_ops_wait--;
|
|
if (self->n_async_ops_wait == 0)
|
|
si_standard_link_do_link (self, transition);
|
|
}
|
|
|
|
static void
|
|
si_standard_link_enable_active (WpSessionItem *si, WpTransition *transition)
|
|
{
|
|
WpSiStandardLink *self = WP_SI_STANDARD_LINK (si);
|
|
g_autoptr (WpSessionItem) si_out = NULL;
|
|
g_autoptr (WpSessionItem) si_in = NULL;
|
|
WpSiAcquisition *out_acquisition = NULL, *in_acquisition = NULL;
|
|
|
|
if (!wp_session_item_is_configured (si)) {
|
|
wp_transition_return_error (transition,
|
|
g_error_new (WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT,
|
|
"si-standard-link: item is not configured"));
|
|
return;
|
|
}
|
|
|
|
/* make sure in/out items are valid */
|
|
si_out = g_weak_ref_get (&self->out_item);
|
|
si_in = g_weak_ref_get (&self->in_item);
|
|
if (!si_out || !si_in) {
|
|
wp_transition_return_error (transition,
|
|
g_error_new (WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT,
|
|
"si-standard-link: in/out items are not valid anymore"));
|
|
return;
|
|
}
|
|
|
|
/* acquire */
|
|
out_acquisition = wp_si_linkable_get_acquisition (WP_SI_LINKABLE (si_out));
|
|
in_acquisition = wp_si_linkable_get_acquisition (WP_SI_LINKABLE (si_in));
|
|
if (out_acquisition && in_acquisition)
|
|
self->n_async_ops_wait = 2;
|
|
else if (out_acquisition || in_acquisition)
|
|
self->n_async_ops_wait = 1;
|
|
else {
|
|
self->n_async_ops_wait = 0;
|
|
si_standard_link_do_link (self, transition);
|
|
return;
|
|
}
|
|
|
|
if (out_acquisition) {
|
|
wp_si_acquisition_acquire (out_acquisition, WP_SI_LINK (self),
|
|
WP_SI_LINKABLE (si_out), (GAsyncReadyCallback) on_item_acquired,
|
|
transition);
|
|
}
|
|
if (in_acquisition) {
|
|
wp_si_acquisition_acquire (in_acquisition, WP_SI_LINK (self),
|
|
WP_SI_LINKABLE (si_in), (GAsyncReadyCallback) on_item_acquired,
|
|
transition);
|
|
}
|
|
}
|
|
|
|
static void
|
|
si_standard_link_finalize (GObject * object)
|
|
{
|
|
WpSiStandardLink *self = WP_SI_STANDARD_LINK (object);
|
|
|
|
g_weak_ref_clear (&self->out_item);
|
|
g_weak_ref_clear (&self->in_item);
|
|
|
|
G_OBJECT_CLASS (si_standard_link_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
si_standard_link_class_init (WpSiStandardLinkClass * klass)
|
|
{
|
|
GObjectClass *object_class = (GObjectClass *) klass;
|
|
WpSessionItemClass *si_class = (WpSessionItemClass *) klass;
|
|
|
|
object_class->finalize = si_standard_link_finalize;
|
|
|
|
si_class->reset = si_standard_link_reset;
|
|
si_class->configure = si_standard_link_configure;
|
|
si_class->get_associated_proxy = si_standard_link_get_associated_proxy;
|
|
si_class->disable_active = si_standard_link_disable_active;
|
|
si_class->enable_active = si_standard_link_enable_active;
|
|
}
|
|
|
|
static GVariant *
|
|
si_standard_link_get_registration_info (WpSiLink * item)
|
|
{
|
|
GVariantBuilder b;
|
|
g_variant_builder_init (&b, G_VARIANT_TYPE ("a{ss}"));
|
|
return g_variant_builder_end (&b);
|
|
}
|
|
|
|
static WpSiLinkable *
|
|
si_standard_link_get_out_item (WpSiLink * item)
|
|
{
|
|
WpSiStandardLink *self = WP_SI_STANDARD_LINK (item);
|
|
return WP_SI_LINKABLE (g_weak_ref_get (&self->out_item));
|
|
}
|
|
|
|
static WpSiLinkable *
|
|
si_standard_link_get_in_item (WpSiLink * item)
|
|
{
|
|
WpSiStandardLink *self = WP_SI_STANDARD_LINK (item);
|
|
return WP_SI_LINKABLE (g_weak_ref_get (&self->in_item));
|
|
}
|
|
|
|
static void
|
|
si_standard_link_link_init (WpSiLinkInterface * iface)
|
|
{
|
|
iface->get_registration_info = si_standard_link_get_registration_info;
|
|
iface->get_out_item = si_standard_link_get_out_item;
|
|
iface->get_in_item = si_standard_link_get_in_item;
|
|
}
|
|
|
|
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_standard_link_get_type ()));
|
|
return TRUE;
|
|
}
|