@@ -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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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(+)