[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