[libcamera-devel,v3,1/1] pipeline: raspberrypi: Fix handling of colour spaces
diff mbox series

Message ID 20230120162258.7039-2-david.plowman@raspberrypi.com
State Accepted
Headers show
Series
  • Fix Raspberry Pi colour spaces
Related show

Commit Message

David Plowman Jan. 20, 2023, 4:22 p.m. UTC
We implement a custom validateColorSpaces method that forces all
(non-raw) streams to same colour space, whilst distinguishing RGB
streams from YUV ones, as the former must have the YCbCr encoding and
range over-written.

When we apply the colour space, we always send the full YUV version as
that gets converted correctly to what our hardware drivers expect. It
is also careful to check what comes back as the YCbCr information gets
overwritten again on the way back.

Signed-off-by: David Plowman <david.plowman@raspberrypi.com>
Reviewed-by: Naushir Patuck <naush@raspberrypi.com>
---
 .../pipeline/raspberrypi/raspberrypi.cpp      | 115 +++++++++++++++++-
 1 file changed, 114 insertions(+), 1 deletion(-)

Comments

Kieran Bingham Jan. 20, 2023, 4:50 p.m. UTC | #1
Quoting David Plowman via libcamera-devel (2023-01-20 16:22:58)
> We implement a custom validateColorSpaces method that forces all
> (non-raw) streams to same colour space, whilst distinguishing RGB
> streams from YUV ones, as the former must have the YCbCr encoding and
> range over-written.
> 
> When we apply the colour space, we always send the full YUV version as
> that gets converted correctly to what our hardware drivers expect. It
> is also careful to check what comes back as the YCbCr information gets
> overwritten again on the way back.
> 
> Signed-off-by: David Plowman <david.plowman@raspberrypi.com>
> Reviewed-by: Naushir Patuck <naush@raspberrypi.com>
> ---
>  .../pipeline/raspberrypi/raspberrypi.cpp      | 115 +++++++++++++++++-
>  1 file changed, 114 insertions(+), 1 deletion(-)
> 
> diff --git a/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp b/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp
> index 8569df17..f768dced 100644
> --- a/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp
> +++ b/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp
> @@ -310,6 +310,7 @@ class RPiCameraConfiguration : public CameraConfiguration
>  public:
>         RPiCameraConfiguration(const RPiCameraData *data);
>  
> +       CameraConfiguration::Status validateColorSpaces(ColorSpaceFlags flags);
>         Status validate() override;
>  
>         /* Cache the combinedTransform_ that will be applied to the sensor */
> @@ -317,6 +318,13 @@ public:
>  
>  private:
>         const RPiCameraData *data_;
> +
> +       /*
> +        * Store the colour spaces that all our streams will have. RGB format streams
> +        * will be the same but need the YCbCr fields cleared.
> +        */
> +       std::optional<ColorSpace> yuvColorSpace_;
> +       std::optional<ColorSpace> rgbColorSpace_;
>  };
>  
>  class PipelineHandlerRPi : public PipelineHandler
> @@ -357,6 +365,105 @@ RPiCameraConfiguration::RPiCameraConfiguration(const RPiCameraData *data)
>  {
>  }
>  
> +static const std::vector<ColorSpace> validColorSpaces = {
> +       ColorSpace::Sycc,
> +       ColorSpace::Smpte170m,
> +       ColorSpace::Rec709
> +};
> +
> +static std::optional<ColorSpace> findValidColorSpace(const ColorSpace &colourSpace)
> +{
> +       for (auto cs : validColorSpaces) {
> +               if (colourSpace.primaries == cs.primaries &&
> +                   colourSpace.transferFunction == cs.transferFunction)
> +                       return cs;
> +       }
> +
> +       return std::nullopt;
> +}
> +
> +static bool isRgb(const PixelFormat &pixFmt)
> +{
> +       const PixelFormatInfo &info = PixelFormatInfo::info(pixFmt);
> +       return info.colourEncoding == PixelFormatInfo::ColourEncodingRGB;
> +}
> +
> +static bool isYuv(const PixelFormat &pixFmt)
> +{
> +       /* The code below would return true for raw mono streams, so weed those out first. */
> +       if (isRaw(pixFmt))
> +               return false;
> +
> +       const PixelFormatInfo &info = PixelFormatInfo::info(pixFmt);
> +       return info.colourEncoding == PixelFormatInfo::ColourEncodingYUV;
> +}
> +
> +/*
> + * Raspberry Pi drivers expect the following colour spaces:
> + * - V4L2_COLORSPACE_RAW for raw streams.
> + * - One of V4L2_COLORSPACE_JPEG, V4L2_COLORSPACE_SMPTE170M, V4L2_COLORSPACE_REC709 for
> + *   non-raw streams. Other fields such as transfer function, YCbCr encoding and
> + *   quantisation are not used.
> + *
> + * The libcamera colour spaces that we wish to use corresponding to these are therefore:
> + * - ColorSpace::Raw for V4L2_COLORSPACE_RAW
> + * - ColorSpace::Sycc for V4L2_COLORSPACE_JPEG
> + * - ColorSpace::Smpte170m for V4L2_COLORSPACE_SMPTE170M
> + * - ColorSpace::Rec709 for V4L2_COLORSPACE_REC709
> + */
> +
> +CameraConfiguration::Status RPiCameraConfiguration::validateColorSpaces([[maybe_unused]] ColorSpaceFlags flags)
> +{
> +       Status status = Valid;
> +       yuvColorSpace_.reset();
> +
> +       for (auto cfg : config_) {
> +               /* First fix up raw streams to have the "raw" colour space. */
> +               if (isRaw(cfg.pixelFormat)) {
> +                       /* If there was no value here, that doesn't count as "adjusted". */
> +                       if (cfg.colorSpace && cfg.colorSpace != ColorSpace::Raw) {
> +                               status = Adjusted;
> +                               cfg.colorSpace = ColorSpace::Raw;
> +                       }
> +                       continue;
> +               }
> +
> +               /* Next we need to find our shared colour space. The first valid one will do. */
> +               if (cfg.colorSpace && !yuvColorSpace_)
> +                       yuvColorSpace_ = findValidColorSpace(cfg.colorSpace.value());
> +       }
> +
> +       /* If no colour space was given anywhere, choose sYCC. */
> +       if (!yuvColorSpace_)
> +               yuvColorSpace_ = ColorSpace::Sycc;
> +
> +       /* Note the version of this that any RGB streams will have to use. */
> +       rgbColorSpace_ = yuvColorSpace_;
> +       rgbColorSpace_->ycbcrEncoding = ColorSpace::YcbcrEncoding::None;
> +       rgbColorSpace_->range = ColorSpace::Range::Full;
> +
> +       /* Go through the streams again and force everyone to the same colour space. */
> +       for (auto cfg : config_) {
> +               if (cfg.colorSpace == ColorSpace::Raw)
> +                       continue;
> +
> +               if (isYuv(cfg.pixelFormat) && cfg.colorSpace != yuvColorSpace_) {
> +                       /* Again, no value means "not adjusted". */
> +                       if (cfg.colorSpace)
> +                               status = Adjusted;
> +                       cfg.colorSpace = yuvColorSpace_;
> +               }

Nit blank line? Or is this hugging of two scopes intentional?

> +               if (isRgb(cfg.pixelFormat) && cfg.colorSpace != rgbColorSpace_) {
> +                       /* Be nice, and let the YUV version count as non-adjusted too. */

I do wonder if we should update the Adjusted to a set of Flags that say
'what' has been adjusted instead ... Then an application can identify if
it cares about the change or not.

But - I don't think that matters to this patch.

The fact we're making small adjustments but not reporting to the
application may worry me a little, but I expect if this passes your
metric for worrying about colorspace, I shouldn't worry, as you're bar
must be higher than mine!

So I'd say:

Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>


> +                       if (cfg.colorSpace && cfg.colorSpace != yuvColorSpace_)
> +                               status = Adjusted;
> +                       cfg.colorSpace = rgbColorSpace_;
> +               }
> +       }
> +
> +       return status;
> +}
> +
>  CameraConfiguration::Status RPiCameraConfiguration::validate()
>  {
>         Status status = Valid;
> @@ -533,7 +640,8 @@ CameraConfiguration::Status RPiCameraConfiguration::validate()
>                 V4L2DeviceFormat format;
>                 format.fourcc = dev->toV4L2PixelFormat(cfg.pixelFormat);
>                 format.size = cfg.size;
> -               format.colorSpace = cfg.colorSpace;
> +               /* We want to send the associated YCbCr info through to the driver. */
> +               format.colorSpace = yuvColorSpace_;
>  
>                 LOG(RPI, Debug)
>                         << "Try color space " << ColorSpace::toString(cfg.colorSpace);
> @@ -542,6 +650,11 @@ CameraConfiguration::Status RPiCameraConfiguration::validate()
>                 if (ret)
>                         return Invalid;
>  
> +               /*
> +                * But for RGB streams, the YCbCr info gets overwritten on the way back
> +                * so we must check against what the stream cfg says, not what we actually
> +                * requested (which carefully included the YCbCr info)!
> +                */
>                 if (cfg.colorSpace != format.colorSpace) {
>                         status = Adjusted;
>                         LOG(RPI, Debug)
> -- 
> 2.30.2
>
Umang Jain Jan. 25, 2023, 6:48 a.m. UTC | #2
Hi David,

On 1/20/23 9:52 PM, David Plowman via libcamera-devel wrote:
> We implement a custom validateColorSpaces method that forces all
> (non-raw) streams to same colour space, whilst distinguishing RGB
> streams from YUV ones, as the former must have the YCbCr encoding and
> range over-written.
>
> When we apply the colour space, we always send the full YUV version as
> that gets converted correctly to what our hardware drivers expect. It
> is also careful to check what comes back as the YCbCr information gets
> overwritten again on the way back.
>
> Signed-off-by: David Plowman <david.plowman@raspberrypi.com>
> Reviewed-by: Naushir Patuck <naush@raspberrypi.com>
> ---
>   .../pipeline/raspberrypi/raspberrypi.cpp      | 115 +++++++++++++++++-
>   1 file changed, 114 insertions(+), 1 deletion(-)
>
> diff --git a/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp b/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp
> index 8569df17..f768dced 100644
> --- a/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp
> +++ b/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp
> @@ -310,6 +310,7 @@ class RPiCameraConfiguration : public CameraConfiguration
>   public:
>   	RPiCameraConfiguration(const RPiCameraData *data);
>   
> +	CameraConfiguration::Status validateColorSpaces(ColorSpaceFlags flags);
>   	Status validate() override;
>   
>   	/* Cache the combinedTransform_ that will be applied to the sensor */
> @@ -317,6 +318,13 @@ public:
>   
>   private:
>   	const RPiCameraData *data_;
> +
> +	/*
> +	 * Store the colour spaces that all our streams will have. RGB format streams
> +	 * will be the same but need the YCbCr fields cleared.

Would re-pharse it like:

         /*
          * Store the colour spaces that all our streams will have. RGB 
format streams
          * will have the same colorspace as YUV streams, with YCbCr 
field cleared and
          * range set to full.
          */

> +	 */
> +	std::optional<ColorSpace> yuvColorSpace_;
> +	std::optional<ColorSpace> rgbColorSpace_;
>   };
>   
>   class PipelineHandlerRPi : public PipelineHandler
> @@ -357,6 +365,105 @@ RPiCameraConfiguration::RPiCameraConfiguration(const RPiCameraData *data)
>   {
>   }
>   
> +static const std::vector<ColorSpace> validColorSpaces = {
> +	ColorSpace::Sycc,
> +	ColorSpace::Smpte170m,
> +	ColorSpace::Rec709
> +};
> +
> +static std::optional<ColorSpace> findValidColorSpace(const ColorSpace &colourSpace)
> +{
> +	for (auto cs : validColorSpaces) {
> +		if (colourSpace.primaries == cs.primaries &&
> +		    colourSpace.transferFunction == cs.transferFunction)
> +			return cs;
> +	}
> +
> +	return std::nullopt;
> +}
> +
> +static bool isRgb(const PixelFormat &pixFmt)
> +{
> +	const PixelFormatInfo &info = PixelFormatInfo::info(pixFmt);
> +	return info.colourEncoding == PixelFormatInfo::ColourEncodingRGB;
> +}
> +
> +static bool isYuv(const PixelFormat &pixFmt)
> +{
> +	/* The code below would return true for raw mono streams, so weed those out first. */
> +	if (isRaw(pixFmt))
> +		return false;
> +
> +	const PixelFormatInfo &info = PixelFormatInfo::info(pixFmt);
> +	return info.colourEncoding == PixelFormatInfo::ColourEncodingYUV;
> +}
> +
> +/*
> + * Raspberry Pi drivers expect the following colour spaces:
> + * - V4L2_COLORSPACE_RAW for raw streams.
> + * - One of V4L2_COLORSPACE_JPEG, V4L2_COLORSPACE_SMPTE170M, V4L2_COLORSPACE_REC709 for
> + *   non-raw streams. Other fields such as transfer function, YCbCr encoding and
> + *   quantisation are not used.
> + *
> + * The libcamera colour spaces that we wish to use corresponding to these are therefore:
> + * - ColorSpace::Raw for V4L2_COLORSPACE_RAW
> + * - ColorSpace::Sycc for V4L2_COLORSPACE_JPEG
> + * - ColorSpace::Smpte170m for V4L2_COLORSPACE_SMPTE170M
> + * - ColorSpace::Rec709 for V4L2_COLORSPACE_REC709
> + */

Thank you for the information. This makes the expectations clearer.

With that,

Reviewed-by: Umang Jain <umang.jain@ideasonboard.com>


I can fixup the comment while applying the patch (if you are OK with the 
comment's explanation).

> +
> +CameraConfiguration::Status RPiCameraConfiguration::validateColorSpaces([[maybe_unused]] ColorSpaceFlags flags)
> +{
> +	Status status = Valid;
> +	yuvColorSpace_.reset();
> +
> +	for (auto cfg : config_) {
> +		/* First fix up raw streams to have the "raw" colour space. */
> +		if (isRaw(cfg.pixelFormat)) {
> +			/* If there was no value here, that doesn't count as "adjusted". */
> +			if (cfg.colorSpace && cfg.colorSpace != ColorSpace::Raw) {
> +				status = Adjusted;
> +				cfg.colorSpace = ColorSpace::Raw;
> +			}
> +			continue;
> +		}
> +
> +		/* Next we need to find our shared colour space. The first valid one will do. */
> +		if (cfg.colorSpace && !yuvColorSpace_)
> +			yuvColorSpace_ = findValidColorSpace(cfg.colorSpace.value());
> +	}
> +
> +	/* If no colour space was given anywhere, choose sYCC. */
> +	if (!yuvColorSpace_)
> +		yuvColorSpace_ = ColorSpace::Sycc;
> +
> +	/* Note the version of this that any RGB streams will have to use. */
> +	rgbColorSpace_ = yuvColorSpace_;
> +	rgbColorSpace_->ycbcrEncoding = ColorSpace::YcbcrEncoding::None;
> +	rgbColorSpace_->range = ColorSpace::Range::Full;
> +
> +	/* Go through the streams again and force everyone to the same colour space. */
> +	for (auto cfg : config_) {
> +		if (cfg.colorSpace == ColorSpace::Raw)
> +			continue;
> +
> +		if (isYuv(cfg.pixelFormat) && cfg.colorSpace != yuvColorSpace_) {
> +			/* Again, no value means "not adjusted". */
> +			if (cfg.colorSpace)
> +				status = Adjusted;
> +			cfg.colorSpace = yuvColorSpace_;
> +		}
> +		if (isRgb(cfg.pixelFormat) && cfg.colorSpace != rgbColorSpace_) {
> +			/* Be nice, and let the YUV version count as non-adjusted too. */
> +			if (cfg.colorSpace && cfg.colorSpace != yuvColorSpace_)
> +				status = Adjusted;
> +			cfg.colorSpace = rgbColorSpace_;
> +		}
> +	}
> +
> +	return status;
> +}
> +
>   CameraConfiguration::Status RPiCameraConfiguration::validate()
>   {
>   	Status status = Valid;
> @@ -533,7 +640,8 @@ CameraConfiguration::Status RPiCameraConfiguration::validate()
>   		V4L2DeviceFormat format;
>   		format.fourcc = dev->toV4L2PixelFormat(cfg.pixelFormat);
>   		format.size = cfg.size;
> -		format.colorSpace = cfg.colorSpace;
> +		/* We want to send the associated YCbCr info through to the driver. */
> +		format.colorSpace = yuvColorSpace_;
>   
>   		LOG(RPI, Debug)
>   			<< "Try color space " << ColorSpace::toString(cfg.colorSpace);
> @@ -542,6 +650,11 @@ CameraConfiguration::Status RPiCameraConfiguration::validate()
>   		if (ret)
>   			return Invalid;
>   
> +		/*
> +		 * But for RGB streams, the YCbCr info gets overwritten on the way back
> +		 * so we must check against what the stream cfg says, not what we actually
> +		 * requested (which carefully included the YCbCr info)!
> +		 */
>   		if (cfg.colorSpace != format.colorSpace) {
>   			status = Adjusted;
>   			LOG(RPI, Debug)
Kieran Bingham Jan. 30, 2023, 11:04 a.m. UTC | #3
Hi David,

There's a small question below that is holding this back from being merged.

I'm planning a release tomorrow, so I'd like to get this colourspace fix
in.

Quoting Umang Jain via libcamera-devel (2023-01-25 06:48:19)
> Hi David,
> 
> On 1/20/23 9:52 PM, David Plowman via libcamera-devel wrote:
> > We implement a custom validateColorSpaces method that forces all
> > (non-raw) streams to same colour space, whilst distinguishing RGB
> > streams from YUV ones, as the former must have the YCbCr encoding and
> > range over-written.
> >
> > When we apply the colour space, we always send the full YUV version as
> > that gets converted correctly to what our hardware drivers expect. It
> > is also careful to check what comes back as the YCbCr information gets
> > overwritten again on the way back.
> >
> > Signed-off-by: David Plowman <david.plowman@raspberrypi.com>
> > Reviewed-by: Naushir Patuck <naush@raspberrypi.com>
> > ---
> >   .../pipeline/raspberrypi/raspberrypi.cpp      | 115 +++++++++++++++++-
> >   1 file changed, 114 insertions(+), 1 deletion(-)
> >
> > diff --git a/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp b/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp
> > index 8569df17..f768dced 100644
> > --- a/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp
> > +++ b/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp
> > @@ -310,6 +310,7 @@ class RPiCameraConfiguration : public CameraConfiguration
> >   public:
> >       RPiCameraConfiguration(const RPiCameraData *data);
> >   
> > +     CameraConfiguration::Status validateColorSpaces(ColorSpaceFlags flags);
> >       Status validate() override;
> >   
> >       /* Cache the combinedTransform_ that will be applied to the sensor */
> > @@ -317,6 +318,13 @@ public:
> >   
> >   private:
> >       const RPiCameraData *data_;
> > +
> > +     /*
> > +      * Store the colour spaces that all our streams will have. RGB format streams
> > +      * will be the same but need the YCbCr fields cleared.
> 
> Would re-pharse it like:
> 
>          /*
>           * Store the colour spaces that all our streams will have. RGB 
> format streams
>           * will have the same colorspace as YUV streams, with YCbCr 
> field cleared and
>           * range set to full.
>           */

Are you happy with the expansion on this comment?


--
Kieran



> 
> > +      */
> > +     std::optional<ColorSpace> yuvColorSpace_;
> > +     std::optional<ColorSpace> rgbColorSpace_;
> >   };
> >   
> >   class PipelineHandlerRPi : public PipelineHandler
> > @@ -357,6 +365,105 @@ RPiCameraConfiguration::RPiCameraConfiguration(const RPiCameraData *data)
> >   {
> >   }
> >   
> > +static const std::vector<ColorSpace> validColorSpaces = {
> > +     ColorSpace::Sycc,
> > +     ColorSpace::Smpte170m,
> > +     ColorSpace::Rec709
> > +};
> > +
> > +static std::optional<ColorSpace> findValidColorSpace(const ColorSpace &colourSpace)
> > +{
> > +     for (auto cs : validColorSpaces) {
> > +             if (colourSpace.primaries == cs.primaries &&
> > +                 colourSpace.transferFunction == cs.transferFunction)
> > +                     return cs;
> > +     }
> > +
> > +     return std::nullopt;
> > +}
> > +
> > +static bool isRgb(const PixelFormat &pixFmt)
> > +{
> > +     const PixelFormatInfo &info = PixelFormatInfo::info(pixFmt);
> > +     return info.colourEncoding == PixelFormatInfo::ColourEncodingRGB;
> > +}
> > +
> > +static bool isYuv(const PixelFormat &pixFmt)
> > +{
> > +     /* The code below would return true for raw mono streams, so weed those out first. */
> > +     if (isRaw(pixFmt))
> > +             return false;
> > +
> > +     const PixelFormatInfo &info = PixelFormatInfo::info(pixFmt);
> > +     return info.colourEncoding == PixelFormatInfo::ColourEncodingYUV;
> > +}
> > +
> > +/*
> > + * Raspberry Pi drivers expect the following colour spaces:
> > + * - V4L2_COLORSPACE_RAW for raw streams.
> > + * - One of V4L2_COLORSPACE_JPEG, V4L2_COLORSPACE_SMPTE170M, V4L2_COLORSPACE_REC709 for
> > + *   non-raw streams. Other fields such as transfer function, YCbCr encoding and
> > + *   quantisation are not used.
> > + *
> > + * The libcamera colour spaces that we wish to use corresponding to these are therefore:
> > + * - ColorSpace::Raw for V4L2_COLORSPACE_RAW
> > + * - ColorSpace::Sycc for V4L2_COLORSPACE_JPEG
> > + * - ColorSpace::Smpte170m for V4L2_COLORSPACE_SMPTE170M
> > + * - ColorSpace::Rec709 for V4L2_COLORSPACE_REC709
> > + */
> 
> Thank you for the information. This makes the expectations clearer.
> 
> With that,
> 
> Reviewed-by: Umang Jain <umang.jain@ideasonboard.com>
> 
> 
> I can fixup the comment while applying the patch (if you are OK with the 
> comment's explanation).
> 
> > +
> > +CameraConfiguration::Status RPiCameraConfiguration::validateColorSpaces([[maybe_unused]] ColorSpaceFlags flags)
> > +{
> > +     Status status = Valid;
> > +     yuvColorSpace_.reset();
> > +
> > +     for (auto cfg : config_) {
> > +             /* First fix up raw streams to have the "raw" colour space. */
> > +             if (isRaw(cfg.pixelFormat)) {
> > +                     /* If there was no value here, that doesn't count as "adjusted". */
> > +                     if (cfg.colorSpace && cfg.colorSpace != ColorSpace::Raw) {
> > +                             status = Adjusted;
> > +                             cfg.colorSpace = ColorSpace::Raw;
> > +                     }
> > +                     continue;
> > +             }
> > +
> > +             /* Next we need to find our shared colour space. The first valid one will do. */
> > +             if (cfg.colorSpace && !yuvColorSpace_)
> > +                     yuvColorSpace_ = findValidColorSpace(cfg.colorSpace.value());
> > +     }
> > +
> > +     /* If no colour space was given anywhere, choose sYCC. */
> > +     if (!yuvColorSpace_)
> > +             yuvColorSpace_ = ColorSpace::Sycc;
> > +
> > +     /* Note the version of this that any RGB streams will have to use. */
> > +     rgbColorSpace_ = yuvColorSpace_;
> > +     rgbColorSpace_->ycbcrEncoding = ColorSpace::YcbcrEncoding::None;
> > +     rgbColorSpace_->range = ColorSpace::Range::Full;
> > +
> > +     /* Go through the streams again and force everyone to the same colour space. */
> > +     for (auto cfg : config_) {
> > +             if (cfg.colorSpace == ColorSpace::Raw)
> > +                     continue;
> > +
> > +             if (isYuv(cfg.pixelFormat) && cfg.colorSpace != yuvColorSpace_) {
> > +                     /* Again, no value means "not adjusted". */
> > +                     if (cfg.colorSpace)
> > +                             status = Adjusted;
> > +                     cfg.colorSpace = yuvColorSpace_;
> > +             }
> > +             if (isRgb(cfg.pixelFormat) && cfg.colorSpace != rgbColorSpace_) {
> > +                     /* Be nice, and let the YUV version count as non-adjusted too. */
> > +                     if (cfg.colorSpace && cfg.colorSpace != yuvColorSpace_)
> > +                             status = Adjusted;
> > +                     cfg.colorSpace = rgbColorSpace_;
> > +             }
> > +     }
> > +
> > +     return status;
> > +}
> > +
> >   CameraConfiguration::Status RPiCameraConfiguration::validate()
> >   {
> >       Status status = Valid;
> > @@ -533,7 +640,8 @@ CameraConfiguration::Status RPiCameraConfiguration::validate()
> >               V4L2DeviceFormat format;
> >               format.fourcc = dev->toV4L2PixelFormat(cfg.pixelFormat);
> >               format.size = cfg.size;
> > -             format.colorSpace = cfg.colorSpace;
> > +             /* We want to send the associated YCbCr info through to the driver. */
> > +             format.colorSpace = yuvColorSpace_;
> >   
> >               LOG(RPI, Debug)
> >                       << "Try color space " << ColorSpace::toString(cfg.colorSpace);
> > @@ -542,6 +650,11 @@ CameraConfiguration::Status RPiCameraConfiguration::validate()
> >               if (ret)
> >                       return Invalid;
> >   
> > +             /*
> > +              * But for RGB streams, the YCbCr info gets overwritten on the way back
> > +              * so we must check against what the stream cfg says, not what we actually
> > +              * requested (which carefully included the YCbCr info)!
> > +              */
> >               if (cfg.colorSpace != format.colorSpace) {
> >                       status = Adjusted;
> >                       LOG(RPI, Debug)
>
David Plowman Jan. 31, 2023, 6:38 a.m. UTC | #4
Hi Kieran, Umag

I didn't spot the question, sorry about that. But yes, more complete
comments always welcome!

Thanks
David

On Mon, 30 Jan 2023 at 11:04, Kieran Bingham
<kieran.bingham@ideasonboard.com> wrote:
>
> Hi David,
>
> There's a small question below that is holding this back from being merged.
>
> I'm planning a release tomorrow, so I'd like to get this colourspace fix
> in.
>
> Quoting Umang Jain via libcamera-devel (2023-01-25 06:48:19)
> > Hi David,
> >
> > On 1/20/23 9:52 PM, David Plowman via libcamera-devel wrote:
> > > We implement a custom validateColorSpaces method that forces all
> > > (non-raw) streams to same colour space, whilst distinguishing RGB
> > > streams from YUV ones, as the former must have the YCbCr encoding and
> > > range over-written.
> > >
> > > When we apply the colour space, we always send the full YUV version as
> > > that gets converted correctly to what our hardware drivers expect. It
> > > is also careful to check what comes back as the YCbCr information gets
> > > overwritten again on the way back.
> > >
> > > Signed-off-by: David Plowman <david.plowman@raspberrypi.com>
> > > Reviewed-by: Naushir Patuck <naush@raspberrypi.com>
> > > ---
> > >   .../pipeline/raspberrypi/raspberrypi.cpp      | 115 +++++++++++++++++-
> > >   1 file changed, 114 insertions(+), 1 deletion(-)
> > >
> > > diff --git a/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp b/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp
> > > index 8569df17..f768dced 100644
> > > --- a/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp
> > > +++ b/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp
> > > @@ -310,6 +310,7 @@ class RPiCameraConfiguration : public CameraConfiguration
> > >   public:
> > >       RPiCameraConfiguration(const RPiCameraData *data);
> > >
> > > +     CameraConfiguration::Status validateColorSpaces(ColorSpaceFlags flags);
> > >       Status validate() override;
> > >
> > >       /* Cache the combinedTransform_ that will be applied to the sensor */
> > > @@ -317,6 +318,13 @@ public:
> > >
> > >   private:
> > >       const RPiCameraData *data_;
> > > +
> > > +     /*
> > > +      * Store the colour spaces that all our streams will have. RGB format streams
> > > +      * will be the same but need the YCbCr fields cleared.
> >
> > Would re-pharse it like:
> >
> >          /*
> >           * Store the colour spaces that all our streams will have. RGB
> > format streams
> >           * will have the same colorspace as YUV streams, with YCbCr
> > field cleared and
> >           * range set to full.
> >           */
>
> Are you happy with the expansion on this comment?
>
>
> --
> Kieran
>
>
>
> >
> > > +      */
> > > +     std::optional<ColorSpace> yuvColorSpace_;
> > > +     std::optional<ColorSpace> rgbColorSpace_;
> > >   };
> > >
> > >   class PipelineHandlerRPi : public PipelineHandler
> > > @@ -357,6 +365,105 @@ RPiCameraConfiguration::RPiCameraConfiguration(const RPiCameraData *data)
> > >   {
> > >   }
> > >
> > > +static const std::vector<ColorSpace> validColorSpaces = {
> > > +     ColorSpace::Sycc,
> > > +     ColorSpace::Smpte170m,
> > > +     ColorSpace::Rec709
> > > +};
> > > +
> > > +static std::optional<ColorSpace> findValidColorSpace(const ColorSpace &colourSpace)
> > > +{
> > > +     for (auto cs : validColorSpaces) {
> > > +             if (colourSpace.primaries == cs.primaries &&
> > > +                 colourSpace.transferFunction == cs.transferFunction)
> > > +                     return cs;
> > > +     }
> > > +
> > > +     return std::nullopt;
> > > +}
> > > +
> > > +static bool isRgb(const PixelFormat &pixFmt)
> > > +{
> > > +     const PixelFormatInfo &info = PixelFormatInfo::info(pixFmt);
> > > +     return info.colourEncoding == PixelFormatInfo::ColourEncodingRGB;
> > > +}
> > > +
> > > +static bool isYuv(const PixelFormat &pixFmt)
> > > +{
> > > +     /* The code below would return true for raw mono streams, so weed those out first. */
> > > +     if (isRaw(pixFmt))
> > > +             return false;
> > > +
> > > +     const PixelFormatInfo &info = PixelFormatInfo::info(pixFmt);
> > > +     return info.colourEncoding == PixelFormatInfo::ColourEncodingYUV;
> > > +}
> > > +
> > > +/*
> > > + * Raspberry Pi drivers expect the following colour spaces:
> > > + * - V4L2_COLORSPACE_RAW for raw streams.
> > > + * - One of V4L2_COLORSPACE_JPEG, V4L2_COLORSPACE_SMPTE170M, V4L2_COLORSPACE_REC709 for
> > > + *   non-raw streams. Other fields such as transfer function, YCbCr encoding and
> > > + *   quantisation are not used.
> > > + *
> > > + * The libcamera colour spaces that we wish to use corresponding to these are therefore:
> > > + * - ColorSpace::Raw for V4L2_COLORSPACE_RAW
> > > + * - ColorSpace::Sycc for V4L2_COLORSPACE_JPEG
> > > + * - ColorSpace::Smpte170m for V4L2_COLORSPACE_SMPTE170M
> > > + * - ColorSpace::Rec709 for V4L2_COLORSPACE_REC709
> > > + */
> >
> > Thank you for the information. This makes the expectations clearer.
> >
> > With that,
> >
> > Reviewed-by: Umang Jain <umang.jain@ideasonboard.com>
> >
> >
> > I can fixup the comment while applying the patch (if you are OK with the
> > comment's explanation).
> >
> > > +
> > > +CameraConfiguration::Status RPiCameraConfiguration::validateColorSpaces([[maybe_unused]] ColorSpaceFlags flags)
> > > +{
> > > +     Status status = Valid;
> > > +     yuvColorSpace_.reset();
> > > +
> > > +     for (auto cfg : config_) {
> > > +             /* First fix up raw streams to have the "raw" colour space. */
> > > +             if (isRaw(cfg.pixelFormat)) {
> > > +                     /* If there was no value here, that doesn't count as "adjusted". */
> > > +                     if (cfg.colorSpace && cfg.colorSpace != ColorSpace::Raw) {
> > > +                             status = Adjusted;
> > > +                             cfg.colorSpace = ColorSpace::Raw;
> > > +                     }
> > > +                     continue;
> > > +             }
> > > +
> > > +             /* Next we need to find our shared colour space. The first valid one will do. */
> > > +             if (cfg.colorSpace && !yuvColorSpace_)
> > > +                     yuvColorSpace_ = findValidColorSpace(cfg.colorSpace.value());
> > > +     }
> > > +
> > > +     /* If no colour space was given anywhere, choose sYCC. */
> > > +     if (!yuvColorSpace_)
> > > +             yuvColorSpace_ = ColorSpace::Sycc;
> > > +
> > > +     /* Note the version of this that any RGB streams will have to use. */
> > > +     rgbColorSpace_ = yuvColorSpace_;
> > > +     rgbColorSpace_->ycbcrEncoding = ColorSpace::YcbcrEncoding::None;
> > > +     rgbColorSpace_->range = ColorSpace::Range::Full;
> > > +
> > > +     /* Go through the streams again and force everyone to the same colour space. */
> > > +     for (auto cfg : config_) {
> > > +             if (cfg.colorSpace == ColorSpace::Raw)
> > > +                     continue;
> > > +
> > > +             if (isYuv(cfg.pixelFormat) && cfg.colorSpace != yuvColorSpace_) {
> > > +                     /* Again, no value means "not adjusted". */
> > > +                     if (cfg.colorSpace)
> > > +                             status = Adjusted;
> > > +                     cfg.colorSpace = yuvColorSpace_;
> > > +             }
> > > +             if (isRgb(cfg.pixelFormat) && cfg.colorSpace != rgbColorSpace_) {
> > > +                     /* Be nice, and let the YUV version count as non-adjusted too. */
> > > +                     if (cfg.colorSpace && cfg.colorSpace != yuvColorSpace_)
> > > +                             status = Adjusted;
> > > +                     cfg.colorSpace = rgbColorSpace_;
> > > +             }
> > > +     }
> > > +
> > > +     return status;
> > > +}
> > > +
> > >   CameraConfiguration::Status RPiCameraConfiguration::validate()
> > >   {
> > >       Status status = Valid;
> > > @@ -533,7 +640,8 @@ CameraConfiguration::Status RPiCameraConfiguration::validate()
> > >               V4L2DeviceFormat format;
> > >               format.fourcc = dev->toV4L2PixelFormat(cfg.pixelFormat);
> > >               format.size = cfg.size;
> > > -             format.colorSpace = cfg.colorSpace;
> > > +             /* We want to send the associated YCbCr info through to the driver. */
> > > +             format.colorSpace = yuvColorSpace_;
> > >
> > >               LOG(RPI, Debug)
> > >                       << "Try color space " << ColorSpace::toString(cfg.colorSpace);
> > > @@ -542,6 +650,11 @@ CameraConfiguration::Status RPiCameraConfiguration::validate()
> > >               if (ret)
> > >                       return Invalid;
> > >
> > > +             /*
> > > +              * But for RGB streams, the YCbCr info gets overwritten on the way back
> > > +              * so we must check against what the stream cfg says, not what we actually
> > > +              * requested (which carefully included the YCbCr info)!
> > > +              */
> > >               if (cfg.colorSpace != format.colorSpace) {
> > >                       status = Adjusted;
> > >                       LOG(RPI, Debug)
> >
Laurent Pinchart Feb. 6, 2023, 5:56 p.m. UTC | #5
Hi David,

Thank you for the patch.

On Fri, Jan 20, 2023 at 04:22:58PM +0000, David Plowman via libcamera-devel wrote:
> We implement a custom validateColorSpaces method that forces all
> (non-raw) streams to same colour space, whilst distinguishing RGB
> streams from YUV ones, as the former must have the YCbCr encoding and
> range over-written.
> 
> When we apply the colour space, we always send the full YUV version as
> that gets converted correctly to what our hardware drivers expect. It
> is also careful to check what comes back as the YCbCr information gets
> overwritten again on the way back.
> 
> Signed-off-by: David Plowman <david.plowman@raspberrypi.com>
> Reviewed-by: Naushir Patuck <naush@raspberrypi.com>
> ---
>  .../pipeline/raspberrypi/raspberrypi.cpp      | 115 +++++++++++++++++-
>  1 file changed, 114 insertions(+), 1 deletion(-)
> 
> diff --git a/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp b/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp
> index 8569df17..f768dced 100644
> --- a/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp
> +++ b/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp
> @@ -310,6 +310,7 @@ class RPiCameraConfiguration : public CameraConfiguration
>  public:
>  	RPiCameraConfiguration(const RPiCameraData *data);
>  
> +	CameraConfiguration::Status validateColorSpaces(ColorSpaceFlags flags);
>  	Status validate() override;
>  
>  	/* Cache the combinedTransform_ that will be applied to the sensor */
> @@ -317,6 +318,13 @@ public:
>  
>  private:
>  	const RPiCameraData *data_;
> +
> +	/*
> +	 * Store the colour spaces that all our streams will have. RGB format streams
> +	 * will be the same but need the YCbCr fields cleared.
> +	 */
> +	std::optional<ColorSpace> yuvColorSpace_;
> +	std::optional<ColorSpace> rgbColorSpace_;
>  };
>  
>  class PipelineHandlerRPi : public PipelineHandler
> @@ -357,6 +365,105 @@ RPiCameraConfiguration::RPiCameraConfiguration(const RPiCameraData *data)
>  {
>  }
>  
> +static const std::vector<ColorSpace> validColorSpaces = {
> +	ColorSpace::Sycc,
> +	ColorSpace::Smpte170m,
> +	ColorSpace::Rec709
> +};
> +
> +static std::optional<ColorSpace> findValidColorSpace(const ColorSpace &colourSpace)
> +{
> +	for (auto cs : validColorSpaces) {
> +		if (colourSpace.primaries == cs.primaries &&
> +		    colourSpace.transferFunction == cs.transferFunction)
> +			return cs;
> +	}
> +
> +	return std::nullopt;
> +}
> +
> +static bool isRgb(const PixelFormat &pixFmt)
> +{
> +	const PixelFormatInfo &info = PixelFormatInfo::info(pixFmt);
> +	return info.colourEncoding == PixelFormatInfo::ColourEncodingRGB;
> +}
> +
> +static bool isYuv(const PixelFormat &pixFmt)
> +{
> +	/* The code below would return true for raw mono streams, so weed those out first. */
> +	if (isRaw(pixFmt))
> +		return false;
> +
> +	const PixelFormatInfo &info = PixelFormatInfo::info(pixFmt);
> +	return info.colourEncoding == PixelFormatInfo::ColourEncodingYUV;
> +}
> +
> +/*
> + * Raspberry Pi drivers expect the following colour spaces:
> + * - V4L2_COLORSPACE_RAW for raw streams.
> + * - One of V4L2_COLORSPACE_JPEG, V4L2_COLORSPACE_SMPTE170M, V4L2_COLORSPACE_REC709 for
> + *   non-raw streams. Other fields such as transfer function, YCbCr encoding and
> + *   quantisation are not used.
> + *
> + * The libcamera colour spaces that we wish to use corresponding to these are therefore:
> + * - ColorSpace::Raw for V4L2_COLORSPACE_RAW
> + * - ColorSpace::Sycc for V4L2_COLORSPACE_JPEG
> + * - ColorSpace::Smpte170m for V4L2_COLORSPACE_SMPTE170M
> + * - ColorSpace::Rec709 for V4L2_COLORSPACE_REC709
> + */
> +
> +CameraConfiguration::Status RPiCameraConfiguration::validateColorSpaces([[maybe_unused]] ColorSpaceFlags flags)
> +{
> +	Status status = Valid;
> +	yuvColorSpace_.reset();
> +
> +	for (auto cfg : config_) {
> +		/* First fix up raw streams to have the "raw" colour space. */
> +		if (isRaw(cfg.pixelFormat)) {
> +			/* If there was no value here, that doesn't count as "adjusted". */
> +			if (cfg.colorSpace && cfg.colorSpace != ColorSpace::Raw) {
> +				status = Adjusted;
> +				cfg.colorSpace = ColorSpace::Raw;

Agreed for the Adjusted flag, but shouldn't you set cfg.colorSpace to
raw even if !cfg.colorSpace ?

> +			}
> +			continue;
> +		}
> +
> +		/* Next we need to find our shared colour space. The first valid one will do. */
> +		if (cfg.colorSpace && !yuvColorSpace_)
> +			yuvColorSpace_ = findValidColorSpace(cfg.colorSpace.value());
> +	}
> +
> +	/* If no colour space was given anywhere, choose sYCC. */
> +	if (!yuvColorSpace_)
> +		yuvColorSpace_ = ColorSpace::Sycc;
> +
> +	/* Note the version of this that any RGB streams will have to use. */
> +	rgbColorSpace_ = yuvColorSpace_;
> +	rgbColorSpace_->ycbcrEncoding = ColorSpace::YcbcrEncoding::None;
> +	rgbColorSpace_->range = ColorSpace::Range::Full;
> +
> +	/* Go through the streams again and force everyone to the same colour space. */
> +	for (auto cfg : config_) {
> +		if (cfg.colorSpace == ColorSpace::Raw)
> +			continue;
> +
> +		if (isYuv(cfg.pixelFormat) && cfg.colorSpace != yuvColorSpace_) {
> +			/* Again, no value means "not adjusted". */
> +			if (cfg.colorSpace)
> +				status = Adjusted;
> +			cfg.colorSpace = yuvColorSpace_;
> +		}
> +		if (isRgb(cfg.pixelFormat) && cfg.colorSpace != rgbColorSpace_) {
> +			/* Be nice, and let the YUV version count as non-adjusted too. */
> +			if (cfg.colorSpace && cfg.colorSpace != yuvColorSpace_)
> +				status = Adjusted;
> +			cfg.colorSpace = rgbColorSpace_;
> +		}
> +	}
> +
> +	return status;
> +}
> +
>  CameraConfiguration::Status RPiCameraConfiguration::validate()
>  {
>  	Status status = Valid;
> @@ -533,7 +640,8 @@ CameraConfiguration::Status RPiCameraConfiguration::validate()
>  		V4L2DeviceFormat format;
>  		format.fourcc = dev->toV4L2PixelFormat(cfg.pixelFormat);
>  		format.size = cfg.size;
> -		format.colorSpace = cfg.colorSpace;
> +		/* We want to send the associated YCbCr info through to the driver. */
> +		format.colorSpace = yuvColorSpace_;
>  
>  		LOG(RPI, Debug)
>  			<< "Try color space " << ColorSpace::toString(cfg.colorSpace);
> @@ -542,6 +650,11 @@ CameraConfiguration::Status RPiCameraConfiguration::validate()
>  		if (ret)
>  			return Invalid;
>  
> +		/*
> +		 * But for RGB streams, the YCbCr info gets overwritten on the way back
> +		 * so we must check against what the stream cfg says, not what we actually
> +		 * requested (which carefully included the YCbCr info)!
> +		 */
>  		if (cfg.colorSpace != format.colorSpace) {
>  			status = Adjusted;
>  			LOG(RPI, Debug)
David Plowman Feb. 13, 2023, 9:10 a.m. UTC | #6
Hi Laurent

Whoops, bit of a slip in the little "optimisation" in the v2 version.
Thanks for spotting it, I'll send a small patch.

David

On Mon, 6 Feb 2023 at 17:56, Laurent Pinchart <
laurent.pinchart@ideasonboard.com> wrote:

> Hi David,
>
> Thank you for the patch.
>
> On Fri, Jan 20, 2023 at 04:22:58PM +0000, David Plowman via
> libcamera-devel wrote:
> > We implement a custom validateColorSpaces method that forces all
> > (non-raw) streams to same colour space, whilst distinguishing RGB
> > streams from YUV ones, as the former must have the YCbCr encoding and
> > range over-written.
> >
> > When we apply the colour space, we always send the full YUV version as
> > that gets converted correctly to what our hardware drivers expect. It
> > is also careful to check what comes back as the YCbCr information gets
> > overwritten again on the way back.
> >
> > Signed-off-by: David Plowman <david.plowman@raspberrypi.com>
> > Reviewed-by: Naushir Patuck <naush@raspberrypi.com>
> > ---
> >  .../pipeline/raspberrypi/raspberrypi.cpp      | 115 +++++++++++++++++-
> >  1 file changed, 114 insertions(+), 1 deletion(-)
> >
> > diff --git a/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp
> b/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp
> > index 8569df17..f768dced 100644
> > --- a/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp
> > +++ b/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp
> > @@ -310,6 +310,7 @@ class RPiCameraConfiguration : public
> CameraConfiguration
> >  public:
> >       RPiCameraConfiguration(const RPiCameraData *data);
> >
> > +     CameraConfiguration::Status validateColorSpaces(ColorSpaceFlags
> flags);
> >       Status validate() override;
> >
> >       /* Cache the combinedTransform_ that will be applied to the sensor
> */
> > @@ -317,6 +318,13 @@ public:
> >
> >  private:
> >       const RPiCameraData *data_;
> > +
> > +     /*
> > +      * Store the colour spaces that all our streams will have. RGB
> format streams
> > +      * will be the same but need the YCbCr fields cleared.
> > +      */
> > +     std::optional<ColorSpace> yuvColorSpace_;
> > +     std::optional<ColorSpace> rgbColorSpace_;
> >  };
> >
> >  class PipelineHandlerRPi : public PipelineHandler
> > @@ -357,6 +365,105 @@
> RPiCameraConfiguration::RPiCameraConfiguration(const RPiCameraData *data)
> >  {
> >  }
> >
> > +static const std::vector<ColorSpace> validColorSpaces = {
> > +     ColorSpace::Sycc,
> > +     ColorSpace::Smpte170m,
> > +     ColorSpace::Rec709
> > +};
> > +
> > +static std::optional<ColorSpace> findValidColorSpace(const ColorSpace
> &colourSpace)
> > +{
> > +     for (auto cs : validColorSpaces) {
> > +             if (colourSpace.primaries == cs.primaries &&
> > +                 colourSpace.transferFunction == cs.transferFunction)
> > +                     return cs;
> > +     }
> > +
> > +     return std::nullopt;
> > +}
> > +
> > +static bool isRgb(const PixelFormat &pixFmt)
> > +{
> > +     const PixelFormatInfo &info = PixelFormatInfo::info(pixFmt);
> > +     return info.colourEncoding == PixelFormatInfo::ColourEncodingRGB;
> > +}
> > +
> > +static bool isYuv(const PixelFormat &pixFmt)
> > +{
> > +     /* The code below would return true for raw mono streams, so weed
> those out first. */
> > +     if (isRaw(pixFmt))
> > +             return false;
> > +
> > +     const PixelFormatInfo &info = PixelFormatInfo::info(pixFmt);
> > +     return info.colourEncoding == PixelFormatInfo::ColourEncodingYUV;
> > +}
> > +
> > +/*
> > + * Raspberry Pi drivers expect the following colour spaces:
> > + * - V4L2_COLORSPACE_RAW for raw streams.
> > + * - One of V4L2_COLORSPACE_JPEG, V4L2_COLORSPACE_SMPTE170M,
> V4L2_COLORSPACE_REC709 for
> > + *   non-raw streams. Other fields such as transfer function, YCbCr
> encoding and
> > + *   quantisation are not used.
> > + *
> > + * The libcamera colour spaces that we wish to use corresponding to
> these are therefore:
> > + * - ColorSpace::Raw for V4L2_COLORSPACE_RAW
> > + * - ColorSpace::Sycc for V4L2_COLORSPACE_JPEG
> > + * - ColorSpace::Smpte170m for V4L2_COLORSPACE_SMPTE170M
> > + * - ColorSpace::Rec709 for V4L2_COLORSPACE_REC709
> > + */
> > +
> > +CameraConfiguration::Status
> RPiCameraConfiguration::validateColorSpaces([[maybe_unused]]
> ColorSpaceFlags flags)
> > +{
> > +     Status status = Valid;
> > +     yuvColorSpace_.reset();
> > +
> > +     for (auto cfg : config_) {
> > +             /* First fix up raw streams to have the "raw" colour
> space. */
> > +             if (isRaw(cfg.pixelFormat)) {
> > +                     /* If there was no value here, that doesn't count
> as "adjusted". */
> > +                     if (cfg.colorSpace && cfg.colorSpace !=
> ColorSpace::Raw) {
> > +                             status = Adjusted;
> > +                             cfg.colorSpace = ColorSpace::Raw;
>
> Agreed for the Adjusted flag, but shouldn't you set cfg.colorSpace to
> raw even if !cfg.colorSpace ?
>
> > +                     }
> > +                     continue;
> > +             }
> > +
> > +             /* Next we need to find our shared colour space. The first
> valid one will do. */
> > +             if (cfg.colorSpace && !yuvColorSpace_)
> > +                     yuvColorSpace_ =
> findValidColorSpace(cfg.colorSpace.value());
> > +     }
> > +
> > +     /* If no colour space was given anywhere, choose sYCC. */
> > +     if (!yuvColorSpace_)
> > +             yuvColorSpace_ = ColorSpace::Sycc;
> > +
> > +     /* Note the version of this that any RGB streams will have to use.
> */
> > +     rgbColorSpace_ = yuvColorSpace_;
> > +     rgbColorSpace_->ycbcrEncoding = ColorSpace::YcbcrEncoding::None;
> > +     rgbColorSpace_->range = ColorSpace::Range::Full;
> > +
> > +     /* Go through the streams again and force everyone to the same
> colour space. */
> > +     for (auto cfg : config_) {
> > +             if (cfg.colorSpace == ColorSpace::Raw)
> > +                     continue;
> > +
> > +             if (isYuv(cfg.pixelFormat) && cfg.colorSpace !=
> yuvColorSpace_) {
> > +                     /* Again, no value means "not adjusted". */
> > +                     if (cfg.colorSpace)
> > +                             status = Adjusted;
> > +                     cfg.colorSpace = yuvColorSpace_;
> > +             }
> > +             if (isRgb(cfg.pixelFormat) && cfg.colorSpace !=
> rgbColorSpace_) {
> > +                     /* Be nice, and let the YUV version count as
> non-adjusted too. */
> > +                     if (cfg.colorSpace && cfg.colorSpace !=
> yuvColorSpace_)
> > +                             status = Adjusted;
> > +                     cfg.colorSpace = rgbColorSpace_;
> > +             }
> > +     }
> > +
> > +     return status;
> > +}
> > +
> >  CameraConfiguration::Status RPiCameraConfiguration::validate()
> >  {
> >       Status status = Valid;
> > @@ -533,7 +640,8 @@ CameraConfiguration::Status
> RPiCameraConfiguration::validate()
> >               V4L2DeviceFormat format;
> >               format.fourcc = dev->toV4L2PixelFormat(cfg.pixelFormat);
> >               format.size = cfg.size;
> > -             format.colorSpace = cfg.colorSpace;
> > +             /* We want to send the associated YCbCr info through to
> the driver. */
> > +             format.colorSpace = yuvColorSpace_;
> >
> >               LOG(RPI, Debug)
> >                       << "Try color space " <<
> ColorSpace::toString(cfg.colorSpace);
> > @@ -542,6 +650,11 @@ CameraConfiguration::Status
> RPiCameraConfiguration::validate()
> >               if (ret)
> >                       return Invalid;
> >
> > +             /*
> > +              * But for RGB streams, the YCbCr info gets overwritten on
> the way back
> > +              * so we must check against what the stream cfg says, not
> what we actually
> > +              * requested (which carefully included the YCbCr info)!
> > +              */
> >               if (cfg.colorSpace != format.colorSpace) {
> >                       status = Adjusted;
> >                       LOG(RPI, Debug)
>
> --
> Regards,
>
> Laurent Pinchart
>

Patch
diff mbox series

diff --git a/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp b/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp
index 8569df17..f768dced 100644
--- a/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp
+++ b/src/libcamera/pipeline/raspberrypi/raspberrypi.cpp
@@ -310,6 +310,7 @@  class RPiCameraConfiguration : public CameraConfiguration
 public:
 	RPiCameraConfiguration(const RPiCameraData *data);
 
+	CameraConfiguration::Status validateColorSpaces(ColorSpaceFlags flags);
 	Status validate() override;
 
 	/* Cache the combinedTransform_ that will be applied to the sensor */
@@ -317,6 +318,13 @@  public:
 
 private:
 	const RPiCameraData *data_;
+
+	/*
+	 * Store the colour spaces that all our streams will have. RGB format streams
+	 * will be the same but need the YCbCr fields cleared.
+	 */
+	std::optional<ColorSpace> yuvColorSpace_;
+	std::optional<ColorSpace> rgbColorSpace_;
 };
 
 class PipelineHandlerRPi : public PipelineHandler
@@ -357,6 +365,105 @@  RPiCameraConfiguration::RPiCameraConfiguration(const RPiCameraData *data)
 {
 }
 
+static const std::vector<ColorSpace> validColorSpaces = {
+	ColorSpace::Sycc,
+	ColorSpace::Smpte170m,
+	ColorSpace::Rec709
+};
+
+static std::optional<ColorSpace> findValidColorSpace(const ColorSpace &colourSpace)
+{
+	for (auto cs : validColorSpaces) {
+		if (colourSpace.primaries == cs.primaries &&
+		    colourSpace.transferFunction == cs.transferFunction)
+			return cs;
+	}
+
+	return std::nullopt;
+}
+
+static bool isRgb(const PixelFormat &pixFmt)
+{
+	const PixelFormatInfo &info = PixelFormatInfo::info(pixFmt);
+	return info.colourEncoding == PixelFormatInfo::ColourEncodingRGB;
+}
+
+static bool isYuv(const PixelFormat &pixFmt)
+{
+	/* The code below would return true for raw mono streams, so weed those out first. */
+	if (isRaw(pixFmt))
+		return false;
+
+	const PixelFormatInfo &info = PixelFormatInfo::info(pixFmt);
+	return info.colourEncoding == PixelFormatInfo::ColourEncodingYUV;
+}
+
+/*
+ * Raspberry Pi drivers expect the following colour spaces:
+ * - V4L2_COLORSPACE_RAW for raw streams.
+ * - One of V4L2_COLORSPACE_JPEG, V4L2_COLORSPACE_SMPTE170M, V4L2_COLORSPACE_REC709 for
+ *   non-raw streams. Other fields such as transfer function, YCbCr encoding and
+ *   quantisation are not used.
+ *
+ * The libcamera colour spaces that we wish to use corresponding to these are therefore:
+ * - ColorSpace::Raw for V4L2_COLORSPACE_RAW
+ * - ColorSpace::Sycc for V4L2_COLORSPACE_JPEG
+ * - ColorSpace::Smpte170m for V4L2_COLORSPACE_SMPTE170M
+ * - ColorSpace::Rec709 for V4L2_COLORSPACE_REC709
+ */
+
+CameraConfiguration::Status RPiCameraConfiguration::validateColorSpaces([[maybe_unused]] ColorSpaceFlags flags)
+{
+	Status status = Valid;
+	yuvColorSpace_.reset();
+
+	for (auto cfg : config_) {
+		/* First fix up raw streams to have the "raw" colour space. */
+		if (isRaw(cfg.pixelFormat)) {
+			/* If there was no value here, that doesn't count as "adjusted". */
+			if (cfg.colorSpace && cfg.colorSpace != ColorSpace::Raw) {
+				status = Adjusted;
+				cfg.colorSpace = ColorSpace::Raw;
+			}
+			continue;
+		}
+
+		/* Next we need to find our shared colour space. The first valid one will do. */
+		if (cfg.colorSpace && !yuvColorSpace_)
+			yuvColorSpace_ = findValidColorSpace(cfg.colorSpace.value());
+	}
+
+	/* If no colour space was given anywhere, choose sYCC. */
+	if (!yuvColorSpace_)
+		yuvColorSpace_ = ColorSpace::Sycc;
+
+	/* Note the version of this that any RGB streams will have to use. */
+	rgbColorSpace_ = yuvColorSpace_;
+	rgbColorSpace_->ycbcrEncoding = ColorSpace::YcbcrEncoding::None;
+	rgbColorSpace_->range = ColorSpace::Range::Full;
+
+	/* Go through the streams again and force everyone to the same colour space. */
+	for (auto cfg : config_) {
+		if (cfg.colorSpace == ColorSpace::Raw)
+			continue;
+
+		if (isYuv(cfg.pixelFormat) && cfg.colorSpace != yuvColorSpace_) {
+			/* Again, no value means "not adjusted". */
+			if (cfg.colorSpace)
+				status = Adjusted;
+			cfg.colorSpace = yuvColorSpace_;
+		}
+		if (isRgb(cfg.pixelFormat) && cfg.colorSpace != rgbColorSpace_) {
+			/* Be nice, and let the YUV version count as non-adjusted too. */
+			if (cfg.colorSpace && cfg.colorSpace != yuvColorSpace_)
+				status = Adjusted;
+			cfg.colorSpace = rgbColorSpace_;
+		}
+	}
+
+	return status;
+}
+
 CameraConfiguration::Status RPiCameraConfiguration::validate()
 {
 	Status status = Valid;
@@ -533,7 +640,8 @@  CameraConfiguration::Status RPiCameraConfiguration::validate()
 		V4L2DeviceFormat format;
 		format.fourcc = dev->toV4L2PixelFormat(cfg.pixelFormat);
 		format.size = cfg.size;
-		format.colorSpace = cfg.colorSpace;
+		/* We want to send the associated YCbCr info through to the driver. */
+		format.colorSpace = yuvColorSpace_;
 
 		LOG(RPI, Debug)
 			<< "Try color space " << ColorSpace::toString(cfg.colorSpace);
@@ -542,6 +650,11 @@  CameraConfiguration::Status RPiCameraConfiguration::validate()
 		if (ret)
 			return Invalid;
 
+		/*
+		 * But for RGB streams, the YCbCr info gets overwritten on the way back
+		 * so we must check against what the stream cfg says, not what we actually
+		 * requested (which carefully included the YCbCr info)!
+		 */
 		if (cfg.colorSpace != format.colorSpace) {
 			status = Adjusted;
 			LOG(RPI, Debug)