Show a patch.

GET /api/patches/20780/?format=api
HTTP 200 OK
Allow: GET, PUT, PATCH, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "id": 20780,
    "url": "https://patchwork.libcamera.org/api/patches/20780/?format=api",
    "web_url": "https://patchwork.libcamera.org/patch/20780/",
    "project": {
        "id": 1,
        "url": "https://patchwork.libcamera.org/api/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": "<20240805135104.139932-4-chenghaoyang@google.com>",
    "date": "2024-08-05T13:48:34",
    "name": "[v8,3/8] libcamera: pipeline: Add VirtualPipelineHandler",
    "commit_ref": null,
    "pull_url": null,
    "state": "superseded",
    "archived": false,
    "hash": "8e3d128e0f6e726e4027c9ab2f0cf53d5ccc6054",
    "submitter": {
        "id": 117,
        "url": "https://patchwork.libcamera.org/api/people/117/?format=api",
        "name": "Cheng-Hao Yang",
        "email": "chenghaoyang@chromium.org"
    },
    "delegate": null,
    "mbox": "https://patchwork.libcamera.org/patch/20780/mbox/",
    "series": [
        {
            "id": 4487,
            "url": "https://patchwork.libcamera.org/api/series/4487/?format=api",
            "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=4487",
            "date": "2024-08-05T13:48:31",
            "name": "Add VirtualPipelineHandler",
            "version": 8,
            "mbox": "https://patchwork.libcamera.org/series/4487/mbox/"
        }
    ],
    "comments": "https://patchwork.libcamera.org/api/patches/20780/comments/",
    "check": "pending",
    "checks": "https://patchwork.libcamera.org/api/patches/20780/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 0EDA5BE173\n\tfor <parsemail@patchwork.libcamera.org>;\n\tMon,  5 Aug 2024 13:51:17 +0000 (UTC)",
            "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id F145363381;\n\tMon,  5 Aug 2024 15:51:15 +0200 (CEST)",
            "from mail-wm1-x329.google.com (mail-wm1-x329.google.com\n\t[IPv6:2a00:1450:4864:20::329])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 10A1963369\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon,  5 Aug 2024 15:51:08 +0200 (CEST)",
            "by mail-wm1-x329.google.com with SMTP id\n\t5b1f17b1804b1-4280c55e488so33122285e9.0\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 05 Aug 2024 06:51:08 -0700 (PDT)",
            "from chenghaoyang-germany.c.googlers.com.com\n\t(49.222.77.34.bc.googleusercontent.com. [34.77.222.49])\n\tby smtp.gmail.com with ESMTPSA id\n\t5b1f17b1804b1-428e8d6555fsm137146675e9.26.2024.08.05.06.51.06\n\t(version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n\tMon, 05 Aug 2024 06:51:06 -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=\"Rn5k6Fmf\"; dkim-atps=neutral",
        "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=chromium.org; s=google; t=1722865867; x=1723470667;\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=urDAndi6oox0tDq7i1gXEYZuOknup8uTDy94++8STp0=;\n\tb=Rn5k6FmfDDReR9UVvpnw3GUlUftVNrU8nVc9wiSJ5egyhiha+9t36uKyR4Vo4ix3PD\n\tJisxewthhOMXISwapbuP+GbrmahPWgMZbbjFip10G+CYpAY4FQ3Yu3zM5EDRoQj/ygee\n\teo3sVtODMPC9AVRHAD64NCpStHhjtFaMl8G38=",
        "X-Google-DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20230601; t=1722865867; x=1723470667;\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=urDAndi6oox0tDq7i1gXEYZuOknup8uTDy94++8STp0=;\n\tb=USCdtFcFDSJEXufnmd0UGh92i7SVXDGIYq8xCEckpf5LAcIwXmog6s3L0dCz9gvY8a\n\twpot4HuTfaJhlo3apg8Fyc/LayqMOeTe6k1uYOwzxafLukhvVUNoO75LGMtmIx8M1QmU\n\tVuCGXkSu6dkO/mD+usvFiX+wNwpxN5PiNCCy+yuMArsrIOxM6WHqfucVr+mGb+gijdJT\n\tuQE7d7dsYoLpC0p5M4FS6Htz6h67m1W0h7MbXm2wlsQ0p1pOX228Se3bVpLMESy/VWKf\n\tV6LaeN4uuHJuX/Xg+0kg9n59WXuiz9glWDGzKsUfBzo/ZP9bC9uVgKYbKxKRhhfugzJU\n\thXpA==",
        "X-Gm-Message-State": "AOJu0YwZRLCWshC6l8i6tF2/r1mgsGFHKqJNToK26XcJZR2CpSMLOuIP\n\toxfy/3WoL+eLqBbCFz9pyVAoSGzamPI38p4dqfOhpotoUahDbf7KJD1sWIEMOyhsOLOs9W3dKtl\n\tH41lSti4=",
        "X-Google-Smtp-Source": "AGHT+IGfNxAjSuSsiSQ6LSCTw+rHBrVmg84KAxJtGKV4lSFyED5R7rFebuiepdY5qjwHgFdnAg5Kpg==",
        "X-Received": "by 2002:a05:600c:1c8b:b0:428:e820:37dc with SMTP id\n\t5b1f17b1804b1-428e8203a6bmr88031715e9.7.1722865867178; \n\tMon, 05 Aug 2024 06:51:07 -0700 (PDT)",
        "From": "Harvey Yang <chenghaoyang@chromium.org>",
        "X-Google-Original-From": "Harvey Yang <chenghaoyang@google.com>",
        "To": "libcamera-devel@lists.libcamera.org",
        "Cc": "Harvey Yang <chenghaoyang@chromium.org>",
        "Subject": "[PATCH v8 3/8] libcamera: pipeline: Add VirtualPipelineHandler",
        "Date": "Mon,  5 Aug 2024 13:48:34 +0000",
        "Message-ID": "<20240805135104.139932-4-chenghaoyang@google.com>",
        "X-Mailer": "git-send-email 2.46.0.rc2.264.g509ed76dc8-goog",
        "In-Reply-To": "<20240805135104.139932-1-chenghaoyang@google.com>",
        "References": "<20240805135104.139932-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: Harvey Yang <chenghaoyang@chromium.org>\n\nAdd VirtualPipelineHandler for more unit tests and verfiy libcamera\ninfrastructure works on devices without using hardware cameras.\n\nSigned-off-by: Harvey Yang <chenghaoyang@chromium.org>\n---\n meson.build                                |   1 +\n meson_options.txt                          |   3 +-\n src/libcamera/pipeline/virtual/meson.build |   5 +\n src/libcamera/pipeline/virtual/virtual.cpp | 251 +++++++++++++++++++++\n src/libcamera/pipeline/virtual/virtual.h   |  78 +++++++\n 5 files changed, 337 insertions(+), 1 deletion(-)\n create mode 100644 src/libcamera/pipeline/virtual/meson.build\n create mode 100644 src/libcamera/pipeline/virtual/virtual.cpp\n create mode 100644 src/libcamera/pipeline/virtual/virtual.h",
    "diff": "diff --git a/meson.build b/meson.build\nindex f946eba9..3cad3249 100644\n--- a/meson.build\n+++ b/meson.build\n@@ -222,6 +222,7 @@ pipelines_support = {\n     'simple':       arch_arm,\n     'uvcvideo':     ['any'],\n     'vimc':         ['test'],\n+    'virtual':      ['test'],\n }\n \n if pipelines.contains('all')\ndiff --git a/meson_options.txt b/meson_options.txt\nindex 7aa41249..c91cd241 100644\n--- a/meson_options.txt\n+++ b/meson_options.txt\n@@ -53,7 +53,8 @@ option('pipelines',\n             'rpi/vc4',\n             'simple',\n             'uvcvideo',\n-            'vimc'\n+            'vimc',\n+            'virtual'\n         ],\n         description : 'Select which pipeline handlers to build. If this is set to \"auto\", all the pipelines applicable to the target architecture will be built. If this is set to \"all\", all the pipelines will be built. If both are selected then \"all\" will take precedence.')\n \ndiff --git a/src/libcamera/pipeline/virtual/meson.build b/src/libcamera/pipeline/virtual/meson.build\nnew file mode 100644\nindex 00000000..ba7ff754\n--- /dev/null\n+++ b/src/libcamera/pipeline/virtual/meson.build\n@@ -0,0 +1,5 @@\n+# SPDX-License-Identifier: CC0-1.0\n+\n+libcamera_sources += files([\n+    'virtual.cpp',\n+])\ndiff --git a/src/libcamera/pipeline/virtual/virtual.cpp b/src/libcamera/pipeline/virtual/virtual.cpp\nnew file mode 100644\nindex 00000000..74eb8c7a\n--- /dev/null\n+++ b/src/libcamera/pipeline/virtual/virtual.cpp\n@@ -0,0 +1,251 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2023, Google Inc.\n+ *\n+ * virtual.cpp - Pipeline handler for virtual cameras\n+ */\n+\n+#include \"virtual.h\"\n+\n+#include <libcamera/base/log.h>\n+\n+#include <libcamera/camera.h>\n+#include <libcamera/control_ids.h>\n+#include <libcamera/controls.h>\n+#include <libcamera/formats.h>\n+#include <libcamera/property_ids.h>\n+\n+#include \"libcamera/internal/camera.h\"\n+#include \"libcamera/internal/formats.h\"\n+#include \"libcamera/internal/pipeline_handler.h\"\n+\n+namespace libcamera {\n+\n+LOG_DEFINE_CATEGORY(Virtual)\n+\n+namespace {\n+\n+uint64_t currentTimestamp()\n+{\n+\tstruct timespec ts;\n+\tif (clock_gettime(CLOCK_MONOTONIC, &ts) < 0) {\n+\t\tLOG(Virtual, Error) << \"Get clock time fails\";\n+\t\treturn 0;\n+\t}\n+\n+\treturn ts.tv_sec * 1'000'000'000LL + ts.tv_nsec;\n+}\n+\n+} // namespace\n+\n+VirtualCameraConfiguration::VirtualCameraConfiguration(VirtualCameraData *data)\n+\t: CameraConfiguration(), data_(data)\n+{\n+}\n+\n+CameraConfiguration::Status VirtualCameraConfiguration::validate()\n+{\n+\tStatus status = Valid;\n+\n+\tif (config_.empty()) {\n+\t\tLOG(Virtual, Error) << \"Empty config\";\n+\t\treturn Invalid;\n+\t}\n+\n+\t/* Currently only one stream is supported */\n+\tif (config_.size() > 1) {\n+\t\tconfig_.resize(1);\n+\t\tstatus = Adjusted;\n+\t}\n+\n+\tSize maxSize;\n+\tfor (const auto &resolution : data_->supportedResolutions_)\n+\t\tmaxSize = std::max(maxSize, resolution.size);\n+\n+\tfor (StreamConfiguration &cfg : config_) {\n+\t\tbool found = false;\n+\t\tfor (const auto &resolution : data_->supportedResolutions_) {\n+\t\t\tif (resolution.size.width == cfg.size.width &&\n+\t\t\t    resolution.size.height == cfg.size.height) {\n+\t\t\t\tfound = true;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n+\n+\t\tif (!found) {\n+\t\t\tcfg.size = maxSize;\n+\t\t\tstatus = Adjusted;\n+\t\t}\n+\n+\t\tconst PixelFormatInfo &info = PixelFormatInfo::info(cfg.pixelFormat);\n+\t\tcfg.stride = info.stride(cfg.size.width, 0, 1);\n+\t\tcfg.frameSize = info.frameSize(cfg.size, 1);\n+\n+\t\tcfg.setStream(const_cast<Stream *>(&data_->stream_));\n+\n+\t\tcfg.bufferCount = VirtualCameraConfiguration::kBufferCount;\n+\t}\n+\n+\treturn status;\n+}\n+\n+PipelineHandlerVirtual::PipelineHandlerVirtual(CameraManager *manager)\n+\t: PipelineHandler(manager)\n+{\n+}\n+\n+std::unique_ptr<CameraConfiguration>\n+PipelineHandlerVirtual::generateConfiguration(Camera *camera,\n+\t\t\t\t\t      Span<const StreamRole> roles)\n+{\n+\tVirtualCameraData *data = cameraData(camera);\n+\tauto config =\n+\t\tstd::make_unique<VirtualCameraConfiguration>(data);\n+\n+\tif (roles.empty())\n+\t\treturn config;\n+\n+\tSize minSize, sensorResolution;\n+\tfor (const auto &resolution : data->supportedResolutions_) {\n+\t\tif (minSize.isNull() || minSize > resolution.size)\n+\t\t\tminSize = resolution.size;\n+\n+\t\tsensorResolution = std::max(sensorResolution, resolution.size);\n+\t}\n+\n+\tfor (const StreamRole role : roles) {\n+\t\tstd::map<PixelFormat, std::vector<SizeRange>> streamFormats;\n+\t\tunsigned int bufferCount;\n+\t\tPixelFormat pixelFormat;\n+\n+\t\tswitch (role) {\n+\t\tcase StreamRole::StillCapture:\n+\t\t\tpixelFormat = formats::NV12;\n+\t\t\tbufferCount = VirtualCameraConfiguration::kBufferCount;\n+\t\t\tstreamFormats[pixelFormat] = { { minSize, sensorResolution } };\n+\n+\t\t\tbreak;\n+\n+\t\tcase StreamRole::Raw: {\n+\t\t\t/* \\todo check */\n+\t\t\tpixelFormat = formats::SBGGR10;\n+\t\t\tbufferCount = VirtualCameraConfiguration::kBufferCount;\n+\t\t\tstreamFormats[pixelFormat] = { { minSize, sensorResolution } };\n+\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tcase StreamRole::Viewfinder:\n+\t\tcase StreamRole::VideoRecording: {\n+\t\t\tpixelFormat = formats::NV12;\n+\t\t\tbufferCount = VirtualCameraConfiguration::kBufferCount;\n+\t\t\tstreamFormats[pixelFormat] = { { minSize, sensorResolution } };\n+\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tdefault:\n+\t\t\tLOG(Virtual, Error)\n+\t\t\t\t<< \"Requested stream role not supported: \" << role;\n+\t\t\tconfig.reset();\n+\t\t\treturn config;\n+\t\t}\n+\n+\t\tStreamFormats formats(streamFormats);\n+\t\tStreamConfiguration cfg(formats);\n+\t\tcfg.size = sensorResolution;\n+\t\tcfg.pixelFormat = pixelFormat;\n+\t\tcfg.bufferCount = bufferCount;\n+\t\tconfig->addConfiguration(cfg);\n+\t}\n+\n+\tif (config->validate() == CameraConfiguration::Invalid)\n+\t\tconfig.reset();\n+\n+\treturn config;\n+}\n+\n+int PipelineHandlerVirtual::configure(\n+\t[[maybe_unused]] Camera *camera,\n+\t[[maybe_unused]] CameraConfiguration *config)\n+{\n+\t// Nothing to be done.\n+\treturn 0;\n+}\n+\n+int PipelineHandlerVirtual::exportFrameBuffers(\n+\t[[maybe_unused]] Camera *camera,\n+\tStream *stream,\n+\tstd::vector<std::unique_ptr<FrameBuffer>> *buffers)\n+{\n+\tif (!dmaBufAllocator_.isValid())\n+\t\treturn -ENOBUFS;\n+\n+\tconst StreamConfiguration &config = stream->configuration();\n+\n+\tauto info = PixelFormatInfo::info(config.pixelFormat);\n+\n+\tstd::vector<std::size_t> planeSizes;\n+\tfor (size_t i = 0; i < info.planes.size(); ++i)\n+\t\tplaneSizes.push_back(info.planeSize(config.size, i));\n+\n+\treturn dmaBufAllocator_.exportBuffers(config.bufferCount, planeSizes, buffers);\n+}\n+\n+int PipelineHandlerVirtual::start([[maybe_unused]] Camera *camera,\n+\t\t\t\t  [[maybe_unused]] const ControlList *controls)\n+{\n+\t/* \\todo Start reading the virtual video if any. */\n+\treturn 0;\n+}\n+\n+void PipelineHandlerVirtual::stopDevice([[maybe_unused]] Camera *camera)\n+{\n+\t/* \\todo Reset the virtual video if any. */\n+}\n+\n+int PipelineHandlerVirtual::queueRequestDevice([[maybe_unused]] Camera *camera,\n+\t\t\t\t\t       Request *request)\n+{\n+\t/* \\todo Read from the virtual video if any. */\n+\tfor (auto it : request->buffers())\n+\t\tcompleteBuffer(request, it.second);\n+\n+\trequest->metadata().set(controls::SensorTimestamp, currentTimestamp());\n+\tcompleteRequest(request);\n+\n+\treturn 0;\n+}\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+\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+\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+\tregisterCamera(std::move(camera));\n+\n+\treturn false; // Prevent infinite loops for now\n+}\n+\n+REGISTER_PIPELINE_HANDLER(PipelineHandlerVirtual, \"virtual\")\n+\n+} /* namespace libcamera */\ndiff --git a/src/libcamera/pipeline/virtual/virtual.h b/src/libcamera/pipeline/virtual/virtual.h\nnew file mode 100644\nindex 00000000..6fc6b34d\n--- /dev/null\n+++ b/src/libcamera/pipeline/virtual/virtual.h\n@@ -0,0 +1,78 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2023, Google Inc.\n+ *\n+ * virtual.h - Pipeline handler for virtual cameras\n+ */\n+\n+#pragma once\n+\n+#include <libcamera/base/file.h>\n+\n+#include \"libcamera/internal/camera.h\"\n+#include \"libcamera/internal/dma_buf_allocator.h\"\n+#include \"libcamera/internal/pipeline_handler.h\"\n+\n+namespace libcamera {\n+\n+class VirtualCameraData : public Camera::Private\n+{\n+public:\n+\tstruct Resolution {\n+\t\tSize size;\n+\t\tstd::vector<int> frame_rates;\n+\t};\n+\tVirtualCameraData(PipelineHandler *pipe)\n+\t\t: Camera::Private(pipe)\n+\t{\n+\t}\n+\n+\t~VirtualCameraData() = default;\n+\n+\tstd::vector<Resolution> supportedResolutions_;\n+\n+\tStream stream_;\n+};\n+\n+class VirtualCameraConfiguration : public CameraConfiguration\n+{\n+public:\n+\tstatic constexpr unsigned int kBufferCount = 4;\n+\n+\tVirtualCameraConfiguration(VirtualCameraData *data);\n+\n+\tStatus validate() override;\n+\n+private:\n+\tconst VirtualCameraData *data_;\n+};\n+\n+class PipelineHandlerVirtual : public PipelineHandler\n+{\n+public:\n+\tPipelineHandlerVirtual(CameraManager *manager);\n+\n+\tstd::unique_ptr<CameraConfiguration> generateConfiguration(Camera *camera,\n+\t\t\t\t\t\t\t\t   Span<const StreamRole> roles) override;\n+\tint configure(Camera *camera, CameraConfiguration *config) override;\n+\n+\tint exportFrameBuffers(Camera *camera, Stream *stream,\n+\t\t\t       std::vector<std::unique_ptr<FrameBuffer>> *buffers) override;\n+\n+\tint start(Camera *camera, const ControlList *controls) override;\n+\tvoid stopDevice(Camera *camera) override;\n+\n+\tint queueRequestDevice(Camera *camera, Request *request) override;\n+\n+\tbool match(DeviceEnumerator *enumerator) override;\n+\n+private:\n+\tVirtualCameraData *cameraData(Camera *camera)\n+\t{\n+\t\treturn static_cast<VirtualCameraData *>(camera->_d());\n+\t}\n+\n+\tDmaBufAllocator dmaBufAllocator_;\n+};\n+\n+} // namespace libcamera\n",
    "prefixes": [
        "v8",
        "3/8"
    ]
}