| OLD | NEW |
| 1 /* | 1 /* |
| 2 * Copyright (c) 2012 The WebM project authors. All Rights Reserved. | 2 * Copyright (c) 2012 The WebM project authors. All Rights Reserved. |
| 3 * | 3 * |
| 4 * Use of this source code is governed by a BSD-style license | 4 * Use of this source code is governed by a BSD-style license |
| 5 * that can be found in the LICENSE file in the root of the source | 5 * that can be found in the LICENSE file in the root of the source |
| 6 * tree. An additional intellectual property rights grant can be found | 6 * tree. An additional intellectual property rights grant can be found |
| 7 * in the file PATENTS. All contributing project authors may | 7 * in the file PATENTS. All contributing project authors may |
| 8 * be found in the AUTHORS file in the root of the source tree. | 8 * be found in the AUTHORS file in the root of the source tree. |
| 9 */ | 9 */ |
| 10 | 10 |
| 11 /* | 11 /* |
| 12 * This is an example demonstrating how to implement a multi-layer | 12 * This is an example demonstrating how to implement a multi-layer |
| 13 * VP9 encoding scheme based on spatial scalability for video applications | 13 * VP9 encoding scheme based on spatial scalability for video applications |
| 14 * that benefit from a scalable bitstream. | 14 * that benefit from a scalable bitstream. |
| 15 */ | 15 */ |
| 16 | 16 |
| 17 #include <math.h> |
| 17 #include <stdarg.h> | 18 #include <stdarg.h> |
| 18 #include <stdlib.h> | 19 #include <stdlib.h> |
| 19 #include <string.h> | 20 #include <string.h> |
| 20 #include <time.h> | 21 #include <time.h> |
| 21 | 22 |
| 23 |
| 22 #include "../args.h" | 24 #include "../args.h" |
| 23 #include "../tools_common.h" | 25 #include "../tools_common.h" |
| 24 #include "../video_writer.h" | 26 #include "../video_writer.h" |
| 25 | 27 |
| 26 #include "vpx/svc_context.h" | 28 #include "vpx/svc_context.h" |
| 27 #include "vpx/vp8cx.h" | 29 #include "vpx/vp8cx.h" |
| 28 #include "vpx/vpx_encoder.h" | 30 #include "vpx/vpx_encoder.h" |
| 29 #include "../vpxstats.h" | 31 #include "../vpxstats.h" |
| 32 #define OUTPUT_RC_STATS 1 |
| 30 | 33 |
| 31 static const arg_def_t skip_frames_arg = | 34 static const arg_def_t skip_frames_arg = |
| 32 ARG_DEF("s", "skip-frames", 1, "input frames to skip"); | 35 ARG_DEF("s", "skip-frames", 1, "input frames to skip"); |
| 33 static const arg_def_t frames_arg = | 36 static const arg_def_t frames_arg = |
| 34 ARG_DEF("f", "frames", 1, "number of frames to encode"); | 37 ARG_DEF("f", "frames", 1, "number of frames to encode"); |
| 38 static const arg_def_t threads_arg = |
| 39 ARG_DEF("th", "threads", 1, "number of threads to use"); |
| 40 #if OUTPUT_RC_STATS |
| 41 static const arg_def_t output_rc_stats_arg = |
| 42 ARG_DEF("rcstat", "output_rc_stats", 1, "output rc stats"); |
| 43 #endif |
| 35 static const arg_def_t width_arg = ARG_DEF("w", "width", 1, "source width"); | 44 static const arg_def_t width_arg = ARG_DEF("w", "width", 1, "source width"); |
| 36 static const arg_def_t height_arg = ARG_DEF("h", "height", 1, "source height"); | 45 static const arg_def_t height_arg = ARG_DEF("h", "height", 1, "source height"); |
| 37 static const arg_def_t timebase_arg = | 46 static const arg_def_t timebase_arg = |
| 38 ARG_DEF("t", "timebase", 1, "timebase (num/den)"); | 47 ARG_DEF("t", "timebase", 1, "timebase (num/den)"); |
| 39 static const arg_def_t bitrate_arg = ARG_DEF( | 48 static const arg_def_t bitrate_arg = ARG_DEF( |
| 40 "b", "target-bitrate", 1, "encoding bitrate, in kilobits per second"); | 49 "b", "target-bitrate", 1, "encoding bitrate, in kilobits per second"); |
| 41 static const arg_def_t spatial_layers_arg = | 50 static const arg_def_t spatial_layers_arg = |
| 42 ARG_DEF("sl", "spatial-layers", 1, "number of spatial SVC layers"); | 51 ARG_DEF("sl", "spatial-layers", 1, "number of spatial SVC layers"); |
| 43 static const arg_def_t temporal_layers_arg = | 52 static const arg_def_t temporal_layers_arg = |
| 44 ARG_DEF("tl", "temporal-layers", 1, "number of temporal SVC layers"); | 53 ARG_DEF("tl", "temporal-layers", 1, "number of temporal SVC layers"); |
| 54 static const arg_def_t temporal_layering_mode_arg = |
| 55 ARG_DEF("tlm", "temporal-layering-mode", 1, "temporal layering scheme." |
| 56 "VP9E_TEMPORAL_LAYERING_MODE"); |
| 45 static const arg_def_t kf_dist_arg = | 57 static const arg_def_t kf_dist_arg = |
| 46 ARG_DEF("k", "kf-dist", 1, "number of frames between keyframes"); | 58 ARG_DEF("k", "kf-dist", 1, "number of frames between keyframes"); |
| 47 static const arg_def_t scale_factors_arg = | 59 static const arg_def_t scale_factors_arg = |
| 48 ARG_DEF("r", "scale-factors", 1, "scale factors (lowest to highest layer)"); | 60 ARG_DEF("r", "scale-factors", 1, "scale factors (lowest to highest layer)"); |
| 49 static const arg_def_t passes_arg = | 61 static const arg_def_t passes_arg = |
| 50 ARG_DEF("p", "passes", 1, "Number of passes (1/2)"); | 62 ARG_DEF("p", "passes", 1, "Number of passes (1/2)"); |
| 51 static const arg_def_t pass_arg = | 63 static const arg_def_t pass_arg = |
| 52 ARG_DEF(NULL, "pass", 1, "Pass to execute (1/2)"); | 64 ARG_DEF(NULL, "pass", 1, "Pass to execute (1/2)"); |
| 53 static const arg_def_t fpf_name_arg = | 65 static const arg_def_t fpf_name_arg = |
| 54 ARG_DEF(NULL, "fpf", 1, "First pass statistics file name"); | 66 ARG_DEF(NULL, "fpf", 1, "First pass statistics file name"); |
| 55 static const arg_def_t min_q_arg = | 67 static const arg_def_t min_q_arg = |
| 56 ARG_DEF(NULL, "min-q", 1, "Minimum quantizer"); | 68 ARG_DEF(NULL, "min-q", 1, "Minimum quantizer"); |
| 57 static const arg_def_t max_q_arg = | 69 static const arg_def_t max_q_arg = |
| 58 ARG_DEF(NULL, "max-q", 1, "Maximum quantizer"); | 70 ARG_DEF(NULL, "max-q", 1, "Maximum quantizer"); |
| 59 static const arg_def_t min_bitrate_arg = | 71 static const arg_def_t min_bitrate_arg = |
| 60 ARG_DEF(NULL, "min-bitrate", 1, "Minimum bitrate"); | 72 ARG_DEF(NULL, "min-bitrate", 1, "Minimum bitrate"); |
| 61 static const arg_def_t max_bitrate_arg = | 73 static const arg_def_t max_bitrate_arg = |
| 62 ARG_DEF(NULL, "max-bitrate", 1, "Maximum bitrate"); | 74 ARG_DEF(NULL, "max-bitrate", 1, "Maximum bitrate"); |
| 63 static const arg_def_t lag_in_frame_arg = | 75 static const arg_def_t lag_in_frame_arg = |
| 64 ARG_DEF(NULL, "lag-in-frames", 1, "Number of frame to input before " | 76 ARG_DEF(NULL, "lag-in-frames", 1, "Number of frame to input before " |
| 65 "generating any outputs"); | 77 "generating any outputs"); |
| 66 static const arg_def_t rc_end_usage_arg = | 78 static const arg_def_t rc_end_usage_arg = |
| 67 ARG_DEF(NULL, "rc-end-usage", 1, "0 - 3: VBR, CBR, CQ, Q"); | 79 ARG_DEF(NULL, "rc-end-usage", 1, "0 - 3: VBR, CBR, CQ, Q"); |
| 80 static const arg_def_t speed_arg = |
| 81 ARG_DEF("sp", "speed", 1, "speed configuration"); |
| 68 | 82 |
| 69 #if CONFIG_VP9_HIGHBITDEPTH | 83 #if CONFIG_VP9_HIGHBITDEPTH |
| 70 static const struct arg_enum_list bitdepth_enum[] = { | 84 static const struct arg_enum_list bitdepth_enum[] = { |
| 71 {"8", VPX_BITS_8}, | 85 {"8", VPX_BITS_8}, |
| 72 {"10", VPX_BITS_10}, | 86 {"10", VPX_BITS_10}, |
| 73 {"12", VPX_BITS_12}, | 87 {"12", VPX_BITS_12}, |
| 74 {NULL, 0} | 88 {NULL, 0} |
| 75 }; | 89 }; |
| 76 | 90 |
| 77 static const arg_def_t bitdepth_arg = | 91 static const arg_def_t bitdepth_arg = |
| 78 ARG_DEF_ENUM("d", "bit-depth", 1, "Bit depth for codec 8, 10 or 12. ", | 92 ARG_DEF_ENUM("d", "bit-depth", 1, "Bit depth for codec 8, 10 or 12. ", |
| 79 bitdepth_enum); | 93 bitdepth_enum); |
| 80 #endif // CONFIG_VP9_HIGHBITDEPTH | 94 #endif // CONFIG_VP9_HIGHBITDEPTH |
| 81 | 95 |
| 82 | 96 |
| 83 static const arg_def_t *svc_args[] = { | 97 static const arg_def_t *svc_args[] = { |
| 84 &frames_arg, &width_arg, &height_arg, | 98 &frames_arg, &width_arg, &height_arg, |
| 85 &timebase_arg, &bitrate_arg, &skip_frames_arg, &spatial_layers_arg, | 99 &timebase_arg, &bitrate_arg, &skip_frames_arg, &spatial_layers_arg, |
| 86 &kf_dist_arg, &scale_factors_arg, &passes_arg, &pass_arg, | 100 &kf_dist_arg, &scale_factors_arg, &passes_arg, &pass_arg, |
| 87 &fpf_name_arg, &min_q_arg, &max_q_arg, &min_bitrate_arg, | 101 &fpf_name_arg, &min_q_arg, &max_q_arg, &min_bitrate_arg, |
| 88 &max_bitrate_arg, &temporal_layers_arg, &lag_in_frame_arg, | 102 &max_bitrate_arg, &temporal_layers_arg, &temporal_layering_mode_arg, |
| 103 &lag_in_frame_arg, &threads_arg, |
| 104 #if OUTPUT_RC_STATS |
| 105 &output_rc_stats_arg, |
| 106 #endif |
| 107 |
| 89 #if CONFIG_VP9_HIGHBITDEPTH | 108 #if CONFIG_VP9_HIGHBITDEPTH |
| 90 &bitdepth_arg, | 109 &bitdepth_arg, |
| 91 #endif | 110 #endif |
| 111 &speed_arg, |
| 92 &rc_end_usage_arg, NULL | 112 &rc_end_usage_arg, NULL |
| 93 }; | 113 }; |
| 94 | 114 |
| 95 static const uint32_t default_frames_to_skip = 0; | 115 static const uint32_t default_frames_to_skip = 0; |
| 96 static const uint32_t default_frames_to_code = 60 * 60; | 116 static const uint32_t default_frames_to_code = 60 * 60; |
| 97 static const uint32_t default_width = 1920; | 117 static const uint32_t default_width = 1920; |
| 98 static const uint32_t default_height = 1080; | 118 static const uint32_t default_height = 1080; |
| 99 static const uint32_t default_timebase_num = 1; | 119 static const uint32_t default_timebase_num = 1; |
| 100 static const uint32_t default_timebase_den = 60; | 120 static const uint32_t default_timebase_den = 60; |
| 101 static const uint32_t default_bitrate = 1000; | 121 static const uint32_t default_bitrate = 1000; |
| 102 static const uint32_t default_spatial_layers = 5; | 122 static const uint32_t default_spatial_layers = 5; |
| 103 static const uint32_t default_temporal_layers = 1; | 123 static const uint32_t default_temporal_layers = 1; |
| 104 static const uint32_t default_kf_dist = 100; | 124 static const uint32_t default_kf_dist = 100; |
| 125 static const uint32_t default_temporal_layering_mode = 0; |
| 126 static const uint32_t default_output_rc_stats = 0; |
| 127 static const int32_t default_speed = -1; // -1 means use library default. |
| 128 static const uint32_t default_threads = 0; // zero means use library default. |
| 105 | 129 |
| 106 typedef struct { | 130 typedef struct { |
| 107 const char *input_filename; | 131 const char *input_filename; |
| 108 const char *output_filename; | 132 const char *output_filename; |
| 109 uint32_t frames_to_code; | 133 uint32_t frames_to_code; |
| 110 uint32_t frames_to_skip; | 134 uint32_t frames_to_skip; |
| 111 struct VpxInputContext input_ctx; | 135 struct VpxInputContext input_ctx; |
| 112 stats_io_t rc_stats; | 136 stats_io_t rc_stats; |
| 113 int passes; | 137 int passes; |
| 114 int pass; | 138 int pass; |
| (...skipping 21 matching lines...) Expand all Loading... |
| 136 int pass = 0; | 160 int pass = 0; |
| 137 const char *fpf_file_name = NULL; | 161 const char *fpf_file_name = NULL; |
| 138 unsigned int min_bitrate = 0; | 162 unsigned int min_bitrate = 0; |
| 139 unsigned int max_bitrate = 0; | 163 unsigned int max_bitrate = 0; |
| 140 char string_options[1024] = {0}; | 164 char string_options[1024] = {0}; |
| 141 | 165 |
| 142 // initialize SvcContext with parameters that will be passed to vpx_svc_init | 166 // initialize SvcContext with parameters that will be passed to vpx_svc_init |
| 143 svc_ctx->log_level = SVC_LOG_DEBUG; | 167 svc_ctx->log_level = SVC_LOG_DEBUG; |
| 144 svc_ctx->spatial_layers = default_spatial_layers; | 168 svc_ctx->spatial_layers = default_spatial_layers; |
| 145 svc_ctx->temporal_layers = default_temporal_layers; | 169 svc_ctx->temporal_layers = default_temporal_layers; |
| 170 svc_ctx->temporal_layering_mode = default_temporal_layering_mode; |
| 171 #if OUTPUT_RC_STATS |
| 172 svc_ctx->output_rc_stat = default_output_rc_stats; |
| 173 #endif |
| 174 svc_ctx->speed = default_speed; |
| 175 svc_ctx->threads = default_threads; |
| 146 | 176 |
| 147 // start with default encoder configuration | 177 // start with default encoder configuration |
| 148 res = vpx_codec_enc_config_default(vpx_codec_vp9_cx(), enc_cfg, 0); | 178 res = vpx_codec_enc_config_default(vpx_codec_vp9_cx(), enc_cfg, 0); |
| 149 if (res) { | 179 if (res) { |
| 150 die("Failed to get config: %s\n", vpx_codec_err_to_string(res)); | 180 die("Failed to get config: %s\n", vpx_codec_err_to_string(res)); |
| 151 } | 181 } |
| 152 // update enc_cfg with app default values | 182 // update enc_cfg with app default values |
| 153 enc_cfg->g_w = default_width; | 183 enc_cfg->g_w = default_width; |
| 154 enc_cfg->g_h = default_height; | 184 enc_cfg->g_h = default_height; |
| 155 enc_cfg->g_timebase.num = default_timebase_num; | 185 enc_cfg->g_timebase.num = default_timebase_num; |
| (...skipping 21 matching lines...) Expand all Loading... |
| 177 } else if (arg_match(&arg, &timebase_arg, argi)) { | 207 } else if (arg_match(&arg, &timebase_arg, argi)) { |
| 178 enc_cfg->g_timebase = arg_parse_rational(&arg); | 208 enc_cfg->g_timebase = arg_parse_rational(&arg); |
| 179 } else if (arg_match(&arg, &bitrate_arg, argi)) { | 209 } else if (arg_match(&arg, &bitrate_arg, argi)) { |
| 180 enc_cfg->rc_target_bitrate = arg_parse_uint(&arg); | 210 enc_cfg->rc_target_bitrate = arg_parse_uint(&arg); |
| 181 } else if (arg_match(&arg, &skip_frames_arg, argi)) { | 211 } else if (arg_match(&arg, &skip_frames_arg, argi)) { |
| 182 app_input->frames_to_skip = arg_parse_uint(&arg); | 212 app_input->frames_to_skip = arg_parse_uint(&arg); |
| 183 } else if (arg_match(&arg, &spatial_layers_arg, argi)) { | 213 } else if (arg_match(&arg, &spatial_layers_arg, argi)) { |
| 184 svc_ctx->spatial_layers = arg_parse_uint(&arg); | 214 svc_ctx->spatial_layers = arg_parse_uint(&arg); |
| 185 } else if (arg_match(&arg, &temporal_layers_arg, argi)) { | 215 } else if (arg_match(&arg, &temporal_layers_arg, argi)) { |
| 186 svc_ctx->temporal_layers = arg_parse_uint(&arg); | 216 svc_ctx->temporal_layers = arg_parse_uint(&arg); |
| 217 #if OUTPUT_RC_STATS |
| 218 } else if (arg_match(&arg, &output_rc_stats_arg, argi)) { |
| 219 svc_ctx->output_rc_stat = arg_parse_uint(&arg); |
| 220 #endif |
| 221 } else if (arg_match(&arg, &speed_arg, argi)) { |
| 222 svc_ctx->speed = arg_parse_uint(&arg); |
| 223 } else if (arg_match(&arg, &threads_arg, argi)) { |
| 224 svc_ctx->threads = arg_parse_uint(&arg); |
| 225 } else if (arg_match(&arg, &temporal_layering_mode_arg, argi)) { |
| 226 svc_ctx->temporal_layering_mode = |
| 227 enc_cfg->temporal_layering_mode = arg_parse_int(&arg); |
| 228 if (svc_ctx->temporal_layering_mode) { |
| 229 enc_cfg->g_error_resilient = 1; |
| 230 } |
| 187 } else if (arg_match(&arg, &kf_dist_arg, argi)) { | 231 } else if (arg_match(&arg, &kf_dist_arg, argi)) { |
| 188 enc_cfg->kf_min_dist = arg_parse_uint(&arg); | 232 enc_cfg->kf_min_dist = arg_parse_uint(&arg); |
| 189 enc_cfg->kf_max_dist = enc_cfg->kf_min_dist; | 233 enc_cfg->kf_max_dist = enc_cfg->kf_min_dist; |
| 190 } else if (arg_match(&arg, &scale_factors_arg, argi)) { | 234 } else if (arg_match(&arg, &scale_factors_arg, argi)) { |
| 191 snprintf(string_options, sizeof(string_options), "%s scale-factors=%s", | 235 snprintf(string_options, sizeof(string_options), "%s scale-factors=%s", |
| 192 string_options, arg.val); | 236 string_options, arg.val); |
| 193 } else if (arg_match(&arg, &passes_arg, argi)) { | 237 } else if (arg_match(&arg, &passes_arg, argi)) { |
| 194 passes = arg_parse_uint(&arg); | 238 passes = arg_parse_uint(&arg); |
| 195 if (passes < 1 || passes > 2) { | 239 if (passes < 1 || passes > 2) { |
| 196 die("Error: Invalid number of passes (%d)\n", passes); | 240 die("Error: Invalid number of passes (%d)\n", passes); |
| (...skipping 112 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 309 "width %d, height: %d,\n" | 353 "width %d, height: %d,\n" |
| 310 "num: %d, den: %d, bitrate: %d,\n" | 354 "num: %d, den: %d, bitrate: %d,\n" |
| 311 "gop size: %d\n", | 355 "gop size: %d\n", |
| 312 vpx_codec_iface_name(vpx_codec_vp9_cx()), app_input->frames_to_code, | 356 vpx_codec_iface_name(vpx_codec_vp9_cx()), app_input->frames_to_code, |
| 313 app_input->frames_to_skip, | 357 app_input->frames_to_skip, |
| 314 svc_ctx->spatial_layers, enc_cfg->g_w, enc_cfg->g_h, | 358 svc_ctx->spatial_layers, enc_cfg->g_w, enc_cfg->g_h, |
| 315 enc_cfg->g_timebase.num, enc_cfg->g_timebase.den, | 359 enc_cfg->g_timebase.num, enc_cfg->g_timebase.den, |
| 316 enc_cfg->rc_target_bitrate, enc_cfg->kf_max_dist); | 360 enc_cfg->rc_target_bitrate, enc_cfg->kf_max_dist); |
| 317 } | 361 } |
| 318 | 362 |
| 363 #if OUTPUT_RC_STATS |
| 364 // For rate control encoding stats. |
| 365 struct RateControlStats { |
| 366 // Number of input frames per layer. |
| 367 int layer_input_frames[VPX_MAX_LAYERS]; |
| 368 // Total (cumulative) number of encoded frames per layer. |
| 369 int layer_tot_enc_frames[VPX_MAX_LAYERS]; |
| 370 // Number of encoded non-key frames per layer. |
| 371 int layer_enc_frames[VPX_MAX_LAYERS]; |
| 372 // Framerate per layer (cumulative). |
| 373 double layer_framerate[VPX_MAX_LAYERS]; |
| 374 // Target average frame size per layer (per-frame-bandwidth per layer). |
| 375 double layer_pfb[VPX_MAX_LAYERS]; |
| 376 // Actual average frame size per layer. |
| 377 double layer_avg_frame_size[VPX_MAX_LAYERS]; |
| 378 // Average rate mismatch per layer (|target - actual| / target). |
| 379 double layer_avg_rate_mismatch[VPX_MAX_LAYERS]; |
| 380 // Actual encoding bitrate per layer (cumulative). |
| 381 double layer_encoding_bitrate[VPX_MAX_LAYERS]; |
| 382 // Average of the short-time encoder actual bitrate. |
| 383 // TODO(marpan): Should we add these short-time stats for each layer? |
| 384 double avg_st_encoding_bitrate; |
| 385 // Variance of the short-time encoder actual bitrate. |
| 386 double variance_st_encoding_bitrate; |
| 387 // Window (number of frames) for computing short-time encoding bitrate. |
| 388 int window_size; |
| 389 // Number of window measurements. |
| 390 int window_count; |
| 391 }; |
| 392 |
| 393 // Note: these rate control stats assume only 1 key frame in the |
| 394 // sequence (i.e., first frame only). |
| 395 static void set_rate_control_stats(struct RateControlStats *rc, |
| 396 vpx_codec_enc_cfg_t *cfg) { |
| 397 unsigned int sl, tl; |
| 398 // Set the layer (cumulative) framerate and the target layer (non-cumulative) |
| 399 // per-frame-bandwidth, for the rate control encoding stats below. |
| 400 const double framerate = cfg->g_timebase.den / cfg->g_timebase.num; |
| 401 |
| 402 for (sl = 0; sl < cfg->ss_number_layers; ++sl) { |
| 403 for (tl = 0; tl < cfg->ts_number_layers; ++tl) { |
| 404 const int layer = sl * cfg->ts_number_layers + tl; |
| 405 const int tlayer0 = sl * cfg->ts_number_layers; |
| 406 rc->layer_framerate[layer] = |
| 407 framerate / cfg->ts_rate_decimator[tl]; |
| 408 if (tl > 0) { |
| 409 rc->layer_pfb[layer] = 1000.0 * |
| 410 (cfg->layer_target_bitrate[layer] - |
| 411 cfg->layer_target_bitrate[layer - 1]) / |
| 412 (rc->layer_framerate[layer] - |
| 413 rc->layer_framerate[layer - 1]); |
| 414 } else { |
| 415 rc->layer_pfb[tlayer0] = 1000.0 * |
| 416 cfg->layer_target_bitrate[tlayer0] / |
| 417 rc->layer_framerate[tlayer0]; |
| 418 } |
| 419 rc->layer_input_frames[layer] = 0; |
| 420 rc->layer_enc_frames[layer] = 0; |
| 421 rc->layer_tot_enc_frames[layer] = 0; |
| 422 rc->layer_encoding_bitrate[layer] = 0.0; |
| 423 rc->layer_avg_frame_size[layer] = 0.0; |
| 424 rc->layer_avg_rate_mismatch[layer] = 0.0; |
| 425 } |
| 426 } |
| 427 rc->window_count = 0; |
| 428 rc->window_size = 15; |
| 429 rc->avg_st_encoding_bitrate = 0.0; |
| 430 rc->variance_st_encoding_bitrate = 0.0; |
| 431 } |
| 432 |
| 433 static void printout_rate_control_summary(struct RateControlStats *rc, |
| 434 vpx_codec_enc_cfg_t *cfg, |
| 435 int frame_cnt) { |
| 436 unsigned int sl, tl; |
| 437 int tot_num_frames = 0; |
| 438 double perc_fluctuation = 0.0; |
| 439 printf("Total number of processed frames: %d\n\n", frame_cnt - 1); |
| 440 printf("Rate control layer stats for sl%d tl%d layer(s):\n\n", |
| 441 cfg->ss_number_layers, cfg->ts_number_layers); |
| 442 for (sl = 0; sl < cfg->ss_number_layers; ++sl) { |
| 443 for (tl = 0; tl < cfg->ts_number_layers; ++tl) { |
| 444 const int layer = sl * cfg->ts_number_layers + tl; |
| 445 const int num_dropped = (tl > 0) ? |
| 446 (rc->layer_input_frames[layer] - rc->layer_enc_frames[layer]) : |
| 447 (rc->layer_input_frames[layer] - rc->layer_enc_frames[layer] - 1); |
| 448 if (!sl) |
| 449 tot_num_frames += rc->layer_input_frames[layer]; |
| 450 rc->layer_encoding_bitrate[layer] = 0.001 * rc->layer_framerate[layer] * |
| 451 rc->layer_encoding_bitrate[layer] / tot_num_frames; |
| 452 rc->layer_avg_frame_size[layer] = rc->layer_avg_frame_size[layer] / |
| 453 rc->layer_enc_frames[layer]; |
| 454 rc->layer_avg_rate_mismatch[layer] = |
| 455 100.0 * rc->layer_avg_rate_mismatch[layer] / |
| 456 rc->layer_enc_frames[layer]; |
| 457 printf("For layer#: sl%d tl%d \n", sl, tl); |
| 458 printf("Bitrate (target vs actual): %d %f.0 kbps\n", |
| 459 cfg->layer_target_bitrate[layer], |
| 460 rc->layer_encoding_bitrate[layer]); |
| 461 printf("Average frame size (target vs actual): %f %f bits\n", |
| 462 rc->layer_pfb[layer], rc->layer_avg_frame_size[layer]); |
| 463 printf("Average rate_mismatch: %f\n", |
| 464 rc->layer_avg_rate_mismatch[layer]); |
| 465 printf("Number of input frames, encoded (non-key) frames, " |
| 466 "and percent dropped frames: %d %d %f.0 \n", |
| 467 rc->layer_input_frames[layer], rc->layer_enc_frames[layer], |
| 468 100.0 * num_dropped / rc->layer_input_frames[layer]); |
| 469 printf("\n"); |
| 470 } |
| 471 } |
| 472 rc->avg_st_encoding_bitrate = rc->avg_st_encoding_bitrate / rc->window_count; |
| 473 rc->variance_st_encoding_bitrate = |
| 474 rc->variance_st_encoding_bitrate / rc->window_count - |
| 475 (rc->avg_st_encoding_bitrate * rc->avg_st_encoding_bitrate); |
| 476 perc_fluctuation = 100.0 * sqrt(rc->variance_st_encoding_bitrate) / |
| 477 rc->avg_st_encoding_bitrate; |
| 478 printf("Short-time stats, for window of %d frames: \n", rc->window_size); |
| 479 printf("Average, rms-variance, and percent-fluct: %f %f %f \n", |
| 480 rc->avg_st_encoding_bitrate, |
| 481 sqrt(rc->variance_st_encoding_bitrate), |
| 482 perc_fluctuation); |
| 483 if (frame_cnt != tot_num_frames) |
| 484 die("Error: Number of input frames not equal to output encoded frames != " |
| 485 "%d tot_num_frames = %d\n", frame_cnt, tot_num_frames); |
| 486 } |
| 487 |
| 488 vpx_codec_err_t parse_superframe_index(const uint8_t *data, |
| 489 size_t data_sz, |
| 490 uint32_t sizes[8], int *count) { |
| 491 // A chunk ending with a byte matching 0xc0 is an invalid chunk unless |
| 492 // it is a super frame index. If the last byte of real video compression |
| 493 // data is 0xc0 the encoder must add a 0 byte. If we have the marker but |
| 494 // not the associated matching marker byte at the front of the index we have |
| 495 // an invalid bitstream and need to return an error. |
| 496 |
| 497 uint8_t marker; |
| 498 |
| 499 marker = *(data + data_sz - 1); |
| 500 *count = 0; |
| 501 |
| 502 |
| 503 if ((marker & 0xe0) == 0xc0) { |
| 504 const uint32_t frames = (marker & 0x7) + 1; |
| 505 const uint32_t mag = ((marker >> 3) & 0x3) + 1; |
| 506 const size_t index_sz = 2 + mag * frames; |
| 507 |
| 508 // This chunk is marked as having a superframe index but doesn't have |
| 509 // enough data for it, thus it's an invalid superframe index. |
| 510 if (data_sz < index_sz) |
| 511 return VPX_CODEC_CORRUPT_FRAME; |
| 512 |
| 513 { |
| 514 const uint8_t marker2 = *(data + data_sz - index_sz); |
| 515 |
| 516 // This chunk is marked as having a superframe index but doesn't have |
| 517 // the matching marker byte at the front of the index therefore it's an |
| 518 // invalid chunk. |
| 519 if (marker != marker2) |
| 520 return VPX_CODEC_CORRUPT_FRAME; |
| 521 } |
| 522 |
| 523 { |
| 524 // Found a valid superframe index. |
| 525 uint32_t i, j; |
| 526 const uint8_t *x = &data[data_sz - index_sz + 1]; |
| 527 |
| 528 for (i = 0; i < frames; ++i) { |
| 529 uint32_t this_sz = 0; |
| 530 |
| 531 for (j = 0; j < mag; ++j) |
| 532 this_sz |= (*x++) << (j * 8); |
| 533 sizes[i] = this_sz; |
| 534 } |
| 535 *count = frames; |
| 536 } |
| 537 } |
| 538 return VPX_CODEC_OK; |
| 539 } |
| 540 #endif |
| 541 |
| 319 int main(int argc, const char **argv) { | 542 int main(int argc, const char **argv) { |
| 320 AppInput app_input = {0}; | 543 AppInput app_input = {0}; |
| 321 VpxVideoWriter *writer = NULL; | 544 VpxVideoWriter *writer = NULL; |
| 322 VpxVideoInfo info = {0}; | 545 VpxVideoInfo info = {0}; |
| 323 vpx_codec_ctx_t codec; | 546 vpx_codec_ctx_t codec; |
| 324 vpx_codec_enc_cfg_t enc_cfg; | 547 vpx_codec_enc_cfg_t enc_cfg; |
| 325 SvcContext svc_ctx; | 548 SvcContext svc_ctx; |
| 326 uint32_t i; | 549 uint32_t i; |
| 327 uint32_t frame_cnt = 0; | 550 uint32_t frame_cnt = 0; |
| 328 vpx_image_t raw; | 551 vpx_image_t raw; |
| 329 vpx_codec_err_t res; | 552 vpx_codec_err_t res; |
| 330 int pts = 0; /* PTS starts at 0 */ | 553 int pts = 0; /* PTS starts at 0 */ |
| 331 int frame_duration = 1; /* 1 timebase tick per frame */ | 554 int frame_duration = 1; /* 1 timebase tick per frame */ |
| 332 FILE *infile = NULL; | 555 FILE *infile = NULL; |
| 333 int end_of_stream = 0; | 556 int end_of_stream = 0; |
| 334 int frames_received = 0; | 557 int frames_received = 0; |
| 335 | 558 #if OUTPUT_RC_STATS |
| 559 VpxVideoWriter *outfile[VPX_TS_MAX_LAYERS] = {NULL}; |
| 560 struct RateControlStats rc; |
| 561 vpx_svc_layer_id_t layer_id; |
| 562 int sl, tl; |
| 563 double sum_bitrate = 0.0; |
| 564 double sum_bitrate2 = 0.0; |
| 565 double framerate = 30.0; |
| 566 #endif |
| 336 memset(&svc_ctx, 0, sizeof(svc_ctx)); | 567 memset(&svc_ctx, 0, sizeof(svc_ctx)); |
| 337 svc_ctx.log_print = 1; | 568 svc_ctx.log_print = 1; |
| 338 exec_name = argv[0]; | 569 exec_name = argv[0]; |
| 339 parse_command_line(argc, argv, &app_input, &svc_ctx, &enc_cfg); | 570 parse_command_line(argc, argv, &app_input, &svc_ctx, &enc_cfg); |
| 340 | 571 |
| 341 // Allocate image buffer | 572 // Allocate image buffer |
| 342 #if CONFIG_VP9_HIGHBITDEPTH | 573 #if CONFIG_VP9_HIGHBITDEPTH |
| 343 if (!vpx_img_alloc(&raw, enc_cfg.g_input_bit_depth == 8 ? | 574 if (!vpx_img_alloc(&raw, enc_cfg.g_input_bit_depth == 8 ? |
| 344 VPX_IMG_FMT_I420 : VPX_IMG_FMT_I42016, | 575 VPX_IMG_FMT_I420 : VPX_IMG_FMT_I42016, |
| 345 enc_cfg.g_w, enc_cfg.g_h, 32)) { | 576 enc_cfg.g_w, enc_cfg.g_h, 32)) { |
| 346 die("Failed to allocate image %dx%d\n", enc_cfg.g_w, enc_cfg.g_h); | 577 die("Failed to allocate image %dx%d\n", enc_cfg.g_w, enc_cfg.g_h); |
| 347 } | 578 } |
| 348 #else | 579 #else |
| 349 if (!vpx_img_alloc(&raw, VPX_IMG_FMT_I420, enc_cfg.g_w, enc_cfg.g_h, 32)) { | 580 if (!vpx_img_alloc(&raw, VPX_IMG_FMT_I420, enc_cfg.g_w, enc_cfg.g_h, 32)) { |
| 350 die("Failed to allocate image %dx%d\n", enc_cfg.g_w, enc_cfg.g_h); | 581 die("Failed to allocate image %dx%d\n", enc_cfg.g_w, enc_cfg.g_h); |
| 351 } | 582 } |
| 352 #endif // CONFIG_VP9_HIGHBITDEPTH | 583 #endif // CONFIG_VP9_HIGHBITDEPTH |
| 353 | 584 |
| 354 if (!(infile = fopen(app_input.input_filename, "rb"))) | 585 if (!(infile = fopen(app_input.input_filename, "rb"))) |
| 355 die("Failed to open %s for reading\n", app_input.input_filename); | 586 die("Failed to open %s for reading\n", app_input.input_filename); |
| 356 | 587 |
| 357 // Initialize codec | 588 // Initialize codec |
| 358 if (vpx_svc_init(&svc_ctx, &codec, vpx_codec_vp9_cx(), &enc_cfg) != | 589 if (vpx_svc_init(&svc_ctx, &codec, vpx_codec_vp9_cx(), &enc_cfg) != |
| 359 VPX_CODEC_OK) | 590 VPX_CODEC_OK) |
| 360 die("Failed to initialize encoder\n"); | 591 die("Failed to initialize encoder\n"); |
| 361 | 592 |
| 593 #if OUTPUT_RC_STATS |
| 594 if (svc_ctx.output_rc_stat) { |
| 595 set_rate_control_stats(&rc, &enc_cfg); |
| 596 framerate = enc_cfg.g_timebase.den / enc_cfg.g_timebase.num; |
| 597 } |
| 598 #endif |
| 599 |
| 362 info.codec_fourcc = VP9_FOURCC; | 600 info.codec_fourcc = VP9_FOURCC; |
| 363 info.time_base.numerator = enc_cfg.g_timebase.num; | 601 info.time_base.numerator = enc_cfg.g_timebase.num; |
| 364 info.time_base.denominator = enc_cfg.g_timebase.den; | 602 info.time_base.denominator = enc_cfg.g_timebase.den; |
| 365 | 603 |
| 366 if (!(app_input.passes == 2 && app_input.pass == 1)) { | 604 if (!(app_input.passes == 2 && app_input.pass == 1)) { |
| 367 // We don't save the bitstream for the 1st pass on two pass rate control | 605 // We don't save the bitstream for the 1st pass on two pass rate control |
| 368 writer = vpx_video_writer_open(app_input.output_filename, kContainerIVF, | 606 writer = vpx_video_writer_open(app_input.output_filename, kContainerIVF, |
| 369 &info); | 607 &info); |
| 370 if (!writer) | 608 if (!writer) |
| 371 die("Failed to open %s for writing\n", app_input.output_filename); | 609 die("Failed to open %s for writing\n", app_input.output_filename); |
| 372 } | 610 } |
| 611 #if OUTPUT_RC_STATS |
| 612 // For now, just write temporal layer streams. |
| 613 // TODO(wonkap): do spatial by re-writing superframe. |
| 614 if (svc_ctx.output_rc_stat) { |
| 615 for (tl = 0; tl < enc_cfg.ts_number_layers; ++tl) { |
| 616 char file_name[PATH_MAX]; |
| 617 |
| 618 snprintf(file_name, sizeof(file_name), "%s_t%d.ivf", |
| 619 app_input.output_filename, tl); |
| 620 outfile[tl] = vpx_video_writer_open(file_name, kContainerIVF, &info); |
| 621 if (!outfile[tl]) |
| 622 die("Failed to open %s for writing", file_name); |
| 623 } |
| 624 } |
| 625 #endif |
| 373 | 626 |
| 374 // skip initial frames | 627 // skip initial frames |
| 375 for (i = 0; i < app_input.frames_to_skip; ++i) | 628 for (i = 0; i < app_input.frames_to_skip; ++i) |
| 376 vpx_img_read(&raw, infile); | 629 vpx_img_read(&raw, infile); |
| 377 | 630 |
| 631 if (svc_ctx.speed != -1) |
| 632 vpx_codec_control(&codec, VP8E_SET_CPUUSED, svc_ctx.speed); |
| 633 vpx_codec_control(&codec, VP9E_SET_TILE_COLUMNS, 0); |
| 634 |
| 378 // Encode frames | 635 // Encode frames |
| 379 while (!end_of_stream) { | 636 while (!end_of_stream) { |
| 380 vpx_codec_iter_t iter = NULL; | 637 vpx_codec_iter_t iter = NULL; |
| 381 const vpx_codec_cx_pkt_t *cx_pkt; | 638 const vpx_codec_cx_pkt_t *cx_pkt; |
| 382 if (frame_cnt >= app_input.frames_to_code || !vpx_img_read(&raw, infile)) { | 639 if (frame_cnt >= app_input.frames_to_code || !vpx_img_read(&raw, infile)) { |
| 383 // We need one extra vpx_svc_encode call at end of stream to flush | 640 // We need one extra vpx_svc_encode call at end of stream to flush |
| 384 // encoder and get remaining data | 641 // encoder and get remaining data |
| 385 end_of_stream = 1; | 642 end_of_stream = 1; |
| 386 } | 643 } |
| 387 | 644 |
| 388 res = vpx_svc_encode(&svc_ctx, &codec, (end_of_stream ? NULL : &raw), | 645 res = vpx_svc_encode(&svc_ctx, &codec, (end_of_stream ? NULL : &raw), |
| 389 pts, frame_duration, VPX_DL_GOOD_QUALITY); | 646 pts, frame_duration, svc_ctx.speed >= 5 ? |
| 647 VPX_DL_REALTIME : VPX_DL_GOOD_QUALITY); |
| 648 |
| 390 printf("%s", vpx_svc_get_message(&svc_ctx)); | 649 printf("%s", vpx_svc_get_message(&svc_ctx)); |
| 391 if (res != VPX_CODEC_OK) { | 650 if (res != VPX_CODEC_OK) { |
| 392 die_codec(&codec, "Failed to encode frame"); | 651 die_codec(&codec, "Failed to encode frame"); |
| 393 } | 652 } |
| 394 | 653 |
| 395 while ((cx_pkt = vpx_codec_get_cx_data(&codec, &iter)) != NULL) { | 654 while ((cx_pkt = vpx_codec_get_cx_data(&codec, &iter)) != NULL) { |
| 396 switch (cx_pkt->kind) { | 655 switch (cx_pkt->kind) { |
| 397 case VPX_CODEC_CX_FRAME_PKT: { | 656 case VPX_CODEC_CX_FRAME_PKT: { |
| 398 if (cx_pkt->data.frame.sz > 0) | 657 if (cx_pkt->data.frame.sz > 0) { |
| 658 #if OUTPUT_RC_STATS |
| 659 uint32_t sizes[8]; |
| 660 int count = 0; |
| 661 #endif |
| 399 vpx_video_writer_write_frame(writer, | 662 vpx_video_writer_write_frame(writer, |
| 400 cx_pkt->data.frame.buf, | 663 cx_pkt->data.frame.buf, |
| 401 cx_pkt->data.frame.sz, | 664 cx_pkt->data.frame.sz, |
| 402 cx_pkt->data.frame.pts); | 665 cx_pkt->data.frame.pts); |
| 666 #if OUTPUT_RC_STATS |
| 667 // TODO(marpan/wonkap): Put this (to line728) in separate function. |
| 668 if (svc_ctx.output_rc_stat) { |
| 669 vpx_codec_control(&codec, VP9E_GET_SVC_LAYER_ID, &layer_id); |
| 670 parse_superframe_index(cx_pkt->data.frame.buf, |
| 671 cx_pkt->data.frame.sz, sizes, &count); |
| 672 for (sl = 0; sl < enc_cfg.ss_number_layers; ++sl) { |
| 673 ++rc.layer_input_frames[sl * enc_cfg.ts_number_layers + |
| 674 layer_id.temporal_layer_id]; |
| 675 } |
| 676 for (tl = layer_id.temporal_layer_id; |
| 677 tl < enc_cfg.ts_number_layers; ++tl) { |
| 678 vpx_video_writer_write_frame(outfile[tl], |
| 679 cx_pkt->data.frame.buf, |
| 680 cx_pkt->data.frame.sz, |
| 681 cx_pkt->data.frame.pts); |
| 682 } |
| 683 |
| 684 for (sl = 0; sl < enc_cfg.ss_number_layers; ++sl) { |
| 685 for (tl = layer_id.temporal_layer_id; |
| 686 tl < enc_cfg.ts_number_layers; ++tl) { |
| 687 const int layer = sl * enc_cfg.ts_number_layers + tl; |
| 688 ++rc.layer_tot_enc_frames[layer]; |
| 689 rc.layer_encoding_bitrate[layer] += 8.0 * sizes[sl]; |
| 690 // Keep count of rate control stats per layer, for non-key |
| 691 // frames. |
| 692 if (tl == layer_id.temporal_layer_id && |
| 693 !(cx_pkt->data.frame.flags & VPX_FRAME_IS_KEY)) { |
| 694 rc.layer_avg_frame_size[layer] += 8.0 * sizes[sl]; |
| 695 rc.layer_avg_rate_mismatch[layer] += |
| 696 fabs(8.0 * sizes[sl] - rc.layer_pfb[layer]) / |
| 697 rc.layer_pfb[layer]; |
| 698 ++rc.layer_enc_frames[layer]; |
| 699 } |
| 700 } |
| 701 } |
| 702 |
| 703 // Update for short-time encoding bitrate states, for moving |
| 704 // window of size rc->window, shifted by rc->window / 2. |
| 705 // Ignore first window segment, due to key frame. |
| 706 if (frame_cnt > rc.window_size) { |
| 707 tl = layer_id.temporal_layer_id; |
| 708 for (sl = 0; sl < enc_cfg.ss_number_layers; ++sl) { |
| 709 sum_bitrate += 0.001 * 8.0 * sizes[sl] * framerate; |
| 710 } |
| 711 if (frame_cnt % rc.window_size == 0) { |
| 712 rc.window_count += 1; |
| 713 rc.avg_st_encoding_bitrate += sum_bitrate / rc.window_size; |
| 714 rc.variance_st_encoding_bitrate += |
| 715 (sum_bitrate / rc.window_size) * |
| 716 (sum_bitrate / rc.window_size); |
| 717 sum_bitrate = 0.0; |
| 718 } |
| 719 } |
| 720 |
| 721 // Second shifted window. |
| 722 if (frame_cnt > rc.window_size + rc.window_size / 2) { |
| 723 tl = layer_id.temporal_layer_id; |
| 724 for (sl = 0; sl < enc_cfg.ss_number_layers; ++sl) { |
| 725 sum_bitrate2 += 0.001 * 8.0 * sizes[sl] * framerate; |
| 726 } |
| 727 |
| 728 if (frame_cnt > 2 * rc.window_size && |
| 729 frame_cnt % rc.window_size == 0) { |
| 730 rc.window_count += 1; |
| 731 rc.avg_st_encoding_bitrate += sum_bitrate2 / rc.window_size; |
| 732 rc.variance_st_encoding_bitrate += |
| 733 (sum_bitrate2 / rc.window_size) * |
| 734 (sum_bitrate2 / rc.window_size); |
| 735 sum_bitrate2 = 0.0; |
| 736 } |
| 737 } |
| 738 } |
| 739 #endif |
| 740 } |
| 403 | 741 |
| 404 printf("SVC frame: %d, kf: %d, size: %d, pts: %d\n", frames_received, | 742 printf("SVC frame: %d, kf: %d, size: %d, pts: %d\n", frames_received, |
| 405 !!(cx_pkt->data.frame.flags & VPX_FRAME_IS_KEY), | 743 !!(cx_pkt->data.frame.flags & VPX_FRAME_IS_KEY), |
| 406 (int)cx_pkt->data.frame.sz, (int)cx_pkt->data.frame.pts); | 744 (int)cx_pkt->data.frame.sz, (int)cx_pkt->data.frame.pts); |
| 407 ++frames_received; | 745 ++frames_received; |
| 408 break; | 746 break; |
| 409 } | 747 } |
| 410 case VPX_CODEC_STATS_PKT: { | 748 case VPX_CODEC_STATS_PKT: { |
| 411 stats_write(&app_input.rc_stats, | 749 stats_write(&app_input.rc_stats, |
| 412 cx_pkt->data.twopass_stats.buf, | 750 cx_pkt->data.twopass_stats.buf, |
| 413 cx_pkt->data.twopass_stats.sz); | 751 cx_pkt->data.twopass_stats.sz); |
| 414 break; | 752 break; |
| 415 } | 753 } |
| 416 default: { | 754 default: { |
| 417 break; | 755 break; |
| 418 } | 756 } |
| 419 } | 757 } |
| 420 } | 758 } |
| 421 | 759 |
| 422 if (!end_of_stream) { | 760 if (!end_of_stream) { |
| 423 ++frame_cnt; | 761 ++frame_cnt; |
| 424 pts += frame_duration; | 762 pts += frame_duration; |
| 425 } | 763 } |
| 426 } | 764 } |
| 427 | |
| 428 printf("Processed %d frames\n", frame_cnt); | 765 printf("Processed %d frames\n", frame_cnt); |
| 429 | |
| 430 fclose(infile); | 766 fclose(infile); |
| 767 #if OUTPUT_RC_STATS |
| 768 if (svc_ctx.output_rc_stat) { |
| 769 printout_rate_control_summary(&rc, &enc_cfg, frame_cnt); |
| 770 printf("\n"); |
| 771 } |
| 772 #endif |
| 431 if (vpx_codec_destroy(&codec)) die_codec(&codec, "Failed to destroy codec"); | 773 if (vpx_codec_destroy(&codec)) die_codec(&codec, "Failed to destroy codec"); |
| 432 | |
| 433 if (app_input.passes == 2) | 774 if (app_input.passes == 2) |
| 434 stats_close(&app_input.rc_stats, 1); | 775 stats_close(&app_input.rc_stats, 1); |
| 435 | |
| 436 if (writer) { | 776 if (writer) { |
| 437 vpx_video_writer_close(writer); | 777 vpx_video_writer_close(writer); |
| 438 } | 778 } |
| 439 | 779 #if OUTPUT_RC_STATS |
| 780 if (svc_ctx.output_rc_stat) { |
| 781 for (tl = 0; tl < enc_cfg.ts_number_layers; ++tl) { |
| 782 vpx_video_writer_close(outfile[tl]); |
| 783 } |
| 784 } |
| 785 #endif |
| 440 vpx_img_free(&raw); | 786 vpx_img_free(&raw); |
| 441 | |
| 442 // display average size, psnr | 787 // display average size, psnr |
| 443 printf("%s", vpx_svc_dump_statistics(&svc_ctx)); | 788 printf("%s", vpx_svc_dump_statistics(&svc_ctx)); |
| 444 | |
| 445 vpx_svc_release(&svc_ctx); | 789 vpx_svc_release(&svc_ctx); |
| 446 | |
| 447 return EXIT_SUCCESS; | 790 return EXIT_SUCCESS; |
| 448 } | 791 } |
| OLD | NEW |