libcamera: virtual: Read config and register cameras based on the config

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@google.com>
Co-developed-by: Harvey Yang <chenghaoyang@chromium.org>
Signed-off-by: Harvey Yang <chenghaoyang@chromium.org>
Co-developed-by: Yunke Cao <yunkec@chromium.org>
Signed-off-by: Yunke Cao <yunkec@chromium.org>
Co-developed-by: Tomasz Figa <tfiga@chromium.org>
Signed-off-by: Tomasz Figa <tfiga@chromium.org>
Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
This commit is contained in:
Harvey Yang
2024-10-22 07:43:42 +00:00
committed by Kieran Bingham
parent 61195be6c8
commit 2716a95852
9 changed files with 500 additions and 69 deletions

View File

@@ -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.

View File

@@ -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 */

View File

@@ -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 */

View File

@@ -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"

View File

@@ -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,

View File

@@ -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

View File

@@ -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',

View File

@@ -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. */
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;
}
std::vector<VirtualCameraData::Resolution> supportedResolutions;
supportedResolutions.resize(2);
supportedResolutions[0] = { .size = Size(1920, 1080), .frameRates = { 30 } };
supportedResolutions[1] = { .size = Size(1280, 720), .frameRates = { 30 } };
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;
}
std::unique_ptr<VirtualCameraData> data =
std::make_unique<VirtualCameraData>(this, supportedResolutions);
/* 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);
data->properties_.set(properties::Location, properties::CameraLocationFront);
data->properties_.set(properties::Model, "Virtual Video Device");
data->properties_.set(properties::PixelArrayActiveAreas, { Rectangle(Size(1920, 1080)) });
if (!initFrameGenerator(camera.get())) {
LOG(Virtual, Error) << "Failed to initialize frame "
<< "generator for camera: " << id;
continue;
}
/* \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);
const std::string id = "Virtual0";
std::shared_ptr<Camera> camera = Camera::create(std::move(data), id, streams);
initFrameGenerator(camera.get());
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")

View File

@@ -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_;
};