diff --git a/vpxenc.c b/vpxenc.c index bb0a3068eedc28af8d7561d217dc61526cca4de7..7d6758a7d6678fda6165ea7423d659a18fdc0e19 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; 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);