mirror of
https://gitlab.freedesktop.org/pipewire/wireplumber.git
synced 2026-02-04 01:30:26 +01:00
monitors/bluez: Avoid recreating A2DP/SCO nodes if loopback is emitted late
Destroying and re-creating the internal A2DP/SCO nodes seem to confuse some applications when the loopback nodes are emitted too late. Instead of doing this, these changes delay the creation of the internal BT nodes until the loopback nodes are fully created, avoiding uneccesary destruction and creation of nodes.
This commit is contained in:
parent
80842cbb96
commit
ccfc6abf65
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