Show a patch.

GET /api/patches/14691/?format=api
HTTP 200 OK
Allow: GET, PUT, PATCH, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "id": 14691,
    "url": "https://patchwork.libcamera.org/api/patches/14691/?format=api",
    "web_url": "https://patchwork.libcamera.org/patch/14691/",
    "project": {
        "id": 1,
        "url": "https://patchwork.libcamera.org/api/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": "<20211123091451.67404-10-jeanmichel.hautbois@ideasonboard.com>",
    "date": "2021-11-23T09:14:49",
    "name": "[libcamera-devel,v2,09/11] ipa: rkisp1: Introduce AGC",
    "commit_ref": null,
    "pull_url": null,
    "state": "superseded",
    "archived": false,
    "hash": "68ae905afb5f23ea47bfda4eec58ba35709b73c6",
    "submitter": {
        "id": 75,
        "url": "https://patchwork.libcamera.org/api/people/75/?format=api",
        "name": "Jean-Michel Hautbois",
        "email": "jeanmichel.hautbois@ideasonboard.com"
    },
    "delegate": null,
    "mbox": "https://patchwork.libcamera.org/patch/14691/mbox/",
    "series": [
        {
            "id": 2741,
            "url": "https://patchwork.libcamera.org/api/series/2741/?format=api",
            "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=2741",
            "date": "2021-11-23T09:14:40",
            "name": "Introduce AGC for RkISP1",
            "version": 2,
            "mbox": "https://patchwork.libcamera.org/series/2741/mbox/"
        }
    ],
    "comments": "https://patchwork.libcamera.org/api/patches/14691/comments/",
    "check": "pending",
    "checks": "https://patchwork.libcamera.org/api/patches/14691/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 0B317C324F\n\tfor <parsemail@patchwork.libcamera.org>;\n\tTue, 23 Nov 2021 09:15:10 +0000 (UTC)",
            "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id A147860490;\n\tTue, 23 Nov 2021 10:15:09 +0100 (CET)",
            "from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 84D576039C\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 23 Nov 2021 10:14:58 +0100 (CET)",
            "from tatooine.ideasonboard.com (unknown\n\t[IPv6:2a01:e0a:169:7140:3c3b:9149:b:8aa9])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 48D15A1B;\n\tTue, 23 Nov 2021 10:14:58 +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=\"qkMjGiqB\"; dkim-atps=neutral",
        "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1637658898;\n\tbh=AfLesHaI81KKFBlXXjJo6bUvn4fCQHqFqc0tnw+fYQ8=;\n\th=From:To:Cc:Subject:Date:In-Reply-To:References:From;\n\tb=qkMjGiqBGMF5+EfTDjepUL63Wz6ESoVn9SOQR0Q0aMNp+KfIA/5KaBAKnuvQo78a6\n\tQnu8X8hS5UgUeic03EXQIHQAk9Gp6Ua74uMQfP/50jDaGEz9ViusXKDAZjQrJ7QML1\n\t2PwirvXO0LLM1SxoyZAqpSl+xHgiJ6RNorm4VBZU=",
        "From": "Jean-Michel Hautbois <jeanmichel.hautbois@ideasonboard.com>",
        "To": "libcamera-devel@lists.libcamera.org",
        "Date": "Tue, 23 Nov 2021 10:14:49 +0100",
        "Message-Id": "<20211123091451.67404-10-jeanmichel.hautbois@ideasonboard.com>",
        "X-Mailer": "git-send-email 2.32.0",
        "In-Reply-To": "<20211123091451.67404-1-jeanmichel.hautbois@ideasonboard.com>",
        "References": "<20211123091451.67404-1-jeanmichel.hautbois@ideasonboard.com>",
        "MIME-Version": "1.0",
        "Content-Transfer-Encoding": "8bit",
        "Subject": "[libcamera-devel] [PATCH v2 09/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 src/ipa/rkisp1/algorithms/agc.cpp     | 265 ++++++++++++++++++++++++++\n src/ipa/rkisp1/algorithms/agc.h       |  55 ++++++\n src/ipa/rkisp1/algorithms/meson.build |   1 +\n src/ipa/rkisp1/ipa_context.cpp        |  44 +++++\n src/ipa/rkisp1/ipa_context.h          |  17 ++\n src/ipa/rkisp1/rkisp1.cpp             |  72 +++----\n 6 files changed, 419 insertions(+), 35 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..9c6d312e\n--- /dev/null\n+++ b/src/ipa/rkisp1/algorithms/agc.cpp\n@@ -0,0 +1,265 @@\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), lineDuration_(0s), minShutterSpeed_(0s),\n+\t  maxShutterSpeed_(0s), filteredExposure_(0s), 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, const IPACameraSensorInfo &configInfo)\n+{\n+\t/* \\todo use the IPAContext to provide the limits */\n+\tlineDuration_ = configInfo.lineLength * 1.0s / configInfo.pixelRate;\n+\n+\tminShutterSpeed_ = context.configuration.agc.minShutterSpeed;\n+\tmaxShutterSpeed_ = std::min(context.configuration.agc.maxShutterSpeed,\n+\t\t\t\t    kMaxShutterSpeed);\n+\n+\tminAnalogueGain_ = std::max(context.configuration.agc.minAnalogueGain, kMinAnalogueGain);\n+\tmaxAnalogueGain_ = std::min(context.configuration.agc.maxAnalogueGain, kMaxAnalogueGain);\n+\n+\t/* Configure the default exposure and gain. */\n+\tcontext.frameContext.agc.gain = minAnalogueGain_;\n+\tcontext.frameContext.agc.exposure = 10ms / lineDuration_;\n+\n+\treturn 0;\n+}\n+\n+/**\n+ * \\brief Apply a filter on the exposure value to limit the speed of changes\n+ */\n+void Agc::filterExposure()\n+{\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\t/* DG stands for digital gain.*/\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+\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(IPAFrameContext &frameContext, double yGain)\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+\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 * 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/* \\todo: estimate if we need to desaturate */\n+\tfilterExposure();\n+\n+\t/* Divide the exposure value as new exposure and gain values */\n+\tutils::Duration exposureValue = filteredExposure_;\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 / 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.frameContext, 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..83159743\n--- /dev/null\n+++ b/src/ipa/rkisp1/algorithms/agc.h\n@@ -0,0 +1,55 @@\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+#ifndef __LIBCAMERA_RKISP1_ALGORITHMS_AGC_H__\n+#define __LIBCAMERA_RKISP1_ALGORITHMS_AGC_H__\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 filterExposure();\n+\tvoid computeExposure(IPAFrameContext &frameContext, double yGain);\n+\tdouble estimateLuminance(const rkisp1_cif_isp_ae_stat *ae, double gain);\n+\n+\tuint64_t frameCount_;\n+\n+\tutils::Duration lineDuration_;\n+\tutils::Duration minShutterSpeed_;\n+\tutils::Duration maxShutterSpeed_;\n+\n+\tdouble minAnalogueGain_;\n+\tdouble maxAnalogueGain_;\n+\n+\tutils::Duration filteredExposure_;\n+\tutils::Duration currentExposure_;\n+};\n+\n+} /* namespace ipa::rkisp1::algorithms */\n+\n+} /* namespace libcamera */\n+\n+#endif /* __LIBCAMERA_RKISP1_ALGORITHMS_AGC_H__ */\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 819b2c73..16fc248f 100644\n--- a/src/ipa/rkisp1/ipa_context.cpp\n+++ b/src/ipa/rkisp1/ipa_context.cpp\n@@ -55,4 +55,48 @@ namespace libcamera::ipa::rkisp1 {\n  * are run. This needs to be turned into real per-frame data storage.\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+\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 ff40efe3..8e756fb1 100644\n--- a/src/ipa/rkisp1/ipa_context.h\n+++ b/src/ipa/rkisp1/ipa_context.h\n@@ -8,14 +8,31 @@\n #ifndef __LIBCAMERA_RKISP1_IPA_CONTEXT_H__\n #define __LIBCAMERA_RKISP1_IPA_CONTEXT_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} agc;\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 b5aa93f8..89d98b6c 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@@ -32,6 +33,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@@ -77,6 +80,8 @@ private:\n \tunsigned int hwGammaOutMaxSamples_;\n \tunsigned int hwHistogramWeightGridsSize_;\n \n+\tutils::Duration lineDuration_;\n+\n \t/* Interface to the Camera Helper */\n \tstd::unique_ptr<CameraSensorHelper> camHelper_;\n \n@@ -120,6 +125,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@@ -171,9 +179,29 @@ int IPARkISP1::configure([[maybe_unused]] const IPACameraSensorInfo &info,\n \t\t<< \"Exposure: \" << minExposure_ << \"-\" << maxExposure_\n \t\t<< \" Gain: \" << minGain_ << \"-\" << maxGain_;\n \n+\tlineDuration_ = info.lineLength * 1.0s / info.pixelRate;\n+\n \t/* Clean context at configuration */\n \tcontext_ = {};\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_ * lineDuration_;\n+\tcontext_.configuration.agc.maxShutterSpeed = maxExposure_ * 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@@ -218,6 +246,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@@ -262,44 +293,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+\tfor (auto const &algo : algorithms_)\n+\t\talgo->process(context_, stats);\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-\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 + 1);\n \n \tmetadataReady(frame, aeState);\n }\n@@ -309,6 +308,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",
        "v2",
        "09/11"
    ]
}