From 5dcd06c3398a0ae80efd515729f4b964075b57c2 Mon Sep 17 00:00:00 2001 From: George Kiagiadakis Date: Wed, 13 Oct 2021 12:48:03 +0300 Subject: [PATCH] policy-node: implement encoded audio passthrough * also respect node.exclusive * also send error to clients if a target node was not found or if it is not possible to link to it * also allow dont-reconnect nodes to be handled normally the first time they appear, until they are linked once --- modules/module-si-audio-adapter.c | 8 +- modules/module-si-standard-link.c | 46 +++++---- src/scripts/policy-node.lua | 156 +++++++++++++++++++++++++----- 3 files changed, 165 insertions(+), 45 deletions(-) diff --git a/modules/module-si-audio-adapter.c b/modules/module-si-audio-adapter.c index 953a0ca3..d2cb9b9c 100644 --- a/modules/module-si-audio-adapter.c +++ b/modules/module-si-audio-adapter.c @@ -238,6 +238,12 @@ si_audio_adapter_configure (WpSessionItem * item, WpProperties *p) self->node = g_object_ref (node); + wp_properties_set (si_props, "item.node.supports-encoded-fmts", + self->have_encoded ? "true" : "false"); + + wp_properties_set (si_props, "item.node.encoded-only", + self->encoded_only ? "true" : "false"); + wp_properties_set (si_props, "item.factory.name", SI_FACTORY_NAME); wp_session_item_set_properties (item, g_steal_pointer (&si_props)); return TRUE; @@ -578,7 +584,7 @@ si_audio_adapter_set_ports_format (WpSiAdapter * item, WpSpaPod *f, } /* build default format if NULL was given */ - if (!format) { + if (!format && !g_strcmp0 (mode, "dsp")) { format = build_adapter_default_format (self, mode); g_return_if_fail (format); } diff --git a/modules/module-si-standard-link.c b/modules/module-si-standard-link.c index fbd7b80e..5b627e2f 100644 --- a/modules/module-si-standard-link.c +++ b/modules/module-si-standard-link.c @@ -23,6 +23,7 @@ struct _WpSiStandardLink const gchar *out_item_port_context; const gchar *in_item_port_context; gboolean passive; + gboolean passthrough; /* activate */ GPtrArray *node_links; @@ -59,6 +60,7 @@ si_standard_link_reset (WpSessionItem * item) 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); } @@ -108,11 +110,10 @@ si_standard_link_configure (WpSessionItem * item, WpProperties * p) "in.item.port.context"); str = wp_properties_get (si_props, "passive"); - if (str && sscanf(str, "%u", &self->passive) != 1) - return FALSE; - if (!str) - wp_properties_setf (si_props, "passive", "%u", - self->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); @@ -340,23 +341,24 @@ on_main_adapter_ready (GObject *obj, GAsyncResult * res, gpointer p) si_out = WP_SI_ADAPTER (g_weak_ref_get (&self->out_item)); si_in = WP_SI_ADAPTER (g_weak_ref_get (&self->in_item)); - g_return_if_fail (si_out); g_return_if_fail (si_in); - /* get format and mode */ - other = si_out; - format = wp_si_adapter_get_ports_format (si_out, &mode); - if (!format) { - other = si_in; - format = wp_si_adapter_get_ports_format (si_in, &mode); - } + /* get the other adapter */ + other = ((gpointer)obj == (gpointer)si_in) ? si_out : si_in; - g_return_if_fail (mode); - g_return_if_fail (format); - wp_si_adapter_set_ports_format (other, wp_spa_pod_ref (format), - g_strcmp0 (mode, "dsp") == 0 ? "dsp" : "convert", - on_adapters_ready, transition); + if (self->passthrough) { + wp_si_adapter_set_ports_format (other, NULL, "passthrough", + on_adapters_ready, transition); + } else { + format = wp_si_adapter_get_ports_format (WP_SI_ADAPTER (obj), &mode); + g_return_if_fail (mode); + g_return_if_fail (format); + + wp_si_adapter_set_ports_format (other, wp_spa_pod_ref (format), + g_strcmp0 (mode, "dsp") == 0 ? "dsp" : "convert", + on_adapters_ready, transition); + } } static gboolean @@ -366,7 +368,7 @@ ports_format_compatible (WpSpaPod *out_format, WpSpaPod *in_format, if (!out_format || !in_format || !out_mode || !in_mode) return FALSE; return wp_spa_pod_equal (out_format, in_format) && - (g_strcmp0 (out_mode, "dsp") == 0) == (g_strcmp0 (in_mode, "dsp") == 0); + (g_strcmp0 (out_mode, "dsp") == 0) && (g_strcmp0 (in_mode, "dsp") == 0); } static void @@ -378,6 +380,12 @@ configure_and_link (WpSiStandardLink *self, WpSiAdapter *main, g_autoptr (WpSpaPod) main_fmt = NULL; g_autoptr (WpSpaPod) other_fmt = NULL; + if (self->passthrough) { + wp_si_adapter_set_ports_format (main, NULL, "passthrough", + on_main_adapter_ready, transition); + return; + } + main_fmt = wp_si_adapter_get_ports_format (main, &main_mode); other_fmt = wp_si_adapter_get_ports_format (other, &other_mode); diff --git a/src/scripts/policy-node.lua b/src/scripts/policy-node.lua index 6c0fde6d..2f1688db 100644 --- a/src/scripts/policy-node.lua +++ b/src/scripts/policy-node.lua @@ -18,11 +18,13 @@ function parseBool(var) return var and (var == "true" or var == "1") end -function createLink (si, si_target) +function createLink (si, si_target, passthrough, exclusive) local out_item = nil local in_item = nil + local si_props = si.properties + local target_props = si_target.properties - if si.properties["item.node.direction"] == "output" then + if si_props["item.node.direction"] == "output" then -- playback out_item = si in_item = si_target @@ -32,13 +34,14 @@ function createLink (si, si_target) out_item = si_target end - local passive = parseBool(si.properties["node.passive"]) or - parseBool(si_target.properties["node.passive"]) + local passive = parseBool(si_props["node.passive"]) or + parseBool(target_props["node.passive"]) - Log.info (string.format("link %s <-> %s passive:%s", - tostring(si.properties["node.name"]), - tostring(si_target.properties["node.name"]), - tostring(passive))) + Log.info ( + string.format("link %s <-> %s passive:%s, passthrough:%s, exclusive:%s", + tostring(si_props["node.name"]), + tostring(target_props["node.name"]), + tostring(passive), tostring(passthrough), tostring(exclusive))) -- create and configure link local si_link = SessionItem ( "si-standard-link" ) @@ -46,6 +49,8 @@ function createLink (si, si_target) ["out.item"] = out_item, ["in.item"] = in_item, ["passive"] = passive, + ["passthrough"] = passthrough, + ["exclusive"] = exclusive, ["out.item.port.context"] = "output", ["in.item.port.context"] = "input", ["is.policy.item.link"] = true, @@ -68,6 +73,47 @@ function createLink (si, si_target) end) end +function isLinked(si_target) + local target_id = si_target.id + local linked = false + local exclusive = false + + for l in links_om:iterate() do + local p = l.properties + local out_id = tonumber(p["out.item.id"]) + local in_id = tonumber(p["in.item.id"]) + linked = (out_id == target_id) or (in_id == target_id) + if linked then + exclusive = parseBool(p["exclusive"]) or parseBool(p["passthrough"]) + break + end + end + return linked, exclusive +end + +function canPassthrough (si, si_target) + -- both nodes must support encoded formats + if not parseBool(si.properties["item.node.supports-encoded-fmts"]) + or not parseBool(si_target.properties["item.node.supports-encoded-fmts"]) then + return false + end + + -- make sure that the nodes have at least one common non-raw format + local n1 = si:get_associated_proxy ("node") + local n2 = si_target:get_associated_proxy ("node") + for p1 in n1:iterate_params("EnumFormat") do + local p1p = p1:parse() + if p1p.properties.mediaSubtype ~= "raw" then + for p2 in n2:iterate_params("EnumFormat") do + if p1:filter(p2) then + return true + end + end + end + end + return false +end + function canLink (properties, si_target) local target_properties = si_target.properties @@ -268,6 +314,8 @@ function checkLinkable(si) return true, si_props end +si_flags = {} + function handleLinkable (si) local valid, si_props = checkLinkable(si) if not valid then @@ -283,28 +331,36 @@ function handleLinkable (si) Log.info (si, "handling item: " .. tostring(si_props["node.name"])) - -- get reconnect - local reconnect = not parseBool(si_props["node.dont-reconnect"]) - - -- find target - local si_target = findDefinedTarget (si_props) - if not si_target and not reconnect then - Log.info (si, "... destroy node") - local node = si:get_associated_proxy ("node") - node:request_destroy() - return - elseif not si_target and reconnect then - si_target = findUndefinedTarget (si_props) + -- prepare flags table + if not si_flags[si.id] then + si_flags[si.id] = {} end + + -- get other important node properties + local reconnect = not parseBool(si_props["node.dont-reconnect"]) + local exclusive = parseBool(si_props["node.exclusive"]) + local must_passthrough = parseBool(si_props["item.node.encoded-only"]) + + -- find defined target + local si_target = findDefinedTarget(si_props) + local can_passthrough = si_target and canPassthrough(si, si_target) + if si_target and must_passthrough and not can_passthrough then + si_target = nil + end + + -- find fallback target if not si_target then - Log.info (si, "... target not found") - return + si_target = findUndefinedTarget(si_props) + can_passthrough = si_target and canPassthrough(si, si_target) + if si_target and must_passthrough and not can_passthrough then + si_target = nil + end end -- Check if item is linked to proper target, otherwise re-link local si_link, si_peer = getSiLinkAndSiPeer (si, si_props) if si_link then - if si_peer and si_peer.id == si_target.id then + if si_peer and si_target and si_peer.id == si_target.id then Log.debug (si, "... already linked to proper target") return end @@ -320,8 +376,53 @@ function handleLinkable (si) end end - -- create new link - createLink (si, si_target) + -- if the stream has dont-reconnect and was already linked before, + -- don't link it to a new target + if not reconnect and si_flags[si.id].was_handled then + si_target = nil + end + + -- check target's availability + if si_target then + local target_is_linked, target_is_exclusive = isLinked(si_target) + if target_is_exclusive then + Log.info(si, "... target is linked exclusively") + si_target = nil + end + + if target_is_linked then + if exclusive or must_passthrough then + Log.info(si, "... target is already linked, cannot link exclusively") + si_target = nil + else + -- disable passthrough, we can live without it + can_passthrough = false + end + end + end + + if not si_target then + Log.info (si, "... target not found, reconnect:" .. tostring(reconnect)) + + local node = si:get_associated_proxy ("node") + if not reconnect then + Log.info (si, "... destroy node") + node:request_destroy() + end + + local client_id = node.properties["client.id"] + if client_id then + local client = clients_om:lookup { + Constraint { "bound-id", "=", client_id, type = "gobject" } + } + if client then + client:send_error(node["bound-id"], -2, "no node available") + end + end + else + createLink (si, si_target, can_passthrough, exclusive) + si_flags[si.id].was_handled = true + end end function unhandleLinkable (si) @@ -341,6 +442,8 @@ function unhandleLinkable (si) Log.info (silink, "... link removed") end end + + si_flags[si.id] = nil end function rescan() @@ -368,6 +471,8 @@ metadata_om = ObjectManager { endpoints_om = ObjectManager { Interest { type = "SiEndpoint" } } +clients_om = ObjectManager { Interest { type = "client" } } + linkables_om = ObjectManager { Interest { type = "SiLinkable", @@ -441,5 +546,6 @@ end) metadata_om:activate() endpoints_om:activate() +clients_om:activate() linkables_om:activate() links_om:activate()