mirror of
https://gitlab.freedesktop.org/pipewire/wireplumber.git
synced 2026-01-15 11:20:30 +01:00
If a stream audio node does not have the 'media.role' property set, the policy will asign the 'Default' media role name to it. In addition to this, if the 'Default' endpoint can not be found, the policy will link the stream audio node with the lowest priority endpoint. Video nodes will always be handled by policy-node.lua, and they will never be handled by policy-endpoint-client.lua This allows users to assign a particular endpoint for streams that don't have the 'media.role' property set.
257 lines
6.9 KiB
Lua
257 lines
6.9 KiB
Lua
-- WirePlumber
|
|
--
|
|
-- Copyright © 2021 Collabora Ltd.
|
|
-- @author Julian Bouzas <julian.bouzas@collabora.com>
|
|
--
|
|
-- SPDX-License-Identifier: MIT
|
|
|
|
-- Receive script arguments from config.lua
|
|
local config = ... or {}
|
|
config.roles = config.roles or {}
|
|
|
|
local self = {}
|
|
self.scanning = false
|
|
self.pending_rescan = false
|
|
|
|
function rescan ()
|
|
for si in linkables_om:iterate() do
|
|
handleLinkable (si)
|
|
end
|
|
end
|
|
|
|
function scheduleRescan ()
|
|
if self.scanning then
|
|
self.pending_rescan = true
|
|
return
|
|
end
|
|
|
|
self.scanning = true
|
|
rescan ()
|
|
self.scanning = false
|
|
|
|
if self.pending_rescan then
|
|
self.pending_rescan = false
|
|
Core.sync(function ()
|
|
scheduleRescan ()
|
|
end)
|
|
end
|
|
end
|
|
|
|
function findRole(role, tmc)
|
|
if role and not config.roles[role] then
|
|
-- find the role with matching alias
|
|
for r, p in pairs(config.roles) do
|
|
-- default media class can be overridden in the role config data
|
|
mc = p["media.class"] or "Audio/Sink"
|
|
if (type(p.alias) == "table" and tmc == mc) then
|
|
for i = 1, #(p.alias), 1 do
|
|
if role == p.alias[i] then
|
|
return r
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- otherwise get the lowest priority role
|
|
local lowest_priority_p = nil
|
|
local lowest_priority_r = nil
|
|
for r, p in pairs(config.roles) do
|
|
mc = p["media.class"] or "Audio/Sink"
|
|
if tmc == mc and (lowest_priority_p == nil or
|
|
p.priority < lowest_priority_p.priority) then
|
|
lowest_priority_p = p
|
|
lowest_priority_r = r
|
|
end
|
|
end
|
|
return lowest_priority_r
|
|
end
|
|
return role
|
|
end
|
|
|
|
function findTargetEndpoint (node, media_class, role)
|
|
local target_class_assoc = {
|
|
["Stream/Input/Audio"] = "Audio/Source",
|
|
["Stream/Output/Audio"] = "Audio/Sink",
|
|
["Stream/Input/Video"] = "Video/Source",
|
|
}
|
|
local media_role = nil
|
|
local highest_priority = -1
|
|
local target = nil
|
|
|
|
-- get target media class
|
|
local target_media_class = target_class_assoc[media_class]
|
|
if not target_media_class then
|
|
return nil
|
|
end
|
|
|
|
-- find highest priority endpoint by role
|
|
media_role = findRole(role, target_media_class)
|
|
for si_target_ep in endpoints_om:iterate {
|
|
Constraint { "role", "=", media_role, type = "pw-global" },
|
|
Constraint { "media.class", "=", target_media_class, type = "pw-global" },
|
|
} do
|
|
local priority = tonumber(si_target_ep.properties["priority"])
|
|
if priority > highest_priority then
|
|
highest_priority = priority
|
|
target = si_target_ep
|
|
end
|
|
end
|
|
|
|
return target
|
|
end
|
|
|
|
function createLink (si, si_target_ep)
|
|
local out_item = nil
|
|
local in_item = nil
|
|
local si_props = si.properties
|
|
local target_ep_props = si_target_ep.properties
|
|
|
|
if si_props["item.node.direction"] == "output" then
|
|
-- playback
|
|
out_item = si
|
|
in_item = si_target_ep
|
|
else
|
|
-- capture
|
|
out_item = si_target_ep
|
|
in_item = si
|
|
end
|
|
|
|
Log.info (string.format("link %s <-> %s",
|
|
tostring(si_props["node.name"]),
|
|
tostring(target_ep_props["name"])))
|
|
|
|
-- create and configure link
|
|
local si_link = SessionItem ( "si-standard-link" )
|
|
if not si_link:configure {
|
|
["out.item"] = out_item,
|
|
["in.item"] = in_item,
|
|
["out.item.port.context"] = "output",
|
|
["in.item.port.context"] = "input",
|
|
["is.policy.endpoint.client.link"] = true,
|
|
["media.role"] = target_ep_props["role"],
|
|
["target.media.class"] = target_ep_props["media.class"],
|
|
["item.plugged.usec"] = si_props["item.plugged.usec"],
|
|
} then
|
|
Log.warning (si_link, "failed to configure si-standard-link")
|
|
return
|
|
end
|
|
|
|
-- register
|
|
si_link:register()
|
|
end
|
|
|
|
function checkLinkable (si)
|
|
-- only handle session items that has a node associated proxy
|
|
local node = si:get_associated_proxy ("node")
|
|
if not node or not node.properties then
|
|
return false
|
|
end
|
|
|
|
-- only handle stream session items
|
|
local media_class = node.properties["media.class"]
|
|
if not media_class or not string.find (media_class, "Stream") then
|
|
return false
|
|
end
|
|
|
|
-- Determine if we can handle item by this policy
|
|
if endpoints_om:get_n_objects () == 0 then
|
|
Log.debug (si, "item won't be handled by this policy")
|
|
return false
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
function handleLinkable (si)
|
|
if not checkLinkable (si) then
|
|
return
|
|
end
|
|
|
|
local node = si:get_associated_proxy ("node")
|
|
local media_class = node.properties["media.class"] or ""
|
|
local media_role = node.properties["media.role"] or "Default"
|
|
Log.info (si, "handling item " .. tostring(node.properties["node.name"]) ..
|
|
" with role " .. media_role)
|
|
|
|
-- find proper target endpoint
|
|
local si_target_ep = findTargetEndpoint (node, media_class, media_role)
|
|
if not si_target_ep then
|
|
Log.info (si, "... target endpoint not found")
|
|
return
|
|
end
|
|
|
|
-- Check if item is linked to proper target, otherwise re-link
|
|
for link in links_om:iterate() do
|
|
local out_id = tonumber(link.properties["out.item.id"])
|
|
local in_id = tonumber(link.properties["in.item.id"])
|
|
if out_id == si.id or in_id == si.id then
|
|
local is_out = out_id == si.id and true or false
|
|
for peer_ep in endpoints_om:iterate() do
|
|
if peer_ep.id == (is_out and in_id or out_id) then
|
|
|
|
if peer_ep.id == si_target_ep.id then
|
|
Log.info (si, "... already linked to proper target endpoint")
|
|
return
|
|
end
|
|
|
|
-- remove old link if active, otherwise schedule rescan
|
|
if ((link:get_active_features() & Feature.SessionItem.ACTIVE) ~= 0) then
|
|
link:remove ()
|
|
Log.info (si, "... moving to new target")
|
|
else
|
|
scheduleRescan ()
|
|
Log.info (si, "... scheduled rescan")
|
|
return
|
|
end
|
|
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- create new link
|
|
createLink (si, si_target_ep)
|
|
end
|
|
|
|
function unhandleLinkable (si)
|
|
if not checkLinkable (si) then
|
|
return
|
|
end
|
|
|
|
local node = si:get_associated_proxy ("node")
|
|
Log.info (si, "unhandling item " .. tostring(node.properties["node.name"]))
|
|
|
|
-- remove any links associated with this item
|
|
for silink in links_om:iterate() do
|
|
local out_id = tonumber (silink.properties["out.item.id"])
|
|
local in_id = tonumber (silink.properties["in.item.id"])
|
|
if out_id == si.id or in_id == si.id then
|
|
silink:remove ()
|
|
Log.info (silink, "... link removed")
|
|
end
|
|
end
|
|
end
|
|
|
|
endpoints_om = ObjectManager { Interest { type = "SiEndpoint" }}
|
|
linkables_om = ObjectManager { Interest { type = "SiLinkable",
|
|
-- only handle si-audio-adapter and si-node
|
|
Constraint {
|
|
"item.factory.name", "=", "si-audio-adapter", type = "pw-global" },
|
|
}
|
|
}
|
|
links_om = ObjectManager { Interest { type = "SiLink",
|
|
-- only handle links created by this policy
|
|
Constraint { "is.policy.endpoint.client.link", "=", true, type = "pw-global" },
|
|
} }
|
|
|
|
linkables_om:connect("objects-changed", function (om)
|
|
scheduleRescan ()
|
|
end)
|
|
|
|
linkables_om:connect("object-removed", function (om, si)
|
|
unhandleLinkable (si)
|
|
end)
|
|
|
|
endpoints_om:activate()
|
|
linkables_om:activate()
|
|
links_om:activate()
|