summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/libcamera/pipeline/virtual/README.md65
-rw-r--r--src/libcamera/pipeline/virtual/config_parser.cpp260
-rw-r--r--src/libcamera/pipeline/virtual/config_parser.h39
-rw-r--r--src/libcamera/pipeline/virtual/data/virtual.yaml36
-rw-r--r--src/libcamera/pipeline/virtual/image_frame_generator.cpp16
-rw-r--r--src/libcamera/pipeline/virtual/image_frame_generator.h5
-rw-r--r--src/libcamera/pipeline/virtual/meson.build1
-rw-r--r--src/libcamera/pipeline/virtual/virtual.cpp126
-rw-r--r--src/libcamera/pipeline/virtual/virtual.h23
9 files changed, 501 insertions, 70 deletions
diff --git a/src/libcamera/pipeline/virtual/README.md b/src/libcamera/pipeline/virtual/README.md
new file mode 100644
index 00000000..a9f39c15
--- /dev/null
+++ b/src/libcamera/pipeline/virtual/README.md
@@ -0,0 +1,65 @@
+# Virtual Pipeline Handler
+
+Virtual pipeline handler emulates fake external camera(s) for testing.
+
+## Parse config file and register cameras
+
+- A sample config file is located at `src/libcamera/pipeline/virtual/data/virtual.yaml`.
+- If libcamera is installed, the config file should be installed at
+ `share/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`.
+ - The test patterns are "bars" which means color bars, and "lines" which means
+ diagonal lines.
+- `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 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
+ "CameraLocationFront", "CameraLocationBack", and "CameraLocationExternal".
+- `model` (`string`, default="Unknown"): The model name of the camera.
+
+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 00000000..0cbfe39b
--- /dev/null
+++ b/src/libcamera/pipeline/virtual/config_parser.cpp
@@ -0,0 +1,260 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Google Inc.
+ *
+ * Virtual cameras helper to parse config file
+ */
+
+#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>("CameraLocationFront");
+
+ /* Default value is properties::CameraLocationFront */
+ 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;
+}
+
+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 00000000..d2000de9
--- /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.
+ *
+ * Virtual cameras helper to parse config file
+ */
+
+#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 00000000..20471bb9
--- /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: "CameraLocationFront"
+ model: "Virtual Video Device"
+"Virtual1":
+ supported_formats:
+ - width: 800
+ height: 600
+ frame_rates:
+ - 60
+ test_pattern: "bars"
+ location: "CameraLocationBack"
+ model: "Virtual Video Device1"
+"Virtual2":
+ supported_formats:
+ - width: 400
+ height: 300
+ test_pattern: "lines"
+ location: "CameraLocationFront"
+ 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 e140969c..2baef588 100644
--- a/src/libcamera/pipeline/virtual/image_frame_generator.cpp
+++ b/src/libcamera/pipeline/virtual/image_frame_generator.cpp
@@ -39,15 +39,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()
@@ -87,6 +79,8 @@ ImageFrameGenerator::create(ImageFrames &imageFrames)
Size(width, height) });
}
+ ASSERT(!imageFrameGenerator->imageFrameDatas_.empty());
+
return imageFrameGenerator;
}
@@ -103,7 +97,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;
@@ -138,7 +132,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 e072a47b..42a077ba 100644
--- a/src/libcamera/pipeline/virtual/image_frame_generator.h
+++ b/src/libcamera/pipeline/virtual/image_frame_generator.h
@@ -9,9 +9,9 @@
#include <filesystem>
#include <memory>
-#include <optional>
#include <stdint.h>
#include <sys/types.h>
+#include <vector>
#include "frame_generator.h"
@@ -19,8 +19,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 bb38c193..4786fe2e 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 04fb08bb..cec8a85b 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"));
+ 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 297b6b67..92ad7d4a 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_;
};