libcamera: rkisp1: Integrate SensorConfiguration support
diff mbox series

Message ID 20241001141738.17471-1-umang.jain@ideasonboard.com
State Accepted
Headers show
Series
  • libcamera: rkisp1: Integrate SensorConfiguration support
Related show

Commit Message

Umang Jain Oct. 1, 2024, 2:17 p.m. UTC
Integrate the RkISP1 pipeline handler to support sensor configuration
provided by applications through CameraConfiguration::sensorConfig.

The SensorConfiguration must be validated on both RkISP1Path (mainPath
and selfPath), so the parameters of RkISP1Path::validate() have been
updated to include sensorConfig.

For RAW paths, if a sensor configuration is provided, it will be
adjusted based on the stream configuration.

Finally, if the sensor configuration fails to apply, the camera
configuration validation will be marked as invalid.

Signed-off-by: Umang Jain <umang.jain@ideasonboard.com>
---
Testing:
- Developed over [PATCH 0/2] libcamera: pixelformat: Add isRaw() helper
- Tested with cam+[1] on i.MX8MP with IMX283 attached

[1]: https://patchwork.libcamera.org/patch/20080/
---
 src/libcamera/pipeline/rkisp1/rkisp1.cpp      | 79 +++++++++++++++++--
 src/libcamera/pipeline/rkisp1/rkisp1_path.cpp | 20 +++--
 src/libcamera/pipeline/rkisp1/rkisp1_path.h   |  2 +
 3 files changed, 87 insertions(+), 14 deletions(-)

Comments

Jacopo Mondi Oct. 2, 2024, 12:15 p.m. UTC | #1
Hi Umang

On Tue, Oct 01, 2024 at 07:47:38PM GMT, Umang Jain wrote:
> Integrate the RkISP1 pipeline handler to support sensor configuration
> provided by applications through CameraConfiguration::sensorConfig.
>
> The SensorConfiguration must be validated on both RkISP1Path (mainPath
> and selfPath), so the parameters of RkISP1Path::validate() have been
> updated to include sensorConfig.
>
> For RAW paths, if a sensor configuration is provided, it will be
> adjusted based on the stream configuration.
>
> Finally, if the sensor configuration fails to apply, the camera
> configuration validation will be marked as invalid.
>
> Signed-off-by: Umang Jain <umang.jain@ideasonboard.com>
> ---
> Testing:
> - Developed over [PATCH 0/2] libcamera: pixelformat: Add isRaw() helper
> - Tested with cam+[1] on i.MX8MP with IMX283 attached
>
> [1]: https://patchwork.libcamera.org/patch/20080/
> ---
>  src/libcamera/pipeline/rkisp1/rkisp1.cpp      | 79 +++++++++++++++++--
>  src/libcamera/pipeline/rkisp1/rkisp1_path.cpp | 20 +++--
>  src/libcamera/pipeline/rkisp1/rkisp1_path.h   |  2 +
>  3 files changed, 87 insertions(+), 14 deletions(-)
>
> diff --git a/src/libcamera/pipeline/rkisp1/rkisp1.cpp b/src/libcamera/pipeline/rkisp1/rkisp1.cpp
> index 0c4519de..49fe8fb1 100644
> --- a/src/libcamera/pipeline/rkisp1/rkisp1.cpp
> +++ b/src/libcamera/pipeline/rkisp1/rkisp1.cpp
> @@ -474,11 +474,12 @@ bool RkISP1CameraConfiguration::fitsAllPaths(const StreamConfiguration &cfg)
>  	StreamConfiguration config;
>
>  	config = cfg;
> -	if (data_->mainPath_->validate(sensor, &config) != Valid)
> +	if (data_->mainPath_->validate(sensor, sensorConfig, &config) != Valid)
>  		return false;
>
>  	config = cfg;
> -	if (data_->selfPath_ && data_->selfPath_->validate(sensor, &config) != Valid)
> +	if (data_->selfPath_ &&
> +	    data_->selfPath_->validate(sensor, sensorConfig, &config) != Valid)
>  		return false;
>
>  	return true;
> @@ -495,6 +496,31 @@ CameraConfiguration::Status RkISP1CameraConfiguration::validate()
>
>  	status = validateColorSpaces(ColorSpaceFlag::StreamsShareColorSpace);
>
> +	/*
> +	 * Make sure that if a sensor configuration has been requested it
> +	 * is valid.
> +	 */
> +	if (sensorConfig && !sensorConfig->isValid()) {
> +		LOG(RkISP1, Error) << "Invalid sensor configuration request";
> +		return Invalid;
> +	}
> +
> +	if (sensorConfig) {
> +		std::optional<unsigned int> bitDepth = sensorConfig->bitDepth;
> +		if (bitDepth != 8 && bitDepth != 10 && bitDepth != 12) {
> +			/*
> +			 * Default to the first supported bit depth, if an
> +			 * unsupported one is supplied.
> +			 */
> +			V4L2SubdeviceFormat format = {};
> +			format.code = sensor->mbusCodes().at(0);
> +
> +			sensorConfig->bitDepth =
> +				MediaBusFormatInfo::info(format.code).bitsPerPixel;
> +			status = Adjusted;

No, we never adjust sensorConfig. Either it's fully valid or we refuse
it. The rational is that we cannot adjust it to something meaningful
and applications that use sensorConfig should be highly specialized
and know precisely what they're doing.

It is documented by the SensorConfiguration class itself, even if
behind a \todo that should only apply to the first statement, not to
the whole paragraph imho

 * \todo Applications shall fully populate all fields of the
 * CameraConfiguration::sensorConfig class members before validating the
 * CameraConfiguration. If the SensorConfiguration is not fully populated, or if
 * any of its parameters cannot be applied to the sensor in use, the
 * CameraConfiguration validation process will fail and return
 * CameraConfiguration::Status::Invalid.

This is the relevant part

 * If the SensorConfiguration is not fully populated, or if
 * any of its parameters cannot be applied to the sensor in use, the
 * CameraConfiguration validation process will fail and return
 * CameraConfiguration::Status::Invalid.

Maybe it's all behind a todo because we currently allow populating
only bitDepth and outputSize, as documented by the isValid() function

 * \todo For now allow applications to populate the bitDepth and the outputSize
 * only as skipping and binnings factors are initialized to 1 and the analog
 * crop is ignored.

But the idea of never adjusting them is still valid.

I don't think we should adjust the sensor configuration.

Also the check for validity assumes only 8 10 and 12 are valid, while there
are sensors that can happily provide 14 or 16 bits.

If this is a limitation of the pipeline handler, of the driver or the
hardware itself, applications that specify a sensorConfig should know
about this as we assume they're highly specialized.

> +		}
> +	}
> +
>  	/* Cap the number of entries to the available streams. */
>  	if (config_.size() > pathCount) {
>  		config_.resize(pathCount);
> @@ -540,7 +566,7 @@ CameraConfiguration::Status RkISP1CameraConfiguration::validate()
>  		/* Try to match stream without adjusting configuration. */
>  		if (mainPathAvailable) {
>  			StreamConfiguration tryCfg = cfg;
> -			if (data_->mainPath_->validate(sensor, &tryCfg) == Valid) {
> +			if (data_->mainPath_->validate(sensor, sensorConfig, &tryCfg) == Valid) {
>  				mainPathAvailable = false;
>  				cfg = tryCfg;
>  				cfg.setStream(const_cast<Stream *>(&data_->mainPathStream_));
> @@ -550,7 +576,7 @@ CameraConfiguration::Status RkISP1CameraConfiguration::validate()
>
>  		if (selfPathAvailable) {
>  			StreamConfiguration tryCfg = cfg;
> -			if (data_->selfPath_->validate(sensor, &tryCfg) == Valid) {
> +			if (data_->selfPath_->validate(sensor, sensorConfig, &tryCfg) == Valid) {
>  				selfPathAvailable = false;
>  				cfg = tryCfg;
>  				cfg.setStream(const_cast<Stream *>(&data_->selfPathStream_));
> @@ -561,7 +587,7 @@ CameraConfiguration::Status RkISP1CameraConfiguration::validate()
>  		/* Try to match stream allowing adjusting configuration. */
>  		if (mainPathAvailable) {
>  			StreamConfiguration tryCfg = cfg;
> -			if (data_->mainPath_->validate(sensor, &tryCfg) == Adjusted) {
> +			if (data_->mainPath_->validate(sensor, sensorConfig, &tryCfg) == Adjusted) {
>  				mainPathAvailable = false;
>  				cfg = tryCfg;
>  				cfg.setStream(const_cast<Stream *>(&data_->mainPathStream_));
> @@ -572,7 +598,7 @@ CameraConfiguration::Status RkISP1CameraConfiguration::validate()
>
>  		if (selfPathAvailable) {
>  			StreamConfiguration tryCfg = cfg;
> -			if (data_->selfPath_->validate(sensor, &tryCfg) == Adjusted) {
> +			if (data_->selfPath_->validate(sensor, sensorConfig, &tryCfg) == Adjusted) {
>  				selfPathAvailable = false;
>  				cfg = tryCfg;
>  				cfg.setStream(const_cast<Stream *>(&data_->selfPathStream_));
> @@ -589,26 +615,63 @@ CameraConfiguration::Status RkISP1CameraConfiguration::validate()
>
>  	/* Select the sensor format. */
>  	PixelFormat rawFormat;
> +	Size rawSize;
>  	Size maxSize;
>
>  	for (const StreamConfiguration &cfg : config_) {
> -		if (cfg.pixelFormat.isRaw())
> +		if (cfg.pixelFormat.isRaw()) {
>  			rawFormat = cfg.pixelFormat;
> +			rawSize = cfg.size;
> +		}
>
>  		maxSize = std::max(maxSize, cfg.size);
>  	}
>
> +	if (rawFormat.isValid() && sensorConfig) {
> +		if (sensorConfig->outputSize != rawSize) {
> +			sensorConfig->outputSize = rawSize;
> +			status = Adjusted;
> +		}
> +
> +		const PixelFormatInfo &info = PixelFormatInfo::info(rawFormat);
> +		if (sensorConfig->bitDepth != info.bitsPerPixel) {
> +			sensorConfig->bitDepth = info.bitsPerPixel;
> +			status = Adjusted;
> +		}

As said above, never adjust the sensor configuration.

> +	}
> +
>  	std::vector<unsigned int> mbusCodes;
> +	Size sz = maxSize;
>
>  	if (rawFormat.isValid()) {
>  		mbusCodes = { rawFormats.at(rawFormat) };
> +		sz = rawSize;
> +	} else if (sensorConfig) {
> +		for (const auto &value : rawFormats) {
> +			const PixelFormatInfo &info = PixelFormatInfo::info(value.first);
> +			if (info.bitsPerPixel == sensorConfig->bitDepth)
> +				mbusCodes.push_back(value.second);
> +		}
> +		sz = sensorConfig->outputSize;

You need to validate that sensorConfig->outputSize is actually
supported by the sensor. I would do that in RkISP1Path::validate() as
suggested below ?

>  	} else {
>  		std::transform(rawFormats.begin(), rawFormats.end(),
>  			       std::back_inserter(mbusCodes),
>  			       [](const auto &value) { return value.second; });
>  	}
>
> -	sensorFormat_ = sensor->getFormat(mbusCodes, maxSize);
> +	sensorFormat_ = sensor->getFormat(mbusCodes, sz);
> +	if (sensorConfig) {
> +		int ret = data_->sensor_->applyConfiguration(*sensorConfig,
> +							     combinedTransform_,
> +							     &sensorFormat_);

I would avoid applying a sensor configuration at validate() but only
do so at configure(), check what RPi does if you need examples.

> +		if (ret) {
> +			LOG(RkISP1, Error)
> +				<< "Sensor Configuration not supported: "
> +				<< strerror(-ret);
> +
> +			return Invalid;
> +		}
> +	}
>
>  	if (sensorFormat_.size.isNull())
>  		sensorFormat_.size = sensor->resolution();
> diff --git a/src/libcamera/pipeline/rkisp1/rkisp1_path.cpp b/src/libcamera/pipeline/rkisp1/rkisp1_path.cpp
> index 7c1f0c73..30becb75 100644
> --- a/src/libcamera/pipeline/rkisp1/rkisp1_path.cpp
> +++ b/src/libcamera/pipeline/rkisp1/rkisp1_path.cpp
> @@ -253,8 +253,10 @@ RkISP1Path::generateConfiguration(const CameraSensor *sensor, const Size &size,
>  	return cfg;
>  }
>
> -CameraConfiguration::Status RkISP1Path::validate(const CameraSensor *sensor,
> -						 StreamConfiguration *cfg)
> +CameraConfiguration::Status
> +RkISP1Path::validate(const CameraSensor *sensor,
> +		     std::optional<SensorConfiguration> sensorConfig,

Does copy-constructing an std::optional<> copy the managed instance ?

From my understanding of
https://en.cppreference.com/w/cpp/utility/optional/optional

Copy constructor: If other contains a value, initializes the contained
value as if direct-initializing (but not direct-list-initializing) an
object of type T with the expression *other

it does.

In other word, we're not only copying the std::optional<> wrapper
(which should in this case point to some dynamically allocated memory
for the managed object) but the whole object itself ?

I would pass it by reference ?

> +		     StreamConfiguration *cfg)
>  {
>  	const std::vector<unsigned int> &mbusCodes = sensor->mbusCodes();
>  	Size resolution = filterSensorResolution(sensor);
> @@ -281,12 +283,18 @@ CameraConfiguration::Status RkISP1Path::validate(const CameraSensor *sensor,
>  			    mbusCodes.end())
>  				continue;
>
> -			/*
> -			 * Store the raw format with the highest bits per pixel
> -			 * for later usage.
> +			/* If the bits per pixel is supplied from the sensor

                        /*
			 * If the bits per pixel is supplied from the sensor

> +			 * configuration, choose a raw format that complies with
> +			 * it. Otherwise, store the raw format with the highest
> +			 * bits per pixel for later usage.
>  			 */
>  			const PixelFormatInfo &info = PixelFormatInfo::info(format);
> -			if (info.bitsPerPixel > rawBitsPerPixel) {
> +
> +			if (sensorConfig &&
> +			    info.bitsPerPixel == sensorConfig->bitDepth) {
> +				rawFormat = format;
> +				rawBitsPerPixel = info.bitsPerPixel;
> +			} else if (info.bitsPerPixel > rawBitsPerPixel) {
>  				rawBitsPerPixel = info.bitsPerPixel;
>  				rawFormat = format;

A few lines below we have

	if (isRaw) {
		/*
		 * Use the sensor output size closest to the requested stream
		 * size.
		 */
		uint32_t mbusCode = formatToMediaBus.at(cfg->pixelFormat);
		V4L2SubdeviceFormat sensorFormat =
			sensor->getFormat({ mbusCode }, cfg->size);

		minResolution = sensorFormat.size;
		maxResolution = sensorFormat.size;

If a SensorConfig is specified, shouldn't the size there provided
be used ? Also, make sure that 'sensorFormat' size is the one
specified in the sensorConfig. IOW the sensorConfig.size should be
supported exactly by the sensor, otherwise we should fail as
sensorConfig shall not be adjusted.

Once we have validated that the (optional) sensorConfig.size is
supported by the sensor, the StreamConfig::size will be then adjusted
to it with:

	cfg->size.boundTo(maxResolution);
	cfg->size.expandTo(minResolution);

a few lines below

If a sensorFormat is there, the RAW stream should be adjusted to match
it, and you can use it to select the sensor format in
RkISP1CameraConfiguration::validate().

Thanks
  j


>  			}
> diff --git a/src/libcamera/pipeline/rkisp1/rkisp1_path.h b/src/libcamera/pipeline/rkisp1/rkisp1_path.h
> index 9f75fe1f..8aae00ef 100644
> --- a/src/libcamera/pipeline/rkisp1/rkisp1_path.h
> +++ b/src/libcamera/pipeline/rkisp1/rkisp1_path.h
> @@ -27,6 +27,7 @@ namespace libcamera {
>  class CameraSensor;
>  class MediaDevice;
>  class V4L2Subdevice;
> +class SensorConfiguration;
>  struct StreamConfiguration;
>  struct V4L2SubdeviceFormat;
>
> @@ -45,6 +46,7 @@ public:
>  						  const Size &resolution,
>  						  StreamRole role);
>  	CameraConfiguration::Status validate(const CameraSensor *sensor,
> +					     std::optional<SensorConfiguration> sensorConfig,
>  					     StreamConfiguration *cfg);
>
>  	int configure(const StreamConfiguration &config,
> --
> 2.45.0
>

Patch
diff mbox series

diff --git a/src/libcamera/pipeline/rkisp1/rkisp1.cpp b/src/libcamera/pipeline/rkisp1/rkisp1.cpp
index 0c4519de..49fe8fb1 100644
--- a/src/libcamera/pipeline/rkisp1/rkisp1.cpp
+++ b/src/libcamera/pipeline/rkisp1/rkisp1.cpp
@@ -474,11 +474,12 @@  bool RkISP1CameraConfiguration::fitsAllPaths(const StreamConfiguration &cfg)
 	StreamConfiguration config;
 
 	config = cfg;
-	if (data_->mainPath_->validate(sensor, &config) != Valid)
+	if (data_->mainPath_->validate(sensor, sensorConfig, &config) != Valid)
 		return false;
 
 	config = cfg;
-	if (data_->selfPath_ && data_->selfPath_->validate(sensor, &config) != Valid)
+	if (data_->selfPath_ &&
+	    data_->selfPath_->validate(sensor, sensorConfig, &config) != Valid)
 		return false;
 
 	return true;
@@ -495,6 +496,31 @@  CameraConfiguration::Status RkISP1CameraConfiguration::validate()
 
 	status = validateColorSpaces(ColorSpaceFlag::StreamsShareColorSpace);
 
+	/*
+	 * Make sure that if a sensor configuration has been requested it
+	 * is valid.
+	 */
+	if (sensorConfig && !sensorConfig->isValid()) {
+		LOG(RkISP1, Error) << "Invalid sensor configuration request";
+		return Invalid;
+	}
+
+	if (sensorConfig) {
+		std::optional<unsigned int> bitDepth = sensorConfig->bitDepth;
+		if (bitDepth != 8 && bitDepth != 10 && bitDepth != 12) {
+			/*
+			 * Default to the first supported bit depth, if an
+			 * unsupported one is supplied.
+			 */
+			V4L2SubdeviceFormat format = {};
+			format.code = sensor->mbusCodes().at(0);
+
+			sensorConfig->bitDepth =
+				MediaBusFormatInfo::info(format.code).bitsPerPixel;
+			status = Adjusted;
+		}
+	}
+
 	/* Cap the number of entries to the available streams. */
 	if (config_.size() > pathCount) {
 		config_.resize(pathCount);
@@ -540,7 +566,7 @@  CameraConfiguration::Status RkISP1CameraConfiguration::validate()
 		/* Try to match stream without adjusting configuration. */
 		if (mainPathAvailable) {
 			StreamConfiguration tryCfg = cfg;
-			if (data_->mainPath_->validate(sensor, &tryCfg) == Valid) {
+			if (data_->mainPath_->validate(sensor, sensorConfig, &tryCfg) == Valid) {
 				mainPathAvailable = false;
 				cfg = tryCfg;
 				cfg.setStream(const_cast<Stream *>(&data_->mainPathStream_));
@@ -550,7 +576,7 @@  CameraConfiguration::Status RkISP1CameraConfiguration::validate()
 
 		if (selfPathAvailable) {
 			StreamConfiguration tryCfg = cfg;
-			if (data_->selfPath_->validate(sensor, &tryCfg) == Valid) {
+			if (data_->selfPath_->validate(sensor, sensorConfig, &tryCfg) == Valid) {
 				selfPathAvailable = false;
 				cfg = tryCfg;
 				cfg.setStream(const_cast<Stream *>(&data_->selfPathStream_));
@@ -561,7 +587,7 @@  CameraConfiguration::Status RkISP1CameraConfiguration::validate()
 		/* Try to match stream allowing adjusting configuration. */
 		if (mainPathAvailable) {
 			StreamConfiguration tryCfg = cfg;
-			if (data_->mainPath_->validate(sensor, &tryCfg) == Adjusted) {
+			if (data_->mainPath_->validate(sensor, sensorConfig, &tryCfg) == Adjusted) {
 				mainPathAvailable = false;
 				cfg = tryCfg;
 				cfg.setStream(const_cast<Stream *>(&data_->mainPathStream_));
@@ -572,7 +598,7 @@  CameraConfiguration::Status RkISP1CameraConfiguration::validate()
 
 		if (selfPathAvailable) {
 			StreamConfiguration tryCfg = cfg;
-			if (data_->selfPath_->validate(sensor, &tryCfg) == Adjusted) {
+			if (data_->selfPath_->validate(sensor, sensorConfig, &tryCfg) == Adjusted) {
 				selfPathAvailable = false;
 				cfg = tryCfg;
 				cfg.setStream(const_cast<Stream *>(&data_->selfPathStream_));
@@ -589,26 +615,63 @@  CameraConfiguration::Status RkISP1CameraConfiguration::validate()
 
 	/* Select the sensor format. */
 	PixelFormat rawFormat;
+	Size rawSize;
 	Size maxSize;
 
 	for (const StreamConfiguration &cfg : config_) {
-		if (cfg.pixelFormat.isRaw())
+		if (cfg.pixelFormat.isRaw()) {
 			rawFormat = cfg.pixelFormat;
+			rawSize = cfg.size;
+		}
 
 		maxSize = std::max(maxSize, cfg.size);
 	}
 
+	if (rawFormat.isValid() && sensorConfig) {
+		if (sensorConfig->outputSize != rawSize) {
+			sensorConfig->outputSize = rawSize;
+			status = Adjusted;
+		}
+
+		const PixelFormatInfo &info = PixelFormatInfo::info(rawFormat);
+		if (sensorConfig->bitDepth != info.bitsPerPixel) {
+			sensorConfig->bitDepth = info.bitsPerPixel;
+			status = Adjusted;
+		}
+	}
+
 	std::vector<unsigned int> mbusCodes;
+	Size sz = maxSize;
 
 	if (rawFormat.isValid()) {
 		mbusCodes = { rawFormats.at(rawFormat) };
+		sz = rawSize;
+	} else if (sensorConfig) {
+		for (const auto &value : rawFormats) {
+			const PixelFormatInfo &info = PixelFormatInfo::info(value.first);
+			if (info.bitsPerPixel == sensorConfig->bitDepth)
+				mbusCodes.push_back(value.second);
+		}
+		sz = sensorConfig->outputSize;
 	} else {
 		std::transform(rawFormats.begin(), rawFormats.end(),
 			       std::back_inserter(mbusCodes),
 			       [](const auto &value) { return value.second; });
 	}
 
-	sensorFormat_ = sensor->getFormat(mbusCodes, maxSize);
+	sensorFormat_ = sensor->getFormat(mbusCodes, sz);
+	if (sensorConfig) {
+		int ret = data_->sensor_->applyConfiguration(*sensorConfig,
+							     combinedTransform_,
+							     &sensorFormat_);
+		if (ret) {
+			LOG(RkISP1, Error)
+				<< "Sensor Configuration not supported: "
+				<< strerror(-ret);
+
+			return Invalid;
+		}
+	}
 
 	if (sensorFormat_.size.isNull())
 		sensorFormat_.size = sensor->resolution();
diff --git a/src/libcamera/pipeline/rkisp1/rkisp1_path.cpp b/src/libcamera/pipeline/rkisp1/rkisp1_path.cpp
index 7c1f0c73..30becb75 100644
--- a/src/libcamera/pipeline/rkisp1/rkisp1_path.cpp
+++ b/src/libcamera/pipeline/rkisp1/rkisp1_path.cpp
@@ -253,8 +253,10 @@  RkISP1Path::generateConfiguration(const CameraSensor *sensor, const Size &size,
 	return cfg;
 }
 
-CameraConfiguration::Status RkISP1Path::validate(const CameraSensor *sensor,
-						 StreamConfiguration *cfg)
+CameraConfiguration::Status
+RkISP1Path::validate(const CameraSensor *sensor,
+		     std::optional<SensorConfiguration> sensorConfig,
+		     StreamConfiguration *cfg)
 {
 	const std::vector<unsigned int> &mbusCodes = sensor->mbusCodes();
 	Size resolution = filterSensorResolution(sensor);
@@ -281,12 +283,18 @@  CameraConfiguration::Status RkISP1Path::validate(const CameraSensor *sensor,
 			    mbusCodes.end())
 				continue;
 
-			/*
-			 * Store the raw format with the highest bits per pixel
-			 * for later usage.
+			/* If the bits per pixel is supplied from the sensor
+			 * configuration, choose a raw format that complies with
+			 * it. Otherwise, store the raw format with the highest
+			 * bits per pixel for later usage.
 			 */
 			const PixelFormatInfo &info = PixelFormatInfo::info(format);
-			if (info.bitsPerPixel > rawBitsPerPixel) {
+
+			if (sensorConfig &&
+			    info.bitsPerPixel == sensorConfig->bitDepth) {
+				rawFormat = format;
+				rawBitsPerPixel = info.bitsPerPixel;
+			} else if (info.bitsPerPixel > rawBitsPerPixel) {
 				rawBitsPerPixel = info.bitsPerPixel;
 				rawFormat = format;
 			}
diff --git a/src/libcamera/pipeline/rkisp1/rkisp1_path.h b/src/libcamera/pipeline/rkisp1/rkisp1_path.h
index 9f75fe1f..8aae00ef 100644
--- a/src/libcamera/pipeline/rkisp1/rkisp1_path.h
+++ b/src/libcamera/pipeline/rkisp1/rkisp1_path.h
@@ -27,6 +27,7 @@  namespace libcamera {
 class CameraSensor;
 class MediaDevice;
 class V4L2Subdevice;
+class SensorConfiguration;
 struct StreamConfiguration;
 struct V4L2SubdeviceFormat;
 
@@ -45,6 +46,7 @@  public:
 						  const Size &resolution,
 						  StreamRole role);
 	CameraConfiguration::Status validate(const CameraSensor *sensor,
+					     std::optional<SensorConfiguration> sensorConfig,
 					     StreamConfiguration *cfg);
 
 	int configure(const StreamConfiguration &config,