[{"id":34311,"web_url":"https://patchwork.libcamera.org/comment/34311/","msgid":"<174784973965.14042.12122332914232241037@calcite>","date":"2025-05-21T17:48:59","subject":"Re: [PATCH 1/1] libcamera: camera: Fix up the AeEnable control\n\tduring Camera::start()","submitter":{"id":17,"url":"https://patchwork.libcamera.org/api/people/17/","name":"Paul Elder","email":"paul.elder@ideasonboard.com"},"content":"Quoting David Plowman (2025-05-21 14:53:27)\n> In Camera::queueRequest() the control list is updated transparently by\n> converting AeEnable into ExposureTimeMode and AnalogueGainMode\n> controls.\n> \n> However, this was not happening during Camera::start(), meaning that\n> setting AeEnable there was having no effect. It should behave the same\n> here too.\n> \n> Fixes: 7abd4139051c (\"libcamera: camera: Pre-process AeEnable control\")\n> Signed-off-by: David Plowman <david.plowman@raspberrypi.com>\n\nLooks good to me.\n\nReviewed-by: Paul Elder <paul.elder@ideasonboard.com>\n\n> ---\n>  include/libcamera/camera.h |  2 ++\n>  src/libcamera/camera.cpp   | 57 +++++++++++++++++++++++++-------------\n>  2 files changed, 40 insertions(+), 19 deletions(-)\n> \n> diff --git a/include/libcamera/camera.h b/include/libcamera/camera.h\n> index 94cee7bd..b24a2974 100644\n> --- a/include/libcamera/camera.h\n> +++ b/include/libcamera/camera.h\n> @@ -165,6 +165,8 @@ private:\n>         friend class FrameBufferAllocator;\n>         int exportFrameBuffers(Stream *stream,\n>                                std::vector<std::unique_ptr<FrameBuffer>> *buffers);\n> +\n> +       void patchControlList(ControlList &controls);\n>  };\n>  \n>  } /* namespace libcamera */\n> diff --git a/src/libcamera/camera.cpp b/src/libcamera/camera.cpp\n> index c180a5fd..23b9207a 100644\n> --- a/src/libcamera/camera.cpp\n> +++ b/src/libcamera/camera.cpp\n> @@ -1266,6 +1266,33 @@ std::unique_ptr<Request> Camera::createRequest(uint64_t cookie)\n>         return request;\n>  }\n>  \n> +/**\n> + * \\brief Patch a control list that contains the AeEnable control\n> + * \\param[inout] controls The control list to be patched\n> + *\n> + * The control list is patched in place, turning the AeEnable control into\n> + * the equivalent ExposureTimeMode/AnalogueGainMode controls.\n> + */\n> +void Camera::patchControlList(ControlList &controls)\n> +{\n> +       const auto &aeEnable = controls.get(controls::AeEnable);\n> +       if (aeEnable) {\n> +               if (_d()->controlInfo_.count(controls::AnalogueGainMode.id()) &&\n> +                   !controls.contains(controls::AnalogueGainMode.id())) {\n> +                       controls.set(controls::AnalogueGainMode,\n> +                                    *aeEnable ? controls::AnalogueGainModeAuto\n> +                                              : controls::AnalogueGainModeManual);\n> +               }\n> +\n> +               if (_d()->controlInfo_.count(controls::ExposureTimeMode.id()) &&\n> +                   !controls.contains(controls::ExposureTimeMode.id())) {\n> +                       controls.set(controls::ExposureTimeMode,\n> +                                    *aeEnable ? controls::ExposureTimeModeAuto\n> +                                              : controls::ExposureTimeModeManual);\n> +               }\n> +       }\n> +}\n> +\n>  /**\n>   * \\brief Queue a request to the camera\n>   * \\param[in] request The request to queue to the camera\n> @@ -1329,23 +1356,7 @@ int Camera::queueRequest(Request *request)\n>         }\n>  \n>         /* Pre-process AeEnable. */\n> -       ControlList &controls = request->controls();\n> -       const auto &aeEnable = controls.get(controls::AeEnable);\n> -       if (aeEnable) {\n> -               if (_d()->controlInfo_.count(controls::AnalogueGainMode.id()) &&\n> -                   !controls.contains(controls::AnalogueGainMode.id())) {\n> -                       controls.set(controls::AnalogueGainMode,\n> -                                    *aeEnable ? controls::AnalogueGainModeAuto\n> -                                              : controls::AnalogueGainModeManual);\n> -               }\n> -\n> -               if (_d()->controlInfo_.count(controls::ExposureTimeMode.id()) &&\n> -                   !controls.contains(controls::ExposureTimeMode.id())) {\n> -                       controls.set(controls::ExposureTimeMode,\n> -                                    *aeEnable ? controls::ExposureTimeModeAuto\n> -                                              : controls::ExposureTimeModeManual);\n> -               }\n> -       }\n> +       patchControlList(request->controls());\n>  \n>         d->pipe_->invokeMethod(&PipelineHandler::queueRequest,\n>                                ConnectionTypeQueued, request);\n> @@ -1383,8 +1394,16 @@ int Camera::start(const ControlList *controls)\n>  \n>         ASSERT(d->requestSequence_ == 0);\n>  \n> -       ret = d->pipe_->invokeMethod(&PipelineHandler::start,\n> -                                    ConnectionTypeBlocking, this, controls);\n> +       if (controls) {\n> +               ControlList copy(*controls);\n> +               patchControlList(copy);\n> +               ret = d->pipe_->invokeMethod(&PipelineHandler::start,\n> +                                            ConnectionTypeBlocking, this, &copy);\n> +       }\n> +       else\n> +               ret = d->pipe_->invokeMethod(&PipelineHandler::start,\n> +                                            ConnectionTypeBlocking, this, nullptr);\n> +\n>         if (ret)\n>                 return ret;\n>  \n> -- \n> 2.39.5\n>","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 1D403BD78E\n\tfor <parsemail@patchwork.libcamera.org>;\n\tWed, 21 May 2025 17:49:05 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 23B6268D92;\n\tWed, 21 May 2025 19:49:04 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 0989C68C91\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 21 May 2025 19:49:02 +0200 (CEST)","from pyrite.rasen.tech (unknown [149.232.183.6])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 78A266AF;\n\tWed, 21 May 2025 19:48:40 +0200 (CEST)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"VQaJ8F22\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1747849720;\n\tbh=sJ/DpW9aqrfdbAW2LxY/TSqVSG6549yZfezfv54IAEw=;\n\th=In-Reply-To:References:Subject:From:Cc:To:Date:From;\n\tb=VQaJ8F22DJ4agw452izeSW/kwdasr2iQgV3heVD2+ubxrxmbso1PLRsZ+XuHXDanY\n\tuG0+c1sugID2Ord2tSALSdmfu0wD1Qd0mDBhzeAJxGgkClLFFrfTqN39TvzmxUucx6\n\tY975HGk+amo0hBPiRobEEXFkIBJFkBTvoSgn48O4=","Content-Type":"text/plain; charset=\"utf-8\"","MIME-Version":"1.0","Content-Transfer-Encoding":"quoted-printable","In-Reply-To":"<20250521125327.6378-2-david.plowman@raspberrypi.com>","References":"<20250521125327.6378-1-david.plowman@raspberrypi.com>\n\t<20250521125327.6378-2-david.plowman@raspberrypi.com>","Subject":"Re: [PATCH 1/1] libcamera: camera: Fix up the AeEnable control\n\tduring Camera::start()","From":"Paul Elder <paul.elder@ideasonboard.com>","Cc":"David Plowman <david.plowman@raspberrypi.com>","To":"David Plowman <david.plowman@raspberrypi.com>,\n\tlibcamera-devel@lists.libcamera.org","Date":"Wed, 21 May 2025 19:48:59 +0200","Message-ID":"<174784973965.14042.12122332914232241037@calcite>","User-Agent":"alot/0.10","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>"}},{"id":34844,"web_url":"https://patchwork.libcamera.org/comment/34844/","msgid":"<72fd2993-9701-463f-b960-bcedd4974f13@ideasonboard.com>","date":"2025-07-10T11:02:48","subject":"Re: [PATCH 1/1] libcamera: camera: Fix up the AeEnable control\n\tduring Camera::start()","submitter":{"id":216,"url":"https://patchwork.libcamera.org/api/people/216/","name":"Barnabás Pőcze","email":"barnabas.pocze@ideasonboard.com"},"content":"Hi\n\n2025. 05. 21. 14:53 keltezéssel, David Plowman írta:\n> In Camera::queueRequest() the control list is updated transparently by\n> converting AeEnable into ExposureTimeMode and AnalogueGainMode\n> controls.\n> \n> However, this was not happening during Camera::start(), meaning that\n> setting AeEnable there was having no effect. It should behave the same\n> here too.\n> \n> Fixes: 7abd4139051c (\"libcamera: camera: Pre-process AeEnable control\")\n> Signed-off-by: David Plowman <david.plowman@raspberrypi.com>\n> ---\n>   include/libcamera/camera.h |  2 ++\n>   src/libcamera/camera.cpp   | 57 +++++++++++++++++++++++++-------------\n>   2 files changed, 40 insertions(+), 19 deletions(-)\n> \n> diff --git a/include/libcamera/camera.h b/include/libcamera/camera.h\n> index 94cee7bd..b24a2974 100644\n> --- a/include/libcamera/camera.h\n> +++ b/include/libcamera/camera.h\n> @@ -165,6 +165,8 @@ private:\n>   \tfriend class FrameBufferAllocator;\n>   \tint exportFrameBuffers(Stream *stream,\n>   \t\t\t       std::vector<std::unique_ptr<FrameBuffer>> *buffers);\n> +\n> +\tvoid patchControlList(ControlList &controls);\n\nMaybe it's just me, but my preference is usually to hide such internal functions\ncompletely, i.e. in an anonymous namespace in the source file it is used.\n\n\n>   };\n>   \n>   } /* namespace libcamera */\n> diff --git a/src/libcamera/camera.cpp b/src/libcamera/camera.cpp\n> index c180a5fd..23b9207a 100644\n> --- a/src/libcamera/camera.cpp\n> +++ b/src/libcamera/camera.cpp\n> @@ -1266,6 +1266,33 @@ std::unique_ptr<Request> Camera::createRequest(uint64_t cookie)\n>   \treturn request;\n>   }\n>   \n> +/**\n> + * \\brief Patch a control list that contains the AeEnable control\n> + * \\param[inout] controls The control list to be patched\n> + *\n> + * The control list is patched in place, turning the AeEnable control into\n> + * the equivalent ExposureTimeMode/AnalogueGainMode controls.\n> + */\n> +void Camera::patchControlList(ControlList &controls)\n> +{\n> +\tconst auto &aeEnable = controls.get(controls::AeEnable);\n> +\tif (aeEnable) {\n> +\t\tif (_d()->controlInfo_.count(controls::AnalogueGainMode.id()) &&\n> +\t\t    !controls.contains(controls::AnalogueGainMode.id())) {\n> +\t\t\tcontrols.set(controls::AnalogueGainMode,\n> +\t\t\t\t     *aeEnable ? controls::AnalogueGainModeAuto\n> +\t\t\t\t\t       : controls::AnalogueGainModeManual);\n> +\t\t}\n> +\n> +\t\tif (_d()->controlInfo_.count(controls::ExposureTimeMode.id()) &&\n> +\t\t    !controls.contains(controls::ExposureTimeMode.id())) {\n> +\t\t\tcontrols.set(controls::ExposureTimeMode,\n> +\t\t\t\t     *aeEnable ? controls::ExposureTimeModeAuto\n> +\t\t\t\t\t       : controls::ExposureTimeModeManual);\n> +\t\t}\n> +\t}\n> +}\n> +\n>   /**\n>    * \\brief Queue a request to the camera\n>    * \\param[in] request The request to queue to the camera\n> @@ -1329,23 +1356,7 @@ int Camera::queueRequest(Request *request)\n>   \t}\n>   \n>   \t/* Pre-process AeEnable. */\n> -\tControlList &controls = request->controls();\n> -\tconst auto &aeEnable = controls.get(controls::AeEnable);\n> -\tif (aeEnable) {\n> -\t\tif (_d()->controlInfo_.count(controls::AnalogueGainMode.id()) &&\n> -\t\t    !controls.contains(controls::AnalogueGainMode.id())) {\n> -\t\t\tcontrols.set(controls::AnalogueGainMode,\n> -\t\t\t\t     *aeEnable ? controls::AnalogueGainModeAuto\n> -\t\t\t\t\t       : controls::AnalogueGainModeManual);\n> -\t\t}\n> -\n> -\t\tif (_d()->controlInfo_.count(controls::ExposureTimeMode.id()) &&\n> -\t\t    !controls.contains(controls::ExposureTimeMode.id())) {\n> -\t\t\tcontrols.set(controls::ExposureTimeMode,\n> -\t\t\t\t     *aeEnable ? controls::ExposureTimeModeAuto\n> -\t\t\t\t\t       : controls::ExposureTimeModeManual);\n> -\t\t}\n> -\t}\n> +\tpatchControlList(request->controls());\n>   \n>   \td->pipe_->invokeMethod(&PipelineHandler::queueRequest,\n>   \t\t\t       ConnectionTypeQueued, request);\n> @@ -1383,8 +1394,16 @@ int Camera::start(const ControlList *controls)\n>   \n>   \tASSERT(d->requestSequence_ == 0);\n>   \n> -\tret = d->pipe_->invokeMethod(&PipelineHandler::start,\n> -\t\t\t\t     ConnectionTypeBlocking, this, controls);\n> +\tif (controls) {\n> +\t\tControlList copy(*controls);\n\n\nI would like to present an alternative approach that generates a separate\n\"patch\" `ControlList` and then applies it to the main one:\n\n\ndiff --git a/src/libcamera/camera.cpp b/src/libcamera/camera.cpp\nindex 1075474ba..520d72184 100644\n--- a/src/libcamera/camera.cpp\n+++ b/src/libcamera/camera.cpp\n@@ -1324,6 +1335,38 @@ std::unique_ptr<Request> Camera::createRequest(uint64_t cookie)\n         return request;\n  }\n  \n+namespace {\n+\n+[[nodiscard]]\n+ControlList patchControlList(const ControlInfoMap &controlInfo, const ControlList *controls)\n+{\n+       ControlList patch;\n+\n+       if (!controls)\n+               return patch;\n+\n+       const auto &aeEnable = controls->get(controls::AeEnable);\n+       if (aeEnable) {\n+               if (controlInfo.count(controls::AnalogueGainMode.id()) &&\n+                   !controls->contains(controls::AnalogueGainMode.id())) {\n+                       patch.set(controls::AnalogueGainMode,\n+                                 *aeEnable ? controls::AnalogueGainModeAuto\n+                                           : controls::AnalogueGainModeManual);\n+               }\n+\n+               if (controlInfo.count(controls::ExposureTimeMode.id()) &&\n+                   !controls->contains(controls::ExposureTimeMode.id())) {\n+                       patch.set(controls::ExposureTimeMode,\n+                                 *aeEnable ? controls::ExposureTimeModeAuto\n+                                           : controls::ExposureTimeModeManual);\n+               }\n+       }\n+\n+       return patch;\n+}\n+\n+} /* namespace */\n+\n  /**\n   * \\brief Queue a request to the camera\n   * \\param[in] request The request to queue to the camera\n@@ -1387,23 +1430,7 @@ int Camera::queueRequest(Request *request)\n         }\n  \n         /* Pre-process AeEnable. */\n-       ControlList &controls = request->controls();\n-       const auto &aeEnable = controls.get(controls::AeEnable);\n-       if (aeEnable) {\n-               if (_d()->controlInfo_.count(controls::AnalogueGainMode.id()) &&\n-                   !controls.contains(controls::AnalogueGainMode.id())) {\n-                       controls.set(controls::AnalogueGainMode,\n-                                    *aeEnable ? controls::AnalogueGainModeAuto\n-                                              : controls::AnalogueGainModeManual);\n-               }\n-\n-               if (_d()->controlInfo_.count(controls::ExposureTimeMode.id()) &&\n-                   !controls.contains(controls::ExposureTimeMode.id())) {\n-                       controls.set(controls::ExposureTimeMode,\n-                                    *aeEnable ? controls::ExposureTimeModeAuto\n-                                              : controls::ExposureTimeModeManual);\n-               }\n-       }\n+       request->controls().merge(patchControlList(_d()->controlInfo_, &request->controls()));\n  \n         d->pipe_->invokeMethod(&PipelineHandler::queueRequest,\n                                ConnectionTypeQueued, request);\n@@ -1441,8 +1468,16 @@ int Camera::start(const ControlList *controls)\n  \n         ASSERT(d->requestSequence_ == 0);\n  \n-       ret = d->pipe_->invokeMethod(&PipelineHandler::start,\n-                                    ConnectionTypeBlocking, this, controls);\n+       auto patch = patchControlList(d->controlInfo_, controls);\n+       if (!patch.empty()) {\n+               patch.merge(*controls);\n+               ret = d->pipe_->invokeMethod(&PipelineHandler::start,\n+                                            ConnectionTypeBlocking, this, &patch);\n+       } else {\n+               ret = d->pipe_->invokeMethod(&PipelineHandler::start,\n+                                            ConnectionTypeBlocking, this, controls);\n+       }\n+\n         if (ret)\n                 return ret;\n\n\nThe above also needs an rvalue overload for ControlList::merge, which can be implemented\nas follows:\n\n\ndiff --git a/src/libcamera/controls.cpp b/src/libcamera/controls.cpp\nindex 1a618801a..7d9778217 100644\n--- a/src/libcamera/controls.cpp\n+++ b/src/libcamera/controls.cpp\n@@ -1192,6 +1224,45 @@ void ControlList::merge(const ControlList &source, MergePolicy policy)\n         }\n  }\n  \n+/**\n+ * \\brief Merge the \\a source into the ControlList\n+ * \\param[in] source The ControlList to merge into this object\n+ * \\param[in] policy Controls if existing elements in *this shall be\n+ * overwritten\n+ *\n+ * Merging two control lists moves elements from the \\a source and inserts\n+ * them in *this. If the \\a source contains elements whose key is already\n+ * present in *this, then those elements are only overwritten if\n+ * \\a policy is MergePolicy::OverwriteExisting.\n+ *\n+ * Only control lists created from the same ControlIdMap or ControlInfoMap may\n+ * be merged. Attempting to do otherwise results in undefined behaviour.\n+ */\n+void ControlList::merge(ControlList &&source, MergePolicy policy)\n+{\n+       /**\n+        * \\todo ASSERT that the current and source ControlList are derived\n+        * from a compatible ControlIdMap, to prevent undefined behaviour due to\n+        * id collisions.\n+        *\n+        * This can not currently be a direct pointer comparison due to the\n+        * duplication of the ControlIdMaps in the isolated IPA use cases.\n+        * Furthermore, manually checking each entry of the id map is identical\n+        * is expensive.\n+        * See https://bugs.libcamera.org/show_bug.cgi?id=31 for further details\n+        */\n+\n+       switch (policy) {\n+       case MergePolicy::KeepExisting:\n+               controls_.merge(source.controls_);\n+               break;\n+       case MergePolicy::OverwriteExisting:\n+               source.controls_.merge(controls_);\n+               controls_.swap(source.controls_);\n+               break;\n+       }\n+}\n+\n\n\nThe slight advantage of the above is that it only makes a copy if it is absolutely\nnecessary. I don't know if anything better can be done with the current design\n(i.e. `Camera::start(const ControlList *)`) wrt. copying.\n\nIn any case, since the extra copy is only present in `Camera::start()`, and only\nif a control list is supplied (although recently pipewire has started supplying\nan initial control list), so I don't think that it is a too significant issue.\n\n\nReviewed-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>\n\n\nRegards,\nBarnabás Pőcze\n\n> +\t\tpatchControlList(copy);\n> +\t\tret = d->pipe_->invokeMethod(&PipelineHandler::start,\n> +\t\t\t\t\t     ConnectionTypeBlocking, this, &copy);\n> +\t}\n> +\telse\n> +\t\tret = d->pipe_->invokeMethod(&PipelineHandler::start,\n> +\t\t\t\t\t     ConnectionTypeBlocking, this, nullptr);\n> +\n>   \tif (ret)\n>   \t\treturn ret;\n>","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 8C4EAC3237\n\tfor <parsemail@patchwork.libcamera.org>;\n\tThu, 10 Jul 2025 11:02:54 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id A5AB868EFF;\n\tThu, 10 Jul 2025 13:02:53 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id D5A1968EE7\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu, 10 Jul 2025 13:02:51 +0200 (CEST)","from [192.168.33.18] (185.221.140.39.nat.pool.zt.hu\n\t[185.221.140.39])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id BCEA47E4;\n\tThu, 10 Jul 2025 13:02:22 +0200 (CEST)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"Gv5WlUci\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1752145343;\n\tbh=6iia4qUlAE+RJnUR6Oa4VwrQHR8zkM9muRUKA+oPaEo=;\n\th=Date:Subject:To:References:From:In-Reply-To:From;\n\tb=Gv5WlUcipPk2QjMK1VVQkBeOwqsnkUH0NROjzuyT5eXLCs67nrzm8BW0x8YTA0B/g\n\tFiKEXyUeu7pa/iqzt24vOSQM0/+G5mdoVbVuWSuUHnUkOpYYVHYbUe2bs2H6pkM358\n\tDpcXyA3f2nLhU5bq+0qlx03Ulorc7Q3yCsXvFsFo=","Message-ID":"<72fd2993-9701-463f-b960-bcedd4974f13@ideasonboard.com>","Date":"Thu, 10 Jul 2025 13:02:48 +0200","MIME-Version":"1.0","User-Agent":"Mozilla Thunderbird","Subject":"Re: [PATCH 1/1] libcamera: camera: Fix up the AeEnable control\n\tduring Camera::start()","To":"David Plowman <david.plowman@raspberrypi.com>,\n\tlibcamera-devel@lists.libcamera.org","References":"<20250521125327.6378-1-david.plowman@raspberrypi.com>\n\t<20250521125327.6378-2-david.plowman@raspberrypi.com>","From":"=?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= <barnabas.pocze@ideasonboard.com>","Content-Language":"en-US, hu-HU","In-Reply-To":"<20250521125327.6378-2-david.plowman@raspberrypi.com>","Content-Type":"text/plain; charset=UTF-8; format=flowed","Content-Transfer-Encoding":"8bit","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>"}},{"id":34993,"web_url":"https://patchwork.libcamera.org/comment/34993/","msgid":"<175312485079.50296.4585976668881223551@ping.linuxembedded.co.uk>","date":"2025-07-21T19:07:30","subject":"Re: [PATCH 1/1] libcamera: camera: Fix up the AeEnable control\n\tduring Camera::start()","submitter":{"id":4,"url":"https://patchwork.libcamera.org/api/people/4/","name":"Kieran Bingham","email":"kieran.bingham@ideasonboard.com"},"content":"Quoting Barnabás Pőcze (2025-07-10 12:02:48)\n> Hi\n> \n> 2025. 05. 21. 14:53 keltezéssel, David Plowman írta:\n> > In Camera::queueRequest() the control list is updated transparently by\n> > converting AeEnable into ExposureTimeMode and AnalogueGainMode\n> > controls.\n> > \n> > However, this was not happening during Camera::start(), meaning that\n> > setting AeEnable there was having no effect. It should behave the same\n> > here too.\n> > \n> > Fixes: 7abd4139051c (\"libcamera: camera: Pre-process AeEnable control\")\n> > Signed-off-by: David Plowman <david.plowman@raspberrypi.com>\n> > ---\n> >   include/libcamera/camera.h |  2 ++\n> >   src/libcamera/camera.cpp   | 57 +++++++++++++++++++++++++-------------\n> >   2 files changed, 40 insertions(+), 19 deletions(-)\n> > \n> > diff --git a/include/libcamera/camera.h b/include/libcamera/camera.h\n> > index 94cee7bd..b24a2974 100644\n> > --- a/include/libcamera/camera.h\n> > +++ b/include/libcamera/camera.h\n> > @@ -165,6 +165,8 @@ private:\n> >       friend class FrameBufferAllocator;\n> >       int exportFrameBuffers(Stream *stream,\n> >                              std::vector<std::unique_ptr<FrameBuffer>> *buffers);\n> > +\n> > +     void patchControlList(ControlList &controls);\n> \n> Maybe it's just me, but my preference is usually to hide such internal functions\n> completely, i.e. in an anonymous namespace in the source file it is used.\n> \n> \n> >   };\n> >   \n> >   } /* namespace libcamera */\n> > diff --git a/src/libcamera/camera.cpp b/src/libcamera/camera.cpp\n> > index c180a5fd..23b9207a 100644\n> > --- a/src/libcamera/camera.cpp\n> > +++ b/src/libcamera/camera.cpp\n> > @@ -1266,6 +1266,33 @@ std::unique_ptr<Request> Camera::createRequest(uint64_t cookie)\n> >       return request;\n> >   }\n> >   \n> > +/**\n> > + * \\brief Patch a control list that contains the AeEnable control\n> > + * \\param[inout] controls The control list to be patched\n> > + *\n> > + * The control list is patched in place, turning the AeEnable control into\n> > + * the equivalent ExposureTimeMode/AnalogueGainMode controls.\n> > + */\n> > +void Camera::patchControlList(ControlList &controls)\n> > +{\n> > +     const auto &aeEnable = controls.get(controls::AeEnable);\n> > +     if (aeEnable) {\n> > +             if (_d()->controlInfo_.count(controls::AnalogueGainMode.id()) &&\n> > +                 !controls.contains(controls::AnalogueGainMode.id())) {\n> > +                     controls.set(controls::AnalogueGainMode,\n> > +                                  *aeEnable ? controls::AnalogueGainModeAuto\n> > +                                            : controls::AnalogueGainModeManual);\n> > +             }\n> > +\n> > +             if (_d()->controlInfo_.count(controls::ExposureTimeMode.id()) &&\n> > +                 !controls.contains(controls::ExposureTimeMode.id())) {\n> > +                     controls.set(controls::ExposureTimeMode,\n> > +                                  *aeEnable ? controls::ExposureTimeModeAuto\n> > +                                            : controls::ExposureTimeModeManual);\n> > +             }\n> > +     }\n> > +}\n> > +\n> >   /**\n> >    * \\brief Queue a request to the camera\n> >    * \\param[in] request The request to queue to the camera\n> > @@ -1329,23 +1356,7 @@ int Camera::queueRequest(Request *request)\n> >       }\n> >   \n> >       /* Pre-process AeEnable. */\n> > -     ControlList &controls = request->controls();\n> > -     const auto &aeEnable = controls.get(controls::AeEnable);\n> > -     if (aeEnable) {\n> > -             if (_d()->controlInfo_.count(controls::AnalogueGainMode.id()) &&\n> > -                 !controls.contains(controls::AnalogueGainMode.id())) {\n> > -                     controls.set(controls::AnalogueGainMode,\n> > -                                  *aeEnable ? controls::AnalogueGainModeAuto\n> > -                                            : controls::AnalogueGainModeManual);\n> > -             }\n> > -\n> > -             if (_d()->controlInfo_.count(controls::ExposureTimeMode.id()) &&\n> > -                 !controls.contains(controls::ExposureTimeMode.id())) {\n> > -                     controls.set(controls::ExposureTimeMode,\n> > -                                  *aeEnable ? controls::ExposureTimeModeAuto\n> > -                                            : controls::ExposureTimeModeManual);\n> > -             }\n> > -     }\n> > +     patchControlList(request->controls());\n> >   \n> >       d->pipe_->invokeMethod(&PipelineHandler::queueRequest,\n> >                              ConnectionTypeQueued, request);\n> > @@ -1383,8 +1394,16 @@ int Camera::start(const ControlList *controls)\n> >   \n> >       ASSERT(d->requestSequence_ == 0);\n> >   \n> > -     ret = d->pipe_->invokeMethod(&PipelineHandler::start,\n> > -                                  ConnectionTypeBlocking, this, controls);\n> > +     if (controls) {\n> > +             ControlList copy(*controls);\n> \n> \n> I would like to present an alternative approach that generates a separate\n> \"patch\" `ControlList` and then applies it to the main one:\n> \n> \n> diff --git a/src/libcamera/camera.cpp b/src/libcamera/camera.cpp\n> index 1075474ba..520d72184 100644\n> --- a/src/libcamera/camera.cpp\n> +++ b/src/libcamera/camera.cpp\n> @@ -1324,6 +1335,38 @@ std::unique_ptr<Request> Camera::createRequest(uint64_t cookie)\n>          return request;\n>   }\n>   \n> +namespace {\n> +\n> +[[nodiscard]]\n> +ControlList patchControlList(const ControlInfoMap &controlInfo, const ControlList *controls)\n> +{\n> +       ControlList patch;\n> +\n> +       if (!controls)\n> +               return patch;\n> +\n> +       const auto &aeEnable = controls->get(controls::AeEnable);\n> +       if (aeEnable) {\n> +               if (controlInfo.count(controls::AnalogueGainMode.id()) &&\n> +                   !controls->contains(controls::AnalogueGainMode.id())) {\n> +                       patch.set(controls::AnalogueGainMode,\n> +                                 *aeEnable ? controls::AnalogueGainModeAuto\n> +                                           : controls::AnalogueGainModeManual);\n> +               }\n> +\n> +               if (controlInfo.count(controls::ExposureTimeMode.id()) &&\n> +                   !controls->contains(controls::ExposureTimeMode.id())) {\n> +                       patch.set(controls::ExposureTimeMode,\n> +                                 *aeEnable ? controls::ExposureTimeModeAuto\n> +                                           : controls::ExposureTimeModeManual);\n> +               }\n> +       }\n> +\n> +       return patch;\n> +}\n> +\n> +} /* namespace */\n> +\n>   /**\n>    * \\brief Queue a request to the camera\n>    * \\param[in] request The request to queue to the camera\n> @@ -1387,23 +1430,7 @@ int Camera::queueRequest(Request *request)\n>          }\n>   \n>          /* Pre-process AeEnable. */\n> -       ControlList &controls = request->controls();\n> -       const auto &aeEnable = controls.get(controls::AeEnable);\n> -       if (aeEnable) {\n> -               if (_d()->controlInfo_.count(controls::AnalogueGainMode.id()) &&\n> -                   !controls.contains(controls::AnalogueGainMode.id())) {\n> -                       controls.set(controls::AnalogueGainMode,\n> -                                    *aeEnable ? controls::AnalogueGainModeAuto\n> -                                              : controls::AnalogueGainModeManual);\n> -               }\n> -\n> -               if (_d()->controlInfo_.count(controls::ExposureTimeMode.id()) &&\n> -                   !controls.contains(controls::ExposureTimeMode.id())) {\n> -                       controls.set(controls::ExposureTimeMode,\n> -                                    *aeEnable ? controls::ExposureTimeModeAuto\n> -                                              : controls::ExposureTimeModeManual);\n> -               }\n> -       }\n> +       request->controls().merge(patchControlList(_d()->controlInfo_, &request->controls()));\n>   \n>          d->pipe_->invokeMethod(&PipelineHandler::queueRequest,\n>                                 ConnectionTypeQueued, request);\n> @@ -1441,8 +1468,16 @@ int Camera::start(const ControlList *controls)\n>   \n>          ASSERT(d->requestSequence_ == 0);\n>   \n> -       ret = d->pipe_->invokeMethod(&PipelineHandler::start,\n> -                                    ConnectionTypeBlocking, this, controls);\n> +       auto patch = patchControlList(d->controlInfo_, controls);\n> +       if (!patch.empty()) {\n> +               patch.merge(*controls);\n> +               ret = d->pipe_->invokeMethod(&PipelineHandler::start,\n> +                                            ConnectionTypeBlocking, this, &patch);\n> +       } else {\n> +               ret = d->pipe_->invokeMethod(&PipelineHandler::start,\n> +                                            ConnectionTypeBlocking, this, controls);\n> +       }\n> +\n>          if (ret)\n>                  return ret;\n> \n> \n> The above also needs an rvalue overload for ControlList::merge, which can be implemented\n> as follows:\n> \n> \n> diff --git a/src/libcamera/controls.cpp b/src/libcamera/controls.cpp\n> index 1a618801a..7d9778217 100644\n> --- a/src/libcamera/controls.cpp\n> +++ b/src/libcamera/controls.cpp\n> @@ -1192,6 +1224,45 @@ void ControlList::merge(const ControlList &source, MergePolicy policy)\n>          }\n>   }\n>   \n> +/**\n> + * \\brief Merge the \\a source into the ControlList\n> + * \\param[in] source The ControlList to merge into this object\n> + * \\param[in] policy Controls if existing elements in *this shall be\n> + * overwritten\n> + *\n> + * Merging two control lists moves elements from the \\a source and inserts\n> + * them in *this. If the \\a source contains elements whose key is already\n> + * present in *this, then those elements are only overwritten if\n> + * \\a policy is MergePolicy::OverwriteExisting.\n> + *\n> + * Only control lists created from the same ControlIdMap or ControlInfoMap may\n> + * be merged. Attempting to do otherwise results in undefined behaviour.\n> + */\n> +void ControlList::merge(ControlList &&source, MergePolicy policy)\n> +{\n> +       /**\n> +        * \\todo ASSERT that the current and source ControlList are derived\n> +        * from a compatible ControlIdMap, to prevent undefined behaviour due to\n> +        * id collisions.\n> +        *\n> +        * This can not currently be a direct pointer comparison due to the\n> +        * duplication of the ControlIdMaps in the isolated IPA use cases.\n> +        * Furthermore, manually checking each entry of the id map is identical\n> +        * is expensive.\n> +        * See https://bugs.libcamera.org/show_bug.cgi?id=31 for further details\n> +        */\n> +\n> +       switch (policy) {\n> +       case MergePolicy::KeepExisting:\n> +               controls_.merge(source.controls_);\n> +               break;\n> +       case MergePolicy::OverwriteExisting:\n> +               source.controls_.merge(controls_);\n> +               controls_.swap(source.controls_);\n> +               break;\n> +       }\n> +}\n> +\n> \n> \n> The slight advantage of the above is that it only makes a copy if it is absolutely\n> necessary. I don't know if anything better can be done with the current design\n> (i.e. `Camera::start(const ControlList *)`) wrt. copying.\n> \n> In any case, since the extra copy is only present in `Camera::start()`, and only\n> if a control list is supplied (although recently pipewire has started supplying\n> an initial control list), so I don't think that it is a too significant issue.\n> \n> \n> Reviewed-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>\n\nGiven this is a fix - I'd like to get it in before the next release -\nand this already passes CI.\n\nIf there are improvement that could be done on top I think here.\nEspecially if they require implementing new merge functions.\n\n> \n> \n> Regards,\n> Barnabás Pőcze\n> \n> > +             patchControlList(copy);\n> > +             ret = d->pipe_->invokeMethod(&PipelineHandler::start,\n> > +                                          ConnectionTypeBlocking, this, &copy);\n> > +     }\n> > +     else\n> > +             ret = d->pipe_->invokeMethod(&PipelineHandler::start,\n> > +                                          ConnectionTypeBlocking, this, nullptr);\n> > +\n\nI'll wrap this else statement with matching  { } to make it clear it's\ntied to the above if though.\n\n--\nKieran\n\n\n> >       if (ret)\n> >               return ret;\n> >   \n>","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 5F9FDC3237\n\tfor <parsemail@patchwork.libcamera.org>;\n\tMon, 21 Jul 2025 19:07:36 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 538B66900F;\n\tMon, 21 Jul 2025 21:07:35 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 5FE2A68FB1\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 21 Jul 2025 21:07:34 +0200 (CEST)","from pendragon.ideasonboard.com\n\t(cpc89244-aztw30-2-0-cust6594.18-1.cable.virginm.net [86.31.185.195])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 1EBDD7950;\n\tMon, 21 Jul 2025 21:06:57 +0200 (CEST)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"vEoxqFVn\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1753124817;\n\tbh=qpO/sM6I9CdNJBSqQykV5ncGUWJmwzgibjgvFp6vzb8=;\n\th=In-Reply-To:References:Subject:From:To:Date:From;\n\tb=vEoxqFVnPLJ5z0elIPFe4ypeX2XamB0qqyTxSPgka1e9tQYZT/q4HreQcib576IUZ\n\tcwDn8SR8vaygAewwCMpo1YdVaNl5a7SiWuNIoIlJsliqdkIohi22jk4NTTpyajL4G7\n\tDG5k4rw4k2yTcK6DnVWd73eAdveddV/+1vwBGSvM=","Content-Type":"text/plain; charset=\"utf-8\"","MIME-Version":"1.0","Content-Transfer-Encoding":"quoted-printable","In-Reply-To":"<72fd2993-9701-463f-b960-bcedd4974f13@ideasonboard.com>","References":"<20250521125327.6378-1-david.plowman@raspberrypi.com>\n\t<20250521125327.6378-2-david.plowman@raspberrypi.com>\n\t<72fd2993-9701-463f-b960-bcedd4974f13@ideasonboard.com>","Subject":"Re: [PATCH 1/1] libcamera: camera: Fix up the AeEnable control\n\tduring Camera::start()","From":"Kieran Bingham <kieran.bingham@ideasonboard.com>","To":"=?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= <barnabas.pocze@ideasonboard.com>,\n\tDavid Plowman <david.plowman@raspberrypi.com>, \n\tlibcamera-devel@lists.libcamera.org","Date":"Mon, 21 Jul 2025 20:07:30 +0100","Message-ID":"<175312485079.50296.4585976668881223551@ping.linuxembedded.co.uk>","User-Agent":"alot/0.9.1","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>"}}]