mirror of
https://gitlab.freedesktop.org/pipewire/wireplumber.git
synced 2026-05-05 00:48:01 +02:00
monitors/alsa: provide splitting of UCM SplitPCM nodes
Instruct ACP to provide information about UCM SplitPCM channel splitting instead of doing it with alsa-lib plugins. Use the provided information to load loopbacks that create virtual sinks that do the channel remapping from UCM.
This commit is contained in:
parent
83e93876d5
commit
4d02d0275f
1 changed files with 193 additions and 5 deletions
|
|
@ -5,6 +5,9 @@
|
|||
--
|
||||
-- SPDX-License-Identifier: MIT
|
||||
|
||||
SPLIT_PCM_PARENT_OFFSET = 256
|
||||
SPLIT_PCM_OFFSET = 512
|
||||
|
||||
cutils = require ("common-utils")
|
||||
log = Log.open_topic ("s-monitors")
|
||||
|
||||
|
|
@ -30,8 +33,150 @@ function applyDefaultDeviceProperties (properties)
|
|||
properties["api.acp.auto-profile"] = false
|
||||
properties["api.acp.auto-port"] = false
|
||||
properties["api.dbus.ReserveDevice1.Priority"] = -20
|
||||
properties["api.alsa.split-enable"] = true
|
||||
end
|
||||
|
||||
function createSplitPCMHWNode(dev_props, properties)
|
||||
local skip_keys = {
|
||||
"api.alsa.split.position", "card.profile.device", "device.profile.description",
|
||||
"device.profile.name"
|
||||
}
|
||||
local props = {}
|
||||
|
||||
for k, v in pairs(properties) do
|
||||
props[k] = v
|
||||
end
|
||||
for _, k in pairs(skip_keys) do
|
||||
props[k] = nil
|
||||
end
|
||||
|
||||
-- create the underlying hidden ALSA node
|
||||
props["node.name"] = props["api.alsa.split.name"]
|
||||
props["node.description"] = string.format("%s %s", dev_props["device.description"],
|
||||
props["api.alsa.path"]:gsub("^[^,]*[,:]", ""))
|
||||
if props["api.alsa.pcm.stream"] == "capture" then
|
||||
props["media.class"] = "Audio/Source/Internal"
|
||||
else
|
||||
props["media.class"] = "Audio/Sink/Internal"
|
||||
end
|
||||
props["api.alsa.use-chmap"] = false
|
||||
props["api.alsa.split.parent"] = true
|
||||
props["audio.position"] = props["api.alsa.split.hw-position"]
|
||||
local channels = Json.Raw (props["api.alsa.split.hw-position"]):parse ()
|
||||
props["audio.channels"] = tostring(#channels)
|
||||
|
||||
props = JsonUtils.match_rules_update_properties (config.rules, props)
|
||||
|
||||
if cutils.parseBool (props ["node.disabled"]) then
|
||||
log:notice ("ALSA node " .. props ["node.name"] .. " disabled")
|
||||
return nil
|
||||
end
|
||||
|
||||
return Node("adapter", props)
|
||||
end
|
||||
|
||||
function createSplitPCMLoopback(parent, id, obj_type, factory, properties)
|
||||
local skip_keys = {
|
||||
-- not suitable for loopback
|
||||
"audio.rate",
|
||||
"clock.quantum-limit",
|
||||
"factory.name",
|
||||
"node.driver",
|
||||
"node.pause-on-idle",
|
||||
"node.want-driver",
|
||||
"port.group",
|
||||
"priority.driver",
|
||||
"resample.disable",
|
||||
"resample.prefill",
|
||||
}
|
||||
local args
|
||||
local props = {}
|
||||
|
||||
props["node.virtual"] = false
|
||||
|
||||
for k, v in pairs(properties) do
|
||||
props[k] = v
|
||||
end
|
||||
for _, k in pairs(skip_keys) do
|
||||
props[k] = nil
|
||||
end
|
||||
|
||||
local split_props = {
|
||||
["node.name"] = properties["node.name"] .. ".split",
|
||||
["node.description"] = string.format(I18n.gettext("Split %s"), properties["node.description"]),
|
||||
["audio.position"] = properties["api.alsa.split.position"],
|
||||
["stream.dont-remix"] = true,
|
||||
["node.passive"] = true,
|
||||
["node.dont-fallback"] = true,
|
||||
["node.linger"] = true,
|
||||
["target.object"] = properties["api.alsa.split.name"],
|
||||
}
|
||||
|
||||
if properties["api.alsa.pcm.stream"] == "playback" then
|
||||
props["media.class"] = "Audio/Sink"
|
||||
split_props["media.class"] = "Stream/Output/Audio/Internal"
|
||||
args = Json.Object {
|
||||
["capture.props"] = Json.Object (props),
|
||||
["playback.props"] = Json.Object (split_props),
|
||||
}
|
||||
else
|
||||
props["media.class"] = "Audio/Source"
|
||||
split_props["media.class"] = "Stream/Input/Audio/Internal"
|
||||
args = Json.Object {
|
||||
["playback.props"] = Json.Object (props),
|
||||
["capture.props"] = Json.Object (split_props),
|
||||
}
|
||||
end
|
||||
|
||||
return LocalModule("libpipewire-module-loopback", args:get_data(), {})
|
||||
end
|
||||
|
||||
devices_om = ObjectManager {
|
||||
Interest {
|
||||
type = "device",
|
||||
}
|
||||
}
|
||||
|
||||
split_nodes_om = ObjectManager {
|
||||
Interest {
|
||||
type = "node",
|
||||
Constraint { "api.alsa.split.position", "+", type = "pw" },
|
||||
}
|
||||
}
|
||||
|
||||
split_nodes_om:connect ("object-added", function(_, node)
|
||||
-- Connect ObjectConfig events to the right node
|
||||
if not monitor then
|
||||
return
|
||||
end
|
||||
|
||||
local interest = Interest {
|
||||
type = "device",
|
||||
Constraint { "object.id", "=", node.properties["device.id"] }
|
||||
}
|
||||
log:info("Split PCM node found: " .. tostring (node["bound-id"]))
|
||||
|
||||
for device in devices_om:iterate (interest) do
|
||||
local device_id = device.properties["spa.object.id"]
|
||||
if not device_id then
|
||||
goto next_device
|
||||
end
|
||||
|
||||
local spa_device = monitor:get_managed_object (tonumber (device_id))
|
||||
if not spa_device then
|
||||
goto next_device
|
||||
end
|
||||
|
||||
local id = node.properties["card.profile.device"]
|
||||
if id ~= nil then
|
||||
log:info(".. assign to device: " .. tostring (device["bound-id"]) .. " node " .. tostring (id))
|
||||
spa_device:store_managed_object (id, node)
|
||||
end
|
||||
|
||||
::next_device::
|
||||
end
|
||||
end)
|
||||
|
||||
function createNode(parent, id, obj_type, factory, properties)
|
||||
local dev_props = parent.properties
|
||||
local parent_id = tonumber(dev_props["spa.object.id"])
|
||||
|
|
@ -161,6 +306,10 @@ function createNode(parent, id, obj_type, factory, properties)
|
|||
end
|
||||
|
||||
-- apply properties from rules defined in JSON .conf file
|
||||
local orig_properties = {}
|
||||
for k, v in pairs(properties) do
|
||||
orig_properties[k] = v
|
||||
end
|
||||
properties = JsonUtils.match_rules_update_properties (config.rules, properties)
|
||||
|
||||
if cutils.parseBool (properties ["node.disabled"]) then
|
||||
|
|
@ -171,6 +320,36 @@ function createNode(parent, id, obj_type, factory, properties)
|
|||
node_names_table[properties["node.name"]] = true
|
||||
id_name_table[parent_id][id] = properties["node.name"]
|
||||
|
||||
-- handle split HW node
|
||||
if properties["api.alsa.split.position"] ~= nil then
|
||||
local split_hw_node_name = string.format("%s.%s",
|
||||
(stream == "capture" and "alsa_input" or "alsa_output"),
|
||||
properties["api.alsa.path"]:gsub("([:,])", "_"))
|
||||
properties["api.alsa.split.name"] = split_hw_node_name
|
||||
orig_properties["api.alsa.split.name"] = split_hw_node_name
|
||||
|
||||
if not node_names_table [split_hw_node_name] then
|
||||
log:info ("Create ALSA SplitPCM HW node " .. split_hw_node_name)
|
||||
|
||||
local node = createSplitPCMHWNode(dev_props, orig_properties)
|
||||
if node ~= nil then
|
||||
node:activate(Feature.Proxy.BOUND)
|
||||
parent:store_managed_object(SPLIT_PCM_PARENT_OFFSET + id, node)
|
||||
|
||||
node_names_table[split_hw_node_name] = true
|
||||
id_name_table[parent_id][SPLIT_PCM_PARENT_OFFSET + id] = split_hw_node_name
|
||||
end
|
||||
end
|
||||
|
||||
-- create split PCM node
|
||||
log:info ("Create ALSA SplitPCM split node " .. properties["node.name"])
|
||||
|
||||
local loopback = createSplitPCMLoopback (parent, id, obj_type, factory, properties)
|
||||
parent:store_managed_object(SPLIT_PCM_OFFSET + id, loopback)
|
||||
parent:set_managed_pending(id)
|
||||
return
|
||||
end
|
||||
|
||||
-- create the node
|
||||
local node = Node("adapter", properties)
|
||||
node:activate(Feature.Proxy.BOUND, function (_, err)
|
||||
|
|
@ -184,12 +363,18 @@ end
|
|||
|
||||
function removeNode(parent, id)
|
||||
local parent_id = tonumber(parent.properties["spa.object.id"])
|
||||
local node_name = id_name_table[parent_id][id]
|
||||
local ids = {id, SPLIT_PCM_PARENT_OFFSET + id, SPLIT_PCM_OFFSET + id}
|
||||
|
||||
if node_name ~= nil then
|
||||
log:info ("Removing node " .. node_name)
|
||||
node_names_table[node_name] = nil
|
||||
id_name_table[parent_id][id] = nil
|
||||
for _, j in pairs(ids) do
|
||||
local node_name = id_name_table[parent_id][j]
|
||||
|
||||
parent:store_managed_object(j, nil)
|
||||
|
||||
if node_name ~= nil then
|
||||
log:info ("Removing node " .. node_name)
|
||||
node_names_table[node_name] = nil
|
||||
id_name_table[parent_id][j] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -421,3 +606,6 @@ end
|
|||
|
||||
-- create the monitor
|
||||
monitor = createMonitor()
|
||||
|
||||
devices_om:activate()
|
||||
split_nodes_om:activate()
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue