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.
This commit is contained in:
Carlos Rafael Giani 2024-07-12 14:14:26 +02:00
parent 479523abf9
commit 401de84fc2
2 changed files with 73 additions and 11 deletions

View file

@ -33,6 +33,18 @@ struct _WpSiAudioAdapter
WpDirection portconfig_direction; WpDirection portconfig_direction;
gboolean is_device; gboolean is_device;
gboolean dont_remix; gboolean dont_remix;
/* This is used during linking to pick the preferred format.
* If the priority in both in- and output ports is the same,
* the format of the input port is picked. If the priority
* is not set, the default 0 is used.
* Furthermore, if the priority is greater than 0, then in
* si_audio_adapter_find_format (), a new format is always
* picked, and in si_audio_adapter_enable_active (), the
* adapter is configured immediately. This is because such
* priorities indicate that this adapter's format is
* preferred during negotiation.
* Priorities lower than 0 are invalid. */
gint format_preference_priority;
gboolean is_autoconnect; gboolean is_autoconnect;
gboolean have_encoded; gboolean have_encoded;
gboolean encoded_only; gboolean encoded_only;
@ -110,6 +122,7 @@ si_audio_adapter_reset (WpSessionItem * item)
self->portconfig_direction = WP_DIRECTION_INPUT; self->portconfig_direction = WP_DIRECTION_INPUT;
self->is_device = FALSE; self->is_device = FALSE;
self->dont_remix = FALSE; self->dont_remix = FALSE;
self->format_preference_priority = 0;
self->is_autoconnect = FALSE; self->is_autoconnect = FALSE;
self->have_encoded = FALSE; self->have_encoded = FALSE;
self->encoded_only = FALSE; self->encoded_only = FALSE;
@ -196,7 +209,15 @@ si_audio_adapter_find_format (WpSiAudioAdapter * self, WpNode * node)
!spa_pod_copy_array(position, SPA_TYPE_Id, raw_format.position, SPA_AUDIO_MAX_CHANNELS)) !spa_pod_copy_array(position, SPA_TYPE_Id, raw_format.position, SPA_AUDIO_MAX_CHANNELS))
SPA_FLAG_SET(raw_format.flags, SPA_AUDIO_FLAG_UNPOSITIONED); SPA_FLAG_SET(raw_format.flags, SPA_AUDIO_FLAG_UNPOSITIONED);
if (self->raw_format.channels < raw_format.channels) { /* Only use this new format if it has _more_ channels than the old one
* to avoid unnecessary renegotiations and reconfigurations. By default,
* it is preferable to open too many channels than too few, since the
* extra channels can simply remain unused.
* Exception: when format_preference_priority is >0, do use the new format
* always. Such priorities indicate that the user wants the new format to
* be picked over the current format in all cases. */
if ((self->format_preference_priority > 0) ||
(self->raw_format.channels < raw_format.channels)) {
self->raw_format = raw_format; self->raw_format = raw_format;
if (is_unpositioned(&raw_format)) if (is_unpositioned(&raw_format))
self->is_unpositioned = TRUE; self->is_unpositioned = TRUE;
@ -285,6 +306,14 @@ si_audio_adapter_configure (WpSessionItem * item, WpProperties *p)
str = wp_properties_get (si_props, PW_KEY_STREAM_DONT_REMIX); str = wp_properties_get (si_props, PW_KEY_STREAM_DONT_REMIX);
self->dont_remix = str && pw_properties_parse_bool (str); self->dont_remix = str && pw_properties_parse_bool (str);
str = wp_properties_get (si_props, "stream.format-preference-priority");
self->format_preference_priority = (str) ? pw_properties_parse_int (str) : 0;
if (self->format_preference_priority < 0) {
wp_critical_object (item, "stream.format-preference-priority value %d "
"is invalid (must be >= 0)", self->format_preference_priority);
return FALSE;
}
str = wp_properties_get (si_props, PW_KEY_NODE_AUTOCONNECT); str = wp_properties_get (si_props, PW_KEY_NODE_AUTOCONNECT);
self->is_autoconnect = str && pw_properties_parse_bool (str); self->is_autoconnect = str && pw_properties_parse_bool (str);
@ -589,9 +618,12 @@ si_audio_adapter_enable_active (WpSessionItem *si, WpTransition *transition)
self->params_changed_sigid = g_signal_connect_object (self->node, self->params_changed_sigid = g_signal_connect_object (self->node,
"params-changed", (GCallback) on_node_params_changed, self, 0); "params-changed", (GCallback) on_node_params_changed, self, 0);
/* If device node, enum available formats and set one of them */ /* If device node, enum available formats and set one of them.
* Also, if format_preference_priority is >0, do the same, since during
* linking, the link will try to use the format as soon as possible. */
if (!self->no_format && (self->is_device || self->dont_remix || if (!self->no_format && (self->is_device || self->dont_remix ||
!self->is_autoconnect || self->disable_dsp || self->is_unpositioned)) !self->is_autoconnect || self->disable_dsp || self->is_unpositioned ||
(self->format_preference_priority > 0)))
si_audio_adapter_configure_node (self, transition); si_audio_adapter_configure_node (self, transition);
/* Otherwise just finish activating */ /* Otherwise just finish activating */

View file

@ -428,10 +428,12 @@ on_adapters_ready (GObject *obj, GAsyncResult * res, gpointer p)
struct adapter struct adapter
{ {
WpSiAdapter *si; WpSiAdapter *si;
guint32 proxy_id;
gboolean is_device; gboolean is_device;
gboolean dont_remix; gboolean dont_remix;
gboolean unpositioned; gboolean unpositioned;
gboolean no_dsp; gboolean no_dsp;
gint format_preference_priority;
WpSpaPod *fmt; WpSpaPod *fmt;
const gchar *mode; const gchar *mode;
}; };
@ -448,8 +450,14 @@ static void
configure_adapter (WpSiStandardLink *self, WpTransition *transition, configure_adapter (WpSiStandardLink *self, WpTransition *transition,
struct adapter *main, struct adapter *other) struct adapter *main, struct adapter *other)
{ {
/* configure other to have the same format with main, if necessary */ /* Configure other to have the same format with main, if necessary.
if (!main->no_dsp && !other->dont_remix && !other->unpositioned && !main->unpositioned) { * 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))) {
/* if formats are the same, no need to reconfigure */ /* if formats are the same, no need to reconfigure */
if (other->fmt && !g_strcmp0 (main->mode, other->mode) if (other->fmt && !g_strcmp0 (main->mode, other->mode)
&& wp_spa_pod_equal (main->fmt, other->fmt)) && wp_spa_pod_equal (main->fmt, other->fmt))
@ -539,6 +547,9 @@ configure_and_link_adapters (WpSiStandardLink *self, WpTransition *transition)
out->si = g_steal_pointer (&si_out); out->si = g_steal_pointer (&si_out);
in->si = g_steal_pointer (&si_in); in->si = g_steal_pointer (&si_in);
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);
str = wp_session_item_get_property (WP_SESSION_ITEM (out->si), "item.node.type"); str = wp_session_item_get_property (WP_SESSION_ITEM (out->si), "item.node.type");
out->is_device = !g_strcmp0 (str, "device"); out->is_device = !g_strcmp0 (str, "device");
str = wp_session_item_get_property (WP_SESSION_ITEM (in->si), "item.node.type"); str = wp_session_item_get_property (WP_SESSION_ITEM (in->si), "item.node.type");
@ -559,19 +570,32 @@ configure_and_link_adapters (WpSiStandardLink *self, WpTransition *transition)
str = wp_session_item_get_property (WP_SESSION_ITEM (in->si), "item.features.no-dsp"); str = wp_session_item_get_property (WP_SESSION_ITEM (in->si), "item.features.no-dsp");
in->no_dsp = str && pw_properties_parse_bool (str); in->no_dsp = str && pw_properties_parse_bool (str);
wp_debug_object (self, "out [device:%d, dont_remix %d, unpos %d], " str = wp_session_item_get_property (WP_SESSION_ITEM (out->si), "stream.format-preference-priority");
"in: [device %d, dont_remix %d, unpos %d]", out->format_preference_priority = (str) ? pw_properties_parse_int (str) : 0;
out->is_device, out->dont_remix, out->unpositioned, str = wp_session_item_get_property (WP_SESSION_ITEM (in->si), "stream.format-preference-priority");
in->is_device, in->dont_remix, in->unpositioned); in->format_preference_priority = (str) ? pw_properties_parse_int (str) : 0;
/* we always use out->si format, unless in->si is device */ wp_debug_object (self, "out [proxy id %" G_GUINT32_FORMAT ", device %d, dont_remix %d, "
if (!out->is_device && in->is_device) { "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)) {
main = in; main = in;
other = out; other = out;
} else { } else {
main = out; main = out;
other = in; other = in;
} }
wp_debug_object (self, "using %s adapter as main adapter", (main == in) ? "in" : "out");
/* always configure both adapters in passthrough mode /* always configure both adapters in passthrough mode
if this is a passthrough link */ if this is a passthrough link */
@ -588,6 +612,12 @@ configure_and_link_adapters (WpSiStandardLink *self, WpTransition *transition)
main->fmt = wp_si_adapter_get_ports_format (main->si, &main->mode); main->fmt = wp_si_adapter_get_ports_format (main->si, &main->mode);
other->fmt = wp_si_adapter_get_ports_format (other->si, &other->mode); other->fmt = wp_si_adapter_get_ports_format (other->si, &other->mode);
/* 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");
if (main->fmt) if (main->fmt)
/* ideally, configure other based on main */ /* ideally, configure other based on main */
configure_adapter (self, transition, main, other); configure_adapter (self, transition, main, other);