{"id":25055,"url":"https://patchwork.libcamera.org/api/1.1/patches/25055/?format=json","web_url":"https://patchwork.libcamera.org/patch/25055/","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":"<20251114-exposure-limits-v3-3-b7c07feba026@ideasonboard.com>","date":"2025-11-14T14:16:58","name":"[v3,03/19] ipa: rkisp1: Store FrameDurationLimits as sensor config","commit_ref":null,"pull_url":null,"state":"new","archived":false,"hash":"0a62d1f7fd3242dc3e14ca00f303dfd137fb6189","submitter":{"id":143,"url":"https://patchwork.libcamera.org/api/1.1/people/143/?format=json","name":"Jacopo Mondi","email":"jacopo.mondi@ideasonboard.com"},"delegate":null,"mbox":"https://patchwork.libcamera.org/patch/25055/mbox/","series":[{"id":5590,"url":"https://patchwork.libcamera.org/api/1.1/series/5590/?format=json","web_url":"https://patchwork.libcamera.org/project/libcamera/list/?series=5590","date":"2025-11-14T14:16:55","name":"libipa: agc: Calculate exposure limits","version":3,"mbox":"https://patchwork.libcamera.org/series/5590/mbox/"}],"comments":"https://patchwork.libcamera.org/api/patches/25055/comments/","check":"pending","checks":"https://patchwork.libcamera.org/api/patches/25055/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 CAF5FC32DB\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri, 14 Nov 2025 14:17:29 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 85BE760AA6;\n\tFri, 14 Nov 2025 15:17:23 +0100 (CET)","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 C50FA60A80\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 14 Nov 2025 15:17:18 +0100 (CET)","from [192.168.1.101] (93-61-96-190.ip145.fastwebnet.it\n\t[93.61.96.190])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id C54E31198;\n\tFri, 14 Nov 2025 15:15:17 +0100 (CET)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"d2myymeX\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1763129718;\n\tbh=1+dy+sNJ94ko/yXx29Va+mfbcWlDmcuQloJYWwxM//M=;\n\th=From:Date:Subject:References:In-Reply-To:To:Cc:From;\n\tb=d2myymeXep8/2y2fR2jlI2hLhyQ/HcMrpfDax1GkCWfmsN0CxzFhOAGaTkekTJE33\n\t4tXCtuGmUJWMYyfq2QQJo2uAIGXjNsKcIdxxPYpBELhsGox1I8FAIECvztHwLiFRTk\n\tmvov00AAG/MWbefXHJDPyR45LNZaC3hkA8ses+4A=","From":"Jacopo Mondi <jacopo.mondi@ideasonboard.com>","Date":"Fri, 14 Nov 2025 15:16:58 +0100","Subject":"[PATCH v3 03/19] ipa: rkisp1: Store FrameDurationLimits as sensor\n\tconfig","MIME-Version":"1.0","Content-Type":"text/plain; charset=\"utf-8\"","Content-Transfer-Encoding":"7bit","Message-Id":"<20251114-exposure-limits-v3-3-b7c07feba026@ideasonboard.com>","References":"<20251114-exposure-limits-v3-0-b7c07feba026@ideasonboard.com>","In-Reply-To":"<20251114-exposure-limits-v3-0-b7c07feba026@ideasonboard.com>","To":"=?utf-8?q?Niklas_S=C3=B6derlund?= <niklas.soderlund@ragnatech.se>,\n\tRobert Mader <robert.mader@collabora.com>, \n\tlibcamera-devel@lists.libcamera.org","Cc":"Jacopo Mondi <jacopo.mondi@ideasonboard.com>","X-Mailer":"b4 0.14.2","X-Developer-Signature":"v=1; a=openpgp-sha256; l=8586;\n\ti=jacopo.mondi@ideasonboard.com; h=from:subject:message-id;\n\tbh=1+dy+sNJ94ko/yXx29Va+mfbcWlDmcuQloJYWwxM//M=;\n\tb=owEBbQKS/ZANAwAKAXI0Bo8WoVY8AcsmYgBpFznq7HzfDhzRSPmnyXWhMyCLvFHbipRhHBJPH\n\tqTvkafcBXKJAjMEAAEKAB0WIQS1xD1IgJogio9YOMByNAaPFqFWPAUCaRc56gAKCRByNAaPFqFW\n\tPKyhD/wPsunu7V3Hujkk1h3XURM17Qp2uuJ+jczTU+wejkCvxPem0oxY9SdrqaUCu+2rcHQQE5C\n\tt+B2wqEu6a3jBOXW1MQCuwqHqos7ZO0HRbmLabyUXssBw4Qitgf6aABwsc33j0CnPKmEnmlcXxI\n\tiF0OMUJiljZUxNpXy1jAEiuT3o3vLYA80qvk/RmRP82O+GdthczhJImZeL7UYfk7pzTrpNwIYfs\n\tZ3J59TrZncWBiiUjGFOSlxuDT0AbCsfXJIYbXZIkvs3GNwpUVw1yELudJl/sxXIyo2NeOJn1nDl\n\t0PD/ipek+Z3XYFoQ93tMWamXAY/nFGSQpv2wGft85NHMt7kXkXZqGi4huL/mduaMuPmvQNsCBsk\n\tON+HebxPmB6zKJihmeViyC4mleRVUs/O3qR3SQZztxwPKEbuwh92/L94qlQs9lRecjzKI26iRYV\n\ty3Esy0301XdOjdzT4OZQ/YxAt/dVF2DGfgczItcxwc/fR4wmwcI0q0cv/5coQ9F2JvkHJ6gPv5T\n\tKb+8xawgWaXUc4jJOAKSsG88saYohoWdhjAxrLLSMxS4d02S3OrCjbVkzERWyy2gPwR1iEzR8uG\n\tYNm7VD2p5I5YZMP7yIIazs9z3XLLoNmreY4VXvoeHi8S6zPCEguYsodvRipbUn8/W/h0VeIjczz\n\t5y6APzDI1KfdQaQ==","X-Developer-Key":"i=jacopo.mondi@ideasonboard.com; a=openpgp;\n\tfpr=72392EDC88144A65C701EA9BA5826A2587AD026B","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":"The RkISP1 IPA context stores a control list ctrlMap which is used by the\nAGC algorithm to initialize controls that are then added to the list of the\nones registered by the IPA module.\n\nThe FrameDurationLimits control is a bit of an outlier, as it's the only\ncontrol that is registered by the IPA in the context ctrlMap and is then\nused by the AGC algorithm.\n\nAs the purpose of the context ctrlMap is to be populated by algorithms for\nthe IPA, do not abuse it by registering a control there in the main IPA\nmodule and only store the control limits in the active context.\n\nRemoving FrameDurationLimits from the ctrlMap requires to store it as\na sensor configuration parameter in the IPA context and use it for\nclamping the FrameDurationLimits control and to initialize the AGC\nactive state.\n\nSigned-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>\n---\n src/ipa/rkisp1/algorithms/agc.cpp | 28 ++++++++++++------------\n src/ipa/rkisp1/ipa_context.h      |  2 ++\n src/ipa/rkisp1/rkisp1.cpp         | 46 ++++++++++++++++++++++++---------------\n 3 files changed, 44 insertions(+), 32 deletions(-)","diff":"diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp\nindex f5a3c917cb6909f6ef918e5ee8e46cf97ba55010..aa1a90daf3ca7d0041c56000c12fc4d1ab5700eb 100644\n--- a/src/ipa/rkisp1/algorithms/agc.cpp\n+++ b/src/ipa/rkisp1/algorithms/agc.cpp\n@@ -190,10 +190,10 @@ int Agc::configure(IPAContext &context, const IPACameraSensorInfo &configInfo)\n \tcontext.activeState.agc.meteringMode =\n \t\tstatic_cast<controls::AeMeteringModeEnum>(meteringModes_.begin()->first);\n \n-\t/* Limit the frame duration to match current initialisation */\n-\tControlInfo &frameDurationLimits = context.ctrlMap[&controls::FrameDurationLimits];\n-\tcontext.activeState.agc.minFrameDuration = std::chrono::microseconds(frameDurationLimits.min().get<int64_t>());\n-\tcontext.activeState.agc.maxFrameDuration = std::chrono::microseconds(frameDurationLimits.max().get<int64_t>());\n+\tcontext.activeState.agc.minFrameDuration =\n+\t\tcontext.configuration.sensor.minFrameDuration;\n+\tcontext.activeState.agc.maxFrameDuration =\n+\t\tcontext.configuration.sensor.maxFrameDuration;\n \n \tcontext.configuration.agc.measureWindow.h_offs = 0;\n \tcontext.configuration.agc.measureWindow.v_offs = 0;\n@@ -320,16 +320,16 @@ void Agc::queueRequest(IPAContext &context,\n \n \tconst auto &frameDurationLimits = controls.get(controls::FrameDurationLimits);\n \tif (frameDurationLimits) {\n-\t\t/* Limit the control value to the limits in ControlInfo */\n-\t\tControlInfo &limits = context.ctrlMap[&controls::FrameDurationLimits];\n-\t\tint64_t minFrameDuration =\n-\t\t\tstd::clamp((*frameDurationLimits).front(),\n-\t\t\t\t   limits.min().get<int64_t>(),\n-\t\t\t\t   limits.max().get<int64_t>());\n-\t\tint64_t maxFrameDuration =\n-\t\t\tstd::clamp((*frameDurationLimits).back(),\n-\t\t\t\t   limits.min().get<int64_t>(),\n-\t\t\t\t   limits.max().get<int64_t>());\n+\t\t/* Limit the control value to the sensor constraints. */\n+\t\tint64_t sensorMinFrameDuration =\n+\t\t\tcontext.configuration.sensor.minFrameDuration.get<std::micro>();\n+\t\tint64_t sensorMaxFrameDuration =\n+\t\t\tcontext.configuration.sensor.maxFrameDuration.get<std::micro>();\n+\n+\t\tint64_t minFrameDuration = std::clamp((*frameDurationLimits).front(),\n+\t\t\t\t\t\t      sensorMinFrameDuration, sensorMaxFrameDuration);\n+\t\tint64_t maxFrameDuration = std::clamp((*frameDurationLimits).back(),\n+\t\t\t\t\t\t      sensorMinFrameDuration, sensorMaxFrameDuration);\n \n \t\tagc.minFrameDuration = std::chrono::microseconds(minFrameDuration);\n \t\tagc.maxFrameDuration = std::chrono::microseconds(maxFrameDuration);\ndiff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h\nindex f85a130d9c23dba7987f388e395239e4b141d776..5fe727bd0b508617d993d226ae785056a3771ce0 100644\n--- a/src/ipa/rkisp1/ipa_context.h\n+++ b/src/ipa/rkisp1/ipa_context.h\n@@ -62,6 +62,8 @@ struct IPASessionConfiguration {\n \tstruct {\n \t\tutils::Duration minExposureTime;\n \t\tutils::Duration maxExposureTime;\n+\t\tutils::Duration minFrameDuration;\n+\t\tutils::Duration maxFrameDuration;\n \t\tdouble minAnalogueGain;\n \t\tdouble maxAnalogueGain;\n \ndiff --git a/src/ipa/rkisp1/rkisp1.cpp b/src/ipa/rkisp1/rkisp1.cpp\nindex fa22bfc349043002345d275b11a60ac983e329d7..f25e477f0fb77241bd1ccddb7778205e58bdc8a9 100644\n--- a/src/ipa/rkisp1/rkisp1.cpp\n+++ b/src/ipa/rkisp1/rkisp1.cpp\n@@ -227,6 +227,7 @@ int IPARkISP1::configure(const IPAConfigInfo &ipaConfig,\n \t\t\t const std::map<uint32_t, IPAStream> &streamConfig,\n \t\t\t ControlInfoMap *ipaControls)\n {\n+\tconst IPACameraSensorInfo &info = ipaConfig.sensorInfo;\n \tsensorControls_ = ipaConfig.sensorControls;\n \n \tconst auto itExp = sensorControls_.find(V4L2_CID_EXPOSURE);\n@@ -237,6 +238,12 @@ int IPARkISP1::configure(const IPAConfigInfo &ipaConfig,\n \tint32_t minGain = itGain->second.min().get<int32_t>();\n \tint32_t maxGain = itGain->second.max().get<int32_t>();\n \n+\tconst auto itVBlank = sensorControls_.find(V4L2_CID_VBLANK);\n+\tstd::array<uint32_t, 2> frameHeights{\n+\t\titVBlank->second.min().get<int32_t>() + info.outputSize.height,\n+\t\titVBlank->second.max().get<int32_t>() + info.outputSize.height,\n+\t};\n+\n \tLOG(IPARkISP1, Debug)\n \t\t<< \"Exposure: [\" << minExposure << \", \" << maxExposure\n \t\t<< \"], gain: [\" << minGain << \", \" << maxGain << \"]\";\n@@ -248,11 +255,10 @@ int IPARkISP1::configure(const IPAConfigInfo &ipaConfig,\n \n \tcontext_.configuration.paramFormat = ipaConfig.paramFormat;\n \n-\tconst IPACameraSensorInfo &info = ipaConfig.sensorInfo;\n-\tconst ControlInfo vBlank = sensorControls_.find(V4L2_CID_VBLANK)->second;\n-\tcontext_.configuration.sensor.defVBlank = vBlank.def().get<int32_t>();\n+\tutils::Duration lineDuration = info.minLineLength * 1.0s / info.pixelRate;\n+\tcontext_.configuration.sensor.defVBlank = itVBlank->second.def().get<int32_t>();\n \tcontext_.configuration.sensor.size = info.outputSize;\n-\tcontext_.configuration.sensor.lineDuration = info.minLineLength * 1.0s / info.pixelRate;\n+\tcontext_.configuration.sensor.lineDuration = lineDuration;\n \n \t/* Update the camera controls using the new sensor settings. */\n \tupdateControls(info, sensorControls_, ipaControls);\n@@ -261,17 +267,13 @@ int IPARkISP1::configure(const IPAConfigInfo &ipaConfig,\n \t * When the AGC computes the new exposure values for a frame, it needs\n \t * to know the limits for exposure time and analogue gain. As it depends\n \t * on the sensor, update it with the controls.\n-\t *\n-\t * \\todo take VBLANK into account for maximum exposure time\n \t */\n-\tcontext_.configuration.sensor.minExposureTime =\n-\t\tminExposure * context_.configuration.sensor.lineDuration;\n-\tcontext_.configuration.sensor.maxExposureTime =\n-\t\tmaxExposure * context_.configuration.sensor.lineDuration;\n-\tcontext_.configuration.sensor.minAnalogueGain =\n-\t\tcontext_.camHelper->gain(minGain);\n-\tcontext_.configuration.sensor.maxAnalogueGain =\n-\t\tcontext_.camHelper->gain(maxGain);\n+\tcontext_.configuration.sensor.minExposureTime = minExposure * lineDuration;\n+\tcontext_.configuration.sensor.maxExposureTime = maxExposure * lineDuration;\n+\tcontext_.configuration.sensor.minFrameDuration = frameHeights[0] * lineDuration;\n+\tcontext_.configuration.sensor.maxFrameDuration = frameHeights[1] * lineDuration;\n+\tcontext_.configuration.sensor.minAnalogueGain = context_.camHelper->gain(minGain);\n+\tcontext_.configuration.sensor.maxAnalogueGain = context_.camHelper->gain(maxGain);\n \n \tcontext_.configuration.raw = std::any_of(streamConfig.begin(), streamConfig.end(),\n \t\t[](auto &cfg) -> bool {\n@@ -436,12 +438,20 @@ void IPARkISP1::updateControls(const IPACameraSensorInfo &sensorInfo,\n \t\tuint64_t frameSize = lineLength * frameHeights[i];\n \t\tframeDurations[i] = frameSize / (sensorInfo.pixelRate / 1000000U);\n \t}\n-\n-\t/* \\todo Move this (and other agc-related controls) to agc */\n-\tcontext_.ctrlMap[&controls::FrameDurationLimits] =\n+\tctrlMap[&controls::FrameDurationLimits] =\n \t\tControlInfo(frameDurations[0], frameDurations[1], frameDurations[2]);\n \n-\tctrlMap.insert(context_.ctrlMap.begin(), context_.ctrlMap.end());\n+\t/*\n+\t * Store the min/max frame duration in the active context to initialize\n+\t * the AGC algorithm.\n+\t *\n+\t * \\todo Move this (and other agc-related controls) to agc\n+\t */\n+\tcontext_.activeState.agc.minFrameDuration = std::chrono::microseconds(frameDurations[0]);\n+\tcontext_.activeState.agc.maxFrameDuration = std::chrono::microseconds(frameDurations[1]);\n+\n+\tctrlMap.merge(context_.ctrlMap);\n+\n \t*ipaControls = ControlInfoMap(std::move(ctrlMap), controls::controls);\n }\n \n","prefixes":["v3","03/19"]}