From patchwork Tue Oct 13 19:43:44 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Robert Mader X-Patchwork-Id: 28445 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 4070EC32F4 for ; Tue, 13 Oct 2026 19:44:06 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id EE42B68D7C; Tue, 13 Oct 2026 21:44:05 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=collabora.com header.i=robert.mader@collabora.com header.b="CePALUch"; dkim-atps=neutral Received: from sender5-op-o11.zoho.com (sender5-op-o11.zoho.com [165.173.182.11]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 98080689FB for ; Tue, 13 Oct 2026 21:44:04 +0200 (CEST) ARC-Seal: i=1; a=rsa-sha256; t=1791920640; cv=none; d=zohomail.com; s=zohoarc; b=UCEVPJCFmSIpCqy57e9X55+DCl921EFxAwY84n0TQnvrA+a/Bqv7TV6k+7J/BC2l7TlVhqHN5Zbhl8tu2hN8DuZkqhchaMr7n9dsubGiFxnW9zqaS2p/uZc0cOaPdA66XQMgE2ACI4ECUW91aR2fbkMCMCyx4kjoqMe5lhbjQ34= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1791920640; h=Content-Type:Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:MIME-Version:Message-ID:Subject:Subject:To:To:Message-Id:Reply-To; bh=NfE5k/qHVz9t/zuYflWqFUUD0ekuFdaLtS1MWR9EhvI=; b=jI0GEkaBLu9oP7AN/ZFgEUj+uBcj5MfXIidUOFUYJIJaY+Y8Qfh56PogqOpSgzPxCZroincAledS11mZcdsKEpm3CHrsOmtI7DZ5Hx+3+I87Br3KoMeJRxOb8HIMy1jrNS4jMc8rkAtMiB6YAmXdm193kecn8g2kAw3jBMav/us= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass header.i=collabora.com; spf=pass smtp.mailfrom=robert.mader@collabora.com; dmarc=pass header.from= DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; t=1791920640; s=zohomail; d=collabora.com; i=robert.mader@collabora.com; h=From:From:To:To:Cc:Cc:Subject:Subject:Date:Date:Message-ID:In-Reply-To:MIME-Version:Content-Type:Content-Transfer-Encoding:Message-Id:Reply-To; bh=NfE5k/qHVz9t/zuYflWqFUUD0ekuFdaLtS1MWR9EhvI=; b=CePALUchNQVGywtovSXXI8aChtjrAZdiQnyBMf/4ATDYo8OJDgVSCfq6E9zhSUYE Nu9XAdGalTQ9R18CS4ySPg7sol0Z41sRZQKvPoUlDCvOC1UGkTiwuugWrUADG8tEryJ XBYKobskGgl86zn28mGfH2rIVx3/wks0vfY4KayE= Received: by smtp.zohomail.com with SMTPS id 1791920638048669.0706230401444; Tue, 13 Oct 2026 12:43:58 -0700 (PDT) From: Robert Mader To: libcamera-devel@lists.libcamera.org Cc: Robert Mader , =?utf-8?q?Barnab=C3=A1s_P?= =?utf-8?b?xZFjemU=?= , Kieran Bingham Subject: [PATCH v3 1/2] virtual: Fill alpha channels Date: Tue, 13 Oct 2026 21:43:44 +0200 Message-ID: <20261013194345.98661-2-robert.mader@collabora.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261013194345.98661-1-robert.mader@collabora.com> References: <20261013194345.98661-1-robert.mader@collabora.com> 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" While libyuv::ARGBToNV12() seems to ignore the alpha values, let's set them to fully opaque to avoid potential glitches going forward. Signed-off-by: Robert Mader Reviewed-by: Barnabás Pőcze Reviewed-by: Kieran Bingham --- src/libcamera/pipeline/virtual/test_pattern_generator.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libcamera/pipeline/virtual/test_pattern_generator.cpp b/src/libcamera/pipeline/virtual/test_pattern_generator.cpp index 1be3847d63a4..7b53e8b32327 100644 --- a/src/libcamera/pipeline/virtual/test_pattern_generator.cpp +++ b/src/libcamera/pipeline/virtual/test_pattern_generator.cpp @@ -100,7 +100,7 @@ void ColorBarsGenerator::configure(const Size &size) *buf++ = kColorBar[index][2]; /* B */ *buf++ = kColorBar[index][1]; /* G */ *buf++ = kColorBar[index][0]; /* R */ - *buf++ = 0x00; /* A */ + *buf++ = 0xff; /* A */ } } } @@ -127,7 +127,7 @@ void DiagonalLinesGenerator::configure(const Size &size) *buf++ = kColorBar[index][2]; /* B */ *buf++ = kColorBar[index][1]; /* G */ *buf++ = kColorBar[index][0]; /* R */ - *buf++ = 0x00; /* A */ + *buf++ = 0xff; /* A */ } } } From patchwork Tue Oct 13 19:43:45 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Robert Mader X-Patchwork-Id: 28446 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 F0C2EC32F4 for ; Tue, 13 Oct 2026 19:44:09 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id AA88268D8D; Tue, 13 Oct 2026 21:44:09 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=collabora.com header.i=robert.mader@collabora.com header.b="Tsca+jUr"; dkim-atps=neutral Received: from sender5-op-o11.zoho.com (sender5-op-o11.zoho.com [165.173.182.11]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 85E8368D8D for ; Tue, 13 Oct 2026 21:44:06 +0200 (CEST) ARC-Seal: i=1; a=rsa-sha256; t=1791920642; cv=none; d=zohomail.com; s=zohoarc; b=c1sQ0mzjyohwydFgTlXFJFA5+bvbshq3BVVnfKkuEmgkBjAHf1Ci4Kwiy/lvT0o9oMfKREHJasc6vURvGqAMuGcPOkPfXy13mPEybBoRBuVB/pNhGLR6cVDgW2a5spcbdTeRRSeez7N3EalXqNSuzN/cUivdT0L20khAZTEwHAY= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1791920642; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:MIME-Version:Message-ID:Subject:Subject:To:To:Message-Id:Reply-To; bh=v4WpkNlWm628j1fdke29V3qMpthIOUf2tPzZgbdjgTo=; b=RedfXhMs40RVTCL7lxcIITpiWoqp9P6oc/CoYViGGCPboAljjQnfM7H/lkQrYOm4JipntphUZ970SlVMDjhjMh7T5ttXAkd7fiQhtVv/OJKrkVNqJRtCA187m3+w20x31MtnkDmY+nw3hcy+bDrhZGReO77XhK6Jek6jwiBYkjE= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass header.i=collabora.com; spf=pass smtp.mailfrom=robert.mader@collabora.com; dmarc=pass header.from= DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; t=1791920642; s=zohomail; d=collabora.com; i=robert.mader@collabora.com; h=From:From:To:To:Cc:Cc:Subject:Subject:Date:Date:Message-ID:In-Reply-To:MIME-Version:Content-Transfer-Encoding:Message-Id:Reply-To; bh=v4WpkNlWm628j1fdke29V3qMpthIOUf2tPzZgbdjgTo=; b=Tsca+jUrdkrprsDSXK8CObtKJhnXW7OH6TELoht84XbHAab5IY49Px8tFd7i+8OK bUqXB6gD2BAmKO7Wh9cEMnzYDML2U27p2EWlSuUspaPIZlV+aYdZFW7MOGaiEV/5fPE CVBW7xDCRJwkfBNxPb4TVGTy6TFkXnBcwzEyVbpM= Received: by smtp.zohomail.com with SMTPS id 1791920639897965.1222287354702; Tue, 13 Oct 2026 12:43:59 -0700 (PDT) From: Robert Mader To: libcamera-devel@lists.libcamera.org Cc: Robert Mader Subject: [PATCH v3 2/2] virtual: Support more common pixel formats Date: Tue, 13 Oct 2026 21:43:45 +0200 Message-ID: <20261013194345.98661-3-robert.mader@collabora.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261013194345.98661-1-robert.mader@collabora.com> References: <20261013194345.98661-1-robert.mader@collabora.com> 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" While NV12 is the most common native format for hardware video encoders, YUV420 is the equivalent for most software video encoders. Thus supporting both is great for testing. Similarly having some RGB formats with and without alpha can be very useful, especially as the swISP is currently limited to such formats. Signed-off-by: Robert Mader --- .../pipeline/virtual/frame_generator.h | 3 +- .../virtual/image_frame_generator.cpp | 55 ++++++++++++++++--- .../pipeline/virtual/image_frame_generator.h | 2 +- .../virtual/test_pattern_generator.cpp | 46 ++++++++++++++-- .../pipeline/virtual/test_pattern_generator.h | 2 +- src/libcamera/pipeline/virtual/virtual.cpp | 41 ++++++++++---- 6 files changed, 123 insertions(+), 26 deletions(-) diff --git a/src/libcamera/pipeline/virtual/frame_generator.h b/src/libcamera/pipeline/virtual/frame_generator.h index a0658c45cd82..fd34a91a5e50 100644 --- a/src/libcamera/pipeline/virtual/frame_generator.h +++ b/src/libcamera/pipeline/virtual/frame_generator.h @@ -9,6 +9,7 @@ #include #include +#include namespace libcamera { @@ -19,7 +20,7 @@ public: virtual void configure(const Size &size) = 0; - virtual int generateFrame(const Size &size, + virtual int generateFrame(const StreamConfiguration &cfg, const FrameBuffer *buffer) = 0; protected: diff --git a/src/libcamera/pipeline/virtual/image_frame_generator.cpp b/src/libcamera/pipeline/virtual/image_frame_generator.cpp index 1853bddeca27..5c56db96d8e2 100644 --- a/src/libcamera/pipeline/virtual/image_frame_generator.cpp +++ b/src/libcamera/pipeline/virtual/image_frame_generator.cpp @@ -14,7 +14,9 @@ #include +#include #include "libcamera/internal/dma_buf_allocator.h" +#include "libcamera/internal/formats.h" #include "libcamera/internal/mapped_framebuffer.h" #include "libyuv/convert.h" @@ -124,7 +126,7 @@ void ImageFrameGenerator::configure(const Size &size) } } -int ImageFrameGenerator::generateFrame(const Size &size, const FrameBuffer *buffer) +int ImageFrameGenerator::generateFrame(const StreamConfiguration &cfg, const FrameBuffer *buffer) { ASSERT(!scaledFrameDatas_.empty()); @@ -141,16 +143,55 @@ int ImageFrameGenerator::generateFrame(const Size &size, const FrameBuffer *buff MappedFrameBuffer::MapFlag::Write); const auto &planes = mappedFrameBuffer.planes(); + auto size = cfg.size; + const PixelFormatInfo &info = PixelFormatInfo::info(cfg.pixelFormat); /* Loop only around the number of images available */ frameIndex_ %= imageFrameDatas_.size(); /* Write the scaledY and scaledUV to the mapped frame buffer */ - libyuv::NV12Copy(scaledFrameDatas_[frameIndex_].Y.get(), size.width, - scaledFrameDatas_[frameIndex_].UV.get(), size.width, - planes[0].data(), size.width, - planes[1].data(), size.width, - size.width, size.height); + int ret; + switch (cfg.pixelFormat) { + case formats::NV12: + ret = libyuv::NV12Copy(scaledFrameDatas_[frameIndex_].Y.get(), + size.width, + scaledFrameDatas_[frameIndex_].UV.get(), + size.width, + planes[0].data(), + info.stride(size.width, 0), + planes[1].data(), + info.stride(size.width, 1), + size.width, size.height); + break; + case formats::YUV420: + ret = libyuv::NV12ToI420(scaledFrameDatas_[frameIndex_].Y.get(), + size.width, + scaledFrameDatas_[frameIndex_].UV.get(), + size.width, + planes[0].data(), + info.stride(size.width, 0), + planes[1].data(), + info.stride(size.width, 1), + planes[2].data(), + info.stride(size.width, 2), + size.width, size.height); + break; + case formats::ABGR8888: + case formats::XBGR8888: + ret = libyuv::NV12ToARGB(scaledFrameDatas_[frameIndex_].Y.get(), + size.width, + scaledFrameDatas_[frameIndex_].UV.get(), + size.width, + planes[0].data(), + info.stride(size.width, 0), + size.width, size.height); + break; + default: + LOG(Virtual, Error) << "Unsupported format"; + return -1; + } + if (ret != 0) + LOG(Virtual, Error) << "Conversation failed with " << ret; /* Proceed to the next image every 4 frames */ /* \todo Consider setting the frameRepeat in the config file */ @@ -158,7 +199,7 @@ int ImageFrameGenerator::generateFrame(const Size &size, const FrameBuffer *buff if (parameter_ % frameRepeat == 0) frameIndex_++; - return 0; + return ret; } /* diff --git a/src/libcamera/pipeline/virtual/image_frame_generator.h b/src/libcamera/pipeline/virtual/image_frame_generator.h index 42a077ba803c..19b6fe38654e 100644 --- a/src/libcamera/pipeline/virtual/image_frame_generator.h +++ b/src/libcamera/pipeline/virtual/image_frame_generator.h @@ -37,7 +37,7 @@ private: }; void configure(const Size &size) override; - int generateFrame(const Size &size, const FrameBuffer *buffer) override; + int generateFrame(const StreamConfiguration &cfg, const FrameBuffer *buffer) override; std::vector imageFrameDatas_; std::vector scaledFrameDatas_; diff --git a/src/libcamera/pipeline/virtual/test_pattern_generator.cpp b/src/libcamera/pipeline/virtual/test_pattern_generator.cpp index 7b53e8b32327..4cde32176661 100644 --- a/src/libcamera/pipeline/virtual/test_pattern_generator.cpp +++ b/src/libcamera/pipeline/virtual/test_pattern_generator.cpp @@ -11,7 +11,9 @@ #include +#include #include "libcamera/internal/dma_buf_allocator.h" +#include "libcamera/internal/formats.h" #include "libcamera/internal/mapped_framebuffer.h" #include @@ -42,7 +44,7 @@ LOG_DECLARE_CATEGORY(Virtual) static const unsigned int kARGBSize = 4; -int TestPatternGenerator::generateFrame(const Size &size, +int TestPatternGenerator::generateFrame(const StreamConfiguration &cfg, const FrameBuffer *buffer) { /* @@ -58,16 +60,48 @@ int TestPatternGenerator::generateFrame(const Size &size, MappedFrameBuffer::MapFlag::Write); const auto &planes = mappedFrameBuffer.planes(); + const auto size = cfg.size; + const PixelFormatInfo &info = PixelFormatInfo::info(cfg.pixelFormat); rotateLeft1Column(size, template_.get()); /* Convert the template_ to the frame buffer */ - int ret = libyuv::ARGBToNV12(template_.get(), size.width * kARGBSize, - planes[0].data(), size.width, - planes[1].data(), size.width, - size.width, size.height); + int ret; + switch (cfg.pixelFormat) { + case formats::NV12: + ret = libyuv::ARGBToNV12(template_.get(), + size.width * kARGBSize, + planes[0].data(), + info.stride(size.width, 0), + planes[1].data(), + info.stride(size.width, 1), + size.width, size.height); + break; + case formats::YUV420: + ret = libyuv::ARGBToI420(template_.get(), + size.width * kARGBSize, + planes[0].data(), + info.stride(size.width, 0), + planes[1].data(), + info.stride(size.width, 1), + planes[2].data(), + info.stride(size.width, 2), + size.width, size.height); + break; + case formats::ABGR8888: + case formats::XBGR8888: + ret = libyuv::ARGBCopy(template_.get(), + size.width * kARGBSize, + planes[0].data(), + info.stride(size.width, 0), + size.width, size.height); + break; + default: + LOG(Virtual, Error) << "Unsupported format"; + return -1; + } if (ret != 0) - LOG(Virtual, Error) << "ARGBToNV12() failed with " << ret; + LOG(Virtual, Error) << "Conversation failed with " << ret; return ret; } diff --git a/src/libcamera/pipeline/virtual/test_pattern_generator.h b/src/libcamera/pipeline/virtual/test_pattern_generator.h index 2a51bd31abdf..9f72d652ab2c 100644 --- a/src/libcamera/pipeline/virtual/test_pattern_generator.h +++ b/src/libcamera/pipeline/virtual/test_pattern_generator.h @@ -24,7 +24,7 @@ enum class TestPattern : char { class TestPatternGenerator : public FrameGenerator { public: - int generateFrame(const Size &size, const FrameBuffer *buffer) override; + int generateFrame(const StreamConfiguration &cfg, const FrameBuffer *buffer) override; protected: /* Buffer of test pattern template */ diff --git a/src/libcamera/pipeline/virtual/virtual.cpp b/src/libcamera/pipeline/virtual/virtual.cpp index e25dd85a7391..7ed2d0b7f596 100644 --- a/src/libcamera/pipeline/virtual/virtual.cpp +++ b/src/libcamera/pipeline/virtual/virtual.cpp @@ -47,6 +47,13 @@ LOG_DEFINE_CATEGORY(Virtual) namespace { +constexpr PixelFormat outputFormats[] = { + formats::NV12, + formats::YUV420, + formats::ABGR8888, + formats::XBGR8888, +}; + uint64_t currentTimestamp() { const auto now = std::chrono::steady_clock::now(); @@ -148,7 +155,7 @@ void VirtualCameraData::processRequest(Request *request) found = true; if (streamConfig.frameGenerator->generateFrame( - stream->configuration().size, buffer)) + stream->configuration(), buffer)) fmd.status = FrameMetadata::Status::FrameError; bufferCompleted.emit(buffer); @@ -202,17 +209,31 @@ CameraConfiguration::Status VirtualCameraConfiguration::validate() adjusted = true; } - if (cfg.pixelFormat != formats::NV12) { - cfg.pixelFormat = formats::NV12; + if (std::find(std::begin(outputFormats), + std::end(outputFormats), + cfg.pixelFormat) == std::end(outputFormats)) { + cfg.pixelFormat = outputFormats[0]; status = Adjusted; adjusted = true; } - if (cfg.colorSpace != ColorSpace::Smpte170m) { - cfg.colorSpace = ColorSpace::Smpte170m; + const PixelFormatInfo &info = PixelFormatInfo::info(cfg.pixelFormat); + std::optional colorSpace; + switch (info.colourEncoding) { + case PixelFormatInfo::ColourEncodingRGB: + colorSpace = ColorSpace::Srgb; + break; + case PixelFormatInfo::ColourEncodingYUV: + colorSpace = ColorSpace::Smpte170m; + break; + default: + ASSERT(false); + } + if (cfg.colorSpace && cfg.colorSpace != colorSpace) { status = Adjusted; adjusted = true; } + cfg.colorSpace = colorSpace; if (validateColorSpaces() == Adjusted) { status = Adjusted; @@ -223,7 +244,6 @@ CameraConfiguration::Status VirtualCameraConfiguration::validate() LOG(Virtual, Info) << "Stream configuration adjusted to " << cfg.toString(); - const PixelFormatInfo &info = PixelFormatInfo::info(cfg.pixelFormat); cfg.stride = info.stride(cfg.size.width, 0, 1); cfg.frameSize = info.frameSize(cfg.size, 1); @@ -275,12 +295,13 @@ PipelineHandlerVirtual::generateConfiguration(Camera *camera, } std::map> streamFormats; - PixelFormat pixelFormat = formats::NV12; - streamFormats[pixelFormat] = { { data->config_.minResolutionSize, - data->config_.maxResolutionSize } }; + for (auto pixelFormat : outputFormats) { + streamFormats[pixelFormat] = { { data->config_.minResolutionSize, + data->config_.maxResolutionSize } }; + } StreamFormats formats(streamFormats); StreamConfiguration cfg(formats); - cfg.pixelFormat = pixelFormat; + cfg.pixelFormat = outputFormats[0]; cfg.size = data->config_.maxResolutionSize; cfg.bufferCount = VirtualCameraConfiguration::kBufferCount; cfg.colorSpace = ColorSpace::Smpte170m;