[RFC,v1,20/20] Documentation: Describe the two-phase camera enumeration API
diff mbox series

Message ID 20260918080734.1228227-21-naush@raspberrypi.com
State New
Headers show
Series
  • libcamera: New enumeration API
Related show

Commit Message

Naushir Patuck Sept. 18, 2026, 7:59 a.m. UTC
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 <naush@raspberrypi.com>
---
 .../guides/application-developer.rst          |  41 +++++++
 Documentation/guides/pipeline-handler.rst     | 101 ++++++++++++++++++
 2 files changed, 142 insertions(+)

Patch
diff mbox series

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<CameraManager> cm = std::make_unique<CameraManager>();
+
+   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> 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<std::shared_ptr<CameraDescriptor>> *descriptors)
+   {
+   	DeviceMatch dm("vivid");
+   	dm.add("vivid-000-vid-cap");
+
+   	for (std::shared_ptr<MediaDevice> &media : enumerator->searchAll(dm)) {
+   		auto data = std::make_unique<CameraDescriptor::Private>();
+   		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<MediaDevice> media = descriptor->_d()->mediaDevices_[0];
+   	if (!acquireMediaDevice(media))
+   		return -EBUSY;
+
+   	std::unique_ptr<VividCameraData> data = std::make_unique<VividCameraData>(this);
+
+   	/* Locate and open the capture video node. */
+   	if (data->init(media.get()))
+   		return -ENODEV;
+
+   	/* Create and register the camera. */
+   	std::set<Stream *> streams{ &data->stream_ };
+   	std::shared_ptr<Camera> 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 <libcamera/camera_descriptor.h>
+   #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
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~