2020-04-10 16:17:25 +03:00
|
|
|
/* 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>
|
|
|
|
|
|
log: implement a log topics system, like pipewire
The intention is to make checks for enabled log topics faster.
Every topic has its own structure that is statically defined in the file
where the logs are printed from. The structure is initialized transparently
when it is first used and it contains all the log level flags for the levels
that this topic should print messages. It is then checked on the wp_log()
macro before printing the message.
Topics from SPA/PipeWire are also handled natively, so messages are printed
directly without checking if the topic is enabled, since the PipeWire and SPA
macros do the checking themselves.
Messages coming from GLib are checked inside the handler.
An internal WpLogFields object is used to manage the state of each log
message, populating all the fields appropriately from the place they
are coming from (wp_log, spa_log, glib log), formatting the message and
then printing it. For printing to the journald, we still use the glib
message handler, converting all the needed fields to GLogField on demand.
That message handler does not do any checks for the topic or the level, so
we can just call it to send the message.
2023-05-16 11:51:29 +03:00
|
|
|
WP_DEFINE_LOCAL_LOG_TOPIC ("m-si-standard-link")
|
|
|
|
|
|
2021-03-17 14:52:41 -04:00
|
|
|
#define SI_FACTORY_NAME "si-standard-link"
|
2020-04-10 16:17:25 +03:00
|
|
|
|
|
|
|
|
struct _WpSiStandardLink
|
|
|
|
|
{
|
|
|
|
|
WpSessionItem parent;
|
|
|
|
|
|
2021-03-17 14:52:41 -04:00
|
|
|
/* configuration */
|
2021-03-23 13:35:52 -04:00
|
|
|
GWeakRef out_item;
|
|
|
|
|
GWeakRef in_item;
|
|
|
|
|
const gchar *out_item_port_context;
|
|
|
|
|
const gchar *in_item_port_context;
|
2021-10-13 12:48:03 +03:00
|
|
|
gboolean passthrough;
|
2020-04-10 16:17:25 +03:00
|
|
|
|
2021-03-17 14:52:41 -04:00
|
|
|
/* activate */
|
2020-04-10 16:17:25 +03:00
|
|
|
GPtrArray *node_links;
|
2021-11-20 13:11:37 -05:00
|
|
|
guint n_active_links;
|
|
|
|
|
guint n_failed_links;
|
2020-04-10 16:17:25 +03:00
|
|
|
guint n_async_ops_wait;
|
|
|
|
|
};
|
|
|
|
|
|
2023-02-11 14:37:02 +01:00
|
|
|
enum {
|
|
|
|
|
SIGNAL_LINK_ERROR,
|
|
|
|
|
LAST_SIGNAL,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static guint signals[LAST_SIGNAL] = { 0 };
|
|
|
|
|
|
2020-04-10 16:17:25 +03:00
|
|
|
static void si_standard_link_link_init (WpSiLinkInterface * iface);
|
|
|
|
|
|
2021-03-17 14:52:41 -04:00
|
|
|
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,
|
2020-04-10 16:17:25 +03:00
|
|
|
G_IMPLEMENT_INTERFACE (WP_TYPE_SI_LINK, si_standard_link_link_init))
|
|
|
|
|
|
2020-05-07 16:38:14 +03:00
|
|
|
static void
|
2021-03-17 14:52:41 -04:00
|
|
|
si_standard_link_init (WpSiStandardLink * self)
|
|
|
|
|
{
|
2021-03-23 13:35:52 -04:00
|
|
|
g_weak_ref_init (&self->out_item, NULL);
|
|
|
|
|
g_weak_ref_init (&self->in_item, NULL);
|
2021-03-17 14:52:41 -04:00
|
|
|
}
|
|
|
|
|
|
2020-04-10 16:17:25 +03:00
|
|
|
static void
|
|
|
|
|
si_standard_link_reset (WpSessionItem * item)
|
|
|
|
|
{
|
|
|
|
|
WpSiStandardLink *self = WP_SI_STANDARD_LINK (item);
|
|
|
|
|
|
2021-03-17 14:52:41 -04:00
|
|
|
/* deactivate first */
|
|
|
|
|
wp_object_deactivate (WP_OBJECT (self),
|
|
|
|
|
WP_SESSION_ITEM_FEATURE_ACTIVE | WP_SESSION_ITEM_FEATURE_EXPORTED);
|
|
|
|
|
|
|
|
|
|
/* reset */
|
2021-03-23 13:35:52 -04:00
|
|
|
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;
|
2021-10-13 12:48:03 +03:00
|
|
|
self->passthrough = FALSE;
|
2020-04-10 16:17:25 +03:00
|
|
|
|
2021-03-17 14:52:41 -04:00
|
|
|
WP_SESSION_ITEM_CLASS (si_standard_link_parent_class)->reset (item);
|
2020-04-10 16:17:25 +03:00
|
|
|
}
|
|
|
|
|
|
2021-03-17 14:52:41 -04:00
|
|
|
static WpSessionItem *
|
2021-03-23 13:35:52 -04:00
|
|
|
get_and_validate_item (WpProperties * props, const gchar *key)
|
2020-04-10 16:17:25 +03:00
|
|
|
{
|
2021-03-17 14:52:41 -04:00
|
|
|
WpSessionItem *res = NULL;
|
|
|
|
|
const gchar *str = NULL;
|
2020-04-10 16:17:25 +03:00
|
|
|
|
2021-03-17 14:52:41 -04:00
|
|
|
str = wp_properties_get (props, key);
|
2021-04-29 18:56:49 -04:00
|
|
|
if (!str || sscanf(str, "%p", &res) != 1 || !WP_IS_SI_LINKABLE (res) ||
|
2023-11-08 12:23:00 +02:00
|
|
|
!(wp_object_test_active_features (WP_OBJECT (res),
|
|
|
|
|
WP_SESSION_ITEM_FEATURE_ACTIVE)))
|
2021-03-17 14:52:41 -04:00
|
|
|
return NULL;
|
|
|
|
|
|
|
|
|
|
return res;
|
2020-04-10 16:17:25 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static gboolean
|
2021-03-17 14:52:41 -04:00
|
|
|
si_standard_link_configure (WpSessionItem * item, WpProperties * p)
|
2020-04-10 16:17:25 +03:00
|
|
|
{
|
|
|
|
|
WpSiStandardLink *self = WP_SI_STANDARD_LINK (item);
|
2021-03-17 14:52:41 -04:00
|
|
|
g_autoptr (WpProperties) si_props = wp_properties_ensure_unique_owner (p);
|
2021-03-23 13:35:52 -04:00
|
|
|
WpSessionItem *out_item, *in_item;
|
2021-03-17 14:52:41 -04:00
|
|
|
const gchar *str;
|
2020-04-10 16:17:25 +03:00
|
|
|
|
2021-03-17 14:52:41 -04:00
|
|
|
/* reset previous config */
|
|
|
|
|
si_standard_link_reset (item);
|
|
|
|
|
|
2021-04-02 11:22:22 -04:00
|
|
|
out_item = get_and_validate_item (si_props, "out.item");
|
2021-03-23 13:35:52 -04:00
|
|
|
if (!out_item)
|
2020-04-10 16:17:25 +03:00
|
|
|
return FALSE;
|
2021-04-02 11:22:22 -04:00
|
|
|
wp_properties_setf (si_props, "out.item.id", "%u",
|
2022-08-23 08:09:36 -04:00
|
|
|
wp_object_get_id (WP_OBJECT (out_item)));
|
2020-04-10 16:17:25 +03:00
|
|
|
|
2021-04-02 11:22:22 -04:00
|
|
|
in_item = get_and_validate_item (si_props, "in.item");
|
2021-03-23 13:35:52 -04:00
|
|
|
if (!in_item)
|
2020-04-10 16:17:25 +03:00
|
|
|
return FALSE;
|
2021-04-02 11:22:22 -04:00
|
|
|
wp_properties_setf (si_props, "in.item.id", "%u",
|
2022-08-23 08:09:36 -04:00
|
|
|
wp_object_get_id (WP_OBJECT (in_item)));
|
2020-04-10 16:17:25 +03:00
|
|
|
|
2021-03-23 13:35:52 -04:00
|
|
|
self->out_item_port_context = wp_properties_get (si_props,
|
2021-04-02 11:22:22 -04:00
|
|
|
"out.item.port.context");
|
2021-03-17 14:52:41 -04:00
|
|
|
|
2021-04-05 14:30:55 -04:00
|
|
|
self->in_item_port_context = wp_properties_get (si_props,
|
2021-04-02 11:22:22 -04:00
|
|
|
"in.item.port.context");
|
2020-04-10 16:17:25 +03:00
|
|
|
|
2021-10-13 12:48:03 +03:00
|
|
|
str = wp_properties_get (si_props, "passthrough");
|
|
|
|
|
self->passthrough = str && pw_properties_parse_bool (str);
|
2020-05-07 16:38:14 +03:00
|
|
|
|
2021-03-23 13:35:52 -04:00
|
|
|
g_weak_ref_set(&self->out_item, out_item);
|
|
|
|
|
g_weak_ref_set(&self->in_item, in_item);
|
2021-03-25 15:34:24 -04:00
|
|
|
|
2021-10-08 00:09:42 +03:00
|
|
|
wp_properties_set (si_props, "item.factory.name", SI_FACTORY_NAME);
|
2021-03-17 14:52:41 -04:00
|
|
|
wp_session_item_set_properties (WP_SESSION_ITEM (self),
|
|
|
|
|
g_steal_pointer (&si_props));
|
2020-04-10 16:17:25 +03:00
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-17 14:52:41 -04:00
|
|
|
static gpointer
|
|
|
|
|
si_standard_link_get_associated_proxy (WpSessionItem * item, GType proxy_type)
|
2020-04-10 16:17:25 +03:00
|
|
|
{
|
2021-03-17 14:52:41 -04:00
|
|
|
return NULL;
|
|
|
|
|
}
|
2020-04-10 16:17:25 +03:00
|
|
|
|
2022-07-19 20:01:10 +03:00
|
|
|
static void
|
|
|
|
|
request_destroy_link (gpointer data, gpointer user_data)
|
|
|
|
|
{
|
|
|
|
|
WpLink *link = WP_LINK (data);
|
|
|
|
|
|
|
|
|
|
wp_global_proxy_request_destroy (WP_GLOBAL_PROXY (link));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
clear_node_links (GPtrArray **node_links_p)
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* Something else (eg. object managers) may be keeping the WpLink
|
|
|
|
|
* objects alive. Deactive the links now, to destroy the PW objects.
|
|
|
|
|
*/
|
|
|
|
|
if (*node_links_p)
|
|
|
|
|
g_ptr_array_foreach (*node_links_p, request_destroy_link, NULL);
|
|
|
|
|
|
|
|
|
|
g_clear_pointer (node_links_p, g_ptr_array_unref);
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-17 14:52:41 -04:00
|
|
|
static void
|
|
|
|
|
si_standard_link_disable_active (WpSessionItem *si)
|
|
|
|
|
{
|
|
|
|
|
WpSiStandardLink *self = WP_SI_STANDARD_LINK (si);
|
2021-03-23 13:35:52 -04:00
|
|
|
g_autoptr (WpSessionItem) si_out = g_weak_ref_get (&self->out_item);
|
|
|
|
|
g_autoptr (WpSessionItem) si_in = g_weak_ref_get (&self->in_item);
|
2021-03-23 12:27:22 -04:00
|
|
|
WpSiAcquisition *out_acquisition, *in_acquisition;
|
2020-04-10 16:17:25 +03:00
|
|
|
|
2021-03-25 15:34:24 -04:00
|
|
|
if (si_out) {
|
2021-04-29 18:56:49 -04:00
|
|
|
out_acquisition = wp_si_linkable_get_acquisition (
|
|
|
|
|
WP_SI_LINKABLE (si_out));
|
2021-03-17 14:52:41 -04:00
|
|
|
if (out_acquisition)
|
2021-03-23 12:27:22 -04:00
|
|
|
wp_si_acquisition_release (out_acquisition, WP_SI_LINK (self),
|
2021-04-29 18:56:49 -04:00
|
|
|
WP_SI_LINKABLE (si_out));
|
2021-03-17 14:52:41 -04:00
|
|
|
}
|
2021-03-25 15:34:24 -04:00
|
|
|
if (si_in) {
|
2021-04-29 18:56:49 -04:00
|
|
|
in_acquisition = wp_si_linkable_get_acquisition (WP_SI_LINKABLE (si_in));
|
2021-03-17 14:52:41 -04:00
|
|
|
if (in_acquisition)
|
2021-03-23 12:27:22 -04:00
|
|
|
wp_si_acquisition_release (in_acquisition, WP_SI_LINK (self),
|
2021-04-29 18:56:49 -04:00
|
|
|
WP_SI_LINKABLE (si_in));
|
2020-04-10 16:17:25 +03:00
|
|
|
}
|
2021-03-17 14:52:41 -04:00
|
|
|
|
2022-07-19 20:01:10 +03:00
|
|
|
clear_node_links (&self->node_links);
|
|
|
|
|
|
2021-11-20 13:11:37 -05:00
|
|
|
self->n_active_links = 0;
|
|
|
|
|
self->n_failed_links = 0;
|
2021-03-17 14:52:41 -04:00
|
|
|
self->n_async_ops_wait = 0;
|
|
|
|
|
|
|
|
|
|
wp_object_update_features (WP_OBJECT (self), 0,
|
|
|
|
|
WP_SESSION_ITEM_FEATURE_ACTIVE);
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-10 16:17:25 +03:00
|
|
|
static void
|
2020-11-13 19:54:00 +02:00
|
|
|
on_link_activated (WpObject * proxy, GAsyncResult * res,
|
2020-04-10 16:17:25 +03:00
|
|
|
WpTransition * transition)
|
|
|
|
|
{
|
|
|
|
|
WpSiStandardLink *self = wp_transition_get_source_object (transition);
|
2022-07-19 20:01:10 +03:00
|
|
|
guint len = self->node_links ? self->node_links->len : 0;
|
2020-04-10 16:17:25 +03:00
|
|
|
|
2021-11-20 13:11:37 -05:00
|
|
|
/* Count the number of failed and active links */
|
|
|
|
|
if (wp_object_activate_finish (proxy, res, NULL))
|
|
|
|
|
self->n_active_links++;
|
|
|
|
|
else
|
|
|
|
|
self->n_failed_links++;
|
|
|
|
|
|
|
|
|
|
/* Wait for all links to finish activation */
|
|
|
|
|
if (self->n_failed_links + self->n_active_links != len)
|
2020-04-10 16:17:25 +03:00
|
|
|
return;
|
|
|
|
|
|
2021-11-20 13:11:37 -05:00
|
|
|
/* We only active feature if all links activated successfully */
|
|
|
|
|
if (self->n_failed_links > 0) {
|
2022-07-19 20:01:10 +03:00
|
|
|
clear_node_links (&self->node_links);
|
2021-11-20 13:11:37 -05:00
|
|
|
wp_transition_return_error (transition, g_error_new (
|
|
|
|
|
WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
|
|
|
|
|
"%d of %d PipeWire links failed to activate",
|
|
|
|
|
self->n_failed_links, len));
|
|
|
|
|
} else {
|
2021-03-17 14:52:41 -04:00
|
|
|
wp_object_update_features (WP_OBJECT (self),
|
|
|
|
|
WP_SESSION_ITEM_FEATURE_ACTIVE, 0);
|
2021-11-20 13:11:37 -05:00
|
|
|
}
|
2020-04-10 16:17:25 +03:00
|
|
|
}
|
|
|
|
|
|
2023-02-11 14:37:02 +01:00
|
|
|
static void
|
|
|
|
|
on_link_state_changed (WpLink *link, WpLinkState old_state,
|
|
|
|
|
WpLinkState new_state, WpSiStandardLink * self)
|
|
|
|
|
{
|
|
|
|
|
if (new_state == WP_LINK_STATE_ERROR) {
|
|
|
|
|
const gchar *error_msg;
|
|
|
|
|
wp_link_get_state (link, &error_msg);
|
|
|
|
|
g_signal_emit_by_name (self, "link-error", error_msg);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-14 16:38:49 +03:00
|
|
|
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;
|
2021-10-14 16:28:51 +02:00
|
|
|
else if (channel_is_aux(in->channel) != channel_is_aux(out->channel))
|
|
|
|
|
score += 7;
|
2021-10-14 16:38:49 +03:00
|
|
|
if (score > 0 && !in->visited)
|
|
|
|
|
score += 5;
|
|
|
|
|
if (score <= 10)
|
|
|
|
|
score = 0;
|
|
|
|
|
return score;
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-10 16:17:25 +03:00
|
|
|
static gboolean
|
2020-05-05 16:31:53 +03:00
|
|
|
create_links (WpSiStandardLink * self, WpTransition * transition,
|
2021-10-14 16:38:49 +03:00
|
|
|
GVariant * out_ports, GVariant * in_ports)
|
2020-04-10 16:17:25 +03:00
|
|
|
{
|
2021-11-20 13:11:37 -05:00
|
|
|
g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (self));
|
2021-10-14 16:38:49 +03:00
|
|
|
g_autoptr (GArray) in_ports_arr = NULL;
|
|
|
|
|
struct port out_port = {0};
|
|
|
|
|
struct port *in_port;
|
2020-05-05 16:31:53 +03:00
|
|
|
GVariantIter *iter = NULL;
|
2020-04-10 16:17:25 +03:00
|
|
|
guint i;
|
|
|
|
|
|
2021-11-20 13:11:37 -05:00
|
|
|
/* Clear old links if any */
|
|
|
|
|
self->n_active_links = 0;
|
|
|
|
|
self->n_failed_links = 0;
|
2022-07-19 20:01:10 +03:00
|
|
|
clear_node_links (&self->node_links);
|
2021-11-20 13:11:37 -05:00
|
|
|
|
2020-04-10 16:17:25 +03:00
|
|
|
/* tuple format:
|
|
|
|
|
uint32 node_id;
|
|
|
|
|
uint32 port_id;
|
|
|
|
|
uint32 channel; // enum spa_audio_channel
|
|
|
|
|
*/
|
2021-11-20 13:11:37 -05:00
|
|
|
if (!g_variant_is_of_type (out_ports, G_VARIANT_TYPE("a(uuu)")))
|
2020-04-10 16:17:25 +03:00
|
|
|
return FALSE;
|
2021-11-20 13:11:37 -05:00
|
|
|
if (!g_variant_is_of_type (in_ports, G_VARIANT_TYPE("a(uuu)")))
|
2020-04-10 16:17:25 +03:00
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
|
|
i = g_variant_n_children (in_ports);
|
2021-10-14 16:38:49 +03:00
|
|
|
if (i == 0)
|
|
|
|
|
return FALSE;
|
2020-04-10 16:17:25 +03:00
|
|
|
|
2021-11-20 13:11:37 -05:00
|
|
|
self->node_links = g_ptr_array_new_with_free_func (g_object_unref);
|
|
|
|
|
|
2021-10-14 16:38:49 +03:00
|
|
|
/* 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);
|
2020-04-10 16:17:25 +03:00
|
|
|
g_variant_get (in_ports, "a(uuu)", &iter);
|
2021-10-14 16:38:49 +03:00
|
|
|
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));
|
2020-04-10 16:17:25 +03:00
|
|
|
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);
|
2021-10-14 16:38:49 +03:00
|
|
|
while (g_variant_iter_loop (iter, "(uuu)", &out_port.node_id,
|
|
|
|
|
&out_port.port_id, &out_port.channel))
|
2020-04-10 16:17:25 +03:00
|
|
|
{
|
2021-10-14 16:38:49 +03:00
|
|
|
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;
|
2020-04-10 16:17:25 +03:00
|
|
|
}
|
|
|
|
|
}
|
2021-10-14 17:16:50 +03:00
|
|
|
|
|
|
|
|
/* not all output ports have to be linked ... */
|
|
|
|
|
if (!best_port || best_port->visited)
|
|
|
|
|
continue;
|
|
|
|
|
|
2021-10-14 16:38:49 +03:00
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
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 */
|
2021-10-16 09:51:00 +03:00
|
|
|
wp_object_activate_closure (WP_OBJECT (link),
|
2023-10-24 11:31:45 +03:00
|
|
|
WP_OBJECT_FEATURES_ALL, NULL,
|
2021-10-16 09:51:00 +03:00
|
|
|
g_cclosure_new_object (
|
|
|
|
|
(GCallback) on_link_activated, G_OBJECT (transition)));
|
2023-02-11 14:37:02 +01:00
|
|
|
|
|
|
|
|
g_signal_connect_object (link, "state-changed",
|
|
|
|
|
G_CALLBACK (on_link_state_changed), self, 0);
|
2020-04-10 16:17:25 +03:00
|
|
|
}
|
2020-05-14 13:44:19 -04:00
|
|
|
g_variant_iter_free (iter);
|
2021-05-27 17:01:00 +03:00
|
|
|
return self->node_links->len > 0;
|
2020-04-10 16:17:25 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2021-05-06 12:41:46 -04:00
|
|
|
get_ports_and_create_links (WpSiStandardLink *self, WpTransition *transition)
|
2020-04-10 16:17:25 +03:00
|
|
|
{
|
2021-05-06 12:41:46 -04:00
|
|
|
g_autoptr (WpSiLinkable) si_out = NULL;
|
|
|
|
|
g_autoptr (WpSiLinkable) si_in = NULL;
|
2021-03-17 14:52:41 -04:00
|
|
|
g_autoptr (GVariant) out_ports = NULL;
|
|
|
|
|
g_autoptr (GVariant) in_ports = NULL;
|
2020-04-10 16:17:25 +03:00
|
|
|
|
2021-05-06 12:41:46 -04:00
|
|
|
si_out = WP_SI_LINKABLE (g_weak_ref_get (&self->out_item));
|
|
|
|
|
si_in = WP_SI_LINKABLE (g_weak_ref_get (&self->in_item));
|
2021-12-22 16:12:04 +02:00
|
|
|
|
|
|
|
|
if (!si_out || !si_in ||
|
2024-03-12 12:44:32 +02:00
|
|
|
!wp_object_test_active_features (WP_OBJECT (si_out), WP_SESSION_ITEM_FEATURE_ACTIVE) ||
|
|
|
|
|
!wp_object_test_active_features (WP_OBJECT (si_in), WP_SESSION_ITEM_FEATURE_ACTIVE)) {
|
2021-12-22 16:12:04 +02:00
|
|
|
wp_transition_return_error (transition,
|
|
|
|
|
g_error_new (WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
|
2024-03-25 09:55:13 +02:00
|
|
|
"some node was destroyed before the link was created"));
|
2021-11-18 11:58:34 -05:00
|
|
|
return;
|
|
|
|
|
}
|
2021-05-06 12:41:46 -04:00
|
|
|
|
|
|
|
|
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);
|
2021-11-20 13:11:37 -05:00
|
|
|
if (!out_ports || !in_ports) {
|
|
|
|
|
wp_transition_return_error (transition, g_error_new (WP_DOMAIN_LIBRARY,
|
|
|
|
|
WP_LIBRARY_ERROR_INVARIANT,
|
|
|
|
|
"Failed to create links because one of the nodes has no ports"));
|
|
|
|
|
return;
|
|
|
|
|
}
|
2020-04-10 16:17:25 +03:00
|
|
|
|
2021-10-14 16:38:49 +03:00
|
|
|
if (!create_links (self, transition, out_ports, in_ports))
|
2021-03-17 14:52:41 -04:00
|
|
|
wp_transition_return_error (transition, g_error_new (WP_DOMAIN_LIBRARY,
|
|
|
|
|
WP_LIBRARY_ERROR_INVARIANT,
|
2021-05-05 09:45:23 -04:00
|
|
|
"Failed to create links because of wrong ports"));
|
2021-03-17 14:52:41 -04:00
|
|
|
}
|
2020-04-10 16:17:25 +03:00
|
|
|
|
2021-05-06 12:41:46 -04:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-14 16:38:49 +03:00
|
|
|
struct adapter
|
|
|
|
|
{
|
|
|
|
|
WpSiAdapter *si;
|
si-linkables: add int format-preference-priority SI audio adapter property
This property influences how the SI standard link picks a format during
negotiations. An adapter with a higher priority is preferred then, that is,
its format has priority over the other adapter's.
The minimum valid priority is 0. The value 0 is the default. 0 means that
there is no priority for this adapter format. If both adapters have
priority 0, the behavior is as it was before this commit.
If an adapter's priority is greater than 0, the following changes in
behavior occur:
1. In si_audio_adapter_find_format (), new formats are always picked
over a format that is already in use. Normally, the new format would
only be picked over the one in use if the new format has a greater
number of channels.
2. si_audio_adapter_configure_node () is called immediately when the
adapter is activated. Normally, this is only done in certain cases,
like when the stream.dont-remix property is true.
3. In the SI link, the adapter is configured even if the unpositioned
flags of one or both adapters are set to true.
The intent of this property is to be used when "upstream" (that
is, the media source) wants to enforce its format "downstream". Normally,
for example, audio sinks tend to open an audio output device with the
maximum amount of channels it can handle, and then keep it open with that
amount, even if incoming streams have fewer channels (the extra channels
then remain unused). By using these format priorities, a different
behavior can be configured where the output device reconfigures the
audio hardware to use the amount of channels from upstream.
2024-07-12 14:14:26 +02:00
|
|
|
guint32 proxy_id;
|
2021-10-14 16:38:49 +03:00
|
|
|
gboolean is_device;
|
|
|
|
|
gboolean dont_remix;
|
|
|
|
|
gboolean unpositioned;
|
|
|
|
|
gboolean no_dsp;
|
si-linkables: add int format-preference-priority SI audio adapter property
This property influences how the SI standard link picks a format during
negotiations. An adapter with a higher priority is preferred then, that is,
its format has priority over the other adapter's.
The minimum valid priority is 0. The value 0 is the default. 0 means that
there is no priority for this adapter format. If both adapters have
priority 0, the behavior is as it was before this commit.
If an adapter's priority is greater than 0, the following changes in
behavior occur:
1. In si_audio_adapter_find_format (), new formats are always picked
over a format that is already in use. Normally, the new format would
only be picked over the one in use if the new format has a greater
number of channels.
2. si_audio_adapter_configure_node () is called immediately when the
adapter is activated. Normally, this is only done in certain cases,
like when the stream.dont-remix property is true.
3. In the SI link, the adapter is configured even if the unpositioned
flags of one or both adapters are set to true.
The intent of this property is to be used when "upstream" (that
is, the media source) wants to enforce its format "downstream". Normally,
for example, audio sinks tend to open an audio output device with the
maximum amount of channels it can handle, and then keep it open with that
amount, even if incoming streams have fewer channels (the extra channels
then remain unused). By using these format priorities, a different
behavior can be configured where the output device reconfigures the
audio hardware to use the amount of channels from upstream.
2024-07-12 14:14:26 +02:00
|
|
|
gint format_preference_priority;
|
2021-10-14 16:38:49 +03:00
|
|
|
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)
|
|
|
|
|
{
|
si-linkables: add int format-preference-priority SI audio adapter property
This property influences how the SI standard link picks a format during
negotiations. An adapter with a higher priority is preferred then, that is,
its format has priority over the other adapter's.
The minimum valid priority is 0. The value 0 is the default. 0 means that
there is no priority for this adapter format. If both adapters have
priority 0, the behavior is as it was before this commit.
If an adapter's priority is greater than 0, the following changes in
behavior occur:
1. In si_audio_adapter_find_format (), new formats are always picked
over a format that is already in use. Normally, the new format would
only be picked over the one in use if the new format has a greater
number of channels.
2. si_audio_adapter_configure_node () is called immediately when the
adapter is activated. Normally, this is only done in certain cases,
like when the stream.dont-remix property is true.
3. In the SI link, the adapter is configured even if the unpositioned
flags of one or both adapters are set to true.
The intent of this property is to be used when "upstream" (that
is, the media source) wants to enforce its format "downstream". Normally,
for example, audio sinks tend to open an audio output device with the
maximum amount of channels it can handle, and then keep it open with that
amount, even if incoming streams have fewer channels (the extra channels
then remain unused). By using these format priorities, a different
behavior can be configured where the output device reconfigures the
audio hardware to use the amount of channels from upstream.
2024-07-12 14:14:26 +02:00
|
|
|
/* Configure other to have the same format with main, if necessary.
|
|
|
|
|
* Do this only if both sides have valid channel positions, otherwise,
|
|
|
|
|
* the channel mixers can't work properly.
|
|
|
|
|
* main->preferred_format overrides this though, since then, the
|
|
|
|
|
* format from main shall be used no matter what. */
|
|
|
|
|
if (!main->no_dsp && !other->dont_remix &&
|
|
|
|
|
((main->format_preference_priority > 0) || (!other->unpositioned &&
|
|
|
|
|
!main->unpositioned))) {
|
2021-10-14 16:38:49 +03:00
|
|
|
/* 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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-06 12:41:46 -04:00
|
|
|
static void
|
2021-09-29 13:13:59 -04:00
|
|
|
on_main_adapter_ready (GObject *obj, GAsyncResult * res, gpointer p)
|
2021-05-06 12:41:46 -04:00
|
|
|
{
|
|
|
|
|
WpTransition *transition = p;
|
|
|
|
|
WpSiStandardLink *self = wp_transition_get_source_object (transition);
|
|
|
|
|
g_autoptr (GError) error = NULL;
|
2021-10-14 16:38:49 +03:00
|
|
|
struct adapter *main, *other;
|
2021-05-06 12:41:46 -04:00
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-14 16:38:49 +03:00
|
|
|
main = g_object_get_data (G_OBJECT (transition), "adapter_main");
|
|
|
|
|
other = g_object_get_data (G_OBJECT (transition), "adapter_other");
|
2021-10-13 12:48:03 +03:00
|
|
|
|
2024-03-12 12:44:32 +02:00
|
|
|
if (!wp_object_test_active_features (WP_OBJECT (main->si), WP_SESSION_ITEM_FEATURE_ACTIVE) ||
|
|
|
|
|
!wp_object_test_active_features (WP_OBJECT (other->si), WP_SESSION_ITEM_FEATURE_ACTIVE)) {
|
2021-12-22 16:12:04 +02:00
|
|
|
wp_transition_return_error (transition,
|
|
|
|
|
g_error_new (WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
|
2024-03-25 09:55:13 +02:00
|
|
|
"some node was destroyed before the link was created"));
|
2021-12-22 16:12:04 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-13 12:48:03 +03:00
|
|
|
if (self->passthrough) {
|
2021-10-14 16:38:49 +03:00
|
|
|
wp_si_adapter_set_ports_format (other->si, NULL, "passthrough",
|
2021-10-13 12:48:03 +03:00
|
|
|
on_adapters_ready, transition);
|
|
|
|
|
} else {
|
2021-10-14 16:38:49 +03:00
|
|
|
/* 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);
|
2021-10-13 12:48:03 +03:00
|
|
|
}
|
2021-05-06 12:41:46 -04:00
|
|
|
}
|
|
|
|
|
|
2021-09-29 13:13:59 -04:00
|
|
|
static void
|
2021-10-14 16:38:49 +03:00
|
|
|
configure_and_link_adapters (WpSiStandardLink *self, WpTransition *transition)
|
2021-09-29 13:13:59 -04:00
|
|
|
{
|
2021-11-18 11:58:34 -05:00
|
|
|
g_autoptr (WpSiAdapter) si_out =
|
|
|
|
|
WP_SI_ADAPTER (g_weak_ref_get (&self->out_item));
|
|
|
|
|
g_autoptr (WpSiAdapter) si_in =
|
|
|
|
|
WP_SI_ADAPTER (g_weak_ref_get (&self->in_item));
|
2021-10-14 16:38:49 +03:00
|
|
|
struct adapter *out, *in, *main, *other;
|
|
|
|
|
const gchar *str = NULL;
|
2021-09-29 13:13:59 -04:00
|
|
|
|
2021-12-22 16:12:04 +02:00
|
|
|
if (!si_out || !si_in ||
|
2024-03-12 12:44:32 +02:00
|
|
|
!wp_object_test_active_features (WP_OBJECT (si_out), WP_SESSION_ITEM_FEATURE_ACTIVE) ||
|
|
|
|
|
!wp_object_test_active_features (WP_OBJECT (si_in), WP_SESSION_ITEM_FEATURE_ACTIVE)) {
|
2021-12-22 16:12:04 +02:00
|
|
|
wp_transition_return_error (transition,
|
|
|
|
|
g_error_new (WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
|
2024-03-25 09:55:13 +02:00
|
|
|
"some node was destroyed before the link was created"));
|
2021-11-18 11:58:34 -05:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-14 16:38:49 +03:00
|
|
|
out = g_slice_new0 (struct adapter);
|
|
|
|
|
in = g_slice_new0 (struct adapter);
|
2021-11-18 11:58:34 -05:00
|
|
|
out->si = g_steal_pointer (&si_out);
|
|
|
|
|
in->si = g_steal_pointer (&si_in);
|
2021-10-14 16:38:49 +03:00
|
|
|
|
si-linkables: add int format-preference-priority SI audio adapter property
This property influences how the SI standard link picks a format during
negotiations. An adapter with a higher priority is preferred then, that is,
its format has priority over the other adapter's.
The minimum valid priority is 0. The value 0 is the default. 0 means that
there is no priority for this adapter format. If both adapters have
priority 0, the behavior is as it was before this commit.
If an adapter's priority is greater than 0, the following changes in
behavior occur:
1. In si_audio_adapter_find_format (), new formats are always picked
over a format that is already in use. Normally, the new format would
only be picked over the one in use if the new format has a greater
number of channels.
2. si_audio_adapter_configure_node () is called immediately when the
adapter is activated. Normally, this is only done in certain cases,
like when the stream.dont-remix property is true.
3. In the SI link, the adapter is configured even if the unpositioned
flags of one or both adapters are set to true.
The intent of this property is to be used when "upstream" (that
is, the media source) wants to enforce its format "downstream". Normally,
for example, audio sinks tend to open an audio output device with the
maximum amount of channels it can handle, and then keep it open with that
amount, even if incoming streams have fewer channels (the extra channels
then remain unused). By using these format priorities, a different
behavior can be configured where the output device reconfigures the
audio hardware to use the amount of channels from upstream.
2024-07-12 14:14:26 +02:00
|
|
|
out->proxy_id = wp_session_item_get_associated_proxy_id (WP_SESSION_ITEM (out->si), WP_TYPE_NODE);
|
|
|
|
|
in->proxy_id = wp_session_item_get_associated_proxy_id (WP_SESSION_ITEM (in->si), WP_TYPE_NODE);
|
|
|
|
|
|
2021-10-14 16:38:49 +03:00
|
|
|
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), "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);
|
|
|
|
|
|
si-linkables: add int format-preference-priority SI audio adapter property
This property influences how the SI standard link picks a format during
negotiations. An adapter with a higher priority is preferred then, that is,
its format has priority over the other adapter's.
The minimum valid priority is 0. The value 0 is the default. 0 means that
there is no priority for this adapter format. If both adapters have
priority 0, the behavior is as it was before this commit.
If an adapter's priority is greater than 0, the following changes in
behavior occur:
1. In si_audio_adapter_find_format (), new formats are always picked
over a format that is already in use. Normally, the new format would
only be picked over the one in use if the new format has a greater
number of channels.
2. si_audio_adapter_configure_node () is called immediately when the
adapter is activated. Normally, this is only done in certain cases,
like when the stream.dont-remix property is true.
3. In the SI link, the adapter is configured even if the unpositioned
flags of one or both adapters are set to true.
The intent of this property is to be used when "upstream" (that
is, the media source) wants to enforce its format "downstream". Normally,
for example, audio sinks tend to open an audio output device with the
maximum amount of channels it can handle, and then keep it open with that
amount, even if incoming streams have fewer channels (the extra channels
then remain unused). By using these format priorities, a different
behavior can be configured where the output device reconfigures the
audio hardware to use the amount of channels from upstream.
2024-07-12 14:14:26 +02:00
|
|
|
str = wp_session_item_get_property (WP_SESSION_ITEM (out->si), "stream.format-preference-priority");
|
|
|
|
|
out->format_preference_priority = (str) ? pw_properties_parse_int (str) : 0;
|
|
|
|
|
str = wp_session_item_get_property (WP_SESSION_ITEM (in->si), "stream.format-preference-priority");
|
|
|
|
|
in->format_preference_priority = (str) ? pw_properties_parse_int (str) : 0;
|
|
|
|
|
|
|
|
|
|
wp_debug_object (self, "out [proxy id %" G_GUINT32_FORMAT ", device %d, dont_remix %d, "
|
|
|
|
|
"unpos %d format_preference_priority %d], in: [proxy id %" G_GUINT32_FORMAT
|
|
|
|
|
", device %d, dont_remix %d, unpos %d format_preference_priority %d]", out->proxy_id,
|
|
|
|
|
out->is_device, out->dont_remix, out->unpositioned, out->format_preference_priority,
|
|
|
|
|
in->proxy_id, in->is_device, in->dont_remix, in->unpositioned,
|
|
|
|
|
in->format_preference_priority);
|
|
|
|
|
|
|
|
|
|
/* If the format_preference_priority values of out and in are equal,
|
|
|
|
|
* always use out->si format, unless in->si is device.
|
|
|
|
|
* If instead one of the two values is higher, use the associated
|
|
|
|
|
* adapter's format. */
|
|
|
|
|
if ((out->format_preference_priority < in->format_preference_priority) ||
|
|
|
|
|
((out->format_preference_priority == in->format_preference_priority) &&
|
|
|
|
|
!out->is_device && in->is_device)) {
|
2021-10-14 16:38:49 +03:00
|
|
|
main = in;
|
|
|
|
|
other = out;
|
|
|
|
|
} else {
|
|
|
|
|
main = out;
|
|
|
|
|
other = in;
|
|
|
|
|
}
|
si-linkables: add int format-preference-priority SI audio adapter property
This property influences how the SI standard link picks a format during
negotiations. An adapter with a higher priority is preferred then, that is,
its format has priority over the other adapter's.
The minimum valid priority is 0. The value 0 is the default. 0 means that
there is no priority for this adapter format. If both adapters have
priority 0, the behavior is as it was before this commit.
If an adapter's priority is greater than 0, the following changes in
behavior occur:
1. In si_audio_adapter_find_format (), new formats are always picked
over a format that is already in use. Normally, the new format would
only be picked over the one in use if the new format has a greater
number of channels.
2. si_audio_adapter_configure_node () is called immediately when the
adapter is activated. Normally, this is only done in certain cases,
like when the stream.dont-remix property is true.
3. In the SI link, the adapter is configured even if the unpositioned
flags of one or both adapters are set to true.
The intent of this property is to be used when "upstream" (that
is, the media source) wants to enforce its format "downstream". Normally,
for example, audio sinks tend to open an audio output device with the
maximum amount of channels it can handle, and then keep it open with that
amount, even if incoming streams have fewer channels (the extra channels
then remain unused). By using these format priorities, a different
behavior can be configured where the output device reconfigures the
audio hardware to use the amount of channels from upstream.
2024-07-12 14:14:26 +02:00
|
|
|
wp_debug_object (self, "using %s adapter as main adapter", (main == in) ? "in" : "out");
|
2021-10-14 16:38:49 +03:00
|
|
|
|
|
|
|
|
/* always configure both adapters in passthrough mode
|
|
|
|
|
if this is a passthrough link */
|
2021-10-13 12:48:03 +03:00
|
|
|
if (self->passthrough) {
|
2021-10-14 16:38:49 +03:00
|
|
|
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",
|
2021-10-13 12:48:03 +03:00
|
|
|
on_main_adapter_ready, transition);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-14 16:38:49 +03:00
|
|
|
main->fmt = wp_si_adapter_get_ports_format (main->si, &main->mode);
|
|
|
|
|
other->fmt = wp_si_adapter_get_ports_format (other->si, &other->mode);
|
|
|
|
|
|
si-linkables: add int format-preference-priority SI audio adapter property
This property influences how the SI standard link picks a format during
negotiations. An adapter with a higher priority is preferred then, that is,
its format has priority over the other adapter's.
The minimum valid priority is 0. The value 0 is the default. 0 means that
there is no priority for this adapter format. If both adapters have
priority 0, the behavior is as it was before this commit.
If an adapter's priority is greater than 0, the following changes in
behavior occur:
1. In si_audio_adapter_find_format (), new formats are always picked
over a format that is already in use. Normally, the new format would
only be picked over the one in use if the new format has a greater
number of channels.
2. si_audio_adapter_configure_node () is called immediately when the
adapter is activated. Normally, this is only done in certain cases,
like when the stream.dont-remix property is true.
3. In the SI link, the adapter is configured even if the unpositioned
flags of one or both adapters are set to true.
The intent of this property is to be used when "upstream" (that
is, the media source) wants to enforce its format "downstream". Normally,
for example, audio sinks tend to open an audio output device with the
maximum amount of channels it can handle, and then keep it open with that
amount, even if incoming streams have fewer channels (the extra channels
then remain unused). By using these format priorities, a different
behavior can be configured where the output device reconfigures the
audio hardware to use the amount of channels from upstream.
2024-07-12 14:14:26 +02:00
|
|
|
/* Logging out and in formats instead of main and other ones on purpose,
|
|
|
|
|
* since main and other can be swapped depending on the logic above,
|
|
|
|
|
* while out and in always have the same unchanging meaning. */
|
|
|
|
|
wp_debug_boxed (WP_TYPE_SPA_POD, out->fmt, "configured out adapter format");
|
|
|
|
|
wp_debug_boxed (WP_TYPE_SPA_POD, in->fmt, "configured in adapter format");
|
|
|
|
|
|
2021-10-14 16:38:49 +03:00
|
|
|
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);
|
2021-09-29 13:13:59 -04:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-14 16:38:49 +03:00
|
|
|
adapter_free (main);
|
|
|
|
|
adapter_free (other);
|
2021-05-06 12:41:46 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
2021-12-22 16:12:04 +02:00
|
|
|
if (!si_out || !si_in ||
|
2024-03-12 12:44:32 +02:00
|
|
|
!wp_object_test_active_features ((WP_OBJECT (si_out)), WP_SESSION_ITEM_FEATURE_ACTIVE) ||
|
|
|
|
|
!wp_object_test_active_features ((WP_OBJECT (si_in)), WP_SESSION_ITEM_FEATURE_ACTIVE)) {
|
2021-12-22 16:12:04 +02:00
|
|
|
wp_transition_return_error (transition,
|
|
|
|
|
g_error_new (WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
|
2024-03-25 09:55:13 +02:00
|
|
|
"some node was destroyed before the link was created"));
|
2021-12-22 16:12:04 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-06 12:41:46 -04:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-17 14:52:41 -04:00
|
|
|
static void
|
|
|
|
|
si_standard_link_enable_active (WpSessionItem *si, WpTransition *transition)
|
|
|
|
|
{
|
|
|
|
|
WpSiStandardLink *self = WP_SI_STANDARD_LINK (si);
|
2021-03-25 15:34:24 -04:00
|
|
|
g_autoptr (WpSessionItem) si_out = NULL;
|
|
|
|
|
g_autoptr (WpSessionItem) si_in = NULL;
|
2021-03-31 10:48:14 -04:00
|
|
|
WpSiAcquisition *out_acquisition = NULL, *in_acquisition = NULL;
|
2021-03-17 14:52:41 -04:00
|
|
|
|
|
|
|
|
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;
|
2020-04-10 16:17:25 +03:00
|
|
|
}
|
|
|
|
|
|
2021-03-31 10:48:14 -04:00
|
|
|
/* make sure in/out items are valid */
|
2021-03-23 13:35:52 -04:00
|
|
|
si_out = g_weak_ref_get (&self->out_item);
|
|
|
|
|
si_in = g_weak_ref_get (&self->in_item);
|
2021-12-22 16:12:04 +02:00
|
|
|
if (!si_out || !si_in ||
|
2024-03-12 12:44:32 +02:00
|
|
|
!wp_object_test_active_features ((WP_OBJECT (si_out)), WP_SESSION_ITEM_FEATURE_ACTIVE) ||
|
|
|
|
|
!wp_object_test_active_features ((WP_OBJECT (si_in)), WP_SESSION_ITEM_FEATURE_ACTIVE)) {
|
2021-03-31 10:48:14 -04:00
|
|
|
wp_transition_return_error (transition,
|
2021-12-22 16:12:04 +02:00
|
|
|
g_error_new (WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
|
2024-03-25 09:55:13 +02:00
|
|
|
"some node was destroyed before the link was created"));
|
2021-03-31 10:48:14 -04:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* acquire */
|
2021-04-29 18:56:49 -04:00
|
|
|
out_acquisition = wp_si_linkable_get_acquisition (WP_SI_LINKABLE (si_out));
|
|
|
|
|
in_acquisition = wp_si_linkable_get_acquisition (WP_SI_LINKABLE (si_in));
|
2021-03-17 14:52:41 -04:00
|
|
|
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;
|
|
|
|
|
}
|
2020-04-10 16:17:25 +03:00
|
|
|
|
2021-03-17 14:52:41 -04:00
|
|
|
if (out_acquisition) {
|
2021-03-23 12:27:22 -04:00
|
|
|
wp_si_acquisition_acquire (out_acquisition, WP_SI_LINK (self),
|
2021-04-29 18:56:49 -04:00
|
|
|
WP_SI_LINKABLE (si_out), (GAsyncReadyCallback) on_item_acquired,
|
2021-03-25 15:34:24 -04:00
|
|
|
transition);
|
2020-04-10 16:17:25 +03:00
|
|
|
}
|
2021-03-17 14:52:41 -04:00
|
|
|
if (in_acquisition) {
|
2021-03-23 12:27:22 -04:00
|
|
|
wp_si_acquisition_acquire (in_acquisition, WP_SI_LINK (self),
|
2021-04-29 18:56:49 -04:00
|
|
|
WP_SI_LINKABLE (si_in), (GAsyncReadyCallback) on_item_acquired,
|
2021-03-17 14:52:41 -04:00
|
|
|
transition);
|
2020-04-10 16:17:25 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-25 15:34:24 -04:00
|
|
|
static void
|
|
|
|
|
si_standard_link_finalize (GObject * object)
|
|
|
|
|
{
|
|
|
|
|
WpSiStandardLink *self = WP_SI_STANDARD_LINK (object);
|
|
|
|
|
|
2021-03-23 13:35:52 -04:00
|
|
|
g_weak_ref_clear (&self->out_item);
|
|
|
|
|
g_weak_ref_clear (&self->in_item);
|
2021-04-07 13:21:40 -04:00
|
|
|
|
|
|
|
|
G_OBJECT_CLASS (si_standard_link_parent_class)->finalize (object);
|
2021-03-25 15:34:24 -04:00
|
|
|
}
|
|
|
|
|
|
2020-04-10 16:17:25 +03:00
|
|
|
static void
|
|
|
|
|
si_standard_link_class_init (WpSiStandardLinkClass * klass)
|
|
|
|
|
{
|
2021-03-25 15:34:24 -04:00
|
|
|
GObjectClass *object_class = (GObjectClass *) klass;
|
2020-04-10 16:17:25 +03:00
|
|
|
WpSessionItemClass *si_class = (WpSessionItemClass *) klass;
|
|
|
|
|
|
2021-03-25 15:34:24 -04:00
|
|
|
object_class->finalize = si_standard_link_finalize;
|
|
|
|
|
|
2020-04-10 16:17:25 +03:00
|
|
|
si_class->reset = si_standard_link_reset;
|
|
|
|
|
si_class->configure = si_standard_link_configure;
|
2021-03-17 14:52:41 -04:00
|
|
|
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;
|
2023-02-11 14:37:02 +01:00
|
|
|
|
|
|
|
|
signals[SIGNAL_LINK_ERROR] = g_signal_new (
|
|
|
|
|
"link-error", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST,
|
|
|
|
|
0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_STRING);
|
2020-04-10 16:17:25 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-29 18:56:49 -04:00
|
|
|
static WpSiLinkable *
|
2021-03-23 13:35:52 -04:00
|
|
|
si_standard_link_get_out_item (WpSiLink * item)
|
2020-04-10 16:17:25 +03:00
|
|
|
{
|
|
|
|
|
WpSiStandardLink *self = WP_SI_STANDARD_LINK (item);
|
2021-04-29 18:56:49 -04:00
|
|
|
return WP_SI_LINKABLE (g_weak_ref_get (&self->out_item));
|
2020-04-10 16:17:25 +03:00
|
|
|
}
|
|
|
|
|
|
2021-04-29 18:56:49 -04:00
|
|
|
static WpSiLinkable *
|
2021-03-23 13:35:52 -04:00
|
|
|
si_standard_link_get_in_item (WpSiLink * item)
|
2020-04-10 16:17:25 +03:00
|
|
|
{
|
|
|
|
|
WpSiStandardLink *self = WP_SI_STANDARD_LINK (item);
|
2021-04-29 18:56:49 -04:00
|
|
|
return WP_SI_LINKABLE (g_weak_ref_get (&self->in_item));
|
2020-04-10 16:17:25 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
si_standard_link_link_init (WpSiLinkInterface * iface)
|
|
|
|
|
{
|
|
|
|
|
iface->get_registration_info = si_standard_link_get_registration_info;
|
2021-03-23 13:35:52 -04:00
|
|
|
iface->get_out_item = si_standard_link_get_out_item;
|
|
|
|
|
iface->get_in_item = si_standard_link_get_in_item;
|
2020-04-10 16:17:25 +03:00
|
|
|
}
|
|
|
|
|
|
2023-02-09 13:11:14 -05:00
|
|
|
WP_PLUGIN_EXPORT GObject *
|
2023-05-25 18:29:58 +03:00
|
|
|
wireplumber__module_init (WpCore * core, WpSpaJson * args, GError ** error)
|
2020-04-10 16:17:25 +03:00
|
|
|
{
|
2023-02-09 13:11:14 -05:00
|
|
|
return G_OBJECT (wp_si_factory_new_simple (SI_FACTORY_NAME,
|
2021-03-17 14:52:41 -04:00
|
|
|
si_standard_link_get_type ()));
|
2020-04-10 16:17:25 +03:00
|
|
|
}
|