[PATCH 15 6/7] libcamera: virtual: Read config and register cameras based on the config

Cheng-Hao Yang chenghaoyang at chromium.org
Tue Oct 22 09:46:05 CEST 2024


Hi Kieran,

On Tue, Oct 22, 2024 at 7:31 AM Kieran Bingham
<kieran.bingham at ideasonboard.com> wrote:
>
> Quoting Harvey Yang (2024-10-04 10:59:19)
> > This patch introduces the configuration file for Virtual Pipeline
> > Handler. The config file is written in yaml, and the format is
> > documented in `README.md`.
> >
> > The config file will define the camera with IDs, supported formats and
> > image sources, etc. In the default config file, only Test Patterns are
> > used. Developers can use real images loading if desired.
> >
> > Signed-off-by: Konami Shu <konamiz at google.com>
> > Co-developed-by: Harvey Yang <chenghaoyang at chromium.org>
> > Co-developed-by: Yunke Cao <yunkec at chromium.org>
> > Co-developed-by: Tomasz Figa <tfiga at chromium.org>
>
> I think we determined Co-developed-by: need SoBs.

Yes, updated. I didn't upload a new version to avoid the spam.

>
> Checkstyle reports:
>
> -----------------------------------------------------------------------------------------------------------------
> 83f896b4afa8e5e7a4029d1e07bcb5dfe8cc5fb2 libcamera: virtual: Read config and register cameras based on the config
> -----------------------------------------------------------------------------------------------------------------
> No 'Signed-off-by' trailer matching author 'Harvey Yang <chenghaoyang at chromium.org>', see Documentation/contributing.rst
> ---
> 1 potential issue detected, please review
>
> And that will apply to all the above.

Done.

>
>
> > ---
> >  src/libcamera/pipeline/virtual/README.md      |  65 +++++
> >  .../pipeline/virtual/config_parser.cpp        | 263 ++++++++++++++++++
> >  .../pipeline/virtual/config_parser.h          |  39 +++
> >  .../pipeline/virtual/data/virtual.yaml        |  36 +++
> >  .../virtual/image_frame_generator.cpp         |  16 +-
> >  .../pipeline/virtual/image_frame_generator.h  |   5 +-
> >  src/libcamera/pipeline/virtual/meson.build    |   1 +
> >  src/libcamera/pipeline/virtual/virtual.cpp    | 126 +++++----
> >  src/libcamera/pipeline/virtual/virtual.h      |  23 +-
> >  9 files changed, 504 insertions(+), 70 deletions(-)
> >  create mode 100644 src/libcamera/pipeline/virtual/README.md
> >  create mode 100644 src/libcamera/pipeline/virtual/config_parser.cpp
> >  create mode 100644 src/libcamera/pipeline/virtual/config_parser.h
> >  create mode 100644 src/libcamera/pipeline/virtual/data/virtual.yaml
> >
> > diff --git a/src/libcamera/pipeline/virtual/README.md b/src/libcamera/pipeline/virtual/README.md
> > new file mode 100644
> > index 000000000..5801e79b5
> > --- /dev/null
> > +++ b/src/libcamera/pipeline/virtual/README.md
> > @@ -0,0 +1,65 @@
> > +# Virtual Pipeline Handler
> > +
> > +Virtual pipeline handler emulates fake external camera(s) on ChromeOS for testing.
>
> It's not only on ChromeOS right ? It's just a virtual camera for any
> platform.

Right, removed.

>
>
>
> > +
> > +## Parse config file and register cameras
> > +
> > +- The config file is located at `src/libcamera/pipeline/virtual/data/virtual.yaml`
>
> I'm sure I mentioned this before - but this is the source location.
> While I preume the users would have to configure their 'installed'
> location. So this should probably reference the fact it would be
> installed in ...
>
> Err - where does it get installed ? What happens if libcamera is
> installed and tries to use the virtual pipeline handler here ?
>
> I don't see any reference in a meson file that installes
> data/virtual.yaml ?
>
>
> Later on virtual.yaml is mentioned as a sample, while up here it's
> specified as '"The" config file'. ?
>
> Maybe this should say "A sample config file is located at ..."

Yes, it depends on if libcamera is installed. Updated and added
the installed path:
`shar/libcamera/pipeline/virtual/virtual.yaml`.

>
> > +
> > +### Config File Format
> > +The config file contains the information about cameras' properties to register.
> > +The config file should be a yaml file with dictionary of the cameraIds
> > +associated with their properties as top level. The default value will be applied
> > +when any property is empty.
> > +
> > +Each camera block is a dictionary, containing the following keys:
> > +- `supported_formats` (list of `VirtualCameraData::Resolution`, optional) :
> > +  List of supported resolution and frame rates of the emulated camera
> > +    - `width` (`unsigned int`, default=1920): Width of the window resolution.
> > +      This needs to be even.
> > +    - `height` (`unsigned int`, default=1080): Height of the window resolution.
> > +    - `frame_rates` (list of `int`, default=`[30,60]` ): Range of the frame
> > +      rate (per second). If the list contains one value, it's the lower bound
> > +      and the upper bound. If the list contains two values, the first is the
> > +      lower bound and the second is the upper bound. No other number of values
> > +      is allowed.
> > +- `test_pattern` (`string`): Which test pattern to use as frames. The options
> > +  are "bars", "lines". Cannot be set with `frames`.
> > +- `frames` (dictionary):
> > +  - `path` (`string`): Path to an image, or path to a directory of a series of
> > +    images. Cannot be set with `test_pattern`.
> > +    - The test patterns are "bars" which means color bars, and "lines" which
> > +      means diagonal lines.
>
> Does this bullet point belong to the 'test_pattern' section above rather
> than frames ?

Right, thanks! Updated.

>
> > +    - The path to an image has ".jpg" extension.
> > +    - The path to a directory ends with "/". The name of the images in the
> > +      directory are "{n}.jpg" with {n} is the sequence of images starting with 0.
> > +- `location` (`string`, default="front"): The location of the camera. Support
> > +  "front" and "back". This is displayed in qcam camera selection window but
>
> I don't think it's relevant to mention 'qcam' here. Setting the location
> defines the location property of the camera which is exposed by the
> camera in /any/ application. Not just qcam.

Right, removed this and the one in `model`.

>
>
> > +  this does not change the output.
> > +- `model` (`string`, default="Unknown"): The model name of the camera.
> > +  This is displayed in qcam camera selection window but this does not change the output.
> > +
> > +Check `data/virtual.yaml` as the sample config file.
> > +
> > +### Implementation
> > +
> > +`Parser` class provides methods to parse the config file to register cameras
> > +in Virtual Pipeline Handler. `parseConfigFile()` is exposed to use in
> > +Virtual Pipeline Handler.
> > +
> > +This is the procedure of the Parser class:
> > +1. `parseConfigFile()` parses the config file to `YamlObject` using `YamlParser::parse()`.
> > +    - Parse the top level of config file which are the camera ids and look into
> > +      each camera properties.
> > +2. For each camera, `parseCameraConfigData()` returns a camera with the configuration.
> > +    - The methods in the next step fill the data with the pointer to the Camera object.
> > +    - If the config file contains invalid configuration, this method returns
> > +      nullptr. The camera will be skipped.
> > +3. Parse each property and register the data.
> > +    - `parseSupportedFormats()`: Parses `supported_formats` in the config, which
> > +      contains resolutions and frame rates.
> > +    - `parseFrameGenerator()`: Parses `test_pattern` or `frames` in the config.
> > +    - `parseLocation()`: Parses `location` in the config.
> > +    - `parseModel()`: Parses `model` in the config.
> > +4. Back to `parseConfigFile()` and append the camera configuration.
> > +5. Returns a list of camera configurations.
> > diff --git a/src/libcamera/pipeline/virtual/config_parser.cpp b/src/libcamera/pipeline/virtual/config_parser.cpp
> > new file mode 100644
> > index 000000000..467dde485
> > --- /dev/null
> > +++ b/src/libcamera/pipeline/virtual/config_parser.cpp
> > @@ -0,0 +1,263 @@
> > +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> > +/*
> > + * Copyright (C) 2024, Google Inc.
> > + *
> > + * config_parser.cpp - Virtual cameras helper to parse config file
>
> Drop 'config_parser.cpp - ' We stopped putting file names in their own
> files as they bitrot and don't add value

Done

>
> > + */
> > +
> > +#include "config_parser.h"
> > +
> > +#include <string.h>
> > +#include <utility>
> > +
> > +#include <libcamera/base/log.h>
> > +
> > +#include <libcamera/control_ids.h>
> > +#include <libcamera/property_ids.h>
> > +
> > +#include "libcamera/internal/pipeline_handler.h"
> > +#include "libcamera/internal/yaml_parser.h"
> > +
> > +#include "virtual.h"
> > +
> > +namespace libcamera {
> > +
> > +LOG_DECLARE_CATEGORY(Virtual)
> > +
> > +std::vector<std::unique_ptr<VirtualCameraData>>
> > +ConfigParser::parseConfigFile(File &file, PipelineHandler *pipe)
> > +{
> > +       std::vector<std::unique_ptr<VirtualCameraData>> configurations;
> > +
> > +       std::unique_ptr<YamlObject> cameras = YamlParser::parse(file);
> > +       if (!cameras) {
> > +               LOG(Virtual, Error) << "Failed to pass config file.";
> > +               return configurations;
> > +       }
> > +
> > +       if (!cameras->isDictionary()) {
> > +               LOG(Virtual, Error) << "Config file is not a dictionary at the top level.";
> > +               return configurations;
> > +       }
> > +
> > +       /* Look into the configuration of each camera */
> > +       for (const auto &[cameraId, cameraConfigData] : cameras->asDict()) {
> > +               std::unique_ptr<VirtualCameraData> data =
> > +                       parseCameraConfigData(cameraConfigData, pipe);
> > +               /* Parse configData to data */
> > +               if (!data) {
> > +                       /* Skip the camera if it has invalid config */
> > +                       LOG(Virtual, Error) << "Failed to parse config of the camera: "
> > +                                           << cameraId;
> > +                       continue;
> > +               }
> > +
> > +               data->config_.id = cameraId;
> > +               ControlInfoMap::Map controls;
> > +               /* todo: Check which resolution's frame rate to be reported */
> > +               controls[&controls::FrameDurationLimits] =
> > +                       ControlInfo(1000000 / data->config_.resolutions[0].frameRates[1],
> > +                                   1000000 / data->config_.resolutions[0].frameRates[0]);
> > +
> > +               std::vector<ControlValue> supportedFaceDetectModes{
> > +                       static_cast<int32_t>(controls::draft::FaceDetectModeOff),
> > +               };
> > +               controls[&controls::draft::FaceDetectMode] = ControlInfo(supportedFaceDetectModes);
> > +
> > +               data->controlInfo_ = ControlInfoMap(std::move(controls), controls::controls);
> > +               configurations.push_back(std::move(data));
> > +       }
> > +
> > +       return configurations;
> > +}
> > +
> > +std::unique_ptr<VirtualCameraData>
> > +ConfigParser::parseCameraConfigData(const YamlObject &cameraConfigData,
> > +                                   PipelineHandler *pipe)
> > +{
> > +       std::vector<VirtualCameraData::Resolution> resolutions;
> > +       if (parseSupportedFormats(cameraConfigData, &resolutions))
> > +               return nullptr;
> > +
> > +       std::unique_ptr<VirtualCameraData> data =
> > +               std::make_unique<VirtualCameraData>(pipe, resolutions);
> > +
> > +       if (parseFrameGenerator(cameraConfigData, data.get()))
> > +               return nullptr;
> > +
> > +       if (parseLocation(cameraConfigData, data.get()))
> > +               return nullptr;
> > +
> > +       if (parseModel(cameraConfigData, data.get()))
> > +               return nullptr;
> > +
> > +       return data;
> > +}
> > +
> > +int ConfigParser::parseSupportedFormats(const YamlObject &cameraConfigData,
> > +                                       std::vector<VirtualCameraData::Resolution> *resolutions)
> > +{
> > +       if (cameraConfigData.contains("supported_formats")) {
> > +               const YamlObject &supportedResolutions = cameraConfigData["supported_formats"];
> > +
> > +               for (const YamlObject &supportedResolution : supportedResolutions.asList()) {
> > +                       unsigned int width = supportedResolution["width"].get<unsigned int>(1920);
> > +                       unsigned int height = supportedResolution["height"].get<unsigned int>(1080);
> > +                       if (width == 0 || height == 0) {
> > +                               LOG(Virtual, Error) << "Invalid width or/and height";
> > +                               return -EINVAL;
> > +                       }
> > +                       if (width % 2 != 0) {
> > +                               LOG(Virtual, Error) << "Invalid width: width needs to be even";
> > +                               return -EINVAL;
> > +                       }
> > +
> > +                       std::vector<int64_t> frameRates;
> > +                       if (supportedResolution.contains("frame_rates")) {
> > +                               auto frameRatesList =
> > +                                       supportedResolution["frame_rates"].getList<int>();
> > +                               if (!frameRatesList || (frameRatesList->size() != 1 &&
> > +                                                       frameRatesList->size() != 2)) {
> > +                                       LOG(Virtual, Error) << "Invalid frame_rates: either one or two values";
> > +                                       return -EINVAL;
> > +                               }
> > +
> > +                               if (frameRatesList->size() == 2 &&
> > +                                   frameRatesList.value()[0] > frameRatesList.value()[1]) {
> > +                                       LOG(Virtual, Error) << "frame_rates's first value(lower bound)"
> > +                                                           << " is higher than the second value(upper bound)";
> > +                                       return -EINVAL;
> > +                               }
> > +                               /*
> > +                                 * Push the min and max framerates. A
> > +                                 * single rate is duplicated.
> > +                                 */
> > +                               frameRates.push_back(frameRatesList.value().front());
> > +                               frameRates.push_back(frameRatesList.value().back());
> > +                       } else {
> > +                               frameRates.push_back(30);
> > +                               frameRates.push_back(60);
> > +                       }
> > +
> > +                       resolutions->emplace_back(
> > +                               VirtualCameraData::Resolution{ Size{ width, height },
> > +                                                              frameRates });
> > +               }
> > +       } else {
> > +               resolutions->emplace_back(
> > +                       VirtualCameraData::Resolution{ Size{ 1920, 1080 },
> > +                                                      { 30, 60 } });
> > +       }
> > +
> > +       return 0;
> > +}
> > +
> > +int ConfigParser::parseFrameGenerator(const YamlObject &cameraConfigData, VirtualCameraData *data)
> > +{
> > +       const std::string testPatternKey = "test_pattern";
> > +       const std::string framesKey = "frames";
> > +       if (cameraConfigData.contains(testPatternKey)) {
> > +               if (cameraConfigData.contains(framesKey)) {
> > +                       LOG(Virtual, Error) << "A camera should use either "
> > +                                           << testPatternKey << " or " << framesKey;
> > +                       return -EINVAL;
> > +               }
> > +
> > +               auto testPattern = cameraConfigData[testPatternKey].get<std::string>("");
> > +
> > +               if (testPattern == "bars") {
> > +                       data->config_.frame = TestPattern::ColorBars;
> > +               } else if (testPattern == "lines") {
> > +                       data->config_.frame = TestPattern::DiagonalLines;
> > +               } else {
> > +                       LOG(Virtual, Debug) << "Test pattern: " << testPattern
> > +                                           << " is not supported";
> > +                       return -EINVAL;
> > +               }
> > +
> > +               return 0;
> > +       }
> > +
> > +       const YamlObject &frames = cameraConfigData[framesKey];
> > +
> > +       /* When there is no frames provided in the config file, use color bar test pattern */
> > +       if (!frames) {
> > +               data->config_.frame = TestPattern::ColorBars;
> > +               return 0;
> > +       }
> > +
> > +       if (!frames.isDictionary()) {
> > +               LOG(Virtual, Error) << "'frames' is not a dictionary.";
> > +               return -EINVAL;
> > +       }
> > +
> > +       auto path = frames["path"].get<std::string>();
> > +
> > +       if (!path) {
> > +               LOG(Virtual, Error) << "Test pattern or path should be specified.";
> > +               return -EINVAL;
> > +       }
> > +
> > +       std::vector<std::filesystem::path> files;
> > +
> > +       switch (std::filesystem::symlink_status(*path).type()) {
> > +       case std::filesystem::file_type::regular:
> > +               files.push_back(*path);
> > +               break;
> > +
> > +       case std::filesystem::file_type::directory:
> > +               for (const auto &dentry : std::filesystem::directory_iterator{ *path }) {
> > +                       if (dentry.is_regular_file())
> > +                               files.push_back(dentry.path());
> > +               }
> > +
> > +               std::sort(files.begin(), files.end(), [](const auto &a, const auto &b) {
> > +                       return ::strverscmp(a.c_str(), b.c_str()) < 0;
> > +               });
> > +
> > +               if (files.empty()) {
> > +                       LOG(Virtual, Error) << "Directory has no files: " << *path;
> > +                       return -EINVAL;
> > +               }
> > +               break;
> > +
> > +       default:
> > +               LOG(Virtual, Error) << "Frame: " << *path << " is not supported";
> > +               return -EINVAL;
> > +       }
> > +
> > +       data->config_.frame = ImageFrames{ std::move(files) };
> > +
> > +       return 0;
> > +}
> > +
> > +int ConfigParser::parseLocation(const YamlObject &cameraConfigData, VirtualCameraData *data)
> > +{
> > +       std::string location = cameraConfigData["location"].get<std::string>("front");
> > +
> > +       /* Default value is properties::CameraLocationFront */
> > +       if (location == "front") {
> > +               data->properties_.set(properties::Location,
> > +                                     properties::CameraLocationFront);
> > +       } else if (location == "back") {
> > +               data->properties_.set(properties::Location,
> > +                                     properties::CameraLocationBack);
> > +       } else {
> > +               LOG(Virtual, Error) << "location: " << location
> > +                                   << " is not supported";
>
> What about external ?
>
> I think the control framework provides a map of string to id for the
> enum types in fact, so you can do something like:
>
>
> int ConfigParser::parseLocation(const YamlObject &cameraConfigData, VirtualCameraData *data)
> {
>         std::string location = cameraConfigData["location"].get<std::string>("front");
>
>         auto it = properties::LocationNameValueMap.find(location);
>         if (it == properties::LocationNameValueMap.end()) {
>                 LOG(Virtual, Error)
>                        << "location: " << location << " is not supported";
>
>                 return -EINVAL;
>         }
>
>         data->properties_.set(properties::Location, it->second);
>
>         return 0;
> }
>
> Which is probably more future proof too if we for some reason add any
> other locations.

Makes sense, although the value has the prefix "CameraLocationXXX".
Updated.

>
>
>
> > +               return -EINVAL;
> > +       }
> > +
> > +       return 0;
> > +}
> > +
> > +int ConfigParser::parseModel(const YamlObject &cameraConfigData, VirtualCameraData *data)
> > +{
> > +       std::string model = cameraConfigData["model"].get<std::string>("Unknown");
> > +
> > +       data->properties_.set(properties::Model, model);
> > +
> > +       return 0;
> > +}
> > +
> > +} /* namespace libcamera */
> > diff --git a/src/libcamera/pipeline/virtual/config_parser.h b/src/libcamera/pipeline/virtual/config_parser.h
> > new file mode 100644
> > index 000000000..9abba62d0
> > --- /dev/null
> > +++ b/src/libcamera/pipeline/virtual/config_parser.h
> > @@ -0,0 +1,39 @@
> > +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> > +/*
> > + * Copyright (C) 2024, Google Inc.
> > + *
> > + * config_parser.h - Virtual cameras helper to parse config file
>
> Drop 'config_parser.h - ' from here.

Done

>
> > + */
> > +
> > +#pragma once
> > +
> > +#include <memory>
> > +#include <vector>
> > +
> > +#include <libcamera/base/file.h>
> > +
> > +#include "libcamera/internal/pipeline_handler.h"
> > +#include "libcamera/internal/yaml_parser.h"
> > +
> > +#include "virtual.h"
> > +
> > +namespace libcamera {
> > +
> > +class ConfigParser
> > +{
> > +public:
> > +       std::vector<std::unique_ptr<VirtualCameraData>>
> > +       parseConfigFile(File &file, PipelineHandler *pipe);
> > +
> > +private:
> > +       std::unique_ptr<VirtualCameraData>
> > +       parseCameraConfigData(const YamlObject &cameraConfigData, PipelineHandler *pipe);
> > +
> > +       int parseSupportedFormats(const YamlObject &cameraConfigData,
> > +                                 std::vector<VirtualCameraData::Resolution> *resolutions);
> > +       int parseFrameGenerator(const YamlObject &cameraConfigData, VirtualCameraData *data);
> > +       int parseLocation(const YamlObject &cameraConfigData, VirtualCameraData *data);
> > +       int parseModel(const YamlObject &cameraConfigData, VirtualCameraData *data);
> > +};
> > +
> > +} /* namespace libcamera */
> > diff --git a/src/libcamera/pipeline/virtual/data/virtual.yaml b/src/libcamera/pipeline/virtual/data/virtual.yaml
> > new file mode 100644
> > index 000000000..6b73ddf2b
> > --- /dev/null
> > +++ b/src/libcamera/pipeline/virtual/data/virtual.yaml
> > @@ -0,0 +1,36 @@
> > +# SPDX-License-Identifier: CC0-1.0
> > +%YAML 1.1
> > +---
> > +"Virtual0":
> > +  supported_formats:
> > +  - width: 1920
> > +    height: 1080
> > +    frame_rates:
> > +    - 30
> > +    - 60
> > +  - width: 1680
> > +    height: 1050
> > +    frame_rates:
> > +    - 70
> > +    - 80
> > +  test_pattern: "lines"
> > +  location: "front"
> > +  model: "Virtual Video Device"
> > +"Virtual1":
> > +  supported_formats:
> > +  - width: 800
> > +    height: 600
> > +    frame_rates:
> > +    - 60
> > +  test_pattern: "bars"
> > +  location: "back"
> > +  model: "Virtual Video Device1"
> > +"Virtual2":
> > +  supported_formats:
> > +  - width: 400
> > +    height: 300
> > +  test_pattern: "lines"
> > +  location: "front"
> > +  model: "Virtual Video Device2"
> > +"Virtual3":
> > +  test_pattern: "bars"
> > diff --git a/src/libcamera/pipeline/virtual/image_frame_generator.cpp b/src/libcamera/pipeline/virtual/image_frame_generator.cpp
> > index 04382beb7..36bdc20e5 100644
> > --- a/src/libcamera/pipeline/virtual/image_frame_generator.cpp
> > +++ b/src/libcamera/pipeline/virtual/image_frame_generator.cpp
> > @@ -40,15 +40,7 @@ ImageFrameGenerator::create(ImageFrames &imageFrames)
> >          * For each file in the directory, load the image,
> >          * convert it to NV12, and store the pointer.
> >          */
> > -       for (unsigned int i = 0; i < imageFrames.number.value_or(1); i++) {
> > -               std::filesystem::path path;
> > -               if (!imageFrames.number)
> > -                       /* If the path is to an image */
> > -                       path = imageFrames.path;
> > -               else
> > -                       /* If the path is to a directory */
> > -                       path = imageFrames.path / (std::to_string(i) + ".jpg");
> > -
> > +       for (std::filesystem::path path : imageFrames.files) {
> >                 File file(path);
> >                 if (!file.open(File::OpenModeFlag::ReadOnly)) {
> >                         LOG(Virtual, Error) << "Failed to open image file " << file.fileName()
> > @@ -88,6 +80,8 @@ ImageFrameGenerator::create(ImageFrames &imageFrames)
> >                                         Size(width, height) });
> >         }
> >
> > +       ASSERT(!imageFrameGenerator->imageFrameDatas_.empty());
> > +
> >         return imageFrameGenerator;
> >  }
> >
> > @@ -104,7 +98,7 @@ void ImageFrameGenerator::configure(const Size &size)
> >         frameIndex_ = 0;
> >         parameter_ = 0;
> >
> > -       for (unsigned int i = 0; i < imageFrames_->number.value_or(1); i++) {
> > +       for (unsigned int i = 0; i < imageFrameDatas_.size(); i++) {
> >                 /* Scale the imageFrameDatas_ to scaledY and scaledUV */
> >                 unsigned int halfSizeWidth = (size.width + 1) / 2;
> >                 unsigned int halfSizeHeight = (size.height + 1) / 2;
> > @@ -139,7 +133,7 @@ int ImageFrameGenerator::generateFrame(const Size &size, const FrameBuffer *buff
> >         auto planes = mappedFrameBuffer.planes();
> >
> >         /* Loop only around the number of images available */
> > -       frameIndex_ %= imageFrames_->number.value_or(1);
> > +       frameIndex_ %= imageFrameDatas_.size();
> >
> >         /* Write the scaledY and scaledUV to the mapped frame buffer */
> >         libyuv::NV12Copy(scaledFrameDatas_[frameIndex_].Y.get(), size.width,
> > diff --git a/src/libcamera/pipeline/virtual/image_frame_generator.h b/src/libcamera/pipeline/virtual/image_frame_generator.h
> > index a68094a66..8554d647d 100644
> > --- a/src/libcamera/pipeline/virtual/image_frame_generator.h
> > +++ b/src/libcamera/pipeline/virtual/image_frame_generator.h
> > @@ -10,9 +10,9 @@
> >
> >  #include <filesystem>
> >  #include <memory>
> > -#include <optional>
> >  #include <stdint.h>
> >  #include <sys/types.h>
> > +#include <vector>
> >
> >  #include "frame_generator.h"
> >
> > @@ -20,8 +20,7 @@ namespace libcamera {
> >
> >  /* Frame configuration provided by the config file */
> >  struct ImageFrames {
> > -       std::filesystem::path path;
> > -       std::optional<unsigned int> number;
> > +       std::vector<std::filesystem::path> files;
> >  };
> >
> >  class ImageFrameGenerator : public FrameGenerator
> > diff --git a/src/libcamera/pipeline/virtual/meson.build b/src/libcamera/pipeline/virtual/meson.build
> > index bb38c193c..4786fe2e0 100644
> > --- a/src/libcamera/pipeline/virtual/meson.build
> > +++ b/src/libcamera/pipeline/virtual/meson.build
> > @@ -1,6 +1,7 @@
> >  # SPDX-License-Identifier: CC0-1.0
> >
> >  libcamera_internal_sources += files([
> > +    'config_parser.cpp',
> >      'image_frame_generator.cpp',
> >      'test_pattern_generator.cpp',
> >      'virtual.cpp',
> > diff --git a/src/libcamera/pipeline/virtual/virtual.cpp b/src/libcamera/pipeline/virtual/virtual.cpp
> > index 1ad7417c7..cafd395c3 100644
> > --- a/src/libcamera/pipeline/virtual/virtual.cpp
> > +++ b/src/libcamera/pipeline/virtual/virtual.cpp
> > @@ -36,6 +36,9 @@
> >  #include "libcamera/internal/formats.h"
> >  #include "libcamera/internal/framebuffer.h"
> >  #include "libcamera/internal/pipeline_handler.h"
> > +#include "libcamera/internal/yaml_parser.h"
> > +
> > +#include "pipeline/virtual/config_parser.h"
> >
> >  namespace libcamera {
> >
> > @@ -54,6 +57,13 @@ uint64_t currentTimestamp()
> >
> >  } /* namespace */
> >
> > +template<class... Ts>
> > +struct overloaded : Ts... {
> > +       using Ts::operator()...;
> > +};
> > +template<class... Ts>
> > +overloaded(Ts...) -> overloaded<Ts...>;
> > +
> >  class VirtualCameraConfiguration : public CameraConfiguration
> >  {
> >  public:
> > @@ -95,7 +105,7 @@ private:
> >                 return static_cast<VirtualCameraData *>(camera->_d());
> >         }
> >
> > -       void initFrameGenerator(Camera *camera);
> > +       bool initFrameGenerator(Camera *camera);
> >
> >         DmaBufAllocator dmaBufAllocator_;
> >
> > @@ -104,15 +114,19 @@ private:
> >
> >  VirtualCameraData::VirtualCameraData(PipelineHandler *pipe,
> >                                      const std::vector<Resolution> &supportedResolutions)
> > -       : Camera::Private(pipe), supportedResolutions_(supportedResolutions)
> > +       : Camera::Private(pipe)
> >  {
> > -       for (const auto &resolution : supportedResolutions_) {
> > -               if (minResolutionSize_.isNull() || minResolutionSize_ > resolution.size)
> > -                       minResolutionSize_ = resolution.size;
> > +       config_.resolutions = supportedResolutions;
> > +       for (const auto &resolution : config_.resolutions) {
> > +               if (config_.minResolutionSize.isNull() || config_.minResolutionSize > resolution.size)
> > +                       config_.minResolutionSize = resolution.size;
> >
> > -               maxResolutionSize_ = std::max(maxResolutionSize_, resolution.size);
> > +               config_.maxResolutionSize = std::max(config_.maxResolutionSize, resolution.size);
> >         }
> >
> > +       properties_.set(properties::PixelArrayActiveAreas,
> > +                       { Rectangle(config_.maxResolutionSize) });
> > +
> >         /* \todo Support multiple streams and pass multi_stream_test */
> >         streamConfigs_.resize(kMaxStream);
> >  }
> > @@ -140,7 +154,7 @@ CameraConfiguration::Status VirtualCameraConfiguration::validate()
> >         for (StreamConfiguration &cfg : config_) {
> >                 bool adjusted = false;
> >                 bool found = false;
> > -               for (const auto &resolution : data_->supportedResolutions_) {
> > +               for (const auto &resolution : data_->config_.resolutions) {
> >                         if (resolution.size.width == cfg.size.width &&
> >                             resolution.size.height == cfg.size.height) {
> >                                 found = true;
> > @@ -155,7 +169,7 @@ CameraConfiguration::Status VirtualCameraConfiguration::validate()
> >                          * Defining the default logic in PipelineHandler to
> >                          * find the closest resolution would be nice.
> >                          */
> > -                       cfg.size = data_->maxResolutionSize_;
> > +                       cfg.size = data_->config_.maxResolutionSize;
> >                         status = Adjusted;
> >                         adjusted = true;
> >                 }
> > @@ -224,12 +238,12 @@ PipelineHandlerVirtual::generateConfiguration(Camera *camera,
> >
> >                 std::map<PixelFormat, std::vector<SizeRange>> streamFormats;
> >                 PixelFormat pixelFormat = formats::NV12;
> > -               streamFormats[pixelFormat] = { { data->minResolutionSize_,
> > -                                                data->maxResolutionSize_ } };
> > +               streamFormats[pixelFormat] = { { data->config_.minResolutionSize,
> > +                                                data->config_.maxResolutionSize } };
> >                 StreamFormats formats(streamFormats);
> >                 StreamConfiguration cfg(formats);
> >                 cfg.pixelFormat = pixelFormat;
> > -               cfg.size = data->maxResolutionSize_;
> > +               cfg.size = data->config_.maxResolutionSize;
> >                 cfg.bufferCount = VirtualCameraConfiguration::kBufferCount;
> >
> >                 config->addConfiguration(cfg);
> > @@ -246,6 +260,7 @@ int PipelineHandlerVirtual::configure(Camera *camera,
> >         VirtualCameraData *data = cameraData(camera);
> >         for (auto [i, c] : utils::enumerate(*config)) {
> >                 c.setStream(&data->streamConfigs_[i].stream);
> > +               /* Start reading the images/generating test patterns */
> >                 data->streamConfigs_[i].frameGenerator->configure(c.size);
> >         }
> >
> > @@ -315,56 +330,67 @@ bool PipelineHandlerVirtual::match([[maybe_unused]] DeviceEnumerator *enumerator
> >
> >         created_ = true;
> >
> > -       /* \todo Add virtual cameras according to a config file. */
> > -
> > -       std::vector<VirtualCameraData::Resolution> supportedResolutions;
> > -       supportedResolutions.resize(2);
> > -       supportedResolutions[0] = { .size = Size(1920, 1080), .frameRates = { 30 } };
> > -       supportedResolutions[1] = { .size = Size(1280, 720), .frameRates = { 30 } };
> > -
> > -       std::unique_ptr<VirtualCameraData> data =
> > -               std::make_unique<VirtualCameraData>(this, supportedResolutions);
> > -
> > -       data->properties_.set(properties::Location, properties::CameraLocationFront);
> > -       data->properties_.set(properties::Model, "Virtual Video Device");
> > -       data->properties_.set(properties::PixelArrayActiveAreas, { Rectangle(Size(1920, 1080)) });
> > -
> > -       /* \todo Set FrameDurationLimits based on config. */
> > -       ControlInfoMap::Map controls;
> > -       int64_t min_frame_duration = 33333, max_frame_duration = 33333;
> > -       controls[&controls::FrameDurationLimits] = ControlInfo(min_frame_duration, max_frame_duration);
> > -       std::vector<ControlValue> supportedFaceDetectModes{
> > -               static_cast<int32_t>(controls::draft::FaceDetectModeOff),
> > -       };
> > -       controls[&controls::draft::FaceDetectMode] = ControlInfo(supportedFaceDetectModes);
> > -       data->controlInfo_ = ControlInfoMap(std::move(controls), controls::controls);
> > -
> > -       /* Create and register the camera. */
> > -       std::set<Stream *> streams;
> > -       for (auto &streamConfig : data->streamConfigs_)
> > -               streams.insert(&streamConfig.stream);
> > +       File file(configurationFile("virtual", "virtual.yaml"));
>
> This tells me that the sample virtual.yaml file should probably be
> installed in the libcamera data path...

Yes, updated the README.md.


>
> This version doesn't currently apply cleanly to master, but I did have
> an branch with it on - and it rebased easily.

Rebased on tot in the next version.


>
> But it needs a new version posting at least to solve the SoB issues and
> so with the above tackled you can add
>
> Reviewed-by: Kieran Bingham <kieran.bingham at ideasonboard.com>
>
>
> > +       bool isOpen = file.open(File::OpenModeFlag::ReadOnly);
> > +       if (!isOpen) {
> > +               LOG(Virtual, Error) << "Failed to open config file: " << file.fileName();
> > +               return false;
> > +       }
> >
> > -       const std::string id = "Virtual0";
> > -       std::shared_ptr<Camera> camera = Camera::create(std::move(data), id, streams);
> > +       ConfigParser parser;
> > +       auto configData = parser.parseConfigFile(file, this);
> > +       if (configData.size() == 0) {
> > +               LOG(Virtual, Error) << "Failed to parse any cameras from the config file: "
> > +                                   << file.fileName();
> > +               return false;
> > +       }
> >
> > -       initFrameGenerator(camera.get());
> > +       /* Configure and register cameras with configData */
> > +       for (auto &data : configData) {
> > +               std::set<Stream *> streams;
> > +               for (auto &streamConfig : data->streamConfigs_)
> > +                       streams.insert(&streamConfig.stream);
> > +               std::string id = data->config_.id;
> > +               std::shared_ptr<Camera> camera = Camera::create(std::move(data), id, streams);
> > +
> > +               if (!initFrameGenerator(camera.get())) {
> > +                       LOG(Virtual, Error) << "Failed to initialize frame "
> > +                                           << "generator for camera: " << id;
> > +                       continue;
> > +               }
> >
> > -       registerCamera(std::move(camera));
> > +               registerCamera(std::move(camera));
> > +       }
> >
> >         resetCreated_ = true;
> >
> >         return true;
> >  }
> >
> > -void PipelineHandlerVirtual::initFrameGenerator(Camera *camera)
> > +bool PipelineHandlerVirtual::initFrameGenerator(Camera *camera)
> >  {
> >         auto data = cameraData(camera);
> > -       for (auto &streamConfig : data->streamConfigs_) {
> > -               if (data->testPattern_ == TestPattern::DiagonalLines)
> > -                       streamConfig.frameGenerator = std::make_unique<DiagonalLinesGenerator>();
> > -               else
> > -                       streamConfig.frameGenerator = std::make_unique<ColorBarsGenerator>();
> > -       }
> > +       auto &frame = data->config_.frame;
> > +       std::visit(overloaded{
> > +                          [&](TestPattern &testPattern) {
> > +                                  for (auto &streamConfig : data->streamConfigs_) {
> > +                                          if (testPattern == TestPattern::DiagonalLines)
> > +                                                  streamConfig.frameGenerator = std::make_unique<DiagonalLinesGenerator>();
> > +                                          else
> > +                                                  streamConfig.frameGenerator = std::make_unique<ColorBarsGenerator>();
> > +                                  }
> > +                          },
> > +                          [&](ImageFrames &imageFrames) {
> > +                                  for (auto &streamConfig : data->streamConfigs_)
> > +                                          streamConfig.frameGenerator = ImageFrameGenerator::create(imageFrames);
> > +                          } },
> > +                  frame);
> > +
> > +       for (auto &streamConfig : data->streamConfigs_)
> > +               if (!streamConfig.frameGenerator)
> > +                       return false;
> > +
> > +       return true;
> >  }
> >
> >  REGISTER_PIPELINE_HANDLER(PipelineHandlerVirtual, "virtual")
> > diff --git a/src/libcamera/pipeline/virtual/virtual.h b/src/libcamera/pipeline/virtual/virtual.h
> > index 0b15f8d9c..8c4d6ae64 100644
> > --- a/src/libcamera/pipeline/virtual/virtual.h
> > +++ b/src/libcamera/pipeline/virtual/virtual.h
> > @@ -7,6 +7,8 @@
> >
> >  #pragma once
> >
> > +#include <string>
> > +#include <variant>
> >  #include <vector>
> >
> >  #include <libcamera/geometry.h>
> > @@ -15,10 +17,14 @@
> >  #include "libcamera/internal/camera.h"
> >  #include "libcamera/internal/pipeline_handler.h"
> >
> > +#include "frame_generator.h"
> > +#include "image_frame_generator.h"
> >  #include "test_pattern_generator.h"
> >
> >  namespace libcamera {
> >
> > +using VirtualFrame = std::variant<TestPattern, ImageFrames>;
> > +
> >  class VirtualCameraData : public Camera::Private
> >  {
> >  public:
> > @@ -26,23 +32,28 @@ public:
> >
> >         struct Resolution {
> >                 Size size;
> > -               std::vector<int> frameRates;
> > +               std::vector<int64_t> frameRates;
> >         };
> >         struct StreamConfig {
> >                 Stream stream;
> >                 std::unique_ptr<FrameGenerator> frameGenerator;
> >         };
> > +       /* The config file is parsed to the Configuration struct */
> > +       struct Configuration {
> > +               std::string id;
> > +               std::vector<Resolution> resolutions;
> > +               VirtualFrame frame;
> > +
> > +               Size maxResolutionSize;
> > +               Size minResolutionSize;
> > +       };
> >
> >         VirtualCameraData(PipelineHandler *pipe,
> >                           const std::vector<Resolution> &supportedResolutions);
> >
> >         ~VirtualCameraData() = default;
> >
> > -       TestPattern testPattern_ = TestPattern::ColorBars;
> > -
> > -       const std::vector<Resolution> supportedResolutions_;
> > -       Size maxResolutionSize_;
> > -       Size minResolutionSize_;
> > +       Configuration config_;
> >
> >         std::vector<StreamConfig> streamConfigs_;
> >  };
> > --
> > 2.47.0.rc0.187.ge670bccf7e-goog
> >


More information about the libcamera-devel mailing list