diff --git a/modules/module-si-audio-adapter.c b/modules/module-si-audio-adapter.c index c6667c54..6d522369 100644 --- a/modules/module-si-audio-adapter.c +++ b/modules/module-si-audio-adapter.c @@ -33,6 +33,18 @@ struct _WpSiAudioAdapter WpDirection portconfig_direction; gboolean is_device; 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 have_encoded; gboolean encoded_only; @@ -110,6 +122,7 @@ si_audio_adapter_reset (WpSessionItem * item) self->portconfig_direction = WP_DIRECTION_INPUT; self->is_device = FALSE; self->dont_remix = FALSE; + self->format_preference_priority = 0; self->is_autoconnect = FALSE; self->have_encoded = 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_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; if (is_unpositioned(&raw_format)) 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); 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); 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, "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 || - !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); /* Otherwise just finish activating */ diff --git a/modules/module-si-standard-link.c b/modules/module-si-standard-link.c index e0febe9d..0becd74a 100644 --- a/modules/module-si-standard-link.c +++ b/modules/module-si-standard-link.c @@ -428,10 +428,12 @@ on_adapters_ready (GObject *obj, GAsyncResult * res, gpointer p) struct adapter { WpSiAdapter *si; + guint32 proxy_id; gboolean is_device; gboolean dont_remix; gboolean unpositioned; gboolean no_dsp; + gint format_preference_priority; WpSpaPod *fmt; const gchar *mode; }; @@ -448,8 +450,14 @@ 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) { + /* 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))) { /* 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)) @@ -539,6 +547,9 @@ configure_and_link_adapters (WpSiStandardLink *self, WpTransition *transition) out->si = g_steal_pointer (&si_out); 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"); out->is_device = !g_strcmp0 (str, "device"); 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"); 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); + 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; - /* we always use out->si format, unless in->si is device */ - if (!out->is_device && in->is_device) { + 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)) { main = in; other = out; } else { main = out; other = in; } + wp_debug_object (self, "using %s adapter as main adapter", (main == in) ? "in" : "out"); /* always configure both adapters in passthrough mode 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); 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) /* ideally, configure other based on main */ configure_adapter (self, transition, main, other);