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