Patch Detail
Show a patch.
GET /api/1.1/patches/26241/?format=api
{ "id": 26241, "url": "https://patchwork.libcamera.org/api/1.1/patches/26241/?format=api", "web_url": "https://patchwork.libcamera.org/patch/26241/", "project": { "id": 1, "url": "https://patchwork.libcamera.org/api/1.1/projects/1/?format=api", "name": "libcamera", "link_name": "libcamera", "list_id": "libcamera_core", "list_email": "libcamera-devel@lists.libcamera.org", "web_url": "", "scm_url": "", "webscm_url": "" }, "msgid": "<aaCz7mW8K3ZTSR83@duo.ucw.cz>", "date": "2026-02-26T20:58:22", "name": "software_isp: Add focus control", "commit_ref": null, "pull_url": null, "state": "new", "archived": false, "hash": "6e693926aebd6c17447224b8583e992a66046199", "submitter": { "id": 49, "url": "https://patchwork.libcamera.org/api/1.1/people/49/?format=api", "name": "Pavel Machek", "email": "pavel@ucw.cz" }, "delegate": null, "mbox": "https://patchwork.libcamera.org/patch/26241/mbox/", "series": [ { "id": 5814, "url": "https://patchwork.libcamera.org/api/1.1/series/5814/?format=api", "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=5814", "date": "2026-02-26T20:58:22", "name": "software_isp: Add focus control", "version": 1, "mbox": "https://patchwork.libcamera.org/series/5814/mbox/" } ], "comments": "https://patchwork.libcamera.org/api/patches/26241/comments/", "check": "pending", "checks": "https://patchwork.libcamera.org/api/patches/26241/checks/", "tags": {}, "headers": { "Return-Path": "<libcamera-devel-bounces@lists.libcamera.org>", "X-Original-To": "parsemail@patchwork.libcamera.org", "Delivered-To": "parsemail@patchwork.libcamera.org", "Received": [ "from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id 0D406BE086\n\tfor <parsemail@patchwork.libcamera.org>;\n\tThu, 26 Feb 2026 20:58:26 +0000 (UTC)", "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 0B57D622E8;\n\tThu, 26 Feb 2026 21:58:25 +0100 (CET)", "from jabberwock.ucw.cz (jabberwock.ucw.cz [46.255.230.98])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 54113620FA\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu, 26 Feb 2026 21:58:23 +0100 (CET)", "by jabberwock.ucw.cz (Postfix, from userid 1017)\n\tid B7289327ECE; Thu, 26 Feb 2026 21:58:22 +0100 (CET)" ], "Authentication-Results": "lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ucw.cz header.i=@ucw.cz header.b=\"r58mr0tm\";\n\tdkim-atps=neutral", "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed; d=ucw.cz; s=gen1;\n\tt=1772139502;\n\th=from:from:reply-to:subject:subject:date:date:message-id:message-id:\n\tto:to:cc:mime-version:mime-version:content-type:content-type;\n\tbh=dtMncSqKXwTOuvLUvlA426puBvunuRhhLP7Rl08ci8k=;\n\tb=r58mr0tmFSe3U843XyVSfO0jkJkeI4eqkuboz7T6ofxaUl1VY8KI5nGDRymBq5Wr7BwlFK\n\tNHJP0Qoo9B3sqA2/78ym52Ivi6+k6aQPg64N1nlMmebKo7/xZIP+L52CkasbQaH39zl6J2\n\tMY0jvraarNEhPvzbhn3uZEQ6n7gD3yQ=", "Date": "Thu, 26 Feb 2026 21:58:22 +0100", "From": "Pavel Machek <pavel@ucw.cz>", "To": "david@ixit.cz, robert.mader@posteo.de, dorota.czaplejewicz@puri.sm,\n\tmartijn@brixit.nl, robert.mader@collabora.com,\n\tlibcamera-devel@lists.libcamera.org, pisa@cmp.felk.cvut.cz,\n\tnekocwd@mainlining.org", "Subject": "[PATCH] software_isp: Add focus control", "Message-ID": "<aaCz7mW8K3ZTSR83@duo.ucw.cz>", "MIME-Version": "1.0", "Content-Type": "multipart/signed; micalg=pgp-sha1;\n\tprotocol=\"application/pgp-signature\"; boundary=\"4DAUdTnpbUUBmE8m\"", "Content-Disposition": "inline", "X-BeenThere": "libcamera-devel@lists.libcamera.org", "X-Mailman-Version": "2.1.29", "Precedence": "list", "List-Id": "<libcamera-devel.lists.libcamera.org>", "List-Unsubscribe": "<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>", "List-Archive": "<https://lists.libcamera.org/pipermail/libcamera-devel/>", "List-Post": "<mailto:libcamera-devel@lists.libcamera.org>", "List-Help": "<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>", "List-Subscribe": "<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>", "Errors-To": "libcamera-devel-bounces@lists.libcamera.org", "Sender": "\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>" }, "content": "From: Vasiliy Doylov <nekocwd@mainlining.org>\n\nThis adds manual focus to software_isp.\n\nSigned-off-by: Vasiliy Doylov <nekocwd@mainlining.org>\nSigned-off-by: Pavel Machek <pavel@ucw.cz>\n\n--\n\nI'm working on millicam -- you can see the results in\ngit@gitlab.com:tui/libcamera.git branch millicam_af_6 . Aim is to\neventually take photos and videos and autofocus is required for\nthat. I'm currently tuning/cleaning that up, but this makes sense on\nits own and it would be nice to get it merged early.\n\nThanks,\n\t\t\t\t\t\t\t\tPavel", "diff": "diff --git a/include/libcamera/internal/software_isp/software_isp.h b/include/libcamera/internal/software_isp/software_isp.h\nindex ad89c9b3c..9e5e05fc0 100644\n--- a/include/libcamera/internal/software_isp/software_isp.h\n+++ b/include/libcamera/internal/software_isp/software_isp.h\n@@ -86,11 +86,11 @@ public:\n \tSignal<FrameBuffer *> outputBufferReady;\n \tSignal<uint32_t, uint32_t> ispStatsReady;\n \tSignal<uint32_t, const ControlList &> metadataReady;\n-\tSignal<const ControlList &> setSensorControls;\n+\tSignal<const ControlList &, const ControlList &> setSensorControls;\n \n private:\n \tvoid saveIspParams();\n-\tvoid setSensorCtrls(const ControlList &sensorControls);\n+\tvoid setSensorCtrls(const ControlList &sensorControls, const ControlList &lensControls);\n \tvoid statsReady(uint32_t frame, uint32_t bufferId);\n \tvoid inputReady(FrameBuffer *input);\n \tvoid outputReady(FrameBuffer *output);\ndiff --git a/include/libcamera/ipa/soft.mojom b/include/libcamera/ipa/soft.mojom\nindex 77328c5fd..e5767532c 100644\n--- a/include/libcamera/ipa/soft.mojom\n+++ b/include/libcamera/ipa/soft.mojom\n@@ -10,6 +10,7 @@ import \"include/libcamera/ipa/core.mojom\";\n \n struct IPAConfigInfo {\n \tlibcamera.ControlInfoMap sensorControls;\n+\tlibcamera.ControlInfoMap lensControls;\n };\n \n interface IPASoftInterface {\n@@ -32,7 +33,7 @@ interface IPASoftInterface {\n };\n \n interface IPASoftEventInterface {\n-\tsetSensorControls(libcamera.ControlList sensorControls);\n+\tsetSensorControls(libcamera.ControlList sensorControls, libcamera.ControlList lensControls);\n \tsetIspParams();\n \tmetadataReady(uint32 frame, libcamera.ControlList metadata);\n };\ndiff --git a/src/ipa/simple/algorithms/af.cpp b/src/ipa/simple/algorithms/af.cpp\nnew file mode 100644\nindex 000000000..b51ed95e4\n--- /dev/null\n+++ b/src/ipa/simple/algorithms/af.cpp\n@@ -0,0 +1,71 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2025 Vasiliy Doylov <nekodevelopper@gmail.com>\n+ *\n+ * Auto focus\n+ */\n+\n+#include \"af.h\"\n+\n+#include <stdint.h>\n+\n+#include <libcamera/base/log.h>\n+\n+#include \"control_ids.h\"\n+\n+namespace libcamera {\n+\n+LOG_DEFINE_CATEGORY(IPASoftAutoFocus)\n+\n+namespace ipa::soft::algorithms {\n+\n+Af::Af()\n+{\n+}\n+\n+int Af::init(IPAContext &context,\n+\t [[maybe_unused]] const YamlObject &tuningData)\n+{\n+\tcontext.ctrlMap[&controls::LensPosition] = ControlInfo(0.0f, 100.0f, 50.0f);\n+\treturn 0;\n+}\n+\n+int Af::configure(IPAContext &context,\n+\t\t [[maybe_unused]] const IPAConfigInfo &configInfo)\n+{\n+\tcontext.activeState.knobs.focus_pos = std::optional<double>();\n+\n+\treturn 0;\n+}\n+\n+void Af::queueRequest([[maybe_unused]] typename Module::Context &context,\n+\t\t [[maybe_unused]] const uint32_t frame,\n+\t\t [[maybe_unused]] typename Module::FrameContext &frameContext,\n+\t\t const ControlList &controls)\n+{\n+\tconst auto &focus_pos = controls.get(controls::LensPosition);\n+\tif (focus_pos.has_value()) {\n+\t\tcontext.activeState.knobs.focus_pos = focus_pos;\n+\t\tLOG(IPASoftAutoFocus, Debug) << \"Setting focus position to \" << focus_pos.value();\n+\t}\n+}\n+\n+void Af::updateFocus([[maybe_unused]] IPAContext &context, [[maybe_unused]] IPAFrameContext &frameContext, [[maybe_unused]] double exposureMSV)\n+{\n+\tframeContext.lens.focus_pos = context.activeState.knobs.focus_pos.value_or(50.0) / 100.0 * (context.configuration.focus.focus_max - context.configuration.focus.focus_min);\n+}\n+\n+void Af::process([[maybe_unused]] IPAContext &context,\n+\t\t [[maybe_unused]] const uint32_t frame,\n+\t\t [[maybe_unused]] IPAFrameContext &frameContext,\n+\t\t [[maybe_unused]] const SwIspStats *stats,\n+\t\t [[maybe_unused]] ControlList &metadata)\n+{\n+\tupdateFocus(context, frameContext, 0);\n+}\n+\n+REGISTER_IPA_ALGORITHM(Af, \"Af\")\n+\n+} /* namespace ipa::soft::algorithms */\n+\n+} /* namespace libcamera */\ndiff --git a/src/ipa/simple/algorithms/af.h b/src/ipa/simple/algorithms/af.h\nnew file mode 100644\nindex 000000000..a575ef102\n--- /dev/null\n+++ b/src/ipa/simple/algorithms/af.h\n@@ -0,0 +1,40 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2025 Vasiliy Doylov <nekodevelopper@gmail.com>\n+ *\n+ * Auto focus\n+ */\n+\n+#pragma once\n+\n+#include \"algorithm.h\"\n+\n+namespace libcamera {\n+\n+namespace ipa::soft::algorithms {\n+\n+class Af : public Algorithm\n+{\n+public:\n+\tAf();\n+\t~Af() = default;\n+\n+\tint init(IPAContext &context, const YamlObject &tuningData) override;\n+\tint configure(IPAContext &context, const IPAConfigInfo &configInfo) override;\n+\tvoid queueRequest(typename Module::Context &context,\n+\t\t\t const uint32_t frame,\n+\t\t\t typename Module::FrameContext &frameContext,\n+\t\t\t const ControlList &controls)\n+\t\toverride;\n+\tvoid process(IPAContext &context, const uint32_t frame,\n+\t\t IPAFrameContext &frameContext,\n+\t\t const SwIspStats *stats,\n+\t\t ControlList &metadata) override;\n+\n+private:\n+\tvoid updateFocus(IPAContext &context, IPAFrameContext &frameContext, double focus);\n+};\n+\n+} /* namespace ipa::soft::algorithms */\n+\n+} /* namespace libcamera */\ndiff --git a/src/ipa/simple/algorithms/meson.build b/src/ipa/simple/algorithms/meson.build\nindex 73c637220..8950b008f 100644\n--- a/src/ipa/simple/algorithms/meson.build\n+++ b/src/ipa/simple/algorithms/meson.build\n@@ -6,4 +6,5 @@ soft_simple_ipa_algorithms = files([\n 'agc.cpp',\n 'blc.cpp',\n 'ccm.cpp',\n+ 'af.cpp',\n ])\ndiff --git a/src/ipa/simple/data/uncalibrated.yaml b/src/ipa/simple/data/uncalibrated.yaml\nindex fc90ca526..ede277d1d 100644\n--- a/src/ipa/simple/data/uncalibrated.yaml\n+++ b/src/ipa/simple/data/uncalibrated.yaml\n@@ -16,4 +16,5 @@ algorithms:\n # 0, 0, 1]\n - Adjust:\n - Agc:\n+ - Af:\n ...\ndiff --git a/src/ipa/simple/ipa_context.h b/src/ipa/simple/ipa_context.h\nindex 34f7403a4..a436fc76a 100644\n--- a/src/ipa/simple/ipa_context.h\n+++ b/src/ipa/simple/ipa_context.h\n@@ -33,6 +33,9 @@ struct IPASessionConfiguration {\n \tstruct {\n \t\tstd::optional<uint8_t> level;\n \t} black;\n+\tstruct {\n+\t\tint32_t focus_min, focus_max;\n+\t} focus;\n };\n \n struct IPAActiveState {\n@@ -60,6 +63,8 @@ struct IPAActiveState {\n \t\t/* 0..2 range, 1.0 = normal */\n \t\tstd::optional<float> contrast;\n \t\tstd::optional<float> saturation;\n+\t\t/* 0..100 range, 50.0 = normal */\n+\t\tstd::optional<double> focus_pos;\n \t} knobs;\n };\n \n@@ -71,6 +76,10 @@ struct IPAFrameContext : public FrameContext {\n \t\tdouble gain;\n \t} sensor;\n \n+\tstruct {\n+\t\tint32_t focus_pos;\n+\t} lens;\n+\n \tstruct {\n \t\tdouble red;\n \t\tdouble blue;\ndiff --git a/src/ipa/simple/soft_simple.cpp b/src/ipa/simple/soft_simple.cpp\nindex 6bef597c8..4f63fa3b4 100644\n--- a/src/ipa/simple/soft_simple.cpp\n+++ b/src/ipa/simple/soft_simple.cpp\n@@ -78,6 +78,7 @@ private:\n \tSwIspStats *stats_;\n \tstd::unique_ptr<CameraSensorHelper> camHelper_;\n \tControlInfoMap sensorInfoMap_;\n+\tControlInfoMap lensInfoMap_;\n \n \t/* Local parameter storage */\n \tstruct IPAContext context_;\n@@ -202,6 +203,7 @@ int IPASoftSimple::init(const IPASettings &settings,\n int IPASoftSimple::configure(const IPAConfigInfo &configInfo)\n {\n \tsensorInfoMap_ = configInfo.sensorControls;\n+\tlensInfoMap_ = configInfo.lensControls;\n \n \tconst ControlInfo &exposureInfo = sensorInfoMap_.find(V4L2_CID_EXPOSURE)->second;\n \tconst ControlInfo &gainInfo = sensorInfoMap_.find(V4L2_CID_ANALOGUE_GAIN)->second;\n@@ -211,6 +213,17 @@ int IPASoftSimple::configure(const IPAConfigInfo &configInfo)\n \tcontext_.activeState = {};\n \tcontext_.frameContexts.clear();\n \n+\tif (lensInfoMap_.empty()) {\n+\t\tLOG(IPASoft, Warning) << \"No camera leans found! Focus control disabled.\";\n+\t\tcontext_.configuration.focus.focus_min = 0;\n+\t\tcontext_.configuration.focus.focus_max = 0;\n+\t} else {\n+\t\tconst ControlInfo &lensInfo = lensInfoMap_.find(V4L2_CID_FOCUS_ABSOLUTE)->second;\n+\t\tcontext_.configuration.focus.focus_min = lensInfo.min().get<int32_t>();\n+\t\tcontext_.configuration.focus.focus_max = lensInfo.max().get<int32_t>();\n+\t\tLOG(IPASoft, Warning) << \"Camera leans found! Focus: \" << context_.configuration.focus.focus_min << \"-\" << context_.configuration.focus.focus_max;\n+\t}\n+\n \tcontext_.configuration.agc.lineDuration =\n \t\tcontext_.sensorInfo.minLineLength * 1.0s / context_.sensorInfo.pixelRate;\n \tcontext_.configuration.agc.exposureMin = exposureInfo.min().get<int32_t>();\n@@ -325,7 +338,10 @@ void IPASoftSimple::processStats(const uint32_t frame,\n \tctrls.set(V4L2_CID_ANALOGUE_GAIN,\n \t\t static_cast<int32_t>(camHelper_ ? camHelper_->gainCode(againNew) : againNew));\n \n-\tsetSensorControls.emit(ctrls);\n+\tControlList lens_ctrls(lensInfoMap_);\n+\tlens_ctrls.set(V4L2_CID_FOCUS_ABSOLUTE, frameContext.lens.focus_pos);\n+\n+\tsetSensorControls.emit(ctrls, lens_ctrls);\n }\n \n std::string IPASoftSimple::logPrefix() const\ndiff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp\nindex 4a0b9f58d..4079f0973 100644\n--- a/src/libcamera/pipeline/simple/simple.cpp\n+++ b/src/libcamera/pipeline/simple/simple.cpp\n@@ -33,6 +33,7 @@\n #include <libcamera/stream.h>\n \n #include \"libcamera/internal/camera.h\"\n+#include \"libcamera/internal/camera_lens.h\"\n #include \"libcamera/internal/camera_manager.h\"\n #include \"libcamera/internal/camera_sensor.h\"\n #include \"libcamera/internal/camera_sensor_properties.h\"\n@@ -48,6 +49,8 @@\n #include \"libcamera/internal/v4l2_subdevice.h\"\n #include \"libcamera/internal/v4l2_videodevice.h\"\n \n+#include \"libcamera/controls.h\"\n+\n namespace libcamera {\n \n LOG_DEFINE_CATEGORY(SimplePipeline)\n@@ -371,7 +374,7 @@ private:\n \n \tvoid ispStatsReady(uint32_t frame, uint32_t bufferId);\n \tvoid metadataReady(uint32_t frame, const ControlList &metadata);\n-\tvoid setSensorControls(const ControlList &sensorControls);\n+\tvoid setSensorControls(const ControlList &sensorControls, const ControlList &lensControls);\n };\n \n class SimpleCameraConfiguration : public CameraConfiguration\n@@ -1041,7 +1044,7 @@ void SimpleCameraData::metadataReady(uint32_t frame, const ControlList &metadata\n \ttryCompleteRequest(info->request);\n }\n \n-void SimpleCameraData::setSensorControls(const ControlList &sensorControls)\n+void SimpleCameraData::setSensorControls(const ControlList &sensorControls, const ControlList &lensControls)\n {\n \tdelayedCtrls_->push(sensorControls);\n \t/*\n@@ -1052,10 +1055,21 @@ void SimpleCameraData::setSensorControls(const ControlList &sensorControls)\n \t * but it also bypasses delayedCtrls_, creating AGC regulation issues.\n \t * Both problems should be fixed.\n \t */\n-\tif (!frameStartEmitter_) {\n-\t\tControlList ctrls(sensorControls);\n-\t\tsensor_->setControls(&ctrls);\n-\t}\n+\tif (frameStartEmitter_)\n+\t\treturn;\n+\n+\tControlList ctrls(sensorControls);\n+\tsensor_->setControls(&ctrls);\n+\n+\tCameraLens *focusLens = sensor_->focusLens();\n+\tif (!focusLens)\n+\t\treturn;\n+\n+\tif (!lensControls.contains(V4L2_CID_FOCUS_ABSOLUTE))\n+\t\treturn;\n+\n+\tconst ControlValue &focusValue = lensControls.get(V4L2_CID_FOCUS_ABSOLUTE);\n+\tfocusLens->setFocusPosition(focusValue.get<int32_t>());\n }\n \n /* Retrieve all source pads connected to a sink pad through active routes. */\n@@ -1593,6 +1607,10 @@ int SimplePipelineHandler::configure(Camera *camera, CameraConfiguration *c)\n \t} else {\n \t\tipa::soft::IPAConfigInfo configInfo;\n \t\tconfigInfo.sensorControls = data->sensor_->controls();\n+\t\tif (data->sensor_->focusLens() != nullptr)\n+\t\t\tconfigInfo.lensControls = data->sensor_->focusLens()->controls();\n+\t\telse\n+\t\t\tconfigInfo.lensControls = ControlInfoMap();\n \t\treturn data->swIsp_->configure(inputCfg, outputCfgs, configInfo);\n \t}\n }\ndiff --git a/src/libcamera/software_isp/software_isp.cpp b/src/libcamera/software_isp/software_isp.cpp\nindex c2baaf0bf..df0c5e054 100644\n--- a/src/libcamera/software_isp/software_isp.cpp\n+++ b/src/libcamera/software_isp/software_isp.cpp\n@@ -408,9 +408,9 @@ void SoftwareIsp::saveIspParams()\n \tdebayerParams_ = *sharedParams_;\n }\n \n-void SoftwareIsp::setSensorCtrls(const ControlList &sensorControls)\n+void SoftwareIsp::setSensorCtrls(const ControlList &sensorControls, const ControlList &lensControls)\n {\n-\tsetSensorControls.emit(sensorControls);\n+\tsetSensorControls.emit(sensorControls, lensControls);\n }\n \n void SoftwareIsp::statsReady(uint32_t frame, uint32_t bufferId)\n", "prefixes": [] }