Patch Detail
Show a patch.
GET /api/1.1/patches/14848/?format=api
{ "id": 14848, "url": "https://patchwork.libcamera.org/api/1.1/patches/14848/?format=api", "web_url": "https://patchwork.libcamera.org/patch/14848/", "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": "<20211129153151.80429-1-jeanmichel.hautbois@ideasonboard.com>", "date": "2021-11-29T15:31:51", "name": "[libcamera-devel,v5.2,10/11] ipa: rkisp1: Introduce AGC", "commit_ref": null, "pull_url": null, "state": "accepted", "archived": false, "hash": "e595a32442dd889fc9a7acc387cb0a213ba4f61b", "submitter": { "id": 75, "url": "https://patchwork.libcamera.org/api/1.1/people/75/?format=api", "name": "Jean-Michel Hautbois", "email": "jeanmichel.hautbois@ideasonboard.com" }, "delegate": null, "mbox": "https://patchwork.libcamera.org/patch/14848/mbox/", "series": [ { "id": 2771, "url": "https://patchwork.libcamera.org/api/1.1/series/2771/?format=api", "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=2771", "date": "2021-11-29T15:31:51", "name": null, "version": 1, "mbox": "https://patchwork.libcamera.org/series/2771/mbox/" } ], "comments": "https://patchwork.libcamera.org/api/patches/14848/comments/", "check": "pending", "checks": "https://patchwork.libcamera.org/api/patches/14848/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 B7E4EBDB13\n\tfor <parsemail@patchwork.libcamera.org>;\n\tMon, 29 Nov 2021 15:31:58 +0000 (UTC)", "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id D16E0605A6;\n\tMon, 29 Nov 2021 16:31:57 +0100 (CET)", "from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id DB28A60592\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 29 Nov 2021 16:31:55 +0100 (CET)", "from tatooine.ideasonboard.com (unknown\n\t[IPv6:2a01:e0a:169:7140:4f15:3be7:f42c:9dba])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 73E7B2A5;\n\tMon, 29 Nov 2021 16:31:55 +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=\"Rq07YUVh\"; dkim-atps=neutral", "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1638199915;\n\tbh=BBPFCuLqlB+zQRQm5K+r29IEbkN8NvcLRFB+HXSk4e0=;\n\th=From:To:Cc:Subject:Date:In-Reply-To:References:From;\n\tb=Rq07YUVhWuy9LnKeVYOFM2HOZqU/fAVH9fF9cqhQW5DXqMU/tk7YJMJBJ3dlmV97F\n\tthIwma+EFoxU4Lq4L2WWrvILHAUXUO/V4nQeZ5lWClGJL0mOtHJa4buGnBOV88Hsvz\n\tA1TGGl2oS1jlAAE51z1cYl3LBnhfUmt/j6SLjwAk=", "From": "Jean-Michel Hautbois <jeanmichel.hautbois@ideasonboard.com>", "To": "libcamera-devel@lists.libcamera.org", "Date": "Mon, 29 Nov 2021 16:31:51 +0100", "Message-Id": "<20211129153151.80429-1-jeanmichel.hautbois@ideasonboard.com>", "X-Mailer": "git-send-email 2.32.0", "In-Reply-To": "<20211126164301.61515-1-jeanmichel.hautbois@ideasonboard.com>", "References": "<20211126164301.61515-1-jeanmichel.hautbois@ideasonboard.com>", "MIME-Version": "1.0", "Content-Transfer-Encoding": "8bit", "Subject": "[libcamera-devel] [PATCH v5.2 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---\nv5.2:\n\t- Rename currentExposure => exposureValue\n\t- Minor comment fixes\n\t- added a todo => YUV-specific algorithm would be better ?\n\t- re-init frameCount_ at configure\nv5.1:\n\t- Move lineDuration in a configuration.sensor structure in\n\t IPAContext\n\t- Add a documentation for filterExposure()\n\t- Move shutterTime initialisation\nv5:\n\t- use private filteredExposure_ and pass currentExposure as a\n\t member variable\n\t- Drop num and replace it with numCells_\n\t- Make exposure and gain local variables in IPARkISP1\n\t- Shorter the lines in processEvent()\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 | 282 ++++++++++++++++++++++++++\n src/ipa/rkisp1/algorithms/agc.h | 46 +++++\n src/ipa/rkisp1/algorithms/meson.build | 1 +\n src/ipa/rkisp1/ipa_context.cpp | 50 +++++\n src/ipa/rkisp1/ipa_context.h | 22 ++\n src/ipa/rkisp1/rkisp1.cpp | 83 ++++----\n 6 files changed, 440 insertions(+), 44 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..7761e097\n--- /dev/null\n+++ b/src/ipa/rkisp1/algorithms/agc.cpp\n@@ -0,0 +1,282 @@\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), filteredExposure_(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.sensor.lineDuration;\n+\n+\t/*\n+\t * According to the RkISP1 documentation:\n+\t * - versions < V12 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+\t/* \\todo Use actual frame index by populating it in the frameContext. */\n+\tframeCount_ = 0;\n+\treturn 0;\n+}\n+\n+/**\n+ * \\brief Apply a filter on the exposure value to limit the speed of changes\n+ * \\param[in] exposureValue The target exposure from the AGC algorithm\n+ *\n+ * The speed of the filter is adaptive, and will produce the target quicker\n+ * during startup, or when the target exposure is within 20% of the most recent\n+ * filter output.\n+ *\n+ * \\return The filtered exposure\n+ */\n+utils::Duration Agc::filterExposure(utils::Duration exposureValue)\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+\t/*\n+\t * If we are close to the desired result, go faster to avoid making\n+\t * multiple micro-adjustments.\n+\t * \\todo Make this customisable?\n+\t */\n+\tif (filteredExposure_ < 1.2 * exposureValue &&\n+\t filteredExposure_ > 0.8 * exposureValue)\n+\t\tspeed = sqrt(speed);\n+\n+\tfilteredExposure_ = speed * exposureValue +\n+\t\t\t filteredExposure_ * (1.0 - speed);\n+\n+\tLOG(RkISP1Agc, Debug) << \"After filtering, 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\treturn;\n+\n+\t/* extracted from Rpi::Agc::computeTargetExposure. */\n+\n+\t/* Calculate the shutter time in seconds. */\n+\tutils::Duration currentShutter = exposure * configuration.sensor.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+\tutils::Duration exposureValue = effectiveExposureValue * yGain;\n+\n+\t/* Clamp the exposure value to the min and max authorized. */\n+\tutils::Duration maxTotalExposure = maxShutterSpeed * maxAnalogueGain;\n+\texposureValue = std::min(exposureValue, maxTotalExposure);\n+\tLOG(RkISP1Agc, Debug) << \"Target total exposure \" << exposureValue\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+\texposureValue = filterExposure(exposureValue);\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+\tutils::Duration shutterTime = std::clamp<utils::Duration>(exposureValue / minAnalogueGain,\n+\t\t\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.sensor.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 to apply to the frame\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. In this case, saturating after the conversion to\n+ * YUV doesn't take into account the fact that the R, G and B components\n+ * contribute differently to the relative luminance.\n+ *\n+ * \\todo Have a dedicated YUV algorithm ?\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+\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+\n+\t/* \\todo Weight with the AWB gains */\n+\n+\treturn ySum / numCells_ / 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 = ¶ms->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..07b9ea37\n--- /dev/null\n+++ b/src/ipa/rkisp1/algorithms/agc.h\n@@ -0,0 +1,46 @@\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+\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(utils::Duration exposureValue);\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 filteredExposure_;\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..9cb2a9fd 100644\n--- a/src/ipa/rkisp1/ipa_context.cpp\n+++ b/src/ipa/rkisp1/ipa_context.cpp\n@@ -56,6 +56,21 @@ 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::hw\n * \\brief RkISP1-specific hardware information\n *\n@@ -63,4 +78,39 @@ namespace libcamera::ipa::rkisp1 {\n * \\brief Hardware revision of the ISP\n */\n \n+/**\n+ * \\var IPASessionConfiguration::sensor\n+ * \\brief Sensor-specific configuration of the IPA\n+ *\n+ * \\var IPASessionConfiguration::sensor.lineDuration\n+ * \\brief Line duration in microseconds\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 9342025b..b94ade0c 100644\n--- a/src/ipa/rkisp1/ipa_context.h\n+++ b/src/ipa/rkisp1/ipa_context.h\n@@ -10,17 +10,39 @@\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} agc;\n+\n+\tstruct {\n+\t\tutils::Duration lineDuration;\n+\t} sensor;\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..38917fb7 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@@ -66,16 +69,13 @@ private:\n \n \t/* Camera sensor controls. */\n \tbool autoExposure_;\n-\tuint32_t exposure_;\n \tuint32_t minExposure_;\n \tuint32_t maxExposure_;\n-\tuint32_t gain_;\n \tuint32_t minGain_;\n \tuint32_t maxGain_;\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 +95,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 +124,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@@ -167,11 +168,9 @@ int IPARkISP1::configure([[maybe_unused]] const IPACameraSensorInfo &info,\n \n \tminExposure_ = itExp->second.min().get<int32_t>();\n \tmaxExposure_ = itExp->second.max().get<int32_t>();\n-\texposure_ = minExposure_;\n \n \tminGain_ = itGain->second.min().get<int32_t>();\n \tmaxGain_ = itGain->second.max().get<int32_t>();\n-\tgain_ = minGain_;\n \n \tLOG(IPARkISP1, Info)\n \t\t<< \"Exposure: \" << minExposure_ << \"-\" << maxExposure_\n@@ -183,6 +182,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.sensor.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.sensor.lineDuration;\n+\tcontext_.configuration.agc.maxShutterSpeed = maxExposure_ * context_.configuration.sensor.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 +246,11 @@ 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 =\n+\t\t\tevent.sensorControls.get(V4L2_CID_EXPOSURE).get<int32_t>();\n+\t\tcontext_.frameContext.sensor.gain =\n+\t\t\tcamHelper_->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 +295,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 = ¶ms->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,9 +310,12 @@ void IPARkISP1::setControls(unsigned int frame)\n \tRkISP1Action op;\n \top.op = ActionV4L2Set;\n \n+\tuint32_t exposure = context_.frameContext.agc.exposure;\n+\tuint32_t gain = 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+\tctrls.set(V4L2_CID_EXPOSURE, static_cast<int32_t>(exposure));\n+\tctrls.set(V4L2_CID_ANALOGUE_GAIN, static_cast<int32_t>(gain));\n \top.sensorControls = ctrls;\n \n \tqueueFrameAction.emit(frame, op);\n", "prefixes": [ "libcamera-devel", "v5.2", "10/11" ] }