[PATCH 15 6/7] libcamera: virtual: Read config and register cameras based on the config
Kieran Bingham
kieran.bingham at ideasonboard.com
Tue Oct 22 09:58:01 CEST 2024
Quoting Cheng-Hao Yang (2024-10-22 08:46:05)
> 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.
Ack,
>
> >
> > 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.
>
Thanks
> >
> >
> > > ---
> > > 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`.
I think that's the default - but it's probably worth mentioning in a way
that says it's will be installed in the 'datadir' which has a default
location of 'share/libcamera/...' as this is configurable when doing the
meson setup stage.
> > > +
> > > +### 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`.
Aha, I missed that one ;-)
>
> >
> >
> > > + 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.
The enum values have CameraLocation indeed- but they are stored in the
LocationEnum because they are associated with the Location property
which has an associated array and map to convert between integer and
ControlValues:
See $(BUILDDIR)/include/libcamera/property_ids.h
enum LocationEnum {
CameraLocationFront = 0,
CameraLocationBack = 1,
CameraLocationExternal = 2,
};
extern const std::array<const ControlValue, 3> LocationValues;
extern const std::map<std::string, int32_t> LocationNameValueMap;
extern const Control<int32_t> Location;
which is autogenerated.
>
> >
> >
> >
> > > + 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.
Thanks - let try and get this merged then :-)
--
Kieran
>
>
> >
> > 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