[{"id":18694,"web_url":"https://patchwork.libcamera.org/comment/18694/","msgid":"<YRO+8UEbt/A16V4L@pendragon.ideasonboard.com>","date":"2021-08-11T12:13:37","subject":"Re: [libcamera-devel] [PATCH v4 1/5] libcamera: controls: Create\n\tControlInfoMap with ControlIdMap","submitter":{"id":2,"url":"https://patchwork.libcamera.org/api/people/2/","name":"Laurent Pinchart","email":"laurent.pinchart@ideasonboard.com"},"content":"Hi Jacopo,\n\nThank you for the patch.\n\nOn Tue, Aug 10, 2021 at 05:12:07PM +0200, Jacopo Mondi wrote:\n> ControlInfoMap does not have a ControlId map associated, but rather\n> creates one with the generateIdMap() function at creation time.\n> \n> As a consequence, when in the need to de-serialize a ControlInfoMap all\n> the ControlId it contains are created by the deserializer instance, not\n> being able to discern if the controls the ControlIdMap refers to are the\n> global libcamera controls (and properties) or instances local to the\n> V4L2 device that has first initialized the controls.\n> \n> As a consequence the ControlId stored in a de-serialized map will always\n> be newly created entities, preventing lookup by ControlId reference on a\n> de-serialized ControlInfoMap.\n> \n> In order to make it possible to use globally available ControlId\n> instances whenever possible, create ControlInfoMap with a reference to\n> an externally allocated ControlIdMap instead of generating one\n> internally.\n> \n> As a consequence the class constructors take and additional argument,\n> which might be not pleasant to type in, but enforces the concepts that\n> ControlInfoMap should be created with controls part of the same id map.\n> \n> As the ControlIdMap the ControlInfoMap refers to needs to be allocated\n> externally:\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\n> - The ControlSerializer that de-serializes a ControlInfoMap has to\n>   create and store the ControlIdMap the de-serialized info map refers to\n> \n> Signed-off-by: Jacopo Mondi <jacopo@jmondi.org>\n> Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>\n\nReviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n\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                    | 121 ++++++++----------\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              |  14 +-\n>  12 files changed, 104 insertions(+), 110 deletions(-)\n> \n> diff --git a/include/libcamera/controls.h b/include/libcamera/controls.h\n> index de733bd868a6..9b0d5a545301 100644\n> --- a/include/libcamera/controls.h\n> +++ b/include/libcamera/controls.h\n> @@ -309,12 +309,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> @@ -339,12 +338,12 @@ 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> +\tbool validate();\n>  \n> -\tControlIdMap idmap_;\n> +\tconst ControlIdMap *idmap_;\n>  };\n>  \n>  class ControlList\n> diff --git a/include/libcamera/internal/control_serializer.h b/include/libcamera/internal/control_serializer.h\n> index 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>  };\n> diff --git a/include/libcamera/internal/v4l2_device.h b/include/libcamera/internal/v4l2_device.h\n> index 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_;\n> diff --git a/include/libcamera/ipa/raspberrypi.h b/include/libcamera/ipa/raspberrypi.h\n> index 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>  \n> diff --git a/src/libcamera/control_serializer.cpp b/src/libcamera/control_serializer.cpp\n> index 6a65999f8161..ed456fd445a0 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;\n> diff --git a/src/libcamera/controls.cpp b/src/libcamera/controls.cpp\n> index 64fd5c296226..5c05ba4a7cd0 100644\n> --- a/src/libcamera/controls.cpp\n> +++ b/src/libcamera/controls.cpp\n> @@ -625,10 +625,10 @@ std::string ControlInfo::toString() const\n>   * constructed, and thus only exposes the read accessors of the\n>   * std::unsorted_map<> base class.\n>   *\n> - * In addition to the features of the standard unsorted map, this class also\n> - * provides access to the mapped elements using numerical ID keys. It maintains\n> - * an internal map of numerical ID to ControlId for this purpose, and exposes it\n> - * through the idmap() function to help construction of ControlList instances.\n> + * The class is constructed with a reference to a ControlIdMap. This allows\n> + * providing access to the mapped elements using numerical ID keys, in addition\n> + * to the features of the standard unsorted map. All ControlId keys in the map\n> + * must appear in the ControlIdMap.\n>   */\n>  \n>  /**\n> @@ -645,24 +645,27 @@ 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> +\tASSERT(validate());\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> +\tASSERT(validate());\n>  }\n>  \n>  /**\n> @@ -672,32 +675,41 @@ 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> +bool ControlInfoMap::validate()\n>  {\n> -\tMap::operator=(init);\n> -\tgenerateIdmap();\n> -\treturn *this;\n> -}\n> +\tfor (const auto &ctrl : *this) {\n> +\t\tconst ControlId *id = ctrl.first;\n> +\t\tauto it = idmap_->find(id->id());\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> +\t\t/*\n> +\t\t * Make sure all control ids are part of the idmap and verify\n> +\t\t * the control info matches the expected type.\n> +\t\t */\n> +\t\tif (it == idmap_->end() || it->second != id) {\n> +\t\t\tLOG(Controls, Error)\n> +\t\t\t\t<< \"Control \" << utils::hex(id->id())\n> +\t\t\t\t<< \" not in the idmap\";\n> +\t\t\treturn false;\n> +\t\t}\n> +\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 = id->type() == ControlTypeString\n> +\t\t\t\t      ? ControlTypeInteger32 : id->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(id->id())\n> +\t\t\t\t<< \" type and info type mismatch\";\n> +\t\t\treturn false;\n> +\t\t}\n> +\t}\n> +\n> +\treturn true;\n>  }\n>  \n>  /**\n> @@ -707,7 +719,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> @@ -717,7 +729,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> @@ -732,7 +744,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> @@ -743,8 +755,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> @@ -758,8 +770,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> @@ -776,33 +788,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\n> diff --git a/src/libcamera/pipeline/ipu3/ipu3.cpp b/src/libcamera/pipeline/ipu3/ipu3.cpp\n> index 19cb5c4ec9c3..9c23788e5231 100644\n> --- a/src/libcamera/pipeline/ipu3/ipu3.cpp\n> +++ b/src/libcamera/pipeline/ipu3/ipu3.cpp\n> @@ -1071,7 +1071,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>  }\n> diff --git a/src/libcamera/pipeline/rkisp1/rkisp1.cpp b/src/libcamera/pipeline/rkisp1/rkisp1.cpp\n> index 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();\n> diff --git a/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp b/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp\n> index 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>  }\n> diff --git a/src/libcamera/pipeline/vimc/vimc.cpp b/src/libcamera/pipeline/vimc/vimc.cpp\n> index 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();\n> diff --git a/src/libcamera/v4l2_device.cpp b/src/libcamera/v4l2_device.cpp\n> index b0a70defc3d0..951592c698f7 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>  /**\n> diff --git a/test/serialization/ipa_data_serializer_test.cpp b/test/serialization/ipa_data_serializer_test.cpp\n> index 880bcd02c6be..bf7e34e28af3 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> -\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> +static const ControlInfoMap Controls = ControlInfoMap({\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::ColourGains, ControlInfo(0.0f, 32.0f) },\n> +\t\t{ &controls::Brightness, ControlInfo(-1.0f, 1.0f) },\n> +\t}, controls::controls);\n>  \n>  namespace libcamera {\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 D5F65BD87D\n\tfor <parsemail@patchwork.libcamera.org>;\n\tWed, 11 Aug 2021 12:13:42 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 60F946884F;\n\tWed, 11 Aug 2021 14:13:42 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 03DEA68826\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 11 Aug 2021 14:13:40 +0200 (CEST)","from pendragon.ideasonboard.com (62-78-145-57.bb.dnainternet.fi\n\t[62.78.145.57])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 7513DEE;\n\tWed, 11 Aug 2021 14:13: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=\"rvtg7ea0\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1628684020;\n\tbh=/XJspFsTwLl43BJFMT6IyMfa46Qsk9ZacxDU8Q8mP/U=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=rvtg7ea0di+1ccepgtzpL4ieuH5JYRf/iUzrCcV9linjS9SZVNenbC8lFOlTJe+uB\n\t/G5VKjkPqRCWPwhrnSEAJQI78Jgdgaml5kKOsKjPhQ1fTbXaH0uQLSqCGTbaGeIdxU\n\t6U76b3x4t86BO5OZZvUxVXNfSD9oiuq87B34TkdA=","Date":"Wed, 11 Aug 2021 15:13:37 +0300","From":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","To":"Jacopo Mondi <jacopo@jmondi.org>","Message-ID":"<YRO+8UEbt/A16V4L@pendragon.ideasonboard.com>","References":"<20210810151211.56702-1-jacopo@jmondi.org>\n\t<20210810151211.56702-2-jacopo@jmondi.org>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","In-Reply-To":"<20210810151211.56702-2-jacopo@jmondi.org>","Subject":"Re: [libcamera-devel] [PATCH v4 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>","Cc":"libcamera-devel@lists.libcamera.org","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}}]