{"id":18906,"url":"https://patchwork.libcamera.org/api/patches/18906/?format=json","web_url":"https://patchwork.libcamera.org/patch/18906/","project":{"id":1,"url":"https://patchwork.libcamera.org/api/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":"<20230731094641.73646-4-david.plowman@raspberrypi.com>","date":"2023-07-31T09:46:39","name":"[libcamera-devel,3/5] ipa: rpi: agc: Implementation of multi-channel AGC","commit_ref":null,"pull_url":null,"state":"superseded","archived":false,"hash":"8b92fe74afe81525386e989bb6415a0b4db92722","submitter":{"id":42,"url":"https://patchwork.libcamera.org/api/people/42/?format=json","name":"David Plowman","email":"david.plowman@raspberrypi.com"},"delegate":null,"mbox":"https://patchwork.libcamera.org/patch/18906/mbox/","series":[{"id":3996,"url":"https://patchwork.libcamera.org/api/series/3996/?format=json","web_url":"https://patchwork.libcamera.org/project/libcamera/list/?series=3996","date":"2023-07-31T09:46:36","name":"Multi-channel AGC","version":1,"mbox":"https://patchwork.libcamera.org/series/3996/mbox/"}],"comments":"https://patchwork.libcamera.org/api/patches/18906/comments/","check":"pending","checks":"https://patchwork.libcamera.org/api/patches/18906/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 35C80C32A9\n\tfor <parsemail@patchwork.libcamera.org>;\n\tMon, 31 Jul 2023 09:46:51 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 3ABA3627F9;\n\tMon, 31 Jul 2023 11:46:50 +0200 (CEST)","from mail-wm1-x32b.google.com (mail-wm1-x32b.google.com\n\t[IPv6:2a00:1450:4864:20::32b])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 27202627EF\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 31 Jul 2023 11:46:47 +0200 (CEST)","by mail-wm1-x32b.google.com with SMTP id\n\t5b1f17b1804b1-3fbf1b82d9cso39674235e9.2\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 31 Jul 2023 02:46:47 -0700 (PDT)","from pi4-davidp.pitowers.org\n\t([2a00:1098:3142:14:2bce:64d6:1a5c:49a2])\n\tby smtp.gmail.com with ESMTPSA id\n\t9-20020a05600c240900b003fa98908014sm13612838wmp.8.2023.07.31.02.46.45\n\t(version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n\tMon, 31 Jul 2023 02:46:45 -0700 (PDT)"],"DKIM-Signature":["v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org;\n\ts=mail; t=1690796810;\n\tbh=ZJSi6bOkfN3TjdsBu0GV55WnZa2Ci/74JphlKiKVBE8=;\n\th=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe:\n\tList-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To:\n\tFrom;\n\tb=ucl7xIw6WbA4lv+qq70QyRsqaWVX70GqXLckNFIygMTPweT1Brnq2GXMuhsMnuks8\n\t0iF9YAY79tM8PN0Ko9M29ZlabCX6g4xEkAVjDwAZg03U5JqgBsu+VBbPglxVsmKGP4\n\tY0tySKUXo22RIRhoQfgwYxcpzYsj9nIhfrqu9bGSgEi67InlqILckR9eLhsVIf3UCw\n\tQaxELqHcegfkkwBfLBPZXDXgPmATzS0tav3KuYK9Dds9LzOHHMJ+8bjyAyBmHC6QOV\n\t/vSCVfEOU4+OvAJP8yGc9hR6v1zSwP0s0jVRF/+VJrAV3f7fVyAKzQGDfhWGWhbCvi\n\td/UbfJ+SSdX9A==","v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=raspberrypi.com; s=google; t=1690796806; x=1691401606;\n\th=content-transfer-encoding:mime-version:references:in-reply-to\n\t:message-id:date:subject:cc:to:from:from:to:cc:subject:date\n\t:message-id:reply-to;\n\tbh=8+ExL/Z4e7Z72TgXvZCoUG9fhEbBhbxBscCQO3R9Tko=;\n\tb=IEL0U3VNPzDWQPvIBFOA7kUPF57l6JsX5zIxmVsCFM49SYdgbjoLl9s51JlDbUiQD9\n\tLjXTIVTh49ZmtGdpziumdFYGoMipNUaKosXoyzMSdDRLf4HupzyljAvEyo2uTt7F9TTH\n\tmDyru3vXZejNqisXzmsNbbwlG3BKSPveK8ME6z/um/fUuqS4kH2qT3IJfJ8Tfmz2YyqP\n\tRLhcFq2vur5VR1n9ryxQC7ugvUUFpl5MuRq+IKRkE2dRSyedT0eL3MjNzEs1J7q2bL00\n\tVSjhmK9y+9h1VPz7QdNbpEuOcmNL0o+K+IYRtZV6M7pxwH7wmcvenIlvBuln+Dbj7/8W\n\tmODg=="],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (2048-bit key; \n\tunprotected) header.d=raspberrypi.com\n\theader.i=@raspberrypi.com\n\theader.b=\"IEL0U3VN\"; dkim-atps=neutral","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20221208; t=1690796806; x=1691401606;\n\th=content-transfer-encoding:mime-version:references:in-reply-to\n\t:message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc\n\t:subject:date:message-id:reply-to;\n\tbh=8+ExL/Z4e7Z72TgXvZCoUG9fhEbBhbxBscCQO3R9Tko=;\n\tb=i6RF+A1MiqxCXjPbPUyIzDgh4Cd+s36ZBj4Vmy5Nwe7DquxT6e+SOg5Vp1DzXIyzs9\n\tbX6xpysATe92swx7fe4JsYEztOIcTjMZ/VTmW0ALDSGFwTGZurA/be/jMm2p5i7jjYfX\n\tk24NcN+daDEe9TNcZj0MdVvFqQXGeHP9/R9L4sBxgnsRL7RH9WEqpqKZwAyzrtGQVSwy\n\tFwRXWGJ1i1ne+CXslA7tAfv4qT/EBY/4mLXtSctxa5UuOnLyLXGiCjNSPR41KhoagnEm\n\tK1Sj0ml/9pKAlOL+8bBITmclaVF1xlmjyw7qDueA5NJTPbwV2Lo1JPEcQr2gA0lp6K+b\n\t6bJg==","X-Gm-Message-State":"ABy/qLYc+AyPy6S3Yq78Z7YNNmZPg/kUs2CGDkertMJQK9pWa3k9H1G0\n\t+24CB06MySeivtr6nqi/KNGaw/pfqJhNMx7i5K0=","X-Google-Smtp-Source":"APBJJlH/b5FGCEMCeeeQ/5l2eYWjNDJzO8KjUUwWrKwllqx+lVnxEFaP6bljfKoQk0HCoTst3tr7Ww==","X-Received":"by 2002:a1c:7917:0:b0:3fe:16f4:d865 with SMTP id\n\tl23-20020a1c7917000000b003fe16f4d865mr3915666wme.23.1690796806169; \n\tMon, 31 Jul 2023 02:46:46 -0700 (PDT)","To":"libcamera-devel@lists.libcamera.org","Date":"Mon, 31 Jul 2023 10:46:39 +0100","Message-Id":"<20230731094641.73646-4-david.plowman@raspberrypi.com>","X-Mailer":"git-send-email 2.30.2","In-Reply-To":"<20230731094641.73646-1-david.plowman@raspberrypi.com>","References":"<20230731094641.73646-1-david.plowman@raspberrypi.com>","MIME-Version":"1.0","Content-Transfer-Encoding":"8bit","Subject":"[libcamera-devel] [PATCH 3/5] ipa: rpi: agc: Implementation of\n\tmulti-channel 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>","From":"David Plowman via libcamera-devel <libcamera-devel@lists.libcamera.org>","Reply-To":"David Plowman <david.plowman@raspberrypi.com>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"},"content":"The switchMode, prepare and process methods are updated to implement\nmulti-channel AGC correctly:\n\n* switchMode now invokes switchMode on all the channels (whether\n  active or not).\n\n* prepare must find what channel the current frame is, and run on\n  behalf of that channel.\n\n* process updates the most recent DeviceStatus and statistics for the\n  channel of the frame that has just arrived, but generates updated\n  values working through the active channels in round-robin fashion.\n\nOne minor detail in process is that we don't want to change the\nDeviceStatus metadata of the current frame, so we now pass this to the\nAgcChannel's process method, rather than letting it find the\nDeviceStatus in the metadata.\n\nSigned-off-by: David Plowman <david.plowman@raspberrypi.com>\n---\n src/ipa/rpi/controller/agc_status.h        |   1 +\n src/ipa/rpi/controller/rpi/agc.cpp         | 108 +++++++++++++++++++--\n src/ipa/rpi/controller/rpi/agc.h           |   1 +\n src/ipa/rpi/controller/rpi/agc_channel.cpp |  13 +--\n src/ipa/rpi/controller/rpi/agc_channel.h   |   4 +-\n 5 files changed, 109 insertions(+), 18 deletions(-)","diff":"diff --git a/src/ipa/rpi/controller/agc_status.h b/src/ipa/rpi/controller/agc_status.h\nindex 597eddd7..e5c4ee22 100644\n--- a/src/ipa/rpi/controller/agc_status.h\n+++ b/src/ipa/rpi/controller/agc_status.h\n@@ -36,6 +36,7 @@ struct AgcStatus {\n \tint floatingRegionEnable;\n \tlibcamera::utils::Duration fixedShutter;\n \tdouble fixedAnalogueGain;\n+\tunsigned int channel;\n };\n \n struct AgcPrepareStatus {\ndiff --git a/src/ipa/rpi/controller/rpi/agc.cpp b/src/ipa/rpi/controller/rpi/agc.cpp\nindex c9c9c297..7e627bba 100644\n--- a/src/ipa/rpi/controller/rpi/agc.cpp\n+++ b/src/ipa/rpi/controller/rpi/agc.cpp\n@@ -22,7 +22,8 @@ LOG_DEFINE_CATEGORY(RPiAgc)\n \n Agc::Agc(Controller *controller)\n \t: AgcAlgorithm(controller),\n-\t  activeChannels_({ 0 })\n+\t  activeChannels_({ 0 }),\n+\t  index_(0)\n {\n }\n \n@@ -203,20 +204,113 @@ void Agc::setActiveChannels(const std::vector<unsigned int> &activeChannels)\n void Agc::switchMode(CameraMode const &cameraMode,\n \t\t     Metadata *metadata)\n {\n-\tLOG(RPiAgc, Debug) << \"switchMode for channel 0\";\n-\tchannelData_[0].channel.switchMode(cameraMode, metadata);\n+\t/*\n+\t * We run switchMode on every channel, and then we're going to start over\n+\t * with the first active channel again which means that this channel's\n+\t * status needs to be the one we leave in the metadata.\n+\t */\n+\tAgcStatus status;\n+\n+\tfor (unsigned int channelIndex = 0; channelIndex < channelData_.size(); channelIndex++) {\n+\t\tLOG(RPiAgc, Debug) << \"switchMode for channel \" << channelIndex;\n+\t\tchannelData_[channelIndex].channel.switchMode(cameraMode, metadata);\n+\t\tif (channelIndex == activeChannels_[0])\n+\t\t\tmetadata->get(\"agc.status\", status);\n+\t}\n+\n+\tstatus.channel = activeChannels_[0];\n+\tmetadata->set(\"agc.status\", status);\n+\tindex_ = 0;\n+}\n+\n+static void getChannelIndex(Metadata *metadata, const char *message, unsigned int &channelIndex)\n+{\n+\tstd::unique_lock<RPiController::Metadata> lock(*metadata);\n+\tAgcStatus *status = metadata->getLocked<AgcStatus>(\"agc.delayed_status\");\n+\tif (status)\n+\t\tchannelIndex = status->channel;\n+\telse\n+\t\t/* This does happen at startup, otherwise it would be a Warning or Error. */\n+\t\tLOG(RPiAgc, Debug) << message;\n+}\n+\n+static void setChannelIndex(Metadata *metadata, const char *message, unsigned int channelIndex)\n+{\n+\tstd::unique_lock<RPiController::Metadata> lock(*metadata);\n+\tAgcStatus *status = metadata->getLocked<AgcStatus>(\"agc.status\");\n+\tif (status)\n+\t\tstatus->channel = channelIndex;\n+\telse\n+\t\t/* This does happen at startup, otherwise it would be a Warning or Error. */\n+\t\tLOG(RPiAgc, Debug) << message;\n }\n \n void Agc::prepare(Metadata *imageMetadata)\n {\n-\tLOG(RPiAgc, Debug) << \"prepare for channel 0\";\n-\tchannelData_[0].channel.prepare(imageMetadata);\n+\t/*\n+\t * The DeviceStatus in the metadata should be correct for the image we\n+\t * are processing. THe delayed status should tell us what channel this frame\n+\t * was from, so we will use that channel's prepare method.\n+\t *\n+\t * \\todo To be honest, there's not much that's stateful in the prepare methods\n+\t * so we should perhaps re-evaluate whether prepare even needs to be done\n+\t * \"per channel\".\n+\t */\n+\tunsigned int channelIndex = activeChannels_[0];\n+\tgetChannelIndex(imageMetadata, \"prepare: no delayed status\", channelIndex);\n+\n+\tLOG(RPiAgc, Debug) << \"prepare for channel \" << channelIndex;\n+\tchannelData_[channelIndex].channel.prepare(imageMetadata);\n }\n \n void Agc::process(StatisticsPtr &stats, Metadata *imageMetadata)\n {\n-\tLOG(RPiAgc, Debug) << \"process for channel 0\";\n-\tchannelData_[0].channel.process(stats, imageMetadata);\n+\t/*\n+\t * We want to generate values for the next channel in round robin fashion\n+\t * (i.e. the channel at location index_ in the activeChannel list), even though\n+\t * the statistics we have will be for a different channel (which we find\n+\t * again from the delayed status).\n+\t */\n+\n+\t/* Generate updated AGC values for this channel: */\n+\tunsigned int channelIndex = activeChannels_[index_];\n+\tAgcChannelData &channelData = channelData_[channelIndex];\n+\t/* Stats are from this channel: */\n+\tunsigned int statsIndex = 0;\n+\tgetChannelIndex(imageMetadata, \"process: no delayed status for stats\", statsIndex);\n+\tLOG(RPiAgc, Debug) << \"process for channel \" << channelIndex;\n+\n+\t/*\n+\t * We keep a cache of the most recent DeviceStatus and stats for each channel,\n+\t * so that we can invoke the next channel's process method with the most up to date\n+\t * values.\n+\t */\n+\tLOG(RPiAgc, Debug) << \"Save DeviceStatus and stats for channel \" << statsIndex;\n+\tDeviceStatus deviceStatus;\n+\tif (imageMetadata->get<DeviceStatus>(\"device.status\", deviceStatus) == 0)\n+\t\tchannelData_[statsIndex].deviceStatus = deviceStatus;\n+\telse\n+\t\t/* Every frame should have a DeviceStatus. */\n+\t\tLOG(RPiAgc, Error) << \"process: no device status found\";\n+\tchannelData_[statsIndex].statistics = stats;\n+\n+\t/*\n+\t * Finally fetch the most recent DeviceStatus and stats for this channel, if both\n+\t * exist, and call process(). We must make the agc.status metadata record correctly\n+\t * which channel this is.\n+\t */\n+\tif (channelData.statistics && channelData.deviceStatus) {\n+\t\tdeviceStatus = *channelData.deviceStatus;\n+\t\tstats = channelData.statistics;\n+\t} else\n+\t\t/* Can also happen when new channels start. */\n+\t\tLOG(RPiAgc, Debug) << \"process: channel \" << channelIndex << \" not seen yet\";\n+\n+\tchannelData.channel.process(stats, &deviceStatus, imageMetadata);\n+\tsetChannelIndex(imageMetadata, \"process: no AGC status found\", channelIndex);\n+\n+\t/* And onto the next channel for the next call. */\n+\tindex_ = (index_ + 1) % activeChannels_.size();\n }\n \n /* Register algorithm with the system. */\ndiff --git a/src/ipa/rpi/controller/rpi/agc.h b/src/ipa/rpi/controller/rpi/agc.h\nindex a9158910..2eed2bab 100644\n--- a/src/ipa/rpi/controller/rpi/agc.h\n+++ b/src/ipa/rpi/controller/rpi/agc.h\n@@ -53,6 +53,7 @@ private:\n \tint checkChannel(unsigned int channel) const;\n \tstd::vector<AgcChannelData> channelData_;\n \tstd::vector<unsigned int> activeChannels_;\n+\tunsigned int index_; /* index into the activeChannels_ */\n };\n \n } /* namespace RPiController */\ndiff --git a/src/ipa/rpi/controller/rpi/agc_channel.cpp b/src/ipa/rpi/controller/rpi/agc_channel.cpp\nindex d6e30ef2..ddec611f 100644\n--- a/src/ipa/rpi/controller/rpi/agc_channel.cpp\n+++ b/src/ipa/rpi/controller/rpi/agc_channel.cpp\n@@ -447,7 +447,7 @@ void AgcChannel::prepare(Metadata *imageMetadata)\n \t}\n }\n \n-void AgcChannel::process(StatisticsPtr &stats, Metadata *imageMetadata)\n+void AgcChannel::process(StatisticsPtr &stats, const DeviceStatus *deviceStatus, Metadata *imageMetadata)\n {\n \tframeCount_++;\n \t/*\n@@ -458,7 +458,7 @@ void AgcChannel::process(StatisticsPtr &stats, Metadata *imageMetadata)\n \t/* Fetch the AWB status immediately, so that we can assume it's there. */\n \tfetchAwbStatus(imageMetadata);\n \t/* Get the current exposure values for the frame that's just arrived. */\n-\tfetchCurrentExposure(imageMetadata);\n+\tfetchCurrentExposure(deviceStatus);\n \t/* Compute the total gain we require relative to the current exposure. */\n \tdouble gain, targetY;\n \tcomputeGain(stats, imageMetadata, gain, targetY);\n@@ -570,18 +570,13 @@ void AgcChannel::housekeepConfig()\n \t\t\t   << meteringModeName_;\n }\n \n-void AgcChannel::fetchCurrentExposure(Metadata *imageMetadata)\n+void AgcChannel::fetchCurrentExposure(const DeviceStatus *deviceStatus)\n {\n-\tstd::unique_lock<Metadata> lock(*imageMetadata);\n-\tDeviceStatus *deviceStatus =\n-\t\timageMetadata->getLocked<DeviceStatus>(\"device.status\");\n \tif (!deviceStatus)\n \t\tLOG(RPiAgc, Fatal) << \"No device metadata\";\n \tcurrent_.shutter = deviceStatus->shutterSpeed;\n \tcurrent_.analogueGain = deviceStatus->analogueGain;\n-\tAgcStatus *agcStatus =\n-\t\timageMetadata->getLocked<AgcStatus>(\"agc.status\");\n-\tcurrent_.totalExposure = agcStatus ? agcStatus->totalExposureValue : 0s;\n+\tcurrent_.totalExposure = 0s; /* this value is unused */\n \tcurrent_.totalExposureNoDG = current_.shutter * current_.analogueGain;\n }\n \ndiff --git a/src/ipa/rpi/controller/rpi/agc_channel.h b/src/ipa/rpi/controller/rpi/agc_channel.h\nindex dc4356f3..0e3d44b0 100644\n--- a/src/ipa/rpi/controller/rpi/agc_channel.h\n+++ b/src/ipa/rpi/controller/rpi/agc_channel.h\n@@ -83,13 +83,13 @@ public:\n \tvoid disableAuto();\n \tvoid switchMode(CameraMode const &cameraMode, Metadata *metadata);\n \tvoid prepare(Metadata *imageMetadata);\n-\tvoid process(StatisticsPtr &stats, Metadata *imageMetadata);\n+\tvoid process(StatisticsPtr &stats, const DeviceStatus *deviceStatus, Metadata *imageMetadata);\n \n private:\n \tbool updateLockStatus(DeviceStatus const &deviceStatus);\n \tAgcConfig config_;\n \tvoid housekeepConfig();\n-\tvoid fetchCurrentExposure(Metadata *imageMetadata);\n+\tvoid fetchCurrentExposure(const DeviceStatus *deviceStatus);\n \tvoid fetchAwbStatus(Metadata *imageMetadata);\n \tvoid computeGain(StatisticsPtr &statistics, Metadata *imageMetadata,\n \t\t\t double &gain, double &targetY);\n","prefixes":["libcamera-devel","3/5"]}