Patch Detail
Show a patch.
GET /api/1.1/patches/27195/?format=api
{ "id": 27195, "url": "https://patchwork.libcamera.org/api/1.1/patches/27195/?format=api", "web_url": "https://patchwork.libcamera.org/patch/27195/", "project": { "id": 1, "url": "https://patchwork.libcamera.org/api/1.1/projects/1/?format=api", "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-16-barnabas.pocze@ideasonboard.com>", "date": "2026-07-03T15:38:17", "name": "[RFC,v1,15/17] ipa: libipa: Add `AgcAlgorithm`", "commit_ref": null, "pull_url": null, "state": "new", "archived": false, "hash": "42841c6fb84ebbb7e4245a8ffb202896215d3a46", "submitter": { "id": 216, "url": "https://patchwork.libcamera.org/api/1.1/people/216/?format=api", "name": "Barnabás Pőcze", "email": "barnabas.pocze@ideasonboard.com" }, "delegate": null, "mbox": "https://patchwork.libcamera.org/patch/27195/mbox/", "series": [ { "id": 6036, "url": "https://patchwork.libcamera.org/api/1.1/series/6036/?format=api", "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/27195/comments/", "check": "pending", "checks": "https://patchwork.libcamera.org/api/patches/27195/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 4B9E8C331D\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri, 3 Jul 2026 15:38:46 +0000 (UTC)", "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 7300E66011;\n\tFri, 3 Jul 2026 17:38:44 +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 31F3065FC1\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 3 Jul 2026 17:38:26 +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 01BA2DF3\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=\"nImFBRzI\"; dkim-atps=neutral", "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1783093060;\n\tbh=IkWf0P3HVc0tMo8+voc9P6z9vWAMhD/CCpLr+aS2F28=;\n\th=From:To:Subject:Date:In-Reply-To:References:From;\n\tb=nImFBRzIMk96GWhMbYzBCNELH459RPWmleHm8XnK/EJ7DSadQ5WxpxBljVW1SOP56\n\tMeOgUZ41fRZCxR72dh/GOHmwfxbYD95XUu5WfsvW8crLj20lITNMV51rmq3AmNs9ld\n\tf37VPakf0DJsNeUOFEWOqsViZkcvF0gRB8puHPoo=", "From": "=?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= <barnabas.pocze@ideasonboard.com>", "To": "libcamera-devel@lists.libcamera.org", "Subject": "[RFC PATCH v1 15/17] ipa: libipa: Add `AgcAlgorithm`", "Date": "Fri, 3 Jul 2026 17:38:17 +0200", "Message-ID": "<20260703153819.1088752-16-barnabas.pocze@ideasonboard.com>", "X-Mailer": "git-send-email 2.54.0", "In-Reply-To": "<20260703153819.1088752-1-barnabas.pocze@ideasonboard.com>", "References": "<20260703153819.1088752-1-barnabas.pocze@ideasonboard.com>", "MIME-Version": "1.0", "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": "Add a class that encompasses the core logic of an agc algorithm with\nsome calculations and control handling. This can be used to simplify\nthe implementation of specific algorithms.\n\nAlso port `AgcMeanLuminanceAlgorithm` to use it.\n\nSigned-off-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>\n---\n src/ipa/ipu3/algorithms/agc.cpp | 4 +-\n src/ipa/libipa/agc.cpp | 488 ++++++++++++++++++++++++++\n src/ipa/libipa/agc.h | 100 ++++++\n src/ipa/libipa/agc_mean_luminance.cpp | 429 ++--------------------\n src/ipa/libipa/agc_mean_luminance.h | 61 +---\n src/ipa/libipa/meson.build | 2 +\n src/ipa/mali-c55/algorithms/agc.cpp | 4 +-\n src/ipa/rkisp1/algorithms/agc.cpp | 4 +-\n src/ipa/rkisp1/algorithms/wdr.cpp | 2 +-\n 9 files changed, 627 insertions(+), 467 deletions(-)\n create mode 100644 src/ipa/libipa/agc.cpp\n create mode 100644 src/ipa/libipa/agc.h", "diff": "diff --git a/src/ipa/ipu3/algorithms/agc.cpp b/src/ipa/ipu3/algorithms/agc.cpp\nindex 0ca02390bd..fbf887c328 100644\n--- a/src/ipa/ipu3/algorithms/agc.cpp\n+++ b/src/ipa/ipu3/algorithms/agc.cpp\n@@ -74,7 +74,7 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData)\n \t\treturn ret;\n \n \tret = agc_.configure(context.configuration.agc, context.activeState.agc, {\n-\t\t.sensor = *context.camHelper,\n+\t\t.sensor = context.camHelper.get(),\n \t\t.sensorInfo = context.sensorInfo,\n \t\t.sensorControls = context.sensorControls,\n \t\t.ctrlMap = context.ctrlMap,\n@@ -99,7 +99,7 @@ int Agc::configure(IPAContext &context,\n \tbdsGrid_ = context.configuration.grid.bdsGrid;\n \n \treturn agc_.configure(context.configuration.agc, context.activeState.agc, {\n-\t\t.sensor = *context.camHelper,\n+\t\t.sensor = context.camHelper.get(),\n \t\t.sensorInfo = context.sensorInfo,\n \t\t.sensorControls = context.sensorControls,\n \t\t.ctrlMap = context.ctrlMap,\ndiff --git a/src/ipa/libipa/agc.cpp b/src/ipa/libipa/agc.cpp\nnew file mode 100644\nindex 0000000000..7fce952171\n--- /dev/null\n+++ b/src/ipa/libipa/agc.cpp\n@@ -0,0 +1,488 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2026 Ideas on Board Oy\n+ */\n+\n+#include \"agc.h\"\n+\n+#include <chrono>\n+\n+#include <linux/v4l2-controls.h>\n+\n+#include <libcamera/base/log.h>\n+\n+#include <libcamera/control_ids.h>\n+\n+namespace libcamera {\n+\n+namespace ipa {\n+\n+using namespace std::chrono_literals;\n+\n+LOG_DEFINE_CATEGORY(Agc)\n+\n+/**\n+ * \\class AgcAlgorithm\n+ * \\brief Commong handling of AGC related controls and calculation\n+ *\n+ * \\todo DigitalGain, DigitalGainMode\n+ */\n+\n+/**\n+ * \\struct AgcAlgorithm::Session\n+ * \\brief Session configuration for AgcAlgorithm\n+ *\n+ * \\var AgcAlgorithm::Session::minExposureTime\n+ * \\brief Minimum exposure time supported with the configured sensor\n+ *\n+ * \\var AgcAlgorithm::Session::maxExposureTime\n+ * \\brief Maximum exposure time supported with the configured sensor\n+ *\n+ * \\var AgcAlgorithm::Session::minAnalogueGain\n+ * \\brief Minimum analogue gain supported with the configured sensor\n+ *\n+ * \\var AgcAlgorithm::Session::maxAnalogueGain\n+ * \\brief Maximum analogue gain supported with the configured sensor\n+ *\n+ * \\var AgcAlgorithm::Session::minFrameDuration\n+ * \\brief Minimum frame duration supported with the configured sensor\n+ *\n+ * \\var AgcAlgorithm::Session::maxFrameDuration\n+ * \\brief Maximum frame duration supported with the configured sensor\n+ *\n+ * \\var AgcAlgorithm::Session::lineDuration\n+ * \\brief Line duration with the configured sensor and output size\n+ *\n+ * \\var AgcAlgorithm::Session::sensor\n+ * \\brief Details of the sensor configuration\n+ *\n+ * \\var AgcAlgorithm::Session::sensor.outputSize\n+ * \\brief Configured output size of the sensor\n+ *\n+ * \\var AgcAlgorithm::Session::autoAllowed\n+ * \\brief Whether automatic controls are allowed\n+ */\n+\n+/**\n+ * \\struct AgcAlgorithm::ActiveState\n+ * \\brief Active state for AgcAlgorithm\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+ * \\var AgcAlgorithm::ActiveState::manual\n+ * \\brief Manual exposure time and analog gain (set through requests)\n+ *\n+ * \\var AgcAlgorithm::ActiveState::manual.exposure\n+ * \\brief Manual exposure time expressed as a number of lines as set by the\n+ * ExposureTime control\n+ *\n+ * \\var AgcAlgorithm::ActiveState::manual.gain\n+ * \\brief Manual analogue gain as set by the AnalogueGain control\n+ *\n+ * \\var AgcAlgorithm::ActiveState::automatic\n+ * \\brief Automatic exposure time and analog gain (computed by the algorithm)\n+ *\n+ * \\var AgcAlgorithm::ActiveState::automatic.exposure\n+ * \\brief Automatic exposure time expressed as a number of lines\n+ *\n+ * \\var AgcAlgorithm::ActiveState::automatic.gain\n+ * \\brief Automatic analogue gain multiplier\n+ *\n+ * \\var AgcAlgorithm::ActiveState::autoExposureEnabled\n+ * \\brief Manual/automatic AGC state (exposure) as set by the ExposureTimeMode control\n+ *\n+ * \\var AgcAlgorithm::ActiveState::autoGainEnabled\n+ * \\brief Manual/automatic AGC state (gain) as set by the AnalogueGainMode control\n+ *\n+ * \\var AgcAlgorithm::ActiveState::minFrameDuration\n+ * \\brief Minimum frame duration as set by the FrameDurationLimits control\n+ *\n+ * \\var AgcAlgorithm::ActiveState::maxFrameDuration\n+ * \\brief Maximum frame duration as set by the FrameDurationLimits control\n+ */\n+\n+/**\n+ * \\struct AgcAlgorithm::FrameContext\n+ * \\brief Per-frame context for AgcAlgorithm\n+ *\n+ * \\var AgcAlgorithm::FrameContext::exposure\n+ * \\brief Exposure time expressed as a number of lines computed by the algorithm\n+ *\n+ * \\var AgcAlgorithm::FrameContext::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 AgcAlgorithm::FrameContext::vblank\n+ * \\brief Vertical blanking parameter computed by the algorithm\n+ *\n+ * \\var AgcAlgorithm::FrameContext::autoExposureEnabled\n+ * \\brief Manual/automatic AGC state (exposure) as set by the ExposureTimeMode control\n+ *\n+ * \\var AgcAlgorithm::FrameContext::autoGainEnabled\n+ * \\brief Manual/automatic AGC state (gain) as set by the AnalogueGainMode control\n+ *\n+ * \\var AgcAlgorithm::FrameContext::minFrameDuration\n+ * \\brief Minimum frame duration as set by the FrameDurationLimits control\n+ *\n+ * \\var AgcAlgorithm::FrameContext::maxFrameDuration\n+ * \\brief Maximum frame duration as set by the FrameDurationLimits control\n+ *\n+ * \\var AgcAlgorithm::FrameContext::frameDuration\n+ * \\brief The actual FrameDuration used by the algorithm for the frame\n+ *\n+ * \\var AgcAlgorithm::FrameContext::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 AgcAlgorithm::FrameContext::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+/**\n+ * \\struct AgcAlgorithm::ConfigurationParams\n+ * \\brief Parameters for AgcAlgorithm::configure()\n+ *\n+ * \\var AgcAlgorithm::ConfigurationParams::sensor\n+ * \\brief CameraSensorHelper for the sensor\n+ *\n+ * \\var AgcAlgorithm::ConfigurationParams::sensorInfo\n+ * \\brief Details of the sensor\n+ *\n+ * \\var AgcAlgorithm::ConfigurationParams::sensorControls\n+ * \\brief ControlInfoMap of the sensor\n+ *\n+ * \\var AgcAlgorithm::ConfigurationParams::ctrlMap\n+ * \\brief ControlMap to update with controls\n+ *\n+ * \\var AgcAlgorithm::ConfigurationParams::autoAllowed\n+ * \\brief Whether to enable auto controls\n+ */\n+\n+/**\n+ * \\struct AgcAlgorithm::Limits\n+ * \\brief Limits of AGC parameters\n+ *\n+ * This structure contains the limits to consider for the actual\n+ * AGC implementation.\n+ *\n+ * \\var AgcAlgorithm::Limits::exposure\n+ * \\brief Limits of exposure time\n+ *\n+ * \\var AgcAlgorithm::Limits::gain\n+ * \\brief Limits of analogue gain\n+ */\n+\n+/**\n+ * \\brief Initialize the session configuration and active state\n+ */\n+int AgcAlgorithm::configure(Session &session, ActiveState &state, const ConfigurationParams &config)\n+{\n+\tsession = {};\n+\tsession.lineDuration = config.sensorInfo.minLineLength * 1.0s\n+\t\t/ config.sensorInfo.pixelRate;\n+\tsession.sensor.outputSize = config.sensorInfo.outputSize;\n+\tsession.autoAllowed = config.autoAllowed;\n+\n+\tconst double lineDurationUs = session.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 = config.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+\tconfig.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 auto mapGain = [&](const ControlValue &v) {\n+\t\tauto code = v.get<int32_t>();\n+\t\treturn config.sensor ? config.sensor->gain(code) : code;\n+\t};\n+\n+\tconst ControlInfo &v4l2Gain = config.sensorControls.find(V4L2_CID_ANALOGUE_GAIN)->second;\n+\tfloat minGain = mapGain(v4l2Gain.min());\n+\tfloat maxGain = mapGain(v4l2Gain.max());\n+\tfloat defGain = mapGain(v4l2Gain.def());\n+\tconfig.ctrlMap[&controls::AnalogueGain] = ControlInfo{\n+\t\tminGain,\n+\t\tmaxGain,\n+\t\tdefGain,\n+\t};\n+\n+\tLOG(Agc, 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 = config.sensorControls.find(V4L2_CID_HBLANK)->second;\n+\tuint32_t hblank = v4l2HBlank.def().get<int32_t>();\n+\tuint32_t lineLength = config.sensorInfo.outputSize.width + hblank;\n+\n+\tconst ControlInfo &v4l2VBlank = config.sensorControls.find(V4L2_CID_VBLANK)->second;\n+\tstd::array<uint32_t, 3> frameHeights{\n+\t\tv4l2VBlank.min().get<int32_t>() + config.sensorInfo.outputSize.height,\n+\t\tv4l2VBlank.max().get<int32_t>() + config.sensorInfo.outputSize.height,\n+\t\tv4l2VBlank.def().get<int32_t>() + config.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 / (config.sensorInfo.pixelRate / 1000000U);\n+\t}\n+\n+\tconfig.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+\tsession.minFrameDuration = std::chrono::microseconds(frameDurations[0]);\n+\tsession.maxFrameDuration = std::chrono::microseconds(frameDurations[1]);\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+\tsession.minExposureTime = minExposure * session.lineDuration;\n+\tsession.maxExposureTime = maxExposure * session.lineDuration;\n+\tsession.minAnalogueGain = minGain;\n+\tsession.maxAnalogueGain = maxGain;\n+\n+\t/* Configure the default exposure and gain. */\n+\tstate = {};\n+\tstate.automatic.gain = session.minAnalogueGain;\n+\tstate.automatic.exposure = 10ms / session.lineDuration;\n+\tstate.manual.gain = state.automatic.gain;\n+\tstate.manual.exposure = state.automatic.exposure;\n+\tstate.autoExposureEnabled = session.autoAllowed;\n+\tstate.autoGainEnabled = session.autoAllowed;\n+\tstate.minFrameDuration = session.minFrameDuration;\n+\tstate.maxFrameDuration = session.maxFrameDuration;\n+\n+\tconst auto add = [&](const ControlId &cid, const auto &automatic, const auto &manual) {\n+\t\tstd::array<ControlValue, 2> values;\n+\t\tsize_t count = 0;\n+\n+\t\tif (session.autoAllowed)\n+\t\t\tvalues[count++] = ControlValue(automatic);\n+\n+\t\tvalues[count++] = ControlValue(manual);\n+\n+\t\tconfig.ctrlMap[&cid] = ControlInfo{\n+\t\t\t{ values.data(), count },\n+\t\t\tControlValue(session.autoAllowed ? automatic : manual),\n+\t\t};\n+\t};\n+\n+\tadd(controls::ExposureTimeMode, controls::ExposureTimeModeAuto, controls::ExposureTimeModeManual);\n+\tadd(controls::AnalogueGainMode, controls::AnalogueGainModeAuto, controls::AnalogueGainModeManual);\n+\n+\t/* \\todo Move this to the `Camera` class. */\n+\tconfig.ctrlMap[&controls::AeEnable] = ControlInfo{\n+\t\tfalse,\n+\t\tsession.autoAllowed,\n+\t\tsession.autoAllowed,\n+\t};\n+\n+\treturn 0;\n+}\n+\n+/**\n+ * \\brief Handle a \\a queueRequest operation\n+ */\n+void AgcAlgorithm::queueRequest(const Session &session, ActiveState &state,\n+\t\t\t\tFrameContext &frameContext, const ControlList &controls)\n+{\n+\tif (session.autoAllowed) {\n+\t\tconst auto &aeEnable = controls.get(controls::ExposureTimeMode);\n+\t\tif (aeEnable &&\n+\t\t (*aeEnable == controls::ExposureTimeModeAuto) != state.autoExposureEnabled) {\n+\t\t\tstate.autoExposureEnabled = (*aeEnable == controls::ExposureTimeModeAuto);\n+\n+\t\t\tLOG(Agc, Debug)\n+\t\t\t\t<< (state.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 (!state.autoExposureEnabled && !controls.get(controls::ExposureTime))\n+\t\t\t\tframeContext.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) != state.autoGainEnabled) {\n+\t\t\tstate.autoGainEnabled = (*agEnable == controls::AnalogueGainModeAuto);\n+\n+\t\t\tLOG(Agc, Debug)\n+\t\t\t\t<< (state.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 (!state.autoGainEnabled && !controls.get(controls::AnalogueGain))\n+\t\t\t\tframeContext.autoGainModeChange = true;\n+\t\t}\n+\t}\n+\n+\tconst auto &exposure = controls.get(controls::ExposureTime);\n+\tif (exposure && !state.autoExposureEnabled) {\n+\t\tstate.manual.exposure = *exposure * 1.0us / session.lineDuration;\n+\n+\t\tLOG(Agc, Debug)\n+\t\t\t<< \"Set exposure to \" << state.manual.exposure;\n+\t}\n+\n+\tconst auto &gain = controls.get(controls::AnalogueGain);\n+\tif (gain && !state.autoGainEnabled) {\n+\t\tstate.manual.gain = *gain;\n+\n+\t\tLOG(Agc, Debug) << \"Set gain to \" << state.manual.gain;\n+\t}\n+\n+\tframeContext.autoExposureEnabled = state.autoExposureEnabled;\n+\tframeContext.autoGainEnabled = state.autoGainEnabled;\n+\n+\tif (!frameContext.autoExposureEnabled)\n+\t\tframeContext.exposure = state.manual.exposure;\n+\tif (!frameContext.autoGainEnabled)\n+\t\tframeContext.gain = state.manual.gain;\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\tstate.minFrameDuration = std::clamp<utils::Duration>(\n+\t\t\tstd::chrono::microseconds((*frameDurationLimits).front()),\n+\t\t\tsession.minFrameDuration,\n+\t\t\tsession.maxFrameDuration\n+\t\t);\n+\n+\t\tstate.maxFrameDuration = std::clamp<utils::Duration>(\n+\t\t\tstd::chrono::microseconds((*frameDurationLimits).back()),\n+\t\t\tsession.minFrameDuration,\n+\t\t\tsession.maxFrameDuration\n+\t\t);\n+\t}\n+\tframeContext.minFrameDuration = state.minFrameDuration;\n+\tframeContext.maxFrameDuration = state.maxFrameDuration;\n+}\n+\n+/**\n+ * \\brief Handle a \\a prepare operation\n+ */\n+void AgcAlgorithm::prepare(ActiveState &state, FrameContext &frameContext)\n+{\n+\tuint32_t activeAutoExposure = state.automatic.exposure;\n+\tdouble activeAutoGain = state.automatic.gain;\n+\n+\t/* Populate exposure and gain in auto mode */\n+\tif (frameContext.autoExposureEnabled)\n+\t\tframeContext.exposure = activeAutoExposure;\n+\tif (frameContext.autoGainEnabled)\n+\t\tframeContext.gain = activeAutoGain;\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.autoExposureEnabled && frameContext.autoExposureModeChange) {\n+\t\tstate.manual.exposure = activeAutoExposure;\n+\t\tframeContext.exposure = activeAutoExposure;\n+\t}\n+\tif (!frameContext.autoGainEnabled && frameContext.autoGainModeChange) {\n+\t\tstate.manual.gain = activeAutoGain;\n+\t\tframeContext.gain = activeAutoGain;\n+\t}\n+}\n+\n+/**\n+ * \\brief Calculate the AGC limits for the given frame\n+ */\n+AgcAlgorithm::Limits AgcAlgorithm::calculateLimits(const Session &session, const FrameContext &frameContext)\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+\tLimits result;\n+\n+\tif (frameContext.autoExposureEnabled) {\n+\t\tresult.exposure = {\n+\t\t\tsession.minExposureTime,\n+\t\t\tstd::clamp(frameContext.maxFrameDuration, session.minExposureTime, session.maxExposureTime),\n+\t\t};\n+\t} else {\n+\t\tresult.exposure.first = session.lineDuration * frameContext.exposure;\n+\t\tresult.exposure.second = result.exposure.first;\n+\t}\n+\n+\tif (frameContext.autoGainEnabled)\n+\t\tresult.gain = { session.minAnalogueGain, session.maxAnalogueGain };\n+\telse\n+\t\tresult.gain = { frameContext.gain, frameContext.gain };\n+\n+\treturn result;\n+}\n+\n+/**\n+ * \\brief Handle a \\a process operation\n+ */\n+void AgcAlgorithm::process(const Session& session, FrameContext& frameContext,\n+\t\t\t utils::Duration newExposureTime, ControlList &metadata)\n+{\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+\tconst auto frameDuration = std::max(frameContext.minFrameDuration, newExposureTime);\n+\tframeContext.vblank = (frameDuration / session.lineDuration) - session.sensor.outputSize.height;\n+\n+\t/* Update frame duration accounting for line length quantization. */\n+\tframeContext.frameDuration = (session.sensor.outputSize.height + frameContext.vblank) * session.lineDuration;\n+\n+\tmetadata.set(controls::AnalogueGain, frameContext.gain);\n+\tmetadata.set(controls::ExposureTime, utils::Duration(session.lineDuration * frameContext.exposure).get<std::micro>());\n+\tmetadata.set(controls::FrameDuration, frameContext.frameDuration.get<std::micro>());\n+\tmetadata.set(controls::ExposureTimeMode,\n+\t\t frameContext.autoExposureEnabled\n+\t\t ? controls::ExposureTimeModeAuto\n+\t\t : controls::ExposureTimeModeManual);\n+\tmetadata.set(controls::AnalogueGainMode,\n+\t\t frameContext.autoGainEnabled\n+\t\t ? controls::AnalogueGainModeAuto\n+\t\t : controls::AnalogueGainModeManual);\n+}\n+\n+} /* namespace ipa */\n+\n+} /* namespace libcamera */\ndiff --git a/src/ipa/libipa/agc.h b/src/ipa/libipa/agc.h\nnew file mode 100644\nindex 0000000000..35d321a0a1\n--- /dev/null\n+++ b/src/ipa/libipa/agc.h\n@@ -0,0 +1,100 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2026 Ideas on Board Oy\n+ */\n+\n+#pragma once\n+\n+\n+#include <libcamera/base/utils.h>\n+\n+#include <libcamera/controls.h>\n+#include <libcamera/geometry.h>\n+\n+#include <libcamera/ipa/core_ipa_interface.h>\n+\n+#include \"camera_sensor_helper.h\"\n+\n+namespace libcamera {\n+\n+namespace ipa {\n+\n+class AgcAlgorithm\n+{\n+public:\n+\tstruct Session {\n+\t\tutils::Duration minExposureTime;\n+\t\tutils::Duration maxExposureTime;\n+\t\tdouble minAnalogueGain;\n+\t\tdouble maxAnalogueGain;\n+\t\tutils::Duration minFrameDuration;\n+\t\tutils::Duration maxFrameDuration;\n+\n+\t\tutils::Duration lineDuration;\n+\n+\t\tstruct {\n+\t\t\tSize outputSize;\n+\t\t} sensor;\n+\n+\t\tbool autoAllowed;\n+\t};\n+\n+\tstruct ActiveState {\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} automatic;\n+\n+\t\tbool autoExposureEnabled;\n+\t\tbool autoGainEnabled;\n+\t\tutils::Duration minFrameDuration;\n+\t\tutils::Duration maxFrameDuration;\n+\t};\n+\n+\tstruct FrameContext {\n+\t\tuint32_t exposure;\n+\t\tdouble gain;\n+\t\tuint32_t vblank;\n+\t\tbool autoExposureEnabled;\n+\t\tbool autoGainEnabled;\n+\t\tutils::Duration minFrameDuration;\n+\t\tutils::Duration maxFrameDuration;\n+\t\tutils::Duration frameDuration;\n+\t\tbool autoExposureModeChange;\n+\t\tbool autoGainModeChange;\n+\t};\n+\n+\tstruct ConfigurationParams {\n+\t\tconst CameraSensorHelper *sensor;\n+\t\tconst IPACameraSensorInfo &sensorInfo;\n+\t\tconst ControlInfoMap &sensorControls;\n+\t\tControlInfoMap::Map &ctrlMap;\n+\t\tbool autoAllowed = true;\n+\t};\n+\n+protected:\n+\tint configure(Session &session, ActiveState &state, const ConfigurationParams &config);\n+\n+\tvoid queueRequest(const Session &session, ActiveState &state,\n+\t\t\t FrameContext &frameContext, const ControlList &controls);\n+\n+\tvoid prepare(ActiveState &state, FrameContext &frameContext);\n+\n+\tstruct Limits {\n+\t\tstd::pair<utils::Duration, utils::Duration> exposure;\n+\t\tstd::pair<double, double> gain;\n+\t};\n+\n+\t[[nodiscard]] Limits calculateLimits(const Session &session, const FrameContext &frameContext);\n+\n+\tvoid process(const Session& session, FrameContext& frameContext,\n+\t\t utils::Duration newExposureTime, ControlList &metadata);\n+};\n+\n+} /* namespace ipa */\n+\n+} /* namespace libcamera */\ndiff --git a/src/ipa/libipa/agc_mean_luminance.cpp b/src/ipa/libipa/agc_mean_luminance.cpp\nindex 951a4b0e02..0755c7ccac 100644\n--- a/src/ipa/libipa/agc_mean_luminance.cpp\n+++ b/src/ipa/libipa/agc_mean_luminance.cpp\n@@ -8,11 +8,8 @@\n #include \"agc_mean_luminance.h\"\n \n #include <algorithm>\n-#include <chrono>\n #include <cmath>\n \n-#include <linux/v4l2-controls.h>\n-\n #include <libcamera/base/log.h>\n #include <libcamera/base/utils.h>\n \n@@ -748,80 +745,16 @@ AgcMeanLuminance::calculateNewEv(uint32_t constraintModeIndex,\n * \\todo DigitalGain, DigitalGainMode\n */\n \n-/**\n- * \\struct AgcMeanLuminanceAlgorithm::Session\n- * \\brief Session configuration for AgcMeanLuminanceAlgorithm\n- *\n- * \\var AgcMeanLuminanceAlgorithm::Session::minExposureTime\n- * \\brief Minimum exposure time supported with the configured sensor\n- *\n- * \\var AgcMeanLuminanceAlgorithm::Session::maxExposureTime\n- * \\brief Maximum exposure time supported with the configured sensor\n- *\n- * \\var AgcMeanLuminanceAlgorithm::Session::minAnalogueGain\n- * \\brief Minimum analogue gain supported with the configured sensor\n- *\n- * \\var AgcMeanLuminanceAlgorithm::Session::maxAnalogueGain\n- * \\brief Maximum analogue gain supported with the configured sensor\n- *\n- * \\var AgcMeanLuminanceAlgorithm::Session::minFrameDuration\n- * \\brief Minimum frame duration supported with the configured sensor\n- *\n- * \\var AgcMeanLuminanceAlgorithm::Session::maxFrameDuration\n- * \\brief Maximum frame duration supported with the configured sensor\n- *\n- * \\var AgcMeanLuminanceAlgorithm::Session::lineDuration\n- * \\brief Line duration with the configured sensor and output size\n- *\n- * \\var AgcMeanLuminanceAlgorithm::Session::sensor\n- * \\brief Details of the sensor configuration\n- *\n- * \\var AgcMeanLuminanceAlgorithm::Session::sensor.outputSize\n- * \\brief Configured output size of the sensor\n- *\n- * \\var AgcMeanLuminanceAlgorithm::Session::autoAllowed\n- * \\brief Whether automatic controls are allowed\n- */\n-\n /**\n * \\struct AgcMeanLuminanceAlgorithm::ActiveState\n * \\brief Active state for AgcMeanLuminanceAlgorithm\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- * \\var AgcMeanLuminanceAlgorithm::ActiveState::manual\n- * \\brief Manual exposure time and analog gain (set through requests)\n- *\n- * \\var AgcMeanLuminanceAlgorithm::ActiveState::manual.exposure\n- * \\brief Manual exposure time expressed as a number of lines as set by the\n- * ExposureTime control\n- *\n- * \\var AgcMeanLuminanceAlgorithm::ActiveState::manual.gain\n- * \\brief Manual analogue gain as set by the AnalogueGain control\n- *\n- * \\var AgcMeanLuminanceAlgorithm::ActiveState::automatic\n- * \\brief Automatic exposure time and analog gain (computed by the algorithm)\n- *\n- * \\var AgcMeanLuminanceAlgorithm::ActiveState::automatic.exposure\n- * \\brief Automatic exposure time expressed as a number of lines\n- *\n- * \\var AgcMeanLuminanceAlgorithm::ActiveState::automatic.gain\n- * \\brief Automatic analogue gain multiplier\n- *\n- * \\var AgcMeanLuminanceAlgorithm::ActiveState::automatic.quantizationGain\n+ * \\var AgcMeanLuminanceAlgorithm::ActiveState::quantizationGain\n * \\brief Automatic quantization gain multiplier\n *\n- * \\var AgcMeanLuminanceAlgorithm::ActiveState::automatic.yTarget\n+ * \\var AgcMeanLuminanceAlgorithm::ActiveState::yTarget\n * \\brief Automatically determined luminance target\n *\n- * \\var AgcMeanLuminanceAlgorithm::ActiveState::autoExposureEnabled\n- * \\brief Manual/automatic AGC state (exposure) as set by the ExposureTimeMode control\n- *\n- * \\var AgcMeanLuminanceAlgorithm::ActiveState::autoGainEnabled\n- * \\brief Manual/automatic AGC state (gain) as set by the AnalogueGainMode control\n- *\n * \\var AgcMeanLuminanceAlgorithm::ActiveState::exposureValue\n * \\brief Exposure value as set by the ExposureValue control\n *\n@@ -830,26 +763,12 @@ AgcMeanLuminance::calculateNewEv(uint32_t constraintModeIndex,\n *\n * \\var AgcMeanLuminanceAlgorithm::ActiveState::exposureMode\n * \\brief Exposure mode as set by the AeExposureMode control\n- *\n- * \\var AgcMeanLuminanceAlgorithm::ActiveState::minFrameDuration\n- * \\brief Minimum frame duration as set by the FrameDurationLimits control\n- *\n- * \\var AgcMeanLuminanceAlgorithm::ActiveState::maxFrameDuration\n- * \\brief Maximum frame duration as set by the FrameDurationLimits control\n */\n \n /**\n * \\struct AgcMeanLuminanceAlgorithm::FrameContext\n * \\brief Per-frame context for AgcMeanLuminanceAlgorithm\n *\n- * \\var AgcMeanLuminanceAlgorithm::FrameContext::exposure\n- * \\brief Exposure time expressed as a number of lines computed by the algorithm\n- *\n- * \\var AgcMeanLuminanceAlgorithm::FrameContext::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 AgcMeanLuminanceAlgorithm::FrameContext::quantizationGain\n * \\brief Quantization gain multiplier computed by the algorithm\n *\n@@ -859,59 +778,11 @@ AgcMeanLuminance::calculateNewEv(uint32_t constraintModeIndex,\n * \\var AgcMeanLuminanceAlgorithm::FrameContext::yTarget\n * \\brief Luminance target computed by the algorithm\n *\n- * \\var AgcMeanLuminanceAlgorithm::FrameContext::vblank\n- * \\brief Vertical blanking parameter computed by the algorithm\n- *\n- * \\var AgcMeanLuminanceAlgorithm::FrameContext::autoExposureEnabled\n- * \\brief Manual/automatic AGC state (exposure) as set by the ExposureTimeMode control\n- *\n- * \\var AgcMeanLuminanceAlgorithm::FrameContext::autoGainEnabled\n- * \\brief Manual/automatic AGC state (gain) as set by the AnalogueGainMode control\n- *\n * \\var AgcMeanLuminanceAlgorithm::FrameContext::constraintMode\n * \\brief Constraint mode as set by the AeConstraintMode control\n *\n * \\var AgcMeanLuminanceAlgorithm::FrameContext::exposureMode\n * \\brief Exposure mode as set by the AeExposureMode control\n- *\n- * \\var AgcMeanLuminanceAlgorithm::FrameContext::minFrameDuration\n- * \\brief Minimum frame duration as set by the FrameDurationLimits control\n- *\n- * \\var AgcMeanLuminanceAlgorithm::FrameContext::maxFrameDuration\n- * \\brief Maximum frame duration as set by the FrameDurationLimits control\n- *\n- * \\var AgcMeanLuminanceAlgorithm::FrameContext::frameDuration\n- * \\brief The actual FrameDuration used by the algorithm for the frame\n- *\n- * \\var AgcMeanLuminanceAlgorithm::FrameContext::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 AgcMeanLuminanceAlgorithm::FrameContext::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-/**\n- * \\struct AgcMeanLuminanceAlgorithm::ConfigurationParams\n- * \\brief Parameters for AgcMeanLuminanceAlgorithm::configure()\n- *\n- * \\var AgcMeanLuminanceAlgorithm::ConfigurationParams::sensor\n- * \\brief CameraSensorHelper for the sensor\n- *\n- * \\var AgcMeanLuminanceAlgorithm::ConfigurationParams::sensorInfo\n- * \\brief Details of the sensor\n- *\n- * \\var AgcMeanLuminanceAlgorithm::ConfigurationParams::sensorControls\n- * \\brief ControlInfoMap of the sensor\n- *\n- * \\var AgcMeanLuminanceAlgorithm::ConfigurationParams::ctrlMap\n- * \\brief ControlMap to update with controls\n- *\n- * \\var AgcMeanLuminanceAlgorithm::ConfigurationParams::autoAllowed\n- * \\brief Whether to enable auto controls\n */\n \n /**\n@@ -954,104 +825,18 @@ int AgcMeanLuminanceAlgorithm::init(const ValueNode &tuningData)\n */\n int AgcMeanLuminanceAlgorithm::configure(Session &session, ActiveState &state, const ConfigurationParams &config)\n {\n-\tsession = {};\n-\tsession.lineDuration = config.sensorInfo.minLineLength * 1.0s\n-\t\t/ config.sensorInfo.pixelRate;\n-\tsession.sensor.outputSize = config.sensorInfo.outputSize;\n-\tsession.autoAllowed = config.autoAllowed;\n-\n-\tconst double lineDurationUs = session.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 = config.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-\tconfig.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 = config.sensorControls.find(V4L2_CID_ANALOGUE_GAIN)->second;\n-\tfloat minGain = config.sensor.gain(v4l2Gain.min().get<int32_t>());\n-\tfloat maxGain = config.sensor.gain(v4l2Gain.max().get<int32_t>());\n-\tfloat defGain = config.sensor.gain(v4l2Gain.def().get<int32_t>());\n-\tconfig.ctrlMap[&controls::AnalogueGain] = ControlInfo{\n-\t\tminGain,\n-\t\tmaxGain,\n-\t\tdefGain,\n-\t};\n-\n-\tLOG(AgcMeanLuminance, 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 = config.sensorControls.find(V4L2_CID_HBLANK)->second;\n-\tuint32_t hblank = v4l2HBlank.def().get<int32_t>();\n-\tuint32_t lineLength = config.sensorInfo.outputSize.width + hblank;\n-\n-\tconst ControlInfo &v4l2VBlank = config.sensorControls.find(V4L2_CID_VBLANK)->second;\n-\tstd::array<uint32_t, 3> frameHeights{\n-\t\tv4l2VBlank.min().get<int32_t>() + config.sensorInfo.outputSize.height,\n-\t\tv4l2VBlank.max().get<int32_t>() + config.sensorInfo.outputSize.height,\n-\t\tv4l2VBlank.def().get<int32_t>() + config.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 / (config.sensorInfo.pixelRate / 1000000U);\n-\t}\n-\n-\tconfig.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-\tsession.minFrameDuration = std::chrono::microseconds(frameDurations[0]);\n-\tsession.maxFrameDuration = std::chrono::microseconds(frameDurations[1]);\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-\tsession.minExposureTime = minExposure * session.lineDuration;\n-\tsession.maxExposureTime = maxExposure * session.lineDuration;\n-\tsession.minAnalogueGain = minGain;\n-\tsession.maxAnalogueGain = maxGain;\n+\tint ret = AgcAlgorithm::configure(session, state, config);\n+\tif (ret)\n+\t\treturn ret;\n \n-\timpl_.configure(session.lineDuration, &config.sensor);\n+\timpl_.configure(session.lineDuration, config.sensor);\n \timpl_.setLimits(session.minExposureTime, session.maxExposureTime,\n \t\t\tsession.minAnalogueGain, session.maxAnalogueGain,\n \t\t\t{});\n \timpl_.resetFrameCount();\n \n-\t/* Configure the default exposure and gain. */\n-\tstate = {};\n-\tstate.automatic.gain = session.minAnalogueGain;\n-\tstate.automatic.exposure = 10ms / session.lineDuration;\n-\tstate.automatic.quantizationGain = 1;\n-\tstate.automatic.yTarget = impl_.effectiveYTarget();\n-\tstate.manual.gain = state.automatic.gain;\n-\tstate.manual.exposure = state.automatic.exposure;\n-\tstate.autoExposureEnabled = session.autoAllowed;\n-\tstate.autoGainEnabled = session.autoAllowed;\n+\tstate.quantizationGain = 1;\n+\tstate.yTarget = impl_.effectiveYTarget();\n \tstate.exposureValue = 0;\n \n \tstate.constraintMode =\n@@ -1059,34 +844,6 @@ int AgcMeanLuminanceAlgorithm::configure(Session &session, ActiveState &state, c\n \tstate.exposureMode =\n \t\tstatic_cast<controls::AeExposureModeEnum>(impl_.exposureModeHelpers().begin()->first);\n \n-\tstate.minFrameDuration = session.minFrameDuration;\n-\tstate.maxFrameDuration = session.maxFrameDuration;\n-\n-\tconst auto add = [&](const ControlId &cid, const auto &automatic, const auto &manual) {\n-\t\tstd::array<ControlValue, 2> values;\n-\t\tsize_t count = 0;\n-\n-\t\tif (session.autoAllowed)\n-\t\t\tvalues[count++] = ControlValue(automatic);\n-\n-\t\tvalues[count++] = ControlValue(manual);\n-\n-\t\tconfig.ctrlMap[&cid] = ControlInfo{\n-\t\t\t{ values.data(), count },\n-\t\t\tControlValue(session.autoAllowed ? automatic : manual),\n-\t\t};\n-\t};\n-\n-\tadd(controls::ExposureTimeMode, controls::ExposureTimeModeAuto, controls::ExposureTimeModeManual);\n-\tadd(controls::AnalogueGainMode, controls::AnalogueGainModeAuto, controls::AnalogueGainModeManual);\n-\n-\t/* \\todo Move this to the `Camera` class. */\n-\tconfig.ctrlMap[&controls::AeEnable] = ControlInfo{\n-\t\tfalse,\n-\t\tsession.autoAllowed,\n-\t\tsession.autoAllowed,\n-\t};\n-\n \t// \\todo Should these be added/removed based on `session.autoAllowed` ?\n \tconfig.ctrlMap[&controls::ExposureValue] = ControlInfo(-8.0f, 8.0f, 0.0f);\n \n@@ -1100,70 +857,9 @@ int AgcMeanLuminanceAlgorithm::configure(Session &session, ActiveState &state, c\n * \\brief Handle a \\a queueRequest operation\n */\n void AgcMeanLuminanceAlgorithm::queueRequest(const Session &session, ActiveState &state,\n-\t\t\t\tFrameContext &frameContext, const ControlList &controls)\n+\t\t\t\t\t FrameContext &frameContext, const ControlList &controls)\n {\n-\tif (session.autoAllowed) {\n-\t\tconst auto &aeEnable = controls.get(controls::ExposureTimeMode);\n-\t\tif (aeEnable &&\n-\t\t (*aeEnable == controls::ExposureTimeModeAuto) != state.autoExposureEnabled) {\n-\t\t\tstate.autoExposureEnabled = (*aeEnable == controls::ExposureTimeModeAuto);\n-\n-\t\t\tLOG(AgcMeanLuminance, Debug)\n-\t\t\t\t<< (state.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 (!state.autoExposureEnabled && !controls.get(controls::ExposureTime))\n-\t\t\t\tframeContext.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) != state.autoGainEnabled) {\n-\t\t\tstate.autoGainEnabled = (*agEnable == controls::AnalogueGainModeAuto);\n-\n-\t\t\tLOG(AgcMeanLuminance, Debug)\n-\t\t\t\t<< (state.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 (!state.autoGainEnabled && !controls.get(controls::AnalogueGain))\n-\t\t\t\tframeContext.autoGainModeChange = true;\n-\t\t}\n-\t}\n-\n-\tconst auto &exposure = controls.get(controls::ExposureTime);\n-\tif (exposure && !state.autoExposureEnabled) {\n-\t\tstate.manual.exposure = *exposure * 1.0us / session.lineDuration;\n-\n-\t\tLOG(AgcMeanLuminance, Debug)\n-\t\t\t<< \"Set exposure to \" << state.manual.exposure;\n-\t}\n-\n-\tconst auto &gain = controls.get(controls::AnalogueGain);\n-\tif (gain && !state.autoGainEnabled) {\n-\t\tstate.manual.gain = *gain;\n-\n-\t\tLOG(AgcMeanLuminance, Debug) << \"Set gain to \" << state.manual.gain;\n-\t}\n-\n-\tframeContext.autoExposureEnabled = state.autoExposureEnabled;\n-\tframeContext.autoGainEnabled = state.autoGainEnabled;\n-\n-\tif (!frameContext.autoExposureEnabled)\n-\t\tframeContext.exposure = state.manual.exposure;\n-\tif (!frameContext.autoGainEnabled)\n-\t\tframeContext.gain = state.manual.gain;\n+\tAgcAlgorithm::queueRequest(session, state, frameContext, controls);\n \n \tif (!frameContext.autoExposureEnabled &&\n \t !frameContext.autoGainEnabled)\n@@ -1185,24 +881,6 @@ void AgcMeanLuminanceAlgorithm::queueRequest(const Session &session, ActiveState\n \tif (exposureValue)\n \t\tstate.exposureValue = *exposureValue;\n \tframeContext.exposureValue = state.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\tstate.minFrameDuration = std::clamp<utils::Duration>(\n-\t\t\tstd::chrono::microseconds((*frameDurationLimits).front()),\n-\t\t\tsession.minFrameDuration,\n-\t\t\tsession.maxFrameDuration\n-\t\t);\n-\n-\t\tstate.maxFrameDuration = std::clamp<utils::Duration>(\n-\t\t\tstd::chrono::microseconds((*frameDurationLimits).back()),\n-\t\t\tsession.minFrameDuration,\n-\t\t\tsession.maxFrameDuration\n-\t\t);\n-\t}\n-\tframeContext.minFrameDuration = state.minFrameDuration;\n-\tframeContext.maxFrameDuration = state.maxFrameDuration;\n }\n \n /**\n@@ -1210,35 +888,17 @@ void AgcMeanLuminanceAlgorithm::queueRequest(const Session &session, ActiveState\n */\n void AgcMeanLuminanceAlgorithm::prepare(ActiveState &state, FrameContext &frameContext)\n {\n-\tuint32_t activeAutoExposure = state.automatic.exposure;\n-\tdouble activeAutoGain = state.automatic.gain;\n-\tdouble activeAutoQGain = state.automatic.quantizationGain;\n+\tAgcAlgorithm::prepare(state, frameContext);\n \n-\t/* Populate exposure and gain in auto mode */\n-\tif (frameContext.autoExposureEnabled) {\n-\t\tframeContext.exposure = activeAutoExposure;\n-\t\tframeContext.quantizationGain = activeAutoQGain;\n-\t}\n-\tif (frameContext.autoGainEnabled) {\n-\t\tframeContext.gain = activeAutoGain;\n-\t\tframeContext.quantizationGain = activeAutoQGain;\n-\t}\n+\tdouble activeAutoQGain = state.quantizationGain;\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.autoExposureEnabled && frameContext.autoExposureModeChange) {\n-\t\tstate.manual.exposure = activeAutoExposure;\n-\t\tframeContext.exposure = activeAutoExposure;\n-\t}\n-\tif (!frameContext.autoGainEnabled && frameContext.autoGainModeChange) {\n-\t\tstate.manual.gain = activeAutoGain;\n-\t\tframeContext.gain = activeAutoGain;\n+\tif (frameContext.autoExposureEnabled || frameContext.autoGainEnabled)\n \t\tframeContext.quantizationGain = activeAutoQGain;\n-\t}\n \n-\tframeContext.yTarget = state.automatic.yTarget;\n+\tif (!frameContext.autoGainEnabled && frameContext.autoGainModeChange)\n+\t\tframeContext.quantizationGain = activeAutoQGain; // \\todo WHAT?!\n+\n+\tframeContext.yTarget = state.yTarget;\n }\n \n /**\n@@ -1254,30 +914,7 @@ void AgcMeanLuminanceAlgorithm::process(const Session &session, ActiveState &sta\n \tif (params) {\n \t\tASSERT(session.autoAllowed);\n \n-\t\t/*\n-\t\t* Set the AGC limits using the fixed exposure time and/or gain in\n-\t\t* manual mode, or the sensor limits in auto mode.\n-\t\t*/\n-\t\tutils::Duration minExposureTime;\n-\t\tutils::Duration maxExposureTime;\n-\t\tdouble minAnalogueGain;\n-\t\tdouble maxAnalogueGain;\n-\n-\t\tif (frameContext.autoExposureEnabled) {\n-\t\t\tminExposureTime = session.minExposureTime;\n-\t\t\tmaxExposureTime = std::clamp(frameContext.maxFrameDuration, session.minExposureTime, session.maxExposureTime);\n-\t\t} else {\n-\t\t\tminExposureTime = lineDuration * frameContext.exposure;\n-\t\t\tmaxExposureTime = minExposureTime;\n-\t\t}\n-\n-\t\tif (frameContext.autoGainEnabled) {\n-\t\t\tminAnalogueGain = session.minAnalogueGain;\n-\t\t\tmaxAnalogueGain = session.maxAnalogueGain;\n-\t\t} else {\n-\t\t\tminAnalogueGain = frameContext.gain;\n-\t\t\tmaxAnalogueGain = frameContext.gain;\n-\t\t}\n+\t\tauto limits = AgcAlgorithm::calculateLimits(session, frameContext);\n \n \t\t/*\n \t\t* The Agc algorithm needs to know the effective exposure value that was\n@@ -1286,8 +923,8 @@ void AgcMeanLuminanceAlgorithm::process(const Session &session, ActiveState &sta\n \t\tutils::Duration effectiveExposureValue =\n \t\t\tlineDuration * params->exposure * params->gain;\n \n-\t\timpl_.setLimits(minExposureTime, maxExposureTime,\n-\t\t\t\tminAnalogueGain, maxAnalogueGain,\n+\t\timpl_.setLimits(limits.exposure.first, limits.exposure.second,\n+\t\t\t\tlimits.gain.first, limits.gain.second,\n \t\t\t\tstd::move(params->additionalConstraints));\n \n \t\timpl_.setExposureCompensation(pow(2.0, frameContext.exposureValue));\n@@ -1306,31 +943,11 @@ void AgcMeanLuminanceAlgorithm::process(const Session &session, ActiveState &sta\n \t\t/* Update the estimated exposure and gain. */\n \t\tstate.automatic.exposure = newExposureTime / lineDuration;\n \t\tstate.automatic.gain = aGain;\n-\t\tstate.automatic.quantizationGain = qGain;\n-\t\tstate.automatic.yTarget = impl_.effectiveYTarget();\n+\t\tstate.quantizationGain = qGain;\n+\t\tstate.yTarget = impl_.effectiveYTarget();\n \t}\n \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-\tconst auto frameDuration = std::max(frameContext.minFrameDuration, newExposureTime);\n-\tframeContext.vblank = (frameDuration / lineDuration) - session.sensor.outputSize.height;\n-\n-\t/* Update frame duration accounting for line length quantization. */\n-\tframeContext.frameDuration = (session.sensor.outputSize.height + frameContext.vblank) * lineDuration;\n-\n-\tmetadata.set(controls::AnalogueGain, frameContext.gain);\n-\tmetadata.set(controls::ExposureTime, utils::Duration(lineDuration * frameContext.exposure).get<std::micro>());\n-\tmetadata.set(controls::FrameDuration, frameContext.frameDuration.get<std::micro>());\n-\tmetadata.set(controls::ExposureTimeMode,\n-\t\t frameContext.autoExposureEnabled\n-\t\t ? controls::ExposureTimeModeAuto\n-\t\t : controls::ExposureTimeModeManual);\n-\tmetadata.set(controls::AnalogueGainMode,\n-\t\t frameContext.autoGainEnabled\n-\t\t ? controls::AnalogueGainModeAuto\n-\t\t : controls::AnalogueGainModeManual);\n+\tAgcAlgorithm::process(session, frameContext, newExposureTime, metadata);\n \n \tmetadata.set(controls::AeExposureMode, frameContext.exposureMode);\n \tmetadata.set(controls::AeConstraintMode, frameContext.constraintMode);\ndiff --git a/src/ipa/libipa/agc_mean_luminance.h b/src/ipa/libipa/agc_mean_luminance.h\nindex e7e2d8ad0d..ceae33ba3e 100644\n--- a/src/ipa/libipa/agc_mean_luminance.h\n+++ b/src/ipa/libipa/agc_mean_luminance.h\n@@ -22,6 +22,7 @@\n \n #include \"libcamera/internal/value_node.h\"\n \n+#include \"agc.h\"\n #include \"exposure_mode_helper.h\"\n #include \"histogram.h\"\n #include \"pwl.h\"\n@@ -119,71 +120,23 @@ private:\n \tControlInfoMap::Map controls_;\n };\n \n-class AgcMeanLuminanceAlgorithm\n+class AgcMeanLuminanceAlgorithm : public AgcAlgorithm\n {\n public:\n-\tstruct Session {\n-\t\tutils::Duration minExposureTime;\n-\t\tutils::Duration maxExposureTime;\n-\t\tdouble minAnalogueGain;\n-\t\tdouble maxAnalogueGain;\n-\t\tutils::Duration minFrameDuration;\n-\t\tutils::Duration maxFrameDuration;\n-\n-\t\tutils::Duration lineDuration;\n-\n-\t\tstruct {\n-\t\t\tSize outputSize;\n-\t\t} sensor;\n-\n-\t\tbool autoAllowed;\n-\t};\n-\n-\tstruct ActiveState {\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+\tstruct ActiveState : AgcAlgorithm::ActiveState {\n+\t\tdouble quantizationGain;\n+\t\tdouble yTarget;\n \t\tdouble exposureValue;\n \t\tcontrols::AeConstraintModeEnum constraintMode;\n \t\tcontrols::AeExposureModeEnum exposureMode;\n-\t\tutils::Duration minFrameDuration;\n-\t\tutils::Duration maxFrameDuration;\n \t};\n \n-\tstruct FrameContext {\n-\t\tuint32_t exposure;\n-\t\tdouble gain;\n+\tstruct FrameContext : AgcAlgorithm::FrameContext {\n \t\tdouble quantizationGain;\n-\t\tdouble exposureValue;\n \t\tdouble yTarget;\n-\t\tuint32_t vblank;\n-\t\tbool autoExposureEnabled;\n-\t\tbool autoGainEnabled;\n+\t\tdouble exposureValue;\n \t\tcontrols::AeConstraintModeEnum constraintMode;\n \t\tcontrols::AeExposureModeEnum exposureMode;\n-\t\tutils::Duration minFrameDuration;\n-\t\tutils::Duration maxFrameDuration;\n-\t\tutils::Duration frameDuration;\n-\t\tbool autoExposureModeChange;\n-\t\tbool autoGainModeChange;\n-\t};\n-\n-\tstruct ConfigurationParams {\n-\t\tconst CameraSensorHelper &sensor;\n-\t\tconst IPACameraSensorInfo &sensorInfo;\n-\t\tconst ControlInfoMap &sensorControls;\n-\t\tControlInfoMap::Map &ctrlMap;\n-\t\tbool autoAllowed = true;\n \t};\n \n \tint init(const ValueNode &tuningData);\ndiff --git a/src/ipa/libipa/meson.build b/src/ipa/libipa/meson.build\nindex 963c5ee730..47c027f707 100644\n--- a/src/ipa/libipa/meson.build\n+++ b/src/ipa/libipa/meson.build\n@@ -2,6 +2,7 @@\n \n libipa_headers = files([\n 'agc_mean_luminance.h',\n+ 'agc.h',\n 'algorithm.h',\n 'awb_bayes.h',\n 'awb_grey.h',\n@@ -23,6 +24,7 @@ libipa_headers = files([\n \n libipa_sources = files([\n 'agc_mean_luminance.cpp',\n+ 'agc.cpp',\n 'algorithm.cpp',\n 'awb_bayes.cpp',\n 'awb_grey.cpp',\ndiff --git a/src/ipa/mali-c55/algorithms/agc.cpp b/src/ipa/mali-c55/algorithms/agc.cpp\nindex 386541fd58..4c6c2b5acb 100644\n--- a/src/ipa/mali-c55/algorithms/agc.cpp\n+++ b/src/ipa/mali-c55/algorithms/agc.cpp\n@@ -128,7 +128,7 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData)\n \t\treturn ret;\n \n \tret = agc_.configure(context.configuration.agc, context.activeState.agc, {\n-\t\t.sensor = *context.camHelper,\n+\t\t.sensor = context.camHelper.get(),\n \t\t.sensorInfo = context.sensorInfo,\n \t\t.sensorControls = context.sensorControls,\n \t\t.ctrlMap = context.ctrlMap,\n@@ -148,7 +148,7 @@ int Agc::configure(IPAContext &context,\n \t\treturn ret;\n \n \tret = agc_.configure(context.configuration.agc, context.activeState.agc, {\n-\t\t.sensor = *context.camHelper,\n+\t\t.sensor = context.camHelper.get(),\n \t\t.sensorInfo = context.sensorInfo,\n \t\t.sensorControls = context.sensorControls,\n \t\t.ctrlMap = context.ctrlMap,\ndiff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp\nindex 0d01ec1bda..ceef60c029 100644\n--- a/src/ipa/rkisp1/algorithms/agc.cpp\n+++ b/src/ipa/rkisp1/algorithms/agc.cpp\n@@ -141,7 +141,7 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData)\n \t\treturn ret;\n \n \tret = agc_.configure(context.configuration.agc, context.activeState.agc, {\n-\t\t.sensor = *context.camHelper,\n+\t\t.sensor = context.camHelper.get(),\n \t\t.sensorInfo = context.sensorInfo,\n \t\t.sensorControls = context.sensorControls,\n \t\t.ctrlMap = context.ctrlMap,\n@@ -167,7 +167,7 @@ int Agc::init(IPAContext &context, const ValueNode &tuningData)\n int Agc::configure(IPAContext &context, const IPACameraSensorInfo &configInfo)\n {\n \tint ret = agc_.configure(context.configuration.agc, context.activeState.agc, {\n-\t\t.sensor = *context.camHelper,\n+\t\t.sensor = context.camHelper.get(),\n \t\t.sensorInfo = context.sensorInfo,\n \t\t.sensorControls = context.sensorControls,\n \t\t.ctrlMap = context.ctrlMap,\ndiff --git a/src/ipa/rkisp1/algorithms/wdr.cpp b/src/ipa/rkisp1/algorithms/wdr.cpp\nindex c3d73da2c5..b16aa7dd3b 100644\n--- a/src/ipa/rkisp1/algorithms/wdr.cpp\n+++ b/src/ipa/rkisp1/algorithms/wdr.cpp\n@@ -464,7 +464,7 @@ void WideDynamicRange::process(IPAContext &context, [[maybe_unused]] const uint3\n \n \t/* Calculate the gain needed to reach the requested yTarget. */\n \tdouble value = cumHist.interQuantileMean(0, 1.0) / cumHist.bins();\n-\tdouble gain = context.activeState.agc.automatic.yTarget / value;\n+\tdouble gain = context.activeState.agc.yTarget / value;\n \tgain = std::max(gain, 1.0);\n \n \tdouble speed = 0.2;\n", "prefixes": [ "RFC", "v1", "15/17" ] }