Patch Detail
Show a patch.
GET /api/1.1/patches/20971/?format=api
{ "id": 20971, "url": "https://patchwork.libcamera.org/api/1.1/patches/20971/?format=api", "web_url": "https://patchwork.libcamera.org/patch/20971/", "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": "<20240820172202.526547-6-chenghaoyang@google.com>", "date": "2024-08-20T16:23:36", "name": "[v9,5/8] 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/20971/mbox/", "series": [ { "id": 4529, "url": "https://patchwork.libcamera.org/api/1.1/series/4529/?format=api", "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=4529", "date": "2024-08-20T16:23:31", "name": "Add VirtualPipelineHandler", "version": 9, "mbox": "https://patchwork.libcamera.org/series/4529/mbox/" } ], "comments": "https://patchwork.libcamera.org/api/patches/20971/comments/", "check": "pending", "checks": "https://patchwork.libcamera.org/api/patches/20971/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 82BCBC323E\n\tfor <parsemail@patchwork.libcamera.org>;\n\tTue, 20 Aug 2024 17:22:29 +0000 (UTC)", "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 941B96340B;\n\tTue, 20 Aug 2024 19:22:27 +0200 (CEST)", "from mail-wr1-x42d.google.com (mail-wr1-x42d.google.com\n\t[IPv6:2a00:1450:4864:20::42d])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 3C3C7633D2\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 20 Aug 2024 19:22:18 +0200 (CEST)", "by mail-wr1-x42d.google.com with SMTP id\n\tffacd0b85a97d-3719753d365so3147396f8f.2\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 20 Aug 2024 10:22:18 -0700 (PDT)", "from chenghaoyang-germany.c.googlers.com.com\n\t(161.126.77.34.bc.googleusercontent.com. [34.77.126.161])\n\tby smtp.gmail.com with ESMTPSA id\n\t5b1f17b1804b1-42ab6e90db5sm30328575e9.0.2024.08.20.10.22.16\n\t(version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n\tTue, 20 Aug 2024 10:22:16 -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=\"Hp59HjFI\"; dkim-atps=neutral", "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=chromium.org; s=google; t=1724174537; x=1724779337;\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=2Ay//vNC4qtVEWwzRLa7yylN/5+6F1GspCmgnRirwsU=;\n\tb=Hp59HjFIr9uyxDEWXWUUskobFd3qgtzA9JC+X0kE8kuSfAhww2fQwoDf89u+Vrd+Dz\n\tU9Gi+et+MNctkumluJlzxV/2mx7iERyHpbMBmqhX2txRnbOhv0ZF+xmkhHzel2thtJOR\n\tNopjEFwzYblQG5yHyxU/1JaW1Ov9wS1CMJXmY=", "X-Google-DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20230601; t=1724174537; x=1724779337;\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=2Ay//vNC4qtVEWwzRLa7yylN/5+6F1GspCmgnRirwsU=;\n\tb=mv9ZduSlbbAt4n4tZwZqGchBNeScoR0duPB5LBW6NYRj8Gf6MTJppypdDAZI3VxoYK\n\tS44e9n/O+xsl1LhHSqZ5iZgkR98NAGDsqFggB0IOWoNsAFLAVNTY4ka0LwfHOBZq0Yi3\n\t/JlDaNlJaVEHYoe4hlt5qFj+qdR32y/4l9dKDZiVLRnKhd+CR5F99egbdlcMwayhAb8Y\n\t0lRI8Ic2KohvIPZdZi3vcprFAabeA1McGTd3V7vhPW1ukBQp3bdL8VGYDttf+P26vigJ\n\tqZIjLsQ3bDAV+GxUeM0unnjnd3MMF3M6p4X081ZfwTccHtloTnZC/pdfuxJlylGG1nT0\n\thdwQ==", "X-Gm-Message-State": "AOJu0YzayMypfybwLBdzydUsHSCqS69wG/yGuXT+unCwLK80dSJQqOpm\n\tukW7JTFhKIs3eQ5nNIxvPafrw6rmbGU71xMuOD+PLzrTfX7dz4Qbs63DsLIdXwMkjiMakfncSI5\n\tAzMBwa1M=", "X-Google-Smtp-Source": "AGHT+IFIcxNLCZJTBf6g6tHR2TQooZF7X8Wptxq3VaM/q1/XlByCWmIjXH64IPTe2SUljJoxtqvE8w==", "X-Received": "by 2002:adf:f0c9:0:b0:368:2f01:307a with SMTP id\n\tffacd0b85a97d-3719469f97cmr9846690f8f.46.1724174537249; \n\tTue, 20 Aug 2024 10:22:17 -0700 (PDT)", "From": "Harvey Yang <chenghaoyang@chromium.org>", "X-Google-Original-From": "Harvey Yang <chenghaoyang@google.com>", "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 v9 5/8] libcamera: pipeline: Read config and register cameras\n\tbased 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", "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---\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 357fdd035..0fe471f00 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@@ -228,32 +232,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": [ "v9", "5/8" ] }