From patchwork Mon Oct 26 13:20: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: 28618 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 422C3C324C for ; Mon, 26 Oct 2026 13:20:57 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 01C6D68FDD; Mon, 26 Oct 2026 14:20:51 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="P7UNJI1f"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 95B0A68FD5 for ; Mon, 26 Oct 2026 14:20: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 6DF0E268 for ; Mon, 26 Oct 2026 14:18:37 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1793020717; bh=AjmMdgyQ8Hryg4uIiv3IseyyJoJCwSuEB2er3sbatQA=; h=From:To:Subject:Date:In-Reply-To:References:From; b=P7UNJI1f4WkgvDafTz4HLqOuJ8ijBjWWH9vjtiyAnxqsRkO10vau+efa9R+pWlowS cA4oQ9iL6GL9+qlc23ND0pJqaByIL7typ1syu/bZ/j1xZuNjWO+wayHNB3iiPZ55+F Ky5dtfDFo343hjPrTAsAZnn2JABKthO+yg/yQASc= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v4 01/23] libcamera: controls: AeExposureMode: Add ExposureMaximize Date: Mon, 26 Oct 2026 14:20:23 +0100 Message-ID: <20261026132045.269007-2-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261026132045.269007-1-barnabas.pocze@ideasonboard.com> References: <20261026132045.269007-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 --- src/libcamera/control_ids_core.yaml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/libcamera/control_ids_core.yaml b/src/libcamera/control_ids_core.yaml index 89991d03d7..acd8ffd85a 100644 --- a/src/libcamera/control_ids_core.yaml +++ b/src/libcamera/control_ids_core.yaml @@ -188,6 +188,25 @@ controls: - name: ExposureCustom value: 3 description: Custom exposure mode. + - name: ExposureMaximize + value: 4 + description: | + Exposure mode allowing very large exposure times and gains. + + This mode is intended to make the full capabilities of the sensor + available. In practice this allows multi-second exposures and + maximum available analogue gain. In contrast to other modes, + it is generally expected that the exposure time is maximized first, + and only then is gain used to achieve the target exposure. Note, + that this mode can be customized in tuning files, thus the behaviour + might differ slightly. + + Such behaviour can be useful for mostly static scenes where the + lighting changes drastically over a period of time (e.g. capturing + the sky 24 hours a day). + + Be aware that FrameDurationLimits will always apply, potentially reducing + the maximum achievable exposure time. - ExposureValue: type: float From patchwork Mon Oct 26 13:20: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: 28619 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 D4403C324C for ; Mon, 26 Oct 2026 13:20:59 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 90B2268FF9; Mon, 26 Oct 2026 14:20:51 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="P0RBR0b7"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id CA49D68FD7 for ; Mon, 26 Oct 2026 14:20: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 A1779D0 for ; Mon, 26 Oct 2026 14:18:37 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1793020717; bh=mp4kRLkYQNInbtBrm20x1PwF9MGIYTHf3c0HONTekHQ=; h=From:To:Subject:Date:In-Reply-To:References:From; b=P0RBR0b7LPz06HEyDZujPGz/GP5SQV8PUhRw91CFc3ss0HmZ5E42tOFb8byz0j/08 GJ13PsO/lyrMqI/5PSoRL3H9GvRwZD9kosOaq1iZydZ89xeXp57HOu44f1kLQAqQQa FeYCuPlAdStTwmAodDdFPdXVSTsW5Em74Dudk5p0= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v4 02/23] ipa: libipa: agc: Change `AgcMSV` exposure mode Date: Mon, 26 Oct 2026 14:20:24 +0100 Message-ID: <20261026132045.269007-3-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261026132045.269007-1-barnabas.pocze@ideasonboard.com> References: <20261026132045.269007-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 --- 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 51b05e3c2a..e23cb13cb7 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::ConstraintNormal; - state.exposureMode = controls::AeExposureModeEnum::ExposureNormal; + state.exposureMode = controls::AeExposureModeEnum::ExposureMaximize; state.automatic.yTarget = 0; /* Not supported. */ From patchwork Mon Oct 26 13:20: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: 28620 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 2ED6EC324C for ; Mon, 26 Oct 2026 13:21:02 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 1BDE468FD5; Mon, 26 Oct 2026 14:20:53 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="jMcLvhDD"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 1A65A68E22 for ; Mon, 26 Oct 2026 14:20: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 D617E268; Mon, 26 Oct 2026 14:18:37 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1793020718; bh=QOg43Tz9alkS4G+bT5qFA7jhnIm4C0qw28fZB4qg/XI=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=jMcLvhDDjoKvSNCwa1TEVrgnKJUexwS0TsAmAPDFTcYE9ONF0evHJxjx4Umng/zaH NnPMTEHG1nMPGqXjT6eVmBOpPB6B4ND9Vh2OmI/1xdnVEwJ+Q3A73+6tXnZyKjEoou qWjKIwbMOyUwi8hT0fg5cE/KBG3yUMZUOMLvpPBw= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi Subject: [RFC PATCH v4 03/23] ipa: libipa: exposure_mode_helper: Remove defaulted destructor Date: Mon, 26 Oct 2026 14:20:25 +0100 Message-ID: <20261026132045.269007-4-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261026132045.269007-1-barnabas.pocze@ideasonboard.com> References: <20261026132045.269007-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 --- 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 Mon Oct 26 13:20: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: 28621 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 3C183C324C for ; Mon, 26 Oct 2026 13:21:04 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 89F4E68FEB; Mon, 26 Oct 2026 14:20:55 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="DAG3qt/6"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 541A168FD5 for ; Mon, 26 Oct 2026 14:20: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 2A661D0 for ; Mon, 26 Oct 2026 14:18:38 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1793020718; bh=yk0OFnrKIZmJdTfO4CAF5ZVDDc45bIHPtDpM4tQfmHM=; h=From:To:Subject:Date:In-Reply-To:References:From; b=DAG3qt/6Qy103vmP88AfJoym1c0F0PBEFh7fIeugZ3UZmN1ui1l0Kij3MrzzYkOpQ IVb0h78cANv5WCwqhRQIoVRhP+sAsOx6ITUVmKR9qYPJC8ZrAVoPs5oJ9CiC0SsaRs FqQK78PhTHDnY2e370ehjktodrb96vGOCjag07DM= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v4 04/23] ipa: libipa: exposure_mode_helper: Keep stage params together Date: Mon, 26 Oct 2026 14:20:26 +0100 Message-ID: <20261026132045.269007-5-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261026132045.269007-1-barnabas.pocze@ideasonboard.com> References: <20261026132045.269007-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. Signed-off-by: Barnabás Pőcze --- 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 72833247c5..eff639fb67 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::ExposureNormal, - 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..61ad142498 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 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 Mon Oct 26 13:20: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: 28622 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 4D7D8C324C for ; Mon, 26 Oct 2026 13:21:06 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id D8F2968FF2; Mon, 26 Oct 2026 14:20:57 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="sxSlLkzo"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 8D68968FD7 for ; Mon, 26 Oct 2026 14:20: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 60987268; Mon, 26 Oct 2026 14:18:38 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1793020718; bh=l78Pyx7CkmBA+dd6DFHwvRbNqdKAUBiugoabPsGXeNs=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=sxSlLkzogyuspsqsk5N+v10mP42z/I5kUtv96EOuXHOywSh0UPWZbC3RBHp9nC5W6 3pdySE6NrtVvGPa6/HznfQPHCfnTmNnMlhTQe+eF6Gtckgm1I6IMwnbCm772Q79iOd zAT2m5xGtcDascjJgZDs1M++EAlOcn2Ej02YyoQY= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi Subject: [RFC PATCH v4 05/23] ipa: libipa: exposure_mode_helper: Remove limit getters Date: Mon, 26 Oct 2026 14:20:27 +0100 Message-ID: <20261026132045.269007-6-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261026132045.269007-1-barnabas.pocze@ideasonboard.com> References: <20261026132045.269007-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 --- 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 61ad142498..3e600a5c76 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 Mon Oct 26 13:20: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: 28623 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 E570FC324C for ; Mon, 26 Oct 2026 13:21:07 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 0B44C69012; Mon, 26 Oct 2026 14:21:00 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="qoKHN71e"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 0AE2E68FEF for ; Mon, 26 Oct 2026 14:20: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 9D9AAD0; Mon, 26 Oct 2026 14:18:38 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1793020718; bh=jfD0L0K1+CFaBLvik38zSoxUkdqUxYH87dw1VnxLUPU=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=qoKHN71eFqz0a5D6uVOYG+c+jrkf9ipvL6eseshcb0wfuGBQIZ3+zj6FOs0GzIZvW 7RKm8v4CFZ8eE/lHNZIxbdpFZil/sv5CZP08YY0A6EpBLeYZBw3PwIwR/QuuYAy4Lr HuExAgsVIRdiq79/UoyHXLk+ZcJeC/4TjcMjcdCU= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi Subject: [RFC PATCH v4 06/23] ipa: libipa: exposure_mode_helper: Move `stageGain` variable Date: Mon, 26 Oct 2026 14:20:28 +0100 Message-ID: <20261026132045.269007-7-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261026132045.269007-1-barnabas.pocze@ideasonboard.com> References: <20261026132045.269007-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" After the loop, `stageGain == lastStageGain`, so use `lastStageGain` in the later calculation instead, and make `stageGain` "local" to the loop, just like `stageExposure` already is. And with that, also move the calculation a bit later, just before the value is actually used. Signed-off-by: Barnabás Pőcze Reviewed-by: Jacopo Mondi --- src/ipa/libipa/exposure_mode_helper.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/ipa/libipa/exposure_mode_helper.cpp b/src/ipa/libipa/exposure_mode_helper.cpp index 3e600a5c76..8a712c6da9 100644 --- a/src/ipa/libipa/exposure_mode_helper.cpp +++ b/src/ipa/libipa/exposure_mode_helper.cpp @@ -240,13 +240,11 @@ ExposureModeHelper::splitExposure(utils::Duration exposure) const exposure / (exposureTime * gain * quantGain) }; } - double stageGain = clampGain(1.0); - double lastStageGain = stageGain; + double lastStageGain = clampGain(1.0); 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 @@ -267,6 +265,8 @@ ExposureModeHelper::splitExposure(utils::Duration exposure) const } /* Clamp the exposureTime to stageExposureTime and regulate gain. */ + const double stageGain = clampGain(stage.gain); + if (stageExposureTime * stageGain >= exposure) { exposureTime = stageExposureTime; gain = clampGain(exposure / exposureTime, &quantGain2); @@ -283,10 +283,10 @@ 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. + * stages to use then the default gain of 1.0 is used so that exposure + * time is maxed before gain is touched at all. */ - exposureTime = clampExposureTime(exposure / stageGain, &quantGain); + exposureTime = clampExposureTime(exposure / lastStageGain, &quantGain); gain = clampGain(exposure / exposureTime, &quantGain2); quantGain *= quantGain2; From patchwork Mon Oct 26 13:20: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: 28624 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 8BA78C324C for ; Mon, 26 Oct 2026 13:21:09 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 90D556901C; Mon, 26 Oct 2026 14:21:01 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="d6HS4xH4"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 3E0C568FF1 for ; Mon, 26 Oct 2026 14:20: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 DD7EF268 for ; Mon, 26 Oct 2026 14:18:38 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1793020719; bh=JhlAxOma+lTURh19hWjWgKqvn/mdpGxZeEjQJ9uohwM=; h=From:To:Subject:Date:In-Reply-To:References:From; b=d6HS4xH4sUOJhujlv49S9QkIIHwrej4I15kH6xOG8EWqIZ9m9/vKJtI7hG1fVSF+v P0z9/Ba69hT3ufz4C/h0bAGA77nosvpvJFyQWRUnJhXepd3nvHn6EN0ohMTukyIVLx XZ8m2UvXPTKJ0XEeCzlEeR28KDVkJkmYxujUz7bk= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v4 07/23] ipa: libipa: agc: Move tuning file parsing Date: Mon, 26 Oct 2026 14:20:29 +0100 Message-ID: <20261026132045.269007-8-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261026132045.269007-1-barnabas.pocze@ideasonboard.com> References: <20261026132045.269007-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. Signed-off-by: Barnabás Pőcze --- src/ipa/libipa/agc.cpp | 257 +++++++++++++++++++++++++- src/ipa/libipa/agc_mean_luminance.cpp | 246 +----------------------- src/ipa/libipa/agc_mean_luminance.h | 22 +-- 3 files changed, 273 insertions(+), 252 deletions(-) diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp index e23cb13cb7..a27813b8bf 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,253 @@ 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. + */ +constexpr double kDefaultRelativeLuminanceTarget = 0.16; + +int parseRelativeLuminanceTarget(AgcMeanLuminance::Configuration &config, const ValueNode &tuningData) +{ + auto &target = tuningData; + 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 &constraintModes) +{ + 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; +} + +/** + * \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: + * ConstraintNormal: + * lower: + * qLo: 0.98 + * qHi: 1.0 + * yTarget: 0.5 + * ConstraintHighlight: + * 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: + * ExposureNormal: + * exposureTime: [ 100, 10000, 30000, 60000, 120000 ] + * gain: [ 2.0, 4.0, 6.0, 8.0, 10.0 ] + * ExposureShort: + * 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 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 fillMissing(AgcMeanLuminance::Configuration &config) +{ + /* + * 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::ExposureNormal, + std::vector{}); + + /* + * 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::ConstraintNormal].push_back(std::move(constraint)); + } +} + +} /* namespace */ + /** * \class AgcAlgorithm * \brief libIPA LSC algorithm algorithm @@ -310,10 +559,14 @@ int AgcAlgorithm::init(const ValueNode &tuningData, CameraSensorHelper *sensor, const ConfigurationParams &config) { if (sensor) { - auto &impl = impl_.emplace(); - int ret = impl.parseTuningData(tuningData); + AgcMeanLuminance::Configuration agcMLconfig; + int ret = parseTuningData(agcMLconfig, tuningData); if (ret) return ret; + + fillMissing(agcMLconfig); + + impl_.emplace(std::move(agcMLconfig)); } else { impl_.emplace(); } diff --git a/src/ipa/libipa/agc_mean_luminance.cpp b/src/ipa/libipa/agc_mean_luminance.cpp index eff639fb67..8b35eab6aa 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 * @@ -177,168 +165,18 @@ static constexpr unsigned int kDefaultLuxLevel = 500; * 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)) { + ASSERT(!constraintModes_.empty()); + ASSERT(!exposureModeHelpers_.empty()); } 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::ConstraintNormal].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::ExposureNormal, - std::vector{}); - - return 0; -} - /** * \brief Configure the exposure mode helpers * \param[in] lineDuration The sensor line length @@ -358,76 +196,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: - * ConstraintNormal: - * lower: - * qLo: 0.98 - * qHi: 1.0 - * yTarget: 0.5 - * ConstraintHighlight: - * 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: - * ExposureNormal: - * exposureTime: [ 100, 10000, 30000, 60000, 120000 ] - * gain: [ 2.0, 4.0, 6.0, 8.0, 10.0 ] - * ExposureShort: - * 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 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 Mon Oct 26 13:20: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: 28626 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 5D8F6C324C for ; Mon, 26 Oct 2026 13:21:12 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 0534A69021; Mon, 26 Oct 2026 14:21:04 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="gQhDLFrs"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 67A1668FF2 for ; Mon, 26 Oct 2026 14:20: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 29A414F1 for ; Mon, 26 Oct 2026 14:18:39 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1793020719; bh=k1BYOEGmo22Y7rWJ0z4CG8Z+CjFHfzhMFGzcQwZEL1Q=; h=From:To:Subject:Date:In-Reply-To:References:From; b=gQhDLFrsLCOQCgQVkvEacIgyRH+YVuCcnSov9dnjgXQwvMfNDlElV3Q84VqWLvCU8 PFa/bUbed9xv4/YhHdl/Vx2o48L6UuR+AmUYnYyt36K60MZfIS+jZLqBfGWZBVJvvm sEXhUBmp1Hw6sVdpslfNLJbKWLuZLSxQpCn+QjiU= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v4 08/23] ipa: libipa: agc: Initialize "Maximize" mode when none given Date: Mon, 26 Oct 2026 14:20:30 +0100 Message-ID: <20261026132045.269007-9-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261026132045.269007-1-barnabas.pocze@ideasonboard.com> References: <20261026132045.269007-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 --- 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 a27813b8bf..b899f7c643 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -448,7 +448,7 @@ void fillMissing(AgcMeanLuminance::Configuration &config) */ if (config.exposureModeHelpers.empty()) config.exposureModeHelpers.try_emplace( - controls::ExposureNormal, + controls::ExposureMaximize, std::vector{}); /* From patchwork Mon Oct 26 13:20: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: 28625 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 196D4C324C for ; Mon, 26 Oct 2026 13:21:11 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 9DBDE69000; Mon, 26 Oct 2026 14:21:02 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="a0ff/yA8"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 96A3B68FD9 for ; Mon, 26 Oct 2026 14:20: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 6CF88D0 for ; Mon, 26 Oct 2026 14:18:39 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1793020719; bh=NdqBb5XcdrEQlYdPaa0RjS4bxnfNciraMA+zYBtjCQc=; h=From:To:Subject:Date:In-Reply-To:References:From; b=a0ff/yA8r1cw9yQGvhWH2QaMcfdnlUkf3FFeyL1K1r6XPge9Tpaq2HazkzmP+lWNb Xf4jmL57kG6Idn+lq6VPgYJyJ4ntNLwAl2zn6wFKsDnOK0AC3ezllRehIq/V1QrTwQ ki/f1xBfahGw73qJ+h9nOU9s1t24TI6JbELHSRS4= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v4 09/23] ipa: libipa: exposure_mode_helper: Consider last stage as hard-limit Date: Mon, 26 Oct 2026 14:20:31 +0100 Message-ID: <20261026132045.269007-10-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261026132045.269007-1-barnabas.pocze@ideasonboard.com> References: <20261026132045.269007-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 assertion is added in the constructor. Also modify the general agc algorithm to provide stages for the implicitly instantiated "Maximize" mode. Closes: https://gitlab.freedesktop.org/camera/libcamera/-/work_items/362 Signed-off-by: Barnabás Pőcze --- src/ipa/libipa/agc.cpp | 13 +++++++++---- src/ipa/libipa/exposure_mode_helper.cpp | 16 +++++++--------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp index b899f7c643..fb615f0af2 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -442,14 +442,19 @@ void fillMissing(AgcMeanLuminance::Configuration &config) { /* * 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. + * ExposureModeHelper using a single stage of effectively unlimited exposure + * and gain. 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::ExposureMaximize, - std::vector{}); + std::vector{ + ExposureModeHelper::Stage{ + .exposureTime = utils::Duration::max(), + .gain = std::numeric_limits::max(), + }, + }); /* * If the tuning data file contains no constraints then we use the diff --git a/src/ipa/libipa/exposure_mode_helper.cpp b/src/ipa/libipa/exposure_mode_helper.cpp index 8a712c6da9..4123f73cc8 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 @@ -280,14 +281,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 gain 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. */ - exposureTime = clampExposureTime(exposure / lastStageGain, &quantGain); - gain = clampGain(exposure / exposureTime, &quantGain2); + exposureTime = clampExposureTime(stages_.back().exposureTime, &quantGain); + gain = clampGain(stages_.back().gain, &quantGain2); quantGain *= quantGain2; return { exposureTime, gain, quantGain, From patchwork Mon Oct 26 13:20: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: 28627 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 C9A1DC324C for ; Mon, 26 Oct 2026 13:21:13 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id B5EC069026; Mon, 26 Oct 2026 14:21:05 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="C7vGb0v+"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id CF3F268FE9 for ; Mon, 26 Oct 2026 14:20: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 A1321268 for ; Mon, 26 Oct 2026 14:18:39 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1793020719; bh=W3GdfSQIwG++AkDl/KlJFzYfJueivph2B6vbEsBqiRw=; h=From:To:Subject:Date:In-Reply-To:References:From; b=C7vGb0v+Y5Fm6HPk45fDqex2WTST+XnggvUvmIaJ5G5+Ut3+IrGkwvSSkK61wHrvN GmigvnDPOMnIq69WZoJtr5m67cDDnCpSt889mHVgmfvLmGedS3WS1dJc2S1Evgooz+ 6+DDHRaeMOPyJIaimVVFWEGqi2zPHlxiloLquONE= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v4 10/23] ipa: libipa: agc: Do not report auto controls if not available Date: Mon, 26 Oct 2026 14:20:32 +0100 Message-ID: <20261026132045.269007-11-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261026132045.269007-1-barnabas.pocze@ideasonboard.com> References: <20261026132045.269007-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 --- 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 fb615f0af2..7e85faf649 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -1134,9 +1134,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 Mon Oct 26 13:20: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: 28628 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 E2BF9C3302 for ; Mon, 26 Oct 2026 13:21:14 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id A00376900F; Mon, 26 Oct 2026 14:21:07 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Cl+DLox6"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 5C63D68FF6 for ; Mon, 26 Oct 2026 14:20: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 DFB73D0; Mon, 26 Oct 2026 14:18:39 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1793020720; bh=PkQNTjN//e4x8PEvTL0bBQl61pSG3SoqXWtbYsHVAFo=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Cl+DLox6RrtDiz1DerTOXZr0rdMiLq9yxa0Qf87OEfFdGLLmvktmNYGNGpnx5e8vI BxRgoQYhWgDgHInAzYozMp09aWl/BUZiARjrPrZfkYUa7mCcRdGyAv00ZKWdVcSRUm Ku5KXalxg85sRqauGCU4W0y+hDeF5UZaHsxoSr2Y= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Stefan Klug , Jacopo Mondi Subject: [RFC PATCH v4 11/23] ipa: libipa: agc: Keep frame duration limits ordered Date: Mon, 26 Oct 2026 14:20:33 +0100 Message-ID: <20261026132045.269007-12-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261026132045.269007-1-barnabas.pocze@ideasonboard.com> References: <20261026132045.269007-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 7e85faf649..90850a296b 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -900,7 +900,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 Mon Oct 26 13:20: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: 28629 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 1A807C324C for ; Mon, 26 Oct 2026 13:21:16 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 2D38169005; Mon, 26 Oct 2026 14:21:09 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="YD8II3Qt"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 9763E68FFA for ; Mon, 26 Oct 2026 14:20: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 6277B268; Mon, 26 Oct 2026 14:18:40 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1793020720; bh=SAdhXQH/TP/AbFUej9qWn9Lz4IVIkpso1C6Ixl9tzls=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=YD8II3QtSOvipg0jzED65P5uozilJ8hrXh3tKx1T1VC21i/HwAh9jb7NsZ04wvM5N LkdpF/cDJAXwu6jNoeuyAJWEc3ZhnKsPlOBEBkG1KlYsEJxiMGWpKhvy4pYyDtJmLw HcPrmb7slJHw6zXY8Blj96tA5HG7YpitysNkVf/M= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Stefan Klug , Jacopo Mondi Subject: [RFC PATCH v4 12/23] ipa: libipa: agc: Retrieve `FrameDurationLimits` earlier Date: Mon, 26 Oct 2026 14:20:34 +0100 Message-ID: <20261026132045.269007-13-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261026132045.269007-1-barnabas.pocze@ideasonboard.com> References: <20261026132045.269007-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 90850a296b..0942f26bf2 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -849,6 +849,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; @@ -890,20 +904,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 Mon Oct 26 13:20: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: 28630 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 F29CDC3307 for ; Mon, 26 Oct 2026 13:21:16 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 7C96E6900C; Mon, 26 Oct 2026 14:21:10 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="E5fpnfr5"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id D846C69002 for ; Mon, 26 Oct 2026 14:20: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 A5FF8D0; Mon, 26 Oct 2026 14:18:40 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1793020720; bh=E7XorB3F3H/yRvf7FQH56DXWx3j7Xm7Jq8UsV5/6dIA=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=E5fpnfr53wncNCEQC7QEH03R9/0bR1CUMEA9ushMgS6nOfQ+8vp9SJtzE/mjSQjri Krk5Ky25DzUXObvgYJX+GIFKnszoESSpDOdGvtGmmRDLM+n/jO1YuAA83PqpDy1tS+ HAx1Csr+i9ekQiowwQnDnyO+r5QZcxr5sYZaqtgY= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi Subject: [RFC PATCH v4 13/23] ipa: libipa: agc: Report `FrameDurationLimits` Date: Mon, 26 Oct 2026 14:20:35 +0100 Message-ID: <20261026132045.269007-14-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261026132045.269007-1-barnabas.pocze@ideasonboard.com> References: <20261026132045.269007-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 --- 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 0942f26bf2..ff3d52c163 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -1127,6 +1127,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 Mon Oct 26 13:20: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: 28631 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 B4920C3302 for ; Mon, 26 Oct 2026 13:21:17 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 3A6B169033; Mon, 26 Oct 2026 14:21:12 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Wy4Nxkd/"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 1C13569003 for ; Mon, 26 Oct 2026 14:20: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 E0B26268; Mon, 26 Oct 2026 14:18:40 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1793020721; bh=WN0t299hJGwNTbU9Fwqo3gHCmdgXVK161BKcS4ggDC8=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Wy4Nxkd/CJRNCbBzL5aizSHLSBKqEP23JwOkJxtRg2PAarmV5+bUElqn7PQ9mozy8 TMqEZB+bM+M66QVxeT3KU1bwRxfAhpYDbjrp9/W0F85eJW9PXQNTtAKy6oqrXNAj+2 xyTOeghQVmZ3gQDtP69tEK6x01y64gT9i8i723KA= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi Subject: [RFC PATCH v4 14/23] ipa: libipa: agc: Calculate vblank and frame duration sooner Date: Mon, 26 Oct 2026 14:20:36 +0100 Message-ID: <20261026132045.269007-15-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261026132045.269007-1-barnabas.pocze@ideasonboard.com> References: <20261026132045.269007-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 --- 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 ff3d52c163..40cc4e26d0 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -908,6 +908,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 * @@ -917,11 +918,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; @@ -952,6 +952,8 @@ void AgcAlgorithm::prepare(agc::ActiveState &state, agc::FrameContext &frameCont } frameContext.yTarget = state.automatic.yTarget; + + processFrameDuration(session, frameContext); } /** @@ -982,7 +984,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; } @@ -1086,13 +1087,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); } @@ -1100,22 +1094,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 1f1d6c96a3..34e186d8b6 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 Mon Oct 26 13:20: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: 28632 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 A3349C324C for ; Mon, 26 Oct 2026 13:21:18 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 5346269031; Mon, 26 Oct 2026 14:21:14 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="qouEPdtU"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 5502469004 for ; Mon, 26 Oct 2026 14:20: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 2B297D0 for ; Mon, 26 Oct 2026 14:18:41 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1793020721; bh=JBLeL77dTPgOaH4xSAJu3Sy47DtYT89xkWqAYf4GGHM=; h=From:To:Subject:Date:In-Reply-To:References:From; b=qouEPdtU79tTOq96goFnRVt+ifll2EtoD4yP0WxvpsjiNyHPaxf82efUWC/Tc8+gF DQ8FWg3Ej/a+xyYsVMxk9qItmaOxRTdtery7mAhip+TNkBLoxHxsl1IuVB6lwFDCXD 9/s+R/FC8i2tREpc1UrRZLPphDUd71JjX1mO+qMo= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v4 15/23] ipa: libipa: agc: Better respect frame duration limits Date: Mon, 26 Oct 2026 14:20:37 +0100 Message-ID: <20261026132045.269007-16-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261026132045.269007-1-barnabas.pocze@ideasonboard.com> References: <20261026132045.269007-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 --- 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 40cc4e26d0..af5c179db6 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -1107,7 +1107,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 Mon Oct 26 13:20: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: 28633 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 7D465C3307 for ; Mon, 26 Oct 2026 13:21:19 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 223AD68FFD; Mon, 26 Oct 2026 14:21:16 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="LqQVQLYW"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 9B1EA68FE0 for ; Mon, 26 Oct 2026 14:20: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 62806268; Mon, 26 Oct 2026 14:18:41 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1793020721; bh=L32BfdKppi/yQ6M89In33PEU6NLlyJBzYRWS+U9yHAk=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=LqQVQLYWaexKoCNXjXVU8k4rTOOnq0dv31b5cgKBdba4pLm8UDFnhnhqHf5T/s8wj gIuR2p5tq84CxsVOyZVMG1cL/y01dcd9tBQr5xPH72UmSYUWuT6/UDx7XKoGYWHo6u gsOLaeRK0mJzlCoOac9LO6d3owDu/gWR4ENIzClU= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Stefan Klug , Jacopo Mondi Subject: [RFC PATCH v4 16/23] ipa: libipa: agc: Take parameters from active state for calculation Date: Mon, 26 Oct 2026 14:20:38 +0100 Message-ID: <20261026132045.269007-17-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261026132045.269007-1-barnabas.pocze@ideasonboard.com> References: <20261026132045.269007-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 af5c179db6..3df3e811d6 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -1001,24 +1001,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{ @@ -1064,10 +1062,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 Mon Oct 26 13:20: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: 28634 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 64B51C3316 for ; Mon, 26 Oct 2026 13:21:20 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 77AFB6902A; Mon, 26 Oct 2026 14:21:17 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="cB4OTPLs"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id E1C4F69007 for ; Mon, 26 Oct 2026 14:20: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 B346DD0; Mon, 26 Oct 2026 14:18:41 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1793020721; bh=rvPSVU993XLUz72pAJPoLFH8j9E/YdslARIettIDTOc=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=cB4OTPLsSjyGa/slaRd1NsPyvAWIWyOWvdGT78dSFgAIOIHlJMx8L4iBUQfF9cIlL WX38lseBmmlZPzfP5/uvvNTz80C2mO7b6UIyYJgoLjEjLemXJegTvBNk6H9PhqhH7l 7AyCoQ80abs6vleWYjtojkjWqv37hZyCxIGuu06Q= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi Subject: [RFC PATCH v4 17/23] libcamera: pipeline: Set vblank on more platforms Date: Mon, 26 Oct 2026 14:20:39 +0100 Message-ID: <20261026132045.269007-18-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261026132045.269007-1-barnabas.pocze@ideasonboard.com> References: <20261026132045.269007-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 --- 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 3df3e811d6..c68b751ec1 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 35c29ceca1..6444df7217 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 Mon Oct 26 13:20: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: 28635 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 49D54C3302 for ; Mon, 26 Oct 2026 13:21:21 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 7FA2A6903F; Mon, 26 Oct 2026 14:21:18 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="Mjtj0n9O"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 3BF1169008 for ; Mon, 26 Oct 2026 14:20: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 0B222268; Mon, 26 Oct 2026 14:18:41 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1793020722; bh=7omr8+zyANhugn60gpAFXBMpO903QT/wiml9Ev+UZTM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Mjtj0n9ONfNJ9p+67T0vil9BSme6wMBvJWVb4TAwkEvgbAy78VtS5czYic7tiU5MJ lYhcLYghgLgPI7/sLlSIEi1dknWa2yLz8BaS6oMxCRh+V8VpSOWxxZdPLSbgzreOiK jv0vbL/FUGdg0Q1br08wapZdBbNMVDaX7OK2bZok= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi Subject: [RFC PATCH v4 18/23] ipa: libipa: agc: Rework frame duration limit calculation Date: Mon, 26 Oct 2026 14:20:40 +0100 Message-ID: <20261026132045.269007-19-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261026132045.269007-1-barnabas.pocze@ideasonboard.com> References: <20261026132045.269007-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 --- 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 c68b751ec1..d52d798f8f 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -646,25 +646,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 @@ -678,8 +659,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 = {}; @@ -718,8 +699,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 Mon Oct 26 13:20: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: 28636 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 DA06BC3357 for ; Mon, 26 Oct 2026 13:21:21 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 925A86902B; Mon, 26 Oct 2026 14:21:20 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="ZeZEqahD"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 7E6986900A for ; Mon, 26 Oct 2026 14:20: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 4B4E0D0; Mon, 26 Oct 2026 14:18:42 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1793020722; bh=2yiui2T6pedd8LAGgN2yVMGMVEYklSJ4xzV0fKiwAdE=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=ZeZEqahDbyTrK6ugkXMQFFhCOUOl8+MNKijhtiEO1Lhk/zQMwyhR54CT1fmW6Hk54 KX2SylhNJHUR8HsTSBnq3Ei8sPqIf0jG4arWdcPFOgIgXOY2w4KJfrYK96zzBlb9ls 0pYuDYIIic/5L5N2E3J6BR0CY1JuWjaO5MyQOrq8= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi Subject: [RFC PATCH v4 19/23] ipa: camera_sensor_helper: Introduce exposure margin Date: Mon, 26 Oct 2026 14:20:41 +0100 Message-ID: <20261026132045.269007-20-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261026132045.269007-1-barnabas.pocze@ideasonboard.com> References: <20261026132045.269007-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 --- 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..3e13a844bc 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; /* FIXME: 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 Mon Oct 26 13:20: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: 28637 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 97217C324C for ; Mon, 26 Oct 2026 13:21:22 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id BE02869028; Mon, 26 Oct 2026 14:21:21 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="QrMuxydf"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id BE3EC6900B for ; Mon, 26 Oct 2026 14:20: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 92C4E4F1 for ; Mon, 26 Oct 2026 14:18:42 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1793020722; bh=FAFiCO6guVfMg396XKiC1hBQd//fgzXwpzvgbGpqnU0=; h=From:To:Subject:Date:In-Reply-To:References:From; b=QrMuxydfhYE++zqWeHtrvMh07R8qR4bYenEzJIJPX8eCYvtcqz/LXjfQa9vncwBW5 Z9NcIKq7uUMy+lDkuOf/Z9rw1OCQQGPYuGKRdxlJF54vrzJW/SLycd9F48R0wUo6hf w21PNpbe+SYnN1ilsOLenHQqD1UIXhiuegujHGB4= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v4 20/23] ipa: libipa: agc: Move control info map setup Date: Mon, 26 Oct 2026 14:20:42 +0100 Message-ID: <20261026132045.269007-21-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261026132045.269007-1-barnabas.pocze@ideasonboard.com> References: <20261026132045.269007-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 --- 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 d52d798f8f..cf2ae19737 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -686,47 +686,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. */ @@ -773,6 +732,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 Mon Oct 26 13:20: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: 28639 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 19B7AC3307 for ; Mon, 26 Oct 2026 13:21:24 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 176C56903B; Mon, 26 Oct 2026 14:21:23 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="AKEsbwYm"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 02C246900C for ; Mon, 26 Oct 2026 14:20:54 +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 CBBA6D0 for ; Mon, 26 Oct 2026 14:18:42 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1793020722; bh=mkDu0bLdcR3bdmr8SJfRTxbboCoxPMB++bCv1MhZYhI=; h=From:To:Subject:Date:In-Reply-To:References:From; b=AKEsbwYmTctLj18F3H3C/WSpBm+7CoNToGieMSqML+x0r1Kgi9ev5VYVle9ipYPrU MEUup+xt47IA1QT4pgkBWiIkEvxgL2ibYTgg7gasUcbBpS8YleLh155Y3f/3AzrUI+ iw9+4EPNZ3cCs4pziwaEor+2anPGu85ThgO45DfE= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v4 21/23] ipa: libipa: agc: Do not run faster than 30 fps by default Date: Mon, 26 Oct 2026 14:20:43 +0100 Message-ID: <20261026132045.269007-22-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261026132045.269007-1-barnabas.pocze@ideasonboard.com> References: <20261026132045.269007-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 --- 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 cf2ae19737..416178177e 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -732,6 +732,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 Mon Oct 26 13:20: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: 28638 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 69C03C3358 for ; Mon, 26 Oct 2026 13:21:23 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 6C08E69038; Mon, 26 Oct 2026 14:21:22 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="WIDQ2Mkl"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 331486900E for ; Mon, 26 Oct 2026 14:20:54 +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 0E1EC4F1 for ; Mon, 26 Oct 2026 14:18:43 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1793020723; bh=xrl86aWFRtOdVD0U6evLGn/nctpKiq96hRbVAxvaChY=; h=From:To:Subject:Date:In-Reply-To:References:From; b=WIDQ2MklYR9zLIm1o1tVg0u1Yt60lXxg1PpnEZ+S7RDFKfOEyP6PvqJbL5l1jpmcl vlePV9q3rX+tRXmrZJaUHIGahNt6T9GzQ815MtBwD6B+viHHgiPDtY3+OQoyr3VDHH kRc8ac4V8p1cdEtrHIEo4cOQ5uSRnvy4YLCsy1Kw= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [RFC PATCH v4 22/23] ipa: libipa: agc: Do not run slower than 15fps by default Date: Mon, 26 Oct 2026 14:20:44 +0100 Message-ID: <20261026132045.269007-23-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261026132045.269007-1-barnabas.pocze@ideasonboard.com> References: <20261026132045.269007-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 --- src/ipa/libipa/agc.cpp | 8 ++++++++ src/ipa/libipa/agc.h | 4 ++++ 2 files changed, 12 insertions(+) diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp index 416178177e..90f51ff035 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -568,6 +568,8 @@ int AgcAlgorithm::init(const ValueNode &tuningData, CameraSensorHelper *sensor, if (ret) return ret; + tuning_.hasExposureStages = !agcMLconfig.exposureModeHelpers.empty(); + fillMissing(agcMLconfig); impl_.emplace(std::move(agcMLconfig)); @@ -737,6 +739,12 @@ int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state, state.minFrameDuration = std::clamp( utils::Duration(1.0s / 30), session.minFrameDuration, session.maxFrameDuration); + + if (!tuning_.hasExposureStages) { + 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..44f5218b0c 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 hasExposureStages = false; + } tuning_; }; } /* namespace ipa */ From patchwork Mon Oct 26 13:20:45 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: 28640 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 C4BF7C335A for ; Mon, 26 Oct 2026 13:21:24 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id BF8B669045; Mon, 26 Oct 2026 14:21:23 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="LbsJufF8"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 7551A68FDC for ; Mon, 26 Oct 2026 14:20:54 +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 42DCDD0; Mon, 26 Oct 2026 14:18:43 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1793020723; bh=xBMyJ/AK3TfFCJY0KlzP7pKmY/Bp9H2x+6M4mWS6LKY=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=LbsJufF8ahKeuutx/uY2YnWNjRicHe4Lwu2TRmWbk6hJOWDT/CflGHaRVez7SpgGM ML4LoAVZzS+ZWEuoLtKGIfEUFDGcuoIzDuzgTRgP0f9VnOum5/7uclvbTXp/ShX5oT gToYDGxq3Dhj84yx3hELvLIq9Cu0P1y9U9MYyiD0= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Cc: Jacopo Mondi Subject: [RFC PATCH v4 23/23] ipa: libipa: agc: Take exposure margin into account Date: Mon, 26 Oct 2026 14:20:45 +0100 Message-ID: <20261026132045.269007-24-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261026132045.269007-1-barnabas.pocze@ideasonboard.com> References: <20261026132045.269007-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 Tested-by: Jacopo Mondi --- src/ipa/libipa/agc.cpp | 92 +++++++++++++++++++++++++++++++----------- src/ipa/libipa/agc.h | 3 +- 2 files changed, 71 insertions(+), 24 deletions(-) diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp index 90f51ff035..a8eb74c680 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 @@ -473,6 +478,23 @@ void fillMissing(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); +} + +[[nodiscard]] +uint32_t clampExposure(const agc::Session &session, const agc::ActiveState &state, + utils::Duration exposureTime) +{ + return clampExposure(session, state, exposureTime / session.lineDuration); +} + } /* namespace */ /** @@ -620,7 +642,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 @@ -629,7 +656,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. */ @@ -642,28 +668,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 +799,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()), @@ -864,7 +909,7 @@ void AgcAlgorithm::queueRequest(const agc::Session &session, agc::ActiveState &s const auto &exposure = controls.get(controls::ExposureTime); if (exposure && !state.autoExposureEnabled) { - state.manual.exposure = *exposure * 1.0us / session.lineDuration; + state.manual.exposure = clampExposure(session, state, *exposure * 1.0us); LOG(Agc, Debug) << "Set exposure to " << state.manual.exposure; } @@ -1002,9 +1047,8 @@ void AgcAlgorithm::process(const agc::Session &session, agc::ActiveState &state, if (state.autoExposureEnabled) { minExposureTime = session.minExposureTime; - maxExposureTime = std::clamp(state.maxFrameDuration, - session.minExposureTime, - session.maxExposureTime); + maxExposureTime = state.maxFrameDuration - + session.sensor.exposureMargin * lineDuration; } else { minExposureTime = lineDuration * state.manual.exposure; maxExposureTime = minExposureTime; @@ -1076,6 +1120,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 +1152,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 44f5218b0c..7bfc85189f 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;