libcamera: backport contrast autofocus for the main camera (r4)
Some checks failed
image / image (push) Has been cancelled

The DW9784 VCM works and libcamera discovers it through the sensor's
ancillary link, but nothing in the software ISP ever moves it. Backport
the out-of-tree autofocus work onto v0.7.2:

- 0007-0009 are Vasiliy Doylov's focus control, contrast autofocus and
  focus-loss detection from the softisp-playground branch;
- 0010 is Pavel Machek's Librem5-tested robustness work (centre-window,
  brightness-normalised sharpness, two-phase sweep, settle skip),
  squashed and adapted;
- 0011-0013 are ours: the lens write no longer sits inside the
  no-frame-start-emitter branch, the lensless-camera paths are guarded,
  and AfMode is advertised with a continuous default so that stock
  applications get autofocus without sending AfTrigger.

Provenance and the adaptations made to each patch are recorded in the
patch commit messages.
This commit is contained in:
Jorijn van der Graaf 2026-08-17 02:02:45 +02:00
commit 40ccdfeed6
8 changed files with 1605 additions and 1 deletions

View file

@ -0,0 +1,379 @@
From d3ee3b91af950deae8edd4ce4148ac11664c48e9 Mon Sep 17 00:00:00 2001
From: Vasiliy Doylov <nekocwd@mainlining.org>
Date: Mon, 17 Mar 2025 04:24:56 +0300
Subject: [PATCH] libcamera: software_isp: Add focus control
Signed-off-by: Vasiliy Doylov <nekocwd@mainlining.org>
---
.../internal/software_isp/software_isp.h | 4 +-
include/libcamera/ipa/soft.mojom | 3 +-
src/ipa/simple/algorithms/af.cpp | 71 +++++++++++++++++++
src/ipa/simple/algorithms/af.h | 40 +++++++++++
src/ipa/simple/algorithms/meson.build | 1 +
src/ipa/simple/data/uncalibrated.yaml | 1 +
src/ipa/simple/ipa_context.h | 9 +++
src/ipa/simple/soft_simple.cpp | 18 ++++-
src/libcamera/pipeline/simple/simple.cpp | 30 ++++++--
src/libcamera/software_isp/software_isp.cpp | 4 +-
10 files changed, 169 insertions(+), 12 deletions(-)
create mode 100644 src/ipa/simple/algorithms/af.cpp
create mode 100644 src/ipa/simple/algorithms/af.h
diff --git a/include/libcamera/internal/software_isp/software_isp.h b/include/libcamera/internal/software_isp/software_isp.h
index 4f72dce9b..bf2cfea43 100644
--- a/include/libcamera/internal/software_isp/software_isp.h
+++ b/include/libcamera/internal/software_isp/software_isp.h
@@ -86,11 +86,11 @@ public:
Signal<FrameBuffer *> outputBufferReady;
Signal<uint32_t, uint32_t> ispStatsReady;
Signal<uint32_t, const ControlList &> metadataReady;
- Signal<const ControlList &> setSensorControls;
+ Signal<const ControlList &, const ControlList &> setSensorControls;
private:
void saveIspParams();
- void setSensorCtrls(const ControlList &sensorControls);
+ void setSensorCtrls(const ControlList &sensorControls, const ControlList &lensControls);
void statsReady(uint32_t frame, uint32_t bufferId);
void inputReady(FrameBuffer *input);
void outputReady(FrameBuffer *output);
diff --git a/include/libcamera/ipa/soft.mojom b/include/libcamera/ipa/soft.mojom
index 77328c5fd..e5767532c 100644
--- a/include/libcamera/ipa/soft.mojom
+++ b/include/libcamera/ipa/soft.mojom
@@ -10,6 +10,7 @@ import "include/libcamera/ipa/core.mojom";
struct IPAConfigInfo {
libcamera.ControlInfoMap sensorControls;
+ libcamera.ControlInfoMap lensControls;
};
interface IPASoftInterface {
@@ -32,7 +33,7 @@ interface IPASoftInterface {
};
interface IPASoftEventInterface {
- setSensorControls(libcamera.ControlList sensorControls);
+ setSensorControls(libcamera.ControlList sensorControls, libcamera.ControlList lensControls);
setIspParams();
metadataReady(uint32 frame, libcamera.ControlList metadata);
};
diff --git a/src/ipa/simple/algorithms/af.cpp b/src/ipa/simple/algorithms/af.cpp
new file mode 100644
index 000000000..6197f3271
--- /dev/null
+++ b/src/ipa/simple/algorithms/af.cpp
@@ -0,0 +1,71 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2025 Vasiliy Doylov <nekodevelopper@gmail.com>
+ *
+ * Auto focus
+ */
+
+#include "af.h"
+
+#include <stdint.h>
+
+#include <libcamera/base/log.h>
+
+#include "control_ids.h"
+
+namespace libcamera {
+
+LOG_DEFINE_CATEGORY(IPASoftAutoFocus)
+
+namespace ipa::soft::algorithms {
+
+Af::Af()
+{
+}
+
+int Af::init(IPAContext &context,
+ [[maybe_unused]] const ValueNode &tuningData)
+{
+ context.ctrlMap[&controls::LensPosition] = ControlInfo(0.0f, 100.0f, 50.0f);
+ return 0;
+}
+
+int Af::configure(IPAContext &context,
+ [[maybe_unused]] const IPAConfigInfo &configInfo)
+{
+ context.activeState.knobs.focus_pos = std::optional<double>();
+
+ return 0;
+}
+
+void Af::queueRequest([[maybe_unused]] typename Module::Context &context,
+ [[maybe_unused]] const uint32_t frame,
+ [[maybe_unused]] typename Module::FrameContext &frameContext,
+ const ControlList &controls)
+{
+ const auto &focus_pos = controls.get(controls::LensPosition);
+ if (focus_pos.has_value()) {
+ context.activeState.knobs.focus_pos = focus_pos;
+ LOG(IPASoftAutoFocus, Debug) << "Setting focus position to " << focus_pos.value();
+ }
+}
+
+void Af::updateFocus([[maybe_unused]] IPAContext &context, [[maybe_unused]] IPAFrameContext &frameContext, [[maybe_unused]] double exposureMSV)
+{
+ frameContext.lens.focus_pos = context.activeState.knobs.focus_pos.value_or(50.0) / 100.0 * (context.configuration.focus.focus_max - context.configuration.focus.focus_min);
+}
+
+void Af::process([[maybe_unused]] IPAContext &context,
+ [[maybe_unused]] const uint32_t frame,
+ [[maybe_unused]] IPAFrameContext &frameContext,
+ [[maybe_unused]] const SwIspStats *stats,
+ [[maybe_unused]] ControlList &metadata)
+{
+ updateFocus(context, frameContext, 0);
+}
+
+REGISTER_IPA_ALGORITHM(Af, "Af")
+
+} /* namespace ipa::soft::algorithms */
+
+} /* namespace libcamera */
diff --git a/src/ipa/simple/algorithms/af.h b/src/ipa/simple/algorithms/af.h
new file mode 100644
index 000000000..b138b4d63
--- /dev/null
+++ b/src/ipa/simple/algorithms/af.h
@@ -0,0 +1,40 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2025 Vasiliy Doylov <nekodevelopper@gmail.com>
+ *
+ * Auto focus
+ */
+
+#pragma once
+
+#include "algorithm.h"
+
+namespace libcamera {
+
+namespace ipa::soft::algorithms {
+
+class Af : public Algorithm
+{
+public:
+ Af();
+ ~Af() = default;
+
+ int init(IPAContext &context, const ValueNode &tuningData) override;
+ int configure(IPAContext &context, const IPAConfigInfo &configInfo) override;
+ void queueRequest(typename Module::Context &context,
+ const uint32_t frame,
+ typename Module::FrameContext &frameContext,
+ const ControlList &controls)
+ override;
+ void process(IPAContext &context, const uint32_t frame,
+ IPAFrameContext &frameContext,
+ const SwIspStats *stats,
+ ControlList &metadata) override;
+
+private:
+ void updateFocus(IPAContext &context, IPAFrameContext &frameContext, double focus);
+};
+
+} /* namespace ipa::soft::algorithms */
+
+} /* namespace libcamera */
diff --git a/src/ipa/simple/algorithms/meson.build b/src/ipa/simple/algorithms/meson.build
index 73c637220..8950b008f 100644
--- a/src/ipa/simple/algorithms/meson.build
+++ b/src/ipa/simple/algorithms/meson.build
@@ -6,4 +6,5 @@ soft_simple_ipa_algorithms = files([
'agc.cpp',
'blc.cpp',
'ccm.cpp',
+ 'af.cpp',
])
diff --git a/src/ipa/simple/data/uncalibrated.yaml b/src/ipa/simple/data/uncalibrated.yaml
index fc90ca526..ede277d1d 100644
--- a/src/ipa/simple/data/uncalibrated.yaml
+++ b/src/ipa/simple/data/uncalibrated.yaml
@@ -16,4 +16,5 @@ algorithms:
# 0, 0, 1]
- Adjust:
- Agc:
+ - Af:
...
diff --git a/src/ipa/simple/ipa_context.h b/src/ipa/simple/ipa_context.h
index 8ccfacb46..2b6bd4b3a 100644
--- a/src/ipa/simple/ipa_context.h
+++ b/src/ipa/simple/ipa_context.h
@@ -33,6 +33,9 @@ struct IPASessionConfiguration {
struct {
std::optional<uint8_t> level;
} black;
+ struct {
+ int32_t focus_min, focus_max;
+ } focus;
};
struct IPAActiveState {
@@ -60,6 +63,8 @@ struct IPAActiveState {
/* 0..2 range, 1.0 = normal */
std::optional<float> contrast;
std::optional<float> saturation;
+ /* 0..100 range, 50.0 = normal */
+ std::optional<double> focus_pos;
} knobs;
};
@@ -76,6 +81,10 @@ struct IPAFrameContext : public FrameContext {
float gamma;
std::optional<float> contrast;
std::optional<float> saturation;
+
+ struct {
+ int32_t focus_pos;
+ } lens;
};
struct IPAContext {
diff --git a/src/ipa/simple/soft_simple.cpp b/src/ipa/simple/soft_simple.cpp
index 629e1a32d..dcf746cb2 100644
--- a/src/ipa/simple/soft_simple.cpp
+++ b/src/ipa/simple/soft_simple.cpp
@@ -78,6 +78,7 @@ private:
SwIspStats *stats_;
std::unique_ptr<CameraSensorHelper> camHelper_;
ControlInfoMap sensorInfoMap_;
+ ControlInfoMap lensInfoMap_;
/* Local parameter storage */
struct IPAContext context_;
@@ -202,6 +203,7 @@ int IPASoftSimple::init(const IPASettings &settings,
int IPASoftSimple::configure(const IPAConfigInfo &configInfo)
{
sensorInfoMap_ = configInfo.sensorControls;
+ lensInfoMap_ = configInfo.lensControls;
const ControlInfo &exposureInfo = sensorInfoMap_.find(V4L2_CID_EXPOSURE)->second;
const ControlInfo &gainInfo = sensorInfoMap_.find(V4L2_CID_ANALOGUE_GAIN)->second;
@@ -211,6 +213,17 @@ int IPASoftSimple::configure(const IPAConfigInfo &configInfo)
context_.activeState = {};
context_.frameContexts.clear();
+ if (lensInfoMap_.empty()) {
+ LOG(IPASoft, Warning) << "No camera leans found! Focus control disabled.";
+ context_.configuration.focus.focus_min = 0;
+ context_.configuration.focus.focus_max = 0;
+ } else {
+ const ControlInfo &lensInfo = lensInfoMap_.find(V4L2_CID_FOCUS_ABSOLUTE)->second;
+ context_.configuration.focus.focus_min = lensInfo.min().get<int32_t>();
+ context_.configuration.focus.focus_max = lensInfo.max().get<int32_t>();
+ LOG(IPASoft, Warning) << "Camera leans found! Focus: " << context_.configuration.focus.focus_min << "-" << context_.configuration.focus.focus_max;
+ }
+
context_.configuration.agc.lineDuration =
context_.sensorInfo.minLineLength * 1.0s / context_.sensorInfo.pixelRate;
context_.configuration.agc.exposureMin = exposureInfo.min().get<int32_t>();
@@ -325,7 +338,10 @@ void IPASoftSimple::processStats(const uint32_t frame,
ctrls.set(V4L2_CID_ANALOGUE_GAIN,
static_cast<int32_t>(camHelper_ ? camHelper_->gainCode(againNew) : againNew));
- setSensorControls.emit(ctrls);
+ ControlList lens_ctrls(lensInfoMap_);
+ lens_ctrls.set(V4L2_CID_FOCUS_ABSOLUTE, frameContext.lens.focus_pos);
+
+ setSensorControls.emit(ctrls, lens_ctrls);
}
std::string IPASoftSimple::logPrefix() const
diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp
index b96b8b529..047800b59 100644
--- a/src/libcamera/pipeline/simple/simple.cpp
+++ b/src/libcamera/pipeline/simple/simple.cpp
@@ -33,6 +33,7 @@
#include <libcamera/stream.h>
#include "libcamera/internal/camera.h"
+#include "libcamera/internal/camera_lens.h"
#include "libcamera/internal/camera_manager.h"
#include "libcamera/internal/camera_sensor.h"
#include "libcamera/internal/camera_sensor_properties.h"
@@ -48,6 +49,8 @@
#include "libcamera/internal/v4l2_subdevice.h"
#include "libcamera/internal/v4l2_videodevice.h"
+#include "libcamera/controls.h"
+
namespace libcamera {
LOG_DEFINE_CATEGORY(SimplePipeline)
@@ -371,7 +374,7 @@ private:
void ispStatsReady(uint32_t frame, uint32_t bufferId);
void metadataReady(uint32_t frame, const ControlList &metadata);
- void setSensorControls(const ControlList &sensorControls);
+ void setSensorControls(const ControlList &sensorControls, const ControlList &lensControls);
};
class SimpleCameraConfiguration : public CameraConfiguration
@@ -1039,7 +1042,7 @@ void SimpleCameraData::metadataReady(uint32_t frame, const ControlList &metadata
tryCompleteRequest(info->request);
}
-void SimpleCameraData::setSensorControls(const ControlList &sensorControls)
+void SimpleCameraData::setSensorControls(const ControlList &sensorControls, const ControlList &lensControls)
{
delayedCtrls_->push(sensorControls);
/*
@@ -1050,10 +1053,21 @@ void SimpleCameraData::setSensorControls(const ControlList &sensorControls)
* but it also bypasses delayedCtrls_, creating AGC regulation issues.
* Both problems should be fixed.
*/
- if (!frameStartEmitter_) {
- ControlList ctrls(sensorControls);
- sensor_->setControls(&ctrls);
- }
+ if (frameStartEmitter_)
+ return;
+
+ ControlList ctrls(sensorControls);
+ sensor_->setControls(&ctrls);
+
+ CameraLens *focusLens = sensor_->focusLens();
+ if (!focusLens)
+ return;
+
+ if (!lensControls.contains(V4L2_CID_FOCUS_ABSOLUTE))
+ return;
+
+ const ControlValue &focusValue = lensControls.get(V4L2_CID_FOCUS_ABSOLUTE);
+ focusLens->setFocusPosition(focusValue.get<int32_t>());
}
/* Retrieve all source pads connected to a sink pad through active routes. */
@@ -1603,6 +1617,10 @@ int SimplePipelineHandler::configure(Camera *camera, CameraConfiguration *c)
} else {
ipa::soft::IPAConfigInfo configInfo;
configInfo.sensorControls = data->sensor_->controls();
+ if (data->sensor_->focusLens() != nullptr)
+ configInfo.lensControls = data->sensor_->focusLens()->controls();
+ else
+ configInfo.lensControls = ControlInfoMap();
return data->swIsp_->configure(inputCfg, outputCfgs, configInfo);
}
}
diff --git a/src/libcamera/software_isp/software_isp.cpp b/src/libcamera/software_isp/software_isp.cpp
index c73a16ce0..2e6fc3d98 100644
--- a/src/libcamera/software_isp/software_isp.cpp
+++ b/src/libcamera/software_isp/software_isp.cpp
@@ -436,9 +436,9 @@ void SoftwareIsp::saveIspParams()
debayerParams_ = *sharedParams_;
}
-void SoftwareIsp::setSensorCtrls(const ControlList &sensorControls)
+void SoftwareIsp::setSensorCtrls(const ControlList &sensorControls, const ControlList &lensControls)
{
- setSensorControls.emit(sensorControls);
+ setSensorControls.emit(sensorControls, lensControls);
}
void SoftwareIsp::statsReady(uint32_t frame, uint32_t bufferId)