[{"id":37983,"web_url":"https://patchwork.libcamera.org/comment/37983/","msgid":"<176958876871.744885.7065770197755269300@neptunite.rasen.tech>","date":"2026-01-28T08:26:08","subject":"Re: [PATCH v5 7/8] layer: Add layer to inject AeEnable control","submitter":{"id":17,"url":"https://patchwork.libcamera.org/api/people/17/","name":"Paul Elder","email":"paul.elder@ideasonboard.com"},"content":"Hi me,\n\nQuoting Paul Elder (2026-01-28 16:49:55)\n> Add a layer to implement the AeEnable control, so that we can remove it\n> from all IPAs and the Camera class, so that it is transparent to all\n> parties and it is available for applications to use.\n> \n> Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>\n> \n> ---\n> For layers like this that we forsee to be \"always built-in\" we might\n> want a way to build it into libcamera instead of into separate shared\n> object files. In any case, this also serves as a demo of how I envision\n> a layer implementation to work.\n> \n> No change in v4\n> \n> No change in v3\n> \n> Changes in v2:\n> - add init() and terminate()\n> - use closures\n> - remove layer namespacing\n> - add gnu_symbol_visibility\n> ---\n>  src/layer/inject_controls/inject_controls.cpp | 176 ++++++++++++++++++\n>  src/layer/inject_controls/inject_controls.h   |  24 +++\n>  src/layer/inject_controls/meson.build         |  16 ++\n>  src/layer/meson.build                         |   2 +\n>  4 files changed, 218 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\n> \n> diff --git a/src/layer/inject_controls/inject_controls.cpp b/src/layer/inject_controls/inject_controls.cpp\n> new file mode 100644\n> index 000000000000..f12faff8318c\n> --- /dev/null\n> +++ b/src/layer/inject_controls/inject_controls.cpp\n> @@ -0,0 +1,176 @@\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/control_ids.h>\n> +#include <libcamera/controls.h>\n> +#include <libcamera/layer.h>\n> +#include <libcamera/request.h>\n> +\n> +void *init([[maybe_unused]] const std::string &id)\n> +{\n> +       InjectControls *closure = new InjectControls;\n> +       *closure = {};\n> +       return static_cast<void *>(closure);\n> +}\n> +\n> +void terminate(void *closure)\n> +{\n> +       InjectControls *obj = static_cast<InjectControls *>(closure);\n> +       delete obj;\n> +}\n> +\n> +libcamera::ControlInfoMap::Map updateControls(void *closure, libcamera::ControlInfoMap &ctrls)\n> +{\n> +       InjectControls *obj = static_cast<InjectControls *>(closure);\n> +\n> +       auto it = ctrls.find(&libcamera::controls::ExposureTimeMode);\n> +       if (it != ctrls.end()) {\n> +               for (auto entry : it->second.values()) {\n> +                       if (entry == libcamera::ControlValue(libcamera::controls::ExposureTimeModeAuto))\n> +                               obj->aeAvailable = true;\n> +                       if (entry == libcamera::ControlValue(libcamera::controls::ExposureTimeModeManual))\n> +                               obj->meAvailable = true;\n> +               }\n> +       }\n> +\n> +       it = ctrls.find(&libcamera::controls::AnalogueGainMode);\n> +       if (it != ctrls.end()) {\n> +               for (auto entry : it->second.values()) {\n> +                       if (entry == libcamera::ControlValue(libcamera::controls::AnalogueGainModeAuto))\n> +                               obj->agAvailable = true;\n> +                       if (entry == libcamera::ControlValue(libcamera::controls::AnalogueGainModeManual))\n> +                               obj->mgAvailable = true;\n> +               }\n> +       }\n> +\n> +       std::set<bool> values;\n> +       if (obj->aeAvailable || obj->agAvailable)\n> +               values.insert(true);\n> +       if (obj->meAvailable || obj->mgAvailable)\n> +               values.insert(false);\n> +\n> +       if (values.empty())\n> +               return {};\n> +\n> +       if (values.size() == 1) {\n> +               bool value = *values.begin();\n> +               return { { &libcamera::controls::AeEnable,\n> +                          libcamera::ControlInfo(value, value, value) } };\n> +       }\n> +\n> +       return { { &libcamera::controls::AeEnable, libcamera::ControlInfo(false, true, true) } };\n> +}\n> +\n> +void queueRequest(void *closure, libcamera::Request *request)\n> +{\n> +       InjectControls *obj = static_cast<InjectControls *>(closure);\n> +\n> +       libcamera::ControlList &ctrls = request->controls();\n> +       auto aeEnable = ctrls.get<bool>(libcamera::controls::AeEnable);\n> +       if (!aeEnable)\n> +               return;\n> +\n> +       if (*aeEnable) {\n> +               if (obj->aeAvailable) {\n> +                       ctrls.set(libcamera::controls::ExposureTimeMode,\n> +                                 libcamera::controls::ExposureTimeModeAuto);\n> +               }\n> +\n> +               if (obj->agAvailable) {\n> +                       ctrls.set(libcamera::controls::AnalogueGainMode,\n> +                                 libcamera::controls::AnalogueGainModeAuto);\n> +               }\n> +       } else {\n> +               if (obj->meAvailable) {\n> +                       ctrls.set(libcamera::controls::ExposureTimeMode,\n> +                                 libcamera::controls::ExposureTimeModeManual);\n> +               }\n> +\n> +               if (obj->mgAvailable) {\n> +                       ctrls.set(libcamera::controls::AnalogueGainMode,\n> +                                 libcamera::controls::AnalogueGainModeManual);\n> +               }\n> +       }\n> +}\n> +\n> +void requestCompleted([[maybe_unused]] void *closure, libcamera::Request *request)\n> +{\n> +       libcamera::ControlList &metadata = request->metadata();\n\nMetadata can no longer be written externally so we'll have to redesign this.\n\n*sigh*\n\n\nPaul\n\n> +\n> +       auto eMode = metadata.get<int>(libcamera::controls::ExposureTimeMode);\n> +       auto aMode = metadata.get<int>(libcamera::controls::AnalogueGainMode);\n> +\n> +       if (!eMode && !aMode)\n> +               return;\n> +\n> +       bool ae = eMode && eMode == libcamera::controls::ExposureTimeModeAuto;\n> +       bool me = eMode && eMode == libcamera::controls::ExposureTimeModeManual;\n> +       bool ag = aMode && aMode == libcamera::controls::AnalogueGainModeAuto;\n> +       bool mg = aMode && aMode == libcamera::controls::AnalogueGainModeManual;\n> +\n> +       /* Exposure time not reported at all; use gain only */\n> +       if (!ae && !me) {\n> +               metadata.set(libcamera::controls::AeEnable, ag);\n> +               return;\n> +       }\n> +\n> +       /* Analogue gain not reported at all; use exposure time only */\n> +       if (!ag && !mg) {\n> +               metadata.set(libcamera::controls::AeEnable, ae);\n> +               return;\n> +       }\n> +\n> +       /*\n> +        * Gain mode and exposure mode are not equal; therefore at least one is\n> +        * manual, so set AeEnable to false\n> +        */\n> +       if (ag != ae) {\n> +               metadata.set(libcamera::controls::AeEnable, false);\n> +               return;\n> +       }\n> +\n> +       /* ag and ae are equal, so just choose one */\n> +       metadata.set(libcamera::controls::AeEnable, ag);\n> +       return;\n> +}\n> +\n> +namespace libcamera {\n> +\n> +extern \"C\" {\n> +\n> +[[gnu::visibility(\"default\")]]\n> +struct LayerInfo layerInfo{\n> +       .name = \"inject_controls\",\n> +       .layerAPIVersion = 1,\n> +};\n> +\n> +[[gnu::visibility(\"default\")]]\n> +struct LayerInterface layerInterface{\n> +       .init = init,\n> +       .terminate = terminate,\n> +       .bufferCompleted = nullptr,\n> +       .requestCompleted = requestCompleted,\n> +       .disconnected = nullptr,\n> +       .acquire = nullptr,\n> +       .release = nullptr,\n> +       .controls = updateControls,\n> +       .properties = nullptr,\n> +       .configure = nullptr,\n> +       .createRequest = nullptr,\n> +       .queueRequest = queueRequest,\n> +       .start = nullptr,\n> +       .stop = nullptr,\n> +};\n> +\n> +}\n> +\n> +} /* namespace libcamera */\n> diff --git a/src/layer/inject_controls/inject_controls.h b/src/layer/inject_controls/inject_controls.h\n> new file mode 100644\n> index 000000000000..42c094d7f76a\n> --- /dev/null\n> +++ b/src/layer/inject_controls/inject_controls.h\n> @@ -0,0 +1,24 @@\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> +struct InjectControls {\n> +       bool aeAvailable;\n> +       bool meAvailable;\n> +       bool agAvailable;\n> +       bool mgAvailable;\n> +};\n> +\n> +void *init(const std::string &id);\n> +void terminate(void *closure);\n> +libcamera::ControlInfoMap::Map updateControls(void *closure, libcamera::ControlInfoMap &ctrls);\n> +void queueRequest(void *closure, libcamera::Request *request);\n> +void requestCompleted([[maybe_unused]] void *closure, libcamera::Request *request);\n> diff --git a/src/layer/inject_controls/meson.build b/src/layer/inject_controls/meson.build\n> new file mode 100644\n> index 000000000000..d1e402746990\n> --- /dev/null\n> +++ b/src/layer/inject_controls/meson.build\n> @@ -0,0 +1,16 @@\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> +                    gnu_symbol_visibility: 'hidden',\n> +                    install : true,\n> +                    install_dir : layer_install_dir)\n> diff --git a/src/layer/meson.build b/src/layer/meson.build\n> index 45dded512a13..3d8b70ad2cd2 100644\n> --- a/src/layer/meson.build\n> +++ b/src/layer/meson.build\n> @@ -12,3 +12,5 @@ config_h.set('LAYER_DIR',\n>  layers_env = environment()\n>  layers_env.set('LIBCAMERA_LAYER_PATH', meson.current_build_dir())\n>  meson.add_devenv(layers_env)\n> +\n> +subdir('inject_controls')\n> -- \n> 2.47.2\n>","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 4F716C3200\n\tfor <parsemail@patchwork.libcamera.org>;\n\tWed, 28 Jan 2026 08:26:17 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 979F761FCF;\n\tWed, 28 Jan 2026 09:26:16 +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 9961361F84\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 28 Jan 2026 09:26:15 +0100 (CET)","from neptunite.rasen.tech (unknown\n\t[IPv6:2404:7a81:160:2100:508d:7983:72a6:2eeb])\n\tby perceval.ideasonboard.com (Postfix) with UTF8SMTPSA id E12F5581\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 28 Jan 2026 09:25:37 +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=\"PWFLp3FR\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1769588738;\n\tbh=l2SBeJHuMYN1DzME9fPG2VrEHPN5jmPo5+yMvfAOJBA=;\n\th=In-Reply-To:References:Subject:From:Cc:To:Date:From;\n\tb=PWFLp3FRZ6tHGzx9tkwjUuUtzMy7tnJZt4DHLeyZtMV2Sfxr4IcBz7vCkHgeRijBF\n\tZed+y67h1ValDGaeLnDEAjos26QG7Hm8fb8U5WkT/RLR0djQ6jIwmfMNSGxsk56OC7\n\t9na34a5Nv/V7L0rlmk8OlX2WyusgoTJ29wwVJ2QY=","Content-Type":"text/plain; charset=\"utf-8\"","MIME-Version":"1.0","Content-Transfer-Encoding":"quoted-printable","In-Reply-To":"<20260128074956.760538-8-paul.elder@ideasonboard.com>","References":"<20260128074956.760538-1-paul.elder@ideasonboard.com>\n\t<20260128074956.760538-8-paul.elder@ideasonboard.com>","Subject":"Re: [PATCH v5 7/8] layer: Add layer to inject AeEnable control","From":"Paul Elder <paul.elder@ideasonboard.com>","Cc":"","To":"libcamera-devel@lists.libcamera.org","Date":"Wed, 28 Jan 2026 17:26:08 +0900","Message-ID":"<176958876871.744885.7065770197755269300@neptunite.rasen.tech>","User-Agent":"alot/0.0.0","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>"}}]