From 9e50ed7f27b8d3185b0891ae636db01e62239226 Mon Sep 17 00:00:00 2001 From: John Koleszar Date: Wed, 15 Feb 2012 12:39:38 -0800 Subject: [PATCH] vpxenc: initial implementation of multistream support 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 | 1033 +++++++++++++++++++++++++++++++++++++++----------------------- 1 file changed, 648 insertions(+), 385 deletions(-) diff --git a/vpxenc.c b/vpxenc.c index bb0a306..7d6758a 100644 --- 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; jarg_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; jfile, 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); -- 2.7.4