fp6-img/aports/temp/libcamera/0013-libcamera-software_isp-af-Add-AfMode-and-focus-witho.patch
Jorijn van der Graaf 40ccdfeed6
Some checks failed
image / image (push) Has been cancelled
libcamera: backport contrast autofocus for the main camera (r4)
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.
2026-08-17 02:02:45 +02:00

207 lines
8 KiB
Diff

From ce00095430294bdb8d99e07e39d94b1cd3c85c88 Mon Sep 17 00:00:00 2001
From: Jorijn van der Graaf <jorijnvdgraaf@catcrafts.net>
Date: Mon, 17 Aug 2026 02:00:43 +0200
Subject: [PATCH] libcamera: software_isp: af: Add AfMode and focus without
being asked
The WIP exposes AfTrigger and LensPosition only, so the lens moves for
an application that knows about libcamera's AF controls and never moves
for one that does not. Every stock camera application on the phone is
the second kind: they open the camera, stream, and expect focus to
happen.
Advertise AfMode and default it to AfModeContinuous, start a sweep from
configure() so that focus happens as soon as the camera streams, and let
the existing focus-loss detection re-run the sweep when the scene
changes. AfModeAuto keeps the triggered behaviour (a sweep per
AfTriggerStart, no chasing afterwards) and AfModeManual hands the lens
to LensPosition, which per its documentation is ignored in the other
modes.
Report AfState and LensPosition in the metadata so applications can see
what the algorithm is doing. This replaces the WIP's debug abuse of
AeState.
Note that LensPosition is a percentage of the lens travel here, not the
dioptres its documentation asks for; converting requires lens
calibration data we do not have for the DW9784. It is inherited from the
WIP and kept for now.
Signed-off-by: Jorijn van der Graaf <jorijnvdgraaf@catcrafts.net>
---
src/ipa/simple/algorithms/af.cpp | 97 ++++++++++++++++++++++++++------
src/ipa/simple/ipa_context.h | 2 +
2 files changed, 81 insertions(+), 18 deletions(-)
diff --git a/src/ipa/simple/algorithms/af.cpp b/src/ipa/simple/algorithms/af.cpp
index 43d4a41c4..00148ce1b 100644
--- a/src/ipa/simple/algorithms/af.cpp
+++ b/src/ipa/simple/algorithms/af.cpp
@@ -34,6 +34,11 @@ constexpr double kFineStepMin = 1.0;
constexpr uint32_t kSettleSkipLong = 3;
/* Relative sharpness change that makes a settled scene worth re-focusing. */
constexpr double kFocusLossThreshold = 0.3;
+/*
+ * Focus continuously by default: applications that know nothing about AF never
+ * send an AfMode or an AfTrigger, and they are the ones this exists for.
+ */
+constexpr int32_t kDefaultAfMode = controls::AfModeContinuous;
} /* namespace */
@@ -45,41 +50,76 @@ Af::Af()
int Af::init(IPAContext &context,
[[maybe_unused]] const ValueNode &tuningData)
{
+ context.ctrlMap[&controls::AfMode] =
+ ControlInfo(controls::AfModeValues,
+ ControlValue(static_cast<int32_t>(kDefaultAfMode)));
+ context.ctrlMap[&controls::AfTrigger] =
+ ControlInfo(controls::AfTriggerValues,
+ ControlValue(static_cast<int32_t>(controls::AfTriggerStart)));
context.ctrlMap[&controls::LensPosition] = ControlInfo(0.0f, 100.0f, 50.0f);
- context.ctrlMap[&controls::AfTrigger] = ControlInfo(0, 1, 0);
return 0;
}
int Af::configure(IPAContext &context,
[[maybe_unused]] const IPAConfigInfo &configInfo)
{
- context.activeState.knobs.focus_sweep = std::optional<bool>();
- context.activeState.knobs.focus_pos = std::optional<double>();
+ context.activeState.knobs.af_mode = kDefaultAfMode;
context.activeState.knobs.focus_sweep = false;
context.activeState.knobs.focus_pos = 0;
context.configuration.focus.skip = kSettleSkipLong;
+
+ /*
+ * Start focusing as soon as the camera streams. Applications that do
+ * not know about AF at all never send an AfTrigger, so waiting for one
+ * would leave the lens parked wherever it was.
+ */
+ if (context.configuration.focus.available &&
+ context.activeState.knobs.af_mode != controls::AfModeManual)
+ restart(context);
+
return 0;
}
-void Af::queueRequest([[maybe_unused]] typename Module::Context &context,
+void Af::queueRequest(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);
- const auto &af_trigger = controls.get(controls::AfTrigger);
- if (focus_pos.has_value()) {
- context.activeState.knobs.focus_pos = focus_pos;
- LOG(IPASoftAutoFocus, Debug) << "Setting focus position to " << focus_pos.value();
+ if (!context.configuration.focus.available)
+ return;
+
+ const auto &afMode = controls.get(controls::AfMode);
+ if (afMode.has_value() && afMode.value() != context.activeState.knobs.af_mode) {
+ context.activeState.knobs.af_mode = afMode.value();
+ LOG(IPASoftAutoFocus, Debug) << "Setting AF mode to " << afMode.value();
+
+ if (afMode.value() == controls::AfModeContinuous)
+ restart(context);
+ else
+ context.activeState.knobs.focus_sweep = false;
}
- if (af_trigger.has_value()) {
- context.activeState.knobs.focus_sweep = af_trigger.value() == 1;
- if (context.activeState.knobs.focus_sweep)
+
+ const auto &lensPosition = controls.get(controls::LensPosition);
+ if (lensPosition.has_value() &&
+ context.activeState.knobs.af_mode == controls::AfModeManual) {
+ context.activeState.knobs.focus_pos = lensPosition.value();
+ context.configuration.focus.skip = kSettleSkipLong;
+ LOG(IPASoftAutoFocus, Debug)
+ << "Setting focus position to " << lensPosition.value();
+ }
+
+ const auto &afTrigger = controls.get(controls::AfTrigger);
+ if (afTrigger.has_value() &&
+ context.activeState.knobs.af_mode == controls::AfModeAuto) {
+ if (afTrigger.value() == controls::AfTriggerStart)
restart(context);
+ else
+ context.activeState.knobs.focus_sweep = false;
}
}
-void Af::updateFocus([[maybe_unused]] IPAContext &context, [[maybe_unused]] IPAFrameContext &frameContext, [[maybe_unused]] double exposureMSV)
+void Af::updateFocus(IPAContext &context, IPAFrameContext &frameContext,
+ [[maybe_unused]] double exposureMSV)
{
const auto &focus = context.configuration.focus;
const double pos = std::clamp(context.activeState.knobs.focus_pos.value_or(50.0),
@@ -118,6 +158,13 @@ void Af::step(IPAContext &context, double &focus_pos, uint64_t sharp, bool &swee
}
if (!sweep) {
+ /*
+ * Only continuous AF chases the scene; in auto mode the lens
+ * stays where the last triggered sweep left it.
+ */
+ if (context.activeState.knobs.af_mode != controls::AfModeContinuous)
+ return;
+
if (utils::abs_diff(sharp, focus.sharpness_max) >
focus.sharpness_max * kFocusLossThreshold) {
LOG(IPASoftAutoFocus, Info)
@@ -163,19 +210,33 @@ void Af::step(IPAContext &context, double &focus_pos, uint64_t sharp, bool &swee
/* sharpness_max is kept to detect the scene changing later on. */
}
-void Af::process([[maybe_unused]] IPAContext &context,
+void Af::process(IPAContext &context,
[[maybe_unused]] const uint32_t frame,
- [[maybe_unused]] IPAFrameContext &frameContext,
- [[maybe_unused]] const SwIspStats *stats,
- [[maybe_unused]] ControlList &metadata)
+ IPAFrameContext &frameContext,
+ const SwIspStats *stats,
+ ControlList &metadata)
{
- if (stats->valid) {
+ if (!context.configuration.focus.available)
+ return;
+
+ if (stats->valid &&
+ context.activeState.knobs.af_mode != controls::AfModeManual) {
step(context, context.activeState.knobs.focus_pos.value(),
stats->sharpness,
context.activeState.knobs.focus_sweep.value());
}
updateFocus(context, frameContext, 0);
+
+ int32_t afState = controls::AfStateIdle;
+ if (context.activeState.knobs.af_mode != controls::AfModeManual)
+ afState = context.activeState.knobs.focus_sweep.value()
+ ? controls::AfStateScanning
+ : controls::AfStateFocused;
+
+ metadata.set(controls::AfState, afState);
+ metadata.set(controls::LensPosition,
+ static_cast<float>(context.activeState.knobs.focus_pos.value()));
}
REGISTER_IPA_ALGORITHM(Af, "Af")
diff --git a/src/ipa/simple/ipa_context.h b/src/ipa/simple/ipa_context.h
index ccc938ce9..4c594cd29 100644
--- a/src/ipa/simple/ipa_context.h
+++ b/src/ipa/simple/ipa_context.h
@@ -73,6 +73,8 @@ struct IPAActiveState {
std::optional<double> focus_pos;
/* 0..1 range, 0 = normal */
std::optional<bool> focus_sweep;
+ /* One of the controls::AfModeEnum values */
+ int32_t af_mode;
} knobs;
};