Relax constraints on Y4M header parsing
authorElliott Karpilovsky <elliottk@google.com>
Thu, 28 Jan 2021 19:22:58 +0000 (11:22 -0800)
committerElliott Karpilovsky <elliottk@google.com>
Thu, 28 Jan 2021 19:52:41 +0000 (11:52 -0800)
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
y4minput.c

index 5df389f..8272263 100644 (file)
@@ -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
index 6800076..1983021 100644 (file)
@@ -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,