blob: 30b366232c61869ee69cfcca61a2ca2fba328df1 [file] [log] [blame]
// Copyright 2021 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.
#include <cstring>
#include <fstream>
#include <iostream>
#include <string>
#include <vector>
#include "./av1_obu.h"
#include "./avif.h"
#include "./container.h"
namespace {
//------------------------------------------------------------------------------
void Help() {
std::cout << "Tool for converting between AVIF and minimal container formats."
<< std::endl;
std::cout << "Usage:" << std::endl;
std::cout << " convert <path> [options]" << std::endl << std::endl;
std::cout << "Options:" << std::endl
<< " -o <path>" << std::endl
<< " --output <path> ... Output file" << std::endl
<< " --container ....... Outputs minimal image container"
<< std::endl
<< " --avif ............ Outputs AVIF" << std::endl
<< " --strip-obu ....... Trims input OBU sequence/frame headers"
<< std::endl
<< " --info ............ Prints details" << std::endl
<< " -h" << std::endl
<< " --help ............ Prints this" << std::endl;
}
//------------------------------------------------------------------------------
bool EndsWith(const std::string& str, const std::string& token) {
return (str.size() >= token.size() &&
str.compare(str.size() - token.size(), token.size(), token) == 0);
}
//------------------------------------------------------------------------------
enum class Format {
kContainer,
kAvif,
kObu,
kDeduceFromExtension
};
struct Params {
std::string input_path;
std::string output_path;
Format output_format = Format::kDeduceFromExtension;
bool strip_obu = false;
container::EncoderConfig config;
bool print_info = false;
};
// Converts the flags given to the binary to 'params' values.
// Returns true if the program should continue or false if the program should
// exit with 'error_code'.
bool ParseArguments(int argc, const char* argv[], Params& params,
int& error_code) {
// argv[0] contains the name of the binary, skip it.
for (int c = 1; c < argc; ++c) {
if ((!std::strcmp(argv[c], "-o") || !std::strcmp(argv[c], "--output")) &&
c + 1 < argc) {
params.output_path = argv[++c];
} else if (!std::strcmp(argv[c], "--container")) {
params.output_format = Format::kContainer;
} else if (!std::strcmp(argv[c], "--avif")) {
params.output_format = Format::kAvif;
} else if (!std::strcmp(argv[c], "--strip-obu")) {
params.strip_obu = true;
} else if (!std::strcmp(argv[c], "--info")) {
params.print_info = true;
} else if (!std::strcmp(argv[c], "-h") || !std::strcmp(argv[c], "--help")) {
Help();
error_code = 0; // Not an error.
return false;
} else {
if (argv[c][0] == '-') {
std::cerr << "Error: Unknown flag " << argv[c] << std::endl;
error_code = 1;
return false;
}
if (!params.input_path.empty()) {
std::cerr << "Error: Input is specified more than once ("
<< params.input_path << ", " << argv[c] << ")" << std::endl;
error_code = 1;
return false;
}
params.input_path = argv[c];
}
}
return true;
}
// Validates the 'params'. Reads the input file into the 'input' bytes.
// Returns true if the program should continue or false if the program should
// exit with 'error_code'.
bool ProcessArguments(Params& params, std::vector<char>& input,
int& error_code) {
if (params.input_path.empty()) {
std::cerr << "Error: Missing input filename" << std::endl;
return false;
}
std::ifstream input_file(params.input_path, std::ios_base::binary);
input = std::vector<char>(std::istreambuf_iterator<char>(input_file),
std::istreambuf_iterator<char>());
if (input.empty()) {
std::cerr << "Error: Unable to read any byte from " << params.input_path
<< std::endl;
error_code = 1;
return false;
}
if (!params.output_path.empty() &&
params.output_format == Format::kDeduceFromExtension) {
if (EndsWith(params.output_path, ".pic")) { // Temporary file extension.
params.output_format = Format::kContainer;
} else if (EndsWith(params.output_path, ".avif")) {
params.output_format = Format::kAvif;
} else if (EndsWith(params.output_path, ".obu")) {
params.output_format = Format::kObu;
} else {
std::cerr << "Error: Unable to deduce a supported output format from "
"the extension of "
<< params.output_path << std::endl;
error_code = 1;
return false;
}
}
return true;
}
} // namespace
//------------------------------------------------------------------------------
int main(int argc, const char* argv[]) {
Params params;
std::vector<char> input; // Input file content.
int error_code;
if (!ParseArguments(argc, argv, params, error_code)) return error_code;
if (!ProcessArguments(params, input, error_code)) return error_code;
const uint8_t* const input_data =
reinterpret_cast<const uint8_t*>(input.data());
container::Image image; // Used to store the image features, not the pixels.
uint32_t codec = 0; // Input pixels format:
// kContainerCodecObu
// kContainerCodecAv1 (stripped OBU)
// kContainerCodecWp2
size_t offset_till_codec_bytes, num_codec_bytes; // Encoded input pixels.
container::SequenceHeaderObu sequence_header_obu; // In case of OBU input.
// Try reading the input as both formats.
const bool input_is_container =
container::UnwrapContainer(input_data, input.size(), image, codec,
offset_till_codec_bytes, num_codec_bytes);
const bool input_is_avif =
!input_is_container &&
container::UnwrapAvif(input_data, input.size(), image,
offset_till_codec_bytes, num_codec_bytes);
const bool input_is_obu = !input_is_container && !input_is_avif &&
container::UnwrapObuSequenceHeader(
input_data, input.size(), sequence_header_obu);
if (input_is_avif) {
codec = container::kContainerCodecObu;
} else if (input_is_obu) {
codec = container::kContainerCodecObu;
offset_till_codec_bytes = 0;
num_codec_bytes = input.size();
image.width = sequence_header_obu.width;
image.height = sequence_header_obu.height;
image.format = sequence_header_obu.high_bitdepth
? sequence_header_obu.twelve_bit
? container::Format::kAYUV12
: container::Format::kAYUV10
: container::Format::kARGB8;
image.has_alpha = false; // TODO(yguyon): Handle transparency
}
if (!input_is_container && !input_is_avif && !input_is_obu) {
std::cerr << "Error: Unsupported input or output" << std::endl;
return 1;
}
// Print info.
if (input_is_container) {
std::cout << "Minimal image container input (" << input.size() << " bytes)"
<< std::endl;
if (params.print_info) {
std::cout << container::ContainerHeaderToStr(input_data, input.size());
}
} else if (input_is_avif) {
std::cout << "AVIF input (" << input.size() << " bytes)" << std::endl;
if (params.print_info) {
std::cout << container::BmffBoxesToStr(input_data, input.size());
}
}
if (codec == container::kContainerCodecObu) {
std::cout << "OBU input (" << num_codec_bytes << " bytes)" << std::endl;
if (params.print_info) {
std::cout << container::ObuUnitsToStr(
input_data + offset_till_codec_bytes, num_codec_bytes);
}
}
container::ObuFeatures features;
// Strip the OBU.
if (codec == container::kContainerCodecAv1 ||
(params.strip_obu && codec == container::kContainerCodecObu)) {
size_t offset_till_tiles_bytes, num_tiles_bytes;
std::string log;
const bool success =
(codec == container::kContainerCodecAv1)
? container::UnwrapStrippedObu(
input_data + offset_till_codec_bytes, num_codec_bytes, image,
features, offset_till_tiles_bytes, num_tiles_bytes, log)
: container::UnwrapObu(
input_data + offset_till_codec_bytes, num_codec_bytes, image,
features, offset_till_tiles_bytes, num_tiles_bytes, log);
if (params.print_info) std::cout << log;
if (!success) {
std::cerr << "Error: Unable to unwrap OBU input" << std::endl;
return 1;
}
// Replace the codec bytes by the tile group bytes and the 'features'.
codec = container::kContainerCodecAv1;
offset_till_codec_bytes += offset_till_tiles_bytes;
num_codec_bytes = num_tiles_bytes;
}
container::Data reconstructed_obu;
std::string reconstructed_obu_log;
const uint8_t* final_codec_bytes = input_data + offset_till_codec_bytes;
size_t num_final_codec_bytes = num_codec_bytes;
if (params.output_format != Format::kDeduceFromExtension &&
codec == container::kContainerCodecAv1) {
// Restore the OBU. Only kContainer output supports stripped OBU.
const bool strip_obu =
(params.strip_obu && params.output_format == Format::kContainer);
const bool success =
strip_obu
? container::WrapStrippedObu(
image, features, input_data + offset_till_codec_bytes,
num_codec_bytes, reconstructed_obu, reconstructed_obu_log)
: container::WrapObu(
image, features, input_data + offset_till_codec_bytes,
num_codec_bytes, reconstructed_obu, reconstructed_obu_log);
if (!success) {
if (params.print_info) std::cout << reconstructed_obu_log;
std::cerr << "Error: Unable to reconstruct OBU" << std::endl;
return 1;
}
codec = strip_obu ? container::kContainerCodecAv1
: container::kContainerCodecObu;
final_codec_bytes = reconstructed_obu.data();
num_final_codec_bytes = reconstructed_obu.size();
}
if (codec == container::kContainerCodecObu) {
std::string log =
"OBU output (" + std::to_string(num_final_codec_bytes) + " bytes)\n";
if (params.print_info) {
log += container::ObuUnitsToStr(final_codec_bytes, num_final_codec_bytes);
}
reconstructed_obu_log = log + reconstructed_obu_log;
}
// Wrap the encoded pixels to the destination format.
container::Data output_data;
if (params.output_format == Format::kContainer) {
container::WrapContainer(image, params.config, codec, final_codec_bytes,
num_final_codec_bytes, output_data);
std::cout << std::endl
<< "Minimal image container output (" << output_data.size()
<< " bytes)" << std::endl;
if (params.print_info) {
std::cout << container::ContainerHeaderToStr(output_data.data(),
output_data.size())
<< reconstructed_obu_log;
}
} else if (params.output_format == Format::kAvif) {
if (!container::WrapAvif(image, final_codec_bytes, num_final_codec_bytes,
output_data)) {
std::cerr << "Error: Unable to create AVIF file from input" << std::endl;
return 1;
}
std::cout << std::endl
<< "AVIF output (" << output_data.size() << " bytes)"
<< std::endl;
if (params.print_info) {
std::cout << container::BmffBoxesToStr(output_data.data(),
output_data.size())
<< reconstructed_obu_log;
}
} else if (params.output_format == Format::kObu) {
if (codec != container::kContainerCodecObu) {
std::cerr << "Error: Unable to get OBU from non-AV1 input" << std::endl;
return 1;
}
output_data = container::Data(final_codec_bytes,
final_codec_bytes + num_final_codec_bytes);
std::cout << std::endl
<< "OBU output (" << output_data.size() << " bytes)"
<< std::endl;
if (params.print_info) {
std::cout << reconstructed_obu_log;
}
}
// Write the file.
if (!params.output_path.empty()) {
std::ofstream output_file(params.output_path, std::ios_base::binary);
output_file.write(reinterpret_cast<const char*>(output_data.data()),
output_data.size());
}
return 0;
}