Neil Birkbeck | 0f0b370 | 2018-03-15 17:14:16 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (c) 2018, Alliance for Open Media. All rights reserved |
| 3 | * |
| 4 | * This source code is subject to the terms of the BSD 2 Clause License and |
| 5 | * the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License |
| 6 | * was not distributed with this source code in the LICENSE file, you can |
| 7 | * obtain it at www.aomedia.org/license/software. If the Alliance for Open |
| 8 | * Media Patent License 1.0 was not distributed with this source code in the |
| 9 | * PATENTS file, you can obtain it at www.aomedia.org/license/patent. |
| 10 | */ |
| 11 | |
| 12 | /*!\file |
| 13 | * \brief This is an sample binary to create noise params from input video. |
| 14 | * |
| 15 | * To allow for external denoising applications, this sample binary illustrates |
| 16 | * how to create a film grain table (film grain params as a function of time) |
| 17 | * from an input video and its corresponding denoised source. |
| 18 | * |
| 19 | * The --output-grain-table file can be passed as input to the encoder (in |
| 20 | * aomenc this is done through the "--film-grain-table" parameter). |
| 21 | */ |
| 22 | #include <stdio.h> |
| 23 | #include <stdlib.h> |
| 24 | #include <string.h> |
| 25 | |
| 26 | #include "../args.h" |
| 27 | #include "../tools_common.h" |
| 28 | #include "../video_writer.h" |
| 29 | #include "aom/aom_encoder.h" |
| 30 | #include "aom_dsp/aom_dsp_common.h" |
| 31 | #include "aom_dsp/noise_model.h" |
| 32 | #include "aom_dsp/noise_util.h" |
| 33 | #include "aom_dsp/grain_table.h" |
| 34 | #include "aom_mem/aom_mem.h" |
| 35 | |
| 36 | static const char *exec_name; |
| 37 | |
| 38 | void usage_exit(void) { |
| 39 | fprintf(stderr, |
| 40 | "Usage: %s --input=<input> --input-denoised=<denoised> " |
| 41 | "--output-grain-table=<outfile> " |
| 42 | "See comments in noise_model.c for more information.\n", |
| 43 | exec_name); |
| 44 | exit(EXIT_FAILURE); |
| 45 | } |
| 46 | |
| 47 | static const arg_def_t help = |
| 48 | ARG_DEF(NULL, "help", 0, "Show usage options and exit"); |
| 49 | static const arg_def_t width_arg = |
| 50 | ARG_DEF("w", "width", 1, "Input width (if rawvideo)"); |
| 51 | static const arg_def_t height_arg = |
| 52 | ARG_DEF("h", "height", 1, "Input height (if rawvideo)"); |
| 53 | static const arg_def_t skip_frames_arg = |
| 54 | ARG_DEF("s", "skip-frames", 1, "Number of frames to skip (default = 1)"); |
| 55 | static const arg_def_t fps_arg = ARG_DEF(NULL, "fps", 1, "Frame rate"); |
| 56 | static const arg_def_t input_arg = ARG_DEF("-i", "input", 1, "Input filename"); |
| 57 | static const arg_def_t output_grain_table_arg = |
| 58 | ARG_DEF("n", "output-grain-table", 1, "Output noise file"); |
| 59 | static const arg_def_t input_denoised_arg = |
| 60 | ARG_DEF("d", "input-denoised", 1, "Input denoised filename (YUV) only"); |
| 61 | static const arg_def_t block_size_arg = |
| 62 | ARG_DEF("b", "block_size", 1, "Block size"); |
| 63 | static const arg_def_t use_i420 = |
| 64 | ARG_DEF(NULL, "i420", 0, "Input file (and denoised) is I420 (default)"); |
| 65 | static const arg_def_t use_i422 = |
| 66 | ARG_DEF(NULL, "i422", 0, "Input file (and denoised) is I422"); |
| 67 | static const arg_def_t use_i444 = |
| 68 | ARG_DEF(NULL, "i444", 0, "Input file (and denoised) is I444"); |
| 69 | |
| 70 | typedef struct { |
| 71 | int width; |
| 72 | int height; |
| 73 | struct aom_rational fps; |
| 74 | const char *input; |
| 75 | const char *input_denoised; |
| 76 | const char *output_grain_table; |
| 77 | int img_fmt; |
| 78 | int block_size; |
| 79 | int run_flat_block_finder; |
| 80 | int force_flat_psd; |
| 81 | int skip_frames; |
| 82 | } noise_model_args_t; |
| 83 | |
| 84 | void parse_args(noise_model_args_t *noise_args, int *argc, char **argv) { |
| 85 | struct arg arg; |
| 86 | static const arg_def_t *main_args[] = { &help, |
| 87 | &input_arg, |
| 88 | &fps_arg, |
| 89 | &width_arg, |
| 90 | &height_arg, |
| 91 | &block_size_arg, |
| 92 | &output_grain_table_arg, |
| 93 | &input_denoised_arg, |
| 94 | &use_i420, |
| 95 | &use_i422, |
| 96 | &use_i444, |
| 97 | NULL }; |
| 98 | for (int argi = *argc + 1; *argv; argi++, argv++) { |
| 99 | if (arg_match(&arg, &help, argv)) { |
| 100 | fprintf(stdout, "\nOptions:\n"); |
| 101 | arg_show_usage(stdout, main_args); |
| 102 | exit(0); |
| 103 | } else if (arg_match(&arg, &width_arg, argv)) { |
| 104 | noise_args->width = atoi(arg.val); |
| 105 | } else if (arg_match(&arg, &height_arg, argv)) { |
| 106 | noise_args->height = atoi(arg.val); |
| 107 | } else if (arg_match(&arg, &input_arg, argv)) { |
| 108 | noise_args->input = arg.val; |
| 109 | } else if (arg_match(&arg, &input_denoised_arg, argv)) { |
| 110 | noise_args->input_denoised = arg.val; |
| 111 | } else if (arg_match(&arg, &output_grain_table_arg, argv)) { |
| 112 | noise_args->output_grain_table = arg.val; |
| 113 | } else if (arg_match(&arg, &block_size_arg, argv)) { |
| 114 | noise_args->block_size = atoi(arg.val); |
| 115 | } else if (arg_match(&arg, &fps_arg, argv)) { |
| 116 | noise_args->fps = arg_parse_rational(&arg); |
| 117 | } else if (arg_match(&arg, &use_i420, argv)) { |
| 118 | noise_args->img_fmt = AOM_IMG_FMT_I420; |
| 119 | } else if (arg_match(&arg, &use_i422, argv)) { |
| 120 | noise_args->img_fmt = AOM_IMG_FMT_I422; |
| 121 | } else if (arg_match(&arg, &use_i444, argv)) { |
| 122 | noise_args->img_fmt = AOM_IMG_FMT_I444; |
| 123 | } else if (arg_match(&arg, &skip_frames_arg, argv)) { |
| 124 | noise_args->skip_frames = atoi(arg.val); |
| 125 | } else { |
| 126 | fprintf(stdout, "Unknown arg: %s\n\nUsage:\n", *argv); |
| 127 | arg_show_usage(stdout, main_args); |
| 128 | exit(0); |
| 129 | } |
| 130 | } |
| 131 | } |
| 132 | |
| 133 | int main(int argc, char *argv[]) { |
| 134 | noise_model_args_t args = { 0, 0, { 1, 25 }, 0, 0, 0, AOM_IMG_FMT_I420, |
| 135 | 32, 0, 0, 1 }; |
| 136 | aom_image_t raw, denoised; |
| 137 | FILE *infile = NULL; |
| 138 | AvxVideoInfo info; |
| 139 | |
| 140 | memset(&info, 0, sizeof(info)); |
| 141 | |
| 142 | exec_name = argv[0]; |
| 143 | parse_args(&args, &argc, argv + 1); |
| 144 | |
| 145 | info.frame_width = args.width; |
| 146 | info.frame_height = args.height; |
| 147 | info.time_base.numerator = args.fps.den; |
| 148 | info.time_base.denominator = args.fps.num; |
| 149 | |
| 150 | if (info.frame_width <= 0 || info.frame_height <= 0 || |
| 151 | (info.frame_width % 2) != 0 || (info.frame_height % 2) != 0) { |
| 152 | die("Invalid frame size: %dx%d", info.frame_width, info.frame_height); |
| 153 | } |
| 154 | if (!aom_img_alloc(&raw, args.img_fmt, info.frame_width, info.frame_height, |
| 155 | 1)) { |
| 156 | die("Failed to allocate image."); |
| 157 | } |
| 158 | if (!aom_img_alloc(&denoised, args.img_fmt, info.frame_width, |
| 159 | info.frame_height, 1)) { |
| 160 | die("Failed to allocate image."); |
| 161 | } |
| 162 | infile = fopen(args.input, "r"); |
| 163 | if (!infile) { |
| 164 | die("Failed to open input file:", args.input); |
| 165 | } |
| 166 | const int block_size = args.block_size; |
| 167 | aom_flat_block_finder_t block_finder; |
| 168 | aom_flat_block_finder_init(&block_finder, block_size); |
| 169 | |
| 170 | const int num_blocks_w = (info.frame_width + block_size - 1) / block_size; |
| 171 | const int num_blocks_h = (info.frame_height + block_size - 1) / block_size; |
| 172 | uint8_t *flat_blocks = (uint8_t *)aom_malloc(num_blocks_w * num_blocks_h); |
| 173 | aom_noise_model_t noise_model; |
| 174 | aom_noise_model_params_t params = { AOM_NOISE_SHAPE_SQUARE, 3 }; |
| 175 | aom_noise_model_init(&noise_model, params); |
| 176 | |
| 177 | FILE *denoised_file = 0; |
| 178 | if (args.input_denoised) { |
| 179 | denoised_file = fopen(args.input_denoised, "rb"); |
| 180 | if (!denoised_file) |
| 181 | die("Unable to open input_denoised: %s", args.input_denoised); |
| 182 | } else { |
| 183 | die("--input-denoised file must be specified"); |
| 184 | } |
Yaowu Xu | adbe7b1 | 2018-03-27 10:34:46 -0700 | [diff] [blame] | 185 | aom_film_grain_table_t grain_table = { 0, 0 }; |
Neil Birkbeck | 0f0b370 | 2018-03-15 17:14:16 -0700 | [diff] [blame] | 186 | |
| 187 | int64_t prev_timestamp = 0; |
| 188 | int frame_count = 0; |
| 189 | while (aom_img_read(&raw, infile)) { |
| 190 | if (args.input_denoised) { |
| 191 | if (!aom_img_read(&denoised, denoised_file)) { |
| 192 | die("Unable to read input denoised file"); |
| 193 | } |
| 194 | } |
| 195 | if (frame_count % args.skip_frames == 0) { |
| 196 | int num_flat_blocks = num_blocks_w * num_blocks_h; |
| 197 | memset(flat_blocks, 1, num_flat_blocks); |
| 198 | if (args.run_flat_block_finder) { |
| 199 | memset(flat_blocks, 0, num_flat_blocks); |
| 200 | num_flat_blocks = aom_flat_block_finder_run( |
| 201 | &block_finder, raw.planes[0], info.frame_width, info.frame_height, |
| 202 | info.frame_width, flat_blocks); |
| 203 | fprintf(stdout, "Num flat blocks %d\n", num_flat_blocks); |
| 204 | } |
| 205 | |
| 206 | const uint8_t *planes[3] = { raw.planes[0], raw.planes[1], |
| 207 | raw.planes[2] }; |
| 208 | uint8_t *denoised_planes[3] = { denoised.planes[0], denoised.planes[1], |
| 209 | denoised.planes[2] }; |
| 210 | int strides[3] = { raw.stride[0], raw.stride[1], raw.stride[2] }; |
| 211 | int chroma_sub[3] = { raw.x_chroma_shift, raw.y_chroma_shift, 0 }; |
| 212 | |
| 213 | fprintf(stdout, "Updating noise model...\n"); |
| 214 | aom_noise_status_t status = aom_noise_model_update( |
| 215 | &noise_model, (const uint8_t *const *)planes, |
| 216 | (const uint8_t *const *)denoised_planes, info.frame_width, |
| 217 | info.frame_height, strides, chroma_sub, flat_blocks, block_size); |
| 218 | |
| 219 | int64_t cur_timestamp = |
| 220 | frame_count * 10000000ULL * args.fps.den / args.fps.num; |
| 221 | if (status == AOM_NOISE_STATUS_DIFFERENT_NOISE_TYPE) { |
| 222 | fprintf(stdout, |
| 223 | "Noise type is different, updating parameters for time " |
| 224 | "[ %" PRId64 ", %" PRId64 ")\n", |
| 225 | prev_timestamp, cur_timestamp); |
| 226 | aom_film_grain_t grain; |
| 227 | aom_noise_model_get_grain_parameters(&noise_model, &grain); |
| 228 | aom_film_grain_table_append(&grain_table, prev_timestamp, cur_timestamp, |
| 229 | &grain); |
| 230 | aom_noise_model_save_latest(&noise_model); |
| 231 | prev_timestamp = cur_timestamp; |
| 232 | } |
| 233 | |
| 234 | fprintf(stdout, "Done noise model update, status = %d\n", status); |
| 235 | } |
| 236 | frame_count++; |
| 237 | } |
| 238 | |
| 239 | aom_film_grain_t grain; |
| 240 | aom_noise_model_get_grain_parameters(&noise_model, &grain); |
| 241 | aom_film_grain_table_append(&grain_table, prev_timestamp, INT64_MAX, &grain); |
| 242 | if (args.output_grain_table) { |
| 243 | struct aom_internal_error_info error_info; |
| 244 | if (AOM_CODEC_OK != aom_film_grain_table_write(&grain_table, |
| 245 | args.output_grain_table, |
| 246 | &error_info)) { |
| 247 | die("Unable to write output film grain table"); |
| 248 | } |
| 249 | } |
| 250 | aom_film_grain_table_free(&grain_table); |
| 251 | |
| 252 | if (infile) fclose(infile); |
| 253 | if (denoised_file) fclose(denoised_file); |
| 254 | aom_img_free(&raw); |
| 255 | aom_img_free(&denoised); |
| 256 | |
| 257 | return EXIT_SUCCESS; |
| 258 | } |