mirror of
https://gitlab.freedesktop.org/pipewire/wireplumber.git
synced 2026-05-05 00:48:01 +02:00
Merge branch 'bluez-monitor-improvements' into 'master'
monitors/bluez: Avoid recreating A2DP/SCO nodes if loopback is emitted late See merge request pipewire/wireplumber!778
This commit is contained in:
commit
20bda1d76c
1 changed files with 154 additions and 79 deletions
|
|
@ -22,12 +22,14 @@ config.rules = Conf.get_section_as_json ("monitor.bluez.rules", Json.Array {})
|
|||
-- This is not a setting, it must always be enabled
|
||||
config.properties["api.bluez5.connection-info"] = true
|
||||
|
||||
-- Properties used for previously creating a SCO source node. key: SPA device id
|
||||
-- Properties used for creating delayed SCO/A2DP nodes. key: SPA device id
|
||||
sco_source_node_properties = {}
|
||||
|
||||
-- Properties used for previously creating a SCO or A2DP sink node. key: SPA device id
|
||||
sco_a2dp_sink_node_properties = {}
|
||||
|
||||
-- Boolean used know if loopbacks are ready or not. key: SPA device id
|
||||
source_loopback_ready = {}
|
||||
sink_loopback_ready = {}
|
||||
|
||||
devices_om = ObjectManager {
|
||||
Interest {
|
||||
type = "device",
|
||||
|
|
@ -265,26 +267,6 @@ function createNode(parent, id, type, factory, properties)
|
|||
local name_prefix = ((factory:find("sink") and "bluez_output") or
|
||||
(factory:find("source") and "bluez_input" or factory))
|
||||
|
||||
-- hide the source node because we use the loopback source instead
|
||||
if parent:get_managed_object (LOOPBACK_SOURCE_ID) ~= nil and
|
||||
(factory == "api.bluez5.sco.source" or
|
||||
(factory == "api.bluez5.a2dp.source" and cutils.parseBool (properties["api.bluez5.a2dp-duplex"]))) then
|
||||
properties["bluez5.loopback-target"] = true
|
||||
properties["api.bluez5.internal"] = true
|
||||
-- add 'internal' to name prefix to not be confused with loopback node
|
||||
name_prefix = name_prefix .. "_internal"
|
||||
end
|
||||
|
||||
-- hide the sink node because we use the loopback sink instead
|
||||
if parent:get_managed_object (LOOPBACK_SINK_ID) ~= nil and
|
||||
(factory == "api.bluez5.sco.sink" or
|
||||
factory == "api.bluez5.a2dp.sink") then
|
||||
properties["bluez5.sink-loopback-target"] = true
|
||||
properties["api.bluez5.internal"] = true
|
||||
-- add 'internal' to name prefix to not be confused with loopback node
|
||||
name_prefix = name_prefix .. "_internal"
|
||||
end
|
||||
|
||||
-- set the node name
|
||||
local name = name_prefix .. "." ..
|
||||
(properties["api.bluez5.address"] or dev_props["device.name"]) .. "." ..
|
||||
|
|
@ -317,20 +299,153 @@ function createNode(parent, id, type, factory, properties)
|
|||
parent:store_managed_object(id + COMBINE_OFFSET, combine)
|
||||
parent:set_managed_pending(id)
|
||||
else
|
||||
log:info("Create node: " .. properties["node.name"] .. ": " .. factory .. " " .. tostring (id))
|
||||
if factory == "api.bluez5.sco.source" then
|
||||
properties["bluez5.loopback"] = false
|
||||
sco_source_node_properties[parent_spa_id] = properties
|
||||
elseif factory == "api.bluez5.sco.sink" or factory == "api.bluez5.a2dp.sink" then
|
||||
properties["bluez5.sink-loopback"] = false
|
||||
sco_a2dp_sink_node_properties[parent_spa_id] = properties
|
||||
local create_node = true
|
||||
|
||||
-- If autoswitch is enabled, add the loopback properties and check if
|
||||
-- we can create the node right away or not
|
||||
if Settings.get_boolean ("bluetooth.autoswitch-to-headset-profile") then
|
||||
-- Delay node creation until the associated loopback node is created
|
||||
if factory == "api.bluez5.sco.source" or (factory == "api.bluez5.a2dp.source"
|
||||
and cutils.parseBool (properties["api.bluez5.a2dp-duplex"])) then
|
||||
properties["bluez5.loopback-target"] = true
|
||||
properties["api.bluez5.internal"] = true
|
||||
properties["bluez5.loopback"] = false
|
||||
|
||||
-- Delay node creation until the associated loopback node is created
|
||||
if source_loopback_ready [parent_spa_id] == nil or
|
||||
not source_loopback_ready [parent_spa_id] then
|
||||
sco_source_node_properties[parent_spa_id] = properties
|
||||
create_node = false
|
||||
end
|
||||
elseif factory == "api.bluez5.sco.sink" or factory == "api.bluez5.a2dp.sink" then
|
||||
properties["bluez5.sink-loopback-target"] = true
|
||||
properties["api.bluez5.internal"] = true
|
||||
properties["bluez5.sink-loopback"] = false
|
||||
|
||||
-- Delay node creation until the associated loopback node is created
|
||||
if sink_loopback_ready [parent_spa_id] == nil or
|
||||
not sink_loopback_ready [parent_spa_id] then
|
||||
sco_a2dp_sink_node_properties[parent_spa_id] = properties
|
||||
create_node = false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Create the node only if autoswitch is disable or the associated loopback
|
||||
-- node is ready. Otherwise, the node creation will be delayed until the
|
||||
-- associated loopback is ready
|
||||
if create_node then
|
||||
log:info("Create node: " .. properties["node.name"] .. ": " ..
|
||||
factory .. " " .. tostring (id))
|
||||
local node = LocalNode("adapter", properties)
|
||||
node:activate(Feature.Proxy.BOUND)
|
||||
parent:store_managed_object(id, node)
|
||||
else
|
||||
log:info("Delay node: " .. properties["node.name"] .. ": " ..
|
||||
factory .. " " .. tostring (id))
|
||||
end
|
||||
local node = LocalNode("adapter", properties)
|
||||
node:activate(Feature.Proxy.BOUND)
|
||||
parent:store_managed_object(id, node)
|
||||
end
|
||||
end
|
||||
|
||||
SimpleEventHook {
|
||||
name = "monitors/bluez/create-internal-source-node",
|
||||
interests = {
|
||||
EventInterest {
|
||||
Constraint { "event.type", "=", "node-added" },
|
||||
Constraint { "media.class", "=", "Audio/Source" },
|
||||
Constraint { "node.link-group", "+", type = "pw" },
|
||||
Constraint { "bluez5.loopback", "=", "true", type = "pw" },
|
||||
},
|
||||
},
|
||||
execute = function (event)
|
||||
local source = event:get_source ()
|
||||
local device_om = source:call ("get-object-manager", "device")
|
||||
local node = event:get_subject ()
|
||||
local dev_id = node.properties:get_int ("device.id")
|
||||
|
||||
-- Get the associated device
|
||||
local device = device_om:lookup {
|
||||
Constraint { "bound-id", "=", dev_id, type = "gobject" }
|
||||
}
|
||||
if device == nil then
|
||||
log:info ("Could not find device for source loopback node")
|
||||
return
|
||||
end
|
||||
|
||||
-- Get the associated spa device
|
||||
local device_spa_id = device.properties:get_int ("spa.object.id")
|
||||
local spa_device = monitor:get_managed_object (device_spa_id)
|
||||
if spa_device == nil then
|
||||
log:info ("Could not find SPA device for source loopback node")
|
||||
return
|
||||
end
|
||||
|
||||
-- Mark source loopback as ready
|
||||
source_loopback_ready [device_spa_id] = true
|
||||
|
||||
-- Create the internal node
|
||||
local properties = sco_source_node_properties[device_spa_id]
|
||||
if properties ~= nil then
|
||||
local node_id = tonumber(properties["spa.object.id"])
|
||||
|
||||
log:info("Create node: " .. properties["node.name"] .. ": " ..
|
||||
properties["factory.name"] .. " " .. tostring (node_id))
|
||||
node = LocalNode("adapter", properties)
|
||||
node:activate(Feature.Proxy.BOUND)
|
||||
spa_device:store_managed_object(node_id, node)
|
||||
end
|
||||
end
|
||||
}:register()
|
||||
|
||||
SimpleEventHook {
|
||||
name = "monitors/bluez/create-internal-sink-node",
|
||||
interests = {
|
||||
EventInterest {
|
||||
Constraint { "event.type", "=", "node-added" },
|
||||
Constraint { "media.class", "=", "Audio/Sink" },
|
||||
Constraint { "node.link-group", "+", type = "pw" },
|
||||
Constraint { "bluez5.sink-loopback", "=", "true", type = "pw" },
|
||||
},
|
||||
},
|
||||
execute = function (event)
|
||||
local source = event:get_source ()
|
||||
local device_om = source:call ("get-object-manager", "device")
|
||||
local node = event:get_subject ()
|
||||
local dev_id = node.properties:get_int ("device.id")
|
||||
|
||||
-- Get the associated device
|
||||
local device = device_om:lookup {
|
||||
Constraint { "bound-id", "=", dev_id, type = "gobject" }
|
||||
}
|
||||
if device == nil then
|
||||
log:info ("Could not find device for sink loopback node")
|
||||
return
|
||||
end
|
||||
|
||||
-- Get the associated spa device
|
||||
local device_spa_id = device.properties:get_int ("spa.object.id")
|
||||
local spa_device = monitor:get_managed_object (device_spa_id)
|
||||
if spa_device == nil then
|
||||
log:info ("Could not find SPA device for sink loopback node")
|
||||
return
|
||||
end
|
||||
|
||||
-- Mark sink loopback as ready
|
||||
sink_loopback_ready [device_spa_id] = true
|
||||
|
||||
-- create the internal node
|
||||
local properties = sco_a2dp_sink_node_properties[device_spa_id]
|
||||
if properties ~= nil then
|
||||
local node_id = tonumber(properties["spa.object.id"])
|
||||
log:info("Create node: " .. properties["node.name"] .. ": " ..
|
||||
properties["factory.name"] .. " " .. tostring (node_id))
|
||||
node = LocalNode("adapter", properties)
|
||||
node:activate(Feature.Proxy.BOUND)
|
||||
spa_device:store_managed_object(node_id, node)
|
||||
end
|
||||
end
|
||||
}:register()
|
||||
|
||||
function removeNode(parent, id)
|
||||
local dev_props = parent.properties
|
||||
local parent_spa_id = tonumber(dev_props["spa.object.id"])
|
||||
|
|
@ -424,6 +539,8 @@ end
|
|||
function removeDevice(parent, id)
|
||||
sco_source_node_properties[id] = nil
|
||||
sco_a2dp_sink_node_properties[id] = nil
|
||||
source_loopback_ready[id] = nil
|
||||
sink_loopback_ready[id] = nil
|
||||
end
|
||||
|
||||
function createMonitor()
|
||||
|
|
@ -525,13 +642,12 @@ function checkProfiles (dev)
|
|||
end
|
||||
|
||||
-- Get the associated BT SpaDevice
|
||||
local internal_id = tostring (props["spa.object.id"])
|
||||
local spa_device = monitor:get_managed_object (internal_id)
|
||||
local spa_device = monitor:get_managed_object (device_spa_id)
|
||||
if spa_device == nil then
|
||||
return
|
||||
end
|
||||
|
||||
-- Ignore devices that don't support both A2DP sink and HSP/HFP profiles
|
||||
-- Check if the device supports A2DP and HFP/HSP profiles
|
||||
local has_a2dpsink_profile = false
|
||||
local has_headset_profile = false
|
||||
for p in dev:iterate_params("EnumProfile") do
|
||||
|
|
@ -542,9 +658,6 @@ function checkProfiles (dev)
|
|||
has_headset_profile = true
|
||||
end
|
||||
end
|
||||
if not has_a2dpsink_profile or not has_headset_profile then
|
||||
return
|
||||
end
|
||||
|
||||
-- Setup Route/Port correctly for loopback nodes
|
||||
local param = Pod.Object ({
|
||||
|
|
@ -556,7 +669,7 @@ function checkProfiles (dev)
|
|||
|
||||
-- Create the source loopback device if never created before
|
||||
local source_loopback = spa_device:get_managed_object (LOOPBACK_SOURCE_ID)
|
||||
if source_loopback == nil then
|
||||
if source_loopback == nil and has_headset_profile then
|
||||
local dev_name = props["api.bluez5.address"] or props["device.name"]
|
||||
local dec_desc = props["device.description"] or props["device.name"]
|
||||
or props["device.nick"] or props["device.alias"] or "bluetooth-device"
|
||||
|
|
@ -567,29 +680,10 @@ function checkProfiles (dev)
|
|||
dec_desc = dec_desc:gsub("(:)", " ")
|
||||
source_loopback = CreateDeviceLoopbackSource (dev_name, dec_desc, device_id)
|
||||
spa_device:store_managed_object(LOOPBACK_SOURCE_ID, source_loopback)
|
||||
|
||||
-- recreate any sco source node
|
||||
local properties = sco_source_node_properties[device_spa_id]
|
||||
if properties ~= nil then
|
||||
local node_id = tonumber(properties["spa.object.id"])
|
||||
local node = spa_device:get_managed_object (node_id)
|
||||
if node ~= nil then
|
||||
log:info("Recreate node: " .. properties["node.name"] .. ": " ..
|
||||
properties["factory.name"] .. " " .. tostring (node_id))
|
||||
|
||||
spa_device:store_managed_object(node_id, nil)
|
||||
|
||||
properties["bluez5.loopback-target"] = true
|
||||
properties["api.bluez5.internal"] = true
|
||||
node = LocalNode("adapter", properties)
|
||||
node:activate(Feature.Proxy.BOUND)
|
||||
spa_device:store_managed_object(node_id, node)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local sink_loopback = spa_device:get_managed_object (LOOPBACK_SINK_ID)
|
||||
if sink_loopback == nil then
|
||||
if sink_loopback == nil and (has_a2dpsink_profile or has_headset_profile) then
|
||||
local dev_name = props["api.bluez5.address"] or props["device.name"]
|
||||
local dec_desc = props["device.description"] or props["device.name"]
|
||||
or props["device.nick"] or props["device.alias"] or "bluetooth-device"
|
||||
|
|
@ -600,25 +694,6 @@ function checkProfiles (dev)
|
|||
dec_desc = dec_desc:gsub("(:)", " ")
|
||||
sink_loopback = CreateDeviceLoopbackSink (dev_name, dec_desc, device_id)
|
||||
spa_device:store_managed_object(LOOPBACK_SINK_ID, sink_loopback)
|
||||
|
||||
-- recreate any sco-a2dp sink node
|
||||
local properties = sco_a2dp_sink_node_properties[device_spa_id]
|
||||
if properties ~= nil then
|
||||
local node_id = tonumber(properties["spa.object.id"])
|
||||
local node = spa_device:get_managed_object (node_id)
|
||||
if node ~= nil then
|
||||
log:info("Recreate node: " .. properties["node.name"] .. ": " ..
|
||||
properties["factory.name"] .. " " .. tostring (node_id))
|
||||
|
||||
spa_device:store_managed_object(node_id, nil)
|
||||
|
||||
properties["bluez5.sink-loopback-target"] = true
|
||||
properties["api.bluez5.internal"] = true
|
||||
node = LocalNode("adapter", properties)
|
||||
node:activate(Feature.Proxy.BOUND)
|
||||
spa_device:store_managed_object(node_id, node)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue