{"id":14758,"url":"https://patchwork.libcamera.org/api/1.1/patches/14758/?format=json","web_url":"https://patchwork.libcamera.org/patch/14758/","project":{"id":1,"url":"https://patchwork.libcamera.org/api/1.1/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":"<20211124134019.110765-11-jeanmichel.hautbois@ideasonboard.com>","date":"2021-11-24T13:40:18","name":"[libcamera-devel,v4,10/11] ipa: rkisp1: Introduce AGC","commit_ref":null,"pull_url":null,"state":"superseded","archived":false,"hash":"9edd6972759a7d2a10bcb2615149c9e31bc424be","submitter":{"id":75,"url":"https://patchwork.libcamera.org/api/1.1/people/75/?format=json","name":"Jean-Michel Hautbois","email":"jeanmichel.hautbois@ideasonboard.com"},"delegate":null,"mbox":"https://patchwork.libcamera.org/patch/14758/mbox/","series":[{"id":2751,"url":"https://patchwork.libcamera.org/api/1.1/series/2751/?format=json","web_url":"https://patchwork.libcamera.org/project/libcamera/list/?series=2751","date":"2021-11-24T13:40:08","name":"Introduce AGC for RkISP1","version":4,"mbox":"https://patchwork.libcamera.org/series/2751/mbox/"}],"comments":"https://patchwork.libcamera.org/api/patches/14758/comments/","check":"pending","checks":"https://patchwork.libcamera.org/api/patches/14758/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 83D43BF415\n\tfor <parsemail@patchwork.libcamera.org>;\n\tWed, 24 Nov 2021 13:40:36 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 18E41603F9;\n\tWed, 24 Nov 2021 14:40:36 +0100 (CET)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 0D72C60233\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 24 Nov 2021 14:40:25 +0100 (CET)","from tatooine.ideasonboard.com (unknown\n\t[IPv6:2a01:e0a:169:7140:968b:bd0c:97fc:7c17])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id A405E1B61;\n\tWed, 24 Nov 2021 14:40:24 +0100 (CET)"],"Authentication-Results":"lancelot.ideasonboard.com;\n\tdkim=fail reason=\"signature verification failed\" (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"NxXir3+n\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1637761224;\n\tbh=sbspvGj11McE/Lr9FRGnL3wogJGYBeWxoCcXzrpm/Jg=;\n\th=From:To:Cc:Subject:Date:In-Reply-To:References:From;\n\tb=NxXir3+nlfC8RS/rzhi17CE0nqZth+KJp78oV9X8WvKMufj7rEiAmVx0Fne+Tnl2m\n\tLQEneXlsZiLETuaA6DoIfQGF0quVczQsUX4l/5xSiZaeIn2v9uucXB9NVWH51BXSKF\n\tD/k507L9TS/qxvQ3WvcXUf96b4NOEzsYNpFTSQRo=","From":"Jean-Michel Hautbois <jeanmichel.hautbois@ideasonboard.com>","To":"libcamera-devel@lists.libcamera.org","Date":"Wed, 24 Nov 2021 14:40:18 +0100","Message-Id":"<20211124134019.110765-11-jeanmichel.hautbois@ideasonboard.com>","X-Mailer":"git-send-email 2.32.0","In-Reply-To":"<20211124134019.110765-1-jeanmichel.hautbois@ideasonboard.com>","References":"<20211124134019.110765-1-jeanmichel.hautbois@ideasonboard.com>","MIME-Version":"1.0","Content-Transfer-Encoding":"8bit","Subject":"[libcamera-devel] [PATCH v4 10/11] ipa: rkisp1: Introduce AGC","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":"Now that we have IPAContext and Algorithm, we can implement a simple AGC\nbased on the IPU3 one. It is very similar, except that there is no\nhistogram used for an inter quantile mean. The RkISP1 is returning a 5x5\narray (for V10) of luminance means. Estimating the relative luminance is\nthus a simple mean of all the blocks already calculated by the ISP.\n\nSigned-off-by: Jean-Michel Hautbois <jeanmichel.hautbois@ideasonboard.com>\n\n---\nv4:\n\t- use #pragma once\n\t- Return filtered value from the function\n\t- Store line duration in IPASessionConfiguration\n\t- Use the hw revision to configure the number of AE cells\n---\n src/ipa/rkisp1/algorithms/agc.cpp     | 279 ++++++++++++++++++++++++++\n src/ipa/rkisp1/algorithms/agc.h       |  45 +++++\n src/ipa/rkisp1/algorithms/meson.build |   1 +\n src/ipa/rkisp1/ipa_context.cpp        |  45 +++++\n src/ipa/rkisp1/ipa_context.h          |  19 ++\n src/ipa/rkisp1/rkisp1.cpp             |  73 ++++---\n 6 files changed, 424 insertions(+), 38 deletions(-)\n create mode 100644 src/ipa/rkisp1/algorithms/agc.cpp\n create mode 100644 src/ipa/rkisp1/algorithms/agc.h","diff":"diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp\nnew file mode 100644\nindex 00000000..84ff8c7a\n--- /dev/null\n+++ b/src/ipa/rkisp1/algorithms/agc.cpp\n@@ -0,0 +1,279 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2021, Ideas On Board\n+ *\n+ * agc.cpp - AGC/AEC mean-based control algorithm\n+ */\n+\n+#include \"agc.h\"\n+\n+#include <algorithm>\n+#include <chrono>\n+#include <cmath>\n+\n+#include <libcamera/base/log.h>\n+\n+#include <libcamera/ipa/core_ipa_interface.h>\n+\n+/**\n+ * \\file agc.h\n+ */\n+\n+namespace libcamera {\n+\n+using namespace std::literals::chrono_literals;\n+\n+namespace ipa::rkisp1::algorithms {\n+\n+/**\n+ * \\class Agc\n+ * \\brief A mean-based auto-exposure algorithm\n+ */\n+\n+LOG_DEFINE_CATEGORY(RkISP1Agc)\n+\n+/* Limits for analogue gain values */\n+static constexpr double kMinAnalogueGain = 1.0;\n+static constexpr double kMaxAnalogueGain = 8.0;\n+\n+/* \\todo Honour the FrameDurationLimits control instead of hardcoding a limit */\n+static constexpr utils::Duration kMaxShutterSpeed = 60ms;\n+\n+/* Number of frames to wait before calculating stats on minimum exposure */\n+static constexpr uint32_t kNumStartupFrames = 10;\n+\n+/*\n+ * Relative luminance target.\n+ *\n+ * It's a number that's chosen so that, when the camera points at a grey\n+ * target, the resulting image brightness is considered right.\n+ */\n+static constexpr double kRelativeLuminanceTarget = 0.4;\n+\n+Agc::Agc()\n+\t: frameCount_(0), currentExposure_(0s)\n+{\n+}\n+\n+/**\n+ * \\brief Configure the AGC given a configInfo\n+ * \\param[in] context The shared IPA context\n+ * \\param[in] configInfo The IPA configuration data\n+ *\n+ * \\return 0\n+ */\n+int Agc::configure(IPAContext &context,\n+\t\t   [[maybe_unused]] const IPACameraSensorInfo &configInfo)\n+{\n+\t/* Configure the default exposure and gain. */\n+\tcontext.frameContext.agc.gain = std::max(context.configuration.agc.minAnalogueGain, kMinAnalogueGain);\n+\tcontext.frameContext.agc.exposure = 10ms / context.configuration.agc.lineDuration;\n+\n+\t/*\n+\t * According to the RkISP1 documentation:\n+\t * - versions <= V11 have RKISP1_CIF_ISP_AE_MEAN_MAX_V10 entries,\n+\t * - versions >= V12 have RKISP1_CIF_ISP_AE_MEAN_MAX_V12 entries.\n+\t */\n+\tif (context.configuration.hw.revision < RKISP1_V12)\n+\t\tnumCells_ = RKISP1_CIF_ISP_AE_MEAN_MAX_V10;\n+\telse\n+\t\tnumCells_ = RKISP1_CIF_ISP_AE_MEAN_MAX_V12;\n+\n+\treturn 0;\n+}\n+\n+/**\n+ * \\brief Apply a filter on the exposure value to limit the speed of changes\n+ */\n+utils::Duration Agc::filterExposure()\n+{\n+\tutils::Duration filteredExposure = currentExposure_;\n+\tdouble speed = 0.2;\n+\n+\t/* Adapt instantly if we are in startup phase */\n+\tif (frameCount_ < kNumStartupFrames)\n+\t\tspeed = 1.0;\n+\n+\tif (filteredExposure == 0s) {\n+\t\tfilteredExposure = currentExposure_;\n+\t} else {\n+\t\t/*\n+\t\t * If we are close to the desired result, go faster to avoid making\n+\t\t * multiple micro-adjustments.\n+\t\t * \\todo Make this customisable?\n+\t\t */\n+\t\tif (filteredExposure < 1.2 * currentExposure_ &&\n+\t\t    filteredExposure > 0.8 * currentExposure_)\n+\t\t\tspeed = sqrt(speed);\n+\n+\t\tfilteredExposure = speed * currentExposure_ +\n+\t\t\t\t   filteredExposure * (1.0 - speed);\n+\t}\n+\n+\tLOG(RkISP1Agc, Debug) << \"After filtering, total_exposure \" << filteredExposure;\n+\n+\treturn filteredExposure;\n+}\n+\n+/**\n+ * \\brief Estimate the new exposure and gain values\n+ * \\param[inout] frameContext The shared IPA frame Context\n+ * \\param[in] yGain The gain calculated on the current brightness level\n+ */\n+void Agc::computeExposure(IPAContext &context, double yGain)\n+{\n+\tIPASessionConfiguration &configuration = context.configuration;\n+\tIPAFrameContext &frameContext = context.frameContext;\n+\n+\t/* Get the effective exposure and gain applied on the sensor. */\n+\tuint32_t exposure = frameContext.sensor.exposure;\n+\tdouble analogueGain = frameContext.sensor.gain;\n+\n+\tutils::Duration minShutterSpeed = configuration.agc.minShutterSpeed;\n+\tutils::Duration maxShutterSpeed = std::min(configuration.agc.maxShutterSpeed,\n+\t\t\t\t\t\t   kMaxShutterSpeed);\n+\n+\tdouble minAnalogueGain = std::max(configuration.agc.minAnalogueGain,\n+\t\t\t\t\t  kMinAnalogueGain);\n+\tdouble maxAnalogueGain = std::min(configuration.agc.maxAnalogueGain,\n+\t\t\t\t\t  kMaxAnalogueGain);\n+\n+\t/* Consider within 1% of the target as correctly exposed */\n+\tif (std::abs(yGain - 1.0) < 0.01)\n+\t\tLOG(RkISP1Agc, Debug) << \"We are well exposed (iqMean = \"\n+\t\t\t\t      << yGain << \")\";\n+\n+\t/* extracted from Rpi::Agc::computeTargetExposure */\n+\n+\t/* Calculate the shutter time in seconds */\n+\tutils::Duration currentShutter = exposure * configuration.agc.lineDuration;\n+\n+\t/*\n+\t * Update the exposure value for the next computation using the values\n+\t * of exposure and gain really used by the sensor.\n+\t */\n+\tutils::Duration effectiveExposureValue = currentShutter * analogueGain;\n+\n+\tLOG(RkISP1Agc, Debug) << \"Actual total exposure \" << currentShutter * analogueGain\n+\t\t\t      << \" Shutter speed \" << currentShutter\n+\t\t\t      << \" Gain \" << analogueGain\n+\t\t\t      << \" Needed ev gain \" << yGain;\n+\n+\t/*\n+\t * Calculate the current exposure value for the scene as the latest\n+\t * exposure value applied multiplied by the new estimated gain.\n+\t */\n+\tcurrentExposure_ = effectiveExposureValue * yGain;\n+\n+\t/* Clamp the exposure value to the min and max authorized */\n+\tutils::Duration maxTotalExposure = maxShutterSpeed * maxAnalogueGain;\n+\tcurrentExposure_ = std::min(currentExposure_, maxTotalExposure);\n+\tLOG(RkISP1Agc, Debug) << \"Target total exposure \" << currentExposure_\n+\t\t\t      << \", maximum is \" << maxTotalExposure;\n+\n+\t/*\n+\t * Divide the exposure value as new exposure and gain values\n+\t * \\todo: estimate if we need to desaturate\n+\t */\n+\tutils::Duration exposureValue = filterExposure();\n+\tutils::Duration shutterTime;\n+\n+\t/*\n+\t* Push the shutter time up to the maximum first, and only then\n+\t* increase the gain.\n+\t*/\n+\tshutterTime = std::clamp<utils::Duration>(exposureValue / minAnalogueGain,\n+\t\t\t\t\t\t  minShutterSpeed, maxShutterSpeed);\n+\tdouble stepGain = std::clamp(exposureValue / shutterTime,\n+\t\t\t\t     minAnalogueGain, maxAnalogueGain);\n+\tLOG(RkISP1Agc, Debug) << \"Divided up shutter and gain are \"\n+\t\t\t      << shutterTime << \" and \"\n+\t\t\t      << stepGain;\n+\n+\t/* Update the estimated exposure and gain. */\n+\tframeContext.agc.exposure = shutterTime / configuration.agc.lineDuration;\n+\tframeContext.agc.gain = stepGain;\n+}\n+\n+/**\n+ * \\brief Estimate the relative luminance of the frame with a given gain\n+ * \\param[in] ae The RkISP1 statistics and ISP results\n+ * \\param[in] gain The gain calculated on the current brightness level\n+ * \\return The relative luminance\n+ *\n+ * This function estimates the average relative luminance of the frame that\n+ * would be output by the sensor if an additional \\a gain was applied.\n+ *\n+ * The estimation is based on the AE statistics for the current frame. Y\n+ * averages for all cells are first multiplied by the gain, and then saturated\n+ * to approximate the sensor behaviour at high brightness values. The\n+ * approximation is quite rough, as it doesn't take into account non-linearities\n+ * when approaching saturation.\n+ *\n+ * The values are normalized to the [0.0, 1.0] range, where 1.0 corresponds to a\n+ * theoretical perfect reflector of 100% reference white.\n+ *\n+ * More detailed information can be found in:\n+ * https://en.wikipedia.org/wiki/Relative_luminance\n+ */\n+double Agc::estimateLuminance(const rkisp1_cif_isp_ae_stat *ae,\n+\t\t\t      double gain)\n+{\n+\tdouble ySum = 0.0;\n+\tunsigned int num = 0;\n+\n+\t/* Sum the averages, saturated to 255. */\n+\tfor (unsigned int aeCell = 0; aeCell < numCells_; aeCell++) {\n+\t\tySum += std::min(ae->exp_mean[aeCell] * gain, 255.0);\n+\t\tnum++;\n+\t}\n+\n+\t/* \\todo Weight with the AWB gains */\n+\n+\treturn ySum / num / 255;\n+}\n+\n+/**\n+ * \\brief Process RkISP1 statistics, and run AGC operations\n+ * \\param[in] context The shared IPA context\n+ * \\param[in] stats The RKIsp1 statistics and ISP results\n+ *\n+ * Identify the current image brightness, and use that to estimate the optimal\n+ * new exposure and gain for the scene.\n+ */\n+void Agc::process(IPAContext &context, const rkisp1_stat_buffer *stats)\n+{\n+\tconst rkisp1_cif_isp_stat *params = &stats->params;\n+\tASSERT(stats->meas_type & RKISP1_CIF_ISP_STAT_AUTOEXP);\n+\n+\tconst rkisp1_cif_isp_ae_stat *ae = &params->ae;\n+\n+\t/*\n+\t * Estimate the gain needed to achieve a relative luminance target. To\n+\t * account for non-linearity caused by saturation, the value needs to be\n+\t * estimated in an iterative process, as multiplying by a gain will not\n+\t * increase the relative luminance by the same factor if some image\n+\t * regions are saturated.\n+\t */\n+\tdouble yGain = 1.0;\n+\tdouble yTarget = kRelativeLuminanceTarget;\n+\n+\tfor (unsigned int i = 0; i < 8; i++) {\n+\t\tdouble yValue = estimateLuminance(ae, yGain);\n+\t\tdouble extra_gain = std::min(10.0, yTarget / (yValue + .001));\n+\n+\t\tyGain *= extra_gain;\n+\t\tLOG(RkISP1Agc, Debug) << \"Y value: \" << yValue\n+\t\t\t\t      << \", Y target: \" << yTarget\n+\t\t\t\t      << \", gives gain \" << yGain;\n+\t\tif (extra_gain < 1.01)\n+\t\t\tbreak;\n+\t}\n+\n+\tcomputeExposure(context, yGain);\n+\tframeCount_++;\n+}\n+\n+} /* namespace ipa::rkisp1::algorithms */\n+\n+} /* namespace libcamera */\ndiff --git a/src/ipa/rkisp1/algorithms/agc.h b/src/ipa/rkisp1/algorithms/agc.h\nnew file mode 100644\nindex 00000000..a59ae9c8\n--- /dev/null\n+++ b/src/ipa/rkisp1/algorithms/agc.h\n@@ -0,0 +1,45 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2021, Ideas On Board\n+ *\n+ * agc.h - RkISP1 AGC/AEC mean-based control algorithm\n+ */\n+#pragma once\n+\n+#include <linux/rkisp1-config.h>\n+\n+#include <libcamera/base/utils.h>\n+\n+#include <libcamera/geometry.h>\n+\n+#include \"algorithm.h\"\n+\n+namespace libcamera {\n+\n+struct IPACameraSensorInfo;\n+\n+namespace ipa::rkisp1::algorithms {\n+\n+class Agc : public Algorithm\n+{\n+public:\n+\tAgc();\n+\t~Agc() = default;\n+\n+\tint configure(IPAContext &context, const IPACameraSensorInfo &configInfo) override;\n+\tvoid process(IPAContext &context, const rkisp1_stat_buffer *stats) override;\n+\n+private:\n+\tvoid computeExposure(IPAContext &Context, double yGain);\n+\tutils::Duration filterExposure();\n+\tdouble estimateLuminance(const rkisp1_cif_isp_ae_stat *ae, double gain);\n+\n+\tuint64_t frameCount_;\n+\n+\tuint32_t numCells_;\n+\n+\tutils::Duration currentExposure_;\n+};\n+\n+} /* namespace ipa::rkisp1::algorithms */\n+} /* namespace libcamera */\ndiff --git a/src/ipa/rkisp1/algorithms/meson.build b/src/ipa/rkisp1/algorithms/meson.build\nindex 1c6c59cf..a19c1a4f 100644\n--- a/src/ipa/rkisp1/algorithms/meson.build\n+++ b/src/ipa/rkisp1/algorithms/meson.build\n@@ -1,4 +1,5 @@\n # SPDX-License-Identifier: CC0-1.0\n \n rkisp1_ipa_algorithms = files([\n+    'agc.cpp',\n ])\ndiff --git a/src/ipa/rkisp1/ipa_context.cpp b/src/ipa/rkisp1/ipa_context.cpp\nindex 6b53dfdf..dff00362 100644\n--- a/src/ipa/rkisp1/ipa_context.cpp\n+++ b/src/ipa/rkisp1/ipa_context.cpp\n@@ -56,6 +56,24 @@ namespace libcamera::ipa::rkisp1 {\n  */\n \n /**\n+ * \\var IPASessionConfiguration::agc\n+ * \\brief AGC parameters configuration of the IPA\n+ *\n+ * \\var IPASessionConfiguration::agc.minShutterSpeed\n+ * \\brief Minimum shutter speed supported with the configured sensor\n+ *\n+ * \\var IPASessionConfiguration::agc.maxShutterSpeed\n+ * \\brief Maximum shutter speed supported with the configured sensor\n+ *\n+ * \\var IPASessionConfiguration::agc.minAnalogueGain\n+ * \\brief Minimum analogue gain supported with the configured sensor\n+ *\n+ * \\var IPASessionConfiguration::agc.maxAnalogueGain\n+ * \\brief Maximum analogue gain supported with the configured sensor\n+ *\n+ * \\var IPASessionConfiguration::agc.lineDuration\n+ * \\brief Line duration in microseconds\n+ *\n  * \\var IPASessionConfiguration::hw\n  * \\brief RkISP1-specific hardware information\n  *\n@@ -63,4 +81,31 @@ namespace libcamera::ipa::rkisp1 {\n  * \\brief Hardware revision of the ISP\n  */\n \n+/**\n+ * \\var IPAFrameContext::agc\n+ * \\brief Context for the Automatic Gain Control algorithm\n+ *\n+ * The exposure and gain determined are expected to be applied to the sensor\n+ * at the earliest opportunity.\n+ *\n+ * \\var IPAFrameContext::agc.exposure\n+ * \\brief Exposure time expressed as a number of lines\n+ *\n+ * \\var IPAFrameContext::agc.gain\n+ * \\brief Analogue gain multiplier\n+ *\n+ * The gain should be adapted to the sensor specific gain code before applying.\n+ */\n+\n+/**\n+ * \\var IPAFrameContext::sensor\n+ * \\brief Effective sensor values\n+ *\n+ * \\var IPAFrameContext::sensor.exposure\n+ * \\brief Exposure time expressed as a number of lines\n+ *\n+ * \\var IPAFrameContext::sensor.gain\n+ * \\brief Analogue gain multiplier\n+ */\n+\n } /* namespace libcamera::ipa::rkisp1 */\ndiff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h\nindex f9ac6833..653d7e65 100644\n--- a/src/ipa/rkisp1/ipa_context.h\n+++ b/src/ipa/rkisp1/ipa_context.h\n@@ -9,17 +9,36 @@\n \n #include <linux/rkisp1-config.h>\n \n+#include <libcamera/base/utils.h>\n+\n namespace libcamera {\n \n namespace ipa::rkisp1 {\n \n struct IPASessionConfiguration {\n+\tstruct {\n+\t\tutils::Duration minShutterSpeed;\n+\t\tutils::Duration maxShutterSpeed;\n+\t\tdouble minAnalogueGain;\n+\t\tdouble maxAnalogueGain;\n+\t\tutils::Duration lineDuration;\n+\t} agc;\n+\n \tstruct {\n \t\trkisp1_cif_isp_version revision;\n \t} hw;\n };\n \n struct IPAFrameContext {\n+\tstruct {\n+\t\tuint32_t exposure;\n+\t\tdouble gain;\n+\t} agc;\n+\n+\tstruct {\n+\t\tuint32_t exposure;\n+\t\tdouble gain;\n+\t} sensor;\n };\n \n struct IPAContext {\ndiff --git a/src/ipa/rkisp1/rkisp1.cpp b/src/ipa/rkisp1/rkisp1.cpp\nindex 59676a70..5c0a0330 100644\n--- a/src/ipa/rkisp1/rkisp1.cpp\n+++ b/src/ipa/rkisp1/rkisp1.cpp\n@@ -25,6 +25,7 @@\n \n #include <libcamera/internal/mapped_framebuffer.h>\n \n+#include \"algorithms/agc.h\"\n #include \"algorithms/algorithm.h\"\n #include \"libipa/camera_sensor_helper.h\"\n \n@@ -34,6 +35,8 @@ namespace libcamera {\n \n LOG_DEFINE_CATEGORY(IPARkISP1)\n \n+using namespace std::literals::chrono_literals;\n+\n namespace ipa::rkisp1 {\n \n class IPARkISP1 : public IPARkISP1Interface\n@@ -75,7 +78,6 @@ private:\n \n \t/* revision-specific data */\n \trkisp1_cif_isp_version hwRevision_;\n-\tunsigned int hwAeMeanMax_;\n \tunsigned int hwHistBinNMax_;\n \tunsigned int hwGammaOutMaxSamples_;\n \tunsigned int hwHistogramWeightGridsSize_;\n@@ -95,13 +97,11 @@ int IPARkISP1::init(const IPASettings &settings, unsigned int hwRevision)\n \t/* \\todo Add support for other revisions */\n \tswitch (hwRevision) {\n \tcase RKISP1_V10:\n-\t\thwAeMeanMax_ = RKISP1_CIF_ISP_AE_MEAN_MAX_V10;\n \t\thwHistBinNMax_ = RKISP1_CIF_ISP_HIST_BIN_N_MAX_V10;\n \t\thwGammaOutMaxSamples_ = RKISP1_CIF_ISP_GAMMA_OUT_MAX_SAMPLES_V10;\n \t\thwHistogramWeightGridsSize_ = RKISP1_CIF_ISP_HISTOGRAM_WEIGHT_GRIDS_SIZE_V10;\n \t\tbreak;\n \tcase RKISP1_V12:\n-\t\thwAeMeanMax_ = RKISP1_CIF_ISP_AE_MEAN_MAX_V12;\n \t\thwHistBinNMax_ = RKISP1_CIF_ISP_HIST_BIN_N_MAX_V12;\n \t\thwGammaOutMaxSamples_ = RKISP1_CIF_ISP_GAMMA_OUT_MAX_SAMPLES_V12;\n \t\thwHistogramWeightGridsSize_ = RKISP1_CIF_ISP_HISTOGRAM_WEIGHT_GRIDS_SIZE_V12;\n@@ -126,6 +126,9 @@ int IPARkISP1::init(const IPASettings &settings, unsigned int hwRevision)\n \t\treturn -ENODEV;\n \t}\n \n+\t/* Construct our Algorithms */\n+\talgorithms_.push_back(std::make_unique<algorithms::Agc>());\n+\n \treturn 0;\n }\n \n@@ -183,6 +186,26 @@ int IPARkISP1::configure([[maybe_unused]] const IPACameraSensorInfo &info,\n \t/* Set the hardware revision for the algorithms. */\n \tcontext_.configuration.hw.revision = hwRevision_;\n \n+\tcontext_.configuration.agc.lineDuration = info.lineLength * 1.0s / info.pixelRate;\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 shutter speed and analogue gain.\n+\t * As it depends on the sensor, update it with the controls.\n+\t *\n+\t * \\todo take VBLANK into account for maximum shutter speed\n+\t */\n+\tcontext_.configuration.agc.minShutterSpeed = minExposure_ * context_.configuration.agc.lineDuration;\n+\tcontext_.configuration.agc.maxShutterSpeed = maxExposure_ * context_.configuration.agc.lineDuration;\n+\tcontext_.configuration.agc.minAnalogueGain = camHelper_->gain(minGain_);\n+\tcontext_.configuration.agc.maxAnalogueGain = camHelper_->gain(maxGain_);\n+\n+\tfor (auto const &algo : algorithms_) {\n+\t\tint ret = algo->configure(context_, info);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\t}\n+\n \treturn 0;\n }\n \n@@ -227,6 +250,9 @@ void IPARkISP1::processEvent(const RkISP1Event &event)\n \t\t\treinterpret_cast<rkisp1_stat_buffer *>(\n \t\t\t\tmappedBuffers_.at(bufferId).planes()[0].data());\n \n+\t\tcontext_.frameContext.sensor.exposure = event.sensorControls.get(V4L2_CID_EXPOSURE).get<int32_t>();\n+\t\tcontext_.frameContext.sensor.gain = camHelper_->gain(event.sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get<int32_t>());\n+\n \t\tupdateStatistics(frame, stats);\n \t\tbreak;\n \t}\n@@ -271,44 +297,12 @@ void IPARkISP1::queueRequest(unsigned int frame, rkisp1_params_cfg *params,\n void IPARkISP1::updateStatistics(unsigned int frame,\n \t\t\t\t const rkisp1_stat_buffer *stats)\n {\n-\tconst rkisp1_cif_isp_stat *params = &stats->params;\n \tunsigned int aeState = 0;\n \n-\tif (stats->meas_type & RKISP1_CIF_ISP_STAT_AUTOEXP) {\n-\t\tconst rkisp1_cif_isp_ae_stat *ae = &params->ae;\n-\n-\t\tconst unsigned int target = 60;\n-\n-\t\tunsigned int value = 0;\n-\t\tunsigned int num = 0;\n-\t\tfor (unsigned int i = 0; i < hwAeMeanMax_; i++) {\n-\t\t\tif (ae->exp_mean[i] <= 15)\n-\t\t\t\tcontinue;\n-\n-\t\t\tvalue += ae->exp_mean[i];\n-\t\t\tnum++;\n-\t\t}\n-\t\tvalue /= num;\n+\tfor (auto const &algo : algorithms_)\n+\t\talgo->process(context_, stats);\n \n-\t\tdouble factor = (double)target / value;\n-\n-\t\tif (frame % 3 == 0) {\n-\t\t\tdouble exposure;\n-\n-\t\t\texposure = factor * exposure_ * gain_ / minGain_;\n-\t\t\texposure_ = std::clamp<uint64_t>((uint64_t)exposure,\n-\t\t\t\t\t\t\t minExposure_,\n-\t\t\t\t\t\t\t maxExposure_);\n-\n-\t\t\texposure = exposure / exposure_ * minGain_;\n-\t\t\tgain_ = std::clamp<uint64_t>((uint64_t)exposure,\n-\t\t\t\t\t\t     minGain_, maxGain_);\n-\n-\t\t\tsetControls(frame + 1);\n-\t\t}\n-\n-\t\taeState = fabs(factor - 1.0f) < 0.05f ? 2 : 1;\n-\t}\n+\tsetControls(frame);\n \n \tmetadataReady(frame, aeState);\n }\n@@ -318,6 +312,9 @@ void IPARkISP1::setControls(unsigned int frame)\n \tRkISP1Action op;\n \top.op = ActionV4L2Set;\n \n+\texposure_ = context_.frameContext.agc.exposure;\n+\tgain_ = camHelper_->gainCode(context_.frameContext.agc.gain);\n+\n \tControlList ctrls(ctrls_);\n \tctrls.set(V4L2_CID_EXPOSURE, static_cast<int32_t>(exposure_));\n \tctrls.set(V4L2_CID_ANALOGUE_GAIN, static_cast<int32_t>(gain_));\n","prefixes":["libcamera-devel","v4","10/11"]}