{"id":27388,"url":"https://patchwork.libcamera.org/api/patches/27388/?format=json","web_url":"https://patchwork.libcamera.org/patch/27388/","project":{"id":1,"url":"https://patchwork.libcamera.org/api/projects/1/?format=json","name":"libcamera","link_name":"libcamera","list_id":"libcamera_core","list_email":"libcamera-devel@lists.libcamera.org","web_url":"","scm_url":"","webscm_url":""},"msgid":"<20260720115144.4392-2-david.plowman@raspberrypi.com>","date":"2026-07-20T11:10:07","name":"[v2,1/1] gstreamer: Prefer non-raw (i.e. non-Bayer) formats in caps negotiation","commit_ref":null,"pull_url":null,"state":"new","archived":false,"hash":"e9dc6b6591036dc3c3d97e3b343c7844212a504b","submitter":{"id":42,"url":"https://patchwork.libcamera.org/api/people/42/?format=json","name":"David Plowman","email":"david.plowman@raspberrypi.com"},"delegate":null,"mbox":"https://patchwork.libcamera.org/patch/27388/mbox/","series":[{"id":6065,"url":"https://patchwork.libcamera.org/api/series/6065/?format=json","web_url":"https://patchwork.libcamera.org/project/libcamera/list/?series=6065","date":"2026-07-20T11:10:06","name":"Make Gstreamer prefer non-raw caps","version":2,"mbox":"https://patchwork.libcamera.org/series/6065/mbox/"}],"comments":"https://patchwork.libcamera.org/api/patches/27388/comments/","check":"pending","checks":"https://patchwork.libcamera.org/api/patches/27388/checks/","tags":{},"headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id DE692C32FE\n\tfor <parsemail@patchwork.libcamera.org>;\n\tMon, 20 Jul 2026 11:51:52 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 6153667E37;\n\tMon, 20 Jul 2026 13:51:51 +0200 (CEST)","from mail-wm1-x330.google.com (mail-wm1-x330.google.com\n\t[IPv6:2a00:1450:4864:20::330])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 3DD0366122\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 20 Jul 2026 13:51:49 +0200 (CEST)","by mail-wm1-x330.google.com with SMTP id\n\t5b1f17b1804b1-49550ec592cso8132375e9.0\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 20 Jul 2026 04:51:49 -0700 (PDT)","from davidp-pi5.pitowers.org\n\t([2a00:1098:3142:1f:4706:89ee:c47e:1087])\n\tby smtp.gmail.com with ESMTPSA id\n\tffacd0b85a97d-47f63ec7c69sm31270120f8f.17.2026.07.20.04.51.48\n\t(version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n\tMon, 20 Jul 2026 04:51:48 -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=\"M2Fjrmgu\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=raspberrypi.com; s=google; t=1784548309; x=1785153109;\n\tdarn=lists.libcamera.org; \n\th=content-transfer-encoding:mime-version:references:in-reply-to\n\t:message-id:date:subject:cc:to:from:from:to:cc:subject:date\n\t:message-id:reply-to:content-type;\n\tbh=fwgRAPskwfGif3jCVguiElbgNIjD4EIyAwHqT2FZV0s=;\n\tb=M2FjrmguOJW6wrZ3nNCYRoDu89ymC7eNiJNEBLeuKvlfkApaamHlg79Jiv6tofO2mQ\n\t0dW8XhwVhMzOgU9U0z/Y81sW/4A38ywTiKYs/L2n2BJzijNVrYrcGoWkGPGI89LO8W++\n\t29ajs0u0ldq89aZCtjFIieAxHIyHLdKxHq5EzjrhA0yQm0mjCQ9d2MhD5tK/sky3XKq5\n\t4ZTrYgm4qAgqG/rA6hPdAlYd6X5KX1oaM/yCq+zQ4pfYzG8M7m5LBF8mbwOOR7hf6fMk\n\tLhaIbDzkd5oMDpjSdYeK3eUkqH7H8saP0KvWdLB2sAJEep8cAVUOuUQMnhlHwUOw/+DP\n\tO6EQ==","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20251104; t=1784548309; x=1785153109;\n\th=content-transfer-encoding:mime-version:references:in-reply-to\n\t:message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from\n\t:to:cc:subject:date:message-id:reply-to:content-type;\n\tbh=fwgRAPskwfGif3jCVguiElbgNIjD4EIyAwHqT2FZV0s=;\n\tb=outHGcASvVbwg3ABaAY+Vt43zFuyPSOlcBWTz0v4vsA2jOp+44ZMNVIcQNANYUppJ2\n\tUXJxbk9o0f3v/SJU+ELe6BBwtlSNYTrWHqMsWGCU1AI+/XuHc8nTnVyDKIgcWF+gDYOS\n\tI6rJUJrTGnN4NAzkpQiPTUKbBYPGcsn3T1hKl5Vg4n+08pxXpTNLsm26ZCVo4H98fkeg\n\tOoAhBXASCQ/W72pHR/FYpvNeLntAWAy4j/KKPH7SpRriUI3qriSXvAUVqSUi6VsaQlQa\n\tfz1cc2ZumtDiwZdQA/oxbFTGTmtCyb1DNxJROoNtBYdq7fKqEtAw8SAytHFNwyCuCWX+\n\tNztg==","X-Gm-Message-State":"AOJu0Yz8O6OdQnkTnQ/Nx2DmeYXfsdTFwq/KS3dGfxOirsdSgnNv60CV\n\tzM35msQYlqgBXxE0rZAz/nphCn/s9p23MHQYJqxpxR4PnfvaeU96UeZkh4GZnzPWy0QoyL2ol3T\n\tW+EjB","X-Gm-Gg":"AR+sD13BmFh+hXApRTj9PRWdOk5qrd4WkEcUOnOoLU2CE9OaT+ET2P9HOPtLNStn8y9\n\trB8jIm+wOmsi0XCOvQdJxz+o/aY6Riq6/R7tZxqn+HpFGBpzHuan93yNymFNk6UBHD5xMfL6OHf\n\tgdAIjdXmj4nnVmC58cS9eC4F6ZOo1tv70lq9lWA52HK3DJ6pOA5pJWMepZI2tsqxjstzdMhPT2H\n\t/M4avqAzb+EyuZrmgOzFGM2ETdI2vN7J2AyG7kK/7s3wJVVOm1UnUyU9UHXLXBeBw7JbbvT4KrV\n\t7Ce16aoydOPs3nCaxprGcryqgho56L6OGT0OjhxLIbfagJZFsrHjVvXaHfwwkEjSAitJKHop3Xd\n\twPk0P0Csv/RD3uHdq5jmwg43I9IwL4ecZRlU1ZkdIvW3t541Ypk/xHFM2o+fjZUEGp7jtko3Wtz\n\t9jKxx9hUtM8azHdiRHBowfbb90KUUFpvjMLcsO5Xw5mvrj1w+xLVrl9l0AOE32rJ1TO7evm4xGY\n\t5W5HUM97gKnuHLohPv9Mw==","X-Received":"by 2002:a05:6000:46d9:b0:47f:6e8b:699c with SMTP id\n\tffacd0b85a97d-47f6e8b6bcdmr7226929f8f.14.1784548308540; \n\tMon, 20 Jul 2026 04:51:48 -0700 (PDT)","From":"David Plowman <david.plowman@raspberrypi.com>","To":"libcamera-devel@lists.libcamera.org","Cc":"David Plowman <david.plowman@raspberrypi.com>","Subject":"[PATCH v2 1/1] gstreamer: Prefer non-raw (i.e. non-Bayer) formats in\n\tcaps negotiation","Date":"Mon, 20 Jul 2026 12:10:07 +0100","Message-ID":"<20260720115144.4392-2-david.plowman@raspberrypi.com>","X-Mailer":"git-send-email 2.47.3","In-Reply-To":"<20260720115144.4392-1-david.plowman@raspberrypi.com>","References":"<20260720115144.4392-1-david.plowman@raspberrypi.com>","MIME-Version":"1.0","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>"},"content":"Existing code was preferring fixed size outputs over ones with ranges,\nresulting in raw output formats being preferred even when applications\n(\"cheese\" is one such example) aren't expecting them and subsequently\nfail.\n\nThe revised version still prefers fixed size outputs over ones with\nranges, but will prefer non-raw (meaning non-Bayer) formats where\nthese are available. This still allows applications that explicitly\nrequest raw formats to get them, but other applications will normally\nget non-raw formats which they handle better.\n\nFixes, for example, \"cheese\" on Raspberry Pi.\n\nSigned-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(-)","diff":"diff --git a/src/gstreamer/gstlibcamera-utils.cpp b/src/gstreamer/gstlibcamera-utils.cpp\nindex 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+\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,\ndiff --git a/src/gstreamer/gstlibcamera-utils.h b/src/gstreamer/gstlibcamera-utils.h\nindex 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,\ndiff --git a/src/gstreamer/gstlibcamerasrc.cpp b/src/gstreamer/gstlibcamerasrc.cpp\nindex 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 \n","prefixes":["v2","1/1"]}