diff --git a/include/libcamera/camera.h b/include/libcamera/camera.h
index a7759ccb..fdcb66ab 100644
--- a/include/libcamera/camera.h
+++ b/include/libcamera/camera.h
@@ -69,6 +69,8 @@ public:
 protected:
 	CameraConfiguration();
 
+	Status validateColorSpaces(bool shareOutputColorSpaces);
+
 	std::vector<StreamConfiguration> config_;
 };
 
diff --git a/src/libcamera/camera.cpp b/src/libcamera/camera.cpp
index 400a7cf0..388af4f7 100644
--- a/src/libcamera/camera.cpp
+++ b/src/libcamera/camera.cpp
@@ -14,12 +14,14 @@
 #include <libcamera/base/log.h>
 #include <libcamera/base/thread.h>
 
+#include <libcamera/color_space.h>
 #include <libcamera/framebuffer_allocator.h>
 #include <libcamera/request.h>
 #include <libcamera/stream.h>
 
 #include "libcamera/internal/camera.h"
 #include "libcamera/internal/camera_controls.h"
+#include "libcamera/internal/formats.h"
 #include "libcamera/internal/pipeline_handler.h"
 
 /**
@@ -314,6 +316,63 @@ std::size_t CameraConfiguration::size() const
 	return config_.size();
 }
 
+static bool isRaw(const PixelFormat &pixFmt)
+{
+	const PixelFormatInfo &info = PixelFormatInfo::info(pixFmt);
+	return info.isValid() &&
+	       info.colourEncoding == PixelFormatInfo::ColourEncodingRAW;
+}
+
+/**
+ * \brief Check and update the color spaces requested for each stream
+ * \param[in] shareOutputColorSpaces Force all non-raw output streams to share
+ * the same color space
+ *
+ * This function performs certain consistency checks on the color spaces of
+ * the streams and may adjust them so that:
+ *
+ * - Any raw streams have the Raw color space
+ * - If shareOutputColorSpaces is set, all output streams are forced to share
+ * the same color space (this may be a constraint on some platforms).
+ *
+ * It is optional for a pipeline handler to use this method.
+ *
+ * \return A CameraConfiguration::Status value that describes the validation
+ * status (same as CameraConfiguration::validate).
+ */
+CameraConfiguration::Status CameraConfiguration::validateColorSpaces(bool shareOutputColorSpaces)
+{
+	Status status = Valid;
+
+	/*
+	 * Set all raw streams to the Raw color space, and make a note of the largest
+	 * non-raw stream with a defined color space (if there is one).
+	 */
+	int index = -1;
+	for (auto [i, cfg] : utils::enumerate(config_)) {
+		if (isRaw(cfg.pixelFormat) && cfg.colorSpace != ColorSpace::Raw) {
+			cfg.colorSpace = ColorSpace::Raw;
+			status = Adjusted;
+		}
+		else if (cfg.colorSpace && (index == -1 || cfg.size > config_[i].size))
+			index = i;
+	}
+
+	if (index < 0 || !shareOutputColorSpaces)
+		return status;
+
+	/* Make all output color spaces the same, if requested. */
+	for (auto &cfg : config_) {
+		if (!isRaw(cfg.pixelFormat) &&
+		    cfg.colorSpace != config_[index].colorSpace) {
+			cfg.colorSpace = config_[index].colorSpace;
+			status = Adjusted;
+		}
+	}
+
+	return status;
+}
+
 /**
  * \var CameraConfiguration::transform
  * \brief User-specified transform to be applied to the image
