From patchwork Mon Aug 17 11:43:46 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: 27824 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 0CAFDC330F for ; Mon, 17 Aug 2026 11:45:12 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 7C798682FD; Mon, 17 Aug 2026 13:45:11 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="CMGv+Jk/"; 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 2D01B6829F for ; Mon, 17 Aug 2026 13:44:08 +0200 (CEST) Received: from pb-laptop.local (catv-89-132-78-151.catv.fixed.one.hu [89.132.78.151]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id EA75F19C6 for ; Mon, 17 Aug 2026 13:42:48 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1786966969; bh=lKY+py8ziPsIU89w9CEo57PRQrl3h2bGppSYLUI6i1Y=; h=From:To:Subject:Date:In-Reply-To:References:From; b=CMGv+Jk/nf4O9njmLvOVLaVJr3KrctuYrTNvvJGhoPDOnTlHoYS1dt2wFETEfQF2s /o0cJnKb4kcaf6pR5DCabv8787VVk5wT8pHGUhtRJ6NYsLXMxN/87frs008LwKh9vX w7LQy5OERPrmtf+wmlzacw0kRZS08lIO1Wb6XTPM= From: =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= To: libcamera-devel@lists.libcamera.org Subject: [PATCH v5 45/47] ipa: libipa: agc: Work without `CameraSensorHelper` Date: Mon, 17 Aug 2026 13:43:46 +0200 Message-ID: <20260817114349.994123-46-barnabas.pocze@ideasonboard.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260817114349.994123-1-barnabas.pocze@ideasonboard.com> References: <20260817114349.994123-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" `AgcMeanLuminance` can operate without a `CameraSensorHelper`, but in that case it assumes an "ideal" gain model, where gains are truly continuous, and a gain of x will apply a gain of exactly x to the result. This is not a good fit when only gain codes are available. So now that the agc algorithm from the simple ipa has been extracted (AgcMSV), let's use that in `AgcAlgorithm` to be able to always provide some level of automatic exposure/gain control. Even though the main use case for operating without a known gain model is empirically determining the gain model using the manual controls. Signed-off-by: Barnabás Pőcze --- src/ipa/ipu3/algorithms/agc.cpp | 4 +- src/ipa/libipa/agc.cpp | 202 +++++++++++++++++++--------- src/ipa/libipa/agc.h | 9 +- src/ipa/mali-c55/algorithms/agc.cpp | 4 +- src/ipa/rkisp1/algorithms/agc.cpp | 4 +- 5 files changed, 145 insertions(+), 78 deletions(-) diff --git a/src/ipa/ipu3/algorithms/agc.cpp b/src/ipa/ipu3/algorithms/agc.cpp index c9ea02ed00..975f82eaf3 100644 --- a/src/ipa/ipu3/algorithms/agc.cpp +++ b/src/ipa/ipu3/algorithms/agc.cpp @@ -68,12 +68,11 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData) { int ret; - ret = agc_.init(tuningData); + ret = agc_.init(tuningData, context.camHelper.get()); if (ret) return ret; ret = agc_.configure(context.configuration.agc, context.activeState.agc, { - .sensor = context.camHelper.get(), .sensorInfo = context.sensorInfo, .sensorControls = context.sensorControls, .ctrlMap = context.ctrlMap, @@ -98,7 +97,6 @@ int Agc::configure(IPAContext &context, bdsGrid_ = context.configuration.grid.bdsGrid; return agc_.configure(context.configuration.agc, context.activeState.agc, { - .sensor = context.camHelper.get(), .sensorInfo = context.sensorInfo, .sensorControls = context.sensorControls, .ctrlMap = context.ctrlMap, diff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp index ff1e8d5a84..d327548979 100644 --- a/src/ipa/libipa/agc.cpp +++ b/src/ipa/libipa/agc.cpp @@ -11,10 +11,12 @@ #include #include #include +#include #include #include +#include #include #include @@ -79,6 +81,9 @@ namespace agc { * \var agc::Session::maxAnalogueGain * \brief Maximum analogue gain for the streaming session * + * \var agc::Session::defAnalogueGain + * \brief Default analogue gain of the configured sensor + * * \var agc::Session::minFrameDuration * \brief Minimum frame duration for the streaming session * @@ -217,9 +222,6 @@ namespace agc { * \struct AgcAlgorithm::ConfigurationParams * \brief Parameters for AgcAlgorithm::configure() * - * \var AgcAlgorithm::ConfigurationParams::sensor - * \brief CameraSensorHelper for the sensor - * * \var AgcAlgorithm::ConfigurationParams::sensorInfo * \brief Current configuration of the sensor * @@ -264,11 +266,18 @@ namespace agc { /** * \brief Load tuning data */ -int AgcAlgorithm::init(const ValueNode &tuningData) +int AgcAlgorithm::init(const ValueNode &tuningData, CameraSensorHelper *sensor) { - int ret = impl_.parseTuningData(tuningData); - if (ret) - return ret; + if (sensor) { + auto &impl = impl_.emplace(); + int ret = impl.parseTuningData(tuningData); + if (ret) + return ret; + } else { + impl_.emplace(); + } + + sensor_ = sensor; return 0; } @@ -303,10 +312,14 @@ int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state, int32_t defExposure = v4l2Exposure.def().get(); /* Compute the analogue gain limits. */ + const auto extractGain = [&](const ControlValue &v) { + auto gainCode = v.get(); + return sensor_ ? sensor_->gain(gainCode) : gainCode; + }; const ControlInfo &v4l2Gain = config.sensorControls.find(V4L2_CID_ANALOGUE_GAIN)->second; - float minGain = config.sensor->gain(v4l2Gain.min().get()); - float maxGain = config.sensor->gain(v4l2Gain.max().get()); - float defGain = config.sensor->gain(v4l2Gain.def().get()); + float minGain = extractGain(v4l2Gain.min()); + float maxGain = extractGain(v4l2Gain.max()); + float defGain = extractGain(v4l2Gain.def()); LOG(Agc, Debug) << "exposure:[" << minExposure << ',' << maxExposure << ']' @@ -345,28 +358,21 @@ int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state, session.maxExposureTime = maxExposure * session.lineDuration; session.minAnalogueGain = minGain; session.maxAnalogueGain = maxGain; + session.defAnalogueGain = defGain; session.minFrameDuration = std::chrono::microseconds(frameDurations[0]); session.maxFrameDuration = std::chrono::microseconds(frameDurations[1]); - impl_.configure(session.lineDuration, config.sensor); - impl_.resetFrameCount(); - /* Configure the default exposure and gain. */ state = {}; state.automatic.gain = session.minAnalogueGain; state.automatic.exposure = defExposure; state.automatic.quantizationGain = 1; state.automatic.digitalGain = 1; - state.automatic.yTarget = impl_.effectiveYTarget(0, 1); state.manual.gain = state.automatic.gain; state.manual.exposure = state.automatic.exposure; state.autoExposureEnabled = session.autoAllowed; state.autoGainEnabled = session.autoAllowed; state.exposureValue = 0; - state.constraintMode = - static_cast(impl_.constraintModes().begin()->first); - state.exposureMode = - static_cast(impl_.exposureModeHelpers().begin()->first); state.minFrameDuration = session.minFrameDuration; state.maxFrameDuration = session.maxFrameDuration; @@ -417,25 +423,57 @@ int AgcAlgorithm::configure(agc::Session &session, agc::ActiveState &state, add(controls::AnalogueGainMode, controls::AnalogueGainModeAuto, controls::AnalogueGainModeManual); - if (session.autoAllowed) { - config.ctrlMap[&controls::ExposureValue] = ControlInfo(-8.0f, 8.0f, 0.0f); - - { - std::vector options; - for (const auto &[id, _] : impl_.constraintModes()) - options.emplace_back(id); - - config.ctrlMap[&controls::AeConstraintMode] = ControlInfo(options); - } - - { - std::vector options; - for (const auto &[id, _] : impl_.exposureModeHelpers()) - options.emplace_back(id); - - config.ctrlMap[&controls::AeExposureMode] = ControlInfo(options); - } - } + std::visit(utils::overloaded{ + [&](AgcMSV&) { + /* no constraint/exposure mode support */ + state.constraintMode = controls::AeConstraintModeEnum::ConstraintNormal; + state.exposureMode = controls::AeExposureModeEnum::ExposureNormal; + + state.automatic.yTarget = (2.5 - 1) / (5 - 1); /* \todo hack? */ + + if (session.autoAllowed) { + config.ctrlMap[&controls::AeConstraintMode] = ControlInfo( + std::array{ ControlValue(state.constraintMode) } + ); + + config.ctrlMap[&controls::AeExposureMode] = ControlInfo( + std::array{ ControlValue(state.exposureMode) } + ); + } + }, + [&](AgcMeanLuminance& impl) { + state.constraintMode = + static_cast(impl.constraintModes().begin()->first); + state.exposureMode = + static_cast(impl.exposureModeHelpers().begin()->first); + + state.automatic.yTarget = impl.effectiveYTarget(0, 1); + + ASSERT(sensor_); + impl.configure(session.lineDuration, sensor_); + impl.resetFrameCount(); + + if (session.autoAllowed) { + config.ctrlMap[&controls::ExposureValue] = ControlInfo(-8.0f, 8.0f, 0.0f); + + { + std::vector options; + for (const auto &[id, _] : impl.constraintModes()) + options.emplace_back(id); + + config.ctrlMap[&controls::AeConstraintMode] = ControlInfo(options); + } + + { + std::vector options; + for (const auto &[id, _] : impl.exposureModeHelpers()) + options.emplace_back(id); + + config.ctrlMap[&controls::AeExposureMode] = ControlInfo(options); + } + } + }, + }, impl_); return 0; } @@ -625,36 +663,68 @@ void AgcAlgorithm::process(const agc::Session &session, agc::ActiveState &state, maxAnalogueGain = frameContext.gain; } - /* - * The Agc algorithm needs to know the effective exposure value that was - * applied to the sensor when the statistics were collected. - */ - utils::Duration effectiveExposureValue = - lineDuration * params->exposure * params->gain; - - impl_.setLimits(minExposureTime, maxExposureTime, - minAnalogueGain, maxAnalogueGain, - std::move(params->additionalConstraints)); - - const auto &newEv = impl_.calculateNewEv({ - .traits = params->traits, - .yHist = params->yHist, - .effectiveExposureValue = effectiveExposureValue, - .constraintModeIndex = frameContext.constraintMode, - .exposureModeIndex = frameContext.exposureMode, - .lux = params->lux, - .exposureCompensation = pow(2.0, frameContext.exposureValue), - }); - - /* Update the estimated exposure and gain. */ - state.automatic.exposure = newEv.exposureTime / lineDuration; - state.automatic.gain = newEv.analogueGain; - state.automatic.quantizationGain = newEv.quantizationGain; - state.automatic.digitalGain = newEv.digitalGain; - state.automatic.yTarget = newEv.yTarget; + std::visit(utils::overloaded{ + [&](AgcMSV& impl) { + impl.setLimits({ + .exposure = { + uint32_t(minExposureTime / lineDuration), + uint32_t(maxExposureTime / lineDuration), + }, + .gain = { + minAnalogueGain, + maxAnalogueGain, + }, + /* gain codes -> step size of 1 */ + .gainMinStep = 1, + /* assume default gain is close to 1.0 */ + .gain1 = session.defAnalogueGain, + }); + + const auto& newEv = impl.calculateNewEv({ + .yHist = params->yHist, + .exposure = params->exposure, + .gain = params->gain, + }); + + state.automatic.exposure = newEv.exposure; + state.automatic.gain = newEv.analogueGain; + }, + [&](AgcMeanLuminance& impl) { + /* + * The Agc algorithm needs to know the effective exposure + * value that was applied to the sensor when the statistics + * were collected. + */ + utils::Duration effectiveExposureValue = + lineDuration * params->exposure * params->gain; + + impl.setLimits(minExposureTime, maxExposureTime, + minAnalogueGain, maxAnalogueGain, + std::move(params->additionalConstraints)); + + const auto &newEv = impl.calculateNewEv({ + .traits = params->traits, + .yHist = params->yHist, + .effectiveExposureValue = effectiveExposureValue, + .constraintModeIndex = frameContext.constraintMode, + .exposureModeIndex = frameContext.exposureMode, + .lux = params->lux, + .exposureCompensation = pow(2.0, frameContext.exposureValue), + }); + + /* Update the estimated exposure and gain. */ + state.automatic.exposure = newEv.exposureTime / lineDuration; + state.automatic.gain = newEv.analogueGain; + state.automatic.quantizationGain = newEv.quantizationGain; + state.automatic.digitalGain = newEv.digitalGain; + state.automatic.yTarget = newEv.yTarget; + }, + }, impl_); + + const utils::Duration newExposureTime = state.automatic.exposure * lineDuration; LOG(Agc, Debug) - << "exposure-time:" << newEv.exposureTime + << "exposure-time:" << newExposureTime << " analogue-gain:" << state.automatic.gain << " quantization-gain:" << state.automatic.quantizationGain << " digital-gain:" << state.automatic.digitalGain; @@ -664,7 +734,7 @@ void AgcAlgorithm::process(const agc::Session &session, agc::ActiveState &state, * the minimum frame duration when we have short exposures. */ processFrameDuration(session, frameContext, - std::max(frameContext.minFrameDuration, newEv.exposureTime)); + std::max(frameContext.minFrameDuration, newExposureTime)); fillMetadata(session, frameContext, metadata); } diff --git a/src/ipa/libipa/agc.h b/src/ipa/libipa/agc.h index 612ac539da..b31f7de8fa 100644 --- a/src/ipa/libipa/agc.h +++ b/src/ipa/libipa/agc.h @@ -9,6 +9,7 @@ #include #include +#include #include @@ -18,6 +19,7 @@ #include #include "agc_mean_luminance.h" +#include "agc_msv.h" #include "camera_sensor_helper.h" #include "histogram.h" @@ -32,6 +34,7 @@ struct Session { utils::Duration maxExposureTime; double minAnalogueGain; double maxAnalogueGain; + double defAnalogueGain; utils::Duration minFrameDuration; utils::Duration maxFrameDuration; utils::Duration lineDuration; @@ -113,7 +116,6 @@ class AgcAlgorithm { public: struct ConfigurationParams { - const CameraSensorHelper *sensor; const IPACameraSensorInfo &sensorInfo; const ControlInfoMap &sensorControls; ControlInfoMap::Map &ctrlMap; @@ -129,7 +131,7 @@ public: double lux = 0; }; - int init(const ValueNode &tuningData); + int init(const ValueNode &tuningData, CameraSensorHelper *sensor); int configure(agc::Session &session, agc::ActiveState &state, const ConfigurationParams &config); @@ -151,7 +153,8 @@ private: const agc::FrameContext &frameContext, ControlList &metadata); - AgcMeanLuminance impl_; + std::variant impl_; + CameraSensorHelper *sensor_ = nullptr; }; } /* namespace ipa */ diff --git a/src/ipa/mali-c55/algorithms/agc.cpp b/src/ipa/mali-c55/algorithms/agc.cpp index 4b675eb7e5..140f34ad50 100644 --- a/src/ipa/mali-c55/algorithms/agc.cpp +++ b/src/ipa/mali-c55/algorithms/agc.cpp @@ -122,12 +122,11 @@ Agc::Agc() int Agc::init(IPAContext &context, const ValueNode &tuningData) { - int ret = agc_.init(tuningData); + int ret = agc_.init(tuningData, context.camHelper.get()); if (ret) return ret; ret = agc_.configure(context.configuration.agc, context.activeState.agc, { - .sensor = context.camHelper.get(), .sensorInfo = context.sensorInfo, .sensorControls = context.sensorControls, .ctrlMap = context.ctrlMap, @@ -147,7 +146,6 @@ int Agc::configure(IPAContext &context, return ret; ret = agc_.configure(context.configuration.agc, context.activeState.agc, { - .sensor = context.camHelper.get(), .sensorInfo = context.sensorInfo, .sensorControls = context.sensorControls, .ctrlMap = context.ctrlMap, diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp index 4c2a066e86..41a8cd581f 100644 --- a/src/ipa/rkisp1/algorithms/agc.cpp +++ b/src/ipa/rkisp1/algorithms/agc.cpp @@ -136,12 +136,11 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData) { int ret; - ret = agc_.init(tuningData); + ret = agc_.init(tuningData, context.camHelper.get()); if (ret) return ret; ret = agc_.configure(context.configuration.agc, context.activeState.agc, { - .sensor = context.camHelper.get(), .sensorInfo = context.sensorInfo, .sensorControls = context.sensorControls, .ctrlMap = context.ctrlMap, @@ -167,7 +166,6 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData) int Agc::configure(IPAContext &context, const IPACameraSensorInfo &configInfo) { int ret = agc_.configure(context.configuration.agc, context.activeState.agc, { - .sensor = context.camHelper.get(), .sensorInfo = context.sensorInfo, .sensorControls = context.sensorControls, .ctrlMap = context.ctrlMap,