From patchwork Thu Jan 29 08:28:13 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 26035 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 08506C3226 for ; Thu, 29 Jan 2026 08:28:42 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 91DB261FDE; Thu, 29 Jan 2026 09:28:41 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="cJlMnBq0"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 5DB7761FC6 for ; Thu, 29 Jan 2026 09:28:37 +0100 (CET) Received: from neptunite.flets-east.jp (unknown [IPv6:2404:7a81:160:2100:2eea:f891:1bd7:2691]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id DC973281A; Thu, 29 Jan 2026 09:27:58 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1769675279; bh=NHq9VgUc/aNX72WH4DVe26BA4/HGxms7PD1QKg6z9A0=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=cJlMnBq0MczpsHLQTjLWnvoeEqD2EhB3xgF0QInOBycJK5gdXPSTm05qjn41yDOTC rhdUXhfH5UDFh8XAjV2WfS2rxK/U2ZFwrzibTm7ObakH7bSvIUds3isFekibXfmbf6 WlwMcHCPgtxugLa9toAsTNgELiNo40CcYE5B/MG8= From: Paul Elder To: libcamera-devel@lists.libcamera.org Cc: Paul Elder Subject: [PATCH v6 7/8] layer: Add layer to inject AeEnable control Date: Thu, 29 Jan 2026 17:28:13 +0900 Message-ID: <20260129082814.1777779-8-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.47.2 In-Reply-To: <20260129082814.1777779-1-paul.elder@ideasonboard.com> References: <20260129082814.1777779-1-paul.elder@ideasonboard.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Add a layer to implement the AeEnable control, so that we can remove it from all IPAs and the Camera class, so that it is transparent to all parties and it is available for applications to use. Signed-off-by: Paul Elder --- For layers like this that we forsee to be "always built-in" we might want a way to build it into libcamera instead of into separate shared object files. In any case, this also serves as a demo of how I envision a layer implementation to work. Changes in v6: - as metadata can no longer be set directly in the Request, instead return it in requestCompleted No change in v5 No change in v4 No change in v3 Changes in v2: - add init() and terminate() - use closures - remove layer namespacing - add gnu_symbol_visibility --- src/layer/inject_controls/inject_controls.cpp | 177 ++++++++++++++++++ src/layer/inject_controls/inject_controls.h | 24 +++ src/layer/inject_controls/meson.build | 16 ++ src/layer/meson.build | 2 + 4 files changed, 219 insertions(+) create mode 100644 src/layer/inject_controls/inject_controls.cpp create mode 100644 src/layer/inject_controls/inject_controls.h create mode 100644 src/layer/inject_controls/meson.build diff --git a/src/layer/inject_controls/inject_controls.cpp b/src/layer/inject_controls/inject_controls.cpp new file mode 100644 index 000000000000..741180f35c60 --- /dev/null +++ b/src/layer/inject_controls/inject_controls.cpp @@ -0,0 +1,177 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2025, Ideas On Board Oy + * + * Layer implementation for injecting controls + */ + +#include "inject_controls.h" + +#include +#include + +#include +#include +#include +#include + +void *init([[maybe_unused]] const std::string &id) +{ + InjectControls *closure = new InjectControls; + *closure = {}; + return static_cast(closure); +} + +void terminate(void *closure) +{ + InjectControls *obj = static_cast(closure); + delete obj; +} + +libcamera::ControlInfoMap::Map updateControls(void *closure, libcamera::ControlInfoMap &ctrls) +{ + InjectControls *obj = static_cast(closure); + + auto it = ctrls.find(&libcamera::controls::ExposureTimeMode); + if (it != ctrls.end()) { + for (auto entry : it->second.values()) { + if (entry == libcamera::ControlValue(libcamera::controls::ExposureTimeModeAuto)) + obj->aeAvailable = true; + if (entry == libcamera::ControlValue(libcamera::controls::ExposureTimeModeManual)) + obj->meAvailable = true; + } + } + + it = ctrls.find(&libcamera::controls::AnalogueGainMode); + if (it != ctrls.end()) { + for (auto entry : it->second.values()) { + if (entry == libcamera::ControlValue(libcamera::controls::AnalogueGainModeAuto)) + obj->agAvailable = true; + if (entry == libcamera::ControlValue(libcamera::controls::AnalogueGainModeManual)) + obj->mgAvailable = true; + } + } + + std::set values; + if (obj->aeAvailable || obj->agAvailable) + values.insert(true); + if (obj->meAvailable || obj->mgAvailable) + values.insert(false); + + if (values.empty()) + return {}; + + if (values.size() == 1) { + bool value = *values.begin(); + return { { &libcamera::controls::AeEnable, + libcamera::ControlInfo(value, value, value) } }; + } + + return { { &libcamera::controls::AeEnable, libcamera::ControlInfo(false, true, true) } }; +} + +void queueRequest(void *closure, libcamera::Request *request) +{ + InjectControls *obj = static_cast(closure); + + libcamera::ControlList &ctrls = request->controls(); + auto aeEnable = ctrls.get(libcamera::controls::AeEnable); + if (!aeEnable) + return; + + if (*aeEnable) { + if (obj->aeAvailable) { + ctrls.set(libcamera::controls::ExposureTimeMode, + libcamera::controls::ExposureTimeModeAuto); + } + + if (obj->agAvailable) { + ctrls.set(libcamera::controls::AnalogueGainMode, + libcamera::controls::AnalogueGainModeAuto); + } + } else { + if (obj->meAvailable) { + ctrls.set(libcamera::controls::ExposureTimeMode, + libcamera::controls::ExposureTimeModeManual); + } + + if (obj->mgAvailable) { + ctrls.set(libcamera::controls::AnalogueGainMode, + libcamera::controls::AnalogueGainModeManual); + } + } +} + +libcamera::ControlList requestCompleted([[maybe_unused]] void *closure, + libcamera::Request *request) +{ + const libcamera::ControlList &metadata = request->metadata(); + libcamera::ControlList ret(libcamera::controls::controls); + + auto eMode = metadata.get(libcamera::controls::ExposureTimeMode); + auto aMode = metadata.get(libcamera::controls::AnalogueGainMode); + + if (!eMode && !aMode) + return ret; + + bool ae = eMode && eMode == libcamera::controls::ExposureTimeModeAuto; + bool me = eMode && eMode == libcamera::controls::ExposureTimeModeManual; + bool ag = aMode && aMode == libcamera::controls::AnalogueGainModeAuto; + bool mg = aMode && aMode == libcamera::controls::AnalogueGainModeManual; + + /* Exposure time not reported at all; use gain only */ + if (!ae && !me) { + ret.set(libcamera::controls::AeEnable, ag); + return ret; + } + + /* Analogue gain not reported at all; use exposure time only */ + if (!ag && !mg) { + ret.set(libcamera::controls::AeEnable, ae); + return ret; + } + + /* + * Gain mode and exposure mode are not equal; therefore at least one is + * manual, so set AeEnable to false + */ + if (ag != ae) { + ret.set(libcamera::controls::AeEnable, false); + return ret; + } + + /* ag and ae are equal, so just choose one */ + ret.set(libcamera::controls::AeEnable, ag); + return ret; +} + +namespace libcamera { + +extern "C" { + +[[gnu::visibility("default")]] +struct LayerInfo layerInfo{ + .name = "inject_controls", + .layerAPIVersion = 1, +}; + +[[gnu::visibility("default")]] +struct LayerInterface layerInterface{ + .init = init, + .terminate = terminate, + .bufferCompleted = nullptr, + .requestCompleted = requestCompleted, + .disconnected = nullptr, + .acquire = nullptr, + .release = nullptr, + .controls = updateControls, + .properties = nullptr, + .configure = nullptr, + .createRequest = nullptr, + .queueRequest = queueRequest, + .start = nullptr, + .stop = nullptr, +}; +} + +} /* namespace libcamera */ diff --git a/src/layer/inject_controls/inject_controls.h b/src/layer/inject_controls/inject_controls.h new file mode 100644 index 000000000000..042872d27630 --- /dev/null +++ b/src/layer/inject_controls/inject_controls.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2025, Ideas On Board Oy + * + * Layer implementation for injecting controls + */ + +#pragma once + +#include +#include + +struct InjectControls { + bool aeAvailable; + bool meAvailable; + bool agAvailable; + bool mgAvailable; +}; + +void *init(const std::string &id); +void terminate(void *closure); +libcamera::ControlInfoMap::Map updateControls(void *closure, libcamera::ControlInfoMap &ctrls); +void queueRequest(void *closure, libcamera::Request *request); +libcamera::ControlList requestCompleted([[maybe_unused]] void *closure, libcamera::Request *request); diff --git a/src/layer/inject_controls/meson.build b/src/layer/inject_controls/meson.build new file mode 100644 index 000000000000..d1e402746990 --- /dev/null +++ b/src/layer/inject_controls/meson.build @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: CC0-1.0 + +layer_name = 'layer_inject_controls' + +layer_inject_sources = files([ + 'inject_controls.h', + 'inject_controls.cpp', +]) + +mod = shared_module(layer_name, layer_inject_sources, + name_prefix : '', + include_directories : [layer_includes], + dependencies : libcamera_public, + gnu_symbol_visibility: 'hidden', + install : true, + install_dir : layer_install_dir) diff --git a/src/layer/meson.build b/src/layer/meson.build index 45dded512a13..3d8b70ad2cd2 100644 --- a/src/layer/meson.build +++ b/src/layer/meson.build @@ -12,3 +12,5 @@ config_h.set('LAYER_DIR', layers_env = environment() layers_env.set('LIBCAMERA_LAYER_PATH', meson.current_build_dir()) meson.add_devenv(layers_env) + +subdir('inject_controls')