From patchwork Tue Aug 20 16:23:36 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Harvey Yang X-Patchwork-Id: 20971 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 82BCBC323E for ; Tue, 20 Aug 2024 17:22:29 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 941B96340B; Tue, 20 Aug 2024 19:22:27 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=chromium.org header.i=@chromium.org header.b="Hp59HjFI"; dkim-atps=neutral Received: from mail-wr1-x42d.google.com (mail-wr1-x42d.google.com [IPv6:2a00:1450:4864:20::42d]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 3C3C7633D2 for ; Tue, 20 Aug 2024 19:22:18 +0200 (CEST) Received: by mail-wr1-x42d.google.com with SMTP id ffacd0b85a97d-3719753d365so3147396f8f.2 for ; Tue, 20 Aug 2024 10:22:18 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; t=1724174537; x=1724779337; 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=2Ay//vNC4qtVEWwzRLa7yylN/5+6F1GspCmgnRirwsU=; b=Hp59HjFIr9uyxDEWXWUUskobFd3qgtzA9JC+X0kE8kuSfAhww2fQwoDf89u+Vrd+Dz U9Gi+et+MNctkumluJlzxV/2mx7iERyHpbMBmqhX2txRnbOhv0ZF+xmkhHzel2thtJOR NopjEFwzYblQG5yHyxU/1JaW1Ov9wS1CMJXmY= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1724174537; x=1724779337; 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=2Ay//vNC4qtVEWwzRLa7yylN/5+6F1GspCmgnRirwsU=; b=mv9ZduSlbbAt4n4tZwZqGchBNeScoR0duPB5LBW6NYRj8Gf6MTJppypdDAZI3VxoYK S44e9n/O+xsl1LhHSqZ5iZgkR98NAGDsqFggB0IOWoNsAFLAVNTY4ka0LwfHOBZq0Yi3 /JlDaNlJaVEHYoe4hlt5qFj+qdR32y/4l9dKDZiVLRnKhd+CR5F99egbdlcMwayhAb8Y 0lRI8Ic2KohvIPZdZi3vcprFAabeA1McGTd3V7vhPW1ukBQp3bdL8VGYDttf+P26vigJ qZIjLsQ3bDAV+GxUeM0unnjnd3MMF3M6p4X081ZfwTccHtloTnZC/pdfuxJlylGG1nT0 hdwQ== X-Gm-Message-State: AOJu0YzayMypfybwLBdzydUsHSCqS69wG/yGuXT+unCwLK80dSJQqOpm ukW7JTFhKIs3eQ5nNIxvPafrw6rmbGU71xMuOD+PLzrTfX7dz4Qbs63DsLIdXwMkjiMakfncSI5 AzMBwa1M= X-Google-Smtp-Source: AGHT+IFIcxNLCZJTBf6g6tHR2TQooZF7X8Wptxq3VaM/q1/XlByCWmIjXH64IPTe2SUljJoxtqvE8w== X-Received: by 2002:adf:f0c9:0:b0:368:2f01:307a with SMTP id ffacd0b85a97d-3719469f97cmr9846690f8f.46.1724174537249; Tue, 20 Aug 2024 10:22:17 -0700 (PDT) Received: from chenghaoyang-germany.c.googlers.com.com (161.126.77.34.bc.googleusercontent.com. [34.77.126.161]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-42ab6e90db5sm30328575e9.0.2024.08.20.10.22.16 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 20 Aug 2024 10:22:16 -0700 (PDT) From: Harvey Yang X-Google-Original-From: Harvey Yang To: libcamera-devel@lists.libcamera.org Cc: Konami Shu , Harvey Yang , Yunke Cao , Tomasz Figa Subject: [PATCH v9 5/8] libcamera: pipeline: Read config and register cameras based on the config Date: Tue, 20 Aug 2024 16:23:36 +0000 Message-ID: <20240820172202.526547-6-chenghaoyang@google.com> X-Mailer: git-send-email 2.46.0.184.g6999bdac58-goog In-Reply-To: <20240820172202.526547-1-chenghaoyang@google.com> References: <20240820172202.526547-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" From: Konami Shu - Use Yaml::Parser to parse the config - Add config file at "virtual/data/virtual.yaml" - README.md contains the documentation for the format of config file and the implementation of Parser class. - Add Parser class to parse config file in Virtual pipeline handler - Add header file of virtual.cpp to use VirtualCameraData class in Parser class - Parse test patterns - Raise Error when the width of a resolution is an odd number Signed-off-by: Konami Shu Co-developed-by: Harvey Yang Co-developed-by: Yunke Cao Co-developed-by: Tomasz Figa --- src/libcamera/pipeline/virtual/README.md | 68 ++++++ .../pipeline/virtual/data/virtual.yaml | 49 +++++ src/libcamera/pipeline/virtual/meson.build | 1 + src/libcamera/pipeline/virtual/parser.cpp | 198 ++++++++++++++++++ src/libcamera/pipeline/virtual/parser.h | 45 ++++ src/libcamera/pipeline/virtual/virtual.cpp | 47 +++-- src/libcamera/pipeline/virtual/virtual.h | 6 +- 7 files changed, 389 insertions(+), 25 deletions(-) create mode 100644 src/libcamera/pipeline/virtual/README.md create mode 100644 src/libcamera/pipeline/virtual/data/virtual.yaml create mode 100644 src/libcamera/pipeline/virtual/parser.cpp create mode 100644 src/libcamera/pipeline/virtual/parser.h diff --git a/src/libcamera/pipeline/virtual/README.md b/src/libcamera/pipeline/virtual/README.md new file mode 100644 index 000000000..27d6283df --- /dev/null +++ b/src/libcamera/pipeline/virtual/README.md @@ -0,0 +1,68 @@ +# 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. The list has to be two values of the lower bound and the upper bound of the frame rate. +- `test_pattern` (`string`, default="bars"): Which test pattern to use as frames. The options are "bars", "lines". +- `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. + +A sample config file: +``` +--- +"Virtual0": + supported_formats: + - width: 1920 + height: 1080 + frame_rates: + - 30 + - 60 + - width: 1680 + height: 1050 + frame_rates: + - 70 + - 80 + test_pattern: "bars" + location: "front" + model: "Virtual Video Device" +"Virtual1": + supported_formats: + - width: 800 + test_pattern: "lines" + location: "back" + model: "Virtual Video Device1" +"Virtual2": +``` + +### 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. + - `parseTestPattern()`: Parses `test_pattern` 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/data/virtual.yaml b/src/libcamera/pipeline/virtual/data/virtual.yaml new file mode 100644 index 000000000..4eb239e24 --- /dev/null +++ b/src/libcamera/pipeline/virtual/data/virtual.yaml @@ -0,0 +1,49 @@ +# 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: + - 30 + - 60 + test_pattern: + location: "back" + model: "Virtual Video Device1" +"Virtual2": + supported_formats: + - width: 100 + height: 100 + test_pattern: "lines" + location: "front" + model: "Virtual Video Device2" +"Virtual3": + supported_formats: + - width: 100 + height: 100 + - width: 800 + height: 600 + - width: 1920 + height: 1080 + frame_rates: + - 20 + - 30 + location: "a" + model: "Virtual Video Device3" +"Virtual4": diff --git a/src/libcamera/pipeline/virtual/meson.build b/src/libcamera/pipeline/virtual/meson.build index e1e65e68d..2e82e64cb 100644 --- a/src/libcamera/pipeline/virtual/meson.build +++ b/src/libcamera/pipeline/virtual/meson.build @@ -3,6 +3,7 @@ libcamera_sources += files([ 'virtual.cpp', 'test_pattern_generator.cpp', + 'parser.cpp', ]) libyuv_dep = dependency('libyuv', required : false) diff --git a/src/libcamera/pipeline/virtual/parser.cpp b/src/libcamera/pipeline/virtual/parser.cpp new file mode 100644 index 000000000..032c0cd9d --- /dev/null +++ b/src/libcamera/pipeline/virtual/parser.cpp @@ -0,0 +1,198 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2023, Google Inc. + * + * parser.cpp - Virtual cameras helper to parse config file + */ + +#include "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> Parser::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->id_ = cameraId; + ControlInfoMap::Map controls; + /* todo: Check which resolution's frame rate to be reported */ + controls[&controls::FrameDurationLimits] = + ControlInfo(int64_t(1000 / data->supportedResolutions_[0].frameRates[1]), + int64_t(1000 / data->supportedResolutions_[0].frameRates[0])); + data->controlInfo_ = ControlInfoMap(std::move(controls), controls::controls); + configurations.push_back(std::move(data)); + } + return configurations; +} + +std::unique_ptr Parser::parseCameraConfigData( + const YamlObject &cameraConfigData, PipelineHandler *pipe) +{ + std::unique_ptr data = std::make_unique(pipe); + + if (parseSupportedFormats(cameraConfigData, data.get())) + return nullptr; + + if (parseTestPattern(cameraConfigData, data.get())) + return nullptr; + + if (parseLocation(cameraConfigData, data.get())) + return nullptr; + + if (parseModel(cameraConfigData, data.get())) + return nullptr; + + return data; +} + +int Parser::parseSupportedFormats( + const YamlObject &cameraConfigData, VirtualCameraData *data) +{ + Size activeResolution{ 0, 0 }; + 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().value(); + if (frameRatesList.size() != 2) { + LOG(Virtual, Error) << "frame_rates needs to be the two edge values of a range"; + return -EINVAL; + } + if (frameRatesList[0] > frameRatesList[1]) { + LOG(Virtual, Error) << "frame_rates's first value(lower bound) is higher than the second value(upper bound)"; + return -EINVAL; + } + frameRates.push_back(frameRatesList[0]); + frameRates.push_back(frameRatesList[1]); + } else { + frameRates.push_back(30); + frameRates.push_back(60); + } + + data->supportedResolutions_.emplace_back( + VirtualCameraData::Resolution{ Size{ width, height }, + frameRates }); + + activeResolution = std::max(activeResolution, Size{ width, height }); + } + } else { + data->supportedResolutions_.emplace_back( + VirtualCameraData::Resolution{ Size{ 1920, 1080 }, + { 30, 60 } }); + activeResolution = Size(1920, 1080); + } + + data->properties_.set(properties::PixelArrayActiveAreas, + { Rectangle(activeResolution) }); + + return 0; +} + +int Parser::parseTestPattern( + const YamlObject &cameraConfigData, VirtualCameraData *data) +{ + std::string testPattern = cameraConfigData["test_pattern"].get().value(); + + /* Default value is "bars" */ + if (testPattern == "bars" || testPattern == "") { + data->testPattern_ = TestPattern::ColorBars; + } else if (testPattern == "lines") { + data->testPattern_ = TestPattern::DiagonalLines; + } else { + LOG(Virtual, Error) << "Test pattern: " << testPattern + << "is not supported"; + return -EINVAL; + } + return 0; +} + +int Parser::parseLocation( + const YamlObject &cameraConfigData, VirtualCameraData *data) +{ + std::string location = cameraConfigData["location"].get().value(); + + /* Default value is properties::CameraLocationFront */ + if (location == "front" || location == "") { + 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 Parser::parseModel( + const YamlObject &cameraConfigData, VirtualCameraData *data) +{ + std::string model = + cameraConfigData["model"].get().value(); + + /* Default value is "Unknown" */ + if (model == "") + data->properties_.set(properties::Model, "Unknown"); + else + data->properties_.set(properties::Model, model); + + return 0; +} + +} /* namespace libcamera */ diff --git a/src/libcamera/pipeline/virtual/parser.h b/src/libcamera/pipeline/virtual/parser.h new file mode 100644 index 000000000..a377d8aa1 --- /dev/null +++ b/src/libcamera/pipeline/virtual/parser.h @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2023, Google Inc. + * + * 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 Parser +{ +public: + Parser() {} + ~Parser() = default; + + std::vector> + parseConfigFile(File &file, PipelineHandler *pipe); + +private: + std::unique_ptr parseCameraConfigData( + const YamlObject &cameraConfigData, PipelineHandler *pipe); + + int parseSupportedFormats( + const YamlObject &cameraConfigData, VirtualCameraData *data); + int parseTestPattern( + 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/virtual.cpp b/src/libcamera/pipeline/virtual/virtual.cpp index 357fdd035..0fe471f00 100644 --- a/src/libcamera/pipeline/virtual/virtual.cpp +++ b/src/libcamera/pipeline/virtual/virtual.cpp @@ -18,6 +18,10 @@ #include "libcamera/internal/camera.h" #include "libcamera/internal/formats.h" #include "libcamera/internal/pipeline_handler.h" +#include "libcamera/internal/yaml_parser.h" + +#include "frame_generator.h" +#include "parser.h" namespace libcamera { @@ -228,32 +232,31 @@ int PipelineHandlerVirtual::queueRequestDevice([[maybe_unused]] Camera *camera, bool PipelineHandlerVirtual::match([[maybe_unused]] DeviceEnumerator *enumerator) { - /* \todo Add virtual cameras according to a config file. */ - - std::unique_ptr data = std::make_unique(this); - - data->supportedResolutions_.resize(2); - data->supportedResolutions_[0] = { .size = Size(1920, 1080), .frame_rates = { 30 } }; - data->supportedResolutions_[1] = { .size = Size(1280, 720), .frame_rates = { 30, 60 } }; - - data->properties_.set(properties::Location, properties::CameraLocationFront); - data->properties_.set(properties::Model, "Virtual Video Device"); - data->properties_.set(properties::PixelArrayActiveAreas, { Rectangle(Size(1920, 1080)) }); + 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; + } - /* \todo Set FrameDurationLimits based on config. */ - ControlInfoMap::Map controls; - int64_t min_frame_duration = 30, max_frame_duration = 60; - controls[&controls::FrameDurationLimits] = ControlInfo(min_frame_duration, max_frame_duration); - data->controlInfo_ = ControlInfoMap(std::move(controls), controls::controls); + Parser 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; + } - /* Create and register the camera. */ - std::set streams{ &data->stream_ }; - const std::string id = "Virtual0"; - std::shared_ptr camera = Camera::create(std::move(data), id, streams); + /* Configure and register cameras with configData */ + for (auto &data : configData) { + std::set streams{ &data->stream_ }; + std::string id = data->id_; + std::shared_ptr camera = Camera::create(std::move(data), id, streams); - initFrameGenerator(camera.get()); + initFrameGenerator(camera.get()); - registerCamera(std::move(camera)); + registerCamera(std::move(camera)); + } return false; // Prevent infinite loops for now } diff --git a/src/libcamera/pipeline/virtual/virtual.h b/src/libcamera/pipeline/virtual/virtual.h index fecd9fa6f..c1ac4eb90 100644 --- a/src/libcamera/pipeline/virtual/virtual.h +++ b/src/libcamera/pipeline/virtual/virtual.h @@ -22,7 +22,7 @@ class VirtualCameraData : public Camera::Private public: struct Resolution { Size size; - std::vector frame_rates; + std::vector frameRates; }; VirtualCameraData(PipelineHandler *pipe) : Camera::Private(pipe) @@ -31,9 +31,9 @@ public: ~VirtualCameraData() = default; - TestPattern testPattern_; - + std::string id_; std::vector supportedResolutions_; + TestPattern testPattern_; Stream stream_;