[{"id":35234,"web_url":"https://patchwork.libcamera.org/comment/35234/","msgid":"<71cfbca88c0673e0463ceaf8469ed27ab0f2bc69.camel@collabora.com>","date":"2025-07-29T16:06:51","subject":"Re: [PATCH v5 3/3] gstreamer: Report camera properties as device\n\tproperties","submitter":{"id":31,"url":"https://patchwork.libcamera.org/api/people/31/","name":"Nicolas Dufresne","email":"nicolas.dufresne@collabora.com"},"content":"Le mardi 29 juillet 2025 à 21:09 +0530, Umang Jain a écrit :\n> Iterate over all libcamera camera properties and report them as\n> device properties. Each libcamera ControlType is mapped to the\n> corresponding gstreamer GType. If the ControlValue is an array of\n> values (ControlValue::isArray()), GValue with type GST_TYPE_ARRAY\n> is used to set the value of that ControlValue with the corresponding\n> GType.\n> \n> Signed-off-by: Umang Jain <uajain@igalia.com>\n\nReviewed-by: Nicolas Dufresne <nicolas.dufresne@collabora.com>\n\n> ---\n>  src/gstreamer/gstlibcamera-utils.cpp   | 197 +++++++++++++++++++++++++\n>  src/gstreamer/gstlibcamera-utils.h     |   3 +\n>  src/gstreamer/gstlibcameraprovider.cpp |  13 ++\n>  3 files changed, 213 insertions(+)\n> \n> diff --git a/src/gstreamer/gstlibcamera-utils.cpp\n> b/src/gstreamer/gstlibcamera-utils.cpp\n> index 63e00fe0..d11d7ba9 100644\n> --- a/src/gstreamer/gstlibcamera-utils.cpp\n> +++ b/src/gstreamer/gstlibcamera-utils.cpp\n> @@ -8,6 +8,8 @@\n>  \n>  #include \"gstlibcamera-utils.h\"\n>  \n> +#include <string>\n> +\n>  #include <libcamera/control_ids.h>\n>  #include <libcamera/formats.h>\n>  \n> @@ -333,6 +335,33 @@ bare_structure_from_format(const PixelFormat &format)\n>  \t}\n>  }\n>  \n> +static const struct {\n> +\tControlType c_type;\n> +\tGType g_type;\n> +} control_type_gtype_map[] = {\n> +\t{ ControlTypeBool, G_TYPE_BOOLEAN },\n> +\t{ ControlTypeByte, G_TYPE_UINT },\n> +\t{ ControlTypeUnsigned16, G_TYPE_UINT },\n> +\t{ ControlTypeUnsigned32, G_TYPE_UINT },\n> +\t{ ControlTypeInteger32, G_TYPE_INT },\n> +\t{ ControlTypeInteger64, G_TYPE_INT64 },\n> +\t{ ControlTypeFloat, G_TYPE_FLOAT },\n> +\t{ ControlTypeString, G_TYPE_STRING },\n> +\t{ ControlTypeRectangle, GST_TYPE_ARRAY },\n> +\t{ ControlTypeSize, GST_TYPE_ARRAY },\n> +\t{ ControlTypePoint, GST_TYPE_ARRAY },\n> +};\n> +\n> +static GType\n> +control_type_to_gtype(const ControlType &type)\n> +{\n> +\tfor (auto &a : control_type_gtype_map) {\n> +\t\tif (a.c_type == type)\n> +\t\t\treturn a.g_type;\n> +\t}\n> +\treturn G_TYPE_INVALID;\n> +}\n> +\n>  GstCaps *\n>  gst_libcamera_stream_formats_to_caps(const StreamFormats &formats)\n>  {\n> @@ -706,3 +735,171 @@ gst_libcamera_get_camera_manager(int &ret)\n>  \n>  \treturn cm;\n>  }\n> +\n> +int gst_libcamera_set_structure_field(GstStructure *structure, const\n> ControlId *id,\n> +\t\t\t\t      const ControlValue &value)\n> +{\n> +\tstd::string prop = \"api.libcamera.\" + id->name();\n> +\tg_auto(GValue) v = G_VALUE_INIT;\n> +\tg_auto(GValue) x = G_VALUE_INIT;\n> +\tgboolean is_array = value.isArray();\n> +\n> +\tGType type = control_type_to_gtype(value.type());\n> +\tif (type == G_TYPE_INVALID)\n> +\t\treturn -EINVAL;\n> +\n> +\tif (is_array || type == GST_TYPE_ARRAY)\n> +\t\tg_value_init(&v, GST_TYPE_ARRAY);\n> +\n> +\tswitch (value.type()) {\n> +\tcase ControlTypeBool:\n> +\t\tif (is_array) {\n> +\t\t\tSpan<const bool> data = value.get<Span<const\n> bool>>();\n> +\t\t\tfor (auto it = data.begin(); it != data.end(); ++it)\n> {\n> +\t\t\t\tg_value_init(&x, type);\n> +\t\t\t\tg_value_set_boolean(&x, *it);\n> +\t\t\t\tgst_value_array_append_and_take_value(&v,\n> &x);\n> +\t\t\t}\n> +\t\t} else {\n> +\t\t\tgst_structure_set(structure, prop.c_str(),\n> G_TYPE_BOOLEAN,\n> +\t\t\t\t\t  value.get<const bool>(), nullptr);\n> +\t\t}\n> +\t\tbreak;\n> +\tcase ControlTypeByte:\n> +\t\tif (is_array) {\n> +\t\t\tSpan<const uint8_t> data = value.get<Span<const\n> uint8_t>>();\n> +\t\t\tfor (auto it = data.begin(); it != data.end(); ++it)\n> {\n> +\t\t\t\tg_value_init(&x, type);\n> +\t\t\t\tg_value_set_uint(&x, *it);\n> +\t\t\t\tgst_value_array_append_and_take_value(&v,\n> &x);\n> +\t\t\t}\n> +\t\t} else {\n> +\t\t\tgst_structure_set(structure, prop.c_str(),\n> G_TYPE_UINT,\n> +\t\t\t\t\t  value.get<const uint8_t>(),\n> nullptr);\n> +\t\t}\n> +\t\tbreak;\n> +\tcase ControlTypeUnsigned16:\n> +\t\tif (is_array) {\n> +\t\t\tSpan<const uint16_t> data = value.get<Span<const\n> uint16_t>>();\n> +\t\t\tfor (auto it = data.begin(); it != data.end(); ++it)\n> {\n> +\t\t\t\tg_value_init(&x, type);\n> +\t\t\t\tg_value_set_uint(&x, *it);\n> +\t\t\t\tgst_value_array_append_and_take_value(&v,\n> &x);\n> +\t\t\t}\n> +\t\t} else {\n> +\t\t\tgst_structure_set(structure, prop.c_str(),\n> G_TYPE_UINT,\n> +\t\t\t\t\t  value.get<const uint16_t>(),\n> nullptr);\n> +\t\t}\n> +\t\tbreak;\n> +\tcase ControlTypeUnsigned32:\n> +\t\tif (is_array) {\n> +\t\t\tSpan<const uint32_t> data = value.get<Span<const\n> uint32_t>>();\n> +\t\t\tfor (auto it = data.begin(); it != data.end(); ++it)\n> {\n> +\t\t\t\tg_value_init(&x, type);\n> +\t\t\t\tg_value_set_uint(&x, *it);\n> +\t\t\t\tgst_value_array_append_and_take_value(&v,\n> &x);\n> +\t\t\t}\n> +\t\t} else {\n> +\t\t\tgst_structure_set(structure, prop.c_str(),\n> G_TYPE_UINT,\n> +\t\t\t\t\t  value.get<const uint32_t>(),\n> nullptr);\n> +\t\t}\n> +\t\tbreak;\n> +\tcase ControlTypeInteger32:\n> +\t\tif (is_array) {\n> +\t\t\tSpan<const int32_t> data = value.get<Span<const\n> int32_t>>();\n> +\t\t\tfor (auto it = data.begin(); it != data.end(); ++it)\n> {\n> +\t\t\t\tg_value_init(&x, type);\n> +\t\t\t\tg_value_set_int(&x, *it);\n> +\t\t\t\tgst_value_array_append_and_take_value(&v,\n> &x);\n> +\t\t\t}\n> +\t\t} else {\n> +\t\t\tif (!id->enumerators().empty()) {\n> +\t\t\t\tint32_t val = value.get<int32_t>();\n> +\t\t\t\tconst auto &iter = id-\n> >enumerators().find(val);\n> +\t\t\t\tif (iter != id->enumerators().end()) {\n> +\t\t\t\t\tgst_structure_set(structure,\n> prop.c_str(),\n> +\t\t\t\t\t\t\t  G_TYPE_STRING,\n> +\t\t\t\t\t\t\t  iter-\n> >second.c_str(),\n> +\t\t\t\t\t\t\t  nullptr);\n> +\t\t\t\t} else {\n> +\t\t\t\t\treturn -EINVAL;\n> +\t\t\t\t}\n> +\t\t\t} else {\n> +\t\t\t\tgst_structure_set(structure, prop.c_str(),\n> G_TYPE_INT,\n> +\t\t\t\t\t\t  value.get<const int32_t>(),\n> nullptr);\n> +\t\t\t}\n> +\t\t}\n> +\t\tbreak;\n> +\tcase ControlTypeInteger64:\n> +\t\tif (is_array) {\n> +\t\t\tSpan<const int64_t> data = value.get<Span<const\n> int64_t>>();\n> +\t\t\tfor (auto it = data.begin(); it != data.end(); ++it)\n> {\n> +\t\t\t\tg_value_init(&x, type);\n> +\t\t\t\tg_value_set_int64(&x, *it);\n> +\t\t\t\tgst_value_array_append_and_take_value(&v,\n> &x);\n> +\t\t\t}\n> +\t\t} else {\n> +\t\t\tgst_structure_set(structure, prop.c_str(),\n> G_TYPE_INT64,\n> +\t\t\t\t\t  value.get<const int64_t>(),\n> nullptr);\n> +\t\t}\n> +\t\tbreak;\n> +\tcase ControlTypeFloat:\n> +\t\tif (is_array) {\n> +\t\t\tSpan<const float> data = value.get<Span<const\n> float>>();\n> +\t\t\tfor (auto it = data.begin(); it != data.end(); ++it)\n> {\n> +\t\t\t\tg_value_init(&x, type);\n> +\t\t\t\tg_value_set_float(&x, *it);\n> +\t\t\t\tgst_value_array_append_and_take_value(&v,\n> &x);\n> +\t\t\t}\n> +\t\t} else {\n> +\t\t\tgst_structure_set(structure, prop.c_str(),\n> G_TYPE_FLOAT,\n> +\t\t\t\t\t  value.get<const float>(), nullptr);\n> +\t\t}\n> +\t\tbreak;\n> +\tcase ControlTypeString:\n> +\t\t/*\n> +\t\t * isArray() is always true for strings hence, unset the\n> GValue\n> +\t\t * array because we are going to the toString() helper\n> directly.\n> +\t\t */\n> +\t\tg_value_unset(&v);\n> +\t\tgst_structure_set(structure, prop.c_str(), G_TYPE_STRING,\n> +\t\t\t\t  value.toString().c_str(), nullptr);\n> +\t\tbreak;\n> +\tcase ControlTypeSize:\n> +\t\tif (is_array) {\n> +\t\t\tSpan<const Size> data = value.get<Span<const\n> Size>>();\n> +\t\t\tfor (auto it = data.begin(); it != data.end(); ++it)\n> +\t\t\t\tgst_libcamera_gvalue_set_size(&v, *it);\n> +\t\t} else {\n> +\t\t\tgst_libcamera_gvalue_set_size(&v, value.get<const\n> Size>());\n> +\t\t}\n> +\t\tbreak;\n> +\tcase ControlTypePoint:\n> +\t\tif (is_array) {\n> +\t\t\tSpan<const Point> data = value.get<Span<const\n> Point>>();\n> +\t\t\tfor (auto it = data.begin(); it != data.end(); ++it)\n> +\t\t\t\tgst_libcamera_gvalue_set_point(&v, *it);\n> +\t\t} else {\n> +\t\t\tgst_libcamera_gvalue_set_point(&v, value.get<const\n> Point>());\n> +\t\t}\n> +\t\tbreak;\n> +\tcase ControlTypeRectangle:\n> +\t\tif (is_array) {\n> +\t\t\tSpan<const Rectangle> data = value.get<Span<const\n> Rectangle>>();\n> +\t\t\tfor (auto it = data.begin(); it != data.end(); ++it)\n> +\t\t\t\tgst_libcamera_gvalue_set_rectangle(&v, *it);\n> +\t\t} else {\n> +\t\t\tgst_libcamera_gvalue_set_rectangle(&v,\n> value.get<const Rectangle>());\n> +\t\t}\n> +\t\tbreak;\n> +\tcase ControlTypeNone:\n> +\t\t[[fallthrough]];\n> +\tdefault:\n> +\t\treturn -EINVAL;\n> +\t}\n> +\n> +\tif (GST_VALUE_HOLDS_ARRAY(&v))\n> +\t\tgst_structure_set_value(structure, prop.c_str(), &v);\n> +\n> +\treturn 0;\n> +}\n> diff --git a/src/gstreamer/gstlibcamera-utils.h b/src/gstreamer/gstlibcamera-\n> utils.h\n> index 1812be75..35df56fb 100644\n> --- a/src/gstreamer/gstlibcamera-utils.h\n> +++ b/src/gstreamer/gstlibcamera-utils.h\n> @@ -29,6 +29,9 @@ void gst_libcamera_gvalue_set_point(GValue *value, const\n> libcamera::Point &point\n>  void gst_libcamera_gvalue_set_size(GValue *value, const libcamera::Size\n> &size);\n>  void gst_libcamera_gvalue_set_rectangle(GValue *value, const\n> libcamera::Rectangle &rect);\n>  libcamera::Rectangle gst_libcamera_gvalue_get_rectangle(const GValue *value);\n> +int gst_libcamera_set_structure_field(GstStructure *structure,\n> +\t\t\t\t      const libcamera::ControlId *id,\n> +\t\t\t\t      const libcamera::ControlValue &value);\n>  \n>  #if !GST_CHECK_VERSION(1, 16, 0)\n>  static inline void gst_clear_event(GstEvent **event_ptr)\n> diff --git a/src/gstreamer/gstlibcameraprovider.cpp\n> b/src/gstreamer/gstlibcameraprovider.cpp\n> index 5da96ea3..1545c856 100644\n> --- a/src/gstreamer/gstlibcameraprovider.cpp\n> +++ b/src/gstreamer/gstlibcameraprovider.cpp\n> @@ -12,6 +12,7 @@\n>  \n>  #include <libcamera/camera.h>\n>  #include <libcamera/camera_manager.h>\n> +#include <libcamera/property_ids.h>\n>  \n>  #include \"gstlibcamerasrc.h\"\n>  #include \"gstlibcamera-utils.h\"\n> @@ -144,12 +145,24 @@ gst_libcamera_device_new(const std::shared_ptr<Camera>\n> &camera)\n>  \t\t\tgst_caps_append(caps, sub_caps);\n>  \t}\n>  \n> +\tg_autoptr(GstStructure) props = gst_structure_new_empty(\"camera-\n> properties\");\n> +\tfor (const auto &[key, value] : camera->properties()) {\n> +\t\tconst ControlId *id = properties::properties.at(key);\n> +\n> +\t\tint ret = gst_libcamera_set_structure_field(props, id,\n> value);\n> +\t\tif (ret < 0) {\n> +\t\t\tGST_ERROR(\"Failed to retrieve value for %s property\",\n> id->name().c_str());\n> +\t\t\treturn nullptr;\n> +\t\t}\n> +\t}\n> +\n>  \treturn GST_DEVICE(g_object_new(GST_TYPE_LIBCAMERA_DEVICE,\n>  \t\t\t\t       /* \\todo Use a unique identifier\n> instead of camera name. */\n>  \t\t\t\t       \"name\", name,\n>  \t\t\t\t       \"display-name\", name,\n>  \t\t\t\t       \"caps\", caps,\n>  \t\t\t\t       \"device-class\", \"Source/Video\",\n> +\t\t\t\t       \"properties\", props,\n>  \t\t\t\t       nullptr));\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 20918BDCC1\n\tfor <parsemail@patchwork.libcamera.org>;\n\tTue, 29 Jul 2025 16:07:02 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 4AA7C691E3;\n\tTue, 29 Jul 2025 18:07:01 +0200 (CEST)","from bali.collaboradmins.com (bali.collaboradmins.com\n\t[148.251.105.195])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 0169869052\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 29 Jul 2025 18:06:57 +0200 (CEST)","from [IPv6:2606:6d00:11:5a76::5ac] (unknown\n\t[IPv6:2606:6d00:11:5a76::5ac])\n\t(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n\tkey-exchange X25519 server-signature RSA-PSS (4096 bits)\n\tserver-digest SHA256)\n\t(No client certificate requested) (Authenticated sender: nicolas)\n\tby bali.collaboradmins.com (Postfix) with ESMTPSA id F060917E0CE7;\n\tTue, 29 Jul 2025 18:06:56 +0200 (CEST)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (2048-bit key;\n\tunprotected) header.d=collabora.com header.i=@collabora.com\n\theader.b=\"X9z0LON7\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=collabora.com;\n\ts=mail; t=1753805217;\n\tbh=akcks70wqN4N8aZ2jnqEetBsSJiYCSVWwSImrglf66I=;\n\th=Subject:From:To:Cc:Date:In-Reply-To:References:From;\n\tb=X9z0LON7+brRiYw+qMlWKRKXesJ1T+Ki23lT0hFi4BDWziaxJaQiIIMjVzPhPd16s\n\tfeElZWy/li6VPrF6T2G4Sh/o+DRchV+qZ9SjXrybBd4kLGc1bnLQ/HIE9jHxXBEbAe\n\tbQd0MXEV8cRIPk6uHpIVnS4jvs21YacwozUG//a7/dPyYGS6SNw576HaLoLwi04Sp6\n\tIcUoCduvC1rvZC8bgfN8xLnIbXnhouhkM+9bMjBsMfJW+L0SJX7cHD2hb+MGsobplC\n\tnP3FDwUGXEyjIvIU0IEQVZpxMGPxfqGGrmSN/jiuxx3M+xJaUQdlBN4vG8VmpJO469\n\tYUft+nEDiBrxA==","Message-ID":"<71cfbca88c0673e0463ceaf8469ed27ab0f2bc69.camel@collabora.com>","Subject":"Re: [PATCH v5 3/3] gstreamer: Report camera properties as device\n\tproperties","From":"Nicolas Dufresne <nicolas.dufresne@collabora.com>","To":"Umang Jain <uajain@igalia.com>, libcamera-devel@lists.libcamera.org","Cc":"Jaslo Ziska <jaslo@ziska.de>","Date":"Tue, 29 Jul 2025 12:06:51 -0400","In-Reply-To":"<20250729153915.159243-4-uajain@igalia.com>","References":"<20250729153915.159243-1-uajain@igalia.com>\n\t<20250729153915.159243-4-uajain@igalia.com>","Autocrypt":"addr=nicolas.dufresne@collabora.com; prefer-encrypt=mutual;\n\tkeydata=mQGiBEUQN0MRBACQYceNSezSdMjx7sx6gwKkMghrrODgl3B0eXBTgNp6c431IfOOEsdvk\n\toOh1kwoYcQgbg4MXw6beOltysX4e8fFWsiRkc2nvvRW9ir9kHDm49MkBLqaDjTqOkYKNMiurFW+go\n\tzpr/lUW15QqT6v68RYe0zRdtwGZqeLzX2LVuukGwCg4AISzswrrYHNV7vQLcbaUhPgIl0D+gILYT9\n\tTJgAEK4YHW+bFRcY+cgUFoLQqQayECMlctKoLOE69nIYOc/hDr9uih1wxrQ/yL0NJvQCohSPyoyLF\n\t9b2EuIGhQVp05XP7FzlTxhYvGO/DtO08ec85+bTfVBMV6eeY4MS3ZU+1z7ObD7Pf29YjyTehN2Dan\n\t6w1g2rBk5MoA/9nDocSlk4pbFpsYSFmVHsDiAOFje3+iY4ftVDKunKYWMhwRVBjAREOByBagmRau0\n\tcLEcElpf4hX5f978GoxSGIsiKoDAlXX+ICDOWC1/EXhEEmBR1gL0QJgiVviNyLfGJlZWnPjw6xhhm\n\ttHYWTDxBOP5peztyc2PqeKsLsLWzAr7QnTmljb2xhcyBEdWZyZXNuZSA8bmljb2xhc0BuZHVmcmVz\n\tbmUuY2E+iGIEExECACIFAlXA3CACGwMGCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEHFTAi2sB\n\tqgcJngAnRDBTr8bhzuH0KQwFP1nEYtfgpKdAKCrQ/sJfuG/8zsd7J8wVl7y3e8ARbRDTmljb2xhcy\n\tBEdWZyZXNuZSAoQi4gU2MuIEluZm9ybWF0aXF1ZSkgPG5pY29sYXMuZHVmcmVzbmVAZ21haWwuY29\n\ttPohgBBMRAgAgBQJFlCyOAhsDBgsJCAcDAgQVAggDBBYCAwECHgECF4AACgkQcVMCLawGqBwhLQCg\n\tzYlrLBj6KIAZ4gmsfjXD6ZtddT8AoIeGDicVq5WvMHNWign6ApQcZUihtElOaWNvbGFzIER1ZnJlc\n\t25lIChCLiBTYy4gSW5mb3JtYXRpcXVlKSA8bmljb2xhcy5kdWZyZXNuZUBjb2xsYWJvcmEuY28udW\n\ts+iGIEExECACIFAkuzca8CGwMGCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEHFTAi2sBqgcQX8\n\tAn2By6LDEeMxi4B9hUbpvRnzaaeNqAJ9Rox8rfqHZnSErw9bCHiBwvwJZ77QxTmljb2xhcyBEdWZy\n\tZXNuZSA8bmljb2xhcy5kdWZyZXNuZUBjb2xsYWJvcmEuY29tPohiBBMRAgAiBQJNzZzPAhsDBgsJC\n\tAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAKCRBxUwItrAaoHLlxAKCYAGf4JL7DYDLs/188CPMGuwLypw\n\tCfWKc9DorA9f5pyYlD5pQo6SgSoiC0R05pY29sYXMgRHVmcmVzbmUgKEIgU2MuIEluZm9ybWF0aXF\n\t1ZSkgPG5pY29sYXMuZHVmcmVzbmVAdXNoZXJicm9va2UuY2E+iGAEExECACAFAkUQN0MCGwMGCwkI\n\tBwMCBBUCCAMEFgIDAQIeAQIXgAAKCRBxUwItrAaoHPTnAJ0WGgJJVspoctAvEcI00mtp5WAFGgCgr\n\t+E7ItOqZEHAs+xabBgknYZIFPU=","Organization":"Collabora Canada","Content-Type":"multipart/signed; micalg=\"pgp-sha1\";\n\tprotocol=\"application/pgp-signature\"; \n\tboundary=\"=-8OaBHuy08hG9b+7smPO6\"","User-Agent":"Evolution 3.56.2 (3.56.2-1.fc42) ","MIME-Version":"1.0","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>"}}]