From 06e11dc4beb23a90f8bda0dba4d836288a78ed00 Mon Sep 17 00:00:00 2001 From: Ashok Sidipotu Date: Mon, 8 Apr 2024 13:27:28 +0530 Subject: [PATCH] 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 --- src/scripts/lib/monitor-utils.lua | 113 +++++++++++++++++++++++------- 1 file changed, 87 insertions(+), 26 deletions(-) diff --git a/src/scripts/lib/monitor-utils.lua b/src/scripts/lib/monitor-utils.lua index c23ec1cd..3503b1c7 100644 --- a/src/scripts/lib/monitor-utils.lua +++ b/src/scripts/lib/monitor-utils.lua @@ -25,36 +25,92 @@ function mutils.find_duplicate (parent, id, property, value) return false end -function mutils.get_cam_data (self, dev_string) - local dev_num = tonumber (dev_string) - if not dev_num then - return +function get_cam_data(self, dev_id) + if not self.cam_data[dev_id] then + self.cam_data[dev_id] = {} + self.cam_data[dev_id]["libcamera"] = {} + self.cam_data[dev_id]["v4l2"] = {} end - - 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 + return self.cam_data[dev_id] end -function mutils.clear_cam_data (self, dev_string) - local dev_num = tonumber (dev_string) +function parse_devids_get_cam_data(self, devids) + 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 return 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 end -function mutils.create_cam_node (self, dev_num) +function mutils.create_cam_node(self, dev_num) 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.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" else api = "libcamera" @@ -63,8 +119,8 @@ function mutils.create_cam_node (self, dev_num) api = cam_data["v4l2"].enum_status and "v4l2" or "libcamera" end - log:info (string.format ("create \"%s\" node for device:%s%s", api, - cam_data.dev_path, (cam_data.is_device_uvc and "(uvc)" or ""))) + log:info (string.format ("create \"%s\" node for device:%s", api, + cam_data.dev_path)) source = source or Plugin.find ("standard-event-source") 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 -- the parties. 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 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 - 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"])) return false end -- only v4l2 can give this info if properties["api.v4l2.cap.driver"] == "uvcvideo" then + log:debug ("Device " .. dev_num .. " is a UVC device") cam_data.is_device_uvc = true end @@ -107,6 +167,7 @@ function mutils.register_cam_node (self, parent, id, factory, properties) -- cache info, it comes handy when creating node cam_api_data.parent = parent cam_api_data.id = id + cam_api_data.name = properties["device.name"] cam_api_data.factory = factory 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 log:trace (string.format ("\"%s\" armed a timer for %d", api, dev_num)) cam_data.source = Core.timeout_add ( - Settings.get_int ("monitor.camera-discovery-timeout"), function() - log:trace (string.format ("\"%s\" armed timer expired for %d", api, dev_num)) - self:create_cam_node (dev_num) - cam_data.source = nil - end) + Settings.get_int ("monitor.camera-discovery-timeout"), function() + log:trace (string.format ("\"%s\" armed timer expired for %d", api, dev_num)) + self:create_cam_node (dev_num) + cam_data.source = nil + end) elseif cam_data.source then log:trace (string.format ("\"%s\" disarmed timer for %d", api, dev_num)) cam_data.source:destroy ()