vpxenc: initial implementation of multistream support
authorJohn Koleszar <jkoleszar@google.com>
Wed, 15 Feb 2012 20:39:38 +0000 (12:39 -0800)
committerJohn Koleszar <jkoleszar@google.com>
Thu, 16 Feb 2012 20:30:01 +0000 (12:30 -0800)
Add the ability to specify multiple output streams on the command line.
Streams are delimited by --, and most parameters inherit from previous
streams.

In this implementation, resizing streams is still not supported. It
does not make use of the new multistream support in the encoder either.
Two pass support runs all streams independently, though it's
theoretically possible that we could combine firstpass runs in the
future. The logic required for this is too tricky to do as part of this
initial implementation. This is mostly an effort to get the parameter
passing and independent streams working from the application's
perspective, and a later commit will add the rescaling and
multiresolution support.

Change-Id: Ibf18c2355f54189fc91952c734c899e5c072b3e0

vpxenc.c

index bb0a306..7d6758a 100644 (file)
--- a/vpxenc.c
+++ b/vpxenc.c
@@ -116,13 +116,17 @@ void warn(const char *fmt, ...)
 }
 
 
-static void ctx_exit_on_error(vpx_codec_ctx_t *ctx, const char *s)
+static void ctx_exit_on_error(vpx_codec_ctx_t *ctx, const char *s, ...)
 {
+    va_list ap;
+
+    va_start(ap, s);
     if (ctx->err)
     {
         const char *detail = vpx_codec_error_detail(ctx);
 
-        fprintf(stderr, "%s: %s\n", s, vpx_codec_error(ctx));
+        vfprintf(stderr, s, ap);
+        fprintf(stderr, ": %s\n", vpx_codec_error(ctx));
 
         if (detail)
             fprintf(stderr, "    %s\n", detail);
@@ -1480,8 +1484,6 @@ struct global_config
     const struct codec_item  *codec;
     int                       passes;
     int                       pass;
-    const char               *stats_fn;
-    const char               *out_fn;
     int                       usage;
     int                       deadline;
     int                       use_i420;
@@ -1490,13 +1492,47 @@ struct global_config
     int                       show_psnr;
     int                       have_framerate;
     struct vpx_rational       framerate;
-    int                       write_webm;
     int                       debug;
     int                       show_q_hist_buckets;
     int                       show_rate_hist_buckets;
 };
 
 
+/* Per-stream configuration */
+struct stream_config
+{
+    struct vpx_codec_enc_cfg  cfg;
+    const char               *out_fn;
+    const char               *stats_fn;
+    stereo_format_t           stereo_fmt;
+    int                       arg_ctrls[ARG_CTRL_CNT_MAX][2];
+    int                       arg_ctrl_cnt;
+    int                       write_webm;
+};
+
+
+struct stream_state
+{
+    int                       index;
+    struct stream_state      *next;
+    struct stream_config      config;
+    FILE                     *file;
+    struct rate_hist          rate_hist;
+    EbmlGlobal                ebml;
+    uint32_t                  hash;
+    uint64_t                  psnr_sse_total;
+    uint64_t                  psnr_samples_total;
+    double                    psnr_totals[4];
+    int                       psnr_count;
+    int                       counts[64];
+    vpx_codec_ctx_t           encoder;
+    unsigned int              frames_out;
+    uint64_t                  cx_time;
+    size_t                    nbytes;
+    stats_io_t                stats;
+};
+
+
 static void parse_global_config(struct global_config *global, char **argv)
 {
     char       **argi, **argj;
@@ -1507,7 +1543,6 @@ static void parse_global_config(struct global_config *global, char **argv)
     global->codec = codecs;
     global->passes = 1;
     global->use_i420 = 1;
-    global->write_webm = 1;
 
     for (argi = argj = argv; (*argj = *argi); argi += arg.argv_step)
     {
@@ -1543,8 +1578,6 @@ static void parse_global_config(struct global_config *global, char **argv)
                 die("Error: Invalid pass selected (%d)\n",
                     global->pass);
         }
-        else if (arg_match(&arg, &fpf_name, argi))
-            global->stats_fn = arg.val;
         else if (arg_match(&arg, &usage, argi))
             global->usage = arg_parse_uint(&arg);
         else if (arg_match(&arg, &deadline, argi))
@@ -1570,10 +1603,6 @@ static void parse_global_config(struct global_config *global, char **argv)
             global->framerate = arg_parse_rational(&arg);
             global->have_framerate = 1;
         }
-        else if (arg_match(&arg, &use_ivf, argi))
-            global->write_webm = 0;
-        else if (arg_match(&arg, &outputfile, argi))
-            global->out_fn = arg.val;
         else if (arg_match(&arg, &debugmode, argi))
             global->debug = 1;
         else if (arg_match(&arg, &q_hist_n, argi))
@@ -1586,9 +1615,6 @@ static void parse_global_config(struct global_config *global, char **argv)
 
     /* Validate global config */
 
-    /* Ensure that --passes and --pass are consistent. If --pass is set and
-     * --passes=2, ensure --fpf was set.
-     */
     if (global->pass)
     {
         /* DWIM: Assume the user meant passes=2 if pass=2 is specified */
@@ -1598,10 +1624,6 @@ static void parse_global_config(struct global_config *global, char **argv)
                  global->pass, global->pass);
             global->passes = global->pass;
         }
-
-        if (global->passes == 2 && !global->stats_fn)
-            die("Must specify --fpf when --pass=%d and --passes=2\n",
-                global->pass);
     }
 }
 
@@ -1667,199 +1689,599 @@ static void close_input_file(struct input_state *input)
         y4m_input_close(&input->y4m);
 }
 
-
-int main(int argc, const char **argv_)
+static struct stream_state *new_stream(struct global_config *global,
+                                       struct stream_state  *prev)
 {
-    vpx_codec_ctx_t        encoder;
-    int                    i;
-    FILE                  *outfile;
-    vpx_codec_enc_cfg_t    cfg;
-    vpx_codec_err_t        res;
-    int                    pass;
-    stats_io_t             stats;
-    vpx_image_t            raw;
-    int                    frame_avail, got_data;
+    struct stream_state *stream;
 
-    struct input_state       input = {0};
-    struct global_config     global;
-    struct arg               arg;
-    char                   **argv, **argi, **argj;
-    int                      arg_ctrls[ARG_CTRL_CNT_MAX][2], arg_ctrl_cnt = 0;
-    static const arg_def_t **ctrl_args = no_args;
-    static const int        *ctrl_args_map = NULL;
-    unsigned long            cx_time = 0;
+    stream = calloc(1, sizeof(*stream));
+    if(!stream)
+        fatal("Failed to allocate new stream.");
+    if(prev)
+    {
+        memcpy(stream, prev, sizeof(*stream));
+        stream->index++;
+        prev->next = stream;
+    }
+    else
+    {
+        vpx_codec_err_t  res;
 
-    EbmlGlobal               ebml = {0};
-    uint32_t                 hash = 0;
-    uint64_t                 psnr_sse_total = 0;
-    uint64_t                 psnr_samples_total = 0;
-    double                   psnr_totals[4] = {0, 0, 0, 0};
-    int                      psnr_count = 0;
-    stereo_format_t          stereo_fmt = STEREO_FORMAT_MONO;
-    int                      counts[64]={0};
-    struct rate_hist         rate_hist={0};
+        /* Populate encoder configuration */
+        res = vpx_codec_enc_config_default(global->codec->iface,
+                                           &stream->config.cfg,
+                                           global->usage);
+        if (res)
+            fatal("Failed to get config: %s\n", vpx_codec_err_to_string(res));
 
-    exec_name = argv_[0];
-    ebml.last_pts_ms = -1;
+        /* Change the default timebase to a high enough value so that the
+         * encoder will always create strictly increasing timestamps.
+         */
+        stream->config.cfg.g_timebase.den = 1000;
 
-    if (argc < 3)
-        usage_exit();
+        /* Never use the library's default resolution, require it be parsed
+         * from the file or set on the command line.
+         */
+        stream->config.cfg.g_w = 0;
+        stream->config.cfg.g_h = 0;
 
-    /* First parse the global configuration values, because we want to apply
-     * other parameters on top of the default configuration provided by the
-     * codec.
-     */
-    argv = argv_dup(argc - 1, argv_ + 1);
-    parse_global_config(&global, argv);
+        /* Initialize remaining stream parameters */
+        stream->config.stereo_fmt = STEREO_FORMAT_MONO;
+        stream->config.write_webm = 1;
+        stream->ebml.last_pts_ms = -1;
+    }
 
-    /* Populate encoder configuration */
-    res = vpx_codec_enc_config_default(global.codec->iface, &cfg,
-                                       global.usage);
+    /* Output files must be specified for each stream */
+    stream->config.out_fn = NULL;
 
-    if (res)
-        fatal("Failed to get config: %s", vpx_codec_err_to_string(res));
+    stream->next = NULL;
+    return stream;
+}
 
-    /* Change the default timebase to a high enough value so that the encoder
-     * will always create strictly increasing timestamps.
-     */
-    cfg.g_timebase.den = 1000;
 
-    /* Never use the library's default resolution, require it be parsed
-     * from the file or set on the command line.
-     */
-    cfg.g_w = 0;
-    cfg.g_h = 0;
+static int parse_stream_params(struct global_config *global,
+                               struct stream_state  *stream,
+                               char **argv)
+{
+    char                   **argi, **argj;
+    struct arg               arg;
+    static const arg_def_t **ctrl_args = no_args;
+    static const int        *ctrl_args_map = NULL;
+    struct stream_config    *config = &stream->config;
+    int                      eos_mark_found = 0;
 
-    /* Setup default input stream settings */
-    input.framerate.num = 30;
-    input.framerate.den = 1;
-    input.use_i420 = 1;
+    /* Handle codec specific options */
+    if (global->codec->iface == &vpx_codec_vp8_cx_algo)
+    {
+        ctrl_args = vp8_args;
+        ctrl_args_map = vp8_arg_ctrl_map;
+    }
 
-    /* Now parse the remainder of the parameters. */
     for (argi = argj = argv; (*argj = *argi); argi += arg.argv_step)
     {
         arg.argv_step = 1;
 
+        /* Once we've found an end-of-stream marker (--) we want to continue
+         * shifting arguments but not consuming them.
+         */
+        if (eos_mark_found)
+        {
+            argj++;
+            continue;
+        }
+        else if (!strcmp(*argj, "--"))
+        {
+            eos_mark_found = 1;
+            continue;
+        }
+
         if (0);
+        else if (arg_match(&arg, &outputfile, argi))
+            config->out_fn = arg.val;
+        else if (arg_match(&arg, &fpf_name, argi))
+            config->stats_fn = arg.val;
+        else if (arg_match(&arg, &use_ivf, argi))
+            config->write_webm = 0;
         else if (arg_match(&arg, &threads, argi))
-            cfg.g_threads = arg_parse_uint(&arg);
+            config->cfg.g_threads = arg_parse_uint(&arg);
         else if (arg_match(&arg, &profile, argi))
-            cfg.g_profile = arg_parse_uint(&arg);
+            config->cfg.g_profile = arg_parse_uint(&arg);
         else if (arg_match(&arg, &width, argi))
-            cfg.g_w = arg_parse_uint(&arg);
+            config->cfg.g_w = arg_parse_uint(&arg);
         else if (arg_match(&arg, &height, argi))
-            cfg.g_h = arg_parse_uint(&arg);
+            config->cfg.g_h = arg_parse_uint(&arg);
         else if (arg_match(&arg, &stereo_mode, argi))
-            stereo_fmt = arg_parse_enum_or_int(&arg);
+            config->stereo_fmt = arg_parse_enum_or_int(&arg);
         else if (arg_match(&arg, &timebase, argi))
-            cfg.g_timebase = arg_parse_rational(&arg);
+            config->cfg.g_timebase = arg_parse_rational(&arg);
         else if (arg_match(&arg, &error_resilient, argi))
-            cfg.g_error_resilient = arg_parse_uint(&arg);
+            config->cfg.g_error_resilient = arg_parse_uint(&arg);
         else if (arg_match(&arg, &lag_in_frames, argi))
-            cfg.g_lag_in_frames = arg_parse_uint(&arg);
+            config->cfg.g_lag_in_frames = arg_parse_uint(&arg);
         else if (arg_match(&arg, &dropframe_thresh, argi))
-            cfg.rc_dropframe_thresh = arg_parse_uint(&arg);
+            config->cfg.rc_dropframe_thresh = arg_parse_uint(&arg);
         else if (arg_match(&arg, &resize_allowed, argi))
-            cfg.rc_resize_allowed = arg_parse_uint(&arg);
+            config->cfg.rc_resize_allowed = arg_parse_uint(&arg);
         else if (arg_match(&arg, &resize_up_thresh, argi))
-            cfg.rc_resize_up_thresh = arg_parse_uint(&arg);
+            config->cfg.rc_resize_up_thresh = arg_parse_uint(&arg);
         else if (arg_match(&arg, &resize_down_thresh, argi))
-            cfg.rc_resize_down_thresh = arg_parse_uint(&arg);
+            config->cfg.rc_resize_down_thresh = arg_parse_uint(&arg);
         else if (arg_match(&arg, &end_usage, argi))
-            cfg.rc_end_usage = arg_parse_enum_or_int(&arg);
+            config->cfg.rc_end_usage = arg_parse_enum_or_int(&arg);
         else if (arg_match(&arg, &target_bitrate, argi))
-            cfg.rc_target_bitrate = arg_parse_uint(&arg);
+            config->cfg.rc_target_bitrate = arg_parse_uint(&arg);
         else if (arg_match(&arg, &min_quantizer, argi))
-            cfg.rc_min_quantizer = arg_parse_uint(&arg);
+            config->cfg.rc_min_quantizer = arg_parse_uint(&arg);
         else if (arg_match(&arg, &max_quantizer, argi))
-            cfg.rc_max_quantizer = arg_parse_uint(&arg);
+            config->cfg.rc_max_quantizer = arg_parse_uint(&arg);
         else if (arg_match(&arg, &undershoot_pct, argi))
-            cfg.rc_undershoot_pct = arg_parse_uint(&arg);
+            config->cfg.rc_undershoot_pct = arg_parse_uint(&arg);
         else if (arg_match(&arg, &overshoot_pct, argi))
-            cfg.rc_overshoot_pct = arg_parse_uint(&arg);
+            config->cfg.rc_overshoot_pct = arg_parse_uint(&arg);
         else if (arg_match(&arg, &buf_sz, argi))
-            cfg.rc_buf_sz = arg_parse_uint(&arg);
+            config->cfg.rc_buf_sz = arg_parse_uint(&arg);
         else if (arg_match(&arg, &buf_initial_sz, argi))
-            cfg.rc_buf_initial_sz = arg_parse_uint(&arg);
+            config->cfg.rc_buf_initial_sz = arg_parse_uint(&arg);
         else if (arg_match(&arg, &buf_optimal_sz, argi))
-            cfg.rc_buf_optimal_sz = arg_parse_uint(&arg);
+            config->cfg.rc_buf_optimal_sz = arg_parse_uint(&arg);
         else if (arg_match(&arg, &bias_pct, argi))
         {
-            cfg.rc_2pass_vbr_bias_pct = arg_parse_uint(&arg);
+            config->cfg.rc_2pass_vbr_bias_pct = arg_parse_uint(&arg);
 
-            if (global.passes < 2)
+            if (global->passes < 2)
                 warn("option %s ignored in one-pass mode.\n", arg.name);
         }
         else if (arg_match(&arg, &minsection_pct, argi))
         {
-            cfg.rc_2pass_vbr_minsection_pct = arg_parse_uint(&arg);
+            config->cfg.rc_2pass_vbr_minsection_pct = arg_parse_uint(&arg);
 
-            if (global.passes < 2)
+            if (global->passes < 2)
                 warn("option %s ignored in one-pass mode.\n", arg.name);
         }
         else if (arg_match(&arg, &maxsection_pct, argi))
         {
-            cfg.rc_2pass_vbr_maxsection_pct = arg_parse_uint(&arg);
+            config->cfg.rc_2pass_vbr_maxsection_pct = arg_parse_uint(&arg);
 
-            if (global.passes < 2)
+            if (global->passes < 2)
                 warn("option %s ignored in one-pass mode.\n", arg.name);
         }
         else if (arg_match(&arg, &kf_min_dist, argi))
-            cfg.kf_min_dist = arg_parse_uint(&arg);
+            config->cfg.kf_min_dist = arg_parse_uint(&arg);
         else if (arg_match(&arg, &kf_max_dist, argi))
-            cfg.kf_max_dist = arg_parse_uint(&arg);
+            config->cfg.kf_max_dist = arg_parse_uint(&arg);
         else if (arg_match(&arg, &kf_disabled, argi))
-            cfg.kf_mode = VPX_KF_DISABLED;
+            config->cfg.kf_mode = VPX_KF_DISABLED;
         else
-            argj++;
+        {
+            int i, match = 0;
+
+            for (i = 0; ctrl_args[i]; i++)
+            {
+                if (arg_match(&arg, ctrl_args[i], argi))
+                {
+                    int j;
+                    match = 1;
+
+                    /* Point either to the next free element or the first
+                    * instance of this control.
+                    */
+                    for(j=0; j<config->arg_ctrl_cnt; j++)
+                        if(config->arg_ctrls[j][0] == ctrl_args_map[i])
+                            break;
+
+                    /* Update/insert */
+                    assert(j < ARG_CTRL_CNT_MAX);
+                    if (j < ARG_CTRL_CNT_MAX)
+                    {
+                        config->arg_ctrls[j][0] = ctrl_args_map[i];
+                        config->arg_ctrls[j][1] = arg_parse_enum_or_int(&arg);
+                        if(j == config->arg_ctrl_cnt)
+                            config->arg_ctrl_cnt++;
+                    }
+
+                }
+            }
+
+            if (!match)
+                argj++;
+        }
     }
 
-    /* Handle codec specific options */
-#if CONFIG_VP8_ENCODER
+    return eos_mark_found;
+}
 
-    if (global.codec->iface == &vpx_codec_vp8_cx_algo)
+
+#define FOREACH_STREAM(func)\
+do\
+{\
+    struct stream_state  *stream;\
+\
+    for(stream = streams; stream; stream = stream->next)\
+        func;\
+}while(0)
+
+
+static void validate_stream_config(struct stream_state *stream)
+{
+    struct stream_state *streami;
+
+    if(!stream->config.cfg.g_w || !stream->config.cfg.g_h)
+        fatal("Stream %d: Specify stream dimensions with --width (-w) "
+              " and --height (-h)", stream->index);
+
+    for(streami = stream; streami; streami = streami->next)
     {
-        ctrl_args = vp8_args;
-        ctrl_args_map = vp8_arg_ctrl_map;
+        /* All streams require output files */
+        if(!streami->config.out_fn)
+            fatal("Stream %d: Output file is required (specify with -o)",
+                  streami->index);
+
+        /* Check for two streams outputting to the same file */
+        if(streami != stream)
+        {
+            const char *a = stream->config.out_fn;
+            const char *b = streami->config.out_fn;
+            if(!strcmp(a,b) && strcmp(a, "/dev/null") && strcmp(a, ":nul"))
+                fatal("Stream %d: duplicate output file (from stream %d)",
+                      streami->index, stream->index);
+        }
+
+        /* Check for two streams sharing a stats file. */
+        if(streami != stream)
+        {
+            const char *a = stream->config.stats_fn;
+            const char *b = streami->config.stats_fn;
+            if(a && b && !strcmp(a,b))
+                fatal("Stream %d: duplicate stats file (from stream %d)",
+                      streami->index, stream->index);
+        }
     }
+}
 
-#endif
 
-    for (argi = argj = argv; (*argj = *argi); argi += arg.argv_step)
+static void set_stream_dimensions(struct stream_state *stream,
+                                  unsigned int w,
+                                  unsigned int h)
+{
+    if ((stream->config.cfg.g_w && stream->config.cfg.g_w != w)
+        ||(stream->config.cfg.g_h && stream->config.cfg.g_h != h))
+        fatal("Stream %d: Resizing not yet supported", stream->index);
+    stream->config.cfg.g_w = w;
+    stream->config.cfg.g_h = h;
+}
+
+
+static void show_stream_config(struct stream_state  *stream,
+                               struct global_config *global,
+                               struct input_state   *input)
+{
+
+#define SHOW(field) \
+    fprintf(stderr, "    %-28s = %d\n", #field, stream->config.cfg.field)
+
+    if(stream->index == 0)
     {
-        int match = 0;
+        fprintf(stderr, "Codec: %s\n",
+                vpx_codec_iface_name(global->codec->iface));
+        fprintf(stderr, "Source file: %s Format: %s\n", input->fn,
+                input->use_i420 ? "I420" : "YV12");
+    }
+    if(stream->next || stream->index)
+        fprintf(stderr, "\nStream Index: %d\n", stream->index);
+    fprintf(stderr, "Destination file: %s\n", stream->config.out_fn);
+    fprintf(stderr, "Encoder parameters:\n");
+
+    SHOW(g_usage);
+    SHOW(g_threads);
+    SHOW(g_profile);
+    SHOW(g_w);
+    SHOW(g_h);
+    SHOW(g_timebase.num);
+    SHOW(g_timebase.den);
+    SHOW(g_error_resilient);
+    SHOW(g_pass);
+    SHOW(g_lag_in_frames);
+    SHOW(rc_dropframe_thresh);
+    SHOW(rc_resize_allowed);
+    SHOW(rc_resize_up_thresh);
+    SHOW(rc_resize_down_thresh);
+    SHOW(rc_end_usage);
+    SHOW(rc_target_bitrate);
+    SHOW(rc_min_quantizer);
+    SHOW(rc_max_quantizer);
+    SHOW(rc_undershoot_pct);
+    SHOW(rc_overshoot_pct);
+    SHOW(rc_buf_sz);
+    SHOW(rc_buf_initial_sz);
+    SHOW(rc_buf_optimal_sz);
+    SHOW(rc_2pass_vbr_bias_pct);
+    SHOW(rc_2pass_vbr_minsection_pct);
+    SHOW(rc_2pass_vbr_maxsection_pct);
+    SHOW(kf_mode);
+    SHOW(kf_min_dist);
+    SHOW(kf_max_dist);
+}
+
+
+static void open_output_file(struct stream_state *stream,
+                             struct global_config *global)
+{
+    const char *fn = stream->config.out_fn;
+
+    stream->file = strcmp(fn, "-") ? fopen(fn, "wb") : set_binary_mode(stdout);
+
+    if (!stream->file)
+        fatal("Failed to open output file");
+
+    if(stream->config.write_webm && fseek(stream->file, 0, SEEK_CUR))
+        fatal("WebM output to pipes not supported.");
+
+    if(stream->config.write_webm)
+    {
+        stream->ebml.stream = stream->file;
+        write_webm_file_header(&stream->ebml, &stream->config.cfg,
+                               &global->framerate,
+                               stream->config.stereo_fmt);
+    }
+    else
+        write_ivf_file_header(stream->file, &stream->config.cfg,
+                              global->codec->fourcc, 0);
+}
+
+
+static void close_output_file(struct stream_state *stream,
+                              unsigned int         fourcc)
+{
+    if(stream->config.write_webm)
+    {
+        write_webm_file_footer(&stream->ebml, stream->hash);
+        free(stream->ebml.cue_list);
+        stream->ebml.cue_list = NULL;
+    }
+    else
+    {
+        if (!fseek(stream->file, 0, SEEK_SET))
+            write_ivf_file_header(stream->file, &stream->config.cfg,
+                                  fourcc,
+                                  stream->frames_out);
+    }
+
+    fclose(stream->file);
+}
+
+
+static void setup_pass(struct stream_state  *stream,
+                       struct global_config *global,
+                       int                   pass)
+{
+    if (stream->config.stats_fn)
+    {
+        if (!stats_open_file(&stream->stats, stream->config.stats_fn,
+                             pass))
+            fatal("Failed to open statistics store");
+    }
+    else
+    {
+        if (!stats_open_mem(&stream->stats, pass))
+            fatal("Failed to open statistics store");
+    }
+
+    stream->config.cfg.g_pass = global->passes == 2
+        ? pass ? VPX_RC_LAST_PASS : VPX_RC_FIRST_PASS
+        : VPX_RC_ONE_PASS;
+    if (pass)
+        stream->config.cfg.rc_twopass_stats_in = stats_get(&stream->stats);
+}
+
+
+static void initialize_encoder(struct stream_state  *stream,
+                               struct global_config *global)
+{
+    int i;
+
+    /* Construct Encoder Context */
+    vpx_codec_enc_init(&stream->encoder, global->codec->iface,
+                       &stream->config.cfg,
+                       global->show_psnr ? VPX_CODEC_USE_PSNR : 0);
+    ctx_exit_on_error(&stream->encoder, "Failed to initialize encoder");
+
+    /* Note that we bypass the vpx_codec_control wrapper macro because
+     * we're being clever to store the control IDs in an array. Real
+     * applications will want to make use of the enumerations directly
+     */
+    for (i = 0; i < stream->config.arg_ctrl_cnt; i++)
+    {
+        int ctrl = stream->config.arg_ctrls[i][0];
+        int value = stream->config.arg_ctrls[i][1];
+        if (vpx_codec_control_(&stream->encoder, ctrl, value))
+            fprintf(stderr, "Error: Tried to set control %d = %d\n",
+                    ctrl, value);
+
+        ctx_exit_on_error(&stream->encoder, "Failed to control codec");
+    }
+}
 
-        arg.argv_step = 1;
 
-        for (i = 0; ctrl_args[i]; i++)
+static void encode_frame(struct stream_state  *stream,
+                         struct global_config *global,
+                         struct vpx_image     *img,
+                         unsigned int          frames_in)
+{
+    vpx_codec_pts_t frame_start, next_frame_start;
+    struct vpx_codec_enc_cfg *cfg = &stream->config.cfg;
+    struct vpx_usec_timer timer;
+
+    frame_start = (cfg->g_timebase.den * (int64_t)(frames_in - 1)
+                  * global->framerate.den)
+                  / cfg->g_timebase.num / global->framerate.num;
+    next_frame_start = (cfg->g_timebase.den * (int64_t)(frames_in)
+                        * global->framerate.den)
+                        / cfg->g_timebase.num / global->framerate.num;
+    vpx_usec_timer_start(&timer);
+    vpx_codec_encode(&stream->encoder, img, frame_start,
+                     next_frame_start - frame_start,
+                     0, global->deadline);
+    vpx_usec_timer_mark(&timer);
+    stream->cx_time += vpx_usec_timer_elapsed(&timer);
+    ctx_exit_on_error(&stream->encoder, "Stream %d: Failed to encode frame",
+                      stream->index);
+}
+
+
+static void update_quantizer_histogram(struct stream_state *stream)
+{
+    if(stream->config.cfg.g_pass != VPX_RC_FIRST_PASS)
+    {
+        int q;
+
+        vpx_codec_control(&stream->encoder, VP8E_GET_LAST_QUANTIZER_64, &q);
+        ctx_exit_on_error(&stream->encoder, "Failed to read quantizer");
+        stream->counts[q]++;
+    }
+}
+
+
+static void get_cx_data(struct stream_state  *stream,
+                        struct global_config *global,
+                        int                  *got_data)
+{
+    const vpx_codec_cx_pkt_t *pkt;
+    const struct vpx_codec_enc_cfg *cfg = &stream->config.cfg;
+    vpx_codec_iter_t iter = NULL;
+
+    while ((pkt = vpx_codec_get_cx_data(&stream->encoder, &iter)))
+    {
+        *got_data = 1;
+
+        switch (pkt->kind)
         {
-            if (arg_match(&arg, ctrl_args[i], argi))
+        case VPX_CODEC_CX_FRAME_PKT:
+            stream->frames_out++;
+            fprintf(stderr, " %6luF",
+                    (unsigned long)pkt->data.frame.sz);
+
+            update_rate_histogram(&stream->rate_hist, cfg, pkt);
+            if(stream->config.write_webm)
+            {
+                /* Update the hash */
+                if(!stream->ebml.debug)
+                    stream->hash = murmur(pkt->data.frame.buf,
+                                          pkt->data.frame.sz, stream->hash);
+
+                write_webm_block(&stream->ebml, cfg, pkt);
+            }
+            else
             {
-                int j;
-                match = 1;
-
-                /* Point either to the next free element or the first
-                * instance of this control.
-                */
-                for(j=0; j<arg_ctrl_cnt; j++)
-                    if(arg_ctrls[j][0] == ctrl_args_map[i])
-                        break;
-
-                /* Update/insert */
-                assert(j < ARG_CTRL_CNT_MAX);
-                if (j < ARG_CTRL_CNT_MAX)
+                write_ivf_frame_header(stream->file, pkt);
+                if(fwrite(pkt->data.frame.buf, 1,
+                          pkt->data.frame.sz, stream->file));
+            }
+            stream->nbytes += pkt->data.raw.sz;
+            break;
+        case VPX_CODEC_STATS_PKT:
+            stream->frames_out++;
+            fprintf(stderr, " %6luS",
+                   (unsigned long)pkt->data.twopass_stats.sz);
+            stats_write(&stream->stats,
+                        pkt->data.twopass_stats.buf,
+                        pkt->data.twopass_stats.sz);
+            stream->nbytes += pkt->data.raw.sz;
+            break;
+        case VPX_CODEC_PSNR_PKT:
+
+            if (global->show_psnr)
+            {
+                int i;
+
+                stream->psnr_sse_total += pkt->data.psnr.sse[0];
+                stream->psnr_samples_total += pkt->data.psnr.samples[0];
+                for (i = 0; i < 4; i++)
                 {
-                    arg_ctrls[j][0] = ctrl_args_map[i];
-                    arg_ctrls[j][1] = arg_parse_enum_or_int(&arg);
-                    if(j == arg_ctrl_cnt)
-                        arg_ctrl_cnt++;
+                    fprintf(stderr, "%.3lf ", pkt->data.psnr.psnr[i]);
+                    stream->psnr_totals[i] += pkt->data.psnr.psnr[i];
                 }
-
+                stream->psnr_count++;
             }
+
+            break;
+        default:
+            break;
         }
+    }
+}
 
-        if (!match)
-            argj++;
+
+static void show_psnr(struct stream_state  *stream)
+{
+    int i;
+    double ovpsnr;
+
+    if (!stream->psnr_count)
+        return;
+
+    fprintf(stderr, "Stream %d PSNR (Overall/Avg/Y/U/V)", stream->index);
+    ovpsnr = vp8_mse2psnr(stream->psnr_samples_total, 255.0,
+                          stream->psnr_sse_total);
+    fprintf(stderr, " %.3lf", ovpsnr);
+
+    for (i = 0; i < 4; i++)
+    {
+        fprintf(stderr, " %.3lf", stream->psnr_totals[i]/stream->psnr_count);
+    }
+    fprintf(stderr, "\n");
+}
+
+
+float usec_to_fps(uint64_t usec, unsigned int frames)
+{
+    return usec > 0 ? (float)frames * 1000000.0 / (float)usec : 0;
+}
+
+
+int main(int argc, const char **argv_)
+{
+    int                    pass;
+    vpx_image_t            raw;
+    int                    frame_avail, got_data;
+
+    struct input_state       input = {0};
+    struct global_config     global;
+    struct stream_state     *streams = NULL;
+    char                   **argv, **argi;
+    unsigned long            cx_time = 0;
+    int                      stream_cnt = 0;
+
+    exec_name = argv_[0];
+
+    if (argc < 3)
+        usage_exit();
+
+    /* Setup default input stream settings */
+    input.framerate.num = 30;
+    input.framerate.den = 1;
+    input.use_i420 = 1;
+
+    /* First parse the global configuration values, because we want to apply
+     * other parameters on top of the default configuration provided by the
+     * codec.
+     */
+    argv = argv_dup(argc - 1, argv_ + 1);
+    parse_global_config(&global, argv);
+
+    {
+        /* Now parse each stream's parameters. Using a local scope here
+         * due to the use of 'stream' as loop variable in FOREACH_STREAM
+         * loops
+         */
+        struct stream_state *stream = NULL;
+
+        do
+        {
+            stream = new_stream(&global, stream);
+            stream_cnt++;
+            if(!streams)
+                streams = stream;
+        } while(parse_stream_params(&global, stream, argv));
     }
 
     /* Check for unrecognized options */
@@ -1873,28 +2295,39 @@ int main(int argc, const char **argv_)
     if (!input.fn)
         usage_exit();
 
-    if(!global.out_fn)
-        die("Error: Output file is required (specify with -o)\n");
-
-    memset(&stats, 0, sizeof(stats));
-
     for (pass = global.pass ? global.pass - 1 : 0; pass < global.passes; pass++)
     {
-        int frames_in = 0, frames_out = 0;
-        int64_t nbytes = 0;
+        int frames_in = 0;
 
         open_input_file(&input);
 
-        /* Update configuration settings parsed from the file */
-        if(input.w && input.h)
-        {
-            cfg.g_w = input.w;
-            cfg.g_h = input.h;
-        }
+        /* If the input file doesn't specify its w/h (raw files), try to get
+         * the data from the first stream's configuration.
+         */
+        if(!input.w || !input.h)
+            FOREACH_STREAM({
+                if(stream->config.cfg.g_w && stream->config.cfg.g_h)
+                {
+                    input.w = stream->config.cfg.g_w;
+                    input.h = stream->config.cfg.g_h;
+                    break;
+                }
+            });
+
+        /* Update stream configurations from the input file's parameters */
+        FOREACH_STREAM(set_stream_dimensions(stream, input.w, input.h));
+        FOREACH_STREAM(validate_stream_config(stream));
+
+        /* Ensure that --passes and --pass are consistent. If --pass is set and
+         * --passes=2, ensure --fpf was set.
+         */
+        if (global.pass && global.passes == 2)
+            FOREACH_STREAM({
+                if(!stream->config.stats_fn)
+                    die("Stream %d: Must specify --fpf when --pass=%d"
+                        " and --passes=2\n", stream->index, global.pass);
+            });
 
-        if(!cfg.g_w || !cfg.g_h)
-            fatal("Specify stream dimensions with --width (-w) "
-                  " and --height (-h).\n");
 
         /* Use the frame rate from the file only if none was specified
          * on the command-line.
@@ -1902,48 +2335,9 @@ int main(int argc, const char **argv_)
         if (!global.have_framerate)
             global.framerate = input.framerate;
 
-
-#define SHOW(field) fprintf(stderr, "    %-28s = %d\n", #field, cfg.field)
-
+        /* Show configuration */
         if (global.verbose && pass == 0)
-        {
-            fprintf(stderr, "Codec: %s\n",
-                    vpx_codec_iface_name(global.codec->iface));
-            fprintf(stderr, "Source file: %s Format: %s\n", input.fn,
-                    global.use_i420 ? "I420" : "YV12");
-            fprintf(stderr, "Destination file: %s\n", global.out_fn);
-            fprintf(stderr, "Encoder parameters:\n");
-
-            SHOW(g_usage);
-            SHOW(g_threads);
-            SHOW(g_profile);
-            SHOW(g_w);
-            SHOW(g_h);
-            SHOW(g_timebase.num);
-            SHOW(g_timebase.den);
-            SHOW(g_error_resilient);
-            SHOW(g_pass);
-            SHOW(g_lag_in_frames);
-            SHOW(rc_dropframe_thresh);
-            SHOW(rc_resize_allowed);
-            SHOW(rc_resize_up_thresh);
-            SHOW(rc_resize_down_thresh);
-            SHOW(rc_end_usage);
-            SHOW(rc_target_bitrate);
-            SHOW(rc_min_quantizer);
-            SHOW(rc_max_quantizer);
-            SHOW(rc_undershoot_pct);
-            SHOW(rc_overshoot_pct);
-            SHOW(rc_buf_sz);
-            SHOW(rc_buf_initial_sz);
-            SHOW(rc_buf_optimal_sz);
-            SHOW(rc_2pass_vbr_bias_pct);
-            SHOW(rc_2pass_vbr_minsection_pct);
-            SHOW(rc_2pass_vbr_maxsection_pct);
-            SHOW(kf_mode);
-            SHOW(kf_min_dist);
-            SHOW(kf_max_dist);
-        }
+            FOREACH_STREAM(show_stream_config(stream, &global, &input));
 
         if(pass == (global.pass ? global.pass - 1 : 0)) {
             if (input.file_type == FILE_TYPE_Y4M)
@@ -1952,80 +2346,26 @@ int main(int argc, const char **argv_)
                    frames.*/
                 memset(&raw, 0, sizeof(raw));
             else
-                vpx_img_alloc(&raw, global.use_i420 ? VPX_IMG_FMT_I420 : VPX_IMG_FMT_YV12,
-                              cfg.g_w, cfg.g_h, 1);
-
-            init_rate_histogram(&rate_hist, &cfg, &global.framerate);
-        }
-
-        outfile = strcmp(global.out_fn, "-") ? fopen(global.out_fn, "wb")
-                                             : set_binary_mode(stdout);
-
-        if (!outfile)
-            fatal("Failed to open output file");
-
-        if(global.write_webm && fseek(outfile, 0, SEEK_CUR))
-            fatal("WebM output to pipes not supported.");
-
-        if (global.stats_fn)
-        {
-            if (!stats_open_file(&stats, global.stats_fn, pass))
-                fatal("Failed to open statistics store");
-        }
-        else
-        {
-            if (!stats_open_mem(&stats, pass))
-                fatal("Failed to open statistics store");
-        }
-
-        cfg.g_pass = global.passes == 2
-                     ? pass ? VPX_RC_LAST_PASS : VPX_RC_FIRST_PASS
-                 : VPX_RC_ONE_PASS;
-#if VPX_ENCODER_ABI_VERSION > (1 + VPX_CODEC_ABI_VERSION)
-
-        if (pass)
-        {
-            cfg.rc_twopass_stats_in = stats_get(&stats);
+                vpx_img_alloc(&raw,
+                              input.use_i420 ? VPX_IMG_FMT_I420
+                                             : VPX_IMG_FMT_YV12,
+                              input.w, input.h, 1);
+
+            FOREACH_STREAM(init_rate_histogram(&stream->rate_hist,
+                                               &stream->config.cfg,
+                                               &global.framerate));
         }
 
-#endif
-
-        if(global.write_webm)
-        {
-            ebml.stream = outfile;
-            write_webm_file_header(&ebml, &cfg, &global.framerate, stereo_fmt);
-        }
-        else
-            write_ivf_file_header(outfile, &cfg, global.codec->fourcc, 0);
-
-
-        /* Construct Encoder Context */
-        vpx_codec_enc_init(&encoder, global.codec->iface, &cfg,
-                           global.show_psnr ? VPX_CODEC_USE_PSNR : 0);
-        ctx_exit_on_error(&encoder, "Failed to initialize encoder");
-
-        /* Note that we bypass the vpx_codec_control wrapper macro because
-         * we're being clever to store the control IDs in an array. Real
-         * applications will want to make use of the enumerations directly
-         */
-        for (i = 0; i < arg_ctrl_cnt; i++)
-        {
-            if (vpx_codec_control_(&encoder, arg_ctrls[i][0], arg_ctrls[i][1]))
-                fprintf(stderr, "Error: Tried to set control %d = %d\n",
-                        arg_ctrls[i][0], arg_ctrls[i][1]);
-
-            ctx_exit_on_error(&encoder, "Failed to control codec");
-        }
+        FOREACH_STREAM(open_output_file(stream, &global));
+        FOREACH_STREAM(setup_pass(stream, &global, pass));
+        FOREACH_STREAM(initialize_encoder(stream, &global));
 
         frame_avail = 1;
         got_data = 0;
 
         while (frame_avail || got_data)
         {
-            vpx_codec_iter_t iter = NULL;
-            const vpx_codec_cx_pkt_t *pkt;
             struct vpx_usec_timer timer;
-            int64_t frame_start, next_frame_start;
 
             if (!global.limit || frames_in < global.limit)
             {
@@ -2034,157 +2374,80 @@ int main(int argc, const char **argv_)
                 if (frame_avail)
                     frames_in++;
 
-                fprintf(stderr,
-                        "\rPass %d/%d frame %4d/%-4d %7"PRId64"B \033[K",
-                        pass + 1, global.passes, frames_in, frames_out, nbytes);
+                if(stream_cnt == 1)
+                    fprintf(stderr,
+                            "\rPass %d/%d frame %4d/%-4d %7"PRId64"B \033[K",
+                            pass + 1, global.passes, frames_in,
+                            streams->frames_out, streams->nbytes);
+                else
+                    fprintf(stderr,
+                            "\rPass %d/%d frame %4d %7lu %s (%.2f fps)\033[K",
+                            pass + 1, global.passes, frames_in,
+                            cx_time > 9999999 ? cx_time / 1000 : cx_time,
+                            cx_time > 9999999 ? "ms" : "us",
+                            usec_to_fps(cx_time, frames_in));
+
             }
             else
                 frame_avail = 0;
 
             vpx_usec_timer_start(&timer);
-
-            frame_start = (cfg.g_timebase.den * (int64_t)(frames_in - 1)
-                          * global.framerate.den) / cfg.g_timebase.num / global.framerate.num;
-            next_frame_start = (cfg.g_timebase.den * (int64_t)(frames_in)
-                                * global.framerate.den)
-                                / cfg.g_timebase.num / global.framerate.num;
-            vpx_codec_encode(&encoder, frame_avail ? &raw : NULL, frame_start,
-                             next_frame_start - frame_start,
-                             0, global.deadline);
+            FOREACH_STREAM(encode_frame(stream, &global,
+                                        frame_avail ? &raw : NULL,
+                                        frames_in));
             vpx_usec_timer_mark(&timer);
             cx_time += vpx_usec_timer_elapsed(&timer);
-            ctx_exit_on_error(&encoder, "Failed to encode frame");
-
-            if(cfg.g_pass != VPX_RC_FIRST_PASS)
-            {
-                int q;
 
-                vpx_codec_control(&encoder, VP8E_GET_LAST_QUANTIZER_64, &q);
-                ctx_exit_on_error(&encoder, "Failed to read quantizer");
-                counts[q]++;
-            }
+            FOREACH_STREAM(update_quantizer_histogram(stream));
 
             got_data = 0;
-
-            while ((pkt = vpx_codec_get_cx_data(&encoder, &iter)))
-            {
-                got_data = 1;
-
-                switch (pkt->kind)
-                {
-                case VPX_CODEC_CX_FRAME_PKT:
-                    frames_out++;
-                    fprintf(stderr, " %6luF",
-                            (unsigned long)pkt->data.frame.sz);
-
-                    update_rate_histogram(&rate_hist, &cfg, pkt);
-                    if(global.write_webm)
-                    {
-                        /* Update the hash */
-                        if(!ebml.debug)
-                            hash = murmur(pkt->data.frame.buf,
-                                          pkt->data.frame.sz, hash);
-
-                        write_webm_block(&ebml, &cfg, pkt);
-                    }
-                    else
-                    {
-                        write_ivf_frame_header(outfile, pkt);
-                        if(fwrite(pkt->data.frame.buf, 1,
-                                  pkt->data.frame.sz, outfile));
-                    }
-                    nbytes += pkt->data.raw.sz;
-                    break;
-                case VPX_CODEC_STATS_PKT:
-                    frames_out++;
-                    fprintf(stderr, " %6luS",
-                           (unsigned long)pkt->data.twopass_stats.sz);
-                    stats_write(&stats,
-                                pkt->data.twopass_stats.buf,
-                                pkt->data.twopass_stats.sz);
-                    nbytes += pkt->data.raw.sz;
-                    break;
-                case VPX_CODEC_PSNR_PKT:
-
-                    if (global.show_psnr)
-                    {
-                        int i;
-
-                        psnr_sse_total += pkt->data.psnr.sse[0];
-                        psnr_samples_total += pkt->data.psnr.samples[0];
-                        for (i = 0; i < 4; i++)
-                        {
-                            fprintf(stderr, "%.3lf ", pkt->data.psnr.psnr[i]);
-                            psnr_totals[i] += pkt->data.psnr.psnr[i];
-                        }
-                        psnr_count++;
-                    }
-
-                    break;
-                default:
-                    break;
-                }
-            }
+            FOREACH_STREAM(get_cx_data(stream, &global, &got_data));
 
             fflush(stdout);
         }
 
-        fprintf(stderr,
-               "\rPass %d/%d frame %4d/%-4d %7"PRId64"B %7lub/f %7"PRId64"b/s"
-               " %7lu %s (%.2f fps)\033[K", pass + 1,
-               global.passes, frames_in, frames_out, nbytes,
-               frames_in ? (unsigned long)(nbytes * 8 / frames_in) : 0,
-               frames_in ? nbytes * 8 *(int64_t)global.framerate.num / global.framerate.den / frames_in : 0,
-               cx_time > 9999999 ? cx_time / 1000 : cx_time,
-               cx_time > 9999999 ? "ms" : "us",
-               cx_time > 0 ? (float)frames_in * 1000000.0 / (float)cx_time : 0);
-
-        if ( (global.show_psnr) && (psnr_count>0) )
-        {
-            int i;
-            double ovpsnr = vp8_mse2psnr(psnr_samples_total, 255.0,
-                                         psnr_sse_total);
-
-            fprintf(stderr, "\nPSNR (Overall/Avg/Y/U/V)");
-
-            fprintf(stderr, " %.3lf", ovpsnr);
-            for (i = 0; i < 4; i++)
-            {
-                fprintf(stderr, " %.3lf", psnr_totals[i]/psnr_count);
-            }
-        }
-
-        vpx_codec_destroy(&encoder);
+        if(stream_cnt > 1)
+            fprintf(stderr, "\n");
+
+        FOREACH_STREAM(fprintf(
+            stderr,
+            "\rPass %d/%d frame %4d/%-4d %7"PRId64"B %7lub/f %7"PRId64"b/s"
+            " %7lu %s (%.2f fps)\033[K\n", pass + 1,
+            global.passes, frames_in, stream->frames_out, stream->nbytes,
+            frames_in ? (unsigned long)(stream->nbytes * 8 / frames_in) : 0,
+            frames_in ? stream->nbytes * 8
+                        * (int64_t)global.framerate.num / global.framerate.den
+                        / frames_in
+                      : 0,
+            stream->cx_time > 9999999 ? stream->cx_time / 1000 : stream->cx_time,
+            stream->cx_time > 9999999 ? "ms" : "us",
+            usec_to_fps(stream->cx_time, frames_in));
+        );
+
+        if (global.show_psnr)
+            FOREACH_STREAM(show_psnr(stream));
+
+        FOREACH_STREAM(vpx_codec_destroy(&stream->encoder));
 
         close_input_file(&input);
 
-        if(global.write_webm)
-        {
-            write_webm_file_footer(&ebml, hash);
-            free(ebml.cue_list);
-            ebml.cue_list = NULL;
-        }
-        else
-        {
-            if (!fseek(outfile, 0, SEEK_SET))
-                write_ivf_file_header(outfile, &cfg, global.codec->fourcc,
-                                      frames_out);
-        }
+        FOREACH_STREAM(close_output_file(stream, global.codec->fourcc));
 
-        fclose(outfile);
-        stats_close(&stats, global.passes-1);
-        fprintf(stderr, "\n");
+        FOREACH_STREAM(stats_close(&stream->stats, global.passes-1));
 
         if (global.pass)
             break;
     }
 
     if (global.show_q_hist_buckets)
-        show_q_histogram(counts, global.show_q_hist_buckets);
+        FOREACH_STREAM(show_q_histogram(stream->counts,
+                                        global.show_q_hist_buckets));
 
     if (global.show_rate_hist_buckets)
-        show_rate_histogram(&rate_hist, &cfg, global.show_rate_hist_buckets);
-    destroy_rate_histogram(&rate_hist);
+        FOREACH_STREAM(show_rate_histogram(&stream->rate_hist,
+                                           &stream->config.cfg,
+                                           global.show_rate_hist_buckets));
+    FOREACH_STREAM(destroy_rate_histogram(&stream->rate_hist));
 
     vpx_img_free(&raw);
     free(argv);