[libcamera-devel,3/3] gstreamer: Provide colorimetry <> libcamera::ColorSpace mappings
diff mbox series

Message ID 20220724144355.104978-4-umang.jain@ideasonboard.com
State Superseded
Headers show
Series
  • gstreamer: Plumb initial colorimetry support
Related show

Commit Message

Umang Jain July 24, 2022, 2:43 p.m. UTC
From: Rishikesh Donadkar <rishikeshdonadkar@gmail.com>

Provide colorimetry <=> libcamera::ColorSpace mappings via:
- GstVideoColorimetry colorimetry_from_colorspace(colorspace);
- ColorSpace colorspace_from_colorimetry(colorimetry);

Read the colorimetry field from caps into the stream configuration.
After stream validation, the sensor supported colorimetry will
be retrieved and the caps will be updated accordingly.

Colorimetry support for gstlibcamerasrc currently undertakes only one
argument. Multiple colorimetry support shall be introduced in
subsequent commits.

Signed-off-by: Rishikesh Donadkar <rishikeshdonadkar@gmail.com>
Signed-off-by: Umang Jain <umang.jain@ideasonboard.com>
---
 src/gstreamer/gstlibcamera-utils.cpp | 175 +++++++++++++++++++++++++++
 1 file changed, 175 insertions(+)

Comments

Rishikesh Donadkar July 24, 2022, 4:33 p.m. UTC | #1
> default:
> +               GST_WARNING("Colorspace YcbcrEncoding not mapped in GstLibcameraSrc");
> +               colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_UNKNOWN;
> +       }

Back in v1 of colorspace and colorimetry integration nicolas pointed
out that we should not use the
_UNKNOWN to set the fields.
https://lists.libcamera.org/pipermail/libcamera-devel/2022-July/031941.html
I am not sure whether we should set the field to _UNKNOWN in the
default switch cases.


On Sun, Jul 24, 2022 at 8:14 PM Umang Jain <umang.jain@ideasonboard.com> wrote:
>
> From: Rishikesh Donadkar <rishikeshdonadkar@gmail.com>
>
> Provide colorimetry <=> libcamera::ColorSpace mappings via:
> - GstVideoColorimetry colorimetry_from_colorspace(colorspace);
> - ColorSpace colorspace_from_colorimetry(colorimetry);
>
> Read the colorimetry field from caps into the stream configuration.
> After stream validation, the sensor supported colorimetry will
> be retrieved and the caps will be updated accordingly.
>
> Colorimetry support for gstlibcamerasrc currently undertakes only one
> argument. Multiple colorimetry support shall be introduced in
> subsequent commits.
>
> Signed-off-by: Rishikesh Donadkar <rishikeshdonadkar@gmail.com>
> Signed-off-by: Umang Jain <umang.jain@ideasonboard.com>
> ---
>  src/gstreamer/gstlibcamera-utils.cpp | 175 +++++++++++++++++++++++++++
>  1 file changed, 175 insertions(+)
>
> diff --git a/src/gstreamer/gstlibcamera-utils.cpp b/src/gstreamer/gstlibcamera-utils.cpp
> index c97c0d43..fb4f0e5c 100644
> --- a/src/gstreamer/gstlibcamera-utils.cpp
> +++ b/src/gstreamer/gstlibcamera-utils.cpp
> @@ -45,6 +45,157 @@ static struct {
>         /* \todo NV42 is used in libcamera but is not mapped in GStreamer yet. */
>  };
>
> +static GstVideoColorimetry
> +colorimetry_from_colorspace(const ColorSpace &colorSpace)
> +{
> +       GstVideoColorimetry colorimetry;
> +
> +       switch (colorSpace.primaries) {
> +       case ColorSpace::Primaries::Rec709:
> +               colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_BT709;
> +               break;
> +       case ColorSpace::Primaries::Rec2020:
> +               colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_BT2020;
> +               break;
> +       case ColorSpace::Primaries::Smpte170m:
> +               colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_SMPTE170M;
> +               break;
> +       default:
> +               GST_WARNING("ColorSpace primaries not mapped in GstLibcameraSrc");
> +               colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_UNKNOWN;
> +       }
> +
> +       switch (colorSpace.transferFunction) {
> +       case ColorSpace::TransferFunction::Rec709:
> +               colorimetry.transfer = GST_VIDEO_TRANSFER_BT709;
> +               break;
> +       case ColorSpace::TransferFunction::Srgb:
> +               colorimetry.transfer = GST_VIDEO_TRANSFER_SRGB;
> +               break;
> +       case ColorSpace::TransferFunction::Linear:
> +               colorimetry.transfer = GST_VIDEO_TRANSFER_GAMMA10;
> +               break;
> +       default:
> +               GST_WARNING("ColorSpace transfer function not mapped in GstLibcameraSrc");
> +               colorimetry.transfer = GST_VIDEO_TRANSFER_UNKNOWN;
> +       }
> +
> +       switch (colorSpace.ycbcrEncoding) {
> +       case ColorSpace::YcbcrEncoding::Rec709:
> +               colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_BT709;
> +               break;
> +       case ColorSpace::YcbcrEncoding::Rec2020:
> +               colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_BT2020;
> +               break;
> +       case ColorSpace::YcbcrEncoding::Rec601:
> +               colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_BT601;
> +               break;
> +       default:
> +               GST_WARNING("Colorspace YcbcrEncoding not mapped in GstLibcameraSrc");
> +               colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_UNKNOWN;
> +       }
> +
> +       switch (colorSpace.range) {
> +       case ColorSpace::Range::Full:
> +               colorimetry.range = GST_VIDEO_COLOR_RANGE_0_255;
> +               break;
> +       case ColorSpace::Range::Limited:
> +               colorimetry.range = GST_VIDEO_COLOR_RANGE_16_235;
> +               break;
> +       default:
> +               GST_WARNING("Colorspace range not mapped in GstLibcameraSrc");
> +               colorimetry.range = GST_VIDEO_COLOR_RANGE_UNKNOWN;
> +       }
> +
> +       return colorimetry;
> +}
> +
> +static std::optional<ColorSpace>
> +colorspace_from_colorimetry(GstVideoColorimetry *colorimetry)
> +{
> +       std::optional<ColorSpace> colorspace = ColorSpace::Default;
> +
> +       switch (colorimetry->primaries) {
> +       case GST_VIDEO_COLOR_PRIMARIES_BT709:
> +               colorspace->primaries = ColorSpace::Primaries::Rec709;
> +               break;
> +       case GST_VIDEO_COLOR_PRIMARIES_BT2020:
> +               colorspace->primaries = ColorSpace::Primaries::Rec2020;
> +               break;
> +       case GST_VIDEO_COLOR_PRIMARIES_SMPTE170M:
> +               colorspace->primaries = ColorSpace::Primaries::Smpte170m;
> +               break;
> +       case GST_VIDEO_COLOR_PRIMARIES_UNKNOWN:
> +               colorspace->primaries = ColorSpace::Primaries::Default;
> +               break;
> +       default:
> +               GST_WARNING("Unknown primaries in colorimetry %d", colorimetry->primaries);
> +       }
> +
> +       switch (colorimetry->transfer) {
> +       /* Tranfer function mappings inspired from v4l2src plugin */
> +       case GST_VIDEO_TRANSFER_GAMMA18:
> +       case GST_VIDEO_TRANSFER_GAMMA20:
> +       case GST_VIDEO_TRANSFER_GAMMA22:
> +       case GST_VIDEO_TRANSFER_GAMMA28:
> +               GST_WARNING("GAMMA 18, 20, 22, 28 transfer functions not supported");
> +       /* fallthrough */
> +       case GST_VIDEO_TRANSFER_GAMMA10:
> +               colorspace->transferFunction = ColorSpace::TransferFunction::Linear;
> +               break;
> +       case GST_VIDEO_TRANSFER_BT601:
> +       case GST_VIDEO_TRANSFER_BT2020_12:
> +       case GST_VIDEO_TRANSFER_BT2020_10:
> +       case GST_VIDEO_TRANSFER_BT709:
> +               colorspace->transferFunction = ColorSpace::TransferFunction::Rec709;
> +               break;
> +       case GST_VIDEO_TRANSFER_SRGB:
> +               colorspace->transferFunction = ColorSpace::TransferFunction::Srgb;
> +               break;
> +       case GST_VIDEO_TRANSFER_UNKNOWN:
> +               colorspace->transferFunction = ColorSpace::TransferFunction::Default;
> +               break;
> +       default:
> +               GST_WARNING("Unknown colorimetry transfer %d", colorimetry->transfer);
> +       }
> +
> +       switch (colorimetry->matrix) {
> +       /* FCC is about the same as BT601 with less digit */
> +       case GST_VIDEO_COLOR_MATRIX_FCC:
> +       case GST_VIDEO_COLOR_MATRIX_BT601:
> +               colorspace->ycbcrEncoding = ColorSpace::YcbcrEncoding::Rec601;
> +               break;
> +       case GST_VIDEO_COLOR_MATRIX_BT709:
> +               colorspace->ycbcrEncoding = ColorSpace::YcbcrEncoding::Rec709;
> +               break;
> +       case GST_VIDEO_COLOR_MATRIX_BT2020:
> +               colorspace->ycbcrEncoding = ColorSpace::YcbcrEncoding::Rec2020;
> +               break;
> +       case GST_VIDEO_COLOR_MATRIX_RGB:
> +       case GST_VIDEO_COLOR_MATRIX_UNKNOWN:
> +               colorspace->ycbcrEncoding = ColorSpace::YcbcrEncoding::Default;
> +               break;
> +       default:
> +               GST_WARNING("Unknown colorimetry matrix %d", colorimetry->matrix);
> +       }
> +
> +       switch (colorimetry->range) {
> +       case GST_VIDEO_COLOR_RANGE_0_255:
> +               colorspace->range = ColorSpace::Range::Full;
> +               break;
> +       case GST_VIDEO_COLOR_RANGE_16_235:
> +               colorspace->range = ColorSpace::Range::Limited;
> +               break;
> +       case GST_VIDEO_COLOR_RANGE_UNKNOWN:
> +               colorspace->range = ColorSpace::Range::Default;
> +               break;
> +       default:
> +               GST_WARNING("Unknown range in colorimetry %d", colorimetry->range);
> +       }
> +
> +       return colorspace;
> +}
> +
>  static GstVideoFormat
>  pixel_format_to_gst_format(const PixelFormat &format)
>  {
> @@ -139,6 +290,17 @@ gst_libcamera_stream_configuration_to_caps(const StreamConfiguration &stream_cfg
>                           "width", G_TYPE_INT, stream_cfg.size.width,
>                           "height", G_TYPE_INT, stream_cfg.size.height,
>                           nullptr);
> +
> +       if (stream_cfg.colorSpace) {
> +               GstVideoColorimetry colorimetry = colorimetry_from_colorspace(stream_cfg.colorSpace.value());
> +               gchar *colorimetry_str = gst_video_colorimetry_to_string(&colorimetry);
> +
> +               if (colorimetry_str != nullptr)
> +                       gst_structure_set(s, "colorimetry", G_TYPE_STRING, colorimetry_str, nullptr);
> +               else
> +                       g_warning("libcamera::ColorSpace found but GstVideoColorimetry unknown");
> +       }
> +
>         gst_caps_append_structure(caps, s);
>
>         return caps;
> @@ -222,6 +384,19 @@ gst_libcamera_configure_stream_from_caps(StreamConfiguration &stream_cfg,
>         gst_structure_get_int(s, "height", &height);
>         stream_cfg.size.width = width;
>         stream_cfg.size.height = height;
> +
> +       /* Configure colorimetry */
> +       if (gst_structure_has_field(s, "colorimetry")) {
> +               const gchar *colorimetry_caps = gst_structure_get_string(s, "colorimetry");
> +               GstVideoColorimetry colorimetry;
> +
> +               if(gst_video_colorimetry_from_string(&colorimetry, colorimetry_caps)) {
> +                       std::optional<ColorSpace> colorSpace = colorspace_from_colorimetry(&colorimetry);
> +                       stream_cfg.colorSpace = colorSpace;
> +               } else {
> +                       g_print("Invalid colorimetry %s", colorimetry_caps);
> +               }
> +       }
>  }
>
>  #if !GST_CHECK_VERSION(1, 17, 1)
> --
> 2.31.1
>
Umang Jain July 24, 2022, 6:18 p.m. UTC | #2
Hi Rishi,

On 7/24/22 22:03, Rishikesh Donadkar wrote:
>> default:
>> +               GST_WARNING("Colorspace YcbcrEncoding not mapped in GstLibcameraSrc");
>> +               colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_UNKNOWN;
>> +       }
> Back in v1 of colorspace and colorimetry integration nicolas pointed
> out that we should not use the
> _UNKNOWN to set the fields.
> https://lists.libcamera.org/pipermail/libcamera-devel/2022-July/031941.html
> I am not sure whether we should set the field to _UNKNOWN in the
> default switch cases.


That's what is in happening here. It's a parsing error hence we use 
_UNKNOWN along with a warning.

This means a identifier exists in the libcamera(probably a new 
identifier is added to libcamera) but not mapped in gstlibcamerasrc, 
hence, it shall pop the warning and the mapping for the new identifier 
needs to be added in gstlibcamerasrc-utils :-)

As discussed on the IRC, we are working on case-by-case basis here, so 
as we get reports of missing identifiers, we shall extend the mappings. 
Hence the entire mapping/warnings has been set up that way.

>
>
> On Sun, Jul 24, 2022 at 8:14 PM Umang Jain <umang.jain@ideasonboard.com> wrote:
>> From: Rishikesh Donadkar <rishikeshdonadkar@gmail.com>
>>
>> Provide colorimetry <=> libcamera::ColorSpace mappings via:
>> - GstVideoColorimetry colorimetry_from_colorspace(colorspace);
>> - ColorSpace colorspace_from_colorimetry(colorimetry);
>>
>> Read the colorimetry field from caps into the stream configuration.
>> After stream validation, the sensor supported colorimetry will
>> be retrieved and the caps will be updated accordingly.
>>
>> Colorimetry support for gstlibcamerasrc currently undertakes only one
>> argument. Multiple colorimetry support shall be introduced in
>> subsequent commits.
>>
>> Signed-off-by: Rishikesh Donadkar <rishikeshdonadkar@gmail.com>
>> Signed-off-by: Umang Jain <umang.jain@ideasonboard.com>
>> ---
>>   src/gstreamer/gstlibcamera-utils.cpp | 175 +++++++++++++++++++++++++++
>>   1 file changed, 175 insertions(+)
>>
>> diff --git a/src/gstreamer/gstlibcamera-utils.cpp b/src/gstreamer/gstlibcamera-utils.cpp
>> index c97c0d43..fb4f0e5c 100644
>> --- a/src/gstreamer/gstlibcamera-utils.cpp
>> +++ b/src/gstreamer/gstlibcamera-utils.cpp
>> @@ -45,6 +45,157 @@ static struct {
>>          /* \todo NV42 is used in libcamera but is not mapped in GStreamer yet. */
>>   };
>>
>> +static GstVideoColorimetry
>> +colorimetry_from_colorspace(const ColorSpace &colorSpace)
>> +{
>> +       GstVideoColorimetry colorimetry;
>> +
>> +       switch (colorSpace.primaries) {
>> +       case ColorSpace::Primaries::Rec709:
>> +               colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_BT709;
>> +               break;
>> +       case ColorSpace::Primaries::Rec2020:
>> +               colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_BT2020;
>> +               break;
>> +       case ColorSpace::Primaries::Smpte170m:
>> +               colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_SMPTE170M;
>> +               break;
>> +       default:
>> +               GST_WARNING("ColorSpace primaries not mapped in GstLibcameraSrc");
>> +               colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_UNKNOWN;
>> +       }
>> +
>> +       switch (colorSpace.transferFunction) {
>> +       case ColorSpace::TransferFunction::Rec709:
>> +               colorimetry.transfer = GST_VIDEO_TRANSFER_BT709;
>> +               break;
>> +       case ColorSpace::TransferFunction::Srgb:
>> +               colorimetry.transfer = GST_VIDEO_TRANSFER_SRGB;
>> +               break;
>> +       case ColorSpace::TransferFunction::Linear:
>> +               colorimetry.transfer = GST_VIDEO_TRANSFER_GAMMA10;
>> +               break;
>> +       default:
>> +               GST_WARNING("ColorSpace transfer function not mapped in GstLibcameraSrc");
>> +               colorimetry.transfer = GST_VIDEO_TRANSFER_UNKNOWN;
>> +       }
>> +
>> +       switch (colorSpace.ycbcrEncoding) {
>> +       case ColorSpace::YcbcrEncoding::Rec709:
>> +               colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_BT709;
>> +               break;
>> +       case ColorSpace::YcbcrEncoding::Rec2020:
>> +               colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_BT2020;
>> +               break;
>> +       case ColorSpace::YcbcrEncoding::Rec601:
>> +               colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_BT601;
>> +               break;
>> +       default:
>> +               GST_WARNING("Colorspace YcbcrEncoding not mapped in GstLibcameraSrc");
>> +               colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_UNKNOWN;
>> +       }
>> +
>> +       switch (colorSpace.range) {
>> +       case ColorSpace::Range::Full:
>> +               colorimetry.range = GST_VIDEO_COLOR_RANGE_0_255;
>> +               break;
>> +       case ColorSpace::Range::Limited:
>> +               colorimetry.range = GST_VIDEO_COLOR_RANGE_16_235;
>> +               break;
>> +       default:
>> +               GST_WARNING("Colorspace range not mapped in GstLibcameraSrc");
>> +               colorimetry.range = GST_VIDEO_COLOR_RANGE_UNKNOWN;
>> +       }
>> +
>> +       return colorimetry;
>> +}
>> +
>> +static std::optional<ColorSpace>
>> +colorspace_from_colorimetry(GstVideoColorimetry *colorimetry)
>> +{
>> +       std::optional<ColorSpace> colorspace = ColorSpace::Default;
>> +
>> +       switch (colorimetry->primaries) {
>> +       case GST_VIDEO_COLOR_PRIMARIES_BT709:
>> +               colorspace->primaries = ColorSpace::Primaries::Rec709;
>> +               break;
>> +       case GST_VIDEO_COLOR_PRIMARIES_BT2020:
>> +               colorspace->primaries = ColorSpace::Primaries::Rec2020;
>> +               break;
>> +       case GST_VIDEO_COLOR_PRIMARIES_SMPTE170M:
>> +               colorspace->primaries = ColorSpace::Primaries::Smpte170m;
>> +               break;
>> +       case GST_VIDEO_COLOR_PRIMARIES_UNKNOWN:
>> +               colorspace->primaries = ColorSpace::Primaries::Default;
>> +               break;
>> +       default:
>> +               GST_WARNING("Unknown primaries in colorimetry %d", colorimetry->primaries);
>> +       }
>> +
>> +       switch (colorimetry->transfer) {
>> +       /* Tranfer function mappings inspired from v4l2src plugin */
>> +       case GST_VIDEO_TRANSFER_GAMMA18:
>> +       case GST_VIDEO_TRANSFER_GAMMA20:
>> +       case GST_VIDEO_TRANSFER_GAMMA22:
>> +       case GST_VIDEO_TRANSFER_GAMMA28:
>> +               GST_WARNING("GAMMA 18, 20, 22, 28 transfer functions not supported");
>> +       /* fallthrough */
>> +       case GST_VIDEO_TRANSFER_GAMMA10:
>> +               colorspace->transferFunction = ColorSpace::TransferFunction::Linear;
>> +               break;
>> +       case GST_VIDEO_TRANSFER_BT601:
>> +       case GST_VIDEO_TRANSFER_BT2020_12:
>> +       case GST_VIDEO_TRANSFER_BT2020_10:
>> +       case GST_VIDEO_TRANSFER_BT709:
>> +               colorspace->transferFunction = ColorSpace::TransferFunction::Rec709;
>> +               break;
>> +       case GST_VIDEO_TRANSFER_SRGB:
>> +               colorspace->transferFunction = ColorSpace::TransferFunction::Srgb;
>> +               break;
>> +       case GST_VIDEO_TRANSFER_UNKNOWN:
>> +               colorspace->transferFunction = ColorSpace::TransferFunction::Default;
>> +               break;
>> +       default:
>> +               GST_WARNING("Unknown colorimetry transfer %d", colorimetry->transfer);
>> +       }
>> +
>> +       switch (colorimetry->matrix) {
>> +       /* FCC is about the same as BT601 with less digit */
>> +       case GST_VIDEO_COLOR_MATRIX_FCC:
>> +       case GST_VIDEO_COLOR_MATRIX_BT601:
>> +               colorspace->ycbcrEncoding = ColorSpace::YcbcrEncoding::Rec601;
>> +               break;
>> +       case GST_VIDEO_COLOR_MATRIX_BT709:
>> +               colorspace->ycbcrEncoding = ColorSpace::YcbcrEncoding::Rec709;
>> +               break;
>> +       case GST_VIDEO_COLOR_MATRIX_BT2020:
>> +               colorspace->ycbcrEncoding = ColorSpace::YcbcrEncoding::Rec2020;
>> +               break;
>> +       case GST_VIDEO_COLOR_MATRIX_RGB:
>> +       case GST_VIDEO_COLOR_MATRIX_UNKNOWN:
>> +               colorspace->ycbcrEncoding = ColorSpace::YcbcrEncoding::Default;
>> +               break;
>> +       default:
>> +               GST_WARNING("Unknown colorimetry matrix %d", colorimetry->matrix);
>> +       }
>> +
>> +       switch (colorimetry->range) {
>> +       case GST_VIDEO_COLOR_RANGE_0_255:
>> +               colorspace->range = ColorSpace::Range::Full;
>> +               break;
>> +       case GST_VIDEO_COLOR_RANGE_16_235:
>> +               colorspace->range = ColorSpace::Range::Limited;
>> +               break;
>> +       case GST_VIDEO_COLOR_RANGE_UNKNOWN:
>> +               colorspace->range = ColorSpace::Range::Default;
>> +               break;
>> +       default:
>> +               GST_WARNING("Unknown range in colorimetry %d", colorimetry->range);
>> +       }
>> +
>> +       return colorspace;
>> +}
>> +
>>   static GstVideoFormat
>>   pixel_format_to_gst_format(const PixelFormat &format)
>>   {
>> @@ -139,6 +290,17 @@ gst_libcamera_stream_configuration_to_caps(const StreamConfiguration &stream_cfg
>>                            "width", G_TYPE_INT, stream_cfg.size.width,
>>                            "height", G_TYPE_INT, stream_cfg.size.height,
>>                            nullptr);
>> +
>> +       if (stream_cfg.colorSpace) {
>> +               GstVideoColorimetry colorimetry = colorimetry_from_colorspace(stream_cfg.colorSpace.value());
>> +               gchar *colorimetry_str = gst_video_colorimetry_to_string(&colorimetry);
>> +
>> +               if (colorimetry_str != nullptr)
>> +                       gst_structure_set(s, "colorimetry", G_TYPE_STRING, colorimetry_str, nullptr);
>> +               else
>> +                       g_warning("libcamera::ColorSpace found but GstVideoColorimetry unknown");
>> +       }
>> +
>>          gst_caps_append_structure(caps, s);
>>
>>          return caps;
>> @@ -222,6 +384,19 @@ gst_libcamera_configure_stream_from_caps(StreamConfiguration &stream_cfg,
>>          gst_structure_get_int(s, "height", &height);
>>          stream_cfg.size.width = width;
>>          stream_cfg.size.height = height;
>> +
>> +       /* Configure colorimetry */
>> +       if (gst_structure_has_field(s, "colorimetry")) {
>> +               const gchar *colorimetry_caps = gst_structure_get_string(s, "colorimetry");
>> +               GstVideoColorimetry colorimetry;
>> +
>> +               if(gst_video_colorimetry_from_string(&colorimetry, colorimetry_caps)) {
>> +                       std::optional<ColorSpace> colorSpace = colorspace_from_colorimetry(&colorimetry);
>> +                       stream_cfg.colorSpace = colorSpace;
>> +               } else {
>> +                       g_print("Invalid colorimetry %s", colorimetry_caps);
>> +               }
>> +       }
>>   }
>>
>>   #if !GST_CHECK_VERSION(1, 17, 1)
>> --
>> 2.31.1
>>
Laurent Pinchart July 27, 2022, 1:17 a.m. UTC | #3
Hi Umang and Rishikesh,

Thank you for the patch.

On Sun, Jul 24, 2022 at 08:13:55PM +0530, Umang Jain via libcamera-devel wrote:
> From: Rishikesh Donadkar <rishikeshdonadkar@gmail.com>
> 
> Provide colorimetry <=> libcamera::ColorSpace mappings via:
> - GstVideoColorimetry colorimetry_from_colorspace(colorspace);
> - ColorSpace colorspace_from_colorimetry(colorimetry);
> 
> Read the colorimetry field from caps into the stream configuration.
> After stream validation, the sensor supported colorimetry will
> be retrieved and the caps will be updated accordingly.
> 
> Colorimetry support for gstlibcamerasrc currently undertakes only one
> argument. Multiple colorimetry support shall be introduced in
> subsequent commits.
> 
> Signed-off-by: Rishikesh Donadkar <rishikeshdonadkar@gmail.com>
> Signed-off-by: Umang Jain <umang.jain@ideasonboard.com>
> ---
>  src/gstreamer/gstlibcamera-utils.cpp | 175 +++++++++++++++++++++++++++
>  1 file changed, 175 insertions(+)
> 
> diff --git a/src/gstreamer/gstlibcamera-utils.cpp b/src/gstreamer/gstlibcamera-utils.cpp
> index c97c0d43..fb4f0e5c 100644
> --- a/src/gstreamer/gstlibcamera-utils.cpp
> +++ b/src/gstreamer/gstlibcamera-utils.cpp
> @@ -45,6 +45,157 @@ static struct {
>  	/* \todo NV42 is used in libcamera but is not mapped in GStreamer yet. */
>  };
>  
> +static GstVideoColorimetry
> +colorimetry_from_colorspace(const ColorSpace &colorSpace)
> +{
> +	GstVideoColorimetry colorimetry;

I would initialize the structure with all fields set to UNKNOWN here...

> +
> +	switch (colorSpace.primaries) {
> +	case ColorSpace::Primaries::Rec709:
> +		colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_BT709;
> +		break;
> +	case ColorSpace::Primaries::Rec2020:
> +		colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_BT2020;
> +		break;
> +	case ColorSpace::Primaries::Smpte170m:
> +		colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_SMPTE170M;
> +		break;
> +	default:
> +		GST_WARNING("ColorSpace primaries not mapped in GstLibcameraSrc");
> +		colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_UNKNOWN;

... and drop the default case. As ColorSpace::Primaries is an enum
class, if you have cases for all the values, the compiler will warn only
if one (or more) of the enum values isn't handled in explicit cases.
This way you can avoid warnings, and have a guarantee that if the
libcamera colorspaces are later extended, the compiler will remind that
this function has to be updated. Same for the other components of the
colorspace below.

> +	}
> +
> +	switch (colorSpace.transferFunction) {
> +	case ColorSpace::TransferFunction::Rec709:
> +		colorimetry.transfer = GST_VIDEO_TRANSFER_BT709;
> +		break;
> +	case ColorSpace::TransferFunction::Srgb:
> +		colorimetry.transfer = GST_VIDEO_TRANSFER_SRGB;
> +		break;
> +	case ColorSpace::TransferFunction::Linear:
> +		colorimetry.transfer = GST_VIDEO_TRANSFER_GAMMA10;
> +		break;
> +	default:
> +		GST_WARNING("ColorSpace transfer function not mapped in GstLibcameraSrc");
> +		colorimetry.transfer = GST_VIDEO_TRANSFER_UNKNOWN;
> +	}
> +
> +	switch (colorSpace.ycbcrEncoding) {
> +	case ColorSpace::YcbcrEncoding::Rec709:
> +		colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_BT709;
> +		break;
> +	case ColorSpace::YcbcrEncoding::Rec2020:
> +		colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_BT2020;
> +		break;
> +	case ColorSpace::YcbcrEncoding::Rec601:
> +		colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_BT601;
> +		break;
> +	default:
> +		GST_WARNING("Colorspace YcbcrEncoding not mapped in GstLibcameraSrc");
> +		colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_UNKNOWN;

Should we map YcbcrEncoding::None to GST_VIDEO_COLOR_MATRIX_RGB ?
Otherwise capturing RGB or raw bayer will produce a warning. Same for
the Raw primaries, it seems that the best mapping would be
GST_VIDEO_COLOR_PRIMARIES_UNKNOWN (unless I'm missing something), but I
don't think we should warn in that case.

> +	}
> +
> +	switch (colorSpace.range) {
> +	case ColorSpace::Range::Full:
> +		colorimetry.range = GST_VIDEO_COLOR_RANGE_0_255;
> +		break;
> +	case ColorSpace::Range::Limited:
> +		colorimetry.range = GST_VIDEO_COLOR_RANGE_16_235;
> +		break;
> +	default:
> +		GST_WARNING("Colorspace range not mapped in GstLibcameraSrc");
> +		colorimetry.range = GST_VIDEO_COLOR_RANGE_UNKNOWN;
> +	}
> +
> +	return colorimetry;
> +}
> +
> +static std::optional<ColorSpace>
> +colorspace_from_colorimetry(GstVideoColorimetry *colorimetry)

I'd pass a const reference instead of a pointer, as there's no use case
for a null colorimetry.

> +{
> +	std::optional<ColorSpace> colorspace = ColorSpace::Default;

No need for std::optional here or in the return type.

> +
> +	switch (colorimetry->primaries) {
> +	case GST_VIDEO_COLOR_PRIMARIES_BT709:
> +		colorspace->primaries = ColorSpace::Primaries::Rec709;
> +		break;
> +	case GST_VIDEO_COLOR_PRIMARIES_BT2020:
> +		colorspace->primaries = ColorSpace::Primaries::Rec2020;
> +		break;
> +	case GST_VIDEO_COLOR_PRIMARIES_SMPTE170M:
> +		colorspace->primaries = ColorSpace::Primaries::Smpte170m;
> +		break;
> +	case GST_VIDEO_COLOR_PRIMARIES_UNKNOWN:
> +		colorspace->primaries = ColorSpace::Primaries::Default;
> +		break;
> +	default:
> +		GST_WARNING("Unknown primaries in colorimetry %d", colorimetry->primaries);
> +	}
> +
> +	switch (colorimetry->transfer) {
> +	/* Tranfer function mappings inspired from v4l2src plugin */
> +	case GST_VIDEO_TRANSFER_GAMMA18:
> +	case GST_VIDEO_TRANSFER_GAMMA20:
> +	case GST_VIDEO_TRANSFER_GAMMA22:
> +	case GST_VIDEO_TRANSFER_GAMMA28:
> +		GST_WARNING("GAMMA 18, 20, 22, 28 transfer functions not supported");
> +	/* fallthrough */
> +	case GST_VIDEO_TRANSFER_GAMMA10:
> +		colorspace->transferFunction = ColorSpace::TransferFunction::Linear;
> +		break;
> +	case GST_VIDEO_TRANSFER_BT601:
> +	case GST_VIDEO_TRANSFER_BT2020_12:
> +	case GST_VIDEO_TRANSFER_BT2020_10:
> +	case GST_VIDEO_TRANSFER_BT709:
> +		colorspace->transferFunction = ColorSpace::TransferFunction::Rec709;
> +		break;
> +	case GST_VIDEO_TRANSFER_SRGB:
> +		colorspace->transferFunction = ColorSpace::TransferFunction::Srgb;
> +		break;
> +	case GST_VIDEO_TRANSFER_UNKNOWN:
> +		colorspace->transferFunction = ColorSpace::TransferFunction::Default;
> +		break;
> +	default:
> +		GST_WARNING("Unknown colorimetry transfer %d", colorimetry->transfer);
> +	}
> +
> +	switch (colorimetry->matrix) {
> +	/* FCC is about the same as BT601 with less digit */
> +	case GST_VIDEO_COLOR_MATRIX_FCC:
> +	case GST_VIDEO_COLOR_MATRIX_BT601:
> +		colorspace->ycbcrEncoding = ColorSpace::YcbcrEncoding::Rec601;
> +		break;
> +	case GST_VIDEO_COLOR_MATRIX_BT709:
> +		colorspace->ycbcrEncoding = ColorSpace::YcbcrEncoding::Rec709;
> +		break;
> +	case GST_VIDEO_COLOR_MATRIX_BT2020:
> +		colorspace->ycbcrEncoding = ColorSpace::YcbcrEncoding::Rec2020;
> +		break;
> +	case GST_VIDEO_COLOR_MATRIX_RGB:

Shouldn't this map to YcbcrEncoding::None ?

> +	case GST_VIDEO_COLOR_MATRIX_UNKNOWN:
> +		colorspace->ycbcrEncoding = ColorSpace::YcbcrEncoding::Default;
> +		break;
> +	default:
> +		GST_WARNING("Unknown colorimetry matrix %d", colorimetry->matrix);
> +	}
> +
> +	switch (colorimetry->range) {
> +	case GST_VIDEO_COLOR_RANGE_0_255:
> +		colorspace->range = ColorSpace::Range::Full;
> +		break;
> +	case GST_VIDEO_COLOR_RANGE_16_235:
> +		colorspace->range = ColorSpace::Range::Limited;
> +		break;
> +	case GST_VIDEO_COLOR_RANGE_UNKNOWN:
> +		colorspace->range = ColorSpace::Range::Default;
> +		break;
> +	default:
> +		GST_WARNING("Unknown range in colorimetry %d", colorimetry->range);
> +	}
> +
> +	return colorspace;
> +}
> +
>  static GstVideoFormat
>  pixel_format_to_gst_format(const PixelFormat &format)
>  {
> @@ -139,6 +290,17 @@ gst_libcamera_stream_configuration_to_caps(const StreamConfiguration &stream_cfg
>  			  "width", G_TYPE_INT, stream_cfg.size.width,
>  			  "height", G_TYPE_INT, stream_cfg.size.height,
>  			  nullptr);
> +
> +	if (stream_cfg.colorSpace) {
> +		GstVideoColorimetry colorimetry = colorimetry_from_colorspace(stream_cfg.colorSpace.value());
> +		gchar *colorimetry_str = gst_video_colorimetry_to_string(&colorimetry);
> +
> +		if (colorimetry_str != nullptr)

		if (colorimetry_str)

> +			gst_structure_set(s, "colorimetry", G_TYPE_STRING, colorimetry_str, nullptr);
> +		else
> +			g_warning("libcamera::ColorSpace found but GstVideoColorimetry unknown");
> +	}
> +
>  	gst_caps_append_structure(caps, s);
>  
>  	return caps;
> @@ -222,6 +384,19 @@ gst_libcamera_configure_stream_from_caps(StreamConfiguration &stream_cfg,
>  	gst_structure_get_int(s, "height", &height);
>  	stream_cfg.size.width = width;
>  	stream_cfg.size.height = height;
> +
> +	/* Configure colorimetry */
> +	if (gst_structure_has_field(s, "colorimetry")) {
> +		const gchar *colorimetry_caps = gst_structure_get_string(s, "colorimetry");
> +		GstVideoColorimetry colorimetry;
> +
> +		if(gst_video_colorimetry_from_string(&colorimetry, colorimetry_caps)) {

Missing space after "if".

> +			std::optional<ColorSpace> colorSpace = colorspace_from_colorimetry(&colorimetry);
> +			stream_cfg.colorSpace = colorSpace;

			stream_cfg.colorSpace = colorspace_from_colorimetry(&colorimetry);

> +		} else {
> +			g_print("Invalid colorimetry %s", colorimetry_caps);
> +		}
> +	}
>  }
>  
>  #if !GST_CHECK_VERSION(1, 17, 1)
Nicolas Dufresne July 27, 2022, 2:48 p.m. UTC | #4
Le dimanche 24 juillet 2022 à 22:03 +0530, Rishikesh Donadkar via libcamera-
devel a écrit :
> > default:
> > +               GST_WARNING("Colorspace YcbcrEncoding not mapped in
> > GstLibcameraSrc");
> > +               colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_UNKNOWN;
> > +       }
> 
> Back in v1 of colorspace and colorimetry integration nicolas pointed
> out that we should not use the
> _UNKNOWN to set the fields.
> https://lists.libcamera.org/pipermail/libcamera-devel/2022-July/031941.html
> I am not sure whether we should set the field to _UNKNOWN in the
> default switch cases.

Its perverse, since the use of a WARNING would mean you want to mitigate the
missing mapping, but in some case it will fail to negotiate. I suggest to post
an error (perhaps GST_STREAM_ERROR_NOT_IMPLEMENTED ?) Or properly mitigate it.

If this should just never be reached, then there is g_assert_not_reached() or
g_error() that will abort the program.

> 
> 
> On Sun, Jul 24, 2022 at 8:14 PM Umang Jain <umang.jain@ideasonboard.com>
> wrote:
> > 
> > From: Rishikesh Donadkar <rishikeshdonadkar@gmail.com>
> > 
> > Provide colorimetry <=> libcamera::ColorSpace mappings via:
> > - GstVideoColorimetry colorimetry_from_colorspace(colorspace);
> > - ColorSpace colorspace_from_colorimetry(colorimetry);
> > 
> > Read the colorimetry field from caps into the stream configuration.
> > After stream validation, the sensor supported colorimetry will
> > be retrieved and the caps will be updated accordingly.
> > 
> > Colorimetry support for gstlibcamerasrc currently undertakes only one
> > argument. Multiple colorimetry support shall be introduced in
> > subsequent commits.
> > 
> > Signed-off-by: Rishikesh Donadkar <rishikeshdonadkar@gmail.com>
> > Signed-off-by: Umang Jain <umang.jain@ideasonboard.com>
> > ---
> >  src/gstreamer/gstlibcamera-utils.cpp | 175 +++++++++++++++++++++++++++
> >  1 file changed, 175 insertions(+)
> > 
> > diff --git a/src/gstreamer/gstlibcamera-utils.cpp
> > b/src/gstreamer/gstlibcamera-utils.cpp
> > index c97c0d43..fb4f0e5c 100644
> > --- a/src/gstreamer/gstlibcamera-utils.cpp
> > +++ b/src/gstreamer/gstlibcamera-utils.cpp
> > @@ -45,6 +45,157 @@ static struct {
> >         /* \todo NV42 is used in libcamera but is not mapped in GStreamer
> > yet. */
> >  };
> > 
> > +static GstVideoColorimetry
> > +colorimetry_from_colorspace(const ColorSpace &colorSpace)
> > +{
> > +       GstVideoColorimetry colorimetry;
> > +
> > +       switch (colorSpace.primaries) {
> > +       case ColorSpace::Primaries::Rec709:
> > +               colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_BT709;
> > +               break;
> > +       case ColorSpace::Primaries::Rec2020:
> > +               colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_BT2020;
> > +               break;
> > +       case ColorSpace::Primaries::Smpte170m:
> > +               colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_SMPTE170M;
> > +               break;
> > +       default:
> > +               GST_WARNING("ColorSpace primaries not mapped in
> > GstLibcameraSrc");
> > +               colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_UNKNOWN;
> > +       }
> > +
> > +       switch (colorSpace.transferFunction) {
> > +       case ColorSpace::TransferFunction::Rec709:
> > +               colorimetry.transfer = GST_VIDEO_TRANSFER_BT709;
> > +               break;
> > +       case ColorSpace::TransferFunction::Srgb:
> > +               colorimetry.transfer = GST_VIDEO_TRANSFER_SRGB;
> > +               break;
> > +       case ColorSpace::TransferFunction::Linear:
> > +               colorimetry.transfer = GST_VIDEO_TRANSFER_GAMMA10;
> > +               break;
> > +       default:
> > +               GST_WARNING("ColorSpace transfer function not mapped in
> > GstLibcameraSrc");
> > +               colorimetry.transfer = GST_VIDEO_TRANSFER_UNKNOWN;
> > +       }
> > +
> > +       switch (colorSpace.ycbcrEncoding) {
> > +       case ColorSpace::YcbcrEncoding::Rec709:
> > +               colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_BT709;
> > +               break;
> > +       case ColorSpace::YcbcrEncoding::Rec2020:
> > +               colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_BT2020;
> > +               break;
> > +       case ColorSpace::YcbcrEncoding::Rec601:
> > +               colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_BT601;
> > +               break;
> > +       default:
> > +               GST_WARNING("Colorspace YcbcrEncoding not mapped in
> > GstLibcameraSrc");
> > +               colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_UNKNOWN;
> > +       }
> > +
> > +       switch (colorSpace.range) {
> > +       case ColorSpace::Range::Full:
> > +               colorimetry.range = GST_VIDEO_COLOR_RANGE_0_255;
> > +               break;
> > +       case ColorSpace::Range::Limited:
> > +               colorimetry.range = GST_VIDEO_COLOR_RANGE_16_235;
> > +               break;
> > +       default:
> > +               GST_WARNING("Colorspace range not mapped in
> > GstLibcameraSrc");
> > +               colorimetry.range = GST_VIDEO_COLOR_RANGE_UNKNOWN;
> > +       }
> > +
> > +       return colorimetry;
> > +}
> > +
> > +static std::optional<ColorSpace>
> > +colorspace_from_colorimetry(GstVideoColorimetry *colorimetry)
> > +{
> > +       std::optional<ColorSpace> colorspace = ColorSpace::Default;
> > +
> > +       switch (colorimetry->primaries) {
> > +       case GST_VIDEO_COLOR_PRIMARIES_BT709:
> > +               colorspace->primaries = ColorSpace::Primaries::Rec709;
> > +               break;
> > +       case GST_VIDEO_COLOR_PRIMARIES_BT2020:
> > +               colorspace->primaries = ColorSpace::Primaries::Rec2020;
> > +               break;
> > +       case GST_VIDEO_COLOR_PRIMARIES_SMPTE170M:
> > +               colorspace->primaries = ColorSpace::Primaries::Smpte170m;
> > +               break;
> > +       case GST_VIDEO_COLOR_PRIMARIES_UNKNOWN:
> > +               colorspace->primaries = ColorSpace::Primaries::Default;
> > +               break;
> > +       default:
> > +               GST_WARNING("Unknown primaries in colorimetry %d",
> > colorimetry->primaries);
> > +       }
> > +
> > +       switch (colorimetry->transfer) {
> > +       /* Tranfer function mappings inspired from v4l2src plugin */
> > +       case GST_VIDEO_TRANSFER_GAMMA18:
> > +       case GST_VIDEO_TRANSFER_GAMMA20:
> > +       case GST_VIDEO_TRANSFER_GAMMA22:
> > +       case GST_VIDEO_TRANSFER_GAMMA28:
> > +               GST_WARNING("GAMMA 18, 20, 22, 28 transfer functions not
> > supported");
> > +       /* fallthrough */
> > +       case GST_VIDEO_TRANSFER_GAMMA10:
> > +               colorspace->transferFunction =
> > ColorSpace::TransferFunction::Linear;
> > +               break;
> > +       case GST_VIDEO_TRANSFER_BT601:
> > +       case GST_VIDEO_TRANSFER_BT2020_12:
> > +       case GST_VIDEO_TRANSFER_BT2020_10:
> > +       case GST_VIDEO_TRANSFER_BT709:
> > +               colorspace->transferFunction =
> > ColorSpace::TransferFunction::Rec709;
> > +               break;
> > +       case GST_VIDEO_TRANSFER_SRGB:
> > +               colorspace->transferFunction =
> > ColorSpace::TransferFunction::Srgb;
> > +               break;
> > +       case GST_VIDEO_TRANSFER_UNKNOWN:
> > +               colorspace->transferFunction =
> > ColorSpace::TransferFunction::Default;
> > +               break;
> > +       default:
> > +               GST_WARNING("Unknown colorimetry transfer %d", colorimetry-
> > >transfer);
> > +       }
> > +
> > +       switch (colorimetry->matrix) {
> > +       /* FCC is about the same as BT601 with less digit */
> > +       case GST_VIDEO_COLOR_MATRIX_FCC:
> > +       case GST_VIDEO_COLOR_MATRIX_BT601:
> > +               colorspace->ycbcrEncoding =
> > ColorSpace::YcbcrEncoding::Rec601;
> > +               break;
> > +       case GST_VIDEO_COLOR_MATRIX_BT709:
> > +               colorspace->ycbcrEncoding =
> > ColorSpace::YcbcrEncoding::Rec709;
> > +               break;
> > +       case GST_VIDEO_COLOR_MATRIX_BT2020:
> > +               colorspace->ycbcrEncoding =
> > ColorSpace::YcbcrEncoding::Rec2020;
> > +               break;
> > +       case GST_VIDEO_COLOR_MATRIX_RGB:
> > +       case GST_VIDEO_COLOR_MATRIX_UNKNOWN:
> > +               colorspace->ycbcrEncoding =
> > ColorSpace::YcbcrEncoding::Default;
> > +               break;
> > +       default:
> > +               GST_WARNING("Unknown colorimetry matrix %d", colorimetry-
> > >matrix);
> > +       }
> > +
> > +       switch (colorimetry->range) {
> > +       case GST_VIDEO_COLOR_RANGE_0_255:
> > +               colorspace->range = ColorSpace::Range::Full;
> > +               break;
> > +       case GST_VIDEO_COLOR_RANGE_16_235:
> > +               colorspace->range = ColorSpace::Range::Limited;
> > +               break;
> > +       case GST_VIDEO_COLOR_RANGE_UNKNOWN:
> > +               colorspace->range = ColorSpace::Range::Default;
> > +               break;
> > +       default:
> > +               GST_WARNING("Unknown range in colorimetry %d", colorimetry-
> > >range);
> > +       }
> > +
> > +       return colorspace;
> > +}
> > +
> >  static GstVideoFormat
> >  pixel_format_to_gst_format(const PixelFormat &format)
> >  {
> > @@ -139,6 +290,17 @@ gst_libcamera_stream_configuration_to_caps(const
> > StreamConfiguration &stream_cfg
> >                           "width", G_TYPE_INT, stream_cfg.size.width,
> >                           "height", G_TYPE_INT, stream_cfg.size.height,
> >                           nullptr);
> > +
> > +       if (stream_cfg.colorSpace) {
> > +               GstVideoColorimetry colorimetry =
> > colorimetry_from_colorspace(stream_cfg.colorSpace.value());
> > +               gchar *colorimetry_str =
> > gst_video_colorimetry_to_string(&colorimetry);
> > +
> > +               if (colorimetry_str != nullptr)
> > +                       gst_structure_set(s, "colorimetry", G_TYPE_STRING,
> > colorimetry_str, nullptr);
> > +               else
> > +                       g_warning("libcamera::ColorSpace found but
> > GstVideoColorimetry unknown");
> > +       }
> > +
> >         gst_caps_append_structure(caps, s);
> > 
> >         return caps;
> > @@ -222,6 +384,19 @@
> > gst_libcamera_configure_stream_from_caps(StreamConfiguration &stream_cfg,
> >         gst_structure_get_int(s, "height", &height);
> >         stream_cfg.size.width = width;
> >         stream_cfg.size.height = height;
> > +
> > +       /* Configure colorimetry */
> > +       if (gst_structure_has_field(s, "colorimetry")) {
> > +               const gchar *colorimetry_caps = gst_structure_get_string(s,
> > "colorimetry");
> > +               GstVideoColorimetry colorimetry;
> > +
> > +               if(gst_video_colorimetry_from_string(&colorimetry,
> > colorimetry_caps)) {
> > +                       std::optional<ColorSpace> colorSpace =
> > colorspace_from_colorimetry(&colorimetry);
> > +                       stream_cfg.colorSpace = colorSpace;
> > +               } else {
> > +                       g_print("Invalid colorimetry %s", colorimetry_caps);
> > +               }
> > +       }
> >  }
> > 
> >  #if !GST_CHECK_VERSION(1, 17, 1)
> > --
> > 2.31.1
> >
Nicolas Dufresne July 27, 2022, 2:50 p.m. UTC | #5
Le dimanche 24 juillet 2022 à 23:48 +0530, Umang Jain via libcamera-devel a
écrit :
> Hi Rishi,
> 
> On 7/24/22 22:03, Rishikesh Donadkar wrote:
> > > default:
> > > +               GST_WARNING("Colorspace YcbcrEncoding not mapped in GstLibcameraSrc");
> > > +               colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_UNKNOWN;
> > > +       }
> > Back in v1 of colorspace and colorimetry integration nicolas pointed
> > out that we should not use the
> > _UNKNOWN to set the fields.
> > https://lists.libcamera.org/pipermail/libcamera-devel/2022-July/031941.html
> > I am not sure whether we should set the field to _UNKNOWN in the
> > default switch cases.
> 
> 
> That's what is in happening here. It's a parsing error hence we use 
> _UNKNOWN along with a warning.
> 
> This means a identifier exists in the libcamera(probably a new 
> identifier is added to libcamera) but not mapped in gstlibcamerasrc, 
> hence, it shall pop the warning and the mapping for the new identifier 
> needs to be added in gstlibcamerasrc-utils :-)
> 
> As discussed on the IRC, we are working on case-by-case basis here, so 
> as we get reports of missing identifiers, we shall extend the mappings. 
> Hence the entire mapping/warnings has been set up that way.

Perhaps you should add some more info to the trace in you want to "get reports
of missing identifiers" ?

As said in previous answers, WARNING should come with proper mitigation, UNKNOWN
may lead to a pipeline error later, as its an invalid value.

> > 
> > 
> > On Sun, Jul 24, 2022 at 8:14 PM Umang Jain <umang.jain@ideasonboard.com> wrote:
> > > From: Rishikesh Donadkar <rishikeshdonadkar@gmail.com>
> > > 
> > > Provide colorimetry <=> libcamera::ColorSpace mappings via:
> > > - GstVideoColorimetry colorimetry_from_colorspace(colorspace);
> > > - ColorSpace colorspace_from_colorimetry(colorimetry);
> > > 
> > > Read the colorimetry field from caps into the stream configuration.
> > > After stream validation, the sensor supported colorimetry will
> > > be retrieved and the caps will be updated accordingly.
> > > 
> > > Colorimetry support for gstlibcamerasrc currently undertakes only one
> > > argument. Multiple colorimetry support shall be introduced in
> > > subsequent commits.
> > > 
> > > Signed-off-by: Rishikesh Donadkar <rishikeshdonadkar@gmail.com>
> > > Signed-off-by: Umang Jain <umang.jain@ideasonboard.com>
> > > ---
> > >   src/gstreamer/gstlibcamera-utils.cpp | 175 +++++++++++++++++++++++++++
> > >   1 file changed, 175 insertions(+)
> > > 
> > > diff --git a/src/gstreamer/gstlibcamera-utils.cpp b/src/gstreamer/gstlibcamera-utils.cpp
> > > index c97c0d43..fb4f0e5c 100644
> > > --- a/src/gstreamer/gstlibcamera-utils.cpp
> > > +++ b/src/gstreamer/gstlibcamera-utils.cpp
> > > @@ -45,6 +45,157 @@ static struct {
> > >          /* \todo NV42 is used in libcamera but is not mapped in GStreamer yet. */
> > >   };
> > > 
> > > +static GstVideoColorimetry
> > > +colorimetry_from_colorspace(const ColorSpace &colorSpace)
> > > +{
> > > +       GstVideoColorimetry colorimetry;
> > > +
> > > +       switch (colorSpace.primaries) {
> > > +       case ColorSpace::Primaries::Rec709:
> > > +               colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_BT709;
> > > +               break;
> > > +       case ColorSpace::Primaries::Rec2020:
> > > +               colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_BT2020;
> > > +               break;
> > > +       case ColorSpace::Primaries::Smpte170m:
> > > +               colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_SMPTE170M;
> > > +               break;
> > > +       default:
> > > +               GST_WARNING("ColorSpace primaries not mapped in GstLibcameraSrc");
> > > +               colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_UNKNOWN;
> > > +       }
> > > +
> > > +       switch (colorSpace.transferFunction) {
> > > +       case ColorSpace::TransferFunction::Rec709:
> > > +               colorimetry.transfer = GST_VIDEO_TRANSFER_BT709;
> > > +               break;
> > > +       case ColorSpace::TransferFunction::Srgb:
> > > +               colorimetry.transfer = GST_VIDEO_TRANSFER_SRGB;
> > > +               break;
> > > +       case ColorSpace::TransferFunction::Linear:
> > > +               colorimetry.transfer = GST_VIDEO_TRANSFER_GAMMA10;
> > > +               break;
> > > +       default:
> > > +               GST_WARNING("ColorSpace transfer function not mapped in GstLibcameraSrc");
> > > +               colorimetry.transfer = GST_VIDEO_TRANSFER_UNKNOWN;
> > > +       }
> > > +
> > > +       switch (colorSpace.ycbcrEncoding) {
> > > +       case ColorSpace::YcbcrEncoding::Rec709:
> > > +               colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_BT709;
> > > +               break;
> > > +       case ColorSpace::YcbcrEncoding::Rec2020:
> > > +               colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_BT2020;
> > > +               break;
> > > +       case ColorSpace::YcbcrEncoding::Rec601:
> > > +               colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_BT601;
> > > +               break;
> > > +       default:
> > > +               GST_WARNING("Colorspace YcbcrEncoding not mapped in GstLibcameraSrc");
> > > +               colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_UNKNOWN;
> > > +       }
> > > +
> > > +       switch (colorSpace.range) {
> > > +       case ColorSpace::Range::Full:
> > > +               colorimetry.range = GST_VIDEO_COLOR_RANGE_0_255;
> > > +               break;
> > > +       case ColorSpace::Range::Limited:
> > > +               colorimetry.range = GST_VIDEO_COLOR_RANGE_16_235;
> > > +               break;
> > > +       default:
> > > +               GST_WARNING("Colorspace range not mapped in GstLibcameraSrc");
> > > +               colorimetry.range = GST_VIDEO_COLOR_RANGE_UNKNOWN;
> > > +       }
> > > +
> > > +       return colorimetry;
> > > +}
> > > +
> > > +static std::optional<ColorSpace>
> > > +colorspace_from_colorimetry(GstVideoColorimetry *colorimetry)
> > > +{
> > > +       std::optional<ColorSpace> colorspace = ColorSpace::Default;
> > > +
> > > +       switch (colorimetry->primaries) {
> > > +       case GST_VIDEO_COLOR_PRIMARIES_BT709:
> > > +               colorspace->primaries = ColorSpace::Primaries::Rec709;
> > > +               break;
> > > +       case GST_VIDEO_COLOR_PRIMARIES_BT2020:
> > > +               colorspace->primaries = ColorSpace::Primaries::Rec2020;
> > > +               break;
> > > +       case GST_VIDEO_COLOR_PRIMARIES_SMPTE170M:
> > > +               colorspace->primaries = ColorSpace::Primaries::Smpte170m;
> > > +               break;
> > > +       case GST_VIDEO_COLOR_PRIMARIES_UNKNOWN:
> > > +               colorspace->primaries = ColorSpace::Primaries::Default;
> > > +               break;
> > > +       default:
> > > +               GST_WARNING("Unknown primaries in colorimetry %d", colorimetry->primaries);
> > > +       }
> > > +
> > > +       switch (colorimetry->transfer) {
> > > +       /* Tranfer function mappings inspired from v4l2src plugin */
> > > +       case GST_VIDEO_TRANSFER_GAMMA18:
> > > +       case GST_VIDEO_TRANSFER_GAMMA20:
> > > +       case GST_VIDEO_TRANSFER_GAMMA22:
> > > +       case GST_VIDEO_TRANSFER_GAMMA28:
> > > +               GST_WARNING("GAMMA 18, 20, 22, 28 transfer functions not supported");
> > > +       /* fallthrough */
> > > +       case GST_VIDEO_TRANSFER_GAMMA10:
> > > +               colorspace->transferFunction = ColorSpace::TransferFunction::Linear;
> > > +               break;
> > > +       case GST_VIDEO_TRANSFER_BT601:
> > > +       case GST_VIDEO_TRANSFER_BT2020_12:
> > > +       case GST_VIDEO_TRANSFER_BT2020_10:
> > > +       case GST_VIDEO_TRANSFER_BT709:
> > > +               colorspace->transferFunction = ColorSpace::TransferFunction::Rec709;
> > > +               break;
> > > +       case GST_VIDEO_TRANSFER_SRGB:
> > > +               colorspace->transferFunction = ColorSpace::TransferFunction::Srgb;
> > > +               break;
> > > +       case GST_VIDEO_TRANSFER_UNKNOWN:
> > > +               colorspace->transferFunction = ColorSpace::TransferFunction::Default;
> > > +               break;
> > > +       default:
> > > +               GST_WARNING("Unknown colorimetry transfer %d", colorimetry->transfer);
> > > +       }
> > > +
> > > +       switch (colorimetry->matrix) {
> > > +       /* FCC is about the same as BT601 with less digit */
> > > +       case GST_VIDEO_COLOR_MATRIX_FCC:
> > > +       case GST_VIDEO_COLOR_MATRIX_BT601:
> > > +               colorspace->ycbcrEncoding = ColorSpace::YcbcrEncoding::Rec601;
> > > +               break;
> > > +       case GST_VIDEO_COLOR_MATRIX_BT709:
> > > +               colorspace->ycbcrEncoding = ColorSpace::YcbcrEncoding::Rec709;
> > > +               break;
> > > +       case GST_VIDEO_COLOR_MATRIX_BT2020:
> > > +               colorspace->ycbcrEncoding = ColorSpace::YcbcrEncoding::Rec2020;
> > > +               break;
> > > +       case GST_VIDEO_COLOR_MATRIX_RGB:
> > > +       case GST_VIDEO_COLOR_MATRIX_UNKNOWN:
> > > +               colorspace->ycbcrEncoding = ColorSpace::YcbcrEncoding::Default;
> > > +               break;
> > > +       default:
> > > +               GST_WARNING("Unknown colorimetry matrix %d", colorimetry->matrix);
> > > +       }
> > > +
> > > +       switch (colorimetry->range) {
> > > +       case GST_VIDEO_COLOR_RANGE_0_255:
> > > +               colorspace->range = ColorSpace::Range::Full;
> > > +               break;
> > > +       case GST_VIDEO_COLOR_RANGE_16_235:
> > > +               colorspace->range = ColorSpace::Range::Limited;
> > > +               break;
> > > +       case GST_VIDEO_COLOR_RANGE_UNKNOWN:
> > > +               colorspace->range = ColorSpace::Range::Default;
> > > +               break;
> > > +       default:
> > > +               GST_WARNING("Unknown range in colorimetry %d", colorimetry->range);
> > > +       }
> > > +
> > > +       return colorspace;
> > > +}
> > > +
> > >   static GstVideoFormat
> > >   pixel_format_to_gst_format(const PixelFormat &format)
> > >   {
> > > @@ -139,6 +290,17 @@ gst_libcamera_stream_configuration_to_caps(const StreamConfiguration &stream_cfg
> > >                            "width", G_TYPE_INT, stream_cfg.size.width,
> > >                            "height", G_TYPE_INT, stream_cfg.size.height,
> > >                            nullptr);
> > > +
> > > +       if (stream_cfg.colorSpace) {
> > > +               GstVideoColorimetry colorimetry = colorimetry_from_colorspace(stream_cfg.colorSpace.value());
> > > +               gchar *colorimetry_str = gst_video_colorimetry_to_string(&colorimetry);
> > > +
> > > +               if (colorimetry_str != nullptr)
> > > +                       gst_structure_set(s, "colorimetry", G_TYPE_STRING, colorimetry_str, nullptr);
> > > +               else
> > > +                       g_warning("libcamera::ColorSpace found but GstVideoColorimetry unknown");
> > > +       }
> > > +
> > >          gst_caps_append_structure(caps, s);
> > > 
> > >          return caps;
> > > @@ -222,6 +384,19 @@ gst_libcamera_configure_stream_from_caps(StreamConfiguration &stream_cfg,
> > >          gst_structure_get_int(s, "height", &height);
> > >          stream_cfg.size.width = width;
> > >          stream_cfg.size.height = height;
> > > +
> > > +       /* Configure colorimetry */
> > > +       if (gst_structure_has_field(s, "colorimetry")) {
> > > +               const gchar *colorimetry_caps = gst_structure_get_string(s, "colorimetry");
> > > +               GstVideoColorimetry colorimetry;
> > > +
> > > +               if(gst_video_colorimetry_from_string(&colorimetry, colorimetry_caps)) {
> > > +                       std::optional<ColorSpace> colorSpace = colorspace_from_colorimetry(&colorimetry);
> > > +                       stream_cfg.colorSpace = colorSpace;
> > > +               } else {
> > > +                       g_print("Invalid colorimetry %s", colorimetry_caps);
> > > +               }
> > > +       }
> > >   }
> > > 
> > >   #if !GST_CHECK_VERSION(1, 17, 1)
> > > --
> > > 2.31.1
> > >
Umang Jain July 29, 2022, 8:06 a.m. UTC | #6
Hi Laurent,

My anaylsis on sRGB mapping ...

On 7/27/22 06:47, Laurent Pinchart wrote:
> Hi Umang and Rishikesh,
>
> Thank you for the patch.
>
> On Sun, Jul 24, 2022 at 08:13:55PM +0530, Umang Jain via libcamera-devel wrote:
>> From: Rishikesh Donadkar <rishikeshdonadkar@gmail.com>
>>
>> Provide colorimetry <=> libcamera::ColorSpace mappings via:
>> - GstVideoColorimetry colorimetry_from_colorspace(colorspace);
>> - ColorSpace colorspace_from_colorimetry(colorimetry);
>>
>> Read the colorimetry field from caps into the stream configuration.
>> After stream validation, the sensor supported colorimetry will
>> be retrieved and the caps will be updated accordingly.
>>
>> Colorimetry support for gstlibcamerasrc currently undertakes only one
>> argument. Multiple colorimetry support shall be introduced in
>> subsequent commits.
>>
>> Signed-off-by: Rishikesh Donadkar <rishikeshdonadkar@gmail.com>
>> Signed-off-by: Umang Jain <umang.jain@ideasonboard.com>
>> ---
>>   src/gstreamer/gstlibcamera-utils.cpp | 175 +++++++++++++++++++++++++++
>>   1 file changed, 175 insertions(+)
>>
>> diff --git a/src/gstreamer/gstlibcamera-utils.cpp b/src/gstreamer/gstlibcamera-utils.cpp
>> index c97c0d43..fb4f0e5c 100644
>> --- a/src/gstreamer/gstlibcamera-utils.cpp
>> +++ b/src/gstreamer/gstlibcamera-utils.cpp
>> @@ -45,6 +45,157 @@ static struct {
>>   	/* \todo NV42 is used in libcamera but is not mapped in GStreamer yet. */
>>   };
>>   
>> +static GstVideoColorimetry
>> +colorimetry_from_colorspace(const ColorSpace &colorSpace)
>> +{
>> +	GstVideoColorimetry colorimetry;
> I would initialize the structure with all fields set to UNKNOWN here...
>
>> +
>> +	switch (colorSpace.primaries) {
>> +	case ColorSpace::Primaries::Rec709:
>> +		colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_BT709;
>> +		break;
>> +	case ColorSpace::Primaries::Rec2020:
>> +		colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_BT2020;
>> +		break;
>> +	case ColorSpace::Primaries::Smpte170m:
>> +		colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_SMPTE170M;
>> +		break;
>> +	default:
>> +		GST_WARNING("ColorSpace primaries not mapped in GstLibcameraSrc");
>> +		colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_UNKNOWN;
> ... and drop the default case. As ColorSpace::Primaries is an enum
> class, if you have cases for all the values, the compiler will warn only
> if one (or more) of the enum values isn't handled in explicit cases.
> This way you can avoid warnings, and have a guarantee that if the
> libcamera colorspaces are later extended, the compiler will remind that
> this function has to be updated. Same for the other components of the
> colorspace below.
>
>> +	}
>> +
>> +	switch (colorSpace.transferFunction) {
>> +	case ColorSpace::TransferFunction::Rec709:
>> +		colorimetry.transfer = GST_VIDEO_TRANSFER_BT709;
>> +		break;
>> +	case ColorSpace::TransferFunction::Srgb:
>> +		colorimetry.transfer = GST_VIDEO_TRANSFER_SRGB;
>> +		break;
>> +	case ColorSpace::TransferFunction::Linear:
>> +		colorimetry.transfer = GST_VIDEO_TRANSFER_GAMMA10;
>> +		break;
>> +	default:
>> +		GST_WARNING("ColorSpace transfer function not mapped in GstLibcameraSrc");
>> +		colorimetry.transfer = GST_VIDEO_TRANSFER_UNKNOWN;
>> +	}
>> +
>> +	switch (colorSpace.ycbcrEncoding) {
>> +	case ColorSpace::YcbcrEncoding::Rec709:
>> +		colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_BT709;
>> +		break;
>> +	case ColorSpace::YcbcrEncoding::Rec2020:
>> +		colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_BT2020;
>> +		break;
>> +	case ColorSpace::YcbcrEncoding::Rec601:
>> +		colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_BT601;
>> +		break;
>> +	default:
>> +		GST_WARNING("Colorspace YcbcrEncoding not mapped in GstLibcameraSrc");
>> +		colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_UNKNOWN;
> Should we map YcbcrEncoding::None to GST_VIDEO_COLOR_MATRIX_RGB ?


See below.

> Otherwise capturing RGB or raw bayer will produce a warning. Same for
> the Raw primaries, it seems that the best mapping would be
> GST_VIDEO_COLOR_PRIMARIES_UNKNOWN (unless I'm missing something), but I
> don't think we should warn in that case.
>
>> +	}
>> +
>> +	switch (colorSpace.range) {
>> +	case ColorSpace::Range::Full:
>> +		colorimetry.range = GST_VIDEO_COLOR_RANGE_0_255;
>> +		break;
>> +	case ColorSpace::Range::Limited:
>> +		colorimetry.range = GST_VIDEO_COLOR_RANGE_16_235;
>> +		break;
>> +	default:
>> +		GST_WARNING("Colorspace range not mapped in GstLibcameraSrc");
>> +		colorimetry.range = GST_VIDEO_COLOR_RANGE_UNKNOWN;
>> +	}
>> +
>> +	return colorimetry;
>> +}
>> +
>> +static std::optional<ColorSpace>
>> +colorspace_from_colorimetry(GstVideoColorimetry *colorimetry)
> I'd pass a const reference instead of a pointer, as there's no use case
> for a null colorimetry.
>
>> +{
>> +	std::optional<ColorSpace> colorspace = ColorSpace::Default;
> No need for std::optional here or in the return type.
>
>> +
>> +	switch (colorimetry->primaries) {
>> +	case GST_VIDEO_COLOR_PRIMARIES_BT709:
>> +		colorspace->primaries = ColorSpace::Primaries::Rec709;
>> +		break;
>> +	case GST_VIDEO_COLOR_PRIMARIES_BT2020:
>> +		colorspace->primaries = ColorSpace::Primaries::Rec2020;
>> +		break;
>> +	case GST_VIDEO_COLOR_PRIMARIES_SMPTE170M:
>> +		colorspace->primaries = ColorSpace::Primaries::Smpte170m;
>> +		break;
>> +	case GST_VIDEO_COLOR_PRIMARIES_UNKNOWN:
>> +		colorspace->primaries = ColorSpace::Primaries::Default;
>> +		break;
>> +	default:
>> +		GST_WARNING("Unknown primaries in colorimetry %d", colorimetry->primaries);
>> +	}
>> +
>> +	switch (colorimetry->transfer) {
>> +	/* Tranfer function mappings inspired from v4l2src plugin */
>> +	case GST_VIDEO_TRANSFER_GAMMA18:
>> +	case GST_VIDEO_TRANSFER_GAMMA20:
>> +	case GST_VIDEO_TRANSFER_GAMMA22:
>> +	case GST_VIDEO_TRANSFER_GAMMA28:
>> +		GST_WARNING("GAMMA 18, 20, 22, 28 transfer functions not supported");
>> +	/* fallthrough */
>> +	case GST_VIDEO_TRANSFER_GAMMA10:
>> +		colorspace->transferFunction = ColorSpace::TransferFunction::Linear;
>> +		break;
>> +	case GST_VIDEO_TRANSFER_BT601:
>> +	case GST_VIDEO_TRANSFER_BT2020_12:
>> +	case GST_VIDEO_TRANSFER_BT2020_10:
>> +	case GST_VIDEO_TRANSFER_BT709:
>> +		colorspace->transferFunction = ColorSpace::TransferFunction::Rec709;
>> +		break;
>> +	case GST_VIDEO_TRANSFER_SRGB:
>> +		colorspace->transferFunction = ColorSpace::TransferFunction::Srgb;
>> +		break;
>> +	case GST_VIDEO_TRANSFER_UNKNOWN:
>> +		colorspace->transferFunction = ColorSpace::TransferFunction::Default;
>> +		break;
>> +	default:
>> +		GST_WARNING("Unknown colorimetry transfer %d", colorimetry->transfer);
>> +	}
>> +
>> +	switch (colorimetry->matrix) {
>> +	/* FCC is about the same as BT601 with less digit */
>> +	case GST_VIDEO_COLOR_MATRIX_FCC:
>> +	case GST_VIDEO_COLOR_MATRIX_BT601:
>> +		colorspace->ycbcrEncoding = ColorSpace::YcbcrEncoding::Rec601;
>> +		break;
>> +	case GST_VIDEO_COLOR_MATRIX_BT709:
>> +		colorspace->ycbcrEncoding = ColorSpace::YcbcrEncoding::Rec709;
>> +		break;
>> +	case GST_VIDEO_COLOR_MATRIX_BT2020:
>> +		colorspace->ycbcrEncoding = ColorSpace::YcbcrEncoding::Rec2020;
>> +		break;
>> +	case GST_VIDEO_COLOR_MATRIX_RGB:
> Shouldn't this map to YcbcrEncoding::None ?


YcbcrEncoding for sRGB as per Kernel is V4L2_YCBCR_ENC_SYCC which is 
deprecated so V4L2_YCBCR_ENC_601  is used instead [1]

The same has been applied *already* in libcamera codebase:

const ColorSpace ColorSpace::Srgb = {
         Primaries::Rec709,
         TransferFunction::Srgb,
         YcbcrEncoding::Rec601,
         Range::Limited
};

So we shouldn't map GST_VIDEO_COLOR_MATRIX_RGB to YcbcrEncoding::None 
but to Rec601 instead.

Now two question arises for reverse mapping it i.e. what should:

    a) YcbcrEncding::None map to

    b) YcbcrEncding::Rec601 map to.

For b) we have two contenders:

     GST_VIDEO_COLOR_MATRIX_BT601
     GST_VIDEO_COLOR_MATRIX_RGB

For a) I think GST_VIDEO_COLOR_MATRIX_RGB would be better ?

[1] 
https://git.linuxtv.org/media_tree.git/tree/include/uapi/linux/videodev2.h#n344

>
>> +	case GST_VIDEO_COLOR_MATRIX_UNKNOWN:
>> +		colorspace->ycbcrEncoding = ColorSpace::YcbcrEncoding::Default;
>> +		break;
>> +	default:
>> +		GST_WARNING("Unknown colorimetry matrix %d", colorimetry->matrix);
>> +	}
>> +
>> +	switch (colorimetry->range) {
>> +	case GST_VIDEO_COLOR_RANGE_0_255:
>> +		colorspace->range = ColorSpace::Range::Full;
>> +		break;
>> +	case GST_VIDEO_COLOR_RANGE_16_235:
>> +		colorspace->range = ColorSpace::Range::Limited;
>> +		break;
>> +	case GST_VIDEO_COLOR_RANGE_UNKNOWN:
>> +		colorspace->range = ColorSpace::Range::Default;
>> +		break;
>> +	default:
>> +		GST_WARNING("Unknown range in colorimetry %d", colorimetry->range);
>> +	}
>> +
>> +	return colorspace;
>> +}
>> +
>>   static GstVideoFormat
>>   pixel_format_to_gst_format(const PixelFormat &format)
>>   {
>> @@ -139,6 +290,17 @@ gst_libcamera_stream_configuration_to_caps(const StreamConfiguration &stream_cfg
>>   			  "width", G_TYPE_INT, stream_cfg.size.width,
>>   			  "height", G_TYPE_INT, stream_cfg.size.height,
>>   			  nullptr);
>> +
>> +	if (stream_cfg.colorSpace) {
>> +		GstVideoColorimetry colorimetry = colorimetry_from_colorspace(stream_cfg.colorSpace.value());
>> +		gchar *colorimetry_str = gst_video_colorimetry_to_string(&colorimetry);
>> +
>> +		if (colorimetry_str != nullptr)
> 		if (colorimetry_str)
>
>> +			gst_structure_set(s, "colorimetry", G_TYPE_STRING, colorimetry_str, nullptr);
>> +		else
>> +			g_warning("libcamera::ColorSpace found but GstVideoColorimetry unknown");
>> +	}
>> +
>>   	gst_caps_append_structure(caps, s);
>>   
>>   	return caps;
>> @@ -222,6 +384,19 @@ gst_libcamera_configure_stream_from_caps(StreamConfiguration &stream_cfg,
>>   	gst_structure_get_int(s, "height", &height);
>>   	stream_cfg.size.width = width;
>>   	stream_cfg.size.height = height;
>> +
>> +	/* Configure colorimetry */
>> +	if (gst_structure_has_field(s, "colorimetry")) {
>> +		const gchar *colorimetry_caps = gst_structure_get_string(s, "colorimetry");
>> +		GstVideoColorimetry colorimetry;
>> +
>> +		if(gst_video_colorimetry_from_string(&colorimetry, colorimetry_caps)) {
> Missing space after "if".
>
>> +			std::optional<ColorSpace> colorSpace = colorspace_from_colorimetry(&colorimetry);
>> +			stream_cfg.colorSpace = colorSpace;
> 			stream_cfg.colorSpace = colorspace_from_colorimetry(&colorimetry);
>
>> +		} else {
>> +			g_print("Invalid colorimetry %s", colorimetry_caps);
>> +		}
>> +	}
>>   }
>>   
>>   #if !GST_CHECK_VERSION(1, 17, 1)
Umang Jain July 29, 2022, 10:10 a.m. UTC | #7
Hello,

On 7/29/22 13:36, Umang Jain via libcamera-devel wrote:
> Hi Laurent,
>
> My anaylsis on sRGB mapping ...
>
> On 7/27/22 06:47, Laurent Pinchart wrote:
>> Hi Umang and Rishikesh,
>>
>> Thank you for the patch.
>>
>> On Sun, Jul 24, 2022 at 08:13:55PM +0530, Umang Jain via 
>> libcamera-devel wrote:
>>> From: Rishikesh Donadkar <rishikeshdonadkar@gmail.com>
>>>
>>> Provide colorimetry <=> libcamera::ColorSpace mappings via:
>>> - GstVideoColorimetry colorimetry_from_colorspace(colorspace);
>>> - ColorSpace colorspace_from_colorimetry(colorimetry);
>>>
>>> Read the colorimetry field from caps into the stream configuration.
>>> After stream validation, the sensor supported colorimetry will
>>> be retrieved and the caps will be updated accordingly.
>>>
>>> Colorimetry support for gstlibcamerasrc currently undertakes only one
>>> argument. Multiple colorimetry support shall be introduced in
>>> subsequent commits.
>>>
>>> Signed-off-by: Rishikesh Donadkar <rishikeshdonadkar@gmail.com>
>>> Signed-off-by: Umang Jain <umang.jain@ideasonboard.com>
>>> ---
>>>   src/gstreamer/gstlibcamera-utils.cpp | 175 
>>> +++++++++++++++++++++++++++
>>>   1 file changed, 175 insertions(+)
>>>
>>> diff --git a/src/gstreamer/gstlibcamera-utils.cpp 
>>> b/src/gstreamer/gstlibcamera-utils.cpp
>>> index c97c0d43..fb4f0e5c 100644
>>> --- a/src/gstreamer/gstlibcamera-utils.cpp
>>> +++ b/src/gstreamer/gstlibcamera-utils.cpp
>>> @@ -45,6 +45,157 @@ static struct {
>>>       /* \todo NV42 is used in libcamera but is not mapped in 
>>> GStreamer yet. */
>>>   };
>>>   +static GstVideoColorimetry
>>> +colorimetry_from_colorspace(const ColorSpace &colorSpace)
>>> +{
>>> +    GstVideoColorimetry colorimetry;
>> I would initialize the structure with all fields set to UNKNOWN here...
>>
>>> +
>>> +    switch (colorSpace.primaries) {
>>> +    case ColorSpace::Primaries::Rec709:
>>> +        colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_BT709;
>>> +        break;
>>> +    case ColorSpace::Primaries::Rec2020:
>>> +        colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_BT2020;
>>> +        break;
>>> +    case ColorSpace::Primaries::Smpte170m:
>>> +        colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_SMPTE170M;
>>> +        break;
>>> +    default:
>>> +        GST_WARNING("ColorSpace primaries not mapped in 
>>> GstLibcameraSrc");
>>> +        colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_UNKNOWN;
>> ... and drop the default case. As ColorSpace::Primaries is an enum
>> class, if you have cases for all the values, the compiler will warn only
>> if one (or more) of the enum values isn't handled in explicit cases.
>> This way you can avoid warnings, and have a guarantee that if the
>> libcamera colorspaces are later extended, the compiler will remind that
>> this function has to be updated. Same for the other components of the
>> colorspace below.
>>
>>> +    }
>>> +
>>> +    switch (colorSpace.transferFunction) {
>>> +    case ColorSpace::TransferFunction::Rec709:
>>> +        colorimetry.transfer = GST_VIDEO_TRANSFER_BT709;
>>> +        break;
>>> +    case ColorSpace::TransferFunction::Srgb:
>>> +        colorimetry.transfer = GST_VIDEO_TRANSFER_SRGB;
>>> +        break;
>>> +    case ColorSpace::TransferFunction::Linear:
>>> +        colorimetry.transfer = GST_VIDEO_TRANSFER_GAMMA10;
>>> +        break;
>>> +    default:
>>> +        GST_WARNING("ColorSpace transfer function not mapped in 
>>> GstLibcameraSrc");
>>> +        colorimetry.transfer = GST_VIDEO_TRANSFER_UNKNOWN;
>>> +    }
>>> +
>>> +    switch (colorSpace.ycbcrEncoding) {
>>> +    case ColorSpace::YcbcrEncoding::Rec709:
>>> +        colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_BT709;
>>> +        break;
>>> +    case ColorSpace::YcbcrEncoding::Rec2020:
>>> +        colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_BT2020;
>>> +        break;
>>> +    case ColorSpace::YcbcrEncoding::Rec601:
>>> +        colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_BT601;
>>> +        break;
>>> +    default:
>>> +        GST_WARNING("Colorspace YcbcrEncoding not mapped in 
>>> GstLibcameraSrc");
>>> +        colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_UNKNOWN;
>> Should we map YcbcrEncoding::None to GST_VIDEO_COLOR_MATRIX_RGB ?
>
>
> See below.
>
>> Otherwise capturing RGB or raw bayer will produce a warning. Same for
>> the Raw primaries, it seems that the best mapping would be
>> GST_VIDEO_COLOR_PRIMARIES_UNKNOWN (unless I'm missing something), but I
>> don't think we should warn in that case.
>>
>>> +    }
>>> +
>>> +    switch (colorSpace.range) {
>>> +    case ColorSpace::Range::Full:
>>> +        colorimetry.range = GST_VIDEO_COLOR_RANGE_0_255;
>>> +        break;
>>> +    case ColorSpace::Range::Limited:
>>> +        colorimetry.range = GST_VIDEO_COLOR_RANGE_16_235;
>>> +        break;
>>> +    default:
>>> +        GST_WARNING("Colorspace range not mapped in GstLibcameraSrc");
>>> +        colorimetry.range = GST_VIDEO_COLOR_RANGE_UNKNOWN;
>>> +    }
>>> +
>>> +    return colorimetry;
>>> +}
>>> +
>>> +static std::optional<ColorSpace>
>>> +colorspace_from_colorimetry(GstVideoColorimetry *colorimetry)
>> I'd pass a const reference instead of a pointer, as there's no use case
>> for a null colorimetry.
>>
>>> +{
>>> +    std::optional<ColorSpace> colorspace = ColorSpace::Default;
>> No need for std::optional here or in the return type.
>>
>>> +
>>> +    switch (colorimetry->primaries) {
>>> +    case GST_VIDEO_COLOR_PRIMARIES_BT709:
>>> +        colorspace->primaries = ColorSpace::Primaries::Rec709;
>>> +        break;
>>> +    case GST_VIDEO_COLOR_PRIMARIES_BT2020:
>>> +        colorspace->primaries = ColorSpace::Primaries::Rec2020;
>>> +        break;
>>> +    case GST_VIDEO_COLOR_PRIMARIES_SMPTE170M:
>>> +        colorspace->primaries = ColorSpace::Primaries::Smpte170m;
>>> +        break;
>>> +    case GST_VIDEO_COLOR_PRIMARIES_UNKNOWN:
>>> +        colorspace->primaries = ColorSpace::Primaries::Default;
>>> +        break;
>>> +    default:
>>> +        GST_WARNING("Unknown primaries in colorimetry %d", 
>>> colorimetry->primaries);
>>> +    }
>>> +
>>> +    switch (colorimetry->transfer) {
>>> +    /* Tranfer function mappings inspired from v4l2src plugin */
>>> +    case GST_VIDEO_TRANSFER_GAMMA18:
>>> +    case GST_VIDEO_TRANSFER_GAMMA20:
>>> +    case GST_VIDEO_TRANSFER_GAMMA22:
>>> +    case GST_VIDEO_TRANSFER_GAMMA28:
>>> +        GST_WARNING("GAMMA 18, 20, 22, 28 transfer functions not 
>>> supported");
>>> +    /* fallthrough */
>>> +    case GST_VIDEO_TRANSFER_GAMMA10:
>>> +        colorspace->transferFunction = 
>>> ColorSpace::TransferFunction::Linear;
>>> +        break;
>>> +    case GST_VIDEO_TRANSFER_BT601:
>>> +    case GST_VIDEO_TRANSFER_BT2020_12:
>>> +    case GST_VIDEO_TRANSFER_BT2020_10:
>>> +    case GST_VIDEO_TRANSFER_BT709:
>>> +        colorspace->transferFunction = 
>>> ColorSpace::TransferFunction::Rec709;
>>> +        break;
>>> +    case GST_VIDEO_TRANSFER_SRGB:
>>> +        colorspace->transferFunction = 
>>> ColorSpace::TransferFunction::Srgb;
>>> +        break;
>>> +    case GST_VIDEO_TRANSFER_UNKNOWN:
>>> +        colorspace->transferFunction = 
>>> ColorSpace::TransferFunction::Default;
>>> +        break;
>>> +    default:
>>> +        GST_WARNING("Unknown colorimetry transfer %d", 
>>> colorimetry->transfer);
>>> +    }
>>> +
>>> +    switch (colorimetry->matrix) {
>>> +    /* FCC is about the same as BT601 with less digit */
>>> +    case GST_VIDEO_COLOR_MATRIX_FCC:
>>> +    case GST_VIDEO_COLOR_MATRIX_BT601:
>>> +        colorspace->ycbcrEncoding = ColorSpace::YcbcrEncoding::Rec601;
>>> +        break;
>>> +    case GST_VIDEO_COLOR_MATRIX_BT709:
>>> +        colorspace->ycbcrEncoding = ColorSpace::YcbcrEncoding::Rec709;
>>> +        break;
>>> +    case GST_VIDEO_COLOR_MATRIX_BT2020:
>>> +        colorspace->ycbcrEncoding = 
>>> ColorSpace::YcbcrEncoding::Rec2020;
>>> +        break;
>>> +    case GST_VIDEO_COLOR_MATRIX_RGB:
>> Shouldn't this map to YcbcrEncoding::None ?
>
>
> YcbcrEncoding for sRGB as per Kernel is V4L2_YCBCR_ENC_SYCC which is 
> deprecated so V4L2_YCBCR_ENC_601  is used instead [1]
>
> The same has been applied *already* in libcamera codebase:
>
> const ColorSpace ColorSpace::Srgb = {
>         Primaries::Rec709,
>         TransferFunction::Srgb,
>         YcbcrEncoding::Rec601,
>         Range::Limited
> };
>
> So we shouldn't map GST_VIDEO_COLOR_MATRIX_RGB to YcbcrEncoding::None 
> but to Rec601 instead.
>
> Now two question arises for reverse mapping it i.e. what should:
>
>    a) YcbcrEncding::None map to
>
>    b) YcbcrEncding::Rec601 map to.
>
> For b) we have two contenders:
>
>     GST_VIDEO_COLOR_MATRIX_BT601
>     GST_VIDEO_COLOR_MATRIX_RGB


b) is currently handled with:

+       case ColorSpace::YcbcrEncoding::Rec601:
+               if (colorSpace == ColorSpace::Srgb)
+                       colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_RGB;
+               else
+                       colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_BT601;
+               break;

Let's review this in v2 (submitting shortly)

>
> For a) I think GST_VIDEO_COLOR_MATRIX_RGB would be better ?
>
> [1] 
> https://git.linuxtv.org/media_tree.git/tree/include/uapi/linux/videodev2.h#n344
>
>>
>>> +    case GST_VIDEO_COLOR_MATRIX_UNKNOWN:
>>> +        colorspace->ycbcrEncoding = 
>>> ColorSpace::YcbcrEncoding::Default;
>>> +        break;
>>> +    default:
>>> +        GST_WARNING("Unknown colorimetry matrix %d", 
>>> colorimetry->matrix);
>>> +    }
>>> +
>>> +    switch (colorimetry->range) {
>>> +    case GST_VIDEO_COLOR_RANGE_0_255:
>>> +        colorspace->range = ColorSpace::Range::Full;
>>> +        break;
>>> +    case GST_VIDEO_COLOR_RANGE_16_235:
>>> +        colorspace->range = ColorSpace::Range::Limited;
>>> +        break;
>>> +    case GST_VIDEO_COLOR_RANGE_UNKNOWN:
>>> +        colorspace->range = ColorSpace::Range::Default;
>>> +        break;
>>> +    default:
>>> +        GST_WARNING("Unknown range in colorimetry %d", 
>>> colorimetry->range);
>>> +    }
>>> +
>>> +    return colorspace;
>>> +}
>>> +
>>>   static GstVideoFormat
>>>   pixel_format_to_gst_format(const PixelFormat &format)
>>>   {
>>> @@ -139,6 +290,17 @@ 
>>> gst_libcamera_stream_configuration_to_caps(const StreamConfiguration 
>>> &stream_cfg
>>>                 "width", G_TYPE_INT, stream_cfg.size.width,
>>>                 "height", G_TYPE_INT, stream_cfg.size.height,
>>>                 nullptr);
>>> +
>>> +    if (stream_cfg.colorSpace) {
>>> +        GstVideoColorimetry colorimetry = 
>>> colorimetry_from_colorspace(stream_cfg.colorSpace.value());
>>> +        gchar *colorimetry_str = 
>>> gst_video_colorimetry_to_string(&colorimetry);
>>> +
>>> +        if (colorimetry_str != nullptr)
>>         if (colorimetry_str)
>>
>>> +            gst_structure_set(s, "colorimetry", G_TYPE_STRING, 
>>> colorimetry_str, nullptr);
>>> +        else
>>> +            g_warning("libcamera::ColorSpace found but 
>>> GstVideoColorimetry unknown");
>>> +    }
>>> +
>>>       gst_caps_append_structure(caps, s);
>>>         return caps;
>>> @@ -222,6 +384,19 @@ 
>>> gst_libcamera_configure_stream_from_caps(StreamConfiguration 
>>> &stream_cfg,
>>>       gst_structure_get_int(s, "height", &height);
>>>       stream_cfg.size.width = width;
>>>       stream_cfg.size.height = height;
>>> +
>>> +    /* Configure colorimetry */
>>> +    if (gst_structure_has_field(s, "colorimetry")) {
>>> +        const gchar *colorimetry_caps = gst_structure_get_string(s, 
>>> "colorimetry");
>>> +        GstVideoColorimetry colorimetry;
>>> +
>>> + if(gst_video_colorimetry_from_string(&colorimetry, 
>>> colorimetry_caps)) {
>> Missing space after "if".
>>
>>> + std::optional<ColorSpace> colorSpace = 
>>> colorspace_from_colorimetry(&colorimetry);
>>> +            stream_cfg.colorSpace = colorSpace;
>>             stream_cfg.colorSpace = 
>> colorspace_from_colorimetry(&colorimetry);
>>
>>> +        } else {
>>> +            g_print("Invalid colorimetry %s", colorimetry_caps);
>>> +        }
>>> +    }
>>>   }
>>>     #if !GST_CHECK_VERSION(1, 17, 1)
Laurent Pinchart July 29, 2022, 2:54 p.m. UTC | #8
Hi Umang,

On Fri, Jul 29, 2022 at 03:40:52PM +0530, Umang Jain wrote:
> On 7/29/22 13:36, Umang Jain via libcamera-devel wrote:
> > Hi Laurent,
> >
> > My anaylsis on sRGB mapping ...
> >
> > On 7/27/22 06:47, Laurent Pinchart wrote:
> >> Hi Umang and Rishikesh,
> >>
> >> Thank you for the patch.
> >>
> >> On Sun, Jul 24, 2022 at 08:13:55PM +0530, Umang Jain via 
> >> libcamera-devel wrote:
> >>> From: Rishikesh Donadkar <rishikeshdonadkar@gmail.com>
> >>>
> >>> Provide colorimetry <=> libcamera::ColorSpace mappings via:
> >>> - GstVideoColorimetry colorimetry_from_colorspace(colorspace);
> >>> - ColorSpace colorspace_from_colorimetry(colorimetry);
> >>>
> >>> Read the colorimetry field from caps into the stream configuration.
> >>> After stream validation, the sensor supported colorimetry will
> >>> be retrieved and the caps will be updated accordingly.
> >>>
> >>> Colorimetry support for gstlibcamerasrc currently undertakes only one
> >>> argument. Multiple colorimetry support shall be introduced in
> >>> subsequent commits.
> >>>
> >>> Signed-off-by: Rishikesh Donadkar <rishikeshdonadkar@gmail.com>
> >>> Signed-off-by: Umang Jain <umang.jain@ideasonboard.com>
> >>> ---
> >>>   src/gstreamer/gstlibcamera-utils.cpp | 175 +++++++++++++++++++++++++++
> >>>   1 file changed, 175 insertions(+)
> >>>
> >>> diff --git a/src/gstreamer/gstlibcamera-utils.cpp 
> >>> b/src/gstreamer/gstlibcamera-utils.cpp
> >>> index c97c0d43..fb4f0e5c 100644
> >>> --- a/src/gstreamer/gstlibcamera-utils.cpp
> >>> +++ b/src/gstreamer/gstlibcamera-utils.cpp
> >>> @@ -45,6 +45,157 @@ static struct {
> >>>       /* \todo NV42 is used in libcamera but is not mapped in GStreamer yet. */
> >>>   };
> >>>   +static GstVideoColorimetry
> >>> +colorimetry_from_colorspace(const ColorSpace &colorSpace)
> >>> +{
> >>> +    GstVideoColorimetry colorimetry;
> >> 
> >> I would initialize the structure with all fields set to UNKNOWN here...
> >>
> >>> +
> >>> +    switch (colorSpace.primaries) {
> >>> +    case ColorSpace::Primaries::Rec709:
> >>> +        colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_BT709;
> >>> +        break;
> >>> +    case ColorSpace::Primaries::Rec2020:
> >>> +        colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_BT2020;
> >>> +        break;
> >>> +    case ColorSpace::Primaries::Smpte170m:
> >>> +        colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_SMPTE170M;
> >>> +        break;
> >>> +    default:
> >>> +        GST_WARNING("ColorSpace primaries not mapped in 
> >>> GstLibcameraSrc");
> >>> +        colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_UNKNOWN;
> >> 
> >> ... and drop the default case. As ColorSpace::Primaries is an enum
> >> class, if you have cases for all the values, the compiler will warn only
> >> if one (or more) of the enum values isn't handled in explicit cases.
> >> This way you can avoid warnings, and have a guarantee that if the
> >> libcamera colorspaces are later extended, the compiler will remind that
> >> this function has to be updated. Same for the other components of the
> >> colorspace below.
> >>
> >>> +    }
> >>> +
> >>> +    switch (colorSpace.transferFunction) {
> >>> +    case ColorSpace::TransferFunction::Rec709:
> >>> +        colorimetry.transfer = GST_VIDEO_TRANSFER_BT709;
> >>> +        break;
> >>> +    case ColorSpace::TransferFunction::Srgb:
> >>> +        colorimetry.transfer = GST_VIDEO_TRANSFER_SRGB;
> >>> +        break;
> >>> +    case ColorSpace::TransferFunction::Linear:
> >>> +        colorimetry.transfer = GST_VIDEO_TRANSFER_GAMMA10;
> >>> +        break;
> >>> +    default:
> >>> +        GST_WARNING("ColorSpace transfer function not mapped in GstLibcameraSrc");
> >>> +        colorimetry.transfer = GST_VIDEO_TRANSFER_UNKNOWN;
> >>> +    }
> >>> +
> >>> +    switch (colorSpace.ycbcrEncoding) {
> >>> +    case ColorSpace::YcbcrEncoding::Rec709:
> >>> +        colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_BT709;
> >>> +        break;
> >>> +    case ColorSpace::YcbcrEncoding::Rec2020:
> >>> +        colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_BT2020;
> >>> +        break;
> >>> +    case ColorSpace::YcbcrEncoding::Rec601:
> >>> +        colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_BT601;
> >>> +        break;
> >>> +    default:
> >>> +        GST_WARNING("Colorspace YcbcrEncoding not mapped in GstLibcameraSrc");
> >>> +        colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_UNKNOWN;
> >> 
> >> Should we map YcbcrEncoding::None to GST_VIDEO_COLOR_MATRIX_RGB ?
> >
> > See below.
> >
> >> Otherwise capturing RGB or raw bayer will produce a warning. Same for
> >> the Raw primaries, it seems that the best mapping would be
> >> GST_VIDEO_COLOR_PRIMARIES_UNKNOWN (unless I'm missing something), but I
> >> don't think we should warn in that case.
> >>
> >>> +    }
> >>> +
> >>> +    switch (colorSpace.range) {
> >>> +    case ColorSpace::Range::Full:
> >>> +        colorimetry.range = GST_VIDEO_COLOR_RANGE_0_255;
> >>> +        break;
> >>> +    case ColorSpace::Range::Limited:
> >>> +        colorimetry.range = GST_VIDEO_COLOR_RANGE_16_235;
> >>> +        break;
> >>> +    default:
> >>> +        GST_WARNING("Colorspace range not mapped in GstLibcameraSrc");
> >>> +        colorimetry.range = GST_VIDEO_COLOR_RANGE_UNKNOWN;
> >>> +    }
> >>> +
> >>> +    return colorimetry;
> >>> +}
> >>> +
> >>> +static std::optional<ColorSpace>
> >>> +colorspace_from_colorimetry(GstVideoColorimetry *colorimetry)
> >> 
> >> I'd pass a const reference instead of a pointer, as there's no use case
> >> for a null colorimetry.
> >>
> >>> +{
> >>> +    std::optional<ColorSpace> colorspace = ColorSpace::Default;
> >> 
> >> No need for std::optional here or in the return type.
> >>
> >>> +
> >>> +    switch (colorimetry->primaries) {
> >>> +    case GST_VIDEO_COLOR_PRIMARIES_BT709:
> >>> +        colorspace->primaries = ColorSpace::Primaries::Rec709;
> >>> +        break;
> >>> +    case GST_VIDEO_COLOR_PRIMARIES_BT2020:
> >>> +        colorspace->primaries = ColorSpace::Primaries::Rec2020;
> >>> +        break;
> >>> +    case GST_VIDEO_COLOR_PRIMARIES_SMPTE170M:
> >>> +        colorspace->primaries = ColorSpace::Primaries::Smpte170m;
> >>> +        break;
> >>> +    case GST_VIDEO_COLOR_PRIMARIES_UNKNOWN:
> >>> +        colorspace->primaries = ColorSpace::Primaries::Default;
> >>> +        break;
> >>> +    default:
> >>> +        GST_WARNING("Unknown primaries in colorimetry %d", colorimetry->primaries);
> >>> +    }
> >>> +
> >>> +    switch (colorimetry->transfer) {
> >>> +    /* Tranfer function mappings inspired from v4l2src plugin */
> >>> +    case GST_VIDEO_TRANSFER_GAMMA18:
> >>> +    case GST_VIDEO_TRANSFER_GAMMA20:
> >>> +    case GST_VIDEO_TRANSFER_GAMMA22:
> >>> +    case GST_VIDEO_TRANSFER_GAMMA28:
> >>> +        GST_WARNING("GAMMA 18, 20, 22, 28 transfer functions not supported");
> >>> +    /* fallthrough */
> >>> +    case GST_VIDEO_TRANSFER_GAMMA10:
> >>> +        colorspace->transferFunction = ColorSpace::TransferFunction::Linear;
> >>> +        break;
> >>> +    case GST_VIDEO_TRANSFER_BT601:
> >>> +    case GST_VIDEO_TRANSFER_BT2020_12:
> >>> +    case GST_VIDEO_TRANSFER_BT2020_10:
> >>> +    case GST_VIDEO_TRANSFER_BT709:
> >>> +        colorspace->transferFunction = ColorSpace::TransferFunction::Rec709;
> >>> +        break;
> >>> +    case GST_VIDEO_TRANSFER_SRGB:
> >>> +        colorspace->transferFunction = ColorSpace::TransferFunction::Srgb;
> >>> +        break;
> >>> +    case GST_VIDEO_TRANSFER_UNKNOWN:
> >>> +        colorspace->transferFunction = ColorSpace::TransferFunction::Default;
> >>> +        break;
> >>> +    default:
> >>> +        GST_WARNING("Unknown colorimetry transfer %d", colorimetry->transfer);
> >>> +    }
> >>> +
> >>> +    switch (colorimetry->matrix) {
> >>> +    /* FCC is about the same as BT601 with less digit */
> >>> +    case GST_VIDEO_COLOR_MATRIX_FCC:
> >>> +    case GST_VIDEO_COLOR_MATRIX_BT601:
> >>> +        colorspace->ycbcrEncoding = ColorSpace::YcbcrEncoding::Rec601;
> >>> +        break;
> >>> +    case GST_VIDEO_COLOR_MATRIX_BT709:
> >>> +        colorspace->ycbcrEncoding = ColorSpace::YcbcrEncoding::Rec709;
> >>> +        break;
> >>> +    case GST_VIDEO_COLOR_MATRIX_BT2020:
> >>> +        colorspace->ycbcrEncoding = ColorSpace::YcbcrEncoding::Rec2020;
> >>> +        break;
> >>> +    case GST_VIDEO_COLOR_MATRIX_RGB:
> >> 
> >> Shouldn't this map to YcbcrEncoding::None ?
> >
> > YcbcrEncoding for sRGB as per Kernel is V4L2_YCBCR_ENC_SYCC which is 
> > deprecated so V4L2_YCBCR_ENC_601  is used instead [1]
> >
> > The same has been applied *already* in libcamera codebase:
> >
> > const ColorSpace ColorSpace::Srgb = {
> >         Primaries::Rec709,
> >         TransferFunction::Srgb,
> >         YcbcrEncoding::Rec601,
> >         Range::Limited
> > };
> >
> > So we shouldn't map GST_VIDEO_COLOR_MATRIX_RGB to YcbcrEncoding::None 
> > but to Rec601 instead.

I'm not sure that's right. GST_VIDEO_COLOR_MATRIX_RGB is defined as 

GST_VIDEO_COLOR_MATRIX_RGB (1) – identity matrix. Order of coefficients
is actually GBR, also IEC 61966-2-1 (sRGB) 

I understand this as meaning that the RGB data is not converted to YUV,
which is expressed in libcamera as YcbcrEncoding::None:

 * \var ColorSpace::YcbcrEncoding::None
 * \brief There is no defined Y'CbCr encoding (used for non-YUV formats)

The V4L2 sRGB colorspace [2] definition is confusing. sRGB is an RGB
colorspace, so it should have no YUV encoding, and use full range. The
sYCC colorspace is sRGB encoded in YUV using the Rec601 encoding, also
using full range. As it's typical for devices that produce sRGB images
to encode them to YUV using the Rec601 encoding and limited range, the
V4L2 sRGB colorspace defines default encoding and quantization range as
Rec601 and limited. However, when the pixels are stored as RGB, those
two parameters don't apply.

Note also how GStreamer defines the SRGB colorspace ([3]):

  MAKE_COLORIMETRY (SRGB, _0_255, RGB, SRGB, BT709),

We could do the same and define sRGB as

const ColorSpace ColorSpace::Srgb = {
        Primaries::Rec709,
        TransferFunction::Srgb,
        YcbcrEncoding::None,
        Range::Full
};

but we would then need to adapt code that produce YUV-encoded sRGB. We
could define, for that purpose,

const ColorSpace ColorSpace::Sycc = {
        Primaries::Rec709,
        TransferFunction::Srgb,
        YcbcrEncoding::Rec601,
        Range::Full
};

but that still wouldn't match the V4L2 definition. It's not necessarily
a problem though, devices can produce { Rec709, Srgb, Rec601, Limited }
without the need to explicitly define a ColorSpace preset in libcamera.
I've alos just realized that the above is identical to ColorSpace::Jpeg.

Another option would be to keep the ColorSpace::Srgb definition as-is
and document that the YCbCrEncoding and Range must be ignored for
non-YUV formats, but I don't like that much, it sounds confusing.

I'm tempted to bite the bullet and define ColorSpace::Srgb with
YcbcrEncoding::None and Range::Full, and never return ColorSpace::Srgb
when capturing YUV data. A pipeline handler that gets ColorSpace::Srgb
from an application would turn it into { Rec709, Srgb, Rec601, Limited }
for YUV streams (assuming that the hardware supports that of course).

David, how is ColorSpace::Srgb interpreted by the Raspberry Pi ISP when
producing YUV data ?

[2] https://linuxtv.org/downloads/v4l-dvb-apis/userspace-api/v4l/colorspaces-details.html#colorspace-srgb-v4l2-colorspace-srgb
[3] https://gitlab.freedesktop.org/gstreamer/gstreamer/-/blob/main/subprojects/gst-plugins-base/gst-libs/gst/video/video-color.c#L66

> > Now two question arises for reverse mapping it i.e. what should:
> >
> >    a) YcbcrEncding::None map to
> >
> >    b) YcbcrEncding::Rec601 map to.
> >
> > For b) we have two contenders:
> >
> >     GST_VIDEO_COLOR_MATRIX_BT601
> >     GST_VIDEO_COLOR_MATRIX_RGB
> 
> b) is currently handled with:
> 
> +       case ColorSpace::YcbcrEncoding::Rec601:
> +               if (colorSpace == ColorSpace::Srgb)
> +                       colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_RGB;
> +               else
> +                       colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_BT601;
> +               break;
> 
> Let's review this in v2 (submitting shortly)

As per the above, I think YcbcrEncding::None should map to
GST_VIDEO_COLOR_MATRIX_RGB and YcbcrEncding::Rec601 to
GST_VIDEO_COLOR_MATRIX_BT601, unconditionally.

> > For a) I think GST_VIDEO_COLOR_MATRIX_RGB would be better ?
> >
> > [1] https://git.linuxtv.org/media_tree.git/tree/include/uapi/linux/videodev2.h#n344
> >
> >>> +    case GST_VIDEO_COLOR_MATRIX_UNKNOWN:
> >>> +        colorspace->ycbcrEncoding = ColorSpace::YcbcrEncoding::Default;
> >>> +        break;
> >>> +    default:
> >>> +        GST_WARNING("Unknown colorimetry matrix %d", colorimetry->matrix);
> >>> +    }
> >>> +
> >>> +    switch (colorimetry->range) {
> >>> +    case GST_VIDEO_COLOR_RANGE_0_255:
> >>> +        colorspace->range = ColorSpace::Range::Full;
> >>> +        break;
> >>> +    case GST_VIDEO_COLOR_RANGE_16_235:
> >>> +        colorspace->range = ColorSpace::Range::Limited;
> >>> +        break;
> >>> +    case GST_VIDEO_COLOR_RANGE_UNKNOWN:
> >>> +        colorspace->range = ColorSpace::Range::Default;
> >>> +        break;
> >>> +    default:
> >>> +        GST_WARNING("Unknown range in colorimetry %d", colorimetry->range);
> >>> +    }
> >>> +
> >>> +    return colorspace;
> >>> +}
> >>> +
> >>>   static GstVideoFormat
> >>>   pixel_format_to_gst_format(const PixelFormat &format)
> >>>   {
> >>> @@ -139,6 +290,17 @@ gst_libcamera_stream_configuration_to_caps(const StreamConfiguration &stream_cfg
> >>>                 "width", G_TYPE_INT, stream_cfg.size.width,
> >>>                 "height", G_TYPE_INT, stream_cfg.size.height,
> >>>                 nullptr);
> >>> +
> >>> +    if (stream_cfg.colorSpace) {
> >>> +        GstVideoColorimetry colorimetry = colorimetry_from_colorspace(stream_cfg.colorSpace.value());
> >>> +        gchar *colorimetry_str = gst_video_colorimetry_to_string(&colorimetry);
> >>> +
> >>> +        if (colorimetry_str != nullptr)
> >> 
> >>         if (colorimetry_str)
> >>
> >>> +            gst_structure_set(s, "colorimetry", G_TYPE_STRING, colorimetry_str, nullptr);
> >>> +        else
> >>> +            g_warning("libcamera::ColorSpace found but GstVideoColorimetry unknown");
> >>> +    }
> >>> +
> >>>       gst_caps_append_structure(caps, s);
> >>>         return caps;
> >>> @@ -222,6 +384,19 @@ gst_libcamera_configure_stream_from_caps(StreamConfiguration &stream_cfg,
> >>>       gst_structure_get_int(s, "height", &height);
> >>>       stream_cfg.size.width = width;
> >>>       stream_cfg.size.height = height;
> >>> +
> >>> +    /* Configure colorimetry */
> >>> +    if (gst_structure_has_field(s, "colorimetry")) {
> >>> +        const gchar *colorimetry_caps = gst_structure_get_string(s, "colorimetry");
> >>> +        GstVideoColorimetry colorimetry;
> >>> +
> >>> + if(gst_video_colorimetry_from_string(&colorimetry, colorimetry_caps)) {
> >> 
> >> Missing space after "if".
> >>
> >>> + std::optional<ColorSpace> colorSpace = colorspace_from_colorimetry(&colorimetry);
> >>> +            stream_cfg.colorSpace = colorSpace;
> >> 
> >>             stream_cfg.colorSpace = colorspace_from_colorimetry(&colorimetry);
> >>
> >>> +        } else {
> >>> +            g_print("Invalid colorimetry %s", colorimetry_caps);
> >>> +        }
> >>> +    }
> >>>   }
> >>>     #if !GST_CHECK_VERSION(1, 17, 1)
Nicolas Dufresne July 29, 2022, 3:15 p.m. UTC | #9
Le vendredi 29 juillet 2022 à 17:54 +0300, Laurent Pinchart a écrit :
> Hi Umang,
> 
> On Fri, Jul 29, 2022 at 03:40:52PM +0530, Umang Jain wrote:
> > On 7/29/22 13:36, Umang Jain via libcamera-devel wrote:
> > > Hi Laurent,
> > > 
> > > My anaylsis on sRGB mapping ...
> > > 
> > > On 7/27/22 06:47, Laurent Pinchart wrote:
> > > > Hi Umang and Rishikesh,
> > > > 
> > > > Thank you for the patch.
> > > > 
> > > > On Sun, Jul 24, 2022 at 08:13:55PM +0530, Umang Jain via 
> > > > libcamera-devel wrote:
> > > > > From: Rishikesh Donadkar <rishikeshdonadkar@gmail.com>
> > > > > 
> > > > > Provide colorimetry <=> libcamera::ColorSpace mappings via:
> > > > > - GstVideoColorimetry colorimetry_from_colorspace(colorspace);
> > > > > - ColorSpace colorspace_from_colorimetry(colorimetry);
> > > > > 
> > > > > Read the colorimetry field from caps into the stream configuration.
> > > > > After stream validation, the sensor supported colorimetry will
> > > > > be retrieved and the caps will be updated accordingly.
> > > > > 
> > > > > Colorimetry support for gstlibcamerasrc currently undertakes only one
> > > > > argument. Multiple colorimetry support shall be introduced in
> > > > > subsequent commits.
> > > > > 
> > > > > Signed-off-by: Rishikesh Donadkar <rishikeshdonadkar@gmail.com>
> > > > > Signed-off-by: Umang Jain <umang.jain@ideasonboard.com>
> > > > > ---
> > > > >   src/gstreamer/gstlibcamera-utils.cpp | 175 +++++++++++++++++++++++++++
> > > > >   1 file changed, 175 insertions(+)
> > > > > 
> > > > > diff --git a/src/gstreamer/gstlibcamera-utils.cpp 
> > > > > b/src/gstreamer/gstlibcamera-utils.cpp
> > > > > index c97c0d43..fb4f0e5c 100644
> > > > > --- a/src/gstreamer/gstlibcamera-utils.cpp
> > > > > +++ b/src/gstreamer/gstlibcamera-utils.cpp
> > > > > @@ -45,6 +45,157 @@ static struct {
> > > > >       /* \todo NV42 is used in libcamera but is not mapped in GStreamer yet. */
> > > > >   };
> > > > >   +static GstVideoColorimetry
> > > > > +colorimetry_from_colorspace(const ColorSpace &colorSpace)
> > > > > +{
> > > > > +    GstVideoColorimetry colorimetry;
> > > > 
> > > > I would initialize the structure with all fields set to UNKNOWN here...
> > > > 
> > > > > +
> > > > > +    switch (colorSpace.primaries) {
> > > > > +    case ColorSpace::Primaries::Rec709:
> > > > > +        colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_BT709;
> > > > > +        break;
> > > > > +    case ColorSpace::Primaries::Rec2020:
> > > > > +        colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_BT2020;
> > > > > +        break;
> > > > > +    case ColorSpace::Primaries::Smpte170m:
> > > > > +        colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_SMPTE170M;
> > > > > +        break;
> > > > > +    default:
> > > > > +        GST_WARNING("ColorSpace primaries not mapped in 
> > > > > GstLibcameraSrc");
> > > > > +        colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_UNKNOWN;
> > > > 
> > > > ... and drop the default case. As ColorSpace::Primaries is an enum
> > > > class, if you have cases for all the values, the compiler will warn only
> > > > if one (or more) of the enum values isn't handled in explicit cases.
> > > > This way you can avoid warnings, and have a guarantee that if the
> > > > libcamera colorspaces are later extended, the compiler will remind that
> > > > this function has to be updated. Same for the other components of the
> > > > colorspace below.
> > > > 
> > > > > +    }
> > > > > +
> > > > > +    switch (colorSpace.transferFunction) {
> > > > > +    case ColorSpace::TransferFunction::Rec709:
> > > > > +        colorimetry.transfer = GST_VIDEO_TRANSFER_BT709;
> > > > > +        break;
> > > > > +    case ColorSpace::TransferFunction::Srgb:
> > > > > +        colorimetry.transfer = GST_VIDEO_TRANSFER_SRGB;
> > > > > +        break;
> > > > > +    case ColorSpace::TransferFunction::Linear:
> > > > > +        colorimetry.transfer = GST_VIDEO_TRANSFER_GAMMA10;
> > > > > +        break;
> > > > > +    default:
> > > > > +        GST_WARNING("ColorSpace transfer function not mapped in GstLibcameraSrc");
> > > > > +        colorimetry.transfer = GST_VIDEO_TRANSFER_UNKNOWN;
> > > > > +    }
> > > > > +
> > > > > +    switch (colorSpace.ycbcrEncoding) {
> > > > > +    case ColorSpace::YcbcrEncoding::Rec709:
> > > > > +        colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_BT709;
> > > > > +        break;
> > > > > +    case ColorSpace::YcbcrEncoding::Rec2020:
> > > > > +        colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_BT2020;
> > > > > +        break;
> > > > > +    case ColorSpace::YcbcrEncoding::Rec601:
> > > > > +        colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_BT601;
> > > > > +        break;
> > > > > +    default:
> > > > > +        GST_WARNING("Colorspace YcbcrEncoding not mapped in GstLibcameraSrc");
> > > > > +        colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_UNKNOWN;
> > > > 
> > > > Should we map YcbcrEncoding::None to GST_VIDEO_COLOR_MATRIX_RGB ?
> > > 
> > > See below.
> > > 
> > > > Otherwise capturing RGB or raw bayer will produce a warning. Same for
> > > > the Raw primaries, it seems that the best mapping would be
> > > > GST_VIDEO_COLOR_PRIMARIES_UNKNOWN (unless I'm missing something), but I
> > > > don't think we should warn in that case.
> > > > 
> > > > > +    }
> > > > > +
> > > > > +    switch (colorSpace.range) {
> > > > > +    case ColorSpace::Range::Full:
> > > > > +        colorimetry.range = GST_VIDEO_COLOR_RANGE_0_255;
> > > > > +        break;
> > > > > +    case ColorSpace::Range::Limited:
> > > > > +        colorimetry.range = GST_VIDEO_COLOR_RANGE_16_235;
> > > > > +        break;
> > > > > +    default:
> > > > > +        GST_WARNING("Colorspace range not mapped in GstLibcameraSrc");
> > > > > +        colorimetry.range = GST_VIDEO_COLOR_RANGE_UNKNOWN;
> > > > > +    }
> > > > > +
> > > > > +    return colorimetry;
> > > > > +}
> > > > > +
> > > > > +static std::optional<ColorSpace>
> > > > > +colorspace_from_colorimetry(GstVideoColorimetry *colorimetry)
> > > > 
> > > > I'd pass a const reference instead of a pointer, as there's no use case
> > > > for a null colorimetry.
> > > > 
> > > > > +{
> > > > > +    std::optional<ColorSpace> colorspace = ColorSpace::Default;
> > > > 
> > > > No need for std::optional here or in the return type.
> > > > 
> > > > > +
> > > > > +    switch (colorimetry->primaries) {
> > > > > +    case GST_VIDEO_COLOR_PRIMARIES_BT709:
> > > > > +        colorspace->primaries = ColorSpace::Primaries::Rec709;
> > > > > +        break;
> > > > > +    case GST_VIDEO_COLOR_PRIMARIES_BT2020:
> > > > > +        colorspace->primaries = ColorSpace::Primaries::Rec2020;
> > > > > +        break;
> > > > > +    case GST_VIDEO_COLOR_PRIMARIES_SMPTE170M:
> > > > > +        colorspace->primaries = ColorSpace::Primaries::Smpte170m;
> > > > > +        break;
> > > > > +    case GST_VIDEO_COLOR_PRIMARIES_UNKNOWN:
> > > > > +        colorspace->primaries = ColorSpace::Primaries::Default;
> > > > > +        break;
> > > > > +    default:
> > > > > +        GST_WARNING("Unknown primaries in colorimetry %d", colorimetry->primaries);
> > > > > +    }
> > > > > +
> > > > > +    switch (colorimetry->transfer) {
> > > > > +    /* Tranfer function mappings inspired from v4l2src plugin */
> > > > > +    case GST_VIDEO_TRANSFER_GAMMA18:
> > > > > +    case GST_VIDEO_TRANSFER_GAMMA20:
> > > > > +    case GST_VIDEO_TRANSFER_GAMMA22:
> > > > > +    case GST_VIDEO_TRANSFER_GAMMA28:
> > > > > +        GST_WARNING("GAMMA 18, 20, 22, 28 transfer functions not supported");
> > > > > +    /* fallthrough */
> > > > > +    case GST_VIDEO_TRANSFER_GAMMA10:
> > > > > +        colorspace->transferFunction = ColorSpace::TransferFunction::Linear;
> > > > > +        break;
> > > > > +    case GST_VIDEO_TRANSFER_BT601:
> > > > > +    case GST_VIDEO_TRANSFER_BT2020_12:
> > > > > +    case GST_VIDEO_TRANSFER_BT2020_10:
> > > > > +    case GST_VIDEO_TRANSFER_BT709:
> > > > > +        colorspace->transferFunction = ColorSpace::TransferFunction::Rec709;
> > > > > +        break;
> > > > > +    case GST_VIDEO_TRANSFER_SRGB:
> > > > > +        colorspace->transferFunction = ColorSpace::TransferFunction::Srgb;
> > > > > +        break;
> > > > > +    case GST_VIDEO_TRANSFER_UNKNOWN:
> > > > > +        colorspace->transferFunction = ColorSpace::TransferFunction::Default;
> > > > > +        break;
> > > > > +    default:
> > > > > +        GST_WARNING("Unknown colorimetry transfer %d", colorimetry->transfer);
> > > > > +    }
> > > > > +
> > > > > +    switch (colorimetry->matrix) {
> > > > > +    /* FCC is about the same as BT601 with less digit */
> > > > > +    case GST_VIDEO_COLOR_MATRIX_FCC:
> > > > > +    case GST_VIDEO_COLOR_MATRIX_BT601:
> > > > > +        colorspace->ycbcrEncoding = ColorSpace::YcbcrEncoding::Rec601;
> > > > > +        break;
> > > > > +    case GST_VIDEO_COLOR_MATRIX_BT709:
> > > > > +        colorspace->ycbcrEncoding = ColorSpace::YcbcrEncoding::Rec709;
> > > > > +        break;
> > > > > +    case GST_VIDEO_COLOR_MATRIX_BT2020:
> > > > > +        colorspace->ycbcrEncoding = ColorSpace::YcbcrEncoding::Rec2020;
> > > > > +        break;
> > > > > +    case GST_VIDEO_COLOR_MATRIX_RGB:
> > > > 
> > > > Shouldn't this map to YcbcrEncoding::None ?
> > > 
> > > YcbcrEncoding for sRGB as per Kernel is V4L2_YCBCR_ENC_SYCC which is 
> > > deprecated so V4L2_YCBCR_ENC_601  is used instead [1]
> > > 
> > > The same has been applied *already* in libcamera codebase:
> > > 
> > > const ColorSpace ColorSpace::Srgb = {
> > >         Primaries::Rec709,
> > >         TransferFunction::Srgb,
> > >         YcbcrEncoding::Rec601,
> > >         Range::Limited
> > > };
> > > 
> > > So we shouldn't map GST_VIDEO_COLOR_MATRIX_RGB to YcbcrEncoding::None 
> > > but to Rec601 instead.
> 
> I'm not sure that's right. GST_VIDEO_COLOR_MATRIX_RGB is defined as 
> 
> GST_VIDEO_COLOR_MATRIX_RGB (1) – identity matrix. Order of coefficients
> is actually GBR, also IEC 61966-2-1 (sRGB) 

To add to this, sRGB and GST_VIDEO_COLOR_MATRIX_RGB have absolutely no
relationship. Please try not to confuse things. GST_VIDEO_COLOR_MATRIX_RGB is
strictly used for the case the matrix is not applicable. Notably for all the RGB
type of data. This is clearly a 1 to 1 match with YcbcrEncoding::None.

> 
> I understand this as meaning that the RGB data is not converted to YUV,
> which is expressed in libcamera as YcbcrEncoding::None:
> 
>  * \var ColorSpace::YcbcrEncoding::None
>  * \brief There is no defined Y'CbCr encoding (used for non-YUV formats)
> 
> The V4L2 sRGB colorspace [2] definition is confusing. sRGB is an RGB
> colorspace, so it should have no YUV encoding, and use full range. The
> sYCC colorspace is sRGB encoded in YUV using the Rec601 encoding, also
> using full range. As it's typical for devices that produce sRGB images
> to encode them to YUV using the Rec601 encoding and limited range, the
> V4L2 sRGB colorspace defines default encoding and quantization range as
> Rec601 and limited. However, when the pixels are stored as RGB, those
> two parameters don't apply.
> 
> Note also how GStreamer defines the SRGB colorspace ([3]):
> 
>   MAKE_COLORIMETRY (SRGB, _0_255, RGB, SRGB, BT709),
> 
> We could do the same and define sRGB as
> 
> const ColorSpace ColorSpace::Srgb = {
>         Primaries::Rec709,
>         TransferFunction::Srgb,
>         YcbcrEncoding::None,
>         Range::Full
> };
> 
> but we would then need to adapt code that produce YUV-encoded sRGB. We
> could define, for that purpose,
> 
> const ColorSpace ColorSpace::Sycc = {
>         Primaries::Rec709,
>         TransferFunction::Srgb,
>         YcbcrEncoding::Rec601,
>         Range::Full
> };
> 
> but that still wouldn't match the V4L2 definition. It's not necessarily
> a problem though, devices can produce { Rec709, Srgb, Rec601, Limited }
> without the need to explicitly define a ColorSpace preset in libcamera.
> I've alos just realized that the above is identical to ColorSpace::Jpeg.
> 
> Another option would be to keep the ColorSpace::Srgb definition as-is
> and document that the YCbCrEncoding and Range must be ignored for
> non-YUV formats, but I don't like that much, it sounds confusing.
> 
> I'm tempted to bite the bullet and define ColorSpace::Srgb with
> YcbcrEncoding::None and Range::Full, and never return ColorSpace::Srgb
> when capturing YUV data. A pipeline handler that gets ColorSpace::Srgb
> from an application would turn it into { Rec709, Srgb, Rec601, Limited }
> for YUV streams (assuming that the hardware supports that of course).
> 
> David, how is ColorSpace::Srgb interpreted by the Raspberry Pi ISP when
> producing YUV data ?
> 
> [2] https://linuxtv.org/downloads/v4l-dvb-apis/userspace-api/v4l/colorspaces-details.html#colorspace-srgb-v4l2-colorspace-srgb
> [3] https://gitlab.freedesktop.org/gstreamer/gstreamer/-/blob/main/subprojects/gst-plugins-base/gst-libs/gst/video/video-color.c#L66
> 
> > > Now two question arises for reverse mapping it i.e. what should:
> > > 
> > >    a) YcbcrEncding::None map to
> > > 
> > >    b) YcbcrEncding::Rec601 map to.
> > > 
> > > For b) we have two contenders:
> > > 
> > >     GST_VIDEO_COLOR_MATRIX_BT601
> > >     GST_VIDEO_COLOR_MATRIX_RGB
> > 
> > b) is currently handled with:
> > 
> > +       case ColorSpace::YcbcrEncoding::Rec601:
> > +               if (colorSpace == ColorSpace::Srgb)
> > +                       colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_RGB;
> > +               else
> > +                       colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_BT601;
> > +               break;
> > 
> > Let's review this in v2 (submitting shortly)
> 
> As per the above, I think YcbcrEncding::None should map to
> GST_VIDEO_COLOR_MATRIX_RGB and YcbcrEncding::Rec601 to
> GST_VIDEO_COLOR_MATRIX_BT601, unconditionally.
> 
> > > For a) I think GST_VIDEO_COLOR_MATRIX_RGB would be better ?
> > > 
> > > [1] https://git.linuxtv.org/media_tree.git/tree/include/uapi/linux/videodev2.h#n344
> > > 
> > > > > +    case GST_VIDEO_COLOR_MATRIX_UNKNOWN:
> > > > > +        colorspace->ycbcrEncoding = ColorSpace::YcbcrEncoding::Default;
> > > > > +        break;
> > > > > +    default:
> > > > > +        GST_WARNING("Unknown colorimetry matrix %d", colorimetry->matrix);
> > > > > +    }
> > > > > +
> > > > > +    switch (colorimetry->range) {
> > > > > +    case GST_VIDEO_COLOR_RANGE_0_255:
> > > > > +        colorspace->range = ColorSpace::Range::Full;
> > > > > +        break;
> > > > > +    case GST_VIDEO_COLOR_RANGE_16_235:
> > > > > +        colorspace->range = ColorSpace::Range::Limited;
> > > > > +        break;
> > > > > +    case GST_VIDEO_COLOR_RANGE_UNKNOWN:
> > > > > +        colorspace->range = ColorSpace::Range::Default;
> > > > > +        break;
> > > > > +    default:
> > > > > +        GST_WARNING("Unknown range in colorimetry %d", colorimetry->range);
> > > > > +    }
> > > > > +
> > > > > +    return colorspace;
> > > > > +}
> > > > > +
> > > > >   static GstVideoFormat
> > > > >   pixel_format_to_gst_format(const PixelFormat &format)
> > > > >   {
> > > > > @@ -139,6 +290,17 @@ gst_libcamera_stream_configuration_to_caps(const StreamConfiguration &stream_cfg
> > > > >                 "width", G_TYPE_INT, stream_cfg.size.width,
> > > > >                 "height", G_TYPE_INT, stream_cfg.size.height,
> > > > >                 nullptr);
> > > > > +
> > > > > +    if (stream_cfg.colorSpace) {
> > > > > +        GstVideoColorimetry colorimetry = colorimetry_from_colorspace(stream_cfg.colorSpace.value());
> > > > > +        gchar *colorimetry_str = gst_video_colorimetry_to_string(&colorimetry);
> > > > > +
> > > > > +        if (colorimetry_str != nullptr)
> > > > 
> > > >         if (colorimetry_str)
> > > > 
> > > > > +            gst_structure_set(s, "colorimetry", G_TYPE_STRING, colorimetry_str, nullptr);
> > > > > +        else
> > > > > +            g_warning("libcamera::ColorSpace found but GstVideoColorimetry unknown");
> > > > > +    }
> > > > > +
> > > > >       gst_caps_append_structure(caps, s);
> > > > >         return caps;
> > > > > @@ -222,6 +384,19 @@ gst_libcamera_configure_stream_from_caps(StreamConfiguration &stream_cfg,
> > > > >       gst_structure_get_int(s, "height", &height);
> > > > >       stream_cfg.size.width = width;
> > > > >       stream_cfg.size.height = height;
> > > > > +
> > > > > +    /* Configure colorimetry */
> > > > > +    if (gst_structure_has_field(s, "colorimetry")) {
> > > > > +        const gchar *colorimetry_caps = gst_structure_get_string(s, "colorimetry");
> > > > > +        GstVideoColorimetry colorimetry;
> > > > > +
> > > > > + if(gst_video_colorimetry_from_string(&colorimetry, colorimetry_caps)) {
> > > > 
> > > > Missing space after "if".
> > > > 
> > > > > + std::optional<ColorSpace> colorSpace = colorspace_from_colorimetry(&colorimetry);
> > > > > +            stream_cfg.colorSpace = colorSpace;
> > > > 
> > > >             stream_cfg.colorSpace = colorspace_from_colorimetry(&colorimetry);
> > > > 
> > > > > +        } else {
> > > > > +            g_print("Invalid colorimetry %s", colorimetry_caps);
> > > > > +        }
> > > > > +    }
> > > > >   }
> > > > >     #if !GST_CHECK_VERSION(1, 17, 1)
>
Nicolas Dufresne July 29, 2022, 3:29 p.m. UTC | #10
Hi Umang,

Have you considered creating your baseline based on v4lsrc existing mapping ?
You can check you V4L2 <-> libcamera <-> gstreamer mapping against the v4l2src
v4l2 <-> gst mapping ? I don't pretend this will have no issues at all, but at
leas the mapping exercise base on V4L2 text have been done there once.

https://gitlab.freedesktop.org/gstreamer/gstreamer/-/blob/main/subprojects/gst-plugins-good/sys/v4l2/gstv4l2object.c#L2055

regards,
Nicolas
Laurent Pinchart July 31, 2022, 8:28 p.m. UTC | #11
Hi Nicolas,

(CC'ing David)

On Fri, Jul 29, 2022 at 11:29:30AM -0400, Nicolas Dufresne wrote:
> Hi Umang,
> 
> Have you considered creating your baseline based on v4lsrc existing mapping ?
> You can check you V4L2 <-> libcamera <-> gstreamer mapping against the v4l2src
> v4l2 <-> gst mapping ? I don't pretend this will have no issues at all, but at
> leas the mapping exercise base on V4L2 text have been done there once.
> 
> https://gitlab.freedesktop.org/gstreamer/gstreamer/-/blob/main/subprojects/gst-plugins-good/sys/v4l2/gstv4l2object.c#L2055

Thanks for the pointer.

That logic seems sensible, and likely something we'll want to do in
V4L2Device::fromColorSpace() and V4L2Device::toColorSpace().
Umang Jain Aug. 1, 2022, 11:08 a.m. UTC | #12
Hi Laurent,

On 7/29/22 20:24, Laurent Pinchart wrote:
> Hi Umang,
>
> On Fri, Jul 29, 2022 at 03:40:52PM +0530, Umang Jain wrote:
>> On 7/29/22 13:36, Umang Jain via libcamera-devel wrote:
>>> Hi Laurent,
>>>
>>> My anaylsis on sRGB mapping ...
>>>
>>> On 7/27/22 06:47, Laurent Pinchart wrote:
>>>> Hi Umang and Rishikesh,
>>>>
>>>> Thank you for the patch.
>>>>
>>>> On Sun, Jul 24, 2022 at 08:13:55PM +0530, Umang Jain via
>>>> libcamera-devel wrote:
>>>>> From: Rishikesh Donadkar <rishikeshdonadkar@gmail.com>
>>>>>
>>>>> Provide colorimetry <=> libcamera::ColorSpace mappings via:
>>>>> - GstVideoColorimetry colorimetry_from_colorspace(colorspace);
>>>>> - ColorSpace colorspace_from_colorimetry(colorimetry);
>>>>>
>>>>> Read the colorimetry field from caps into the stream configuration.
>>>>> After stream validation, the sensor supported colorimetry will
>>>>> be retrieved and the caps will be updated accordingly.
>>>>>
>>>>> Colorimetry support for gstlibcamerasrc currently undertakes only one
>>>>> argument. Multiple colorimetry support shall be introduced in
>>>>> subsequent commits.
>>>>>
>>>>> Signed-off-by: Rishikesh Donadkar <rishikeshdonadkar@gmail.com>
>>>>> Signed-off-by: Umang Jain <umang.jain@ideasonboard.com>
>>>>> ---
>>>>>    src/gstreamer/gstlibcamera-utils.cpp | 175 +++++++++++++++++++++++++++
>>>>>    1 file changed, 175 insertions(+)
>>>>>
>>>>> diff --git a/src/gstreamer/gstlibcamera-utils.cpp
>>>>> b/src/gstreamer/gstlibcamera-utils.cpp
>>>>> index c97c0d43..fb4f0e5c 100644
>>>>> --- a/src/gstreamer/gstlibcamera-utils.cpp
>>>>> +++ b/src/gstreamer/gstlibcamera-utils.cpp
>>>>> @@ -45,6 +45,157 @@ static struct {
>>>>>        /* \todo NV42 is used in libcamera but is not mapped in GStreamer yet. */
>>>>>    };
>>>>>    +static GstVideoColorimetry
>>>>> +colorimetry_from_colorspace(const ColorSpace &colorSpace)
>>>>> +{
>>>>> +    GstVideoColorimetry colorimetry;
>>>> I would initialize the structure with all fields set to UNKNOWN here...
>>>>
>>>>> +
>>>>> +    switch (colorSpace.primaries) {
>>>>> +    case ColorSpace::Primaries::Rec709:
>>>>> +        colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_BT709;
>>>>> +        break;
>>>>> +    case ColorSpace::Primaries::Rec2020:
>>>>> +        colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_BT2020;
>>>>> +        break;
>>>>> +    case ColorSpace::Primaries::Smpte170m:
>>>>> +        colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_SMPTE170M;
>>>>> +        break;
>>>>> +    default:
>>>>> +        GST_WARNING("ColorSpace primaries not mapped in
>>>>> GstLibcameraSrc");
>>>>> +        colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_UNKNOWN;
>>>> ... and drop the default case. As ColorSpace::Primaries is an enum
>>>> class, if you have cases for all the values, the compiler will warn only
>>>> if one (or more) of the enum values isn't handled in explicit cases.
>>>> This way you can avoid warnings, and have a guarantee that if the
>>>> libcamera colorspaces are later extended, the compiler will remind that
>>>> this function has to be updated. Same for the other components of the
>>>> colorspace below.
>>>>
>>>>> +    }
>>>>> +
>>>>> +    switch (colorSpace.transferFunction) {
>>>>> +    case ColorSpace::TransferFunction::Rec709:
>>>>> +        colorimetry.transfer = GST_VIDEO_TRANSFER_BT709;
>>>>> +        break;
>>>>> +    case ColorSpace::TransferFunction::Srgb:
>>>>> +        colorimetry.transfer = GST_VIDEO_TRANSFER_SRGB;
>>>>> +        break;
>>>>> +    case ColorSpace::TransferFunction::Linear:
>>>>> +        colorimetry.transfer = GST_VIDEO_TRANSFER_GAMMA10;
>>>>> +        break;
>>>>> +    default:
>>>>> +        GST_WARNING("ColorSpace transfer function not mapped in GstLibcameraSrc");
>>>>> +        colorimetry.transfer = GST_VIDEO_TRANSFER_UNKNOWN;
>>>>> +    }
>>>>> +
>>>>> +    switch (colorSpace.ycbcrEncoding) {
>>>>> +    case ColorSpace::YcbcrEncoding::Rec709:
>>>>> +        colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_BT709;
>>>>> +        break;
>>>>> +    case ColorSpace::YcbcrEncoding::Rec2020:
>>>>> +        colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_BT2020;
>>>>> +        break;
>>>>> +    case ColorSpace::YcbcrEncoding::Rec601:
>>>>> +        colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_BT601;
>>>>> +        break;
>>>>> +    default:
>>>>> +        GST_WARNING("Colorspace YcbcrEncoding not mapped in GstLibcameraSrc");
>>>>> +        colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_UNKNOWN;
>>>> Should we map YcbcrEncoding::None to GST_VIDEO_COLOR_MATRIX_RGB ?
>>> See below.
>>>
>>>> Otherwise capturing RGB or raw bayer will produce a warning. Same for
>>>> the Raw primaries, it seems that the best mapping would be
>>>> GST_VIDEO_COLOR_PRIMARIES_UNKNOWN (unless I'm missing something), but I
>>>> don't think we should warn in that case.
>>>>
>>>>> +    }
>>>>> +
>>>>> +    switch (colorSpace.range) {
>>>>> +    case ColorSpace::Range::Full:
>>>>> +        colorimetry.range = GST_VIDEO_COLOR_RANGE_0_255;
>>>>> +        break;
>>>>> +    case ColorSpace::Range::Limited:
>>>>> +        colorimetry.range = GST_VIDEO_COLOR_RANGE_16_235;
>>>>> +        break;
>>>>> +    default:
>>>>> +        GST_WARNING("Colorspace range not mapped in GstLibcameraSrc");
>>>>> +        colorimetry.range = GST_VIDEO_COLOR_RANGE_UNKNOWN;
>>>>> +    }
>>>>> +
>>>>> +    return colorimetry;
>>>>> +}
>>>>> +
>>>>> +static std::optional<ColorSpace>
>>>>> +colorspace_from_colorimetry(GstVideoColorimetry *colorimetry)
>>>> I'd pass a const reference instead of a pointer, as there's no use case
>>>> for a null colorimetry.
>>>>
>>>>> +{
>>>>> +    std::optional<ColorSpace> colorspace = ColorSpace::Default;
>>>> No need for std::optional here or in the return type.
>>>>
>>>>> +
>>>>> +    switch (colorimetry->primaries) {
>>>>> +    case GST_VIDEO_COLOR_PRIMARIES_BT709:
>>>>> +        colorspace->primaries = ColorSpace::Primaries::Rec709;
>>>>> +        break;
>>>>> +    case GST_VIDEO_COLOR_PRIMARIES_BT2020:
>>>>> +        colorspace->primaries = ColorSpace::Primaries::Rec2020;
>>>>> +        break;
>>>>> +    case GST_VIDEO_COLOR_PRIMARIES_SMPTE170M:
>>>>> +        colorspace->primaries = ColorSpace::Primaries::Smpte170m;
>>>>> +        break;
>>>>> +    case GST_VIDEO_COLOR_PRIMARIES_UNKNOWN:
>>>>> +        colorspace->primaries = ColorSpace::Primaries::Default;
>>>>> +        break;
>>>>> +    default:
>>>>> +        GST_WARNING("Unknown primaries in colorimetry %d", colorimetry->primaries);
>>>>> +    }
>>>>> +
>>>>> +    switch (colorimetry->transfer) {
>>>>> +    /* Tranfer function mappings inspired from v4l2src plugin */
>>>>> +    case GST_VIDEO_TRANSFER_GAMMA18:
>>>>> +    case GST_VIDEO_TRANSFER_GAMMA20:
>>>>> +    case GST_VIDEO_TRANSFER_GAMMA22:
>>>>> +    case GST_VIDEO_TRANSFER_GAMMA28:
>>>>> +        GST_WARNING("GAMMA 18, 20, 22, 28 transfer functions not supported");
>>>>> +    /* fallthrough */
>>>>> +    case GST_VIDEO_TRANSFER_GAMMA10:
>>>>> +        colorspace->transferFunction = ColorSpace::TransferFunction::Linear;
>>>>> +        break;
>>>>> +    case GST_VIDEO_TRANSFER_BT601:
>>>>> +    case GST_VIDEO_TRANSFER_BT2020_12:
>>>>> +    case GST_VIDEO_TRANSFER_BT2020_10:
>>>>> +    case GST_VIDEO_TRANSFER_BT709:
>>>>> +        colorspace->transferFunction = ColorSpace::TransferFunction::Rec709;
>>>>> +        break;
>>>>> +    case GST_VIDEO_TRANSFER_SRGB:
>>>>> +        colorspace->transferFunction = ColorSpace::TransferFunction::Srgb;
>>>>> +        break;
>>>>> +    case GST_VIDEO_TRANSFER_UNKNOWN:
>>>>> +        colorspace->transferFunction = ColorSpace::TransferFunction::Default;
>>>>> +        break;
>>>>> +    default:
>>>>> +        GST_WARNING("Unknown colorimetry transfer %d", colorimetry->transfer);
>>>>> +    }
>>>>> +
>>>>> +    switch (colorimetry->matrix) {
>>>>> +    /* FCC is about the same as BT601 with less digit */
>>>>> +    case GST_VIDEO_COLOR_MATRIX_FCC:
>>>>> +    case GST_VIDEO_COLOR_MATRIX_BT601:
>>>>> +        colorspace->ycbcrEncoding = ColorSpace::YcbcrEncoding::Rec601;
>>>>> +        break;
>>>>> +    case GST_VIDEO_COLOR_MATRIX_BT709:
>>>>> +        colorspace->ycbcrEncoding = ColorSpace::YcbcrEncoding::Rec709;
>>>>> +        break;
>>>>> +    case GST_VIDEO_COLOR_MATRIX_BT2020:
>>>>> +        colorspace->ycbcrEncoding = ColorSpace::YcbcrEncoding::Rec2020;
>>>>> +        break;
>>>>> +    case GST_VIDEO_COLOR_MATRIX_RGB:
>>>> Shouldn't this map to YcbcrEncoding::None ?
>>> YcbcrEncoding for sRGB as per Kernel is V4L2_YCBCR_ENC_SYCC which is
>>> deprecated so V4L2_YCBCR_ENC_601  is used instead [1]
>>>
>>> The same has been applied *already* in libcamera codebase:
>>>
>>> const ColorSpace ColorSpace::Srgb = {
>>>          Primaries::Rec709,
>>>          TransferFunction::Srgb,
>>>          YcbcrEncoding::Rec601,
>>>          Range::Limited
>>> };
>>>
>>> So we shouldn't map GST_VIDEO_COLOR_MATRIX_RGB to YcbcrEncoding::None
>>> but to Rec601 instead.
> I'm not sure that's right. GST_VIDEO_COLOR_MATRIX_RGB is defined as
>
> GST_VIDEO_COLOR_MATRIX_RGB (1) – identity matrix. Order of coefficients
> is actually GBR, also IEC 61966-2-1 (sRGB)
>
> I understand this as meaning that the RGB data is not converted to YUV,
> which is expressed in libcamera as YcbcrEncoding::None:
>
>   * \var ColorSpace::YcbcrEncoding::None
>   * \brief There is no defined Y'CbCr encoding (used for non-YUV formats)
>
> The V4L2 sRGB colorspace [2] definition is confusing. sRGB is an RGB
> colorspace, so it should have no YUV encoding, and use full range. The
> sYCC colorspace is sRGB encoded in YUV using the Rec601 encoding, also
> using full range. As it's typical for devices that produce sRGB images
> to encode them to YUV using the Rec601 encoding and limited range, the
> V4L2 sRGB colorspace defines default encoding and quantization range as
> Rec601 and limited. However, when the pixels are stored as RGB, those
> two parameters don't apply.
>
> Note also how GStreamer defines the SRGB colorspace ([3]):
>
>    MAKE_COLORIMETRY (SRGB, _0_255, RGB, SRGB, BT709),
>
> We could do the same and define sRGB as
>
> const ColorSpace ColorSpace::Srgb = {
>          Primaries::Rec709,
>          TransferFunction::Srgb,
>          YcbcrEncoding::None,
>          Range::Full
> };
>
> but we would then need to adapt code that produce YUV-encoded sRGB. We
> could define, for that purpose,
>
> const ColorSpace ColorSpace::Sycc = {
>          Primaries::Rec709,
>          TransferFunction::Srgb,
>          YcbcrEncoding::Rec601,
>          Range::Full
> };
>
> but that still wouldn't match the V4L2 definition. It's not necessarily
> a problem though, devices can produce { Rec709, Srgb, Rec601, Limited }
> without the need to explicitly define a ColorSpace preset in libcamera.
> I've alos just realized that the above is identical to ColorSpace::Jpeg.
>
> Another option would be to keep the ColorSpace::Srgb definition as-is
> and document that the YCbCrEncoding and Range must be ignored for
> non-YUV formats, but I don't like that much, it sounds confusing.
>
> I'm tempted to bite the bullet and define ColorSpace::Srgb with
> YcbcrEncoding::None and Range::Full, and never return ColorSpace::Srgb
> when capturing YUV data. A pipeline handler that gets ColorSpace::Srgb
> from an application would turn it into { Rec709, Srgb, Rec601, Limited }
> for YUV streams (assuming that the hardware supports that of course).


So I am getting a bit confused here for the part where YUV encoded RGB 
data needs to use which range? Limited or Full? You seem to have 
referred to limited, it seems - but I don't get the difference.

I guess I'm asking the difference between ColorSpace::Jpeg vs { Rec709, 
Srgb, Rec601, Limited } really

The pipeline-handler specific handling on sRGB colorspace is 
interesting. To re-iterate and if I am understanding things correctly 
(from API point of view):

a) ColorSpace::sRGB preset will be used to present both sRGB and 
YUV-encoded RGB colorspace, by the user (i.e. the user doesn't seem to 
care/understand)

     - libcamera PH being intelligent enough to deduce the stream 
configuration and convert ColorSpace::sRGB to { Rec709, Srgb, Rec601, 
Limited } and validate + return back to application

     - If there is no YUV stream requested, ColorSpace::sRGB preset is 
used, as-is.

b) Application intelligent enough that it's requesting a YUV stream and 
ColorSpace::sRGB doesn't mean YUV-encoded RGB(which will be true after 
we modify sRGB preset)

     - In that case, application send in directly { Rec709, Srgb, 
Rec601, Limited } as colorspace.

Is this understanding correct on my part? If yes, follow up question is:

     - The pipeline-handler colorspace adjustment logic(for YUV streams) 
- do you see it per-PH or something generic that all pipeline-handlers 
would use ? (I guess the latter)

>
> David, how is ColorSpace::Srgb interpreted by the Raspberry Pi ISP when
> producing YUV data ?
>
> [2] https://linuxtv.org/downloads/v4l-dvb-apis/userspace-api/v4l/colorspaces-details.html#colorspace-srgb-v4l2-colorspace-srgb
> [3] https://gitlab.freedesktop.org/gstreamer/gstreamer/-/blob/main/subprojects/gst-plugins-base/gst-libs/gst/video/video-color.c#L66
>
>>> Now two question arises for reverse mapping it i.e. what should:
>>>
>>>     a) YcbcrEncding::None map to
>>>
>>>     b) YcbcrEncding::Rec601 map to.
>>>
>>> For b) we have two contenders:
>>>
>>>      GST_VIDEO_COLOR_MATRIX_BT601
>>>      GST_VIDEO_COLOR_MATRIX_RGB
>> b) is currently handled with:
>>
>> +       case ColorSpace::YcbcrEncoding::Rec601:
>> +               if (colorSpace == ColorSpace::Srgb)
>> +                       colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_RGB;
>> +               else
>> +                       colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_BT601;
>> +               break;
>>
>> Let's review this in v2 (submitting shortly)
> As per the above, I think YcbcrEncding::None should map to
> GST_VIDEO_COLOR_MATRIX_RGB and YcbcrEncding::Rec601 to
> GST_VIDEO_COLOR_MATRIX_BT601, unconditionally.
>
>>> For a) I think GST_VIDEO_COLOR_MATRIX_RGB would be better ?
>>>
>>> [1] https://git.linuxtv.org/media_tree.git/tree/include/uapi/linux/videodev2.h#n344
>>>
>>>>> +    case GST_VIDEO_COLOR_MATRIX_UNKNOWN:
>>>>> +        colorspace->ycbcrEncoding = ColorSpace::YcbcrEncoding::Default;
>>>>> +        break;
>>>>> +    default:
>>>>> +        GST_WARNING("Unknown colorimetry matrix %d", colorimetry->matrix);
>>>>> +    }
>>>>> +
>>>>> +    switch (colorimetry->range) {
>>>>> +    case GST_VIDEO_COLOR_RANGE_0_255:
>>>>> +        colorspace->range = ColorSpace::Range::Full;
>>>>> +        break;
>>>>> +    case GST_VIDEO_COLOR_RANGE_16_235:
>>>>> +        colorspace->range = ColorSpace::Range::Limited;
>>>>> +        break;
>>>>> +    case GST_VIDEO_COLOR_RANGE_UNKNOWN:
>>>>> +        colorspace->range = ColorSpace::Range::Default;
>>>>> +        break;
>>>>> +    default:
>>>>> +        GST_WARNING("Unknown range in colorimetry %d", colorimetry->range);
>>>>> +    }
>>>>> +
>>>>> +    return colorspace;
>>>>> +}
>>>>> +
>>>>>    static GstVideoFormat
>>>>>    pixel_format_to_gst_format(const PixelFormat &format)
>>>>>    {
>>>>> @@ -139,6 +290,17 @@ gst_libcamera_stream_configuration_to_caps(const StreamConfiguration &stream_cfg
>>>>>                  "width", G_TYPE_INT, stream_cfg.size.width,
>>>>>                  "height", G_TYPE_INT, stream_cfg.size.height,
>>>>>                  nullptr);
>>>>> +
>>>>> +    if (stream_cfg.colorSpace) {
>>>>> +        GstVideoColorimetry colorimetry = colorimetry_from_colorspace(stream_cfg.colorSpace.value());
>>>>> +        gchar *colorimetry_str = gst_video_colorimetry_to_string(&colorimetry);
>>>>> +
>>>>> +        if (colorimetry_str != nullptr)
>>>>          if (colorimetry_str)
>>>>
>>>>> +            gst_structure_set(s, "colorimetry", G_TYPE_STRING, colorimetry_str, nullptr);
>>>>> +        else
>>>>> +            g_warning("libcamera::ColorSpace found but GstVideoColorimetry unknown");
>>>>> +    }
>>>>> +
>>>>>        gst_caps_append_structure(caps, s);
>>>>>          return caps;
>>>>> @@ -222,6 +384,19 @@ gst_libcamera_configure_stream_from_caps(StreamConfiguration &stream_cfg,
>>>>>        gst_structure_get_int(s, "height", &height);
>>>>>        stream_cfg.size.width = width;
>>>>>        stream_cfg.size.height = height;
>>>>> +
>>>>> +    /* Configure colorimetry */
>>>>> +    if (gst_structure_has_field(s, "colorimetry")) {
>>>>> +        const gchar *colorimetry_caps = gst_structure_get_string(s, "colorimetry");
>>>>> +        GstVideoColorimetry colorimetry;
>>>>> +
>>>>> + if(gst_video_colorimetry_from_string(&colorimetry, colorimetry_caps)) {
>>>> Missing space after "if".
>>>>
>>>>> + std::optional<ColorSpace> colorSpace = colorspace_from_colorimetry(&colorimetry);
>>>>> +            stream_cfg.colorSpace = colorSpace;
>>>>              stream_cfg.colorSpace = colorspace_from_colorimetry(&colorimetry);
>>>>
>>>>> +        } else {
>>>>> +            g_print("Invalid colorimetry %s", colorimetry_caps);
>>>>> +        }
>>>>> +    }
>>>>>    }
>>>>>      #if !GST_CHECK_VERSION(1, 17, 1)
Laurent Pinchart Aug. 1, 2022, 1:17 p.m. UTC | #13
Hi Umang,

On Mon, Aug 01, 2022 at 04:38:43PM +0530, Umang Jain wrote:
> On 7/29/22 20:24, Laurent Pinchart wrote:
> > On Fri, Jul 29, 2022 at 03:40:52PM +0530, Umang Jain wrote:
> >> On 7/29/22 13:36, Umang Jain via libcamera-devel wrote:
> >>> On 7/27/22 06:47, Laurent Pinchart wrote:
> >>>> On Sun, Jul 24, 2022 at 08:13:55PM +0530, Umang Jain via
> >>>> libcamera-devel wrote:
> >>>>> From: Rishikesh Donadkar <rishikeshdonadkar@gmail.com>
> >>>>>
> >>>>> Provide colorimetry <=> libcamera::ColorSpace mappings via:
> >>>>> - GstVideoColorimetry colorimetry_from_colorspace(colorspace);
> >>>>> - ColorSpace colorspace_from_colorimetry(colorimetry);
> >>>>>
> >>>>> Read the colorimetry field from caps into the stream configuration.
> >>>>> After stream validation, the sensor supported colorimetry will
> >>>>> be retrieved and the caps will be updated accordingly.
> >>>>>
> >>>>> Colorimetry support for gstlibcamerasrc currently undertakes only one
> >>>>> argument. Multiple colorimetry support shall be introduced in
> >>>>> subsequent commits.
> >>>>>
> >>>>> Signed-off-by: Rishikesh Donadkar <rishikeshdonadkar@gmail.com>
> >>>>> Signed-off-by: Umang Jain <umang.jain@ideasonboard.com>
> >>>>> ---
> >>>>>    src/gstreamer/gstlibcamera-utils.cpp | 175 +++++++++++++++++++++++++++
> >>>>>    1 file changed, 175 insertions(+)
> >>>>>
> >>>>> diff --git a/src/gstreamer/gstlibcamera-utils.cpp b/src/gstreamer/gstlibcamera-utils.cpp
> >>>>> index c97c0d43..fb4f0e5c 100644
> >>>>> --- a/src/gstreamer/gstlibcamera-utils.cpp
> >>>>> +++ b/src/gstreamer/gstlibcamera-utils.cpp
> >>>>> @@ -45,6 +45,157 @@ static struct {
> >>>>>        /* \todo NV42 is used in libcamera but is not mapped in GStreamer yet. */
> >>>>>    };
> >>>>>  
> >>>>> +static GstVideoColorimetry
> >>>>> +colorimetry_from_colorspace(const ColorSpace &colorSpace)
> >>>>> +{
> >>>>> +    GstVideoColorimetry colorimetry;
> >>>>
> >>>> I would initialize the structure with all fields set to UNKNOWN here...
> >>>>
> >>>>> +
> >>>>> +    switch (colorSpace.primaries) {
> >>>>> +    case ColorSpace::Primaries::Rec709:
> >>>>> +        colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_BT709;
> >>>>> +        break;
> >>>>> +    case ColorSpace::Primaries::Rec2020:
> >>>>> +        colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_BT2020;
> >>>>> +        break;
> >>>>> +    case ColorSpace::Primaries::Smpte170m:
> >>>>> +        colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_SMPTE170M;
> >>>>> +        break;
> >>>>> +    default:
> >>>>> +        GST_WARNING("ColorSpace primaries not mapped in GstLibcameraSrc");
> >>>>> +        colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_UNKNOWN;
> >>>>
> >>>> ... and drop the default case. As ColorSpace::Primaries is an enum
> >>>> class, if you have cases for all the values, the compiler will warn only
> >>>> if one (or more) of the enum values isn't handled in explicit cases.
> >>>> This way you can avoid warnings, and have a guarantee that if the
> >>>> libcamera colorspaces are later extended, the compiler will remind that
> >>>> this function has to be updated. Same for the other components of the
> >>>> colorspace below.
> >>>>
> >>>>> +    }
> >>>>> +
> >>>>> +    switch (colorSpace.transferFunction) {
> >>>>> +    case ColorSpace::TransferFunction::Rec709:
> >>>>> +        colorimetry.transfer = GST_VIDEO_TRANSFER_BT709;
> >>>>> +        break;
> >>>>> +    case ColorSpace::TransferFunction::Srgb:
> >>>>> +        colorimetry.transfer = GST_VIDEO_TRANSFER_SRGB;
> >>>>> +        break;
> >>>>> +    case ColorSpace::TransferFunction::Linear:
> >>>>> +        colorimetry.transfer = GST_VIDEO_TRANSFER_GAMMA10;
> >>>>> +        break;
> >>>>> +    default:
> >>>>> +        GST_WARNING("ColorSpace transfer function not mapped in GstLibcameraSrc");
> >>>>> +        colorimetry.transfer = GST_VIDEO_TRANSFER_UNKNOWN;
> >>>>> +    }
> >>>>> +
> >>>>> +    switch (colorSpace.ycbcrEncoding) {
> >>>>> +    case ColorSpace::YcbcrEncoding::Rec709:
> >>>>> +        colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_BT709;
> >>>>> +        break;
> >>>>> +    case ColorSpace::YcbcrEncoding::Rec2020:
> >>>>> +        colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_BT2020;
> >>>>> +        break;
> >>>>> +    case ColorSpace::YcbcrEncoding::Rec601:
> >>>>> +        colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_BT601;
> >>>>> +        break;
> >>>>> +    default:
> >>>>> +        GST_WARNING("Colorspace YcbcrEncoding not mapped in GstLibcameraSrc");
> >>>>> +        colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_UNKNOWN;
> >>>> 
> >>>> Should we map YcbcrEncoding::None to GST_VIDEO_COLOR_MATRIX_RGB ?
> >>>
> >>> See below.
> >>>
> >>>> Otherwise capturing RGB or raw bayer will produce a warning. Same for
> >>>> the Raw primaries, it seems that the best mapping would be
> >>>> GST_VIDEO_COLOR_PRIMARIES_UNKNOWN (unless I'm missing something), but I
> >>>> don't think we should warn in that case.
> >>>>
> >>>>> +    }
> >>>>> +
> >>>>> +    switch (colorSpace.range) {
> >>>>> +    case ColorSpace::Range::Full:
> >>>>> +        colorimetry.range = GST_VIDEO_COLOR_RANGE_0_255;
> >>>>> +        break;
> >>>>> +    case ColorSpace::Range::Limited:
> >>>>> +        colorimetry.range = GST_VIDEO_COLOR_RANGE_16_235;
> >>>>> +        break;
> >>>>> +    default:
> >>>>> +        GST_WARNING("Colorspace range not mapped in GstLibcameraSrc");
> >>>>> +        colorimetry.range = GST_VIDEO_COLOR_RANGE_UNKNOWN;
> >>>>> +    }
> >>>>> +
> >>>>> +    return colorimetry;
> >>>>> +}
> >>>>> +
> >>>>> +static std::optional<ColorSpace>
> >>>>> +colorspace_from_colorimetry(GstVideoColorimetry *colorimetry)
> >>>> 
> >>>> I'd pass a const reference instead of a pointer, as there's no use case
> >>>> for a null colorimetry.
> >>>>
> >>>>> +{
> >>>>> +    std::optional<ColorSpace> colorspace = ColorSpace::Default;
> >>>> 
> >>>> No need for std::optional here or in the return type.
> >>>>
> >>>>> +
> >>>>> +    switch (colorimetry->primaries) {
> >>>>> +    case GST_VIDEO_COLOR_PRIMARIES_BT709:
> >>>>> +        colorspace->primaries = ColorSpace::Primaries::Rec709;
> >>>>> +        break;
> >>>>> +    case GST_VIDEO_COLOR_PRIMARIES_BT2020:
> >>>>> +        colorspace->primaries = ColorSpace::Primaries::Rec2020;
> >>>>> +        break;
> >>>>> +    case GST_VIDEO_COLOR_PRIMARIES_SMPTE170M:
> >>>>> +        colorspace->primaries = ColorSpace::Primaries::Smpte170m;
> >>>>> +        break;
> >>>>> +    case GST_VIDEO_COLOR_PRIMARIES_UNKNOWN:
> >>>>> +        colorspace->primaries = ColorSpace::Primaries::Default;
> >>>>> +        break;
> >>>>> +    default:
> >>>>> +        GST_WARNING("Unknown primaries in colorimetry %d", colorimetry->primaries);
> >>>>> +    }
> >>>>> +
> >>>>> +    switch (colorimetry->transfer) {
> >>>>> +    /* Tranfer function mappings inspired from v4l2src plugin */
> >>>>> +    case GST_VIDEO_TRANSFER_GAMMA18:
> >>>>> +    case GST_VIDEO_TRANSFER_GAMMA20:
> >>>>> +    case GST_VIDEO_TRANSFER_GAMMA22:
> >>>>> +    case GST_VIDEO_TRANSFER_GAMMA28:
> >>>>> +        GST_WARNING("GAMMA 18, 20, 22, 28 transfer functions not supported");
> >>>>> +    /* fallthrough */
> >>>>> +    case GST_VIDEO_TRANSFER_GAMMA10:
> >>>>> +        colorspace->transferFunction = ColorSpace::TransferFunction::Linear;
> >>>>> +        break;
> >>>>> +    case GST_VIDEO_TRANSFER_BT601:
> >>>>> +    case GST_VIDEO_TRANSFER_BT2020_12:
> >>>>> +    case GST_VIDEO_TRANSFER_BT2020_10:
> >>>>> +    case GST_VIDEO_TRANSFER_BT709:
> >>>>> +        colorspace->transferFunction = ColorSpace::TransferFunction::Rec709;
> >>>>> +        break;
> >>>>> +    case GST_VIDEO_TRANSFER_SRGB:
> >>>>> +        colorspace->transferFunction = ColorSpace::TransferFunction::Srgb;
> >>>>> +        break;
> >>>>> +    case GST_VIDEO_TRANSFER_UNKNOWN:
> >>>>> +        colorspace->transferFunction = ColorSpace::TransferFunction::Default;
> >>>>> +        break;
> >>>>> +    default:
> >>>>> +        GST_WARNING("Unknown colorimetry transfer %d", colorimetry->transfer);
> >>>>> +    }
> >>>>> +
> >>>>> +    switch (colorimetry->matrix) {
> >>>>> +    /* FCC is about the same as BT601 with less digit */
> >>>>> +    case GST_VIDEO_COLOR_MATRIX_FCC:
> >>>>> +    case GST_VIDEO_COLOR_MATRIX_BT601:
> >>>>> +        colorspace->ycbcrEncoding = ColorSpace::YcbcrEncoding::Rec601;
> >>>>> +        break;
> >>>>> +    case GST_VIDEO_COLOR_MATRIX_BT709:
> >>>>> +        colorspace->ycbcrEncoding = ColorSpace::YcbcrEncoding::Rec709;
> >>>>> +        break;
> >>>>> +    case GST_VIDEO_COLOR_MATRIX_BT2020:
> >>>>> +        colorspace->ycbcrEncoding = ColorSpace::YcbcrEncoding::Rec2020;
> >>>>> +        break;
> >>>>> +    case GST_VIDEO_COLOR_MATRIX_RGB:
> >>>> 
> >>>> Shouldn't this map to YcbcrEncoding::None ?
> >>> 
> >>> YcbcrEncoding for sRGB as per Kernel is V4L2_YCBCR_ENC_SYCC which is
> >>> deprecated so V4L2_YCBCR_ENC_601  is used instead [1]
> >>>
> >>> The same has been applied *already* in libcamera codebase:
> >>>
> >>> const ColorSpace ColorSpace::Srgb = {
> >>>          Primaries::Rec709,
> >>>          TransferFunction::Srgb,
> >>>          YcbcrEncoding::Rec601,
> >>>          Range::Limited
> >>> };
> >>>
> >>> So we shouldn't map GST_VIDEO_COLOR_MATRIX_RGB to YcbcrEncoding::None
> >>> but to Rec601 instead.
> > 
> > I'm not sure that's right. GST_VIDEO_COLOR_MATRIX_RGB is defined as
> >
> > GST_VIDEO_COLOR_MATRIX_RGB (1) – identity matrix. Order of coefficients
> > is actually GBR, also IEC 61966-2-1 (sRGB)
> >
> > I understand this as meaning that the RGB data is not converted to YUV,
> > which is expressed in libcamera as YcbcrEncoding::None:
> >
> >   * \var ColorSpace::YcbcrEncoding::None
> >   * \brief There is no defined Y'CbCr encoding (used for non-YUV formats)
> >
> > The V4L2 sRGB colorspace [2] definition is confusing. sRGB is an RGB
> > colorspace, so it should have no YUV encoding, and use full range. The
> > sYCC colorspace is sRGB encoded in YUV using the Rec601 encoding, also
> > using full range. As it's typical for devices that produce sRGB images
> > to encode them to YUV using the Rec601 encoding and limited range, the
> > V4L2 sRGB colorspace defines default encoding and quantization range as
> > Rec601 and limited. However, when the pixels are stored as RGB, those
> > two parameters don't apply.
> >
> > Note also how GStreamer defines the SRGB colorspace ([3]):
> >
> >    MAKE_COLORIMETRY (SRGB, _0_255, RGB, SRGB, BT709),
> >
> > We could do the same and define sRGB as
> >
> > const ColorSpace ColorSpace::Srgb = {
> >          Primaries::Rec709,
> >          TransferFunction::Srgb,
> >          YcbcrEncoding::None,
> >          Range::Full
> > };
> >
> > but we would then need to adapt code that produce YUV-encoded sRGB. We
> > could define, for that purpose,
> >
> > const ColorSpace ColorSpace::Sycc = {
> >          Primaries::Rec709,
> >          TransferFunction::Srgb,
> >          YcbcrEncoding::Rec601,
> >          Range::Full
> > };
> >
> > but that still wouldn't match the V4L2 definition. It's not necessarily
> > a problem though, devices can produce { Rec709, Srgb, Rec601, Limited }
> > without the need to explicitly define a ColorSpace preset in libcamera.
> > I've alos just realized that the above is identical to ColorSpace::Jpeg.
> >
> > Another option would be to keep the ColorSpace::Srgb definition as-is
> > and document that the YCbCrEncoding and Range must be ignored for
> > non-YUV formats, but I don't like that much, it sounds confusing.
> >
> > I'm tempted to bite the bullet and define ColorSpace::Srgb with
> > YcbcrEncoding::None and Range::Full, and never return ColorSpace::Srgb
> > when capturing YUV data. A pipeline handler that gets ColorSpace::Srgb
> > from an application would turn it into { Rec709, Srgb, Rec601, Limited }
> > for YUV streams (assuming that the hardware supports that of course).
> 
> So I am getting a bit confused here for the part where YUV encoded RGB 
> data needs to use which range? Limited or Full? You seem to have 
> referred to limited, it seems - but I don't get the difference.
> 
> I guess I'm asking the difference between ColorSpace::Jpeg vs { Rec709, 
> Srgb, Rec601, Limited } really

The difference is that, after conversion from RGB to YUV using the
Rec601 matrix, the Y and U/V values will be limited to [16, 235] and
[16, 240] respectively (by shifting and scaling them to that range).
sYCC mandates usage of limited range, but many devices that use the sRGB
color space encoded in YUV produce full range instead, which led to the
naming of the informal JPEG color space.

> The pipeline-handler specific handling on sRGB colorspace is 
> interesting. To re-iterate and if I am understanding things correctly 
> (from API point of view):
> 
> a) ColorSpace::sRGB preset will be used to present both sRGB and 
> YUV-encoded RGB colorspace, by the user (i.e. the user doesn't seem to 
> care/understand)

To be clear, assuming we will redefine sRGB the same way as GStreamer,

const ColorSpace ColorSpace::Srgb = {
         Primaries::Rec709,
         TransferFunction::Srgb,
         YcbcrEncoding::None,
         Range::Full
};

then it can represent RGB data only. If an application picks it to
capture RGB data, all is fine. If an application picks it to capture YUV
data, that's technically wrong, but libcamera should adjust it as our
color space API requires pipeline handlers to adjust color spaces
requested by applications and not supported by the device.

>      - libcamera PH being intelligent enough to deduce the stream 
> configuration and convert ColorSpace::sRGB to { Rec709, Srgb, Rec601, 
> Limited } and validate + return back to application

Nearly. I think the logic should check the transfer function, and if
it's Srgb and the encoding is None for a YUV format, it should pick
Rec601 as the encoding. Actually, looking at the presets we have,
something like the following could work:

	if (format == YUV && encoding == None) {
		if (transferFunction == Rec709) {
			switch (primaries) {
			case Raw:
			case Smpte170m:
				encoding = Rec601;
				break;
			case Rec709:
				encoding = Rec709;
				break;
			case Rec2020:
				encoding = Rec2020;
				break;
			}
		} else {
			encoding = Rec601;
		}
	}

(the Raw case should never happen, I've picked a default encoding in
that case.)

I'm sure there are other ways to express the logic, looking at the
GStreamer v4l2src element could help. It would also be nice to
centralize this kind of adjustement logic somewhere (possibly in the
ColorSpace class).

I said "nearly" above as I'm not sure about the default range we should
select (limited vs. full). sYCC is limited, JPEG is full. Should we
assume that an application that incorrectly asks for ColorSpace::Srgb
for a YUV stream will actually want sYCC or JPEG ?

>      - If there is no YUV stream requested, ColorSpace::sRGB preset is 
> used, as-is.

Right.

> b) Application intelligent enough that it's requesting a YUV stream and 
> ColorSpace::sRGB doesn't mean YUV-encoded RGB(which will be true after 
> we modify sRGB preset)
> 
>      - In that case, application send in directly { Rec709, Srgb, 
> Rec601, Limited } as colorspace.

That, or the same with Full range, depending on what the application
wants.

> Is this understanding correct on my part? If yes, follow up question is:

I think so.

>      - The pipeline-handler colorspace adjustment logic(for YUV streams) 
> - do you see it per-PH or something generic that all pipeline-handlers 
> would use ? (I guess the latter)

I think I've answered that above :-) I'd like to centralize as much
logic as possible, and at least the logic that picks a default YCbCr
encoding for YUV formats. For the rest, the adjustment also depends on
what the hardware supports, so fully generic helpers may not be
possible.

Even if we can't implement the adjustment code in shared code that would
work with all pipeline handlers, I would like to document the adjustment
heuristics to have consistent behaviour across pipeline handlers.

> > David, how is ColorSpace::Srgb interpreted by the Raspberry Pi ISP when
> > producing YUV data ?
> >
> > [2] https://linuxtv.org/downloads/v4l-dvb-apis/userspace-api/v4l/colorspaces-details.html#colorspace-srgb-v4l2-colorspace-srgb
> > [3] https://gitlab.freedesktop.org/gstreamer/gstreamer/-/blob/main/subprojects/gst-plugins-base/gst-libs/gst/video/video-color.c#L66
> >
> >>> Now two question arises for reverse mapping it i.e. what should:
> >>>
> >>>     a) YcbcrEncding::None map to
> >>>
> >>>     b) YcbcrEncding::Rec601 map to.
> >>>
> >>> For b) we have two contenders:
> >>>
> >>>      GST_VIDEO_COLOR_MATRIX_BT601
> >>>      GST_VIDEO_COLOR_MATRIX_RGB
> >> 
> >> b) is currently handled with:
> >>
> >> +       case ColorSpace::YcbcrEncoding::Rec601:
> >> +               if (colorSpace == ColorSpace::Srgb)
> >> +                       colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_RGB;
> >> +               else
> >> +                       colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_BT601;
> >> +               break;
> >>
> >> Let's review this in v2 (submitting shortly)
> > 
> > As per the above, I think YcbcrEncding::None should map to
> > GST_VIDEO_COLOR_MATRIX_RGB and YcbcrEncding::Rec601 to
> > GST_VIDEO_COLOR_MATRIX_BT601, unconditionally.
> >
> >>> For a) I think GST_VIDEO_COLOR_MATRIX_RGB would be better ?
> >>>
> >>> [1] https://git.linuxtv.org/media_tree.git/tree/include/uapi/linux/videodev2.h#n344
> >>>
> >>>>> +    case GST_VIDEO_COLOR_MATRIX_UNKNOWN:
> >>>>> +        colorspace->ycbcrEncoding = ColorSpace::YcbcrEncoding::Default;
> >>>>> +        break;
> >>>>> +    default:
> >>>>> +        GST_WARNING("Unknown colorimetry matrix %d", colorimetry->matrix);
> >>>>> +    }
> >>>>> +
> >>>>> +    switch (colorimetry->range) {
> >>>>> +    case GST_VIDEO_COLOR_RANGE_0_255:
> >>>>> +        colorspace->range = ColorSpace::Range::Full;
> >>>>> +        break;
> >>>>> +    case GST_VIDEO_COLOR_RANGE_16_235:
> >>>>> +        colorspace->range = ColorSpace::Range::Limited;
> >>>>> +        break;
> >>>>> +    case GST_VIDEO_COLOR_RANGE_UNKNOWN:
> >>>>> +        colorspace->range = ColorSpace::Range::Default;
> >>>>> +        break;
> >>>>> +    default:
> >>>>> +        GST_WARNING("Unknown range in colorimetry %d", colorimetry->range);
> >>>>> +    }
> >>>>> +
> >>>>> +    return colorspace;
> >>>>> +}
> >>>>> +
> >>>>>    static GstVideoFormat
> >>>>>    pixel_format_to_gst_format(const PixelFormat &format)
> >>>>>    {
> >>>>> @@ -139,6 +290,17 @@ gst_libcamera_stream_configuration_to_caps(const StreamConfiguration &stream_cfg
> >>>>>                  "width", G_TYPE_INT, stream_cfg.size.width,
> >>>>>                  "height", G_TYPE_INT, stream_cfg.size.height,
> >>>>>                  nullptr);
> >>>>> +
> >>>>> +    if (stream_cfg.colorSpace) {
> >>>>> +        GstVideoColorimetry colorimetry = colorimetry_from_colorspace(stream_cfg.colorSpace.value());
> >>>>> +        gchar *colorimetry_str = gst_video_colorimetry_to_string(&colorimetry);
> >>>>> +
> >>>>> +        if (colorimetry_str != nullptr)
> >>>> 
> >>>>          if (colorimetry_str)
> >>>>
> >>>>> +            gst_structure_set(s, "colorimetry", G_TYPE_STRING, colorimetry_str, nullptr);
> >>>>> +        else
> >>>>> +            g_warning("libcamera::ColorSpace found but GstVideoColorimetry unknown");
> >>>>> +    }
> >>>>> +
> >>>>>        gst_caps_append_structure(caps, s);
> >>>>>          return caps;
> >>>>> @@ -222,6 +384,19 @@ gst_libcamera_configure_stream_from_caps(StreamConfiguration &stream_cfg,
> >>>>>        gst_structure_get_int(s, "height", &height);
> >>>>>        stream_cfg.size.width = width;
> >>>>>        stream_cfg.size.height = height;
> >>>>> +
> >>>>> +    /* Configure colorimetry */
> >>>>> +    if (gst_structure_has_field(s, "colorimetry")) {
> >>>>> +        const gchar *colorimetry_caps = gst_structure_get_string(s, "colorimetry");
> >>>>> +        GstVideoColorimetry colorimetry;
> >>>>> +
> >>>>> + if(gst_video_colorimetry_from_string(&colorimetry, colorimetry_caps)) {
> >>>> 
> >>>> Missing space after "if".
> >>>>
> >>>>> + std::optional<ColorSpace> colorSpace = colorspace_from_colorimetry(&colorimetry);
> >>>>> +            stream_cfg.colorSpace = colorSpace;
> >>>> 
> >>>>              stream_cfg.colorSpace = colorspace_from_colorimetry(&colorimetry);
> >>>>
> >>>>> +        } else {
> >>>>> +            g_print("Invalid colorimetry %s", colorimetry_caps);
> >>>>> +        }
> >>>>> +    }
> >>>>>    }
> >>>>>      #if !GST_CHECK_VERSION(1, 17, 1)

Patch
diff mbox series

diff --git a/src/gstreamer/gstlibcamera-utils.cpp b/src/gstreamer/gstlibcamera-utils.cpp
index c97c0d43..fb4f0e5c 100644
--- a/src/gstreamer/gstlibcamera-utils.cpp
+++ b/src/gstreamer/gstlibcamera-utils.cpp
@@ -45,6 +45,157 @@  static struct {
 	/* \todo NV42 is used in libcamera but is not mapped in GStreamer yet. */
 };
 
+static GstVideoColorimetry
+colorimetry_from_colorspace(const ColorSpace &colorSpace)
+{
+	GstVideoColorimetry colorimetry;
+
+	switch (colorSpace.primaries) {
+	case ColorSpace::Primaries::Rec709:
+		colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_BT709;
+		break;
+	case ColorSpace::Primaries::Rec2020:
+		colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_BT2020;
+		break;
+	case ColorSpace::Primaries::Smpte170m:
+		colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_SMPTE170M;
+		break;
+	default:
+		GST_WARNING("ColorSpace primaries not mapped in GstLibcameraSrc");
+		colorimetry.primaries = GST_VIDEO_COLOR_PRIMARIES_UNKNOWN;
+	}
+
+	switch (colorSpace.transferFunction) {
+	case ColorSpace::TransferFunction::Rec709:
+		colorimetry.transfer = GST_VIDEO_TRANSFER_BT709;
+		break;
+	case ColorSpace::TransferFunction::Srgb:
+		colorimetry.transfer = GST_VIDEO_TRANSFER_SRGB;
+		break;
+	case ColorSpace::TransferFunction::Linear:
+		colorimetry.transfer = GST_VIDEO_TRANSFER_GAMMA10;
+		break;
+	default:
+		GST_WARNING("ColorSpace transfer function not mapped in GstLibcameraSrc");
+		colorimetry.transfer = GST_VIDEO_TRANSFER_UNKNOWN;
+	}
+
+	switch (colorSpace.ycbcrEncoding) {
+	case ColorSpace::YcbcrEncoding::Rec709:
+		colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_BT709;
+		break;
+	case ColorSpace::YcbcrEncoding::Rec2020:
+		colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_BT2020;
+		break;
+	case ColorSpace::YcbcrEncoding::Rec601:
+		colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_BT601;
+		break;
+	default:
+		GST_WARNING("Colorspace YcbcrEncoding not mapped in GstLibcameraSrc");
+		colorimetry.matrix = GST_VIDEO_COLOR_MATRIX_UNKNOWN;
+	}
+
+	switch (colorSpace.range) {
+	case ColorSpace::Range::Full:
+		colorimetry.range = GST_VIDEO_COLOR_RANGE_0_255;
+		break;
+	case ColorSpace::Range::Limited:
+		colorimetry.range = GST_VIDEO_COLOR_RANGE_16_235;
+		break;
+	default:
+		GST_WARNING("Colorspace range not mapped in GstLibcameraSrc");
+		colorimetry.range = GST_VIDEO_COLOR_RANGE_UNKNOWN;
+	}
+
+	return colorimetry;
+}
+
+static std::optional<ColorSpace>
+colorspace_from_colorimetry(GstVideoColorimetry *colorimetry)
+{
+	std::optional<ColorSpace> colorspace = ColorSpace::Default;
+
+	switch (colorimetry->primaries) {
+	case GST_VIDEO_COLOR_PRIMARIES_BT709:
+		colorspace->primaries = ColorSpace::Primaries::Rec709;
+		break;
+	case GST_VIDEO_COLOR_PRIMARIES_BT2020:
+		colorspace->primaries = ColorSpace::Primaries::Rec2020;
+		break;
+	case GST_VIDEO_COLOR_PRIMARIES_SMPTE170M:
+		colorspace->primaries = ColorSpace::Primaries::Smpte170m;
+		break;
+	case GST_VIDEO_COLOR_PRIMARIES_UNKNOWN:
+		colorspace->primaries = ColorSpace::Primaries::Default;
+		break;
+	default:
+		GST_WARNING("Unknown primaries in colorimetry %d", colorimetry->primaries);
+	}
+
+	switch (colorimetry->transfer) {
+	/* Tranfer function mappings inspired from v4l2src plugin */
+	case GST_VIDEO_TRANSFER_GAMMA18:
+	case GST_VIDEO_TRANSFER_GAMMA20:
+	case GST_VIDEO_TRANSFER_GAMMA22:
+	case GST_VIDEO_TRANSFER_GAMMA28:
+		GST_WARNING("GAMMA 18, 20, 22, 28 transfer functions not supported");
+	/* fallthrough */
+	case GST_VIDEO_TRANSFER_GAMMA10:
+		colorspace->transferFunction = ColorSpace::TransferFunction::Linear;
+		break;
+	case GST_VIDEO_TRANSFER_BT601:
+	case GST_VIDEO_TRANSFER_BT2020_12:
+	case GST_VIDEO_TRANSFER_BT2020_10:
+	case GST_VIDEO_TRANSFER_BT709:
+		colorspace->transferFunction = ColorSpace::TransferFunction::Rec709;
+		break;
+	case GST_VIDEO_TRANSFER_SRGB:
+		colorspace->transferFunction = ColorSpace::TransferFunction::Srgb;
+		break;
+	case GST_VIDEO_TRANSFER_UNKNOWN:
+		colorspace->transferFunction = ColorSpace::TransferFunction::Default;
+		break;
+	default:
+		GST_WARNING("Unknown colorimetry transfer %d", colorimetry->transfer);
+	}
+
+	switch (colorimetry->matrix) {
+	/* FCC is about the same as BT601 with less digit */
+	case GST_VIDEO_COLOR_MATRIX_FCC:
+	case GST_VIDEO_COLOR_MATRIX_BT601:
+		colorspace->ycbcrEncoding = ColorSpace::YcbcrEncoding::Rec601;
+		break;
+	case GST_VIDEO_COLOR_MATRIX_BT709:
+		colorspace->ycbcrEncoding = ColorSpace::YcbcrEncoding::Rec709;
+		break;
+	case GST_VIDEO_COLOR_MATRIX_BT2020:
+		colorspace->ycbcrEncoding = ColorSpace::YcbcrEncoding::Rec2020;
+		break;
+	case GST_VIDEO_COLOR_MATRIX_RGB:
+	case GST_VIDEO_COLOR_MATRIX_UNKNOWN:
+		colorspace->ycbcrEncoding = ColorSpace::YcbcrEncoding::Default;
+		break;
+	default:
+		GST_WARNING("Unknown colorimetry matrix %d", colorimetry->matrix);
+	}
+
+	switch (colorimetry->range) {
+	case GST_VIDEO_COLOR_RANGE_0_255:
+		colorspace->range = ColorSpace::Range::Full;
+		break;
+	case GST_VIDEO_COLOR_RANGE_16_235:
+		colorspace->range = ColorSpace::Range::Limited;
+		break;
+	case GST_VIDEO_COLOR_RANGE_UNKNOWN:
+		colorspace->range = ColorSpace::Range::Default;
+		break;
+	default:
+		GST_WARNING("Unknown range in colorimetry %d", colorimetry->range);
+	}
+
+	return colorspace;
+}
+
 static GstVideoFormat
 pixel_format_to_gst_format(const PixelFormat &format)
 {
@@ -139,6 +290,17 @@  gst_libcamera_stream_configuration_to_caps(const StreamConfiguration &stream_cfg
 			  "width", G_TYPE_INT, stream_cfg.size.width,
 			  "height", G_TYPE_INT, stream_cfg.size.height,
 			  nullptr);
+
+	if (stream_cfg.colorSpace) {
+		GstVideoColorimetry colorimetry = colorimetry_from_colorspace(stream_cfg.colorSpace.value());
+		gchar *colorimetry_str = gst_video_colorimetry_to_string(&colorimetry);
+
+		if (colorimetry_str != nullptr)
+			gst_structure_set(s, "colorimetry", G_TYPE_STRING, colorimetry_str, nullptr);
+		else
+			g_warning("libcamera::ColorSpace found but GstVideoColorimetry unknown");
+	}
+
 	gst_caps_append_structure(caps, s);
 
 	return caps;
@@ -222,6 +384,19 @@  gst_libcamera_configure_stream_from_caps(StreamConfiguration &stream_cfg,
 	gst_structure_get_int(s, "height", &height);
 	stream_cfg.size.width = width;
 	stream_cfg.size.height = height;
+
+	/* Configure colorimetry */
+	if (gst_structure_has_field(s, "colorimetry")) {
+		const gchar *colorimetry_caps = gst_structure_get_string(s, "colorimetry");
+		GstVideoColorimetry colorimetry;
+
+		if(gst_video_colorimetry_from_string(&colorimetry, colorimetry_caps)) {
+			std::optional<ColorSpace> colorSpace = colorspace_from_colorimetry(&colorimetry);
+			stream_cfg.colorSpace = colorSpace;
+		} else {
+			g_print("Invalid colorimetry %s", colorimetry_caps);
+		}
+	}
 }
 
 #if !GST_CHECK_VERSION(1, 17, 1)