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
This commit is contained in:
George Kiagiadakis 2021-10-13 12:48:03 +03:00
parent 52c4839a8c
commit 5dcd06c339
3 changed files with 165 additions and 45 deletions

View file

@ -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);
}

View file

@ -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);

View file

@ -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()