From ebb5ffc1d462c70dfb2283a5c7afcb75288c7692 Mon Sep 17 00:00:00 2001 From: Elliott Karpilovsky Date: Thu, 28 Jan 2021 11:22:58 -0800 Subject: [PATCH] Relax constraints on Y4M header parsing Previous parser assumed that the header would not exceed 80 characters. However, with latest FFMPEG changes, the header of Y4M files can exceed this limit. New parser can parse up to ~200 characters. Arbitrary parsing in future commit. BUG=aomedia:2876 Change-Id: I2ab8a7930cb5b76004e6731321d0ea20ddf333c1 --- test/y4m_test.cc | 26 +++++++++++++++++ y4minput.c | 76 ++++++++++++++++++++++++++++-------------------- 2 files changed, 71 insertions(+), 31 deletions(-) diff --git a/test/y4m_test.cc b/test/y4m_test.cc index 5df389f52..8272263f6 100644 --- a/test/y4m_test.cc +++ b/test/y4m_test.cc @@ -213,4 +213,30 @@ TEST(Y4MHeaderTest, RegularHeader) { y4m_input_close(&y4m); } +// Testing that headers over 100 characters can be parsed. +static const char kY4MLongHeader[] = + "YUV4MPEG2 W4 H4 F30:1 Ip A0:0 C420jpeg XYSCSS=420JPEG " + "XCOLORRANGE=LIMITED XSOME_UNKNOWN_METADATA XOTHER_UNKNOWN_METADATA\n" + "FRAME\n" + "012345678912345601230123"; + +TEST(Y4MHeaderTest, LongHeader) { + libvpx_test::TempOutFile f; + fwrite(kY4MLongHeader, 1, sizeof(kY4MLongHeader), f.file()); + fflush(f.file()); + EXPECT_EQ(fseek(f.file(), 0, 0), 0); + + y4m_input y4m; + EXPECT_EQ(y4m_input_open(&y4m, f.file(), /*skip_buffer=*/NULL, + /*num_skip=*/0, /*only_420=*/0), + 0); + EXPECT_EQ(y4m.pic_w, 4); + EXPECT_EQ(y4m.pic_h, 4); + EXPECT_EQ(y4m.fps_n, 30); + EXPECT_EQ(y4m.fps_d, 1); + EXPECT_EQ(y4m.interlace, 'p'); + EXPECT_EQ(strcmp("420jpeg", y4m.chroma_type), 0); + y4m_input_close(&y4m); +} + } // namespace diff --git a/y4minput.c b/y4minput.c index 68000768c..1983021a1 100644 --- a/y4minput.c +++ b/y4minput.c @@ -52,15 +52,8 @@ static int file_read(void *buf, size_t size, FILE *file) { } static int y4m_parse_tags(y4m_input *_y4m, char *_tags) { - int got_w; - int got_h; - int got_fps; - int got_interlace; - int got_par; - int got_chroma; char *p; char *q; - got_w = got_h = got_fps = got_interlace = got_par = got_chroma = 0; for (p = _tags;; p = q) { /*Skip any leading spaces.*/ while (*p == ' ') p++; @@ -73,52 +66,74 @@ static int y4m_parse_tags(y4m_input *_y4m, char *_tags) { switch (p[0]) { case 'W': { if (sscanf(p + 1, "%d", &_y4m->pic_w) != 1) return -1; - got_w = 1; break; } case 'H': { if (sscanf(p + 1, "%d", &_y4m->pic_h) != 1) return -1; - got_h = 1; break; } case 'F': { if (sscanf(p + 1, "%d:%d", &_y4m->fps_n, &_y4m->fps_d) != 2) { return -1; } - got_fps = 1; break; } case 'I': { _y4m->interlace = p[1]; - got_interlace = 1; break; } case 'A': { if (sscanf(p + 1, "%d:%d", &_y4m->par_n, &_y4m->par_d) != 2) { return -1; } - got_par = 1; break; } case 'C': { if (q - p > 16) return -1; memcpy(_y4m->chroma_type, p + 1, q - p - 1); _y4m->chroma_type[q - p - 1] = '\0'; - got_chroma = 1; break; } /*Ignore unknown tags.*/ } } - if (!got_w || !got_h || !got_fps) return -1; - if (!got_interlace) _y4m->interlace = '?'; - if (!got_par) _y4m->par_n = _y4m->par_d = 0; - /*Chroma-type is not specified in older files, e.g., those generated by - mplayer.*/ - if (!got_chroma) strcpy(_y4m->chroma_type, "420"); return 0; } +/* Returns 1 if tags were parsed successfully, 0 otherwise. */ +static int parse_tags(y4m_input *y4m_ctx, char *buffer) { + /* Set Y4M tags to defaults, updating them as processing occurs. Mandatory + fields are marked with -1 and will be checked after the tags are parsed. */ + int ret; + y4m_ctx->pic_w = -1; + y4m_ctx->pic_h = -1; + y4m_ctx->fps_n = -1; /* Also serves as marker for fps_d */ + y4m_ctx->par_n = 0; + y4m_ctx->par_d = 0; + y4m_ctx->interlace = '?'; + snprintf(y4m_ctx->chroma_type, sizeof(y4m_ctx->chroma_type), "420"); + + ret = y4m_parse_tags(y4m_ctx, buffer); + if (ret < 0) { + return 0; + } + + /* Check the mandatory fields. */ + if (y4m_ctx->pic_w == -1) { + fprintf(stderr, "Width field missing\n"); + return 0; + } + if (y4m_ctx->pic_h == -1) { + fprintf(stderr, "Height field missing\n"); + return 0; + } + if (y4m_ctx->fps_n == -1) { + fprintf(stderr, "FPS field missing\n"); + return 0; + } + return 1; +} + /*All anti-aliasing filters in the following conversion functions are based on one of two window functions: The 6-tap Lanczos window (for down-sampling and shifts): @@ -786,13 +801,14 @@ static void y4m_convert_null(y4m_input *_y4m, unsigned char *_dst, } static const char TAG[] = "YUV4MPEG2"; +/* Temporary until arbitrary header parsing submitted. */ +#define Y4M_HEADER_BUF_SIZE 200 int y4m_input_open(y4m_input *y4m_ctx, FILE *file, char *skip_buffer, int num_skip, int only_420) { // File must start with |TAG|. - char tag_buffer[9]; // 9 == strlen(TAG) - char buffer[80] = { 0 }; - int ret; + char tag_buffer[9]; // 9 == strlen(TAG) + char buffer[Y4M_HEADER_BUF_SIZE] = { 0 }; // Rest of header. int i; // Read as much as possible from |skip_buffer|, which were characters // that were previously read from the file to do input-type detection. @@ -813,21 +829,19 @@ int y4m_input_open(y4m_input *y4m_ctx, FILE *file, char *skip_buffer, fprintf(stderr, "Error parsing header: space must follow %s\n", TAG); return -1; } - /*Read until newline, or 80 cols, whichever happens first.*/ - for (i = 0; i < 79; i++) { + /*Read until newline, or Y4M_HEADER_BUF_SIZE cols, whichever happens first.*/ + for (i = 0; i < Y4M_HEADER_BUF_SIZE - 1; i++) { if (!file_read(buffer + i, 1, file)) return -1; if (buffer[i] == '\n') break; } - /*We skipped too much header data.*/ - if (i == 79) { - fprintf(stderr, "Error parsing header; not a YUV4MPEG2 file?\n"); + if (i == Y4M_HEADER_BUF_SIZE - 1) { + fprintf(stderr, "Error parsing header; not a %s file?\n", TAG); return -1; } buffer[i] = '\0'; - ret = y4m_parse_tags(y4m_ctx, buffer); - if (ret < 0) { - fprintf(stderr, "Error parsing YUV4MPEG2 header.\n"); - return ret; + if (!parse_tags(y4m_ctx, buffer)) { + fprintf(stderr, "Error parsing %s header.\n", TAG); + return -1; } if (y4m_ctx->interlace == '?') { fprintf(stderr, -- 2.40.0