[{"id":35226,"web_url":"https://patchwork.libcamera.org/comment/35226/","msgid":"<6b0b787b4d7f2e65ed126627a5f46a941d4ebe91.camel@collabora.com>","date":"2025-07-28T23:07:09","subject":"Re: [PATCH v4 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 jeudi 24 juillet 2025 à 19:03 +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 corresponding\n> gstreamer GType. If the ControlValue is an array of values\n> (ControlValue::isArray()), GValue with type GST_TYPE_ARRAY is used to set the\n> value of that ControlValue with the corresponding GType.\n> \n\nCosmetic, but the line width seems to very more then needed.\n\n> \n> Signed-off-by: Umang Jain <uajain@igalia.com>\n> ---\n>  src/gstreamer/gstlibcamera-utils.cpp   | 199 +++++++++++++++++++++++++\n>  src/gstreamer/gstlibcamera-utils.h     |   3 +\n>  src/gstreamer/gstlibcameraprovider.cpp |  16 ++\n>  3 files changed, 218 insertions(+)\n> \n> diff --git a/src/gstreamer/gstlibcamera-utils.cpp b/src/gstreamer/gstlibcamera-utils.cpp\n> index 63e00fe0..3d728cbc 100644\n> --- a/src/gstreamer/gstlibcamera-utils.cpp\n> +++ b/src/gstreamer/gstlibcamera-utils.cpp\n> @@ -333,6 +333,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_LONG },\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 +733,175 @@ gst_libcamera_get_camera_manager(int &ret)\n>  \n>  \treturn cm;\n>  }\n> +\n> +int gst_libcamera_set_structure_field(GstStructure *structure, GString *field,\n> +\t\t\t\t      const ControlId *id, const ControlValue &value)\n> +{\n> +\tGValue v = G_VALUE_INIT;\n> +\tGValue 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> +\t\tg_value_init(&x, type);\n> +\t}\n> +\n> +\tswitch (value.type()) {\n> +\tcase ControlTypeBool:\n> +\t\tif (is_array) {\n> +\t\t\tSpan<const bool> data = value.get<Span<const bool>>();\n> +\t\t\tfor (auto it = data.begin(); it != data.end(); ++it) {\n> +\t\t\t\tg_value_set_boolean(&x, *it);\n> +\t\t\t\tgst_value_array_append_and_take_value(&v, &x);\n> +\t\t\t\tg_value_init(&x, type);\n> +\t\t\t}\n> +\t\t} else {\n> +\t\t\tgst_structure_set(structure, field->str, 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 uint8_t>>();\n> +\t\t\tfor (auto it = data.begin(); it != data.end(); ++it) {\n> +\t\t\t\tg_value_set_uint(&x, *it);\n> +\t\t\t\tgst_value_array_append_and_take_value(&v, &x);\n> +\t\t\t\tg_value_init(&x, type);\n> +\t\t\t}\n> +\t\t} else {\n> +\t\t\tgst_structure_set(structure, field->str, G_TYPE_UINT,\n> +\t\t\t\t\t  value.get<const uint8_t>(), 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 uint16_t>>();\n> +\t\t\tfor (auto it = data.begin(); it != data.end(); ++it) {\n> +\t\t\t\tg_value_set_uint(&x, *it);\n> +\t\t\t\tgst_value_array_append_and_take_value(&v, &x);\n> +\t\t\t\tg_value_init(&x, type);\n> +\t\t\t}\n> +\t\t} else {\n> +\t\t\tgst_structure_set(structure, field->str, G_TYPE_UINT,\n> +\t\t\t\t\t  value.get<const uint16_t>(), 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 uint32_t>>();\n> +\t\t\tfor (auto it = data.begin(); it != data.end(); ++it) {\n> +\t\t\t\tg_value_set_ulong(&x, *it);\n\nYou don't really need a long do you ?\n\n> +\t\t\t\tgst_value_array_append_and_take_value(&v, &x);\n> +\t\t\t\tg_value_init(&x, type);\n> +\t\t\t}\n> +\t\t} else {\n> +\t\t\tgst_structure_set(structure, field->str, G_TYPE_ULONG,\n> +\t\t\t\t\t  value.get<const uint32_t>(), 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 int32_t>>();\n> +\t\t\tfor (auto it = data.begin(); it != data.end(); ++it) {\n> +\t\t\t\tg_value_set_long(&x, *it);\n> +\t\t\t\tgst_value_array_append_and_take_value(&v, &x);\n> +\t\t\t\tg_value_init(&x, type);\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->enumerators().find(val);\n> +\t\t\t\tif (iter != id->enumerators().end()) {\n> +\t\t\t\t\tgst_structure_set(structure, field->str,\n> +\t\t\t\t\t\t\t  G_TYPE_STRING,\n> +\t\t\t\t\t\t\t  iter->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, field->str, G_TYPE_LONG,\n> +\t\t\t\t\t\t  value.get<const int32_t>(), 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 int64_t>>();\n> +\t\t\tfor (auto it = data.begin(); it != data.end(); ++it) {\n> +\t\t\t\tg_value_set_int64(&x, *it);\n> +\t\t\t\tgst_value_array_append_and_take_value(&v, &x);\n> +\t\t\t\tg_value_init(&x, type);\n> +\t\t\t}\n> +\t\t} else {\n> +\t\t\tgst_structure_set(structure, field->str, G_TYPE_INT64,\n> +\t\t\t\t\t  value.get<const int64_t>(), 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 float>>();\n> +\t\t\tfor (auto it = data.begin(); it != data.end(); ++it) {\n> +\t\t\t\tg_value_set_float(&x, *it);\n> +\t\t\t\tgst_value_array_append_and_take_value(&v, &x);\n> +\t\t\t\tg_value_init(&x, type);\n> +\t\t\t}\n> +\t\t} else {\n> +\t\t\tgst_structure_set(structure, field->str, G_TYPE_FLOAT,\n> +\t\t\t\t\t  value.get<const float>(), nullptr);\n> +\t\t}\n> +\t\tbreak;\n> +\tcase ControlTypeString:\n\nWhy not using GST_TYPE_ARRAY too ? Otherwise, unset 'v'.\n\n> +\t\tgst_structure_set(structure, field->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 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 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 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 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 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, 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> +\t/*\n> +\t * Set values for array types with the only exception of strings which\n> +\t * is handled by ControlValue::toString() helper directly above.\n> +\t */\n> +\tif ((is_array || type == GST_TYPE_ARRAY) &&\n> +\t    (value.type() != ControlTypeString))\n\nThis would be simpler no ?\n\n\tif (GST_VALUE_HOLDS_ARRAY(v)\n\n> +\t\tgst_structure_set_value(structure, field->str, &v);\n> +\n> +\tg_value_unset(&v);\n> +\tg_value_unset(&x);\n> +\n> +\treturn 0;\n> +}\n> diff --git a/src/gstreamer/gstlibcamera-utils.h b/src/gstreamer/gstlibcamera-utils.h\n> index 1812be75..53ad6911 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 libcamera::Point &point\n>  void gst_libcamera_gvalue_set_size(GValue *value, const libcamera::Size &size);\n>  void gst_libcamera_gvalue_set_rectangle(GValue *value, const libcamera::Rectangle &rect);\n>  libcamera::Rectangle gst_libcamera_gvalue_get_rectangle(const GValue *value);\n> +int gst_libcamera_set_structure_field(GstStructure *structure, GString *field,\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 b/src/gstreamer/gstlibcameraprovider.cpp\n> index 5da96ea3..06501fd8 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,27 @@ gst_libcamera_device_new(const std::shared_ptr<Camera> &camera)\n>  \t\t\tgst_caps_append(caps, sub_caps);\n>  \t}\n>  \n> +\tg_autoptr(GstStructure) props = gst_structure_new_empty(\"camera-properties\");\n> +\tfor (const auto &[key, value] : camera->properties()) {\n> +\t\tconst ControlId *id = properties::properties.at(key);\n> +\n> +\t\tg_autoptr(GString) prop_str = g_string_new(\"api.libcamera.\");\n> +\t\tg_string_append(prop_str, id->name().c_str());\n\nWouldn't this be nicer using C++ ?\n\n> +\n> +\t\tint ret = gst_libcamera_set_structure_field(props, prop_str, id, value);\n> +\t\tif (ret < 0) {\n> +\t\t\tGST_ERROR(\"Failed to retrieve value for %s property\", 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 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 DB1D7BDCC1\n\tfor <parsemail@patchwork.libcamera.org>;\n\tMon, 28 Jul 2025 23:07:16 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 83289691C5;\n\tTue, 29 Jul 2025 01:07:15 +0200 (CEST)","from bali.collaboradmins.com (bali.collaboradmins.com\n\t[IPv6:2a01:4f8:201:9162::2])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id AC5FE6146A\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 29 Jul 2025 01:07:13 +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 94FF717E1321;\n\tTue, 29 Jul 2025 01:07:12 +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=\"G+8EyAOm\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=collabora.com;\n\ts=mail; t=1753744033;\n\tbh=ESmLwHCUaFY1noF8GFx3pFEh+RvJjUIWqoe2hOzeNWI=;\n\th=Subject:From:To:Cc:Date:In-Reply-To:References:From;\n\tb=G+8EyAOmKlvh0eSlUMmh14UhyaoUm+FD1VDfDzt8fvcwQuYnMxkcjNU6Tgvr+mJvu\n\tGeH198YvS8ciDu7mgqrCvMx46MfYsUQQVwOxGlIomeLxxQkqWNattxv65qfrbdZCkN\n\tkWolKkMruHjCrhrB+CqjL6UfGpIT/fFSVMherfSLiz0zGz+dvVZ8JQkQ65BBHliyE8\n\tZwDTT9hhR4gduCw3gBFx6EUVKv0vHKNGwtL6QnN85KQMpsW8Uixb3cgImtUDv4jHEc\n\tOiK/WhFaY2U2K0daUkLGVrJ+JjlEoOFLerlnKfnVQScxoprqQ6aCYV1zpheDrSJ/3x\n\tag22N4mHlcByQ==","Message-ID":"<6b0b787b4d7f2e65ed126627a5f46a941d4ebe91.camel@collabora.com>","Subject":"Re: [PATCH v4 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":"Mon, 28 Jul 2025 19:07:09 -0400","In-Reply-To":"<20250724133343.353044-4-uajain@igalia.com>","References":"<20250724133343.353044-1-uajain@igalia.com>\n\t<20250724133343.353044-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=\"=-sBAnUChV7HGbEVy2JRqR\"","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>"}},{"id":35227,"web_url":"https://patchwork.libcamera.org/comment/35227/","msgid":"<gapqqaxiv64cqmuqagaci4boosmg57fnevrj66yxn3het3jj2u@ulrgrr4hvw2c>","date":"2025-07-29T06:33:19","subject":"Re: [PATCH v4 3/3] gstreamer: Report camera properties as device\n\tproperties","submitter":{"id":232,"url":"https://patchwork.libcamera.org/api/people/232/","name":"Umang Jain","email":"uajain@igalia.com"},"content":"On Mon, Jul 28, 2025 at 07:07:09PM -0400, Nicolas Dufresne wrote:\n> Le jeudi 24 juillet 2025 à 19:03 +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 corresponding\n> > gstreamer GType. If the ControlValue is an array of values\n> > (ControlValue::isArray()), GValue with type GST_TYPE_ARRAY is used to set the\n> > value of that ControlValue with the corresponding GType.\n> > \n> \n> Cosmetic, but the line width seems to very more then needed.\n> \n> > \n> > Signed-off-by: Umang Jain <uajain@igalia.com>\n> > ---\n> >  src/gstreamer/gstlibcamera-utils.cpp   | 199 +++++++++++++++++++++++++\n> >  src/gstreamer/gstlibcamera-utils.h     |   3 +\n> >  src/gstreamer/gstlibcameraprovider.cpp |  16 ++\n> >  3 files changed, 218 insertions(+)\n> > \n> > diff --git a/src/gstreamer/gstlibcamera-utils.cpp b/src/gstreamer/gstlibcamera-utils.cpp\n> > index 63e00fe0..3d728cbc 100644\n> > --- a/src/gstreamer/gstlibcamera-utils.cpp\n> > +++ b/src/gstreamer/gstlibcamera-utils.cpp\n> > @@ -333,6 +333,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_LONG },\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 +733,175 @@ gst_libcamera_get_camera_manager(int &ret)\n> >  \n> >  \treturn cm;\n> >  }\n> > +\n> > +int gst_libcamera_set_structure_field(GstStructure *structure, GString *field,\n> > +\t\t\t\t      const ControlId *id, const ControlValue &value)\n> > +{\n> > +\tGValue v = G_VALUE_INIT;\n> > +\tGValue 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> > +\t\tg_value_init(&x, type);\n> > +\t}\n> > +\n> > +\tswitch (value.type()) {\n> > +\tcase ControlTypeBool:\n> > +\t\tif (is_array) {\n> > +\t\t\tSpan<const bool> data = value.get<Span<const bool>>();\n> > +\t\t\tfor (auto it = data.begin(); it != data.end(); ++it) {\n> > +\t\t\t\tg_value_set_boolean(&x, *it);\n> > +\t\t\t\tgst_value_array_append_and_take_value(&v, &x);\n> > +\t\t\t\tg_value_init(&x, type);\n> > +\t\t\t}\n> > +\t\t} else {\n> > +\t\t\tgst_structure_set(structure, field->str, 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 uint8_t>>();\n> > +\t\t\tfor (auto it = data.begin(); it != data.end(); ++it) {\n> > +\t\t\t\tg_value_set_uint(&x, *it);\n> > +\t\t\t\tgst_value_array_append_and_take_value(&v, &x);\n> > +\t\t\t\tg_value_init(&x, type);\n> > +\t\t\t}\n> > +\t\t} else {\n> > +\t\t\tgst_structure_set(structure, field->str, G_TYPE_UINT,\n> > +\t\t\t\t\t  value.get<const uint8_t>(), 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 uint16_t>>();\n> > +\t\t\tfor (auto it = data.begin(); it != data.end(); ++it) {\n> > +\t\t\t\tg_value_set_uint(&x, *it);\n> > +\t\t\t\tgst_value_array_append_and_take_value(&v, &x);\n> > +\t\t\t\tg_value_init(&x, type);\n> > +\t\t\t}\n> > +\t\t} else {\n> > +\t\t\tgst_structure_set(structure, field->str, G_TYPE_UINT,\n> > +\t\t\t\t\t  value.get<const uint16_t>(), 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 uint32_t>>();\n> > +\t\t\tfor (auto it = data.begin(); it != data.end(); ++it) {\n> > +\t\t\t\tg_value_set_ulong(&x, *it);\n> \n> You don't really need a long do you ?\n\n\nNo? Should it be set to uint? I initially thought of it, but I saw GLib\nprovides long types, I decided to use it pedantically.\n\nI also realised in type_gtype_map[] I have set ControlTypeUnsigned32 to\nG_TYPE_UINT; whereas for consistency I should have used G_TYPE_ULONG.\n\n> \n> > +\t\t\t\tgst_value_array_append_and_take_value(&v, &x);\n> > +\t\t\t\tg_value_init(&x, type);\n> > +\t\t\t}\n> > +\t\t} else {\n> > +\t\t\tgst_structure_set(structure, field->str, G_TYPE_ULONG,\n> > +\t\t\t\t\t  value.get<const uint32_t>(), 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 int32_t>>();\n> > +\t\t\tfor (auto it = data.begin(); it != data.end(); ++it) {\n> > +\t\t\t\tg_value_set_long(&x, *it);\n> > +\t\t\t\tgst_value_array_append_and_take_value(&v, &x);\n> > +\t\t\t\tg_value_init(&x, type);\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->enumerators().find(val);\n> > +\t\t\t\tif (iter != id->enumerators().end()) {\n> > +\t\t\t\t\tgst_structure_set(structure, field->str,\n> > +\t\t\t\t\t\t\t  G_TYPE_STRING,\n> > +\t\t\t\t\t\t\t  iter->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, field->str, G_TYPE_LONG,\n> > +\t\t\t\t\t\t  value.get<const int32_t>(), 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 int64_t>>();\n> > +\t\t\tfor (auto it = data.begin(); it != data.end(); ++it) {\n> > +\t\t\t\tg_value_set_int64(&x, *it);\n> > +\t\t\t\tgst_value_array_append_and_take_value(&v, &x);\n> > +\t\t\t\tg_value_init(&x, type);\n> > +\t\t\t}\n> > +\t\t} else {\n> > +\t\t\tgst_structure_set(structure, field->str, G_TYPE_INT64,\n> > +\t\t\t\t\t  value.get<const int64_t>(), 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 float>>();\n> > +\t\t\tfor (auto it = data.begin(); it != data.end(); ++it) {\n> > +\t\t\t\tg_value_set_float(&x, *it);\n> > +\t\t\t\tgst_value_array_append_and_take_value(&v, &x);\n> > +\t\t\t\tg_value_init(&x, type);\n> > +\t\t\t}\n> > +\t\t} else {\n> > +\t\t\tgst_structure_set(structure, field->str, G_TYPE_FLOAT,\n> > +\t\t\t\t\t  value.get<const float>(), nullptr);\n> > +\t\t}\n> > +\t\tbreak;\n> > +\tcase ControlTypeString:\n> \n> Why not using GST_TYPE_ARRAY too ? Otherwise, unset 'v'.\n\nControlTypeString is always an array of chars. It would simply split off\neach char if I use GST_TYPE_ARRAY here. Directly using string helpers from\nlibcamera are helpful here for ControlTypeString.\n\nRegarding unsetting v, I preferred to it at end of the function, near to\nreturn; Does g_autoptr() works for GValue? I'll probably experiment\nthis for RAII style cleanup.\n> \n> > +\t\tgst_structure_set(structure, field->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 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 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 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 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 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, 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> > +\t/*\n> > +\t * Set values for array types with the only exception of strings which\n> > +\t * is handled by ControlValue::toString() helper directly above.\n> > +\t */\n> > +\tif ((is_array || type == GST_TYPE_ARRAY) &&\n> > +\t    (value.type() != ControlTypeString))\n> \n> This would be simpler no ?\n> \n> \tif (GST_VALUE_HOLDS_ARRAY(v)\n> \n> > +\t\tgst_structure_set_value(structure, field->str, &v);\n> > +\n> > +\tg_value_unset(&v);\n> > +\tg_value_unset(&x);\n> > +\n> > +\treturn 0;\n> > +}\n> > diff --git a/src/gstreamer/gstlibcamera-utils.h b/src/gstreamer/gstlibcamera-utils.h\n> > index 1812be75..53ad6911 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 libcamera::Point &point\n> >  void gst_libcamera_gvalue_set_size(GValue *value, const libcamera::Size &size);\n> >  void gst_libcamera_gvalue_set_rectangle(GValue *value, const libcamera::Rectangle &rect);\n> >  libcamera::Rectangle gst_libcamera_gvalue_get_rectangle(const GValue *value);\n> > +int gst_libcamera_set_structure_field(GstStructure *structure, GString *field,\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 b/src/gstreamer/gstlibcameraprovider.cpp\n> > index 5da96ea3..06501fd8 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,27 @@ gst_libcamera_device_new(const std::shared_ptr<Camera> &camera)\n> >  \t\t\tgst_caps_append(caps, sub_caps);\n> >  \t}\n> >  \n> > +\tg_autoptr(GstStructure) props = gst_structure_new_empty(\"camera-properties\");\n> > +\tfor (const auto &[key, value] : camera->properties()) {\n> > +\t\tconst ControlId *id = properties::properties.at(key);\n> > +\n> > +\t\tg_autoptr(GString) prop_str = g_string_new(\"api.libcamera.\");\n> > +\t\tg_string_append(prop_str, id->name().c_str());\n> \n> Wouldn't this be nicer using C++ ?\n> \n> > +\n> > +\t\tint ret = gst_libcamera_set_structure_field(props, prop_str, id, value);\n> > +\t\tif (ret < 0) {\n> > +\t\t\tGST_ERROR(\"Failed to retrieve value for %s property\", 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 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 9CEFCBDC71\n\tfor <parsemail@patchwork.libcamera.org>;\n\tTue, 29 Jul 2025 06:33:33 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 4EA14691C4;\n\tTue, 29 Jul 2025 08:33:32 +0200 (CEST)","from fanzine2.igalia.com (fanzine2.igalia.com [213.97.179.56])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id CD6B76146A\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 29 Jul 2025 08:33:16 +0200 (CEST)","from [49.36.71.87] (helo=uajain) by fanzine2.igalia.com with\n\tesmtpsa \n\t(Cipher TLS1.3:ECDHE_SECP256R1__RSA_PSS_RSAE_SHA256__AES_256_GCM:256)\n\t(Exim) id 1ugdth-005Hua-Ne; Tue, 29 Jul 2025 08:33:14 +0200"],"Authentication-Results":"lancelot.ideasonboard.com;\n\tdkim=fail reason=\"signature verification failed\" (2048-bit key;\n\tunprotected) header.d=igalia.com header.i=@igalia.com\n\theader.b=\"fNurHMHS\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=igalia.com;\n\ts=20170329;\n\th=In-Reply-To:Content-Transfer-Encoding:Content-Type:MIME-Version\n\t:References:Message-ID:Subject:Cc:To:From:Date:Sender:Reply-To:Content-ID:\n\tContent-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc\n\t:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe:\n\tList-Post:List-Owner:List-Archive;\n\tbh=YZvVC0jU7vGC1N8BfYg3e6+rx8kcvRHK7DkQMlvggHY=;\n\tb=fNurHMHSAal47MKUm/0ONTKLjo\n\t0XEzHa1nr1oc+njmS9Qe5EV6r8UKf8l6MOi6NQu9H/AUGyFmDTjxITN62rywDdOYo11WIvOYYo7cg\n\tWzXMyB0lVAxX7FNuDw+LnX7v05KPUUh1PMDeqHkLwXDvFKjEdisAhqSPYKOw9Z8aURIzzO2apPUfT\n\tNxPjEzqobOOjxVbv1MnXEWqy4MBtmKHY0POwoBMkxDEn0YVgOgKENU2AK6yq6CZS454Bd+KxLaK/N\n\tqaAZYqHK0v2jIQoXcrY8AkC+zCaWwO+nP7Q1hXJZfWP2uMQ2h4qAT8RKlj+Mj8NCTl5IxvsBNSXaD\n\tMvBobVIQ==;","Date":"Tue, 29 Jul 2025 12:03:19 +0530","From":"Umang Jain <uajain@igalia.com>","To":"Nicolas Dufresne <nicolas.dufresne@collabora.com>","Cc":"libcamera-devel@lists.libcamera.org, Jaslo Ziska <jaslo@ziska.de>","Subject":"Re: [PATCH v4 3/3] gstreamer: Report camera properties as device\n\tproperties","Message-ID":"<gapqqaxiv64cqmuqagaci4boosmg57fnevrj66yxn3het3jj2u@ulrgrr4hvw2c>","References":"<20250724133343.353044-1-uajain@igalia.com>\n\t<20250724133343.353044-4-uajain@igalia.com>\n\t<6b0b787b4d7f2e65ed126627a5f46a941d4ebe91.camel@collabora.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=iso-8859-1","Content-Disposition":"inline","Content-Transfer-Encoding":"8bit","In-Reply-To":"<6b0b787b4d7f2e65ed126627a5f46a941d4ebe91.camel@collabora.com>","User-Agent":"NeoMutt/20250510-dirty","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":35232,"web_url":"https://patchwork.libcamera.org/comment/35232/","msgid":"<9834822164069953dba1533c830783a66863f5ac.camel@collabora.com>","date":"2025-07-29T12:29:54","subject":"Re: [PATCH v4 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 à 12:03 +0530, Umang Jain a écrit :\n> On Mon, Jul 28, 2025 at 07:07:09PM -0400, Nicolas Dufresne wrote:\n> > Le jeudi 24 juillet 2025 à 19:03 +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\n> > > gstreamer GType. If the ControlValue is an array of values\n> > > (ControlValue::isArray()), GValue with type GST_TYPE_ARRAY is used to set\n> > > the\n> > > value of that ControlValue with the corresponding GType.\n> > > \n> > \n> > Cosmetic, but the line width seems to very more then needed.\n> > \n> > > \n> > > Signed-off-by: Umang Jain <uajain@igalia.com>\n> > > ---\n> > >  src/gstreamer/gstlibcamera-utils.cpp   | 199 +++++++++++++++++++++++++\n> > >  src/gstreamer/gstlibcamera-utils.h     |   3 +\n> > >  src/gstreamer/gstlibcameraprovider.cpp |  16 ++\n> > >  3 files changed, 218 insertions(+)\n> > > \n> > > diff --git a/src/gstreamer/gstlibcamera-utils.cpp\n> > > b/src/gstreamer/gstlibcamera-utils.cpp\n> > > index 63e00fe0..3d728cbc 100644\n> > > --- a/src/gstreamer/gstlibcamera-utils.cpp\n> > > +++ b/src/gstreamer/gstlibcamera-utils.cpp\n> > > @@ -333,6 +333,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_LONG },\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 +733,175 @@ gst_libcamera_get_camera_manager(int &ret)\n> > >  \n> > >  \treturn cm;\n> > >  }\n> > > +\n> > > +int gst_libcamera_set_structure_field(GstStructure *structure, GString\n> > > *field,\n> > > +\t\t\t\t      const ControlId *id, const\n> > > ControlValue &value)\n> > > +{\n> > > +\tGValue v = G_VALUE_INIT;\n> > > +\tGValue 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> > > +\t\tg_value_init(&x, type);\n> > > +\t}\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();\n> > > ++it) {\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\tg_value_init(&x, type);\n> > > +\t\t\t}\n> > > +\t\t} else {\n> > > +\t\t\tgst_structure_set(structure, field->str,\n> > > G_TYPE_BOOLEAN,\n> > > +\t\t\t\t\t  value.get<const bool>(),\n> > > 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();\n> > > ++it) {\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\tg_value_init(&x, type);\n> > > +\t\t\t}\n> > > +\t\t} else {\n> > > +\t\t\tgst_structure_set(structure, field->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();\n> > > ++it) {\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\tg_value_init(&x, type);\n> > > +\t\t\t}\n> > > +\t\t} else {\n> > > +\t\t\tgst_structure_set(structure, field->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();\n> > > ++it) {\n> > > +\t\t\t\tg_value_set_ulong(&x, *it);\n> > \n> > You don't really need a long do you ?\n> \n> \n> No? Should it be set to uint? I initially thought of it, but I saw GLib\n> provides long types, I decided to use it pedantically.\n> \n> I also realised in type_gtype_map[] I have set ControlTypeUnsigned32 to\n> G_TYPE_UINT; whereas for consistency I should have used G_TYPE_ULONG.\n\nTypically, you want to use unsigned long for cases where you need the integers\nto match the size of a pointer. For all supported platform, uint32 fits into\nuint, and its size won't depends on the pointer size.\n\n(glib have no support for anything that is not 32 or 64 bits)\n\n> \n> > \n> > > +\t\t\t\tgst_value_array_append_and_take_value(&v,\n> > > &x);\n> > > +\t\t\t\tg_value_init(&x, type);\n> > > +\t\t\t}\n> > > +\t\t} else {\n> > > +\t\t\tgst_structure_set(structure, field->str,\n> > > G_TYPE_ULONG,\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();\n> > > ++it) {\n> > > +\t\t\t\tg_value_set_long(&x, *it);\n> > > +\t\t\t\tgst_value_array_append_and_take_value(&v,\n> > > &x);\n> > > +\t\t\t\tg_value_init(&x, type);\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> > > field->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, field->str,\n> > > G_TYPE_LONG,\n> > > +\t\t\t\t\t\t  value.get<const\n> > > int32_t>(), 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();\n> > > ++it) {\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\tg_value_init(&x, type);\n> > > +\t\t\t}\n> > > +\t\t} else {\n> > > +\t\t\tgst_structure_set(structure, field->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();\n> > > ++it) {\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\tg_value_init(&x, type);\n> > > +\t\t\t}\n> > > +\t\t} else {\n> > > +\t\t\tgst_structure_set(structure, field->str,\n> > > G_TYPE_FLOAT,\n> > > +\t\t\t\t\t  value.get<const float>(),\n> > > nullptr);\n> > > +\t\t}\n> > > +\t\tbreak;\n> > > +\tcase ControlTypeString:\n> > \n> > Why not using GST_TYPE_ARRAY too ? Otherwise, unset 'v'.\n> \n> ControlTypeString is always an array of chars. It would simply split off\n> each char if I use GST_TYPE_ARRAY here. Directly using string helpers from\n> libcamera are helpful here for ControlTypeString.\n> \n> Regarding unsetting v, I preferred to it at end of the function, near to\n> return; Does g_autoptr() works for GValue? I'll probably experiment\n> this for RAII style cleanup.\n> > \n> > > +\t\tgst_structure_set(structure, field->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();\n> > > ++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();\n> > > ++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,\n> > > value.get<const 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();\n> > > ++it)\n> > > +\t\t\t\tgst_libcamera_gvalue_set_rectangle(&v,\n> > > *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> > > +\t/*\n> > > +\t * Set values for array types with the only exception of strings\n> > > which\n> > > +\t * is handled by ControlValue::toString() helper directly above.\n> > > +\t */\n> > > +\tif ((is_array || type == GST_TYPE_ARRAY) &&\n> > > +\t    (value.type() != ControlTypeString))\n> > \n> > This would be simpler no ?\n> > \n> > \tif (GST_VALUE_HOLDS_ARRAY(v)\n> > \n> > > +\t\tgst_structure_set_value(structure, field->str, &v);\n> > > +\n> > > +\tg_value_unset(&v);\n> > > +\tg_value_unset(&x);\n> > > +\n> > > +\treturn 0;\n> > > +}\n> > > diff --git a/src/gstreamer/gstlibcamera-utils.h\n> > > b/src/gstreamer/gstlibcamera-utils.h\n> > > index 1812be75..53ad6911 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\n> > > *value);\n> > > +int gst_libcamera_set_structure_field(GstStructure *structure, GString\n> > > *field,\n> > > +\t\t\t\t      const libcamera::ControlId *id,\n> > > +\t\t\t\t      const libcamera::ControlValue\n> > > &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..06501fd8 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,27 @@ gst_libcamera_device_new(const\n> > > std::shared_ptr<Camera> &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\tg_autoptr(GString) prop_str =\n> > > g_string_new(\"api.libcamera.\");\n> > > +\t\tg_string_append(prop_str, id->name().c_str());\n> > \n> > Wouldn't this be nicer using C++ ?\n> > \n> > > +\n> > > +\t\tint ret = gst_libcamera_set_structure_field(props,\n> > > prop_str, id, value);\n> > > +\t\tif (ret < 0) {\n> > > +\t\t\tGST_ERROR(\"Failed to retrieve value for %s\n> > > property\", 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> > >  \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 61186BDCC1\n\tfor <parsemail@patchwork.libcamera.org>;\n\tTue, 29 Jul 2025 12:30:24 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 0C3C1691DF;\n\tTue, 29 Jul 2025 14:30:23 +0200 (CEST)","from bali.collaboradmins.com (bali.collaboradmins.com\n\t[148.251.105.195])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 1E0AA69052\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 29 Jul 2025 14:30:12 +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 E29C117E0CE7;\n\tTue, 29 Jul 2025 14:29: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=\"lbkHt9zg\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=collabora.com;\n\ts=mail; t=1753792197;\n\tbh=qhW7OeHIUfbV0vF+u2B5snsO5Uy7t4vN54fVCX3wf6U=;\n\th=Subject:From:To:Cc:Date:In-Reply-To:References:From;\n\tb=lbkHt9zg0DS8IElvkjC3rgp04UQAF84prUdb4GqpVhT1tHf2Mz6Hpn9NE3RWH0X0l\n\tyrotfsDvAWpa2GL8ecfSDtaG1m/P9ziiFs8NWfcI+bIJMgmFrDVuRLv5ieqvJ4G7/k\n\t4vRkHi4zMtBTACQV56SlG9qEKuz09qAAe40iE6u8mE6f2iczUSmCgUIZo1MpD00aGv\n\t2cXDaetyLTbVme7Odg7Wos+SRcyOyQITxx2qBFcmtkMOtXvWw3A5iR5f3/jsmkCl9F\n\tFJDdRuUOeuEu7GgUAUfv1aOGtdpIuHpx10KKxAK3iap/2Ymfy/+CGPllhe9IZJeKwa\n\t9gjYGbZBHoX9g==","Message-ID":"<9834822164069953dba1533c830783a66863f5ac.camel@collabora.com>","Subject":"Re: [PATCH v4 3/3] gstreamer: Report camera properties as device\n\tproperties","From":"Nicolas Dufresne <nicolas.dufresne@collabora.com>","To":"Umang Jain <uajain@igalia.com>","Cc":"libcamera-devel@lists.libcamera.org, Jaslo Ziska <jaslo@ziska.de>","Date":"Tue, 29 Jul 2025 08:29:54 -0400","In-Reply-To":"<gapqqaxiv64cqmuqagaci4boosmg57fnevrj66yxn3het3jj2u@ulrgrr4hvw2c>","References":"<20250724133343.353044-1-uajain@igalia.com>\n\t<20250724133343.353044-4-uajain@igalia.com>\n\t<6b0b787b4d7f2e65ed126627a5f46a941d4ebe91.camel@collabora.com>\n\t<gapqqaxiv64cqmuqagaci4boosmg57fnevrj66yxn3het3jj2u@ulrgrr4hvw2c>","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=\"=-tljQjUr0Tkk86Ia7gw0w\"","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>"}}]