From patchwork Thu Oct 29 12:58:21 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: 28667 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 BE940C3261 for ; Thu, 29 Oct 2026 12:58:57 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 207C5690BA; Thu, 29 Oct 2026 13:58:53 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="NSq2p8dx"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 60C8A69091 for ; Thu, 29 Oct 2026 13:58:48 +0100 (CET) Received: from pb-laptop.local (185.221.143.99.nat.pool.zt.hu [185.221.143.99]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id E30C56DC for ; Thu, 29 Oct 2026 13:56:34 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1793278595; bh=QNf+Bj3X+IA0Vi7az46SVk+QAAtWZZBzpmVLidAONXM=; h=From:To:Subject:Date:In-Reply-To:References:From; b=NSq2p8dxeI0D464uYYuyUkBqfo8MvVviAz/D7FsgnDkkDHMxCzksqsPoLJoyO+z1s m4lVhlfggHZNcTI0KRNr4dqSB7cmMDyRTuoOgJlLi8OFRGoVsADO3Px1gmT491/I8s BggiFPH/8fKm/TtY7xPe0Asv/6sazQkgo3DOxfPs= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [PATCH v5 01/24] libcamera: controls: AeExposureMode: Add ExposureMaximize Date: Thu, 29 Oct 2026 13:58:21 +0100 Message-ID: <20261029125844.170324-2-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261029125844.170324-1-barnabas.pocze@ideasonboard.com> References: <20261029125844.170324-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 new exposure mode that is intended to make the full capabilities of a sensor available for applications, potentially allowing multi-second exposure times and maximal analogue gain. Signed-off-by: Barnabás Pőcze Reviewed-by: Jacopo Mondi Reviewed-by: Kieran Bingham --- src/libcamera/control_ids_core.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/libcamera/control_ids_core.yaml b/src/libcamera/control_ids_core.yaml index 73643e7e84..d27be4be6a 100644 --- a/src/libcamera/control_ids_core.yaml +++ b/src/libcamera/control_ids_core.yaml @@ -188,6 +188,11 @@ controls: - name: Custom value: 3 description: Custom exposure mode. + - name: Maximize + value: 4 + description: | + Exposure mode allowing very large exposure times and gains, + up to the sensors capabilities. - ExposureValue: type: float From patchwork Thu Oct 29 12:58:22 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: 28668 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 C9CA4C3261 for ; Thu, 29 Oct 2026 12:59:00 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 8AEDF690B6; Thu, 29 Oct 2026 13:58:54 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Smqn2yRx"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 89A7D69094 for ; Thu, 29 Oct 2026 13:58:48 +0100 (CET) Received: from pb-laptop.local (185.221.143.99.nat.pool.zt.hu [185.221.143.99]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 1CA32802; Thu, 29 Oct 2026 13:56:35 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1793278595; bh=E8zMGakT0PYi1I3aY6CfaNF5VT5/7kCErgkjGkanTaA=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Smqn2yRxt8SB/1ZlvVNhVxNp9MKWwp0fDGQ9PChhezorO64g3EhqPpX1S3SVqFx1R A5X4nhXPMg9F4StV7bOHE/Bo2UELcLS1kZmqt5rtXsXkk/04iDpPuLhL+G/SdemVZb P1liiyN4IfwXO/TbhA0FZ+TsqDwwrqEy2r4q3v7c= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi Subject: [PATCH v5 02/24] ipa: libipa: agc: Change `AgcMSV` exposure mode Date: Thu, 29 Oct 2026 13:58:22 +0100 Message-ID: <20261029125844.170324-3-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261029125844.170324-1-barnabas.pocze@ideasonboard.com> References: <20261029125844.170324-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 `AgcMSV` implementation does not really support exposure modes like `AgcMeanLuminance`. Instead it works by maximizing exposure time, then analogue gain. This behaviour is best described by the newly introduced "Maximize" exposure mode, so report that. Signed-off-by: Barnabás Pőcze Reviewed-by: Jacopo Mondi Reviewed-by: Kieran Bingham --- src/ipa/libipa/agc.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp index 182dd7380e..7438825d30 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -487,9 +487,9 @@ int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state, std::visit(utils::overloaded{ [&](AgcMSV &) { - /* No constraint/exposure mode support. */ + /* No constraint mode support. */ state.constraintMode = controls::AeConstraintModeEnum::AeConstraintModeNormal; - state.exposureMode = controls::AeExposureModeEnum::AeExposureModeNormal; + state.exposureMode = controls::AeExposureModeEnum::AeExposureModeMaximize; state.automatic.yTarget = 0; /* Not supported. */ From patchwork Thu Oct 29 12:58:23 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: 28669 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 DD0C3C3261 for ; Thu, 29 Oct 2026 12:59:03 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 00BD1690B9; Thu, 29 Oct 2026 13:58:55 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="UKzAwhGS"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id B4A4D69096 for ; Thu, 29 Oct 2026 13:58:48 +0100 (CET) Received: from pb-laptop.local (185.221.143.99.nat.pool.zt.hu [185.221.143.99]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 50EFDC1; Thu, 29 Oct 2026 13:56:35 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1793278595; bh=Fn337qqjgerBHCHoSE9nbm8n76qMFjXFXI9MR1A++wc=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=UKzAwhGSC6a2UBfh9xm9X1EfT8CoQz89G0tbhi991sNR4eVE+x1PpEl0X19gBpk2+ ZKHUeYnP64mhYR6jOsqUhuVZdepKScYD4M7FxtppWnsx3P2Psg4yRWwVpSThk8RTeX 5UAmPS0zGowgwGW6ZkeMZiKDDpnyBxx6KO1KONYc= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Kieran Bingham Subject: [PATCH v5 03/24] ipa: libipa: exposure_mode_helper: Remove defaulted destructor Date: Thu, 29 Oct 2026 13:58:23 +0100 Message-ID: <20261029125844.170324-4-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261029125844.170324-1-barnabas.pocze@ideasonboard.com> References: <20261029125844.170324-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 does not have any effect, the destructor will be defaulted implicitly, so remove it. Signed-off-by: Barnabás Pőcze Reviewed-by: Jacopo Mondi Reviewed-by: Kieran Bingham --- src/ipa/libipa/exposure_mode_helper.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ipa/libipa/exposure_mode_helper.h b/src/ipa/libipa/exposure_mode_helper.h index a56e19061b..d0d468a6a2 100644 --- a/src/ipa/libipa/exposure_mode_helper.h +++ b/src/ipa/libipa/exposure_mode_helper.h @@ -23,7 +23,6 @@ class ExposureModeHelper { public: ExposureModeHelper(std::span> stages); - ~ExposureModeHelper() = default; void configure(utils::Duration lineLength, const CameraSensorHelper *sensorHelper); void setLimits(utils::Duration minExposureTime, utils::Duration maxExposureTime, From patchwork Thu Oct 29 12:58:24 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: 28670 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 E9EADC3261 for ; Thu, 29 Oct 2026 12:59:05 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id F078F69097; Thu, 29 Oct 2026 13:58:56 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="i4QB11Y6"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id E186669097 for ; Thu, 29 Oct 2026 13:58:48 +0100 (CET) Received: from pb-laptop.local (185.221.143.99.nat.pool.zt.hu [185.221.143.99]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 8BB346DC for ; Thu, 29 Oct 2026 13:56:35 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1793278595; bh=W62O4+QelqHVFxkIbKq0HLLsWw/6pShWiwoBDxEyUSk=; h=From:To:Subject:Date:In-Reply-To:References:From; b=i4QB11Y69yf2sc8eYGfLoV7xuT2s28/r9hDZf+e6b1gNE0V+pTte7j34iG9+4mAao F+MhgG9SgKRVIDWZY9SVrR2aI3gao0sB8xmJmn9hQLQlqpaFxXLvii5PanGWNcnvIH QPFCQRw/sBOsDplfLj59f1Qhdj+un9TzQxeCIqy4= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [PATCH v5 04/24] ipa: libipa: exposure_mode_helper: Keep stage params together Date: Thu, 29 Oct 2026 13:58:24 +0100 Message-ID: <20261029125844.170324-5-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261029125844.170324-1-barnabas.pocze@ideasonboard.com> References: <20261029125844.170324-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 new type `Stage` that contains both the exposure time and gain in one place, and have a single vector of `Stage`s instead of the two same size exposure time / gain vectors. Also clarify in the documentation that the gain of a stage only constrains the analogue gain at the moment. Signed-off-by: Barnabás Pőcze Reviewed-by: Jacopo Mondi --- src/ipa/libipa/agc_mean_luminance.cpp | 12 ++++----- src/ipa/libipa/exposure_mode_helper.cpp | 33 +++++++++++++++---------- src/ipa/libipa/exposure_mode_helper.h | 12 +++++---- 3 files changed, 33 insertions(+), 24 deletions(-) diff --git a/src/ipa/libipa/agc_mean_luminance.cpp b/src/ipa/libipa/agc_mean_luminance.cpp index 486c09c076..4c47770424 100644 --- a/src/ipa/libipa/agc_mean_luminance.cpp +++ b/src/ipa/libipa/agc_mean_luminance.cpp @@ -314,15 +314,15 @@ int AgcMeanLuminance::parseExposureModes(const ValueNode &tuningData) return -EINVAL; } - std::vector> stages; - for (unsigned int i = 0; i < exposureTimes.size(); i++) { + std::vector stages; + for (size_t i = 0; i < exposureTimes.size(); i++) { stages.push_back({ - std::chrono::microseconds(exposureTimes[i]), - gains[i] + .exposureTime = std::chrono::microseconds(exposureTimes[i]), + .gain = gains[i], }); } - exposureModeHelpers_.try_emplace(it->second, stages); + exposureModeHelpers_.try_emplace(it->second, std::move(stages)); } } @@ -334,7 +334,7 @@ int AgcMeanLuminance::parseExposureModes(const ValueNode &tuningData) */ if (exposureModeHelpers_.empty()) exposureModeHelpers_.try_emplace(controls::AeExposureModeNormal, - std::span>{}); + std::vector{}); return 0; } diff --git a/src/ipa/libipa/exposure_mode_helper.cpp b/src/ipa/libipa/exposure_mode_helper.cpp index b3e75f6358..059792af28 100644 --- a/src/ipa/libipa/exposure_mode_helper.cpp +++ b/src/ipa/libipa/exposure_mode_helper.cpp @@ -59,24 +59,31 @@ namespace ipa { * used. */ +/** + * \class ExposureModeHelper::Stage + * \brief Details of an exposure stage + * + * This type contains the parameters of a single exposure stage as used by the + * algorithm. + * + * \var ExposureModeHelper::Stage::exposureTime + * \brief The maximum exposure time of the stage + * + * \var ExposureModeHelper::Stage::gain + * \brief The maximum analogue gain of the stage + */ + /** * \brief Construct an ExposureModeHelper instance * \param[in] stages The vector of paired exposure time and gain limits * - * The input stages are exposure time and _total_ gain pairs; the gain - * encompasses both analogue and digital gain. - * * The vector of stages may be empty. In that case, the helper will simply use * the runtime limits set through setLimits() instead. */ -ExposureModeHelper::ExposureModeHelper(std::span> stages) - : lineDuration_(1us), minExposureTime_(0us), maxExposureTime_(0us), - minGain_(0), maxGain_(0), sensorHelper_(nullptr) +ExposureModeHelper::ExposureModeHelper(std::vector &&stages) + : stages_(std::move(stages)), lineDuration_(1us), minExposureTime_(0us), + maxExposureTime_(0us), minGain_(0), maxGain_(0), sensorHelper_(nullptr) { - for (const auto &[s, g] : stages) { - exposureTimes_.push_back(s); - gains_.push_back(g); - } } /** @@ -236,10 +243,10 @@ ExposureModeHelper::splitExposure(utils::Duration exposure) const double stageGain = clampGain(1.0); double lastStageGain = stageGain; - for (unsigned int stage = 0; stage < gains_.size(); stage++) { - utils::Duration stageExposureTime = clampExposureTime(exposureTimes_[stage], + for (const auto &stage : stages_) { + utils::Duration stageExposureTime = clampExposureTime(stage.exposureTime, &quantGain); - stageGain = clampGain(gains_[stage]); + stageGain = clampGain(stage.gain); /* * We perform the clamping on both exposure time and gain in diff --git a/src/ipa/libipa/exposure_mode_helper.h b/src/ipa/libipa/exposure_mode_helper.h index d0d468a6a2..1a77bf4da8 100644 --- a/src/ipa/libipa/exposure_mode_helper.h +++ b/src/ipa/libipa/exposure_mode_helper.h @@ -7,8 +7,6 @@ #pragma once -#include -#include #include #include @@ -22,7 +20,12 @@ namespace ipa { class ExposureModeHelper { public: - ExposureModeHelper(std::span> stages); + struct Stage { + utils::Duration exposureTime; + double gain; + }; + + ExposureModeHelper(std::vector &&stages); void configure(utils::Duration lineLength, const CameraSensorHelper *sensorHelper); void setLimits(utils::Duration minExposureTime, utils::Duration maxExposureTime, @@ -47,8 +50,7 @@ private: double *quantizationGain = nullptr) const; double clampGain(double gain, double *quantizationGain = nullptr) const; - std::vector exposureTimes_; - std::vector gains_; + std::vector stages_; utils::Duration lineDuration_; utils::Duration minExposureTime_; From patchwork Thu Oct 29 12:58:25 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: 28671 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 E3975C3261 for ; Thu, 29 Oct 2026 12:59:07 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id D2B91690C9; Thu, 29 Oct 2026 13:58:58 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="jdUtvTmA"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 26C9D69098 for ; Thu, 29 Oct 2026 13:58:49 +0100 (CET) Received: from pb-laptop.local (185.221.143.99.nat.pool.zt.hu [185.221.143.99]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id B8A0C802; Thu, 29 Oct 2026 13:56:35 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1793278595; bh=BIpYFm8nA2wckzbLdUxOZrqBY/uiKzwYn2ams6doWYU=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=jdUtvTmAF09Jhqa2QXCB9zxs/8mY6M30WletYnVX+4x2Be96Bi3kdMd+N4j3VknZf hqAgHI5HpzqBHzpJdX/tfgE6uVSdHpGz26vLo3xE1lZhQxZYJhVNL2Ymxi2FgQx49J p7B6tA4BeGZUM6Q7NbuJIA/MpesKYXEkeRu498Bs= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Kieran Bingham Subject: [PATCH v5 05/24] ipa: libipa: exposure_mode_helper: Remove limit getters Date: Thu, 29 Oct 2026 13:58:25 +0100 Message-ID: <20261029125844.170324-6-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261029125844.170324-1-barnabas.pocze@ideasonboard.com> References: <20261029125844.170324-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" These getters have not been used since their introduction, so remove them for now. They can be easily restored if needed. Signed-off-by: Barnabás Pőcze Reviewed-by: Jacopo Mondi Reviewed-by: Kieran Bingham --- src/ipa/libipa/exposure_mode_helper.cpp | 25 ------------------------- src/ipa/libipa/exposure_mode_helper.h | 5 ----- 2 files changed, 30 deletions(-) diff --git a/src/ipa/libipa/exposure_mode_helper.cpp b/src/ipa/libipa/exposure_mode_helper.cpp index 059792af28..4ec31f54c3 100644 --- a/src/ipa/libipa/exposure_mode_helper.cpp +++ b/src/ipa/libipa/exposure_mode_helper.cpp @@ -294,31 +294,6 @@ ExposureModeHelper::splitExposure(utils::Duration exposure) const exposure / (exposureTime * gain * quantGain) }; } -/** - * \fn ExposureModeHelper::minExposureTime() - * \brief Retrieve the configured minimum exposure time limit set through - * setLimits() - * \return The minExposureTime_ value - */ - -/** - * \fn ExposureModeHelper::maxExposureTime() - * \brief Retrieve the configured maximum exposure time set through setLimits() - * \return The maxExposureTime_ value - */ - -/** - * \fn ExposureModeHelper::minGain() - * \brief Retrieve the configured minimum gain set through setLimits() - * \return The minGain_ value - */ - -/** - * \fn ExposureModeHelper::maxGain() - * \brief Retrieve the configured maximum gain set through setLimits() - * \return The maxGain_ value - */ - } /* namespace ipa */ } /* namespace libcamera */ diff --git a/src/ipa/libipa/exposure_mode_helper.h b/src/ipa/libipa/exposure_mode_helper.h index 1a77bf4da8..d33a0fd822 100644 --- a/src/ipa/libipa/exposure_mode_helper.h +++ b/src/ipa/libipa/exposure_mode_helper.h @@ -40,11 +40,6 @@ public: [[nodiscard]] Result splitExposure(utils::Duration exposure) const; - utils::Duration minExposureTime() const { return minExposureTime_; } - utils::Duration maxExposureTime() const { return maxExposureTime_; } - double minGain() const { return minGain_; } - double maxGain() const { return maxGain_; } - private: utils::Duration clampExposureTime(utils::Duration exposureTime, double *quantizationGain = nullptr) const; From patchwork Thu Oct 29 12:58:26 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: 28672 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 DD736C3261 for ; Thu, 29 Oct 2026 12:59:09 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id BC013690A1; Thu, 29 Oct 2026 13:59:00 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="XmP6Bxu6"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 63BF668E07 for ; Thu, 29 Oct 2026 13:58:49 +0100 (CET) Received: from pb-laptop.local (185.221.143.99.nat.pool.zt.hu [185.221.143.99]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id F2B0E6DC for ; Thu, 29 Oct 2026 13:56:35 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1793278596; bh=rTdiWpMBxTCsmJ95GZ1c+1lYSsnjpC/+eMOdDdI305k=; h=From:To:Subject:Date:In-Reply-To:References:From; b=XmP6Bxu6d0ZeB8ZBdcAbPjm5L7uuEL+yTPpm6nDPMK9/tngdJjQ0CJH9baotLjZdE ciYXIYGytQYCE5vG1KucreA8fKMhYGRYOwywn3JSqSLwQVm5vpa3NpIo2z0cPpIR4T NlxrsVo+Goxi9xHGOBSNXX+AbgpEoA9/kfa5vzoU= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [PATCH v5 06/24] ipa: libipa: agc: Move tuning file parsing Date: Thu, 29 Oct 2026 13:58:26 +0100 Message-ID: <20261029125844.170324-7-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261029125844.170324-1-barnabas.pocze@ideasonboard.com> References: <20261029125844.170324-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 tuning file parsing out of `AgcMeanLuminance`, and provide the configuration via the constructor. This allows `AgcAlgorithm` to inspect the configuration, specifically it can check if any exposure modes have been defined. Signed-off-by: Barnabás Pőcze Reviewed-by: Jacopo Mondi --- src/ipa/libipa/agc.cpp | 254 +++++++++++++++++++++++- src/ipa/libipa/agc_mean_luminance.cpp | 268 ++------------------------ src/ipa/libipa/agc_mean_luminance.h | 22 +-- 3 files changed, 275 insertions(+), 269 deletions(-) diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp index 7438825d30..54aa8207d4 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -26,6 +26,8 @@ #include +#include "exposure_mode_helper.h" + /** * \file agc.h * \brief libipa AGC algorithm @@ -222,6 +224,196 @@ namespace agc { } /* namespace agc */ +namespace { + +/* + * Default relative luminance target + * + * This value should be chosen so that when the camera points at a grey target, + * the resulting image brightness looks "right". Custom values can be passed + * as the relativeLuminanceTarget value in sensor tuning files. + */ +static constexpr double kDefaultRelativeLuminanceTarget = 0.16; + +int parseRelativeLuminanceTarget(AgcMeanLuminance::Configuration &config, const ValueNode &tuningData) +{ + auto &target = tuningData["relativeLuminanceTarget"]; + if (!target) { + config.relativeLuminanceTarget = { { { { 0.0, kDefaultRelativeLuminanceTarget } } } }; + return 0; + } + + auto pwl = target.get(); + if (!pwl) { + LOG(Agc, Error) + << "Failed to load relative luminance target."; + return -EINVAL; + } + + config.relativeLuminanceTarget = std::move(*pwl); + return 0; +} + +int parseConstraint(AgcMeanLuminance::Configuration &config, const ValueNode &modeDict, int32_t id) +{ + for (const auto &[boundName, content] : modeDict.asDict()) { + if (boundName != "upper" && boundName != "lower") { + LOG(Agc, Warning) + << "Ignoring unknown constraint bound '" << boundName << "'"; + continue; + } + + unsigned int idx = static_cast(boundName == "upper"); + auto bound = static_cast(idx); + double qLo = content["qLo"].get().value_or(0.98); + double qHi = content["qHi"].get().value_or(1.0); + auto yTarget = content["yTarget"].get(); + if (!yTarget) { + LOG(Agc, Error) + << "Failed to parse yTarget"; + return -EINVAL; + } + + AgcMeanLuminance::AgcConstraint constraint = { + bound, + qLo, + qHi, + std::move(*yTarget), + }; + auto &constraints = config.constraintModes[id]; + + if (idx) + constraints.push_back(std::move(constraint)); + else + constraints.insert(constraints.begin(), std::move(constraint)); + } + + return 0; +} + +int parseConstraintModes(AgcMeanLuminance::Configuration &config, const ValueNode &tuningData) +{ + const ValueNode &constraintModes = tuningData[controls::AeConstraintMode.name()]; + if (constraintModes.isDictionary()) { + for (const auto &[modeName, modeDict] : constraintModes.asDict()) { + auto it = controls::AeConstraintModeNameValueMap.find(modeName); + if (it == controls::AeConstraintModeNameValueMap.end()) { + LOG(Agc, Warning) + << "Skipping unknown constraint mode '" << modeName << "'"; + continue; + } + + if (!modeDict.isDictionary()) { + LOG(Agc, Error) + << "Invalid constraint mode '" << modeName << "'"; + return -EINVAL; + } + + int ret = parseConstraint(config, modeDict, it->second); + if (ret) + return ret; + } + } + + /* + * If the tuning data file contains no constraints then we use the + * default constraint that the IPU3/RkISP1 Agc algorithms were adhering + * to anyway before centralisation; this constraint forces the top 2% of + * the histogram to be at least 0.5. + */ + if (config.constraintModes.empty()) { + AgcMeanLuminance::AgcConstraint constraint = { + AgcMeanLuminance::AgcConstraint::Bound::Lower, + 0.98, + 1.0, + Pwl({ { { 0.0, 0.5 } } }), + }; + + config.constraintModes[controls::AeConstraintModeNormal].push_back( + std::move(constraint)); + } + + return 0; +} + +int parseExposureModes(AgcMeanLuminance::Configuration &config, const ValueNode &tuningData) +{ + const ValueNode &exposureModes = tuningData[controls::AeExposureMode.name()]; + if (exposureModes.isDictionary()) { + for (const auto &[modeName, modeValues] : exposureModes.asDict()) { + auto it = controls::AeExposureModeNameValueMap.find(modeName); + if (it == controls::AeExposureModeNameValueMap.end()) { + LOG(Agc, Warning) + << "Skipping unknown exposure mode '" << modeName << "'"; + continue; + } + + if (!modeValues.isDictionary()) { + LOG(Agc, Error) + << "Invalid exposure mode '" << modeName << "'"; + return -EINVAL; + } + + std::vector exposureTimes = + modeValues["exposureTime"].get>().value_or(utils::defopt); + std::vector gains = + modeValues["gain"].get>().value_or(utils::defopt); + + if (exposureTimes.size() != gains.size()) { + LOG(Agc, Error) + << "Exposure time and gain array sizes unequal"; + return -EINVAL; + } + + if (exposureTimes.empty()) { + LOG(Agc, Error) + << "Exposure time and gain arrays are empty"; + return -EINVAL; + } + + std::vector stages; + for (size_t i = 0; i < exposureTimes.size(); i++) { + stages.push_back({ + .exposureTime = std::chrono::microseconds(exposureTimes[i]), + .gain = gains[i], + }); + } + + config.exposureModeHelpers.try_emplace(it->second, std::move(stages)); + } + } + + /* + * If we don't have any exposure modes in the tuning data we create an + * ExposureModeHelper using an empty vector of stages. This will result + * in the ExposureModeHelper simply driving the exposure time as high as + * possible before touching gain. + */ + if (config.exposureModeHelpers.empty()) + config.exposureModeHelpers.try_emplace( + controls::AeExposureModeNormal, + std::vector{}); + + return 0; +} + +int parseTuningData(AgcMeanLuminance::Configuration &config, const ValueNode &tuningData) +{ + int ret; + + ret = parseRelativeLuminanceTarget(config, tuningData); + if (ret) + return ret; + + ret = parseConstraintModes(config, tuningData); + if (ret) + return ret; + + return parseExposureModes(config, tuningData); +} + +} /* namespace */ + /** * \class AgcAlgorithm * \brief libIPA LSC algorithm algorithm @@ -238,6 +430,57 @@ namespace agc { * virtual function, they should simply call the identically named member * function of AgcAlgorithm. * + * When a CameraSensorHelper is provided, AgcMeanLuminance is used and support for + * exposure (controls::AeExposureMode) and constraint (controls::AeConstraintMode) + * modes, and specifying the default luminance target is available via the tuning data. + * + * For controls::AeConstraintMode, the dictionary under the key "AeConstraintMode" + * is parsed: per-mode setting dictionaries with the key being a value from + * controls::AeConstraintModeNameValueMap. The yTarget can either be provided + * as single value or as array in which case it is interpreted as a PWL mapping + * lux levels to yTarget values. Each mode dict may contain either a "lower" or + * "upper" key or both, for example: + * + * \code{.unparsed} + * algorithms: + * - Agc: + * AeConstraintMode: + * Normal: + * lower: + * qLo: 0.98 + * qHi: 1.0 + * yTarget: 0.5 + * Highlight: + * lower: + * qLo: 0.98 + * qHi: 1.0 + * yTarget: 0.5 + * upper: + * qLo: 0.98 + * qHi: 1.0 + * yTarget: [ 100, 0.8, 20000, 0.5 ] + * + * \endcode + * + * For controls::AeExposureMode, the dictionary under the key AeExposureMode + * is parsed: per-mode setting dictionaries with the key being a value from + * controls::AeExposureModeNameValueMap. Each mode dict should contain + * an array of exposure times with the key "exposureTime" and an array of gain + * values with the key "gain", in this format: + * + * \code{.unparsed} + * algorithms: + * - Agc: + * AeExposureMode: + * Normal: + * exposureTime: [ 100, 10000, 30000, 60000, 120000 ] + * gain: [ 2.0, 4.0, 6.0, 8.0, 10.0 ] + * Short: + * exposureTime: [ 100, 10000, 30000, 60000, 120000 ] + * gain: [ 2.0, 4.0, 6.0, 8.0, 10.0 ] + * + * \endcode + * * \todo DigitalGain, DigitalGainMode * \todo Expand documentation */ @@ -298,9 +541,8 @@ namespace agc { * in ConfigurationParams::ctrlMap. * * If a CameraSensorHelper is provided in \a sensor, an instance of - * AgcMeanLuminance is instantiated and the tuning data is loaded by - * calling AgcMeanLuminance::parseTuningData(). Otherwise an instance - * of AgcMSV is instantiated, and in this case the tuning data is ignored. + * AgcMeanLuminance is instantiated. Otherwise an instance of AgcMSV + * is instantiated, and in this case the tuning data is ignored. * * \return 0 on success, or a negative error code * @@ -310,10 +552,12 @@ int AgcAlgorithm::init(const ValueNode &tuningData, CameraSensorHelper *sensor, const ConfigurationParams &config) { if (sensor) { - auto &impl = impl_.emplace(); - int ret = impl.parseTuningData(tuningData); + AgcMeanLuminance::Configuration agcConfig; + int ret = parseTuningData(agcConfig, tuningData); if (ret) return ret; + + impl_.emplace(std::move(agcConfig)); } else { impl_.emplace(); } diff --git a/src/ipa/libipa/agc_mean_luminance.cpp b/src/ipa/libipa/agc_mean_luminance.cpp index 4c47770424..4f2b5edef6 100644 --- a/src/ipa/libipa/agc_mean_luminance.cpp +++ b/src/ipa/libipa/agc_mean_luminance.cpp @@ -9,7 +9,6 @@ #include #include -#include #include #include @@ -18,8 +17,6 @@ #include "exposure_mode_helper.h" -using namespace libcamera::controls; - /** * \file agc_mean_luminance.h * \brief Class implementing mean luminance AEGC @@ -39,15 +36,6 @@ namespace ipa { */ static constexpr uint32_t kNumStartupFrames = 10; -/* - * Default relative luminance target - * - * This value should be chosen so that when the camera points at a grey target, - * the resulting image brightness looks "right". Custom values can be passed - * as the relativeLuminanceTarget value in sensor tuning files. - */ -static constexpr double kDefaultRelativeLuminanceTarget = 0.16; - /* * Maximum relative luminance target * @@ -148,17 +136,13 @@ static constexpr unsigned int kDefaultLuxLevel = 500; * section of a histogram to a target value, where the target and the boundaries * of the section of the histogram used in the calculation are taken from the * values defined for the currently configured AeConstraintMode within the - * tuning data. This class provides a helper function to parse those tuning data - * to discover the constraints, and so requires a specific format for those - * data which is described in \ref parseTuningData(). The gain from the first - * stage is then clamped to the gain from this stage. + * tuning data. The gain from the first stage is then clamped to the gain from + * this stage. * * The final gain is used to adjust the effective exposure value of the image, * and that new exposure value is divided into exposure time, analogue gain and * digital gain according to the selected AeExposureMode. This class uses the - * \ref ExposureModeHelper class to assist in that division, and expects the - * data needed to initialise that class to be present in tuning data in a - * format described in \ref parseTuningData(). + * \ref ExposureModeHelper class to assist in that division. * * In order to be able to use this algorithm an IPA module needs to be able to * do the following: @@ -170,175 +154,23 @@ static constexpr unsigned int kDefaultLuxLevel = 500; * * IPA modules that want to use this class to implement their AEGC algorithm * should derive AgcMeanLuminance::Traits and override the necessary functions. - * The users must call parseTuningData() in init(), and must also call configure() - * and setLimits() in configure(). They may then use calculateNewEv() in process(). - * If the limits passed to setLimits() change for any reason (for example, in - * response to a FrameDurationLimit control being passed in queueRequest()) then - * setLimits() must be called again with the new values. + * The users must call configure() and setLimits() in configure(). They may + * then use calculateNewEv() in process(). If the limits passed to setLimits() + * change for any reason (for example, in response to a FrameDurationLimit control + * being passed in queueRequest()) then setLimits() must be called again with the + * new values. */ -AgcMeanLuminance::AgcMeanLuminance() - : filteredExposure_(0s), luxWarningEnabled_(true), frameCount_(0) +AgcMeanLuminance::AgcMeanLuminance(Configuration &&config) + : filteredExposure_(0s), luxWarningEnabled_(true), frameCount_(0), + relativeLuminanceTarget_(std::move(config.relativeLuminanceTarget)), + constraintModes_(std::move(config.constraintModes)), + exposureModeHelpers_(std::move(config.exposureModeHelpers)) { } AgcMeanLuminance::~AgcMeanLuminance() = default; -int AgcMeanLuminance::parseRelativeLuminanceTarget(const ValueNode &tuningData) -{ - auto &target = tuningData["relativeLuminanceTarget"]; - if (!target) { - relativeLuminanceTarget_ = { { { { 0.0, kDefaultRelativeLuminanceTarget } } } }; - return 0; - } - - auto pwl = target.get(); - if (!pwl) { - LOG(AgcMeanLuminance, Error) - << "Failed to load relative luminance target."; - return -EINVAL; - } - - relativeLuminanceTarget_ = std::move(*pwl); - return 0; -} - -int AgcMeanLuminance::parseConstraint(const ValueNode &modeDict, int32_t id) -{ - for (const auto &[boundName, content] : modeDict.asDict()) { - if (boundName != "upper" && boundName != "lower") { - LOG(AgcMeanLuminance, Warning) - << "Ignoring unknown constraint bound '" << boundName << "'"; - continue; - } - - unsigned int idx = static_cast(boundName == "upper"); - AgcConstraint::Bound bound = static_cast(idx); - double qLo = content["qLo"].get().value_or(0.98); - double qHi = content["qHi"].get().value_or(1.0); - auto yTarget = content["yTarget"].get(); - if (!yTarget) { - LOG(AgcMeanLuminance, Error) - << "Failed to parse yTarget"; - return -EINVAL; - } - - AgcConstraint constraint = { bound, qLo, qHi, std::move(*yTarget) }; - auto &constraints = constraintModes_[id]; - - if (idx) - constraints.push_back(std::move(constraint)); - else - constraints.insert(constraints.begin(), std::move(constraint)); - } - - return 0; -} - -int AgcMeanLuminance::parseConstraintModes(const ValueNode &tuningData) -{ - const ValueNode &constraintModes = tuningData[controls::AeConstraintMode.name()]; - if (constraintModes.isDictionary()) { - for (const auto &[modeName, modeDict] : constraintModes.asDict()) { - auto it = AeConstraintModeNameValueMap.find(modeName); - if (it == AeConstraintModeNameValueMap.end()) { - LOG(AgcMeanLuminance, Warning) - << "Skipping unknown constraint mode '" << modeName << "'"; - continue; - } - - if (!modeDict.isDictionary()) { - LOG(AgcMeanLuminance, Error) - << "Invalid constraint mode '" << modeName << "'"; - return -EINVAL; - } - - int ret = parseConstraint(modeDict, it->second); - if (ret) - return ret; - } - } - - /* - * If the tuning data file contains no constraints then we use the - * default constraint that the IPU3/RkISP1 Agc algorithms were adhering - * to anyway before centralisation; this constraint forces the top 2% of - * the histogram to be at least 0.5. - */ - if (constraintModes_.empty()) { - AgcConstraint constraint = { - AgcConstraint::Bound::Lower, - 0.98, - 1.0, - Pwl({ { { 0.0, 0.5 } } }) - }; - - constraintModes_[controls::AeConstraintModeNormal].push_back(std::move(constraint)); - } - - return 0; -} - -int AgcMeanLuminance::parseExposureModes(const ValueNode &tuningData) -{ - const ValueNode &exposureModes = tuningData[controls::AeExposureMode.name()]; - if (exposureModes.isDictionary()) { - for (const auto &[modeName, modeValues] : exposureModes.asDict()) { - auto it = AeExposureModeNameValueMap.find(modeName); - if (it == AeExposureModeNameValueMap.end()) { - LOG(AgcMeanLuminance, Warning) - << "Skipping unknown exposure mode '" << modeName << "'"; - continue; - } - - if (!modeValues.isDictionary()) { - LOG(AgcMeanLuminance, Error) - << "Invalid exposure mode '" << modeName << "'"; - return -EINVAL; - } - - std::vector exposureTimes = - modeValues["exposureTime"].get>().value_or(utils::defopt); - std::vector gains = - modeValues["gain"].get>().value_or(utils::defopt); - - if (exposureTimes.size() != gains.size()) { - LOG(AgcMeanLuminance, Error) - << "Exposure time and gain array sizes unequal"; - return -EINVAL; - } - - if (exposureTimes.empty()) { - LOG(AgcMeanLuminance, Error) - << "Exposure time and gain arrays are empty"; - return -EINVAL; - } - - std::vector stages; - for (size_t i = 0; i < exposureTimes.size(); i++) { - stages.push_back({ - .exposureTime = std::chrono::microseconds(exposureTimes[i]), - .gain = gains[i], - }); - } - - exposureModeHelpers_.try_emplace(it->second, std::move(stages)); - } - } - - /* - * If we don't have any exposure modes in the tuning data we create an - * ExposureModeHelper using an empty vector of stages. This will result - * in the ExposureModeHelper simply driving the exposure time as high as - * possible before touching gain. - */ - if (exposureModeHelpers_.empty()) - exposureModeHelpers_.try_emplace(controls::AeExposureModeNormal, - std::vector{}); - - return 0; -} - /** * \brief Configure the exposure mode helpers * \param[in] lineDuration The sensor line length @@ -358,76 +190,6 @@ void AgcMeanLuminance::configure(utils::Duration lineDuration, frameCount_ = 0; } -/** - * \brief Parse tuning data for AeConstraintMode and AeExposureMode controls - * \param[in] tuningData the ValueNode representing the tuning data - * - * This function parses tuning data to build the list of allowed values for the - * AeConstraintMode and AeExposureMode controls. Those tuning data must provide - * the data in a specific format; the Agc algorithm's tuning data should contain - * a dictionary called AeConstraintMode containing per-mode setting dictionaries - * with the key being a value from \ref controls::AeConstraintModeNameValueMap. - * The yTarget can either be provided as single value or as array in which case - * it is interpreted as a PWL mapping lux levels to yTarget values. Each mode - * dict may contain either a "lower" or "upper" key or both, for example: - * - * \code{.unparsed} - * algorithms: - * - Agc: - * AeConstraintMode: - * Normal: - * lower: - * qLo: 0.98 - * qHi: 1.0 - * yTarget: 0.5 - * Highlight: - * lower: - * qLo: 0.98 - * qHi: 1.0 - * yTarget: 0.5 - * upper: - * qLo: 0.98 - * qHi: 1.0 - * yTarget: [ 100, 0.8, 20000, 0.5 ] - * - * \endcode - * - * For the AeExposureMode control the data should contain a dictionary called - * AeExposureMode containing per-mode setting dictionaries with the key being a - * value from \ref controls::AeExposureModeNameValueMap. Each mode dict should - * contain an array of exposure times with the key "exposureTime" and an array - * of gain values with the key "gain", in this format: - * - * \code{.unparsed} - * algorithms: - * - Agc: - * AeExposureMode: - * Normal: - * exposureTime: [ 100, 10000, 30000, 60000, 120000 ] - * gain: [ 2.0, 4.0, 6.0, 8.0, 10.0 ] - * Short: - * exposureTime: [ 100, 10000, 30000, 60000, 120000 ] - * gain: [ 2.0, 4.0, 6.0, 8.0, 10.0 ] - * - * \endcode - * - * \return 0 on success or a negative error code - */ -int AgcMeanLuminance::parseTuningData(const ValueNode &tuningData) -{ - int ret; - - ret = parseRelativeLuminanceTarget(tuningData); - if (ret) - return ret; - - ret = parseConstraintModes(tuningData); - if (ret) - return ret; - - return parseExposureModes(tuningData); -} - /** * \brief Set the ExposureModeHelper limits for this class * \param[in] minExposureTime Minimum exposure time to allow @@ -452,12 +214,12 @@ void AgcMeanLuminance::setLimits(utils::Duration minExposureTime, /** * \fn AgcMeanLuminance::constraintModes() - * \brief Get the constraint modes that have been parsed from tuning data + * \brief Get the available constraint modes */ /** * \fn AgcMeanLuminance::exposureModeHelpers() - * \brief Get the ExposureModeHelpers that have been parsed from tuning data + * \brief Get the available ExposureModeHelpers */ /** diff --git a/src/ipa/libipa/agc_mean_luminance.h b/src/ipa/libipa/agc_mean_luminance.h index f799618ffc..c32bf44a2e 100644 --- a/src/ipa/libipa/agc_mean_luminance.h +++ b/src/ipa/libipa/agc_mean_luminance.h @@ -8,13 +8,10 @@ #pragma once #include -#include #include #include -#include "libcamera/internal/value_node.h" - #include "exposure_mode_helper.h" #include "histogram.h" #include "pwl.h" @@ -26,9 +23,6 @@ namespace ipa { class AgcMeanLuminance { public: - AgcMeanLuminance(); - ~AgcMeanLuminance(); - struct AgcConstraint { enum class Bound { Lower = 0, @@ -40,6 +34,15 @@ public: Pwl yTarget; }; + struct Configuration { + Pwl relativeLuminanceTarget; + std::map> constraintModes; + std::map exposureModeHelpers; + }; + + AgcMeanLuminance(Configuration &&config); + ~AgcMeanLuminance(); + struct Traits { virtual ~Traits() = default; virtual double estimateLuminance(double gain) const = 0; @@ -80,20 +83,17 @@ public: double effectiveYTarget(double lux, double exposureCompensation) const; private: - 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 Traits &traits, double yTarget) const; double constraintClampGain(const Params ¶ms, double gain) const; utils::Duration filterExposure(utils::Duration exposureValue); utils::Duration filteredExposure_; mutable bool luxWarningEnabled_; - Pwl relativeLuminanceTarget_; uint64_t frameCount_; std::vector additionalConstraints_; + + Pwl relativeLuminanceTarget_; std::map> constraintModes_; std::map exposureModeHelpers_; }; From patchwork Thu Oct 29 12:58:27 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: 28673 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 8D5E6C3261 for ; Thu, 29 Oct 2026 12:59:11 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 5F1A6690CD; Thu, 29 Oct 2026 13:59:02 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="krmzbWLd"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 8B92069099 for ; Thu, 29 Oct 2026 13:58:49 +0100 (CET) Received: from pb-laptop.local (185.221.143.99.nat.pool.zt.hu [185.221.143.99]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 36E50929 for ; Thu, 29 Oct 2026 13:56:36 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1793278596; bh=D50N2qKVC/zDpkJSbmZlEzFEl4NIebj1gzkXTph8f5s=; h=From:To:Subject:Date:In-Reply-To:References:From; b=krmzbWLdhNyd6Nh1i4jcDBHSe8CNo08mZM+T002WFJ+fq2JCgh9Z94u2qKS/4bCyx fY3wZ/THps50OcZj8Muiyo9frF8w6rWzxEGCXuXy2CVyB4+k6X526lZMPGP52gwnfG uoviUvr0n14E++ASIfqoqIaa9Sg+F+0BcBYP8a8g= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [PATCH v5 07/24] ipa: libipa: agc: Split tuning data fallback handling Date: Thu, 29 Oct 2026 13:58:27 +0100 Message-ID: <20261029125844.170324-8-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261029125844.170324-1-barnabas.pocze@ideasonboard.com> References: <20261029125844.170324-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 separate function whose job is to add the fallback values for the various configuration parameters that were not specified in the tuning file. This allows some simplification of the code by making use of early returns, so do that as well. And additionally, pass only the relevant object to the "sub" parsers instead of the whole thing. This is needed in order to be able to determine what exactly the tuning file contains, before adding the fallback values. Signed-off-by: Barnabás Pőcze Reviewed-by: Jacopo Mondi --- src/ipa/libipa/agc.cpp | 189 +++++++++++++++++++++-------------------- 1 file changed, 97 insertions(+), 92 deletions(-) diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp index 54aa8207d4..229c37b0ea 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -235,13 +235,10 @@ namespace { */ static constexpr double kDefaultRelativeLuminanceTarget = 0.16; -int parseRelativeLuminanceTarget(AgcMeanLuminance::Configuration &config, const ValueNode &tuningData) +int parseRelativeLuminanceTarget(AgcMeanLuminance::Configuration &config, const ValueNode &target) { - auto &target = tuningData["relativeLuminanceTarget"]; - if (!target) { - config.relativeLuminanceTarget = { { { { 0.0, kDefaultRelativeLuminanceTarget } } } }; + if (!target) return 0; - } auto pwl = target.get(); if (!pwl) { @@ -291,30 +288,103 @@ int parseConstraint(AgcMeanLuminance::Configuration &config, const ValueNode &mo return 0; } -int parseConstraintModes(AgcMeanLuminance::Configuration &config, const ValueNode &tuningData) +int parseConstraintModes(AgcMeanLuminance::Configuration &config, const ValueNode &constraintModes) { - const ValueNode &constraintModes = tuningData[controls::AeConstraintMode.name()]; - if (constraintModes.isDictionary()) { - for (const auto &[modeName, modeDict] : constraintModes.asDict()) { - auto it = controls::AeConstraintModeNameValueMap.find(modeName); - if (it == controls::AeConstraintModeNameValueMap.end()) { - LOG(Agc, Warning) - << "Skipping unknown constraint mode '" << modeName << "'"; - continue; - } - - if (!modeDict.isDictionary()) { - LOG(Agc, Error) - << "Invalid constraint mode '" << modeName << "'"; - return -EINVAL; - } - - int ret = parseConstraint(config, modeDict, it->second); - if (ret) - return ret; + if (!constraintModes.isDictionary()) + return 0; + + for (const auto &[modeName, modeDict] : constraintModes.asDict()) { + auto it = controls::AeConstraintModeNameValueMap.find(modeName); + if (it == controls::AeConstraintModeNameValueMap.end()) { + LOG(Agc, Warning) + << "Skipping unknown constraint mode '" << modeName << "'"; + continue; } + + if (!modeDict.isDictionary()) { + LOG(Agc, Error) + << "Invalid constraint mode '" << modeName << "'"; + return -EINVAL; + } + + int ret = parseConstraint(config, modeDict, it->second); + if (ret) + return ret; + } + + return 0; +} + +int parseExposureModes(AgcMeanLuminance::Configuration &config, const ValueNode &exposureModes) +{ + if (!exposureModes.isDictionary()) + return 0; + + for (const auto &[modeName, modeValues] : exposureModes.asDict()) { + auto it = controls::AeExposureModeNameValueMap.find(modeName); + if (it == controls::AeExposureModeNameValueMap.end()) { + LOG(Agc, Warning) + << "Skipping unknown exposure mode '" << modeName << "'"; + continue; + } + + if (!modeValues.isDictionary()) { + LOG(Agc, Error) + << "Invalid exposure mode '" << modeName << "'"; + return -EINVAL; + } + + std::vector exposureTimes = + modeValues["exposureTime"].get>().value_or(utils::defopt); + std::vector gains = + modeValues["gain"].get>().value_or(utils::defopt); + + if (exposureTimes.size() != gains.size()) { + LOG(Agc, Error) + << "Exposure time and gain array sizes unequal"; + return -EINVAL; + } + + if (exposureTimes.empty()) { + LOG(Agc, Error) + << "Exposure time and gain arrays are empty"; + return -EINVAL; + } + + std::vector stages; + for (size_t i = 0; i < exposureTimes.size(); i++) { + stages.push_back({ + .exposureTime = std::chrono::microseconds(exposureTimes[i]), + .gain = gains[i], + }); + } + + config.exposureModeHelpers.try_emplace(it->second, std::move(stages)); } + return 0; +} + +int parseTuningData(AgcMeanLuminance::Configuration &config, const ValueNode &tuningData) +{ + int ret; + + ret = parseRelativeLuminanceTarget(config, tuningData["relativeLuminanceTarget"]); + if (ret) + return ret; + + ret = parseConstraintModes(config, tuningData[controls::AeConstraintMode.name()]); + if (ret) + return ret; + + return parseExposureModes(config, tuningData[controls::AeExposureMode.name()]); +} + +void addFallbacks(AgcMeanLuminance::Configuration &config) +{ + if (config.relativeLuminanceTarget.empty()) + config.relativeLuminanceTarget = { { { { 0.0, kDefaultRelativeLuminanceTarget } } } }; + /* * If the tuning data file contains no constraints then we use the * default constraint that the IPU3/RkISP1 Agc algorithms were adhering @@ -333,56 +403,6 @@ int parseConstraintModes(AgcMeanLuminance::Configuration &config, const ValueNod std::move(constraint)); } - return 0; -} - -int parseExposureModes(AgcMeanLuminance::Configuration &config, const ValueNode &tuningData) -{ - const ValueNode &exposureModes = tuningData[controls::AeExposureMode.name()]; - if (exposureModes.isDictionary()) { - for (const auto &[modeName, modeValues] : exposureModes.asDict()) { - auto it = controls::AeExposureModeNameValueMap.find(modeName); - if (it == controls::AeExposureModeNameValueMap.end()) { - LOG(Agc, Warning) - << "Skipping unknown exposure mode '" << modeName << "'"; - continue; - } - - if (!modeValues.isDictionary()) { - LOG(Agc, Error) - << "Invalid exposure mode '" << modeName << "'"; - return -EINVAL; - } - - std::vector exposureTimes = - modeValues["exposureTime"].get>().value_or(utils::defopt); - std::vector gains = - modeValues["gain"].get>().value_or(utils::defopt); - - if (exposureTimes.size() != gains.size()) { - LOG(Agc, Error) - << "Exposure time and gain array sizes unequal"; - return -EINVAL; - } - - if (exposureTimes.empty()) { - LOG(Agc, Error) - << "Exposure time and gain arrays are empty"; - return -EINVAL; - } - - std::vector stages; - for (size_t i = 0; i < exposureTimes.size(); i++) { - stages.push_back({ - .exposureTime = std::chrono::microseconds(exposureTimes[i]), - .gain = gains[i], - }); - } - - config.exposureModeHelpers.try_emplace(it->second, std::move(stages)); - } - } - /* * If we don't have any exposure modes in the tuning data we create an * ExposureModeHelper using an empty vector of stages. This will result @@ -393,23 +413,6 @@ int parseExposureModes(AgcMeanLuminance::Configuration &config, const ValueNode config.exposureModeHelpers.try_emplace( controls::AeExposureModeNormal, std::vector{}); - - return 0; -} - -int parseTuningData(AgcMeanLuminance::Configuration &config, const ValueNode &tuningData) -{ - int ret; - - ret = parseRelativeLuminanceTarget(config, tuningData); - if (ret) - return ret; - - ret = parseConstraintModes(config, tuningData); - if (ret) - return ret; - - return parseExposureModes(config, tuningData); } } /* namespace */ @@ -557,6 +560,8 @@ int AgcAlgorithm::init(const ValueNode &tuningData, CameraSensorHelper *sensor, if (ret) return ret; + addFallbacks(agcConfig); + impl_.emplace(std::move(agcConfig)); } else { impl_.emplace(); From patchwork Thu Oct 29 12:58:28 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: 28674 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 20DD4C3261 for ; Thu, 29 Oct 2026 12:59:13 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 9D1F1690BE; Thu, 29 Oct 2026 13:59:03 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="sWqPEJAp"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id C491C69091 for ; Thu, 29 Oct 2026 13:58:49 +0100 (CET) Received: from pb-laptop.local (185.221.143.99.nat.pool.zt.hu [185.221.143.99]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 626886DC; Thu, 29 Oct 2026 13:56:36 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1793278596; bh=puvYV1dvRXqZm1F0eYYzJJJ55yOA/ha+dSPQ+T7+Wa0=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=sWqPEJAp8tJiPwGdZBDW+WIUB2/JZlizAYwLWIfA5dsMAHl5rS7v4GQiAiFO/pLQk 37bcBd4XumQ99WyEbMxJdc4M4juXjx+rFRh0QsFQ1epDeuqTNLV6TbSs8tz1ybRUm4 EPQngY8AK88ec+dmtd2+saLlfSf8eqFg78Efzxkw= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi Subject: [PATCH v5 08/24] ipa: libipa: agc: Initialize "Maximize" mode when none given Date: Thu, 29 Oct 2026 13:58:28 +0100 Message-ID: <20261029125844.170324-9-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261029125844.170324-1-barnabas.pocze@ideasonboard.com> References: <20261029125844.170324-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" If the tuning file containts no exposure modes, then automatically initialize a "Maximize" mode instead of "Normal", because that better describes the actual behaviour. Signed-off-by: Barnabás Pőcze Reviewed-by: Jacopo Mondi --- src/ipa/libipa/agc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp index 229c37b0ea..d3ef44bd86 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -411,7 +411,7 @@ void addFallbacks(AgcMeanLuminance::Configuration &config) */ if (config.exposureModeHelpers.empty()) config.exposureModeHelpers.try_emplace( - controls::AeExposureModeNormal, + controls::AeExposureModeMaximize, std::vector{}); } From patchwork Thu Oct 29 12:58:29 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: 28675 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 409D7C3261 for ; Thu, 29 Oct 2026 12:59:14 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 7AAEE690D5; Thu, 29 Oct 2026 13:59:05 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="qZL2W/rU"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 0B0916909F for ; Thu, 29 Oct 2026 13:58:49 +0100 (CET) Received: from pb-laptop.local (185.221.143.99.nat.pool.zt.hu [185.221.143.99]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 975DE929 for ; Thu, 29 Oct 2026 13:56:36 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1793278596; bh=t5Fn66Sg3G8aZ3trFDCe6yWlv+vXx+w4nD9C16q5PgA=; h=From:To:Subject:Date:In-Reply-To:References:From; b=qZL2W/rUmb+1LFU786UsET+35GF7YNBGT5D4KQwemRxsVeNSrwO41LLwdCItwrLKL C1D2A4RoaSCNRExUw3F6ttiSDi2Qscqk1u6yw3u+LbEbuy18Lva7LDnM7nTiBKa9Kt daHoi8p6WImv//DdC4DyhE5bHbpaCGMb8q5+JNwY= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [PATCH v5 09/24] ipa: libipa: exposure_mode_helper: Remove `lastStageGain` Date: Thu, 29 Oct 2026 13:58:29 +0100 Message-ID: <20261029125844.170324-10-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261029125844.170324-1-barnabas.pocze@ideasonboard.com> References: <20261029125844.170324-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" There is no need for a separate variable, `stageGain` already contains the previous stage's gain at the beginning of the loop. Moving the assignment to a later point removes the need for a separate variable. Signed-off-by: Barnabás Pőcze Reviewed-by: Jacopo Mondi --- src/ipa/libipa/exposure_mode_helper.cpp | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/ipa/libipa/exposure_mode_helper.cpp b/src/ipa/libipa/exposure_mode_helper.cpp index 4ec31f54c3..4076d6f385 100644 --- a/src/ipa/libipa/exposure_mode_helper.cpp +++ b/src/ipa/libipa/exposure_mode_helper.cpp @@ -241,12 +241,10 @@ ExposureModeHelper::splitExposure(utils::Duration exposure) const } double stageGain = clampGain(1.0); - double lastStageGain = stageGain; for (const auto &stage : stages_) { utils::Duration stageExposureTime = clampExposureTime(stage.exposureTime, &quantGain); - stageGain = clampGain(stage.gain); /* * We perform the clamping on both exposure time and gain in @@ -256,9 +254,9 @@ ExposureModeHelper::splitExposure(utils::Duration exposure) const * the stage limits are initialised. */ - /* Clamp the gain to lastStageGain and regulate exposureTime. */ - if (stageExposureTime * lastStageGain >= exposure) { - exposureTime = clampExposureTime(exposure / lastStageGain, &quantGain); + /* Clamp the gain to that of the last stage and regulate exposure time. */ + if (stageExposureTime * stageGain >= exposure) { + exposureTime = clampExposureTime(exposure / stageGain, &quantGain); gain = clampGain(exposure / exposureTime, &quantGain2); quantGain *= quantGain2; @@ -266,7 +264,9 @@ ExposureModeHelper::splitExposure(utils::Duration exposure) const exposure / (exposureTime * gain * quantGain) }; } - /* Clamp the exposureTime to stageExposureTime and regulate gain. */ + /* Clamp the exposure time to stageExposureTime and regulate gain. */ + stageGain = clampGain(stage.gain); + if (stageExposureTime * stageGain >= exposure) { exposureTime = stageExposureTime; gain = clampGain(exposure / exposureTime, &quantGain2); @@ -275,8 +275,6 @@ ExposureModeHelper::splitExposure(utils::Duration exposure) const return { exposureTime, gain, quantGain, exposure / (exposureTime * gain * quantGain) }; } - - lastStageGain = stageGain; } /* From patchwork Thu Oct 29 12:58:30 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: 28676 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 43E1BC3302 for ; Thu, 29 Oct 2026 12:59:15 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 54E56690D8; Thu, 29 Oct 2026 13:59:07 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="dQF4iHZ5"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 3ADED690A1 for ; Thu, 29 Oct 2026 13:58:50 +0100 (CET) Received: from pb-laptop.local (185.221.143.99.nat.pool.zt.hu [185.221.143.99]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id CC67EC1 for ; Thu, 29 Oct 2026 13:56:36 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1793278596; bh=s2eYXa2sJAsA/VcCoGE4wBx8DgyT8TcdwhYgdmf4pAM=; h=From:To:Subject:Date:In-Reply-To:References:From; b=dQF4iHZ5/j2kA4kyH3Nfqv4JeXvCazZcd+TPy3Eho3JyZqHFMniKf9LgI82XhNhH1 bfXXSKzlxawSScpkZ/IXWyktn54/HpeL3oDyzthiTEsWuQ7TxegmWwlwf22+yj/hjg IahhA2qQL4RJFN5sDGv/2VaW7qxDhF9BygQHoa6M= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [PATCH v5 10/24] ipa: libipa: exposure_mode_helper: Consider last stage as hard-limit Date: Thu, 29 Oct 2026 13:58:30 +0100 Message-ID: <20261029125844.170324-11-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261029125844.170324-1-barnabas.pocze@ideasonboard.com> References: <20261029125844.170324-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" Currently `splitExposure()` will raise the exposure and analogue gain above what is specified in the last stage, if the desired exposure could not be reached. This behaviour is different from that of the raspberry pi ipa, and also could be surprising to the user / tuning file creator. So make sure that the exposure time and analogue gain is limited by the last stage, in addition to the runtime controlled upper bound. With this change, at least one stage is required, so an assertions are added. And the general agc algorithm is adjusted to provide stages for the fallback "Maximize" mode. Closes: https://gitlab.freedesktop.org/camera/libcamera/-/work_items/362 Signed-off-by: Barnabás Pőcze Reviewed-by: Jacopo Mondi --- src/ipa/libipa/agc.cpp | 7 ++++++- src/ipa/libipa/exposure_mode_helper.cpp | 23 +++++++++++------------ 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp index d3ef44bd86..fd40fbdeb3 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -412,7 +412,12 @@ void addFallbacks(AgcMeanLuminance::Configuration &config) if (config.exposureModeHelpers.empty()) config.exposureModeHelpers.try_emplace( controls::AeExposureModeMaximize, - std::vector{}); + std::vector{ + ExposureModeHelper::Stage{ + .exposureTime = utils::Duration::max(), + .gain = std::numeric_limits::max(), + }, + }); } } /* namespace */ diff --git a/src/ipa/libipa/exposure_mode_helper.cpp b/src/ipa/libipa/exposure_mode_helper.cpp index 4076d6f385..9714cc950e 100644 --- a/src/ipa/libipa/exposure_mode_helper.cpp +++ b/src/ipa/libipa/exposure_mode_helper.cpp @@ -84,6 +84,7 @@ ExposureModeHelper::ExposureModeHelper(std::vector &&stages) : stages_(std::move(stages)), lineDuration_(1us), minExposureTime_(0us), maxExposureTime_(0us), minGain_(0), maxGain_(0), sensorHelper_(nullptr) { + ASSERT(!stages_.empty()); } /** @@ -203,8 +204,8 @@ double ExposureModeHelper::clampGain(double gain, double *quantizationGain) cons * value. * * If no combination of exposure time and gain limits is found that meets the - * required exposure, the helper falls-back to simply maximising the exposure - * time first, followed by analogue gain, followed by digital gain. + * required exposure, then the exposure and gain of the last stage will be used, + * and the digital gain will be increased as necessary. * * During the calculations the gain missed due to quantization is recorded and * returned as quantization gain. The quantization gain is not included in the @@ -219,6 +220,7 @@ ExposureModeHelper::splitExposure(utils::Duration exposure) const { ASSERT(maxExposureTime_); ASSERT(maxGain_); + ASSERT(!stages_.empty()); utils::Duration exposureTime; double gain; @@ -240,11 +242,11 @@ ExposureModeHelper::splitExposure(utils::Duration exposure) const exposure / (exposureTime * gain * quantGain) }; } + utils::Duration stageExposureTime = {}; double stageGain = clampGain(1.0); for (const auto &stage : stages_) { - utils::Duration stageExposureTime = clampExposureTime(stage.exposureTime, - &quantGain); + stageExposureTime = clampExposureTime(stage.exposureTime, &quantGain); /* * We perform the clamping on both exposure time and gain in @@ -265,7 +267,7 @@ ExposureModeHelper::splitExposure(utils::Duration exposure) const } /* Clamp the exposure time to stageExposureTime and regulate gain. */ - stageGain = clampGain(stage.gain); + stageGain = clampGain(stage.gain, &quantGain2); if (stageExposureTime * stageGain >= exposure) { exposureTime = stageExposureTime; @@ -278,14 +280,11 @@ ExposureModeHelper::splitExposure(utils::Duration exposure) const } /* - * From here on all we can do is max out the exposure time, followed by - * the analogue gain. If we still haven't achieved the target we send - * the rest of the exposure time to digital gain. If we were given no - * stages to use then the default stageGain of 1.0 is used so that - * exposure time is maxed before gain is touched at all. + * If no stages could satisfy the desired exposure, then use the last + * stage's exposure time and gain, which is the maximum allowed. */ - exposureTime = clampExposureTime(exposure / stageGain, &quantGain); - gain = clampGain(exposure / exposureTime, &quantGain2); + exposureTime = stageExposureTime; + gain = stageGain; quantGain *= quantGain2; return { exposureTime, gain, quantGain, From patchwork Thu Oct 29 12:58:31 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: 28677 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 25333C3261 for ; Thu, 29 Oct 2026 12:59:16 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 66F9169096; Thu, 29 Oct 2026 13:59:09 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="vYxbka8J"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 6DC59690AA for ; Thu, 29 Oct 2026 13:58:50 +0100 (CET) Received: from pb-laptop.local (185.221.143.99.nat.pool.zt.hu [185.221.143.99]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 094BF6DC; Thu, 29 Oct 2026 13:56:37 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1793278597; bh=pipisRWPchpuzrZjDF9KLbm6S/EWXr0CylrMDGunvKs=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=vYxbka8JtF+5xFmWJE+4663PVvT/+0nURNA6O/1Ut68VWvolddS7x+5qQgDwxgsr+ xlCbu+WOZ30+AbMIGXO/TSN1KJvIJLXMpNKQVzzivLjuwY6nD1wfpT7xrZxpRqdbIH zZMIoDuAfpl2tunsXvP295KjwmyMqwKAWkRm/EsQ= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi Subject: [PATCH v5 11/24] ipa: libipa: agc: Do not report auto controls if not available Date: Thu, 29 Oct 2026 13:58:31 +0100 Message-ID: <20261029125844.170324-12-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261029125844.170324-1-barnabas.pocze@ideasonboard.com> References: <20261029125844.170324-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" If the auto mode is disabled for the session, do not report controls in metadata that are related to controlling the auto algorithm. Signed-off-by: Barnabás Pőcze Reviewed-by: Jacopo Mondi Reviewed-by: Kieran Bingham --- src/ipa/libipa/agc.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp index fd40fbdeb3..0d25b2dc31 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -1130,9 +1130,11 @@ void AgcAlgorithm::fillMetadata(const agc::Session &session, ? controls::AnalogueGainModeAuto : controls::AnalogueGainModeManual); - metadata.set(controls::AeExposureMode, frameContext.exposureMode); - metadata.set(controls::AeConstraintMode, frameContext.constraintMode); - metadata.set(controls::ExposureValue, frameContext.exposureValue); + if (session.autoAllowed) { + metadata.set(controls::AeExposureMode, frameContext.exposureMode); + metadata.set(controls::AeConstraintMode, frameContext.constraintMode); + metadata.set(controls::ExposureValue, frameContext.exposureValue); + } } } /* namespace ipa */ From patchwork Thu Oct 29 12:58:32 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: 28678 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 281F6C3302 for ; Thu, 29 Oct 2026 12:59:17 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 2E97B690DC; Thu, 29 Oct 2026 13:59:11 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="B9XgNsIa"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id B3323690AD for ; Thu, 29 Oct 2026 13:58:50 +0100 (CET) Received: from pb-laptop.local (185.221.143.99.nat.pool.zt.hu [185.221.143.99]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 40FFCC1; Thu, 29 Oct 2026 13:56:37 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1793278597; bh=mwTxCUmATXOr36NhqMELf2367fssb48hTNJ4lF68skQ=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=B9XgNsIapyMkka/Qr1vskt8fWVTO9hF54y31zIJQ1Z1YzHNJ3mGoMFZ9M8ZcjZVtT 5+dOT/MzCXqCFnSY0upG9blkp6AcLU7D9oHi8pjvKXS/u5/dk2vIswGGBXt7f7nhNF onTjZ1mB0C61ILpcycrWb1cioJwD8F/bQt1AGmsA= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Stefan Klug , Jacopo Mondi Subject: [PATCH v5 12/24] ipa: libipa: agc: Keep frame duration limits ordered Date: Thu, 29 Oct 2026 13:58:32 +0100 Message-ID: <20261029125844.170324-13-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261029125844.170324-1-barnabas.pocze@ideasonboard.com> References: <20261029125844.170324-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 frame duration limits from controls are already clamped to the session limits, but that does not prevent a situation where min > max. Use the just computed `state.minFrameDuration` as the lower bound when clamping the max value in order to avoid this possibility. Signed-off-by: Barnabás Pőcze Reviewed-by: Stefan Klug Reviewed-by: Jacopo Mondi --- src/ipa/libipa/agc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp index 0d25b2dc31..9bb0f16f12 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -896,7 +896,7 @@ void AgcAlgorithm::queueRequest(const agc::Session &session, agc::ActiveState &s state.maxFrameDuration = std::clamp( std::chrono::microseconds((*frameDurationLimits).back()), - session.minFrameDuration, session.maxFrameDuration); + state.minFrameDuration, session.maxFrameDuration); } frameContext.minFrameDuration = state.minFrameDuration; frameContext.maxFrameDuration = state.maxFrameDuration; From patchwork Thu Oct 29 12:58:33 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: 28679 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 326CBC3261 for ; Thu, 29 Oct 2026 12:59:18 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id AA8DD690D0; Thu, 29 Oct 2026 13:59:12 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="KIl47Z/R"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id E659A690AE for ; Thu, 29 Oct 2026 13:58:50 +0100 (CET) Received: from pb-laptop.local (185.221.143.99.nat.pool.zt.hu [185.221.143.99]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 817786DC; Thu, 29 Oct 2026 13:56:37 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1793278597; bh=cDy+VLPThZfuvIK8sH1uESXL17aD/iBmZ4iu0WKG29w=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=KIl47Z/RVu3SsCkEMSuaPaiIdeVYKCzTUIpYSMJnPEeGFgyGjaCZGrtwDtvYqCLFs ldVgjismSo/e/hAFtailn4sUyM5HT5HYBIkwZsbcYr0MROu42ppdJMcg8i2zXdCPy6 5rFLL70+rBFaD8CNl/e/bFfkuErAnLA1V3kr4M70= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Stefan Klug , Jacopo Mondi Subject: [PATCH v5 13/24] ipa: libipa: agc: Retrieve `FrameDurationLimits` earlier Date: Thu, 29 Oct 2026 13:58:33 +0100 Message-ID: <20261029125844.170324-14-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261029125844.170324-1-barnabas.pocze@ideasonboard.com> References: <20261029125844.170324-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 frame duration limits will be used to clamp the manually provided exposure time. Handle them earlier so that they are available. Signed-off-by: Barnabás Pőcze Reviewed-by: Stefan Klug Reviewed-by: Jacopo Mondi --- src/ipa/libipa/agc.cpp | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp index 9bb0f16f12..546006e040 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -845,6 +845,20 @@ void AgcAlgorithm::queueRequest(const agc::Session &session, agc::ActiveState &s } } + 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()), + state.minFrameDuration, session.maxFrameDuration); + } + frameContext.minFrameDuration = state.minFrameDuration; + frameContext.maxFrameDuration = state.maxFrameDuration; + const auto &exposure = controls.get(controls::ExposureTime); if (exposure && !state.autoExposureEnabled) { state.manual.exposure = *exposure * 1.0us / session.lineDuration; @@ -886,20 +900,6 @@ void AgcAlgorithm::queueRequest(const agc::Session &session, agc::ActiveState &s 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()), - state.minFrameDuration, session.maxFrameDuration); - } - frameContext.minFrameDuration = state.minFrameDuration; - frameContext.maxFrameDuration = state.maxFrameDuration; } /** From patchwork Thu Oct 29 12:58:34 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: 28680 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 148B4C3302 for ; Thu, 29 Oct 2026 12:59:19 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 88C72690BB; Thu, 29 Oct 2026 13:59:13 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="TDrT4GHy"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 3220569094 for ; Thu, 29 Oct 2026 13:58:51 +0100 (CET) Received: from pb-laptop.local (185.221.143.99.nat.pool.zt.hu [185.221.143.99]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id B9D54C1; Thu, 29 Oct 2026 13:56:37 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1793278597; bh=XE0J7eSsILJSTcdOVewPQoZlHgP8WlaBM4G2eh9pL98=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=TDrT4GHy55R56S/ngtKIxlRDNyfMzh4h0WlGjWjnsQhvEWZ7prQklgP2CBKJC6IUH wHV5pEp/EyGVKfl7FiMleJmy4YZ1iPhZiXfC219p562gJk4O3wkb/2bPipM2m0Z7OS IfMqVIEuG7HMPTrtP3VNQodWd3C3fBBO4aqgZXU4= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Kieran Bingham Subject: [PATCH v5 14/24] ipa: libipa: agc: Report `FrameDurationLimits` Date: Thu, 29 Oct 2026 13:58:34 +0100 Message-ID: <20261029125844.170324-15-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261029125844.170324-1-barnabas.pocze@ideasonboard.com> References: <20261029125844.170324-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" `FrameDurationLimits` can be reported in metadata, to signal the exact limits that were used when processing the frame. This was missing from the agc algorithm, so add it. Signed-off-by: Barnabás Pőcze Reviewed-by: Jacopo Mondi Reviewed-by: Kieran Bingham --- src/ipa/libipa/agc.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp index 546006e040..2e7484c91d 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -1123,6 +1123,9 @@ void AgcAlgorithm::fillMetadata(const agc::Session &session, metadata.set(controls::ExposureTime, utils::Duration(session.lineDuration * frameContext.exposure).get()); metadata.set(controls::FrameDuration, frameContext.frameDuration.get()); + metadata.set(controls::FrameDurationLimits, + { static_cast(frameContext.minFrameDuration.get()), + static_cast(frameContext.maxFrameDuration.get()) }); metadata.set(controls::ExposureTimeMode, frameContext.autoExposureEnabled ? controls::ExposureTimeModeAuto : controls::ExposureTimeModeManual); From patchwork Thu Oct 29 12:58:35 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: 28682 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 AA2AAC3302 for ; Thu, 29 Oct 2026 12:59:21 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 0AE94690E5; Thu, 29 Oct 2026 13:59:15 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="FtuFR/z1"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 691E56909C for ; Thu, 29 Oct 2026 13:58:51 +0100 (CET) Received: from pb-laptop.local (185.221.143.99.nat.pool.zt.hu [185.221.143.99]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 01DA16DC; Thu, 29 Oct 2026 13:56:37 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1793278598; bh=cTqVobjjyhQ5autsM96X7HulGwTI5ygoIbdMnEMlJjg=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=FtuFR/z1KX0wNTrKKKt84V/j8oTPbPSKglCK2MEmAR0GxfD63S11EBCbCptGzOtRa UzSmV8QaIm7fl+Mk5cqdqyIdNgLsojgZgGNHaqpYZ4xnOJ4Gk3nJliryYdIAAnwxQI KsNC4bZq8XST59tFMhcgquJvhSmLycc61G458+1g= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Kieran Bingham Subject: [PATCH v5 15/24] ipa: libipa: agc: Calculate vblank and frame duration sooner Date: Thu, 29 Oct 2026 13:58:35 +0100 Message-ID: <20261029125844.170324-16-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261029125844.170324-1-barnabas.pocze@ideasonboard.com> References: <20261029125844.170324-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" Calculating the vblank and frame duration is problematic in `process()` because at the moment it is calculated for an already finished frame based on the new suggested exposure time. Instead, move the calculation to `prepare()` where the frame's exposure and gain are finalized. Signed-off-by: Barnabás Pőcze Reviewed-by: Jacopo Mondi Reviewed-by: Kieran Bingham --- src/ipa/ipu3/algorithms/agc.cpp | 2 +- src/ipa/libipa/agc.cpp | 36 ++++++++++++++--------------- src/ipa/libipa/agc.h | 6 ++--- src/ipa/mali-c55/algorithms/agc.cpp | 2 +- src/ipa/rkisp1/algorithms/agc.cpp | 2 +- src/ipa/softisp/algorithms/agc.cpp | 2 +- 6 files changed, 24 insertions(+), 26 deletions(-) diff --git a/src/ipa/ipu3/algorithms/agc.cpp b/src/ipa/ipu3/algorithms/agc.cpp index ccf65da317..afe80dc062 100644 --- a/src/ipa/ipu3/algorithms/agc.cpp +++ b/src/ipa/ipu3/algorithms/agc.cpp @@ -111,7 +111,7 @@ 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); + agc_.prepare(context.configuration.agc, context.activeState.agc, frameContext.agc); } Histogram Agc::parseStatistics(const ipu3_uapi_stats_3a *stats, diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp index 2e7484c91d..3090a0073b 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -904,6 +904,7 @@ void AgcAlgorithm::queueRequest(const agc::Session &session, agc::ActiveState &s /** * \brief Prepare a frame + * \param[in] session The agc session configuration * \param[in] state The agc active state * \param[in] frameContext The agc frame context * @@ -913,11 +914,10 @@ void AgcAlgorithm::queueRequest(const agc::Session &session, agc::ActiveState &s * \ref agc::FrameContext::gain "frameContext.gain" will be finalized * and may be used by the caller (see agc::prepareControls()). * - * \todo Finalize \ref agc::FrameContext::vblank "frameContext.vblank" as well - * * \sa Algorithm::prepare() */ -void AgcAlgorithm::prepare(agc::ActiveState &state, agc::FrameContext &frameContext) +void AgcAlgorithm::prepare(const agc::Session &session, agc::ActiveState &state, + agc::FrameContext &frameContext) { uint32_t activeAutoExposure = state.automatic.exposure; double activeAutoGain = state.automatic.gain; @@ -948,6 +948,8 @@ void AgcAlgorithm::prepare(agc::ActiveState &state, agc::FrameContext &frameCont } frameContext.yTarget = state.automatic.yTarget; + + processFrameDuration(session, frameContext); } /** @@ -978,7 +980,6 @@ void AgcAlgorithm::process(const agc::Session &session, agc::ActiveState &state, ControlList &metadata) { if (!params) { - processFrameDuration(session, frameContext, frameContext.minFrameDuration); fillMetadata(session, frameContext, metadata); return; } @@ -1082,13 +1083,6 @@ void AgcAlgorithm::process(const agc::Session &session, agc::ActiveState &state, << "quantization-gain: " << state.automatic.quantizationGain << ", " << "digital-gain: " << state.automatic.digitalGain; - /* - * Expand the target frame duration so that we do not run faster than - * the minimum frame duration when we have short exposures. - */ - processFrameDuration(session, frameContext, - std::max(frameContext.minFrameDuration, newExposureTime)); - fillMetadata(session, frameContext, metadata); } @@ -1096,22 +1090,26 @@ void AgcAlgorithm::process(const agc::Session &session, agc::ActiveState &state, * \brief Process frame duration and compute vblank * \param[in] session The session parameters * \param[in] frameContext The current frame context - * \param[in] frameDuration The target frame duration * - * Compute and populate vblank from the target frame duration. + * Compute and populate vblank for \a frameContext. */ void AgcAlgorithm::processFrameDuration(const agc::Session &session, - agc::FrameContext &frameContext, - utils::Duration frameDuration) + agc::FrameContext &frameContext) { const utils::Duration &lineDuration = session.lineDuration; - frameContext.vblank = - (frameDuration / lineDuration) - session.sensor.outputSize.height; + /* + * Expand the target frame duration so that we do not run faster than + * the minimum frame duration when we have short exposures. + */ + const auto frameLength = std::max( + frameContext.minFrameDuration / lineDuration, + frameContext.exposure); + + frameContext.vblank = frameLength - session.sensor.outputSize.height; /* Update frame duration accounting for line length quantization. */ - frameContext.frameDuration = - (session.sensor.outputSize.height + frameContext.vblank) * lineDuration; + frameContext.frameDuration = frameLength * lineDuration; } void AgcAlgorithm::fillMetadata(const agc::Session &session, diff --git a/src/ipa/libipa/agc.h b/src/ipa/libipa/agc.h index a5700d2625..4914c9cee2 100644 --- a/src/ipa/libipa/agc.h +++ b/src/ipa/libipa/agc.h @@ -146,7 +146,8 @@ public: void queueRequest(const agc::Session &session, agc::ActiveState &state, agc::FrameContext &frameContext, const ControlList &controls); - void prepare(agc::ActiveState &state, agc::FrameContext &frameContext); + void prepare(const agc::Session &session, agc::ActiveState &state, + agc::FrameContext &frameContext); void process(const agc::Session &session, agc::ActiveState &state, agc::FrameContext &frameContext, std::optional &¶ms, @@ -154,8 +155,7 @@ public: private: void processFrameDuration(const agc::Session &session, - agc::FrameContext &frameContext, - utils::Duration frameDuration); + agc::FrameContext &frameContext); void fillMetadata(const agc::Session &session, const agc::FrameContext &frameContext, ControlList &metadata); diff --git a/src/ipa/mali-c55/algorithms/agc.cpp b/src/ipa/mali-c55/algorithms/agc.cpp index 2c747da891..5684b4df95 100644 --- a/src/ipa/mali-c55/algorithms/agc.cpp +++ b/src/ipa/mali-c55/algorithms/agc.cpp @@ -207,7 +207,7 @@ void Agc::fillWeightsArrayBuffer(MaliC55Params *params, const enum MaliC55Blocks void Agc::prepare(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, MaliC55Params *params) { - agc_.prepare(context.activeState.agc, frameContext.agc); + agc_.prepare(context.configuration.agc, context.activeState.agc, frameContext.agc); if (frame > 0) return; diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp index 1c323ede87..d82bb2ece2 100644 --- a/src/ipa/rkisp1/algorithms/agc.cpp +++ b/src/ipa/rkisp1/algorithms/agc.cpp @@ -209,7 +209,7 @@ void Agc::queueRequest(IPAContext &context, void Agc::prepare(IPAContext &context, const uint32_t frame, IPAFrameContext &frameContext, RkISP1Params *params) { - agc_.prepare(context.activeState.agc, frameContext.agc); + agc_.prepare(context.configuration.agc, context.activeState.agc, frameContext.agc); if (context.configuration.compress.supported) { frameContext.compress.enable = true; diff --git a/src/ipa/softisp/algorithms/agc.cpp b/src/ipa/softisp/algorithms/agc.cpp index ea234bc2ae..7d2a53b385 100644 --- a/src/ipa/softisp/algorithms/agc.cpp +++ b/src/ipa/softisp/algorithms/agc.cpp @@ -74,7 +74,7 @@ void Agc::queueRequest(IPAContext &context, [[maybe_unused]] const uint32_t fram void Agc::prepare(IPAContext &context, [[maybe_unused]] const uint32_t frame, IPAFrameContext &frameContext, [[maybe_unused]] DebayerParams *params) { - agc_.prepare(context.activeState.agc, frameContext.agc); + agc_.prepare(context.configuration.agc, context.activeState.agc, frameContext.agc); } void Agc::process(IPAContext &context, From patchwork Thu Oct 29 12:58:36 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: 28681 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 0C2A8C3261 for ; Thu, 29 Oct 2026 12:59:20 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 50F25690CF; Thu, 29 Oct 2026 13:59:14 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="BDGNg7A6"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 91BEF6909A for ; Thu, 29 Oct 2026 13:58:51 +0100 (CET) Received: from pb-laptop.local (185.221.143.99.nat.pool.zt.hu [185.221.143.99]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 3CC35802 for ; Thu, 29 Oct 2026 13:56:38 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1793278598; bh=aosIxHz11/acP5I932FZmj7MF3kk/2I0vszwT2uLjH8=; h=From:To:Subject:Date:In-Reply-To:References:From; b=BDGNg7A6knaSHOrF70tW7rEJ1jRjHU9nPIqfYek6Vsbh61LzLc1q862mB6sA5wRSM XfJ9bB/tjplbwoybY+BVvlq93avGU+9vG8arZijS8Wvk4Fn9Evmll5jDyR1QuXy/Qz MZ87sIAmwdnwVefV3bQG6FSHuTTg0B7RLeLFSP70= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [PATCH v5 16/24] ipa: libipa: agc: Better respect frame duration limits Date: Thu, 29 Oct 2026 13:58:36 +0100 Message-ID: <20261029125844.170324-17-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261029125844.170324-1-barnabas.pocze@ideasonboard.com> References: <20261029125844.170324-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" Only frame durations that are multiples of the line duration are possible, and currently it is possible that a smaller frame duration is selected even if a frame duration satisfying the requested limits is available. This is caused by the float -> integer truncation during the calculation. For (an unrealistic) example, if the exposure time is 24ms, the min frame duration is 33ms, and the line duration is 2ms, then 32ms will be used as the frame duration even if e.g. 34 would be allowed by the max frame duration. So round the result to ensure that if not prevented by the max frame duration then a value that satisfies the limits is selected. Signed-off-by: Barnabás Pőcze Reviewed-by: Jacopo Mondi --- src/ipa/libipa/agc.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp index 3090a0073b..05ca0ad2d0 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -1103,7 +1103,8 @@ void AgcAlgorithm::processFrameDuration(const agc::Session &session, * the minimum frame duration when we have short exposures. */ const auto frameLength = std::max( - frameContext.minFrameDuration / lineDuration, + std::min(std::ceil(frameContext.minFrameDuration / lineDuration), + frameContext.maxFrameDuration / lineDuration), frameContext.exposure); frameContext.vblank = frameLength - session.sensor.outputSize.height; From patchwork Thu Oct 29 12:58:37 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: 28683 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 AC377C3316 for ; Thu, 29 Oct 2026 12:59:22 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 79985690E8; Thu, 29 Oct 2026 13:59:16 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="UzSeLcdt"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id D6E4469096 for ; Thu, 29 Oct 2026 13:58:51 +0100 (CET) Received: from pb-laptop.local (185.221.143.99.nat.pool.zt.hu [185.221.143.99]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 6C247929; Thu, 29 Oct 2026 13:56:38 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1793278598; bh=HAsLmGO1qKPP+2v3+3IzoBQISTzEGoJbld6G2Zb0x4s=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=UzSeLcdtOtHPddqKagqljdR0/X21gXVHgGGeHrJyUUR+SHDd/UE/8GN8t440iaF0C xKcgvD6sFnW7gEykgC5yPnww+bFFJZ2YmTmAPkNyU5y8Dbq7MrjaejMXPH3iJnkN7+ xKrViz/52PC5l/+CcY0tLluhvWokt4fVoXxLFgRQ= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Stefan Klug , Jacopo Mondi Subject: [PATCH v5 17/24] ipa: libipa: agc: Take parameters from active state for calculation Date: Thu, 29 Oct 2026 13:58:37 +0100 Message-ID: <20261029125844.170324-18-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261029125844.170324-1-barnabas.pocze@ideasonboard.com> References: <20261029125844.170324-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 `frameContext` is of an already completed frame, its exposure/gain/etc is of no real concern for future frames, so take the most recent settings from the active state. This is still not ideal, but better than the previous status quo. Signed-off-by: Barnabás Pőcze Reviewed-by: Stefan Klug Reviewed-by: Jacopo Mondi --- src/ipa/libipa/agc.cpp | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp index 05ca0ad2d0..e84c0ea479 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -997,24 +997,22 @@ void AgcAlgorithm::process(const agc::Session &session, agc::ActiveState &state, double minAnalogueGain; double maxAnalogueGain; - /* \todo This uses the configuration from an already completed frame. */ - - if (frameContext.autoExposureEnabled) { + if (state.autoExposureEnabled) { minExposureTime = session.minExposureTime; - maxExposureTime = std::clamp(frameContext.maxFrameDuration, + maxExposureTime = std::clamp(state.maxFrameDuration, session.minExposureTime, session.maxExposureTime); } else { - minExposureTime = lineDuration * frameContext.exposure; + minExposureTime = lineDuration * state.manual.exposure; maxExposureTime = minExposureTime; } - if (frameContext.autoGainEnabled) { + if (state.autoGainEnabled) { minAnalogueGain = session.minAnalogueGain; maxAnalogueGain = session.maxAnalogueGain; } else { - minAnalogueGain = frameContext.gain; - maxAnalogueGain = frameContext.gain; + minAnalogueGain = state.manual.gain; + maxAnalogueGain = state.manual.gain; } std::visit(utils::overloaded{ @@ -1060,10 +1058,10 @@ void AgcAlgorithm::process(const agc::Session &session, agc::ActiveState &state, .traits = params->traits, .yHist = params->yHist, .effectiveExposureValue = effectiveExposureValue, - .constraintModeIndex = frameContext.constraintMode, - .exposureModeIndex = frameContext.exposureMode, + .constraintModeIndex = state.constraintMode, + .exposureModeIndex = state.exposureMode, .lux = params->lux, - .exposureCompensation = std::pow(2.0, frameContext.exposureValue), + .exposureCompensation = std::pow(2.0, state.exposureValue), }); /* Update the estimated exposure and gain. */ From patchwork Thu Oct 29 12:58:38 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: 28684 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 DDFAFC3261 for ; Thu, 29 Oct 2026 12:59:23 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 9E59C690D2; Thu, 29 Oct 2026 13:59:18 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="mACE3Cuh"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 1D685690A7 for ; Thu, 29 Oct 2026 13:58:52 +0100 (CET) Received: from pb-laptop.local (185.221.143.99.nat.pool.zt.hu [185.221.143.99]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id A8826802; Thu, 29 Oct 2026 13:56:38 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1793278598; bh=SsdYBB27Pc8utWCQ61mRxX7aV7fTOgyxnu/VsEOMQ7Y=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=mACE3CuhexPGi75qu9EPinqghOf1fW3vLdI10pJRmJcXlOL0Dwfef/YykHVn4K1UK PPFhJv2o+Z/J0mHjd+2km4sC+BlcsO3Nqj06FhNJ90uAACtC0/gMHFQgv1qlXGwuTj ukZb2Pm60PAOewvwd3ofIypvn3Zup0eE9lSKrsIo= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Kieran Bingham Subject: [PATCH v5 18/24] libcamera: pipeline: Set vblank on more platforms Date: Thu, 29 Oct 2026 13:58:38 +0100 Message-ID: <20261029125844.170324-19-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261029125844.170324-1-barnabas.pocze@ideasonboard.com> References: <20261029125844.170324-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 common `AgcAlgorithm` provides the desired vblank value the given frame, so adjust `agc::prepareControls()` accordingly, and propagate the vblank value to the sensors in the pipeline handlers. Signed-off-by: Barnabás Pőcze Reviewed-by: Jacopo Mondi Reviewed-by: Kieran Bingham --- src/ipa/ipu3/ipu3.cpp | 3 +-- src/ipa/libipa/agc.cpp | 11 +++++------ src/ipa/libipa/agc.h | 9 +++++---- src/ipa/mali-c55/mali-c55.cpp | 3 +-- src/ipa/rkisp1/rkisp1.cpp | 4 +--- src/ipa/softisp/softisp.cpp | 3 +-- src/libcamera/pipeline/ipu3/ipu3.cpp | 1 + src/libcamera/pipeline/mali-c55/mali-c55.cpp | 1 + src/libcamera/pipeline/simple/simple.cpp | 1 + 9 files changed, 17 insertions(+), 19 deletions(-) diff --git a/src/ipa/ipu3/ipu3.cpp b/src/ipa/ipu3/ipu3.cpp index 30d52cfcf9..e4333539af 100644 --- a/src/ipa/ipu3/ipu3.cpp +++ b/src/ipa/ipu3/ipu3.cpp @@ -551,8 +551,7 @@ void IPAIPU3::setControls(unsigned int frame) IPAFrameContext &frameContext = context_.frameContexts.get(frame); ControlList ctrls(context_.sensorControls); - agc::prepareControls(ctrls, context_.camHelper.get(), - frameContext.agc.exposure, frameContext.agc.gain); + agc::prepareControls(ctrls, context_.camHelper.get(), frameContext.agc); ControlList lensCtrls(lensCtrls_); lensCtrls.set(V4L2_CID_FOCUS_ABSOLUTE, diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp index e84c0ea479..2386d9a151 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -58,15 +58,14 @@ namespace agc { /** * \fn prepareControls(ControlList &controls, const CameraSensorHelper *sensor, - * uint32_t exposure, double gain) + * const FrameContext &frameContext) * \param[out] controls The controls list to populate * \param[in] sensor The CameraSensorHelper - * \param[in] exposure The exposure (in lines) - * \param[in] gain The analogue gain + * \param[in] frameContext The agc frame context * - * This function sets \a V4L2_CID_EXPOSURE and \a V4L2_CID_ANALOGUE_GAIN - * in \a controls. The gain is mapped to the gain code if \a sensor is provided, - * otherwise the gain value will be used directly. + * This function sets \a V4L2_CID_EXPOSURE, \a V4L2_CID_ANALOGUE_GAIN, and + * \a V4L2_CID_VBLANK in \a controls. The gain is mapped to the gain code + * if \a sensor is provided, otherwise the gain value will be used directly. */ /** diff --git a/src/ipa/libipa/agc.h b/src/ipa/libipa/agc.h index 4914c9cee2..3ca36aea64 100644 --- a/src/ipa/libipa/agc.h +++ b/src/ipa/libipa/agc.h @@ -107,13 +107,14 @@ extractControls(const ControlList &controls, const CameraSensorHelper *sensor) inline void prepareControls(ControlList &controls, const CameraSensorHelper *sensor, - uint32_t exposure, double gain) + const FrameContext &frameContext) { - controls.set(V4L2_CID_EXPOSURE, static_cast(exposure)); + controls.set(V4L2_CID_EXPOSURE, static_cast(frameContext.exposure)); controls.set(V4L2_CID_ANALOGUE_GAIN, static_cast(sensor - ? sensor->gainCode(gain) - : static_cast(gain))); + ? sensor->gainCode(frameContext.gain) + : static_cast(frameContext.gain))); + controls.set(V4L2_CID_VBLANK, static_cast(frameContext.vblank)); } } /* namespace agc */ diff --git a/src/ipa/mali-c55/mali-c55.cpp b/src/ipa/mali-c55/mali-c55.cpp index 9a2918cf2c..9541417c96 100644 --- a/src/ipa/mali-c55/mali-c55.cpp +++ b/src/ipa/mali-c55/mali-c55.cpp @@ -134,8 +134,7 @@ int IPAMaliC55::init(const IPASettings &settings, const IPAConfigInfo &ipaConfig void IPAMaliC55::setControls(const IPAFrameContext &frameContext) { ControlList ctrls(context_.sensorControls); - agc::prepareControls(ctrls, context_.camHelper.get(), - frameContext.agc.exposure, frameContext.agc.gain); + agc::prepareControls(ctrls, context_.camHelper.get(), frameContext.agc); setSensorControls.emit(ctrls); } diff --git a/src/ipa/rkisp1/rkisp1.cpp b/src/ipa/rkisp1/rkisp1.cpp index 79ab7338c1..cefaf40aee 100644 --- a/src/ipa/rkisp1/rkisp1.cpp +++ b/src/ipa/rkisp1/rkisp1.cpp @@ -367,9 +367,7 @@ void IPARkISP1::setControls(unsigned int frame) << ", gain " << frameContext.agc.gain << ", vblank " << vblank; ControlList ctrls(context_.sensorControls); - agc::prepareControls(ctrls, context_.camHelper.get(), - exposure, frameContext.agc.gain); - ctrls.set(V4L2_CID_VBLANK, static_cast(vblank)); + agc::prepareControls(ctrls, context_.camHelper.get(), frameContext.agc); setSensorControls.emit(frame, ctrls); } diff --git a/src/ipa/softisp/softisp.cpp b/src/ipa/softisp/softisp.cpp index 00249a3004..e35f077378 100644 --- a/src/ipa/softisp/softisp.cpp +++ b/src/ipa/softisp/softisp.cpp @@ -254,8 +254,7 @@ void IPASoftIsp::processStats(const uint32_t frame, metadataReady.emit(frame, metadata); ControlList ctrls(context_.sensorControls); - agc::prepareControls(ctrls, context_.camHelper.get(), - frameContext.agc.exposure, frameContext.agc.gain); + agc::prepareControls(ctrls, context_.camHelper.get(), frameContext.agc); setSensorControls.emit(ctrls); } diff --git a/src/libcamera/pipeline/ipu3/ipu3.cpp b/src/libcamera/pipeline/ipu3/ipu3.cpp index 14cab9e555..46af776ee9 100644 --- a/src/libcamera/pipeline/ipu3/ipu3.cpp +++ b/src/libcamera/pipeline/ipu3/ipu3.cpp @@ -1084,6 +1084,7 @@ int PipelineHandlerIPU3::registerCameras() std::unordered_map params = { { V4L2_CID_ANALOGUE_GAIN, { delays.gainDelay, false } }, { V4L2_CID_EXPOSURE, { delays.exposureDelay, false } }, + { V4L2_CID_VBLANK, { delays.vblankDelay, true } }, }; data->delayedCtrls_ = diff --git a/src/libcamera/pipeline/mali-c55/mali-c55.cpp b/src/libcamera/pipeline/mali-c55/mali-c55.cpp index 73a03373c8..8225472ce8 100644 --- a/src/libcamera/pipeline/mali-c55/mali-c55.cpp +++ b/src/libcamera/pipeline/mali-c55/mali-c55.cpp @@ -1909,6 +1909,7 @@ bool PipelineHandlerMaliC55::registerMemoryInputCamera(MediaLink *link) std::unordered_map params = { { V4L2_CID_ANALOGUE_GAIN, { delays.gainDelay, false } }, { V4L2_CID_EXPOSURE, { delays.exposureDelay, false } }, + { V4L2_CID_VBLANK, { delays.vblankDelay, true } }, }; data->delayedCtrls_ = diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp index 38e94a1591..8a57e59129 100644 --- a/src/libcamera/pipeline/simple/simple.cpp +++ b/src/libcamera/pipeline/simple/simple.cpp @@ -569,6 +569,7 @@ SimpleCameraData::SimpleCameraData(SimplePipelineHandler *pipe, std::unordered_map params = { { V4L2_CID_ANALOGUE_GAIN, { delays.gainDelay, false } }, { V4L2_CID_EXPOSURE, { delays.exposureDelay, false } }, + { V4L2_CID_VBLANK, { delays.vblankDelay, true } }, }; delayedCtrls_ = std::make_unique(sensor_->device(), params); From patchwork Thu Oct 29 12:58:39 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: 28685 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 B0D78C3302 for ; Thu, 29 Oct 2026 12:59:24 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 311D9690A1; Thu, 29 Oct 2026 13:59:21 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="mVKMYDfK"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 5131D690AB for ; Thu, 29 Oct 2026 13:58:52 +0100 (CET) Received: from pb-laptop.local (185.221.143.99.nat.pool.zt.hu [185.221.143.99]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id E3EBAC22; Thu, 29 Oct 2026 13:56:38 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1793278599; bh=CzOWrXdueKBirLoyNQaHkLbUzt7qWerZ3JZnj5cgUvI=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=mVKMYDfKQ/7ShLDyq7gcxMujwJQ7bTQ4kcmgUzwWneDh0xE5qTy8PEzjo+/SJU3BJ wGNSm1jGaPyGRZTIXIPaQiWJOTlMpaKt882hqSWokmcHrNaWAjKFbAwJ1Wg1ljYGV1 8/DByjMa3Ya5HMWGIKqjlJjusvou7j/as2Jzx2r4= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Kieran Bingham Subject: [PATCH v5 19/24] ipa: libipa: agc: Rework frame duration limit calculation Date: Thu, 29 Oct 2026 13:58:39 +0100 Message-ID: <20261029125844.170324-20-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261029125844.170324-1-barnabas.pocze@ideasonboard.com> References: <20261029125844.170324-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 frame duration is calculated as `frameHeight * lineDuration`, but it was effectively repeating the line duration calculation, but with microsecond precision. Instead, simply reuse the already calculated line duration, and use the already calculated min/max values in the provided `IPACameraSensorInfo`. Signed-off-by: Barnabás Pőcze Reviewed-by: Jacopo Mondi Reviewed-by: Kieran Bingham --- src/ipa/libipa/agc.cpp | 31 ++++++++----------------------- 1 file changed, 8 insertions(+), 23 deletions(-) diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp index 2386d9a151..59362409d8 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -642,25 +642,6 @@ int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state, << "line-duration: " << session.lineDuration << ", " << "sensor-output: " << session.sensor.outputSize; - /* - * Compute the frame duration limits. - * - * The frame length is computed assuming a fixed line length combined - * with the vertical frame sizes. - */ - - 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 = static_cast(lineLength) * frameHeights[i]; - frameDurations[i] = frameSize * 1000000U / config.sensorInfo.pixelRate; - } /* * When the AGC computes the new exposure values for a frame, it needs @@ -674,8 +655,8 @@ int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state, session.minAnalogueGain = minGain; session.maxAnalogueGain = maxGain; session.defAnalogueGain = defGain; - session.minFrameDuration = std::chrono::microseconds(frameDurations[0]); - session.maxFrameDuration = std::chrono::microseconds(frameDurations[1]); + session.minFrameDuration = config.sensorInfo.minFrameLength * session.lineDuration; + session.maxFrameDuration = config.sensorInfo.maxFrameLength * session.lineDuration; /* Configure the default exposure and gain. */ state = {}; @@ -714,8 +695,12 @@ int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state, static_cast(defExposure * lineDurationUs), }; config.ctrlMap[&controls::FrameDurationLimits] = ControlInfo{ - frameDurations[0], frameDurations[1], - std::span{ { frameDurations[0], frameDurations[1] } }, + static_cast(session.minFrameDuration.get()), + static_cast(session.maxFrameDuration.get()), + std::span{ { + static_cast(state.minFrameDuration.get()), + static_cast(state.maxFrameDuration.get()), + } }, }; const auto add = [&](const ControlId &cid, const auto &automatic, const auto &manual) { From patchwork Thu Oct 29 12:58:40 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: 28686 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 E28C2C3316 for ; Thu, 29 Oct 2026 12:59:25 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 3BD78690EF; Thu, 29 Oct 2026 13:59:23 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="gzw93r8H"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 8DD1E690B7 for ; Thu, 29 Oct 2026 13:58:52 +0100 (CET) Received: from pb-laptop.local (185.221.143.99.nat.pool.zt.hu [185.221.143.99]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 2838BC1; Thu, 29 Oct 2026 13:56:39 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1793278599; bh=V2/+BiuG6GdM1Uq1M/WBs2tijATo00dJ4mKY0xvtclc=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=gzw93r8H+pNOt/YYNCgMr3xubdgd12D6NczSyOlQoq8nDutElsGHb+F/j58wgsWFo V/4Wlmx2yht+3H//lMPxgB6HaxiThZDMduVebVvL+O6lHfCiW/zSZ5xa7Y7bSWt+un +e8RVlwwgX/bZqLXKw0QwGPa+y2ItV1ZltBUSIMk= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi , Kieran Bingham Subject: [PATCH v5 20/24] ipa: camera_sensor_helper: Introduce exposure margin Date: Thu, 29 Oct 2026 13:58:40 +0100 Message-ID: <20261029125844.170324-21-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261029125844.170324-1-barnabas.pocze@ideasonboard.com> References: <20261029125844.170324-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" From: Jacopo Mondi There is a margin between the maximum achievable shutter time and the frame length. The value is sensor-dependent and should be used to adjust the maximum achievable exposure time whenever the frame length changes. Introduce in the CameraSensorHelper the notion of exposureMargin_ as the minimum difference in number of lines between the current frame length and the maximum exposure time. This feature is already implemented in the RPi CamHelper class hierarchy with the name of frameIntegrationDiff. Populate the CameraSensorHelper instances with an exposureMargin_. The value of the exposure margin comes from the mainline driver version of each sensor, and it has been compared with the frameIntegrationDiff_ value in the rpi camera helpers. Signed-off-by: Jacopo Mondi Signed-off-by: Barnabás Pőcze Reviewed-by: Kieran Bingham --- src/ipa/libipa/camera_sensor_helper.cpp | 68 +++++++++++++++++++++++++ src/ipa/libipa/camera_sensor_helper.h | 2 + 2 files changed, 70 insertions(+) diff --git a/src/ipa/libipa/camera_sensor_helper.cpp b/src/ipa/libipa/camera_sensor_helper.cpp index 359040ec74..aee768a981 100644 --- a/src/ipa/libipa/camera_sensor_helper.cpp +++ b/src/ipa/libipa/camera_sensor_helper.cpp @@ -76,6 +76,16 @@ namespace ipa { * \return The black level of the sensor, or std::nullopt if not known */ +/** + * \fn CameraSensorHelper::exposureMargin() + * \brief Fetch the exposure margin of the sensor + * + * This function returns the exposure margin of the sensor, in lines, which + * is the minimum difference between the integration time and frame length. + * + * \return The exposure margin of the sensor, or std::nullopt if not known + */ + /** * \brief Compute gain code from the analogue gain absolute value * \param[in] gain The real gain to pass @@ -229,6 +239,12 @@ double CameraSensorHelper::quantizeGain(double _gain, double *quantizationGain) * sensor specific. Use this variable to store the values at init time. */ +/** + * \var CameraSensorHelper::exposureMargin_ + * \brief The smallest margin between the integration time and the frame length + * in lines + */ + /** * \class CameraSensorHelperFactoryBase * \brief Base class for camera sensor helper factories @@ -387,6 +403,7 @@ public: { /* Power-on default value: 168 at 12bits. */ blackLevel_ = 2688; + exposureMargin_ = 4; } uint32_t gainCode(double gain) const override @@ -473,6 +490,11 @@ REGISTER_CAMERA_SENSOR_HELPER("ar0144", CameraSensorHelperAr0144) class CameraSensorHelperAr0521 : public CameraSensorHelper { public: + CameraSensorHelperAr0521() + { + exposureMargin_ = 4; + } + uint32_t gainCode(double gain) const override { gain = std::clamp(gain, 1.0, 15.5); @@ -503,6 +525,7 @@ public: /* From datasheet: 64 at 10bits. */ blackLevel_ = 4096; gain_ = AnalogueGainLinear{ 100, 0, 0, 1024 }; + exposureMargin_ = 4; } }; REGISTER_CAMERA_SENSOR_HELPER("gc05a2", CameraSensorHelperGc05a2) @@ -515,6 +538,7 @@ public: /* From datasheet: 64 at 10bits. */ blackLevel_ = 4096; gain_ = AnalogueGainLinear{ 100, 0, 0, 1024 }; + exposureMargin_ = 16; } }; REGISTER_CAMERA_SENSOR_HELPER("gc08a3", CameraSensorHelperGc08a3) @@ -525,6 +549,7 @@ public: CameraSensorHelperHm1246() { gain_ = AnalogueGainLinear{ 1, 16, 0, 16 }; + exposureMargin_ = 2; } }; REGISTER_CAMERA_SENSOR_HELPER("hm1246", CameraSensorHelperHm1246) @@ -537,6 +562,7 @@ public: /* From datasheet: 64 at 10bits. */ blackLevel_ = 4096; gain_ = AnalogueGainLinear{ 0, 512, -1, 512 }; + exposureMargin_ = 10; } }; REGISTER_CAMERA_SENSOR_HELPER("imx214", CameraSensorHelperImx214) @@ -549,6 +575,7 @@ public: /* From datasheet: 64 at 10bits. */ blackLevel_ = 4096; gain_ = AnalogueGainLinear{ 0, 256, -1, 256 }; + exposureMargin_ = 4; } }; REGISTER_CAMERA_SENSOR_HELPER("imx219", CameraSensorHelperImx219) @@ -561,6 +588,7 @@ public: /* From datasheet: 0x40 at 10bits. */ blackLevel_ = 4096; gain_ = AnalogueGainLinear{ 0, 512, -1, 512 }; + exposureMargin_ = 10; } }; REGISTER_CAMERA_SENSOR_HELPER("imx258", CameraSensorHelperImx258) @@ -573,6 +601,7 @@ public: /* From datasheet: 0x32 at 10bits. */ blackLevel_ = 3200; gain_ = AnalogueGainLinear{ 0, 2048, -1, 2048 }; + exposureMargin_ = 4; } }; REGISTER_CAMERA_SENSOR_HELPER("imx283", CameraSensorHelperImx283) @@ -585,6 +614,7 @@ public: /* From datasheet: 0xf0 at 12bits. */ blackLevel_ = 3840; gain_ = AnalogueGainExp{ 1.0, expGainDb(0.3) }; + exposureMargin_ = 2; } }; REGISTER_CAMERA_SENSOR_HELPER("imx290", CameraSensorHelperImx290) @@ -595,6 +625,11 @@ public: CameraSensorHelperImx296() { gain_ = AnalogueGainExp{ 1.0, expGainDb(0.1) }; + /* + * The driver doesn't apply any margin. Use the value + * in RPi's CamHelper. + */ + exposureMargin_ = 4; } }; REGISTER_CAMERA_SENSOR_HELPER("imx296", CameraSensorHelperImx296) @@ -612,6 +647,7 @@ public: /* From datasheet: 0x32 at 10bits. */ blackLevel_ = 3200; gain_ = AnalogueGainExp{ 1.0, expGainDb(0.3) }; + exposureMargin_ = 10; } }; REGISTER_CAMERA_SENSOR_HELPER("imx335", CameraSensorHelperImx335) @@ -624,6 +660,7 @@ public: /* From datasheet: 64 at 10bits. */ blackLevel_ = 4096; gain_ = AnalogueGainLinear{ 0, 1024, -1, 1024 }; + exposureMargin_ = 4; } }; REGISTER_CAMERA_SENSOR_HELPER("imx355", CameraSensorHelperImx355) @@ -634,6 +671,7 @@ public: CameraSensorHelperImx415() { gain_ = AnalogueGainExp{ 1.0, expGainDb(0.3) }; + exposureMargin_ = 8; } }; REGISTER_CAMERA_SENSOR_HELPER("imx415", CameraSensorHelperImx415) @@ -652,6 +690,7 @@ public: blackLevel_ = 4096; /* gain = 1024/(1024 - code); code = [0, 800]; max gain = x4.57 */ gain_ = AnalogueGainLinear{ 0, 1024, -1, 1024 }; + exposureMargin_ = 18; } }; REGISTER_CAMERA_SENSOR_HELPER("imx471", CameraSensorHelperImx471) @@ -662,6 +701,7 @@ public: CameraSensorHelperImx477() { gain_ = AnalogueGainLinear{ 0, 1024, -1, 1024 }; + exposureMargin_ = 22; } }; REGISTER_CAMERA_SENSOR_HELPER("imx477", CameraSensorHelperImx477) @@ -674,6 +714,7 @@ public: /* From datasheet: 0xc8 at 12bits. */ blackLevel_ = 3200; gain_ = AnalogueGainExp{ 1.0, expGainDb(0.3) }; + exposureMargin_ = 2; } }; REGISTER_CAMERA_SENSOR_HELPER("imx662", CameraSensorHelperImx662) @@ -686,6 +727,7 @@ public: /* From datasheet: 0x32 at 10bits. */ blackLevel_ = 3200; gain_ = AnalogueGainExp{ 1.0, expGainDb(0.3) }; + exposureMargin_ = 8; /* \todo kernel driver: 8, rpi: 4 */ } }; REGISTER_CAMERA_SENSOR_HELPER("imx678", CameraSensorHelperImx678) @@ -697,6 +739,7 @@ public: { blackLevel_ = 4096; gain_ = AnalogueGainLinear{ 0, 1024, -1, 1024 }; + exposureMargin_ = 48; } }; REGISTER_CAMERA_SENSOR_HELPER("imx708", CameraSensorHelperImx708) @@ -709,6 +752,7 @@ public: /* From dark frame measurement: 0x40 at 10bits. */ blackLevel_ = 4096; gain_ = AnalogueGainLinear{ 1, 0, 0, 256 }; + exposureMargin_ = 8; } }; REGISTER_CAMERA_SENSOR_HELPER("ov01a10", CameraSensorHelperOv01a10) @@ -721,6 +765,7 @@ public: /* From Linux kernel driver: 0x40 at 10bits. */ blackLevel_ = 4096; gain_ = AnalogueGainLinear{ 1, 0, 0, 128 }; + exposureMargin_ = 6; } }; REGISTER_CAMERA_SENSOR_HELPER("ov08d10", CameraSensorHelperOv08d10) @@ -732,6 +777,7 @@ public: { blackLevel_ = 4096; gain_ = AnalogueGainLinear{ 1, 0, 0, 128 }; + exposureMargin_ = 8; } }; REGISTER_CAMERA_SENSOR_HELPER("ov08x40", CameraSensorHelperOv08x40) @@ -744,6 +790,7 @@ public: blackLevel_ = 3600; /* gain is fixed to 1x, this is just a place holder. */ gain_ = AnalogueGainLinear{ 0, 2048, -1, 2048 }; + exposureMargin_ = 6; } }; REGISTER_CAMERA_SENSOR_HELPER("mira220", CameraSensorHelperMira220) @@ -758,6 +805,7 @@ public: * This has been validated with some empirical testing only. */ gain_ = AnalogueGainLinear{ 1, 0, 0, 128 }; + exposureMargin_ = 4; } }; REGISTER_CAMERA_SENSOR_HELPER("ov2685", CameraSensorHelperOv2685) @@ -770,6 +818,7 @@ public: /* From Linux kernel driver: 0x40 at 10bits. */ blackLevel_ = 4096; gain_ = AnalogueGainLinear{ 1, 0, 0, 128 }; + exposureMargin_ = 8; } }; REGISTER_CAMERA_SENSOR_HELPER("ov2740", CameraSensorHelperOv2740) @@ -782,6 +831,7 @@ public: /* From datasheet: 0x40 at 12bits. */ blackLevel_ = 1024; gain_ = AnalogueGainLinear{ 1, 0, 0, 128 }; + exposureMargin_ = 4; } }; REGISTER_CAMERA_SENSOR_HELPER("ov4689", CameraSensorHelperOv4689) @@ -794,6 +844,14 @@ public: /* From datasheet: 0x10 at 10bits. */ blackLevel_ = 1024; gain_ = AnalogueGainLinear{ 1, 0, 0, 16 }; + /* + * Very convoluted in the driver that however applies a margin + * of 4 lines when setting vts. + * + * cap_vts = cap_shutter + 4; + * ret = ov5640_set_vts(sensor, cap_vts); + */ + exposureMargin_ = 4; } }; REGISTER_CAMERA_SENSOR_HELPER("ov5640", CameraSensorHelperOv5640) @@ -804,6 +862,7 @@ public: CameraSensorHelperOv5647() { gain_ = AnalogueGainLinear{ 1, 0, 0, 16 }; + exposureMargin_ = 4; } }; REGISTER_CAMERA_SENSOR_HELPER("ov5647", CameraSensorHelperOv5647) @@ -814,6 +873,7 @@ public: CameraSensorHelperOv5670() { gain_ = AnalogueGainLinear{ 1, 0, 0, 128 }; + exposureMargin_ = 8; } }; REGISTER_CAMERA_SENSOR_HELPER("ov5670", CameraSensorHelperOv5670) @@ -826,6 +886,7 @@ public: /* From Linux kernel driver: 0x40 at 10bits. */ blackLevel_ = 4096; gain_ = AnalogueGainLinear{ 1, 0, 0, 128 }; + exposureMargin_ = 4; } }; REGISTER_CAMERA_SENSOR_HELPER("ov5675", CameraSensorHelperOv5675) @@ -837,6 +898,7 @@ public: { blackLevel_ = 1024; gain_ = AnalogueGainLinear{ 1, 0, 0, 16 }; + exposureMargin_ = 8; } }; REGISTER_CAMERA_SENSOR_HELPER("ov5693", CameraSensorHelperOv5693) @@ -847,6 +909,7 @@ public: CameraSensorHelperOv64a40() { gain_ = AnalogueGainLinear{ 1, 0, 0, 128 }; + exposureMargin_ = 32; } }; REGISTER_CAMERA_SENSOR_HELPER("ov64a40", CameraSensorHelperOv64a40) @@ -863,6 +926,7 @@ public: * See: https://patchwork.linuxtv.org/project/linux-media/patch/20221106171129.166892-2-nicholas@rothemail.net/#142267 */ gain_ = AnalogueGainLinear{ 1, 0, 0, 128 }; + exposureMargin_ = 4; } }; REGISTER_CAMERA_SENSOR_HELPER("ov8858", CameraSensorHelperOv8858) @@ -873,6 +937,7 @@ public: CameraSensorHelperOv8865() { gain_ = AnalogueGainLinear{ 1, 0, 0, 128 }; + exposureMargin_ = 8; } }; REGISTER_CAMERA_SENSOR_HELPER("ov8865", CameraSensorHelperOv8865) @@ -883,6 +948,7 @@ public: CameraSensorHelperOv13858() { gain_ = AnalogueGainLinear{ 1, 0, 0, 128 }; + exposureMargin_ = 8; } }; REGISTER_CAMERA_SENSOR_HELPER("ov13858", CameraSensorHelperOv13858) @@ -895,6 +961,7 @@ public: /* From datasheet: 0x40 at 10bits. */ blackLevel_ = 4096; gain_ = AnalogueGainLinear{ 0, 32, -1, 32 }; + exposureMargin_ = 64; } }; REGISTER_CAMERA_SENSOR_HELPER("vd55g1", CameraSensorHelperVd55g1) @@ -907,6 +974,7 @@ public: /* From datasheet: 0x40 at 10bits. */ blackLevel_ = 4096; gain_ = AnalogueGainLinear{ 0, 32, -1, 32 }; + exposureMargin_ = 75; } }; REGISTER_CAMERA_SENSOR_HELPER("vd56g3", CameraSensorHelperVd56g3) diff --git a/src/ipa/libipa/camera_sensor_helper.h b/src/ipa/libipa/camera_sensor_helper.h index bd3d0beec7..a9e55c832d 100644 --- a/src/ipa/libipa/camera_sensor_helper.h +++ b/src/ipa/libipa/camera_sensor_helper.h @@ -27,6 +27,7 @@ public: virtual ~CameraSensorHelper() = default; std::optional blackLevel() const { return blackLevel_; } + std::optional exposureMargin() const { return exposureMargin_; } virtual uint32_t gainCode(double gain) const; virtual double gain(uint32_t gainCode) const; double quantizeGain(double gain, double *quantizationGain) const; @@ -46,6 +47,7 @@ protected: std::optional blackLevel_; std::variant gain_; + std::optional exposureMargin_; private: LIBCAMERA_DISABLE_COPY_AND_MOVE(CameraSensorHelper) From patchwork Thu Oct 29 12:58:41 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: 28687 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 F3E83C3261 for ; Thu, 29 Oct 2026 12:59:26 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 7E29B690AB; Thu, 29 Oct 2026 13:59:25 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="DHBkoHfy"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id C1A53690B8 for ; Thu, 29 Oct 2026 13:58:52 +0100 (CET) Received: from pb-laptop.local (185.221.143.99.nat.pool.zt.hu [185.221.143.99]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 64265929; Thu, 29 Oct 2026 13:56:39 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1793278599; bh=p0CbAREh+ksMnQnE0LHI/dLcyU9IpMOiZI6tQzN0Ng8=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=DHBkoHfyS+DsN3nQXHDOOwkhwXj3HD+MsGZpL1lG6racvliJ8/bAdCLo+aJzVAvpL mS9+ZlAZ4xNs3hw52ORpPElGRi12G1thKATRynjonfnYTizn3CxWGzxP9WSn1uPuFf QniLB0v1kMdxo9FqtUsEFt6O4JoOrg7rbumCO1Ik= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Kieran Bingham Subject: [PATCH v5 21/24] ipa: libipa: agc: Move control info map setup Date: Thu, 29 Oct 2026 13:58:41 +0100 Message-ID: <20261029125844.170324-22-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261029125844.170324-1-barnabas.pocze@ideasonboard.com> References: <20261029125844.170324-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 setup of common controls to the end of `configure()`, so that it happens after everything has been configured and calculated. Signed-off-by: Barnabás Pőcze Reviewed-by: Kieran Bingham Reviewed-by: Jacopo Mondi --- src/ipa/libipa/agc.cpp | 82 +++++++++++++++++++++--------------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp index 59362409d8..d95daef941 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -682,47 +682,6 @@ int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state, config.ctrlMap.erase(&controls::AeConstraintMode); config.ctrlMap.erase(&controls::AeExposureMode); - /* \todo Move this to the `Camera` class. */ - config.ctrlMap[&controls::AeEnable] = ControlInfo{ - false, session.autoAllowed, session.autoAllowed - }; - config.ctrlMap[&controls::AnalogueGain] = ControlInfo{ - minGain, maxGain, defGain - }; - config.ctrlMap[&controls::ExposureTime] = ControlInfo{ - static_cast(minExposure * lineDurationUs), - static_cast(maxExposure * lineDurationUs), - static_cast(defExposure * lineDurationUs), - }; - config.ctrlMap[&controls::FrameDurationLimits] = ControlInfo{ - static_cast(session.minFrameDuration.get()), - static_cast(session.maxFrameDuration.get()), - std::span{ { - static_cast(state.minFrameDuration.get()), - static_cast(state.maxFrameDuration.get()), - } }, - }; - - 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); - std::visit(utils::overloaded{ [&](AgcMSV &) { /* No constraint mode support. */ @@ -769,6 +728,47 @@ int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state, }, }, impl_); + /* \todo Move this to the `Camera` class. */ + config.ctrlMap[&controls::AeEnable] = ControlInfo{ + false, session.autoAllowed, session.autoAllowed + }; + config.ctrlMap[&controls::AnalogueGain] = ControlInfo{ + minGain, maxGain, defGain + }; + config.ctrlMap[&controls::ExposureTime] = ControlInfo{ + static_cast(minExposure * lineDurationUs), + static_cast(maxExposure * lineDurationUs), + static_cast(defExposure * lineDurationUs), + }; + config.ctrlMap[&controls::FrameDurationLimits] = ControlInfo{ + static_cast(session.minFrameDuration.get()), + static_cast(session.maxFrameDuration.get()), + std::span{ { + static_cast(state.minFrameDuration.get()), + static_cast(state.maxFrameDuration.get()), + } }, + }; + + 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); + return 0; } From patchwork Thu Oct 29 12:58:42 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: 28688 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 116E8C3302 for ; Thu, 29 Oct 2026 12:59:28 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 69BE7690EA; Thu, 29 Oct 2026 13:59:27 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="qRYGke1M"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 00B376909D for ; Thu, 29 Oct 2026 13:58:53 +0100 (CET) Received: from pb-laptop.local (185.221.143.99.nat.pool.zt.hu [185.221.143.99]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 985EBC4A; Thu, 29 Oct 2026 13:56:39 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1793278599; bh=breDhBdy5MRSWK0xQtR/OJZqnISegJbvqyT89gp0ZLo=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=qRYGke1Mj6J+z6xBS5kbystjwQp8XUyTgyYga/LiPCvweJLQnx8qbyCFw33N9SbnE 2A0Yc6Ay/vjEuNBkroy5yd+32EttNRYgblohqkHW4Y3L4GuYuMl6eB7YngG/BY8qI/ 8otEJv7B+//s0iOxrYipxJq+w1/uMirNtKMQceZU= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi Subject: [PATCH v5 22/24] ipa: libipa: agc: Do not run faster than 30 fps by default Date: Thu, 29 Oct 2026 13:58:42 +0100 Message-ID: <20261029125844.170324-23-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261029125844.170324-1-barnabas.pocze@ideasonboard.com> References: <20261029125844.170324-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 auto mode, by default the camera should not run faster than 30fps unless the user explicitly adjusts `FrameDurationLimits`. This matches the behaviour of the raspberry pi agc algorithm. Signed-off-by: Barnabás Pőcze Reviewed-by: Jacopo Mondi --- src/ipa/libipa/agc.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp index d95daef941..775dc12002 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -728,6 +728,13 @@ int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state, }, }, impl_); + if (session.autoAllowed) { + /* By default do not run faster than 30fps. */ + state.minFrameDuration = std::clamp( + utils::Duration(1.0s / 30), + session.minFrameDuration, session.maxFrameDuration); + } + /* \todo Move this to the `Camera` class. */ config.ctrlMap[&controls::AeEnable] = ControlInfo{ false, session.autoAllowed, session.autoAllowed From patchwork Thu Oct 29 12:58:43 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: 28689 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 570ACC3316 for ; Thu, 29 Oct 2026 12:59:29 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id D9FA7690F4; Thu, 29 Oct 2026 13:59:28 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="n3cO22sv"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 2F8A6690BB for ; Thu, 29 Oct 2026 13:58:53 +0100 (CET) Received: from pb-laptop.local (185.221.143.99.nat.pool.zt.hu [185.221.143.99]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id C979A6DC for ; Thu, 29 Oct 2026 13:56:39 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1793278599; bh=EZzR8IV/wUDAhbVsa4ObykQNIgc/STXvL4uufwaOG+Q=; h=From:To:Subject:Date:In-Reply-To:References:From; b=n3cO22svDRvLtgL/p40Hk9bzUbeB5cKjjxOqwOSyyPIPNyIjmHb9EO2MBgCIKw/Ba x9/IbzfIAiVw7t3yH2Nl/fOkwoLx7eL81XDeSabZFqxfJto+CaHbr+dMipU8/6+cZK VCaePI+157c+ryLl2UFXA8o3pz1imzWhJAxO8aFo= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [PATCH v5 23/24] ipa: libipa: agc: Do not run slower than 15fps by default Date: Thu, 29 Oct 2026 13:58:43 +0100 Message-ID: <20261029125844.170324-24-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261029125844.170324-1-barnabas.pocze@ideasonboard.com> References: <20261029125844.170324-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" If the tuning file defines no exposure modes, then the "Maximize" mode will be implicitly instantiated and used. But in this case, do not let the camera run slower than 15fps, by default, unless the user explicitly requests it using `FrameDurationLimits`. Signed-off-by: Barnabás Pőcze Reviewed-by: Jacopo Mondi --- src/ipa/libipa/agc.cpp | 12 ++++++++++++ src/ipa/libipa/agc.h | 4 ++++ 2 files changed, 16 insertions(+) diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp index 775dc12002..60e8614777 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -564,6 +564,8 @@ int AgcAlgorithm::init(const ValueNode &tuningData, CameraSensorHelper *sensor, if (ret) return ret; + tuning_.hasExposureModes = !agcConfig.exposureModeHelpers.empty(); + addFallbacks(agcConfig); impl_.emplace(std::move(agcConfig)); @@ -733,6 +735,16 @@ int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state, state.minFrameDuration = std::clamp( utils::Duration(1.0s / 30), session.minFrameDuration, session.maxFrameDuration); + + if (!tuning_.hasExposureModes) { + /* + * By default do not run slower than 15fps + * if no exposure modes are defined. + */ + state.maxFrameDuration = std::clamp( + utils::Duration(1.0s / 15), + state.minFrameDuration, session.maxFrameDuration); + } } /* \todo Move this to the `Camera` class. */ diff --git a/src/ipa/libipa/agc.h b/src/ipa/libipa/agc.h index 3ca36aea64..d8057720d0 100644 --- a/src/ipa/libipa/agc.h +++ b/src/ipa/libipa/agc.h @@ -163,6 +163,10 @@ private: std::variant impl_; CameraSensorHelper *sensor_ = nullptr; + + struct { + bool hasExposureModes = false; + } tuning_; }; } /* namespace ipa */ From patchwork Thu Oct 29 12:58:44 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: 28690 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 2FD0FC3261 for ; Thu, 29 Oct 2026 12:59:30 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id B7B81690D5; Thu, 29 Oct 2026 13:59:29 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="X3eP7EXa"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 59D3F690BC for ; Thu, 29 Oct 2026 13:58:53 +0100 (CET) Received: from pb-laptop.local (185.221.143.99.nat.pool.zt.hu [185.221.143.99]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 01899C4A for ; Thu, 29 Oct 2026 13:56:39 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1793278600; bh=2vrOCXteZnPfWER5TBPCRfbkOYbVJ3khoKihXogaL6U=; h=From:To:Subject:Date:In-Reply-To:References:From; b=X3eP7EXafXGKUygnqw5XrDk785Reo4o2TtfM2n9xV1HFO7VmcGtxUoUrcRbslmiP4 VYKdvnuogXyo44cPcF+S4AomK5bUbq7ZQ5raLAIyT69dx5xz0rnfXjHObZFq/FXvbk c4QE5ImCAP15ZwxFv3lXCh0c3XsGCfDjiBZOrdNg= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [PATCH v5 24/24] ipa: libipa: agc: Take exposure margin into account Date: Thu, 29 Oct 2026 13:58:44 +0100 Message-ID: <20261029125844.170324-25-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261029125844.170324-1-barnabas.pocze@ideasonboard.com> References: <20261029125844.170324-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 sensor exposure time depends on the vertical blanking amount and the exposure margin. Instead of relying on the maximum exposure available at onfiguration, use the frame duration limits (~ vblank) and exposure margin to dynamically calculate the max available exposure time. Signed-off-by: Barnabás Pőcze Reviewed-by: Jacopo Mondi --- src/ipa/libipa/agc.cpp | 96 +++++++++++++++++++++++++++++------------- src/ipa/libipa/agc.h | 3 +- 2 files changed, 69 insertions(+), 30 deletions(-) diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp index 60e8614777..cc88c99f14 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -72,12 +72,12 @@ namespace agc { * \struct Session * \brief Session configuration for AgcAlgorithm * + * \var Session::minExposure + * \brief Minimum exposure (in lines) for the streaming session + * * \var Session::minExposureTime * \brief Minimum exposure time for the streaming session * - * \var Session::maxExposureTime - * \brief Maximum exposure time for the streaming session - * * \var Session::minAnalogueGain * \brief Minimum analogue gain for the streaming session * @@ -102,6 +102,11 @@ namespace agc { * \var Session::sensor.outputSize * \brief Configured output size of the sensor * + * \var Session::sensor.exposureMargin + * \brief Exposure margin of the sensor + * + * \sa CameraSensorHelper::exposureMargin() + * * \var Session::autoAllowed * \copybrief AgcAlgorithm::ConfigurationParams::autoAllowed * \sa AgcAlgorithm::ConfigurationParams::autoAllowed @@ -419,6 +424,16 @@ void addFallbacks(AgcMeanLuminance::Configuration &config) }); } +[[nodiscard]] +uint32_t clampExposure(const agc::Session &session, const agc::ActiveState &state, + uint32_t exposure) +{ + const uint32_t maxFrame = state.maxFrameDuration / session.lineDuration; + const uint32_t maxExposure = maxFrame - session.sensor.exposureMargin; + + return std::clamp(exposure, session.minExposure, maxExposure); +} + } /* namespace */ /** @@ -616,7 +631,12 @@ int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state, session.lineDuration = lineLength * 1.0s / config.sensorInfo.pixelRate; session.sensor.outputSize = config.sensorInfo.outputSize; - const double lineDurationUs = session.lineDuration.get(); + auto exposureMargin = sensor_ ? sensor_->exposureMargin() : std::nullopt; + session.sensor.exposureMargin = exposureMargin.value_or(4); + if (!exposureMargin) + LOG(Agc, Warning) + << "Sensor exposure margin not available, using " + << session.sensor.exposureMargin; /* * Compute exposure time limits from the V4L2_CID_EXPOSURE control @@ -625,7 +645,6 @@ int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state, 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(); /* Compute the analogue gain limits. */ @@ -638,28 +657,46 @@ int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state, float maxGain = extractGain(v4l2Gain.max()); float defGain = extractGain(v4l2Gain.def()); - LOG(Agc, Debug) - << "exposure: [" << minExposure << ',' << maxExposure << "], " - << "gain: [" << minGain << ',' << maxGain << "], " - << "line-duration: " << session.lineDuration << ", " - << "sensor-output: " << session.sensor.outputSize; - - /* * 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.minExposure = minExposure; session.minExposureTime = minExposure * session.lineDuration; - session.maxExposureTime = maxExposure * session.lineDuration; session.minAnalogueGain = minGain; session.maxAnalogueGain = maxGain; session.defAnalogueGain = defGain; session.minFrameDuration = config.sensorInfo.minFrameLength * session.lineDuration; session.maxFrameDuration = config.sensorInfo.maxFrameLength * session.lineDuration; + /* + * The assumption is that the minimum exposure time is independent of vblank, + * only the maximum exposure time is affected by it. + */ + const uint32_t maxExposure = config.sensorInfo.maxFrameLength - session.sensor.exposureMargin; + const utils::Duration maxExposureTime = maxExposure * session.lineDuration; + + LOG(Agc, Debug) + << "exposure: [" << session.minExposure << ',' << maxExposure << "], " + << "exposure-time: [" << session.minExposureTime << ',' << maxExposureTime << "], " + << "gain: [" << session.minAnalogueGain << ',' << session.maxAnalogueGain << "], " + << "line-length: " << lineLength << ", " + << "line-duration: " << session.lineDuration << ", " + << "frame-height: [" << config.sensorInfo.minFrameLength << ',' << config.sensorInfo.maxFrameLength << "], " + << "sensor-output: " << session.sensor.outputSize << ", " + << "sensor-exposure-margin: " << session.sensor.exposureMargin; + + /* + * This is a sanity check. If this fails, something is very likely off. + * Equality could theoretically be allowed, but it is not so that + * `(frameDuration / lineDuration) - exposureMargin` is no less + * than the minimum exposure time even if the division is slightly + * inaccurate. + */ + if (session.minExposure + session.sensor.exposureMargin >= config.sensorInfo.minFrameLength) + return -EINVAL; + /* Configure the default exposure and gain. */ state = {}; state.automatic.gain = session.minAnalogueGain; @@ -755,9 +792,10 @@ int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state, minGain, maxGain, defGain }; config.ctrlMap[&controls::ExposureTime] = ControlInfo{ - static_cast(minExposure * lineDurationUs), - static_cast(maxExposure * lineDurationUs), - static_cast(defExposure * lineDurationUs), + static_cast(session.minExposureTime.get()), + static_cast(maxExposureTime.get()), + static_cast(state.automatic.exposure * + session.lineDuration.get()), }; config.ctrlMap[&controls::FrameDurationLimits] = ControlInfo{ static_cast(session.minFrameDuration.get()), @@ -880,7 +918,7 @@ void AgcAlgorithm::queueRequest(const agc::Session &session, agc::ActiveState &s frameContext.autoGainEnabled = state.autoGainEnabled; if (!frameContext.autoExposureEnabled) - frameContext.exposure = state.manual.exposure; + frameContext.exposure = clampExposure(session, state, state.manual.exposure); if (!frameContext.autoGainEnabled) frameContext.gain = state.manual.gain; @@ -995,18 +1033,16 @@ void AgcAlgorithm::process(const agc::Session &session, agc::ActiveState &state, * 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; + utils::Duration minExposureTime = session.minExposureTime; + utils::Duration maxExposureTime = state.maxFrameDuration - + session.sensor.exposureMargin * lineDuration; double minAnalogueGain; double maxAnalogueGain; - if (state.autoExposureEnabled) { - minExposureTime = session.minExposureTime; - maxExposureTime = std::clamp(state.maxFrameDuration, - session.minExposureTime, - session.maxExposureTime); - } else { - minExposureTime = lineDuration * state.manual.exposure; + if (!state.autoExposureEnabled) { + minExposureTime = std::clamp( + state.manual.exposure * lineDuration, + minExposureTime, maxExposureTime); maxExposureTime = minExposureTime; } @@ -1076,6 +1112,8 @@ void AgcAlgorithm::process(const agc::Session &session, agc::ActiveState &state, }, }, impl_); + state.automatic.exposure = clampExposure(session, state, state.automatic.exposure); + const utils::Duration newExposureTime = state.automatic.exposure * lineDuration; LOG(Agc, Debug) @@ -1106,7 +1144,7 @@ void AgcAlgorithm::processFrameDuration(const agc::Session &session, const auto frameLength = std::max( std::min(std::ceil(frameContext.minFrameDuration / lineDuration), frameContext.maxFrameDuration / lineDuration), - frameContext.exposure); + frameContext.exposure + session.sensor.exposureMargin); frameContext.vblank = frameLength - session.sensor.outputSize.height; diff --git a/src/ipa/libipa/agc.h b/src/ipa/libipa/agc.h index d8057720d0..9b4bcf0bed 100644 --- a/src/ipa/libipa/agc.h +++ b/src/ipa/libipa/agc.h @@ -36,8 +36,8 @@ class Histogram; namespace agc { struct Session { + uint32_t minExposure; utils::Duration minExposureTime; - utils::Duration maxExposureTime; double minAnalogueGain; double maxAnalogueGain; double defAnalogueGain; @@ -47,6 +47,7 @@ struct Session { struct { Size outputSize; + uint32_t exposureMargin; } sensor; bool autoAllowed;