[{"id":29192,"web_url":"https://patchwork.libcamera.org/comment/29192/","msgid":"<6d10b13f-ad2a-4fc1-82cd-d57e843ea04d@ideasonboard.com>","date":"2024-04-10T15:00:44","subject":"Re: [PATCH 5/5] ipa: rkisp1: agc: Plumb mode-selection and frame\n\tduration controls","submitter":{"id":156,"url":"https://patchwork.libcamera.org/api/people/156/","name":"Dan Scally","email":"dan.scally@ideasonboard.com"},"content":"Hi Paul\n\nOn 05/04/2024 15:47, Paul Elder wrote:\n> Plumb controls for setting metering mode, exposure mode, constraint\n> mode, and frame duration limits. Also report them as available controls,\n> as well as in metadata.\n>\n> Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>\n> ---\n>   src/ipa/rkisp1/algorithms/agc.cpp     | 67 ++++++++++++++++++++-------\n>   src/ipa/rkisp1/algorithms/agc.h       |  5 ++\n>   src/ipa/rkisp1/algorithms/algorithm.h |  2 +\n>   src/ipa/rkisp1/ipa_context.h          |  4 ++\n>   src/ipa/rkisp1/rkisp1.cpp             | 10 ++++\n>   5 files changed, 72 insertions(+), 16 deletions(-)\n>\n> diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp\n> index a1b6eb39..ed4d6330 100644\n> --- a/src/ipa/rkisp1/algorithms/agc.cpp\n> +++ b/src/ipa/rkisp1/algorithms/agc.cpp\n> @@ -11,6 +11,7 @@\n>   #include <chrono>\n>   #include <cmath>\n>   #include <tuple>\n> +#include <vector>\n>   \n>   #include <libcamera/base/log.h>\n>   #include <libcamera/base/utils.h>\n> @@ -49,6 +50,7 @@ int Agc::parseMeteringModes(IPAContext &context, const YamlObject &tuningData,\n>   \t\treturn -EINVAL;\n>   \t}\n>   \n> +\tstd::vector<ControlValue> availableMeteringModes;\n>   \tfor (const auto &[key, value] : yamlMeteringModes.asDict()) {\n>   \t\tif (controls::AeMeteringModeNameValueMap.find(key) ==\n>   \t\t    controls::AeMeteringModeNameValueMap.end()) {\n> @@ -67,9 +69,14 @@ int Agc::parseMeteringModes(IPAContext &context, const YamlObject &tuningData,\n>   \t\t\treturn -ENODATA;\n>   \t\t}\n>   \n> -\t\tmeteringModes_[controls::AeMeteringModeNameValueMap.at(key)] = weights;\n> +\t\tint32_t control = controls::AeMeteringModeNameValueMap.at(key);\n> +\t\tmeteringModes_[control] = weights;\n> +\t\tavailableMeteringModes.push_back(control);\n>   \t}\n>   \n> +\tAlgorithm::controls_[&controls::AeMeteringMode] =\n> +\t\tControlInfo(availableMeteringModes);\n> +\n>   \treturn 0;\n>   }\n>   \n> @@ -131,9 +138,25 @@ int Agc::init(IPAContext &context, const YamlObject &tuningData)\n>   \n>   \tcontext.ctrlMap.merge(controls());\n>   \n> +\tdefaultConstraintMode_ = constraintModes().begin()->first;\n> +\tdefaultExposureMode_ = exposureModeHelpers().begin()->first;\n> +\tdefaultMeteringMode_ = meteringModes_.begin()->first;\n> +\n> +\tAlgorithm::controls_.merge(ControlInfoMap::Map(controls()));\n> +\n>   \treturn 0;\n>   }\n>   \n> +void Agc::configureExposureModeHelpers(IPAContext &context, utils::Duration maxShutterSpeed)\n> +{\n> +\tfor (auto &[id, helper] : exposureModeHelpers()) {\n> +\t\thelper->configure(context.configuration.sensor.minShutterSpeed,\n> +\t\t\t\t  maxShutterSpeed,\n> +\t\t\t\t  context.configuration.sensor.minAnalogueGain,\n> +\t\t\t\t  context.configuration.sensor.maxAnalogueGain);\n> +\t}\n> +}\n> +\n>   /**\n>    * \\brief Configure the AGC given a configInfo\n>    * \\param[in] context The shared IPA context\n> @@ -153,12 +176,8 @@ int Agc::configure(IPAContext &context, const IPACameraSensorInfo &configInfo)\n>   \tcontext.activeState.agc.manual.dgain = 1;\n>   \tcontext.activeState.agc.autoEnabled = !context.configuration.raw;\n>   \n> -\t/*\n> -\t* \\todo We should use the first available mode rather than assume that\n> -\t* the \"Normal\" modes are present in tuning data.\n> -\t*/\n> -\tcontext.activeState.agc.constraintMode = controls::ConstraintNormal;\n> -\tcontext.activeState.agc.exposureMode = controls::ExposureNormal;\n> +\tcontext.activeState.agc.constraintMode = defaultConstraintMode_;\n> +\tcontext.activeState.agc.exposureMode = defaultExposureMode_;\n>   \n>   \t/*\n>   \t * Define the measurement window for AGC as a centered rectangle\n> @@ -169,13 +188,7 @@ int Agc::configure(IPAContext &context, const IPACameraSensorInfo &configInfo)\n>   \tcontext.configuration.agc.measureWindow.h_size = 3 * configInfo.outputSize.width / 4;\n>   \tcontext.configuration.agc.measureWindow.v_size = 3 * configInfo.outputSize.height / 4;\n>   \n> -\tfor (auto &[id, helper] : exposureModeHelpers()) {\n> -\t\t/* \\todo Run this again when FrameDurationLimits is passed in */\n> -\t\thelper->configure(context.configuration.sensor.minShutterSpeed,\n> -\t\t\t\t  context.configuration.sensor.maxShutterSpeed,\n> -\t\t\t\t  context.configuration.sensor.minAnalogueGain,\n> -\t\t\t\t  context.configuration.sensor.maxAnalogueGain);\n> -\t}\n> +\tconfigureExposureModeHelpers(context, context.configuration.sensor.maxShutterSpeed);\n>   \n>   \treturn 0;\n>   }\n> @@ -223,6 +236,20 @@ void Agc::queueRequest(IPAContext &context,\n>   \t\tframeContext.agc.exposure = agc.manual.exposure;\n>   \t\tframeContext.agc.gain = agc.manual.gain;\n>   \t}\n> +\n> +\tconst auto &meteringMode = controls.get(controls::AeMeteringMode);\n> +\tframeContext.agc.meteringMode = meteringMode.value_or(defaultMeteringMode_);\n> +\n> +\tconst auto &exposureMode = controls.get(controls::AeExposureMode);\n> +\tframeContext.agc.exposureMode = exposureMode.value_or(defaultExposureMode_);\n> +\n> +\tconst auto &constraintMode = controls.get(controls::AeConstraintMode);\n> +\tframeContext.agc.constraintMode = constraintMode.value_or(defaultConstraintMode_);\n\n\nWouldn't this mean the modes revert to default unless we pass in those controls with every request? \nI have this same operation as:\n\n\nframeContext.agc.exposureMode = exposureMode.value_or(frameContext.agc.exposureMode)\n\n\nso that we keep the already configured value instead.\n\n> +\n> +\tconst auto &frameDurationLimits = controls.get(controls::FrameDurationLimits);\n> +\tframeContext.agc.maxShutterSpeed = frameDurationLimits\n> +\t\t\t\t\t ? std::chrono::milliseconds((*frameDurationLimits).back())\n> +\t\t\t\t\t : 60ms;\n>   }\n>   \n>   /**\n> @@ -262,8 +289,7 @@ void Agc::prepare(IPAContext &context, const uint32_t frame,\n>   \t\tparams->meas.hst_config.hist_weight,\n>   \t\tcontext.hw->numHistogramWeights\n>   \t};\n> -\t/* \\todo Get this from control */\n> -\tstd::vector<uint8_t> &modeWeights = meteringModes_.at(controls::MeteringMatrix);\n> +\tstd::vector<uint8_t> &modeWeights = meteringModes_.at(frameContext.agc.meteringMode);\n>   \tstd::copy(modeWeights.begin(), modeWeights.end(), weights.begin());\n>   \n>   \tstd::stringstream str;\n> @@ -290,6 +316,7 @@ void Agc::fillMetadata(IPAContext &context, IPAFrameContext &frameContext,\n>   \t\t\t\t     * frameContext.sensor.exposure;\n>   \tmetadata.set(controls::AnalogueGain, frameContext.sensor.gain);\n>   \tmetadata.set(controls::ExposureTime, exposureTime.get<std::micro>());\n> +\tmetadata.set(controls::AeEnable, frameContext.agc.autoEnabled);\n>   \n>   \t/* \\todo Use VBlank value calculated from each frame exposure. */\n>   \tuint32_t vTotal = context.configuration.sensor.size.height\n> @@ -297,6 +324,10 @@ void Agc::fillMetadata(IPAContext &context, IPAFrameContext &frameContext,\n>   \tutils::Duration frameDuration = context.configuration.sensor.lineDuration\n>   \t\t\t\t      * vTotal;\n>   \tmetadata.set(controls::FrameDuration, frameDuration.get<std::micro>());\n> +\n> +\tmetadata.set(controls::AeMeteringMode, frameContext.agc.meteringMode);\n> +\tmetadata.set(controls::AeExposureMode, frameContext.agc.exposureMode);\n> +\tmetadata.set(controls::AeConstraintMode, frameContext.agc.constraintMode);\n>   }\n>   \n>   void Agc::parseStatistics(const rkisp1_stat_buffer *stats,\n> @@ -378,6 +409,10 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame,\n>   \n>   \tASSERT(stats->meas_type & RKISP1_CIF_ISP_STAT_AUTOEXP);\n>   \n> +\tutils::Duration maxShutterSpeed = std::min(context.configuration.sensor.maxShutterSpeed,\n> +\t\t\t\t\t\t   frameContext.agc.maxShutterSpeed);\n> +\tconfigureExposureModeHelpers(context, maxShutterSpeed);\n> +\n>   \tparseStatistics(stats, context);\n>   \n>   \t/*\n> diff --git a/src/ipa/rkisp1/algorithms/agc.h b/src/ipa/rkisp1/algorithms/agc.h\n> index 43e3d5b2..c05ba4be 100644\n> --- a/src/ipa/rkisp1/algorithms/agc.h\n> +++ b/src/ipa/rkisp1/algorithms/agc.h\n> @@ -47,6 +47,7 @@ private:\n>   \tint parseMeteringModes(IPAContext &context, const YamlObject &tuningData,\n>   \t\t\t       const char *prop);\n>   \tuint8_t predivider(Size &size);\n> +\tvoid configureExposureModeHelpers(IPAContext &context, utils::Duration maxShutterSpeed);\n>   \n>   \tvoid fillMetadata(IPAContext &context, IPAFrameContext &frameContext,\n>   \t\t\t  ControlList &metadata);\n> @@ -58,6 +59,10 @@ private:\n>   \tSpan<const uint8_t> expMeans_;\n>   \n>   \tstd::map<int32_t, std::vector<uint8_t>> meteringModes_;\n> +\n> +\tint32_t defaultConstraintMode_;\n> +\tint32_t defaultExposureMode_;\n> +\tint32_t defaultMeteringMode_;\n>   };\n>   \n>   } /* namespace ipa::rkisp1::algorithms */\n> diff --git a/src/ipa/rkisp1/algorithms/algorithm.h b/src/ipa/rkisp1/algorithms/algorithm.h\n> index 9454c9a1..c3a002b8 100644\n> --- a/src/ipa/rkisp1/algorithms/algorithm.h\n> +++ b/src/ipa/rkisp1/algorithms/algorithm.h\n> @@ -25,6 +25,8 @@ public:\n>   \n>   \tbool disabled_;\n>   \tbool supportsRaw_;\n> +\n> +\tControlInfoMap::Map controls_;\n>   };\n>   \n>   } /* namespace ipa::rkisp1 */\n> diff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h\n> index a70c7eb3..dc876da0 100644\n> --- a/src/ipa/rkisp1/ipa_context.h\n> +++ b/src/ipa/rkisp1/ipa_context.h\n> @@ -114,6 +114,10 @@ struct IPAFrameContext : public FrameContext {\n>   \t\tdouble gain;\n>   \t\tdouble dgain;\n>   \t\tbool autoEnabled;\n> +\t\tint32_t meteringMode;\n> +\t\tint32_t exposureMode;\n> +\t\tint32_t constraintMode;\n> +\t\tutils::Duration maxShutterSpeed;\n>   \t} agc;\n>   \n>   \tstruct {\n> diff --git a/src/ipa/rkisp1/rkisp1.cpp b/src/ipa/rkisp1/rkisp1.cpp\n> index d66dfdd7..3654b5a6 100644\n> --- a/src/ipa/rkisp1/rkisp1.cpp\n> +++ b/src/ipa/rkisp1/rkisp1.cpp\n> @@ -80,6 +80,7 @@ private:\n>   \tstd::map<unsigned int, MappedFrameBuffer> mappedBuffers_;\n>   \n>   \tControlInfoMap sensorControls_;\n> +\tControlInfoMap::Map algoControls_;\n>   \n>   \t/* Interface to the Camera Helper */\n>   \tstd::unique_ptr<CameraSensorHelper> camHelper_;\n> @@ -193,6 +194,14 @@ int IPARkISP1::init(const IPASettings &settings, unsigned int hwRevision,\n>   \tif (ret)\n>   \t\treturn ret;\n>   \n> +\tfor (auto const &a : algorithms()) {\n> +\t\tAlgorithm *algo = static_cast<Algorithm *>(a.get());\n> +\n> +\t\t/* \\todo Avoid merging duplicate controls */\n> +\t\tif (!algo->controls_.empty())\n> +\t\t\talgoControls_.merge(ControlInfoMap::Map(algo->controls_));\n> +\t}\n> +\n>   \t/* Initialize controls. */\n>   \tupdateControls(sensorInfo, sensorControls, ipaControls);\n>   \n> @@ -377,6 +386,7 @@ void IPARkISP1::updateControls(const IPACameraSensorInfo &sensorInfo,\n>   \t\t\t       ControlInfoMap *ipaControls)\n>   {\n>   \tControlInfoMap::Map ctrlMap = rkisp1Controls;\n> +\tctrlMap.merge(algoControls_);\n>   \n>   \t/*\n>   \t * Compute exposure time limits from the V4L2_CID_EXPOSURE control","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 BC55EC0DA4\n\tfor <parsemail@patchwork.libcamera.org>;\n\tWed, 10 Apr 2024 15:00:49 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 2180963352;\n\tWed, 10 Apr 2024 17:00:49 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 62D4661B8D\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 10 Apr 2024 17:00:47 +0200 (CEST)","from [192.168.0.43]\n\t(cpc141996-chfd3-2-0-cust928.12-3.cable.virginm.net [86.13.91.161])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id D9AE1735\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 10 Apr 2024 17:00:04 +0200 (CEST)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"SCV3sPeV\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1712761205;\n\tbh=3iX5VS38xg//rHH8HSI5IDLvv9CEyT31Wn8kWeGsDks=;\n\th=Date:Subject:To:References:From:In-Reply-To:From;\n\tb=SCV3sPeV5mIXRpF0ex7es/spdk4dQpPessSKMBZ5MJQtxFThHhMEaXKPcCUjD+5HX\n\t6WmJv0TaWXVbfp7cfRlmVCNA+PDE8zGln1BHPAgPRxT8Eh1qD/d3gk4W9RyLwAMXTn\n\tA2EuBMh6/IGwYSyh10XvfnlJc3HQrcbZyuERmr3s=","Message-ID":"<6d10b13f-ad2a-4fc1-82cd-d57e843ea04d@ideasonboard.com>","Date":"Wed, 10 Apr 2024 16:00:44 +0100","MIME-Version":"1.0","User-Agent":"Mozilla Thunderbird","Subject":"Re: [PATCH 5/5] ipa: rkisp1: agc: Plumb mode-selection and frame\n\tduration controls","To":"libcamera-devel@lists.libcamera.org","References":"<20240405144729.2992219-1-paul.elder@ideasonboard.com>\n\t<20240405144729.2992219-6-paul.elder@ideasonboard.com>","Content-Language":"en-US","From":"Dan Scally <dan.scally@ideasonboard.com>","Autocrypt":"addr=dan.scally@ideasonboard.com; keydata=\n\t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","In-Reply-To":"<20240405144729.2992219-6-paul.elder@ideasonboard.com>","Content-Type":"text/plain; charset=UTF-8; format=flowed","Content-Transfer-Encoding":"7bit","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>"}}]