[{"id":39763,"web_url":"https://patchwork.libcamera.org/comment/39763/","msgid":"<a01e5680-b85c-4165-a785-f0a9e5765efb@ideasonboard.com>","date":"2026-07-20T13:11:42","subject":"Re: [PATCH v2 1/1] gstreamer: Prefer non-raw (i.e. non-Bayer)\n\tformats in caps negotiation","submitter":{"id":216,"url":"https://patchwork.libcamera.org/api/people/216/","name":"Barnabás Pőcze","email":"barnabas.pocze@ideasonboard.com"},"content":"Hi\n\n2026. 07. 20. 13:10 keltezéssel, David Plowman írta:\n> Existing code was preferring fixed size outputs over ones with ranges,\n> resulting in raw output formats being preferred even when applications\n> (\"cheese\" is one such example) aren't expecting them and subsequently\n> fail.\n> \n> The revised version still prefers fixed size outputs over ones with\n> ranges, but will prefer non-raw (meaning non-Bayer) formats where\n> these are available. This still allows applications that explicitly\n> request raw formats to get them, but other applications will normally\n> get non-raw formats which they handle better.\n> \n> Fixes, for example, \"cheese\" on Raspberry Pi.\n> \n> Signed-off-by: David Plowman <david.plowman@raspberrypi.com>\n> ---\n>   src/gstreamer/gstlibcamera-utils.cpp | 125 ++++++++++++++++++++-------\n>   src/gstreamer/gstlibcamera-utils.h   |   2 +-\n>   src/gstreamer/gstlibcamerasrc.cpp    |   3 +-\n>   3 files changed, 97 insertions(+), 33 deletions(-)\n> \n> diff --git a/src/gstreamer/gstlibcamera-utils.cpp b/src/gstreamer/gstlibcamera-utils.cpp\n> index 6541d478c..f0f128880 100644\n> --- a/src/gstreamer/gstlibcamera-utils.cpp\n> +++ b/src/gstreamer/gstlibcamera-utils.cpp\n> @@ -9,6 +9,7 @@\n>   #include \"gstlibcamera-utils.h\"\n>   \n>   #include <string>\n> +#include <vector>\n>   \n>   #include <libcamera/control_ids.h>\n>   #include <libcamera/formats.h>\n> @@ -435,61 +436,121 @@ gst_libcamera_stream_configuration_to_caps(const StreamConfiguration &stream_cfg\n>   \treturn caps;\n>   }\n>   \n> -void gst_libcamera_configure_stream_from_caps(StreamConfiguration &stream_cfg,\n> +/*\n> + * We will want to distinguish between caps structures corresponding to\n> + * raw and non-raw (processed) output images, as this will help inform\n> + * our choice of preferred format.\n> + *\n> + * We identify Bayer as being the principle \"raw\" format here, but note\n> + * that we are considering greyscale (images with one component) to be\n> + * raw too (e.g. a raw monochrome sensor).\n> + */\n> +static bool\n> +gst_libcamera_structure_is_raw_capture(const GstStructure *s)\n> +{\n> +\tif (gst_structure_has_name(s, \"video/x-bayer\"))\n> +\t\treturn true;\n> +\n> +\tif (gst_structure_has_name(s, \"video/x-raw\")) {\n> +\t\tconst gchar *format = gst_structure_get_string(s, \"format\");\n> +\t\tif (!format)\n> +\t\t\treturn false;\n> +\n> +\t\tconst GstVideoFormatInfo *finfo =\n> +\t\t\tgst_video_format_get_info(gst_video_format_from_string(format));\n> +\n> +\t\treturn finfo->n_components == 1;\n> +\t}\n> +\n> +\treturn false;\n> +}\n> +\n> +/*\n> + * Data recorded for each caps structure candidate, used to rank them\n> + * against each other in gst_libcamera_compare_caps_candidates().\n> + */\n> +struct CapsCandidate {\n> +\tguint index;\n> +\tbool is_raw;\n> +\tbool is_fixed;\n> +\tguint delta;\n> +};\n> +\n> +static gint\n> +gst_libcamera_compare_caps_candidates(gconstpointer a, gconstpointer b)\n> +{\n> +\tconst CapsCandidate *ca = static_cast<const CapsCandidate *>(a);\n> +\tconst CapsCandidate *cb = static_cast<const CapsCandidate *>(b);\n> +\n> +\t/*\n> +\t * Raw formats are likely to be useful only to applications that\n> +\t * specifically ask for them. Other applications will typically be\n> +\t * unable to handle them and fail, so prefer non-raw candidates.\n> +\t */\n> +\tif (ca->is_raw != cb->is_raw)\n> +\t\treturn ca->is_raw ? 1 : -1;\n> +\n> +\t/* Prefer a reliable fixed value over a range. */\n> +\tif (ca->is_fixed != cb->is_fixed)\n> +\t\treturn ca->is_fixed ? -1 : 1;\n> +\n> +\t/* Otherwise the closest size match wins. */\n> +\tif (ca->delta != cb->delta)\n> +\t\treturn ca->delta < cb->delta ? -1 : 1;\n> +\n> +\treturn 0;\n> +}\n> +\n> +bool gst_libcamera_configure_stream_from_caps(StreamConfiguration &stream_cfg,\n>   \t\t\t\t\t      GstCaps *caps, GstVideoTransferFunction *transfer)\n>   {\n>   \tGstVideoFormat gst_format = pixel_format_to_gst_format(stream_cfg.pixelFormat);\n>   \tguint i;\n> -\tgint best_fixed = -1, best_in_range = -1;\n>   \tGstStructure *s;\n>   \n> -\t/*\n> -\t * These are delta weight computed from:\n> -\t *   ABS(width - stream_cfg.size.width) * ABS(height - stream_cfg.size.height)\n> -\t */\n> -\tguint best_fixed_delta = G_MAXUINT;\n> -\tguint best_in_range_delta = G_MAXUINT;\n> -\n>   \t/* First fixate the caps using default configuration value. */\n>   \tg_assert(gst_caps_is_writable(caps));\n>   \n> -\t/* Lookup the structure for a close match to the stream_cfg.size */\n> +\t/*\n> +\t * Build a candidate for every available caps structure, and insert\n> +\t * it into a list kept sorted by preference as we go. The vector\n> +\t * is constructed at its final size immediately, so it's safe to use\n> +\t * pointers to the elements in it.\n> +\t */\n> +\tstd::vector<CapsCandidate> candidates(gst_caps_get_size(caps));\n> +\tGList *sorted = nullptr;\n\nIt looks to me that `sorted` is unnecessary. In fact, it looks to me that\n`candidates` is unnecessary as well. Now that there is a comparison function,\nit should be a matter of a minimum search, no? Meaning that you only need\nto store the current best? Or am I missing something?\n\nFor example, like this:\n\n\ndiff --git a/src/gstreamer/gstlibcamera-utils.cpp b/src/gstreamer/gstlibcamera-utils.cpp\nindex f0f128880f..95be8a8dcb 100644\n--- a/src/gstreamer/gstlibcamera-utils.cpp\n+++ b/src/gstreamer/gstlibcamera-utils.cpp\n@@ -8,8 +8,9 @@\n  \n  #include \"gstlibcamera-utils.h\"\n  \n+#include <cmath>\n+#include <optional>\n  #include <string>\n-#include <vector>\n  \n  #include <libcamera/control_ids.h>\n  #include <libcamera/formats.h>\n@@ -474,32 +475,28 @@ struct CapsCandidate {\n  \tbool is_raw;\n  \tbool is_fixed;\n  \tguint delta;\n-};\n-\n-static gint\n-gst_libcamera_compare_caps_candidates(gconstpointer a, gconstpointer b)\n-{\n-\tconst CapsCandidate *ca = static_cast<const CapsCandidate *>(a);\n-\tconst CapsCandidate *cb = static_cast<const CapsCandidate *>(b);\n  \n-\t/*\n-\t * Raw formats are likely to be useful only to applications that\n-\t * specifically ask for them. Other applications will typically be\n-\t * unable to handle them and fail, so prefer non-raw candidates.\n-\t */\n-\tif (ca->is_raw != cb->is_raw)\n-\t\treturn ca->is_raw ? 1 : -1;\n+\tconstexpr bool operator<(const CapsCandidate &other) const\n+\t{\n+\t\t/*\n+\t\t * Raw formats are likely to be useful only to applications that\n+\t\t * specifically ask for them. Other applications will typically be\n+\t\t * unable to handle them and fail, so prefer non-raw candidates.\n+\t\t */\n+\t\tif (is_raw != other.is_raw)\n+\t\t\treturn !is_raw;\n  \n-\t/* Prefer a reliable fixed value over a range. */\n-\tif (ca->is_fixed != cb->is_fixed)\n-\t\treturn ca->is_fixed ? -1 : 1;\n+\t\t/* Prefer a reliable fixed value over a range. */\n+\t\tif (is_fixed != other.is_fixed)\n+\t\t\treturn is_fixed;\n  \n-\t/* Otherwise the closest size match wins. */\n-\tif (ca->delta != cb->delta)\n-\t\treturn ca->delta < cb->delta ? -1 : 1;\n+\t\t/* Otherwise the closest size match wins. */\n+\t\tif (delta < other.delta)\n+\t\t\treturn true;\n  \n-\treturn 0;\n-}\n+\t\treturn false;\n+\t}\n+};\n  \n  bool gst_libcamera_configure_stream_from_caps(StreamConfiguration &stream_cfg,\n  \t\t\t\t\t      GstCaps *caps, GstVideoTransferFunction *transfer)\n@@ -517,8 +514,7 @@ bool gst_libcamera_configure_stream_from_caps(StreamConfiguration &stream_cfg,\n  \t * is constructed at its final size immediately, so it's safe to use\n  \t * pointers to the elements in it.\n  \t */\n-\tstd::vector<CapsCandidate> candidates(gst_caps_get_size(caps));\n-\tGList *sorted = nullptr;\n+\tstd::optional<CapsCandidate> best;\n  \n  \tfor (i = 0; i < gst_caps_get_size(caps); i++) {\n  \t\ts = gst_caps_get_structure(caps, i);\n@@ -536,21 +532,27 @@ bool gst_libcamera_configure_stream_from_caps(StreamConfiguration &stream_cfg,\n  \t\t\tgst_structure_get_int(s, \"height\", &height);\n  \t\t}\n  \n-\t\tguint delta = ABS(width - (gint)stream_cfg.size.width) * ABS(height - (gint)stream_cfg.size.height);\n+\t\tguint delta =\n+\t\t\tguint(std::abs(width - (gint)stream_cfg.size.width))\n+\t\t\t* guint(std::abs(height - (gint)stream_cfg.size.height));\n+\n+\t\tCapsCandidate candidate = {\n+\t\t\t.index = i,\n+\t\t\t.is_raw = gst_libcamera_structure_is_raw_capture(s),\n+\t\t\t.is_fixed = is_fixed,\n+\t\t\t.delta = delta,\n+\t\t};\n  \n-\t\tcandidates[i] = { i, gst_libcamera_structure_is_raw_capture(s), is_fixed, delta };\n-\t\tsorted = g_list_insert_sorted(sorted, &candidates[i], gst_libcamera_compare_caps_candidates);\n+\t\tif (!best || candidate < *best)\n+\t\t\tbest = candidate;\n  \t}\n  \n-\tif (!sorted) {\n+\tif (!best) {\n  \t\tGST_WARNING(\"Failed to find a suitable caps structure to configure the stream\");\n  \t\treturn false;\n  \t}\n  \n-\tguint best = static_cast<CapsCandidate *>(sorted->data)->index;\n-\tg_list_free(sorted);\n-\n-\ts = gst_caps_get_structure(caps, best);\n+\ts = gst_caps_get_structure(caps, best->index);\n  \n  \tif (gst_structure_has_name(s, \"video/x-raw\")) {\n  \t\tconst gchar *format = gst_video_format_to_string(gst_format);\n\n\n\n\n> +\n>   \tfor (i = 0; i < gst_caps_get_size(caps); i++) {\n>   \t\ts = gst_caps_get_structure(caps, i);\n>   \t\tgint width, height;\n> -\t\tguint delta;\n> +\t\tbool is_fixed = gst_structure_has_field_typed(s, \"width\", G_TYPE_INT) &&\n> +\t\t\t\tgst_structure_has_field_typed(s, \"height\", G_TYPE_INT);\n>   \n> -\t\tif (gst_structure_has_field_typed(s, \"width\", G_TYPE_INT) &&\n> -\t\t    gst_structure_has_field_typed(s, \"height\", G_TYPE_INT)) {\n> +\t\tif (is_fixed) {\n>   \t\t\tgst_structure_get_int(s, \"width\", &width);\n>   \t\t\tgst_structure_get_int(s, \"height\", &height);\n> -\n> -\t\t\tdelta = ABS(width - (gint)stream_cfg.size.width) * ABS(height - (gint)stream_cfg.size.height);\n> -\n> -\t\t\tif (delta < best_fixed_delta) {\n> -\t\t\t\tbest_fixed_delta = delta;\n> -\t\t\t\tbest_fixed = i;\n> -\t\t\t}\n>   \t\t} else {\n>   \t\t\tgst_structure_fixate_field_nearest_int(s, \"width\", stream_cfg.size.width);\n>   \t\t\tgst_structure_fixate_field_nearest_int(s, \"height\", stream_cfg.size.height);\n>   \t\t\tgst_structure_get_int(s, \"width\", &width);\n>   \t\t\tgst_structure_get_int(s, \"height\", &height);\n> +\t\t}\n>   \n> -\t\t\tdelta = ABS(width - (gint)stream_cfg.size.width) * ABS(height - (gint)stream_cfg.size.height);\n> +\t\tguint delta = ABS(width - (gint)stream_cfg.size.width) * ABS(height - (gint)stream_cfg.size.height);\n>   \n> -\t\t\tif (delta < best_in_range_delta) {\n> -\t\t\t\tbest_in_range_delta = delta;\n> -\t\t\t\tbest_in_range = i;\n> -\t\t\t}\n> -\t\t}\n> +\t\tcandidates[i] = { i, gst_libcamera_structure_is_raw_capture(s), is_fixed, delta };\n> +\t\tsorted = g_list_insert_sorted(sorted, &candidates[i], gst_libcamera_compare_caps_candidates);\n> +\t}\n> +\n> +\tif (!sorted) {\n> +\t\tGST_WARNING(\"Failed to find a suitable caps structure to configure the stream\");\n> +\t\treturn false;\n>   \t}\n>   \n> -\t/* Prefer reliable fixed value over ranges */\n> -\tif (best_fixed >= 0)\n> -\t\ts = gst_caps_get_structure(caps, best_fixed);\n> -\telse\n> -\t\ts = gst_caps_get_structure(caps, best_in_range);\n> +\tguint best = static_cast<CapsCandidate *>(sorted->data)->index;\n> +\tg_list_free(sorted);\n> +\n> +\ts = gst_caps_get_structure(caps, best);\n>   \n>   \tif (gst_structure_has_name(s, \"video/x-raw\")) {\n>   \t\tconst gchar *format = gst_video_format_to_string(gst_format);\n> @@ -529,6 +590,8 @@ void gst_libcamera_configure_stream_from_caps(StreamConfiguration &stream_cfg,\n>   \n>   \t\tstream_cfg.colorSpace = colorspace_from_colorimetry(colorimetry, transfer);\n>   \t}\n> +\n> +\treturn true;\n>   }\n>   \n>   void gst_libcamera_get_framerate_from_caps(GstCaps *caps,\n> diff --git a/src/gstreamer/gstlibcamera-utils.h b/src/gstreamer/gstlibcamera-utils.h\n> index 35df56fb9..5ce0e839c 100644\n> --- a/src/gstreamer/gstlibcamera-utils.h\n> +++ b/src/gstreamer/gstlibcamera-utils.h\n> @@ -18,7 +18,7 @@\n>   GstCaps *gst_libcamera_stream_formats_to_caps(const libcamera::StreamFormats &formats);\n>   GstCaps *gst_libcamera_stream_configuration_to_caps(const libcamera::StreamConfiguration &stream_cfg,\n>   \t\t\t\t\t\t    GstVideoTransferFunction transfer);\n> -void gst_libcamera_configure_stream_from_caps(libcamera::StreamConfiguration &stream_cfg,\n> +bool gst_libcamera_configure_stream_from_caps(libcamera::StreamConfiguration &stream_cfg,\n>   \t\t\t\t\t      GstCaps *caps, GstVideoTransferFunction *transfer);\n>   void gst_libcamera_get_framerate_from_caps(GstCaps *caps, GstStructure *element_caps);\n>   void gst_libcamera_clamp_and_set_frameduration(libcamera::ControlList &controls,\n> diff --git a/src/gstreamer/gstlibcamerasrc.cpp b/src/gstreamer/gstlibcamerasrc.cpp\n> index 9061f9163..66b31ce82 100644\n> --- a/src/gstreamer/gstlibcamerasrc.cpp\n> +++ b/src/gstreamer/gstlibcamerasrc.cpp\n> @@ -605,7 +605,8 @@ gst_libcamera_src_negotiate(GstLibcameraSrc *self)\n>   \n>   \t\t/* Fixate caps and configure the stream. */\n>   \t\tcaps = gst_caps_make_writable(caps);\n> -\t\tgst_libcamera_configure_stream_from_caps(stream_cfg, caps, &transfer[i]);\n> +\t\tif (!gst_libcamera_configure_stream_from_caps(stream_cfg, caps, &transfer[i]))\n> +\t\t\treturn false;\n>   \t\tgst_libcamera_get_framerate_from_caps(caps, element_caps);\n>   \t}\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 B1336C328C\n\tfor <parsemail@patchwork.libcamera.org>;\n\tMon, 20 Jul 2026 13:11:47 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 6789A66177;\n\tMon, 20 Jul 2026 15:11:47 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id ADF2066177\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 20 Jul 2026 15:11:45 +0200 (CEST)","from [192.168.33.35] (185.221.143.80.nat.pool.zt.hu\n\t[185.221.143.80])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 0ED69270;\n\tMon, 20 Jul 2026 15:10:47 +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=\"UWDY8LEx\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1784553047;\n\tbh=8fpKykI+JyfPRUn1fzJd2wZKu8d1vrXwvnT/Ro8pVFs=;\n\th=Date:Subject:To:References:From:In-Reply-To:From;\n\tb=UWDY8LExiw0YSZWVHOznxCQdI3S+O9YOWyC2Swddp/ObdI10lvWTBIcjnL9SG5ZZ+\n\tvI/ayRhrpy+70yF2qmS1iHdQyePw5MLrn+yEDo4G80lU08lZDMsZHZRgNeQe3Za5vr\n\t7FFYny1sLdStZS5PpTzw5Odh3wVDIQoPuRMK9MJk=","Message-ID":"<a01e5680-b85c-4165-a785-f0a9e5765efb@ideasonboard.com>","Date":"Mon, 20 Jul 2026 15:11:42 +0200","MIME-Version":"1.0","User-Agent":"Mozilla Thunderbird","Subject":"Re: [PATCH v2 1/1] gstreamer: Prefer non-raw (i.e. non-Bayer)\n\tformats in caps negotiation","To":"David Plowman <david.plowman@raspberrypi.com>,\n\tlibcamera-devel@lists.libcamera.org","References":"<20260720115144.4392-1-david.plowman@raspberrypi.com>\n\t<20260720115144.4392-2-david.plowman@raspberrypi.com>","From":"=?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= <barnabas.pocze@ideasonboard.com>","Content-Language":"en-US, hu-HU","In-Reply-To":"<20260720115144.4392-2-david.plowman@raspberrypi.com>","Content-Type":"text/plain; charset=UTF-8; format=flowed","Content-Transfer-Encoding":"8bit","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":39796,"web_url":"https://patchwork.libcamera.org/comment/39796/","msgid":"<CAHW6GYLfQT0R=FSrUHZk-1_H03KVjcMWwnyhsJ6vO0L=h+HziQ@mail.gmail.com>","date":"2026-07-22T12:31:15","subject":"Re: [PATCH v2 1/1] gstreamer: Prefer non-raw (i.e. non-Bayer)\n\tformats in caps negotiation","submitter":{"id":42,"url":"https://patchwork.libcamera.org/api/people/42/","name":"David Plowman","email":"david.plowman@raspberrypi.com"},"content":"Hi Barnabas\n\nThanks for that. Yes, I was copying the v4l2src version a bit too\nclosely, there's no need to do the same thing here. I'll send a\nrevised version.\n\nDavid\n\nOn Mon, 20 Jul 2026 at 14:11, Barnabás Pőcze\n<barnabas.pocze@ideasonboard.com> wrote:\n>\n> Hi\n>\n> 2026. 07. 20. 13:10 keltezéssel, David Plowman írta:\n> > Existing code was preferring fixed size outputs over ones with ranges,\n> > resulting in raw output formats being preferred even when applications\n> > (\"cheese\" is one such example) aren't expecting them and subsequently\n> > fail.\n> >\n> > The revised version still prefers fixed size outputs over ones with\n> > ranges, but will prefer non-raw (meaning non-Bayer) formats where\n> > these are available. This still allows applications that explicitly\n> > request raw formats to get them, but other applications will normally\n> > get non-raw formats which they handle better.\n> >\n> > Fixes, for example, \"cheese\" on Raspberry Pi.\n> >\n> > Signed-off-by: David Plowman <david.plowman@raspberrypi.com>\n> > ---\n> >   src/gstreamer/gstlibcamera-utils.cpp | 125 ++++++++++++++++++++-------\n> >   src/gstreamer/gstlibcamera-utils.h   |   2 +-\n> >   src/gstreamer/gstlibcamerasrc.cpp    |   3 +-\n> >   3 files changed, 97 insertions(+), 33 deletions(-)\n> >\n> > diff --git a/src/gstreamer/gstlibcamera-utils.cpp b/src/gstreamer/gstlibcamera-utils.cpp\n> > index 6541d478c..f0f128880 100644\n> > --- a/src/gstreamer/gstlibcamera-utils.cpp\n> > +++ b/src/gstreamer/gstlibcamera-utils.cpp\n> > @@ -9,6 +9,7 @@\n> >   #include \"gstlibcamera-utils.h\"\n> >\n> >   #include <string>\n> > +#include <vector>\n> >\n> >   #include <libcamera/control_ids.h>\n> >   #include <libcamera/formats.h>\n> > @@ -435,61 +436,121 @@ gst_libcamera_stream_configuration_to_caps(const StreamConfiguration &stream_cfg\n> >       return caps;\n> >   }\n> >\n> > -void gst_libcamera_configure_stream_from_caps(StreamConfiguration &stream_cfg,\n> > +/*\n> > + * We will want to distinguish between caps structures corresponding to\n> > + * raw and non-raw (processed) output images, as this will help inform\n> > + * our choice of preferred format.\n> > + *\n> > + * We identify Bayer as being the principle \"raw\" format here, but note\n> > + * that we are considering greyscale (images with one component) to be\n> > + * raw too (e.g. a raw monochrome sensor).\n> > + */\n> > +static bool\n> > +gst_libcamera_structure_is_raw_capture(const GstStructure *s)\n> > +{\n> > +     if (gst_structure_has_name(s, \"video/x-bayer\"))\n> > +             return true;\n> > +\n> > +     if (gst_structure_has_name(s, \"video/x-raw\")) {\n> > +             const gchar *format = gst_structure_get_string(s, \"format\");\n> > +             if (!format)\n> > +                     return false;\n> > +\n> > +             const GstVideoFormatInfo *finfo =\n> > +                     gst_video_format_get_info(gst_video_format_from_string(format));\n> > +\n> > +             return finfo->n_components == 1;\n> > +     }\n> > +\n> > +     return false;\n> > +}\n> > +\n> > +/*\n> > + * Data recorded for each caps structure candidate, used to rank them\n> > + * against each other in gst_libcamera_compare_caps_candidates().\n> > + */\n> > +struct CapsCandidate {\n> > +     guint index;\n> > +     bool is_raw;\n> > +     bool is_fixed;\n> > +     guint delta;\n> > +};\n> > +\n> > +static gint\n> > +gst_libcamera_compare_caps_candidates(gconstpointer a, gconstpointer b)\n> > +{\n> > +     const CapsCandidate *ca = static_cast<const CapsCandidate *>(a);\n> > +     const CapsCandidate *cb = static_cast<const CapsCandidate *>(b);\n> > +\n> > +     /*\n> > +      * Raw formats are likely to be useful only to applications that\n> > +      * specifically ask for them. Other applications will typically be\n> > +      * unable to handle them and fail, so prefer non-raw candidates.\n> > +      */\n> > +     if (ca->is_raw != cb->is_raw)\n> > +             return ca->is_raw ? 1 : -1;\n> > +\n> > +     /* Prefer a reliable fixed value over a range. */\n> > +     if (ca->is_fixed != cb->is_fixed)\n> > +             return ca->is_fixed ? -1 : 1;\n> > +\n> > +     /* Otherwise the closest size match wins. */\n> > +     if (ca->delta != cb->delta)\n> > +             return ca->delta < cb->delta ? -1 : 1;\n> > +\n> > +     return 0;\n> > +}\n> > +\n> > +bool gst_libcamera_configure_stream_from_caps(StreamConfiguration &stream_cfg,\n> >                                             GstCaps *caps, GstVideoTransferFunction *transfer)\n> >   {\n> >       GstVideoFormat gst_format = pixel_format_to_gst_format(stream_cfg.pixelFormat);\n> >       guint i;\n> > -     gint best_fixed = -1, best_in_range = -1;\n> >       GstStructure *s;\n> >\n> > -     /*\n> > -      * These are delta weight computed from:\n> > -      *   ABS(width - stream_cfg.size.width) * ABS(height - stream_cfg.size.height)\n> > -      */\n> > -     guint best_fixed_delta = G_MAXUINT;\n> > -     guint best_in_range_delta = G_MAXUINT;\n> > -\n> >       /* First fixate the caps using default configuration value. */\n> >       g_assert(gst_caps_is_writable(caps));\n> >\n> > -     /* Lookup the structure for a close match to the stream_cfg.size */\n> > +     /*\n> > +      * Build a candidate for every available caps structure, and insert\n> > +      * it into a list kept sorted by preference as we go. The vector\n> > +      * is constructed at its final size immediately, so it's safe to use\n> > +      * pointers to the elements in it.\n> > +      */\n> > +     std::vector<CapsCandidate> candidates(gst_caps_get_size(caps));\n> > +     GList *sorted = nullptr;\n>\n> It looks to me that `sorted` is unnecessary. In fact, it looks to me that\n> `candidates` is unnecessary as well. Now that there is a comparison function,\n> it should be a matter of a minimum search, no? Meaning that you only need\n> to store the current best? Or am I missing something?\n>\n> For example, like this:\n>\n>\n> diff --git a/src/gstreamer/gstlibcamera-utils.cpp b/src/gstreamer/gstlibcamera-utils.cpp\n> index f0f128880f..95be8a8dcb 100644\n> --- a/src/gstreamer/gstlibcamera-utils.cpp\n> +++ b/src/gstreamer/gstlibcamera-utils.cpp\n> @@ -8,8 +8,9 @@\n>\n>   #include \"gstlibcamera-utils.h\"\n>\n> +#include <cmath>\n> +#include <optional>\n>   #include <string>\n> -#include <vector>\n>\n>   #include <libcamera/control_ids.h>\n>   #include <libcamera/formats.h>\n> @@ -474,32 +475,28 @@ struct CapsCandidate {\n>         bool is_raw;\n>         bool is_fixed;\n>         guint delta;\n> -};\n> -\n> -static gint\n> -gst_libcamera_compare_caps_candidates(gconstpointer a, gconstpointer b)\n> -{\n> -       const CapsCandidate *ca = static_cast<const CapsCandidate *>(a);\n> -       const CapsCandidate *cb = static_cast<const CapsCandidate *>(b);\n>\n> -       /*\n> -        * Raw formats are likely to be useful only to applications that\n> -        * specifically ask for them. Other applications will typically be\n> -        * unable to handle them and fail, so prefer non-raw candidates.\n> -        */\n> -       if (ca->is_raw != cb->is_raw)\n> -               return ca->is_raw ? 1 : -1;\n> +       constexpr bool operator<(const CapsCandidate &other) const\n> +       {\n> +               /*\n> +                * Raw formats are likely to be useful only to applications that\n> +                * specifically ask for them. Other applications will typically be\n> +                * unable to handle them and fail, so prefer non-raw candidates.\n> +                */\n> +               if (is_raw != other.is_raw)\n> +                       return !is_raw;\n>\n> -       /* Prefer a reliable fixed value over a range. */\n> -       if (ca->is_fixed != cb->is_fixed)\n> -               return ca->is_fixed ? -1 : 1;\n> +               /* Prefer a reliable fixed value over a range. */\n> +               if (is_fixed != other.is_fixed)\n> +                       return is_fixed;\n>\n> -       /* Otherwise the closest size match wins. */\n> -       if (ca->delta != cb->delta)\n> -               return ca->delta < cb->delta ? -1 : 1;\n> +               /* Otherwise the closest size match wins. */\n> +               if (delta < other.delta)\n> +                       return true;\n>\n> -       return 0;\n> -}\n> +               return false;\n> +       }\n> +};\n>\n>   bool gst_libcamera_configure_stream_from_caps(StreamConfiguration &stream_cfg,\n>                                               GstCaps *caps, GstVideoTransferFunction *transfer)\n> @@ -517,8 +514,7 @@ bool gst_libcamera_configure_stream_from_caps(StreamConfiguration &stream_cfg,\n>          * is constructed at its final size immediately, so it's safe to use\n>          * pointers to the elements in it.\n>          */\n> -       std::vector<CapsCandidate> candidates(gst_caps_get_size(caps));\n> -       GList *sorted = nullptr;\n> +       std::optional<CapsCandidate> best;\n>\n>         for (i = 0; i < gst_caps_get_size(caps); i++) {\n>                 s = gst_caps_get_structure(caps, i);\n> @@ -536,21 +532,27 @@ bool gst_libcamera_configure_stream_from_caps(StreamConfiguration &stream_cfg,\n>                         gst_structure_get_int(s, \"height\", &height);\n>                 }\n>\n> -               guint delta = ABS(width - (gint)stream_cfg.size.width) * ABS(height - (gint)stream_cfg.size.height);\n> +               guint delta =\n> +                       guint(std::abs(width - (gint)stream_cfg.size.width))\n> +                       * guint(std::abs(height - (gint)stream_cfg.size.height));\n> +\n> +               CapsCandidate candidate = {\n> +                       .index = i,\n> +                       .is_raw = gst_libcamera_structure_is_raw_capture(s),\n> +                       .is_fixed = is_fixed,\n> +                       .delta = delta,\n> +               };\n>\n> -               candidates[i] = { i, gst_libcamera_structure_is_raw_capture(s), is_fixed, delta };\n> -               sorted = g_list_insert_sorted(sorted, &candidates[i], gst_libcamera_compare_caps_candidates);\n> +               if (!best || candidate < *best)\n> +                       best = candidate;\n>         }\n>\n> -       if (!sorted) {\n> +       if (!best) {\n>                 GST_WARNING(\"Failed to find a suitable caps structure to configure the stream\");\n>                 return false;\n>         }\n>\n> -       guint best = static_cast<CapsCandidate *>(sorted->data)->index;\n> -       g_list_free(sorted);\n> -\n> -       s = gst_caps_get_structure(caps, best);\n> +       s = gst_caps_get_structure(caps, best->index);\n>\n>         if (gst_structure_has_name(s, \"video/x-raw\")) {\n>                 const gchar *format = gst_video_format_to_string(gst_format);\n>\n>\n>\n>\n> > +\n> >       for (i = 0; i < gst_caps_get_size(caps); i++) {\n> >               s = gst_caps_get_structure(caps, i);\n> >               gint width, height;\n> > -             guint delta;\n> > +             bool is_fixed = gst_structure_has_field_typed(s, \"width\", G_TYPE_INT) &&\n> > +                             gst_structure_has_field_typed(s, \"height\", G_TYPE_INT);\n> >\n> > -             if (gst_structure_has_field_typed(s, \"width\", G_TYPE_INT) &&\n> > -                 gst_structure_has_field_typed(s, \"height\", G_TYPE_INT)) {\n> > +             if (is_fixed) {\n> >                       gst_structure_get_int(s, \"width\", &width);\n> >                       gst_structure_get_int(s, \"height\", &height);\n> > -\n> > -                     delta = ABS(width - (gint)stream_cfg.size.width) * ABS(height - (gint)stream_cfg.size.height);\n> > -\n> > -                     if (delta < best_fixed_delta) {\n> > -                             best_fixed_delta = delta;\n> > -                             best_fixed = i;\n> > -                     }\n> >               } else {\n> >                       gst_structure_fixate_field_nearest_int(s, \"width\", stream_cfg.size.width);\n> >                       gst_structure_fixate_field_nearest_int(s, \"height\", stream_cfg.size.height);\n> >                       gst_structure_get_int(s, \"width\", &width);\n> >                       gst_structure_get_int(s, \"height\", &height);\n> > +             }\n> >\n> > -                     delta = ABS(width - (gint)stream_cfg.size.width) * ABS(height - (gint)stream_cfg.size.height);\n> > +             guint delta = ABS(width - (gint)stream_cfg.size.width) * ABS(height - (gint)stream_cfg.size.height);\n> >\n> > -                     if (delta < best_in_range_delta) {\n> > -                             best_in_range_delta = delta;\n> > -                             best_in_range = i;\n> > -                     }\n> > -             }\n> > +             candidates[i] = { i, gst_libcamera_structure_is_raw_capture(s), is_fixed, delta };\n> > +             sorted = g_list_insert_sorted(sorted, &candidates[i], gst_libcamera_compare_caps_candidates);\n> > +     }\n> > +\n> > +     if (!sorted) {\n> > +             GST_WARNING(\"Failed to find a suitable caps structure to configure the stream\");\n> > +             return false;\n> >       }\n> >\n> > -     /* Prefer reliable fixed value over ranges */\n> > -     if (best_fixed >= 0)\n> > -             s = gst_caps_get_structure(caps, best_fixed);\n> > -     else\n> > -             s = gst_caps_get_structure(caps, best_in_range);\n> > +     guint best = static_cast<CapsCandidate *>(sorted->data)->index;\n> > +     g_list_free(sorted);\n> > +\n> > +     s = gst_caps_get_structure(caps, best);\n> >\n> >       if (gst_structure_has_name(s, \"video/x-raw\")) {\n> >               const gchar *format = gst_video_format_to_string(gst_format);\n> > @@ -529,6 +590,8 @@ void gst_libcamera_configure_stream_from_caps(StreamConfiguration &stream_cfg,\n> >\n> >               stream_cfg.colorSpace = colorspace_from_colorimetry(colorimetry, transfer);\n> >       }\n> > +\n> > +     return true;\n> >   }\n> >\n> >   void gst_libcamera_get_framerate_from_caps(GstCaps *caps,\n> > diff --git a/src/gstreamer/gstlibcamera-utils.h b/src/gstreamer/gstlibcamera-utils.h\n> > index 35df56fb9..5ce0e839c 100644\n> > --- a/src/gstreamer/gstlibcamera-utils.h\n> > +++ b/src/gstreamer/gstlibcamera-utils.h\n> > @@ -18,7 +18,7 @@\n> >   GstCaps *gst_libcamera_stream_formats_to_caps(const libcamera::StreamFormats &formats);\n> >   GstCaps *gst_libcamera_stream_configuration_to_caps(const libcamera::StreamConfiguration &stream_cfg,\n> >                                                   GstVideoTransferFunction transfer);\n> > -void gst_libcamera_configure_stream_from_caps(libcamera::StreamConfiguration &stream_cfg,\n> > +bool gst_libcamera_configure_stream_from_caps(libcamera::StreamConfiguration &stream_cfg,\n> >                                             GstCaps *caps, GstVideoTransferFunction *transfer);\n> >   void gst_libcamera_get_framerate_from_caps(GstCaps *caps, GstStructure *element_caps);\n> >   void gst_libcamera_clamp_and_set_frameduration(libcamera::ControlList &controls,\n> > diff --git a/src/gstreamer/gstlibcamerasrc.cpp b/src/gstreamer/gstlibcamerasrc.cpp\n> > index 9061f9163..66b31ce82 100644\n> > --- a/src/gstreamer/gstlibcamerasrc.cpp\n> > +++ b/src/gstreamer/gstlibcamerasrc.cpp\n> > @@ -605,7 +605,8 @@ gst_libcamera_src_negotiate(GstLibcameraSrc *self)\n> >\n> >               /* Fixate caps and configure the stream. */\n> >               caps = gst_caps_make_writable(caps);\n> > -             gst_libcamera_configure_stream_from_caps(stream_cfg, caps, &transfer[i]);\n> > +             if (!gst_libcamera_configure_stream_from_caps(stream_cfg, caps, &transfer[i]))\n> > +                     return false;\n> >               gst_libcamera_get_framerate_from_caps(caps, element_caps);\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 0B24CBDE17\n\tfor <parsemail@patchwork.libcamera.org>;\n\tWed, 22 Jul 2026 12:31:30 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id D374667EA3;\n\tWed, 22 Jul 2026 14:31:28 +0200 (CEST)","from mail-ed1-x533.google.com (mail-ed1-x533.google.com\n\t[IPv6:2a00:1450:4864:20::533])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 44EA167E8A\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 22 Jul 2026 14:31:27 +0200 (CEST)","by mail-ed1-x533.google.com with SMTP id\n\t4fb4d7f45d1cf-698beff7178so12306252a12.3\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 22 Jul 2026 05:31:27 -0700 (PDT)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (2048-bit key;\n\tunprotected) header.d=raspberrypi.com header.i=@raspberrypi.com\n\theader.b=\"n+DRituF\"; dkim-atps=neutral","ARC-Seal":"i=1; a=rsa-sha256; t=1784723486; cv=none;\n\td=google.com; s=arc-20260327;\n\tb=j8Scs7lKkDmekbJBdcA5jHS1lsmFe0gTe1ciC4iCM11A2XmHUhm4cGzFtv0tRegWRk\n\tv6Jwvmifpa2AVYM2tNZ29t8QtSqaQg+PQ4wuT/7acECrSI5Ox3phV7iXNODyiSupr3vK\n\tlAZLYc90LxFg8rntXgVi1LQZS61LU5iEQngTm3CpeAZOxniU4K4jZmFomwWwXEv02CX1\n\tU1dlUyujRvhiYN0CJxrGa08RZGHpQ3yC3nvsJfrnOaUKsGtN0eHwgaFvrccBbotxO8og\n\tjik6QFzatwCzKCtib917SWViSvPGCqBgZUAU/0k6JWXjJ4iIz93AyMuAegnz3rq1iioA\n\tboHQ==","ARC-Message-Signature":"i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com;\n\ts=arc-20260327; \n\th=content-transfer-encoding:cc:to:subject:message-id:date:from\n\t:in-reply-to:references:mime-version:dkim-signature;\n\tbh=7g/blX5iLASwDbB9U57PQA82OvBqPT32XoMhs02vnps=;\n\tfh=6eMZ9Peh5oKJMXVlU5do7GxLXGl0cSCd64pkjvWpyo4=;\n\tb=bexre668HTXsX0rq58TFxXQwgUtzdNfzDS8l9vgdj3ID3nKnH0itX4OmAkIBhIFDbf\n\tUe5uvdHWfxr2t6yl2Xroblfv5vrgH2F7OIlZmpZuU37BrOyqqolh+U1fvN9sYNCD3942\n\tewc2iWd4QpNg37Y2IdSN4YMeAt8/wlW+oTZAxmBnTOKytatzVV7dNrC6Z59jHBBaPiex\n\tfrvZCPiXRpR0oR5eitFGA1577Ag43JtTO9DNkKuBN1fNSib5K3bXcBPaDnAuFzIQU2EN\n\tsGebkq+f1eYVZaHjn3B9J7hxerQU25psDcFZRaMummUuOPQC2X5SBzePVEGq21QvLis8\n\tQ1dw==; darn=lists.libcamera.org","ARC-Authentication-Results":"i=1; mx.google.com; arc=none","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=raspberrypi.com; s=google; t=1784723486; x=1785328286;\n\tdarn=lists.libcamera.org; \n\th=content-transfer-encoding:content-type:cc:to:subject:message-id\n\t:date:from:in-reply-to:references:mime-version:from:to:cc:subject\n\t:date:message-id:reply-to:content-type;\n\tbh=7g/blX5iLASwDbB9U57PQA82OvBqPT32XoMhs02vnps=;\n\tb=n+DRituFAuRCUPD9M7RX5S5393fN1Fqb3Sm/qsopBq9Net0HZd0geu2V2phVfxnnjg\n\tTjkrSAd9Ft4RLn1Gfo1XO/AxWq2yKtjnIUlunPNJp6LGwg+btTNzr8qiVMC+KVaZHfaj\n\tmjqYdA7K7nYQFnEub1WFEX7CtfoDRLwNikBkAXK5EyZLjL4wMZmJeHHwW2+TnWY2uaIJ\n\tEYp2mmpQXwwXB1iCbxMf14525s4bh6FB/htqzZZ00ry4eqOftE+GX3YgFlD+s0alhX6j\n\tBFgrgU3ZKuBYusrVW6QvdPMVoUvQcH/rMR0GJJEfLHZ4WiCbSLJlRJFcym0lwSoXf2Zn\n\tDhUw==","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20251104; t=1784723486; x=1785328286;\n\th=content-transfer-encoding:content-type:cc:to:subject:message-id\n\t:date:from:in-reply-to:references:mime-version:x-gm-gg\n\t:x-gm-message-state:from:to:cc:subject:date:message-id:reply-to\n\t:content-type;\n\tbh=7g/blX5iLASwDbB9U57PQA82OvBqPT32XoMhs02vnps=;\n\tb=HutSPon2NHVnjyiXQ9ck5XBRpNb65Pih82c4FEk8THW/8oAbVGR3s9BIezwW3whrn4\n\tGJhHXT9nRz53Qikfn82E0ZQz7+2i6uDYpgwkJ2/SI5BmVtQ7+9eaLQCk187eQKy26cPV\n\tclkOA56ItyBSx2ZfyNCWGjvaFjtcCwXY94biS1yduYi5Z6TCKQYwMbwARF7aBBNVrgyb\n\t7qJd9BEmPuCBOSdY+ifZIuzdwrq2R9/0ENZ2Ndt5CbXO/H+5+D/gILzWvnN0fAyLtYfh\n\tHRKZJvJZPDiueM+LXfZbN1M3t7miRCYAXqlgemPQene0k1ZG8bGI5kv61WNjo14LIO8a\n\t/p0g==","X-Gm-Message-State":"AOJu0YxqK0MFaEWR6NqEuBEOr6MLCPC7yPrtsyU6vSBmEBWdYcKzCEcE\n\tl8Buxhe8N7984F4PigUGenla0ayjXcbq08VQAjCV16iL2wg0Yz20vbLxC6q5+3/ldwgAcKZH7fi\n\tgtWLM8AfC6WZ6+vTt1tQgqPB4YFZxAlCcPSgToD4QFw==","X-Gm-Gg":"AR+sD10e5fpsqOYJK4/vRfQoiuZ95X6Y/hOFjPzezrdAuO4ZvwAkJ79jzmP+I9JK9dq\n\tXOKzgqnuMwYIwq+8dpYwIUnOqp/BTmCrfQkoj9zUB+8jCfcFBl42O6f8qKghsDidNJjuox/IgNa\n\tc4Ap3GTtd22QERsWYALP69FOejIcb4y81pGHGv1qR9k5p7HjiKrybANYPMz4mIhdO2OqjIfpXh5\n\t+y/oO5t+CVWsvCu/q8J6Kerw7UwskXK2OqEz3AABy7/G7anMCEHM7TuE5VHpMlx47RW8tZTdhZb\n\tuAc1v8FhAQhWK03Bq7kdLUQbh6pq0k3dOVO28dT8UhW9CkIH9LWzIx2+Gh7eyl59XLeSQKObIQj\n\t84g==","X-Received":"by 2002:a05:6402:454a:b0:699:728e:48c0 with SMTP id\n\t4fb4d7f45d1cf-69e6516720emr7260021a12.0.1784723486340;\n\tWed, 22 Jul 2026 05:31:26 -0700 (PDT)","MIME-Version":"1.0","References":"<20260720115144.4392-1-david.plowman@raspberrypi.com>\n\t<20260720115144.4392-2-david.plowman@raspberrypi.com>\n\t<a01e5680-b85c-4165-a785-f0a9e5765efb@ideasonboard.com>","In-Reply-To":"<a01e5680-b85c-4165-a785-f0a9e5765efb@ideasonboard.com>","From":"David Plowman <david.plowman@raspberrypi.com>","Date":"Wed, 22 Jul 2026 13:31:15 +0100","X-Gm-Features":"AUfX_myTK3J8N13gz_kW5ksmXf0BO93s5mpiSO7h-VvZrNAYLFVghBCEEMOs0ZU","Message-ID":"<CAHW6GYLfQT0R=FSrUHZk-1_H03KVjcMWwnyhsJ6vO0L=h+HziQ@mail.gmail.com>","Subject":"Re: [PATCH v2 1/1] gstreamer: Prefer non-raw (i.e. non-Bayer)\n\tformats in caps negotiation","To":"=?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= <barnabas.pocze@ideasonboard.com>","Cc":"libcamera-devel@lists.libcamera.org","Content-Type":"text/plain; charset=\"UTF-8\"","Content-Transfer-Encoding":"quoted-printable","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>"}}]