diff --git a/src/gstreamer/gstlibcamera-utils.cpp b/src/gstreamer/gstlibcamera-utils.cpp
index 6541d478c..f0f128880 100644
--- a/src/gstreamer/gstlibcamera-utils.cpp
+++ b/src/gstreamer/gstlibcamera-utils.cpp
@@ -9,6 +9,7 @@
 #include "gstlibcamera-utils.h"
 
 #include <string>
+#include <vector>
 
 #include <libcamera/control_ids.h>
 #include <libcamera/formats.h>
@@ -435,61 +436,121 @@ gst_libcamera_stream_configuration_to_caps(const StreamConfiguration &stream_cfg
 	return caps;
 }
 
-void gst_libcamera_configure_stream_from_caps(StreamConfiguration &stream_cfg,
+/*
+ * We will want to distinguish between caps structures corresponding to
+ * raw and non-raw (processed) output images, as this will help inform
+ * our choice of preferred format.
+ *
+ * We identify Bayer as being the principle "raw" format here, but note
+ * that we are considering greyscale (images with one component) to be
+ * raw too (e.g. a raw monochrome sensor).
+ */
+static bool
+gst_libcamera_structure_is_raw_capture(const GstStructure *s)
+{
+	if (gst_structure_has_name(s, "video/x-bayer"))
+		return true;
+
+	if (gst_structure_has_name(s, "video/x-raw")) {
+		const gchar *format = gst_structure_get_string(s, "format");
+		if (!format)
+			return false;
+
+		const GstVideoFormatInfo *finfo =
+			gst_video_format_get_info(gst_video_format_from_string(format));
+
+		return finfo->n_components == 1;
+	}
+
+	return false;
+}
+
+/*
+ * Data recorded for each caps structure candidate, used to rank them
+ * against each other in gst_libcamera_compare_caps_candidates().
+ */
+struct CapsCandidate {
+	guint index;
+	bool is_raw;
+	bool is_fixed;
+	guint delta;
+};
+
+static gint
+gst_libcamera_compare_caps_candidates(gconstpointer a, gconstpointer b)
+{
+	const CapsCandidate *ca = static_cast<const CapsCandidate *>(a);
+	const CapsCandidate *cb = static_cast<const CapsCandidate *>(b);
+
+	/*
+	 * Raw formats are likely to be useful only to applications that
+	 * specifically ask for them. Other applications will typically be
+	 * unable to handle them and fail, so prefer non-raw candidates.
+	 */
+	if (ca->is_raw != cb->is_raw)
+		return ca->is_raw ? 1 : -1;
+
+	/* Prefer a reliable fixed value over a range. */
+	if (ca->is_fixed != cb->is_fixed)
+		return ca->is_fixed ? -1 : 1;
+
+	/* Otherwise the closest size match wins. */
+	if (ca->delta != cb->delta)
+		return ca->delta < cb->delta ? -1 : 1;
+
+	return 0;
+}
+
+bool gst_libcamera_configure_stream_from_caps(StreamConfiguration &stream_cfg,
 					      GstCaps *caps, GstVideoTransferFunction *transfer)
 {
 	GstVideoFormat gst_format = pixel_format_to_gst_format(stream_cfg.pixelFormat);
 	guint i;
-	gint best_fixed = -1, best_in_range = -1;
 	GstStructure *s;
 
-	/*
-	 * These are delta weight computed from:
-	 *   ABS(width - stream_cfg.size.width) * ABS(height - stream_cfg.size.height)
-	 */
-	guint best_fixed_delta = G_MAXUINT;
-	guint best_in_range_delta = G_MAXUINT;
-
 	/* First fixate the caps using default configuration value. */
 	g_assert(gst_caps_is_writable(caps));
 
-	/* Lookup the structure for a close match to the stream_cfg.size */
+	/*
+	 * Build a candidate for every available caps structure, and insert
+	 * it into a list kept sorted by preference as we go. The vector
+	 * is constructed at its final size immediately, so it's safe to use
+	 * pointers to the elements in it.
+	 */
+	std::vector<CapsCandidate> candidates(gst_caps_get_size(caps));
+	GList *sorted = nullptr;
+
 	for (i = 0; i < gst_caps_get_size(caps); i++) {
 		s = gst_caps_get_structure(caps, i);
 		gint width, height;
-		guint delta;
+		bool is_fixed = gst_structure_has_field_typed(s, "width", G_TYPE_INT) &&
+				gst_structure_has_field_typed(s, "height", G_TYPE_INT);
 
-		if (gst_structure_has_field_typed(s, "width", G_TYPE_INT) &&
-		    gst_structure_has_field_typed(s, "height", G_TYPE_INT)) {
+		if (is_fixed) {
 			gst_structure_get_int(s, "width", &width);
 			gst_structure_get_int(s, "height", &height);
-
-			delta = ABS(width - (gint)stream_cfg.size.width) * ABS(height - (gint)stream_cfg.size.height);
-
-			if (delta < best_fixed_delta) {
-				best_fixed_delta = delta;
-				best_fixed = i;
-			}
 		} else {
 			gst_structure_fixate_field_nearest_int(s, "width", stream_cfg.size.width);
 			gst_structure_fixate_field_nearest_int(s, "height", stream_cfg.size.height);
 			gst_structure_get_int(s, "width", &width);
 			gst_structure_get_int(s, "height", &height);
+		}
 
-			delta = ABS(width - (gint)stream_cfg.size.width) * ABS(height - (gint)stream_cfg.size.height);
+		guint delta = ABS(width - (gint)stream_cfg.size.width) * ABS(height - (gint)stream_cfg.size.height);
 
-			if (delta < best_in_range_delta) {
-				best_in_range_delta = delta;
-				best_in_range = i;
-			}
-		}
+		candidates[i] = { i, gst_libcamera_structure_is_raw_capture(s), is_fixed, delta };
+		sorted = g_list_insert_sorted(sorted, &candidates[i], gst_libcamera_compare_caps_candidates);
+	}
+
+	if (!sorted) {
+		GST_WARNING("Failed to find a suitable caps structure to configure the stream");
+		return false;
 	}
 
-	/* Prefer reliable fixed value over ranges */
-	if (best_fixed >= 0)
-		s = gst_caps_get_structure(caps, best_fixed);
-	else
-		s = gst_caps_get_structure(caps, best_in_range);
+	guint best = static_cast<CapsCandidate *>(sorted->data)->index;
+	g_list_free(sorted);
+
+	s = gst_caps_get_structure(caps, best);
 
 	if (gst_structure_has_name(s, "video/x-raw")) {
 		const gchar *format = gst_video_format_to_string(gst_format);
@@ -529,6 +590,8 @@ void gst_libcamera_configure_stream_from_caps(StreamConfiguration &stream_cfg,
 
 		stream_cfg.colorSpace = colorspace_from_colorimetry(colorimetry, transfer);
 	}
+
+	return true;
 }
 
 void gst_libcamera_get_framerate_from_caps(GstCaps *caps,
diff --git a/src/gstreamer/gstlibcamera-utils.h b/src/gstreamer/gstlibcamera-utils.h
index 35df56fb9..5ce0e839c 100644
--- a/src/gstreamer/gstlibcamera-utils.h
+++ b/src/gstreamer/gstlibcamera-utils.h
@@ -18,7 +18,7 @@
 GstCaps *gst_libcamera_stream_formats_to_caps(const libcamera::StreamFormats &formats);
 GstCaps *gst_libcamera_stream_configuration_to_caps(const libcamera::StreamConfiguration &stream_cfg,
 						    GstVideoTransferFunction transfer);
-void gst_libcamera_configure_stream_from_caps(libcamera::StreamConfiguration &stream_cfg,
+bool gst_libcamera_configure_stream_from_caps(libcamera::StreamConfiguration &stream_cfg,
 					      GstCaps *caps, GstVideoTransferFunction *transfer);
 void gst_libcamera_get_framerate_from_caps(GstCaps *caps, GstStructure *element_caps);
 void gst_libcamera_clamp_and_set_frameduration(libcamera::ControlList &controls,
diff --git a/src/gstreamer/gstlibcamerasrc.cpp b/src/gstreamer/gstlibcamerasrc.cpp
index 9061f9163..66b31ce82 100644
--- a/src/gstreamer/gstlibcamerasrc.cpp
+++ b/src/gstreamer/gstlibcamerasrc.cpp
@@ -605,7 +605,8 @@ gst_libcamera_src_negotiate(GstLibcameraSrc *self)
 
 		/* Fixate caps and configure the stream. */
 		caps = gst_caps_make_writable(caps);
-		gst_libcamera_configure_stream_from_caps(stream_cfg, caps, &transfer[i]);
+		if (!gst_libcamera_configure_stream_from_caps(stream_cfg, caps, &transfer[i]))
+			return false;
 		gst_libcamera_get_framerate_from_caps(caps, element_caps);
 	}
 
