From patchwork Fri Sep 18 07:59:58 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Naushir Patuck X-Patchwork-Id: 28332 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 3162EC3358 for ; Fri, 18 Sep 2026 08:08:12 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id A5C8A68743; Fri, 18 Sep 2026 10:08:11 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (2048-bit key; unprotected) header.d=raspberrypi.com header.i=@raspberrypi.com header.b="c3XOL9Ur"; dkim-atps=neutral Received: from mail-wr2-x0f.google.com (mail-wr2-x0f.google.com [IPv6:2a00:1450:4864:30::f]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 6D30868734 for ; Fri, 18 Sep 2026 10:07:58 +0200 (CEST) Received: by mail-wr2-x0f.google.com with SMTP id ffacd0b85a97d-482e1b429f7so64384f8f.2 for ; Fri, 18 Sep 2026 01:07:58 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=raspberrypi.com; s=google; t=1789718878; x=1790323678; 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=cgeO87m08I8rjm4UZl64Fa/+TlLLQaT8WNsNCUyuluM=; b=c3XOL9Ur+2CwxRdwmZ6ArYz5LZhqYpo1S//nZAmoOEVqZibm14QoqyogjiNgsu2rXr LLMoZ4GNzSB3vFqrqhXGF7JRACWyRsKDg7ghWwNOJG7YUhkHP9PKeVUTMSXi+OmOqi6M azFAOwSVc1efbqWw+UPQ5xdN5OJsaL91z4vge8UtjVB/6RH3BzfBSllmUn+PI/eX/6OV GxBl0z6b8mPjMv7iN0uzSvIhDkLrJOKt3zGkNGCpIhNbWpjx/i3XnhdP7FsR8dcCv8Z+ YFz4N2+OvUxG6NsKLLw0rOK1K7CVKmulArYoQjrtliNVdZxWzT9+w1IZmsiOnTdtY2Y2 g1nw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1789718878; x=1790323678; 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=cgeO87m08I8rjm4UZl64Fa/+TlLLQaT8WNsNCUyuluM=; b=LqtvnzErVYggk3hyXKbh52U/xfBtmQB4shDCCMpiXJfg7Vyu5goN6U9Hr2+2MIT9Ds /LUgsyNUmd2jzJitby6jexvhjGFCH+QeXw5slsNsLyDBGuoYYHwWneE3LGrsgmPUagPg eC7TRgzb+wBJ2BsxmmJjamLMfnzmQjcNm7ABWXuTDT4uScHn4nyz/No3XQmybyCw1tQF Qwa/LPia3x34lDrXTMZHr5uRhWp2LzfzitWA3t6b8thaIVZGsZV8vvez2222j/sI3iZo RY0IzS7nEN+Gl6+q1K5LWwW2Pj+EQk2DFU/tl3jmdUptC5RxZvKIriL/hSVCVdWeeZn9 FHtw== X-Gm-Message-State: AFuF++nhjCkTw9OJH+Y3RaUl9zZqglLOHEh/fYsuyIAAnIIpxfcNca1N VUpj4+edcUxiM8nqPT+sIeiaj2XGRo2GoVNYcbQy015SvDsBm8H0YdMsyR17Yz/ElspQt5Y0d16 j8ljK4AM= X-Gm-Gg: AYBFou1Z6Mp6CX/8hwrlLeY78eDMj5LxUocMhXenmgw8Di6SExw0SHxr6XUoAN+JfHR Hs25ZyhCSsHBANETl1sYhX8n6cR23ZqDxnmsyQydMqFm36cpJyQqqUPxzdpYcHqtMUGND8uzhq4 HQmuPgVTXHV+sb5sCK9Z1gg+zEYyjl08gOOQR8rXc2xRjbD/TunYxM/V2RpLzuTLn4855TEvmG4 DXK5Txbt1WDRWAiOFinrpKSimprFE09f2GAi7NnGb4beHQnU1+JKr0B19qYbkdCMbIfem4eG33/ wlAzsPTe7ZVkjUdzOA1ifYeIvDhRPBwdQXSR7PGPdrdPjKO4sPi+oF4nNh7zo/JIKdXqqv65lm5 Ns32dRfykf6bfFQ6EguJvmAPmgGG7JEgcQqASyk9lB0MGY65bMCpGZrmmwus1l2xJjqWIa3yLLM pMPn4ob2TTegphjZsAxiC+yhQNVhzit8/CvqhHVqda1bLoH0h7aT3rCReQ6idIBvX7du60rsz0u inqOs5s4KqvZij8ZDrGqEMZPszrZmCevlmu/XXl8zCBv4TArA87N7WBj7jO1gS72ZruQwrhdxKR Rpgw3XqSQ/FTzv8o1SRz2QW6EddxvYAY5ELdTfs47bd3BE3+aaOnp3wwbT8Ui3/aQsr7P4CbWdg vAgXyS8lH45FB2IAEHjVUHFPUtLebNXVGt/w= X-Received: by 2002:a05:6000:4819:b0:487:1a14:7f6c with SMTP id ffacd0b85a97d-4871faa25efmr1174355f8f.5.1789718877785; Fri, 18 Sep 2026 01:07:57 -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:57 -0700 (PDT) From: Naushir Patuck To: libcamera-devel@lists.libcamera.org Cc: Naushir Patuck Subject: [RFC PATCH v1 20/20] Documentation: Describe the two-phase camera enumeration API Date: Fri, 18 Sep 2026 08:59:58 +0100 Message-ID: <20260918080734.1228227-21-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" Document the enumerate() and initialize() camera manager APIs in the application writer's guide. Document survey() and createCamera() in the pipeline handler writer's guide, with a vivid example of each, similar to the match() documentation. Signed-off-by: Naushir Patuck --- .../guides/application-developer.rst | 41 +++++++ Documentation/guides/pipeline-handler.rst | 101 ++++++++++++++++++ 2 files changed, 142 insertions(+) diff --git a/Documentation/guides/application-developer.rst b/Documentation/guides/application-developer.rst index abc67dd010bd..17e6c97ca2b3 100644 --- a/Documentation/guides/application-developer.rst +++ b/Documentation/guides/application-developer.rst @@ -89,6 +89,47 @@ Printing the camera id lists the machine-readable unique identifiers, so for example, the output on a Linux machine with a connected USB webcam is ``\_SB_.PCI0.XHC_.RHUB.HS08-8:1.0-5986:2115``. +Enumerating cameras without initialising them +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:doxy-pub:`CameraManager::start()` initialises every camera in the system, +including loading the IPA module associated with each camera. This is often a +heavyweight operation. An application that only uses a single camera can instead +enumerate the cameras first, and initialise the one it needs: + +.. code:: cpp + + std::unique_ptr cm = std::make_unique(); + + for (const auto &descriptor : cm->enumerate()) + std::cout << descriptor->id() << std::endl; + +:doxy-pub:`CameraManager::enumerate()` starts the camera manager if it is not +running yet, and returns a :doxy-pub:`CameraDescriptor` for every camera of a +pipeline handler that supports enumeration. A descriptor reports the camera id +and its properties without the camera being initialised; no media device is +acquired, no device node is opened, and no IPA module is loaded. Cameras of +pipeline handlers that do not support enumeration are created by ``start()``, +and reported through ``cameras()`` as before. + +A camera is then initialised from its descriptor: + +.. code:: cpp + + std::shared_ptr camera = cm->initialize(descriptor); + +The resulting camera is identical to one created by start() and is reported +through :doxy-pub:`CameraManager::cameras`, ``get()`` and the ``cameraAdded`` +signal. Initialising a camera that has already been initialised returns the +existing instance. + +The list returned by ``enumerate()`` is a snapshot of the cameras present when +it is called. Cameras hotplugged afterwards are initialised automatically and +reported through the ``cameraAdded`` signal, as they are after ``start()``. + +Note that ``stop()`` invalidates the descriptors returned by ``enumerate()``: +they can no longer be initialised. + What libcamera considers a camera ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/Documentation/guides/pipeline-handler.rst b/Documentation/guides/pipeline-handler.rst index e630199a5fb9..135b5f18d8c3 100644 --- a/Documentation/guides/pipeline-handler.rst +++ b/Documentation/guides/pipeline-handler.rst @@ -334,6 +334,13 @@ to the search using the ``.add()`` function on the DeviceMatch. This example uses search patterns that match vivid, but when developing a new pipeline handler, you should change this value to suit your device identifier. +.. note:: + + ``match()`` finds and creates the cameras in one step. A pipeline handler + can instead let applications list the cameras before initialising them, by + implementing ``survey()`` and ``createCamera()`` as described in + `Enumerating cameras without creating them`_ below. + Replace the contents of the ``PipelineHandlerVivid::match`` function with the following: @@ -557,6 +564,100 @@ interface, and device interaction interfaces. #include "libcamera/internal/media_device.h" #include "libcamera/internal/v4l2_videodevice.h" +Enumerating cameras without creating them +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``match()`` finds and creates the cameras of a pipeline handler in one step, and +:doxy-pub:`CameraManager::start()` calls it for every pipeline handler in the +system. Creating a camera includes loading its IPA module, which is often a +heavyweight operation. An application that only uses a single camera can instead +call :doxy-pub:`CameraManager::enumerate()` to list the cameras first, and +initialise the one it needs. To support this, a pipeline handler implements +:doxy-int:`PipelineHandler::survey` and +:doxy-int:`PipelineHandler::createCamera` in place of ``match()``. + +``survey()`` reports a :doxy-pub:`CameraDescriptor` for every camera the +pipeline handler would create, using only the information available from the +``DeviceEnumerator``. It shall not acquire a media device, open a device node or +alter any hardware state. It returns 0 on success, appending one descriptor per +camera found, or ``-ENOTSUP`` if the pipeline handler cannot survey its cameras. +Returning ``-ENOTSUP`` tells the camera manager to fall back to ``match()``. + +A descriptor carries the camera id, the properties that are known without +opening the device, such as the model, and the media devices the camera needs. +The :doxy-int:`DeviceEnumerator::searchAll` function returns every media device +matching a ``DeviceMatch`` without acquiring it. For vivid, one camera is +reported per matching media device: + +.. code-block:: cpp + + int PipelineHandlerVivid::survey(const DeviceEnumerator *enumerator, + std::vector> *descriptors) + { + DeviceMatch dm("vivid"); + dm.add("vivid-000-vid-cap"); + + for (std::shared_ptr &media : enumerator->searchAll(dm)) { + auto data = std::make_unique(); + data->id_ = media->getEntityByName("vivid-000-vid-cap")->name(); + data->properties_.set(properties::Model, media->model()); + data->mediaDevices_ = { media }; + + descriptors->push_back(CameraDescriptor::create(std::move(data))); + } + + return 0; + } + +``createCamera()`` then performs, for a single descriptor, the per-camera work +that ``match()`` would have done, i.e. acquiring the media devices the camera +needs, opening the device nodes and registering the camera. The camera shall be +created with the id of its descriptor, so that applications can relate the two. +For vivid, this is the body of the ``match()`` function written above, with the +media device taken from the descriptor instead of searched for: + +.. code-block:: cpp + + int PipelineHandlerVivid::createCamera(const CameraDescriptor *descriptor) + { + std::shared_ptr media = descriptor->_d()->mediaDevices_[0]; + if (!acquireMediaDevice(media)) + return -EBUSY; + + std::unique_ptr data = std::make_unique(this); + + /* Locate and open the capture video node. */ + if (data->init(media.get())) + return -ENODEV; + + /* Create and register the camera. */ + std::set streams{ &data->stream_ }; + std::shared_ptr camera = Camera::create(std::move(data), + descriptor->id(), streams); + registerCamera(std::move(camera)); + + return 0; + } + +When several cameras share a media device, for instance sensors behind a video +mux, the camera manager routes them to the same pipeline handler instance. +``createCamera()`` shall then only acquire the media device if the instance does +not already hold it, which can be checked with +:doxy-int:`PipelineHandler::usesMediaDevice`. + +The descriptor classes need the following includes: + +.. code-block:: cpp + + #include + #include "libcamera/internal/camera_descriptor.h" + +A pipeline handler that implements ``survey()`` and ``createCamera()`` does not +need ``match()``. ``start()`` creates its cameras by surveying and initialising +all of them. A pipeline handler that implements neither is not reported by +``CameraManager::enumerate()`` and its cameras are created by ``start()`` +through ``match()`` as before. + Registering controls and properties ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~