From 40ccdfeed62debed97673d4396eeb0dca8380f31 Mon Sep 17 00:00:00 2001 From: Jorijn van der Graaf Date: Mon, 17 Aug 2026 02:02:45 +0200 Subject: [PATCH] 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. --- ...amera-software_isp-Add-focus-control.patch | 379 +++++++++++++ ...libcamera-software_isp-Add-autofocus.patch | 224 ++++++++ .../libcamera/0009-AF-detect-focus-loss.patch | 64 +++ ...ep-over-a-brightness-normalised-cent.patch | 529 ++++++++++++++++++ ...ne-simple-Apply-the-lens-position-un.patch | 74 +++ ...re_isp-af-Harden-the-lens-control-ha.patch | 113 ++++ ...re_isp-af-Add-AfMode-and-focus-witho.patch | 207 +++++++ aports/temp/libcamera/APKBUILD | 16 +- 8 files changed, 1605 insertions(+), 1 deletion(-) create mode 100644 aports/temp/libcamera/0007-libcamera-software_isp-Add-focus-control.patch create mode 100644 aports/temp/libcamera/0008-libcamera-software_isp-Add-autofocus.patch create mode 100644 aports/temp/libcamera/0009-AF-detect-focus-loss.patch create mode 100644 aports/temp/libcamera/0010-af-two-phase-sweep-over-a-brightness-normalised-cent.patch create mode 100644 aports/temp/libcamera/0011-libcamera-pipeline-simple-Apply-the-lens-position-un.patch create mode 100644 aports/temp/libcamera/0012-libcamera-software_isp-af-Harden-the-lens-control-ha.patch create mode 100644 aports/temp/libcamera/0013-libcamera-software_isp-af-Add-AfMode-and-focus-witho.patch diff --git a/aports/temp/libcamera/0007-libcamera-software_isp-Add-focus-control.patch b/aports/temp/libcamera/0007-libcamera-software_isp-Add-focus-control.patch new file mode 100644 index 0000000..85f4c6a --- /dev/null +++ b/aports/temp/libcamera/0007-libcamera-software_isp-Add-focus-control.patch @@ -0,0 +1,379 @@ +From d3ee3b91af950deae8edd4ce4148ac11664c48e9 Mon Sep 17 00:00:00 2001 +From: Vasiliy Doylov +Date: Mon, 17 Mar 2025 04:24:56 +0300 +Subject: [PATCH] libcamera: software_isp: Add focus control + +Signed-off-by: Vasiliy Doylov +--- + .../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 outputBufferReady; + Signal ispStatsReady; + Signal metadataReady; +- Signal setSensorControls; ++ Signal 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 ++ * ++ * Auto focus ++ */ ++ ++#include "af.h" ++ ++#include ++ ++#include ++ ++#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(); ++ ++ 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 ++ * ++ * 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 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 contrast; + std::optional saturation; ++ /* 0..100 range, 50.0 = normal */ ++ std::optional focus_pos; + } knobs; + }; + +@@ -76,6 +81,10 @@ struct IPAFrameContext : public FrameContext { + float gamma; + std::optional contrast; + std::optional 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 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(); ++ context_.configuration.focus.focus_max = lensInfo.max().get(); ++ 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(); +@@ -325,7 +338,10 @@ void IPASoftSimple::processStats(const uint32_t frame, + ctrls.set(V4L2_CID_ANALOGUE_GAIN, + static_cast(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 + + #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()); + } + + /* 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) diff --git a/aports/temp/libcamera/0008-libcamera-software_isp-Add-autofocus.patch b/aports/temp/libcamera/0008-libcamera-software_isp-Add-autofocus.patch new file mode 100644 index 0000000..e387da9 --- /dev/null +++ b/aports/temp/libcamera/0008-libcamera-software_isp-Add-autofocus.patch @@ -0,0 +1,224 @@ +From 832c016ad79e1280d1a2e840cc47f434cba4e382 Mon Sep 17 00:00:00 2001 +From: Vasiliy Doylov +Date: Wed, 9 Jul 2025 16:07:14 +0300 +Subject: [PATCH] libcamera: software_isp: Add autofocus + +Signed-off-by: Vasiliy Doylov +--- + .../internal/software_isp/swisp_stats.h | 4 ++ + src/ipa/simple/algorithms/af.cpp | 59 ++++++++++++++++++- + src/ipa/simple/algorithms/af.h | 1 + + src/ipa/simple/ipa_context.h | 5 ++ + src/libcamera/software_isp/swstats_cpu.cpp | 22 +++++-- + 5 files changed, 86 insertions(+), 5 deletions(-) + +diff --git a/include/libcamera/internal/software_isp/swisp_stats.h b/include/libcamera/internal/software_isp/swisp_stats.h +index d9d0d9be8..c2ec20e42 100644 +--- a/include/libcamera/internal/software_isp/swisp_stats.h ++++ b/include/libcamera/internal/software_isp/swisp_stats.h +@@ -43,6 +43,10 @@ struct SwIspStats { + * \brief A histogram of luminance values of all the sampled pixels + */ + Histogram yHistogram; ++ /** ++ * \brief Holds the sharpness of an image ++ */ ++ uint64_t sharpness; + }; + + } /* namespace libcamera */ +diff --git a/src/ipa/simple/algorithms/af.cpp b/src/ipa/simple/algorithms/af.cpp +index 6197f3271..3f0f98f93 100644 +--- a/src/ipa/simple/algorithms/af.cpp ++++ b/src/ipa/simple/algorithms/af.cpp +@@ -27,14 +27,17 @@ int Af::init(IPAContext &context, + [[maybe_unused]] const ValueNode &tuningData) + { + 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(); + context.activeState.knobs.focus_pos = std::optional(); +- ++ context.activeState.knobs.focus_sweep = false; ++ context.activeState.knobs.focus_pos = 0; + return 0; + } + +@@ -44,10 +47,24 @@ void Af::queueRequest([[maybe_unused]] typename Module::Context &context, + 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 (af_trigger.has_value()) { ++ context.activeState.knobs.focus_sweep = af_trigger.value() == 1; ++ if(context.activeState.knobs.focus_sweep){ ++ context.activeState.knobs.focus_pos = 0; ++ context.configuration.focus.focus_max_pos = 0; ++ context.configuration.focus.sharpness_max = 0; ++ context.configuration.focus.start = 0; ++ context.configuration.focus.stop = 100; ++ context.configuration.focus.step = 25; ++ LOG(IPASoftAutoFocus, Info) << "Starting focus sweep"; ++ } ++ } ++ + } + + void Af::updateFocus([[maybe_unused]] IPAContext &context, [[maybe_unused]] IPAFrameContext &frameContext, [[maybe_unused]] double exposureMSV) +@@ -55,12 +72,52 @@ void Af::updateFocus([[maybe_unused]] IPAContext &context, [[maybe_unused]] IPAF + 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::step(double& start, double& stop, double& step, double& focus_pos, double& max_pos, uint64_t& max_sharp, uint64_t sharp, bool& sweep){ ++ if(!sweep) ++ return; ++ if(focus_pos < start) { ++ focus_pos = start; ++ return; ++ } ++ if(sharp > max_sharp) { ++ max_sharp = sharp; ++ max_pos = focus_pos; ++ } ++ if(focus_pos >= stop) { ++ LOG(IPASoftAutoFocus, Info) << "Best focus on step " <valid) { ++ step(context.configuration.focus.start, ++ context.configuration.focus.stop, ++ context.configuration.focus.step, ++ context.activeState.knobs.focus_pos.value(), ++ context.configuration.focus.focus_max_pos, ++ context.configuration.focus.sharpness_max, ++ stats->sharpness, ++ context.activeState.knobs.focus_sweep.value()); ++ } ++ + updateFocus(context, frameContext, 0); + } + +diff --git a/src/ipa/simple/algorithms/af.h b/src/ipa/simple/algorithms/af.h +index b138b4d63..4d0ea58b3 100644 +--- a/src/ipa/simple/algorithms/af.h ++++ b/src/ipa/simple/algorithms/af.h +@@ -33,6 +33,7 @@ public: + + private: + void updateFocus(IPAContext &context, IPAFrameContext &frameContext, double focus); ++ void step(double& start, double& stop, double& step, double& focus_pos, double& max_pos, uint64_t& max_sharp, uint64_t sharp, bool& sweep); + }; + + } /* namespace ipa::soft::algorithms */ +diff --git a/src/ipa/simple/ipa_context.h b/src/ipa/simple/ipa_context.h +index 2b6bd4b3a..f869d1a67 100644 +--- a/src/ipa/simple/ipa_context.h ++++ b/src/ipa/simple/ipa_context.h +@@ -35,6 +35,9 @@ struct IPASessionConfiguration { + } black; + struct { + int32_t focus_min, focus_max; ++ double focus_max_pos; ++ uint64_t sharpness_max; ++ double start, stop, step; + } focus; + }; + +@@ -65,6 +68,8 @@ struct IPAActiveState { + std::optional saturation; + /* 0..100 range, 50.0 = normal */ + std::optional focus_pos; ++ /* 0..1 range, 0 = normal */ ++ std::optional focus_sweep; + } knobs; + }; + +diff --git a/src/libcamera/software_isp/swstats_cpu.cpp b/src/libcamera/software_isp/swstats_cpu.cpp +index 7fb77ce7d..f243992fb 100644 +--- a/src/libcamera/software_isp/swstats_cpu.cpp ++++ b/src/libcamera/software_isp/swstats_cpu.cpp +@@ -177,7 +177,10 @@ static constexpr unsigned int kBlueYMul = 29; /* 0.114 * 256 */ + \ + uint64_t sumR = 0; \ + uint64_t sumG = 0; \ +- uint64_t sumB = 0; ++ uint64_t sumB = 0; \ ++ pixel_t r0 = 0, r1 = 0, b0 = 0, \ ++ b1 = 0, g0 = 0, g1 = 0; \ ++ uint64_t sharpness = 0; + + #define SWSTATS_ACCUMULATE_LINE_STATS(div) \ + sumR += r; \ +@@ -187,12 +190,20 @@ static constexpr unsigned int kBlueYMul = 29; /* 0.114 * 256 */ + yVal = r * kRedYMul; \ + yVal += g * kGreenYMul; \ + yVal += b * kBlueYMul; \ +- stats.yHistogram[yVal * SwIspStats::kYHistogramSize / (256 * 256 * (div))]++; +- ++ stats.yHistogram[yVal * SwIspStats::kYHistogramSize / (256 * 256 * (div))]++; \ ++ if (r0 != 0) \ ++ sharpness += abs(r - 2 * r1 + r0) * kRedYMul + abs(g - 2 * g1 + g0) * kGreenYMul + abs(b - 2 * b1 + b0) * kBlueYMul; \ ++ r0 = r1; \ ++ g0 = g1; \ ++ b0 = b1; \ ++ r1 = r; \ ++ g1 = g; \ ++ b1 = b; + #define SWSTATS_FINISH_LINE_STATS() \ + stats.sum_.r() += sumR; \ + stats.sum_.g() += sumG; \ +- stats.sum_.b() += sumB; ++ stats.sum_.b() += sumB; \ ++ stats.sharpness += sharpness; + + void SwStatsCpu::statsBGGR8Line0(const uint8_t *src[], SwIspStats &stats) + { +@@ -392,6 +403,7 @@ void SwStatsCpu::startFrame(uint32_t frame) + for (auto &s : stats_) { + s.sum_ = RGB({ 0, 0, 0 }); + s.yHistogram.fill(0); ++ s.sharpness = 0; + } + } + +@@ -408,8 +420,10 @@ void SwStatsCpu::finishFrame(uint32_t frame, uint32_t bufferId) + + if (valid) { + sharedStats_->sum_ = RGB({ 0, 0, 0 }); ++ sharedStats_->sharpness = 0; + sharedStats_->yHistogram.fill(0); + for (const auto &s : stats_) { ++ sharedStats_->sharpness += s.sharpness; + sharedStats_->sum_ += s.sum_; + for (unsigned int j = 0; j < SwIspStats::kYHistogramSize; j++) + sharedStats_->yHistogram[j] += s.yHistogram[j]; diff --git a/aports/temp/libcamera/0009-AF-detect-focus-loss.patch b/aports/temp/libcamera/0009-AF-detect-focus-loss.patch new file mode 100644 index 0000000..06a9a6d --- /dev/null +++ b/aports/temp/libcamera/0009-AF-detect-focus-loss.patch @@ -0,0 +1,64 @@ +From 5f34df040cda47cfb08d1cb02f4a288de2b8ab8c Mon Sep 17 00:00:00 2001 +From: Vasiliy Doylov +Date: Mon, 17 Aug 2026 01:51:48 +0200 +Subject: [PATCH] AF: detect focus loss + +Re-trigger the focus sweep when the sharpness of the settled scene +departs from the sharpness the sweep converged on by more than 30%, +which is what happens when the scene or the subject distance changes. +Keeping the converged maximum around (instead of clearing it at every +phase transition) is what makes that comparison possible. + +[Backported from 4da3fec35a15 on gitlab.com/tui/libcamera + millicam_af_6. That tree already carried the settle-skip counter, so + the skip handling of the original hunk is dropped here; it arrives + with the next patch.] +Signed-off-by: Vasiliy Doylov +--- + src/ipa/simple/algorithms/af.cpp | 19 ++++++++++++++++--- + 1 file changed, 16 insertions(+), 3 deletions(-) + +diff --git a/src/ipa/simple/algorithms/af.cpp b/src/ipa/simple/algorithms/af.cpp +index 3f0f98f93..f321f361e 100644 +--- a/src/ipa/simple/algorithms/af.cpp ++++ b/src/ipa/simple/algorithms/af.cpp +@@ -10,6 +10,7 @@ + #include + + #include ++#include + + #include "control_ids.h" + +@@ -73,8 +74,21 @@ void Af::updateFocus([[maybe_unused]] IPAContext &context, [[maybe_unused]] IPAF + } + + void Af::step(double& start, double& stop, double& step, double& focus_pos, double& max_pos, uint64_t& max_sharp, uint64_t sharp, bool& sweep){ +- if(!sweep) +- return; ++ if(!sweep){ ++ if(utils::abs_diff(sharp, max_sharp) > max_sharp*0.3){ ++ LOG(IPASoftAutoFocus, Info) << "Focus lost :("; ++ sweep = true; ++ max_sharp = 0; ++ max_pos = 0; ++ focus_pos = 0; ++ start = 0; ++ stop = 100; ++ step = 25; ++ } ++ else { ++ return; ++ } ++ } + if(focus_pos < start) { + focus_pos = start; + return; +@@ -88,7 +102,6 @@ void Af::step(double& start, double& stop, double& step, double& focus_pos, doub + start = std::clamp(max_pos - step, 0.0, 100.0); + stop = std::clamp(max_pos + step, 0.0, 100.0); + focus_pos = start; +- max_sharp = 0; + step /= 2; + if(step <= 0.2){ + sweep = false; diff --git a/aports/temp/libcamera/0010-af-two-phase-sweep-over-a-brightness-normalised-cent.patch b/aports/temp/libcamera/0010-af-two-phase-sweep-over-a-brightness-normalised-cent.patch new file mode 100644 index 0000000..060621a --- /dev/null +++ b/aports/temp/libcamera/0010-af-two-phase-sweep-over-a-brightness-normalised-cent.patch @@ -0,0 +1,529 @@ +From eeaef1aff4f95ca78f6005b13cf82a9bccee5844 Mon Sep 17 00:00:00 2001 +From: Pavel Machek +Date: Mon, 17 Aug 2026 01:57:53 +0200 +Subject: [PATCH] af: two-phase sweep over a brightness-normalised + centre-window metric + +Three changes that together turn the sweep from "moves the lens" into +"finds the focus": + +- Measure sharpness over the centre of the frame only, so that a + cluttered background does not outvote the subject. + +- Compare green samples two sampling steps apart instead of taking a + full RGB Laplacian, which keeps the metric away from the spatial + frequencies where sensor noise lives, and normalise the result by the + brightness of the same pixels: the AGC keeps moving while a sweep + runs, and an unnormalised metric simply follows the exposure. + +- Sweep in two phases (coarse, then fine around the coarse maximum) and + skip a few frames after every large lens movement so that the lens has + settled before the next sample is taken. + +[Squashed from da42da564afa, 3ac05187459d and 4312a90e32ea on + gitlab.com/tui/libcamera millicam_af_6, and adapted to libcamera + v0.7.2: + + - the statistics line functions take a SwIspStats reference for + multi-threaded stats, so the y coordinate is threaded through as an + extra parameter rather than replacing one; + - the centre window is band 2 of 5 on both axes (the original used band + 3, which is off-centre), and the x window is derived from each + format's own loop bound because the packed formats count bytes, not + pixels; + - the normalisation divides by the square of the mean green level of + the sampled pixels, which makes the metric independent of exposure + rather than merely less dependent on it, and is guarded against an + all-black window; + - the sweep constants are named and tuned for a 0..4095 VCM sampled + once every four frames: coarse 10%, fine 2%, ending below 1%; + - the mcam test application, the forced-focus button and the + AeState/step debug output are dropped.] +Signed-off-by: Pavel Machek +--- + .../internal/software_isp/swstats_cpu.h | 20 +-- + src/ipa/simple/algorithms/af.cpp | 138 +++++++++++------- + src/ipa/simple/algorithms/af.h | 5 +- + src/ipa/simple/ipa_context.h | 2 + + src/libcamera/software_isp/swstats_cpu.cpp | 84 +++++++---- + 5 files changed, 160 insertions(+), 89 deletions(-) + +diff --git a/include/libcamera/internal/software_isp/swstats_cpu.h b/include/libcamera/internal/software_isp/swstats_cpu.h +index 551870921..df399d71c 100644 +--- a/include/libcamera/internal/software_isp/swstats_cpu.h ++++ b/include/libcamera/internal/software_isp/swstats_cpu.h +@@ -67,7 +67,7 @@ public: + y >= (window_.y + window_.height)) + return; + +- (this->*stats0_)(src, stats_[statsBufferIndex]); ++ (this->*stats0_)(src, y, stats_[statsBufferIndex]); + } + + void processLine2(uint32_t frame, unsigned int y, const uint8_t *src[], unsigned int statsBufferIndex = 0) +@@ -79,28 +79,28 @@ public: + y >= (window_.y + window_.height)) + return; + +- (this->*stats2_)(src, stats_[statsBufferIndex]); ++ (this->*stats2_)(src, y, stats_[statsBufferIndex]); + } + + Signal statsReady; + + private: +- using statsProcessFn = void (SwStatsCpu::*)(const uint8_t *src[], SwIspStats &stats); ++ using statsProcessFn = void (SwStatsCpu::*)(const uint8_t *src[], unsigned int y, SwIspStats &stats); + using processFrameFn = void (SwStatsCpu::*)(MappedFrameBuffer &in); + + int setupStandardBayerOrder(BayerFormat::Order order); + /* Bayer 8 bpp unpacked */ +- void statsBGGR8Line0(const uint8_t *src[], SwIspStats &stats); ++ void statsBGGR8Line0(const uint8_t *src[], unsigned int y, SwIspStats &stats); + /* Bayer 10 bpp unpacked */ +- void statsBGGR10Line0(const uint8_t *src[], SwIspStats &stats); ++ void statsBGGR10Line0(const uint8_t *src[], unsigned int y, SwIspStats &stats); + /* Bayer 12 bpp unpacked */ +- void statsBGGR12Line0(const uint8_t *src[], SwIspStats &stats); ++ void statsBGGR12Line0(const uint8_t *src[], unsigned int y, SwIspStats &stats); + /* Bayer 10 bpp packed */ +- void statsBGGR10PLine0(const uint8_t *src[], SwIspStats &stats); +- void statsGBRG10PLine0(const uint8_t *src[], SwIspStats &stats); ++ void statsBGGR10PLine0(const uint8_t *src[], unsigned int y, SwIspStats &stats); ++ void statsGBRG10PLine0(const uint8_t *src[], unsigned int y, SwIspStats &stats); + /* Bayer 12 bpp packed */ +- void statsBGGR12PLine0(const uint8_t *src[], SwIspStats &stats); +- void statsGBRG12PLine0(const uint8_t *src[], SwIspStats &stats); ++ void statsBGGR12PLine0(const uint8_t *src[], unsigned int y, SwIspStats &stats); ++ void statsGBRG12PLine0(const uint8_t *src[], unsigned int y, SwIspStats &stats); + + void processBayerFrame2(MappedFrameBuffer &in); + +diff --git a/src/ipa/simple/algorithms/af.cpp b/src/ipa/simple/algorithms/af.cpp +index f321f361e..19235a567 100644 +--- a/src/ipa/simple/algorithms/af.cpp ++++ b/src/ipa/simple/algorithms/af.cpp +@@ -7,6 +7,7 @@ + + #include "af.h" + ++#include + #include + + #include +@@ -20,7 +21,23 @@ LOG_DEFINE_CATEGORY(IPASoftAutoFocus) + + namespace ipa::soft::algorithms { + ++namespace { ++ ++/* Percentage of the lens travel between two samples of the coarse sweep. */ ++constexpr double kCoarseStep = 10.0; ++/* The step is divided by this at every phase of the sweep. */ ++constexpr double kStepDivisor = 5.0; ++/* The sweep ends once the step would become finer than this. */ ++constexpr double kFineStepMin = 1.0; ++/* Stats frames to ignore after a large lens movement. */ ++constexpr uint32_t kSettleSkipLong = 3; ++/* Relative sharpness change that makes a settled scene worth re-focusing. */ ++constexpr double kFocusLossThreshold = 0.3; ++ ++} /* namespace */ ++ + Af::Af() ++ : steps_(0) + { + } + +@@ -39,6 +56,7 @@ int Af::configure(IPAContext &context, + context.activeState.knobs.focus_pos = std::optional(); + context.activeState.knobs.focus_sweep = false; + context.activeState.knobs.focus_pos = 0; ++ context.configuration.focus.skip = kSettleSkipLong; + return 0; + } + +@@ -55,17 +73,9 @@ void Af::queueRequest([[maybe_unused]] typename Module::Context &context, + } + if (af_trigger.has_value()) { + context.activeState.knobs.focus_sweep = af_trigger.value() == 1; +- if(context.activeState.knobs.focus_sweep){ +- context.activeState.knobs.focus_pos = 0; +- context.configuration.focus.focus_max_pos = 0; +- context.configuration.focus.sharpness_max = 0; +- context.configuration.focus.start = 0; +- context.configuration.focus.stop = 100; +- context.configuration.focus.step = 25; +- LOG(IPASoftAutoFocus, Info) << "Starting focus sweep"; +- } ++ if (context.activeState.knobs.focus_sweep) ++ restart(context); + } +- + } + + void Af::updateFocus([[maybe_unused]] IPAContext &context, [[maybe_unused]] IPAFrameContext &frameContext, [[maybe_unused]] double exposureMSV) +@@ -73,45 +83,78 @@ void Af::updateFocus([[maybe_unused]] IPAContext &context, [[maybe_unused]] IPAF + 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::step(double& start, double& stop, double& step, double& focus_pos, double& max_pos, uint64_t& max_sharp, uint64_t sharp, bool& sweep){ +- if(!sweep){ +- if(utils::abs_diff(sharp, max_sharp) > max_sharp*0.3){ +- LOG(IPASoftAutoFocus, Info) << "Focus lost :("; +- sweep = true; +- max_sharp = 0; +- max_pos = 0; +- focus_pos = 0; +- start = 0; +- stop = 100; +- step = 25; +- } +- else { +- return; +- } ++void Af::restart(IPAContext &context) ++{ ++ auto &focus = context.configuration.focus; ++ ++ steps_ = 0; ++ focus.focus_max_pos = 0; ++ focus.sharpness_max = 0; ++ focus.start = 0; ++ focus.stop = 100; ++ focus.step = kCoarseStep; ++ focus.skip = kSettleSkipLong; ++ context.activeState.knobs.focus_pos = 0; ++ context.activeState.knobs.focus_sweep = true; ++ LOG(IPASoftAutoFocus, Info) << "Starting focus sweep"; ++} ++ ++void Af::step(IPAContext &context, double &focus_pos, uint64_t sharp, bool &sweep) ++{ ++ auto &focus = context.configuration.focus; ++ ++ steps_++; ++ ++ /* Even during a sweep the lens needs time to reach the new position. */ ++ if (focus.skip != 0) { ++ focus.skip--; ++ return; + } +- if(focus_pos < start) { +- focus_pos = start; ++ ++ if (!sweep) { ++ if (utils::abs_diff(sharp, focus.sharpness_max) > ++ focus.sharpness_max * kFocusLossThreshold) { ++ LOG(IPASoftAutoFocus, Info) ++ << "Focus lost: " << sharp << " vs " ++ << focus.sharpness_max; ++ restart(context); ++ } + return; + } +- if(sharp > max_sharp) { +- max_sharp = sharp; +- max_pos = focus_pos; ++ ++ if (sharp > focus.sharpness_max) { ++ focus.sharpness_max = sharp; ++ focus.focus_max_pos = focus_pos; + } +- if(focus_pos >= stop) { +- LOG(IPASoftAutoFocus, Info) << "Best focus on step " < focus.start && focus_pos < focus.stop) ++ return; ++ ++ /* Sweep phase over, narrow the range around the best position. */ ++ focus.start = std::clamp(focus.focus_max_pos - focus.step, 0.0, 100.0); ++ focus.stop = std::clamp(focus.focus_max_pos + focus.step, 0.0, 100.0); ++ LOG(IPASoftAutoFocus, Info) ++ << "Best focus with step " << focus.step << ": " ++ << focus.focus_max_pos << " (sharpness " ++ << focus.sharpness_max << "), next range " << focus.start ++ << ".." << focus.stop; ++ focus_pos = focus.start; ++ focus.step /= kStepDivisor; ++ focus.skip = kSettleSkipLong; ++ ++ if (focus.step >= kFineStepMin) { ++ /* Look for the maximum again at the finer step. */ ++ focus.sharpness_max = 0; + return; + } + +- focus_pos += step; ++ sweep = false; ++ focus_pos = focus.focus_max_pos; ++ LOG(IPASoftAutoFocus, Info) ++ << "Sweep end. Best focus: " << focus.focus_max_pos ++ << " after " << steps_ << " frames"; ++ /* sharpness_max is kept to detect the scene changing later on. */ + } + + void Af::process([[maybe_unused]] IPAContext &context, +@@ -121,14 +164,9 @@ void Af::process([[maybe_unused]] IPAContext &context, + [[maybe_unused]] ControlList &metadata) + { + if (stats->valid) { +- step(context.configuration.focus.start, +- context.configuration.focus.stop, +- context.configuration.focus.step, +- context.activeState.knobs.focus_pos.value(), +- context.configuration.focus.focus_max_pos, +- context.configuration.focus.sharpness_max, +- stats->sharpness, +- context.activeState.knobs.focus_sweep.value()); ++ step(context, context.activeState.knobs.focus_pos.value(), ++ stats->sharpness, ++ context.activeState.knobs.focus_sweep.value()); + } + + updateFocus(context, frameContext, 0); +diff --git a/src/ipa/simple/algorithms/af.h b/src/ipa/simple/algorithms/af.h +index 4d0ea58b3..0e0f19423 100644 +--- a/src/ipa/simple/algorithms/af.h ++++ b/src/ipa/simple/algorithms/af.h +@@ -33,7 +33,10 @@ public: + + private: + void updateFocus(IPAContext &context, IPAFrameContext &frameContext, double focus); +- void step(double& start, double& stop, double& step, double& focus_pos, double& max_pos, uint64_t& max_sharp, uint64_t sharp, bool& sweep); ++ void step(IPAContext &context, double &focus_pos, uint64_t sharp, bool &sweep); ++ void restart(IPAContext &context); ++ ++ unsigned int steps_; + }; + + } /* namespace ipa::soft::algorithms */ +diff --git a/src/ipa/simple/ipa_context.h b/src/ipa/simple/ipa_context.h +index f869d1a67..4fc9c7cdf 100644 +--- a/src/ipa/simple/ipa_context.h ++++ b/src/ipa/simple/ipa_context.h +@@ -38,6 +38,8 @@ struct IPASessionConfiguration { + double focus_max_pos; + uint64_t sharpness_max; + double start, stop, step; ++ /* Stats frames to ignore while the lens settles */ ++ uint32_t skip; + } focus; + }; + +diff --git a/src/libcamera/software_isp/swstats_cpu.cpp b/src/libcamera/software_isp/swstats_cpu.cpp +index f243992fb..2cbdbefa3 100644 +--- a/src/libcamera/software_isp/swstats_cpu.cpp ++++ b/src/libcamera/software_isp/swstats_cpu.cpp +@@ -130,6 +130,7 @@ namespace libcamera { + * \typedef SwStatsCpu::statsProcessFn + * \brief Called when there is data to get statistics from + * \param[in] src The input data ++ * \param[in] y The y coordinate of the line, relative to the window + * + * These functions take an array of (patternSize_.height + 1) src + * pointers each pointing to a line in the source image. The middle +@@ -171,17 +172,34 @@ static constexpr unsigned int kRedYMul = 77; /* 0.299 * 256 */ + static constexpr unsigned int kGreenYMul = 150; /* 0.587 * 256 */ + static constexpr unsigned int kBlueYMul = 29; /* 0.114 * 256 */ + +-#define SWSTATS_START_LINE_STATS(pixel_t) \ ++/* ++ * The sharpness metric is only gathered over the centre fifth of the frame, ++ * which is where the subject an autofocus run should focus on normally is. ++ * \a lineLength is the upper bound of the sampling loop of the caller, in the ++ * units that loop counts in (pixels or bytes depending on the format). ++ */ ++#define SWSTATS_START_LINE_STATS(pixel_t, lineLength) \ + pixel_t r, g, g2, b; \ + uint64_t yVal; \ + \ + uint64_t sumR = 0; \ + uint64_t sumG = 0; \ + uint64_t sumB = 0; \ +- pixel_t r0 = 0, r1 = 0, b0 = 0, \ +- b1 = 0, g0 = 0, g1 = 0; \ +- uint64_t sharpness = 0; ++ pixel_t gPrev = 0, gPrev2 = 0; \ ++ uint64_t sharpness = 0; \ ++ uint64_t sharpSumG = 0; \ ++ unsigned int sharpCount = 0; \ ++ const unsigned int sharpXBegin = (lineLength) * 2 / 5; \ ++ const unsigned int sharpXEnd = (lineLength) * 3 / 5; \ ++ const bool sharpRow = y >= window_.height * 2 / 5 && \ ++ y < window_.height * 3 / 5; + ++/* ++ * Sharpness is the sum of the squared differences between green samples two ++ * sampling steps apart; skipping one sample keeps the metric away from the ++ * highest spatial frequencies, where sensor noise dominates. gPrev/gPrev2 are ++ * updated for every sample so that the window always has valid history. ++ */ + #define SWSTATS_ACCUMULATE_LINE_STATS(div) \ + sumR += r; \ + sumG += g; \ +@@ -191,26 +209,36 @@ static constexpr unsigned int kBlueYMul = 29; /* 0.114 * 256 */ + yVal += g * kGreenYMul; \ + yVal += b * kBlueYMul; \ + stats.yHistogram[yVal * SwIspStats::kYHistogramSize / (256 * 256 * (div))]++; \ +- if (r0 != 0) \ +- sharpness += abs(r - 2 * r1 + r0) * kRedYMul + abs(g - 2 * g1 + g0) * kGreenYMul + abs(b - 2 * b1 + b0) * kBlueYMul; \ +- r0 = r1; \ +- g0 = g1; \ +- b0 = b1; \ +- r1 = r; \ +- g1 = g; \ +- b1 = b; ++ if (sharpRow && x >= sharpXBegin && x < sharpXEnd) { \ ++ const int64_t gDiff = static_cast(g) - gPrev2; \ ++ sharpness += gDiff * gDiff; \ ++ sharpSumG += g; \ ++ sharpCount++; \ ++ } \ ++ gPrev2 = gPrev; \ ++ gPrev = g; ++ ++/* ++ * Normalise the sharpness by the square of the mean green level of the same ++ * pixels, so that the metric tracks contrast rather than exposure: the AGC ++ * keeps moving while an autofocus sweep runs. ++ */ + #define SWSTATS_FINISH_LINE_STATS() \ + stats.sum_.r() += sumR; \ + stats.sum_.g() += sumG; \ + stats.sum_.b() += sumB; \ +- stats.sharpness += sharpness; ++ if (sharpCount) { \ ++ const uint64_t meanG = sharpSumG / sharpCount; \ ++ if (meanG) \ ++ stats.sharpness += sharpness * 1024 / (meanG * meanG); \ ++ } + +-void SwStatsCpu::statsBGGR8Line0(const uint8_t *src[], SwIspStats &stats) ++void SwStatsCpu::statsBGGR8Line0(const uint8_t *src[], unsigned int y, SwIspStats &stats) + { + const uint8_t *src0 = src[1] + window_.x; + const uint8_t *src1 = src[2] + window_.x; + +- SWSTATS_START_LINE_STATS(uint8_t) ++ SWSTATS_START_LINE_STATS(uint8_t, window_.width) + + if (swapLines_) + std::swap(src0, src1); +@@ -230,12 +258,12 @@ void SwStatsCpu::statsBGGR8Line0(const uint8_t *src[], SwIspStats &stats) + SWSTATS_FINISH_LINE_STATS() + } + +-void SwStatsCpu::statsBGGR10Line0(const uint8_t *src[], SwIspStats &stats) ++void SwStatsCpu::statsBGGR10Line0(const uint8_t *src[], unsigned int y, SwIspStats &stats) + { + const uint16_t *src0 = (const uint16_t *)src[1] + window_.x; + const uint16_t *src1 = (const uint16_t *)src[2] + window_.x; + +- SWSTATS_START_LINE_STATS(uint16_t) ++ SWSTATS_START_LINE_STATS(uint16_t, window_.width) + + if (swapLines_) + std::swap(src0, src1); +@@ -256,12 +284,12 @@ void SwStatsCpu::statsBGGR10Line0(const uint8_t *src[], SwIspStats &stats) + SWSTATS_FINISH_LINE_STATS() + } + +-void SwStatsCpu::statsBGGR12Line0(const uint8_t *src[], SwIspStats &stats) ++void SwStatsCpu::statsBGGR12Line0(const uint8_t *src[], unsigned int y, SwIspStats &stats) + { + const uint16_t *src0 = (const uint16_t *)src[1] + window_.x; + const uint16_t *src1 = (const uint16_t *)src[2] + window_.x; + +- SWSTATS_START_LINE_STATS(uint16_t) ++ SWSTATS_START_LINE_STATS(uint16_t, window_.width) + + if (swapLines_) + std::swap(src0, src1); +@@ -282,7 +310,7 @@ void SwStatsCpu::statsBGGR12Line0(const uint8_t *src[], SwIspStats &stats) + SWSTATS_FINISH_LINE_STATS() + } + +-void SwStatsCpu::statsBGGR10PLine0(const uint8_t *src[], SwIspStats &stats) ++void SwStatsCpu::statsBGGR10PLine0(const uint8_t *src[], unsigned int y, SwIspStats &stats) + { + const uint8_t *src0 = src[1] + window_.x * 5 / 4; + const uint8_t *src1 = src[2] + window_.x * 5 / 4; +@@ -291,7 +319,7 @@ void SwStatsCpu::statsBGGR10PLine0(const uint8_t *src[], SwIspStats &stats) + if (swapLines_) + std::swap(src0, src1); + +- SWSTATS_START_LINE_STATS(uint8_t) ++ SWSTATS_START_LINE_STATS(uint8_t, widthInBytes) + + /* x += 5 sample every other 2x2 block */ + for (unsigned int x = 0; x < widthInBytes; x += 5) { +@@ -308,7 +336,7 @@ void SwStatsCpu::statsBGGR10PLine0(const uint8_t *src[], SwIspStats &stats) + SWSTATS_FINISH_LINE_STATS() + } + +-void SwStatsCpu::statsGBRG10PLine0(const uint8_t *src[], SwIspStats &stats) ++void SwStatsCpu::statsGBRG10PLine0(const uint8_t *src[], unsigned int y, SwIspStats &stats) + { + const uint8_t *src0 = src[1] + window_.x * 5 / 4; + const uint8_t *src1 = src[2] + window_.x * 5 / 4; +@@ -317,7 +345,7 @@ void SwStatsCpu::statsGBRG10PLine0(const uint8_t *src[], SwIspStats &stats) + if (swapLines_) + std::swap(src0, src1); + +- SWSTATS_START_LINE_STATS(uint8_t) ++ SWSTATS_START_LINE_STATS(uint8_t, widthInBytes) + + /* x += 5 sample every other 2x2 block */ + for (unsigned int x = 0; x < widthInBytes; x += 5) { +@@ -334,13 +362,13 @@ void SwStatsCpu::statsGBRG10PLine0(const uint8_t *src[], SwIspStats &stats) + SWSTATS_FINISH_LINE_STATS() + } + +-void SwStatsCpu::statsBGGR12PLine0(const uint8_t *src[], SwIspStats &stats) ++void SwStatsCpu::statsBGGR12PLine0(const uint8_t *src[], unsigned int y, SwIspStats &stats) + { + const uint8_t *src0 = src[1] + window_.x * 3 / 2; + const uint8_t *src1 = src[2] + window_.x * 3 / 2; + const unsigned int widthInBytes = window_.width * 3 / 2; + +- SWSTATS_START_LINE_STATS(uint8_t) ++ SWSTATS_START_LINE_STATS(uint8_t, widthInBytes) + + if (swapLines_) + std::swap(src0, src1); +@@ -360,13 +388,13 @@ void SwStatsCpu::statsBGGR12PLine0(const uint8_t *src[], SwIspStats &stats) + SWSTATS_FINISH_LINE_STATS() + } + +-void SwStatsCpu::statsGBRG12PLine0(const uint8_t *src[], SwIspStats &stats) ++void SwStatsCpu::statsGBRG12PLine0(const uint8_t *src[], unsigned int y, SwIspStats &stats) + { + const uint8_t *src0 = src[1] + window_.x * 3 / 2; + const uint8_t *src1 = src[2] + window_.x * 3 / 2; + const unsigned int widthInBytes = window_.width * 3 / 2; + +- SWSTATS_START_LINE_STATS(uint8_t) ++ SWSTATS_START_LINE_STATS(uint8_t, widthInBytes) + + if (swapLines_) + std::swap(src0, src1); +@@ -603,7 +631,7 @@ void SwStatsCpu::processBayerFrame2(MappedFrameBuffer &in) + /* linePointers[0] is not used by any stats0_ functions */ + linePointers[1] = src; + linePointers[2] = src + stride_; +- (this->*stats0_)(linePointers, stats_[0]); ++ (this->*stats0_)(linePointers, y, stats_[0]); + src += stride_ * 2; + } + } diff --git a/aports/temp/libcamera/0011-libcamera-pipeline-simple-Apply-the-lens-position-un.patch b/aports/temp/libcamera/0011-libcamera-pipeline-simple-Apply-the-lens-position-un.patch new file mode 100644 index 0000000..8403cac --- /dev/null +++ b/aports/temp/libcamera/0011-libcamera-pipeline-simple-Apply-the-lens-position-un.patch @@ -0,0 +1,74 @@ +From 8ee13c9f2a3a8084d2360e78da32ed9793bfe5e9 Mon Sep 17 00:00:00 2001 +From: Jorijn van der Graaf +Date: Mon, 17 Aug 2026 01:58:13 +0200 +Subject: [PATCH] libcamera: pipeline: simple: Apply the lens position + unconditionally + +The focus control patch applies the lens position inside the branch that +only runs when the pipeline has no frame-start emitter, because that is +where the sensor controls are applied directly. The lens is a different +kind of device: it is a separate subdevice, it is not pushed through +delayedCtrls_, and it has no frame-latched controls, so there is nothing +to synchronise it to. On a platform whose pipeline does register a +frame-start emitter the lens would simply never move. + +Hoist the lens write above the early return. The FP6's CAMSS pipeline +registers no frame-start emitter (no subdevice in the graph supports +V4L2_EVENT_FRAME_SYNC), so this makes no difference there, but it is a +prerequisite for the feature working anywhere else. + +Signed-off-by: Jorijn van der Graaf +--- + src/libcamera/pipeline/simple/simple.cpp | 32 +++++++++++++----------- + 1 file changed, 17 insertions(+), 15 deletions(-) + +diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp +index 047800b59..0095ea7a5 100644 +--- a/src/libcamera/pipeline/simple/simple.cpp ++++ b/src/libcamera/pipeline/simple/simple.cpp +@@ -1045,6 +1045,19 @@ void SimpleCameraData::metadataReady(uint32_t frame, const ControlList &metadata + void SimpleCameraData::setSensorControls(const ControlList &sensorControls, const ControlList &lensControls) + { + delayedCtrls_->push(sensorControls); ++ ++ /* ++ * The lens is a separate subdevice with no frame-latched controls, so ++ * its position is applied straight away and independently of the ++ * sensor controls below. ++ */ ++ CameraLens *focusLens = sensor_->focusLens(); ++ if (focusLens && lensControls.contains(V4L2_CID_FOCUS_ABSOLUTE)) { ++ const ControlValue &focusValue = ++ lensControls.get(V4L2_CID_FOCUS_ABSOLUTE); ++ focusLens->setFocusPosition(focusValue.get()); ++ } ++ + /* + * Directly apply controls now if there is no frameStart signal. + * +@@ -1053,21 +1066,10 @@ void SimpleCameraData::setSensorControls(const ControlList &sensorControls, cons + * but it also bypasses delayedCtrls_, creating AGC regulation issues. + * Both problems should be fixed. + */ +- 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()); ++ if (!frameStartEmitter_) { ++ ControlList ctrls(sensorControls); ++ sensor_->setControls(&ctrls); ++ } + } + + /* Retrieve all source pads connected to a sink pad through active routes. */ diff --git a/aports/temp/libcamera/0012-libcamera-software_isp-af-Harden-the-lens-control-ha.patch b/aports/temp/libcamera/0012-libcamera-software_isp-af-Harden-the-lens-control-ha.patch new file mode 100644 index 0000000..c94e826 --- /dev/null +++ b/aports/temp/libcamera/0012-libcamera-software_isp-af-Harden-the-lens-control-ha.patch @@ -0,0 +1,113 @@ +From 0e454d335ba23c306e7c8f011ef7d5234dd3aa9a Mon Sep 17 00:00:00 2001 +From: Jorijn van der Graaf +Date: Mon, 17 Aug 2026 01:58:59 +0200 +Subject: [PATCH] libcamera: software_isp: af: Harden the lens control handling + +Three defects in the focus control patch that show up on a camera +without a focus lens, which on the FP6 is the ultra-wide: + +- configure() dereferences the result of ControlInfoMap::find() for + V4L2_CID_FOCUS_ABSOLUTE whenever the lens control map is non-empty, + without checking that the control is actually there. + +- processStats() unconditionally sets V4L2_CID_FOCUS_ABSOLUTE on a + ControlList built from an empty ControlInfoMap, which logs a control + validation error for every frame of every capture. + +- the lens position is computed as a fraction of the travel but the + minimum is never added back, so a lens whose range does not start at + zero is driven past its near end. + +Also demote the "found a lens" message to Info and fix its spelling. + +Signed-off-by: Jorijn van der Graaf +--- + src/ipa/simple/algorithms/af.cpp | 8 +++++++- + src/ipa/simple/ipa_context.h | 1 + + src/ipa/simple/soft_simple.cpp | 24 +++++++++++++++--------- + 3 files changed, 23 insertions(+), 10 deletions(-) + +diff --git a/src/ipa/simple/algorithms/af.cpp b/src/ipa/simple/algorithms/af.cpp +index 19235a567..43d4a41c4 100644 +--- a/src/ipa/simple/algorithms/af.cpp ++++ b/src/ipa/simple/algorithms/af.cpp +@@ -8,6 +8,7 @@ + #include "af.h" + + #include ++#include + #include + + #include +@@ -80,7 +81,12 @@ void Af::queueRequest([[maybe_unused]] typename Module::Context &context, + + 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); ++ const auto &focus = context.configuration.focus; ++ const double pos = std::clamp(context.activeState.knobs.focus_pos.value_or(50.0), ++ 0.0, 100.0); ++ ++ frameContext.lens.focus_pos = focus.focus_min + ++ std::lround(pos / 100.0 * (focus.focus_max - focus.focus_min)); + } + + void Af::restart(IPAContext &context) +diff --git a/src/ipa/simple/ipa_context.h b/src/ipa/simple/ipa_context.h +index 4fc9c7cdf..ccc938ce9 100644 +--- a/src/ipa/simple/ipa_context.h ++++ b/src/ipa/simple/ipa_context.h +@@ -34,6 +34,7 @@ struct IPASessionConfiguration { + std::optional level; + } black; + struct { ++ bool available; + int32_t focus_min, focus_max; + double focus_max_pos; + uint64_t sharpness_max; +diff --git a/src/ipa/simple/soft_simple.cpp b/src/ipa/simple/soft_simple.cpp +index dcf746cb2..51a73d2dd 100644 +--- a/src/ipa/simple/soft_simple.cpp ++++ b/src/ipa/simple/soft_simple.cpp +@@ -213,15 +213,20 @@ int IPASoftSimple::configure(const IPAConfigInfo &configInfo) + context_.activeState = {}; + context_.frameContexts.clear(); + +- if (lensInfoMap_.empty()) { +- LOG(IPASoft, Warning) << "No camera leans found! Focus control disabled."; ++ const auto lensFocus = lensInfoMap_.find(V4L2_CID_FOCUS_ABSOLUTE); ++ if (lensFocus == lensInfoMap_.end()) { ++ LOG(IPASoft, Info) << "No focus lens, focus control disabled"; ++ context_.configuration.focus.available = false; + 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(); +- context_.configuration.focus.focus_max = lensInfo.max().get(); +- LOG(IPASoft, Warning) << "Camera leans found! Focus: " << context_.configuration.focus.focus_min << "-" << context_.configuration.focus.focus_max; ++ context_.configuration.focus.available = true; ++ context_.configuration.focus.focus_min = lensFocus->second.min().get(); ++ context_.configuration.focus.focus_max = lensFocus->second.max().get(); ++ LOG(IPASoft, Info) ++ << "Focus lens range " ++ << context_.configuration.focus.focus_min << "-" ++ << context_.configuration.focus.focus_max; + } + + context_.configuration.agc.lineDuration = +@@ -338,10 +343,11 @@ void IPASoftSimple::processStats(const uint32_t frame, + ctrls.set(V4L2_CID_ANALOGUE_GAIN, + static_cast(camHelper_ ? camHelper_->gainCode(againNew) : againNew)); + +- ControlList lens_ctrls(lensInfoMap_); +- lens_ctrls.set(V4L2_CID_FOCUS_ABSOLUTE, frameContext.lens.focus_pos); ++ ControlList lensCtrls(lensInfoMap_); ++ if (context_.configuration.focus.available) ++ lensCtrls.set(V4L2_CID_FOCUS_ABSOLUTE, frameContext.lens.focus_pos); + +- setSensorControls.emit(ctrls, lens_ctrls); ++ setSensorControls.emit(ctrls, lensCtrls); + } + + std::string IPASoftSimple::logPrefix() const diff --git a/aports/temp/libcamera/0013-libcamera-software_isp-af-Add-AfMode-and-focus-witho.patch b/aports/temp/libcamera/0013-libcamera-software_isp-af-Add-AfMode-and-focus-witho.patch new file mode 100644 index 0000000..443a273 --- /dev/null +++ b/aports/temp/libcamera/0013-libcamera-software_isp-af-Add-AfMode-and-focus-witho.patch @@ -0,0 +1,207 @@ +From ce00095430294bdb8d99e07e39d94b1cd3c85c88 Mon Sep 17 00:00:00 2001 +From: Jorijn van der Graaf +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 +--- + 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(kDefaultAfMode))); ++ context.ctrlMap[&controls::AfTrigger] = ++ ControlInfo(controls::AfTriggerValues, ++ ControlValue(static_cast(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(); +- context.activeState.knobs.focus_pos = std::optional(); ++ 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(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 focus_pos; + /* 0..1 range, 0 = normal */ + std::optional focus_sweep; ++ /* One of the controls::AfModeEnum values */ ++ int32_t af_mode; + } knobs; + }; + diff --git a/aports/temp/libcamera/APKBUILD b/aports/temp/libcamera/APKBUILD index 0592f08..d4e714a 100644 --- a/aports/temp/libcamera/APKBUILD +++ b/aports/temp/libcamera/APKBUILD @@ -3,7 +3,7 @@ maintainer="Robert Mader " pkgname=libcamera _pkgver=0.7.2 pkgver=9999$_pkgver -pkgrel=3 +pkgrel=4 pkgdesc="Linux camera framework" url="https://libcamera.org/" arch="all" @@ -63,6 +63,13 @@ source="https://gitlab.freedesktop.org/camera/libcamera/-/archive/v$_pkgver/libc 0004-libcamera-sensor-Add-OV13B10-sensor-properties.patch 0005-libipa-Add-camera-sensor-helper-for-IMX896.patch 0006-libcamera-sensor-Add-IMX896-sensor-properties.patch + 0007-libcamera-software_isp-Add-focus-control.patch + 0008-libcamera-software_isp-Add-autofocus.patch + 0009-AF-detect-focus-loss.patch + 0010-af-two-phase-sweep-over-a-brightness-normalised-cent.patch + 0011-libcamera-pipeline-simple-Apply-the-lens-position-un.patch + 0012-libcamera-software_isp-af-Harden-the-lens-control-ha.patch + 0013-libcamera-software_isp-af-Add-AfMode-and-focus-witho.patch qcam.desktop $_tuning_files " @@ -182,6 +189,13 @@ eef5e38efc874f2049d17f08b04d9f21c3e4616f66873358cd2c446e651f78d6952ae25ee13a6ef9 89e0fc4a12aa4d5fbdc77b50e28b0a6d2cf8dfeb884b536507b6c24aa2ae5c616a32a517deb086e3284c72a82e6d6ddcf6eaa88f6c1c021adc6a0167194b7711 0004-libcamera-sensor-Add-OV13B10-sensor-properties.patch f746cff46f78a8e7bd4cadd72c4ab05044971e36e412a56d4a14a420aeb1c9168b1f4ef411f4cc81a596159cab3d8a3713214a4ff0b2a4daf839b1a380c09b6e 0005-libipa-Add-camera-sensor-helper-for-IMX896.patch 1ac9747c941787c7ae76ed6520585b0d26494eb0f697bd74a9155ed04736a7b61f838dfb1bad01cf13b9cdd36f644a95a6ec1cbfe4f5c5a42cb23d4b6d9aa166 0006-libcamera-sensor-Add-IMX896-sensor-properties.patch +09fcc064f5263fbf5f759d74a798ac0a667af1b24729b7e3e7ff9806b7e4156fb68812f495454563e9a96d38d0f383f5b341adfadf31ebb3a54067bffdc2de74 0007-libcamera-software_isp-Add-focus-control.patch +4d4ed6462c6ba334fc0c568d93a15f0a4afc330d5e548c14eb2846855e6a08c5fb2627a1eb24f323f9c1861f22b57315d2d29e9c5875fe926d65658ebc53d1db 0008-libcamera-software_isp-Add-autofocus.patch +3797467e1a6a3370549fc3516ecbc41e02e01f347173146c569d7ba1ca239c5273316a532ba205550ce99ae2a4fd5e8a32aa5f021237096093319138694fea7f 0009-AF-detect-focus-loss.patch +c8ddc64ab943d9b215f4c997f2629e4403744c0ddb5de88cd42d384506a27c0762191eddd4d13559e7cc15e1a4d4542a55e5de985c7ea98f938ce796b43cc9e2 0010-af-two-phase-sweep-over-a-brightness-normalised-cent.patch +3965405be757917030c89db5da60aa226d0f0591e213d055d8312dbddee0c3d81fbcaa5928d6de4d3cb636572a27261d8686d87ae39b9634d70b33cf8388dc73 0011-libcamera-pipeline-simple-Apply-the-lens-position-un.patch +8734e27966bd1bc5a4b0324a3d8a3ee4ab371f6d1536500b2bb44d49bde874c3fd6f244312332226c352e4716ba12fb72bf51e64b9e97cf6fa646aa6dfef3277 0012-libcamera-software_isp-af-Harden-the-lens-control-ha.patch +dce81c2863ff6b9374115325f4af14ac5d396a6e2aee12f87d55a47d1d0bda860ae2af5a9d6885b126e20a12b7f3f40e49a2b9d738f94c04177bff6149da64e1 0013-libcamera-software_isp-af-Add-AfMode-and-focus-witho.patch 22167a4eceb6d1b40b0b7c45fdf116c71684f5340de7f767535cb8e160ad9d2ae0f00cb3d461f73a344520a48a4641cf46226841d78bee06bfbfd2a91337f754 qcam.desktop 2ee566653b17d565d2c0de67cbe6ebad25df5aac6051cad78e6bae52016fa2ca0247e964d735ac776b3e0bd447ead9c4fcb26cd7629228cdba3ceb91b46f378f hi846.yaml 55dab9dcb9b1982143b9d63e4e51138c5bffd7c8f4a8e4e122750b3ad9d475446956bc297d9f564819ade4be75a575d06633a064e14f8ba3ef77986783c5f067 imx355.yaml