{"id":27188,"url":"https://patchwork.libcamera.org/api/patches/27188/?format=json","web_url":"https://patchwork.libcamera.org/patch/27188/","project":{"id":1,"url":"https://patchwork.libcamera.org/api/projects/1/?format=json","name":"libcamera","link_name":"libcamera","list_id":"libcamera_core","list_email":"libcamera-devel@lists.libcamera.org","web_url":"","scm_url":"","webscm_url":""},"msgid":"<20260703153819.1088752-12-barnabas.pocze@ideasonboard.com>","date":"2026-07-03T15:38:13","name":"[RFC,v1,11/17] ipa: rkisp1: Port to `AgcMeanLuminanceAlgorithm`","commit_ref":null,"pull_url":null,"state":"new","archived":false,"hash":"f3888807f658922dd3a8b3a66d4ac5de482160f2","submitter":{"id":216,"url":"https://patchwork.libcamera.org/api/people/216/?format=json","name":"Barnabás Pőcze","email":"barnabas.pocze@ideasonboard.com"},"delegate":null,"mbox":"https://patchwork.libcamera.org/patch/27188/mbox/","series":[{"id":6036,"url":"https://patchwork.libcamera.org/api/series/6036/?format=json","web_url":"https://patchwork.libcamera.org/project/libcamera/list/?series=6036","date":"2026-07-03T15:38:02","name":"ipa: libipa: agc rework","version":1,"mbox":"https://patchwork.libcamera.org/series/6036/mbox/"}],"comments":"https://patchwork.libcamera.org/api/patches/27188/comments/","check":"pending","checks":"https://patchwork.libcamera.org/api/patches/27188/checks/","tags":{},"headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id 34481C3302\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri,  3 Jul 2026 15:38:42 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 58FEC65FF9;\n\tFri,  3 Jul 2026 17:38:36 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 33C4D65FDF\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri,  3 Jul 2026 17:38:25 +0200 (CEST)","from pb-laptop.local (185.221.140.128.nat.pool.zt.hu\n\t[185.221.140.128])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 0F867DF3\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri,  3 Jul 2026 17:37:39 +0200 (CEST)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"A9bQlbNp\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1783093059;\n\tbh=8vr9OTo49b+4szn/5T10Ifcn3J9n5asCiGNYlN1BSvI=;\n\th=From:To:Subject:Date:In-Reply-To:References:From;\n\tb=A9bQlbNpp2ZPGx2CLS8LaTFNH7nVxGLd0VdmrOVnu3HwdLCsfmcds3a0KxXdnfWrI\n\tDY/kylH+MVs/tfHmRu1QYwGxJi2smTVExi2accFLYNM1KafEh5hNuC/DOBiYxks7AL\n\tWshmi1lmA/cxxnXL56hFAaQar2QfDEuQ8tLyfOvM=","From":"=?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= <barnabas.pocze@ideasonboard.com>","To":"libcamera-devel@lists.libcamera.org","Subject":"[RFC PATCH v1 11/17] ipa: rkisp1: Port to\n\t`AgcMeanLuminanceAlgorithm`","Date":"Fri,  3 Jul 2026 17:38:13 +0200","Message-ID":"<20260703153819.1088752-12-barnabas.pocze@ideasonboard.com>","X-Mailer":"git-send-email 2.54.0","In-Reply-To":"<20260703153819.1088752-1-barnabas.pocze@ideasonboard.com>","References":"<20260703153819.1088752-1-barnabas.pocze@ideasonboard.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"},"content":"Signed-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>\n---\n src/ipa/rkisp1/algorithms/agc.cpp | 481 ++++--------------------------\n src/ipa/rkisp1/algorithms/agc.h   |   8 +-\n src/ipa/rkisp1/algorithms/lux.cpp |   2 +-\n src/ipa/rkisp1/ipa_context.cpp    |  98 ------\n src/ipa/rkisp1/ipa_context.h      |  45 +--\n src/ipa/rkisp1/rkisp1.cpp         |   8 +-\n 6 files changed, 73 insertions(+), 569 deletions(-)","diff":"diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp\nindex 689d045b7a..0d01ec1bda 100644\n--- a/src/ipa/rkisp1/algorithms/agc.cpp\n+++ b/src/ipa/rkisp1/algorithms/agc.cpp\n@@ -8,9 +8,7 @@\n #include \"agc.h\"\n \n #include <algorithm>\n-#include <chrono>\n #include <cmath>\n-#include <tuple>\n #include <vector>\n \n #include <libcamera/base/log.h>\n@@ -35,89 +33,6 @@ namespace ipa::rkisp1::algorithms {\n \n LOG_DEFINE_CATEGORY(RkISP1Agc)\n \n-namespace {\n-\n-void reconfigure(IPAContext &context)\n-{\n-\tcontext.configuration.sensor.lineDuration =\n-\t\tcontext.sensorInfo.minLineLength * 1.0s / context.sensorInfo.pixelRate;\n-\n-\tdouble lineDurationUs = context.configuration.sensor.lineDuration.get<std::micro>();\n-\n-\t/*\n-\t * Compute exposure time limits from the V4L2_CID_EXPOSURE control\n-\t * limits and the line duration.\n-\t */\n-\n-\tconst ControlInfo &v4l2Exposure = context.sensorControls.find(V4L2_CID_EXPOSURE)->second;\n-\tint32_t minExposure = v4l2Exposure.min().get<int32_t>();\n-\tint32_t maxExposure = v4l2Exposure.max().get<int32_t>();\n-\tint32_t defExposure = v4l2Exposure.def().get<int32_t>();\n-\tcontext.ctrlMap[&controls::ExposureTime] = ControlInfo{\n-\t\tstatic_cast<int32_t>(minExposure * lineDurationUs),\n-\t\tstatic_cast<int32_t>(maxExposure * lineDurationUs),\n-\t\tstatic_cast<int32_t>(defExposure * lineDurationUs),\n-\t};\n-\n-\t/* Compute the analogue gain limits. */\n-\tconst ControlInfo &v4l2Gain = context.sensorControls.find(V4L2_CID_ANALOGUE_GAIN)->second;\n-\tfloat minGain = context.camHelper->gain(v4l2Gain.min().get<int32_t>());\n-\tfloat maxGain = context.camHelper->gain(v4l2Gain.max().get<int32_t>());\n-\tfloat defGain = context.camHelper->gain(v4l2Gain.def().get<int32_t>());\n-\tcontext.ctrlMap[&controls::AnalogueGain] = ControlInfo{\n-\t\tminGain,\n-\t\tmaxGain,\n-\t\tdefGain,\n-\t};\n-\n-\tLOG(RkISP1Agc, Debug)\n-\t\t<< \"Exposure: [\" << minExposure << \", \" << maxExposure\n-\t\t<< \"], gain: [\" << minGain << \", \" << maxGain << \"]\";\n-\n-\t/*\n-\t* Compute the frame duration limits.\n-\t*\n-\t* The frame length is computed assuming a fixed line length combined\n-\t* with the vertical frame sizes.\n-\t*/\n-\tconst ControlInfo &v4l2HBlank = context.sensorControls.find(V4L2_CID_HBLANK)->second;\n-\tuint32_t hblank = v4l2HBlank.def().get<int32_t>();\n-\tuint32_t lineLength = context.sensorInfo.outputSize.width + hblank;\n-\n-\tconst ControlInfo &v4l2VBlank = context.sensorControls.find(V4L2_CID_VBLANK)->second;\n-\tstd::array<uint32_t, 3> frameHeights{\n-\t\tv4l2VBlank.min().get<int32_t>() + context.sensorInfo.outputSize.height,\n-\t\tv4l2VBlank.max().get<int32_t>() + context.sensorInfo.outputSize.height,\n-\t\tv4l2VBlank.def().get<int32_t>() + context.sensorInfo.outputSize.height,\n-\t};\n-\n-\tstd::array<int64_t, 3> frameDurations;\n-\tfor (unsigned int i = 0; i < frameHeights.size(); ++i) {\n-\t\tuint64_t frameSize = lineLength * frameHeights[i];\n-\t\tframeDurations[i] = frameSize / (context.sensorInfo.pixelRate / 1000000U);\n-\t}\n-\n-\tcontext.ctrlMap[&controls::FrameDurationLimits] = ControlInfo{\n-\t\tframeDurations[0],\n-\t\tframeDurations[1],\n-\t\tSpan<const int64_t, 2>{ { frameDurations[2], frameDurations[2] } },\n-\t};\n-\n-\t/*\n-\t * When the AGC computes the new exposure values for a frame, it needs\n-\t * to know the limits for exposure time and analogue gain. As it depends\n-\t * on the sensor, update it with the controls.\n-\t *\n-\t * \\todo take VBLANK into account for maximum exposure time\n-\t */\n-\tcontext.configuration.sensor.minExposureTime = minExposure * context.configuration.sensor.lineDuration;\n-\tcontext.configuration.sensor.maxExposureTime = maxExposure * context.configuration.sensor.lineDuration;\n-\tcontext.configuration.sensor.minAnalogueGain = context.camHelper->gain(minGain);\n-\tcontext.configuration.sensor.maxAnalogueGain = context.camHelper->gain(maxGain);\n-}\n-\n-} /* namespace */\n-\n /**\n  * \\class Agc\n  * \\brief A mean-based auto-exposure algorithm\n@@ -221,7 +136,16 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData)\n {\n \tint ret;\n \n-\tret = agc_.parseTuningData(tuningData);\n+\tret = agc_.init(tuningData);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = agc_.configure(context.configuration.agc, context.activeState.agc, {\n+\t\t.sensor = *context.camHelper,\n+\t\t.sensorInfo = context.sensorInfo,\n+\t\t.sensorControls = context.sensorControls,\n+\t\t.ctrlMap = context.ctrlMap,\n+\t});\n \tif (ret)\n \t\treturn ret;\n \n@@ -230,21 +154,6 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData)\n \tif (ret)\n \t\treturn ret;\n \n-\tcontext.ctrlMap[&controls::ExposureTimeMode] =\n-\t\tControlInfo({ { ControlValue(controls::ExposureTimeModeAuto),\n-\t\t\t\tControlValue(controls::ExposureTimeModeManual) } },\n-\t\t\t    ControlValue(controls::ExposureTimeModeAuto));\n-\tcontext.ctrlMap[&controls::AnalogueGainMode] =\n-\t\tControlInfo({ { ControlValue(controls::AnalogueGainModeAuto),\n-\t\t\t\tControlValue(controls::AnalogueGainModeManual) } },\n-\t\t\t    ControlValue(controls::AnalogueGainModeAuto));\n-\t/* \\todo Move this to the Camera class */\n-\tcontext.ctrlMap[&controls::AeEnable] = ControlInfo(false, true, true);\n-\tcontext.ctrlMap[&controls::ExposureValue] = ControlInfo(-8.0f, 8.0f, 0.0f);\n-\tcontext.ctrlMap.merge(agc_.controls());\n-\n-\treconfigure(context);\n-\n \treturn 0;\n }\n \n@@ -257,47 +166,24 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData)\n  */\n int Agc::configure(IPAContext &context, const IPACameraSensorInfo &configInfo)\n {\n-\treconfigure(context);\n-\n-\t/* Configure the default exposure and gain. */\n-\tcontext.activeState.agc.automatic.gain = context.configuration.sensor.minAnalogueGain;\n-\tcontext.activeState.agc.automatic.exposure =\n-\t\t10ms / context.configuration.sensor.lineDuration;\n-\tcontext.activeState.agc.automatic.quantizationGain = 1.0;\n-\tcontext.activeState.agc.manual.gain = context.activeState.agc.automatic.gain;\n-\tcontext.activeState.agc.manual.exposure = context.activeState.agc.automatic.exposure;\n-\tcontext.activeState.agc.autoExposureEnabled = !context.configuration.raw;\n-\tcontext.activeState.agc.autoGainEnabled = !context.configuration.raw;\n-\tcontext.activeState.agc.exposureValue = 0.0;\n-\n-\tcontext.activeState.agc.constraintMode =\n-\t\tstatic_cast<controls::AeConstraintModeEnum>(agc_.constraintModes().begin()->first);\n-\tcontext.activeState.agc.exposureMode =\n-\t\tstatic_cast<controls::AeExposureModeEnum>(agc_.exposureModeHelpers().begin()->first);\n+\tint ret = agc_.configure(context.configuration.agc, context.activeState.agc, {\n+\t\t.sensor = *context.camHelper,\n+\t\t.sensorInfo = context.sensorInfo,\n+\t\t.sensorControls = context.sensorControls,\n+\t\t.ctrlMap = context.ctrlMap,\n+\t\t.autoAllowed = !context.configuration.raw,\n+\t});\n+\tif (ret)\n+\t\treturn ret;\n+\n \tcontext.activeState.agc.meteringMode =\n \t\tstatic_cast<controls::AeMeteringModeEnum>(meteringModes_.begin()->first);\n \n-\t/* Limit the frame duration to match current initialisation */\n-\tControlInfo &frameDurationLimits = context.ctrlMap[&controls::FrameDurationLimits];\n-\tcontext.activeState.agc.minFrameDuration = std::chrono::microseconds(frameDurationLimits.min().get<int64_t>());\n-\tcontext.activeState.agc.maxFrameDuration = std::chrono::microseconds(frameDurationLimits.max().get<int64_t>());\n-\n \tcontext.configuration.agc.measureWindow.h_offs = 0;\n \tcontext.configuration.agc.measureWindow.v_offs = 0;\n \tcontext.configuration.agc.measureWindow.h_size = configInfo.outputSize.width;\n \tcontext.configuration.agc.measureWindow.v_size = configInfo.outputSize.height;\n \n-\tagc_.configure(context.configuration.sensor.lineDuration, context.camHelper.get());\n-\n-\tagc_.setLimits(context.configuration.sensor.minExposureTime,\n-\t\t       context.configuration.sensor.maxExposureTime,\n-\t\t       context.configuration.sensor.minAnalogueGain,\n-\t\t       context.configuration.sensor.maxAnalogueGain, {});\n-\n-\tcontext.activeState.agc.automatic.yTarget = agc_.effectiveYTarget();\n-\n-\tagc_.resetFrameCount();\n-\n \treturn 0;\n }\n \n@@ -311,73 +197,7 @@ void Agc::queueRequest(IPAContext &context,\n {\n \tauto &agc = context.activeState.agc;\n \n-\tif (!context.configuration.raw) {\n-\t\tconst auto &aeEnable = controls.get(controls::ExposureTimeMode);\n-\t\tif (aeEnable &&\n-\t\t    (*aeEnable == controls::ExposureTimeModeAuto) != agc.autoExposureEnabled) {\n-\t\t\tagc.autoExposureEnabled = (*aeEnable == controls::ExposureTimeModeAuto);\n-\n-\t\t\tLOG(RkISP1Agc, Debug)\n-\t\t\t\t<< (agc.autoExposureEnabled ? \"Enabling\" : \"Disabling\")\n-\t\t\t\t<< \" AGC (exposure)\";\n-\n-\t\t\t/*\n-\t\t\t * If we go from auto -> manual with no manual control\n-\t\t\t * set, use the last computed value, which we don't\n-\t\t\t * know until prepare() so save this information.\n-\t\t\t *\n-\t\t\t * \\todo Check the previous frame at prepare() time\n-\t\t\t * instead of saving a flag here\n-\t\t\t */\n-\t\t\tif (!agc.autoExposureEnabled && !controls.get(controls::ExposureTime))\n-\t\t\t\tframeContext.agc.autoExposureModeChange = true;\n-\t\t}\n-\n-\t\tconst auto &agEnable = controls.get(controls::AnalogueGainMode);\n-\t\tif (agEnable &&\n-\t\t    (*agEnable == controls::AnalogueGainModeAuto) != agc.autoGainEnabled) {\n-\t\t\tagc.autoGainEnabled = (*agEnable == controls::AnalogueGainModeAuto);\n-\n-\t\t\tLOG(RkISP1Agc, Debug)\n-\t\t\t\t<< (agc.autoGainEnabled ? \"Enabling\" : \"Disabling\")\n-\t\t\t\t<< \" AGC (gain)\";\n-\t\t\t/*\n-\t\t\t * If we go from auto -> manual with no manual control\n-\t\t\t * set, use the last computed value, which we don't\n-\t\t\t * know until prepare() so save this information.\n-\t\t\t */\n-\t\t\tif (!agc.autoGainEnabled && !controls.get(controls::AnalogueGain))\n-\t\t\t\tframeContext.agc.autoGainModeChange = true;\n-\t\t}\n-\t}\n-\n-\tconst auto &exposure = controls.get(controls::ExposureTime);\n-\tif (exposure && !agc.autoExposureEnabled) {\n-\t\tagc.manual.exposure = *exposure * 1.0us\n-\t\t\t\t    / context.configuration.sensor.lineDuration;\n-\n-\t\tLOG(RkISP1Agc, Debug)\n-\t\t\t<< \"Set exposure to \" << agc.manual.exposure;\n-\t}\n-\n-\tconst auto &gain = controls.get(controls::AnalogueGain);\n-\tif (gain && !agc.autoGainEnabled) {\n-\t\tagc.manual.gain = *gain;\n-\n-\t\tLOG(RkISP1Agc, Debug) << \"Set gain to \" << agc.manual.gain;\n-\t}\n-\n-\tframeContext.agc.autoExposureEnabled = agc.autoExposureEnabled;\n-\tframeContext.agc.autoGainEnabled = agc.autoGainEnabled;\n-\n-\tif (!frameContext.agc.autoExposureEnabled)\n-\t\tframeContext.agc.exposure = agc.manual.exposure;\n-\tif (!frameContext.agc.autoGainEnabled)\n-\t\tframeContext.agc.gain = agc.manual.gain;\n-\n-\tif (!frameContext.agc.autoExposureEnabled &&\n-\t    !frameContext.agc.autoGainEnabled)\n-\t\tframeContext.agc.quantizationGain = 1.0;\n+\tagc_.queueRequest(context.configuration.agc, context.activeState.agc, frameContext.agc, controls);\n \n \tconst auto &meteringMode = controls.get(controls::AeMeteringMode);\n \tif (meteringMode) {\n@@ -386,42 +206,6 @@ void Agc::queueRequest(IPAContext &context,\n \t\t\tstatic_cast<controls::AeMeteringModeEnum>(*meteringMode);\n \t}\n \tframeContext.agc.meteringMode = agc.meteringMode;\n-\n-\tconst auto &exposureMode = controls.get(controls::AeExposureMode);\n-\tif (exposureMode)\n-\t\tagc.exposureMode =\n-\t\t\tstatic_cast<controls::AeExposureModeEnum>(*exposureMode);\n-\tframeContext.agc.exposureMode = agc.exposureMode;\n-\n-\tconst auto &constraintMode = controls.get(controls::AeConstraintMode);\n-\tif (constraintMode)\n-\t\tagc.constraintMode =\n-\t\t\tstatic_cast<controls::AeConstraintModeEnum>(*constraintMode);\n-\tframeContext.agc.constraintMode = agc.constraintMode;\n-\n-\tconst auto &exposureValue = controls.get(controls::ExposureValue);\n-\tif (exposureValue)\n-\t\tagc.exposureValue = *exposureValue;\n-\tframeContext.agc.exposureValue = agc.exposureValue;\n-\n-\tconst auto &frameDurationLimits = controls.get(controls::FrameDurationLimits);\n-\tif (frameDurationLimits) {\n-\t\t/* Limit the control value to the limits in ControlInfo */\n-\t\tControlInfo &limits = context.ctrlMap[&controls::FrameDurationLimits];\n-\t\tint64_t minFrameDuration =\n-\t\t\tstd::clamp((*frameDurationLimits).front(),\n-\t\t\t\t   limits.min().get<int64_t>(),\n-\t\t\t\t   limits.max().get<int64_t>());\n-\t\tint64_t maxFrameDuration =\n-\t\t\tstd::clamp((*frameDurationLimits).back(),\n-\t\t\t\t   limits.min().get<int64_t>(),\n-\t\t\t\t   limits.max().get<int64_t>());\n-\n-\t\tagc.minFrameDuration = std::chrono::microseconds(minFrameDuration);\n-\t\tagc.maxFrameDuration = std::chrono::microseconds(maxFrameDuration);\n-\t}\n-\tframeContext.agc.minFrameDuration = agc.minFrameDuration;\n-\tframeContext.agc.maxFrameDuration = agc.maxFrameDuration;\n }\n \n /**\n@@ -430,41 +214,13 @@ void Agc::queueRequest(IPAContext &context,\n void Agc::prepare(IPAContext &context, const uint32_t frame,\n \t\t  IPAFrameContext &frameContext, RkISP1Params *params)\n {\n-\tuint32_t activeAutoExposure = context.activeState.agc.automatic.exposure;\n-\tdouble activeAutoGain = context.activeState.agc.automatic.gain;\n-\tdouble activeAutoQGain = context.activeState.agc.automatic.quantizationGain;\n-\n-\t/* Populate exposure and gain in auto mode */\n-\tif (frameContext.agc.autoExposureEnabled) {\n-\t\tframeContext.agc.exposure = activeAutoExposure;\n-\t\tframeContext.agc.quantizationGain = activeAutoQGain;\n-\t}\n-\tif (frameContext.agc.autoGainEnabled) {\n-\t\tframeContext.agc.gain = activeAutoGain;\n-\t\tframeContext.agc.quantizationGain = activeAutoQGain;\n-\t}\n-\n-\t/*\n-\t * Populate manual exposure and gain from the active auto values when\n-\t * transitioning from auto to manual\n-\t */\n-\tif (!frameContext.agc.autoExposureEnabled && frameContext.agc.autoExposureModeChange) {\n-\t\tcontext.activeState.agc.manual.exposure = activeAutoExposure;\n-\t\tframeContext.agc.exposure = activeAutoExposure;\n-\t}\n-\tif (!frameContext.agc.autoGainEnabled && frameContext.agc.autoGainModeChange) {\n-\t\tcontext.activeState.agc.manual.gain = activeAutoGain;\n-\t\tframeContext.agc.gain = activeAutoGain;\n-\t\tframeContext.agc.quantizationGain = activeAutoQGain;\n-\t}\n+\tagc_.prepare(context.activeState.agc, frameContext.agc);\n \n \tif (context.configuration.compress.supported) {\n \t\tframeContext.compress.enable = true;\n \t\tframeContext.compress.gain = frameContext.agc.quantizationGain;\n \t}\n \n-\tframeContext.agc.yTarget = context.activeState.agc.automatic.yTarget;\n-\n \tif (frame > 0 && !frameContext.agc.updateMetering)\n \t\treturn;\n \n@@ -520,50 +276,6 @@ void Agc::prepare(IPAContext &context, const uint32_t frame,\n \t\t\t\t\t   static_cast<rkisp1_cif_isp_histogram_mode>(hstConfig->mode));\n }\n \n-void Agc::fillMetadata(IPAContext &context, IPAFrameContext &frameContext,\n-\t\t       ControlList &metadata)\n-{\n-\tutils::Duration exposureTime = context.configuration.sensor.lineDuration\n-\t\t\t\t     * frameContext.sensor.exposure;\n-\tmetadata.set(controls::AnalogueGain, frameContext.sensor.gain);\n-\tmetadata.set(controls::ExposureTime, exposureTime.get<std::micro>());\n-\tmetadata.set(controls::FrameDuration, frameContext.agc.frameDuration.get<std::micro>());\n-\tmetadata.set(controls::ExposureTimeMode,\n-\t\t     frameContext.agc.autoExposureEnabled\n-\t\t     ? controls::ExposureTimeModeAuto\n-\t\t     : controls::ExposureTimeModeManual);\n-\tmetadata.set(controls::AnalogueGainMode,\n-\t\t     frameContext.agc.autoGainEnabled\n-\t\t     ? controls::AnalogueGainModeAuto\n-\t\t     : controls::AnalogueGainModeManual);\n-\n-\tmetadata.set(controls::AeMeteringMode, frameContext.agc.meteringMode);\n-\tmetadata.set(controls::AeExposureMode, frameContext.agc.exposureMode);\n-\tmetadata.set(controls::AeConstraintMode, frameContext.agc.constraintMode);\n-\tmetadata.set(controls::ExposureValue, frameContext.agc.exposureValue);\n-}\n-\n-/**\n- * \\brief Process frame duration and compute vblank\n- * \\param[in] context The shared IPA context\n- * \\param[in] frameContext The current frame context\n- * \\param[in] frameDuration The target frame duration\n- *\n- * Compute and populate vblank from the target frame duration.\n- */\n-void Agc::processFrameDuration(IPAContext &context,\n-\t\t\t       IPAFrameContext &frameContext,\n-\t\t\t       utils::Duration frameDuration)\n-{\n-\tIPACameraSensorInfo &sensorInfo = context.sensorInfo;\n-\tutils::Duration lineDuration = context.configuration.sensor.lineDuration;\n-\n-\tframeContext.agc.vblank = (frameDuration / lineDuration) - sensorInfo.outputSize.height;\n-\n-\t/* Update frame duration accounting for line length quantization. */\n-\tframeContext.agc.frameDuration = (sensorInfo.outputSize.height + frameContext.agc.vblank) * lineDuration;\n-}\n-\n namespace {\n \n class AgcTraits final : public AgcMeanLuminance::Traits\n@@ -637,119 +349,54 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame,\n \t\t  IPAFrameContext &frameContext, const rkisp1_stat_buffer *stats,\n \t\t  ControlList &metadata)\n {\n-\tif (!stats) {\n-\t\tprocessFrameDuration(context, frameContext,\n-\t\t\t\t     frameContext.agc.minFrameDuration);\n-\t\tfillMetadata(context, frameContext, metadata);\n-\t\treturn;\n-\t}\n-\n-\tif (!(stats->meas_type & RKISP1_CIF_ISP_STAT_AUTOEXP)) {\n-\t\tfillMetadata(context, frameContext, metadata);\n-\t\tLOG(RkISP1Agc, Error) << \"AUTOEXP data is missing in statistics\";\n-\t\treturn;\n-\t}\n-\n-\tconst utils::Duration &lineDuration = context.configuration.sensor.lineDuration;\n-\n-\t/*\n-\t * \\todo Verify that the exposure and gain applied by the sensor for\n-\t * this frame match what has been requested. This isn't a hard\n-\t * requirement for stability of the AGC (the guarantee we need in\n-\t * automatic mode is a perfect match between the frame and the values\n-\t * we receive), but is important in manual mode.\n-\t */\n+\tmetadata.set(controls::AeMeteringMode, frameContext.agc.meteringMode);\n \n-\tconst rkisp1_cif_isp_stat *params = &stats->params;\n+\tconst rkisp1_cif_isp_stat *params = nullptr;\n \n-\t/*\n-\t * Set the AGC limits using the fixed exposure time and/or gain in\n-\t * manual mode, or the sensor limits in auto mode.\n-\t */\n-\tutils::Duration minExposureTime;\n-\tutils::Duration maxExposureTime;\n-\tdouble minAnalogueGain;\n-\tdouble maxAnalogueGain;\n-\n-\tif (frameContext.agc.autoExposureEnabled) {\n-\t\tminExposureTime = context.configuration.sensor.minExposureTime;\n-\t\tmaxExposureTime = std::clamp(frameContext.agc.maxFrameDuration,\n-\t\t\t\t\t     context.configuration.sensor.minExposureTime,\n-\t\t\t\t\t     context.configuration.sensor.maxExposureTime);\n-\t} else {\n-\t\tminExposureTime = context.configuration.sensor.lineDuration\n-\t\t\t\t* frameContext.agc.exposure;\n-\t\tmaxExposureTime = minExposureTime;\n+\tif (stats) {\n+\t\tif (stats->meas_type & RKISP1_CIF_ISP_STAT_AUTOEXP)\n+\t\t\tparams = &stats->params;\n+\t\telse\n+\t\t\tLOG(RkISP1Agc, Error) << \"AUTOEXP data is missing in statistics\";\n \t}\n \n-\tif (frameContext.agc.autoGainEnabled) {\n-\t\tminAnalogueGain = context.configuration.sensor.minAnalogueGain;\n-\t\tmaxAnalogueGain = context.configuration.sensor.maxAnalogueGain;\n+\tif (params) {\n+\t\t/*\n+\t\t* \\todo Verify that the exposure and gain applied by the sensor for\n+\t\t* this frame match what has been requested. This isn't a hard\n+\t\t* requirement for stability of the AGC (the guarantee we need in\n+\t\t* automatic mode is a perfect match between the frame and the values\n+\t\t* we receive), but is important in manual mode.\n+\t\t*/\n+\n+\t\tstd::vector<AgcMeanLuminance::AgcConstraint> additionalConstraints;\n+\t\tif (context.activeState.wdr.mode != controls::WdrOff)\n+\t\t\tadditionalConstraints.push_back(context.activeState.wdr.constraint);\n+\n+\t\tagc_.process(context.configuration.agc, context.activeState.agc, frameContext.agc, {{\n+\t\t\t.traits = AgcTraits{\n+\t\t\t\t{ params->ae.exp_mean, context.hw.numAeCells },\n+\t\t\t\tmeteringModes_.at(frameContext.agc.meteringMode),\n+\t\t\t},\n+\t\t\t.hist = {\n+\t\t\t\t/* The lower 4 bits are fractional and meant to be discarded. */\n+\t\t\t\t{ params->hist.hist_bins, context.hw.numHistogramBins },\n+\t\t\t\t[](uint32_t x) { return x >> 4; },\n+\t\t\t},\n+\t\t\t.exposure = frameContext.sensor.exposure,\n+\t\t\t/*\n+\t\t\t* Include the quantization gain if it was applied. Do not use\n+\t\t\t* compress.gain because it will include gains that shall not be\n+\t\t\t* reported to the user when HDR is implemented.\n+\t\t\t*/\n+\t\t\t.gain = frameContext.sensor.gain\n+\t\t\t\t* (frameContext.compress.enable ? frameContext.agc.quantizationGain : 1),\n+\t\t\t.additionalConstraints = std::move(additionalConstraints),\n+\t\t\t.lux = static_cast<unsigned int>(frameContext.lux.lux),\n+\t\t}}, metadata);\n \t} else {\n-\t\tminAnalogueGain = frameContext.agc.gain;\n-\t\tmaxAnalogueGain = frameContext.agc.gain;\n+\t\tagc_.process(context.configuration.agc, context.activeState.agc, frameContext.agc, {}, metadata);\n \t}\n-\n-\tstd::vector<AgcMeanLuminance::AgcConstraint> additionalConstraints;\n-\tif (context.activeState.wdr.mode != controls::WdrOff)\n-\t\tadditionalConstraints.push_back(context.activeState.wdr.constraint);\n-\n-\tagc_.setLimits(minExposureTime, maxExposureTime, minAnalogueGain, maxAnalogueGain,\n-\t\t       std::move(additionalConstraints));\n-\n-\t/*\n-\t * The Agc algorithm needs to know the effective exposure value that was\n-\t * applied to the sensor when the statistics were collected.\n-\t */\n-\tutils::Duration exposureTime = lineDuration * frameContext.sensor.exposure;\n-\tdouble analogueGain = frameContext.sensor.gain;\n-\tutils::Duration effectiveExposureValue = exposureTime * analogueGain;\n-\n-\t/*\n-\t * Include the quantization gain if it was applied. Do not use\n-\t * compress.gain because it will include gains that shall not be\n-\t * reported to the user when HDR is implemented.\n-\t */\n-\tif (frameContext.compress.enable)\n-\t\teffectiveExposureValue *= frameContext.agc.quantizationGain;\n-\n-\t/* The lower 4 bits are fractional and meant to be discarded. */\n-\tHistogram hist({ params->hist.hist_bins, context.hw.numHistogramBins },\n-\t\t       [](uint32_t x) { return x >> 4; });\n-\tAgcTraits agcTraits{\n-\t\t{ params->ae.exp_mean, context.hw.numAeCells },\n-\t\tmeteringModes_.at(frameContext.agc.meteringMode),\n-\t};\n-\n-\tagc_.setExposureCompensation(pow(2.0, frameContext.agc.exposureValue));\n-\tagc_.setLux(frameContext.lux.lux);\n-\n-\tutils::Duration newExposureTime;\n-\tdouble aGain, qGain, dGain;\n-\tstd::tie(newExposureTime, aGain, qGain, dGain) =\n-\t\tagc_.calculateNewEv(frameContext.agc.constraintMode,\n-\t\t\t\t    frameContext.agc.exposureMode,\n-\t\t\t\t    hist, effectiveExposureValue, agcTraits);\n-\n-\tLOG(RkISP1Agc, Debug)\n-\t\t<< \"Divided up exposure time, analogue gain, quantization gain\"\n-\t\t<< \" and digital gain are \" << newExposureTime << \", \" << aGain\n-\t\t<< \", \" << qGain << \" and \" << dGain;\n-\n-\tIPAActiveState &activeState = context.activeState;\n-\t/* Update the estimated exposure and gain. */\n-\tactiveState.agc.automatic.exposure = newExposureTime / lineDuration;\n-\tactiveState.agc.automatic.gain = aGain;\n-\tactiveState.agc.automatic.quantizationGain = qGain;\n-\tactiveState.agc.automatic.yTarget = agc_.effectiveYTarget();\n-\t/*\n-\t * Expand the target frame duration so that we do not run faster than\n-\t * the minimum frame duration when we have short exposures.\n-\t */\n-\tprocessFrameDuration(context, frameContext,\n-\t\t\t     std::max(frameContext.agc.minFrameDuration, newExposureTime));\n-\n-\tfillMetadata(context, frameContext, metadata);\n }\n \n REGISTER_IPA_ALGORITHM(Agc, \"Agc\")\ndiff --git a/src/ipa/rkisp1/algorithms/agc.h b/src/ipa/rkisp1/algorithms/agc.h\nindex 0527ca0d5f..9d3286709e 100644\n--- a/src/ipa/rkisp1/algorithms/agc.h\n+++ b/src/ipa/rkisp1/algorithms/agc.h\n@@ -47,14 +47,8 @@ private:\n \tuint8_t computeHistogramPredivider(const Size &size,\n \t\t\t\t\t   enum rkisp1_cif_isp_histogram_mode mode);\n \n-\tvoid fillMetadata(IPAContext &context, IPAFrameContext &frameContext,\n-\t\t\t  ControlList &metadata);\n-\tvoid processFrameDuration(IPAContext &context,\n-\t\t\t\t  IPAFrameContext &frameContext,\n-\t\t\t\t  utils::Duration frameDuration);\n-\n \tstd::map<int32_t, std::vector<uint8_t>> meteringModes_;\n-\tAgcMeanLuminance agc_;\n+\tAgcMeanLuminanceAlgorithm agc_;\n };\n \n } /* namespace ipa::rkisp1::algorithms */\ndiff --git a/src/ipa/rkisp1/algorithms/lux.cpp b/src/ipa/rkisp1/algorithms/lux.cpp\nindex 86e46c492f..ce6928a55d 100644\n--- a/src/ipa/rkisp1/algorithms/lux.cpp\n+++ b/src/ipa/rkisp1/algorithms/lux.cpp\n@@ -74,7 +74,7 @@ void Lux::process(IPAContext &context,\n \tif (!stats)\n \t\treturn;\n \n-\tutils::Duration exposureTime = context.configuration.sensor.lineDuration *\n+\tutils::Duration exposureTime = context.configuration.agc.lineDuration *\n \t\t\t\t       frameContext.sensor.exposure;\n \tdouble gain = frameContext.sensor.gain;\n \ndiff --git a/src/ipa/rkisp1/ipa_context.cpp b/src/ipa/rkisp1/ipa_context.cpp\nindex 1f94afda6b..47691674ad 100644\n--- a/src/ipa/rkisp1/ipa_context.cpp\n+++ b/src/ipa/rkisp1/ipa_context.cpp\n@@ -86,21 +86,6 @@ namespace libcamera::ipa::rkisp1 {\n  * \\var IPASessionConfiguration::sensor\n  * \\brief Sensor-specific configuration of the IPA\n  *\n- * \\var IPASessionConfiguration::sensor.minExposureTime\n- * \\brief Minimum exposure time supported with the sensor\n- *\n- * \\var IPASessionConfiguration::sensor.maxExposureTime\n- * \\brief Maximum exposure time supported with the sensor\n- *\n- * \\var IPASessionConfiguration::sensor.minAnalogueGain\n- * \\brief Minimum analogue gain supported with the sensor\n- *\n- * \\var IPASessionConfiguration::sensor.maxAnalogueGain\n- * \\brief Maximum analogue gain supported with the sensor\n- *\n- * \\var IPASessionConfiguration::sensor.lineDuration\n- * \\brief Line duration in microseconds\n- *\n  * \\var IPASessionConfiguration::sensor.size\n  * \\brief Sensor output resolution\n  */\n@@ -147,49 +132,8 @@ namespace libcamera::ipa::rkisp1 {\n  * \\var IPAActiveState::agc\n  * \\brief State for the Automatic Gain Control algorithm\n  *\n- * The \\a automatic variables track the latest values computed by algorithm\n- * based on the latest processed statistics. All other variables track the\n- * consolidated controls requested in queued requests.\n- *\n- * \\struct IPAActiveState::agc.manual\n- * \\brief Manual exposure time and analog gain (set through requests)\n- *\n- * \\var IPAActiveState::agc.manual.exposure\n- * \\brief Manual exposure time expressed as a number of lines as set by the\n- * ExposureTime control\n- *\n- * \\var IPAActiveState::agc.manual.gain\n- * \\brief Manual analogue gain as set by the AnalogueGain control\n- *\n- * \\struct IPAActiveState::agc.automatic\n- * \\brief Automatic exposure time and analog gain (computed by the algorithm)\n- *\n- * \\var IPAActiveState::agc.automatic.exposure\n- * \\brief Automatic exposure time expressed as a number of lines\n- *\n- * \\var IPAActiveState::agc.automatic.gain\n- * \\brief Automatic analogue gain multiplier\n- *\n- * \\var IPAActiveState::agc.autoExposureEnabled\n- * \\brief Manual/automatic AGC state (exposure) as set by the ExposureTimeMode control\n- *\n- * \\var IPAActiveState::agc.autoGainEnabled\n- * \\brief Manual/automatic AGC state (gain) as set by the AnalogueGainMode control\n- *\n- * \\var IPAActiveState::agc.constraintMode\n- * \\brief Constraint mode as set by the AeConstraintMode control\n- *\n- * \\var IPAActiveState::agc.exposureMode\n- * \\brief Exposure mode as set by the AeExposureMode control\n- *\n  * \\var IPAActiveState::agc.meteringMode\n  * \\brief Metering mode as set by the AeMeteringMode control\n- *\n- * \\var IPAActiveState::agc.minFrameDuration\n- * \\brief Minimum frame duration as set by the FrameDurationLimits control\n- *\n- * \\var IPAActiveState::agc.maxFrameDuration\n- * \\brief Maximum frame duration as set by the FrameDurationLimits control\n  */\n \n /**\n@@ -314,53 +258,11 @@ namespace libcamera::ipa::rkisp1 {\n  * the vertical blanking period is determined to maintain a consistent frame\n  * rate matched to the FrameDurationLimits as set by the user.\n  *\n- * \\var IPAFrameContext::agc.exposure\n- * \\brief Exposure time expressed as a number of lines computed by the algorithm\n- *\n- * \\var IPAFrameContext::agc.gain\n- * \\brief Analogue gain multiplier computed by the algorithm\n- *\n- * The gain should be adapted to the sensor specific gain code before applying.\n- *\n- * \\var IPAFrameContext::agc.vblank\n- * \\brief Vertical blanking parameter computed by the algorithm\n- *\n- * \\var IPAFrameContext::agc.autoExposureEnabled\n- * \\brief Manual/automatic AGC state (exposure) as set by the ExposureTimeMode control\n- *\n- * \\var IPAFrameContext::agc.autoGainEnabled\n- * \\brief Manual/automatic AGC state (gain) as set by the AnalogueGainMode control\n- *\n- * \\var IPAFrameContext::agc.constraintMode\n- * \\brief Constraint mode as set by the AeConstraintMode control\n- *\n- * \\var IPAFrameContext::agc.exposureMode\n- * \\brief Exposure mode as set by the AeExposureMode control\n- *\n  * \\var IPAFrameContext::agc.meteringMode\n  * \\brief Metering mode as set by the AeMeteringMode control\n  *\n- * \\var IPAFrameContext::agc.minFrameDuration\n- * \\brief Minimum frame duration as set by the FrameDurationLimits control\n- *\n- * \\var IPAFrameContext::agc.maxFrameDuration\n- * \\brief Maximum frame duration as set by the FrameDurationLimits control\n- *\n- * \\var IPAFrameContext::agc.frameDuration\n- * \\brief The actual FrameDuration used by the algorithm for the frame\n- *\n  * \\var IPAFrameContext::agc.updateMetering\n  * \\brief Indicate if new ISP AGC metering parameters need to be applied\n- *\n- * \\var IPAFrameContext::agc.autoExposureModeChange\n- * \\brief Indicate if autoExposureEnabled has changed from true in the previous\n- * frame to false in the current frame, and no manual exposure value has been\n- * supplied in the current frame.\n- *\n- * \\var IPAFrameContext::agc.autoGainModeChange\n- * \\brief Indicate if autoGainEnabled has changed from true in the previous\n- * frame to false in the current frame, and no manual gain value has been\n- * supplied in the current frame.\n  */\n \n /**\ndiff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h\nindex eff88b72f5..4f620b0672 100644\n--- a/src/ipa/rkisp1/ipa_context.h\n+++ b/src/ipa/rkisp1/ipa_context.h\n@@ -49,7 +49,7 @@ struct IPAHwSettings {\n };\n \n struct IPASessionConfiguration {\n-\tstruct {\n+\tstruct Agc : AgcMeanLuminanceAlgorithm::Session {\n \t\tstruct rkisp1_cif_isp_window measureWindow;\n \t} agc;\n \n@@ -63,12 +63,6 @@ struct IPASessionConfiguration {\n \t} compress;\n \n \tstruct {\n-\t\tutils::Duration minExposureTime;\n-\t\tutils::Duration maxExposureTime;\n-\t\tdouble minAnalogueGain;\n-\t\tdouble maxAnalogueGain;\n-\n-\t\tutils::Duration lineDuration;\n \t\tSize size;\n \t} sensor;\n \n@@ -77,26 +71,8 @@ struct IPASessionConfiguration {\n };\n \n struct IPAActiveState {\n-\tstruct {\n-\t\tstruct {\n-\t\t\tuint32_t exposure;\n-\t\t\tdouble gain;\n-\t\t} manual;\n-\t\tstruct {\n-\t\t\tuint32_t exposure;\n-\t\t\tdouble gain;\n-\t\t\tdouble quantizationGain;\n-\t\t\tdouble yTarget;\n-\t\t} automatic;\n-\n-\t\tbool autoExposureEnabled;\n-\t\tbool autoGainEnabled;\n-\t\tdouble exposureValue;\n-\t\tcontrols::AeConstraintModeEnum constraintMode;\n-\t\tcontrols::AeExposureModeEnum exposureMode;\n+\tstruct Agc : AgcMeanLuminanceAlgorithm::ActiveState {\n \t\tcontrols::AeMeteringModeEnum meteringMode;\n-\t\tutils::Duration minFrameDuration;\n-\t\tutils::Duration maxFrameDuration;\n \t} agc;\n \n \tstruct {\n@@ -155,24 +131,9 @@ struct IPAActiveState {\n };\n \n struct IPAFrameContext : public FrameContext {\n-\tstruct {\n-\t\tuint32_t exposure;\n-\t\tdouble gain;\n-\t\tdouble exposureValue;\n-\t\tdouble quantizationGain;\n-\t\tuint32_t vblank;\n-\t\tdouble yTarget;\n-\t\tbool autoExposureEnabled;\n-\t\tbool autoGainEnabled;\n-\t\tcontrols::AeConstraintModeEnum constraintMode;\n-\t\tcontrols::AeExposureModeEnum exposureMode;\n+\tstruct Agc : AgcMeanLuminanceAlgorithm::FrameContext {\n \t\tcontrols::AeMeteringModeEnum meteringMode;\n-\t\tutils::Duration minFrameDuration;\n-\t\tutils::Duration maxFrameDuration;\n-\t\tutils::Duration frameDuration;\n \t\tbool updateMetering;\n-\t\tbool autoExposureModeChange;\n-\t\tbool autoGainModeChange;\n \t} agc;\n \n \tstruct {\ndiff --git a/src/ipa/rkisp1/rkisp1.cpp b/src/ipa/rkisp1/rkisp1.cpp\nindex 38f55b1d86..7470159e0a 100644\n--- a/src/ipa/rkisp1/rkisp1.cpp\n+++ b/src/ipa/rkisp1/rkisp1.cpp\n@@ -328,10 +328,10 @@ void IPARkISP1::processStats(const uint32_t frame, const uint32_t bufferId,\n \t\tstats = reinterpret_cast<rkisp1_stat_buffer *>(\n \t\t\tmappedBuffers_.at(bufferId).planes()[0].data());\n \n-\tframeContext.sensor.exposure =\n-\t\tsensorControls.get(V4L2_CID_EXPOSURE).get<int32_t>();\n-\tframeContext.sensor.gain =\n-\t\tcontext_.camHelper->gain(sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get<int32_t>());\n+\tframeContext.sensor = {\n+\t\t.exposure = static_cast<uint32_t>(sensorControls.get(V4L2_CID_EXPOSURE).get<int32_t>()),\n+\t\t.gain = context_.camHelper->gain(sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get<int32_t>()),\n+\t};\n \n \tControlList metadata(controls::controls);\n \n","prefixes":["RFC","v1","11/17"]}