From 1b023dee2ef7b2fb92f8a06f27060d6db5c23d2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Manuel=20J=C3=A1quez=20Leal?= Date: Sat, 26 Apr 2025 10:41:11 +0200 Subject: [PATCH] y4m: add color mappings Now the chroma subsampling tag will include the chroma site. Tests where updated accordingly. Part-of: --- .../gst-plugins-good/gst/y4m/gsty4mdec.c | 78 +--------- .../gst-plugins-good/gst/y4m/gsty4menc.c | 22 +-- .../gst-plugins-good/gst/y4m/gsty4mformat.c | 140 ++++++++++++++++++ .../gst-plugins-good/gst/y4m/gsty4mformat.h | 18 +++ .../tests/check/elements/y4mdata.c | 4 +- .../tests/check/elements/y4menc.c | 2 +- 6 files changed, 171 insertions(+), 93 deletions(-) diff --git a/subprojects/gst-plugins-good/gst/y4m/gsty4mdec.c b/subprojects/gst-plugins-good/gst/y4m/gsty4mdec.c index b0ecfbda82..d274c358ca 100644 --- a/subprojects/gst-plugins-good/gst/y4m/gsty4mdec.c +++ b/subprojects/gst-plugins-good/gst/y4m/gsty4mdec.c @@ -160,78 +160,6 @@ gst_y4m_dec_stop (GstBaseParse * parse) return TRUE; } -static GstVideoFormat -parse_colorspace (const char *param) -{ - char *end; - guint iformat = g_ascii_strtoull (param, &end, 10); - - if (*end == '\0') { - switch (iformat) { - case 420: - return GST_VIDEO_FORMAT_I420; - case 411: - return GST_VIDEO_FORMAT_Y41B; - case 422: - return GST_VIDEO_FORMAT_Y42B; - case 444: - return GST_VIDEO_FORMAT_Y444; - } - } - - /* - * Parse non-standard (i.e., unknown to mjpegtools) streams that are - * generated by FFmpeg: - * https://wiki.multimedia.cx/index.php/YUV4MPEG2 - * https://github.com/FFmpeg/FFmpeg/blob/eee3b7e2/libavformat/yuv4mpegenc.c#L74-L166 - * Will assume little-endian because this is an on-disk serialization format. - */ - - // TODO: Differentiate between: - // * C420jpeg: biaxially-displaced chroma planes - // * C420paldv: coincident R and vertically-displaced B - // * C420mpeg2: vertically-displaced chroma planes - if (iformat == 420 && (g_strcmp0 (end, "jpeg") == 0 || - g_strcmp0 (end, "paldv") == 0 || g_strcmp0 (end, "mpeg2") == 0)) - return GST_VIDEO_FORMAT_I420; - - if (iformat == 0 && strncmp (end, "mono", 4) == 0) { - char *type = end + 4; - if (*type == '\0') - return GST_VIDEO_FORMAT_GRAY8; - if (g_strcmp0 (type, "16") == 0) - return GST_VIDEO_FORMAT_GRAY16_LE; - } - - if (*end == 'p') { - guint depth = g_ascii_strtoull (end + 1, NULL, 10); - if (depth == 10) { - switch (iformat) { - case 420: - return GST_VIDEO_FORMAT_I420_10LE; - case 422: - return GST_VIDEO_FORMAT_I422_10LE; - case 444: - return GST_VIDEO_FORMAT_Y444_10LE; - } - } else if (depth == 12) { - switch (iformat) { - case 420: - return GST_VIDEO_FORMAT_I420_12LE; - case 422: - return GST_VIDEO_FORMAT_I422_12LE; - case 444: - return GST_VIDEO_FORMAT_Y444_12LE; - } - } else if (depth == 16 && iformat == 444) { - return GST_VIDEO_FORMAT_Y444_16LE; - } - } - - GST_WARNING ("%s is not a supported format", param); - return GST_VIDEO_FORMAT_UNKNOWN; -} - static gboolean parse_ratio (const char *param, gulong * n, gulong * d) { @@ -259,6 +187,7 @@ gst_y4m_dec_parse_header (GstY4mDec * y4mdec, const char *header) gulong par_n = 0, par_d = 0; gulong width = 0, height = 0; GstVideoFormat format = GST_VIDEO_FORMAT_I420; + GstVideoChromaSite chroma_site = GST_VIDEO_CHROMA_SITE_NONE; GstVideoInterlaceMode interlace_mode; if (memcmp (header, "YUV4MPEG2 ", 10) != 0) { @@ -282,8 +211,8 @@ gst_y4m_dec_parse_header (GstY4mDec * y4mdec, const char *header) const char *param_value = param + 1; switch (param_type) { case 'C': - format = parse_colorspace (param_value); - if (format == GST_VIDEO_FORMAT_UNKNOWN) { + if (!gst_y4m_video_get_format_from_chroma_tag (param_value, &format, + &chroma_site)) { GST_ERROR_OBJECT (y4mdec, "Failed to parse colorspace: %s", param); return FALSE; } @@ -321,6 +250,7 @@ gst_y4m_dec_parse_header (GstY4mDec * y4mdec, const char *header) } continue; } + /* TODO: parse common metadata ('X') such as YSCSS and color range */ GST_WARNING_OBJECT (y4mdec, "Unknown y4m param field '%s', ignoring", param); } diff --git a/subprojects/gst-plugins-good/gst/y4m/gsty4menc.c b/subprojects/gst-plugins-good/gst/y4m/gsty4menc.c index 903268bc75..1441e557ad 100644 --- a/subprojects/gst-plugins-good/gst/y4m/gsty4menc.c +++ b/subprojects/gst-plugins-good/gst/y4m/gsty4menc.c @@ -144,22 +144,11 @@ gst_y4m_encode_set_format (GstVideoEncoder * encoder, y4menc = GST_Y4M_ENCODE (encoder); info = &state->info; - switch (GST_VIDEO_INFO_FORMAT (info)) { - case GST_VIDEO_FORMAT_I420: - y4menc->colorspace = "420"; - break; - case GST_VIDEO_FORMAT_Y42B: - y4menc->colorspace = "422"; - break; - case GST_VIDEO_FORMAT_Y41B: - y4menc->colorspace = "411"; - break; - case GST_VIDEO_FORMAT_Y444: - y4menc->colorspace = "444"; - break; - default: - goto invalid_format; - } + y4menc->colorspace = + gst_y4m_video_get_chroma_tag_from_format (GST_VIDEO_INFO_FORMAT (info), + GST_VIDEO_INFO_CHROMA_SITE (info)); + if (y4menc->colorspace == NULL) + goto invalid_format; if (!gst_y4m_video_unpadded_info (&out_info, info)) goto invalid_format; @@ -199,6 +188,7 @@ gst_y4m_encode_get_stream_header (GstY4mEncode * filter, gboolean tff) interlaced = 'p'; } + /* TODO: add YSCSS and color range */ header = g_strdup_printf ("YUV4MPEG2 C%s W%d H%d I%c F%d:%d A%d:%d\n", filter->colorspace, GST_VIDEO_INFO_WIDTH (&filter->info), GST_VIDEO_INFO_HEIGHT (&filter->info), interlaced, diff --git a/subprojects/gst-plugins-good/gst/y4m/gsty4mformat.c b/subprojects/gst-plugins-good/gst/y4m/gsty4mformat.c index 7f7c4a561f..9d91bae9a2 100644 --- a/subprojects/gst-plugins-good/gst/y4m/gsty4mformat.c +++ b/subprojects/gst-plugins-good/gst/y4m/gsty4mformat.c @@ -91,3 +91,143 @@ gst_y4m_video_unpadded_info (GstVideoInfo * y4m_info, return TRUE; } + +/* + * Parse non-standard (i.e., unknown to mjpegtools) streams that are + * generated by FFmpeg: + * https://wiki.multimedia.cx/index.php/YUV4MPEG2 + * https://github.com/FFmpeg/FFmpeg/blob/eee3b7e2/libavformat/yuv4mpegenc.c#L74-L166 + * Will assume little-endian because this is an on-disk serialization format. + */ + +/* *INDENT-OFF* */ +static const struct { + const char *chroma_tag; + const char *yscss_tag; + GstVideoFormat format; + GstVideoChromaSite chroma_site; +} ChromaSubsamplingMap[] = { + { "420jpeg", "420JPEG", GST_VIDEO_FORMAT_I420, GST_VIDEO_CHROMA_SITE_JPEG }, + { "420mpeg2", "420MPEG2", GST_VIDEO_FORMAT_I420, GST_VIDEO_CHROMA_SITE_MPEG2 }, + { "420paldv", "420PALDV", GST_VIDEO_FORMAT_I420, GST_VIDEO_CHROMA_SITE_DV }, + /* { "420p16", "420P16", GST_VIDEO_FORMAT_I420_16LE, GST_VIDEO_CHROMA_SITE_UNKNOWN }, */ + /* { "422p16", "422P16", GST_VIDEO_FORMAT_I422_16LE, GST_VIDEO_CHROMA_SITE_UNKNOWN }, */ + /* { "444p16", "444P16", GST_VIDEO_FORMAT_Y444_16LE, GST_VIDEO_CHROMA_SITE_UNKNOWN }, */ + /* { "420p14", "420P14", GST_VIDEO_FORMAT_I420_14LE, GST_VIDEO_CHROMA_SITE_UNKNOWN }, */ + /* { "422p14", "422P14", GST_VIDEO_FORMAT_I422_14LE, GST_VIDEO_CHROMA_SITE_UNKNOWN }, */ + /* { "444p14", "444P14", GST_VIDEO_FORMAT_Y444_14LE, GST_VIDEO_CHROMA_SITE_UNKNOWN }, */ + { "420p12", "420P12", GST_VIDEO_FORMAT_I420_12LE, GST_VIDEO_CHROMA_SITE_NONE }, + { "422p12", "422P12", GST_VIDEO_FORMAT_I422_12LE, GST_VIDEO_CHROMA_SITE_NONE }, + { "444p12", "444P12", GST_VIDEO_FORMAT_Y444_12LE, GST_VIDEO_CHROMA_SITE_NONE }, + { "420p10", "420P10", GST_VIDEO_FORMAT_I420_10LE, GST_VIDEO_CHROMA_SITE_NONE }, + { "422p10", "422P10", GST_VIDEO_FORMAT_I422_10LE, GST_VIDEO_CHROMA_SITE_NONE }, + { "444p10", "444P10", GST_VIDEO_FORMAT_Y444_10LE, GST_VIDEO_CHROMA_SITE_NONE }, + /* { "420p9", GST_VIDEO_FORMAT_I420_9LE, GST_VIDEO_CHROMA_SITE_UNKNOWN }, */ + /* { "422p9", GST_VIDEO_FORMAT_I422_9LE, GST_VIDEO_CHROMA_SITE_UNKNOWN }, */ + /* { "444p9", GST_VIDEO_FORMAT_Y444_9LE, GST_VIDEO_CHROMA_SITE_UNKNOWN }, */ + { "420", NULL, GST_VIDEO_FORMAT_I420, GST_VIDEO_CHROMA_SITE_NONE }, + { "411", "411", GST_VIDEO_FORMAT_Y41B, GST_VIDEO_CHROMA_SITE_NONE }, + { "422", "422", GST_VIDEO_FORMAT_Y42B, GST_VIDEO_CHROMA_SITE_NONE }, + { "444alpha", NULL, GST_VIDEO_FORMAT_A444, GST_VIDEO_CHROMA_SITE_NONE }, + { "444", "444", GST_VIDEO_FORMAT_Y444, GST_VIDEO_CHROMA_SITE_NONE }, + { "mono16", NULL, GST_VIDEO_FORMAT_GRAY16_LE, GST_VIDEO_CHROMA_SITE_UNKNOWN }, + /* { "mono12", AV_PIX_FMT_GRAY12, GST_VIDEO_CHROMA_SITE_UNKNOWN }, */ + { "mono10", NULL, GST_VIDEO_FORMAT_GRAY10_LE16, GST_VIDEO_CHROMA_SITE_UNKNOWN }, + /* { "mono9", AV_PIX_FMT_GRAY9, GST_VIDEO_CHROMA_SITE_UNKNOWN }, */ + { "mono", NULL, GST_VIDEO_FORMAT_GRAY8, GST_VIDEO_CHROMA_SITE_UNKNOWN }, +}; +/* *INDENT-ON* */ + +gboolean +gst_y4m_video_get_format_from_chroma_tag (const char *chroma_tag, + GstVideoFormat * format, GstVideoChromaSite * chroma_site) +{ + for (guint i = 0; i < G_N_ELEMENTS (ChromaSubsamplingMap); i++) { + if (g_strcmp0 (ChromaSubsamplingMap[i].chroma_tag, chroma_tag) == 0) { + if (format) + *format = ChromaSubsamplingMap[i].format; + if (chroma_site) + *chroma_site = ChromaSubsamplingMap[i].chroma_site; + return TRUE; + } + } + + return FALSE; +} + +const char * +gst_y4m_video_get_chroma_tag_from_format (GstVideoFormat format, + GstVideoChromaSite chroma_site) +{ + for (guint i = 0; i < G_N_ELEMENTS (ChromaSubsamplingMap); i++) { + if (ChromaSubsamplingMap[i].format == format + && ChromaSubsamplingMap[i].chroma_site == chroma_site) + return ChromaSubsamplingMap[i].chroma_tag; + } + + return NULL; +} + +gboolean +gst_y4m_video_get_format_from_yscss_tag (const char *yscss_tag, + GstVideoFormat * format, GstVideoChromaSite * chroma_site) +{ + for (guint i = 0; i < G_N_ELEMENTS (ChromaSubsamplingMap); i++) { + if (g_strcmp0 (ChromaSubsamplingMap[i].chroma_tag, yscss_tag) == 0) { + if (format) + *format = ChromaSubsamplingMap[i].format; + if (chroma_site) + *chroma_site = ChromaSubsamplingMap[i].chroma_site; + return TRUE; + } + } + + return FALSE; +} + +const char * +gst_y4m_video_get_yscss_tag_from_format (GstVideoFormat format, + GstVideoChromaSite chroma_site) +{ + for (guint i = 0; i < G_N_ELEMENTS (ChromaSubsamplingMap); i++) { + if (ChromaSubsamplingMap[i].format == format + && ChromaSubsamplingMap[i].chroma_site == chroma_site) + return ChromaSubsamplingMap[i].yscss_tag; + } + + return NULL; +} + +/* *INDENT-OFF* */ +static const struct { + const char *range_tag; + GstVideoColorRange range; +} ColorRangeMap[] = { + { "FULL", GST_VIDEO_COLOR_RANGE_0_255 }, + { "LIMITED", GST_VIDEO_COLOR_RANGE_16_235 }, +}; +/* *INDENT-ON* */ + +GstVideoColorRange +gst_y4m_video_get_color_range_from_range_tag (const char *range_tag) +{ + for (guint i = 0; i < G_N_ELEMENTS (ColorRangeMap); i++) { + if (g_strcmp0 (range_tag, ColorRangeMap[i].range_tag) == 0) { + return ColorRangeMap[i].range; + } + } + + return GST_VIDEO_COLOR_RANGE_UNKNOWN; +} + +const char * +gst_y4m_video_get_range_tag_from_color_range (GstVideoColorRange range) +{ + for (guint i = 0; i < G_N_ELEMENTS (ColorRangeMap); i++) { + if (range == ColorRangeMap[i].range) { + return ColorRangeMap[i].range_tag; + } + } + + return NULL; +} diff --git a/subprojects/gst-plugins-good/gst/y4m/gsty4mformat.h b/subprojects/gst-plugins-good/gst/y4m/gsty4mformat.h index f43498a84a..dfcddb229d 100644 --- a/subprojects/gst-plugins-good/gst/y4m/gsty4mformat.h +++ b/subprojects/gst-plugins-good/gst/y4m/gsty4mformat.h @@ -25,3 +25,21 @@ gboolean gst_y4m_video_unpadded_info (GstVideoInfo * y4m_info, const GstVideoInfo * vinfo); + +gboolean gst_y4m_video_get_format_from_chroma_tag (const char * chroma_tag, + GstVideoFormat * format, + GstVideoChromaSite * chroma_site); + +const char * gst_y4m_video_get_chroma_tag_from_format (GstVideoFormat format, + GstVideoChromaSite chroma_site); + +gboolean gst_y4m_video_get_format_from_yscss_tag (const char * yscss_tag, + GstVideoFormat * format, + GstVideoChromaSite * chroma_site); + +const char * gst_y4m_video_get_yscss_tag_from_format (GstVideoFormat format, + GstVideoChromaSite chroma_site); + +GstVideoColorRange gst_y4m_video_get_color_range_from_range_tag (const char * range_tag); + +const char * gst_y4m_video_get_range_tag_from_color_range (GstVideoColorRange range); diff --git a/subprojects/gst-plugins-good/tests/check/elements/y4mdata.c b/subprojects/gst-plugins-good/tests/check/elements/y4mdata.c index 88f98a8683..ce6c02251b 100644 --- a/subprojects/gst-plugins-good/tests/check/elements/y4mdata.c +++ b/subprojects/gst-plugins-good/tests/check/elements/y4mdata.c @@ -52,7 +52,7 @@ unsigned int red_box_i420_15x15_yuv_len = 384; unsigned char red_box_y4m[] = { 0x59, 0x55, 0x56, 0x34, 0x4d, 0x50, 0x45, 0x47, 0x32, 0x20, 0x43, 0x34, - 0x32, 0x30, 0x20, 0x57, 0x31, 0x35, 0x20, 0x48, 0x31, 0x35, 0x20, 0x49, + 0x32, 0x30, 0x6a, 0x70, 0x65, 0x67, 0x20, 0x57, 0x31, 0x35, 0x20, 0x48, 0x31, 0x35, 0x20, 0x49, 0x70, 0x20, 0x46, 0x33, 0x30, 0x3a, 0x31, 0x20, 0x41, 0x31, 0x3a, 0x31, 0x0a, 0x46, 0x52, 0x41, 0x4d, 0x45, 0x0a, 0x51, 0x51, 0x51, 0x51, 0x51, 0x51, 0x51, 0x51, 0x51, 0x51, 0x51, 0x51, 0x51, 0x51, 0x51, 0x51, 0x51, @@ -85,7 +85,7 @@ unsigned char red_box_y4m[] = { 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0 }; -unsigned int red_box_y4m_len = 396; +unsigned int red_box_y4m_len = 400; unsigned char red_box_y42b_16x16_yuv[] = { 0x51, 0x51, 0x51, 0x51, 0x51, 0x51, 0x51, 0x51, 0x51, 0x51, 0x51, 0x51, diff --git a/subprojects/gst-plugins-good/tests/check/elements/y4menc.c b/subprojects/gst-plugins-good/tests/check/elements/y4menc.c index 523f18cd4d..25eaeee9b5 100644 --- a/subprojects/gst-plugins-good/tests/check/elements/y4menc.c +++ b/subprojects/gst-plugins-good/tests/check/elements/y4menc.c @@ -85,7 +85,7 @@ GST_START_TEST (test_y4m) GstCaps *caps; int i, num_buffers, size; const gchar *data0 = "YUV4MPEG2 W384 H288 Ip F25:1 A1:1\n"; - const gchar *data1 = "YUV4MPEG2 C420 W384 H288 Ip F25:1 A1:1\n"; + const gchar *data1 = "YUV4MPEG2 C420jpeg W384 H288 Ip F25:1 A1:1\n"; const gchar *data2 = "FRAME\n"; y4menc = setup_y4menc ();