From patchwork Fri May 1 10:52:13 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Max Bretschneider X-Patchwork-Id: 26591 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 B81D2BDCB5 for ; Fri, 1 May 2026 10:52:20 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 661766301A; Fri, 1 May 2026 12:52:20 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (2048-bit key; unprotected) header.d=protonmail.com header.i=@protonmail.com header.b="UqC0zwWV"; dkim-atps=neutral Received: from mail-43166.protonmail.ch (mail-43166.protonmail.ch [185.70.43.166]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id A725B62FE8 for ; Fri, 1 May 2026 12:52:18 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=protonmail.com; s=protonmail3; t=1777632738; x=1777891938; bh=ZJPOY3/OwUQG4BZcbsfUxqqrPOe0aqRGxct2M8cNYEw=; h=Date:To:From:Cc:Subject:Message-ID:In-Reply-To:References: Feedback-ID:From:To:Cc:Date:Subject:Reply-To:Feedback-ID: Message-ID:BIMI-Selector; b=UqC0zwWVupwyTxL9Ct9aqFQiPp9rbdS3kLIbv1Z5rDdyEtt4+nI7XQ8etg5fVKmFt WzwyhhDd8p4EMxjI36nZnlEHL7bZNZpIF+V1swdJo7Orw9msRY/ObI7flYG1sfRoFe lcBHwWCs2qu7fT1wAPmtgVAtzvB1PYOqVO/CRojGREiZh7zn4czaEEo0Xabz/yGHFc 7koxeDzJ/mIwJ2zp1F8IlePEXGt9YfMQ+HD9OQER5xlABkdnfxinKawJluc93pnbU2 ZF5WZzXU1FecM8Mqqop6cSiI0HTk7rEN+tcYzkA2Zim3vrv7an2gG7bLnkhaGlOmVR cVkY1IuJRknNQ== Date: Fri, 01 May 2026 10:52:13 +0000 To: libcamera-devel@lists.libcamera.org From: Max Bretschneider Cc: Max Bretschneider Subject: [RFC PATCH v1 3/3] libcamera: pipeline: virtual: Support StreamRole::Raw and Bayer formats Message-ID: <20260501105137.439519-4-maxbretschneider@protonmail.com> In-Reply-To: <20260501105137.439519-1-maxbretschneider@protonmail.com> References: <20260501105137.439519-1-maxbretschneider@protonmail.com> Feedback-ID: 122687743:user:proton X-Pm-Message-ID: 90c2765669d8b9305bc89b33b05cb288f6a206ee 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" Extend generateConfiguration() to return a Bayer StreamConfiguration when the StreamRole::Raw is requested on a camera configured with raw_frames. Extend validate() to allow Bayer formats, also set the correct stride and frameSize for single plane Bayer buffers. Sensor properties required for SoftISP integration are also declared in match(). Signed-off-by: Max Bretschneider --- src/libcamera/pipeline/virtual/virtual.cpp | 123 ++++++++++++++++++--- src/libcamera/pipeline/virtual/virtual.h | 3 +- 2 files changed, 109 insertions(+), 17 deletions(-) -- 2.43.0 diff --git a/src/libcamera/pipeline/virtual/virtual.cpp b/src/libcamera/pipeline/virtual/virtual.cpp index 81d2ddda..beec2f5a 100644 --- a/src/libcamera/pipeline/virtual/virtual.cpp +++ b/src/libcamera/pipeline/virtual/virtual.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -37,7 +38,6 @@ #include "libcamera/internal/framebuffer.h" #include "libcamera/internal/pipeline_handler.h" #include "libcamera/internal/request.h" -#include "libcamera/internal/value_node.h" #include "pipeline/virtual/config_parser.h" @@ -202,21 +202,28 @@ CameraConfiguration::Status VirtualCameraConfiguration::validate() adjusted = true; } - if (cfg.pixelFormat != formats::NV12) { - cfg.pixelFormat = formats::NV12; - status = Adjusted; - adjusted = true; - } + const PixelFormatInfo &fmtInfo = PixelFormatInfo::info(cfg.pixelFormat); + const bool rawStream = fmtInfo.colourEncoding == PixelFormatInfo::ColourEncodingRAW; - if (cfg.colorSpace != ColorSpace::Rec709) { - cfg.colorSpace = ColorSpace::Rec709; - status = Adjusted; - adjusted = true; - } + if (!rawStream) { + if (cfg.pixelFormat != formats::NV12) { + cfg.pixelFormat = formats::NV12; + status = Adjusted; + adjusted = true; + } - if (validateColorSpaces() == Adjusted) { - status = Adjusted; - adjusted = true; + if (cfg.colorSpace != ColorSpace::Rec709) { + cfg.colorSpace = ColorSpace::Rec709; + status = Adjusted; + adjusted = true; + } + + if (validateColorSpaces() == Adjusted) { + status = Adjusted; + adjusted = true; + } + } else { + cfg.colorSpace = ColorSpace::Raw; } if (adjusted) @@ -267,7 +274,64 @@ PipelineHandlerVirtual::generateConfiguration(Camera *camera, case StreamRole::Viewfinder: break; - case StreamRole::Raw: + case StreamRole::Raw: { + if (!std::holds_alternative(data->config_.frame)) { + LOG(Virtual, Error) + << "StreamRole::Raw requested but camera is not configured with raw_frames"; + return {}; + } + + const auto &rawFrames = std::get(data->config_.frame); + PixelFormat rawFormat; + + /* + * \todo Possibly replace with a lookup table to + * be able to just index by (cfaPatter, bitDepth), + * might be cleaner. + */ + auto bayerFormat = [&](PixelFormat f8, PixelFormat f10, + PixelFormat f12, PixelFormat f14, + PixelFormat f16) { + switch (rawFrames.bitDepth) { + case 8: + return f8; + case 10: + return f10; + case 12: + return f12; + case 14: + return f14; + default: + return f16; + } + }; + + /* Map bayer order and bit depth to pixel format */ + if (rawFrames.cfaPattern == properties::draft::ColorFilterArrangementEnum::RGGB) + rawFormat = bayerFormat(formats::SRGGB8, formats::SRGGB10, formats::SRGGB12, formats::SRGGB14, formats::SRGGB16); + else if (rawFrames.cfaPattern == properties::draft::ColorFilterArrangementEnum::BGGR) + rawFormat = bayerFormat(formats::SBGGR8, formats::SBGGR10, formats::SBGGR12, formats::SBGGR14, formats::SBGGR16); + else if (rawFrames.cfaPattern == properties::draft::ColorFilterArrangementEnum::GRBG) + rawFormat = bayerFormat(formats::SGRBG8, formats::SGRBG10, formats::SGRBG12, formats::SGRBG14, formats::SGRBG16); + else + rawFormat = bayerFormat(formats::SGBRG8, formats::SGBRG10, formats::SGBRG12, formats::SGBRG14, formats::SGBRG16); + + /* + * Use the Bayer format matching the raw frame + * configuration. + */ + std::map> rawStreamFormats; + rawStreamFormats[rawFormat] = { { data->config_.minResolutionSize, data->config_.maxResolutionSize } }; + StreamFormats rawFormats(rawStreamFormats); + StreamConfiguration rawCfg(rawFormats); + rawCfg.pixelFormat = rawFormat; + rawCfg.size = data->config_.maxResolutionSize; + rawCfg.bufferCount = VirtualCameraConfiguration::kBufferCount; + rawCfg.colorSpace = ColorSpace::Raw; + config->addConfiguration(rawCfg); + continue; + } + default: LOG(Virtual, Error) << "Requested stream role not supported: " << role; @@ -401,6 +465,28 @@ bool PipelineHandlerVirtual::match([[maybe_unused]] DeviceEnumerator *enumerator std::set streams; for (auto &streamConfig : data->streamConfigs_) streams.insert(&streamConfig.stream); + + /* Add sensor properties and controls required by SoftISP for raw streams */ + if (std::holds_alternative(data->config_.frame)) { + const auto &rawFrames = std::get(data->config_.frame); + data->properties_.set(properties::draft::ColorFilterArrangement, static_cast(rawFrames.cfaPattern)); + data->properties_.set(properties::PixelArraySize, data->config_.maxResolutionSize); + data->properties_.set(properties::UnitCellSize, Size(1000, 1000)); + + /* Extends existing controlInfo_ with SoftISP required controls */ + ControlInfoMap::Map controls; + for (const auto &[id, info] : data->controlInfo_) + controls[id] = info; + + /* \todo Allow configuration via YAML */ + controls[&controls::AnalogueGain] = ControlInfo(1.0f, 16.0f, 1.0f); + controls[&controls::ExposureTime] = ControlInfo(100, 33333, 10000); + controls[&controls::AeEnable] = ControlInfo(false, true, true); + controls[&controls::AwbEnable] = ControlInfo(false, true, true); + + data->controlInfo_ = ControlInfoMap(std::move(controls), controls::controls); + } + std::string id = data->config_.id; std::shared_ptr camera = Camera::create(std::move(data), id, streams); @@ -434,7 +520,12 @@ bool PipelineHandlerVirtual::initFrameGenerator(Camera *camera) [&](ImageFrames &imageFrames) { for (auto &streamConfig : data->streamConfigs_) streamConfig.frameGenerator = ImageFrameGenerator::create(imageFrames); - } }, + }, + [&](RawFrames &rawFrames) { + for (auto &streamConfig : data->streamConfigs_) + streamConfig.frameGenerator = RawFrameGenerator::create(rawFrames); + }, + }, frame); for (auto &streamConfig : data->streamConfigs_) diff --git a/src/libcamera/pipeline/virtual/virtual.h b/src/libcamera/pipeline/virtual/virtual.h index 215e56fa..48392808 100644 --- a/src/libcamera/pipeline/virtual/virtual.h +++ b/src/libcamera/pipeline/virtual/virtual.h @@ -23,11 +23,12 @@ #include "frame_generator.h" #include "image_frame_generator.h" +#include "raw_frame_generator.h" #include "test_pattern_generator.h" namespace libcamera { -using VirtualFrame = std::variant; +using VirtualFrame = std::variant; class VirtualCameraData : public Camera::Private, public Thread,