{"id":26611,"url":"https://patchwork.libcamera.org/api/patches/26611/?format=json","web_url":"https://patchwork.libcamera.org/patch/26611/","project":{"id":1,"url":"https://patchwork.libcamera.org/api/projects/1/?format=json","name":"libcamera","link_name":"libcamera","list_id":"libcamera_core","list_email":"libcamera-devel@lists.libcamera.org","web_url":"","scm_url":"","webscm_url":""},"msgid":"<20260504091623.3354474-2-maxbretschneider@protonmail.com>","date":"2026-05-04T09:16:47","name":"[1/2] libcamera: pipeline: virtual: Add RawFrameGenerator","commit_ref":null,"pull_url":null,"state":"new","archived":false,"hash":"ccb4b723517fb331837ff1e6e082b7bd9ebf6106","submitter":{"id":266,"url":"https://patchwork.libcamera.org/api/people/266/?format=json","name":"Max Bretschneider","email":"maxbretschneider@protonmail.com"},"delegate":null,"mbox":"https://patchwork.libcamera.org/patch/26611/mbox/","series":[{"id":5901,"url":"https://patchwork.libcamera.org/api/series/5901/?format=json","web_url":"https://patchwork.libcamera.org/project/libcamera/list/?series=5901","date":"2026-05-04T09:16:41","name":"libcamera: pipeline: virtual: Add raw Bayer frame support","version":2,"mbox":"https://patchwork.libcamera.org/series/5901/mbox/"}],"comments":"https://patchwork.libcamera.org/api/patches/26611/comments/","check":"pending","checks":"https://patchwork.libcamera.org/api/patches/26611/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 3F88EBDCB5\n\tfor <parsemail@patchwork.libcamera.org>;\n\tMon,  4 May 2026 09:16:53 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id CB95963022;\n\tMon,  4 May 2026 11:16:52 +0200 (CEST)","from mail-106104.protonmail.ch (mail-106104.protonmail.ch\n\t[79.135.106.104])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id AA0A96301E\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon,  4 May 2026 11:16:50 +0200 (CEST)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (2048-bit key;\n\tunprotected) header.d=protonmail.com header.i=@protonmail.com\n\theader.b=\"SpCQyyQo\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed; d=protonmail.com;\n\ts=protonmail3; t=1777886209; x=1778145409;\n\tbh=fWWbMJxjuZVJPSG3I7OlCAMGN5MOvg3IzMT15kbxeCw=;\n\th=Date:To:From:Cc:Subject:Message-ID:In-Reply-To:References:\n\tFeedback-ID:From:To:Cc:Date:Subject:Reply-To:Feedback-ID:\n\tMessage-ID:BIMI-Selector;\n\tb=SpCQyyQoUwxILNlkUC5MykBiGYeoiSoJ6WutWbB3neJ57BdgO9LgyhwcHQztcqU9U\n\tu6bjSogM1u94Zr+MD0OEZqCuJjoxvrsRFNZmbjHjgujhND96/5B38oQu/lwQzGm4Ts\n\tVmOaMVTuIf2BChoA30CrgK7TUEMROGzmterdNCKPFYpRQNf3ea6Ho+9U1VMNfa8Lnx\n\tA1VWQe8brP9ktj7A9JaMWWPSAmzUIkorpQQz/1jNeb3MrlBv4BJkD0grqb/5E3hh5t\n\t2b9iO7ZEne10ZSE3gmJoSVF8V/QzT8H2oR5iGfsvwnatk5AYUibU/3qtZojP6KBJnD\n\t9pqT+pMgRYsjA==","Date":"Mon, 04 May 2026 09:16:47 +0000","To":"libcamera-devel@lists.libcamera.org","From":"maxbretschneider@protonmail.com","Cc":"Max Bretschneider <maxbretschneider@protonmail.com>","Subject":"[PATCH 1/2] libcamera: pipeline: virtual: Add RawFrameGenerator","Message-ID":"<20260504091623.3354474-2-maxbretschneider@protonmail.com>","In-Reply-To":"<20260504091623.3354474-1-maxbretschneider@protonmail.com>","References":"<20260501105137.439519-1-maxbretschneider@protonmail.com>\n\t<20260504091623.3354474-1-maxbretschneider@protonmail.com>","Feedback-ID":"122687743:user:proton","X-Pm-Message-ID":"f253ce13c6bdcce8634068b20fc26ded0f63eb82","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Transfer-Encoding":"quoted-printable","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: Max Bretschneider <maxbretschneider@protonmail.com>\n\nAdded a new FrameGenerator subclass which reads plain binary raw Bayer\nfiles from disk and copies them into the output FrameBuffers unchanged.\nNo format conversion is performed. Input files must be plain binary\nsensor dumps without a header, and they must match the declared\nbayer_order and bit_depht. Modelled on ImageFrameGenerator.\n\nSigned-off-by: Max Bretschneider <maxbretschneider@protonmail.com>\n---\n src/libcamera/pipeline/virtual/meson.build    |   1 +\n .../pipeline/virtual/raw_frame_generator.cpp  | 131 ++++++++++++++++++\n .../pipeline/virtual/raw_frame_generator.h    |  47 +++++++\n 3 files changed, 179 insertions(+)\n create mode 100644 src/libcamera/pipeline/virtual/raw_frame_generator.cpp\n create mode 100644 src/libcamera/pipeline/virtual/raw_frame_generator.h","diff":"diff --git a/src/libcamera/pipeline/virtual/meson.build b/src/libcamera/pipeline/virtual/meson.build\nindex c8434593..c6576142 100644\n--- a/src/libcamera/pipeline/virtual/meson.build\n+++ b/src/libcamera/pipeline/virtual/meson.build\n@@ -3,6 +3,7 @@\n libcamera_internal_sources += files([\n     'config_parser.cpp',\n     'image_frame_generator.cpp',\n+    'raw_frame_generator.cpp',\n     'test_pattern_generator.cpp',\n     'virtual.cpp',\n ])\ndiff --git a/src/libcamera/pipeline/virtual/raw_frame_generator.cpp b/src/libcamera/pipeline/virtual/raw_frame_generator.cpp\nnew file mode 100644\nindex 00000000..e0be28cf\n--- /dev/null\n+++ b/src/libcamera/pipeline/virtual/raw_frame_generator.cpp\n@@ -0,0 +1,131 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2026, max.bretschneider@leica-geosystems.com\n+ *\n+ * Derived class of FrameGenerator for generating raw Bayer frames\n+ */\n+\n+#include \"raw_frame_generator.h\"\n+\n+#include <errno.h>\n+#include <string.h>\n+\n+#include <libcamera/base/file.h>\n+#include <libcamera/base/log.h>\n+\n+#include <libcamera/framebuffer.h>\n+\n+#include \"libcamera/internal/mapped_framebuffer.h\"\n+\n+namespace libcamera {\n+\n+LOG_DECLARE_CATEGORY(Virtual)\n+\n+/*\n+ * Factory function to create a RawFrameGenerator object.\n+ * Read the raw Bayer frames from disk and store them in memory.\n+ */\n+std::unique_ptr<RawFrameGenerator>\n+RawFrameGenerator::create(RawFrames &rawFrames)\n+{\n+\tstd::unique_ptr<RawFrameGenerator> rawFrameGenerator =\n+\t\tstd::make_unique<RawFrameGenerator>();\n+\n+\t/*\n+\t * For each file in the directory, load the raw frame\n+\t * and store it. No format conversion is performed, but\n+\t * raw Bayer bytes are stored as-is.\n+\t */\n+\tfor (const auto &path : rawFrames.files) {\n+\t\tFile file(path);\n+\t\tif (!file.open(File::OpenModeFlag::ReadOnly)) {\n+\t\t\tLOG(Virtual, Error) << \"Failed to open raw frame file \"\n+\t\t\t\t\t    << file.fileName()\n+\t\t\t\t\t    << \": \" << strerror(file.error());\n+\t\t\treturn nullptr;\n+\t\t}\n+\n+\t\tauto fileSize = file.size();\n+\t\tauto buffer = std::make_unique<uint8_t[]>(fileSize);\n+\t\tif (file.read({ buffer.get(), static_cast<size_t>(fileSize) }) != fileSize) {\n+\t\t\tLOG(Virtual, Error) << \"Failed to read raw frame file \"\n+\t\t\t\t\t    << file.fileName()\n+\t\t\t\t\t    << \": \" << strerror(file.error());\n+\t\t\treturn nullptr;\n+\t\t}\n+\n+\t\trawFrameGenerator->framesDatas_.emplace_back(\n+\t\t\tRawFrameData{ std::move(buffer), static_cast<size_t>(fileSize) });\n+\t}\n+\n+\tASSERT(!rawFrameGenerator->framesDatas_.empty());\n+\n+\treturn rawFrameGenerator;\n+}\n+\n+void RawFrameGenerator::configure(const Size & /*size*/)\n+{\n+\t/*\n+\t * Raw frames cannot be scaled, the configured size is not used for\n+\t * processing but mismatches are caught in generateFrame().\n+\t */\n+\tframeIndex_ = 0;\n+\tparameter_ = 0;\n+}\n+\n+int RawFrameGenerator::generateFrame(const Size & /*size*/, const FrameBuffer *buffer)\n+{\n+\tASSERT(!framesDatas_.empty());\n+\n+\tMappedFrameBuffer mappedFrameBuffer(buffer,\n+\t\t\t\t\t    MappedFrameBuffer::MapFlag::Write);\n+\n+\tconst auto &planes = mappedFrameBuffer.planes();\n+\n+\t/* Loop only around the number of frames available */\n+\tframeIndex_ %= framesDatas_.size();\n+\n+\tconst auto &frame = framesDatas_[frameIndex_];\n+\n+\t/*\n+\t * Raw Bayer frames must exactly match the configured output size.\n+\t * They cannot be scaled to fit.\n+\t */\n+\tif (frame.size != planes[0].size()) {\n+\t\tLOG(Virtual, Error) << \"Raw frame size mismatch: file has \"\n+\t\t\t\t    << frame.size << \" bytes, buffer expects \"\n+\t\t\t\t    << planes[0].size() << \" bytes\";\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tmemcpy(planes[0].data(), frame.data.get(), frame.size);\n+\n+\t/* Proceed to the next frame on every request */\n+\tparameter_++;\n+\tif (parameter_ % frameRepeat == 0) {\n+\t\tframeIndex_++;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * \\var RawFrameGenerator::frameRepeat\n+ * \\brief Number of frames to repeat before proceeding to the next frame\n+ */\n+\n+/*\n+ * \\var RawFrameGenerator::framesDatas_\n+ * \\brief List of raw Bayer frame buffers loaded from disk\n+ */\n+\n+/* \\var RawFrameGenerator::frameIndex_\n+ * \\brief Index of the current frame in framesDatas_\n+ */\n+\n+/*\n+ * \\var RawFrameGenerator::parameter_\n+ * \\brief Counter used to implement frameRepeat behaviour\n+ */\n+\n+} /* namespace libcamera */\ndiff --git a/src/libcamera/pipeline/virtual/raw_frame_generator.h b/src/libcamera/pipeline/virtual/raw_frame_generator.h\nnew file mode 100644\nindex 00000000..d711a47d\n--- /dev/null\n+++ b/src/libcamera/pipeline/virtual/raw_frame_generator.h\n@@ -0,0 +1,47 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2026, max.bretschneider@leica-geosystems.com\n+ *\n+ * Raw Bayer frame generator for the virtual pipeline handler\n+ */\n+\n+#pragma once\n+\n+#include <filesystem>\n+#include <memory>\n+#include <stdint.h>\n+#include <vector>\n+\n+#include \"frame_generator.h\"\n+\n+namespace libcamera {\n+\n+/* Frame configuration provided by the config file */\n+struct RawFrames {\n+\tstd::vector<std::filesystem::path> files;\n+\tuint32_t cfaPattern;\n+\tunsigned int bitDepth;\n+};\n+\n+class RawFrameGenerator : public FrameGenerator\n+{\n+public:\n+\tstatic std::unique_ptr<RawFrameGenerator> create(RawFrames &rawFrames);\n+\n+private:\n+\tstatic constexpr unsigned int frameRepeat = 1; /*advance every frame*/\n+\n+\tstruct RawFrameData {\n+\t\tstd::unique_ptr<uint8_t[]> data;\n+\t\tsize_t size;\n+\t};\n+\n+\tvoid configure(const Size &size) override;\n+\tint generateFrame(const Size &size, const FrameBuffer *buffer) override;\n+\n+\tstd::vector<RawFrameData> framesDatas_;\n+\tunsigned int frameIndex_;\n+\tunsigned int parameter_;\n+};\n+\n+} /* namespace libcamera */\n","prefixes":["1/2"]}