[libcamera-devel,v2] qcam: dng_writer: Add support for IPU3 Bayer formats

Message ID 20200611014536.1683233-1-niklas.soderlund@ragnatech.se
State Superseded
Headers show
Series
  • [libcamera-devel,v2] qcam: dng_writer: Add support for IPU3 Bayer formats
Related show

Commit Message

Niklas Söderlund June 11, 2020, 1:45 a.m. UTC
Add support for the Bayer formats produced on the IPU3. The format uses
a memory layout that is hard to repack and keep the 10-bit sample size,
therefore scale the samples to 16-bit when creating the scanlines.

Signed-off-by: Niklas Söderlund <niklas.soderlund@ragnatech.se>
---
 src/qcam/dng_writer.cpp | 121 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 121 insertions(+)

Comments

Laurent Pinchart June 11, 2020, 3:05 a.m. UTC | #1
Hi Niklas,

Thank you for the patch.

On Thu, Jun 11, 2020 at 03:45:36AM +0200, Niklas Söderlund wrote:
> Add support for the Bayer formats produced on the IPU3. The format uses
> a memory layout that is hard to repack and keep the 10-bit sample size,
> therefore scale the samples to 16-bit when creating the scanlines.
> 
> Signed-off-by: Niklas Söderlund <niklas.soderlund@ragnatech.se>
> ---
>  src/qcam/dng_writer.cpp | 121 ++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 121 insertions(+)
> 
> diff --git a/src/qcam/dng_writer.cpp b/src/qcam/dng_writer.cpp
> index cbd8bed3e6d02269..330b169af25b7ac7 100644
> --- a/src/qcam/dng_writer.cpp
> +++ b/src/qcam/dng_writer.cpp
> @@ -82,6 +82,103 @@ void thumbScanlineSBGGRxxP(const FormatInfo &info, void *output,
>  	}
>  }
>  
> +void packScanlineIPU3(void *output, const void *input, unsigned int width)
> +{
> +	const uint8_t *in = static_cast<const uint8_t *>(input);
> +	uint16_t *out = static_cast<uint16_t *>(output);
> +
> +	/*
> +	 * Upscale the 10-bit format to 16-bit as it's not trivial to pack it
> +	 * as 10-bit without gaps.
> +	 *
> +	 * \todo Improve packing to keep the 10-bit sample size.
> +	 */

I think we could use an intermediate line buffer. When we'll address
that, not now :-)

> +	unsigned int x = 0;
> +	while (true) {
> +		for (unsigned int i = 0; i < 6; i++) {
> +			*out++ = (in[1] & 0x03) << 14 | (in[0] & 0xff) << 6;
> +			if (++x >= width)
> +				break;
> +
> +			*out++ = (in[2] & 0x0f) << 12 | (in[1] & 0xfc) << 4;
> +			if (++x >= width)
> +				break;
> +
> +			*out++ = (in[3] & 0x3f) << 10 | (in[2] & 0xf0) << 2;
> +			if (++x >= width)
> +				break;
> +
> +			*out++ = (in[4] & 0xff) <<  8 | (in[3] & 0xc0) << 0;
> +			if (++x >= width)
> +				break;
> +
> +			in += 5;
> +		}
> +
> +		*out++ = (in[1] & 0x03) << 14 | (in[0] & 0xff) << 6;

You will execute this even if the above for loop stopped due to EOL
being reached. You could replace all the break by return to fix that.

What an awful format... I wonder how they handle it at the hardware
level, I suppose there's an efficient mechanism.

> +		if (++x >= width)
> +			break;
> +
> +		in += 2;
> +	}
> +}
> +
> +void thumbScanlineIPU3(const FormatInfo &info, void *output,
> +		       const void *input, unsigned int width,
> +		       unsigned int stride)
> +{
> +	uint8_t *out = static_cast<uint8_t *>(output);
> +
> +	for (unsigned int x = 0; x < width; x++) {
> +		unsigned int pixel = x * 16;
> +		unsigned int block = pixel / 25;
> +		unsigned int pixelInBlock = pixel - block * 25;
> +
> +		/*
> +		 * If the pixel is the last in the block cheat a little and
> +		 * move one pixel backward to avoid reading between two blocks
> +		 * and having to deal with the padding bits.
> +		 */
> +		if (pixelInBlock == 24)
> +			pixelInBlock--;

I think that's fair enough. I wonder if we could switch (pixelInBlock)
below instead of switch (pixelInBlock % 4), in order to handle case 24
explicitly, but if it would require any additional complexity, I think
we can live with this hack.

> +
> +		const uint8_t *in = static_cast<const uint8_t *>(input)
> +			+ block * 32 + (pixelInBlock / 4) * 5;

Aligning + under = ?

> +
> +		uint16_t val1, val2, val3, val4;
> +		switch (pixelInBlock % 4) {
> +		case 0:
> +			val1 = (in[1] & 0x03) << 14 | (in[0] & 0xff) << 6;
> +			val2 = (in[2] & 0x0f) << 12 | (in[1] & 0xfc) << 4;
> +			val3 = (in[stride + 1] & 0x03) << 14 | (in[stride + 0] & 0xff) << 6;
> +			val4 = (in[stride + 2] & 0x0f) << 12 | (in[stride + 1] & 0xfc) << 4;
> +			break;
> +		case 1:
> +			val1 = (in[2] & 0x0f) << 12 | (in[1] & 0xfc) << 4;
> +			val2 = (in[3] & 0x3f) << 10 | (in[2] & 0xf0) << 2;
> +			val3 = (in[stride + 2] & 0x0f) << 12 | (in[stride + 1] & 0xfc) << 4;
> +			val4 = (in[stride + 3] & 0x3f) << 10 | (in[stride + 2] & 0xf0) << 2;
> +			break;
> +		case 2:
> +			val1 = (in[3] & 0x3f) << 10 | (in[2] & 0xf0) << 2;
> +			val2 = (in[4] & 0xff) <<  8 | (in[3] & 0xc0) << 0;
> +			val3 = (in[stride + 3] & 0x3f) << 10 | (in[stride + 2] & 0xf0) << 2;
> +			val4 = (in[stride + 4] & 0xff) <<  8 | (in[stride + 3] & 0xc0) << 0;
> +			break;
> +		case 3:
> +			val1 = (in[4] & 0xff) <<  8 | (in[3] & 0xc0) << 0;
> +			val2 = (in[6] & 0x03) << 14 | (in[5] & 0xff) << 6;
> +			val3 = (in[stride + 4] & 0xff) <<  8 | (in[stride + 3] & 0xc0) << 0;
> +			val4 = (in[stride + 6] & 0x03) << 14 | (in[stride + 5] & 0xff) << 6;
> +		}
> +
> +		uint8_t value = (val1 + val2 + val3 + val4) >> 8;

As val[1234] are 16-bit values, shouldn't you shift right by 10 to
convert to 8-bit and average the four values ?

Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>

> +		*out++ = value;
> +		*out++ = value;
> +		*out++ = value;
> +	}
> +}
> +
>  static const std::map<PixelFormat, FormatInfo> formatInfo = {
>  	{ PixelFormat(DRM_FORMAT_SBGGR10, MIPI_FORMAT_MOD_CSI2_PACKED), {
>  		.bitsPerSample = 10,
> @@ -131,6 +228,30 @@ static const std::map<PixelFormat, FormatInfo> formatInfo = {
>  		.packScanline = packScanlineSBGGR12P,
>  		.thumbScanline = thumbScanlineSBGGRxxP,
>  	} },
> +	{ PixelFormat(DRM_FORMAT_SBGGR10, IPU3_FORMAT_MOD_PACKED), {
> +		.bitsPerSample = 16,
> +		.pattern = { CFAPatternBlue, CFAPatternGreen, CFAPatternGreen, CFAPatternRed },
> +		.packScanline = packScanlineIPU3,
> +		.thumbScanline = thumbScanlineIPU3,
> +	} },
> +	{ PixelFormat(DRM_FORMAT_SGBRG10, IPU3_FORMAT_MOD_PACKED), {
> +		.bitsPerSample = 16,
> +		.pattern = { CFAPatternGreen, CFAPatternBlue, CFAPatternRed, CFAPatternGreen },
> +		.packScanline = packScanlineIPU3,
> +		.thumbScanline = thumbScanlineIPU3,
> +	} },
> +	{ PixelFormat(DRM_FORMAT_SGRBG10, IPU3_FORMAT_MOD_PACKED), {
> +		.bitsPerSample = 16,
> +		.pattern = { CFAPatternGreen, CFAPatternRed, CFAPatternBlue, CFAPatternGreen },
> +		.packScanline = packScanlineIPU3,
> +		.thumbScanline = thumbScanlineIPU3,
> +	} },
> +	{ PixelFormat(DRM_FORMAT_SRGGB10, IPU3_FORMAT_MOD_PACKED), {
> +		.bitsPerSample = 16,
> +		.pattern = { CFAPatternRed, CFAPatternGreen, CFAPatternGreen, CFAPatternBlue },
> +		.packScanline = packScanlineIPU3,
> +		.thumbScanline = thumbScanlineIPU3,
> +	} },
>  };
>  
>  int DNGWriter::write(const char *filename, const Camera *camera,

Patch

diff --git a/src/qcam/dng_writer.cpp b/src/qcam/dng_writer.cpp
index cbd8bed3e6d02269..330b169af25b7ac7 100644
--- a/src/qcam/dng_writer.cpp
+++ b/src/qcam/dng_writer.cpp
@@ -82,6 +82,103 @@  void thumbScanlineSBGGRxxP(const FormatInfo &info, void *output,
 	}
 }
 
+void packScanlineIPU3(void *output, const void *input, unsigned int width)
+{
+	const uint8_t *in = static_cast<const uint8_t *>(input);
+	uint16_t *out = static_cast<uint16_t *>(output);
+
+	/*
+	 * Upscale the 10-bit format to 16-bit as it's not trivial to pack it
+	 * as 10-bit without gaps.
+	 *
+	 * \todo Improve packing to keep the 10-bit sample size.
+	 */
+	unsigned int x = 0;
+	while (true) {
+		for (unsigned int i = 0; i < 6; i++) {
+			*out++ = (in[1] & 0x03) << 14 | (in[0] & 0xff) << 6;
+			if (++x >= width)
+				break;
+
+			*out++ = (in[2] & 0x0f) << 12 | (in[1] & 0xfc) << 4;
+			if (++x >= width)
+				break;
+
+			*out++ = (in[3] & 0x3f) << 10 | (in[2] & 0xf0) << 2;
+			if (++x >= width)
+				break;
+
+			*out++ = (in[4] & 0xff) <<  8 | (in[3] & 0xc0) << 0;
+			if (++x >= width)
+				break;
+
+			in += 5;
+		}
+
+		*out++ = (in[1] & 0x03) << 14 | (in[0] & 0xff) << 6;
+		if (++x >= width)
+			break;
+
+		in += 2;
+	}
+}
+
+void thumbScanlineIPU3(const FormatInfo &info, void *output,
+		       const void *input, unsigned int width,
+		       unsigned int stride)
+{
+	uint8_t *out = static_cast<uint8_t *>(output);
+
+	for (unsigned int x = 0; x < width; x++) {
+		unsigned int pixel = x * 16;
+		unsigned int block = pixel / 25;
+		unsigned int pixelInBlock = pixel - block * 25;
+
+		/*
+		 * If the pixel is the last in the block cheat a little and
+		 * move one pixel backward to avoid reading between two blocks
+		 * and having to deal with the padding bits.
+		 */
+		if (pixelInBlock == 24)
+			pixelInBlock--;
+
+		const uint8_t *in = static_cast<const uint8_t *>(input)
+			+ block * 32 + (pixelInBlock / 4) * 5;
+
+		uint16_t val1, val2, val3, val4;
+		switch (pixelInBlock % 4) {
+		case 0:
+			val1 = (in[1] & 0x03) << 14 | (in[0] & 0xff) << 6;
+			val2 = (in[2] & 0x0f) << 12 | (in[1] & 0xfc) << 4;
+			val3 = (in[stride + 1] & 0x03) << 14 | (in[stride + 0] & 0xff) << 6;
+			val4 = (in[stride + 2] & 0x0f) << 12 | (in[stride + 1] & 0xfc) << 4;
+			break;
+		case 1:
+			val1 = (in[2] & 0x0f) << 12 | (in[1] & 0xfc) << 4;
+			val2 = (in[3] & 0x3f) << 10 | (in[2] & 0xf0) << 2;
+			val3 = (in[stride + 2] & 0x0f) << 12 | (in[stride + 1] & 0xfc) << 4;
+			val4 = (in[stride + 3] & 0x3f) << 10 | (in[stride + 2] & 0xf0) << 2;
+			break;
+		case 2:
+			val1 = (in[3] & 0x3f) << 10 | (in[2] & 0xf0) << 2;
+			val2 = (in[4] & 0xff) <<  8 | (in[3] & 0xc0) << 0;
+			val3 = (in[stride + 3] & 0x3f) << 10 | (in[stride + 2] & 0xf0) << 2;
+			val4 = (in[stride + 4] & 0xff) <<  8 | (in[stride + 3] & 0xc0) << 0;
+			break;
+		case 3:
+			val1 = (in[4] & 0xff) <<  8 | (in[3] & 0xc0) << 0;
+			val2 = (in[6] & 0x03) << 14 | (in[5] & 0xff) << 6;
+			val3 = (in[stride + 4] & 0xff) <<  8 | (in[stride + 3] & 0xc0) << 0;
+			val4 = (in[stride + 6] & 0x03) << 14 | (in[stride + 5] & 0xff) << 6;
+		}
+
+		uint8_t value = (val1 + val2 + val3 + val4) >> 8;
+		*out++ = value;
+		*out++ = value;
+		*out++ = value;
+	}
+}
+
 static const std::map<PixelFormat, FormatInfo> formatInfo = {
 	{ PixelFormat(DRM_FORMAT_SBGGR10, MIPI_FORMAT_MOD_CSI2_PACKED), {
 		.bitsPerSample = 10,
@@ -131,6 +228,30 @@  static const std::map<PixelFormat, FormatInfo> formatInfo = {
 		.packScanline = packScanlineSBGGR12P,
 		.thumbScanline = thumbScanlineSBGGRxxP,
 	} },
+	{ PixelFormat(DRM_FORMAT_SBGGR10, IPU3_FORMAT_MOD_PACKED), {
+		.bitsPerSample = 16,
+		.pattern = { CFAPatternBlue, CFAPatternGreen, CFAPatternGreen, CFAPatternRed },
+		.packScanline = packScanlineIPU3,
+		.thumbScanline = thumbScanlineIPU3,
+	} },
+	{ PixelFormat(DRM_FORMAT_SGBRG10, IPU3_FORMAT_MOD_PACKED), {
+		.bitsPerSample = 16,
+		.pattern = { CFAPatternGreen, CFAPatternBlue, CFAPatternRed, CFAPatternGreen },
+		.packScanline = packScanlineIPU3,
+		.thumbScanline = thumbScanlineIPU3,
+	} },
+	{ PixelFormat(DRM_FORMAT_SGRBG10, IPU3_FORMAT_MOD_PACKED), {
+		.bitsPerSample = 16,
+		.pattern = { CFAPatternGreen, CFAPatternRed, CFAPatternBlue, CFAPatternGreen },
+		.packScanline = packScanlineIPU3,
+		.thumbScanline = thumbScanlineIPU3,
+	} },
+	{ PixelFormat(DRM_FORMAT_SRGGB10, IPU3_FORMAT_MOD_PACKED), {
+		.bitsPerSample = 16,
+		.pattern = { CFAPatternRed, CFAPatternGreen, CFAPatternGreen, CFAPatternBlue },
+		.packScanline = packScanlineIPU3,
+		.thumbScanline = thumbScanlineIPU3,
+	} },
 };
 
 int DNGWriter::write(const char *filename, const Camera *camera,