From patchwork Fri Oct 4 09:59:19 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Cheng-Hao Yang X-Patchwork-Id: 21508 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 3194DC3257 for ; Fri, 4 Oct 2024 10:00:16 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id BD97163537; Fri, 4 Oct 2024 12:00:15 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=chromium.org header.i=@chromium.org header.b="Q3Dy4BLf"; dkim-atps=neutral Received: from mail-pf1-x42b.google.com (mail-pf1-x42b.google.com [IPv6:2607:f8b0:4864:20::42b]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 2670F6352D for ; Fri, 4 Oct 2024 12:00:04 +0200 (CEST) Received: by mail-pf1-x42b.google.com with SMTP id d2e1a72fcca58-71de776bc69so286380b3a.1 for ; Fri, 04 Oct 2024 03:00:04 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; t=1728036002; x=1728640802; darn=lists.libcamera.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=ewx9Da/rZEtCO2SvT1OinnkZN0t86Pz4pHb1XX8yOj0=; b=Q3Dy4BLfpyLjWLdCocOUSqnrJ52WaIg3bodpvB1gHSer778l8ENmkd0kl79fxD0H09 iEmZlIN+2ZJslRAqy87YcgjBwQwrH9zLQDI88JeK2wOKfcJS/Nete4HyHgPAPmPGggh7 tn4vQA57GrAcqT4/7LSgQpedLJrdtYBqTgDWU= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1728036002; x=1728640802; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=ewx9Da/rZEtCO2SvT1OinnkZN0t86Pz4pHb1XX8yOj0=; b=Yl7iN5mjYQjdl/yX8rw6iGZnqIPYzwvat1v+oxDCHwCA5HLzE/halgMtrGEZLdFYL+ HUvUKbCVaSt41GADOswO3KT/SEKMpiKds1HVsuwuEQAir57CWhnjO5aS8RarNUviLKP0 ae7wxJsdKjN+MQUgDtkHP7kAA2pz3up7IeXMNtWIVBpHH5Z1SQ7OygqSpWWz0Fk1nL6j 2v4798UeLY4zzmXzFSbqC7HidcN10+XN/oXTM0uLU/PijwDHo0nznu0XshyI/5b4DH9+ zhyIpAfjPxJEGI6I6HsVsn4IPjNHTF1rx2wSDmsznAV4kZe2ZCKRbwQHNJQ3hoqgl1v3 rHew== X-Gm-Message-State: AOJu0Yxdf3CgsbZ7ecSpZ5ET/G1KWzajYuN2sDCJ40uSTtJ8w45gMckp V7KRBKBk0QSHJfTLpkPoAEFaKEUiVuzI83USYitue7A5/4xi2GUuhF/Iykqad+SRd6tsA9p4zxy 1Jw== X-Google-Smtp-Source: AGHT+IGqCNIu+TF3ddqrncp3hVK9gXRiYCpjx6ulzUAwgezySF4WW6Zs74U0q5fjwsqcck1cIHz0ZQ== X-Received: by 2002:a05:6a00:886:b0:702:3e36:b7c4 with SMTP id d2e1a72fcca58-71de23a6620mr3113927b3a.5.1728036001682; Fri, 04 Oct 2024 03:00:01 -0700 (PDT) Received: from chenghaoyang-low.c.googlers.com.com (27.247.221.35.bc.googleusercontent.com. [35.221.247.27]) by smtp.gmail.com with ESMTPSA id d2e1a72fcca58-71ded2ad931sm316911b3a.41.2024.10.04.03.00.00 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 04 Oct 2024 03:00:01 -0700 (PDT) From: Harvey Yang X-Google-Original-From: Harvey Yang To: libcamera-devel@lists.libcamera.org Cc: Harvey Yang , Konami Shu , Harvey Yang , Yunke Cao , Tomasz Figa Subject: [PATCH 15 6/7] libcamera: virtual: Read config and register cameras based on the config Date: Fri, 4 Oct 2024 09:59:19 +0000 Message-ID: <20241004095946.264537-7-chenghaoyang@google.com> X-Mailer: git-send-email 2.47.0.rc0.187.ge670bccf7e-goog In-Reply-To: <20241004095946.264537-1-chenghaoyang@google.com> References: <20241004095946.264537-1-chenghaoyang@google.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" 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 Co-developed-by: Harvey Yang Co-developed-by: Yunke Cao Co-developed-by: Tomasz Figa Reviewed-by: Kieran Bingham --- 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. + +## Parse config file and register cameras + +- The config file is located at `src/libcamera/pipeline/virtual/data/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. + - 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 + 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 + */ + +#include "config_parser.h" + +#include +#include + +#include + +#include +#include + +#include "libcamera/internal/pipeline_handler.h" +#include "libcamera/internal/yaml_parser.h" + +#include "virtual.h" + +namespace libcamera { + +LOG_DECLARE_CATEGORY(Virtual) + +std::vector> +ConfigParser::parseConfigFile(File &file, PipelineHandler *pipe) +{ + std::vector> configurations; + + std::unique_ptr 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 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 supportedFaceDetectModes{ + static_cast(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 +ConfigParser::parseCameraConfigData(const YamlObject &cameraConfigData, + PipelineHandler *pipe) +{ + std::vector resolutions; + if (parseSupportedFormats(cameraConfigData, &resolutions)) + return nullptr; + + std::unique_ptr data = + std::make_unique(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 *resolutions) +{ + if (cameraConfigData.contains("supported_formats")) { + const YamlObject &supportedResolutions = cameraConfigData["supported_formats"]; + + for (const YamlObject &supportedResolution : supportedResolutions.asList()) { + unsigned int width = supportedResolution["width"].get(1920); + unsigned int height = supportedResolution["height"].get(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 frameRates; + if (supportedResolution.contains("frame_rates")) { + auto frameRatesList = + supportedResolution["frame_rates"].getList(); + 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(""); + + 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(); + + if (!path) { + LOG(Virtual, Error) << "Test pattern or path should be specified."; + return -EINVAL; + } + + std::vector 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("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"; + return -EINVAL; + } + + return 0; +} + +int ConfigParser::parseModel(const YamlObject &cameraConfigData, VirtualCameraData *data) +{ + std::string model = cameraConfigData["model"].get("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 + */ + +#pragma once + +#include +#include + +#include + +#include "libcamera/internal/pipeline_handler.h" +#include "libcamera/internal/yaml_parser.h" + +#include "virtual.h" + +namespace libcamera { + +class ConfigParser +{ +public: + std::vector> + parseConfigFile(File &file, PipelineHandler *pipe); + +private: + std::unique_ptr + parseCameraConfigData(const YamlObject &cameraConfigData, PipelineHandler *pipe); + + int parseSupportedFormats(const YamlObject &cameraConfigData, + std::vector *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 #include -#include #include #include +#include #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 number; + std::vector 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 +struct overloaded : Ts... { + using Ts::operator()...; +}; +template +overloaded(Ts...) -> overloaded; + class VirtualCameraConfiguration : public CameraConfiguration { public: @@ -95,7 +105,7 @@ private: return static_cast(camera->_d()); } - void initFrameGenerator(Camera *camera); + bool initFrameGenerator(Camera *camera); DmaBufAllocator dmaBufAllocator_; @@ -104,15 +114,19 @@ private: VirtualCameraData::VirtualCameraData(PipelineHandler *pipe, const std::vector &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> 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 supportedResolutions; - supportedResolutions.resize(2); - supportedResolutions[0] = { .size = Size(1920, 1080), .frameRates = { 30 } }; - supportedResolutions[1] = { .size = Size(1280, 720), .frameRates = { 30 } }; - - std::unique_ptr data = - std::make_unique(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 supportedFaceDetectModes{ - static_cast(controls::draft::FaceDetectModeOff), - }; - controls[&controls::draft::FaceDetectMode] = ControlInfo(supportedFaceDetectModes); - data->controlInfo_ = ControlInfoMap(std::move(controls), controls::controls); - - /* Create and register the camera. */ - std::set 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::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 streams; + for (auto &streamConfig : data->streamConfigs_) + streams.insert(&streamConfig.stream); + std::string id = data->config_.id; + std::shared_ptr 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(); - else - streamConfig.frameGenerator = std::make_unique(); - } + auto &frame = data->config_.frame; + std::visit(overloaded{ + [&](TestPattern &testPattern) { + for (auto &streamConfig : data->streamConfigs_) { + if (testPattern == TestPattern::DiagonalLines) + streamConfig.frameGenerator = std::make_unique(); + else + streamConfig.frameGenerator = std::make_unique(); + } + }, + [&](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 +#include #include #include @@ -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; + class VirtualCameraData : public Camera::Private { public: @@ -26,23 +32,28 @@ public: struct Resolution { Size size; - std::vector frameRates; + std::vector frameRates; }; struct StreamConfig { Stream stream; std::unique_ptr frameGenerator; }; + /* The config file is parsed to the Configuration struct */ + struct Configuration { + std::string id; + std::vector resolutions; + VirtualFrame frame; + + Size maxResolutionSize; + Size minResolutionSize; + }; VirtualCameraData(PipelineHandler *pipe, const std::vector &supportedResolutions); ~VirtualCameraData() = default; - TestPattern testPattern_ = TestPattern::ColorBars; - - const std::vector supportedResolutions_; - Size maxResolutionSize_; - Size minResolutionSize_; + Configuration config_; std::vector streamConfigs_; };