{"id":13140,"url":"https://patchwork.libcamera.org/api/1.1/patches/13140/?format=json","web_url":"https://patchwork.libcamera.org/patch/13140/","project":{"id":1,"url":"https://patchwork.libcamera.org/api/1.1/projects/1/?format=json","name":"libcamera","link_name":"libcamera","list_id":"libcamera_core","list_email":"libcamera-devel@lists.libcamera.org","web_url":"","scm_url":"","webscm_url":""},"msgid":"<20210728161116.64489-2-jacopo@jmondi.org>","date":"2021-07-28T16:11:12","name":"[libcamera-devel,v2,1/5] libcamera: controls: Create ControlInfoMap with ControlIdMap","commit_ref":null,"pull_url":null,"state":"superseded","archived":false,"hash":"a75d0dde13e630c900e24be179970222a7a5ee8e","submitter":{"id":3,"url":"https://patchwork.libcamera.org/api/1.1/people/3/?format=json","name":"Jacopo Mondi","email":"jacopo@jmondi.org"},"delegate":null,"mbox":"https://patchwork.libcamera.org/patch/13140/mbox/","series":[{"id":2288,"url":"https://patchwork.libcamera.org/api/1.1/series/2288/?format=json","web_url":"https://patchwork.libcamera.org/project/libcamera/list/?series=2288","date":"2021-07-28T16:11:11","name":"libcamera: Initialize controls in the IPA","version":2,"mbox":"https://patchwork.libcamera.org/series/2288/mbox/"}],"comments":"https://patchwork.libcamera.org/api/patches/13140/comments/","check":"pending","checks":"https://patchwork.libcamera.org/api/patches/13140/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 3F921C322E\n\tfor <parsemail@patchwork.libcamera.org>;\n\tWed, 28 Jul 2021 16:10:35 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 0A586687D0;\n\tWed, 28 Jul 2021 18:10:35 +0200 (CEST)","from relay3-d.mail.gandi.net (relay3-d.mail.gandi.net\n\t[217.70.183.195])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 9B1E9687C2\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 28 Jul 2021 18:10:32 +0200 (CEST)","(Authenticated sender: jacopo@jmondi.org)\n\tby relay3-d.mail.gandi.net (Postfix) with ESMTPSA id 0A06E60009;\n\tWed, 28 Jul 2021 16:10:31 +0000 (UTC)"],"From":"Jacopo Mondi <jacopo@jmondi.org>","To":"libcamera-devel@lists.libcamera.org","Date":"Wed, 28 Jul 2021 18:11:12 +0200","Message-Id":"<20210728161116.64489-2-jacopo@jmondi.org>","X-Mailer":"git-send-email 2.32.0","In-Reply-To":"<20210728161116.64489-1-jacopo@jmondi.org>","References":"<20210728161116.64489-1-jacopo@jmondi.org>","MIME-Version":"1.0","Content-Transfer-Encoding":"8bit","Subject":"[libcamera-devel] [PATCH v2 1/5] libcamera: controls: Create\n\tControlInfoMap with ControlIdMap","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":"ControlInfoMap do not have a ControlId map associated, but rather\ncreate one with the generateIdMap() function at creation time.\n\nAs a consequence, when in the need to de-serialize a ControlInfoMap all\nthe ControlId it contains are created by the deserializer instance, not\nbeing able to discern if the controls the ControlIdMap refers to are the\nglobal libcamera controls (and proeprties) or instances local to the\nV4L2 device that has first initialized the controls.\n\nAs a consequence the ControlId stored in a de-serialized map will always\nbe newly created entities, preventing lookup by ControlId reference on a\nde-serialized ControlInfoMap.\n\nIn order to make it possible to use globally available ControlId\ninstances whenever possible, create ControlInfoMap with a reference to\nan externally allocated ControlIdMap instead of generating one\ninternally.\n\nHas a consequence the class constructors take and additional argument,\nwhich might be not pleaseant to type in, but enforces the concepts that\nControlInfoMap should be created with controls part of the same id map.\n\nAs the ControlIdMap the ControlInfoMap refers to need to be allocated\nexternally:\n- Use the globally available controls::controls (or\n  properties::properties) id map when referring to libcamera controls\n- The V4L2 device that creates ControlInfoMap by parsing the device's\n  controls has to allocate a ControlIdMap and ties its lifetime\n  management to the its own one\n- The ControlSerializer that de-serializes a ControlInfoMap has to\n  create and store the ControlIdMap the de-serialized info map refers to\n\nSigned-off-by: Jacopo Mondi <jacopo@jmondi.org>\n---\n include/libcamera/controls.h                  | 13 ++-\n .../libcamera/internal/control_serializer.h   |  1 +\n include/libcamera/internal/v4l2_device.h      |  1 +\n include/libcamera/ipa/raspberrypi.h           | 40 ++++-----\n src/libcamera/control_serializer.cpp          | 11 ++-\n src/libcamera/controls.cpp                    | 82 ++++---------------\n src/libcamera/pipeline/ipu3/ipu3.cpp          |  3 +-\n src/libcamera/pipeline/rkisp1/rkisp1.cpp      |  3 +-\n src/libcamera/pipeline/uvcvideo/uvcvideo.cpp  |  2 +-\n src/libcamera/pipeline/vimc/vimc.cpp          |  2 +-\n src/libcamera/v4l2_device.cpp                 |  3 +-\n .../ipa_data_serializer_test.cpp              |  4 +-\n 12 files changed, 59 insertions(+), 106 deletions(-)","diff":"diff --git a/include/libcamera/controls.h b/include/libcamera/controls.h\nindex 1bc958a43b22..4a2cdb335870 100644\n--- a/include/libcamera/controls.h\n+++ b/include/libcamera/controls.h\n@@ -306,12 +306,11 @@ public:\n \n \tControlInfoMap() = default;\n \tControlInfoMap(const ControlInfoMap &other) = default;\n-\tControlInfoMap(std::initializer_list<Map::value_type> init);\n-\tControlInfoMap(Map &&info);\n+\tControlInfoMap(std::initializer_list<Map::value_type> init,\n+\t\t       const ControlIdMap &idmap);\n+\tControlInfoMap(Map &&info, const ControlIdMap &idmap);\n \n \tControlInfoMap &operator=(const ControlInfoMap &other) = default;\n-\tControlInfoMap &operator=(std::initializer_list<Map::value_type> init);\n-\tControlInfoMap &operator=(Map &&info);\n \n \tusing Map::key_type;\n \tusing Map::mapped_type;\n@@ -336,12 +335,10 @@ public:\n \titerator find(unsigned int key);\n \tconst_iterator find(unsigned int key) const;\n \n-\tconst ControlIdMap &idmap() const { return idmap_; }\n+\tconst ControlIdMap &idmap() const { return *idmap_; }\n \n private:\n-\tvoid generateIdmap();\n-\n-\tControlIdMap idmap_;\n+\tconst ControlIdMap *idmap_;\n };\n \n class ControlList\ndiff --git a/include/libcamera/internal/control_serializer.h b/include/libcamera/internal/control_serializer.h\nindex 7d4426c95d12..8a66be324138 100644\n--- a/include/libcamera/internal/control_serializer.h\n+++ b/include/libcamera/internal/control_serializer.h\n@@ -48,6 +48,7 @@ private:\n \n \tunsigned int serial_;\n \tstd::vector<std::unique_ptr<ControlId>> controlIds_;\n+\tstd::vector<std::unique_ptr<ControlIdMap>> controlIdMaps_;\n \tstd::map<unsigned int, ControlInfoMap> infoMaps_;\n \tstd::map<const ControlInfoMap *, unsigned int> infoMapHandles_;\n };\ndiff --git a/include/libcamera/internal/v4l2_device.h b/include/libcamera/internal/v4l2_device.h\nindex 77b835b3cb80..423c8fb11845 100644\n--- a/include/libcamera/internal/v4l2_device.h\n+++ b/include/libcamera/internal/v4l2_device.h\n@@ -69,6 +69,7 @@ private:\n \n \tstd::map<unsigned int, struct v4l2_query_ext_ctrl> controlInfo_;\n \tstd::vector<std::unique_ptr<ControlId>> controlIds_;\n+\tControlIdMap controlIdMap_;\n \tControlInfoMap controls_;\n \tstd::string deviceNode_;\n \tint fd_;\ndiff --git a/include/libcamera/ipa/raspberrypi.h b/include/libcamera/ipa/raspberrypi.h\nindex a8790000e2d9..521eaecd19b2 100644\n--- a/include/libcamera/ipa/raspberrypi.h\n+++ b/include/libcamera/ipa/raspberrypi.h\n@@ -27,26 +27,26 @@ namespace RPi {\n  * and the pipeline handler may be reverted so that it aborts when an\n  * unsupported control is encountered.\n  */\n-static const ControlInfoMap Controls = {\n-\t{ &controls::AeEnable, ControlInfo(false, true) },\n-\t{ &controls::ExposureTime, ControlInfo(0, 999999) },\n-\t{ &controls::AnalogueGain, ControlInfo(1.0f, 32.0f) },\n-\t{ &controls::AeMeteringMode, ControlInfo(controls::AeMeteringModeValues) },\n-\t{ &controls::AeConstraintMode, ControlInfo(controls::AeConstraintModeValues) },\n-\t{ &controls::AeExposureMode, ControlInfo(controls::AeExposureModeValues) },\n-\t{ &controls::ExposureValue, ControlInfo(0.0f, 16.0f) },\n-\t{ &controls::AwbEnable, ControlInfo(false, true) },\n-\t{ &controls::ColourGains, ControlInfo(0.0f, 32.0f) },\n-\t{ &controls::AwbMode, ControlInfo(controls::AwbModeValues) },\n-\t{ &controls::Brightness, ControlInfo(-1.0f, 1.0f) },\n-\t{ &controls::Contrast, ControlInfo(0.0f, 32.0f) },\n-\t{ &controls::Saturation, ControlInfo(0.0f, 32.0f) },\n-\t{ &controls::Sharpness, ControlInfo(0.0f, 16.0f, 1.0f) },\n-\t{ &controls::ColourCorrectionMatrix, ControlInfo(-16.0f, 16.0f) },\n-\t{ &controls::ScalerCrop, ControlInfo(Rectangle{}, Rectangle(65535, 65535, 65535, 65535), Rectangle{}) },\n-\t{ &controls::FrameDurationLimits, ControlInfo(INT64_C(1000), INT64_C(1000000000)) },\n-\t{ &controls::draft::NoiseReductionMode, ControlInfo(controls::draft::NoiseReductionModeValues) },\n-};\n+static const ControlInfoMap Controls({\n+\t\t{ &controls::AeEnable, ControlInfo(false, true) },\n+\t\t{ &controls::ExposureTime, ControlInfo(0, 999999) },\n+\t\t{ &controls::AnalogueGain, ControlInfo(1.0f, 32.0f) },\n+\t\t{ &controls::AeMeteringMode, ControlInfo(controls::AeMeteringModeValues) },\n+\t\t{ &controls::AeConstraintMode, ControlInfo(controls::AeConstraintModeValues) },\n+\t\t{ &controls::AeExposureMode, ControlInfo(controls::AeExposureModeValues) },\n+\t\t{ &controls::ExposureValue, ControlInfo(0.0f, 16.0f) },\n+\t\t{ &controls::AwbEnable, ControlInfo(false, true) },\n+\t\t{ &controls::ColourGains, ControlInfo(0.0f, 32.0f) },\n+\t\t{ &controls::AwbMode, ControlInfo(controls::AwbModeValues) },\n+\t\t{ &controls::Brightness, ControlInfo(-1.0f, 1.0f) },\n+\t\t{ &controls::Contrast, ControlInfo(0.0f, 32.0f) },\n+\t\t{ &controls::Saturation, ControlInfo(0.0f, 32.0f) },\n+\t\t{ &controls::Sharpness, ControlInfo(0.0f, 16.0f, 1.0f) },\n+\t\t{ &controls::ColourCorrectionMatrix, ControlInfo(-16.0f, 16.0f) },\n+\t\t{ &controls::ScalerCrop, ControlInfo(Rectangle{}, Rectangle(65535, 65535, 65535, 65535), Rectangle{}) },\n+\t\t{ &controls::FrameDurationLimits, ControlInfo(INT64_C(1000), INT64_C(1000000000)) },\n+\t\t{ &controls::draft::NoiseReductionMode, ControlInfo(controls::draft::NoiseReductionModeValues) }\n+\t}, controls::controls);\n \n } /* namespace RPi */\n \ndiff --git a/src/libcamera/control_serializer.cpp b/src/libcamera/control_serializer.cpp\nindex 300466285a57..df6ed93f477e 100644\n--- a/src/libcamera/control_serializer.cpp\n+++ b/src/libcamera/control_serializer.cpp\n@@ -93,6 +93,7 @@ void ControlSerializer::reset()\n \tinfoMapHandles_.clear();\n \tinfoMaps_.clear();\n \tcontrolIds_.clear();\n+\tcontrolIdMaps_.clear();\n }\n \n size_t ControlSerializer::binarySize(const ControlValue &value)\n@@ -376,6 +377,8 @@ ControlInfoMap ControlSerializer::deserialize<ControlInfoMap>(ByteStreamBuffer &\n \t}\n \n \tControlInfoMap::Map ctrls;\n+\tcontrolIdMaps_.emplace_back(std::make_unique<ControlIdMap>());\n+\tControlIdMap *localIdMap = controlIdMaps_.back().get();\n \n \tfor (unsigned int i = 0; i < hdr->entries; ++i) {\n \t\tconst struct ipa_control_info_entry *entry =\n@@ -392,6 +395,8 @@ ControlInfoMap ControlSerializer::deserialize<ControlInfoMap>(ByteStreamBuffer &\n \t\t * purpose.\n \t\t */\n \t\tcontrolIds_.emplace_back(std::make_unique<ControlId>(entry->id, \"\", type));\n+\t\tControlId *controlId = controlIds_.back().get();\n+\t\t(*localIdMap)[entry->id] = controlId;\n \n \t\tif (entry->offset != values.offset()) {\n \t\t\tLOG(Serializer, Error)\n@@ -401,15 +406,15 @@ ControlInfoMap ControlSerializer::deserialize<ControlInfoMap>(ByteStreamBuffer &\n \t\t}\n \n \t\t/* Create and store the ControlInfo. */\n-\t\tctrls.emplace(controlIds_.back().get(),\n-\t\t\t      loadControlInfo(type, values));\n+\t\tctrls.emplace(controlId, loadControlInfo(type, values));\n \t}\n \n \t/*\n \t * Create the ControlInfoMap in the cache, and store the map to handle\n \t * association.\n \t */\n-\tControlInfoMap &map = infoMaps_[hdr->handle] = std::move(ctrls);\n+\tinfoMaps_[hdr->handle] = ControlInfoMap(std::move(ctrls), *localIdMap);\n+\tControlInfoMap &map = infoMaps_[hdr->handle];\n \tinfoMapHandles_[&map] = hdr->handle;\n \n \treturn map;\ndiff --git a/src/libcamera/controls.cpp b/src/libcamera/controls.cpp\nindex 78109f4130a8..9409f0f9e782 100644\n--- a/src/libcamera/controls.cpp\n+++ b/src/libcamera/controls.cpp\n@@ -616,24 +616,25 @@ std::string ControlInfo::toString() const\n /**\n  * \\brief Construct a ControlInfoMap from an initializer list\n  * \\param[in] init The initializer list\n+ * \\param[in] idmap The idmap used by the ControlInfoMap\n  */\n-ControlInfoMap::ControlInfoMap(std::initializer_list<Map::value_type> init)\n-\t: Map(init)\n+ControlInfoMap::ControlInfoMap(std::initializer_list<Map::value_type> init,\n+\t\t\t       const ControlIdMap &idmap)\n+\t: Map(init), idmap_(&idmap)\n {\n-\tgenerateIdmap();\n }\n \n /**\n  * \\brief Construct a ControlInfoMap from a plain map\n  * \\param[in] info The control info plain map\n+ * \\param[in] idmap The idmap used by the ControlInfoMap\n  *\n  * Construct a new ControlInfoMap and populate its contents with those of\n  * \\a info using move semantics. Upon return the \\a info map will be empty.\n  */\n-ControlInfoMap::ControlInfoMap(Map &&info)\n-\t: Map(std::move(info))\n+ControlInfoMap::ControlInfoMap(Map &&info, const ControlIdMap &idmap)\n+\t: Map(std::move(info)), idmap_(&idmap)\n {\n-\tgenerateIdmap();\n }\n \n /**\n@@ -643,34 +644,6 @@ ControlInfoMap::ControlInfoMap(Map &&info)\n  * \\return A reference to the ControlInfoMap\n  */\n \n-/**\n- * \\brief Replace the contents with those from the initializer list\n- * \\param[in] init The initializer list\n- * \\return A reference to the ControlInfoMap\n- */\n-ControlInfoMap &ControlInfoMap::operator=(std::initializer_list<Map::value_type> init)\n-{\n-\tMap::operator=(init);\n-\tgenerateIdmap();\n-\treturn *this;\n-}\n-\n-/**\n- * \\brief Move assignment operator from a plain map\n- * \\param[in] info The control info plain map\n- *\n- * Populate the map by replacing its contents with those of \\a info using move\n- * semantics. Upon return the \\a info map will be empty.\n- *\n- * \\return A reference to the populated ControlInfoMap\n- */\n-ControlInfoMap &ControlInfoMap::operator=(Map &&info)\n-{\n-\tMap::operator=(std::move(info));\n-\tgenerateIdmap();\n-\treturn *this;\n-}\n-\n /**\n  * \\brief Access specified element by numerical ID\n  * \\param[in] id The numerical ID\n@@ -678,7 +651,7 @@ ControlInfoMap &ControlInfoMap::operator=(Map &&info)\n  */\n ControlInfoMap::mapped_type &ControlInfoMap::at(unsigned int id)\n {\n-\treturn at(idmap_.at(id));\n+\treturn at(idmap_->at(id));\n }\n \n /**\n@@ -688,7 +661,7 @@ ControlInfoMap::mapped_type &ControlInfoMap::at(unsigned int id)\n  */\n const ControlInfoMap::mapped_type &ControlInfoMap::at(unsigned int id) const\n {\n-\treturn at(idmap_.at(id));\n+\treturn at(idmap_->at(id));\n }\n \n /**\n@@ -703,7 +676,7 @@ ControlInfoMap::size_type ControlInfoMap::count(unsigned int id) const\n \t * entries, we can thus just count the matching entries in idmap to\n \t * avoid an additional lookup.\n \t */\n-\treturn idmap_.count(id);\n+\treturn idmap_->count(id);\n }\n \n /**\n@@ -714,8 +687,8 @@ ControlInfoMap::size_type ControlInfoMap::count(unsigned int id) const\n  */\n ControlInfoMap::iterator ControlInfoMap::find(unsigned int id)\n {\n-\tauto iter = idmap_.find(id);\n-\tif (iter == idmap_.end())\n+\tauto iter = idmap_->find(id);\n+\tif (iter == idmap_->end())\n \t\treturn end();\n \n \treturn find(iter->second);\n@@ -729,8 +702,8 @@ ControlInfoMap::iterator ControlInfoMap::find(unsigned int id)\n  */\n ControlInfoMap::const_iterator ControlInfoMap::find(unsigned int id) const\n {\n-\tauto iter = idmap_.find(id);\n-\tif (iter == idmap_.end())\n+\tauto iter = idmap_->find(id);\n+\tif (iter == idmap_->end())\n \t\treturn end();\n \n \treturn find(iter->second);\n@@ -747,33 +720,6 @@ ControlInfoMap::const_iterator ControlInfoMap::find(unsigned int id) const\n  * \\return The ControlId map\n  */\n \n-void ControlInfoMap::generateIdmap()\n-{\n-\tidmap_.clear();\n-\n-\tfor (const auto &ctrl : *this) {\n-\t\t/*\n-\t\t * For string controls, min and max define the valid\n-\t\t * range for the string size, not for the individual\n-\t\t * values.\n-\t\t */\n-\t\tControlType rangeType = ctrl.first->type() == ControlTypeString\n-\t\t\t\t      ? ControlTypeInteger32 : ctrl.first->type();\n-\t\tconst ControlInfo &info = ctrl.second;\n-\n-\t\tif (info.min().type() != rangeType) {\n-\t\t\tLOG(Controls, Error)\n-\t\t\t\t<< \"Control \" << utils::hex(ctrl.first->id())\n-\t\t\t\t<< \" type and info type mismatch\";\n-\t\t\tidmap_.clear();\n-\t\t\tclear();\n-\t\t\treturn;\n-\t\t}\n-\n-\t\tidmap_[ctrl.first->id()] = ctrl.first;\n-\t}\n-}\n-\n /**\n  * \\class ControlList\n  * \\brief Associate a list of ControlId with their values for an object\ndiff --git a/src/libcamera/pipeline/ipu3/ipu3.cpp b/src/libcamera/pipeline/ipu3/ipu3.cpp\nindex 76c3bb3d8aa9..048993365b44 100644\n--- a/src/libcamera/pipeline/ipu3/ipu3.cpp\n+++ b/src/libcamera/pipeline/ipu3/ipu3.cpp\n@@ -1057,7 +1057,8 @@ int PipelineHandlerIPU3::initControls(IPU3CameraData *data)\n \n \tcontrols[&controls::ScalerCrop] = ControlInfo(minCrop, maxCrop, maxCrop);\n \n-\tdata->controlInfo_ = std::move(controls);\n+\tdata->controlInfo_ = ControlInfoMap(std::move(controls),\n+\t\t\t\t\t    controls::controls);\n \n \treturn 0;\n }\ndiff --git a/src/libcamera/pipeline/rkisp1/rkisp1.cpp b/src/libcamera/pipeline/rkisp1/rkisp1.cpp\nindex 42911a8fdfdb..710b9309448e 100644\n--- a/src/libcamera/pipeline/rkisp1/rkisp1.cpp\n+++ b/src/libcamera/pipeline/rkisp1/rkisp1.cpp\n@@ -935,7 +935,8 @@ int PipelineHandlerRkISP1::createCamera(MediaEntity *sensor)\n \t\t      std::forward_as_tuple(&controls::AeEnable),\n \t\t      std::forward_as_tuple(false, true));\n \n-\tdata->controlInfo_ = std::move(ctrls);\n+\tdata->controlInfo_ = ControlInfoMap(std::move(ctrls),\n+\t\t\t\t\t    controls::controls);\n \n \tdata->sensor_ = std::make_unique<CameraSensor>(sensor);\n \tret = data->sensor_->init();\ndiff --git a/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp b/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp\nindex 0f634b8da609..573d8042c18c 100644\n--- a/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp\n+++ b/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp\n@@ -535,7 +535,7 @@ int UVCCameraData::init(MediaDevice *media)\n \t\taddControl(cid, info, &ctrls);\n \t}\n \n-\tcontrolInfo_ = std::move(ctrls);\n+\tcontrolInfo_ = ControlInfoMap(std::move(ctrls), controls::controls);\n \n \treturn 0;\n }\ndiff --git a/src/libcamera/pipeline/vimc/vimc.cpp b/src/libcamera/pipeline/vimc/vimc.cpp\nindex 12f7517fd0ae..d4b041d33eb4 100644\n--- a/src/libcamera/pipeline/vimc/vimc.cpp\n+++ b/src/libcamera/pipeline/vimc/vimc.cpp\n@@ -512,7 +512,7 @@ int VimcCameraData::init()\n \t\tctrls.emplace(id, info);\n \t}\n \n-\tcontrolInfo_ = std::move(ctrls);\n+\tcontrolInfo_ = ControlInfoMap(std::move(ctrls), controls::controls);\n \n \t/* Initialize the camera properties. */\n \tproperties_ = sensor_->properties();\ndiff --git a/src/libcamera/v4l2_device.cpp b/src/libcamera/v4l2_device.cpp\nindex 98d93a12a7be..6ccd8eb86016 100644\n--- a/src/libcamera/v4l2_device.cpp\n+++ b/src/libcamera/v4l2_device.cpp\n@@ -611,12 +611,13 @@ void V4L2Device::listControls()\n \t\t\t\t << \" (\" << utils::hex(ctrl.id) << \")\";\n \n \t\tcontrolIds_.emplace_back(v4l2ControlId(ctrl));\n+\t\tcontrolIdMap_[ctrl.id] = controlIds_.back().get();\n \t\tcontrolInfo_.emplace(ctrl.id, ctrl);\n \n \t\tctrls.emplace(controlIds_.back().get(), v4l2ControlInfo(ctrl));\n \t}\n \n-\tcontrols_ = std::move(ctrls);\n+\tcontrols_ = ControlInfoMap(std::move(ctrls), controlIdMap_);\n }\n \n /**\ndiff --git a/test/serialization/ipa_data_serializer_test.cpp b/test/serialization/ipa_data_serializer_test.cpp\nindex 880bcd02c6be..3d845a129a5f 100644\n--- a/test/serialization/ipa_data_serializer_test.cpp\n+++ b/test/serialization/ipa_data_serializer_test.cpp\n@@ -32,13 +32,13 @@\n using namespace std;\n using namespace libcamera;\n \n-static const ControlInfoMap Controls = {\n+static const ControlInfoMap Controls = ControlInfoMap({\n \t{ &controls::AeEnable, ControlInfo(false, true) },\n \t{ &controls::ExposureTime, ControlInfo(0, 999999) },\n \t{ &controls::AnalogueGain, ControlInfo(1.0f, 32.0f) },\n \t{ &controls::ColourGains, ControlInfo(0.0f, 32.0f) },\n \t{ &controls::Brightness, ControlInfo(-1.0f, 1.0f) },\n-};\n+\t}, controls::controls);\n \n namespace libcamera {\n \n","prefixes":["libcamera-devel","v2","1/5"]}