y4mdec: Parse extended headers written out by FFmpeg

References:
https://wiki.multimedia.cx/index.php/YUV4MPEG2
https://github.com/FFmpeg/FFmpeg/blob/eee3b7e2/libavformat/yuv4mpegenc.c#L74-L166

The primary purpose is to add high bit-depth y4m support, which is
commonly used for testing codecs.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/5997>
This commit is contained in:
Nirbheek Chauhan 2024-01-26 04:26:07 +05:30 committed by GStreamer Marge Bot
parent 968ebd26ab
commit 56b16e5232
2 changed files with 87 additions and 34 deletions

View file

@ -245783,7 +245783,7 @@
"presence": "always"
},
"src": {
"caps": "video/x-raw:\n format: { I420, Y42B, Y444 }\n width: [ 1, 2147483647 ]\n height: [ 1, 2147483647 ]\n framerate: [ 0/1, 2147483647/1 ]\n",
"caps": "video/x-raw:\n format: { I420, Y41B, Y42B, Y444, I420_10LE, I422_10LE, Y444_10LE, I420_12LE, I422_12LE, Y444_12LE, Y444_16LE, GRAY8, GRAY16_LE }\n width: [ 1, 2147483647 ]\n height: [ 1, 2147483647 ]\n framerate: [ 0/1, 2147483647/1 ]\n",
"direction": "src",
"presence": "always"
}

View file

@ -86,8 +86,12 @@ static GstStaticPadTemplate gst_y4m_dec_src_template =
GST_STATIC_PAD_TEMPLATE ("src",
GST_PAD_SRC,
GST_PAD_ALWAYS,
GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("{I420,Y42B,Y444}"))
);
GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("{ \
I420,Y41B,Y42B,Y444, \
I420_10LE,I422_10LE,Y444_10LE, \
I420_12LE,I422_12LE,Y444_12LE, \
Y444_16LE,GRAY8,GRAY16_LE \
}")));
/* class initialization */
#define gst_y4m_dec_parent_class parent_class
@ -287,38 +291,90 @@ gst_y4m_dec_bytes_to_timestamp (GstY4mDec * y4mdec, gint64 bytes)
gst_y4m_dec_bytes_to_frames (y4mdec, bytes));
}
static gboolean
parse_colorspace (const char *param, GstVideoFormat * format)
static GstVideoFormat
parse_colorspace (const char *param)
{
gulong iformat = strtoul (param, NULL, 10);
switch (iformat) {
case 420:
*format = GST_VIDEO_FORMAT_I420;
break;
case 422:
*format = GST_VIDEO_FORMAT_Y42B;
break;
case 444:
*format = GST_VIDEO_FORMAT_Y444;
break;
default:
return FALSE;
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;
}
}
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.
*/
// 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)
{
char *end;
*n = strtoul (param, &end, 10);
*n = g_ascii_strtoull (param, &end, 10);
if (end == param)
return FALSE;
param = end;
if (param[0] != ':')
return FALSE;
param++;
*d = strtoul (param, &end, 10);
*d = g_ascii_strtoull (param, &end, 10);
if (end == param)
return FALSE;
return TRUE;
@ -328,9 +384,7 @@ static gboolean
gst_y4m_dec_parse_header (GstY4mDec * y4mdec, char *header)
{
guint len;
char *valid;
char **params;
const char *end;
guint interlaced_char = 0;
gulong fps_n = 0, fps_d = 0;
gulong par_n = 0, par_d = 0;
@ -343,16 +397,13 @@ gst_y4m_dec_parse_header (GstY4mDec * y4mdec, char *header)
}
header += 10;
g_utf8_validate (header, -1, &end);
if (header == end) {
GST_ERROR_OBJECT (y4mdec, "Empty y4m header");
if (!g_str_is_ascii (header)) {
GST_ERROR_OBJECT (y4mdec, "Invalid non-ASCII y4m header: %s", header);
return FALSE;
}
valid = g_strndup (header, end - header);
GST_INFO_OBJECT (y4mdec, "Found header: %s", valid);
params = g_strsplit (valid, " ", -1);
g_free (valid);
GST_INFO_OBJECT (y4mdec, "Found header: %s", header);
params = g_strsplit (header, " ", -1);
len = g_strv_length (params);
for (int i = 0; i < len; i++) {
@ -361,19 +412,22 @@ gst_y4m_dec_parse_header (GstY4mDec * y4mdec, char *header)
const char *param_value = param + 1;
switch (param_type) {
case 'C':
if (!parse_colorspace (param_value, &format)) {
format = parse_colorspace (param_value);
if (format == GST_VIDEO_FORMAT_UNKNOWN) {
GST_ERROR_OBJECT (y4mdec, "Failed to parse colorspace: %s", param);
return FALSE;
}
GST_INFO_OBJECT (y4mdec, "Parsed format as %s",
gst_video_format_to_string (format));
continue;
case 'W':
if ((width = strtoul (param_value, NULL, 10)) == 0) {
if ((width = g_ascii_strtoull (param_value, NULL, 10)) == 0) {
GST_ERROR_OBJECT (y4mdec, "Failed to parse width: %s", param);
return FALSE;
}
continue;
case 'H':
if ((height = strtoul (param_value, NULL, 10)) == 0) {
if ((height = g_ascii_strtoull (param_value, NULL, 10)) == 0) {
GST_ERROR_OBJECT (y4mdec, "Failed to parse height: %s", param);
return FALSE;
}
@ -449,7 +503,6 @@ gst_y4m_dec_parse_header (GstY4mDec * y4mdec, char *header)
y4mdec->info.offset[2] + y4mdec->info.stride[2] * height;
break;
default:
g_assert_not_reached ();
break;
}