[v5,7/8] layer: Add layer to inject AeEnable control
diff mbox series

Message ID 20260128074956.760538-8-paul.elder@ideasonboard.com
State New
Headers show
Series
  • Add Layers support
Related show

Commit Message

Paul Elder Jan. 28, 2026, 7:49 a.m. UTC
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 <paul.elder@ideasonboard.com>

---
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.

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 | 176 ++++++++++++++++++
 src/layer/inject_controls/inject_controls.h   |  24 +++
 src/layer/inject_controls/meson.build         |  16 ++
 src/layer/meson.build                         |   2 +
 4 files changed, 218 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

Comments

Paul Elder Jan. 28, 2026, 8:26 a.m. UTC | #1
Hi me,

Quoting Paul Elder (2026-01-28 16:49:55)
> 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 <paul.elder@ideasonboard.com>
> 
> ---
> 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.
> 
> 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 | 176 ++++++++++++++++++
>  src/layer/inject_controls/inject_controls.h   |  24 +++
>  src/layer/inject_controls/meson.build         |  16 ++
>  src/layer/meson.build                         |   2 +
>  4 files changed, 218 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..f12faff8318c
> --- /dev/null
> +++ b/src/layer/inject_controls/inject_controls.cpp
> @@ -0,0 +1,176 @@
> +/* 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 <algorithm>
> +#include <set>
> +
> +#include <libcamera/control_ids.h>
> +#include <libcamera/controls.h>
> +#include <libcamera/layer.h>
> +#include <libcamera/request.h>
> +
> +void *init([[maybe_unused]] const std::string &id)
> +{
> +       InjectControls *closure = new InjectControls;
> +       *closure = {};
> +       return static_cast<void *>(closure);
> +}
> +
> +void terminate(void *closure)
> +{
> +       InjectControls *obj = static_cast<InjectControls *>(closure);
> +       delete obj;
> +}
> +
> +libcamera::ControlInfoMap::Map updateControls(void *closure, libcamera::ControlInfoMap &ctrls)
> +{
> +       InjectControls *obj = static_cast<InjectControls *>(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<bool> 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<InjectControls *>(closure);
> +
> +       libcamera::ControlList &ctrls = request->controls();
> +       auto aeEnable = ctrls.get<bool>(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);
> +               }
> +       }
> +}
> +
> +void requestCompleted([[maybe_unused]] void *closure, libcamera::Request *request)
> +{
> +       libcamera::ControlList &metadata = request->metadata();

Metadata can no longer be written externally so we'll have to redesign this.

*sigh*


Paul

> +
> +       auto eMode = metadata.get<int>(libcamera::controls::ExposureTimeMode);
> +       auto aMode = metadata.get<int>(libcamera::controls::AnalogueGainMode);
> +
> +       if (!eMode && !aMode)
> +               return;
> +
> +       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) {
> +               metadata.set(libcamera::controls::AeEnable, ag);
> +               return;
> +       }
> +
> +       /* Analogue gain not reported at all; use exposure time only */
> +       if (!ag && !mg) {
> +               metadata.set(libcamera::controls::AeEnable, ae);
> +               return;
> +       }
> +
> +       /*
> +        * Gain mode and exposure mode are not equal; therefore at least one is
> +        * manual, so set AeEnable to false
> +        */
> +       if (ag != ae) {
> +               metadata.set(libcamera::controls::AeEnable, false);
> +               return;
> +       }
> +
> +       /* ag and ae are equal, so just choose one */
> +       metadata.set(libcamera::controls::AeEnable, ag);
> +       return;
> +}
> +
> +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..42c094d7f76a
> --- /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 <libcamera/controls.h>
> +#include <libcamera/request.h>
> +
> +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);
> +void 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')
> -- 
> 2.47.2
>

Patch
diff mbox series

diff --git a/src/layer/inject_controls/inject_controls.cpp b/src/layer/inject_controls/inject_controls.cpp
new file mode 100644
index 000000000000..f12faff8318c
--- /dev/null
+++ b/src/layer/inject_controls/inject_controls.cpp
@@ -0,0 +1,176 @@ 
+/* 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 <algorithm>
+#include <set>
+
+#include <libcamera/control_ids.h>
+#include <libcamera/controls.h>
+#include <libcamera/layer.h>
+#include <libcamera/request.h>
+
+void *init([[maybe_unused]] const std::string &id)
+{
+	InjectControls *closure = new InjectControls;
+	*closure = {};
+	return static_cast<void *>(closure);
+}
+
+void terminate(void *closure)
+{
+	InjectControls *obj = static_cast<InjectControls *>(closure);
+	delete obj;
+}
+
+libcamera::ControlInfoMap::Map updateControls(void *closure, libcamera::ControlInfoMap &ctrls)
+{
+	InjectControls *obj = static_cast<InjectControls *>(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<bool> 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<InjectControls *>(closure);
+
+	libcamera::ControlList &ctrls = request->controls();
+	auto aeEnable = ctrls.get<bool>(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);
+		}
+	}
+}
+
+void requestCompleted([[maybe_unused]] void *closure, libcamera::Request *request)
+{
+	libcamera::ControlList &metadata = request->metadata();
+
+	auto eMode = metadata.get<int>(libcamera::controls::ExposureTimeMode);
+	auto aMode = metadata.get<int>(libcamera::controls::AnalogueGainMode);
+
+	if (!eMode && !aMode)
+		return;
+
+	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) {
+		metadata.set(libcamera::controls::AeEnable, ag);
+		return;
+	}
+
+	/* Analogue gain not reported at all; use exposure time only */
+	if (!ag && !mg) {
+		metadata.set(libcamera::controls::AeEnable, ae);
+		return;
+	}
+
+	/*
+	 * Gain mode and exposure mode are not equal; therefore at least one is
+	 * manual, so set AeEnable to false
+	 */
+	if (ag != ae) {
+		metadata.set(libcamera::controls::AeEnable, false);
+		return;
+	}
+
+	/* ag and ae are equal, so just choose one */
+	metadata.set(libcamera::controls::AeEnable, ag);
+	return;
+}
+
+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..42c094d7f76a
--- /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 <libcamera/controls.h>
+#include <libcamera/request.h>
+
+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);
+void 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')