Patch Detail
Show a patch.
GET /api/1.1/patches/20706/?format=api
{ "id": 20706, "url": "https://patchwork.libcamera.org/api/1.1/patches/20706/?format=api", "web_url": "https://patchwork.libcamera.org/patch/20706/", "project": { "id": 1, "url": "https://patchwork.libcamera.org/api/1.1/projects/1/?format=api", "name": "libcamera", "link_name": "libcamera", "list_id": "libcamera_core", "list_email": "libcamera-devel@lists.libcamera.org", "web_url": "", "scm_url": "", "webscm_url": "" }, "msgid": "<20240726095626.3632402-6-chenghaoyang@chromium.org>", "date": "2024-07-26T09:54:26", "name": "[v6,5/7] libcamera: pipeline: Read config and register cameras based on the config", "commit_ref": null, "pull_url": null, "state": "superseded", "archived": false, "hash": "4039ab154fffc1d9804609a6ba53bdc6aa730fe1", "submitter": { "id": 117, "url": "https://patchwork.libcamera.org/api/1.1/people/117/?format=api", "name": "Cheng-Hao Yang", "email": "chenghaoyang@chromium.org" }, "delegate": null, "mbox": "https://patchwork.libcamera.org/patch/20706/mbox/", "series": [ { "id": 4461, "url": "https://patchwork.libcamera.org/api/1.1/series/4461/?format=api", "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=4461", "date": "2024-07-26T09:54:21", "name": "Add VirtualPipelineHandler", "version": 6, "mbox": "https://patchwork.libcamera.org/series/4461/mbox/" } ], "comments": "https://patchwork.libcamera.org/api/patches/20706/comments/", "check": "pending", "checks": "https://patchwork.libcamera.org/api/patches/20706/checks/", "tags": {}, "headers": { "Return-Path": "<libcamera-devel-bounces@lists.libcamera.org>", "X-Original-To": "parsemail@patchwork.libcamera.org", "Delivered-To": "parsemail@patchwork.libcamera.org", "Received": [ "from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id 3FEDABDC71\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri, 26 Jul 2024 09:56:50 +0000 (UTC)", "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id AAA8C6337F;\n\tFri, 26 Jul 2024 11:56:49 +0200 (CEST)", "from mail-pl1-x62e.google.com (mail-pl1-x62e.google.com\n\t[IPv6:2607:f8b0:4864:20::62e])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id CD4DF6336D\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 26 Jul 2024 11:56:40 +0200 (CEST)", "by mail-pl1-x62e.google.com with SMTP id\n\td9443c01a7336-1fc566ac769so4048515ad.1\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 26 Jul 2024 02:56:40 -0700 (PDT)", "from chenghaoyang-low.c.googlers.com.com\n\t(148.175.199.104.bc.googleusercontent.com. [104.199.175.148])\n\tby smtp.gmail.com with ESMTPSA id\n\td9443c01a7336-1fed7c7f63asm28396665ad.46.2024.07.26.02.56.37\n\t(version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n\tFri, 26 Jul 2024 02:56:38 -0700 (PDT)" ], "Authentication-Results": "lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=chromium.org header.i=@chromium.org\n\theader.b=\"ng63tnoU\"; dkim-atps=neutral", "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=chromium.org; s=google; t=1721987799; x=1722592599;\n\tdarn=lists.libcamera.org; \n\th=content-transfer-encoding:mime-version:references:in-reply-to\n\t:message-id:date:subject:cc:to:from:from:to:cc:subject:date\n\t:message-id:reply-to;\n\tbh=EhpPiLHnLHb4kvcO5QY0MVzu9cMTTnTFFGiNRM6KewI=;\n\tb=ng63tnoUV+jlnVhl+aHnv5CYhXNbV6FVejCpJPFyX+uQmDYY+qtEtajyjroDnkQkwn\n\t86NxlKta9j0MfoPzYJIG7f1+X9+u48h8S+dcKYVPiLfUOWy3s9SyqbyUmHr7nRaSbwWl\n\tTnBNwyJV0dVv/6TCw1bZW+4jPuVP4pMOvStjU=", "X-Google-DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20230601; t=1721987799; x=1722592599;\n\th=content-transfer-encoding:mime-version:references:in-reply-to\n\t:message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc\n\t:subject:date:message-id:reply-to;\n\tbh=EhpPiLHnLHb4kvcO5QY0MVzu9cMTTnTFFGiNRM6KewI=;\n\tb=PP6oO5JHg7eQw7NScu+h7Hop6l33NF4fAqdqgZBZuM5DiVDdCwWPimS3OpQ+L6X4ja\n\tqQeeLdk86JGHj+DJ08gzcgOwIpHw3wmKQROxXKV2TuN28SH3xMxJ2HEvy62L9Zw7rM6D\n\taxrQcqRqPRtLQ1aoqDM8WZxGAKy8t8qWzvn70jdbFsLUBVFU7EXLSCgZ8cdImCl0yREy\n\t8DJnH8UaCRUSFzh4hEfk7AsaO+2fXSSimu1udDshg+dZ3FjtKAPbx3l+7Au8JLYze+Gf\n\t04DMlgxSnYNVzswESbpqbedZTe49q/Hkq14RZeSeXj/kOWQqJLbTOS95yqqvjusEqamp\n\tIzIQ==", "X-Gm-Message-State": "AOJu0YweaRS1uebHU0QOL4kiJoNsEwfBBYV9KKYpJSQ09SOpL+cHYstx\n\tu2iQxfebhTkdu5g5bvAiWKynRb1m7HRYnZ4s9PWjD4WZFdJpEQU/lXb/t8/L2kHYFaEL6t/LsuE\n\tqMg==", "X-Google-Smtp-Source": "AGHT+IFCP9OYkxTMKsTbeKroTp7qRT2UGoEfAANGlpeN4buh1g69beCSNVdkZ9G2NOEI1UaKORKq8w==", "X-Received": "by 2002:a17:902:ce84:b0:1fb:779e:4fca with SMTP id\n\td9443c01a7336-1fed384829emr63299395ad.14.1721987798612; \n\tFri, 26 Jul 2024 02:56:38 -0700 (PDT)", "From": "Harvey Yang <chenghaoyang@chromium.org>", "To": "libcamera-devel@lists.libcamera.org", "Cc": "Konami Shu <konamiz@google.com>, Harvey Yang <chenghaoyang@chromium.org>,\n\tYunke Cao <yunkec@chromium.org>, Tomasz Figa <tfiga@chromium.org>", "Subject": "[PATCH v6 5/7] libcamera: pipeline: Read config and register cameras\n\tbased on the config", "Date": "Fri, 26 Jul 2024 09:54:26 +0000", "Message-ID": "<20240726095626.3632402-6-chenghaoyang@chromium.org>", "X-Mailer": "git-send-email 2.46.0.rc1.232.g9752f9e123-goog", "In-Reply-To": "<20240726095626.3632402-1-chenghaoyang@chromium.org>", "References": "<20240726095626.3632402-1-chenghaoyang@chromium.org>", "MIME-Version": "1.0", "Content-Transfer-Encoding": "8bit", "X-BeenThere": "libcamera-devel@lists.libcamera.org", "X-Mailman-Version": "2.1.29", "Precedence": "list", "List-Id": "<libcamera-devel.lists.libcamera.org>", "List-Unsubscribe": "<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>", "List-Archive": "<https://lists.libcamera.org/pipermail/libcamera-devel/>", "List-Post": "<mailto:libcamera-devel@lists.libcamera.org>", "List-Help": "<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>", "List-Subscribe": "<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>", "Errors-To": "libcamera-devel-bounces@lists.libcamera.org", "Sender": "\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>" }, "content": "From: Konami Shu <konamiz@google.com>\n\n- Use Yaml::Parser to parse the config\n- Add config file at \"virtual/data/virtual.yaml\"\n- README.md contains the documentation for the format of config file and\n the implementation of Parser class.\n- Add Parser class to parse config file in Virtual\npipeline handler\n- Add header file of virtual.cpp to use VirtualCameraData class in\n Parser class\n- Parse test patterns\n- Raise Error when the width of a resolution is an odd number\n\nSigned-off-by: Konami Shu <konamiz@google.com>\nCo-developed-by: Harvey Yang <chenghaoyang@chromium.org>\nCo-developed-by: Yunke Cao <yunkec@chromium.org>\nCo-developed-by: Tomasz Figa <tfiga@chromium.org>\n\nChange-Id: I7f8c375e9a2dc4d06a94c06ddc0e8bc3a432dc2f\n---\n src/libcamera/pipeline/virtual/README.md | 68 ++++++\n .../pipeline/virtual/data/virtual.yaml | 49 +++++\n src/libcamera/pipeline/virtual/meson.build | 1 +\n src/libcamera/pipeline/virtual/parser.cpp | 198 ++++++++++++++++++\n src/libcamera/pipeline/virtual/parser.h | 45 ++++\n src/libcamera/pipeline/virtual/virtual.cpp | 47 +++--\n src/libcamera/pipeline/virtual/virtual.h | 6 +-\n 7 files changed, 389 insertions(+), 25 deletions(-)\n create mode 100644 src/libcamera/pipeline/virtual/README.md\n create mode 100644 src/libcamera/pipeline/virtual/data/virtual.yaml\n create mode 100644 src/libcamera/pipeline/virtual/parser.cpp\n create mode 100644 src/libcamera/pipeline/virtual/parser.h", "diff": "diff --git a/src/libcamera/pipeline/virtual/README.md b/src/libcamera/pipeline/virtual/README.md\nnew file mode 100644\nindex 000000000..27d6283df\n--- /dev/null\n+++ b/src/libcamera/pipeline/virtual/README.md\n@@ -0,0 +1,68 @@\n+# Virtual Pipeline Handler\n+\n+Virtual pipeline handler emulates fake external camera(s) on ChromeOS for testing.\n+\n+## Parse config file and register cameras\n+\n+- The config file is located at `src/libcamera/pipeline/virtual/data/virtual.yaml`\n+\n+### Config File Format\n+The config file contains the information about cameras' properties to register.\n+The config file should be a yaml file with dictionary of the cameraIds\n+associated with their properties as top level. The default value will be applied when any property is empty.\n+\n+Each camera block is a dictionary, containing the following keys:\n+- `supported_formats` (list of `VirtualCameraData::Resolution`, optional) : List of supported resolution and frame rates of the emulated camera\n+ - `width` (`unsigned int`, default=1920): Width of the window resolution. This needs to be even.\n+ - `height` (`unsigned int`, default=1080): Height of the window resolution.\n+ - `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.\n+- `test_pattern` (`string`, default=\"bars\"): Which test pattern to use as frames. The options are \"bars\", \"lines\".\n+- `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.\n+- `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.\n+\n+A sample config file:\n+```\n+---\n+\"Virtual0\":\n+ supported_formats:\n+ - width: 1920\n+ height: 1080\n+ frame_rates:\n+ - 30\n+ - 60\n+ - width: 1680\n+ height: 1050\n+ frame_rates:\n+ - 70\n+ - 80\n+ test_pattern: \"bars\"\n+ location: \"front\"\n+ model: \"Virtual Video Device\"\n+\"Virtual1\":\n+ supported_formats:\n+ - width: 800\n+ test_pattern: \"lines\"\n+ location: \"back\"\n+ model: \"Virtual Video Device1\"\n+\"Virtual2\":\n+```\n+\n+### Implementation\n+\n+`Parser` class provides methods to parse the config file to register cameras\n+in Virtual Pipeline Handler. `parseConfigFile()` is exposed to use in\n+Virtual Pipeline Handler.\n+\n+This is the procedure of the Parser class:\n+1. `parseConfigFile()` parses the config file to `YamlObject` using `YamlParser::parse()`.\n+ - Parse the top level of config file which are the camera ids and look into each camera properties.\n+2. For each camera, `parseCameraConfigData()` returns a camera with the configuration.\n+ - The methods in the next step fill the data with the pointer to the Camera object.\n+ - If the config file contains invalid configuration, this method returns nullptr. The camera will be skipped.\n+3. Parse each property and register the data.\n+ - `parseSupportedFormats()`: Parses `supported_formats` in the config, which contains resolutions and frame rates.\n+ - `parseTestPattern()`: Parses `test_pattern` in the config.\n+ - `parseLocation()`: Parses `location` in the config.\n+ - `parseModel()`: Parses `model` in the config.\n+4. Back to `parseConfigFile()` and append the camera configuration.\n+5. Returns a list of camera configurations.\ndiff --git a/src/libcamera/pipeline/virtual/data/virtual.yaml b/src/libcamera/pipeline/virtual/data/virtual.yaml\nnew file mode 100644\nindex 000000000..4eb239e24\n--- /dev/null\n+++ b/src/libcamera/pipeline/virtual/data/virtual.yaml\n@@ -0,0 +1,49 @@\n+# SPDX-License-Identifier: CC0-1.0\n+%YAML 1.1\n+---\n+\"Virtual0\":\n+ supported_formats:\n+ - width: 1920\n+ height: 1080\n+ frame_rates:\n+ - 30\n+ - 60\n+ - width: 1680\n+ height: 1050\n+ frame_rates:\n+ - 70\n+ - 80\n+ test_pattern: \"lines\"\n+ location: \"front\"\n+ model: \"Virtual Video Device\"\n+\"Virtual1\":\n+ supported_formats:\n+ - width: 800\n+ height: 600\n+ frame_rates:\n+ - 30\n+ - 60\n+ test_pattern:\n+ location: \"back\"\n+ model: \"Virtual Video Device1\"\n+\"Virtual2\":\n+ supported_formats:\n+ - width: 100\n+ height: 100\n+ test_pattern: \"lines\"\n+ location: \"front\"\n+ model: \"Virtual Video Device2\"\n+\"Virtual3\":\n+ supported_formats:\n+ - width: 100\n+ height: 100\n+ - width: 800\n+ height: 600\n+ - width: 1920\n+ height: 1080\n+ frame_rates:\n+ - 20\n+ - 30\n+ location: \"a\"\n+ model: \"Virtual Video Device3\"\n+\"Virtual4\":\ndiff --git a/src/libcamera/pipeline/virtual/meson.build b/src/libcamera/pipeline/virtual/meson.build\nindex e1e65e68d..2e82e64cb 100644\n--- a/src/libcamera/pipeline/virtual/meson.build\n+++ b/src/libcamera/pipeline/virtual/meson.build\n@@ -3,6 +3,7 @@\n libcamera_sources += files([\n 'virtual.cpp',\n 'test_pattern_generator.cpp',\n+ 'parser.cpp',\n ])\n \n libyuv_dep = dependency('libyuv', required : false)\ndiff --git a/src/libcamera/pipeline/virtual/parser.cpp b/src/libcamera/pipeline/virtual/parser.cpp\nnew file mode 100644\nindex 000000000..032c0cd9d\n--- /dev/null\n+++ b/src/libcamera/pipeline/virtual/parser.cpp\n@@ -0,0 +1,198 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2023, Google Inc.\n+ *\n+ * parser.cpp - Virtual cameras helper to parse config file\n+ */\n+\n+#include \"parser.h\"\n+\n+#include <memory>\n+#include <utility>\n+\n+#include <libcamera/base/log.h>\n+\n+#include <libcamera/control_ids.h>\n+#include <libcamera/property_ids.h>\n+\n+#include \"libcamera/internal/pipeline_handler.h\"\n+#include \"libcamera/internal/yaml_parser.h\"\n+\n+#include \"virtual.h\"\n+\n+namespace libcamera {\n+\n+LOG_DECLARE_CATEGORY(Virtual)\n+\n+std::vector<std::unique_ptr<VirtualCameraData>> Parser::parseConfigFile(\n+\tFile &file, PipelineHandler *pipe)\n+{\n+\tstd::vector<std::unique_ptr<VirtualCameraData>> configurations;\n+\n+\tstd::unique_ptr<YamlObject> cameras = YamlParser::parse(file);\n+\tif (!cameras) {\n+\t\tLOG(Virtual, Error) << \"Failed to pass config file.\";\n+\t\treturn configurations;\n+\t}\n+\n+\tif (!cameras->isDictionary()) {\n+\t\tLOG(Virtual, Error) << \"Config file is not a dictionary at the top level.\";\n+\t\treturn configurations;\n+\t}\n+\n+\t/* Look into the configuration of each camera */\n+\tfor (const auto &[cameraId, cameraConfigData] : cameras->asDict()) {\n+\t\tstd::unique_ptr<VirtualCameraData> data =\n+\t\t\tparseCameraConfigData(cameraConfigData, pipe);\n+\t\t/* Parse configData to data*/\n+\t\tif (!data) {\n+\t\t\t/* Skip the camera if it has invalid config */\n+\t\t\tLOG(Virtual, Error) << \"Failed to parse config of the camera: \"\n+\t\t\t\t\t << cameraId;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tdata->id_ = cameraId;\n+\t\tControlInfoMap::Map controls;\n+\t\t/* todo: Check which resolution's frame rate to be reported */\n+\t\tcontrols[&controls::FrameDurationLimits] =\n+\t\t\tControlInfo(int64_t(1000 / data->supportedResolutions_[0].frameRates[1]),\n+\t\t\t\t int64_t(1000 / data->supportedResolutions_[0].frameRates[0]));\n+\t\tdata->controlInfo_ = ControlInfoMap(std::move(controls), controls::controls);\n+\t\tconfigurations.push_back(std::move(data));\n+\t}\n+\treturn configurations;\n+}\n+\n+std::unique_ptr<VirtualCameraData> Parser::parseCameraConfigData(\n+\tconst YamlObject &cameraConfigData, PipelineHandler *pipe)\n+{\n+\tstd::unique_ptr<VirtualCameraData> data = std::make_unique<VirtualCameraData>(pipe);\n+\n+\tif (parseSupportedFormats(cameraConfigData, data.get()))\n+\t\treturn nullptr;\n+\n+\tif (parseTestPattern(cameraConfigData, data.get()))\n+\t\treturn nullptr;\n+\n+\tif (parseLocation(cameraConfigData, data.get()))\n+\t\treturn nullptr;\n+\n+\tif (parseModel(cameraConfigData, data.get()))\n+\t\treturn nullptr;\n+\n+\treturn data;\n+}\n+\n+int Parser::parseSupportedFormats(\n+\tconst YamlObject &cameraConfigData, VirtualCameraData *data)\n+{\n+\tSize activeResolution{ 0, 0 };\n+\tif (cameraConfigData.contains(\"supported_formats\")) {\n+\t\tconst YamlObject &supportedResolutions = cameraConfigData[\"supported_formats\"];\n+\n+\t\tfor (const YamlObject &supportedResolution : supportedResolutions.asList()) {\n+\t\t\tunsigned int width = supportedResolution[\"width\"].get<unsigned int>(1920);\n+\t\t\tunsigned int height = supportedResolution[\"height\"].get<unsigned int>(1080);\n+\t\t\tif (width <= 0 || height <= 0) {\n+\t\t\t\tLOG(Virtual, Error) << \"Invalid width or/and height\";\n+\t\t\t\treturn -EINVAL;\n+\t\t\t}\n+\t\t\tif (width % 2 != 0) {\n+\t\t\t\tLOG(Virtual, Error) << \"Invalid width: width needs to be even\";\n+\t\t\t\treturn -EINVAL;\n+\t\t\t}\n+\n+\t\t\tstd::vector<int> frameRates;\n+\t\t\tif (supportedResolution.contains(\"frame_rates\")) {\n+\t\t\t\tauto frameRatesList =\n+\t\t\t\t\tsupportedResolution[\"frame_rates\"].getList<int>().value();\n+\t\t\t\tif (frameRatesList.size() != 2) {\n+\t\t\t\t\tLOG(Virtual, Error) << \"frame_rates needs to be the two edge values of a range\";\n+\t\t\t\t\treturn -EINVAL;\n+\t\t\t\t}\n+\t\t\t\tif (frameRatesList[0] > frameRatesList[1]) {\n+\t\t\t\t\tLOG(Virtual, Error) << \"frame_rates's first value(lower bound) is higher than the second value(upper bound)\";\n+\t\t\t\t\treturn -EINVAL;\n+\t\t\t\t}\n+\t\t\t\tframeRates.push_back(frameRatesList[0]);\n+\t\t\t\tframeRates.push_back(frameRatesList[1]);\n+\t\t\t} else {\n+\t\t\t\tframeRates.push_back(30);\n+\t\t\t\tframeRates.push_back(60);\n+\t\t\t}\n+\n+\t\t\tdata->supportedResolutions_.emplace_back(\n+\t\t\t\tVirtualCameraData::Resolution{ Size{ width, height },\n+\t\t\t\t\t\t\t frameRates });\n+\n+\t\t\tactiveResolution = std::max(activeResolution, Size{ width, height });\n+\t\t}\n+\t} else {\n+\t\tdata->supportedResolutions_.emplace_back(\n+\t\t\tVirtualCameraData::Resolution{ Size{ 1920, 1080 },\n+\t\t\t\t\t\t { 30, 60 } });\n+\t\tactiveResolution = Size(1920, 1080);\n+\t}\n+\n+\tdata->properties_.set(properties::PixelArrayActiveAreas,\n+\t\t\t { Rectangle(activeResolution) });\n+\n+\treturn 0;\n+}\n+\n+int Parser::parseTestPattern(\n+\tconst YamlObject &cameraConfigData, VirtualCameraData *data)\n+{\n+\tstd::string testPattern = cameraConfigData[\"test_pattern\"].get<std::string>().value();\n+\n+\t/* Default value is \"bars\" */\n+\tif (testPattern == \"bars\" || testPattern == \"\") {\n+\t\tdata->testPattern_ = TestPattern::ColorBars;\n+\t} else if (testPattern == \"lines\") {\n+\t\tdata->testPattern_ = TestPattern::DiagonalLines;\n+\t} else {\n+\t\tLOG(Virtual, Error) << \"Test pattern: \" << testPattern\n+\t\t\t\t << \"is not supported\";\n+\t\treturn -EINVAL;\n+\t}\n+\treturn 0;\n+}\n+\n+int Parser::parseLocation(\n+\tconst YamlObject &cameraConfigData, VirtualCameraData *data)\n+{\n+\tstd::string location = cameraConfigData[\"location\"].get<std::string>().value();\n+\n+\t/* Default value is properties::CameraLocationFront */\n+\tif (location == \"front\" || location == \"\") {\n+\t\tdata->properties_.set(properties::Location,\n+\t\t\t\t properties::CameraLocationFront);\n+\t} else if (location == \"back\") {\n+\t\tdata->properties_.set(properties::Location,\n+\t\t\t\t properties::CameraLocationBack);\n+\t} else {\n+\t\tLOG(Virtual, Error) << \"location: \" << location\n+\t\t\t\t << \" is not supported\";\n+\t\treturn -EINVAL;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+int Parser::parseModel(\n+\tconst YamlObject &cameraConfigData, VirtualCameraData *data)\n+{\n+\tstd::string model =\n+\t\tcameraConfigData[\"model\"].get<std::string>().value();\n+\n+\t/* Default value is \"Unknown\" */\n+\tif (model == \"\")\n+\t\tdata->properties_.set(properties::Model, \"Unknown\");\n+\telse\n+\t\tdata->properties_.set(properties::Model, model);\n+\n+\treturn 0;\n+}\n+\n+} /* namespace libcamera */\ndiff --git a/src/libcamera/pipeline/virtual/parser.h b/src/libcamera/pipeline/virtual/parser.h\nnew file mode 100644\nindex 000000000..a377d8aa1\n--- /dev/null\n+++ b/src/libcamera/pipeline/virtual/parser.h\n@@ -0,0 +1,45 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2023, Google Inc.\n+ *\n+ * parser.h - Virtual cameras helper to parse config file\n+ */\n+\n+#pragma once\n+\n+#include <memory>\n+#include <vector>\n+\n+#include <libcamera/base/file.h>\n+\n+#include \"libcamera/internal/pipeline_handler.h\"\n+#include \"libcamera/internal/yaml_parser.h\"\n+\n+#include \"virtual.h\"\n+\n+namespace libcamera {\n+\n+class Parser\n+{\n+public:\n+\tParser() {}\n+\t~Parser() = default;\n+\n+\tstd::vector<std::unique_ptr<VirtualCameraData>>\n+\tparseConfigFile(File &file, PipelineHandler *pipe);\n+\n+private:\n+\tstd::unique_ptr<VirtualCameraData> parseCameraConfigData(\n+\t\tconst YamlObject &cameraConfigData, PipelineHandler *pipe);\n+\n+\tint parseSupportedFormats(\n+\t\tconst YamlObject &cameraConfigData, VirtualCameraData *data);\n+\tint parseTestPattern(\n+\t\tconst YamlObject &cameraConfigData, VirtualCameraData *data);\n+\tint parseLocation(\n+\t\tconst YamlObject &cameraConfigData, VirtualCameraData *data);\n+\tint parseModel(\n+\t\tconst YamlObject &cameraConfigData, VirtualCameraData *data);\n+};\n+\n+} // namespace libcamera\ndiff --git a/src/libcamera/pipeline/virtual/virtual.cpp b/src/libcamera/pipeline/virtual/virtual.cpp\nindex a36ffd42b..5563c0f66 100644\n--- a/src/libcamera/pipeline/virtual/virtual.cpp\n+++ b/src/libcamera/pipeline/virtual/virtual.cpp\n@@ -18,6 +18,10 @@\n #include \"libcamera/internal/camera.h\"\n #include \"libcamera/internal/formats.h\"\n #include \"libcamera/internal/pipeline_handler.h\"\n+#include \"libcamera/internal/yaml_parser.h\"\n+\n+#include \"frame_generator.h\"\n+#include \"parser.h\"\n \n namespace libcamera {\n \n@@ -220,32 +224,31 @@ int PipelineHandlerVirtual::queueRequestDevice([[maybe_unused]] Camera *camera,\n \n bool PipelineHandlerVirtual::match([[maybe_unused]] DeviceEnumerator *enumerator)\n {\n-\t/* \\todo Add virtual cameras according to a config file. */\n-\n-\tstd::unique_ptr<VirtualCameraData> data = std::make_unique<VirtualCameraData>(this);\n-\n-\tdata->supportedResolutions_.resize(2);\n-\tdata->supportedResolutions_[0] = { .size = Size(1920, 1080), .frame_rates = { 30 } };\n-\tdata->supportedResolutions_[1] = { .size = Size(1280, 720), .frame_rates = { 30, 60 } };\n-\n-\tdata->properties_.set(properties::Location, properties::CameraLocationFront);\n-\tdata->properties_.set(properties::Model, \"Virtual Video Device\");\n-\tdata->properties_.set(properties::PixelArrayActiveAreas, { Rectangle(Size(1920, 1080)) });\n+\tFile file(configurationFile(\"virtual\", \"virtual.yaml\"));\n+\tbool isOpen = file.open(File::OpenModeFlag::ReadOnly);\n+\tif (!isOpen) {\n+\t\tLOG(Virtual, Error) << \"Failed to open config file: \" << file.fileName();\n+\t\treturn false;\n+\t}\n \n-\t/* \\todo Set FrameDurationLimits based on config. */\n-\tControlInfoMap::Map controls;\n-\tint64_t min_frame_duration = 30, max_frame_duration = 60;\n-\tcontrols[&controls::FrameDurationLimits] = ControlInfo(min_frame_duration, max_frame_duration);\n-\tdata->controlInfo_ = ControlInfoMap(std::move(controls), controls::controls);\n+\tParser parser;\n+\tauto configData = parser.parseConfigFile(file, this);\n+\tif (configData.size() == 0) {\n+\t\tLOG(Virtual, Error) << \"Failed to parse any cameras from the config file: \"\n+\t\t\t\t << file.fileName();\n+\t\treturn false;\n+\t}\n \n-\t/* Create and register the camera. */\n-\tstd::set<Stream *> streams{ &data->stream_ };\n-\tconst std::string id = \"Virtual0\";\n-\tstd::shared_ptr<Camera> camera = Camera::create(std::move(data), id, streams);\n+\t/* Configure and register cameras with configData */\n+\tfor (auto &data : configData) {\n+\t\tstd::set<Stream *> streams{ &data->stream_ };\n+\t\tstd::string id = data->id_;\n+\t\tstd::shared_ptr<Camera> camera = Camera::create(std::move(data), id, streams);\n \n-\tinitFrameGenerator(camera.get());\n+\t\tinitFrameGenerator(camera.get());\n \n-\tregisterCamera(std::move(camera));\n+\t\tregisterCamera(std::move(camera));\n+\t}\n \n \treturn false; // Prevent infinite loops for now\n }\ndiff --git a/src/libcamera/pipeline/virtual/virtual.h b/src/libcamera/pipeline/virtual/virtual.h\nindex fecd9fa6f..c1ac4eb90 100644\n--- a/src/libcamera/pipeline/virtual/virtual.h\n+++ b/src/libcamera/pipeline/virtual/virtual.h\n@@ -22,7 +22,7 @@ class VirtualCameraData : public Camera::Private\n public:\n \tstruct Resolution {\n \t\tSize size;\n-\t\tstd::vector<int> frame_rates;\n+\t\tstd::vector<int> frameRates;\n \t};\n \tVirtualCameraData(PipelineHandler *pipe)\n \t\t: Camera::Private(pipe)\n@@ -31,9 +31,9 @@ public:\n \n \t~VirtualCameraData() = default;\n \n-\tTestPattern testPattern_;\n-\n+\tstd::string id_;\n \tstd::vector<Resolution> supportedResolutions_;\n+\tTestPattern testPattern_;\n \n \tStream stream_;\n \n", "prefixes": [ "v6", "5/7" ] }