From patchwork Fri Sep 18 07:59:57 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Naushir Patuck X-Patchwork-Id: 28331 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 5F2C7C3360 for ; Fri, 18 Sep 2026 08:08:11 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id C18336873E; Fri, 18 Sep 2026 10:08:10 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (2048-bit key; unprotected) header.d=raspberrypi.com header.i=@raspberrypi.com header.b="sS/+Bn+y"; dkim-atps=neutral Received: from mail-wr2-x10.google.com (mail-wr2-x10.google.com [IPv6:2a00:1450:4864:30::10]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 4A6A568721 for ; Fri, 18 Sep 2026 10:07:57 +0200 (CEST) Received: by mail-wr2-x10.google.com with SMTP id ffacd0b85a97d-4843971bdd0so58228f8f.1 for ; Fri, 18 Sep 2026 01:07:57 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=raspberrypi.com; s=google; t=1789718877; x=1790323677; darn=lists.libcamera.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=ykexR92vSEb9rcyPQHfJWlqmYtXhEco9jPeZViYeKIs=; b=sS/+Bn+yLvA2NjnNbbEO6eW2K6kq6uyymFYrD2la9dt1ugz4lTGM1S7NNVHkVvl6SY 6pdRsoR7QIlcBOmlao6iNVv5/QrTG4lfODdUFCBlABxD3D3k3yFeJml1P4vUln+OaG3G 2mjwABK36L7r4XRCyxR6dpPRpXRyVm6eOHgoLJqXGV045noA14k2Lcxa/ISaM9DiLyCp n4fVmF64wDSlf7QqrRhL3I0V+ViOR6VUWNN3Kf0DiR7IvizuX8Bhh0odRW6ALM0ELp3c Ds9BLZuWrCs4HIiyyPRbxbxnmXn/kt082wDUbjhjIQueFHdMyrEUEGj/CpHAtjTF8tiL bNSg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1789718877; x=1790323677; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=ykexR92vSEb9rcyPQHfJWlqmYtXhEco9jPeZViYeKIs=; b=cCWpaMfwLb6qXWreJu3yQhgvhiZ+iFeBb55Eou/R5kDjc2wW/nahbkIMZOjfE5v7LJ CBZaN8fOrw+yRI70go/QZTc01nFQNbnYgtIiDvME+l5gw6pWKjDLK9WjHQ27Itj9Px4H zlvDPw8Mdw6Qg4DtyeGXJKFvz80aACdnmdWAIdsI7f3V0pgDFnHAV4rwzodo+1vIU8Fm W1U+RpiI+SYXO7CEnry+qv2L8rpwK1QH7I6FinQcPObUFX0nMuotd0/I3BB3ZSBjwz8g dp10geVynBg567xaYhmjX2U18jMNNvENJDh/FovU398uQ8qmrdr0ngSo8gF1w5Rv9Rjm 30aw== X-Gm-Message-State: AFuF++nVw2p0hZGvIWXF4brn7OqCv87U+K3J4ZfDcgOYHXf8eSW5XauA DShM9V3e1q6XlkwdLArOVK4W6znF/tF8lQcEJWfUJlD9jMuqAY8JHEDOe8tmtN6jEUWpZlz71nQ WsKzwddk= X-Gm-Gg: AYBFou3me8cPxeDjUNi94I5IHzLmzzAAsdnneZVKssFDUhZc6fACBcGB4+WfyFFpmk2 uQEm3NCY+Igx1t9m5NObZKRew1r1RwiGNqkOlO/gUaJL4pDNqVA8p7maCpiumKllwMA5CoAc9xR dNp/tYLwHWI6ur9GI5TbWRDhB3wbYhS/OyJh4qIdpVM1SFrKLkXQ2aV8FFhgLh009sMV13kkQCH A4fpMOedd8C3H+kLmP+0SNV9kmEcrzg77rfM+5VYDik5TAiphfYWj3X/P0Fjt/R7Uzpvqs8S0SL obNsBdCvj8OAusiDq9Ag/MozhxD50asNJc02PpXRJfSw273JtQUegBKKKTSH78K7QN+AXfoYWPw 6UKzg8gRDFC1jpd8Q4J3JxwMYNDzLLP7pjhgA7iaaw1tfQ+Q1T8hOrol8sNwP00G01pg+P9zrXt ri0e+NIX12jMJoI/nwYSF+k9Ktcv5BkM3F3GBs3eFBq6R82XWMzq4Ll8YSOTvaU3EGZ6+w6VDqC 1Pq3/efUDeEPDnraPkteioY7zuYTIv/wwqQWR5dTtHnoFXJeu1wRZy2fdxubEbWgJRJt1cpl9Ja q7C69giYWBugMAtG/cjTz5BWRQmAUxMOeqOAwCWfLIJexmdR8E9MKjqrHZEiIG3jQ7ddCAVeVM1 g4rxUvIlNhMUHslERGYpNUM8RXPYxc6Aiu4wVpRTqPen/cA== X-Received: by 2002:a05:6000:41d8:b0:487:1251:20fb with SMTP id ffacd0b85a97d-4871fa333bbmr1126138f8f.2.1789718876671; Fri, 18 Sep 2026 01:07:56 -0700 (PDT) Received: from naush-dell.pitowers.org ([2a00:1098:3142:1f:45e3:df8b:2b18:6253]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-487203f077fsm1742230f8f.26.2026.09.18.01.07.56 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 18 Sep 2026 01:07:56 -0700 (PDT) From: Naushir Patuck To: libcamera-devel@lists.libcamera.org Cc: Naushir Patuck Subject: [RFC PATCH v1 19/20] pipeline: uvcvideo: Implement survey() and createCamera() Date: Fri, 18 Sep 2026 08:59:57 +0100 Message-ID: <20260918080734.1228227-20-naush@raspberrypi.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260918080734.1228227-1-naush@raspberrypi.com> References: <20260918080734.1228227-1-naush@raspberrypi.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Report a descriptor for every UVC camera in the system, identified by the default video node of its media entity. The camera ID is generated from the sysfs path derived from the video device numbers and the model from the media device model, so no device is acquired or opened while surveying. createCamera() acquires the media device of the described camera and creates it through the camera creation code previously in match(), which has now been removed. Signed-off-by: Naushir Patuck --- src/libcamera/pipeline/uvcvideo/uvcvideo.cpp | 123 +++++++++++++------ 1 file changed, 84 insertions(+), 39 deletions(-) diff --git a/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp b/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp index 5fa1bdb0ff0e..9d6dd1ec9bcc 100644 --- a/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp +++ b/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp @@ -21,12 +21,14 @@ #include #include +#include #include #include #include #include #include "libcamera/internal/camera.h" +#include "libcamera/internal/camera_descriptor.h" #include "libcamera/internal/device_enumerator.h" #include "libcamera/internal/media_device.h" #include "libcamera/internal/pipeline_handler.h" @@ -46,7 +48,7 @@ public: { } - int init(std::shared_ptr media); + int init(std::shared_ptr media, const std::string &id); void addControl(uint32_t cid, const ControlInfo &v4l2info, ControlInfoMap::Map *ctrls); void imageBufferReady(FrameBuffer *buffer); @@ -62,8 +64,6 @@ public: std::optional manualExposureMode_; private: - bool generateId(); - std::string id_; }; @@ -95,7 +95,9 @@ public: int queueRequestDevice(Camera *camera, Request *request) override; - bool match(DeviceEnumerator *enumerator) override; + int survey(const DeviceEnumerator *enumerator, + std::vector> *descriptors) override; + int createCamera(const CameraDescriptor *descriptor) override; private: int processControl(const UVCCameraData *data, ControlList *controls, @@ -129,6 +131,20 @@ std::optional v4l2ToExposureMode(int32_t x) } } +/* Locate the default video entity of a UVC media device. */ +MediaEntity *defaultEntity(const MediaDevice *media) +{ + const std::vector &entities = media->entities(); + auto entity = std::find_if(entities.begin(), entities.end(), + [](MediaEntity *e) { + return e->flags() & MEDIA_ENT_FL_DEFAULT; + }); + if (entity == entities.end()) + return nullptr; + + return *entity; +} + /* * Generate the camera ID from the sysfs path of the UVC device video node. The * path is derived from the device numbers of the media entity, so the ID can be @@ -136,12 +152,15 @@ std::optional v4l2ToExposureMode(int32_t x) */ std::string generateIdFromPath(const std::string &path) { + if (path.empty()) + return {}; + /* Create a controller ID from first device described in firmware. */ std::string controllerId; std::string searchPath = path; while (true) { std::string::size_type pos = searchPath.rfind('/'); - if (pos <= 1) { + if (pos == std::string::npos || pos <= 1) { LOG(UVC, Error) << "Can not find controller ID"; return {}; } @@ -533,31 +552,72 @@ int PipelineHandlerUVC::queueRequestDevice(Camera *camera, Request *request) return 0; } -bool PipelineHandlerUVC::match(DeviceEnumerator *enumerator) +int PipelineHandlerUVC::survey(const DeviceEnumerator *enumerator, + std::vector> *descriptors) { - std::shared_ptr media; DeviceMatch dm("uvcvideo"); - media = acquireMediaDevice(enumerator, dm); - if (!media) - return false; + for (std::shared_ptr &media : enumerator->searchAll(dm)) { + MediaEntity *entity = defaultEntity(media.get()); + if (!entity) + continue; - std::unique_ptr data = std::make_unique(this); + /* + * The sysfs path of the device is derived from the entity + * device numbers, so the ID is generated without accessing + * the video device. + */ + std::string id = + generateIdFromPath(sysfs::devicePath(entity->deviceMajor(), + entity->deviceMinor())); + if (id.empty()) { + LOG(UVC, Warning) + << "Failed to generate an ID for " + << media->model(); + continue; + } - if (data->init(media)) - return false; + auto data = std::make_unique(); + data->id_ = std::move(id); + data->properties_.set(properties::Model, + utils::toAscii(media->model())); + data->mediaDevices_ = { media }; + data->entityName_ = entity->name(); + + descriptors->push_back(CameraDescriptor::create(std::move(data))); + } + + return 0; +} + +int PipelineHandlerUVC::createCamera(const CameraDescriptor *descriptor) +{ + const CameraDescriptor::Private *data = descriptor->_d(); + + if (data->mediaDevices_.size() != 1) + return -EINVAL; + + std::shared_ptr media = data->mediaDevices_[0]; + if (!acquireMediaDevice(media)) + return -EBUSY; + + std::unique_ptr cameraData = std::make_unique(this); + + int ret = cameraData->init(media, descriptor->id()); + if (ret) + return ret; /* Create and register the camera. */ - std::string id = data->id(); - std::set streams{ &data->stream_ }; + std::string id = cameraData->id(); + std::set streams{ &cameraData->stream_ }; std::shared_ptr camera = - Camera::create(std::move(data), id, streams); + Camera::create(std::move(cameraData), id, streams); registerCamera(std::move(camera)); /* Enable hot-unplug notifications. */ - hotplugMediaDevice(media); + hotplugMediaDevice(std::move(media)); - return true; + return 0; } bool PipelineHandlerUVC::acquireDevice(Camera *camera) @@ -577,35 +637,27 @@ void PipelineHandlerUVC::releaseDevice(Camera *camera) data->video_->close(); } -int UVCCameraData::init(std::shared_ptr media) +int UVCCameraData::init(std::shared_ptr media, const std::string &id) { int ret; + id_ = id; + /* Locate and initialise the camera data with the default video node. */ - const std::vector &entities = media->entities(); - auto entity = std::find_if(entities.begin(), entities.end(), - [](MediaEntity *e) { - return e->flags() & MEDIA_ENT_FL_DEFAULT; - }); - if (entity == entities.end()) { + MediaEntity *entity = defaultEntity(media.get()); + if (!entity) { LOG(UVC, Error) << "Could not find a default video device"; return -ENODEV; } /* Create and open the video device. */ - video_ = std::make_unique(*entity); + video_ = std::make_unique(entity); ret = video_->open(); if (ret) return ret; video_->bufferReady.connect(this, &UVCCameraData::imageBufferReady); - /* Generate the camera ID. */ - if (!generateId()) { - LOG(UVC, Error) << "Failed to generate camera ID"; - return -EINVAL; - } - /* * Populate the map of supported formats, and infer the camera sensor * resolution from the largest size it advertises. @@ -692,13 +744,6 @@ int UVCCameraData::init(std::shared_ptr media) return 0; } -bool UVCCameraData::generateId() -{ - id_ = generateIdFromPath(video_->devicePath()); - - return !id_.empty(); -} - void UVCCameraData::addControl(uint32_t cid, const ControlInfo &v4l2Info, ControlInfoMap::Map *ctrls) {