From patchwork Fri Jul 3 15:38:03 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 27180 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 B42D9C3261 for ; Fri, 3 Jul 2026 15:38:30 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 50F3765FEF; Fri, 3 Jul 2026 17:38:27 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="fTV0hEWg"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 567FC65FC1 for ; Fri, 3 Jul 2026 17:38:23 +0200 (CEST) Received: from pb-laptop.local (185.221.140.128.nat.pool.zt.hu [185.221.140.128]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id E77D81121 for ; Fri, 3 Jul 2026 17:37:36 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783093057; bh=2uoOx1wfKuNJtedF4ojlGyj05mcIY26CqhY3YV3/jTc=; h=From:To:Subject:Date:In-Reply-To:References:From; b=fTV0hEWgPEytBEBYNIfdeNdVlsPjydx39M6OZwlyVjZVWxsbZCO9/B3dy9tIjI/YS FRm0x9oXq8xKjLtbgjSL1VZ0GbC2/pbCoujGjPW9KOaD8XsSU4DY9360vfMfJN8MMQ rZlovqyMWeRd1LXL8F/wGlBJurxm4wqTYAGDZLJ0= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v1 01/17] ipa: simple: Fix control presence sanity check Date: Fri, 3 Jul 2026 17:38:03 +0200 Message-ID: <20260703153819.1088752-2-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260703153819.1088752-1-barnabas.pocze@ideasonboard.com> References: <20260703153819.1088752-1-barnabas.pocze@ideasonboard.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" Check existence before the values are retrieved. Link: https://gitlab.freedesktop.org/camera/libcamera/-/work_items/241 Fixes: fb8ad13dc3e3 ("libcamera: software_isp: Move exposure+gain to an algorithm module") Signed-off-by: Barnabás Pőcze --- src/ipa/simple/soft_simple.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/ipa/simple/soft_simple.cpp b/src/ipa/simple/soft_simple.cpp index 629e1a32de..2a41b5bac8 100644 --- a/src/ipa/simple/soft_simple.cpp +++ b/src/ipa/simple/soft_simple.cpp @@ -299,6 +299,13 @@ void IPASoftSimple::processStats(const uint32_t frame, [[maybe_unused]] const uint32_t bufferId, const ControlList &sensorControls) { + /* Sanity check */ + if (!sensorControls.contains(V4L2_CID_EXPOSURE) || + !sensorControls.contains(V4L2_CID_ANALOGUE_GAIN)) { + LOG(IPASoft, Error) << "Control(s) missing"; + return; + } + IPAFrameContext &frameContext = context_.frameContexts.get(frame); frameContext.sensor.exposure = @@ -311,13 +318,6 @@ void IPASoftSimple::processStats(const uint32_t frame, algo->process(context_, frame, frameContext, stats_, metadata); metadataReady.emit(frame, metadata); - /* Sanity check */ - if (!sensorControls.contains(V4L2_CID_EXPOSURE) || - !sensorControls.contains(V4L2_CID_ANALOGUE_GAIN)) { - LOG(IPASoft, Error) << "Control(s) missing"; - return; - } - ControlList ctrls(sensorInfoMap_); auto &againNew = frameContext.sensor.gain; From patchwork Fri Jul 3 15:38:04 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 27181 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 9364FC3261 for ; Fri, 3 Jul 2026 15:38:33 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 0DBFF65FF7; Fri, 3 Jul 2026 17:38:28 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="WQkzke50"; 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 7486B65FC3 for ; Fri, 3 Jul 2026 17:38:23 +0200 (CEST) Received: from pb-laptop.local (185.221.140.128.nat.pool.zt.hu [185.221.140.128]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 20E4D11DD for ; Fri, 3 Jul 2026 17:37:37 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783093057; bh=VeZFnauTbXqiYNo8BUlT1SVFGB1R8WuIXeqBiBTPIuk=; h=From:To:Subject:Date:In-Reply-To:References:From; b=WQkzke50kVd/pAVUxGsVD5xA6m0lomKgLMpNI401RM5JD7z8WzoBjQRlttYKELvkU 2mhsQRQXcnNmhehqWpHLK7gx2cYeQT+rlA4M6kVOGc93hngv0Z797YgB+LAGuFwtpT 9e+dzDkkb7KxCb4ERX61L4xbiBInDl1Nnd6eI6uQ= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v1 02/17] ipa: rkisp1: Remove `IPASessionConfiguration::sensor.defVBlank` Date: Fri, 3 Jul 2026 17:38:04 +0200 Message-ID: <20260703153819.1088752-3-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260703153819.1088752-1-barnabas.pocze@ideasonboard.com> References: <20260703153819.1088752-1-barnabas.pocze@ideasonboard.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" It is not used, so remove it. Signed-off-by: Barnabás Pőcze --- src/ipa/rkisp1/ipa_context.cpp | 3 --- src/ipa/rkisp1/ipa_context.h | 1 - src/ipa/rkisp1/rkisp1.cpp | 2 -- 3 files changed, 6 deletions(-) diff --git a/src/ipa/rkisp1/ipa_context.cpp b/src/ipa/rkisp1/ipa_context.cpp index f9d0aff058..302b4cfd70 100644 --- a/src/ipa/rkisp1/ipa_context.cpp +++ b/src/ipa/rkisp1/ipa_context.cpp @@ -98,9 +98,6 @@ namespace libcamera::ipa::rkisp1 { * \var IPASessionConfiguration::sensor.maxAnalogueGain * \brief Maximum analogue gain supported with the sensor * - * \var IPASessionConfiguration::sensor.defVBlank - * \brief The default vblank value of the sensor - * * \var IPASessionConfiguration::sensor.lineDuration * \brief Line duration in microseconds * diff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h index e5f70d934d..9a4e0f12c6 100644 --- a/src/ipa/rkisp1/ipa_context.h +++ b/src/ipa/rkisp1/ipa_context.h @@ -68,7 +68,6 @@ struct IPASessionConfiguration { double minAnalogueGain; double maxAnalogueGain; - int32_t defVBlank; utils::Duration lineDuration; Size size; } sensor; diff --git a/src/ipa/rkisp1/rkisp1.cpp b/src/ipa/rkisp1/rkisp1.cpp index 58ef163d85..273540a330 100644 --- a/src/ipa/rkisp1/rkisp1.cpp +++ b/src/ipa/rkisp1/rkisp1.cpp @@ -249,8 +249,6 @@ int IPARkISP1::configure(const IPAConfigInfo &ipaConfig, context_.configuration.paramFormat = ipaConfig.paramFormat; const IPACameraSensorInfo &info = ipaConfig.sensorInfo; - const ControlInfo vBlank = sensorControls_.find(V4L2_CID_VBLANK)->second; - context_.configuration.sensor.defVBlank = vBlank.def().get(); context_.configuration.sensor.size = info.outputSize; context_.configuration.sensor.lineDuration = info.minLineLength * 1.0s / info.pixelRate; From patchwork Fri Jul 3 15:38:05 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 27182 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 80342C3261 for ; Fri, 3 Jul 2026 15:38:35 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 12F4965FD9; Fri, 3 Jul 2026 17:38:29 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Th1xeRBn"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 8707C65FC5 for ; Fri, 3 Jul 2026 17:38:23 +0200 (CEST) Received: from pb-laptop.local (185.221.140.128.nat.pool.zt.hu [185.221.140.128]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 4DEBDDF3 for ; Fri, 3 Jul 2026 17:37:37 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783093057; bh=hlJaDkt7R0c5ZFDJHx4xibLsuI0JVwjjxUfp3cDs3y0=; h=From:To:Subject:Date:In-Reply-To:References:From; b=Th1xeRBnuq1sYDMfnjxppYzxtXgbW8Eh38l3MD7mfSqqe8gUc9EMugkhvRZQ8VLG8 NwvVbKZBgfhjvni1Z8wTORXYFfRWixLjx6sTO+4SKT7lcQYskqCxegViT0qx2m8xFl RlzcNefqONT6SeF4FF0Gf7zig0YL1aMqhWcn/Ci4= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v1 03/17] ipa: simple: agc: Do not overwrite sensor exposure/gain Date: Fri, 3 Jul 2026 17:38:05 +0200 Message-ID: <20260703153819.1088752-4-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260703153819.1088752-1-barnabas.pocze@ideasonboard.com> References: <20260703153819.1088752-1-barnabas.pocze@ideasonboard.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" In the agc algorithm do not overwrite the sensor exposure/gain values, instead add a new "agc" member to the frame context and update that. No functional changes are intended as the agc algorithm runs last. Signed-off-by: Barnabás Pőcze --- src/ipa/simple/algorithms/agc.cpp | 11 +++++++---- src/ipa/simple/ipa_context.h | 5 +++++ src/ipa/simple/soft_simple.cpp | 9 +++++---- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/ipa/simple/algorithms/agc.cpp b/src/ipa/simple/algorithms/agc.cpp index a13a755280..e9bcb2c032 100644 --- a/src/ipa/simple/algorithms/agc.cpp +++ b/src/ipa/simple/algorithms/agc.cpp @@ -67,8 +67,8 @@ Agc::Agc() void Agc::updateExposure(IPAContext &context, IPAFrameContext &frameContext, double exposureMSV) { - int32_t &exposure = frameContext.sensor.exposure; - double &again = frameContext.sensor.gain; + int32_t exposure = frameContext.sensor.exposure; + double again = frameContext.sensor.gain; double error = kExposureOptimal - exposureMSV; @@ -115,6 +115,9 @@ void Agc::updateExposure(IPAContext &context, IPAFrameContext &frameContext, dou again = std::clamp(again, context.configuration.agc.againMin, context.configuration.agc.againMax); + frameContext.agc.exposure = exposure; + frameContext.agc.gain = again; + context.activeState.agc.exposure = exposure; context.activeState.agc.again = again; @@ -150,8 +153,8 @@ void Agc::process(IPAContext &context, * Use the new exposure and gain values calculated the last time * there were valid stats. */ - frameContext.sensor.exposure = context.activeState.agc.exposure; - frameContext.sensor.gain = context.activeState.agc.again; + frameContext.agc.exposure = context.activeState.agc.exposure; + frameContext.agc.gain = context.activeState.agc.again; return; } diff --git a/src/ipa/simple/ipa_context.h b/src/ipa/simple/ipa_context.h index 8ccfacb46a..d35fb1e91d 100644 --- a/src/ipa/simple/ipa_context.h +++ b/src/ipa/simple/ipa_context.h @@ -66,6 +66,11 @@ struct IPAActiveState { struct IPAFrameContext : public FrameContext { Matrix ccm; + struct { + int32_t exposure; + double gain; + } agc; + struct { int32_t exposure; double gain; diff --git a/src/ipa/simple/soft_simple.cpp b/src/ipa/simple/soft_simple.cpp index 2a41b5bac8..22702638bb 100644 --- a/src/ipa/simple/soft_simple.cpp +++ b/src/ipa/simple/soft_simple.cpp @@ -320,10 +320,11 @@ void IPASoftSimple::processStats(const uint32_t frame, ControlList ctrls(sensorInfoMap_); - auto &againNew = frameContext.sensor.gain; - ctrls.set(V4L2_CID_EXPOSURE, frameContext.sensor.exposure); - ctrls.set(V4L2_CID_ANALOGUE_GAIN, - static_cast(camHelper_ ? camHelper_->gainCode(againNew) : againNew)); + int32_t againNew = camHelper_ + ? camHelper_->gainCode(frameContext.agc.gain) + : static_cast(frameContext.agc.gain); + ctrls.set(V4L2_CID_EXPOSURE, frameContext.agc.exposure); + ctrls.set(V4L2_CID_ANALOGUE_GAIN, againNew); setSensorControls.emit(ctrls); } From patchwork Fri Jul 3 15:38:06 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 27183 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 0B240C3302 for ; Fri, 3 Jul 2026 15:38:37 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 43A2665FCE; Fri, 3 Jul 2026 17:38:30 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="chkZsJY3"; 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 AF29665FC9 for ; Fri, 3 Jul 2026 17:38:23 +0200 (CEST) Received: from pb-laptop.local (185.221.140.128.nat.pool.zt.hu [185.221.140.128]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 81BA31494 for ; Fri, 3 Jul 2026 17:37:37 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783093057; bh=9quqwvpZ6B8TLCeNodTbKpKCGXDei4Hb7D+OLbi0SGA=; h=From:To:Subject:Date:In-Reply-To:References:From; b=chkZsJY3FCiVLx0UMOxFMDuBu7QA0e9r41mgQijXpZRTgLd8E22VcDv9fUvjA523E vV+1QVcfrjQFtPleKfYcjHt66tNN+DmeACMAEUJ7MnJLck3Ev7ozsbMsy4HOaOD/y8 NoHsXWuUr748d2ReScH0+Cq9IUiYDaFsgCr/efH4= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v1 04/17] ipa: libipa: agc_mean_luminance: Remove the need for inheritance Date: Fri, 3 Jul 2026 17:38:06 +0200 Message-ID: <20260703153819.1088752-5-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260703153819.1088752-1-barnabas.pocze@ideasonboard.com> References: <20260703153819.1088752-1-barnabas.pocze@ideasonboard.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" Create a separate type with the necessary callbacks instead of using inheritance. This removes the need for users to have to use member variables to store temporary data that is only needed for luminance estimation. Signed-off-by: Barnabás Pőcze --- src/ipa/ipu3/algorithms/agc.cpp | 111 ++++++++++++------- src/ipa/ipu3/algorithms/agc.h | 8 +- src/ipa/libipa/agc_mean_luminance.cpp | 52 +++++---- src/ipa/libipa/agc_mean_luminance.h | 14 ++- src/ipa/mali-c55/algorithms/agc.cpp | 57 ++++++---- src/ipa/mali-c55/algorithms/agc.h | 4 +- src/ipa/rkisp1/algorithms/agc.cpp | 154 ++++++++++++++------------ src/ipa/rkisp1/algorithms/agc.h | 7 +- 8 files changed, 238 insertions(+), 169 deletions(-) diff --git a/src/ipa/ipu3/algorithms/agc.cpp b/src/ipa/ipu3/algorithms/agc.cpp index d6a7036c65..5b152dcda2 100644 --- a/src/ipa/ipu3/algorithms/agc.cpp +++ b/src/ipa/ipu3/algorithms/agc.cpp @@ -76,11 +76,11 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData) { int ret; - ret = parseTuningData(tuningData); + ret = agc_.parseTuningData(tuningData); if (ret) return ret; - context.ctrlMap.merge(controls()); + context.ctrlMap.merge(agc_.controls()); return 0; } @@ -112,13 +112,13 @@ int Agc::configure(IPAContext &context, activeState.agc.gain = minAnalogueGain_; activeState.agc.exposure = 10ms / configuration.sensor.lineDuration; - context.activeState.agc.constraintMode = constraintModes().begin()->first; - context.activeState.agc.exposureMode = exposureModeHelpers().begin()->first; + context.activeState.agc.constraintMode = agc_.constraintModes().begin()->first; + context.activeState.agc.exposureMode = agc_.exposureModeHelpers().begin()->first; /* \todo Run this again when FrameDurationLimits is passed in */ - setLimits(minExposureTime_, maxExposureTime_, minAnalogueGain_, - maxAnalogueGain_, {}); - resetFrameCount(); + agc_.setLimits(minExposureTime_, maxExposureTime_, minAnalogueGain_, + maxAnalogueGain_, {}); + agc_.resetFrameCount(); return 0; } @@ -156,39 +156,58 @@ Histogram Agc::parseStatistics(const ipu3_uapi_stats_3a *stats, return Histogram(Span(hist)); } -/** - * \brief Estimate the relative luminance of the frame with a given gain - * \param[in] gain The gain to apply in estimating luminance - * - * The estimation is based on the AWB statistics for the current frame. Red, - * green and blue averages for all cells are first multiplied by the gain, and - * then saturated to approximate the sensor behaviour at high brightness - * values. The approximation is quite rough, as it doesn't take into account - * non-linearities when approaching saturation. - * - * The relative luminance (Y) is computed from the linear RGB components using - * the Rec. 601 formula. The values are normalized to the [0.0, 1.0] range, - * where 1.0 corresponds to a theoretical perfect reflector of 100% reference - * white. - * - * More detailed information can be found in: - * https://en.wikipedia.org/wiki/Relative_luminance - * - * \return The relative luminance of the frame - */ -double Agc::estimateLuminance(double gain) const +namespace { + +class AgcTraits final : public AgcMeanLuminance::Traits { - RGB sum{ 0.0 }; +public: + AgcTraits(Span> rgbTriples, + RGB gains, const ipu3_uapi_grid_config &bdsGrid) + : rgbTriples_(rgbTriples), gains_(gains), bdsGrid_(bdsGrid) + { + } - for (unsigned int i = 0; i < rgbTriples_.size(); i++) { - sum.r() += std::min(std::get<0>(rgbTriples_[i]) * gain, 255.0); - sum.g() += std::min(std::get<1>(rgbTriples_[i]) * gain, 255.0); - sum.b() += std::min(std::get<2>(rgbTriples_[i]) * gain, 255.0); + /** + * \brief Estimate the relative luminance of the frame with a given gain + * \param[in] gain The gain to apply in estimating luminance + * + * The estimation is based on the AWB statistics for the current frame. Red, + * green and blue averages for all cells are first multiplied by the gain, and + * then saturated to approximate the sensor behaviour at high brightness + * values. The approximation is quite rough, as it doesn't take into account + * non-linearities when approaching saturation. + * + * The relative luminance (Y) is computed from the linear RGB components using + * the Rec. 601 formula. The values are normalized to the [0.0, 1.0] range, + * where 1.0 corresponds to a theoretical perfect reflector of 100% reference + * white. + * + * More detailed information can be found in: + * https://en.wikipedia.org/wiki/Relative_luminance + * + * \return The relative luminance of the frame + */ + + double estimateLuminance(double gain) const override + { + RGB sum{ 0.0 }; + + for (unsigned int i = 0; i < rgbTriples_.size(); i++) { + sum.r() += std::min(std::get<0>(rgbTriples_[i]) * gain, 255.0); + sum.g() += std::min(std::get<1>(rgbTriples_[i]) * gain, 255.0); + sum.b() += std::min(std::get<2>(rgbTriples_[i]) * gain, 255.0); + } + + double ySum = rec601LuminanceFromRGB(sum * gains_); + return ySum / (bdsGrid_.height * bdsGrid_.width) / 255; } - RGB gains{{ rGain_, gGain_, bGain_ }}; - double ySum = rec601LuminanceFromRGB(sum * gains); - return ySum / (bdsGrid_.height * bdsGrid_.width) / 255; +private: + Span> rgbTriples_; + RGB gains_; + const ipu3_uapi_grid_config &bdsGrid_; +}; + } /** @@ -208,9 +227,7 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, ControlList &metadata) { Histogram hist = parseStatistics(stats, context.configuration.grid.bdsGrid); - rGain_ = context.activeState.awb.gains.red; - gGain_ = context.activeState.awb.gains.blue; - bGain_ = context.activeState.awb.gains.green; + /* * The Agc algorithm needs to know the effective exposure value that was @@ -221,12 +238,22 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, double analogueGain = frameContext.sensor.gain; utils::Duration effectiveExposureValue = exposureTime * analogueGain; + AgcTraits agcTraits{ + rgbTriples_, + {{ + context.activeState.awb.gains.red, + context.activeState.awb.gains.blue, + context.activeState.awb.gains.green, + }}, + bdsGrid_, + }; + utils::Duration newExposureTime; double aGain, qGain, dGain; std::tie(newExposureTime, aGain, qGain, dGain) = - calculateNewEv(context.activeState.agc.constraintMode, - context.activeState.agc.exposureMode, hist, - effectiveExposureValue); + agc_.calculateNewEv(context.activeState.agc.constraintMode, + context.activeState.agc.exposureMode, hist, + effectiveExposureValue, agcTraits); LOG(IPU3Agc, Debug) << "Divided up exposure time, analogue gain and digital gain are " diff --git a/src/ipa/ipu3/algorithms/agc.h b/src/ipa/ipu3/algorithms/agc.h index d274a23504..d08da7600e 100644 --- a/src/ipa/ipu3/algorithms/agc.h +++ b/src/ipa/ipu3/algorithms/agc.h @@ -24,7 +24,7 @@ struct IPACameraSensorInfo; namespace ipa::ipu3::algorithms { -class Agc : public Algorithm, public AgcMeanLuminance +class Agc : public Algorithm { public: Agc(); @@ -38,7 +38,6 @@ public: ControlList &metadata) override; private: - double estimateLuminance(double gain) const override; Histogram parseStatistics(const ipu3_uapi_stats_3a *stats, const ipu3_uapi_grid_config &grid); @@ -49,11 +48,10 @@ private: double maxAnalogueGain_; uint32_t stride_; - double rGain_; - double gGain_; - double bGain_; ipu3_uapi_grid_config bdsGrid_; std::vector> rgbTriples_; + + AgcMeanLuminance agc_; }; } /* namespace ipa::ipu3::algorithms */ diff --git a/src/ipa/libipa/agc_mean_luminance.cpp b/src/ipa/libipa/agc_mean_luminance.cpp index 2d3bc709f3..1529d55864 100644 --- a/src/ipa/libipa/agc_mean_luminance.cpp +++ b/src/ipa/libipa/agc_mean_luminance.cpp @@ -21,7 +21,7 @@ using namespace libcamera::controls; /** * \file agc_mean_luminance.h - * \brief Base class implementing mean luminance AEGC + * \brief Class implementing mean luminance AEGC */ namespace libcamera { @@ -105,6 +105,30 @@ static constexpr unsigned int kDefaultLuxLevel = 500; * \brief The luminance target for the constraint */ +/** + * \class AgcMeanLuminance::Traits + * \brief A collection of callbacks + * + * This type contains virtual methods that provide the necessary pieces of + * information for the algorithm, and are to be implemented by the user. + */ + + /** + * \fn AgcMeanLuminance::Traits::estimateLuminance(double gain) + * \brief Estimate the luminance of an image, adjusted by a given gain + * \param[in] gain The gain with which to adjust the luminance estimate + * + * This function estimates the average relative luminance of the frame that + * would be output by the sensor if an additional \a gain was applied. It is a + * pure virtual function because estimation of luminance is a hardware-specific + * operation, which depends wholly on the format of the stats that are delivered + * to libcamera from the ISP. Derived classes must override this function with + * one that calculates the normalised mean luminance value across the entire + * image. + * + * \return The normalised relative luminance of the image + */ + /** * \class AgcMeanLuminance * \brief A mean-based auto-exposure algorithm @@ -487,28 +511,12 @@ void AgcMeanLuminance::setLimits(utils::Duration minExposureTime, * \brief Get the controls that have been generated after parsing tuning data */ -/** - * \fn AgcMeanLuminance::estimateLuminance(const double gain) - * \brief Estimate the luminance of an image, adjusted by a given gain - * \param[in] gain The gain with which to adjust the luminance estimate - * - * This function estimates the average relative luminance of the frame that - * would be output by the sensor if an additional \a gain was applied. It is a - * pure virtual function because estimation of luminance is a hardware-specific - * operation, which depends wholly on the format of the stats that are delivered - * to libcamera from the ISP. Derived classes must override this function with - * one that calculates the normalised mean luminance value across the entire - * image. - * - * \return The normalised relative luminance of the image - */ - /** * \brief Estimate the initial gain needed to achieve a relative luminance * target * \return The calculated initial gain */ -double AgcMeanLuminance::estimateInitialGain() const +double AgcMeanLuminance::estimateInitialGain(const Traits &traits) const { double yTarget = effectiveYTarget(); double yGain = 1.0; @@ -520,7 +528,7 @@ double AgcMeanLuminance::estimateInitialGain() const * regions are saturated. */ for (unsigned int i = 0; i < 8; i++) { - double yValue = estimateLuminance(yGain); + double yValue = traits.estimateLuminance(yGain); double extra_gain = std::min(10.0, yTarget / (yValue + .001)); yGain *= extra_gain; @@ -663,6 +671,7 @@ utils::Duration AgcMeanLuminance::filterExposure(utils::Duration exposureValue) * the calculated gain * \param[in] effectiveExposureValue The EV applied to the frame from which the * statistics in use derive + * \param[in] traits The traits object implementing the necessary functions * * Calculate a new exposure value to try to obtain the target. The calculated * exposure value is filtered to prevent rapid changes from frame to frame, and @@ -675,7 +684,8 @@ std::tuple AgcMeanLuminance::calculateNewEv(uint32_t constraintModeIndex, uint32_t exposureModeIndex, const Histogram &yHist, - utils::Duration effectiveExposureValue) + utils::Duration effectiveExposureValue, + const Traits &traits) { /* * The pipeline handler should validate that we have received an allowed @@ -696,7 +706,7 @@ AgcMeanLuminance::calculateNewEv(uint32_t constraintModeIndex, return exposureModeHelper->splitExposure(10ms); } - double gain = estimateInitialGain(); + double gain = estimateInitialGain(traits); gain = constraintClampGain(constraintModeIndex, yHist, gain); /* diff --git a/src/ipa/libipa/agc_mean_luminance.h b/src/ipa/libipa/agc_mean_luminance.h index 27e92b6fce..f4e1680ab5 100644 --- a/src/ipa/libipa/agc_mean_luminance.h +++ b/src/ipa/libipa/agc_mean_luminance.h @@ -30,7 +30,7 @@ class AgcMeanLuminance { public: AgcMeanLuminance(); - virtual ~AgcMeanLuminance(); + ~AgcMeanLuminance(); struct AgcConstraint { enum class Bound { @@ -43,6 +43,11 @@ public: Pwl yTarget; }; + struct Traits { + virtual ~Traits() = default; + virtual double estimateLuminance(double gain) const = 0; + }; + void configure(utils::Duration lineDuration, const CameraSensorHelper *sensorHelper); int parseTuningData(const ValueNode &tuningData); @@ -76,7 +81,8 @@ public: std::tuple calculateNewEv(uint32_t constraintModeIndex, uint32_t exposureModeIndex, - const Histogram &yHist, utils::Duration effectiveExposureValue); + const Histogram &yHist, utils::Duration effectiveExposureValue, + const Traits &traits); double effectiveYTarget() const; @@ -86,13 +92,11 @@ public: } private: - virtual double estimateLuminance(const double gain) const = 0; - int parseRelativeLuminanceTarget(const ValueNode &tuningData); int parseConstraint(const ValueNode &modeDict, int32_t id); int parseConstraintModes(const ValueNode &tuningData); int parseExposureModes(const ValueNode &tuningData); - double estimateInitialGain() const; + double estimateInitialGain(const Traits &traits) const; double constraintClampGain(uint32_t constraintModeIndex, const Histogram &hist, double gain); diff --git a/src/ipa/mali-c55/algorithms/agc.cpp b/src/ipa/mali-c55/algorithms/agc.cpp index 83bbf69385..6e091603f9 100644 --- a/src/ipa/mali-c55/algorithms/agc.cpp +++ b/src/ipa/mali-c55/algorithms/agc.cpp @@ -127,20 +127,19 @@ void AgcStatistics::parseStatistics(const mali_c55_stats_buffer *stats) } Agc::Agc() - : AgcMeanLuminance() { } int Agc::init(IPAContext &context, const ValueNode &tuningData) { - int ret = parseTuningData(tuningData); + int ret = agc_.parseTuningData(tuningData); if (ret) return ret; context.ctrlMap[&controls::AeEnable] = ControlInfo(false, true); context.ctrlMap[&controls::DigitalGain] = ControlInfo( kMinDigitalGain, kMaxDigitalGain, kMinDigitalGain); - context.ctrlMap.merge(controls()); + context.ctrlMap.merge(agc_.controls()); return 0; } @@ -163,17 +162,17 @@ int Agc::configure(IPAContext &context, context.activeState.agc.manual.sensorGain = context.configuration.agc.minAnalogueGain; context.activeState.agc.manual.exposure = context.configuration.agc.defaultExposure; context.activeState.agc.manual.ispGain = kMinDigitalGain; - context.activeState.agc.constraintMode = constraintModes().begin()->first; - context.activeState.agc.exposureMode = exposureModeHelpers().begin()->first; + context.activeState.agc.constraintMode = agc_.constraintModes().begin()->first; + context.activeState.agc.exposureMode = agc_.exposureModeHelpers().begin()->first; /* \todo Run this again when FrameDurationLimits is passed in */ - setLimits(context.configuration.agc.minShutterSpeed, - context.configuration.agc.maxShutterSpeed, - context.configuration.agc.minAnalogueGain, - context.configuration.agc.maxAnalogueGain, - {}); + agc_.setLimits(context.configuration.agc.minShutterSpeed, + context.configuration.agc.maxShutterSpeed, + context.configuration.agc.minAnalogueGain, + context.configuration.agc.maxAnalogueGain, + {}); - resetFrameCount(); + agc_.resetFrameCount(); return 0; } @@ -320,14 +319,30 @@ void Agc::prepare(IPAContext &context, const uint32_t frame, fillWeightsArrayBuffer(params, MaliC55Blocks::AexpIhistWeights); } -double Agc::estimateLuminance(const double gain) const +namespace { + +class AgcTraits final : public AgcMeanLuminance::Traits { - double rAvg = statistics_.rHist.interQuantileMean(0, 1) * gain; - double gAvg = statistics_.gHist.interQuantileMean(0, 1) * gain; - double bAvg = statistics_.bHist.interQuantileMean(0, 1) * gain; - double yAvg = rec601LuminanceFromRGB({ { rAvg, gAvg, bAvg } }); +public: + AgcTraits(const AgcStatistics &statistics) + : statistics_(statistics) + { + } + + double estimateLuminance(double gain) const override + { + double rAvg = statistics_.rHist.interQuantileMean(0, 1) * gain; + double gAvg = statistics_.gHist.interQuantileMean(0, 1) * gain; + double bAvg = statistics_.bHist.interQuantileMean(0, 1) * gain; + double yAvg = rec601LuminanceFromRGB({ { rAvg, gAvg, bAvg } }); + + return yAvg / kNumHistogramBins; + } + +private: + const AgcStatistics &statistics_; +}; - return yAvg / kNumHistogramBins; } void Agc::process(IPAContext &context, @@ -359,13 +374,15 @@ void Agc::process(IPAContext &context, double totalGain = analogueGain * digitalGain; utils::Duration currentShutter = exposure * configuration.sensor.lineDuration; utils::Duration effectiveExposureValue = currentShutter * totalGain; + AgcTraits agcTraits(statistics_); + utils::Duration shutterTime; double aGain, qGain, dGain; std::tie(shutterTime, aGain, qGain, dGain) = - calculateNewEv(activeState.agc.constraintMode, - activeState.agc.exposureMode, statistics_.yHist, - effectiveExposureValue); + agc_.calculateNewEv(activeState.agc.constraintMode, + activeState.agc.exposureMode, statistics_.yHist, + effectiveExposureValue, agcTraits); UQ<5, 8> dGainQ = std::clamp(static_cast(dGain), kMinDigitalGain, diff --git a/src/ipa/mali-c55/algorithms/agc.h b/src/ipa/mali-c55/algorithms/agc.h index ee913de2b2..e36378a2ac 100644 --- a/src/ipa/mali-c55/algorithms/agc.h +++ b/src/ipa/mali-c55/algorithms/agc.h @@ -43,7 +43,7 @@ private: unsigned int bIndex_; }; -class Agc : public Algorithm, public AgcMeanLuminance +class Agc : public Algorithm { public: Agc(); @@ -64,7 +64,6 @@ public: ControlList &metadata) override; private: - double estimateLuminance(const double gain) const override; void fillGainParamBlock(IPAContext &context, IPAFrameContext &frameContext, MaliC55Params *params); @@ -72,6 +71,7 @@ private: void fillWeightsArrayBuffer(MaliC55Params *params, enum MaliC55Blocks type); AgcStatistics statistics_; + AgcMeanLuminance agc_; }; } /* namespace ipa::mali_c55::algorithms */ diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp index aace46a8d0..540b64ff99 100644 --- a/src/ipa/rkisp1/algorithms/agc.cpp +++ b/src/ipa/rkisp1/algorithms/agc.cpp @@ -138,7 +138,7 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData) { int ret; - ret = parseTuningData(tuningData); + ret = agc_.parseTuningData(tuningData); if (ret) return ret; @@ -158,7 +158,7 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData) /* \todo Move this to the Camera class */ context.ctrlMap[&controls::AeEnable] = ControlInfo(false, true, true); context.ctrlMap[&controls::ExposureValue] = ControlInfo(-8.0f, 8.0f, 0.0f); - context.ctrlMap.merge(controls()); + context.ctrlMap.merge(agc_.controls()); return 0; } @@ -184,9 +184,9 @@ int Agc::configure(IPAContext &context, const IPACameraSensorInfo &configInfo) context.activeState.agc.exposureValue = 0.0; context.activeState.agc.constraintMode = - static_cast(constraintModes().begin()->first); + static_cast(agc_.constraintModes().begin()->first); context.activeState.agc.exposureMode = - static_cast(exposureModeHelpers().begin()->first); + static_cast(agc_.exposureModeHelpers().begin()->first); context.activeState.agc.meteringMode = static_cast(meteringModes_.begin()->first); @@ -200,17 +200,16 @@ int Agc::configure(IPAContext &context, const IPACameraSensorInfo &configInfo) context.configuration.agc.measureWindow.h_size = configInfo.outputSize.width; context.configuration.agc.measureWindow.v_size = configInfo.outputSize.height; - AgcMeanLuminance::configure(context.configuration.sensor.lineDuration, - context.camHelper.get()); + agc_.configure(context.configuration.sensor.lineDuration, context.camHelper.get()); - setLimits(context.configuration.sensor.minExposureTime, - context.configuration.sensor.maxExposureTime, - context.configuration.sensor.minAnalogueGain, - context.configuration.sensor.maxAnalogueGain, {}); + agc_.setLimits(context.configuration.sensor.minExposureTime, + context.configuration.sensor.maxExposureTime, + context.configuration.sensor.minAnalogueGain, + context.configuration.sensor.maxAnalogueGain, {}); - context.activeState.agc.automatic.yTarget = effectiveYTarget(); + context.activeState.agc.automatic.yTarget = agc_.effectiveYTarget(); - resetFrameCount(); + agc_.resetFrameCount(); return 0; } @@ -457,47 +456,6 @@ void Agc::fillMetadata(IPAContext &context, IPAFrameContext &frameContext, metadata.set(controls::ExposureValue, frameContext.agc.exposureValue); } -/** - * \brief Estimate the relative luminance of the frame with a given gain - * \param[in] gain The gain to apply to the frame - * - * This function estimates the average relative luminance of the frame that - * would be output by the sensor if an additional \a gain was applied. - * - * The estimation is based on the AE statistics for the current frame. Y - * averages for all cells are first multiplied by the gain, and then saturated - * to approximate the sensor behaviour at high brightness values. The - * approximation is quite rough, as it doesn't take into account non-linearities - * when approaching saturation. In this case, saturating after the conversion to - * YUV doesn't take into account the fact that the R, G and B components - * contribute differently to the relative luminance. - * - * The values are normalized to the [0.0, 1.0] range, where 1.0 corresponds to a - * theoretical perfect reflector of 100% reference white. - * - * More detailed information can be found in: - * https://en.wikipedia.org/wiki/Relative_luminance - * - * \return The relative luminance - */ -double Agc::estimateLuminance(double gain) const -{ - ASSERT(expMeans_.size() == weights_.size()); - double ySum = 0.0; - double wSum = 0.0; - - /* Sum the averages, saturated to 255. */ - for (unsigned i = 0; i < expMeans_.size(); i++) { - double w = weights_[i]; - ySum += std::min(expMeans_[i] * gain, 255.0) * w; - wSum += w; - } - - /* \todo Weight with the AWB gains */ - - return ySum / wSum / 255; -} - /** * \brief Process frame duration and compute vblank * \param[in] context The shared IPA context @@ -519,6 +477,64 @@ void Agc::processFrameDuration(IPAContext &context, frameContext.agc.frameDuration = (sensorInfo.outputSize.height + frameContext.agc.vblank) * lineDuration; } +namespace { + +class AgcTraits final : public AgcMeanLuminance::Traits +{ +public: + AgcTraits(Span expMeans, Span weights) + : expMeans_(expMeans), weights_(weights) + { + } + + /** + * \brief Estimate the relative luminance of the frame with a given gain + * \param[in] gain The gain to apply to the frame + * + * This function estimates the average relative luminance of the frame that + * would be output by the sensor if an additional \a gain was applied. + * + * The estimation is based on the AE statistics for the current frame. Y + * averages for all cells are first multiplied by the gain, and then saturated + * to approximate the sensor behaviour at high brightness values. The + * approximation is quite rough, as it doesn't take into account non-linearities + * when approaching saturation. In this case, saturating after the conversion to + * YUV doesn't take into account the fact that the R, G and B components + * contribute differently to the relative luminance. + * + * The values are normalized to the [0.0, 1.0] range, where 1.0 corresponds to a + * theoretical perfect reflector of 100% reference white. + * + * More detailed information can be found in: + * https://en.wikipedia.org/wiki/Relative_luminance + * + * \return The relative luminance + */ + double estimateLuminance(double gain) const override + { + ASSERT(expMeans_.size() == weights_.size()); + double ySum = 0.0; + double wSum = 0.0; + + /* Sum the averages, saturated to 255. */ + for (unsigned i = 0; i < expMeans_.size(); i++) { + double w = weights_[i]; + ySum += std::min(expMeans_[i] * gain, 255.0) * w; + wSum += w; + } + + /* \todo Weight with the AWB gains */ + + return ySum / wSum / 255; + } + +private: + Span expMeans_; + Span weights_; +}; + +} + /** * \brief Process RkISP1 statistics, and run AGC operations * \param[in] context The shared IPA context @@ -559,13 +575,6 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, const rkisp1_cif_isp_stat *params = &stats->params; - /* The lower 4 bits are fractional and meant to be discarded. */ - Histogram hist({ params->hist.hist_bins, context.hw.numHistogramBins }, - [](uint32_t x) { return x >> 4; }); - expMeans_ = { params->ae.exp_mean, context.hw.numAeCells }; - std::vector &modeWeights = meteringModes_.at(frameContext.agc.meteringMode); - weights_ = { modeWeights.data(), modeWeights.size() }; - /* * Set the AGC limits using the fixed exposure time and/or gain in * manual mode, or the sensor limits in auto mode. @@ -598,8 +607,8 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, if (context.activeState.wdr.mode != controls::WdrOff) additionalConstraints.push_back(context.activeState.wdr.constraint); - setLimits(minExposureTime, maxExposureTime, minAnalogueGain, maxAnalogueGain, - std::move(additionalConstraints)); + agc_.setLimits(minExposureTime, maxExposureTime, minAnalogueGain, maxAnalogueGain, + std::move(additionalConstraints)); /* * The Agc algorithm needs to know the effective exposure value that was @@ -617,15 +626,23 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, if (frameContext.compress.enable) effectiveExposureValue *= frameContext.agc.quantizationGain; - setExposureCompensation(pow(2.0, frameContext.agc.exposureValue)); - setLux(frameContext.lux.lux); + /* The lower 4 bits are fractional and meant to be discarded. */ + Histogram hist({ params->hist.hist_bins, context.hw.numHistogramBins }, + [](uint32_t x) { return x >> 4; }); + AgcTraits agcTraits{ + { params->ae.exp_mean, context.hw.numAeCells }, + meteringModes_.at(frameContext.agc.meteringMode), + }; + + agc_.setExposureCompensation(pow(2.0, frameContext.agc.exposureValue)); + agc_.setLux(frameContext.lux.lux); utils::Duration newExposureTime; double aGain, qGain, dGain; std::tie(newExposureTime, aGain, qGain, dGain) = - calculateNewEv(frameContext.agc.constraintMode, - frameContext.agc.exposureMode, - hist, effectiveExposureValue); + agc_.calculateNewEv(frameContext.agc.constraintMode, + frameContext.agc.exposureMode, + hist, effectiveExposureValue, agcTraits); LOG(RkISP1Agc, Debug) << "Divided up exposure time, analogue gain, quantization gain" @@ -637,7 +654,7 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, activeState.agc.automatic.exposure = newExposureTime / lineDuration; activeState.agc.automatic.gain = aGain; activeState.agc.automatic.quantizationGain = qGain; - activeState.agc.automatic.yTarget = effectiveYTarget(); + activeState.agc.automatic.yTarget = agc_.effectiveYTarget(); /* * Expand the target frame duration so that we do not run faster than * the minimum frame duration when we have short exposures. @@ -646,7 +663,6 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, std::max(frameContext.agc.minFrameDuration, newExposureTime)); fillMetadata(context, frameContext, metadata); - expMeans_ = {}; } REGISTER_IPA_ALGORITHM(Agc, "Agc") diff --git a/src/ipa/rkisp1/algorithms/agc.h b/src/ipa/rkisp1/algorithms/agc.h index dec79f2f39..0527ca0d5f 100644 --- a/src/ipa/rkisp1/algorithms/agc.h +++ b/src/ipa/rkisp1/algorithms/agc.h @@ -22,7 +22,7 @@ namespace libcamera { namespace ipa::rkisp1::algorithms { -class Agc : public Algorithm, public AgcMeanLuminance +class Agc : public Algorithm { public: Agc(); @@ -49,15 +49,12 @@ private: void fillMetadata(IPAContext &context, IPAFrameContext &frameContext, ControlList &metadata); - double estimateLuminance(double gain) const override; void processFrameDuration(IPAContext &context, IPAFrameContext &frameContext, utils::Duration frameDuration); - Span expMeans_; - Span weights_; - std::map> meteringModes_; + AgcMeanLuminance agc_; }; } /* namespace ipa::rkisp1::algorithms */ From patchwork Fri Jul 3 15:38:07 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 27184 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 8062DC3261 for ; Fri, 3 Jul 2026 15:38:38 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 3074065FFC; Fri, 3 Jul 2026 17:38:31 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="khJPFneL"; 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 DC5A865FCB for ; Fri, 3 Jul 2026 17:38:23 +0200 (CEST) Received: from pb-laptop.local (185.221.140.128.nat.pool.zt.hu [185.221.140.128]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id D3152DF3 for ; Fri, 3 Jul 2026 17:37:37 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783093057; bh=qj6xAoXwMzwAsdLgf2Etgr5CiR0/gXBNUh7XMvtKIvs=; h=From:To:Subject:Date:In-Reply-To:References:From; b=khJPFneLi4ZMHiHoYnBueSV8aUpAN01JSw3+ThPARvERdN5idyTxBp4OhpRcSlbq7 hfKoki9vqFgHnUUt8yUl2mmef3ecjHtqiGjyDkn5zBnH2IAr/7VQYGnlsuEO6nslGK ixnDXZk4yJkJyMGNH2jl6wZy9vpGsOnL/FOm2dRY= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v1 05/17] ipa: rkisp1: Move sensor control info map to context Date: Fri, 3 Jul 2026 17:38:07 +0200 Message-ID: <20260703153819.1088752-6-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260703153819.1088752-1-barnabas.pocze@ideasonboard.com> References: <20260703153819.1088752-1-barnabas.pocze@ideasonboard.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" So that it is available to each algorithm. This will be used by the AGC algorithm. Signed-off-by: Barnabás Pőcze --- src/ipa/rkisp1/ipa_context.cpp | 3 +++ src/ipa/rkisp1/ipa_context.h | 2 ++ src/ipa/rkisp1/rkisp1.cpp | 25 +++++++++++-------------- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/ipa/rkisp1/ipa_context.cpp b/src/ipa/rkisp1/ipa_context.cpp index 302b4cfd70..1f94afda6b 100644 --- a/src/ipa/rkisp1/ipa_context.cpp +++ b/src/ipa/rkisp1/ipa_context.cpp @@ -488,6 +488,9 @@ namespace libcamera::ipa::rkisp1 { * * \var IPAContext::frameContexts * \brief Ring buffer of per-frame contexts + * + * \var IPAContext::sensorControls + * \brief V4L2 controls of the sensor */ } /* namespace libcamera::ipa::rkisp1 */ diff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h index 9a4e0f12c6..eff88b72f5 100644 --- a/src/ipa/rkisp1/ipa_context.h +++ b/src/ipa/rkisp1/ipa_context.h @@ -252,6 +252,8 @@ struct IPAContext { ControlInfoMap::Map ctrlMap; + ControlInfoMap sensorControls; + DebugMetadata debugMetadata; /* Interface to the Camera Helper */ diff --git a/src/ipa/rkisp1/rkisp1.cpp b/src/ipa/rkisp1/rkisp1.cpp index 273540a330..9f1f133e6f 100644 --- a/src/ipa/rkisp1/rkisp1.cpp +++ b/src/ipa/rkisp1/rkisp1.cpp @@ -75,15 +75,12 @@ protected: private: void updateControls(const IPACameraSensorInfo &sensorInfo, - const ControlInfoMap &sensorControls, ControlInfoMap *ipaControls); void setControls(unsigned int frame); std::map buffers_; std::map mappedBuffers_; - ControlInfoMap sensorControls_; - /* Local parameter storage */ struct IPAContext context_; }; @@ -163,6 +160,7 @@ int IPARkISP1::init(const IPASettings &settings, unsigned int hwRevision, LOG(IPARkISP1, Debug) << "Hardware revision is " << hwRevision; context_.sensorInfo = sensorInfo; + context_.sensorControls = sensorControls; context_.camHelper = CameraSensorHelperFactoryBase::create(settings.sensorModel); if (!context_.camHelper) { @@ -207,7 +205,7 @@ int IPARkISP1::init(const IPASettings &settings, unsigned int hwRevision, return ret; /* Initialize controls. */ - updateControls(sensorInfo, sensorControls, ipaControls); + updateControls(sensorInfo, ipaControls); return 0; } @@ -227,13 +225,13 @@ int IPARkISP1::configure(const IPAConfigInfo &ipaConfig, const std::map &streamConfig, ControlInfoMap *ipaControls) { - sensorControls_ = ipaConfig.sensorControls; + context_.sensorControls = ipaConfig.sensorControls; - const auto itExp = sensorControls_.find(V4L2_CID_EXPOSURE); + const auto itExp = context_.sensorControls.find(V4L2_CID_EXPOSURE); int32_t minExposure = itExp->second.min().get(); int32_t maxExposure = itExp->second.max().get(); - const auto itGain = sensorControls_.find(V4L2_CID_ANALOGUE_GAIN); + const auto itGain = context_.sensorControls.find(V4L2_CID_ANALOGUE_GAIN); int32_t minGain = itGain->second.min().get(); int32_t maxGain = itGain->second.max().get(); @@ -253,7 +251,7 @@ int IPARkISP1::configure(const IPAConfigInfo &ipaConfig, context_.configuration.sensor.lineDuration = info.minLineLength * 1.0s / info.pixelRate; /* Update the camera controls using the new sensor settings. */ - updateControls(info, sensorControls_, ipaControls); + updateControls(info, ipaControls); /* * When the AGC computes the new exposure values for a frame, it needs @@ -385,7 +383,6 @@ void IPARkISP1::processStats(const uint32_t frame, const uint32_t bufferId, } void IPARkISP1::updateControls(const IPACameraSensorInfo &sensorInfo, - const ControlInfoMap &sensorControls, ControlInfoMap *ipaControls) { ControlInfoMap::Map ctrlMap = rkisp1Controls; @@ -395,7 +392,7 @@ void IPARkISP1::updateControls(const IPACameraSensorInfo &sensorInfo, * limits and the line duration. */ double lineDuration = context_.configuration.sensor.lineDuration.get(); - const ControlInfo &v4l2Exposure = sensorControls.find(V4L2_CID_EXPOSURE)->second; + const ControlInfo &v4l2Exposure = context_.sensorControls.find(V4L2_CID_EXPOSURE)->second; int32_t minExposure = v4l2Exposure.min().get() * lineDuration; int32_t maxExposure = v4l2Exposure.max().get() * lineDuration; int32_t defExposure = v4l2Exposure.def().get() * lineDuration; @@ -404,7 +401,7 @@ void IPARkISP1::updateControls(const IPACameraSensorInfo &sensorInfo, std::forward_as_tuple(minExposure, maxExposure, defExposure)); /* Compute the analogue gain limits. */ - const ControlInfo &v4l2Gain = sensorControls.find(V4L2_CID_ANALOGUE_GAIN)->second; + const ControlInfo &v4l2Gain = context_.sensorControls.find(V4L2_CID_ANALOGUE_GAIN)->second; float minGain = context_.camHelper->gain(v4l2Gain.min().get()); float maxGain = context_.camHelper->gain(v4l2Gain.max().get()); float defGain = context_.camHelper->gain(v4l2Gain.def().get()); @@ -418,11 +415,11 @@ void IPARkISP1::updateControls(const IPACameraSensorInfo &sensorInfo, * The frame length is computed assuming a fixed line length combined * with the vertical frame sizes. */ - const ControlInfo &v4l2HBlank = sensorControls.find(V4L2_CID_HBLANK)->second; + const ControlInfo &v4l2HBlank = context_.sensorControls.find(V4L2_CID_HBLANK)->second; uint32_t hblank = v4l2HBlank.def().get(); uint32_t lineLength = sensorInfo.outputSize.width + hblank; - const ControlInfo &v4l2VBlank = sensorControls.find(V4L2_CID_VBLANK)->second; + const ControlInfo &v4l2VBlank = context_.sensorControls.find(V4L2_CID_VBLANK)->second; std::array frameHeights{ v4l2VBlank.min().get() + sensorInfo.outputSize.height, v4l2VBlank.max().get() + sensorInfo.outputSize.height, @@ -460,7 +457,7 @@ void IPARkISP1::setControls(unsigned int frame) << "Set controls for frame " << frame << ": exposure " << exposure << ", gain " << frameContext.agc.gain << ", vblank " << vblank; - ControlList ctrls(sensorControls_); + ControlList ctrls(context_.sensorControls); ctrls.set(V4L2_CID_EXPOSURE, static_cast(exposure)); ctrls.set(V4L2_CID_ANALOGUE_GAIN, static_cast(gain)); ctrls.set(V4L2_CID_VBLANK, static_cast(vblank)); From patchwork Fri Jul 3 15:38:08 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 27185 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 A6E95C3302 for ; Fri, 3 Jul 2026 15:38:39 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id F1D7466000; Fri, 3 Jul 2026 17:38:31 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="B7yQiDgJ"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 2028165F9F for ; Fri, 3 Jul 2026 17:38:24 +0200 (CEST) Received: from pb-laptop.local (185.221.140.128.nat.pool.zt.hu [185.221.140.128]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 0AE451121 for ; Fri, 3 Jul 2026 17:37:38 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783093058; bh=TCDe4QpY7XkUUT/pR4gR2sKK/EeGCaPXqU82QKO6mzA=; h=From:To:Subject:Date:In-Reply-To:References:From; b=B7yQiDgJaOZYl5IUB3NRUhH94MAJrNwwo4w6KoVdAaVUCB646CD0GW82jMGnP67Vt LdjaZOoA5MRUHrQlI50EukqpGFQwEWuotcZjW4LBkL/9seRR9qM1c8oBTp3yJuMbyD 043Sg+ZZeX2TssiKbvMtYS/2PrmI6b1Vs9Fcrxjc= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v1 06/17] ipa: rkisp1: Update sensor info when configuring Date: Fri, 3 Jul 2026 17:38:08 +0200 Message-ID: <20260703153819.1088752-7-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260703153819.1088752-1-barnabas.pocze@ideasonboard.com> References: <20260703153819.1088752-1-barnabas.pocze@ideasonboard.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" 830340615afd ("ipa: rkisp1: Add sensor info to context") added the sensor info to the `IPAContext` type, but it only updates it in `init()`, do it in configure as well. Signed-off-by: Barnabás Pőcze --- src/ipa/rkisp1/rkisp1.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ipa/rkisp1/rkisp1.cpp b/src/ipa/rkisp1/rkisp1.cpp index 9f1f133e6f..b31d5b59a8 100644 --- a/src/ipa/rkisp1/rkisp1.cpp +++ b/src/ipa/rkisp1/rkisp1.cpp @@ -225,6 +225,7 @@ int IPARkISP1::configure(const IPAConfigInfo &ipaConfig, const std::map &streamConfig, ControlInfoMap *ipaControls) { + context_.sensorInfo = ipaConfig.sensorInfo; context_.sensorControls = ipaConfig.sensorControls; const auto itExp = context_.sensorControls.find(V4L2_CID_EXPOSURE); From patchwork Fri Jul 3 15:38:09 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 27186 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 ACC9FC3261 for ; Fri, 3 Jul 2026 15:38:40 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id D3D7165FDF; Fri, 3 Jul 2026 17:38:33 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="OgfttVzu"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 4BB9965FD0 for ; Fri, 3 Jul 2026 17:38:24 +0200 (CEST) Received: from pb-laptop.local (185.221.140.128.nat.pool.zt.hu [185.221.140.128]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 4170CDF3 for ; Fri, 3 Jul 2026 17:37:38 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783093058; bh=i4goCozmdZQvtFjLSpkj3E1ghO59Uy5NOL7ZrJ7hOMw=; h=From:To:Subject:Date:In-Reply-To:References:From; b=OgfttVzusZ8QMTLHgv0VxMbxROaDpm5ZNmsCt19EGx5g6ro/FSTstE0shlgsHo+Bd n4J6s/7h8Pz4cvVtkZxDHtURvZ0b9+1ExOWU296/QdTZlYm6JL/PN3mXfFws2Fg1Rr ZznHfIZbEXRqE8vclSHNIz+yoXfCRcnCD7RnfxMM= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v1 07/17] ipa: rkisp1: updateControls(): Use sensor info from context Date: Fri, 3 Jul 2026 17:38:09 +0200 Message-ID: <20260703153819.1088752-8-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260703153819.1088752-1-barnabas.pocze@ideasonboard.com> References: <20260703153819.1088752-1-barnabas.pocze@ideasonboard.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" It is already available so do not take it as a parameter. Signed-off-by: Barnabás Pőcze --- src/ipa/rkisp1/rkisp1.cpp | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/src/ipa/rkisp1/rkisp1.cpp b/src/ipa/rkisp1/rkisp1.cpp index b31d5b59a8..66a91c4d79 100644 --- a/src/ipa/rkisp1/rkisp1.cpp +++ b/src/ipa/rkisp1/rkisp1.cpp @@ -74,8 +74,7 @@ protected: std::string logPrefix() const override; private: - void updateControls(const IPACameraSensorInfo &sensorInfo, - ControlInfoMap *ipaControls); + void updateControls(ControlInfoMap *ipaControls); void setControls(unsigned int frame); std::map buffers_; @@ -205,7 +204,7 @@ int IPARkISP1::init(const IPASettings &settings, unsigned int hwRevision, return ret; /* Initialize controls. */ - updateControls(sensorInfo, ipaControls); + updateControls(ipaControls); return 0; } @@ -247,12 +246,12 @@ int IPARkISP1::configure(const IPAConfigInfo &ipaConfig, context_.configuration.paramFormat = ipaConfig.paramFormat; - const IPACameraSensorInfo &info = ipaConfig.sensorInfo; - context_.configuration.sensor.size = info.outputSize; - context_.configuration.sensor.lineDuration = info.minLineLength * 1.0s / info.pixelRate; + context_.configuration.sensor.size = context_.sensorInfo.outputSize; + context_.configuration.sensor.lineDuration = context_.sensorInfo.minLineLength * 1.0s + / context_.sensorInfo.pixelRate; /* Update the camera controls using the new sensor settings. */ - updateControls(info, ipaControls); + updateControls(ipaControls); /* * When the AGC computes the new exposure values for a frame, it needs @@ -285,7 +284,7 @@ int IPARkISP1::configure(const IPAConfigInfo &ipaConfig, if (algo->disabled_) continue; - int ret = algo->configure(context_, info); + int ret = algo->configure(context_, context_.sensorInfo); if (ret) return ret; } @@ -383,8 +382,7 @@ void IPARkISP1::processStats(const uint32_t frame, const uint32_t bufferId, metadataReady.emit(frame, metadata); } -void IPARkISP1::updateControls(const IPACameraSensorInfo &sensorInfo, - ControlInfoMap *ipaControls) +void IPARkISP1::updateControls(ControlInfoMap *ipaControls) { ControlInfoMap::Map ctrlMap = rkisp1Controls; @@ -418,19 +416,19 @@ void IPARkISP1::updateControls(const IPACameraSensorInfo &sensorInfo, */ const ControlInfo &v4l2HBlank = context_.sensorControls.find(V4L2_CID_HBLANK)->second; uint32_t hblank = v4l2HBlank.def().get(); - uint32_t lineLength = sensorInfo.outputSize.width + hblank; + uint32_t lineLength = context_.sensorInfo.outputSize.width + hblank; const ControlInfo &v4l2VBlank = context_.sensorControls.find(V4L2_CID_VBLANK)->second; std::array frameHeights{ - v4l2VBlank.min().get() + sensorInfo.outputSize.height, - v4l2VBlank.max().get() + sensorInfo.outputSize.height, - v4l2VBlank.def().get() + sensorInfo.outputSize.height, + v4l2VBlank.min().get() + context_.sensorInfo.outputSize.height, + v4l2VBlank.max().get() + context_.sensorInfo.outputSize.height, + v4l2VBlank.def().get() + context_.sensorInfo.outputSize.height, }; std::array frameDurations; for (unsigned int i = 0; i < frameHeights.size(); ++i) { uint64_t frameSize = lineLength * frameHeights[i]; - frameDurations[i] = frameSize / (sensorInfo.pixelRate / 1000000U); + frameDurations[i] = frameSize / (context_.sensorInfo.pixelRate / 1000000U); } /* \todo Move this (and other agc-related controls) to agc */ From patchwork Fri Jul 3 15:38:10 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 27187 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 5BB13C3303 for ; Fri, 3 Jul 2026 15:38:41 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 0BEC66600B; Fri, 3 Jul 2026 17:38:35 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="WcZeFPHX"; 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 82C7765FD8 for ; Fri, 3 Jul 2026 17:38:24 +0200 (CEST) Received: from pb-laptop.local (185.221.140.128.nat.pool.zt.hu [185.221.140.128]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 6F3E51121 for ; Fri, 3 Jul 2026 17:37:38 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783093058; bh=nxvZ/KS10UA5ATQQMLxrZN3L87OapVP3zburJqk2ZhI=; h=From:To:Subject:Date:In-Reply-To:References:From; b=WcZeFPHX444nK5nIMdmpb8CmNLiuyVKrFPnQwxnA6XNcx4W/V8SZ5cmYNed3OqC93 GkHE1THaHnnsoi91DivEvICHiUBJUsuaRO312a9AMILwh50YyLB5Jti4rO3KW0gVk0 M60igCaFL5fild8LchTcSzCX0PxD2ZAlHcn6h1y4= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v1 08/17] ipa: rkisp1: Move AGC related controls into AGC algorithm Date: Fri, 3 Jul 2026 17:38:10 +0200 Message-ID: <20260703153819.1088752-9-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260703153819.1088752-1-barnabas.pocze@ideasonboard.com> References: <20260703153819.1088752-1-barnabas.pocze@ideasonboard.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" Move the `ControlInfo` setup and related initialization into the AGC algorithm instead of having it in the main IPA file. Signed-off-by: Barnabás Pőcze --- src/ipa/rkisp1/algorithms/agc.cpp | 91 ++++++++++++++++++++++++++++++- src/ipa/rkisp1/rkisp1.cpp | 88 +----------------------------- 2 files changed, 91 insertions(+), 88 deletions(-) diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp index 540b64ff99..689d045b7a 100644 --- a/src/ipa/rkisp1/algorithms/agc.cpp +++ b/src/ipa/rkisp1/algorithms/agc.cpp @@ -33,13 +33,96 @@ using namespace std::literals::chrono_literals; namespace ipa::rkisp1::algorithms { +LOG_DEFINE_CATEGORY(RkISP1Agc) + +namespace { + +void reconfigure(IPAContext &context) +{ + context.configuration.sensor.lineDuration = + context.sensorInfo.minLineLength * 1.0s / context.sensorInfo.pixelRate; + + double lineDurationUs = context.configuration.sensor.lineDuration.get(); + + /* + * Compute exposure time limits from the V4L2_CID_EXPOSURE control + * limits and the line duration. + */ + + const ControlInfo &v4l2Exposure = context.sensorControls.find(V4L2_CID_EXPOSURE)->second; + int32_t minExposure = v4l2Exposure.min().get(); + int32_t maxExposure = v4l2Exposure.max().get(); + int32_t defExposure = v4l2Exposure.def().get(); + context.ctrlMap[&controls::ExposureTime] = ControlInfo{ + static_cast(minExposure * lineDurationUs), + static_cast(maxExposure * lineDurationUs), + static_cast(defExposure * lineDurationUs), + }; + + /* Compute the analogue gain limits. */ + const ControlInfo &v4l2Gain = context.sensorControls.find(V4L2_CID_ANALOGUE_GAIN)->second; + float minGain = context.camHelper->gain(v4l2Gain.min().get()); + float maxGain = context.camHelper->gain(v4l2Gain.max().get()); + float defGain = context.camHelper->gain(v4l2Gain.def().get()); + context.ctrlMap[&controls::AnalogueGain] = ControlInfo{ + minGain, + maxGain, + defGain, + }; + + LOG(RkISP1Agc, Debug) + << "Exposure: [" << minExposure << ", " << maxExposure + << "], gain: [" << minGain << ", " << maxGain << "]"; + + /* + * Compute the frame duration limits. + * + * The frame length is computed assuming a fixed line length combined + * with the vertical frame sizes. + */ + const ControlInfo &v4l2HBlank = context.sensorControls.find(V4L2_CID_HBLANK)->second; + uint32_t hblank = v4l2HBlank.def().get(); + uint32_t lineLength = context.sensorInfo.outputSize.width + hblank; + + const ControlInfo &v4l2VBlank = context.sensorControls.find(V4L2_CID_VBLANK)->second; + std::array frameHeights{ + v4l2VBlank.min().get() + context.sensorInfo.outputSize.height, + v4l2VBlank.max().get() + context.sensorInfo.outputSize.height, + v4l2VBlank.def().get() + context.sensorInfo.outputSize.height, + }; + + std::array frameDurations; + for (unsigned int i = 0; i < frameHeights.size(); ++i) { + uint64_t frameSize = lineLength * frameHeights[i]; + frameDurations[i] = frameSize / (context.sensorInfo.pixelRate / 1000000U); + } + + context.ctrlMap[&controls::FrameDurationLimits] = ControlInfo{ + frameDurations[0], + frameDurations[1], + Span{ { frameDurations[2], frameDurations[2] } }, + }; + + /* + * When the AGC computes the new exposure values for a frame, it needs + * to know the limits for exposure time and analogue gain. As it depends + * on the sensor, update it with the controls. + * + * \todo take VBLANK into account for maximum exposure time + */ + context.configuration.sensor.minExposureTime = minExposure * context.configuration.sensor.lineDuration; + context.configuration.sensor.maxExposureTime = maxExposure * context.configuration.sensor.lineDuration; + context.configuration.sensor.minAnalogueGain = context.camHelper->gain(minGain); + context.configuration.sensor.maxAnalogueGain = context.camHelper->gain(maxGain); +} + +} /* namespace */ + /** * \class Agc * \brief A mean-based auto-exposure algorithm */ -LOG_DEFINE_CATEGORY(RkISP1Agc) - int Agc::parseMeteringModes(IPAContext &context, const ValueNode &tuningData) { if (!tuningData.isDictionary()) @@ -160,6 +243,8 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData) context.ctrlMap[&controls::ExposureValue] = ControlInfo(-8.0f, 8.0f, 0.0f); context.ctrlMap.merge(agc_.controls()); + reconfigure(context); + return 0; } @@ -172,6 +257,8 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData) */ int Agc::configure(IPAContext &context, const IPACameraSensorInfo &configInfo) { + reconfigure(context); + /* Configure the default exposure and gain. */ context.activeState.agc.automatic.gain = context.configuration.sensor.minAnalogueGain; context.activeState.agc.automatic.exposure = diff --git a/src/ipa/rkisp1/rkisp1.cpp b/src/ipa/rkisp1/rkisp1.cpp index 66a91c4d79..38f55b1d86 100644 --- a/src/ipa/rkisp1/rkisp1.cpp +++ b/src/ipa/rkisp1/rkisp1.cpp @@ -169,9 +169,6 @@ int IPARkISP1::init(const IPASettings &settings, unsigned int hwRevision, return -ENODEV; } - context_.configuration.sensor.lineDuration = - sensorInfo.minLineLength * 1.0s / sensorInfo.pixelRate; - /* Load the tuning data file. */ File file(settings.configurationFile); if (!file.open(File::OpenModeFlag::ReadOnly)) { @@ -227,18 +224,6 @@ int IPARkISP1::configure(const IPAConfigInfo &ipaConfig, context_.sensorInfo = ipaConfig.sensorInfo; context_.sensorControls = ipaConfig.sensorControls; - const auto itExp = context_.sensorControls.find(V4L2_CID_EXPOSURE); - int32_t minExposure = itExp->second.min().get(); - int32_t maxExposure = itExp->second.max().get(); - - const auto itGain = context_.sensorControls.find(V4L2_CID_ANALOGUE_GAIN); - int32_t minGain = itGain->second.min().get(); - int32_t maxGain = itGain->second.max().get(); - - LOG(IPARkISP1, Debug) - << "Exposure: [" << minExposure << ", " << maxExposure - << "], gain: [" << minGain << ", " << maxGain << "]"; - /* Clear the IPA context before the streaming session. */ context_.configuration = {}; context_.activeState = {}; @@ -247,27 +232,6 @@ int IPARkISP1::configure(const IPAConfigInfo &ipaConfig, context_.configuration.paramFormat = ipaConfig.paramFormat; context_.configuration.sensor.size = context_.sensorInfo.outputSize; - context_.configuration.sensor.lineDuration = context_.sensorInfo.minLineLength * 1.0s - / context_.sensorInfo.pixelRate; - - /* Update the camera controls using the new sensor settings. */ - updateControls(ipaControls); - - /* - * When the AGC computes the new exposure values for a frame, it needs - * to know the limits for exposure time and analogue gain. As it depends - * on the sensor, update it with the controls. - * - * \todo take VBLANK into account for maximum exposure time - */ - context_.configuration.sensor.minExposureTime = - minExposure * context_.configuration.sensor.lineDuration; - context_.configuration.sensor.maxExposureTime = - maxExposure * context_.configuration.sensor.lineDuration; - context_.configuration.sensor.minAnalogueGain = - context_.camHelper->gain(minGain); - context_.configuration.sensor.maxAnalogueGain = - context_.camHelper->gain(maxGain); context_.configuration.raw = std::any_of(streamConfig.begin(), streamConfig.end(), [](auto &cfg) -> bool { @@ -289,6 +253,8 @@ int IPARkISP1::configure(const IPAConfigInfo &ipaConfig, return ret; } + updateControls(ipaControls); + return 0; } @@ -386,56 +352,6 @@ void IPARkISP1::updateControls(ControlInfoMap *ipaControls) { ControlInfoMap::Map ctrlMap = rkisp1Controls; - /* - * Compute exposure time limits from the V4L2_CID_EXPOSURE control - * limits and the line duration. - */ - double lineDuration = context_.configuration.sensor.lineDuration.get(); - const ControlInfo &v4l2Exposure = context_.sensorControls.find(V4L2_CID_EXPOSURE)->second; - int32_t minExposure = v4l2Exposure.min().get() * lineDuration; - int32_t maxExposure = v4l2Exposure.max().get() * lineDuration; - int32_t defExposure = v4l2Exposure.def().get() * lineDuration; - ctrlMap.emplace(std::piecewise_construct, - std::forward_as_tuple(&controls::ExposureTime), - std::forward_as_tuple(minExposure, maxExposure, defExposure)); - - /* Compute the analogue gain limits. */ - const ControlInfo &v4l2Gain = context_.sensorControls.find(V4L2_CID_ANALOGUE_GAIN)->second; - float minGain = context_.camHelper->gain(v4l2Gain.min().get()); - float maxGain = context_.camHelper->gain(v4l2Gain.max().get()); - float defGain = context_.camHelper->gain(v4l2Gain.def().get()); - ctrlMap.emplace(std::piecewise_construct, - std::forward_as_tuple(&controls::AnalogueGain), - std::forward_as_tuple(minGain, maxGain, defGain)); - - /* - * Compute the frame duration limits. - * - * The frame length is computed assuming a fixed line length combined - * with the vertical frame sizes. - */ - const ControlInfo &v4l2HBlank = context_.sensorControls.find(V4L2_CID_HBLANK)->second; - uint32_t hblank = v4l2HBlank.def().get(); - uint32_t lineLength = context_.sensorInfo.outputSize.width + hblank; - - const ControlInfo &v4l2VBlank = context_.sensorControls.find(V4L2_CID_VBLANK)->second; - std::array frameHeights{ - v4l2VBlank.min().get() + context_.sensorInfo.outputSize.height, - v4l2VBlank.max().get() + context_.sensorInfo.outputSize.height, - v4l2VBlank.def().get() + context_.sensorInfo.outputSize.height, - }; - - std::array frameDurations; - for (unsigned int i = 0; i < frameHeights.size(); ++i) { - uint64_t frameSize = lineLength * frameHeights[i]; - frameDurations[i] = frameSize / (context_.sensorInfo.pixelRate / 1000000U); - } - - /* \todo Move this (and other agc-related controls) to agc */ - context_.ctrlMap[&controls::FrameDurationLimits] = - ControlInfo(frameDurations[0], frameDurations[1], - ControlValue(Span{ { frameDurations[2], frameDurations[2] } })); - ctrlMap.insert(context_.ctrlMap.begin(), context_.ctrlMap.end()); *ipaControls = ControlInfoMap(std::move(ctrlMap), controls::controls); } From patchwork Fri Jul 3 15:38:11 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 27189 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 B283BC3308 for ; Fri, 3 Jul 2026 15:38:42 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 4964366013; Fri, 3 Jul 2026 17:38:38 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="odu/k0LX"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id ACD0E65FDB for ; Fri, 3 Jul 2026 17:38:24 +0200 (CEST) Received: from pb-laptop.local (185.221.140.128.nat.pool.zt.hu [185.221.140.128]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 9A4DEDF3 for ; Fri, 3 Jul 2026 17:37:38 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783093058; bh=NNyDV7gIsfCimV6vCLZyrJiavwy/f/CN6usMamS2Y+g=; h=From:To:Subject:Date:In-Reply-To:References:From; b=odu/k0LXD6PURPVAI3oS6sKWXrEIr2cC5qjtUyNT+Ato8jqpLn4LF8s8rGjoec4bJ lplN+iYNx2Vt0VXgBGyCTzPhhUT9iPrvTQJoJ28GIICaWvPK7hemMP+0yPrCLwAwL0 847/BzFAdnp81SZ3NoA8yLiocvg9pUcGp6It573E= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v1 09/17] ipa: mali-c55: Remove `DigitalGain` support Date: Fri, 3 Jul 2026 17:38:11 +0200 Message-ID: <20260703153819.1088752-10-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260703153819.1088752-1-barnabas.pocze@ideasonboard.com> References: <20260703153819.1088752-1-barnabas.pocze@ideasonboard.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" The mali-c55 agc algorithm still mainly uses the `AeEnable` control, but the interaction of that control and `AeEnable` is not well defined, and not other platform supports setting it. So remove it for now until things are properly defined. Link: https://gitlab.freedesktop.org/camera/libcamera/-/work_items/262 Signed-off-by: Barnabás Pőcze --- src/ipa/mali-c55/algorithms/agc.cpp | 57 ++--------------------------- src/ipa/mali-c55/algorithms/agc.h | 3 -- src/ipa/mali-c55/ipa_context.h | 3 -- 3 files changed, 4 insertions(+), 59 deletions(-) diff --git a/src/ipa/mali-c55/algorithms/agc.cpp b/src/ipa/mali-c55/algorithms/agc.cpp index 6e091603f9..8d104e01f2 100644 --- a/src/ipa/mali-c55/algorithms/agc.cpp +++ b/src/ipa/mali-c55/algorithms/agc.cpp @@ -16,7 +16,6 @@ #include #include "libipa/colours.h" -#include "libipa/fixedpoint.h" namespace libcamera { @@ -33,14 +32,6 @@ LOG_DEFINE_CATEGORY(MaliC55Agc) */ static constexpr unsigned int kNumHistogramBins = 256; -/* - * The Mali-C55 ISP has a digital gain block which allows setting gain in Q5.8 - * format, a range of 0.0 to (very nearly) 32.0. We clamp from 1.0 to the actual - * max value which is 8191 * 2^-8. - */ -static constexpr float kMinDigitalGain = 1.0; -static constexpr float kMaxDigitalGain = UQ<5, 8>::TraitsType::max; - uint32_t AgcStatistics::decodeBinValue(uint16_t binVal) { int exponent = (binVal & 0xf000) >> 12; @@ -137,8 +128,6 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData) return ret; context.ctrlMap[&controls::AeEnable] = ControlInfo(false, true); - context.ctrlMap[&controls::DigitalGain] = ControlInfo( - kMinDigitalGain, kMaxDigitalGain, kMinDigitalGain); context.ctrlMap.merge(agc_.controls()); return 0; @@ -158,10 +147,8 @@ int Agc::configure(IPAContext &context, context.activeState.agc.autoEnabled = true; context.activeState.agc.automatic.sensorGain = context.configuration.agc.minAnalogueGain; context.activeState.agc.automatic.exposure = context.configuration.agc.defaultExposure; - context.activeState.agc.automatic.ispGain = kMinDigitalGain; context.activeState.agc.manual.sensorGain = context.configuration.agc.minAnalogueGain; context.activeState.agc.manual.exposure = context.configuration.agc.defaultExposure; - context.activeState.agc.manual.ispGain = kMinDigitalGain; context.activeState.agc.constraintMode = agc_.constraintModes().begin()->first; context.activeState.agc.exposureMode = agc_.exposureModeHelpers().begin()->first; @@ -226,32 +213,6 @@ void Agc::queueRequest(IPAContext &context, const uint32_t frame, << "Analogue gain set to " << agc.manual.sensorGain << " on request sequence " << frame; } - - const auto &digitalGain = controls.get(controls::DigitalGain); - if (digitalGain) { - agc.manual.ispGain = *digitalGain; - - LOG(MaliC55Agc, Debug) - << "Digital gain set to " << agc.manual.ispGain - << " on request sequence " << frame; - } -} - -void Agc::fillGainParamBlock(IPAContext &context, IPAFrameContext &frameContext, - MaliC55Params *params) -{ - IPAActiveState &activeState = context.activeState; - UQ<5, 8> gain; - - if (activeState.agc.autoEnabled) - gain = activeState.agc.automatic.ispGain; - else - gain = activeState.agc.manual.ispGain; - - auto block = params->block(); - block->gain = gain.quantized(); - - frameContext.agc.ispGain = gain; } void Agc::fillParamsBuffer(MaliC55Params *params, enum MaliC55Blocks type) @@ -304,11 +265,9 @@ void Agc::fillWeightsArrayBuffer(MaliC55Params *params, const enum MaliC55Blocks std::fill(weights.begin(), weights.end(), 1); } -void Agc::prepare(IPAContext &context, const uint32_t frame, - IPAFrameContext &frameContext, MaliC55Params *params) +void Agc::prepare([[maybe_unused]] IPAContext &context, const uint32_t frame, + [[maybe_unused]] IPAFrameContext &frameContext, MaliC55Params *params) { - fillGainParamBlock(context, frameContext, params); - if (frame > 0) return; @@ -370,10 +329,8 @@ void Agc::process(IPAContext &context, */ uint32_t exposure = frameContext.agc.exposure; double analogueGain = frameContext.agc.sensorGain; - double digitalGain = frameContext.agc.ispGain.value(); - double totalGain = analogueGain * digitalGain; utils::Duration currentShutter = exposure * configuration.sensor.lineDuration; - utils::Duration effectiveExposureValue = currentShutter * totalGain; + utils::Duration effectiveExposureValue = currentShutter * analogueGain; AgcTraits agcTraits(statistics_); @@ -384,21 +341,15 @@ void Agc::process(IPAContext &context, activeState.agc.exposureMode, statistics_.yHist, effectiveExposureValue, agcTraits); - UQ<5, 8> dGainQ = std::clamp(static_cast(dGain), - kMinDigitalGain, - kMaxDigitalGain); - LOG(MaliC55Agc, Debug) << "Divided up shutter, analogue gain and digital gain are " - << shutterTime << ", " << aGain << " and " << dGainQ; + << shutterTime << ", " << aGain << " and " << dGain; activeState.agc.automatic.exposure = shutterTime / configuration.sensor.lineDuration; activeState.agc.automatic.sensorGain = aGain; - activeState.agc.automatic.ispGain = dGainQ; metadata.set(controls::ExposureTime, currentShutter.get()); metadata.set(controls::AnalogueGain, frameContext.agc.sensorGain); - metadata.set(controls::DigitalGain, frameContext.agc.ispGain.value()); metadata.set(controls::ColourTemperature, context.activeState.agc.temperatureK); } diff --git a/src/ipa/mali-c55/algorithms/agc.h b/src/ipa/mali-c55/algorithms/agc.h index e36378a2ac..ec7fe28c17 100644 --- a/src/ipa/mali-c55/algorithms/agc.h +++ b/src/ipa/mali-c55/algorithms/agc.h @@ -64,9 +64,6 @@ public: ControlList &metadata) override; private: - void fillGainParamBlock(IPAContext &context, - IPAFrameContext &frameContext, - MaliC55Params *params); void fillParamsBuffer(MaliC55Params *params, enum MaliC55Blocks type); void fillWeightsArrayBuffer(MaliC55Params *params, enum MaliC55Blocks type); diff --git a/src/ipa/mali-c55/ipa_context.h b/src/ipa/mali-c55/ipa_context.h index ac4b837738..075d6f66ef 100644 --- a/src/ipa/mali-c55/ipa_context.h +++ b/src/ipa/mali-c55/ipa_context.h @@ -41,12 +41,10 @@ struct IPAActiveState { struct { uint32_t exposure; double sensorGain; - UQ<5, 8> ispGain; } automatic; struct { uint32_t exposure; double sensorGain; - UQ<5, 8> ispGain; } manual; bool autoEnabled; uint32_t constraintMode; @@ -64,7 +62,6 @@ struct IPAFrameContext : public FrameContext { struct { uint32_t exposure; double sensorGain; - UQ<5, 8> ispGain; } agc; struct { From patchwork Fri Jul 3 15:38:12 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 27190 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 8D0DEC330A for ; Fri, 3 Jul 2026 15:38:43 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 1C4F56600C; Fri, 3 Jul 2026 17:38:39 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="AMTG1rjI"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id E08E565FDC for ; Fri, 3 Jul 2026 17:38:24 +0200 (CEST) Received: from pb-laptop.local (185.221.140.128.nat.pool.zt.hu [185.221.140.128]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id C81DD1121 for ; Fri, 3 Jul 2026 17:37:38 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783093058; bh=5O8ZU2Gcw9FKr8ilWmixdJ36NgReYWWAbkJRbpl3tIs=; h=From:To:Subject:Date:In-Reply-To:References:From; b=AMTG1rjIQV1FseWcBe1KGcVLAJaBxO48tJqCkKcE8h5mg30R9/mzZGEo/Jc591kux paddmhH1YmOcJIxuwkFKDgthlSNVxe4+lacxt7l7mrSP2PtCW8xWODh+kePwEbyUGP IrRzDqks6LJaewo6L0VzpVgV5keRLoeh1RqxHFPQ= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v1 10/17] ipa: libipa: Add `AgcMeanLuminance` wrapper Date: Fri, 3 Jul 2026 17:38:12 +0200 Message-ID: <20260703153819.1088752-11-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260703153819.1088752-1-barnabas.pocze@ideasonboard.com> References: <20260703153819.1088752-1-barnabas.pocze@ideasonboard.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" Add an `AgcMeanLuminance` wrapper that can be used to easily implement the `Algorithm` interface in an IPA module. This implementation is a copy of the rkisp1 agc algorithm with minor adjustments. Signed-off-by: Barnabás Pőcze --- src/ipa/libipa/agc_mean_luminance.cpp | 599 ++++++++++++++++++++++++++ src/ipa/libipa/agc_mean_luminance.h | 96 +++++ 2 files changed, 695 insertions(+) diff --git a/src/ipa/libipa/agc_mean_luminance.cpp b/src/ipa/libipa/agc_mean_luminance.cpp index 1529d55864..951a4b0e02 100644 --- a/src/ipa/libipa/agc_mean_luminance.cpp +++ b/src/ipa/libipa/agc_mean_luminance.cpp @@ -8,8 +8,11 @@ #include "agc_mean_luminance.h" #include +#include #include +#include + #include #include @@ -738,6 +741,602 @@ AgcMeanLuminance::calculateNewEv(uint32_t constraintModeIndex, * their configure() function. */ +/** + * \class AgcMeanLuminanceAlgorithm + * \brief AgcMeanLuminance wrapper for implementing the Algorithm interface + * + * \todo DigitalGain, DigitalGainMode + */ + +/** + * \struct AgcMeanLuminanceAlgorithm::Session + * \brief Session configuration for AgcMeanLuminanceAlgorithm + * + * \var AgcMeanLuminanceAlgorithm::Session::minExposureTime + * \brief Minimum exposure time supported with the configured sensor + * + * \var AgcMeanLuminanceAlgorithm::Session::maxExposureTime + * \brief Maximum exposure time supported with the configured sensor + * + * \var AgcMeanLuminanceAlgorithm::Session::minAnalogueGain + * \brief Minimum analogue gain supported with the configured sensor + * + * \var AgcMeanLuminanceAlgorithm::Session::maxAnalogueGain + * \brief Maximum analogue gain supported with the configured sensor + * + * \var AgcMeanLuminanceAlgorithm::Session::minFrameDuration + * \brief Minimum frame duration supported with the configured sensor + * + * \var AgcMeanLuminanceAlgorithm::Session::maxFrameDuration + * \brief Maximum frame duration supported with the configured sensor + * + * \var AgcMeanLuminanceAlgorithm::Session::lineDuration + * \brief Line duration with the configured sensor and output size + * + * \var AgcMeanLuminanceAlgorithm::Session::sensor + * \brief Details of the sensor configuration + * + * \var AgcMeanLuminanceAlgorithm::Session::sensor.outputSize + * \brief Configured output size of the sensor + * + * \var AgcMeanLuminanceAlgorithm::Session::autoAllowed + * \brief Whether automatic controls are allowed + */ + +/** + * \struct AgcMeanLuminanceAlgorithm::ActiveState + * \brief Active state for AgcMeanLuminanceAlgorithm + * + * The \a automatic variables track the latest values computed by algorithm + * based on the latest processed statistics. All other variables track the + * consolidated controls requested in queued requests. + * + * \var AgcMeanLuminanceAlgorithm::ActiveState::manual + * \brief Manual exposure time and analog gain (set through requests) + * + * \var AgcMeanLuminanceAlgorithm::ActiveState::manual.exposure + * \brief Manual exposure time expressed as a number of lines as set by the + * ExposureTime control + * + * \var AgcMeanLuminanceAlgorithm::ActiveState::manual.gain + * \brief Manual analogue gain as set by the AnalogueGain control + * + * \var AgcMeanLuminanceAlgorithm::ActiveState::automatic + * \brief Automatic exposure time and analog gain (computed by the algorithm) + * + * \var AgcMeanLuminanceAlgorithm::ActiveState::automatic.exposure + * \brief Automatic exposure time expressed as a number of lines + * + * \var AgcMeanLuminanceAlgorithm::ActiveState::automatic.gain + * \brief Automatic analogue gain multiplier + * + * \var AgcMeanLuminanceAlgorithm::ActiveState::automatic.quantizationGain + * \brief Automatic quantization gain multiplier + * + * \var AgcMeanLuminanceAlgorithm::ActiveState::automatic.yTarget + * \brief Automatically determined luminance target + * + * \var AgcMeanLuminanceAlgorithm::ActiveState::autoExposureEnabled + * \brief Manual/automatic AGC state (exposure) as set by the ExposureTimeMode control + * + * \var AgcMeanLuminanceAlgorithm::ActiveState::autoGainEnabled + * \brief Manual/automatic AGC state (gain) as set by the AnalogueGainMode control + * + * \var AgcMeanLuminanceAlgorithm::ActiveState::exposureValue + * \brief Exposure value as set by the ExposureValue control + * + * \var AgcMeanLuminanceAlgorithm::ActiveState::constraintMode + * \brief Constraint mode as set by the AeConstraintMode control + * + * \var AgcMeanLuminanceAlgorithm::ActiveState::exposureMode + * \brief Exposure mode as set by the AeExposureMode control + * + * \var AgcMeanLuminanceAlgorithm::ActiveState::minFrameDuration + * \brief Minimum frame duration as set by the FrameDurationLimits control + * + * \var AgcMeanLuminanceAlgorithm::ActiveState::maxFrameDuration + * \brief Maximum frame duration as set by the FrameDurationLimits control + */ + +/** + * \struct AgcMeanLuminanceAlgorithm::FrameContext + * \brief Per-frame context for AgcMeanLuminanceAlgorithm + * + * \var AgcMeanLuminanceAlgorithm::FrameContext::exposure + * \brief Exposure time expressed as a number of lines computed by the algorithm + * + * \var AgcMeanLuminanceAlgorithm::FrameContext::gain + * \brief Analogue gain multiplier computed by the algorithm + * + * The gain should be adapted to the sensor specific gain code before applying. + * + * \var AgcMeanLuminanceAlgorithm::FrameContext::quantizationGain + * \brief Quantization gain multiplier computed by the algorithm + * + * \var AgcMeanLuminanceAlgorithm::FrameContext::exposureValue + * \brief Exposure value as set by the ExposureValue control + * + * \var AgcMeanLuminanceAlgorithm::FrameContext::yTarget + * \brief Luminance target computed by the algorithm + * + * \var AgcMeanLuminanceAlgorithm::FrameContext::vblank + * \brief Vertical blanking parameter computed by the algorithm + * + * \var AgcMeanLuminanceAlgorithm::FrameContext::autoExposureEnabled + * \brief Manual/automatic AGC state (exposure) as set by the ExposureTimeMode control + * + * \var AgcMeanLuminanceAlgorithm::FrameContext::autoGainEnabled + * \brief Manual/automatic AGC state (gain) as set by the AnalogueGainMode control + * + * \var AgcMeanLuminanceAlgorithm::FrameContext::constraintMode + * \brief Constraint mode as set by the AeConstraintMode control + * + * \var AgcMeanLuminanceAlgorithm::FrameContext::exposureMode + * \brief Exposure mode as set by the AeExposureMode control + * + * \var AgcMeanLuminanceAlgorithm::FrameContext::minFrameDuration + * \brief Minimum frame duration as set by the FrameDurationLimits control + * + * \var AgcMeanLuminanceAlgorithm::FrameContext::maxFrameDuration + * \brief Maximum frame duration as set by the FrameDurationLimits control + * + * \var AgcMeanLuminanceAlgorithm::FrameContext::frameDuration + * \brief The actual FrameDuration used by the algorithm for the frame + * + * \var AgcMeanLuminanceAlgorithm::FrameContext::autoExposureModeChange + * \brief Indicate if autoExposureEnabled has changed from true in the previous + * frame to false in the current frame, and no manual exposure value has been + * supplied in the current frame. + * + * \var AgcMeanLuminanceAlgorithm::FrameContext::autoGainModeChange + * \brief Indicate if autoGainEnabled has changed from true in the previous + * frame to false in the current frame, and no manual gain value has been + * supplied in the current frame. + */ + +/** + * \struct AgcMeanLuminanceAlgorithm::ConfigurationParams + * \brief Parameters for AgcMeanLuminanceAlgorithm::configure() + * + * \var AgcMeanLuminanceAlgorithm::ConfigurationParams::sensor + * \brief CameraSensorHelper for the sensor + * + * \var AgcMeanLuminanceAlgorithm::ConfigurationParams::sensorInfo + * \brief Details of the sensor + * + * \var AgcMeanLuminanceAlgorithm::ConfigurationParams::sensorControls + * \brief ControlInfoMap of the sensor + * + * \var AgcMeanLuminanceAlgorithm::ConfigurationParams::ctrlMap + * \brief ControlMap to update with controls + * + * \var AgcMeanLuminanceAlgorithm::ConfigurationParams::autoAllowed + * \brief Whether to enable auto controls + */ + +/** + * \struct AgcMeanLuminanceAlgorithm::ProcessParams + * \brief Parameters for AgcMeanLuminanceAlgorithm::process() + * + * \var AgcMeanLuminanceAlgorithm::ProcessParams::traits + * \brief Implementation of AgcMeanLuminance::Traits + * + * \var AgcMeanLuminanceAlgorithm::ProcessParams::hist + * \brief Luminance histogram of the frame + * + * \var AgcMeanLuminanceAlgorithm::ProcessParams::exposure + * \brief Effective exposure of the frame + * + * \var AgcMeanLuminanceAlgorithm::ProcessParams::gain + * \brief Effective gain of the frame + * + * \var AgcMeanLuminanceAlgorithm::ProcessParams::additionalConstraints + * \brief Addition AgcMeanLuminance::AgcConstraints to apply + * + * \var AgcMeanLuminanceAlgorithm::ProcessParams::lux + * \brief Lux value for the frame + */ + +/** + * \brief Load tuning data + */ +int AgcMeanLuminanceAlgorithm::init(const ValueNode &tuningData) +{ + int ret = impl_.parseTuningData(tuningData); + if (ret) + return ret; + + return 0; +} + +/** + * \brief Initialize the session configuration and active state + */ +int AgcMeanLuminanceAlgorithm::configure(Session &session, ActiveState &state, const ConfigurationParams &config) +{ + session = {}; + session.lineDuration = config.sensorInfo.minLineLength * 1.0s + / config.sensorInfo.pixelRate; + session.sensor.outputSize = config.sensorInfo.outputSize; + session.autoAllowed = config.autoAllowed; + + const double lineDurationUs = session.lineDuration.get(); + + /* + * Compute exposure time limits from the V4L2_CID_EXPOSURE control + * limits and the line duration. + */ + + const ControlInfo &v4l2Exposure = config.sensorControls.find(V4L2_CID_EXPOSURE)->second; + int32_t minExposure = v4l2Exposure.min().get(); + int32_t maxExposure = v4l2Exposure.max().get(); + int32_t defExposure = v4l2Exposure.def().get(); + config.ctrlMap[&controls::ExposureTime] = ControlInfo{ + static_cast(minExposure * lineDurationUs), + static_cast(maxExposure * lineDurationUs), + static_cast(defExposure * lineDurationUs), + }; + + /* Compute the analogue gain limits. */ + const ControlInfo &v4l2Gain = config.sensorControls.find(V4L2_CID_ANALOGUE_GAIN)->second; + float minGain = config.sensor.gain(v4l2Gain.min().get()); + float maxGain = config.sensor.gain(v4l2Gain.max().get()); + float defGain = config.sensor.gain(v4l2Gain.def().get()); + config.ctrlMap[&controls::AnalogueGain] = ControlInfo{ + minGain, + maxGain, + defGain, + }; + + LOG(AgcMeanLuminance, Debug) + << "Exposure: [" << minExposure << ", " << maxExposure + << "], gain: [" << minGain << ", " << maxGain << "]"; + + /* + * Compute the frame duration limits. + * + * The frame length is computed assuming a fixed line length combined + * with the vertical frame sizes. + */ + const ControlInfo &v4l2HBlank = config.sensorControls.find(V4L2_CID_HBLANK)->second; + uint32_t hblank = v4l2HBlank.def().get(); + uint32_t lineLength = config.sensorInfo.outputSize.width + hblank; + + const ControlInfo &v4l2VBlank = config.sensorControls.find(V4L2_CID_VBLANK)->second; + std::array frameHeights{ + v4l2VBlank.min().get() + config.sensorInfo.outputSize.height, + v4l2VBlank.max().get() + config.sensorInfo.outputSize.height, + v4l2VBlank.def().get() + config.sensorInfo.outputSize.height, + }; + + std::array frameDurations; + for (unsigned int i = 0; i < frameHeights.size(); ++i) { + uint64_t frameSize = lineLength * frameHeights[i]; + frameDurations[i] = frameSize / (config.sensorInfo.pixelRate / 1000000U); + } + + config.ctrlMap[&controls::FrameDurationLimits] = ControlInfo{ + frameDurations[0], + frameDurations[1], + Span{ { frameDurations[2], frameDurations[2] } }, + }; + + session.minFrameDuration = std::chrono::microseconds(frameDurations[0]); + session.maxFrameDuration = std::chrono::microseconds(frameDurations[1]); + + /* + * When the AGC computes the new exposure values for a frame, it needs + * to know the limits for exposure time and analogue gain. As it depends + * on the sensor, update it with the controls. + * + * \todo take VBLANK into account for maximum exposure time + */ + session.minExposureTime = minExposure * session.lineDuration; + session.maxExposureTime = maxExposure * session.lineDuration; + session.minAnalogueGain = minGain; + session.maxAnalogueGain = maxGain; + + impl_.configure(session.lineDuration, &config.sensor); + impl_.setLimits(session.minExposureTime, session.maxExposureTime, + session.minAnalogueGain, session.maxAnalogueGain, + {}); + impl_.resetFrameCount(); + + /* Configure the default exposure and gain. */ + state = {}; + state.automatic.gain = session.minAnalogueGain; + state.automatic.exposure = 10ms / session.lineDuration; + state.automatic.quantizationGain = 1; + state.automatic.yTarget = impl_.effectiveYTarget(); + state.manual.gain = state.automatic.gain; + state.manual.exposure = state.automatic.exposure; + state.autoExposureEnabled = session.autoAllowed; + state.autoGainEnabled = session.autoAllowed; + state.exposureValue = 0; + + state.constraintMode = + static_cast(impl_.constraintModes().begin()->first); + state.exposureMode = + static_cast(impl_.exposureModeHelpers().begin()->first); + + state.minFrameDuration = session.minFrameDuration; + state.maxFrameDuration = session.maxFrameDuration; + + const auto add = [&](const ControlId &cid, const auto &automatic, const auto &manual) { + std::array values; + size_t count = 0; + + if (session.autoAllowed) + values[count++] = ControlValue(automatic); + + values[count++] = ControlValue(manual); + + config.ctrlMap[&cid] = ControlInfo{ + { values.data(), count }, + ControlValue(session.autoAllowed ? automatic : manual), + }; + }; + + add(controls::ExposureTimeMode, controls::ExposureTimeModeAuto, controls::ExposureTimeModeManual); + add(controls::AnalogueGainMode, controls::AnalogueGainModeAuto, controls::AnalogueGainModeManual); + + /* \todo Move this to the `Camera` class. */ + config.ctrlMap[&controls::AeEnable] = ControlInfo{ + false, + session.autoAllowed, + session.autoAllowed, + }; + + // \todo Should these be added/removed based on `session.autoAllowed` ? + config.ctrlMap[&controls::ExposureValue] = ControlInfo(-8.0f, 8.0f, 0.0f); + + for (const auto &[id, info] : impl_.controls()) + config.ctrlMap[id] = info; + + return 0; +} + +/** + * \brief Handle a \a queueRequest operation + */ +void AgcMeanLuminanceAlgorithm::queueRequest(const Session &session, ActiveState &state, + FrameContext &frameContext, const ControlList &controls) +{ + if (session.autoAllowed) { + const auto &aeEnable = controls.get(controls::ExposureTimeMode); + if (aeEnable && + (*aeEnable == controls::ExposureTimeModeAuto) != state.autoExposureEnabled) { + state.autoExposureEnabled = (*aeEnable == controls::ExposureTimeModeAuto); + + LOG(AgcMeanLuminance, Debug) + << (state.autoExposureEnabled ? "Enabling" : "Disabling") + << " AGC (exposure)"; + + /* + * If we go from auto -> manual with no manual control + * set, use the last computed value, which we don't + * know until prepare() so save this information. + * + * \todo Check the previous frame at prepare() time + * instead of saving a flag here + */ + if (!state.autoExposureEnabled && !controls.get(controls::ExposureTime)) + frameContext.autoExposureModeChange = true; + } + + const auto &agEnable = controls.get(controls::AnalogueGainMode); + if (agEnable && + (*agEnable == controls::AnalogueGainModeAuto) != state.autoGainEnabled) { + state.autoGainEnabled = (*agEnable == controls::AnalogueGainModeAuto); + + LOG(AgcMeanLuminance, Debug) + << (state.autoGainEnabled ? "Enabling" : "Disabling") + << " AGC (gain)"; + /* + * If we go from auto -> manual with no manual control + * set, use the last computed value, which we don't + * know until prepare() so save this information. + */ + if (!state.autoGainEnabled && !controls.get(controls::AnalogueGain)) + frameContext.autoGainModeChange = true; + } + } + + const auto &exposure = controls.get(controls::ExposureTime); + if (exposure && !state.autoExposureEnabled) { + state.manual.exposure = *exposure * 1.0us / session.lineDuration; + + LOG(AgcMeanLuminance, Debug) + << "Set exposure to " << state.manual.exposure; + } + + const auto &gain = controls.get(controls::AnalogueGain); + if (gain && !state.autoGainEnabled) { + state.manual.gain = *gain; + + LOG(AgcMeanLuminance, Debug) << "Set gain to " << state.manual.gain; + } + + frameContext.autoExposureEnabled = state.autoExposureEnabled; + frameContext.autoGainEnabled = state.autoGainEnabled; + + if (!frameContext.autoExposureEnabled) + frameContext.exposure = state.manual.exposure; + if (!frameContext.autoGainEnabled) + frameContext.gain = state.manual.gain; + + if (!frameContext.autoExposureEnabled && + !frameContext.autoGainEnabled) + frameContext.quantizationGain = 1.0; + + const auto &exposureMode = controls.get(controls::AeExposureMode); + if (exposureMode) + state.exposureMode = + static_cast(*exposureMode); + frameContext.exposureMode = state.exposureMode; + + const auto &constraintMode = controls.get(controls::AeConstraintMode); + if (constraintMode) + state.constraintMode = + static_cast(*constraintMode); + frameContext.constraintMode = state.constraintMode; + + const auto &exposureValue = controls.get(controls::ExposureValue); + if (exposureValue) + state.exposureValue = *exposureValue; + frameContext.exposureValue = state.exposureValue; + + const auto &frameDurationLimits = controls.get(controls::FrameDurationLimits); + if (frameDurationLimits) { + /* Limit the control value to the limits in ControlInfo */ + state.minFrameDuration = std::clamp( + std::chrono::microseconds((*frameDurationLimits).front()), + session.minFrameDuration, + session.maxFrameDuration + ); + + state.maxFrameDuration = std::clamp( + std::chrono::microseconds((*frameDurationLimits).back()), + session.minFrameDuration, + session.maxFrameDuration + ); + } + frameContext.minFrameDuration = state.minFrameDuration; + frameContext.maxFrameDuration = state.maxFrameDuration; +} + +/** + * \brief Handle a \a prepare operation + */ +void AgcMeanLuminanceAlgorithm::prepare(ActiveState &state, FrameContext &frameContext) +{ + uint32_t activeAutoExposure = state.automatic.exposure; + double activeAutoGain = state.automatic.gain; + double activeAutoQGain = state.automatic.quantizationGain; + + /* Populate exposure and gain in auto mode */ + if (frameContext.autoExposureEnabled) { + frameContext.exposure = activeAutoExposure; + frameContext.quantizationGain = activeAutoQGain; + } + if (frameContext.autoGainEnabled) { + frameContext.gain = activeAutoGain; + frameContext.quantizationGain = activeAutoQGain; + } + + /* + * Populate manual exposure and gain from the active auto values when + * transitioning from auto to manual + */ + if (!frameContext.autoExposureEnabled && frameContext.autoExposureModeChange) { + state.manual.exposure = activeAutoExposure; + frameContext.exposure = activeAutoExposure; + } + if (!frameContext.autoGainEnabled && frameContext.autoGainModeChange) { + state.manual.gain = activeAutoGain; + frameContext.gain = activeAutoGain; + frameContext.quantizationGain = activeAutoQGain; + } + + frameContext.yTarget = state.automatic.yTarget; +} + +/** + * \brief Handle a \a process operation + */ +void AgcMeanLuminanceAlgorithm::process(const Session &session, ActiveState &state, + FrameContext &frameContext, std::optional &¶ms, + ControlList &metadata) +{ + const utils::Duration &lineDuration = session.lineDuration; + utils::Duration newExposureTime = {}; + + if (params) { + ASSERT(session.autoAllowed); + + /* + * Set the AGC limits using the fixed exposure time and/or gain in + * manual mode, or the sensor limits in auto mode. + */ + utils::Duration minExposureTime; + utils::Duration maxExposureTime; + double minAnalogueGain; + double maxAnalogueGain; + + if (frameContext.autoExposureEnabled) { + minExposureTime = session.minExposureTime; + maxExposureTime = std::clamp(frameContext.maxFrameDuration, session.minExposureTime, session.maxExposureTime); + } else { + minExposureTime = lineDuration * frameContext.exposure; + maxExposureTime = minExposureTime; + } + + if (frameContext.autoGainEnabled) { + minAnalogueGain = session.minAnalogueGain; + maxAnalogueGain = session.maxAnalogueGain; + } else { + minAnalogueGain = frameContext.gain; + maxAnalogueGain = frameContext.gain; + } + + /* + * The Agc algorithm needs to know the effective exposure value that was + * applied to the sensor when the statistics were collected. + */ + utils::Duration effectiveExposureValue = + lineDuration * params->exposure * params->gain; + + impl_.setLimits(minExposureTime, maxExposureTime, + minAnalogueGain, maxAnalogueGain, + std::move(params->additionalConstraints)); + + impl_.setExposureCompensation(pow(2.0, frameContext.exposureValue)); + impl_.setLux(params->lux); + + double aGain, qGain, dGain; + std::tie(newExposureTime, aGain, qGain, dGain) = + impl_.calculateNewEv(frameContext.constraintMode, frameContext.exposureMode, + params->hist, effectiveExposureValue, params->traits); + + LOG(AgcMeanLuminance, Debug) + << "Divided up exposure time, analogue gain, quantization gain" + << " and digital gain are " << newExposureTime << ", " << aGain + << ", " << qGain << " and " << dGain; + + /* Update the estimated exposure and gain. */ + state.automatic.exposure = newExposureTime / lineDuration; + state.automatic.gain = aGain; + state.automatic.quantizationGain = qGain; + state.automatic.yTarget = impl_.effectiveYTarget(); + } + + /* + * Expand the target frame duration so that we do not run faster than + * the minimum frame duration when we have short exposures. + */ + const auto frameDuration = std::max(frameContext.minFrameDuration, newExposureTime); + frameContext.vblank = (frameDuration / lineDuration) - session.sensor.outputSize.height; + + /* Update frame duration accounting for line length quantization. */ + frameContext.frameDuration = (session.sensor.outputSize.height + frameContext.vblank) * lineDuration; + + metadata.set(controls::AnalogueGain, frameContext.gain); + metadata.set(controls::ExposureTime, utils::Duration(lineDuration * frameContext.exposure).get()); + metadata.set(controls::FrameDuration, frameContext.frameDuration.get()); + metadata.set(controls::ExposureTimeMode, + frameContext.autoExposureEnabled + ? controls::ExposureTimeModeAuto + : controls::ExposureTimeModeManual); + metadata.set(controls::AnalogueGainMode, + frameContext.autoGainEnabled + ? controls::AnalogueGainModeAuto + : controls::AnalogueGainModeManual); + + metadata.set(controls::AeExposureMode, frameContext.exposureMode); + metadata.set(controls::AeConstraintMode, frameContext.constraintMode); + metadata.set(controls::ExposureValue, frameContext.exposureValue); +} + } /* namespace ipa */ } /* namespace libcamera */ diff --git a/src/ipa/libipa/agc_mean_luminance.h b/src/ipa/libipa/agc_mean_luminance.h index f4e1680ab5..3378ec7faa 100644 --- a/src/ipa/libipa/agc_mean_luminance.h +++ b/src/ipa/libipa/agc_mean_luminance.h @@ -9,13 +9,17 @@ #include #include +#include #include #include #include +#include #include +#include + #include "libcamera/internal/value_node.h" #include "exposure_mode_helper.h" @@ -115,6 +119,98 @@ private: ControlInfoMap::Map controls_; }; +class AgcMeanLuminanceAlgorithm +{ +public: + struct Session { + utils::Duration minExposureTime; + utils::Duration maxExposureTime; + double minAnalogueGain; + double maxAnalogueGain; + utils::Duration minFrameDuration; + utils::Duration maxFrameDuration; + + utils::Duration lineDuration; + + struct { + Size outputSize; + } sensor; + + bool autoAllowed; + }; + + struct ActiveState { + struct { + uint32_t exposure; + double gain; + } manual; + struct { + uint32_t exposure; + double gain; + double quantizationGain; + double yTarget; + } automatic; + + bool autoExposureEnabled; + bool autoGainEnabled; + double exposureValue; + controls::AeConstraintModeEnum constraintMode; + controls::AeExposureModeEnum exposureMode; + utils::Duration minFrameDuration; + utils::Duration maxFrameDuration; + }; + + struct FrameContext { + uint32_t exposure; + double gain; + double quantizationGain; + double exposureValue; + double yTarget; + uint32_t vblank; + bool autoExposureEnabled; + bool autoGainEnabled; + controls::AeConstraintModeEnum constraintMode; + controls::AeExposureModeEnum exposureMode; + utils::Duration minFrameDuration; + utils::Duration maxFrameDuration; + utils::Duration frameDuration; + bool autoExposureModeChange; + bool autoGainModeChange; + }; + + struct ConfigurationParams { + const CameraSensorHelper &sensor; + const IPACameraSensorInfo &sensorInfo; + const ControlInfoMap &sensorControls; + ControlInfoMap::Map &ctrlMap; + bool autoAllowed = true; + }; + + int init(const ValueNode &tuningData); + + int configure(Session &session, ActiveState &state, const ConfigurationParams &config); + + void queueRequest(const Session &session, ActiveState &state, + FrameContext &frameContext, const ControlList &controls); + + void prepare(ActiveState &state, FrameContext &frameContext); + + struct ProcessParams { + const AgcMeanLuminance::Traits &traits; + const Histogram &hist; + uint32_t exposure; + double gain; + std::vector &&additionalConstraints = {}; + unsigned int lux = 0; + }; + + void process(const Session &session, ActiveState &state, FrameContext &frameContext, + std::optional &¶ms, ControlList &metadata); + +private: + AgcMeanLuminance impl_; +}; + } /* namespace ipa */ } /* namespace libcamera */ From patchwork Fri Jul 3 15:38:13 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 27188 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 34481C3302 for ; Fri, 3 Jul 2026 15:38:42 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 58FEC65FF9; Fri, 3 Jul 2026 17:38:36 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="A9bQlbNp"; 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 33C4D65FDF for ; Fri, 3 Jul 2026 17:38:25 +0200 (CEST) Received: from pb-laptop.local (185.221.140.128.nat.pool.zt.hu [185.221.140.128]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 0F867DF3 for ; Fri, 3 Jul 2026 17:37:39 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783093059; bh=8vr9OTo49b+4szn/5T10Ifcn3J9n5asCiGNYlN1BSvI=; h=From:To:Subject:Date:In-Reply-To:References:From; b=A9bQlbNpp2ZPGx2CLS8LaTFNH7nVxGLd0VdmrOVnu3HwdLCsfmcds3a0KxXdnfWrI DY/kylH+MVs/tfHmRu1QYwGxJi2smTVExi2accFLYNM1KafEh5hNuC/DOBiYxks7AL Wshmi1lmA/cxxnXL56hFAaQar2QfDEuQ8tLyfOvM= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v1 11/17] ipa: rkisp1: Port to `AgcMeanLuminanceAlgorithm` Date: Fri, 3 Jul 2026 17:38:13 +0200 Message-ID: <20260703153819.1088752-12-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260703153819.1088752-1-barnabas.pocze@ideasonboard.com> References: <20260703153819.1088752-1-barnabas.pocze@ideasonboard.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" Signed-off-by: Barnabás Pőcze --- src/ipa/rkisp1/algorithms/agc.cpp | 481 ++++-------------------------- src/ipa/rkisp1/algorithms/agc.h | 8 +- src/ipa/rkisp1/algorithms/lux.cpp | 2 +- src/ipa/rkisp1/ipa_context.cpp | 98 ------ src/ipa/rkisp1/ipa_context.h | 45 +-- src/ipa/rkisp1/rkisp1.cpp | 8 +- 6 files changed, 73 insertions(+), 569 deletions(-) diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp index 689d045b7a..0d01ec1bda 100644 --- a/src/ipa/rkisp1/algorithms/agc.cpp +++ b/src/ipa/rkisp1/algorithms/agc.cpp @@ -8,9 +8,7 @@ #include "agc.h" #include -#include #include -#include #include #include @@ -35,89 +33,6 @@ namespace ipa::rkisp1::algorithms { LOG_DEFINE_CATEGORY(RkISP1Agc) -namespace { - -void reconfigure(IPAContext &context) -{ - context.configuration.sensor.lineDuration = - context.sensorInfo.minLineLength * 1.0s / context.sensorInfo.pixelRate; - - double lineDurationUs = context.configuration.sensor.lineDuration.get(); - - /* - * Compute exposure time limits from the V4L2_CID_EXPOSURE control - * limits and the line duration. - */ - - const ControlInfo &v4l2Exposure = context.sensorControls.find(V4L2_CID_EXPOSURE)->second; - int32_t minExposure = v4l2Exposure.min().get(); - int32_t maxExposure = v4l2Exposure.max().get(); - int32_t defExposure = v4l2Exposure.def().get(); - context.ctrlMap[&controls::ExposureTime] = ControlInfo{ - static_cast(minExposure * lineDurationUs), - static_cast(maxExposure * lineDurationUs), - static_cast(defExposure * lineDurationUs), - }; - - /* Compute the analogue gain limits. */ - const ControlInfo &v4l2Gain = context.sensorControls.find(V4L2_CID_ANALOGUE_GAIN)->second; - float minGain = context.camHelper->gain(v4l2Gain.min().get()); - float maxGain = context.camHelper->gain(v4l2Gain.max().get()); - float defGain = context.camHelper->gain(v4l2Gain.def().get()); - context.ctrlMap[&controls::AnalogueGain] = ControlInfo{ - minGain, - maxGain, - defGain, - }; - - LOG(RkISP1Agc, Debug) - << "Exposure: [" << minExposure << ", " << maxExposure - << "], gain: [" << minGain << ", " << maxGain << "]"; - - /* - * Compute the frame duration limits. - * - * The frame length is computed assuming a fixed line length combined - * with the vertical frame sizes. - */ - const ControlInfo &v4l2HBlank = context.sensorControls.find(V4L2_CID_HBLANK)->second; - uint32_t hblank = v4l2HBlank.def().get(); - uint32_t lineLength = context.sensorInfo.outputSize.width + hblank; - - const ControlInfo &v4l2VBlank = context.sensorControls.find(V4L2_CID_VBLANK)->second; - std::array frameHeights{ - v4l2VBlank.min().get() + context.sensorInfo.outputSize.height, - v4l2VBlank.max().get() + context.sensorInfo.outputSize.height, - v4l2VBlank.def().get() + context.sensorInfo.outputSize.height, - }; - - std::array frameDurations; - for (unsigned int i = 0; i < frameHeights.size(); ++i) { - uint64_t frameSize = lineLength * frameHeights[i]; - frameDurations[i] = frameSize / (context.sensorInfo.pixelRate / 1000000U); - } - - context.ctrlMap[&controls::FrameDurationLimits] = ControlInfo{ - frameDurations[0], - frameDurations[1], - Span{ { frameDurations[2], frameDurations[2] } }, - }; - - /* - * When the AGC computes the new exposure values for a frame, it needs - * to know the limits for exposure time and analogue gain. As it depends - * on the sensor, update it with the controls. - * - * \todo take VBLANK into account for maximum exposure time - */ - context.configuration.sensor.minExposureTime = minExposure * context.configuration.sensor.lineDuration; - context.configuration.sensor.maxExposureTime = maxExposure * context.configuration.sensor.lineDuration; - context.configuration.sensor.minAnalogueGain = context.camHelper->gain(minGain); - context.configuration.sensor.maxAnalogueGain = context.camHelper->gain(maxGain); -} - -} /* namespace */ - /** * \class Agc * \brief A mean-based auto-exposure algorithm @@ -221,7 +136,16 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData) { int ret; - ret = agc_.parseTuningData(tuningData); + ret = agc_.init(tuningData); + if (ret) + return ret; + + ret = agc_.configure(context.configuration.agc, context.activeState.agc, { + .sensor = *context.camHelper, + .sensorInfo = context.sensorInfo, + .sensorControls = context.sensorControls, + .ctrlMap = context.ctrlMap, + }); if (ret) return ret; @@ -230,21 +154,6 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData) if (ret) return ret; - context.ctrlMap[&controls::ExposureTimeMode] = - ControlInfo({ { ControlValue(controls::ExposureTimeModeAuto), - ControlValue(controls::ExposureTimeModeManual) } }, - ControlValue(controls::ExposureTimeModeAuto)); - context.ctrlMap[&controls::AnalogueGainMode] = - ControlInfo({ { ControlValue(controls::AnalogueGainModeAuto), - ControlValue(controls::AnalogueGainModeManual) } }, - ControlValue(controls::AnalogueGainModeAuto)); - /* \todo Move this to the Camera class */ - context.ctrlMap[&controls::AeEnable] = ControlInfo(false, true, true); - context.ctrlMap[&controls::ExposureValue] = ControlInfo(-8.0f, 8.0f, 0.0f); - context.ctrlMap.merge(agc_.controls()); - - reconfigure(context); - return 0; } @@ -257,47 +166,24 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData) */ int Agc::configure(IPAContext &context, const IPACameraSensorInfo &configInfo) { - reconfigure(context); - - /* Configure the default exposure and gain. */ - context.activeState.agc.automatic.gain = context.configuration.sensor.minAnalogueGain; - context.activeState.agc.automatic.exposure = - 10ms / context.configuration.sensor.lineDuration; - context.activeState.agc.automatic.quantizationGain = 1.0; - context.activeState.agc.manual.gain = context.activeState.agc.automatic.gain; - context.activeState.agc.manual.exposure = context.activeState.agc.automatic.exposure; - context.activeState.agc.autoExposureEnabled = !context.configuration.raw; - context.activeState.agc.autoGainEnabled = !context.configuration.raw; - context.activeState.agc.exposureValue = 0.0; - - context.activeState.agc.constraintMode = - static_cast(agc_.constraintModes().begin()->first); - context.activeState.agc.exposureMode = - static_cast(agc_.exposureModeHelpers().begin()->first); + int ret = agc_.configure(context.configuration.agc, context.activeState.agc, { + .sensor = *context.camHelper, + .sensorInfo = context.sensorInfo, + .sensorControls = context.sensorControls, + .ctrlMap = context.ctrlMap, + .autoAllowed = !context.configuration.raw, + }); + if (ret) + return ret; + context.activeState.agc.meteringMode = static_cast(meteringModes_.begin()->first); - /* Limit the frame duration to match current initialisation */ - ControlInfo &frameDurationLimits = context.ctrlMap[&controls::FrameDurationLimits]; - context.activeState.agc.minFrameDuration = std::chrono::microseconds(frameDurationLimits.min().get()); - context.activeState.agc.maxFrameDuration = std::chrono::microseconds(frameDurationLimits.max().get()); - context.configuration.agc.measureWindow.h_offs = 0; context.configuration.agc.measureWindow.v_offs = 0; context.configuration.agc.measureWindow.h_size = configInfo.outputSize.width; context.configuration.agc.measureWindow.v_size = configInfo.outputSize.height; - agc_.configure(context.configuration.sensor.lineDuration, context.camHelper.get()); - - agc_.setLimits(context.configuration.sensor.minExposureTime, - context.configuration.sensor.maxExposureTime, - context.configuration.sensor.minAnalogueGain, - context.configuration.sensor.maxAnalogueGain, {}); - - context.activeState.agc.automatic.yTarget = agc_.effectiveYTarget(); - - agc_.resetFrameCount(); - return 0; } @@ -311,73 +197,7 @@ void Agc::queueRequest(IPAContext &context, { auto &agc = context.activeState.agc; - if (!context.configuration.raw) { - const auto &aeEnable = controls.get(controls::ExposureTimeMode); - if (aeEnable && - (*aeEnable == controls::ExposureTimeModeAuto) != agc.autoExposureEnabled) { - agc.autoExposureEnabled = (*aeEnable == controls::ExposureTimeModeAuto); - - LOG(RkISP1Agc, Debug) - << (agc.autoExposureEnabled ? "Enabling" : "Disabling") - << " AGC (exposure)"; - - /* - * If we go from auto -> manual with no manual control - * set, use the last computed value, which we don't - * know until prepare() so save this information. - * - * \todo Check the previous frame at prepare() time - * instead of saving a flag here - */ - if (!agc.autoExposureEnabled && !controls.get(controls::ExposureTime)) - frameContext.agc.autoExposureModeChange = true; - } - - const auto &agEnable = controls.get(controls::AnalogueGainMode); - if (agEnable && - (*agEnable == controls::AnalogueGainModeAuto) != agc.autoGainEnabled) { - agc.autoGainEnabled = (*agEnable == controls::AnalogueGainModeAuto); - - LOG(RkISP1Agc, Debug) - << (agc.autoGainEnabled ? "Enabling" : "Disabling") - << " AGC (gain)"; - /* - * If we go from auto -> manual with no manual control - * set, use the last computed value, which we don't - * know until prepare() so save this information. - */ - if (!agc.autoGainEnabled && !controls.get(controls::AnalogueGain)) - frameContext.agc.autoGainModeChange = true; - } - } - - const auto &exposure = controls.get(controls::ExposureTime); - if (exposure && !agc.autoExposureEnabled) { - agc.manual.exposure = *exposure * 1.0us - / context.configuration.sensor.lineDuration; - - LOG(RkISP1Agc, Debug) - << "Set exposure to " << agc.manual.exposure; - } - - const auto &gain = controls.get(controls::AnalogueGain); - if (gain && !agc.autoGainEnabled) { - agc.manual.gain = *gain; - - LOG(RkISP1Agc, Debug) << "Set gain to " << agc.manual.gain; - } - - frameContext.agc.autoExposureEnabled = agc.autoExposureEnabled; - frameContext.agc.autoGainEnabled = agc.autoGainEnabled; - - if (!frameContext.agc.autoExposureEnabled) - frameContext.agc.exposure = agc.manual.exposure; - if (!frameContext.agc.autoGainEnabled) - frameContext.agc.gain = agc.manual.gain; - - if (!frameContext.agc.autoExposureEnabled && - !frameContext.agc.autoGainEnabled) - frameContext.agc.quantizationGain = 1.0; + agc_.queueRequest(context.configuration.agc, context.activeState.agc, frameContext.agc, controls); const auto &meteringMode = controls.get(controls::AeMeteringMode); if (meteringMode) { @@ -386,42 +206,6 @@ void Agc::queueRequest(IPAContext &context, static_cast(*meteringMode); } frameContext.agc.meteringMode = agc.meteringMode; - - const auto &exposureMode = controls.get(controls::AeExposureMode); - if (exposureMode) - agc.exposureMode = - static_cast(*exposureMode); - frameContext.agc.exposureMode = agc.exposureMode; - - const auto &constraintMode = controls.get(controls::AeConstraintMode); - if (constraintMode) - agc.constraintMode = - static_cast(*constraintMode); - frameContext.agc.constraintMode = agc.constraintMode; - - const auto &exposureValue = controls.get(controls::ExposureValue); - if (exposureValue) - agc.exposureValue = *exposureValue; - frameContext.agc.exposureValue = agc.exposureValue; - - const auto &frameDurationLimits = controls.get(controls::FrameDurationLimits); - if (frameDurationLimits) { - /* Limit the control value to the limits in ControlInfo */ - ControlInfo &limits = context.ctrlMap[&controls::FrameDurationLimits]; - int64_t minFrameDuration = - std::clamp((*frameDurationLimits).front(), - limits.min().get(), - limits.max().get()); - int64_t maxFrameDuration = - std::clamp((*frameDurationLimits).back(), - limits.min().get(), - limits.max().get()); - - agc.minFrameDuration = std::chrono::microseconds(minFrameDuration); - agc.maxFrameDuration = std::chrono::microseconds(maxFrameDuration); - } - frameContext.agc.minFrameDuration = agc.minFrameDuration; - frameContext.agc.maxFrameDuration = agc.maxFrameDuration; } /** @@ -430,41 +214,13 @@ void Agc::queueRequest(IPAContext &context, void Agc::prepare(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, RkISP1Params *params) { - uint32_t activeAutoExposure = context.activeState.agc.automatic.exposure; - double activeAutoGain = context.activeState.agc.automatic.gain; - double activeAutoQGain = context.activeState.agc.automatic.quantizationGain; - - /* Populate exposure and gain in auto mode */ - if (frameContext.agc.autoExposureEnabled) { - frameContext.agc.exposure = activeAutoExposure; - frameContext.agc.quantizationGain = activeAutoQGain; - } - if (frameContext.agc.autoGainEnabled) { - frameContext.agc.gain = activeAutoGain; - frameContext.agc.quantizationGain = activeAutoQGain; - } - - /* - * Populate manual exposure and gain from the active auto values when - * transitioning from auto to manual - */ - if (!frameContext.agc.autoExposureEnabled && frameContext.agc.autoExposureModeChange) { - context.activeState.agc.manual.exposure = activeAutoExposure; - frameContext.agc.exposure = activeAutoExposure; - } - if (!frameContext.agc.autoGainEnabled && frameContext.agc.autoGainModeChange) { - context.activeState.agc.manual.gain = activeAutoGain; - frameContext.agc.gain = activeAutoGain; - frameContext.agc.quantizationGain = activeAutoQGain; - } + agc_.prepare(context.activeState.agc, frameContext.agc); if (context.configuration.compress.supported) { frameContext.compress.enable = true; frameContext.compress.gain = frameContext.agc.quantizationGain; } - frameContext.agc.yTarget = context.activeState.agc.automatic.yTarget; - if (frame > 0 && !frameContext.agc.updateMetering) return; @@ -520,50 +276,6 @@ void Agc::prepare(IPAContext &context, const uint32_t frame, static_cast(hstConfig->mode)); } -void Agc::fillMetadata(IPAContext &context, IPAFrameContext &frameContext, - ControlList &metadata) -{ - utils::Duration exposureTime = context.configuration.sensor.lineDuration - * frameContext.sensor.exposure; - metadata.set(controls::AnalogueGain, frameContext.sensor.gain); - metadata.set(controls::ExposureTime, exposureTime.get()); - metadata.set(controls::FrameDuration, frameContext.agc.frameDuration.get()); - metadata.set(controls::ExposureTimeMode, - frameContext.agc.autoExposureEnabled - ? controls::ExposureTimeModeAuto - : controls::ExposureTimeModeManual); - metadata.set(controls::AnalogueGainMode, - frameContext.agc.autoGainEnabled - ? controls::AnalogueGainModeAuto - : controls::AnalogueGainModeManual); - - metadata.set(controls::AeMeteringMode, frameContext.agc.meteringMode); - metadata.set(controls::AeExposureMode, frameContext.agc.exposureMode); - metadata.set(controls::AeConstraintMode, frameContext.agc.constraintMode); - metadata.set(controls::ExposureValue, frameContext.agc.exposureValue); -} - -/** - * \brief Process frame duration and compute vblank - * \param[in] context The shared IPA context - * \param[in] frameContext The current frame context - * \param[in] frameDuration The target frame duration - * - * Compute and populate vblank from the target frame duration. - */ -void Agc::processFrameDuration(IPAContext &context, - IPAFrameContext &frameContext, - utils::Duration frameDuration) -{ - IPACameraSensorInfo &sensorInfo = context.sensorInfo; - utils::Duration lineDuration = context.configuration.sensor.lineDuration; - - frameContext.agc.vblank = (frameDuration / lineDuration) - sensorInfo.outputSize.height; - - /* Update frame duration accounting for line length quantization. */ - frameContext.agc.frameDuration = (sensorInfo.outputSize.height + frameContext.agc.vblank) * lineDuration; -} - namespace { class AgcTraits final : public AgcMeanLuminance::Traits @@ -637,119 +349,54 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, IPAFrameContext &frameContext, const rkisp1_stat_buffer *stats, ControlList &metadata) { - if (!stats) { - processFrameDuration(context, frameContext, - frameContext.agc.minFrameDuration); - fillMetadata(context, frameContext, metadata); - return; - } - - if (!(stats->meas_type & RKISP1_CIF_ISP_STAT_AUTOEXP)) { - fillMetadata(context, frameContext, metadata); - LOG(RkISP1Agc, Error) << "AUTOEXP data is missing in statistics"; - return; - } - - const utils::Duration &lineDuration = context.configuration.sensor.lineDuration; - - /* - * \todo Verify that the exposure and gain applied by the sensor for - * this frame match what has been requested. This isn't a hard - * requirement for stability of the AGC (the guarantee we need in - * automatic mode is a perfect match between the frame and the values - * we receive), but is important in manual mode. - */ + metadata.set(controls::AeMeteringMode, frameContext.agc.meteringMode); - const rkisp1_cif_isp_stat *params = &stats->params; + const rkisp1_cif_isp_stat *params = nullptr; - /* - * Set the AGC limits using the fixed exposure time and/or gain in - * manual mode, or the sensor limits in auto mode. - */ - utils::Duration minExposureTime; - utils::Duration maxExposureTime; - double minAnalogueGain; - double maxAnalogueGain; - - if (frameContext.agc.autoExposureEnabled) { - minExposureTime = context.configuration.sensor.minExposureTime; - maxExposureTime = std::clamp(frameContext.agc.maxFrameDuration, - context.configuration.sensor.minExposureTime, - context.configuration.sensor.maxExposureTime); - } else { - minExposureTime = context.configuration.sensor.lineDuration - * frameContext.agc.exposure; - maxExposureTime = minExposureTime; + if (stats) { + if (stats->meas_type & RKISP1_CIF_ISP_STAT_AUTOEXP) + params = &stats->params; + else + LOG(RkISP1Agc, Error) << "AUTOEXP data is missing in statistics"; } - if (frameContext.agc.autoGainEnabled) { - minAnalogueGain = context.configuration.sensor.minAnalogueGain; - maxAnalogueGain = context.configuration.sensor.maxAnalogueGain; + if (params) { + /* + * \todo Verify that the exposure and gain applied by the sensor for + * this frame match what has been requested. This isn't a hard + * requirement for stability of the AGC (the guarantee we need in + * automatic mode is a perfect match between the frame and the values + * we receive), but is important in manual mode. + */ + + std::vector additionalConstraints; + if (context.activeState.wdr.mode != controls::WdrOff) + additionalConstraints.push_back(context.activeState.wdr.constraint); + + agc_.process(context.configuration.agc, context.activeState.agc, frameContext.agc, {{ + .traits = AgcTraits{ + { params->ae.exp_mean, context.hw.numAeCells }, + meteringModes_.at(frameContext.agc.meteringMode), + }, + .hist = { + /* The lower 4 bits are fractional and meant to be discarded. */ + { params->hist.hist_bins, context.hw.numHistogramBins }, + [](uint32_t x) { return x >> 4; }, + }, + .exposure = frameContext.sensor.exposure, + /* + * Include the quantization gain if it was applied. Do not use + * compress.gain because it will include gains that shall not be + * reported to the user when HDR is implemented. + */ + .gain = frameContext.sensor.gain + * (frameContext.compress.enable ? frameContext.agc.quantizationGain : 1), + .additionalConstraints = std::move(additionalConstraints), + .lux = static_cast(frameContext.lux.lux), + }}, metadata); } else { - minAnalogueGain = frameContext.agc.gain; - maxAnalogueGain = frameContext.agc.gain; + agc_.process(context.configuration.agc, context.activeState.agc, frameContext.agc, {}, metadata); } - - std::vector additionalConstraints; - if (context.activeState.wdr.mode != controls::WdrOff) - additionalConstraints.push_back(context.activeState.wdr.constraint); - - agc_.setLimits(minExposureTime, maxExposureTime, minAnalogueGain, maxAnalogueGain, - std::move(additionalConstraints)); - - /* - * The Agc algorithm needs to know the effective exposure value that was - * applied to the sensor when the statistics were collected. - */ - utils::Duration exposureTime = lineDuration * frameContext.sensor.exposure; - double analogueGain = frameContext.sensor.gain; - utils::Duration effectiveExposureValue = exposureTime * analogueGain; - - /* - * Include the quantization gain if it was applied. Do not use - * compress.gain because it will include gains that shall not be - * reported to the user when HDR is implemented. - */ - if (frameContext.compress.enable) - effectiveExposureValue *= frameContext.agc.quantizationGain; - - /* The lower 4 bits are fractional and meant to be discarded. */ - Histogram hist({ params->hist.hist_bins, context.hw.numHistogramBins }, - [](uint32_t x) { return x >> 4; }); - AgcTraits agcTraits{ - { params->ae.exp_mean, context.hw.numAeCells }, - meteringModes_.at(frameContext.agc.meteringMode), - }; - - agc_.setExposureCompensation(pow(2.0, frameContext.agc.exposureValue)); - agc_.setLux(frameContext.lux.lux); - - utils::Duration newExposureTime; - double aGain, qGain, dGain; - std::tie(newExposureTime, aGain, qGain, dGain) = - agc_.calculateNewEv(frameContext.agc.constraintMode, - frameContext.agc.exposureMode, - hist, effectiveExposureValue, agcTraits); - - LOG(RkISP1Agc, Debug) - << "Divided up exposure time, analogue gain, quantization gain" - << " and digital gain are " << newExposureTime << ", " << aGain - << ", " << qGain << " and " << dGain; - - IPAActiveState &activeState = context.activeState; - /* Update the estimated exposure and gain. */ - activeState.agc.automatic.exposure = newExposureTime / lineDuration; - activeState.agc.automatic.gain = aGain; - activeState.agc.automatic.quantizationGain = qGain; - activeState.agc.automatic.yTarget = agc_.effectiveYTarget(); - /* - * Expand the target frame duration so that we do not run faster than - * the minimum frame duration when we have short exposures. - */ - processFrameDuration(context, frameContext, - std::max(frameContext.agc.minFrameDuration, newExposureTime)); - - fillMetadata(context, frameContext, metadata); } REGISTER_IPA_ALGORITHM(Agc, "Agc") diff --git a/src/ipa/rkisp1/algorithms/agc.h b/src/ipa/rkisp1/algorithms/agc.h index 0527ca0d5f..9d3286709e 100644 --- a/src/ipa/rkisp1/algorithms/agc.h +++ b/src/ipa/rkisp1/algorithms/agc.h @@ -47,14 +47,8 @@ private: uint8_t computeHistogramPredivider(const Size &size, enum rkisp1_cif_isp_histogram_mode mode); - void fillMetadata(IPAContext &context, IPAFrameContext &frameContext, - ControlList &metadata); - void processFrameDuration(IPAContext &context, - IPAFrameContext &frameContext, - utils::Duration frameDuration); - std::map> meteringModes_; - AgcMeanLuminance agc_; + AgcMeanLuminanceAlgorithm agc_; }; } /* namespace ipa::rkisp1::algorithms */ diff --git a/src/ipa/rkisp1/algorithms/lux.cpp b/src/ipa/rkisp1/algorithms/lux.cpp index 86e46c492f..ce6928a55d 100644 --- a/src/ipa/rkisp1/algorithms/lux.cpp +++ b/src/ipa/rkisp1/algorithms/lux.cpp @@ -74,7 +74,7 @@ void Lux::process(IPAContext &context, if (!stats) return; - utils::Duration exposureTime = context.configuration.sensor.lineDuration * + utils::Duration exposureTime = context.configuration.agc.lineDuration * frameContext.sensor.exposure; double gain = frameContext.sensor.gain; diff --git a/src/ipa/rkisp1/ipa_context.cpp b/src/ipa/rkisp1/ipa_context.cpp index 1f94afda6b..47691674ad 100644 --- a/src/ipa/rkisp1/ipa_context.cpp +++ b/src/ipa/rkisp1/ipa_context.cpp @@ -86,21 +86,6 @@ namespace libcamera::ipa::rkisp1 { * \var IPASessionConfiguration::sensor * \brief Sensor-specific configuration of the IPA * - * \var IPASessionConfiguration::sensor.minExposureTime - * \brief Minimum exposure time supported with the sensor - * - * \var IPASessionConfiguration::sensor.maxExposureTime - * \brief Maximum exposure time supported with the sensor - * - * \var IPASessionConfiguration::sensor.minAnalogueGain - * \brief Minimum analogue gain supported with the sensor - * - * \var IPASessionConfiguration::sensor.maxAnalogueGain - * \brief Maximum analogue gain supported with the sensor - * - * \var IPASessionConfiguration::sensor.lineDuration - * \brief Line duration in microseconds - * * \var IPASessionConfiguration::sensor.size * \brief Sensor output resolution */ @@ -147,49 +132,8 @@ namespace libcamera::ipa::rkisp1 { * \var IPAActiveState::agc * \brief State for the Automatic Gain Control algorithm * - * The \a automatic variables track the latest values computed by algorithm - * based on the latest processed statistics. All other variables track the - * consolidated controls requested in queued requests. - * - * \struct IPAActiveState::agc.manual - * \brief Manual exposure time and analog gain (set through requests) - * - * \var IPAActiveState::agc.manual.exposure - * \brief Manual exposure time expressed as a number of lines as set by the - * ExposureTime control - * - * \var IPAActiveState::agc.manual.gain - * \brief Manual analogue gain as set by the AnalogueGain control - * - * \struct IPAActiveState::agc.automatic - * \brief Automatic exposure time and analog gain (computed by the algorithm) - * - * \var IPAActiveState::agc.automatic.exposure - * \brief Automatic exposure time expressed as a number of lines - * - * \var IPAActiveState::agc.automatic.gain - * \brief Automatic analogue gain multiplier - * - * \var IPAActiveState::agc.autoExposureEnabled - * \brief Manual/automatic AGC state (exposure) as set by the ExposureTimeMode control - * - * \var IPAActiveState::agc.autoGainEnabled - * \brief Manual/automatic AGC state (gain) as set by the AnalogueGainMode control - * - * \var IPAActiveState::agc.constraintMode - * \brief Constraint mode as set by the AeConstraintMode control - * - * \var IPAActiveState::agc.exposureMode - * \brief Exposure mode as set by the AeExposureMode control - * * \var IPAActiveState::agc.meteringMode * \brief Metering mode as set by the AeMeteringMode control - * - * \var IPAActiveState::agc.minFrameDuration - * \brief Minimum frame duration as set by the FrameDurationLimits control - * - * \var IPAActiveState::agc.maxFrameDuration - * \brief Maximum frame duration as set by the FrameDurationLimits control */ /** @@ -314,53 +258,11 @@ namespace libcamera::ipa::rkisp1 { * the vertical blanking period is determined to maintain a consistent frame * rate matched to the FrameDurationLimits as set by the user. * - * \var IPAFrameContext::agc.exposure - * \brief Exposure time expressed as a number of lines computed by the algorithm - * - * \var IPAFrameContext::agc.gain - * \brief Analogue gain multiplier computed by the algorithm - * - * The gain should be adapted to the sensor specific gain code before applying. - * - * \var IPAFrameContext::agc.vblank - * \brief Vertical blanking parameter computed by the algorithm - * - * \var IPAFrameContext::agc.autoExposureEnabled - * \brief Manual/automatic AGC state (exposure) as set by the ExposureTimeMode control - * - * \var IPAFrameContext::agc.autoGainEnabled - * \brief Manual/automatic AGC state (gain) as set by the AnalogueGainMode control - * - * \var IPAFrameContext::agc.constraintMode - * \brief Constraint mode as set by the AeConstraintMode control - * - * \var IPAFrameContext::agc.exposureMode - * \brief Exposure mode as set by the AeExposureMode control - * * \var IPAFrameContext::agc.meteringMode * \brief Metering mode as set by the AeMeteringMode control * - * \var IPAFrameContext::agc.minFrameDuration - * \brief Minimum frame duration as set by the FrameDurationLimits control - * - * \var IPAFrameContext::agc.maxFrameDuration - * \brief Maximum frame duration as set by the FrameDurationLimits control - * - * \var IPAFrameContext::agc.frameDuration - * \brief The actual FrameDuration used by the algorithm for the frame - * * \var IPAFrameContext::agc.updateMetering * \brief Indicate if new ISP AGC metering parameters need to be applied - * - * \var IPAFrameContext::agc.autoExposureModeChange - * \brief Indicate if autoExposureEnabled has changed from true in the previous - * frame to false in the current frame, and no manual exposure value has been - * supplied in the current frame. - * - * \var IPAFrameContext::agc.autoGainModeChange - * \brief Indicate if autoGainEnabled has changed from true in the previous - * frame to false in the current frame, and no manual gain value has been - * supplied in the current frame. */ /** diff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h index eff88b72f5..4f620b0672 100644 --- a/src/ipa/rkisp1/ipa_context.h +++ b/src/ipa/rkisp1/ipa_context.h @@ -49,7 +49,7 @@ struct IPAHwSettings { }; struct IPASessionConfiguration { - struct { + struct Agc : AgcMeanLuminanceAlgorithm::Session { struct rkisp1_cif_isp_window measureWindow; } agc; @@ -63,12 +63,6 @@ struct IPASessionConfiguration { } compress; struct { - utils::Duration minExposureTime; - utils::Duration maxExposureTime; - double minAnalogueGain; - double maxAnalogueGain; - - utils::Duration lineDuration; Size size; } sensor; @@ -77,26 +71,8 @@ struct IPASessionConfiguration { }; struct IPAActiveState { - struct { - struct { - uint32_t exposure; - double gain; - } manual; - struct { - uint32_t exposure; - double gain; - double quantizationGain; - double yTarget; - } automatic; - - bool autoExposureEnabled; - bool autoGainEnabled; - double exposureValue; - controls::AeConstraintModeEnum constraintMode; - controls::AeExposureModeEnum exposureMode; + struct Agc : AgcMeanLuminanceAlgorithm::ActiveState { controls::AeMeteringModeEnum meteringMode; - utils::Duration minFrameDuration; - utils::Duration maxFrameDuration; } agc; struct { @@ -155,24 +131,9 @@ struct IPAActiveState { }; struct IPAFrameContext : public FrameContext { - struct { - uint32_t exposure; - double gain; - double exposureValue; - double quantizationGain; - uint32_t vblank; - double yTarget; - bool autoExposureEnabled; - bool autoGainEnabled; - controls::AeConstraintModeEnum constraintMode; - controls::AeExposureModeEnum exposureMode; + struct Agc : AgcMeanLuminanceAlgorithm::FrameContext { controls::AeMeteringModeEnum meteringMode; - utils::Duration minFrameDuration; - utils::Duration maxFrameDuration; - utils::Duration frameDuration; bool updateMetering; - bool autoExposureModeChange; - bool autoGainModeChange; } agc; struct { diff --git a/src/ipa/rkisp1/rkisp1.cpp b/src/ipa/rkisp1/rkisp1.cpp index 38f55b1d86..7470159e0a 100644 --- a/src/ipa/rkisp1/rkisp1.cpp +++ b/src/ipa/rkisp1/rkisp1.cpp @@ -328,10 +328,10 @@ void IPARkISP1::processStats(const uint32_t frame, const uint32_t bufferId, stats = reinterpret_cast( mappedBuffers_.at(bufferId).planes()[0].data()); - frameContext.sensor.exposure = - sensorControls.get(V4L2_CID_EXPOSURE).get(); - frameContext.sensor.gain = - context_.camHelper->gain(sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get()); + frameContext.sensor = { + .exposure = static_cast(sensorControls.get(V4L2_CID_EXPOSURE).get()), + .gain = context_.camHelper->gain(sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get()), + }; ControlList metadata(controls::controls); From patchwork Fri Jul 3 15:38:14 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 27191 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 27CDDC3261 for ; Fri, 3 Jul 2026 15:38:44 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 6B52965FEF; Fri, 3 Jul 2026 17:38:40 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="FNA1Mj4A"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 6A50965FE9 for ; Fri, 3 Jul 2026 17:38:25 +0200 (CEST) Received: from pb-laptop.local (185.221.140.128.nat.pool.zt.hu [185.221.140.128]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 54F6B1121 for ; Fri, 3 Jul 2026 17:37:39 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783093059; bh=Yq3fSQ3nwQmUtEPaiAY44uP5soMd+QiQsd5MJF9LopE=; h=From:To:Subject:Date:In-Reply-To:References:From; b=FNA1Mj4AIe0RVPJvV6iw/WG/adVtPgcphKiVQfGvOR4cj4ovF36fQczlvOf2ahPfy /58WHbcvX44m26xlFOyvx+2jCwF1b0GPytVDVCI4dV0Wg9D77hN4C4bvMBhrau9pWV b+ngbAtSZ4IDPkEssR3MFRgfcCfsJxEG1GKgVHOs= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v1 12/17] ipa: mali-c55: Port to `AgcMeanLuminanceAlgorithm` Date: Fri, 3 Jul 2026 17:38:14 +0200 Message-ID: <20260703153819.1088752-13-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260703153819.1088752-1-barnabas.pocze@ideasonboard.com> References: <20260703153819.1088752-1-barnabas.pocze@ideasonboard.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" Closes: https://gitlab.freedesktop.org/camera/libcamera/-/work_items/262 Signed-off-by: Barnabás Pőcze --- src/ipa/mali-c55/algorithms/agc.cpp | 135 ++++++----------------- src/ipa/mali-c55/algorithms/agc.h | 2 +- src/ipa/mali-c55/ipa_context.h | 35 +++--- src/ipa/mali-c55/mali-c55.cpp | 163 ++++++---------------------- 4 files changed, 79 insertions(+), 256 deletions(-) diff --git a/src/ipa/mali-c55/algorithms/agc.cpp b/src/ipa/mali-c55/algorithms/agc.cpp index 8d104e01f2..386541fd58 100644 --- a/src/ipa/mali-c55/algorithms/agc.cpp +++ b/src/ipa/mali-c55/algorithms/agc.cpp @@ -123,12 +123,19 @@ Agc::Agc() int Agc::init(IPAContext &context, const ValueNode &tuningData) { - int ret = agc_.parseTuningData(tuningData); + int ret = agc_.init(tuningData); if (ret) return ret; - context.ctrlMap[&controls::AeEnable] = ControlInfo(false, true); - context.ctrlMap.merge(agc_.controls()); + ret = agc_.configure(context.configuration.agc, context.activeState.agc, { + .sensor = *context.camHelper, + .sensorInfo = context.sensorInfo, + .sensorControls = context.sensorControls, + .ctrlMap = context.ctrlMap, + .autoAllowed = true, // \todo if not raw? + }); + if (ret) + return ret; return 0; } @@ -140,79 +147,24 @@ int Agc::configure(IPAContext &context, if (ret) return ret; - /* - * Defaults; we use whatever the sensor's default exposure is and the - * minimum analogue gain. AEGC is _active_ by default. - */ - context.activeState.agc.autoEnabled = true; - context.activeState.agc.automatic.sensorGain = context.configuration.agc.minAnalogueGain; - context.activeState.agc.automatic.exposure = context.configuration.agc.defaultExposure; - context.activeState.agc.manual.sensorGain = context.configuration.agc.minAnalogueGain; - context.activeState.agc.manual.exposure = context.configuration.agc.defaultExposure; - context.activeState.agc.constraintMode = agc_.constraintModes().begin()->first; - context.activeState.agc.exposureMode = agc_.exposureModeHelpers().begin()->first; - - /* \todo Run this again when FrameDurationLimits is passed in */ - agc_.setLimits(context.configuration.agc.minShutterSpeed, - context.configuration.agc.maxShutterSpeed, - context.configuration.agc.minAnalogueGain, - context.configuration.agc.maxAnalogueGain, - {}); - - agc_.resetFrameCount(); + ret = agc_.configure(context.configuration.agc, context.activeState.agc, { + .sensor = *context.camHelper, + .sensorInfo = context.sensorInfo, + .sensorControls = context.sensorControls, + .ctrlMap = context.ctrlMap, + .autoAllowed = true, // \todo if not raw? + }); + if (ret) + return ret; return 0; } -void Agc::queueRequest(IPAContext &context, const uint32_t frame, - [[maybe_unused]] IPAFrameContext &frameContext, +void Agc::queueRequest(IPAContext &context, [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, const ControlList &controls) { - auto &agc = context.activeState.agc; - - const auto &constraintMode = controls.get(controls::AeConstraintMode); - agc.constraintMode = constraintMode.value_or(agc.constraintMode); - - const auto &exposureMode = controls.get(controls::AeExposureMode); - agc.exposureMode = exposureMode.value_or(agc.exposureMode); - - const auto &agcEnable = controls.get(controls::AeEnable); - if (agcEnable && *agcEnable != agc.autoEnabled) { - agc.autoEnabled = *agcEnable; - - LOG(MaliC55Agc, Info) - << (agc.autoEnabled ? "Enabling" : "Disabling") - << " AGC"; - } - - /* - * If the automatic exposure and gain is enabled we have no further work - * to do here... - */ - if (agc.autoEnabled) - return; - - /* - * ...otherwise we need to look for exposure and gain controls and use - * those to set the activeState. - */ - const auto &exposure = controls.get(controls::ExposureTime); - if (exposure) { - agc.manual.exposure = *exposure * 1.0us / context.configuration.sensor.lineDuration; - - LOG(MaliC55Agc, Debug) - << "Exposure set to " << agc.manual.exposure - << " on request sequence " << frame; - } - - const auto &analogueGain = controls.get(controls::AnalogueGain); - if (analogueGain) { - agc.manual.sensorGain = *analogueGain; - - LOG(MaliC55Agc, Debug) - << "Analogue gain set to " << agc.manual.sensorGain - << " on request sequence " << frame; - } + agc_.queueRequest(context.configuration.agc, context.activeState.agc, frameContext.agc, controls); } void Agc::fillParamsBuffer(MaliC55Params *params, enum MaliC55Blocks type) @@ -265,9 +217,11 @@ void Agc::fillWeightsArrayBuffer(MaliC55Params *params, const enum MaliC55Blocks std::fill(weights.begin(), weights.end(), 1); } -void Agc::prepare([[maybe_unused]] IPAContext &context, const uint32_t frame, - [[maybe_unused]] IPAFrameContext &frameContext, MaliC55Params *params) +void Agc::prepare(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, MaliC55Params *params) { + agc_.prepare(context.activeState.agc, frameContext.agc); + if (frame > 0) return; @@ -310,9 +264,6 @@ void Agc::process(IPAContext &context, const mali_c55_stats_buffer *stats, [[maybe_unused]] ControlList &metadata) { - IPASessionConfiguration &configuration = context.configuration; - IPAActiveState &activeState = context.activeState; - if (!stats) { LOG(MaliC55Agc, Error) << "No statistics buffer passed to Agc"; return; @@ -323,34 +274,12 @@ void Agc::process(IPAContext &context, statistics_.gHist.interQuantileMean(0, 1), statistics_.bHist.interQuantileMean(0, 1) } }); - /* - * The Agc algorithm needs to know the effective exposure value that was - * applied to the sensor when the statistics were collected. - */ - uint32_t exposure = frameContext.agc.exposure; - double analogueGain = frameContext.agc.sensorGain; - utils::Duration currentShutter = exposure * configuration.sensor.lineDuration; - utils::Duration effectiveExposureValue = currentShutter * analogueGain; - AgcTraits agcTraits(statistics_); - - - utils::Duration shutterTime; - double aGain, qGain, dGain; - std::tie(shutterTime, aGain, qGain, dGain) = - agc_.calculateNewEv(activeState.agc.constraintMode, - activeState.agc.exposureMode, statistics_.yHist, - effectiveExposureValue, agcTraits); - - LOG(MaliC55Agc, Debug) - << "Divided up shutter, analogue gain and digital gain are " - << shutterTime << ", " << aGain << " and " << dGain; - - activeState.agc.automatic.exposure = shutterTime / configuration.sensor.lineDuration; - activeState.agc.automatic.sensorGain = aGain; - - metadata.set(controls::ExposureTime, currentShutter.get()); - metadata.set(controls::AnalogueGain, frameContext.agc.sensorGain); - metadata.set(controls::ColourTemperature, context.activeState.agc.temperatureK); + agc_.process(context.configuration.agc, context.activeState.agc, frameContext.agc, {{ + .traits = AgcTraits(statistics_), + .hist = statistics_.yHist, + .exposure = frameContext.sensor.exposure, + .gain = frameContext.sensor.gain, + }}, metadata); } REGISTER_IPA_ALGORITHM(Agc, "Agc") diff --git a/src/ipa/mali-c55/algorithms/agc.h b/src/ipa/mali-c55/algorithms/agc.h index ec7fe28c17..995a176a19 100644 --- a/src/ipa/mali-c55/algorithms/agc.h +++ b/src/ipa/mali-c55/algorithms/agc.h @@ -68,7 +68,7 @@ private: void fillWeightsArrayBuffer(MaliC55Params *params, enum MaliC55Blocks type); AgcStatistics statistics_; - AgcMeanLuminance agc_; + AgcMeanLuminanceAlgorithm agc_; }; } /* namespace ipa::mali_c55::algorithms */ diff --git a/src/ipa/mali-c55/ipa_context.h b/src/ipa/mali-c55/ipa_context.h index 075d6f66ef..0d8c0f986c 100644 --- a/src/ipa/mali-c55/ipa_context.h +++ b/src/ipa/mali-c55/ipa_context.h @@ -12,6 +12,8 @@ #include "libcamera/internal/bayer_format.h" +#include +#include #include #include "libipa/fixedpoint.h" @@ -21,34 +23,17 @@ namespace libcamera { namespace ipa::mali_c55 { struct IPASessionConfiguration { - struct { - utils::Duration minShutterSpeed; - utils::Duration maxShutterSpeed; - uint32_t defaultExposure; - double minAnalogueGain; - double maxAnalogueGain; + struct Agc : AgcMeanLuminanceAlgorithm::Session { } agc; struct { BayerFormat::Order bayerOrder; - utils::Duration lineDuration; uint32_t blackLevel; } sensor; }; struct IPAActiveState { - struct { - struct { - uint32_t exposure; - double sensorGain; - } automatic; - struct { - uint32_t exposure; - double sensorGain; - } manual; - bool autoEnabled; - uint32_t constraintMode; - uint32_t exposureMode; + struct Agc : AgcMeanLuminanceAlgorithm::ActiveState { uint32_t temperatureK; } agc; @@ -59,10 +44,13 @@ struct IPAActiveState { }; struct IPAFrameContext : public FrameContext { + struct Agc : AgcMeanLuminanceAlgorithm::FrameContext { + } agc; + struct { uint32_t exposure; - double sensorGain; - } agc; + double gain; + } sensor; struct { UQ<4, 8> rGain; @@ -77,10 +65,15 @@ struct IPAContext { } IPASessionConfiguration configuration; + IPACameraSensorInfo sensorInfo; IPAActiveState activeState; FCQueue frameContexts; + ControlInfoMap sensorControls; + + std::unique_ptr camHelper; + ControlInfoMap::Map ctrlMap; }; diff --git a/src/ipa/mali-c55/mali-c55.cpp b/src/ipa/mali-c55/mali-c55.cpp index 1d3af0627f..3374316fa6 100644 --- a/src/ipa/mali-c55/mali-c55.cpp +++ b/src/ipa/mali-c55/mali-c55.cpp @@ -65,21 +65,11 @@ protected: std::string logPrefix() const override; private: - void updateSessionConfiguration(const IPACameraSensorInfo &info, - const ControlInfoMap &sensorControls, - BayerFormat::Order bayerOrder); - void updateControls(const IPACameraSensorInfo &sensorInfo, - const ControlInfoMap &sensorControls, - ControlInfoMap *ipaControls); - void setControls(); + void updateControls(ControlInfoMap *ipaControls); + void setControls(const IPAFrameContext &frameContext); std::map buffers_; - ControlInfoMap sensorControls_; - - /* Interface to the Camera Helper */ - std::unique_ptr camHelper_; - /* Local parameter storage */ struct IPAContext context_; }; @@ -101,8 +91,8 @@ std::string IPAMaliC55::logPrefix() const int IPAMaliC55::init(const IPASettings &settings, const IPAConfigInfo &ipaConfig, ControlInfoMap *ipaControls) { - camHelper_ = CameraSensorHelperFactoryBase::create(settings.sensorModel); - if (!camHelper_) { + context_.camHelper = CameraSensorHelperFactoryBase::create(settings.sensorModel); + if (!context_.camHelper) { LOG(IPAMaliC55, Error) << "Failed to create camera sensor helper for " << settings.sensorModel; @@ -128,30 +118,24 @@ int IPAMaliC55::init(const IPASettings &settings, const IPAConfigInfo &ipaConfig return -EINVAL; } + context_.sensorControls = ipaConfig.sensorControls; + context_.sensorInfo = ipaConfig.sensorInfo; + int ret = createAlgorithms(context_, (*data)["algorithms"]); if (ret) return ret; - updateControls(ipaConfig.sensorInfo, ipaConfig.sensorControls, ipaControls); + updateControls(ipaControls); return 0; } -void IPAMaliC55::setControls() +void IPAMaliC55::setControls(const IPAFrameContext &frameContext) { - IPAActiveState &activeState = context_.activeState; - uint32_t exposure; - uint32_t gain; - - if (activeState.agc.autoEnabled) { - exposure = activeState.agc.automatic.exposure; - gain = camHelper_->gainCode(activeState.agc.automatic.sensorGain); - } else { - exposure = activeState.agc.manual.exposure; - gain = camHelper_->gainCode(activeState.agc.manual.sensorGain); - } + uint32_t exposure = frameContext.agc.exposure; + uint32_t gain = context_.camHelper->gainCode(frameContext.agc.gain); - ControlList ctrls(sensorControls_); + ControlList ctrls(context_.sensorControls); ctrls.set(V4L2_CID_EXPOSURE, static_cast(exposure)); ctrls.set(V4L2_CID_ANALOGUE_GAIN, static_cast(gain)); @@ -168,111 +152,19 @@ void IPAMaliC55::stop() context_.frameContexts.clear(); } -void IPAMaliC55::updateSessionConfiguration(const IPACameraSensorInfo &info, - const ControlInfoMap &sensorControls, - BayerFormat::Order bayerOrder) -{ - context_.configuration.sensor.bayerOrder = bayerOrder; - - const ControlInfo &v4l2Exposure = sensorControls.find(V4L2_CID_EXPOSURE)->second; - int32_t minExposure = v4l2Exposure.min().get(); - int32_t maxExposure = v4l2Exposure.max().get(); - int32_t defExposure = v4l2Exposure.def().get(); - - const ControlInfo &v4l2Gain = sensorControls.find(V4L2_CID_ANALOGUE_GAIN)->second; - int32_t minGain = v4l2Gain.min().get(); - int32_t maxGain = v4l2Gain.max().get(); - - /* - * When the AGC computes the new exposure values for a frame, it needs - * to know the limits for shutter speed and analogue gain. - * As it depends on the sensor, update it with the controls. - * - * \todo take VBLANK into account for maximum shutter speed - */ - context_.configuration.sensor.lineDuration = info.minLineLength * 1.0s / info.pixelRate; - context_.configuration.agc.minShutterSpeed = minExposure * context_.configuration.sensor.lineDuration; - context_.configuration.agc.maxShutterSpeed = maxExposure * context_.configuration.sensor.lineDuration; - context_.configuration.agc.defaultExposure = defExposure; - context_.configuration.agc.minAnalogueGain = camHelper_->gain(minGain); - context_.configuration.agc.maxAnalogueGain = camHelper_->gain(maxGain); - - if (camHelper_->blackLevel().has_value()) { - /* - * The black level from CameraSensorHelper is a 16-bit value. - * The Mali-C55 ISP expects 20-bit settings, so we shift it to - * the appropriate width - */ - context_.configuration.sensor.blackLevel = - camHelper_->blackLevel().value() << 4; - } -} - -void IPAMaliC55::updateControls(const IPACameraSensorInfo &sensorInfo, - const ControlInfoMap &sensorControls, - ControlInfoMap *ipaControls) +void IPAMaliC55::updateControls(ControlInfoMap *ipaControls) { ControlInfoMap::Map ctrlMap; - /* - * Compute the frame duration limits. - * - * The frame length is computed assuming a fixed line length combined - * with the vertical frame sizes. - */ - const ControlInfo &v4l2HBlank = sensorControls.find(V4L2_CID_HBLANK)->second; - uint32_t hblank = v4l2HBlank.def().get(); - uint32_t lineLength = sensorInfo.outputSize.width + hblank; - - const ControlInfo &v4l2VBlank = sensorControls.find(V4L2_CID_VBLANK)->second; - std::array frameHeights{ - v4l2VBlank.min().get() + sensorInfo.outputSize.height, - v4l2VBlank.max().get() + sensorInfo.outputSize.height, - v4l2VBlank.def().get() + sensorInfo.outputSize.height, - }; - - std::array frameDurations; - for (unsigned int i = 0; i < frameHeights.size(); ++i) { - uint64_t frameSize = lineLength * frameHeights[i]; - frameDurations[i] = frameSize / (sensorInfo.pixelRate / 1000000U); - } - - ctrlMap[&controls::FrameDurationLimits] = ControlInfo(frameDurations[0], - frameDurations[1], - Span{ { frameDurations[2], frameDurations[2] } }); - - /* - * Compute exposure time limits from the V4L2_CID_EXPOSURE control - * limits and the line duration. - */ - double lineDuration = sensorInfo.minLineLength / sensorInfo.pixelRate; - - const ControlInfo &v4l2Exposure = sensorControls.find(V4L2_CID_EXPOSURE)->second; - int32_t minExposure = v4l2Exposure.min().get() * lineDuration; - int32_t maxExposure = v4l2Exposure.max().get() * lineDuration; - int32_t defExposure = v4l2Exposure.def().get() * lineDuration; - ctrlMap[&controls::ExposureTime] = ControlInfo(minExposure, maxExposure, defExposure); - - /* Compute the analogue gain limits. */ - const ControlInfo &v4l2Gain = sensorControls.find(V4L2_CID_ANALOGUE_GAIN)->second; - float minGain = camHelper_->gain(v4l2Gain.min().get()); - float maxGain = camHelper_->gain(v4l2Gain.max().get()); - float defGain = camHelper_->gain(v4l2Gain.def().get()); - ctrlMap[&controls::AnalogueGain] = ControlInfo(minGain, maxGain, defGain); - - /* - * Merge in any controls that we support either statically or from the - * algorithms. - */ ctrlMap.insert(context_.ctrlMap.begin(), context_.ctrlMap.end()); - *ipaControls = ControlInfoMap(std::move(ctrlMap), controls::controls); } int IPAMaliC55::configure(const IPAConfigInfo &ipaConfig, uint8_t bayerOrder, ControlInfoMap *ipaControls) { - sensorControls_ = ipaConfig.sensorControls; + context_.sensorControls = ipaConfig.sensorControls; + context_.sensorInfo = ipaConfig.sensorInfo; /* Clear the IPA context before the streaming session. */ context_.configuration = {}; @@ -281,9 +173,16 @@ int IPAMaliC55::configure(const IPAConfigInfo &ipaConfig, uint8_t bayerOrder, const IPACameraSensorInfo &info = ipaConfig.sensorInfo; - updateSessionConfiguration(info, ipaConfig.sensorControls, - static_cast(bayerOrder)); - updateControls(info, ipaConfig.sensorControls, ipaControls); + context_.configuration.sensor.bayerOrder = static_cast(bayerOrder); + + if (auto bl = context_.camHelper->blackLevel()) { + /* + * The black level from CameraSensorHelper is a 16-bit value. + * The Mali-C55 ISP expects 20-bit settings, so we shift it to + * the appropriate width + */ + context_.configuration.sensor.blackLevel = *bl << 4; + } for (const auto &a : algorithms()) { Algorithm *algo = static_cast(a.get()); @@ -293,6 +192,8 @@ int IPAMaliC55::configure(const IPAConfigInfo &ipaConfig, uint8_t bayerOrder, return ret; } + updateControls(ipaControls); + return 0; } @@ -352,10 +253,10 @@ void IPAMaliC55::processStats(unsigned int request, unsigned int bufferId, stats = reinterpret_cast( buffers_.at(bufferId).planes()[0].data()); - frameContext.agc.exposure = - sensorControls.get(V4L2_CID_EXPOSURE).get(); - frameContext.agc.sensorGain = - camHelper_->gain(sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get()); + frameContext.sensor = { + .exposure = static_cast(sensorControls.get(V4L2_CID_EXPOSURE).get()), + .gain = context_.camHelper->gain(sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get()), + }; ControlList metadata(controls::controls); @@ -365,7 +266,7 @@ void IPAMaliC55::processStats(unsigned int request, unsigned int bufferId, algo->process(context_, request, frameContext, stats, metadata); } - setControls(); + setControls(frameContext); statsProcessed.emit(request, metadata); } From patchwork Fri Jul 3 15:38:15 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 27192 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 ABDE5C330F for ; Fri, 3 Jul 2026 15:38:44 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 2422F66008; Fri, 3 Jul 2026 17:38:41 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="QzvoOIyE"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 9AB5365FD1 for ; Fri, 3 Jul 2026 17:38:25 +0200 (CEST) Received: from pb-laptop.local (185.221.140.128.nat.pool.zt.hu [185.221.140.128]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 8800EDF3 for ; Fri, 3 Jul 2026 17:37:39 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783093059; bh=EpXVn8DUACcO1Qs1kjcPbmVPnUJvJ8nOFUvevW9d86U=; h=From:To:Subject:Date:In-Reply-To:References:From; b=QzvoOIyEzTfu6hD9qJ5O6crTmPTVHUpGOLbnk/HSMqx/besoS4QQOrYceswZqHWj3 Y8z0hgd5EBNi58jyDNywYEJi5b2tMbphUVwCO/JiESoZ5sSqvW3ajOS6Hye9adn+DG Fz+HAGi5nRctzbfrP4HIN32QdP6pv0D11zWfY4Bk= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v1 13/17] ipa: ipu3: Port to `AgcMeanLuminanceAlgorithm` Date: Fri, 3 Jul 2026 17:38:15 +0200 Message-ID: <20260703153819.1088752-14-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260703153819.1088752-1-barnabas.pocze@ideasonboard.com> References: <20260703153819.1088752-1-barnabas.pocze@ideasonboard.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" Signed-off-by: Barnabás Pőcze --- src/ipa/ipu3/algorithms/agc.cpp | 128 ++++++++++++----------------- src/ipa/ipu3/algorithms/agc.h | 13 ++- src/ipa/ipu3/ipa_context.cpp | 49 +++--------- src/ipa/ipu3/ipa_context.h | 27 +++---- src/ipa/ipu3/ipu3.cpp | 138 ++++++-------------------------- 5 files changed, 104 insertions(+), 251 deletions(-) diff --git a/src/ipa/ipu3/algorithms/agc.cpp b/src/ipa/ipu3/algorithms/agc.cpp index 5b152dcda2..0ca02390bd 100644 --- a/src/ipa/ipu3/algorithms/agc.cpp +++ b/src/ipa/ipu3/algorithms/agc.cpp @@ -48,17 +48,10 @@ namespace ipa::ipu3::algorithms { LOG_DEFINE_CATEGORY(IPU3Agc) -/* Minimum limit for analogue gain value */ -static constexpr double kMinAnalogueGain = 1.0; - -/* \todo Honour the FrameDurationLimits control instead of hardcoding a limit */ -static constexpr utils::Duration kMaxExposureTime = 60ms; - /* Histogram constants */ static constexpr uint32_t knumHistogramBins = 256; Agc::Agc() - : minExposureTime_(0s), maxExposureTime_(0s) { } @@ -76,11 +69,18 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData) { int ret; - ret = agc_.parseTuningData(tuningData); + ret = agc_.init(tuningData); if (ret) return ret; - context.ctrlMap.merge(agc_.controls()); + ret = agc_.configure(context.configuration.agc, context.activeState.agc, { + .sensor = *context.camHelper, + .sensorInfo = context.sensorInfo, + .sensorControls = context.sensorControls, + .ctrlMap = context.ctrlMap, + }); + if (ret) + return ret; return 0; } @@ -95,32 +95,36 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData) int Agc::configure(IPAContext &context, [[maybe_unused]] const IPAConfigInfo &configInfo) { - const IPASessionConfiguration &configuration = context.configuration; - IPAActiveState &activeState = context.activeState; - - stride_ = configuration.grid.stride; - bdsGrid_ = configuration.grid.bdsGrid; - - minExposureTime_ = configuration.agc.minExposureTime; - maxExposureTime_ = std::min(configuration.agc.maxExposureTime, - kMaxExposureTime); - - minAnalogueGain_ = std::max(configuration.agc.minAnalogueGain, kMinAnalogueGain); - maxAnalogueGain_ = configuration.agc.maxAnalogueGain; - - /* Configure the default exposure and gain. */ - activeState.agc.gain = minAnalogueGain_; - activeState.agc.exposure = 10ms / configuration.sensor.lineDuration; - - context.activeState.agc.constraintMode = agc_.constraintModes().begin()->first; - context.activeState.agc.exposureMode = agc_.exposureModeHelpers().begin()->first; + stride_ = context.configuration.grid.stride; + bdsGrid_ = context.configuration.grid.bdsGrid; + + return agc_.configure(context.configuration.agc, context.activeState.agc, { + .sensor = *context.camHelper, + .sensorInfo = context.sensorInfo, + .sensorControls = context.sensorControls, + .ctrlMap = context.ctrlMap, + .autoAllowed = true, // \todo if not raw? + }); +} - /* \todo Run this again when FrameDurationLimits is passed in */ - agc_.setLimits(minExposureTime_, maxExposureTime_, minAnalogueGain_, - maxAnalogueGain_, {}); - agc_.resetFrameCount(); +/** + * \copydoc libcamera::ipa::Algorithm::queueRequest + */ +void Agc::queueRequest(IPAContext &context, [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, const ControlList &controls) +{ + agc_.queueRequest(context.configuration.agc, context.activeState.agc, + frameContext.agc, controls); +} - return 0; +/** + * \copydoc libcamera::ipa::Algorithm::prepare + */ +void Agc::prepare(IPAContext &context, [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, + [[maybe_unused]] ipu3_uapi_params *params) +{ + agc_.prepare(context.activeState.agc, frameContext.agc); } Histogram Agc::parseStatistics(const ipu3_uapi_stats_3a *stats, @@ -229,50 +233,20 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame, Histogram hist = parseStatistics(stats, context.configuration.grid.bdsGrid); - /* - * The Agc algorithm needs to know the effective exposure value that was - * applied to the sensor when the statistics were collected. - */ - utils::Duration exposureTime = context.configuration.sensor.lineDuration - * frameContext.sensor.exposure; - double analogueGain = frameContext.sensor.gain; - utils::Duration effectiveExposureValue = exposureTime * analogueGain; - - AgcTraits agcTraits{ - rgbTriples_, - {{ - context.activeState.awb.gains.red, - context.activeState.awb.gains.blue, - context.activeState.awb.gains.green, - }}, - bdsGrid_, - }; - - utils::Duration newExposureTime; - double aGain, qGain, dGain; - std::tie(newExposureTime, aGain, qGain, dGain) = - agc_.calculateNewEv(context.activeState.agc.constraintMode, - context.activeState.agc.exposureMode, hist, - effectiveExposureValue, agcTraits); - - LOG(IPU3Agc, Debug) - << "Divided up exposure time, analogue gain and digital gain are " - << newExposureTime << ", " << aGain << " and " << dGain; - - IPAActiveState &activeState = context.activeState; - /* Update the estimated exposure time and gain. */ - activeState.agc.exposure = newExposureTime / context.configuration.sensor.lineDuration; - activeState.agc.gain = aGain; - - metadata.set(controls::AnalogueGain, frameContext.sensor.gain); - metadata.set(controls::ExposureTime, exposureTime.get()); - - /* \todo Use VBlank value calculated from each frame exposure. */ - uint32_t vTotal = context.configuration.sensor.size.height - + context.configuration.sensor.defVBlank; - utils::Duration frameDuration = context.configuration.sensor.lineDuration - * vTotal; - metadata.set(controls::FrameDuration, frameDuration.get()); + agc_.process(context.configuration.agc, context.activeState.agc, frameContext.agc, {{ + .traits = AgcTraits{ + rgbTriples_, + {{ + context.activeState.awb.gains.red, + context.activeState.awb.gains.blue, + context.activeState.awb.gains.green, + }}, + bdsGrid_, + }, + .hist = hist, + .exposure = frameContext.sensor.exposure, + .gain = frameContext.sensor.gain, + }}, metadata); } REGISTER_IPA_ALGORITHM(Agc, "Agc") diff --git a/src/ipa/ipu3/algorithms/agc.h b/src/ipa/ipu3/algorithms/agc.h index d08da7600e..d0a82949c7 100644 --- a/src/ipa/ipu3/algorithms/agc.h +++ b/src/ipa/ipu3/algorithms/agc.h @@ -32,6 +32,11 @@ public: int init(IPAContext &context, const ValueNode &tuningData) override; int configure(IPAContext &context, const IPAConfigInfo &configInfo) override; + void queueRequest(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, const ControlList &controls) override; + void prepare(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + ipu3_uapi_params *params) override; void process(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, const ipu3_uapi_stats_3a *stats, @@ -41,17 +46,11 @@ private: Histogram parseStatistics(const ipu3_uapi_stats_3a *stats, const ipu3_uapi_grid_config &grid); - utils::Duration minExposureTime_; - utils::Duration maxExposureTime_; - - double minAnalogueGain_; - double maxAnalogueGain_; - uint32_t stride_; ipu3_uapi_grid_config bdsGrid_; std::vector> rgbTriples_; - AgcMeanLuminance agc_; + AgcMeanLuminanceAlgorithm agc_; }; } /* namespace ipa::ipu3::algorithms */ diff --git a/src/ipa/ipu3/ipa_context.cpp b/src/ipa/ipu3/ipa_context.cpp index 3b22f79176..c358b09981 100644 --- a/src/ipa/ipu3/ipa_context.cpp +++ b/src/ipa/ipu3/ipa_context.cpp @@ -46,12 +46,21 @@ namespace libcamera::ipa::ipu3 { * \var IPAContext::configuration * \brief The IPA session configuration, immutable during the session * + * \var IPAContext::sensorInfo + * \brief The IPA camera session details, immutable during the session + * + * \var IPAContext::sensorControls + * \brief The camera sensor controls, immutable during the session + * * \var IPAContext::frameContexts * \brief Ring buffer of the IPAFrameContext(s) * * \var IPAContext::activeState * \brief The current state of IPA algorithms * + * \var IPAContext::camHelper + * \brief The camera sensor helper + * * \var IPAContext::ctrlMap * \brief A ControlInfoMap::Map of controls populated by the algorithms */ @@ -95,48 +104,11 @@ namespace libcamera::ipa::ipu3 { /** * \var IPASessionConfiguration::agc * \brief AGC parameters configuration of the IPA - * - * \var IPASessionConfiguration::agc.minExposureTime - * \brief Minimum exposure time supported with the configured sensor - * - * \var IPASessionConfiguration::agc.maxExposureTime - * \brief Maximum exposure time supported with the configured sensor - * - * \var IPASessionConfiguration::agc.minAnalogueGain - * \brief Minimum analogue gain supported with the configured sensor - * - * \var IPASessionConfiguration::agc.maxAnalogueGain - * \brief Maximum analogue gain supported with the configured sensor - */ - -/** - * \var IPASessionConfiguration::sensor - * \brief Sensor-specific configuration of the IPA - * - * \var IPASessionConfiguration::sensor.lineDuration - * \brief Line duration in microseconds - * - * \var IPASessionConfiguration::sensor.defVBlank - * \brief The default vblank value of the sensor - * - * \var IPASessionConfiguration::sensor.size - * \brief Sensor output resolution */ /** * \var IPAActiveState::agc * \brief Context for the Automatic Gain Control algorithm - * - * The exposure and gain determined are expected to be applied to the sensor - * at the earliest opportunity. - * - * \var IPAActiveState::agc.exposure - * \brief Exposure time expressed as a number of lines - * - * \var IPAActiveState::agc.gain - * \brief Analogue gain multiplier - * - * The gain should be adapted to the sensor specific gain code before applying. */ /** @@ -185,6 +157,9 @@ namespace libcamera::ipa::ipu3 { * * \var IPAFrameContext::sensor.gain * \brief Analogue gain multiplier + * + * \var IPAFrameContext::agc + * \brief Per-frame state for the AGC algorithm */ } /* namespace libcamera::ipa::ipu3 */ diff --git a/src/ipa/ipu3/ipa_context.h b/src/ipa/ipu3/ipa_context.h index 97fcf06cd4..682fe4474f 100644 --- a/src/ipa/ipu3/ipa_context.h +++ b/src/ipa/ipu3/ipa_context.h @@ -15,6 +15,8 @@ #include #include +#include +#include #include namespace libcamera { @@ -32,18 +34,8 @@ struct IPASessionConfiguration { ipu3_uapi_grid_config afGrid; } af; - struct { - utils::Duration minExposureTime; - utils::Duration maxExposureTime; - double minAnalogueGain; - double maxAnalogueGain; + struct Agc : AgcMeanLuminanceAlgorithm::Session { } agc; - - struct { - int32_t defVBlank; - utils::Duration lineDuration; - Size size; - } sensor; }; struct IPAActiveState { @@ -53,11 +45,7 @@ struct IPAActiveState { bool stable; } af; - struct { - uint32_t exposure; - double gain; - uint32_t constraintMode; - uint32_t exposureMode; + struct Agc : AgcMeanLuminanceAlgorithm::ActiveState { } agc; struct { @@ -81,6 +69,9 @@ struct IPAFrameContext : public FrameContext { uint32_t exposure; double gain; } sensor; + + struct Agc : AgcMeanLuminanceAlgorithm::FrameContext { + } agc; }; struct IPAContext { @@ -90,10 +81,14 @@ struct IPAContext { } IPASessionConfiguration configuration; + IPACameraSensorInfo sensorInfo; + ControlInfoMap sensorControls; IPAActiveState activeState; FCQueue frameContexts; + std::unique_ptr camHelper; + ControlInfoMap::Map ctrlMap; }; diff --git a/src/ipa/ipu3/ipu3.cpp b/src/ipa/ipu3/ipu3.cpp index 4bdc4b7677..fa8e1031f1 100644 --- a/src/ipa/ipu3/ipu3.cpp +++ b/src/ipa/ipu3/ipu3.cpp @@ -165,24 +165,15 @@ protected: std::string logPrefix() const override; private: - void updateControls(const IPACameraSensorInfo &sensorInfo, - const ControlInfoMap &sensorControls, - ControlInfoMap *ipaControls); - void updateSessionConfiguration(const ControlInfoMap &sensorControls); + void updateControls(ControlInfoMap *ipaControls); void setControls(unsigned int frame); void calculateBdsGrid(const Size &bdsOutputSize); std::map buffers_; - ControlInfoMap sensorCtrls_; ControlInfoMap lensCtrls_; - IPACameraSensorInfo sensorInfo_; - - /* Interface to the Camera Helper */ - std::unique_ptr camHelper_; - /* Local parameter storage */ struct IPAContext context_; }; @@ -197,36 +188,6 @@ std::string IPAIPU3::logPrefix() const return "ipu3"; } -/** - * \brief Compute IPASessionConfiguration using the sensor information and the - * sensor V4L2 controls - */ -void IPAIPU3::updateSessionConfiguration(const ControlInfoMap &sensorControls) -{ - const ControlInfo vBlank = sensorControls.find(V4L2_CID_VBLANK)->second; - context_.configuration.sensor.defVBlank = vBlank.def().get(); - - const ControlInfo &v4l2Exposure = sensorControls.find(V4L2_CID_EXPOSURE)->second; - int32_t minExposure = v4l2Exposure.min().get(); - int32_t maxExposure = v4l2Exposure.max().get(); - - const ControlInfo &v4l2Gain = sensorControls.find(V4L2_CID_ANALOGUE_GAIN)->second; - int32_t minGain = v4l2Gain.min().get(); - int32_t maxGain = v4l2Gain.max().get(); - - /* - * When the AGC computes the new exposure values for a frame, it needs - * to know the limits for exposure time and analogue gain. - * As it depends on the sensor, update it with the controls. - * - * \todo take VBLANK into account for maximum exposure time - */ - context_.configuration.agc.minExposureTime = minExposure * context_.configuration.sensor.lineDuration; - context_.configuration.agc.maxExposureTime = maxExposure * context_.configuration.sensor.lineDuration; - context_.configuration.agc.minAnalogueGain = camHelper_->gain(minGain); - context_.configuration.agc.maxAnalogueGain = camHelper_->gain(maxGain); -} - /** * \brief Compute camera controls using the sensor information and the sensor * V4L2 controls @@ -239,54 +200,12 @@ void IPAIPU3::updateSessionConfiguration(const ControlInfoMap &sensorControls) * - controls::ExposureTime * - controls::FrameDurationLimits */ -void IPAIPU3::updateControls(const IPACameraSensorInfo &sensorInfo, - const ControlInfoMap &sensorControls, - ControlInfoMap *ipaControls) +void IPAIPU3::updateControls(ControlInfoMap *ipaControls) { - ControlInfoMap::Map controls{}; - double lineDuration = context_.configuration.sensor.lineDuration.get(); - - /* - * Compute exposure time limits by using line length and pixel rate - * converted to microseconds. Use the V4L2_CID_EXPOSURE control to get - * exposure min, max and default and convert it from lines to - * microseconds. - */ - const ControlInfo &v4l2Exposure = sensorControls.find(V4L2_CID_EXPOSURE)->second; - int32_t minExposure = v4l2Exposure.min().get() * lineDuration; - int32_t maxExposure = v4l2Exposure.max().get() * lineDuration; - int32_t defExposure = v4l2Exposure.def().get() * lineDuration; - controls[&controls::ExposureTime] = ControlInfo(minExposure, maxExposure, - defExposure); - - /* - * Compute the frame duration limits. - * - * The frame length is computed assuming a fixed line length combined - * with the vertical frame sizes. - */ - const ControlInfo &v4l2HBlank = sensorControls.find(V4L2_CID_HBLANK)->second; - uint32_t hblank = v4l2HBlank.def().get(); - uint32_t lineLength = sensorInfo.outputSize.width + hblank; - - const ControlInfo &v4l2VBlank = sensorControls.find(V4L2_CID_VBLANK)->second; - std::array frameHeights{ - v4l2VBlank.min().get() + sensorInfo.outputSize.height, - v4l2VBlank.max().get() + sensorInfo.outputSize.height, - v4l2VBlank.def().get() + sensorInfo.outputSize.height, - }; + ControlInfoMap::Map ctrlMap; - std::array frameDurations; - for (unsigned int i = 0; i < frameHeights.size(); ++i) { - uint64_t frameSize = lineLength * frameHeights[i]; - frameDurations[i] = frameSize / (sensorInfo.pixelRate / 1000000U); - } - controls[&controls::FrameDurationLimits] = ControlInfo(frameDurations[0], - frameDurations[1], - Span{ { frameDurations[2], frameDurations[2] } }); - - controls.insert(context_.ctrlMap.begin(), context_.ctrlMap.end()); - *ipaControls = ControlInfoMap(std::move(controls), controls::controls); + ctrlMap.insert(context_.ctrlMap.begin(), context_.ctrlMap.end()); + *ipaControls = ControlInfoMap(std::move(ctrlMap), controls::controls); } /** @@ -301,18 +220,19 @@ int IPAIPU3::init(const IPASettings &settings, const ControlInfoMap &sensorControls, ControlInfoMap *ipaControls) { - camHelper_ = CameraSensorHelperFactoryBase::create(settings.sensorModel); - if (camHelper_ == nullptr) { + context_.camHelper = CameraSensorHelperFactoryBase::create(settings.sensorModel); + if (!context_.camHelper) { LOG(IPAIPU3, Error) << "Failed to create camera sensor helper for " << settings.sensorModel; return -ENODEV; } + context_.sensorInfo = sensorInfo; + context_.sensorControls = sensorControls; + /* Clean context */ context_.configuration = {}; - context_.configuration.sensor.lineDuration = - sensorInfo.minLineLength * 1.0s / sensorInfo.pixelRate; /* Load the tuning data file. */ File file(settings.configurationFile); @@ -346,7 +266,7 @@ int IPAIPU3::init(const IPASettings &settings, return ret; /* Initialize controls. */ - updateControls(sensorInfo, sensorControls, ipaControls); + updateControls(ipaControls); return 0; } @@ -465,7 +385,8 @@ int IPAIPU3::configure(const IPAConfigInfo &configInfo, return -ENODATA; } - sensorInfo_ = configInfo.sensorInfo; + context_.sensorInfo = configInfo.sensorInfo; + context_.sensorControls = configInfo.sensorControls; lensCtrls_ = configInfo.lensControls; @@ -474,31 +395,16 @@ int IPAIPU3::configure(const IPAConfigInfo &configInfo, context_.configuration = {}; context_.frameContexts.clear(); - /* Initialise the sensor configuration. */ - context_.configuration.sensor.lineDuration = - sensorInfo_.minLineLength * 1.0s / sensorInfo_.pixelRate; - context_.configuration.sensor.size = sensorInfo_.outputSize; - - /* - * Compute the sensor V4L2 controls to be used by the algorithms and - * to be set on the sensor. - */ - sensorCtrls_ = configInfo.sensorControls; - calculateBdsGrid(configInfo.bdsOutputSize); - /* Update the camera controls using the new sensor settings. */ - updateControls(sensorInfo_, sensorCtrls_, ipaControls); - - /* Update the IPASessionConfiguration using the sensor settings. */ - updateSessionConfiguration(sensorCtrls_); - for (const auto &algo : algorithms()) { int ret = algo->configure(context_, configInfo); if (ret) return ret; } + updateControls(ipaControls); + return 0; } @@ -596,8 +502,10 @@ void IPAIPU3::processStats(const uint32_t frame, IPAFrameContext &frameContext = context_.frameContexts.get(frame); - frameContext.sensor.exposure = sensorControls.get(V4L2_CID_EXPOSURE).get(); - frameContext.sensor.gain = camHelper_->gain(sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get()); + frameContext.sensor = { + .exposure = static_cast(sensorControls.get(V4L2_CID_EXPOSURE).get()), + .gain = context_.camHelper->gain(sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get()), + }; ControlList metadata(controls::controls); @@ -642,10 +550,12 @@ void IPAIPU3::queueRequest(const uint32_t frame, const ControlList &controls) */ void IPAIPU3::setControls(unsigned int frame) { - int32_t exposure = context_.activeState.agc.exposure; - int32_t gain = camHelper_->gainCode(context_.activeState.agc.gain); + IPAFrameContext &frameContext = context_.frameContexts.get(frame); + + int32_t exposure = frameContext.agc.exposure; + int32_t gain = context_.camHelper->gainCode(frameContext.agc.gain); - ControlList ctrls(sensorCtrls_); + ControlList ctrls(context_.sensorControls); ctrls.set(V4L2_CID_EXPOSURE, exposure); ctrls.set(V4L2_CID_ANALOGUE_GAIN, gain); From patchwork Fri Jul 3 15:38:16 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 27193 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 73823C3318 for ; Fri, 3 Jul 2026 15:38:45 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 1A2B36600F; Fri, 3 Jul 2026 17:38:42 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="NDxKQWmQ"; 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 D2C7365FE0 for ; Fri, 3 Jul 2026 17:38:25 +0200 (CEST) Received: from pb-laptop.local (185.221.140.128.nat.pool.zt.hu [185.221.140.128]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id BE1AE1121 for ; Fri, 3 Jul 2026 17:37:39 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783093059; bh=5WOirKhO6Iav3CeuKawf9yYkxt2wqOKxdj7itOI3kWY=; h=From:To:Subject:Date:In-Reply-To:References:From; b=NDxKQWmQ/NB91RoN9LmoOu4yBQEe9eGlJnf7W4TKwjcNszzZcmVL/TMFRCUJRBoXk poMru7Lwj65iZDEj1laBiFg6Aoerr6LzIPaxtbsSX5C9kuTom1TPrZWCF0ZnobbqWK wyxfnpLB5XqgoUVLjxnLvxGWph2JsnSswdEWmtmo= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v1 14/17] ipa: libipa: agc_mean_luminance: controls(): Return const ref Date: Fri, 3 Jul 2026 17:38:16 +0200 Message-ID: <20260703153819.1088752-15-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260703153819.1088752-1-barnabas.pocze@ideasonboard.com> References: <20260703153819.1088752-1-barnabas.pocze@ideasonboard.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" Now that no users use `std::unordered_map::merge()` to add the controls to the control info map, there is no reason to return a copy. Signed-off-by: Barnabás Pőcze --- src/ipa/libipa/agc_mean_luminance.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ipa/libipa/agc_mean_luminance.h b/src/ipa/libipa/agc_mean_luminance.h index 3378ec7faa..e7e2d8ad0d 100644 --- a/src/ipa/libipa/agc_mean_luminance.h +++ b/src/ipa/libipa/agc_mean_luminance.h @@ -78,7 +78,7 @@ public: return exposureModeHelpers_; } - ControlInfoMap::Map controls() + const ControlInfoMap::Map &controls() const { return controls_; } From patchwork Fri Jul 3 15:38:17 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 27195 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 4B9E8C331D for ; Fri, 3 Jul 2026 15:38:46 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 7300E66011; Fri, 3 Jul 2026 17:38:44 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="nImFBRzI"; 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 31F3065FC1 for ; Fri, 3 Jul 2026 17:38:26 +0200 (CEST) Received: from pb-laptop.local (185.221.140.128.nat.pool.zt.hu [185.221.140.128]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 01BA2DF3 for ; Fri, 3 Jul 2026 17:37:39 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783093060; bh=IkWf0P3HVc0tMo8+voc9P6z9vWAMhD/CCpLr+aS2F28=; h=From:To:Subject:Date:In-Reply-To:References:From; b=nImFBRzIMk96GWhMbYzBCNELH459RPWmleHm8XnK/EJ7DSadQ5WxpxBljVW1SOP56 MeOgUZ41fRZCxR72dh/GOHmwfxbYD95XUu5WfsvW8crLj20lITNMV51rmq3AmNs9ld f37VPakf0DJsNeUOFEWOqsViZkcvF0gRB8puHPoo= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v1 15/17] ipa: libipa: Add `AgcAlgorithm` Date: Fri, 3 Jul 2026 17:38:17 +0200 Message-ID: <20260703153819.1088752-16-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260703153819.1088752-1-barnabas.pocze@ideasonboard.com> References: <20260703153819.1088752-1-barnabas.pocze@ideasonboard.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" Add a class that encompasses the core logic of an agc algorithm with some calculations and control handling. This can be used to simplify the implementation of specific algorithms. Also port `AgcMeanLuminanceAlgorithm` to use it. Signed-off-by: Barnabás Pőcze --- src/ipa/ipu3/algorithms/agc.cpp | 4 +- src/ipa/libipa/agc.cpp | 488 ++++++++++++++++++++++++++ src/ipa/libipa/agc.h | 100 ++++++ src/ipa/libipa/agc_mean_luminance.cpp | 429 ++-------------------- src/ipa/libipa/agc_mean_luminance.h | 61 +--- src/ipa/libipa/meson.build | 2 + src/ipa/mali-c55/algorithms/agc.cpp | 4 +- src/ipa/rkisp1/algorithms/agc.cpp | 4 +- src/ipa/rkisp1/algorithms/wdr.cpp | 2 +- 9 files changed, 627 insertions(+), 467 deletions(-) create mode 100644 src/ipa/libipa/agc.cpp create mode 100644 src/ipa/libipa/agc.h diff --git a/src/ipa/ipu3/algorithms/agc.cpp b/src/ipa/ipu3/algorithms/agc.cpp index 0ca02390bd..fbf887c328 100644 --- a/src/ipa/ipu3/algorithms/agc.cpp +++ b/src/ipa/ipu3/algorithms/agc.cpp @@ -74,7 +74,7 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData) return ret; ret = agc_.configure(context.configuration.agc, context.activeState.agc, { - .sensor = *context.camHelper, + .sensor = context.camHelper.get(), .sensorInfo = context.sensorInfo, .sensorControls = context.sensorControls, .ctrlMap = context.ctrlMap, @@ -99,7 +99,7 @@ int Agc::configure(IPAContext &context, bdsGrid_ = context.configuration.grid.bdsGrid; return agc_.configure(context.configuration.agc, context.activeState.agc, { - .sensor = *context.camHelper, + .sensor = context.camHelper.get(), .sensorInfo = context.sensorInfo, .sensorControls = context.sensorControls, .ctrlMap = context.ctrlMap, diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp new file mode 100644 index 0000000000..7fce952171 --- /dev/null +++ b/src/ipa/libipa/agc.cpp @@ -0,0 +1,488 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026 Ideas on Board Oy + */ + +#include "agc.h" + +#include + +#include + +#include + +#include + +namespace libcamera { + +namespace ipa { + +using namespace std::chrono_literals; + +LOG_DEFINE_CATEGORY(Agc) + +/** + * \class AgcAlgorithm + * \brief Commong handling of AGC related controls and calculation + * + * \todo DigitalGain, DigitalGainMode + */ + +/** + * \struct AgcAlgorithm::Session + * \brief Session configuration for AgcAlgorithm + * + * \var AgcAlgorithm::Session::minExposureTime + * \brief Minimum exposure time supported with the configured sensor + * + * \var AgcAlgorithm::Session::maxExposureTime + * \brief Maximum exposure time supported with the configured sensor + * + * \var AgcAlgorithm::Session::minAnalogueGain + * \brief Minimum analogue gain supported with the configured sensor + * + * \var AgcAlgorithm::Session::maxAnalogueGain + * \brief Maximum analogue gain supported with the configured sensor + * + * \var AgcAlgorithm::Session::minFrameDuration + * \brief Minimum frame duration supported with the configured sensor + * + * \var AgcAlgorithm::Session::maxFrameDuration + * \brief Maximum frame duration supported with the configured sensor + * + * \var AgcAlgorithm::Session::lineDuration + * \brief Line duration with the configured sensor and output size + * + * \var AgcAlgorithm::Session::sensor + * \brief Details of the sensor configuration + * + * \var AgcAlgorithm::Session::sensor.outputSize + * \brief Configured output size of the sensor + * + * \var AgcAlgorithm::Session::autoAllowed + * \brief Whether automatic controls are allowed + */ + +/** + * \struct AgcAlgorithm::ActiveState + * \brief Active state for AgcAlgorithm + * + * The \a automatic variables track the latest values computed by algorithm + * based on the latest processed statistics. All other variables track the + * consolidated controls requested in queued requests. + * + * \var AgcAlgorithm::ActiveState::manual + * \brief Manual exposure time and analog gain (set through requests) + * + * \var AgcAlgorithm::ActiveState::manual.exposure + * \brief Manual exposure time expressed as a number of lines as set by the + * ExposureTime control + * + * \var AgcAlgorithm::ActiveState::manual.gain + * \brief Manual analogue gain as set by the AnalogueGain control + * + * \var AgcAlgorithm::ActiveState::automatic + * \brief Automatic exposure time and analog gain (computed by the algorithm) + * + * \var AgcAlgorithm::ActiveState::automatic.exposure + * \brief Automatic exposure time expressed as a number of lines + * + * \var AgcAlgorithm::ActiveState::automatic.gain + * \brief Automatic analogue gain multiplier + * + * \var AgcAlgorithm::ActiveState::autoExposureEnabled + * \brief Manual/automatic AGC state (exposure) as set by the ExposureTimeMode control + * + * \var AgcAlgorithm::ActiveState::autoGainEnabled + * \brief Manual/automatic AGC state (gain) as set by the AnalogueGainMode control + * + * \var AgcAlgorithm::ActiveState::minFrameDuration + * \brief Minimum frame duration as set by the FrameDurationLimits control + * + * \var AgcAlgorithm::ActiveState::maxFrameDuration + * \brief Maximum frame duration as set by the FrameDurationLimits control + */ + +/** + * \struct AgcAlgorithm::FrameContext + * \brief Per-frame context for AgcAlgorithm + * + * \var AgcAlgorithm::FrameContext::exposure + * \brief Exposure time expressed as a number of lines computed by the algorithm + * + * \var AgcAlgorithm::FrameContext::gain + * \brief Analogue gain multiplier computed by the algorithm + * + * The gain should be adapted to the sensor specific gain code before applying. + * + * \var AgcAlgorithm::FrameContext::vblank + * \brief Vertical blanking parameter computed by the algorithm + * + * \var AgcAlgorithm::FrameContext::autoExposureEnabled + * \brief Manual/automatic AGC state (exposure) as set by the ExposureTimeMode control + * + * \var AgcAlgorithm::FrameContext::autoGainEnabled + * \brief Manual/automatic AGC state (gain) as set by the AnalogueGainMode control + * + * \var AgcAlgorithm::FrameContext::minFrameDuration + * \brief Minimum frame duration as set by the FrameDurationLimits control + * + * \var AgcAlgorithm::FrameContext::maxFrameDuration + * \brief Maximum frame duration as set by the FrameDurationLimits control + * + * \var AgcAlgorithm::FrameContext::frameDuration + * \brief The actual FrameDuration used by the algorithm for the frame + * + * \var AgcAlgorithm::FrameContext::autoExposureModeChange + * \brief Indicate if autoExposureEnabled has changed from true in the previous + * frame to false in the current frame, and no manual exposure value has been + * supplied in the current frame. + * + * \var AgcAlgorithm::FrameContext::autoGainModeChange + * \brief Indicate if autoGainEnabled has changed from true in the previous + * frame to false in the current frame, and no manual gain value has been + * supplied in the current frame. + */ + +/** + * \struct AgcAlgorithm::ConfigurationParams + * \brief Parameters for AgcAlgorithm::configure() + * + * \var AgcAlgorithm::ConfigurationParams::sensor + * \brief CameraSensorHelper for the sensor + * + * \var AgcAlgorithm::ConfigurationParams::sensorInfo + * \brief Details of the sensor + * + * \var AgcAlgorithm::ConfigurationParams::sensorControls + * \brief ControlInfoMap of the sensor + * + * \var AgcAlgorithm::ConfigurationParams::ctrlMap + * \brief ControlMap to update with controls + * + * \var AgcAlgorithm::ConfigurationParams::autoAllowed + * \brief Whether to enable auto controls + */ + +/** + * \struct AgcAlgorithm::Limits + * \brief Limits of AGC parameters + * + * This structure contains the limits to consider for the actual + * AGC implementation. + * + * \var AgcAlgorithm::Limits::exposure + * \brief Limits of exposure time + * + * \var AgcAlgorithm::Limits::gain + * \brief Limits of analogue gain + */ + +/** + * \brief Initialize the session configuration and active state + */ +int AgcAlgorithm::configure(Session &session, ActiveState &state, const ConfigurationParams &config) +{ + session = {}; + session.lineDuration = config.sensorInfo.minLineLength * 1.0s + / config.sensorInfo.pixelRate; + session.sensor.outputSize = config.sensorInfo.outputSize; + session.autoAllowed = config.autoAllowed; + + const double lineDurationUs = session.lineDuration.get(); + + /* + * Compute exposure time limits from the V4L2_CID_EXPOSURE control + * limits and the line duration. + */ + + const ControlInfo &v4l2Exposure = config.sensorControls.find(V4L2_CID_EXPOSURE)->second; + int32_t minExposure = v4l2Exposure.min().get(); + int32_t maxExposure = v4l2Exposure.max().get(); + int32_t defExposure = v4l2Exposure.def().get(); + config.ctrlMap[&controls::ExposureTime] = ControlInfo{ + static_cast(minExposure * lineDurationUs), + static_cast(maxExposure * lineDurationUs), + static_cast(defExposure * lineDurationUs), + }; + + /* Compute the analogue gain limits. */ + const auto mapGain = [&](const ControlValue &v) { + auto code = v.get(); + return config.sensor ? config.sensor->gain(code) : code; + }; + + const ControlInfo &v4l2Gain = config.sensorControls.find(V4L2_CID_ANALOGUE_GAIN)->second; + float minGain = mapGain(v4l2Gain.min()); + float maxGain = mapGain(v4l2Gain.max()); + float defGain = mapGain(v4l2Gain.def()); + config.ctrlMap[&controls::AnalogueGain] = ControlInfo{ + minGain, + maxGain, + defGain, + }; + + LOG(Agc, Debug) + << "Exposure: [" << minExposure << ", " << maxExposure + << "], gain: [" << minGain << ", " << maxGain << "]"; + + /* + * Compute the frame duration limits. + * + * The frame length is computed assuming a fixed line length combined + * with the vertical frame sizes. + */ + const ControlInfo &v4l2HBlank = config.sensorControls.find(V4L2_CID_HBLANK)->second; + uint32_t hblank = v4l2HBlank.def().get(); + uint32_t lineLength = config.sensorInfo.outputSize.width + hblank; + + const ControlInfo &v4l2VBlank = config.sensorControls.find(V4L2_CID_VBLANK)->second; + std::array frameHeights{ + v4l2VBlank.min().get() + config.sensorInfo.outputSize.height, + v4l2VBlank.max().get() + config.sensorInfo.outputSize.height, + v4l2VBlank.def().get() + config.sensorInfo.outputSize.height, + }; + + std::array frameDurations; + for (unsigned int i = 0; i < frameHeights.size(); ++i) { + uint64_t frameSize = lineLength * frameHeights[i]; + frameDurations[i] = frameSize / (config.sensorInfo.pixelRate / 1000000U); + } + + config.ctrlMap[&controls::FrameDurationLimits] = ControlInfo{ + frameDurations[0], + frameDurations[1], + Span{ { frameDurations[2], frameDurations[2] } }, + }; + + session.minFrameDuration = std::chrono::microseconds(frameDurations[0]); + session.maxFrameDuration = std::chrono::microseconds(frameDurations[1]); + + /* + * When the AGC computes the new exposure values for a frame, it needs + * to know the limits for exposure time and analogue gain. As it depends + * on the sensor, update it with the controls. + * + * \todo take VBLANK into account for maximum exposure time + */ + session.minExposureTime = minExposure * session.lineDuration; + session.maxExposureTime = maxExposure * session.lineDuration; + session.minAnalogueGain = minGain; + session.maxAnalogueGain = maxGain; + + /* Configure the default exposure and gain. */ + state = {}; + state.automatic.gain = session.minAnalogueGain; + state.automatic.exposure = 10ms / session.lineDuration; + state.manual.gain = state.automatic.gain; + state.manual.exposure = state.automatic.exposure; + state.autoExposureEnabled = session.autoAllowed; + state.autoGainEnabled = session.autoAllowed; + state.minFrameDuration = session.minFrameDuration; + state.maxFrameDuration = session.maxFrameDuration; + + const auto add = [&](const ControlId &cid, const auto &automatic, const auto &manual) { + std::array values; + size_t count = 0; + + if (session.autoAllowed) + values[count++] = ControlValue(automatic); + + values[count++] = ControlValue(manual); + + config.ctrlMap[&cid] = ControlInfo{ + { values.data(), count }, + ControlValue(session.autoAllowed ? automatic : manual), + }; + }; + + add(controls::ExposureTimeMode, controls::ExposureTimeModeAuto, controls::ExposureTimeModeManual); + add(controls::AnalogueGainMode, controls::AnalogueGainModeAuto, controls::AnalogueGainModeManual); + + /* \todo Move this to the `Camera` class. */ + config.ctrlMap[&controls::AeEnable] = ControlInfo{ + false, + session.autoAllowed, + session.autoAllowed, + }; + + return 0; +} + +/** + * \brief Handle a \a queueRequest operation + */ +void AgcAlgorithm::queueRequest(const Session &session, ActiveState &state, + FrameContext &frameContext, const ControlList &controls) +{ + if (session.autoAllowed) { + const auto &aeEnable = controls.get(controls::ExposureTimeMode); + if (aeEnable && + (*aeEnable == controls::ExposureTimeModeAuto) != state.autoExposureEnabled) { + state.autoExposureEnabled = (*aeEnable == controls::ExposureTimeModeAuto); + + LOG(Agc, Debug) + << (state.autoExposureEnabled ? "Enabling" : "Disabling") + << " AGC (exposure)"; + + /* + * If we go from auto -> manual with no manual control + * set, use the last computed value, which we don't + * know until prepare() so save this information. + * + * \todo Check the previous frame at prepare() time + * instead of saving a flag here + */ + if (!state.autoExposureEnabled && !controls.get(controls::ExposureTime)) + frameContext.autoExposureModeChange = true; + } + + const auto &agEnable = controls.get(controls::AnalogueGainMode); + if (agEnable && + (*agEnable == controls::AnalogueGainModeAuto) != state.autoGainEnabled) { + state.autoGainEnabled = (*agEnable == controls::AnalogueGainModeAuto); + + LOG(Agc, Debug) + << (state.autoGainEnabled ? "Enabling" : "Disabling") + << " AGC (gain)"; + /* + * If we go from auto -> manual with no manual control + * set, use the last computed value, which we don't + * know until prepare() so save this information. + */ + if (!state.autoGainEnabled && !controls.get(controls::AnalogueGain)) + frameContext.autoGainModeChange = true; + } + } + + const auto &exposure = controls.get(controls::ExposureTime); + if (exposure && !state.autoExposureEnabled) { + state.manual.exposure = *exposure * 1.0us / session.lineDuration; + + LOG(Agc, Debug) + << "Set exposure to " << state.manual.exposure; + } + + const auto &gain = controls.get(controls::AnalogueGain); + if (gain && !state.autoGainEnabled) { + state.manual.gain = *gain; + + LOG(Agc, Debug) << "Set gain to " << state.manual.gain; + } + + frameContext.autoExposureEnabled = state.autoExposureEnabled; + frameContext.autoGainEnabled = state.autoGainEnabled; + + if (!frameContext.autoExposureEnabled) + frameContext.exposure = state.manual.exposure; + if (!frameContext.autoGainEnabled) + frameContext.gain = state.manual.gain; + + const auto &frameDurationLimits = controls.get(controls::FrameDurationLimits); + if (frameDurationLimits) { + /* Limit the control value to the limits in ControlInfo */ + state.minFrameDuration = std::clamp( + std::chrono::microseconds((*frameDurationLimits).front()), + session.minFrameDuration, + session.maxFrameDuration + ); + + state.maxFrameDuration = std::clamp( + std::chrono::microseconds((*frameDurationLimits).back()), + session.minFrameDuration, + session.maxFrameDuration + ); + } + frameContext.minFrameDuration = state.minFrameDuration; + frameContext.maxFrameDuration = state.maxFrameDuration; +} + +/** + * \brief Handle a \a prepare operation + */ +void AgcAlgorithm::prepare(ActiveState &state, FrameContext &frameContext) +{ + uint32_t activeAutoExposure = state.automatic.exposure; + double activeAutoGain = state.automatic.gain; + + /* Populate exposure and gain in auto mode */ + if (frameContext.autoExposureEnabled) + frameContext.exposure = activeAutoExposure; + if (frameContext.autoGainEnabled) + frameContext.gain = activeAutoGain; + + /* + * Populate manual exposure and gain from the active auto values when + * transitioning from auto to manual + */ + if (!frameContext.autoExposureEnabled && frameContext.autoExposureModeChange) { + state.manual.exposure = activeAutoExposure; + frameContext.exposure = activeAutoExposure; + } + if (!frameContext.autoGainEnabled && frameContext.autoGainModeChange) { + state.manual.gain = activeAutoGain; + frameContext.gain = activeAutoGain; + } +} + +/** + * \brief Calculate the AGC limits for the given frame + */ +AgcAlgorithm::Limits AgcAlgorithm::calculateLimits(const Session &session, const FrameContext &frameContext) +{ + /* + * Set the AGC limits using the fixed exposure time and/or gain in + * manual mode, or the sensor limits in auto mode. + */ + Limits result; + + if (frameContext.autoExposureEnabled) { + result.exposure = { + session.minExposureTime, + std::clamp(frameContext.maxFrameDuration, session.minExposureTime, session.maxExposureTime), + }; + } else { + result.exposure.first = session.lineDuration * frameContext.exposure; + result.exposure.second = result.exposure.first; + } + + if (frameContext.autoGainEnabled) + result.gain = { session.minAnalogueGain, session.maxAnalogueGain }; + else + result.gain = { frameContext.gain, frameContext.gain }; + + return result; +} + +/** + * \brief Handle a \a process operation + */ +void AgcAlgorithm::process(const Session& session, FrameContext& frameContext, + utils::Duration newExposureTime, ControlList &metadata) +{ + /* + * Expand the target frame duration so that we do not run faster than + * the minimum frame duration when we have short exposures. + */ + const auto frameDuration = std::max(frameContext.minFrameDuration, newExposureTime); + frameContext.vblank = (frameDuration / session.lineDuration) - session.sensor.outputSize.height; + + /* Update frame duration accounting for line length quantization. */ + frameContext.frameDuration = (session.sensor.outputSize.height + frameContext.vblank) * session.lineDuration; + + metadata.set(controls::AnalogueGain, frameContext.gain); + metadata.set(controls::ExposureTime, utils::Duration(session.lineDuration * frameContext.exposure).get()); + metadata.set(controls::FrameDuration, frameContext.frameDuration.get()); + metadata.set(controls::ExposureTimeMode, + frameContext.autoExposureEnabled + ? controls::ExposureTimeModeAuto + : controls::ExposureTimeModeManual); + metadata.set(controls::AnalogueGainMode, + frameContext.autoGainEnabled + ? controls::AnalogueGainModeAuto + : controls::AnalogueGainModeManual); +} + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/agc.h b/src/ipa/libipa/agc.h new file mode 100644 index 0000000000..35d321a0a1 --- /dev/null +++ b/src/ipa/libipa/agc.h @@ -0,0 +1,100 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026 Ideas on Board Oy + */ + +#pragma once + + +#include + +#include +#include + +#include + +#include "camera_sensor_helper.h" + +namespace libcamera { + +namespace ipa { + +class AgcAlgorithm +{ +public: + struct Session { + utils::Duration minExposureTime; + utils::Duration maxExposureTime; + double minAnalogueGain; + double maxAnalogueGain; + utils::Duration minFrameDuration; + utils::Duration maxFrameDuration; + + utils::Duration lineDuration; + + struct { + Size outputSize; + } sensor; + + bool autoAllowed; + }; + + struct ActiveState { + struct { + uint32_t exposure; + double gain; + } manual; + struct { + uint32_t exposure; + double gain; + } automatic; + + bool autoExposureEnabled; + bool autoGainEnabled; + utils::Duration minFrameDuration; + utils::Duration maxFrameDuration; + }; + + struct FrameContext { + uint32_t exposure; + double gain; + uint32_t vblank; + bool autoExposureEnabled; + bool autoGainEnabled; + utils::Duration minFrameDuration; + utils::Duration maxFrameDuration; + utils::Duration frameDuration; + bool autoExposureModeChange; + bool autoGainModeChange; + }; + + struct ConfigurationParams { + const CameraSensorHelper *sensor; + const IPACameraSensorInfo &sensorInfo; + const ControlInfoMap &sensorControls; + ControlInfoMap::Map &ctrlMap; + bool autoAllowed = true; + }; + +protected: + int configure(Session &session, ActiveState &state, const ConfigurationParams &config); + + void queueRequest(const Session &session, ActiveState &state, + FrameContext &frameContext, const ControlList &controls); + + void prepare(ActiveState &state, FrameContext &frameContext); + + struct Limits { + std::pair exposure; + std::pair gain; + }; + + [[nodiscard]] Limits calculateLimits(const Session &session, const FrameContext &frameContext); + + void process(const Session& session, FrameContext& frameContext, + utils::Duration newExposureTime, ControlList &metadata); +}; + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/agc_mean_luminance.cpp b/src/ipa/libipa/agc_mean_luminance.cpp index 951a4b0e02..0755c7ccac 100644 --- a/src/ipa/libipa/agc_mean_luminance.cpp +++ b/src/ipa/libipa/agc_mean_luminance.cpp @@ -8,11 +8,8 @@ #include "agc_mean_luminance.h" #include -#include #include -#include - #include #include @@ -748,80 +745,16 @@ AgcMeanLuminance::calculateNewEv(uint32_t constraintModeIndex, * \todo DigitalGain, DigitalGainMode */ -/** - * \struct AgcMeanLuminanceAlgorithm::Session - * \brief Session configuration for AgcMeanLuminanceAlgorithm - * - * \var AgcMeanLuminanceAlgorithm::Session::minExposureTime - * \brief Minimum exposure time supported with the configured sensor - * - * \var AgcMeanLuminanceAlgorithm::Session::maxExposureTime - * \brief Maximum exposure time supported with the configured sensor - * - * \var AgcMeanLuminanceAlgorithm::Session::minAnalogueGain - * \brief Minimum analogue gain supported with the configured sensor - * - * \var AgcMeanLuminanceAlgorithm::Session::maxAnalogueGain - * \brief Maximum analogue gain supported with the configured sensor - * - * \var AgcMeanLuminanceAlgorithm::Session::minFrameDuration - * \brief Minimum frame duration supported with the configured sensor - * - * \var AgcMeanLuminanceAlgorithm::Session::maxFrameDuration - * \brief Maximum frame duration supported with the configured sensor - * - * \var AgcMeanLuminanceAlgorithm::Session::lineDuration - * \brief Line duration with the configured sensor and output size - * - * \var AgcMeanLuminanceAlgorithm::Session::sensor - * \brief Details of the sensor configuration - * - * \var AgcMeanLuminanceAlgorithm::Session::sensor.outputSize - * \brief Configured output size of the sensor - * - * \var AgcMeanLuminanceAlgorithm::Session::autoAllowed - * \brief Whether automatic controls are allowed - */ - /** * \struct AgcMeanLuminanceAlgorithm::ActiveState * \brief Active state for AgcMeanLuminanceAlgorithm * - * The \a automatic variables track the latest values computed by algorithm - * based on the latest processed statistics. All other variables track the - * consolidated controls requested in queued requests. - * - * \var AgcMeanLuminanceAlgorithm::ActiveState::manual - * \brief Manual exposure time and analog gain (set through requests) - * - * \var AgcMeanLuminanceAlgorithm::ActiveState::manual.exposure - * \brief Manual exposure time expressed as a number of lines as set by the - * ExposureTime control - * - * \var AgcMeanLuminanceAlgorithm::ActiveState::manual.gain - * \brief Manual analogue gain as set by the AnalogueGain control - * - * \var AgcMeanLuminanceAlgorithm::ActiveState::automatic - * \brief Automatic exposure time and analog gain (computed by the algorithm) - * - * \var AgcMeanLuminanceAlgorithm::ActiveState::automatic.exposure - * \brief Automatic exposure time expressed as a number of lines - * - * \var AgcMeanLuminanceAlgorithm::ActiveState::automatic.gain - * \brief Automatic analogue gain multiplier - * - * \var AgcMeanLuminanceAlgorithm::ActiveState::automatic.quantizationGain + * \var AgcMeanLuminanceAlgorithm::ActiveState::quantizationGain * \brief Automatic quantization gain multiplier * - * \var AgcMeanLuminanceAlgorithm::ActiveState::automatic.yTarget + * \var AgcMeanLuminanceAlgorithm::ActiveState::yTarget * \brief Automatically determined luminance target * - * \var AgcMeanLuminanceAlgorithm::ActiveState::autoExposureEnabled - * \brief Manual/automatic AGC state (exposure) as set by the ExposureTimeMode control - * - * \var AgcMeanLuminanceAlgorithm::ActiveState::autoGainEnabled - * \brief Manual/automatic AGC state (gain) as set by the AnalogueGainMode control - * * \var AgcMeanLuminanceAlgorithm::ActiveState::exposureValue * \brief Exposure value as set by the ExposureValue control * @@ -830,26 +763,12 @@ AgcMeanLuminance::calculateNewEv(uint32_t constraintModeIndex, * * \var AgcMeanLuminanceAlgorithm::ActiveState::exposureMode * \brief Exposure mode as set by the AeExposureMode control - * - * \var AgcMeanLuminanceAlgorithm::ActiveState::minFrameDuration - * \brief Minimum frame duration as set by the FrameDurationLimits control - * - * \var AgcMeanLuminanceAlgorithm::ActiveState::maxFrameDuration - * \brief Maximum frame duration as set by the FrameDurationLimits control */ /** * \struct AgcMeanLuminanceAlgorithm::FrameContext * \brief Per-frame context for AgcMeanLuminanceAlgorithm * - * \var AgcMeanLuminanceAlgorithm::FrameContext::exposure - * \brief Exposure time expressed as a number of lines computed by the algorithm - * - * \var AgcMeanLuminanceAlgorithm::FrameContext::gain - * \brief Analogue gain multiplier computed by the algorithm - * - * The gain should be adapted to the sensor specific gain code before applying. - * * \var AgcMeanLuminanceAlgorithm::FrameContext::quantizationGain * \brief Quantization gain multiplier computed by the algorithm * @@ -859,59 +778,11 @@ AgcMeanLuminance::calculateNewEv(uint32_t constraintModeIndex, * \var AgcMeanLuminanceAlgorithm::FrameContext::yTarget * \brief Luminance target computed by the algorithm * - * \var AgcMeanLuminanceAlgorithm::FrameContext::vblank - * \brief Vertical blanking parameter computed by the algorithm - * - * \var AgcMeanLuminanceAlgorithm::FrameContext::autoExposureEnabled - * \brief Manual/automatic AGC state (exposure) as set by the ExposureTimeMode control - * - * \var AgcMeanLuminanceAlgorithm::FrameContext::autoGainEnabled - * \brief Manual/automatic AGC state (gain) as set by the AnalogueGainMode control - * * \var AgcMeanLuminanceAlgorithm::FrameContext::constraintMode * \brief Constraint mode as set by the AeConstraintMode control * * \var AgcMeanLuminanceAlgorithm::FrameContext::exposureMode * \brief Exposure mode as set by the AeExposureMode control - * - * \var AgcMeanLuminanceAlgorithm::FrameContext::minFrameDuration - * \brief Minimum frame duration as set by the FrameDurationLimits control - * - * \var AgcMeanLuminanceAlgorithm::FrameContext::maxFrameDuration - * \brief Maximum frame duration as set by the FrameDurationLimits control - * - * \var AgcMeanLuminanceAlgorithm::FrameContext::frameDuration - * \brief The actual FrameDuration used by the algorithm for the frame - * - * \var AgcMeanLuminanceAlgorithm::FrameContext::autoExposureModeChange - * \brief Indicate if autoExposureEnabled has changed from true in the previous - * frame to false in the current frame, and no manual exposure value has been - * supplied in the current frame. - * - * \var AgcMeanLuminanceAlgorithm::FrameContext::autoGainModeChange - * \brief Indicate if autoGainEnabled has changed from true in the previous - * frame to false in the current frame, and no manual gain value has been - * supplied in the current frame. - */ - -/** - * \struct AgcMeanLuminanceAlgorithm::ConfigurationParams - * \brief Parameters for AgcMeanLuminanceAlgorithm::configure() - * - * \var AgcMeanLuminanceAlgorithm::ConfigurationParams::sensor - * \brief CameraSensorHelper for the sensor - * - * \var AgcMeanLuminanceAlgorithm::ConfigurationParams::sensorInfo - * \brief Details of the sensor - * - * \var AgcMeanLuminanceAlgorithm::ConfigurationParams::sensorControls - * \brief ControlInfoMap of the sensor - * - * \var AgcMeanLuminanceAlgorithm::ConfigurationParams::ctrlMap - * \brief ControlMap to update with controls - * - * \var AgcMeanLuminanceAlgorithm::ConfigurationParams::autoAllowed - * \brief Whether to enable auto controls */ /** @@ -954,104 +825,18 @@ int AgcMeanLuminanceAlgorithm::init(const ValueNode &tuningData) */ int AgcMeanLuminanceAlgorithm::configure(Session &session, ActiveState &state, const ConfigurationParams &config) { - session = {}; - session.lineDuration = config.sensorInfo.minLineLength * 1.0s - / config.sensorInfo.pixelRate; - session.sensor.outputSize = config.sensorInfo.outputSize; - session.autoAllowed = config.autoAllowed; - - const double lineDurationUs = session.lineDuration.get(); - - /* - * Compute exposure time limits from the V4L2_CID_EXPOSURE control - * limits and the line duration. - */ - - const ControlInfo &v4l2Exposure = config.sensorControls.find(V4L2_CID_EXPOSURE)->second; - int32_t minExposure = v4l2Exposure.min().get(); - int32_t maxExposure = v4l2Exposure.max().get(); - int32_t defExposure = v4l2Exposure.def().get(); - config.ctrlMap[&controls::ExposureTime] = ControlInfo{ - static_cast(minExposure * lineDurationUs), - static_cast(maxExposure * lineDurationUs), - static_cast(defExposure * lineDurationUs), - }; - - /* Compute the analogue gain limits. */ - const ControlInfo &v4l2Gain = config.sensorControls.find(V4L2_CID_ANALOGUE_GAIN)->second; - float minGain = config.sensor.gain(v4l2Gain.min().get()); - float maxGain = config.sensor.gain(v4l2Gain.max().get()); - float defGain = config.sensor.gain(v4l2Gain.def().get()); - config.ctrlMap[&controls::AnalogueGain] = ControlInfo{ - minGain, - maxGain, - defGain, - }; - - LOG(AgcMeanLuminance, Debug) - << "Exposure: [" << minExposure << ", " << maxExposure - << "], gain: [" << minGain << ", " << maxGain << "]"; - - /* - * Compute the frame duration limits. - * - * The frame length is computed assuming a fixed line length combined - * with the vertical frame sizes. - */ - const ControlInfo &v4l2HBlank = config.sensorControls.find(V4L2_CID_HBLANK)->second; - uint32_t hblank = v4l2HBlank.def().get(); - uint32_t lineLength = config.sensorInfo.outputSize.width + hblank; - - const ControlInfo &v4l2VBlank = config.sensorControls.find(V4L2_CID_VBLANK)->second; - std::array frameHeights{ - v4l2VBlank.min().get() + config.sensorInfo.outputSize.height, - v4l2VBlank.max().get() + config.sensorInfo.outputSize.height, - v4l2VBlank.def().get() + config.sensorInfo.outputSize.height, - }; - - std::array frameDurations; - for (unsigned int i = 0; i < frameHeights.size(); ++i) { - uint64_t frameSize = lineLength * frameHeights[i]; - frameDurations[i] = frameSize / (config.sensorInfo.pixelRate / 1000000U); - } - - config.ctrlMap[&controls::FrameDurationLimits] = ControlInfo{ - frameDurations[0], - frameDurations[1], - Span{ { frameDurations[2], frameDurations[2] } }, - }; - - session.minFrameDuration = std::chrono::microseconds(frameDurations[0]); - session.maxFrameDuration = std::chrono::microseconds(frameDurations[1]); - - /* - * When the AGC computes the new exposure values for a frame, it needs - * to know the limits for exposure time and analogue gain. As it depends - * on the sensor, update it with the controls. - * - * \todo take VBLANK into account for maximum exposure time - */ - session.minExposureTime = minExposure * session.lineDuration; - session.maxExposureTime = maxExposure * session.lineDuration; - session.minAnalogueGain = minGain; - session.maxAnalogueGain = maxGain; + int ret = AgcAlgorithm::configure(session, state, config); + if (ret) + return ret; - impl_.configure(session.lineDuration, &config.sensor); + impl_.configure(session.lineDuration, config.sensor); impl_.setLimits(session.minExposureTime, session.maxExposureTime, session.minAnalogueGain, session.maxAnalogueGain, {}); impl_.resetFrameCount(); - /* Configure the default exposure and gain. */ - state = {}; - state.automatic.gain = session.minAnalogueGain; - state.automatic.exposure = 10ms / session.lineDuration; - state.automatic.quantizationGain = 1; - state.automatic.yTarget = impl_.effectiveYTarget(); - state.manual.gain = state.automatic.gain; - state.manual.exposure = state.automatic.exposure; - state.autoExposureEnabled = session.autoAllowed; - state.autoGainEnabled = session.autoAllowed; + state.quantizationGain = 1; + state.yTarget = impl_.effectiveYTarget(); state.exposureValue = 0; state.constraintMode = @@ -1059,34 +844,6 @@ int AgcMeanLuminanceAlgorithm::configure(Session &session, ActiveState &state, c state.exposureMode = static_cast(impl_.exposureModeHelpers().begin()->first); - state.minFrameDuration = session.minFrameDuration; - state.maxFrameDuration = session.maxFrameDuration; - - const auto add = [&](const ControlId &cid, const auto &automatic, const auto &manual) { - std::array values; - size_t count = 0; - - if (session.autoAllowed) - values[count++] = ControlValue(automatic); - - values[count++] = ControlValue(manual); - - config.ctrlMap[&cid] = ControlInfo{ - { values.data(), count }, - ControlValue(session.autoAllowed ? automatic : manual), - }; - }; - - add(controls::ExposureTimeMode, controls::ExposureTimeModeAuto, controls::ExposureTimeModeManual); - add(controls::AnalogueGainMode, controls::AnalogueGainModeAuto, controls::AnalogueGainModeManual); - - /* \todo Move this to the `Camera` class. */ - config.ctrlMap[&controls::AeEnable] = ControlInfo{ - false, - session.autoAllowed, - session.autoAllowed, - }; - // \todo Should these be added/removed based on `session.autoAllowed` ? config.ctrlMap[&controls::ExposureValue] = ControlInfo(-8.0f, 8.0f, 0.0f); @@ -1100,70 +857,9 @@ int AgcMeanLuminanceAlgorithm::configure(Session &session, ActiveState &state, c * \brief Handle a \a queueRequest operation */ void AgcMeanLuminanceAlgorithm::queueRequest(const Session &session, ActiveState &state, - FrameContext &frameContext, const ControlList &controls) + FrameContext &frameContext, const ControlList &controls) { - if (session.autoAllowed) { - const auto &aeEnable = controls.get(controls::ExposureTimeMode); - if (aeEnable && - (*aeEnable == controls::ExposureTimeModeAuto) != state.autoExposureEnabled) { - state.autoExposureEnabled = (*aeEnable == controls::ExposureTimeModeAuto); - - LOG(AgcMeanLuminance, Debug) - << (state.autoExposureEnabled ? "Enabling" : "Disabling") - << " AGC (exposure)"; - - /* - * If we go from auto -> manual with no manual control - * set, use the last computed value, which we don't - * know until prepare() so save this information. - * - * \todo Check the previous frame at prepare() time - * instead of saving a flag here - */ - if (!state.autoExposureEnabled && !controls.get(controls::ExposureTime)) - frameContext.autoExposureModeChange = true; - } - - const auto &agEnable = controls.get(controls::AnalogueGainMode); - if (agEnable && - (*agEnable == controls::AnalogueGainModeAuto) != state.autoGainEnabled) { - state.autoGainEnabled = (*agEnable == controls::AnalogueGainModeAuto); - - LOG(AgcMeanLuminance, Debug) - << (state.autoGainEnabled ? "Enabling" : "Disabling") - << " AGC (gain)"; - /* - * If we go from auto -> manual with no manual control - * set, use the last computed value, which we don't - * know until prepare() so save this information. - */ - if (!state.autoGainEnabled && !controls.get(controls::AnalogueGain)) - frameContext.autoGainModeChange = true; - } - } - - const auto &exposure = controls.get(controls::ExposureTime); - if (exposure && !state.autoExposureEnabled) { - state.manual.exposure = *exposure * 1.0us / session.lineDuration; - - LOG(AgcMeanLuminance, Debug) - << "Set exposure to " << state.manual.exposure; - } - - const auto &gain = controls.get(controls::AnalogueGain); - if (gain && !state.autoGainEnabled) { - state.manual.gain = *gain; - - LOG(AgcMeanLuminance, Debug) << "Set gain to " << state.manual.gain; - } - - frameContext.autoExposureEnabled = state.autoExposureEnabled; - frameContext.autoGainEnabled = state.autoGainEnabled; - - if (!frameContext.autoExposureEnabled) - frameContext.exposure = state.manual.exposure; - if (!frameContext.autoGainEnabled) - frameContext.gain = state.manual.gain; + AgcAlgorithm::queueRequest(session, state, frameContext, controls); if (!frameContext.autoExposureEnabled && !frameContext.autoGainEnabled) @@ -1185,24 +881,6 @@ void AgcMeanLuminanceAlgorithm::queueRequest(const Session &session, ActiveState if (exposureValue) state.exposureValue = *exposureValue; frameContext.exposureValue = state.exposureValue; - - const auto &frameDurationLimits = controls.get(controls::FrameDurationLimits); - if (frameDurationLimits) { - /* Limit the control value to the limits in ControlInfo */ - state.minFrameDuration = std::clamp( - std::chrono::microseconds((*frameDurationLimits).front()), - session.minFrameDuration, - session.maxFrameDuration - ); - - state.maxFrameDuration = std::clamp( - std::chrono::microseconds((*frameDurationLimits).back()), - session.minFrameDuration, - session.maxFrameDuration - ); - } - frameContext.minFrameDuration = state.minFrameDuration; - frameContext.maxFrameDuration = state.maxFrameDuration; } /** @@ -1210,35 +888,17 @@ void AgcMeanLuminanceAlgorithm::queueRequest(const Session &session, ActiveState */ void AgcMeanLuminanceAlgorithm::prepare(ActiveState &state, FrameContext &frameContext) { - uint32_t activeAutoExposure = state.automatic.exposure; - double activeAutoGain = state.automatic.gain; - double activeAutoQGain = state.automatic.quantizationGain; + AgcAlgorithm::prepare(state, frameContext); - /* Populate exposure and gain in auto mode */ - if (frameContext.autoExposureEnabled) { - frameContext.exposure = activeAutoExposure; - frameContext.quantizationGain = activeAutoQGain; - } - if (frameContext.autoGainEnabled) { - frameContext.gain = activeAutoGain; - frameContext.quantizationGain = activeAutoQGain; - } + double activeAutoQGain = state.quantizationGain; - /* - * Populate manual exposure and gain from the active auto values when - * transitioning from auto to manual - */ - if (!frameContext.autoExposureEnabled && frameContext.autoExposureModeChange) { - state.manual.exposure = activeAutoExposure; - frameContext.exposure = activeAutoExposure; - } - if (!frameContext.autoGainEnabled && frameContext.autoGainModeChange) { - state.manual.gain = activeAutoGain; - frameContext.gain = activeAutoGain; + if (frameContext.autoExposureEnabled || frameContext.autoGainEnabled) frameContext.quantizationGain = activeAutoQGain; - } - frameContext.yTarget = state.automatic.yTarget; + if (!frameContext.autoGainEnabled && frameContext.autoGainModeChange) + frameContext.quantizationGain = activeAutoQGain; // \todo WHAT?! + + frameContext.yTarget = state.yTarget; } /** @@ -1254,30 +914,7 @@ void AgcMeanLuminanceAlgorithm::process(const Session &session, ActiveState &sta if (params) { ASSERT(session.autoAllowed); - /* - * Set the AGC limits using the fixed exposure time and/or gain in - * manual mode, or the sensor limits in auto mode. - */ - utils::Duration minExposureTime; - utils::Duration maxExposureTime; - double minAnalogueGain; - double maxAnalogueGain; - - if (frameContext.autoExposureEnabled) { - minExposureTime = session.minExposureTime; - maxExposureTime = std::clamp(frameContext.maxFrameDuration, session.minExposureTime, session.maxExposureTime); - } else { - minExposureTime = lineDuration * frameContext.exposure; - maxExposureTime = minExposureTime; - } - - if (frameContext.autoGainEnabled) { - minAnalogueGain = session.minAnalogueGain; - maxAnalogueGain = session.maxAnalogueGain; - } else { - minAnalogueGain = frameContext.gain; - maxAnalogueGain = frameContext.gain; - } + auto limits = AgcAlgorithm::calculateLimits(session, frameContext); /* * The Agc algorithm needs to know the effective exposure value that was @@ -1286,8 +923,8 @@ void AgcMeanLuminanceAlgorithm::process(const Session &session, ActiveState &sta utils::Duration effectiveExposureValue = lineDuration * params->exposure * params->gain; - impl_.setLimits(minExposureTime, maxExposureTime, - minAnalogueGain, maxAnalogueGain, + impl_.setLimits(limits.exposure.first, limits.exposure.second, + limits.gain.first, limits.gain.second, std::move(params->additionalConstraints)); impl_.setExposureCompensation(pow(2.0, frameContext.exposureValue)); @@ -1306,31 +943,11 @@ void AgcMeanLuminanceAlgorithm::process(const Session &session, ActiveState &sta /* Update the estimated exposure and gain. */ state.automatic.exposure = newExposureTime / lineDuration; state.automatic.gain = aGain; - state.automatic.quantizationGain = qGain; - state.automatic.yTarget = impl_.effectiveYTarget(); + state.quantizationGain = qGain; + state.yTarget = impl_.effectiveYTarget(); } - /* - * Expand the target frame duration so that we do not run faster than - * the minimum frame duration when we have short exposures. - */ - const auto frameDuration = std::max(frameContext.minFrameDuration, newExposureTime); - frameContext.vblank = (frameDuration / lineDuration) - session.sensor.outputSize.height; - - /* Update frame duration accounting for line length quantization. */ - frameContext.frameDuration = (session.sensor.outputSize.height + frameContext.vblank) * lineDuration; - - metadata.set(controls::AnalogueGain, frameContext.gain); - metadata.set(controls::ExposureTime, utils::Duration(lineDuration * frameContext.exposure).get()); - metadata.set(controls::FrameDuration, frameContext.frameDuration.get()); - metadata.set(controls::ExposureTimeMode, - frameContext.autoExposureEnabled - ? controls::ExposureTimeModeAuto - : controls::ExposureTimeModeManual); - metadata.set(controls::AnalogueGainMode, - frameContext.autoGainEnabled - ? controls::AnalogueGainModeAuto - : controls::AnalogueGainModeManual); + AgcAlgorithm::process(session, frameContext, newExposureTime, metadata); metadata.set(controls::AeExposureMode, frameContext.exposureMode); metadata.set(controls::AeConstraintMode, frameContext.constraintMode); diff --git a/src/ipa/libipa/agc_mean_luminance.h b/src/ipa/libipa/agc_mean_luminance.h index e7e2d8ad0d..ceae33ba3e 100644 --- a/src/ipa/libipa/agc_mean_luminance.h +++ b/src/ipa/libipa/agc_mean_luminance.h @@ -22,6 +22,7 @@ #include "libcamera/internal/value_node.h" +#include "agc.h" #include "exposure_mode_helper.h" #include "histogram.h" #include "pwl.h" @@ -119,71 +120,23 @@ private: ControlInfoMap::Map controls_; }; -class AgcMeanLuminanceAlgorithm +class AgcMeanLuminanceAlgorithm : public AgcAlgorithm { public: - struct Session { - utils::Duration minExposureTime; - utils::Duration maxExposureTime; - double minAnalogueGain; - double maxAnalogueGain; - utils::Duration minFrameDuration; - utils::Duration maxFrameDuration; - - utils::Duration lineDuration; - - struct { - Size outputSize; - } sensor; - - bool autoAllowed; - }; - - struct ActiveState { - struct { - uint32_t exposure; - double gain; - } manual; - struct { - uint32_t exposure; - double gain; - double quantizationGain; - double yTarget; - } automatic; - - bool autoExposureEnabled; - bool autoGainEnabled; + struct ActiveState : AgcAlgorithm::ActiveState { + double quantizationGain; + double yTarget; double exposureValue; controls::AeConstraintModeEnum constraintMode; controls::AeExposureModeEnum exposureMode; - utils::Duration minFrameDuration; - utils::Duration maxFrameDuration; }; - struct FrameContext { - uint32_t exposure; - double gain; + struct FrameContext : AgcAlgorithm::FrameContext { double quantizationGain; - double exposureValue; double yTarget; - uint32_t vblank; - bool autoExposureEnabled; - bool autoGainEnabled; + double exposureValue; controls::AeConstraintModeEnum constraintMode; controls::AeExposureModeEnum exposureMode; - utils::Duration minFrameDuration; - utils::Duration maxFrameDuration; - utils::Duration frameDuration; - bool autoExposureModeChange; - bool autoGainModeChange; - }; - - struct ConfigurationParams { - const CameraSensorHelper &sensor; - const IPACameraSensorInfo &sensorInfo; - const ControlInfoMap &sensorControls; - ControlInfoMap::Map &ctrlMap; - bool autoAllowed = true; }; int init(const ValueNode &tuningData); diff --git a/src/ipa/libipa/meson.build b/src/ipa/libipa/meson.build index 963c5ee730..47c027f707 100644 --- a/src/ipa/libipa/meson.build +++ b/src/ipa/libipa/meson.build @@ -2,6 +2,7 @@ libipa_headers = files([ 'agc_mean_luminance.h', + 'agc.h', 'algorithm.h', 'awb_bayes.h', 'awb_grey.h', @@ -23,6 +24,7 @@ libipa_headers = files([ libipa_sources = files([ 'agc_mean_luminance.cpp', + 'agc.cpp', 'algorithm.cpp', 'awb_bayes.cpp', 'awb_grey.cpp', diff --git a/src/ipa/mali-c55/algorithms/agc.cpp b/src/ipa/mali-c55/algorithms/agc.cpp index 386541fd58..4c6c2b5acb 100644 --- a/src/ipa/mali-c55/algorithms/agc.cpp +++ b/src/ipa/mali-c55/algorithms/agc.cpp @@ -128,7 +128,7 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData) return ret; ret = agc_.configure(context.configuration.agc, context.activeState.agc, { - .sensor = *context.camHelper, + .sensor = context.camHelper.get(), .sensorInfo = context.sensorInfo, .sensorControls = context.sensorControls, .ctrlMap = context.ctrlMap, @@ -148,7 +148,7 @@ int Agc::configure(IPAContext &context, return ret; ret = agc_.configure(context.configuration.agc, context.activeState.agc, { - .sensor = *context.camHelper, + .sensor = context.camHelper.get(), .sensorInfo = context.sensorInfo, .sensorControls = context.sensorControls, .ctrlMap = context.ctrlMap, diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp index 0d01ec1bda..ceef60c029 100644 --- a/src/ipa/rkisp1/algorithms/agc.cpp +++ b/src/ipa/rkisp1/algorithms/agc.cpp @@ -141,7 +141,7 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData) return ret; ret = agc_.configure(context.configuration.agc, context.activeState.agc, { - .sensor = *context.camHelper, + .sensor = context.camHelper.get(), .sensorInfo = context.sensorInfo, .sensorControls = context.sensorControls, .ctrlMap = context.ctrlMap, @@ -167,7 +167,7 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData) int Agc::configure(IPAContext &context, const IPACameraSensorInfo &configInfo) { int ret = agc_.configure(context.configuration.agc, context.activeState.agc, { - .sensor = *context.camHelper, + .sensor = context.camHelper.get(), .sensorInfo = context.sensorInfo, .sensorControls = context.sensorControls, .ctrlMap = context.ctrlMap, diff --git a/src/ipa/rkisp1/algorithms/wdr.cpp b/src/ipa/rkisp1/algorithms/wdr.cpp index c3d73da2c5..b16aa7dd3b 100644 --- a/src/ipa/rkisp1/algorithms/wdr.cpp +++ b/src/ipa/rkisp1/algorithms/wdr.cpp @@ -464,7 +464,7 @@ void WideDynamicRange::process(IPAContext &context, [[maybe_unused]] const uint3 /* Calculate the gain needed to reach the requested yTarget. */ double value = cumHist.interQuantileMean(0, 1.0) / cumHist.bins(); - double gain = context.activeState.agc.automatic.yTarget / value; + double gain = context.activeState.agc.yTarget / value; gain = std::max(gain, 1.0); double speed = 0.2; From patchwork Fri Jul 3 15:38:18 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 27194 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 C1C17C3303 for ; Fri, 3 Jul 2026 15:38:45 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 1583D66019; Fri, 3 Jul 2026 17:38:43 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="BHLtqs7Y"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 68EB965FED for ; Fri, 3 Jul 2026 17:38:26 +0200 (CEST) Received: from pb-laptop.local (185.221.140.128.nat.pool.zt.hu [185.221.140.128]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 50F671121 for ; Fri, 3 Jul 2026 17:37:40 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783093060; bh=r4esyH1S/bN/Om3T3n+FhiOHUVvyWbjje7vOg/227eg=; h=From:To:Subject:Date:In-Reply-To:References:From; b=BHLtqs7Y7rBPHSBg1+RfzwQxfTXTnWs5NAdzsyKPy8bmqCxgzhVqlZxZIjnjqiTZW vIs7hT5bzW0Tn9ErR0LYJjujOY7HYugxL7ZjrZrs7nLHvszVXDgZJLRyWkc8Rw2KBw pX0C9IgUP3+5Ynm/6GpOY6AsSMu/iLaTII6flg0Q= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v1 16/17] ipa: simple: agc: Port to `AgcAlgorithm` Date: Fri, 3 Jul 2026 17:38:18 +0200 Message-ID: <20260703153819.1088752-17-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260703153819.1088752-1-barnabas.pocze@ideasonboard.com> References: <20260703153819.1088752-1-barnabas.pocze@ideasonboard.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" Move the agc algorithm into a separate class and use `AgcAlgorithm`. Signed-off-by: Barnabás Pőcze --- src/ipa/simple/agc_simple.cpp | 197 +++++++++++++++++++++++++++++ src/ipa/simple/agc_simple.h | 59 +++++++++ src/ipa/simple/algorithms/agc.cpp | 200 ++++++------------------------ src/ipa/simple/algorithms/agc.h | 9 +- src/ipa/simple/ipa_context.h | 16 +-- src/ipa/simple/meson.build | 1 + src/ipa/simple/soft_simple.cpp | 61 ++------- 7 files changed, 322 insertions(+), 221 deletions(-) create mode 100644 src/ipa/simple/agc_simple.cpp create mode 100644 src/ipa/simple/agc_simple.h diff --git a/src/ipa/simple/agc_simple.cpp b/src/ipa/simple/agc_simple.cpp new file mode 100644 index 0000000000..0734600105 --- /dev/null +++ b/src/ipa/simple/agc_simple.cpp @@ -0,0 +1,197 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Red Hat Inc. + * + * Exposure and gain + */ + +#include "agc_simple.h" + +#include +#include + +#include + +#include + +namespace libcamera { + +namespace ipa::soft { + +LOG_DEFINE_CATEGORY(IPASoftAgcSimple) + + +/* + * The number of bins to use for the optimal exposure calculations. + */ +static constexpr unsigned int kExposureBinsCount = 5; + +/* + * The exposure is optimal when the mean sample value of the histogram is + * in the middle of the range. + */ +static constexpr float kExposureOptimal = kExposureBinsCount / 2.0; + +/* + * This implements the hysteresis for the exposure adjustment. + * It is small enough to have the exposure close to the optimal, and is big + * enough to prevent the exposure from wobbling around the optimal value. + */ +static constexpr float kExposureSatisfactory = 0.2; + +/* + * Proportional gain for exposure/gain adjustment. Maps the MSV error to a + * multiplicative correction factor: + * + * factor = 1.0 + kExpProportionalGain * error + * + * With kExpProportionalGain = 0.04: + * - max error ~2.5 -> factor 1.10 (~10% step, same as before) + * - error 1.0 -> factor 1.04 (~4% step) + * - error 0.3 -> factor 1.012 (~1.2% step) + * + * This replaces the fixed 10% bang-bang step with a proportional correction + * that converges smoothly and avoids overshooting near the target. + */ +static constexpr float kExpProportionalGain = 0.04; + +/* + * Maximum multiplicative step per frame, to bound the correction when the + * scene changes dramatically. + */ +static constexpr float kExpMaxStep = 0.15; + +void AgcSimpleAlgorithm::updateExposure(const Session &session, ActiveState &state, FrameContext &frameContext, + const ProcessParams ¶ms, double exposureMSV) +{ + double error = kExposureOptimal - exposureMSV; + if (std::abs(error) <= kExposureSatisfactory) + return; + + utils::Duration exposureDuration = params.exposure * session.lineDuration; + int32_t exposure = params.exposure; + double again = params.gain; + + /* + * Compute a proportional correction factor. The sign of the error + * determines the direction: positive error means too dark (increase), + * negative means too bright (decrease). + */ + float step = std::clamp(static_cast(error) * kExpProportionalGain, + -kExpMaxStep, kExpMaxStep); + float factor = 1.0f + step; + + const auto limits = AgcAlgorithm::calculateLimits(session, frameContext); + + if (factor > 1.0f) { + /* Scene too dark: increase exposure first, then gain. */ + if (exposureDuration < limits.exposure.second) { + int32_t next = static_cast(exposure * factor); + exposure = std::max(next, exposure + 1); + } else { + double next = again * factor; + if (next - again < session.gainMinStep) + again += session.gainMinStep; + else + again = next; + } + } else { + /* Scene too bright: decrease gain first, then exposure. */ + if (again > std::max(session.gain10, limits.gain.first)) { + double next = again * factor; + if (again - next < session.gainMinStep) + again -= session.gainMinStep; + else + again = next; + } else { + int32_t next = static_cast(exposure * factor); + exposure = std::min(next, exposure - 1); + } + } + + exposureDuration = std::clamp( + exposure * session.lineDuration, + limits.exposure.first, limits.exposure.second); + exposure = exposureDuration / session.lineDuration; + again = std::clamp(again, limits.gain.first, limits.gain.second); + + state.automatic.exposure = exposure; + state.automatic.gain = again; + + LOG(IPASoftAgcSimple, Debug) + << "exposureMSV " << exposureMSV + << " error " << error << " factor " << factor + << " exp " << exposure << " again " << again; +} + +int AgcSimpleAlgorithm::configure(Session &session, ActiveState &state, const ConfigurationParams &config) +{ + int ret = AgcAlgorithm::configure(session, state, config); + if (ret) + return ret; + + const ControlInfo &v4l2Gain = config.sensorControls.find(V4L2_CID_ANALOGUE_GAIN)->second; + auto defGain = v4l2Gain.def().get(); + + if (config.sensor) { + session.gain10 = std::max(session.minAnalogueGain, 1.0); + session.gainMinStep = (session.maxAnalogueGain - session.minAnalogueGain) / 100.0; + } else { + session.gain10 = defGain; + session.gainMinStep = 1.0; + } + + return 0; +} + +void AgcSimpleAlgorithm::process(const Session &session, ActiveState &state, + FrameContext &frameContext, std::optional &¶ms, + ControlList &metadata) +{ + utils::Duration newExposureTime = {}; + + if (params) { + /* + * Calculate Mean Sample Value (MSV) according to formula from: + * https://www.araa.asn.au/acra/acra2007/papers/paper84final.pdf + */ + const auto &histogram = params->stats.yHistogram; + const unsigned int blackLevelHistIdx = params->blackLevel / (256 / SwIspStats::kYHistogramSize); + const unsigned int histogramSize = + SwIspStats::kYHistogramSize - blackLevelHistIdx; + const unsigned int yHistValsPerBin = histogramSize / kExposureBinsCount; + const unsigned int yHistValsPerBinMod = + histogramSize / (histogramSize % kExposureBinsCount + 1); + int exposureBins[kExposureBinsCount] = {}; + unsigned int denom = 0; + unsigned int num = 0; + + if (yHistValsPerBin == 0) { + LOG(IPASoftAgcSimple, Debug) + << "Not adjusting exposure due to insufficient histogram data"; + return; + } + + for (unsigned int i = 0; i < histogramSize; i++) { + unsigned int idx = (i - (i / yHistValsPerBinMod)) / yHistValsPerBin; + exposureBins[idx] += histogram[blackLevelHistIdx + i]; + } + + for (unsigned int i = 0; i < kExposureBinsCount; i++) { + LOG(IPASoftAgcSimple, Debug) << i << ": " << exposureBins[i]; + denom += exposureBins[i]; + num += exposureBins[i] * (i + 1); + } + + float exposureMSV = (denom == 0 ? 0 : static_cast(num) / denom); + updateExposure(session, state, frameContext, *params, exposureMSV); + newExposureTime = state.automatic.exposure * session.lineDuration; + } + + AgcAlgorithm::process(session, frameContext, newExposureTime, metadata); +} + + +} /* namespace ipa::soft::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/simple/agc_simple.h b/src/ipa/simple/agc_simple.h new file mode 100644 index 0000000000..28e414a291 --- /dev/null +++ b/src/ipa/simple/agc_simple.h @@ -0,0 +1,59 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024, Red Hat Inc. + * + * Exposure and gain + */ + +#pragma once + +#include + +#include + +#include "libcamera/internal/software_isp/swisp_stats.h" + +namespace libcamera { + +namespace ipa::soft { + +class AgcSimpleAlgorithm : public AgcAlgorithm +{ +public: + struct Session : AgcAlgorithm::Session { + double gain10; + double gainMinStep; + }; + + struct ProcessParams { + int32_t exposure; + double gain; + const SwIspStats &stats; + unsigned int blackLevel; + }; + + int configure(Session &session, ActiveState &state, const ConfigurationParams &config); + + void prepare(ActiveState &state, FrameContext &frameContext) + { + return AgcAlgorithm::prepare(state, frameContext); + } + + void queueRequest(const Session &session, ActiveState &state, + FrameContext &frameContext, const ControlList &controls) + { + return AgcAlgorithm::queueRequest(session, state, frameContext, controls); + } + + void process(const Session &session, ActiveState &state, + FrameContext &frameContext, std::optional &¶ms, + ControlList &metadata); + +private: + void updateExposure(const Session &session, ActiveState &state, FrameContext &frameContext, + const ProcessParams ¶ms, double exposureMSV); +}; + +} /* namepsace ipa::soft */ + +} /* namespace libcamera */ diff --git a/src/ipa/simple/algorithms/agc.cpp b/src/ipa/simple/algorithms/agc.cpp index e9bcb2c032..db4f63eba5 100644 --- a/src/ipa/simple/algorithms/agc.cpp +++ b/src/ipa/simple/algorithms/agc.cpp @@ -7,124 +7,46 @@ #include "agc.h" -#include -#include -#include - #include -#include "control_ids.h" - namespace libcamera { LOG_DEFINE_CATEGORY(IPASoftExposure) namespace ipa::soft::algorithms { -/* - * The number of bins to use for the optimal exposure calculations. - */ -static constexpr unsigned int kExposureBinsCount = 5; - -/* - * The exposure is optimal when the mean sample value of the histogram is - * in the middle of the range. - */ -static constexpr float kExposureOptimal = kExposureBinsCount / 2.0; - -/* - * This implements the hysteresis for the exposure adjustment. - * It is small enough to have the exposure close to the optimal, and is big - * enough to prevent the exposure from wobbling around the optimal value. - */ -static constexpr float kExposureSatisfactory = 0.2; - -/* - * Proportional gain for exposure/gain adjustment. Maps the MSV error to a - * multiplicative correction factor: - * - * factor = 1.0 + kExpProportionalGain * error - * - * With kExpProportionalGain = 0.04: - * - max error ~2.5 -> factor 1.10 (~10% step, same as before) - * - error 1.0 -> factor 1.04 (~4% step) - * - error 0.3 -> factor 1.012 (~1.2% step) - * - * This replaces the fixed 10% bang-bang step with a proportional correction - * that converges smoothly and avoids overshooting near the target. - */ -static constexpr float kExpProportionalGain = 0.04; - -/* - * Maximum multiplicative step per frame, to bound the correction when the - * scene changes dramatically. - */ -static constexpr float kExpMaxStep = 0.15; - -Agc::Agc() +int Agc::init(IPAContext &context, [[maybe_unused]] const ValueNode &tuningData) { + return agc_.configure(context.configuration.agc.simple, context.activeState.agc.simple, { + .sensor = context.camHelper.get(), + .sensorInfo = context.sensorInfo, + .sensorControls = context.sensorControls, + .ctrlMap = context.ctrlMap, + .autoAllowed = true, + }); } -void Agc::updateExposure(IPAContext &context, IPAFrameContext &frameContext, double exposureMSV) +int Agc::configure(IPAContext &context, [[maybe_unused]] const IPAConfigInfo &configInfo) { - int32_t exposure = frameContext.sensor.exposure; - double again = frameContext.sensor.gain; - - double error = kExposureOptimal - exposureMSV; - - if (std::abs(error) <= kExposureSatisfactory) - return; - - /* - * Compute a proportional correction factor. The sign of the error - * determines the direction: positive error means too dark (increase), - * negative means too bright (decrease). - */ - float step = std::clamp(static_cast(error) * kExpProportionalGain, - -kExpMaxStep, kExpMaxStep); - float factor = 1.0f + step; - - if (factor > 1.0f) { - /* Scene too dark: increase exposure first, then gain. */ - if (exposure < context.configuration.agc.exposureMax) { - int32_t next = static_cast(exposure * factor); - exposure = std::max(next, exposure + 1); - } else { - double next = again * factor; - if (next - again < context.configuration.agc.againMinStep) - again += context.configuration.agc.againMinStep; - else - again = next; - } - } else { - /* Scene too bright: decrease gain first, then exposure. */ - if (again > context.configuration.agc.again10) { - double next = again * factor; - if (again - next < context.configuration.agc.againMinStep) - again -= context.configuration.agc.againMinStep; - else - again = next; - } else { - int32_t next = static_cast(exposure * factor); - exposure = std::min(next, exposure - 1); - } - } - - exposure = std::clamp(exposure, context.configuration.agc.exposureMin, - context.configuration.agc.exposureMax); - again = std::clamp(again, context.configuration.agc.againMin, - context.configuration.agc.againMax); - - frameContext.agc.exposure = exposure; - frameContext.agc.gain = again; + return agc_.configure(context.configuration.agc.simple, context.activeState.agc.simple, { + .sensor = context.camHelper.get(), + .sensorInfo = context.sensorInfo, + .sensorControls = context.sensorControls, + .ctrlMap = context.ctrlMap, + .autoAllowed = true, // \todo not if raw? + }); +} - context.activeState.agc.exposure = exposure; - context.activeState.agc.again = again; +void Agc::queueRequest(IPAContext &context, [[maybe_unused]] const uint32_t frame, IPAFrameContext &frameContext, const ControlList &controls) +{ + agc_.queueRequest(context.configuration.agc.simple, context.activeState.agc.simple, + frameContext.agc.simple, controls); +} - LOG(IPASoftExposure, Debug) - << "exposureMSV " << exposureMSV - << " error " << error << " factor " << factor - << " exp " << exposure << " again " << again; +void Agc::prepare(IPAContext &context, [[maybe_unused]] const uint32_t frame, + IPAFrameContext &frameContext, [[maybe_unused]] DebayerParams *params) +{ + agc_.prepare(context.activeState.agc.simple, frameContext.agc.simple); } void Agc::process(IPAContext &context, @@ -133,66 +55,20 @@ void Agc::process(IPAContext &context, const SwIspStats *stats, ControlList &metadata) { - utils::Duration exposureTime = - context.configuration.agc.lineDuration * frameContext.sensor.exposure; - metadata.set(controls::ExposureTime, exposureTime.get()); - metadata.set(controls::AnalogueGain, frameContext.sensor.gain); - - if (!context.activeState.agc.valid) { - /* - * Init active-state from sensor values in case updateExposure() - * does not run for the first frame. - */ - context.activeState.agc.exposure = frameContext.sensor.exposure; - context.activeState.agc.again = frameContext.sensor.gain; - context.activeState.agc.valid = true; - } - - if (!stats->valid) { - /* - * Use the new exposure and gain values calculated the last time - * there were valid stats. - */ - frameContext.agc.exposure = context.activeState.agc.exposure; - frameContext.agc.gain = context.activeState.agc.again; - return; - } - - /* - * Calculate Mean Sample Value (MSV) according to formula from: - * https://www.araa.asn.au/acra/acra2007/papers/paper84final.pdf - */ - const auto &histogram = stats->yHistogram; - const unsigned int blackLevelHistIdx = - context.activeState.blc.level / (256 / SwIspStats::kYHistogramSize); - const unsigned int histogramSize = - SwIspStats::kYHistogramSize - blackLevelHistIdx; - const unsigned int yHistValsPerBin = histogramSize / kExposureBinsCount; - const unsigned int yHistValsPerBinMod = - histogramSize / (histogramSize % kExposureBinsCount + 1); - int exposureBins[kExposureBinsCount] = {}; - unsigned int denom = 0; - unsigned int num = 0; - - if (yHistValsPerBin == 0) { - LOG(IPASoftExposure, Debug) - << "Not adjusting exposure due to insufficient histogram data"; - return; - } - - for (unsigned int i = 0; i < histogramSize; i++) { - unsigned int idx = (i - (i / yHistValsPerBinMod)) / yHistValsPerBin; - exposureBins[idx] += histogram[blackLevelHistIdx + i]; - } - - for (unsigned int i = 0; i < kExposureBinsCount; i++) { - LOG(IPASoftExposure, Debug) << i << ": " << exposureBins[i]; - denom += exposureBins[i]; - num += exposureBins[i] * (i + 1); + if (stats->valid) { + agc_.process(context.configuration.agc.simple, context.activeState.agc.simple, frameContext.agc.simple, {{ + .exposure = frameContext.sensor.exposure, + .gain = frameContext.sensor.gain, + .stats = *stats, + .blackLevel = context.activeState.blc.level, + }}, metadata); + } else { + agc_.process(context.configuration.agc.simple, context.activeState.agc.simple, + frameContext.agc.simple, {}, metadata); } - float exposureMSV = (denom == 0 ? 0 : static_cast(num) / denom); - updateExposure(context, frameContext, exposureMSV); + frameContext.agc.exposure = frameContext.agc.simple.exposure; + frameContext.agc.gain = frameContext.agc.simple.gain; } REGISTER_IPA_ALGORITHM(Agc, "Agc") diff --git a/src/ipa/simple/algorithms/agc.h b/src/ipa/simple/algorithms/agc.h index 112d9f5a19..869817fc95 100644 --- a/src/ipa/simple/algorithms/agc.h +++ b/src/ipa/simple/algorithms/agc.h @@ -8,6 +8,7 @@ #pragma once #include "algorithm.h" +#include "agc_simple.h" namespace libcamera { @@ -16,16 +17,20 @@ namespace ipa::soft::algorithms { class Agc : public Algorithm { public: - Agc(); + Agc() = default; ~Agc() = default; + int init(IPAContext &context, const ValueNode &tuningData) override; + int configure(IPAContext &context, const IPAConfigInfo &configInfo) override; + void queueRequest(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, const ControlList &controls) override; + void prepare(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, DebayerParams *params) override; void process(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, const SwIspStats *stats, ControlList &metadata) override; private: - void updateExposure(IPAContext &context, IPAFrameContext &frameContext, double exposureMSV); + AgcSimpleAlgorithm agc_; }; } /* namespace ipa::soft::algorithms */ diff --git a/src/ipa/simple/ipa_context.h b/src/ipa/simple/ipa_context.h index d35fb1e91d..36e1d8bba8 100644 --- a/src/ipa/simple/ipa_context.h +++ b/src/ipa/simple/ipa_context.h @@ -7,7 +7,6 @@ #pragma once -#include #include #include @@ -16,19 +15,21 @@ #include "libcamera/internal/matrix.h" #include "libcamera/internal/vector.h" +#include #include #include "core_ipa_interface.h" +#include "agc_simple.h" + namespace libcamera { namespace ipa::soft { struct IPASessionConfiguration { struct { - int32_t exposureMin, exposureMax; - double againMin, againMax, again10, againMinStep; - utils::Duration lineDuration; + AgcSimpleAlgorithm::Session simple; + double again10, againMinStep; } agc; struct { std::optional level; @@ -37,9 +38,7 @@ struct IPASessionConfiguration { struct IPAActiveState { struct { - int32_t exposure; - double again; - bool valid; + AgcSimpleAlgorithm::ActiveState simple; } agc; struct { @@ -67,6 +66,7 @@ struct IPAFrameContext : public FrameContext { Matrix ccm; struct { + AgcSimpleAlgorithm::FrameContext simple; int32_t exposure; double gain; } agc; @@ -90,10 +90,12 @@ struct IPAContext { } IPACameraSensorInfo sensorInfo; + ControlInfoMap sensorControls; IPASessionConfiguration configuration; IPAActiveState activeState; FCQueue frameContexts; ControlInfoMap::Map ctrlMap; + std::unique_ptr camHelper; bool ccmEnabled = false; }; diff --git a/src/ipa/simple/meson.build b/src/ipa/simple/meson.build index 2f9f15f4aa..3033e91763 100644 --- a/src/ipa/simple/meson.build +++ b/src/ipa/simple/meson.build @@ -6,6 +6,7 @@ subdir('data') ipa_name = 'ipa_soft_simple' soft_simple_sources = files([ + 'agc_simple.cpp', 'ipa_context.cpp', 'soft_simple.cpp', ]) diff --git a/src/ipa/simple/soft_simple.cpp b/src/ipa/simple/soft_simple.cpp index 22702638bb..5c85b3986f 100644 --- a/src/ipa/simple/soft_simple.cpp +++ b/src/ipa/simple/soft_simple.cpp @@ -5,7 +5,6 @@ * Simple Software Image Processing Algorithm module */ -#include #include #include @@ -34,8 +33,6 @@ namespace libcamera { LOG_DEFINE_CATEGORY(IPASoft) -using namespace std::literals::chrono_literals; - namespace ipa::soft { /* Maximum number of frame contexts to be held */ @@ -76,8 +73,6 @@ private: DebayerParams *params_; SwIspStats *stats_; - std::unique_ptr camHelper_; - ControlInfoMap sensorInfoMap_; /* Local parameter storage */ struct IPAContext context_; @@ -99,14 +94,15 @@ int IPASoftSimple::init(const IPASettings &settings, ControlInfoMap *ipaControls, bool *ccmEnabled) { - camHelper_ = CameraSensorHelperFactoryBase::create(settings.sensorModel); - if (!camHelper_) { + context_.camHelper = CameraSensorHelperFactoryBase::create(settings.sensorModel); + if (!context_.camHelper) { LOG(IPASoft, Warning) << "Failed to create camera sensor helper for " << settings.sensorModel; } context_.sensorInfo = sensorInfo; + context_.sensorControls = sensorControls; /* Load the tuning data file */ File file(settings.configurationFile); @@ -201,38 +197,15 @@ int IPASoftSimple::init(const IPASettings &settings, int IPASoftSimple::configure(const IPAConfigInfo &configInfo) { - sensorInfoMap_ = configInfo.sensorControls; - - const ControlInfo &exposureInfo = sensorInfoMap_.find(V4L2_CID_EXPOSURE)->second; - const ControlInfo &gainInfo = sensorInfoMap_.find(V4L2_CID_ANALOGUE_GAIN)->second; + context_.sensorControls = configInfo.sensorControls; /* Clear the IPA context before the streaming session. */ context_.configuration = {}; context_.activeState = {}; context_.frameContexts.clear(); - context_.configuration.agc.lineDuration = - context_.sensorInfo.minLineLength * 1.0s / context_.sensorInfo.pixelRate; - context_.configuration.agc.exposureMin = exposureInfo.min().get(); - context_.configuration.agc.exposureMax = exposureInfo.max().get(); - if (!context_.configuration.agc.exposureMin) { - LOG(IPASoft, Warning) << "Minimum exposure is zero, that can't be linear"; - context_.configuration.agc.exposureMin = 1; - } - - int32_t againMin = gainInfo.min().get(); - int32_t againMax = gainInfo.max().get(); - int32_t againDef = gainInfo.def().get(); - - if (camHelper_) { - context_.configuration.agc.againMin = camHelper_->gain(againMin); - context_.configuration.agc.againMax = camHelper_->gain(againMax); - context_.configuration.agc.again10 = std::max(context_.configuration.agc.againMin, 1.0); - context_.configuration.agc.againMinStep = - (context_.configuration.agc.againMax - - context_.configuration.agc.againMin) / - 100.0; - if (camHelper_->blackLevel().has_value()) { + if (context_.camHelper) { + if (context_.camHelper->blackLevel().has_value()) { /* * The black level from camHelper_ is a 16 bit value, software ISP * works with 8 bit pixel values, both regardless of the actual @@ -240,13 +213,8 @@ int IPASoftSimple::configure(const IPAConfigInfo &configInfo) * by dividing the value from the helper by 256. */ context_.configuration.black.level = - camHelper_->blackLevel().value() / 256; + context_.camHelper->blackLevel().value() / 256; } - } else { - context_.configuration.agc.againMax = againMax; - context_.configuration.agc.again10 = againDef; - context_.configuration.agc.againMin = againMin; - context_.configuration.agc.againMinStep = 1.0; } for (const auto &algo : algorithms()) { @@ -255,13 +223,6 @@ int IPASoftSimple::configure(const IPAConfigInfo &configInfo) return ret; } - LOG(IPASoft, Info) - << "Exposure " << context_.configuration.agc.exposureMin << "-" - << context_.configuration.agc.exposureMax - << ", gain " << context_.configuration.agc.againMin << "-" - << context_.configuration.agc.againMax - << " (" << context_.configuration.agc.againMinStep << ")"; - return 0; } @@ -311,17 +272,17 @@ void IPASoftSimple::processStats(const uint32_t frame, frameContext.sensor.exposure = sensorControls.get(V4L2_CID_EXPOSURE).get(); int32_t again = sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get(); - frameContext.sensor.gain = camHelper_ ? camHelper_->gain(again) : again; + frameContext.sensor.gain = context_.camHelper ? context_.camHelper->gain(again) : again; ControlList metadata(controls::controls); for (const auto &algo : algorithms()) algo->process(context_, frame, frameContext, stats_, metadata); metadataReady.emit(frame, metadata); - ControlList ctrls(sensorInfoMap_); + ControlList ctrls(context_.sensorControls); - int32_t againNew = camHelper_ - ? camHelper_->gainCode(frameContext.agc.gain) + int32_t againNew = context_.camHelper + ? context_.camHelper->gainCode(frameContext.agc.gain) : static_cast(frameContext.agc.gain); ctrls.set(V4L2_CID_EXPOSURE, frameContext.agc.exposure); ctrls.set(V4L2_CID_ANALOGUE_GAIN, againNew); From patchwork Fri Jul 3 15:38:19 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= X-Patchwork-Id: 27196 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 C39A6C3323 for ; Fri, 3 Jul 2026 15:38:46 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 482976601D; Fri, 3 Jul 2026 17:38:45 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="fpNMW/nT"; 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 99FD765FC5 for ; Fri, 3 Jul 2026 17:38:26 +0200 (CEST) Received: from pb-laptop.local (185.221.140.128.nat.pool.zt.hu [185.221.140.128]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 8BCBADF3 for ; Fri, 3 Jul 2026 17:37:40 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1783093060; bh=GXzn8M8jH7x9pvaQEZpWK5TfAICmqncbl1I+BrEZruM=; h=From:To:Subject:Date:In-Reply-To:References:From; b=fpNMW/nTxKapBxY4Gh0KFpxjsFV7i+hBrpnKu7kn1fPZ6FhF/54pZlFmSkwYpxWHB e2lz2vNKvDdLTDFcC6L1ca0ypjqf2q93anCpLOted2plimQ5BTKTxoxZv8KrBNB5zK NTQKYiWPpQfOw7uglIfVZctYISiDHM//PHa/hpUw= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v1 17/17] ipa: simple: agc: Use `AgcMeanLuminance` if sensor helper is available Date: Fri, 3 Jul 2026 17:38:19 +0200 Message-ID: <20260703153819.1088752-18-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260703153819.1088752-1-barnabas.pocze@ideasonboard.com> References: <20260703153819.1088752-1-barnabas.pocze@ideasonboard.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" Signed-off-by: Barnabás Pőcze --- src/ipa/simple/algorithms/agc.cpp | 153 +++++++++++++++++++++++++----- src/ipa/simple/algorithms/agc.h | 9 +- src/ipa/simple/ipa_context.h | 4 + 3 files changed, 143 insertions(+), 23 deletions(-) diff --git a/src/ipa/simple/algorithms/agc.cpp b/src/ipa/simple/algorithms/agc.cpp index db4f63eba5..7d035618e8 100644 --- a/src/ipa/simple/algorithms/agc.cpp +++ b/src/ipa/simple/algorithms/agc.cpp @@ -7,7 +7,12 @@ #include "agc.h" +#include + #include +#include + +#include namespace libcamera { @@ -15,38 +20,119 @@ LOG_DEFINE_CATEGORY(IPASoftExposure) namespace ipa::soft::algorithms { -int Agc::init(IPAContext &context, [[maybe_unused]] const ValueNode &tuningData) +namespace { + +class AgcTraits : public AgcMeanLuminance::Traits { - return agc_.configure(context.configuration.agc.simple, context.activeState.agc.simple, { +public: + AgcTraits(const SwIspStats &stats) + : stats_(stats) + { + } + + double estimateLuminance(double gain) const override + { + double sum = 0; + double count = 0; + + for (const auto &[i, cnt] : utils::enumerate(stats_.yHistogram)) { + sum += std::min(1.0, gain * i / stats_.yHistogram.size()) * cnt; + count += cnt; + } + + return sum / count; + } + +private: + const SwIspStats &stats_; +}; + +} + +int Agc::init(IPAContext &context, const ValueNode &tuningData) +{ + const AgcAlgorithm::ConfigurationParams config = { .sensor = context.camHelper.get(), .sensorInfo = context.sensorInfo, .sensorControls = context.sensorControls, .ctrlMap = context.ctrlMap, .autoAllowed = true, - }); + }; + + if (config.sensor) + agc_.emplace(); + else + agc_.emplace(); + + return std::visit(utils::overloaded { + [&](AgcSimpleAlgorithm &impl) { + return impl.configure(context.configuration.agc.simple, + context.activeState.agc.simple, + config); + }, + [&](AgcMeanLuminanceAlgorithm &impl) { + int ret = impl.init(tuningData); + if (ret) + return ret; + + return impl.configure(context.configuration.agc.ml, + context.activeState.agc.ml, + config); + }, + }, agc_); } int Agc::configure(IPAContext &context, [[maybe_unused]] const IPAConfigInfo &configInfo) { - return agc_.configure(context.configuration.agc.simple, context.activeState.agc.simple, { + const AgcAlgorithm::ConfigurationParams config = { .sensor = context.camHelper.get(), .sensorInfo = context.sensorInfo, .sensorControls = context.sensorControls, .ctrlMap = context.ctrlMap, - .autoAllowed = true, // \todo not if raw? - }); + .autoAllowed = true, // \todo if not raw? + }; + + return std::visit(utils::overloaded { + [&](AgcSimpleAlgorithm &impl) { + return impl.configure(context.configuration.agc.simple, + context.activeState.agc.simple, + config); + }, + [&](AgcMeanLuminanceAlgorithm &impl) { + return impl.configure(context.configuration.agc.ml, + context.activeState.agc.ml, + config); + }, + }, agc_); } void Agc::queueRequest(IPAContext &context, [[maybe_unused]] const uint32_t frame, IPAFrameContext &frameContext, const ControlList &controls) { - agc_.queueRequest(context.configuration.agc.simple, context.activeState.agc.simple, - frameContext.agc.simple, controls); + std::visit(utils::overloaded { + [&](AgcSimpleAlgorithm &impl) { + impl.queueRequest(context.configuration.agc.simple, + context.activeState.agc.simple, + frameContext.agc.simple, controls); + }, + [&](AgcMeanLuminanceAlgorithm &impl) { + impl.queueRequest(context.configuration.agc.ml, + context.activeState.agc.ml, + frameContext.agc.ml, controls); + }, + }, agc_); } void Agc::prepare(IPAContext &context, [[maybe_unused]] const uint32_t frame, IPAFrameContext &frameContext, [[maybe_unused]] DebayerParams *params) { - agc_.prepare(context.activeState.agc.simple, frameContext.agc.simple); + std::visit(utils::overloaded { + [&](AgcSimpleAlgorithm &impl) { + impl.prepare(context.activeState.agc.simple, frameContext.agc.simple); + }, + [&](AgcMeanLuminanceAlgorithm &impl) { + impl.prepare(context.activeState.agc.ml, frameContext.agc.ml); + }, + }, agc_); } void Agc::process(IPAContext &context, @@ -55,20 +141,43 @@ void Agc::process(IPAContext &context, const SwIspStats *stats, ControlList &metadata) { - if (stats->valid) { - agc_.process(context.configuration.agc.simple, context.activeState.agc.simple, frameContext.agc.simple, {{ - .exposure = frameContext.sensor.exposure, - .gain = frameContext.sensor.gain, - .stats = *stats, - .blackLevel = context.activeState.blc.level, - }}, metadata); - } else { - agc_.process(context.configuration.agc.simple, context.activeState.agc.simple, - frameContext.agc.simple, {}, metadata); - } + std::visit(utils::overloaded { + [&](AgcSimpleAlgorithm &impl) { + if (stats->valid) { + impl.process(context.configuration.agc.simple, context.activeState.agc.simple, frameContext.agc.simple, {{ + .exposure = frameContext.sensor.exposure, + .gain = frameContext.sensor.gain, + .stats = *stats, + .blackLevel = context.activeState.blc.level, + }}, metadata); + } else { + impl.process(context.configuration.agc.simple, context.activeState.agc.simple, + frameContext.agc.simple, {}, metadata); + } + + frameContext.agc.exposure = frameContext.agc.simple.exposure; + frameContext.agc.gain = frameContext.agc.simple.gain; + }, + [&](AgcMeanLuminanceAlgorithm &impl) { + if (stats->valid) { + Histogram hist(stats->yHistogram); + + impl.process(context.configuration.agc.ml, context.activeState.agc.ml, frameContext.agc.ml, {{ + .traits = AgcTraits(*stats), + .hist = hist, + .exposure = uint32_t(frameContext.sensor.exposure), + .gain = frameContext.sensor.gain, + }}, metadata); + + } else { + impl.process(context.configuration.agc.ml, context.activeState.agc.ml, + frameContext.agc.ml, {}, metadata); + } - frameContext.agc.exposure = frameContext.agc.simple.exposure; - frameContext.agc.gain = frameContext.agc.simple.gain; + frameContext.agc.exposure = frameContext.agc.ml.exposure; + frameContext.agc.gain = frameContext.agc.ml.gain; + }, + }, agc_); } REGISTER_IPA_ALGORITHM(Agc, "Agc") diff --git a/src/ipa/simple/algorithms/agc.h b/src/ipa/simple/algorithms/agc.h index 869817fc95..5dac17ab1d 100644 --- a/src/ipa/simple/algorithms/agc.h +++ b/src/ipa/simple/algorithms/agc.h @@ -7,6 +7,10 @@ #pragma once +#include + +#include + #include "algorithm.h" #include "agc_simple.h" @@ -30,7 +34,10 @@ public: ControlList &metadata) override; private: - AgcSimpleAlgorithm agc_; + std::variant< + AgcSimpleAlgorithm, + AgcMeanLuminanceAlgorithm + > agc_; }; } /* namespace ipa::soft::algorithms */ diff --git a/src/ipa/simple/ipa_context.h b/src/ipa/simple/ipa_context.h index 36e1d8bba8..b4a11b04cd 100644 --- a/src/ipa/simple/ipa_context.h +++ b/src/ipa/simple/ipa_context.h @@ -15,6 +15,7 @@ #include "libcamera/internal/matrix.h" #include "libcamera/internal/vector.h" +#include #include #include @@ -29,6 +30,7 @@ namespace ipa::soft { struct IPASessionConfiguration { struct { AgcSimpleAlgorithm::Session simple; + AgcMeanLuminanceAlgorithm::Session ml; double again10, againMinStep; } agc; struct { @@ -39,6 +41,7 @@ struct IPASessionConfiguration { struct IPAActiveState { struct { AgcSimpleAlgorithm::ActiveState simple; + AgcMeanLuminanceAlgorithm::ActiveState ml; } agc; struct { @@ -67,6 +70,7 @@ struct IPAFrameContext : public FrameContext { struct { AgcSimpleAlgorithm::FrameContext simple; + AgcMeanLuminanceAlgorithm::FrameContext ml; int32_t exposure; double gain; } agc;