| // Copyright 2019 Google LLC |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // https://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| // ----------------------------------------------------------------------------- |
| // |
| // compressor/decompressor for testing AV1 |
| // |
| // ./av1enc input [-o output.png] [-d bitstream.av1] [-q xx ...] |
| // |
| // The -setenv option allows AV1 behavior modifications and comparisons. |
| // For example the following code can be added anywhere |
| // (such as in third_party/libaom/git_root/av1/decoder/decodeframe.c): |
| // if (getenv("STUFF_ON")) { /* do stuff */ } |
| // And then this tool can be called: |
| // ./av1enc src.png -O -setenv STUFF_ON -setenv STUFF_OFF |
| // This will generate 'src_dec_STUFF_ON.png' and 'src_dec_STUFF_OFF.png' |
| // This tool is not available on _WIN32. |
| // |
| // Author: Skal (pascal.massimino@gmail.com) |
| |
| #include <cstdio> |
| #include <cstring> |
| #include <string> |
| #include <vector> |
| |
| #include "examples/example_utils.h" |
| #include "extras/aom_utils.h" |
| #include "imageio/file_format.h" |
| #include "imageio/image_dec.h" |
| #include "imageio/image_enc.h" |
| |
| namespace { |
| |
| //------------------------------------------------------------------------------ |
| |
| std::string AppendToFileName(const std::string& file_path, |
| const std::string& suffix) { |
| return WP2::RemoveFileExtension(file_path) + suffix + "." + |
| WP2::GetFileExtension(file_path); |
| } |
| |
| //------------------------------------------------------------------------------ |
| |
| #if defined(WP2_HAVE_AOM_DBG) |
| // Names of most variables in struct 'insp_mi_data' from AV1 inspection.h |
| const char* const kAllMiDataNames[]{ |
| "mode", "uv_mode", "sb_type", "skip", |
| "segment_id", "dual_filter_type", "filter[0]", "filter[1]", |
| "tx_type", "tx_size", "cdef_level", "cdef_strength", |
| "cfl_alpha_idx", "cfl_alpha_sign", "current_qindex", "compound_type", |
| "motion_mode", "intrabc", "palette", "uv_palette"}; |
| #endif |
| |
| //------------------------------------------------------------------------------ |
| |
| void Help() { |
| WP2::ProgramOptions opt; |
| opt.Add("Usage:"); |
| opt.Add(" av1enc [options] in_file [-o decoded_img] [-d encoded_av1]"); |
| opt.Add(""); |
| opt.Add("Options:"); |
| opt.Add("-q <float>", "quality factor [0..100]"); |
| opt.Add("-alpha_q <float>", "alpha quality factor [0..100]"); |
| opt.Add("-size <int>", "target size (predates -q)"); |
| opt.Add("-effort <int>", "compression effort [0..9]"); |
| opt.Add("-threads <int>", "number of threads used"); |
| opt.Add("-pass <int>", "number of passes [1..2]"); |
| opt.Add("-420", "use YUV420 encoding (default)"); |
| opt.Add("-444", "use YUV444 encoding"); |
| opt.Add("-tune <metric>", "choose metric for tuning. " |
| "One of: psnr, ssim, vmaf, butteraugli"); |
| opt.Add("-notransforms", "disable transforms other than DCT"); |
| opt.Add("-nopreds", |
| "disable predictors other than DC (and some angles for now)"); |
| opt.Add("-nopalette", "disable palette"); |
| opt.Add("-noblock_copy", "disable block copy"); |
| opt.Add("-notrellis", "disable trellis (uses dropout instead)"); |
| opt.Add("-force_block_size <int>", |
| "forces a square block size (4, 8, 16, 32, 64, 128"); |
| opt.AddMetricOptions(); |
| #if defined(WP2_HAVE_AOM_DBG) |
| opt.Add("-bt / -BT", "report bit use (if compiled)"); |
| opt.Add("-compact", "use compact trace for bt/BT"); |
| opt.Add("-b", "draw blocks (if compiled)"); |
| opt.Add("-B", "dump blocks in /tmp/partition.txt"); |
| opt.Add("-t", "draw luma transforms boundaries (if compiled)"); |
| opt.Add("-T", "dump luma transforms layout in /tmp/partition.txt"); |
| opt.Add("-mi_data [var_name]", "draw insp_mi_data (.var_name)"); |
| opt.Add("-mi_number", "draw mi value as a red square"); |
| opt.Add("-quant [y|u|v]", "draw/print quantization for the given channel"); |
| #if !defined(_WIN32) |
| opt.Add("-setenv <name> [value]", |
| "set environment variable (one encoding per -setenv)"); |
| #endif // !_WIN32 |
| #endif // WP2_HAVE_AOM_DBG |
| opt.Add("-o <file path>", "path to decoded image"); |
| opt.Add("-O", "same as -o [in_file]_dec.png"); |
| opt.Add("-d <file path>", "path to encoded AV1 image"); |
| opt.Add("-D", "same as -d [in_file].av1"); |
| opt.Add("-a option=value", "extra option for libaom"); |
| opt.Add("-quiet", "minimal output"); |
| opt.Add("-avif", "write as AVIF"); |
| opt.Add("-ivf", "write as ivf file (incompatible with --avif)"); |
| opt.Add("-libgav1", "use libgav1 decoder"); |
| opt.Add("-h", "this help"); |
| opt.Print(); |
| } |
| |
| } // namespace |
| |
| //------------------------------------------------------------------------------ |
| |
| int main(int argc, const char* argv[]) { |
| std::string input_path; |
| std::string decoded_path; |
| std::string encoded_path; |
| bool decoded_path_from_input = false, encoded_path_from_input = false; |
| WP2::ParamsAV1 params; |
| WP2::MetricType type = WP2::PSNR; |
| std::vector<std::pair<std::string, std::string>> env_vars; |
| std::vector<const char*> mi_data_names; |
| std::vector<WP2::Rectangle> blocks; |
| std::vector<WP2::Rectangle> transforms; |
| bool store_blocks = false, store_transforms = false; |
| bool count_blocks = false, count_transforms = false; |
| bool print_quant = false; |
| bool quiet = false; |
| bool use_avif = false; |
| bool use_ivf = false; |
| for (int c = 1; c < argc; ++c) { |
| if (!strcmp(argv[c], "-d") && c + 1 < argc) { |
| encoded_path = argv[++c]; |
| } else if (!strcmp(argv[c], "-D")) { |
| encoded_path_from_input = true; |
| } else if (!strcmp(argv[c], "-o") && c + 1 < argc) { |
| decoded_path = argv[++c]; |
| } else if (!strcmp(argv[c], "-O")) { |
| decoded_path_from_input = true; |
| } else if (!strcmp(argv[c], "-q") && c + 1 < argc) { |
| params.quality = atof(argv[++c]); // NOLINT |
| } else if (!strcmp(argv[c], "-alpha_q") && c + 1 < argc) { |
| params.alpha_quality = atof(argv[++c]); // NOLINT |
| } else if (!strcmp(argv[c], "-420")) { |
| params.use_yuv444 = false; |
| } else if (!strcmp(argv[c], "-444")) { |
| params.use_yuv444 = true; |
| } else if (!strcmp(argv[c], "-notransforms")) { |
| params.disable_transforms = true; |
| } else if (!strcmp(argv[c], "-nopreds")) { |
| params.disable_predictors = true; |
| } else if (!strcmp(argv[c], "-nopalette")) { |
| params.disable_palette = true; |
| } else if (!strcmp(argv[c], "-noblock_copy")) { |
| params.disable_block_copy = true; |
| } else if (!strcmp(argv[c], "-notrellis")) { |
| params.disable_trellis = true; |
| } else if (!strcmp(argv[c], "-force_block_size") && c + 1 < argc) { |
| params.force_block_size = atoi(argv[++c]); // NOLINT |
| } else if (!strcmp(argv[c], "-tune") && c + 1 < argc) { |
| ++c; |
| if (!strcmp(argv[c], "psnr") || !strcmp(argv[c], "PSNR")) { |
| params.tuning = WP2::ParamsAV1::Tuning::kPsnr; |
| } else if (!strcmp(argv[c], "ssim") || !strcmp(argv[c], "SSIM")) { |
| params.tuning = WP2::ParamsAV1::Tuning::kSsim; |
| } else if (!strcmp(argv[c], "vmaf")) { |
| params.tuning = WP2::ParamsAV1::Tuning::kVmaf; |
| } else if (!strcmp(argv[c], "butteraugli")) { |
| params.tuning = WP2::ParamsAV1::Tuning::kButteraugli; |
| } else { |
| fprintf(stderr, "Ignored unhandled -tune '%s'\n", argv[c]); |
| } |
| #if defined(WP2_HAVE_AOM_DBG) |
| } else if (!strcmp(argv[c], "-bt")) { |
| params.bittrace = 1; |
| } else if (!strcmp(argv[c], "-BT")) { |
| params.bittrace = 2; |
| } else if (!strcmp(argv[c], "-compact")) { |
| params.compact_trace = true; |
| } else if (!strcmp(argv[c], "-b")) { |
| params.draw_blocks = true; |
| } else if (!strcmp(argv[c], "-B")) { |
| store_blocks = true; |
| } else if (!strcmp(argv[c], "-count_blocks")) { |
| count_blocks = true; |
| } else if (!strcmp(argv[c], "-t")) { |
| params.draw_transforms = true; |
| } else if (!strcmp(argv[c], "-T")) { |
| store_transforms = true; |
| } else if (!strcmp(argv[c], "-count_transforms")) { |
| count_transforms = true; |
| } else if (!strcmp(argv[c], "-mi_data")) { |
| if (c + 1 < argc && argv[c + 1][0] != '-') { |
| ++c; |
| bool found = false; |
| for (const char* n : kAllMiDataNames) found |= (!strcmp(argv[c], n)); |
| if (found) mi_data_names.emplace_back(argv[c]); |
| if (!found) { |
| fprintf(stderr, "Ignored unknown mi_data '%s'. Must be one of:\n", |
| argv[c]); |
| for (const char* n : kAllMiDataNames) fprintf(stderr, "%s ", n); |
| fprintf(stderr, "\n"); |
| } |
| } else { |
| for (const char* n : kAllMiDataNames) mi_data_names.emplace_back(n); |
| mi_data_names.emplace_back(nullptr); // Also output the regular img. |
| } |
| } else if (!strcmp(argv[c], "-mi_number")) { |
| params.draw_mi_number = true; |
| } else if (!strcmp(argv[c], "-quant") && c + 1 < argc) { |
| ++c; |
| print_quant = true; |
| if (!strcmp(argv[c], "y")) { |
| mi_data_names.emplace_back("quanty"); |
| } else if (!strcmp(argv[c], "u")) { |
| mi_data_names.emplace_back("quantu"); |
| } else if (!strcmp(argv[c], "v")) { |
| mi_data_names.emplace_back("quantv"); |
| } else { |
| fprintf(stderr, "Unknown channel '%s'. Must be one of: y, u, v\n", |
| argv[c]); |
| } |
| #if !defined(_WIN32) |
| } else if (!strcmp(argv[c], "-setenv") && c + 1 < argc) { |
| const bool has_value = (c + 2 < argc && argv[c + 2][0] != '-'); |
| env_vars.emplace_back(argv[c + 1], has_value ? argv[c + 2] : ""); |
| c += (has_value ? 2 : 1); |
| #endif // !_WIN32 |
| #endif // WP2_HAVE_AOM_DBG |
| |
| } else if (!strcmp(argv[c], "-size") && c + 1 < argc) { |
| params.size = atoi(argv[++c]); // NOLINT |
| } else if (!strcmp(argv[c], "-threads") && c + 1 < argc) { |
| params.threads = atoi(argv[++c]); // NOLINT |
| } else if (!strcmp(argv[c], "-effort") && c + 1 < argc) { |
| params.effort = atoi(argv[++c]); // NOLINT |
| } else if (!strcmp(argv[c], "-pass") && c + 1 < argc) { |
| params.pass = atoi(argv[++c]); // NOLINT |
| } else if (!strcmp(argv[c], "-a") && c + 1 < argc) { |
| const std::string param(argv[++c]); |
| int option_enum; |
| int option_value; |
| if (WP2::ParseLibaomOption(param, &option_enum, &option_value)) { |
| params.extra_options[option_enum] = option_value; |
| } |
| } else if (!strcmp(argv[c], "-quiet")) { |
| quiet = true; |
| } else if (!strcmp(argv[c], "-avif")) { |
| use_avif = true; |
| } else if (!strcmp(argv[c], "-ivf")) { |
| use_ivf = true; |
| } else if (!strcmp(argv[c], "-libgav1")) { |
| params.use_libgav1_decoder = true; |
| } else if (!strcmp(argv[c], "-h")) { |
| Help(); |
| return 0; |
| } else if (WP2::ProgramOptions::ParseMetricOptions(argv[c], &type)) { |
| continue; |
| } else { |
| input_path = argv[c]; |
| } |
| } |
| if (input_path.empty()) { |
| fprintf(stderr, "Missing input filename!\n"); |
| Help(); |
| return 255; |
| } |
| if (use_avif && use_ivf) { |
| fprintf(stderr, "--ivf and --avif are incompatible\n"); |
| return 255; |
| } |
| if (decoded_path_from_input) { |
| decoded_path = AppendToFileName(input_path, "_dec"); |
| } |
| if (encoded_path_from_input) { |
| encoded_path = WP2::RemoveFileExtension(input_path) + ".av1"; |
| } |
| |
| WP2::ArgbBuffer ref; |
| if (WP2::ReadImage(input_path.c_str(), &ref) != WP2_STATUS_OK) { |
| fprintf(stderr, "Error opening file: %s\n", input_path.c_str()); |
| return 1; |
| } |
| for (size_t i = 0; i < std::max((size_t)1, env_vars.size()); ++i) { |
| for (size_t n = 0; n < std::max((size_t)1, mi_data_names.size()); ++n) { |
| std::string suffix; |
| #if !defined(_WIN32) |
| if (!env_vars.empty()) { |
| setenv(env_vars[i].first.c_str(), env_vars[i].second.c_str(), 1); |
| if (env_vars.size() > 1) { |
| // Only append it to the file name if there are several env vars. |
| suffix = "_" + env_vars[i].first; |
| if (!env_vars[i].second.empty()) suffix += "_" + env_vars[i].second; |
| } |
| } |
| #endif // !_WIN32 |
| if (!mi_data_names.empty()) { |
| params.draw_mi_data = mi_data_names[n]; |
| if (mi_data_names[n] != nullptr) { |
| suffix += "-"; |
| suffix += mi_data_names[n]; |
| } |
| } |
| |
| std::string bits; |
| WP2::ArgbBuffer decoded; |
| double timing[2]; |
| WP2::QuantAV1 quant; |
| |
| if (use_avif) { |
| // Disable statistics gathering. |
| store_blocks = count_blocks = false; |
| store_transforms = count_transforms = false; |
| print_quant = false; |
| if (WP2::CompressAVIF(ref, params, &decoded, &bits, timing) != |
| WP2_STATUS_OK) { |
| fprintf(stderr, "Error compressing to AVIF\n"); |
| return 2; |
| } |
| } else if (WP2::CompressAV1( |
| ref, params, &decoded, &bits, timing, |
| (store_blocks || count_blocks) ? &blocks : nullptr, |
| (store_transforms || count_transforms) ? &transforms : nullptr, |
| print_quant ? &quant : nullptr) != WP2_STATUS_OK) { |
| fprintf(stderr, "Error compressing to AV1\n"); |
| return 2; |
| } |
| #if !defined(_WIN32) |
| if (!env_vars.empty()) { |
| unsetenv(env_vars[i].first.c_str()); |
| } |
| #endif // !_WIN32 |
| |
| // Align text with bit traces. |
| if (count_blocks) { |
| printf("num-blocks %u\n", (uint32_t)blocks.size()); |
| } |
| if (count_transforms) { |
| printf("num-transforms %u\n", |
| (uint32_t)transforms.size()); |
| } |
| if (print_quant) { |
| for (uint32_t s = 0; s < 8; ++s) { |
| printf("segment %d\n", s); |
| printf("\tY quant, DC: %d AC: %d\n", quant.y_dequant[s][0], |
| quant.y_dequant[s][1]); |
| printf("\tU quant, DC: %d AC: %d\n", quant.u_dequant[s][0], |
| quant.u_dequant[s][1]); |
| printf("\tV quant, DC: %d AC: %d\n", quant.v_dequant[s][0], |
| quant.v_dequant[s][1]); |
| } |
| } |
| |
| if (use_ivf) { |
| bits = |
| WP2::IvfStillImageHeader(ref.width(), ref.height(), bits.size()) + |
| bits; |
| } |
| |
| // Don't display the distortion if there is debug info in the image. |
| if (!params.draw_blocks && !params.draw_transforms && |
| params.draw_mi_data == nullptr) { |
| float disto[5]; |
| if (ref.GetDistortionBlackOrWhiteBackground(decoded, type, disto) != |
| WP2_STATUS_OK) { |
| fprintf(stderr, "Error while computing the distortion.\n"); |
| return 2; |
| } |
| printf("%u %f (%f %f %f %f) ", (uint32_t)bits.size(), disto[4], |
| disto[0], disto[1], disto[2], disto[3]); |
| printf("[ %.2f bpp ] [ %d x %d ] [%.3f / %.3f secs]\n", |
| 8.f * bits.size() / (ref.width() * ref.height()), ref.width(), |
| ref.height(), timing[0], timing[1]); |
| } |
| |
| if (store_blocks || store_transforms) { |
| const char path[] = "/tmp/partition.txt"; |
| // Transforms are contained in blocks so output only transforms if both. |
| if (!WritePartition(store_transforms ? transforms : blocks, path)) { |
| fprintf(stderr, "Error saving partition file (%s)\n", path); |
| return 3; |
| } else if (!quiet) { |
| printf("Saved partition file to %s\n", path); |
| } |
| } |
| |
| if (!decoded_path.empty()) { |
| const std::string path = AppendToFileName(decoded_path, suffix); |
| if (WP2::SaveImage(decoded, path.c_str(), /*overwrite=*/true) != |
| WP2_STATUS_OK) { |
| fprintf(stderr, "Error saving decoded image (%s)\n", path.c_str()); |
| return 3; |
| } else if (!quiet) { |
| printf("Saved decoded image %s\n", path.c_str()); |
| } |
| } |
| |
| if (!encoded_path.empty() && !params.draw_blocks && |
| !params.draw_transforms && params.draw_mi_data == nullptr) { |
| const std::string path = AppendToFileName(encoded_path, suffix); |
| if (WP2::IoUtilWriteFile(bits, path.c_str(), |
| /*overwrite=*/true) != WP2_STATUS_OK) { |
| fprintf(stderr, "Error saving encoded file (%s)\n", path.c_str()); |
| return 3; |
| } else if (!quiet) { |
| printf("Saved encoded file: %s (%u bytes)\n", path.c_str(), |
| (uint32_t)bits.size()); |
| } |
| } |
| } |
| } |
| return 0; |
| } |