monitors/camera: fix camera device deduplication not working for certain devices.

Enhance the parsing logic to consider more than one device number. libcamera
devices some times use up multiple V4L2 devices, in this case libcamera should
be given a chance to enumerate the device.

Fixes #623
This commit is contained in:
Ashok Sidipotu
2024-04-08 13:27:28 +05:30
committed by George Kiagiadakis
parent ae983e6fd7
commit 06e11dc4be

View File

@@ -25,36 +25,92 @@ function mutils.find_duplicate (parent, id, property, value)
return false return false
end end
function mutils.get_cam_data (self, dev_string) function get_cam_data(self, dev_id)
local dev_num = tonumber (dev_string) if not self.cam_data[dev_id] then
if not dev_num then self.cam_data[dev_id] = {}
return self.cam_data[dev_id]["libcamera"] = {}
self.cam_data[dev_id]["v4l2"] = {}
end end
return self.cam_data[dev_id]
if not self.cam_data[dev_num] then
self.cam_data[dev_num] = {}
self.cam_data[dev_num]["libcamera"] = {}
self.cam_data[dev_num]["v4l2"] = {}
end
return self.cam_data[dev_num], dev_num
end end
function mutils.clear_cam_data (self, dev_string) function parse_devids_get_cam_data(self, devids)
local dev_num = tonumber (dev_string) local dev_ids_json = Json.Raw(devids)
local dev_ids_table = {}
if dev_ids_json:is_array() then
dev_ids_table = dev_ids_json:parse()
else
-- to maintain the backward compatibility with earlier pipewire versions.
for dev_id_str in devids:gmatch("%S+") do
local dev_id = tonumber(dev_id_str)
if dev_id then
table.insert(dev_ids_table, dev_id)
end
end
end
local dev_num = nil
-- `device.devids` is a json array of device numbers
for _, dev_id_str in ipairs(dev_ids_table) do
local dev_id = tonumber(dev_id_str)
if not dev_id then
log:notice ("invalid device number")
return
end
log:debug ("Working on device " .. dev_id)
local dev_cam_data = get_cam_data (self, dev_id)
if not dev_num then
dev_num = dev_id
if #dev_ids_table > 1 then
-- libcam node can some times use more tha one V4L2 devices, in this
-- case, return the first device id and mark rest of the them as peers
-- to the first one.
log:debug ("Device " .. dev_id .. " uses multi V4L2 devices")
dev_cam_data.uses_multi_v4l2_devices = true
end
else
log:debug ("Device " .. dev_id .. " is peer to " .. dev_num)
dev_cam_data.peer_id = dev_num
end
end
if dev_num then
return self.cam_data[dev_num], dev_num
end
end
function mutils.clear_cam_data (self, dev_num)
local dev_cam_data = self.cam_data[dev_num]
if not dev_num then if not dev_num then
return return
end end
if dev_cam_data.uses_multi_v4l2_devices then
for dev_id, cam_data_ in pairs(self.cam_data) do
if cam_data_.peer_id == dev_num then
log:debug("clear " .. dev_id .. " it is peer to " .. dev_num)
self.cam_data[dev_id] = nil
end
end
end
self.cam_data[dev_num] = nil self.cam_data[dev_num] = nil
end end
function mutils.create_cam_node (self, dev_num) function mutils.create_cam_node(self, dev_num)
local api = nil local api = nil
local cam_data = self:get_cam_data (dev_num) local cam_data = get_cam_data (self, dev_num)
if cam_data["v4l2"].enum_status and cam_data["libcamera"].enum_status then if cam_data["v4l2"].enum_status and cam_data["libcamera"].enum_status then
if cam_data.is_device_uvc then if cam_data.uses_multi_v4l2_devices then
api = "libcamera"
elseif cam_data.peer_id ~= nil then
-- no need to create node for peer
log:notice ("timer expired for peer device " .. dev_num)
return
elseif cam_data.is_device_uvc then
api = "v4l2" api = "v4l2"
else else
api = "libcamera" api = "libcamera"
@@ -63,8 +119,8 @@ function mutils.create_cam_node (self, dev_num)
api = cam_data["v4l2"].enum_status and "v4l2" or "libcamera" api = cam_data["v4l2"].enum_status and "v4l2" or "libcamera"
end end
log:info (string.format ("create \"%s\" node for device:%s%s", api, log:info (string.format ("create \"%s\" node for device:%s", api,
cam_data.dev_path, (cam_data.is_device_uvc and "(uvc)" or ""))) cam_data.dev_path))
source = source or Plugin.find ("standard-event-source") source = source or Plugin.find ("standard-event-source")
local e = source:call ("create-event", "create-" .. api .. "-device-node", local e = source:call ("create-event", "create-" .. api .. "-device-node",
@@ -82,17 +138,21 @@ end
-- for a device, logic is based on the device number of the device given by both -- for a device, logic is based on the device number of the device given by both
-- the parties. -- the parties.
function mutils.register_cam_node (self, parent, id, factory, properties) function mutils.register_cam_node (self, parent, id, factory, properties)
local cam_data, dev_num = self:get_cam_data (properties["device.devids"])
local api = properties["device.api"] local api = properties["device.api"]
local dev_ids = properties["device.devids"]
log:debug(api .. " reported " .. dev_ids)
local cam_data, dev_num = parse_devids_get_cam_data(self, dev_ids)
if not cam_data then if not cam_data then
log:notice (string.format ("device number invalid for %s device:%s", log:notice (string.format ("device numbers invalid for %s device:%s",
api, properties["device.name"])) api, properties["device.name"]))
return false return false
end end
-- only v4l2 can give this info -- only v4l2 can give this info
if properties["api.v4l2.cap.driver"] == "uvcvideo" then if properties["api.v4l2.cap.driver"] == "uvcvideo" then
log:debug ("Device " .. dev_num .. " is a UVC device")
cam_data.is_device_uvc = true cam_data.is_device_uvc = true
end end
@@ -107,6 +167,7 @@ function mutils.register_cam_node (self, parent, id, factory, properties)
-- cache info, it comes handy when creating node -- cache info, it comes handy when creating node
cam_api_data.parent = parent cam_api_data.parent = parent
cam_api_data.id = id cam_api_data.id = id
cam_api_data.name = properties["device.name"]
cam_api_data.factory = factory cam_api_data.factory = factory
cam_api_data.properties = properties cam_api_data.properties = properties
@@ -114,11 +175,11 @@ function mutils.register_cam_node (self, parent, id, factory, properties)
if cam_api_data.enum_status and not cam_data[other_api].enum_status then if cam_api_data.enum_status and not cam_data[other_api].enum_status then
log:trace (string.format ("\"%s\" armed a timer for %d", api, dev_num)) log:trace (string.format ("\"%s\" armed a timer for %d", api, dev_num))
cam_data.source = Core.timeout_add ( cam_data.source = Core.timeout_add (
Settings.get_int ("monitor.camera-discovery-timeout"), function() Settings.get_int ("monitor.camera-discovery-timeout"), function()
log:trace (string.format ("\"%s\" armed timer expired for %d", api, dev_num)) log:trace (string.format ("\"%s\" armed timer expired for %d", api, dev_num))
self:create_cam_node (dev_num) self:create_cam_node (dev_num)
cam_data.source = nil cam_data.source = nil
end) end)
elseif cam_data.source then elseif cam_data.source then
log:trace (string.format ("\"%s\" disarmed timer for %d", api, dev_num)) log:trace (string.format ("\"%s\" disarmed timer for %d", api, dev_num))
cam_data.source:destroy () cam_data.source:destroy ()