{"id":23664,"url":"https://patchwork.libcamera.org/api/patches/23664/?format=json","web_url":"https://patchwork.libcamera.org/patch/23664/","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":"<20250626095944.1746345-7-paul.elder@ideasonboard.com>","date":"2025-06-26T09:59:41","name":"[RFC,6/7] layer: Add layer to inject AeEnable control","commit_ref":null,"pull_url":null,"state":"superseded","archived":false,"hash":"5edb694387a20279891bc3054295ee41da29c5f0","submitter":{"id":17,"url":"https://patchwork.libcamera.org/api/people/17/?format=json","name":"Paul Elder","email":"paul.elder@ideasonboard.com"},"delegate":null,"mbox":"https://patchwork.libcamera.org/patch/23664/mbox/","series":[{"id":5251,"url":"https://patchwork.libcamera.org/api/series/5251/?format=json","web_url":"https://patchwork.libcamera.org/project/libcamera/list/?series=5251","date":"2025-06-26T09:59:35","name":"Add Layers support","version":1,"mbox":"https://patchwork.libcamera.org/series/5251/mbox/"}],"comments":"https://patchwork.libcamera.org/api/patches/23664/comments/","check":"pending","checks":"https://patchwork.libcamera.org/api/patches/23664/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 AD752BDCBF\n\tfor <parsemail@patchwork.libcamera.org>;\n\tThu, 26 Jun 2025 10:00:32 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 0F9BE68DFE;\n\tThu, 26 Jun 2025 12:00:32 +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 E4E1F68DFC\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu, 26 Jun 2025 12:00:24 +0200 (CEST)","from neptunite.hamster-moth.ts.net (unknown\n\t[IPv6:2404:7a81:160:2100:258b:9e43:6dff:c39d])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 1B1CA743;\n\tThu, 26 Jun 2025 12: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=\"roR3kXJi\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1750932006;\n\tbh=a0vtHLYTYKS0mx8qsHaCE3ZbBQ8aSnBmLOL29vEioM0=;\n\th=From:To:Cc:Subject:Date:In-Reply-To:References:From;\n\tb=roR3kXJir96qGSYESEdR41c+eQvFKFeh5YCLuaYtuGDBNNm6cQdIAM23qasAn/whf\n\tkNdWTxpd46ARks0ZwYzOoemrFAJMhw9m8BtNBqiKZ1ItmaE9NM2srTqwp0l+VDcHIe\n\t3l18b+WY/AxxDSJkGkZmoJq5yvYqcI95amFpCmuo=","From":"Paul Elder <paul.elder@ideasonboard.com>","To":"libcamera-devel@lists.libcamera.org","Cc":"Paul Elder <paul.elder@ideasonboard.com>, kieran.bingham@ideasonboard.com","Subject":"[RFC PATCH 6/7] layer: Add layer to inject AeEnable control","Date":"Thu, 26 Jun 2025 18:59:41 +0900","Message-ID":"<20250626095944.1746345-7-paul.elder@ideasonboard.com>","X-Mailer":"git-send-email 2.47.2","In-Reply-To":"<20250626095944.1746345-1-paul.elder@ideasonboard.com>","References":"<20250626095944.1746345-1-paul.elder@ideasonboard.com>","MIME-Version":"1.0","Content-Transfer-Encoding":"8bit","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":"Add a layer to implement the AeEnable control, so that we can remove it\nfrom all IPAs and the Camera class, so that it is transparent to all\nparties and it is available for applications to use.\n\nSigned-off-by: Paul Elder <paul.elder@ideasonboard.com>\n\n---\nFor layers like this that we forsee to be \"always built-in\" we might\nwant a way to build it into libcamera instead of into separate shared\nobject files. In any case, this also serves as a demo of how I envision\na layer implementation to work.\n---\n src/layer/inject_controls/inject_controls.cpp | 164 ++++++++++++++++++\n src/layer/inject_controls/inject_controls.h   |  29 ++++\n src/layer/inject_controls/meson.build         |  15 ++\n src/layer/meson.build                         |   2 +\n 4 files changed, 210 insertions(+)\n create mode 100644 src/layer/inject_controls/inject_controls.cpp\n create mode 100644 src/layer/inject_controls/inject_controls.h\n create mode 100644 src/layer/inject_controls/meson.build","diff":"diff --git a/src/layer/inject_controls/inject_controls.cpp b/src/layer/inject_controls/inject_controls.cpp\nnew file mode 100644\nindex 000000000000..133a8f51a1f6\n--- /dev/null\n+++ b/src/layer/inject_controls/inject_controls.cpp\n@@ -0,0 +1,164 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2025, Ideas On Board Oy\n+ *\n+ * Layer implementation for injecting controls\n+ */\n+\n+#include \"inject_controls.h\"\n+\n+#include <algorithm>\n+#include <set>\n+\n+#include <libcamera/layer.h>\n+\n+#include <libcamera/control_ids.h>\n+#include <libcamera/controls.h>\n+#include <libcamera/request.h>\n+\n+namespace layer {\n+\n+namespace inject_controls {\n+\n+libcamera::ControlInfoMap::Map controls(libcamera::ControlInfoMap &ctrls)\n+{\n+\tauto it = ctrls.find(&libcamera::controls::ExposureTimeMode);\n+\tif (it != ctrls.end()) {\n+\t\tfor (auto entry : it->second.values()) {\n+\t\t\tif (entry == libcamera::ControlValue(libcamera::controls::ExposureTimeModeAuto))\n+\t\t\t\taeAvailable_ = true;\n+\t\t\tif (entry == libcamera::ControlValue(libcamera::controls::ExposureTimeModeManual))\n+\t\t\t\tmeAvailable_ = true;\n+\t\t}\n+\t}\n+\n+\tit = ctrls.find(&libcamera::controls::AnalogueGainMode);\n+\tif (it != ctrls.end()) {\n+\t\tfor (auto entry : it->second.values()) {\n+\t\t\tif (entry == libcamera::ControlValue(libcamera::controls::AnalogueGainModeAuto))\n+\t\t\t\tagAvailable_ = true;\n+\t\t\tif (entry == libcamera::ControlValue(libcamera::controls::AnalogueGainModeManual))\n+\t\t\t\tmgAvailable_ = true;\n+\t\t}\n+\t}\n+\n+\tstd::set<bool> values;\n+\tif (aeAvailable_ || agAvailable_)\n+\t\tvalues.insert(true);\n+\tif (meAvailable_ || mgAvailable_)\n+\t\tvalues.insert(false);\n+\n+\tif (values.empty())\n+\t\treturn {};\n+\n+\tif (values.size() == 1) {\n+\t\tbool value = *values.begin();\n+\t\treturn { { &libcamera::controls::AeEnable,\n+\t\t\t   libcamera::ControlInfo(value, value, value) } };\n+\t}\n+\n+\treturn { { &libcamera::controls::AeEnable, libcamera::ControlInfo(false, true, true) } };\n+}\n+\n+void queueRequest(libcamera::Request *request)\n+{\n+\tlibcamera::ControlList &ctrls = request->controls();\n+\tauto aeEnable = ctrls.get<bool>(libcamera::controls::AeEnable);\n+\tif (!aeEnable)\n+\t\treturn;\n+\n+\tif (*aeEnable) {\n+\t\tif (aeAvailable_) {\n+\t\t\tctrls.set(libcamera::controls::ExposureTimeMode,\n+\t\t\t\t  libcamera::controls::ExposureTimeModeAuto);\n+\t\t}\n+\n+\t\tif (agAvailable_) {\n+\t\t\tctrls.set(libcamera::controls::AnalogueGainMode,\n+\t\t\t\t  libcamera::controls::AnalogueGainModeAuto);\n+\t\t}\n+\t} else {\n+\t\tif (meAvailable_) {\n+\t\t\tctrls.set(libcamera::controls::ExposureTimeMode,\n+\t\t\t\t  libcamera::controls::ExposureTimeModeManual);\n+\t\t}\n+\n+\t\tif (mgAvailable_) {\n+\t\t\tctrls.set(libcamera::controls::AnalogueGainMode,\n+\t\t\t\t  libcamera::controls::AnalogueGainModeManual);\n+\t\t}\n+\t}\n+}\n+\n+void requestCompleted(libcamera::Request *request)\n+{\n+\tlibcamera::ControlList &metadata = request->metadata();\n+\n+\tauto eMode = metadata.get<int>(libcamera::controls::ExposureTimeMode);\n+\tauto aMode = metadata.get<int>(libcamera::controls::AnalogueGainMode);\n+\n+\tif (!eMode && !aMode)\n+\t\treturn;\n+\n+\tbool ae = eMode && eMode == libcamera::controls::ExposureTimeModeAuto;\n+\tbool me = eMode && eMode == libcamera::controls::ExposureTimeModeManual;\n+\tbool ag = aMode && aMode == libcamera::controls::AnalogueGainModeAuto;\n+\tbool mg = aMode && aMode == libcamera::controls::AnalogueGainModeManual;\n+\n+\t/* Exposure time not reported at all; use gain only */\n+\tif (!ae && !me) {\n+\t\tmetadata.set(libcamera::controls::AeEnable, ag);\n+\t\treturn;\n+\t}\n+\n+\t/* Analogue gain not reported at all; use exposure time only */\n+\tif (!ag && !mg) {\n+\t\tmetadata.set(libcamera::controls::AeEnable, ae);\n+\t\treturn;\n+\t}\n+\n+\t/*\n+\t * Gain mode and exposure mode are not equal; therefore at least one is\n+\t * manual, so set AeEnable to false\n+\t */\n+\tif (ag != ae) {\n+\t\tmetadata.set(libcamera::controls::AeEnable, false);\n+\t\treturn;\n+\t}\n+\n+\t/* ag and ae are equal, so just choose one */\n+\tmetadata.set(libcamera::controls::AeEnable, ag);\n+\treturn;\n+}\n+\n+} /* namespace inject_controls */\n+\n+} /* namespace layer */\n+\n+namespace libcamera {\n+\n+extern \"C\" {\n+\n+struct Layer layerInfo {\n+\t.name = \"inject_controls\",\n+\t.layerAPIVersion = 1,\n+\t.init = nullptr,\n+\t.bufferCompleted = nullptr,\n+\t.requestCompleted = layer::inject_controls::requestCompleted,\n+\t.disconnected = nullptr,\n+\t.acquire = nullptr,\n+\t.release = nullptr,\n+\t.controls = layer::inject_controls::controls,\n+\t.properties = nullptr,\n+\t.streams = nullptr,\n+\t.generateConfiguration = nullptr,\n+\t.configure = nullptr,\n+\t.createRequest = nullptr,\n+\t.queueRequest = layer::inject_controls::queueRequest,\n+\t.start = nullptr,\n+\t.stop = nullptr,\n+};\n+\n+}\n+\n+} /* namespace libcamera */\ndiff --git a/src/layer/inject_controls/inject_controls.h b/src/layer/inject_controls/inject_controls.h\nnew file mode 100644\nindex 000000000000..a65cda897836\n--- /dev/null\n+++ b/src/layer/inject_controls/inject_controls.h\n@@ -0,0 +1,29 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2025, Ideas On Board Oy\n+ *\n+ * Layer implementation for injecting controls\n+ */\n+\n+#pragma once\n+\n+#include <libcamera/controls.h>\n+#include <libcamera/request.h>\n+\n+namespace layer {\n+\n+namespace inject_controls {\n+\n+libcamera::ControlInfoMap::Map controls(libcamera::ControlInfoMap &ctrls);\n+void queueRequest(libcamera::Request *);\n+void requestCompleted(libcamera::Request *);\n+\n+bool initialized_ = false;\n+bool aeAvailable_ = false;\n+bool meAvailable_ = false;\n+bool agAvailable_ = false;\n+bool mgAvailable_ = false;\n+\n+} /* namespace inject_controls */\n+\n+} /* namespace layer */\ndiff --git a/src/layer/inject_controls/meson.build b/src/layer/inject_controls/meson.build\nnew file mode 100644\nindex 000000000000..72f22e184923\n--- /dev/null\n+++ b/src/layer/inject_controls/meson.build\n@@ -0,0 +1,15 @@\n+# SPDX-License-Identifier: CC0-1.0\n+\n+layer_name = 'layer_inject_controls'\n+\n+layer_inject_sources = files([\n+    'inject_controls.h',\n+    'inject_controls.cpp',\n+])\n+\n+mod = shared_module(layer_name, layer_inject_sources,\n+                    name_prefix : '',\n+                    include_directories : [layer_includes],\n+                    dependencies : libcamera_public,\n+                    install : true,\n+                    install_dir : layer_install_dir)\ndiff --git a/src/layer/meson.build b/src/layer/meson.build\nindex dee5e5ac5804..d5793f8c4525 100644\n--- a/src/layer/meson.build\n+++ b/src/layer/meson.build\n@@ -8,3 +8,5 @@ layer_install_dir = libcamera_libdir / 'layers'\n \n config_h.set('LAYER_DIR',\n              '\"' + get_option('prefix') / layer_install_dir + '\"')\n+\n+subdir('inject_controls')\n","prefixes":["RFC","6/7"]}