Show a patch.

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

{
    "id": 14655,
    "url": "https://patchwork.libcamera.org/api/patches/14655/?format=api",
    "web_url": "https://patchwork.libcamera.org/patch/14655/",
    "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": "<20211119111654.68445-7-jeanmichel.hautbois@ideasonboard.com>",
    "date": "2021-11-19T11:16:52",
    "name": "[libcamera-devel,v1,6/8] ipa: rkisp1: Introduce AGC",
    "commit_ref": null,
    "pull_url": null,
    "state": "superseded",
    "archived": false,
    "hash": "f15e0f77aeb0a18614055ad73f8f9ad88a22f969",
    "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/14655/mbox/",
    "series": [
        {
            "id": 2735,
            "url": "https://patchwork.libcamera.org/api/series/2735/?format=api",
            "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=2735",
            "date": "2021-11-19T11:16:46",
            "name": "Introduce AGC for RkISP1",
            "version": 1,
            "mbox": "https://patchwork.libcamera.org/series/2735/mbox/"
        }
    ],
    "comments": "https://patchwork.libcamera.org/api/patches/14655/comments/",
    "check": "pending",
    "checks": "https://patchwork.libcamera.org/api/patches/14655/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 D0BEEC3251\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri, 19 Nov 2021 11:17:07 +0000 (UTC)",
            "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 8298860371;\n\tFri, 19 Nov 2021 12:17:07 +0100 (CET)",
            "from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 628616037A\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 19 Nov 2021 12:17:00 +0100 (CET)",
            "from tatooine.ideasonboard.com (unknown\n\t[IPv6:2a01:e0a:169:7140:db30:8e54:a96:9838])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 2901C1C19;\n\tFri, 19 Nov 2021 12:17:00 +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=\"GBWQXqXx\"; dkim-atps=neutral",
        "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1637320620;\n\tbh=ZWG9gX474vUUOgKnjwrRR35c6icXmt+2acx/eE8WtAo=;\n\th=From:To:Cc:Subject:Date:In-Reply-To:References:From;\n\tb=GBWQXqXxXHIsqw9sCooHnbYVOF/ryr759JPvNkdbo7RIjBQ3Y5IVHVWle2CwYcwZS\n\tk6Qbu5KVGvtCJXvQC+DfxabwdeP3eSnGOgRR8HOIMI8NuhNCcHzAHiIqkDkCyO9Cnq\n\t5Z6rYTczfxMxuxfuvA0M5B5osJsohwjQNoDW7Bm4=",
        "From": "Jean-Michel Hautbois <jeanmichel.hautbois@ideasonboard.com>",
        "To": "libcamera-devel@lists.libcamera.org",
        "Date": "Fri, 19 Nov 2021 12:16:52 +0100",
        "Message-Id": "<20211119111654.68445-7-jeanmichel.hautbois@ideasonboard.com>",
        "X-Mailer": "git-send-email 2.32.0",
        "In-Reply-To": "<20211119111654.68445-1-jeanmichel.hautbois@ideasonboard.com>",
        "References": "<20211119111654.68445-1-jeanmichel.hautbois@ideasonboard.com>",
        "MIME-Version": "1.0",
        "Content-Transfer-Encoding": "8bit",
        "Subject": "[libcamera-devel] [PATCH v1 6/8] 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     | 249 ++++++++++++++++++++++++++\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          |  15 ++\n src/ipa/rkisp1/rkisp1.cpp             |  72 ++++----\n 6 files changed, 401 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..3d4a17ff\n--- /dev/null\n+++ b/src/ipa/rkisp1/algorithms/agc.cpp\n@@ -0,0 +1,249 @@\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+/* Maximum luminance used for brightness normalization */\n+static constexpr double kMaxLuminance = 255.0;\n+\n+/*\n+ * Normalized luma value 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 kNormalizedLumaTarget = 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] currentYGain The gain calculated on the current brightness level\n+ */\n+void Agc::computeExposure(IPAFrameContext &frameContext, double currentYGain)\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(currentYGain - 1.0) < 0.01)\n+\t\tLOG(RkISP1Agc, Debug) << \"We are well exposed (iqMean = \"\n+\t\t\t\t      << currentYGain << \")\";\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 \" << currentYGain;\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 * currentYGain;\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 average brightness of the frame\n+ * \\param[in] ae The RkISP1 statistics and ISP results\n+ * \\param[in] currentYGain The gain calculated on the current brightness level\n+ * \\return The normalized luma\n+ */\n+double Agc::computeInitialY(const rkisp1_cif_isp_ae_stat *ae, double currentYGain)\n+{\n+\tdouble ySum = 0.0;\n+\tunsigned int num = 0;\n+\n+\tfor (unsigned int aeCell = 0; aeCell < RKISP1_CIF_ISP_AE_MEAN_MAX_V10; aeCell++) {\n+\t\tySum += ae->exp_mean[aeCell] * currentYGain;\n+\t\tnum++;\n+\t}\n+\n+\t/* \\todo Weight with the AWB gains */\n+\n+\t/* Return the normalized relative luminance. */\n+\treturn ySum / num / kMaxLuminance;\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+\tdouble currentYGain = 1.0;\n+\tdouble targetY = kNormalizedLumaTarget;\n+\n+\t/*\n+\t * Do this calculation a few times as brightness increase can be\n+\t * non-linear when there are saturated regions.\n+\t */\n+\tfor (int i = 0; i < 8; i++) {\n+\t\tdouble initialY = computeInitialY(ae, currentYGain);\n+\t\tdouble extra_gain = std::min(10.0, targetY / (initialY + .001));\n+\n+\t\tcurrentYGain *= extra_gain;\n+\t\tLOG(RkISP1Agc, Debug) << \"Initial Y \" << initialY\n+\t\t\t\t      << \" target \" << targetY\n+\t\t\t\t      << \" gives gain \" << currentYGain;\n+\t\tif (extra_gain < 1.01)\n+\t\t\tbreak;\n+\t}\n+\n+\tcomputeExposure(context.frameContext, currentYGain);\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..fbb8ea98\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 currentYGain);\n+\tdouble computeInitialY(const rkisp1_cif_isp_ae_stat *ae, double currentYGain);\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 d98f77e2..05195d99 100644\n--- a/src/ipa/rkisp1/algorithms/meson.build\n+++ b/src/ipa/rkisp1/algorithms/meson.build\n@@ -1,5 +1,6 @@\n # SPDX-License-Identifier: CC0-1.0\n \n rkisp1_ipa_algorithms = files([\n+    'agc.cpp',\n     'algorithm.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 a1dc39fe..ca48ffc5 100644\n--- a/src/ipa/rkisp1/ipa_context.h\n+++ b/src/ipa/rkisp1/ipa_context.h\n@@ -19,9 +19,24 @@ namespace libcamera {\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 d2f4380a..1783f91f 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@@ -121,6 +126,9 @@ int IPARkISP1::init([[maybe_unused]] const IPASettings &settings,\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@@ -172,9 +180,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@@ -219,6 +247,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@@ -263,44 +294,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@@ -310,6 +309,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",
        "v1",
        "6/8"
    ]
}