Add code for parsing a subgop config file format

Adds support for subgop config file format. The parsing code
is enhanced to append configs derived from a string and a config
file. The code to parse a config file parameter from the
commandline is to be added in the next patch.

The example of a parseable subgop config file is as follows.
Note anything within box brackets is optional and not parsed:

---- File starts -----
[num_configs:2]
config:[0]
  num_frames:16  subgop_in_gop_code:0  num_steps:24
  [step:0] disp_frame_idx:16 type_code:F pyr_level:1 references:1^1
  [step:1] disp_frame_idx:8 type_code:F pyr_level:2 references:1^1^2^-1
  [step:2] disp_frame_idx:4 type_code:U pyr_level:3 references:1^1^2^-2^-1
  [step:3] disp_frame_idx:2 type_code:U pyr_level:4 references:1^1^-3^-2^-1
  [step:4] disp_frame_idx:1 type_code:V pyr_level:5 references:1^1^-4^-3^-2^-1
  [step:5] disp_frame_idx:2 type_code:S pyr_level:4
  [step:6] disp_frame_idx:3 type_code:V pyr_level:5 references:1^5^4^-3^-2^-1
  [step:7] disp_frame_idx:4 type_code:S pyr_level:3
  [step:8] disp_frame_idx:6 type_code:U pyr_level:4 references:1^3^4^-2^-1
  [step:9] disp_frame_idx:5 type_code:V pyr_level:5 references:1^4^5^3^-4^-2^-1
  [step:10] disp_frame_idx:6 type_code:S pyr_level:4
  [step:11] disp_frame_idx:7 type_code:V pyr_level:5 references:1^3^5^4^-2^-1
  [step:12] disp_frame_idx:8 type_code:R pyr_level:2 references:1^2^-1
  [step:13] disp_frame_idx:12 type_code:U pyr_level:3 references:1^3^2^-1
  [step:14] disp_frame_idx:10 type_code:U pyr_level:4 references:1^3^4^2^-3^-1
  [step:15] disp_frame_idx:9 type_code:V pyr_level:5 references:1^3^4^2^-4^-3^-1
  [step:16] disp_frame_idx:10 type_code:S pyr_level:4
  [step:17] disp_frame_idx:11 type_code:V pyr_level:5 references:1^2^4^5^-3^-1
  [step:18] disp_frame_idx:12 type_code:S pyr_level:3
  [step:19] disp_frame_idx:14 type_code:U pyr_level:4 references:1^2^4^3^-1
  [step:20] disp_frame_idx:13 type_code:V pyr_level:5 references:1^2^4^5^3^-4^-1
  [step:21] disp_frame_idx:14 type_code:S pyr_level:4
  [step:22] disp_frame_idx:15 type_code:V pyr_level:5 references:1^2^3^4^5^-1
  [step:23] disp_frame_idx:16 type_code:R pyr_level:1 references:1^1
config:[1]
  num_frames:16  subgop_in_gop_code:1  num_steps:25
  [step:0] disp_frame_idx:14 type_code:F pyr_level:1 references:1^1
  [step:1] disp_frame_idx:7 type_code:F pyr_level:2 references:1^1^2^-1
  [step:2] disp_frame_idx:4 type_code:U pyr_level:3 references:1^1^2^-2^-1
  [step:3] disp_frame_idx:2 type_code:U pyr_level:4 references:1^1^-3^-2^-1
  [step:4] disp_frame_idx:1 type_code:V pyr_level:5 references:1^1^-4^-3^-2^-1
  [step:5] disp_frame_idx:2 type_code:S pyr_level:4
  [step:6] disp_frame_idx:3 type_code:V pyr_level:5 references:1^5^4^-3^-2^-1
  [step:7] disp_frame_idx:4 type_code:S pyr_level:3
  [step:8] disp_frame_idx:6 type_code:U pyr_level:4 references:1^3^4^-2^-1
  [step:9] disp_frame_idx:5 type_code:V pyr_level:5 references:1^4^5^3^-4^-2^-1
  [step:10] disp_frame_idx:6 type_code:S pyr_level:4
  [step:11] disp_frame_idx:7 type_code:R pyr_level:2 references:1^2^-1
  [step:12] disp_frame_idx:11 type_code:U pyr_level:3 references:1^3^2^-1
  [step:13] disp_frame_idx:9 type_code:U pyr_level:4 references:1^3^4^2^-3^-1
  [step:14] disp_frame_idx:8 type_code:V pyr_level:5 references:1^3^4^2^-4^-3^-1
  [step:15] disp_frame_idx:9 type_code:S pyr_level:4
  [step:16] disp_frame_idx:10 type_code:V pyr_level:5 references:1^2^4^5^-3^-1
  [step:17] disp_frame_idx:11 type_code:S pyr_level:3
  [step:18] disp_frame_idx:13 type_code:U pyr_level:4 references:1^2^4^3^-1
  [step:19] disp_frame_idx:12 type_code:V pyr_level:5 references:1^2^4^5^3^-4^-1
  [step:20] disp_frame_idx:13 type_code:S pyr_level:4
  [step:21] disp_frame_idx:14 type_code:R pyr_level:1 references:1^-1
  [step:22] disp_frame_idx:16 type_code:U pyr_level:4 references:1^1^2^3^4
  [step:23] disp_frame_idx:15 type_code:V pyr_level:5 references:1^1^2^3^4^5
  [step:24] disp_frame_idx:16 type_code:S pyr_level:4
---- File end -----

Change-Id: I62ff654ff7bc438cba75891256b1f496335b001f
diff --git a/av1/encoder/encoder.c b/av1/encoder/encoder.c
index 0f90450..be04e23 100644
--- a/av1/encoder/encoder.c
+++ b/av1/encoder/encoder.c
@@ -805,6 +805,7 @@
                              sizeof(*oxcf->subgop_config_str));
       strcpy(cpi->subgop_config_str, oxcf->subgop_config_str);
       if (cpi->compressor_stage == ENCODE_STAGE) {
+        av1_init_subgop_config_set(&cpi->subgop_config_set);
         av1_process_subgop_config_set(oxcf->subgop_config_str,
                                       &cpi->subgop_config_set);
         printf("Successfully processed %d subgop configs.\n",
diff --git a/av1/encoder/subgop.c b/av1/encoder/subgop.c
index 7ec64ca..1f43311 100644
--- a/av1/encoder/subgop.c
+++ b/av1/encoder/subgop.c
@@ -9,6 +9,7 @@
  * PATENTS file, you can obtain it at www.aomedia.org/license/patent.
  */
 
+#include <ctype.h>
 #include <stdio.h>
 #include <string.h>
 #include "av1/encoder/subgop.h"
@@ -28,7 +29,42 @@
   return ptr;
 }
 
-static void init_subgop_config_set(SubGOPSetCfg *config_set) {
+static char *read_token_after(char *str, const char *delim, char **saveptr) {
+  if (str == NULL) return NULL;
+  if (strlen(str) == 0) return NULL;
+  char *ptr = str;
+  char *x = strstr(str, delim);
+  if (x) {
+    ptr = x + strlen(delim);
+    while (*x != 0 && !isspace(*x)) x++;
+    *x = 0;
+    *saveptr = x + 1;
+    return ptr;
+  } else {
+    *saveptr = NULL;
+    return NULL;
+  }
+}
+
+static bool readline(char *buf, int size, FILE *fp) {
+  buf[0] = '\0';
+  buf[size - 1] = '\0';
+  char *tmp;
+  while (1) {
+    if (fgets(buf, size, fp) == NULL) {
+      *buf = '\0';
+      return false;
+    } else {
+      if ((tmp = strrchr(buf, '\n')) != NULL) *tmp = '\0';
+      for (int i = 0; i < (int)strlen(buf); ++i) {
+        if (!isspace(buf[i])) return true;
+      }
+    }
+  }
+  return true;
+}
+
+void av1_init_subgop_config_set(SubGOPSetCfg *config_set) {
   memset(config_set, 0, sizeof(*config_set));
   config_set->num_configs = 0;
   for (int i = 0; i < MAX_SUBGOP_CONFIGS; ++i) {
@@ -36,6 +72,21 @@
   }
 }
 
+static void check_duplicate_and_add(SubGOPSetCfg *config_set) {
+  int k;
+  const int n = config_set->num_configs;
+  for (k = 0; k < n; ++k) {
+    if (config_set->config[k].num_frames == config_set->config[n].num_frames &&
+        config_set->config[k].subgop_in_gop_code ==
+            config_set->config[n].subgop_in_gop_code) {
+      memcpy(&config_set->config[k], &config_set->config[n],
+             sizeof(config_set->config[k]));
+      return;
+    }
+  }
+  if (k == n) config_set->num_configs++;
+}
+
 static int process_subgop_step(char *str, SubGOPStepCfg *step) {
   char *ptr;
   step->num_references = 0;
@@ -171,7 +222,6 @@
 }
 
 int av1_process_subgop_config_set(const char *param, SubGOPSetCfg *config_set) {
-  init_subgop_config_set(config_set);
   if (!param) return 1;
   if (!strlen(param)) return 1;
   const int bufsize = (int)((strlen(param) + 1) * sizeof(*param));
@@ -187,7 +237,7 @@
     if (res) {
       res = check_subgop_config(&config_set->config[config_set->num_configs]);
       if (res) {
-        config_set->num_configs++;
+        check_duplicate_and_add(config_set);
       } else {
         printf(
             "Warning: Subgop config validation failed for config #%d, "
@@ -207,19 +257,155 @@
   return 1;
 }
 
+static int process_subgop_config_fromfile(FILE *fp, SubGOPCfg *config) {
+  char line[256];
+  int linesize = 256;
+  int s;
+  if (!readline(line, linesize, fp)) return 0;
+  char *token;
+  char *str = line;
+  token = read_token_after(str, "num_frames:", &str);
+  if (!token) return 0;
+  if (strlen(token) == 0) return 0;
+  const int num_frames = atoi(token);
+  if (num_frames <= 0) return 0;
+  config->num_frames = num_frames;
+
+  token = read_token_after(str, "subgop_in_gop_code:", &str);
+  if (!token) return 0;
+  if (strlen(token) == 0) return 0;
+  const int subgop_in_gop_code = atoi(token);
+  // check for invalid subgop_in_gop_code
+  if (subgop_in_gop_code < 0 || subgop_in_gop_code >= SUBGOP_IN_GOP_CODES)
+    return 0;
+  config->subgop_in_gop_code = (SUBGOP_IN_GOP_CODE)subgop_in_gop_code;
+
+  token = read_token_after(str, "num_steps:", &str);
+  if (!token) return 0;
+  if (strlen(token) == 0) return 0;
+  config->num_steps = atoi(token);
+  for (s = 0; s < config->num_steps; ++s) {
+    SubGOPStepCfg *step = &config->step[s];
+    step->num_references = 0;
+    if (!readline(line, linesize, fp)) return 0;
+    str = line;
+
+    token = read_token_after(str, "disp_frame_idx:", &str);
+    if (!token) return 0;
+    if (strlen(token) == 0) return 0;
+    const int disp_frame_idx = atoi(token);
+    if (disp_frame_idx < 0 || disp_frame_idx > config->num_frames) return 0;
+    step->disp_frame_idx = disp_frame_idx;
+    token = read_token_after(str, "type_code:", &str);
+    if (!token) return 0;
+    if (strlen(token) != 1) return 0;
+    switch (*token) {
+      case 'V': step->type_code = FRAME_TYPE_INO_VISIBLE; break;
+      case 'R': step->type_code = FRAME_TYPE_INO_REPEAT; break;
+      case 'S': step->type_code = FRAME_TYPE_INO_SHOWEXISTING; break;
+      case 'F': step->type_code = FRAME_TYPE_OOO_FILTERED; break;
+      case 'U': step->type_code = FRAME_TYPE_OOO_UNFILTERED; break;
+      default: return 0;
+    }
+    if (step->type_code == FRAME_TYPE_INO_SHOWEXISTING) {
+      int k;
+      for (k = 0; k < s; ++k) {
+        if (config->step[k].disp_frame_idx == step->disp_frame_idx) {
+          step->pyr_level = config->step[k].pyr_level;
+          break;
+        }
+      }
+      // showexisting for a frame not coded before is invalid
+      if (k == s) return 0;
+      continue;
+    }
+    token = read_token_after(str, "pyr_level:", &str);
+    if (!token) return 0;
+    if (strlen(token) == 0) return 0;
+    const int pyr_level = atoi(token);
+    if (pyr_level <= 0) return 0;
+    step->pyr_level = pyr_level;
+
+    token = read_token_after(str, "references:", &str);
+    if (!token) {  // no references specified
+      step->num_references = -1;
+      continue;
+    }
+    if (strlen(token) == 0) continue;  // no references
+
+    char delim[] = "^";
+    str = token;
+    while ((token = my_strtok_r(str, delim, &str)) != NULL) {
+      if (step->num_references >= INTER_REFS_PER_FRAME) return 0;
+      step->references[step->num_references] = (int8_t)strtol(token, NULL, 10);
+      step->num_references++;
+    }
+  }
+  return 1;
+}
+
+int av1_process_subgop_config_set_fromfile(const char *paramfile,
+                                           SubGOPSetCfg *config_set) {
+  if (!paramfile) return 1;
+  if (!strlen(paramfile)) return 1;
+  FILE *fp = fopen(paramfile, "r");
+  if (!fp) return 0;
+  char line[256];
+  int linesize = 256;
+  char *tok, *str;
+  if (readline(line, linesize, fp)) {
+    tok = read_token_after(line, "[num_configs:", &str);
+    if (!tok) rewind(fp);
+  } else {
+    // Blank file
+    return 1;
+  }
+  while (readline(line, linesize, fp)) {
+    str = line;
+    tok = read_token_after(str, "config:", &str);
+    if (tok) {
+      int res = process_subgop_config_fromfile(
+          fp, &config_set->config[config_set->num_configs]);
+      if (res) {
+        res = check_subgop_config(&config_set->config[config_set->num_configs]);
+        if (res) {
+          check_duplicate_and_add(config_set);
+        } else {
+          printf(
+              "Warning: Subgop config validation failed for config #%d, "
+              "skipping the rest.\n",
+              config_set->num_configs);
+          fclose(fp);
+          return 0;
+        }
+      } else {
+        printf("Warning: config parsing failed, skipping the rest.\n");
+        fclose(fp);
+        return 0;
+      }
+    } else {
+      printf("Warning: config not found, skipping the rest.\n");
+      fclose(fp);
+      return 0;
+    }
+  }
+  fclose(fp);
+  return 1;
+}
+
 void av1_print_subgop_config_set(SubGOPSetCfg *config_set) {
   if (!config_set->num_configs) return;
   printf("SUBGOP CONFIG SET\n");
   printf("=================\n");
-  printf("num_configs:%d\n", config_set->num_configs);
+  printf("[num_configs:%d]\n", config_set->num_configs);
   for (int i = 0; i < config_set->num_configs; ++i) {
-    printf("config:%d ->\n", i);
+    printf("config:[%d]\n", i);
     SubGOPCfg *config = &config_set->config[i];
-    printf("  num_frames:%d\n", config->num_frames);
-    printf("  subgop_in_gop_code:%d\n", config->subgop_in_gop_code);
-    printf("  num_steps:%d\n", config->num_steps);
+    printf("  num_frames:%d", config->num_frames);
+    printf(" subgop_in_gop_code:%d", config->subgop_in_gop_code);
+    printf(" num_steps:%d\n", config->num_steps);
     for (int j = 0; j < config->num_steps; ++j) {
-      printf("  step:%d ->", j);
+      printf("  [step:%d]", j);
       printf(" disp_frame_idx:%d", config->step[j].disp_frame_idx);
       printf(" type_code:%c", config->step[j].type_code);
       printf(" pyr_level:%d", config->step[j].pyr_level);
diff --git a/av1/encoder/subgop.h b/av1/encoder/subgop.h
index 9c23dfe..464af04 100644
--- a/av1/encoder/subgop.h
+++ b/av1/encoder/subgop.h
@@ -17,8 +17,11 @@
 extern "C" {
 #endif
 
+void av1_init_subgop_config_set(SubGOPSetCfg *config_set);
 int av1_process_subgop_config_set(const char *param, SubGOPSetCfg *config_set);
 void av1_print_subgop_config_set(SubGOPSetCfg *config_set);
+int av1_process_subgop_config_set_fromfile(const char *paramfile,
+                                           SubGOPSetCfg *config_set);
 
 // Finds the ptr to the subgop config with the queried number of
 // frames and whether it is the last or first subgop in a gop.