From patchwork Wed Nov 26 09:38:51 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 25210 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 F2857C32DE for ; Wed, 26 Nov 2025 09:39:12 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id BFE2760AA2; Wed, 26 Nov 2025 10:39:09 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="imqtkQT4"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 66DE9609E0 for ; Wed, 26 Nov 2025 10:39:06 +0100 (CET) Received: from [192.168.0.172] (mob-5-90-63-219.net.vodafone.it [5.90.63.219]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id D3D386DE; Wed, 26 Nov 2025 10:36:56 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1764149817; bh=mDBl7Wvas50tmJkad3FT4JCKFaF+rjyT+QZXO++qVDc=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=imqtkQT48inwC/Rc7J5kNUsXEAuRTCj+9p4E9ELw1OHOt5U0mJ4svknYb/+cOhli+ DV3z4deTu75yANMOgQCgxhPxGqJVWx1aI8NwRh2lpbCh+lznHFC6jVAaCSwr1PuoVY oVsLinN69WRNzCkJMxzkEauqIYXasywWrNRN2Cto= From: Jacopo Mondi Date: Wed, 26 Nov 2025 10:38:51 +0100 Subject: [PATCH v2 1/3] libcamera: request: Create control list with Camera info map MIME-Version: 1.0 Message-Id: <20251126-cam-control-override-v2-1-305024a1f190@ideasonboard.com> References: <20251126-cam-control-override-v2-0-305024a1f190@ideasonboard.com> In-Reply-To: <20251126-cam-control-override-v2-0-305024a1f190@ideasonboard.com> To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Kieran Bingham X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=1562; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=mDBl7Wvas50tmJkad3FT4JCKFaF+rjyT+QZXO++qVDc=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBpJsq5/h0OrfdHkCZS/yUyRM0i9tA7mHkGrCH9B V0Hv22d39SJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCaSbKuQAKCRByNAaPFqFW PJBhEADEa3SE656Rtwv/v1CJkk7pgDhO6FLyWRSF7FbloTjyasiLYmqqN78YkRXCXPBGsv0waHy whcHUC6C97cZNZ57MX7gJEtf4KBqjQGuQpafHQ9qoaKS4J2GPlU+Dmn7Dd6dtXB+iCcKXiUREKm JzrPtHkxsGoho8h5XUCHpSoQfwlPfVxp66zssbhQ8gL1RelcqC1Dd3rSNtQoDkKEoC8dZAq3/Nt h8mP339Q/7mwtQQXZ18qSVywzSYTFrZU42qPAs5ylxvjR7rD7rn2Zj/pLuVwBBvk0thuJysX2Nk gJKjfOGs00h1iDAaRqwHrSCFF3jfXUu5K6ZR4c4HL2khsLC98lRFnm9cLeDZVEHlKu57Jl+bi33 pbNyg0n/OfMKFDPE3ABJh7kYArCfOEULwzCsVbx8vt71yxWDe2UMV/HJJrOTipMI5t+5nw8lnzS TkQzriyDRVHpGpIwYNVku5athpm+pU1OMu4xdst54nyVf0665wbi9ZRm2QDdHqidFC+Wa4AtBmW qS4wE73VEkwq9fZtY6Q7aYRLXSJ5cCifMQVCI3/yll6gjgTYpeR2CSlUBuDOIjeqdXdKKhC8YkJ ElpyVLmW4NoPQL2+CbQ1qFFkQ6Z7nVticIoSbszM2JoxQjdz6qHEAxlyYS9mwgzmiU4BfShw1iA X6+oknc9LWPNbzw== X-Developer-Key: i=jacopo.mondi@ideasonboard.com; a=openpgp; fpr=72392EDC88144A65C701EA9BA5826A2587AD026B 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" The control lists associated with a Request is created with the global libcamera::controls::controls id map. This is fine as the idmap/info map used to contruct a ControlList are not used for any control validation purposes by the libcamera code. However creating a ControlList with the camera ControlInfoMap has two advantages: 1) The idmap can be extracted from the info map, but not the other way around 2) The control list is constructed with a valid map of info to the controls it can actually supports, instead of the global map of libcamera controls Initialize the ControlList part of a Request with the Camera control info map, as the association between a Request and a Camera is permanent and valid for the whole lifetime of a Request. Signed-off-by: Jacopo Mondi Reviewed-by: Kieran Bingham --- src/libcamera/request.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libcamera/request.cpp b/src/libcamera/request.cpp index 60565f5984971c7ab34e119a15b8141799f71071..2544a059f6984d930ec909c74e0b621c9fe82726 100644 --- a/src/libcamera/request.cpp +++ b/src/libcamera/request.cpp @@ -356,7 +356,7 @@ Request::Request(Camera *camera, uint64_t cookie) : Extensible(std::make_unique(camera)), cookie_(cookie), status_(RequestPending) { - controls_ = new ControlList(controls::controls, + controls_ = new ControlList(camera->controls(), camera->_d()->validator()); /** From patchwork Wed Nov 26 09:38:52 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 25211 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 62880C32EF for ; Wed, 26 Nov 2025 09:39:14 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 5B33B60AA1; Wed, 26 Nov 2025 10:39:11 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="YaCKX7iY"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id CEA656069A for ; Wed, 26 Nov 2025 10:39:06 +0100 (CET) Received: from [192.168.0.172] (mob-5-90-63-219.net.vodafone.it [5.90.63.219]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 4437D10D4; Wed, 26 Nov 2025 10:36:57 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1764149817; bh=QFCYggUnmgJLhzJC16sTgHRAjjav1gL655R+S5+bvto=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=YaCKX7iY2sPNHZfrwl+U5wrEIbOFXMHWpvP/7FPWrmQ0nYgSnCPOF2uQPdsJ9BXKS gfw6ghntcmFtLLhHONLOJefvoKclMqfLHdWBP3J9crkaf2CWRIFUwk1VXao9IPhris Em5Z6/cobbk7Ariz8R9Awt9bSSIny7NBJ1al3Eaw= From: Jacopo Mondi Date: Wed, 26 Nov 2025 10:38:52 +0100 Subject: [PATCH v2 2/3] libcamera: request: Move metadata_ to Private MIME-Version: 1.0 Message-Id: <20251126-cam-control-override-v2-2-305024a1f190@ideasonboard.com> References: <20251126-cam-control-override-v2-0-305024a1f190@ideasonboard.com> In-Reply-To: <20251126-cam-control-override-v2-0-305024a1f190@ideasonboard.com> To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=19843; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=QFCYggUnmgJLhzJC16sTgHRAjjav1gL655R+S5+bvto=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBpJsq5FThkeyfaAsBxihvVHhUjC8SeXOlqT526T fJi6QFnrLCJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCaSbKuQAKCRByNAaPFqFW PNseD/0eLjxFznNHT/iKhe+VAbhbejN1/CSttqDJtdZ9FhH0aitPuABkE4WuTioeSZIvfYESoC2 ZUkjyB0wECaBkZTlIsX6qPzqHjSfC7UhoxFRVIu/g/aG6wnLDiKKTj5hdpmi1bKQ4VE4y3AtvrG 2kUdO/FjlZjeU9ybqFi3IVIP/rDTxfop9SEQBOKxGM36u2iVMnPxtDjy7lgf3Ni5FkGMLHvi1wR MCx6zJhYeMeWVyB4lDYai5mdyWZOlfbWSk9H/FLo9JKW0OlZX8rAQwAO7IWt3MTAUDQoEO1c6ED GKKUem9OOmlBnZNtRFueoolltzFTD6GnU7+KM908hZGjSdcDdpmLS4bInlD55oJIihBWS9yk9Ad VL8XG6trsM4E0015sGIFpc7f4vKVgNTiX7m3idDNK/gjZkNUYNW2G5b3Core2H5IcH+H6G0kCiN WAf6Z8/WNL41j/v9YoIoe1ToMqHdKlAicTRBeVj93tqeh7fZj8AvrmVl8XrDIOmHQAXF1C8IfLV zsOPv3vS7XceNwGnMCpoezGmuN7k84d3FuZoWi2yGBf69lSVwptg57OeV6lxj9kMi9NODhdlm3q RVjS+Vv1t3z8WjTUrWnZaTogm2AdYChS84J2odKSdyaTIT4iVish2BAfFxywkfCFOEN/NxqSJ44 PbXxs9ShSW5Ve5Q== X-Developer-Key: i=jacopo.mondi@ideasonboard.com; a=openpgp; fpr=72392EDC88144A65C701EA9BA5826A2587AD026B 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" Address a long standing \todo item that suggested to implement a read-only interface for the Request::metadata() accessor and deflect to the internal implementation for the read-write accessor used by pipeline handlers. Signed-off-by: Jacopo Mondi --- include/libcamera/internal/request.h | 3 ++ include/libcamera/request.h | 3 +- src/libcamera/pipeline/imx8-isi/imx8-isi.cpp | 3 +- src/libcamera/pipeline/ipu3/ipu3.cpp | 16 +++++----- src/libcamera/pipeline/mali-c55/mali-c55.cpp | 2 +- src/libcamera/pipeline/rkisp1/rkisp1.cpp | 10 +++--- .../pipeline/rpi/common/pipeline_base.cpp | 13 ++++---- src/libcamera/pipeline/rpi/common/pipeline_base.h | 2 +- src/libcamera/pipeline/simple/simple.cpp | 8 ++--- src/libcamera/pipeline/uvcvideo/uvcvideo.cpp | 6 ++-- src/libcamera/pipeline/vimc/vimc.cpp | 6 ++-- src/libcamera/pipeline/virtual/virtual.cpp | 4 +-- src/libcamera/request.cpp | 37 ++++++++++++---------- 13 files changed, 61 insertions(+), 52 deletions(-) diff --git a/include/libcamera/internal/request.h b/include/libcamera/internal/request.h index 78cb99f3604504dfb7145c605835b7493b656ced..643f67cabf5778f7515c0117d06d4e0a3fdec4f8 100644 --- a/include/libcamera/internal/request.h +++ b/include/libcamera/internal/request.h @@ -36,6 +36,8 @@ public: Camera *camera() const { return camera_; } bool hasPendingBuffers() const; + ControlList &metadata() { return *metadata_; } + bool completeBuffer(FrameBuffer *buffer); void complete(); void cancel(); @@ -61,6 +63,7 @@ private: std::unordered_set pending_; std::map notifiers_; std::unique_ptr timer_; + ControlList *metadata_; }; } /* namespace libcamera */ diff --git a/include/libcamera/request.h b/include/libcamera/request.h index 0c5939f7b3336fd089a2fe231f4f6f266cc422f7..c9aeddb62680923dc3490a23dc6c3f70f70cc075 100644 --- a/include/libcamera/request.h +++ b/include/libcamera/request.h @@ -50,7 +50,7 @@ public: void reuse(ReuseFlag flags = Default); ControlList &controls() { return *controls_; } - ControlList &metadata() { return *metadata_; } + const ControlList &metadata() const; const BufferMap &buffers() const { return bufferMap_; } int addBuffer(const Stream *stream, FrameBuffer *buffer, std::unique_ptr &&fence = {}); @@ -68,7 +68,6 @@ private: LIBCAMERA_DISABLE_COPY(Request) ControlList *controls_; - ControlList *metadata_; BufferMap bufferMap_; const uint64_t cookie_; diff --git a/src/libcamera/pipeline/imx8-isi/imx8-isi.cpp b/src/libcamera/pipeline/imx8-isi/imx8-isi.cpp index 9550f54600c4d98e9d110a5e255d1ac85e2b5660..b0fb117b52e2a00fe9b5289c957442c1142fe7c6 100644 --- a/src/libcamera/pipeline/imx8-isi/imx8-isi.cpp +++ b/src/libcamera/pipeline/imx8-isi/imx8-isi.cpp @@ -26,6 +26,7 @@ #include "libcamera/internal/device_enumerator.h" #include "libcamera/internal/media_device.h" #include "libcamera/internal/pipeline_handler.h" +#include "libcamera/internal/request.h" #include "libcamera/internal/v4l2_subdevice.h" #include "libcamera/internal/v4l2_videodevice.h" @@ -1151,7 +1152,7 @@ void PipelineHandlerISI::bufferReady(FrameBuffer *buffer) Request *request = buffer->request(); /* Record the sensor's timestamp in the request metadata. */ - ControlList &metadata = request->metadata(); + ControlList &metadata = request->_d()->metadata(); if (!metadata.contains(controls::SensorTimestamp.id())) metadata.set(controls::SensorTimestamp, buffer->metadata().timestamp); diff --git a/src/libcamera/pipeline/ipu3/ipu3.cpp b/src/libcamera/pipeline/ipu3/ipu3.cpp index d6b7edcb5a7f9fcb4083d4105a193978e265ef67..49df8fe5b41035b164c3a6828b791ef084ff18c8 100644 --- a/src/libcamera/pipeline/ipu3/ipu3.cpp +++ b/src/libcamera/pipeline/ipu3/ipu3.cpp @@ -19,7 +19,6 @@ #include #include #include -#include #include #include @@ -35,6 +34,7 @@ #include "libcamera/internal/ipa_manager.h" #include "libcamera/internal/media_device.h" #include "libcamera/internal/pipeline_handler.h" +#include "libcamera/internal/request.h" #include "cio2.h" #include "frames.h" @@ -1249,7 +1249,7 @@ void IPU3CameraData::metadataReady(unsigned int id, const ControlList &metadata) return; Request *request = info->request; - request->metadata().merge(metadata); + request->_d()->metadata().merge(metadata); info->metadataProcessed = true; if (frameInfos_.tryComplete(info)) @@ -1276,12 +1276,12 @@ void IPU3CameraData::imguOutputBufferReady(FrameBuffer *buffer) pipe()->completeBuffer(request, buffer); - request->metadata().set(controls::draft::PipelineDepth, 3); + request->_d()->metadata().set(controls::draft::PipelineDepth, 3); /* \todo Actually apply the scaler crop region to the ImgU. */ const auto &scalerCrop = request->controls().get(controls::ScalerCrop); if (scalerCrop) cropRegion_ = *scalerCrop; - request->metadata().set(controls::ScalerCrop, cropRegion_); + request->_d()->metadata().set(controls::ScalerCrop, cropRegion_); if (frameInfos_.tryComplete(info)) pipe()->completeRequest(request); @@ -1321,8 +1321,8 @@ void IPU3CameraData::cio2BufferReady(FrameBuffer *buffer) * \todo The sensor timestamp should be better estimated by connecting * to the V4L2Device::frameStart signal. */ - request->metadata().set(controls::SensorTimestamp, - buffer->metadata().timestamp); + request->_d()->metadata().set(controls::SensorTimestamp, + buffer->metadata().timestamp); info->effectiveSensorControls = delayedCtrls_->get(buffer->metadata().sequence); @@ -1416,8 +1416,8 @@ void IPU3CameraData::frameStart(uint32_t sequence) return; } - request->metadata().set(controls::draft::TestPatternMode, - *testPatternMode); + request->_d()->metadata().set(controls::draft::TestPatternMode, + *testPatternMode); } REGISTER_PIPELINE_HANDLER(PipelineHandlerIPU3, "ipu3") diff --git a/src/libcamera/pipeline/mali-c55/mali-c55.cpp b/src/libcamera/pipeline/mali-c55/mali-c55.cpp index 38bdc6138ed167a2c05f43ca30e60ed549fd953c..68be4cd66050b56e50805336efa5da8dafa4d3b9 100644 --- a/src/libcamera/pipeline/mali-c55/mali-c55.cpp +++ b/src/libcamera/pipeline/mali-c55/mali-c55.cpp @@ -1528,7 +1528,7 @@ void PipelineHandlerMaliC55::statsProcessed(unsigned int requestId, MaliC55FrameInfo &frameInfo = frameInfoMap_[requestId]; frameInfo.statsDone = true; - frameInfo.request->metadata().merge(metadata); + frameInfo.request->_d()->metadata().merge(metadata); tryComplete(&frameInfo); } diff --git a/src/libcamera/pipeline/rkisp1/rkisp1.cpp b/src/libcamera/pipeline/rkisp1/rkisp1.cpp index d7195b6c484d091857a91de709e0e060810c7c32..823160f58d989600ff3213df6bbec2a015d8745f 100644 --- a/src/libcamera/pipeline/rkisp1/rkisp1.cpp +++ b/src/libcamera/pipeline/rkisp1/rkisp1.cpp @@ -25,7 +25,6 @@ #include #include #include -#include #include #include @@ -44,6 +43,7 @@ #include "libcamera/internal/media_device.h" #include "libcamera/internal/media_pipeline.h" #include "libcamera/internal/pipeline_handler.h" +#include "libcamera/internal/request.h" #include "libcamera/internal/v4l2_subdevice.h" #include "libcamera/internal/v4l2_videodevice.h" @@ -452,7 +452,7 @@ void RkISP1CameraData::metadataReady(unsigned int frame, const ControlList &meta if (!info) return; - info->request->metadata().merge(metadata); + info->request->_d()->metadata().merge(metadata); info->metadataProcessed = true; pipe()->tryCompleteRequest(info); @@ -1518,8 +1518,8 @@ void PipelineHandlerRkISP1::imageBufferReady(FrameBuffer *buffer) * \todo The sensor timestamp should be better estimated by connecting * to the V4L2Device::frameStart signal. */ - request->metadata().set(controls::SensorTimestamp, - metadata.timestamp); + request->_d()->metadata().set(controls::SensorTimestamp, + metadata.timestamp); if (isRaw_) { const ControlList &ctrls = @@ -1602,7 +1602,7 @@ void PipelineHandlerRkISP1::imageBufferReady(FrameBuffer *buffer) LOG(RkISP1, Error) << "Cannot queue buffers to dewarper: " << strerror(-ret); - request->metadata().set(controls::ScalerCrop, activeCrop_.value()); + request->_d()->metadata().set(controls::ScalerCrop, activeCrop_.value()); } void PipelineHandlerRkISP1::dewarpBufferReady(FrameBuffer *buffer) diff --git a/src/libcamera/pipeline/rpi/common/pipeline_base.cpp b/src/libcamera/pipeline/rpi/common/pipeline_base.cpp index c209aa596311a7d902e32caedc3f98cfa3da2b09..4a15839afbc32c3e57caff11904484540a61745e 100644 --- a/src/libcamera/pipeline/rpi/common/pipeline_base.cpp +++ b/src/libcamera/pipeline/rpi/common/pipeline_base.cpp @@ -1219,7 +1219,7 @@ void CameraData::metadataReady(const ControlList &metadata) /* Add to the Request metadata buffer what the IPA has provided. */ /* Last thing to do is to fill up the request metadata. */ Request *request = requestQueue_.front(); - request->metadata().merge(metadata); + request->_d()->metadata().merge(metadata); /* * Inform the sensor of the latest colour gains if it has the @@ -1490,9 +1490,9 @@ void CameraData::checkRequestCompleted() void CameraData::fillRequestMetadata(const ControlList &bufferControls, Request *request) { if (auto x = bufferControls.get(controls::SensorTimestamp)) - request->metadata().set(controls::SensorTimestamp, *x); + request->_d()->metadata().set(controls::SensorTimestamp, *x); if (auto x = bufferControls.get(controls::FrameWallClock)) - request->metadata().set(controls::FrameWallClock, *x); + request->_d()->metadata().set(controls::FrameWallClock, *x); if (cropParams_.size()) { std::vector crops; @@ -1500,10 +1500,11 @@ void CameraData::fillRequestMetadata(const ControlList &bufferControls, Request for (auto const &[k, v] : cropParams_) crops.push_back(scaleIspCrop(v.ispCrop)); - request->metadata().set(controls::ScalerCrop, crops[0]); + request->_d()->metadata().set(controls::ScalerCrop, crops[0]); if (crops.size() > 1) { - request->metadata().set(controls::rpi::ScalerCrops, - Span(crops.data(), crops.size())); + request->_d()->metadata().set(controls::rpi::ScalerCrops, + Span(crops.data(), + crops.size())); } } } diff --git a/src/libcamera/pipeline/rpi/common/pipeline_base.h b/src/libcamera/pipeline/rpi/common/pipeline_base.h index 4bce4ec4f978cedd7d10d354ce2231c9296a3e39..d9114f1cb954e541b2a35a748a95cca0af59ebbe 100644 --- a/src/libcamera/pipeline/rpi/common/pipeline_base.h +++ b/src/libcamera/pipeline/rpi/common/pipeline_base.h @@ -15,7 +15,6 @@ #include #include -#include #include "libcamera/internal/bayer_format.h" #include "libcamera/internal/camera.h" @@ -25,6 +24,7 @@ #include "libcamera/internal/media_device.h" #include "libcamera/internal/media_object.h" #include "libcamera/internal/pipeline_handler.h" +#include "libcamera/internal/request.h" #include "libcamera/internal/v4l2_videodevice.h" #include "libcamera/internal/yaml_parser.h" diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp index 97009fbc03c64108a23f9edf6df95177593f9035..58cac390eb5be7231101c92fda1caf8e8b088e63 100644 --- a/src/libcamera/pipeline/simple/simple.cpp +++ b/src/libcamera/pipeline/simple/simple.cpp @@ -27,7 +27,6 @@ #include #include #include -#include #include #include "libcamera/internal/camera.h" @@ -41,6 +40,7 @@ #include "libcamera/internal/global_configuration.h" #include "libcamera/internal/media_device.h" #include "libcamera/internal/pipeline_handler.h" +#include "libcamera/internal/request.h" #include "libcamera/internal/software_isp/software_isp.h" #include "libcamera/internal/v4l2_subdevice.h" #include "libcamera/internal/v4l2_videodevice.h" @@ -926,8 +926,8 @@ void SimpleCameraData::imageBufferReady(FrameBuffer *buffer) } if (request) - request->metadata().set(controls::SensorTimestamp, - buffer->metadata().timestamp); + request->_d()->metadata().set(controls::SensorTimestamp, + buffer->metadata().timestamp); /* * Queue the captured and the request buffer to the converter or Software @@ -1014,7 +1014,7 @@ void SimpleCameraData::metadataReady(uint32_t frame, const ControlList &metadata if (!info) return; - info->request->metadata().merge(metadata); + info->request->_d()->metadata().merge(metadata); info->metadataProcessed = true; tryCompleteRequest(info->request); } diff --git a/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp b/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp index 48d6b39d532009bc32a2148f1d35b3a3be0bc0d4..21f2d28399bdfe97e0d97bf2e025721de36607c8 100644 --- a/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp +++ b/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp @@ -24,13 +24,13 @@ #include #include #include -#include #include #include "libcamera/internal/camera.h" #include "libcamera/internal/device_enumerator.h" #include "libcamera/internal/media_device.h" #include "libcamera/internal/pipeline_handler.h" +#include "libcamera/internal/request.h" #include "libcamera/internal/sysfs.h" #include "libcamera/internal/v4l2_videodevice.h" @@ -895,8 +895,8 @@ void UVCCameraData::imageBufferReady(FrameBuffer *buffer) Request *request = buffer->request(); /* \todo Use the UVC metadata to calculate a more precise timestamp */ - request->metadata().set(controls::SensorTimestamp, - buffer->metadata().timestamp); + request->_d()->metadata().set(controls::SensorTimestamp, + buffer->metadata().timestamp); pipe()->completeBuffer(request, buffer); pipe()->completeRequest(request); diff --git a/src/libcamera/pipeline/vimc/vimc.cpp b/src/libcamera/pipeline/vimc/vimc.cpp index 5022101505a15af1ebf6a8be4a52bcefb1fccfa0..9b1cfb090a7a274894b90969c4dad361c6748cea 100644 --- a/src/libcamera/pipeline/vimc/vimc.cpp +++ b/src/libcamera/pipeline/vimc/vimc.cpp @@ -24,7 +24,6 @@ #include #include #include -#include #include #include @@ -39,6 +38,7 @@ #include "libcamera/internal/ipa_manager.h" #include "libcamera/internal/media_device.h" #include "libcamera/internal/pipeline_handler.h" +#include "libcamera/internal/request.h" #include "libcamera/internal/v4l2_subdevice.h" #include "libcamera/internal/v4l2_videodevice.h" @@ -618,8 +618,8 @@ void VimcCameraData::imageBufferReady(FrameBuffer *buffer) } /* Record the sensor's timestamp in the request metadata. */ - request->metadata().set(controls::SensorTimestamp, - buffer->metadata().timestamp); + request->_d()->metadata().set(controls::SensorTimestamp, + buffer->metadata().timestamp); pipe->completeBuffer(request, buffer); pipe->completeRequest(request); diff --git a/src/libcamera/pipeline/virtual/virtual.cpp b/src/libcamera/pipeline/virtual/virtual.cpp index 7855e8b9db0c3bdfb3662a0b6e71332ed463a0ed..40c35264c5687c0f94458e19a272612cefb76285 100644 --- a/src/libcamera/pipeline/virtual/virtual.cpp +++ b/src/libcamera/pipeline/virtual/virtual.cpp @@ -29,13 +29,13 @@ #include #include #include -#include #include "libcamera/internal/camera.h" #include "libcamera/internal/dma_buf_allocator.h" #include "libcamera/internal/formats.h" #include "libcamera/internal/framebuffer.h" #include "libcamera/internal/pipeline_handler.h" +#include "libcamera/internal/request.h" #include "libcamera/internal/yaml_parser.h" #include "pipeline/virtual/config_parser.h" @@ -366,7 +366,7 @@ int PipelineHandlerVirtual::queueRequestDevice([[maybe_unused]] Camera *camera, VirtualCameraData *data = cameraData(camera); const auto timestamp = currentTimestamp(); - request->metadata().set(controls::SensorTimestamp, timestamp); + request->_d()->metadata().set(controls::SensorTimestamp, timestamp); data->invokeMethod(&VirtualCameraData::processRequest, ConnectionTypeQueued, request); diff --git a/src/libcamera/request.cpp b/src/libcamera/request.cpp index 2544a059f6984d930ec909c74e0b621c9fe82726..a661b2f5c8ae9ae2bcbab2dcdceeef7dcb8d0930 100644 --- a/src/libcamera/request.cpp +++ b/src/libcamera/request.cpp @@ -57,11 +57,16 @@ LOG_DEFINE_CATEGORY(Request) Request::Private::Private(Camera *camera) : camera_(camera), cancelled_(false) { + /** + * \todo Add a validator for metadata controls. + */ + metadata_ = new ControlList(controls::controls); } Request::Private::~Private() { doCancelRequest(); + delete metadata_; } /** @@ -82,6 +87,12 @@ bool Request::Private::hasPendingBuffers() const return !pending_.empty(); } +/** + * \fn Request::Private::metadata() + * \brief Retrieve the request's metadata + * \return The metadata associated with the request + */ + /** * \brief Complete a buffer for the request * \param[in] buffer The buffer that has completed @@ -359,11 +370,6 @@ Request::Request(Camera *camera, uint64_t cookie) controls_ = new ControlList(camera->controls(), camera->_d()->validator()); - /** - * \todo Add a validator for metadata controls. - */ - metadata_ = new ControlList(controls::controls); - LIBCAMERA_TRACEPOINT(request_construct, this); LOG(Request, Debug) << "Created request - cookie: " << cookie_; @@ -372,8 +378,6 @@ Request::Request(Camera *camera, uint64_t cookie) Request::~Request() { LIBCAMERA_TRACEPOINT(request_destroy, this); - - delete metadata_; delete controls_; } @@ -406,7 +410,7 @@ void Request::reuse(ReuseFlag flags) status_ = RequestPending; controls_->clear(); - metadata_->clear(); + _d()->metadata_->clear(); } /** @@ -425,6 +429,15 @@ void Request::reuse(ReuseFlag flags) * \return A reference to the ControlList in this request */ +/** + * \brief Retrieve the request's metadata + * \return The a const reference to the metadata associated with the request + */ +const ControlList &Request::metadata() const +{ + return *_d()->metadata_; +} + /** * \fn Request::buffers() * \brief Retrieve the request's streams to buffers map @@ -525,14 +538,6 @@ FrameBuffer *Request::findBuffer(const Stream *stream) const return it->second; } -/** - * \fn Request::metadata() - * \brief Retrieve the request's metadata - * \todo Offer a read-only API towards applications while keeping a read/write - * API internally. - * \return The metadata associated with the request - */ - /** * \brief Retrieve the sequence number for the request * From patchwork Wed Nov 26 09:38:53 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jacopo Mondi X-Patchwork-Id: 25212 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 AAA71C32DE for ; Wed, 26 Nov 2025 09:39:15 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id B6D1B60AA9; Wed, 26 Nov 2025 10:39:13 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="CrWyUcCY"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 3A8F0609E0 for ; Wed, 26 Nov 2025 10:39:07 +0100 (CET) Received: from [192.168.0.172] (mob-5-90-63-219.net.vodafone.it [5.90.63.219]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id AA36211EB; Wed, 26 Nov 2025 10:36:57 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1764149817; bh=JquQKFGDNT2oXXwkYN2POmo8yNxXO2Mp7Nl8lJNbuO8=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=CrWyUcCYD1o10pAT5n91x7wP7hbHGbycDYUQBhhIFN25Dg6V7hxC7c0ZUoJ6YmJd9 EUgXpJHDshytuTu4BPrbH0khAO2jJF1Cj0z/yigSWs7yGTPMsO6OJd9AFn7Evvzh3i 84mFewTGEO9TvR6MZK7eLBfViutxzpqduI3JuZkY= From: Jacopo Mondi Date: Wed, 26 Nov 2025 10:38:53 +0100 Subject: [PATCH v2 3/3] libcamera: request: Remove write-access to controls_ MIME-Version: 1.0 Message-Id: <20251126-cam-control-override-v2-3-305024a1f190@ideasonboard.com> References: <20251126-cam-control-override-v2-0-305024a1f190@ideasonboard.com> In-Reply-To: <20251126-cam-control-override-v2-0-305024a1f190@ideasonboard.com> To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=openpgp-sha256; l=10228; i=jacopo.mondi@ideasonboard.com; h=from:subject:message-id; bh=JquQKFGDNT2oXXwkYN2POmo8yNxXO2Mp7Nl8lJNbuO8=; b=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBpJsq502gPHQyhi6sGADZGvy4gej0Emux0PfRar nWqRcuDxh+JAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCaSbKuQAKCRByNAaPFqFW PIWYD/9YDKjKlyfVTQIn36tXBKy4ygN1J3OyJm6+Jbf70CKcVDsR/160Z3m6oULkZfZ2EwaYxLQ Xsfb8snIA+uuSQMRMVJPG218VPakDO6jBN2cniFfJWjy5kYEE6m66PSVbY2qWdJnngw6AeFSR+X mBEaFCGBbPVqMD7DyCzfu6ISWdA1/vnBTlTU0xzmitJeCbofN3c6x+VEMTv0zbF5Sq6jRYf3qPK NWu3G6YgeOayc/ZNyN6vPrUzT7z/LKavqJg0DFhJaMZdnWU/92GH00vu3/WLQkgBE88CFDH62Z5 dYb948nIZBgcW9v1P9P3LLlOycdSY58Vav0n+KlbpqKX0uuP2PyerwwIPqdqDgzho8koS/bz80I /gEubfDM9DVHXHcfrgBjLksyOd59rtU7uonSZb2ofQftdOAumfm91BBEUKY6ugCAvotfLXawOIT JfgcfMF4j7uGf+hosZP/yLBbSC7ukaQJeOEtWQpvkIo49XGr7CxxTiH2gCBx2BP5T5aijGY0RB0 0FHvPbzjdYbXK5Hv4ry6el0/EesH4kvxSWwNv+dKJ71fIA0hVi6kQekljpOxNbvJrzRZG/WJ3bD bTmzRB2hYl105Yi/rkhlDmFic8m0ClxaLFnY8EzuimG06JMtr7T1SlLfynTvyW9vL3guYUEaZkt F/DPvN2C3GUOD4w== X-Developer-Key: i=jacopo.mondi@ideasonboard.com; a=openpgp; fpr=72392EDC88144A65C701EA9BA5826A2587AD026B 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" The ControlList inside a Request is initialized with the Camera ControlInfoMap, which lists the limits of the controls supported by the Camera. The Request class currently exposes the control list it contains through the Request::controls() function as a raw pointer, potentially allowing applications to replace the Request's control list with a different one. The following pattern: request->controls() = {}; is then currently valid but opens the door to potential issues, as the new list might be initialized with a different info map or without any info map at all. To avoid applications to override the control list contained in a Request, make the Request::controls() function return a const reference and move the non-const version to the Request::Private class for internal usage. Add to the Request class 4 overloads of the setControl() function that allows applications to set controls on a Request control list without accessing it. Closes: https://gitlab.freedesktop.org/camera/libcamera/-/issues/295 Signed-off-by: Jacopo Mondi --- include/libcamera/internal/request.h | 1 + include/libcamera/request.h | 27 +++++++++++++++++- src/android/camera_device.cpp | 8 +++--- src/apps/cam/camera_session.cpp | 2 +- src/gstreamer/gstlibcamera-controls.cpp.in | 2 +- src/libcamera/camera.cpp | 2 +- src/libcamera/request.cpp | 46 +++++++++++++++++++++--------- src/py/libcamera/py_main.cpp | 2 +- src/v4l2/v4l2_camera.cpp | 2 +- 9 files changed, 69 insertions(+), 23 deletions(-) diff --git a/include/libcamera/internal/request.h b/include/libcamera/internal/request.h index 643f67cabf5778f7515c0117d06d4e0a3fdec4f8..4807036b7ae22c0e90886889ff5741fd9c5c0e9f 100644 --- a/include/libcamera/internal/request.h +++ b/include/libcamera/internal/request.h @@ -36,6 +36,7 @@ public: Camera *camera() const { return camera_; } bool hasPendingBuffers() const; + ControlList &controls(); ControlList &metadata() { return *metadata_; } bool completeBuffer(FrameBuffer *buffer); diff --git a/include/libcamera/request.h b/include/libcamera/request.h index c9aeddb62680923dc3490a23dc6c3f70f70cc075..31860e1e1e7680ded00148b7d96ca03f820d1ebc 100644 --- a/include/libcamera/request.h +++ b/include/libcamera/request.h @@ -49,7 +49,7 @@ public: void reuse(ReuseFlag flags = Default); - ControlList &controls() { return *controls_; } + const ControlList &controls() const { return *controls_; } const ControlList &metadata() const; const BufferMap &buffers() const { return bufferMap_; } int addBuffer(const Stream *stream, FrameBuffer *buffer, @@ -64,6 +64,31 @@ public: std::string toString() const; + template + void setControl(const Control &ctrl, const V &value) + { + controls_->set(ctrl, value); + } + +#ifndef __DOXYGEN__ + template + void setControl(const Control> &ctrl, + const Span &values) + { + controls_->set(ctrl, values); + } +#endif + + void setControls(const ControlList &other) + { + controls_->merge(other); + } + + void setControl(unsigned int id, const ControlValue &value) + { + controls_->set(id, value); + } + private: LIBCAMERA_DISABLE_COPY(Request) diff --git a/src/android/camera_device.cpp b/src/android/camera_device.cpp index 80ff248c2aa316d2f7ccc59d4621a7c86770bf5f..80145f6d103546b4214316f58ecf283accd108ff 100644 --- a/src/android/camera_device.cpp +++ b/src/android/camera_device.cpp @@ -804,19 +804,19 @@ int CameraDevice::processControls(Camera3RequestDescriptor *descriptor) return 0; /* Translate the Android request settings to libcamera controls. */ - ControlList &controls = descriptor->request_->controls(); + Request *req = descriptor->request_.get(); camera_metadata_ro_entry_t entry; if (settings.getEntry(ANDROID_SCALER_CROP_REGION, &entry)) { const int32_t *data = entry.data.i32; Rectangle cropRegion{ data[0], data[1], static_cast(data[2]), static_cast(data[3]) }; - controls.set(controls::ScalerCrop, cropRegion); + req->setControl(controls::ScalerCrop, cropRegion); } if (settings.getEntry(ANDROID_STATISTICS_FACE_DETECT_MODE, &entry)) { const uint8_t *data = entry.data.u8; - controls.set(controls::draft::FaceDetectMode, data[0]); + req->setControl(controls::draft::FaceDetectMode, data[0]); } if (settings.getEntry(ANDROID_SENSOR_TEST_PATTERN_MODE, &entry)) { @@ -854,7 +854,7 @@ int CameraDevice::processControls(Camera3RequestDescriptor *descriptor) return -EINVAL; } - controls.set(controls::draft::TestPatternMode, testPatternMode); + req->setControl(controls::draft::TestPatternMode, testPatternMode); } return 0; diff --git a/src/apps/cam/camera_session.cpp b/src/apps/cam/camera_session.cpp index 1596a25a3abed9c2d93e6657b92e35fdfd3d1a26..246806a0502cec465ac85989f7c93d5885427767 100644 --- a/src/apps/cam/camera_session.cpp +++ b/src/apps/cam/camera_session.cpp @@ -447,7 +447,7 @@ int CameraSession::queueRequest(Request *request) return 0; if (script_) - request->controls() = script_->frameControls(queueCount_); + request->setControls(script_->frameControls(queueCount_)); queueCount_++; diff --git a/src/gstreamer/gstlibcamera-controls.cpp.in b/src/gstreamer/gstlibcamera-controls.cpp.in index 6faf3ee7a6c3401bb42c3720505ea9d5d6f4186a..40a543e755e29f29b797417a58537fc2d05fa9ea 100644 --- a/src/gstreamer/gstlibcamera-controls.cpp.in +++ b/src/gstreamer/gstlibcamera-controls.cpp.in @@ -276,7 +276,7 @@ void GstCameraControls::setCamera(const std::shared_ptr &cam) void GstCameraControls::applyControls(std::unique_ptr &request) { - request->controls().merge(controls_); + request->setControls(controls_); controls_.clear(); } diff --git a/src/libcamera/camera.cpp b/src/libcamera/camera.cpp index 2e1e146a25b13b94f3a0df5935c0861f78c949ed..1918c6f4340d790e93ac23baa906f038cd30f0bc 100644 --- a/src/libcamera/camera.cpp +++ b/src/libcamera/camera.cpp @@ -1366,7 +1366,7 @@ int Camera::queueRequest(Request *request) } /* Pre-process AeEnable. */ - patchControlList(request->controls()); + patchControlList(request->_d()->controls()); d->pipe_->invokeMethod(&PipelineHandler::queueRequest, ConnectionTypeQueued, request); diff --git a/src/libcamera/request.cpp b/src/libcamera/request.cpp index a661b2f5c8ae9ae2bcbab2dcdceeef7dcb8d0930..c8c47f4c75cad116efcdd95032b5def19c900d95 100644 --- a/src/libcamera/request.cpp +++ b/src/libcamera/request.cpp @@ -87,6 +87,16 @@ bool Request::Private::hasPendingBuffers() const return !pending_.empty(); } +/** + * \fn Request::Private::controls() + * \brief Retrieve the request's ControlList + * \return A reference to the ControlList in this request + */ +ControlList &Request::Private::controls() +{ + return *_o()->controls_; +} + /** * \fn Request::Private::metadata() * \brief Retrieve the request's metadata @@ -414,19 +424,9 @@ void Request::reuse(ReuseFlag flags) } /** - * \fn Request::controls() - * \brief Retrieve the request's ControlList - * - * Requests store a list of controls to be applied to all frames captured for - * the request. They are created with an empty list of controls that can be - * accessed through this function. Control values can be retrieved using - * ControlList::get() and updated using ControlList::set(). - * - * Only controls supported by the camera to which this request will be - * submitted shall be included in the controls list. Attempting to add an - * unsupported control causes undefined behaviour. - * - * \return A reference to the ControlList in this request + * \fn Request::controls() const + * \brief Retrieve a const reference to the request's ControlList + * \return A const reference to the ControlList in this request */ /** @@ -623,4 +623,24 @@ std::ostream &operator<<(std::ostream &out, const Request &r) return out; } +/** + * \fn Request::setControl(const Control &ctrl, const V &value) + * \brief Set control \a ctrl in the Request + * \param[in] ctrl The control + * \param[in] value The control value + */ + +/** + * \fn Request::setControls(const ControlList &other) + * \brief Merge the control list \a other in the Request + * \param[in] other The control list to add to the Request + */ + +/** + * \fn Request::setControl(unsigned int id, const ControlValue &value) + * \brief Set control \a id in the Request to \a value + * \param[in] id The control numerical id + * \param[in] value The control value + */ + } /* namespace libcamera */ diff --git a/src/py/libcamera/py_main.cpp b/src/py/libcamera/py_main.cpp index a983ea75c35669cc5d1ff433eddfdd99e9388e7c..97cc6cfbe7fbc4ece5c7a779a9ae6c8c7d23cf7b 100644 --- a/src/py/libcamera/py_main.cpp +++ b/src/py/libcamera/py_main.cpp @@ -463,7 +463,7 @@ PYBIND11_MODULE(_libcamera, m) .def_property_readonly("sequence", &Request::sequence) .def_property_readonly("has_pending_buffers", &Request::hasPendingBuffers) .def("set_control", [](Request &self, const ControlId &id, py::object value) { - self.controls().set(id.id(), pyToControlValue(value, id.type())); + self.setControl(id.id(), pyToControlValue(value, id.type())); }) .def_property_readonly("metadata", [](Request &self) { /* Convert ControlList to std container */ diff --git a/src/v4l2/v4l2_camera.cpp b/src/v4l2/v4l2_camera.cpp index 94d138cd5710b9bd5c83cacd3cbd963f71aeb3c7..7e918cc3e803cdad67043f1dbdc212d5b57e37f3 100644 --- a/src/v4l2/v4l2_camera.cpp +++ b/src/v4l2/v4l2_camera.cpp @@ -269,7 +269,7 @@ int V4L2Camera::qbuf(unsigned int index) return 0; } - request->controls().merge(std::move(controls_)); + request->setControls(controls_); ret = camera_->queueRequest(request); if (ret < 0) {