blob: 71cf1966bda92784f9afddce30f93516c4ea4f4b [file] [log] [blame]
/*
* Copyright (c) 2024, Alliance for Open Media. All rights reserved.
*
* This source code is subject to the terms of the BSD 2 Clause License and
* the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License
* was not distributed with this source code in the LICENSE file, you can
* obtain it at www.aomedia.org/license/software. If the Alliance for Open
* Media Patent License 1.0 was not distributed with this source code in the
* PATENTS file, you can obtain it at www.aomedia.org/license/patent.
*/
#include "examples/multilayer_metadata.h"
#include <assert.h>
#include <inttypes.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <cmath>
#include <fstream>
#include <iostream>
#include <limits>
#include <string>
#include <vector>
#include "aom/aom_integer.h"
#include "examples/multilayer_metadata.h"
namespace libaom_examples {
namespace {
#define RETURN_IF_FALSE(A) \
do { \
if (!(A)) { \
return false; \
} \
} while (0)
constexpr int kMaxNumSpatialLayers = 4;
// Removes comments and trailing spaces from the line.
void cleanup_line(std::string &line) {
// Remove everything after the first '#'.
std::size_t comment_pos = line.find('#');
if (comment_pos != std::string::npos) {
line.resize(comment_pos);
}
// Remove spaces at the end of the line.
while (!line.empty() && line.back() == ' ') {
line.resize(line.length() - 1);
}
}
// Finds the indentation level of the line, and sets 'has_list_prefix' to true
// if the line has a '-' indicating a new item in a list.
void get_indent(const std::string &line, int *indent, bool *has_list_prefix) {
*indent = 0;
*has_list_prefix = false;
while (
*indent < static_cast<int>(line.length()) &&
(line[*indent] == ' ' || line[*indent] == '\t' || line[*indent] == '-')) {
if (line[*indent] == '-') {
*has_list_prefix = true;
}
++(*indent);
}
}
class ParsedValue {
public:
enum class Type { kNone, kInteger, kFloatingPoint };
void SetIntegerValue(int64_t v) {
type_ = Type::kInteger;
int_value_ = v;
}
void SetFloatingPointValue(double v) {
type_ = Type::kFloatingPoint;
double_value_ = v;
}
void Clear() { type_ = Type::kNone; }
bool ValueAsFloatingPoint(int line_idx, double *v) {
if (type_ == Type::kNone) {
fprintf(
stderr,
"No value found where floating point value was expected at line %d\n",
line_idx);
return false;
}
*v = (type_ == Type::kFloatingPoint) ? double_value_
: static_cast<double>(int_value_);
return true;
}
template <typename T>
bool IntegerValueInRange(int64_t min, int64_t max, int line_idx, T *v) {
switch (type_) {
case Type::kInteger:
if (int_value_ < min || int_value_ > max) {
fprintf(stderr,
"Integer value %" PRId64 " out of range [%" PRId64
", %" PRId64 "] at line %d\n",
int_value_, min, max, line_idx);
return false;
}
*v = static_cast<T>(int_value_);
return true;
case Type::kFloatingPoint:
fprintf(stderr,
"Floating point value found where integer was expected at line "
"%d\n",
line_idx);
return false;
case Type::kNone:
default:
fprintf(stderr,
"No value found where integer was expected at line %d\n",
line_idx);
return false;
}
}
private:
Type type_ = Type::kNone;
int64_t int_value_ = 0;
double double_value_ = 0.0f;
};
/*
* Parses the next line from the file, skipping empty lines.
* Returns false if the end of the file was reached, or if the line was indented
* less than 'min_indent', meaning that parsing should go back to the previous
* function in the stack.
*
* 'min_indent' is the minimum indentation expected for the next line.
* 'is_list' must be true if the line is allowed to contain list items ('-').
* 'indent' MUST be initialized to -1 before the first call, and is then set to
* the indentation of the line.
* 'has_list_prefix' is set to true if the line starts a new list item with '-'.
* 'line_idx' is set to the index of the last line read.
* 'field_name' is set to the field name if the line contains a colon, or to an
* empty string otherwise.
* 'value' is set to the value on the line if present.
* In case of syntax error, 'syntax_error' is set to true and the function
* returns false.
*/
bool parse_line(std::fstream &file, int min_indent, bool is_list, int *indent,
bool *has_list_prefix, int *line_idx, std::string *field_name,
ParsedValue *value, bool *syntax_error) {
*field_name = "";
*syntax_error = false;
value->Clear();
std::string line;
std::fstream::pos_type prev_file_position;
const int prev_indent = *indent;
while (prev_file_position = file.tellg(), std::getline(file, line)) {
cleanup_line(line);
get_indent(line, indent, has_list_prefix);
line = line.substr(*indent); // skip indentation
// If the line is indented less than 'min_indent', it belongs to the outer
// object, and parsing should go back to the previous function in the stack.
if (!line.empty() &&
(*indent < min_indent || (prev_indent > 0 && *indent < prev_indent))) {
// Undo reading the last line.
if (!file.seekg(prev_file_position, std::ios::beg)) {
fprintf(stderr, "Failed to seek to previous file position\n");
*syntax_error = true;
return false;
}
return false;
}
++(*line_idx);
if (line.empty()) continue;
if (prev_indent >= 0 && prev_indent != *indent) {
fprintf(stderr, "Error: Bad indentation at line %d\n", *line_idx);
*syntax_error = true;
return false;
}
if (*has_list_prefix && !is_list) {
fprintf(stderr, "Error: Unexpected list item at line %d\n", *line_idx);
*syntax_error = true;
return false;
}
std::string value_str = line;
size_t colon_pos = line.find(':');
if (colon_pos != std::string::npos) {
*field_name = line.substr(0, colon_pos);
value_str = line.substr(colon_pos + 1);
}
if (!value_str.empty()) {
char *endptr;
if (line.find('.') != std::string::npos) {
value->SetFloatingPointValue(strtod(value_str.c_str(), &endptr));
if (*endptr != '\0') {
fprintf(stderr,
"Error: Failed to parse floating point value from '%s' at "
"line %d\n",
value_str.c_str(), *line_idx);
*syntax_error = true;
return false;
}
} else {
value->SetIntegerValue(strtol(value_str.c_str(), &endptr, 10));
if (*endptr != '\0') {
fprintf(stderr,
"Error: Failed to parse integer from '%s' at line %d\n",
value_str.c_str(), *line_idx);
*syntax_error = true;
return false;
}
}
}
return true;
}
return false; // Reached the end of the file.
}
template <typename T>
bool parse_integer_list(std::fstream &file, int min_indent, int *line_idx,
std::vector<T> *result) {
bool has_list_prefix;
int indent = -1;
std::string field_name;
ParsedValue value;
bool syntax_error;
while (parse_line(file, min_indent, /*is_list=*/true, &indent,
&has_list_prefix, line_idx, &field_name, &value,
&syntax_error)) {
if (!field_name.empty()) {
fprintf(
stderr,
"Error: Unexpected field name '%s' at line %d, expected a number\n",
field_name.c_str(), *line_idx);
return false;
} else if (!has_list_prefix) {
fprintf(stderr, "Error: Missing list prefix '-' at line %d\n", *line_idx);
return false;
} else {
T v;
RETURN_IF_FALSE(value.IntegerValueInRange(
static_cast<int64_t>(std::numeric_limits<T>::min()),
static_cast<int64_t>(std::numeric_limits<T>::max()), *line_idx, &v));
result->push_back(v);
}
}
if (syntax_error) return false;
return true;
}
template <typename T>
std::pair<T, bool> value_present(const T &v) {
return std::make_pair(v, true);
}
bool parse_color_properties(std::fstream &file, int min_indent, int *line_idx,
ColorProperties *color) {
bool has_list_prefix;
int indent = -1;
std::string field_name;
ParsedValue value;
bool syntax_error;
*color = {};
while (parse_line(file, min_indent, /*is_list=*/false, &indent,
&has_list_prefix, line_idx, &field_name, &value,
&syntax_error)) {
if (field_name == "color_range") {
RETURN_IF_FALSE(value.IntegerValueInRange(/*min=*/0, /*max=*/1, *line_idx,
&color->color_range));
} else if (field_name == "color_primaries") {
if (!value.IntegerValueInRange(/*min=*/0, /*max=*/255, *line_idx,
&color->color_primaries)) {
return false;
}
} else if (field_name == "transfer_characteristics") {
RETURN_IF_FALSE(value.IntegerValueInRange(
/*min=*/0, /*max=*/255, *line_idx, &color->transfer_characteristics));
} else if (field_name == "matrix_coefficients") {
RETURN_IF_FALSE(value.IntegerValueInRange(
/*min=*/0, /*max=*/255, *line_idx, &color->matrix_coefficients));
} else {
fprintf(stderr, "Error: Unknown field '%s' at line %d\n",
field_name.c_str(), *line_idx);
return false;
}
}
if (syntax_error) return false;
return true;
}
bool parse_multilayer_layer_alpha(std::fstream &file, int min_indent,
int *line_idx, AlphaInformation *alpha_info) {
bool has_list_prefix;
int indent = -1;
std::string field_name;
ParsedValue value;
bool syntax_error;
*alpha_info = {};
while (parse_line(file, min_indent, /*is_list=*/false, &indent,
&has_list_prefix, line_idx, &field_name, &value,
&syntax_error)) {
if (field_name == "alpha_use_idc") {
RETURN_IF_FALSE(value.IntegerValueInRange(
/*min=*/0, /*max=*/7, *line_idx, &alpha_info->alpha_use_idc));
} else if (field_name == "alpha_bit_depth") {
RETURN_IF_FALSE(value.IntegerValueInRange(
/*min=*/8, /*max=*/15, *line_idx, &alpha_info->alpha_bit_depth));
} else if (field_name == "alpha_clip_idc") {
RETURN_IF_FALSE(value.IntegerValueInRange(/*min=*/0, /*max=*/3, *line_idx,
&alpha_info->alpha_clip_idc));
} else if (field_name == "alpha_incr_flag") {
RETURN_IF_FALSE(value.IntegerValueInRange(/*min=*/0, /*max=*/1, *line_idx,
&alpha_info->alpha_incr_flag));
} else if (field_name == "alpha_transparent_value") {
// At this point we may not have parsed 'alpha_bit_depth' yet, so the
// exact range is checked later.
RETURN_IF_FALSE(value.IntegerValueInRange(
std::numeric_limits<uint16_t>::min(),
std::numeric_limits<uint16_t>::max(), *line_idx,
&alpha_info->alpha_transparent_value));
} else if (field_name == "alpha_opaque_value") {
// At this point we may not have parsed 'alpha_bit_depth' yet, so the
// exact range is checked later.
RETURN_IF_FALSE(value.IntegerValueInRange(
std::numeric_limits<uint16_t>::min(),
std::numeric_limits<uint16_t>::max(), *line_idx,
&alpha_info->alpha_opaque_value));
} else if (field_name == "alpha_color_description") {
ColorProperties color;
RETURN_IF_FALSE(parse_color_properties(file, indent, line_idx, &color));
alpha_info->alpha_color_description = value_present(color);
} else if (field_name == "label_type_id") {
RETURN_IF_FALSE(
parse_integer_list<uint16_t>(file, /*min_indent=*/indent + 1,
line_idx, &alpha_info->label_type_id));
} else {
fprintf(stderr, "Error: Unknown field '%s' at line %d\n",
field_name.c_str(), *line_idx);
return false;
}
}
if (syntax_error) return false;
// Validation.
if (alpha_info->alpha_bit_depth == 0) {
fprintf(stderr,
"Error: alpha_bit_depth must be specified (in range [8, 15]) for "
"alpha info\n");
return false;
}
const int alpha_max = (1 << (alpha_info->alpha_bit_depth + 1)) - 1;
if (alpha_info->alpha_transparent_value > alpha_max) {
fprintf(stderr, "Error: alpha_transparent_value %d out of range [0, %d]\n",
alpha_info->alpha_transparent_value, alpha_max);
return false;
}
if (alpha_info->alpha_opaque_value > alpha_max) {
fprintf(stderr, "Error: alpha_opaque_value %d out of range [0, %d]\n",
alpha_info->alpha_opaque_value, alpha_max);
return false;
}
if ((!alpha_info->label_type_id.empty()) &&
(alpha_info->alpha_use_idc != ALPHA_SEGMENTATION)) {
fprintf(stderr,
"Error: label_type_id can only be set if alpha_use_idc is %d\n",
ALPHA_SEGMENTATION);
return false;
}
const int alpha_range = (std::abs(alpha_info->alpha_opaque_value -
alpha_info->alpha_transparent_value) +
1);
if (!alpha_info->label_type_id.empty() &&
static_cast<int>(alpha_info->label_type_id.size()) != alpha_range) {
fprintf(stderr,
"Error: if present, label_type_id size must be "
"equal to the range of alpha values between "
"alpha_transparent_value and alpha_opaque_value (expected "
"%d values, found %d values)\n",
alpha_range, static_cast<int>(alpha_info->label_type_id.size()));
return false;
}
if (alpha_info->alpha_color_description.second &&
(alpha_info->alpha_use_idc != ALPHA_STRAIGHT)) {
fprintf(stderr,
"Error: alpha_color_description can only be set if alpha_use_idc "
"is %d\n",
ALPHA_STRAIGHT);
return false;
}
return true;
}
bool parse_multilayer_layer_depth(std::fstream &file, int min_indent,
int *line_idx, DepthInformation *depth_info) {
bool has_list_prefix;
int indent = -1;
std::string field_name;
ParsedValue value;
bool syntax_error;
*depth_info = {};
while (parse_line(file, min_indent, /*is_list=*/false, &indent,
&has_list_prefix, line_idx, &field_name, &value,
&syntax_error)) {
if (field_name == "z_near") {
double tmp;
RETURN_IF_FALSE(value.ValueAsFloatingPoint(*line_idx, &tmp));
DepthRepresentationElement el;
RETURN_IF_FALSE(double_to_depth_representation_element(tmp, &el));
depth_info->z_near = value_present(el);
} else if (field_name == "z_far") {
double tmp;
RETURN_IF_FALSE(value.ValueAsFloatingPoint(*line_idx, &tmp));
DepthRepresentationElement el;
RETURN_IF_FALSE(double_to_depth_representation_element(tmp, &el));
depth_info->z_far = value_present(el);
} else if (field_name == "d_min") {
double tmp;
RETURN_IF_FALSE(value.ValueAsFloatingPoint(*line_idx, &tmp));
DepthRepresentationElement el;
RETURN_IF_FALSE(double_to_depth_representation_element(tmp, &el));
depth_info->d_min = value_present(el);
} else if (field_name == "d_max") {
double tmp;
RETURN_IF_FALSE(value.ValueAsFloatingPoint(*line_idx, &tmp));
DepthRepresentationElement el;
RETURN_IF_FALSE(double_to_depth_representation_element(tmp, &el));
depth_info->d_max = value_present(el);
} else if (field_name == "depth_representation_type") {
RETURN_IF_FALSE(
value.IntegerValueInRange(/*min=*/0, /*max=*/15, *line_idx,
&depth_info->depth_representation_type));
} else if (field_name == "disparity_ref_view_id") {
RETURN_IF_FALSE(value.IntegerValueInRange(
/*min=*/0, /*max=*/3, *line_idx, &depth_info->disparity_ref_view_id));
} else if (field_name == "depth_nonlinear_precision") {
RETURN_IF_FALSE(
value.IntegerValueInRange(/*min=*/8, /*max=*/23, *line_idx,
&depth_info->depth_nonlinear_precision));
} else if (field_name == "depth_nonlinear_representation_model") {
RETURN_IF_FALSE(parse_integer_list<uint32_t>(
file,
/*min_indent=*/indent + 1, line_idx,
&depth_info->depth_nonlinear_representation_model));
} else {
fprintf(stderr, "Error: Unknown field '%s' at line %d\n",
field_name.c_str(), *line_idx);
return false;
}
}
if (syntax_error) return false;
// Validation.
if (depth_info->depth_representation_type == 3 &&
depth_info->depth_nonlinear_precision == 0) {
fprintf(stderr,
"Error: depth_nonlinear_precision must be specified (in range [8, "
"23]) when "
"depth_representation_type is 3\n");
return false;
}
if ((depth_info->depth_representation_type == 3) !=
(!depth_info->depth_nonlinear_representation_model.empty())) {
fprintf(stderr,
"Error: depth_nonlinear_representation_model must be set if and "
"only if depth_representation_type is 3\n");
return false;
}
const uint32_t depth_max = (1 << depth_info->depth_nonlinear_precision) - 1;
for (uint32_t v : depth_info->depth_nonlinear_representation_model) {
if (v > depth_max) {
fprintf(stderr,
"Error: depth_nonlinear_representation_model value %d out of "
"range [0, %d]\n",
v, depth_max);
return false;
}
}
return true;
}
bool validate_layer(const LayerMetadata &layer, bool layer_has_alpha,
bool layer_has_depth) {
if (layer_has_alpha != (layer.layer_type == MULTILAYER_LAYER_TYPE_ALPHA &&
layer.layer_metadata_scope >= SCOPE_GLOBAL)) {
fprintf(stderr,
"Error: alpha info must be set if and only if layer_type is "
"%d and layer_metadata_scpoe is >= %d\n",
MULTILAYER_LAYER_TYPE_ALPHA, SCOPE_GLOBAL);
return false;
}
if (layer_has_depth != (layer.layer_type == MULTILAYER_LAYER_TYPE_DEPTH &&
layer.layer_metadata_scope >= SCOPE_GLOBAL)) {
fprintf(stderr,
"Error: depth info must be set if and only if layer_type is "
"%d and layer_metadata_scpoe is >= %d\n",
MULTILAYER_LAYER_TYPE_DEPTH, SCOPE_GLOBAL);
return false;
}
return true;
}
bool parse_multilayer_layer_metadata(std::fstream &file, int min_indent,
int *line_idx,
std::vector<LayerMetadata> &layers) {
bool has_list_prefix;
int indent = -1;
std::string field_name;
ParsedValue value;
bool syntax_error;
bool layer_has_alpha = false;
bool layer_has_depth = false;
while (parse_line(file, min_indent, /*is_list=*/true, &indent,
&has_list_prefix, line_idx, &field_name, &value,
&syntax_error)) {
if (has_list_prefix) {
// Start of a new layer.
if (layers.size() >= kMaxNumSpatialLayers) {
fprintf(stderr,
"Error: Too many layers at line %d, the maximum is %d\n",
*line_idx, kMaxNumSpatialLayers);
return false;
}
// Validate the previous layer.
if (!layers.empty()) {
validate_layer(layers.back(), layer_has_alpha, layer_has_depth);
}
if (layers.size() == 1 && layers.back().layer_color_description.second) {
fprintf(stderr,
"Error: layer_color_description cannot be specified for the "
"first layer\n");
return false;
}
layers.push_back({});
layer_has_alpha = false;
layer_has_depth = false;
}
if (layers.empty()) {
fprintf(stderr, "Error: Missing list prefix '-' at line %d\n", *line_idx);
return false;
}
LayerMetadata *layer = &layers.back();
// Check if string starts with field name.
if ((field_name == "layer_type")) {
RETURN_IF_FALSE(value.IntegerValueInRange(
/*min=*/0, /*max=*/31, *line_idx, &layer->layer_type));
} else if ((field_name == "luma_plane_only_flag")) {
RETURN_IF_FALSE(value.IntegerValueInRange(/*min=*/0, /*max=*/1, *line_idx,
&layer->luma_plane_only_flag));
} else if ((field_name == "layer_view_type")) {
RETURN_IF_FALSE(value.IntegerValueInRange(
/*min=*/0, /*max=*/7, *line_idx, &layer->layer_view_type));
} else if ((field_name == "group_id")) {
RETURN_IF_FALSE(value.IntegerValueInRange(/*min=*/0, /*max=*/3, *line_idx,
&layer->group_id));
} else if ((field_name == "layer_dependency_idc")) {
RETURN_IF_FALSE(value.IntegerValueInRange(/*min=*/0, /*max=*/7, *line_idx,
&layer->layer_dependency_idc));
} else if ((field_name == "layer_metadata_scope")) {
RETURN_IF_FALSE(value.IntegerValueInRange(
/*min=*/0, /*max=*/3, *line_idx, &layer->layer_metadata_scope));
} else if ((field_name == "layer_color_description")) {
ColorProperties color_properties;
RETURN_IF_FALSE(
parse_color_properties(file, indent, line_idx, &color_properties));
layer->layer_color_description = value_present(color_properties);
} else if ((field_name == "alpha")) {
layer_has_alpha = true;
RETURN_IF_FALSE(parse_multilayer_layer_alpha(
file,
/*min_indent=*/indent + 1, line_idx, &layer->global_alpha_info));
} else if (field_name == "depth") {
layer_has_depth = true;
RETURN_IF_FALSE(parse_multilayer_layer_depth(
file,
/*min_indent=*/indent + 1, line_idx, &layer->global_depth_info));
if ((layer->global_depth_info.d_min.second ||
layer->global_depth_info.d_max.second) &&
layer->global_depth_info.disparity_ref_view_id ==
(layers.size() - 1)) {
fprintf(stderr,
"disparity_ref_view_id must be different from the layer's id "
"for layer %d (zero-based index)\n",
static_cast<int>(layers.size()) - 1);
return false;
}
} else {
fprintf(stderr, "Error: Unknown field %s at line %d\n",
field_name.c_str(), *line_idx);
return false;
}
}
if (syntax_error) return false;
validate_layer(layers.back(), layer_has_alpha, layer_has_depth);
return true;
}
bool parse_multilayer_metadata(std::fstream &file,
MultilayerMetadata *multilayer) {
int line_idx = 0;
bool has_list_prefix;
int indent = -1;
std::string field_name;
ParsedValue value;
bool syntax_error;
*multilayer = {};
while (parse_line(file, /*min_indent=*/0, /*is_list=*/false, &indent,
&has_list_prefix, &line_idx, &field_name, &value,
&syntax_error)) {
// Check if string starts with field name.
if ((field_name == "use_case")) {
RETURN_IF_FALSE(value.IntegerValueInRange(
/*min=*/0, /*max=*/63, line_idx, &multilayer->use_case));
} else if ((field_name == "layers")) {
RETURN_IF_FALSE(parse_multilayer_layer_metadata(
file,
/*min_indent=*/indent + 1, &line_idx, multilayer->layers));
} else {
fprintf(stderr, "Error: Unknown field %s at line %d\n",
field_name.c_str(), line_idx);
return false;
}
}
if (syntax_error) return false;
return true;
}
std::string format_depth_representation_element(
const std::pair<DepthRepresentationElement, bool> &element) {
if (!element.second) {
return "absent";
} else {
return std::to_string(
depth_representation_element_to_double(element.first)) +
" (sign " + std::to_string(element.first.sign_flag) + " exponent " +
std::to_string(element.first.exponent) + " mantissa " +
std::to_string(element.first.mantissa) + " mantissa_len " +
std::to_string(element.first.mantissa_len) + ")";
}
}
std::string format_color_properties(
const std::pair<ColorProperties, bool> &color_properties) {
if (!color_properties.second) {
return "absent";
} else {
return std::to_string(color_properties.first.color_primaries) + "/" +
std::to_string(color_properties.first.transfer_characteristics) +
"/" + std::to_string(color_properties.first.matrix_coefficients) +
(color_properties.first.color_range ? "F" : "L");
}
}
bool validate_multilayer_metadata(const MultilayerMetadata &multilayer) {
if (multilayer.layers.empty()) {
fprintf(stderr, "Error: No layers found, there must be at least one\n");
return false;
}
if (multilayer.layers.size() > 4) {
fprintf(stderr, "Error: Too many layers, found %d, max 4\n",
static_cast<int>(multilayer.layers.size()));
return false;
}
bool same_view_type = true;
MultilayerViewType view_type = multilayer.layers[0].layer_view_type;
for (const LayerMetadata &layer : multilayer.layers) {
if (layer.layer_view_type != view_type) {
same_view_type = false;
break;
}
}
for (int i = 0; i < static_cast<int>(multilayer.layers.size()); ++i) {
const LayerMetadata &layer = multilayer.layers[i];
switch (multilayer.use_case) {
case MULTILAYER_USE_CASE_GLOBAL_ALPHA:
case MULTILAYER_USE_CASE_GLOBAL_DEPTH:
case MULTILAYER_USE_CASE_STEREO:
case MULTILAYER_USE_CASE_STEREO_GLOBAL_ALPHA:
case MULTILAYER_USE_CASE_STEREO_GLOBAL_DEPTH:
case MULTILAYER_USE_CASE_444_GLOBAL_ALPHA:
case MULTILAYER_USE_CASE_444_GLOBAL_DEPTH:
if (layer.layer_metadata_scope != SCOPE_GLOBAL) {
fprintf(
stderr,
"Error: for use_case %d, all layers must have scope %d, found %d "
"instead for layer %d (zero-based index)\n",
multilayer.use_case, SCOPE_GLOBAL, layer.layer_metadata_scope, i);
return false;
}
break;
default: break;
}
switch (multilayer.use_case) {
case MULTILAYER_USE_CASE_GLOBAL_ALPHA:
case MULTILAYER_USE_CASE_GLOBAL_DEPTH:
case MULTILAYER_USE_CASE_ALPHA:
case MULTILAYER_USE_CASE_DEPTH:
case MULTILAYER_USE_CASE_444_GLOBAL_ALPHA:
case MULTILAYER_USE_CASE_444_GLOBAL_DEPTH:
case MULTILAYER_USE_CASE_444:
case MULTILAYER_USE_CASE_420_444:
if (!same_view_type) {
fprintf(stderr,
"Error: for use_case %d, all layers must have the same view "
"type, found different view_type for layer %d (zero-based "
"index)\n",
multilayer.use_case, i);
return false;
}
default: break;
}
if (layer.layer_type != MULTILAYER_LAYER_TYPE_UNSPECIFIED)
switch (multilayer.use_case) {
case MULTILAYER_USE_CASE_GLOBAL_ALPHA:
case MULTILAYER_USE_CASE_ALPHA:
case MULTILAYER_USE_CASE_STEREO_GLOBAL_ALPHA:
case MULTILAYER_USE_CASE_STEREO_ALPHA:
if (layer.layer_type != MULTILAYER_LAYER_TYPE_TEXTURE &&
layer.layer_type != MULTILAYER_LAYER_TYPE_ALPHA) {
fprintf(stderr,
"Error: for use_case %d, all layers must be of type %d or "
"%d, found %d for layer %d (zero-based index)\n",
multilayer.use_case, MULTILAYER_LAYER_TYPE_TEXTURE,
MULTILAYER_LAYER_TYPE_ALPHA, layer.layer_type, i);
return false;
}
break;
case MULTILAYER_USE_CASE_GLOBAL_DEPTH:
case MULTILAYER_USE_CASE_DEPTH:
case MULTILAYER_USE_CASE_STEREO_GLOBAL_DEPTH:
case MULTILAYER_USE_CASE_STEREO_DEPTH:
if (layer.layer_type != MULTILAYER_LAYER_TYPE_TEXTURE &&
layer.layer_type != MULTILAYER_LAYER_TYPE_DEPTH) {
fprintf(stderr,
"Error: for use_case %d, all layers must be of type %d or "
"%d, found %d for layer %d (zero-based index)\n",
multilayer.use_case, MULTILAYER_LAYER_TYPE_TEXTURE,
MULTILAYER_LAYER_TYPE_DEPTH, layer.layer_type, i);
return false;
}
break;
case MULTILAYER_USE_CASE_STEREO:
if (layer.layer_type != MULTILAYER_LAYER_TYPE_TEXTURE) {
fprintf(stderr,
"Error: for use_case %d, all layers must be of type %d, "
"found %d for layer %d (zero-based index)\n",
multilayer.use_case, MULTILAYER_LAYER_TYPE_TEXTURE,
layer.layer_type, i);
return false;
}
break;
case MULTILAYER_USE_CASE_444_GLOBAL_ALPHA:
if (layer.layer_type != MULTILAYER_LAYER_TYPE_TEXTURE_1 &&
layer.layer_type != MULTILAYER_LAYER_TYPE_TEXTURE_2 &&
layer.layer_type != MULTILAYER_LAYER_TYPE_TEXTURE_3 &&
layer.layer_type != MULTILAYER_LAYER_TYPE_ALPHA) {
fprintf(stderr,
"Error: for use_case %d, all layers must be of type %d, "
"%d, %d, or %d, found %d for layer %d (zero-based index)\n",
multilayer.use_case, MULTILAYER_LAYER_TYPE_TEXTURE_1,
MULTILAYER_LAYER_TYPE_TEXTURE_2,
MULTILAYER_LAYER_TYPE_TEXTURE_3,
MULTILAYER_LAYER_TYPE_ALPHA, layer.layer_type, i);
return false;
}
break;
case MULTILAYER_USE_CASE_444_GLOBAL_DEPTH:
if (layer.layer_type != MULTILAYER_LAYER_TYPE_TEXTURE_1 &&
layer.layer_type != MULTILAYER_LAYER_TYPE_TEXTURE_2 &&
layer.layer_type != MULTILAYER_LAYER_TYPE_TEXTURE_3 &&
layer.layer_type != MULTILAYER_LAYER_TYPE_DEPTH) {
fprintf(stderr,
"Error: for use_case %d, all layers must be of type %d, "
"%d, %d, or %d, found %d for layer %d (zero-based index)\n",
multilayer.use_case, MULTILAYER_LAYER_TYPE_TEXTURE_1,
MULTILAYER_LAYER_TYPE_TEXTURE_2,
MULTILAYER_LAYER_TYPE_TEXTURE_3,
MULTILAYER_LAYER_TYPE_DEPTH, layer.layer_type, i);
return false;
}
break;
case MULTILAYER_USE_CASE_444:
if (layer.layer_type != MULTILAYER_LAYER_TYPE_TEXTURE_1 &&
layer.layer_type != MULTILAYER_LAYER_TYPE_TEXTURE_2 &&
layer.layer_type != MULTILAYER_LAYER_TYPE_TEXTURE_3) {
fprintf(
stderr,
"Error: for use_case %d, all layers must be of type %d, %d, or "
"%d, found %d for layer %d (zero-based index)\n",
multilayer.use_case, MULTILAYER_LAYER_TYPE_TEXTURE_1,
MULTILAYER_LAYER_TYPE_TEXTURE_2,
MULTILAYER_LAYER_TYPE_TEXTURE_3, layer.layer_type, i);
return false;
}
break;
case MULTILAYER_USE_CASE_420_444:
if (layer.layer_type != MULTILAYER_LAYER_TYPE_TEXTURE &&
layer.layer_type != MULTILAYER_LAYER_TYPE_TEXTURE_1 &&
layer.layer_type != MULTILAYER_LAYER_TYPE_TEXTURE_2 &&
layer.layer_type != MULTILAYER_LAYER_TYPE_TEXTURE_3) {
fprintf(stderr,
"Error: for use_case %d, all layers must be of type %d, "
"%d, %d, or %d, found %d for layer %d (zero-based index)\n",
multilayer.use_case, MULTILAYER_LAYER_TYPE_TEXTURE,
MULTILAYER_LAYER_TYPE_TEXTURE_1,
MULTILAYER_LAYER_TYPE_TEXTURE_2,
MULTILAYER_LAYER_TYPE_TEXTURE_3, layer.layer_type, i);
return false;
}
break;
default: break;
}
if (layer.layer_dependency_idc >= (1 << i)) {
fprintf(stderr,
"Error: layer_dependency_idc of layer %d (zero-based index) must "
"be in [0, %d], found %d for layer %d (zero-based index)\n",
i, (1 << i) - 1, layer.layer_dependency_idc, i);
return false;
}
if ((layer.layer_type == MULTILAYER_LAYER_TYPE_ALPHA ||
layer.layer_type == MULTILAYER_LAYER_TYPE_DEPTH) &&
layer.layer_color_description.second) {
fprintf(stderr,
"Error: alpha or depth layers cannot have "
"layer_color_description for layer %d (zero-based index)\n",
i);
return false;
}
}
return true;
}
} // namespace
double depth_representation_element_to_double(
const DepthRepresentationElement &e) {
// Let x be a variable that is computed using four variables s, e, m, and n,
// as follows: If e is greater than 0 and less than 127, x is set equal to
// (−1)^s*2^(e−31) * (1+m÷2^n).
// Otherwise (e is equal to 0), x is set equal to (−1)^s*2^−(30+n)*m.
if (e.exponent > 0) {
return (e.sign_flag ? -1 : 1) * std::pow(2.0, e.exponent - 31) *
(1 + static_cast<double>(e.mantissa) /
(static_cast<int64_t>(1) << e.mantissa_len));
} else {
return (e.sign_flag ? -1 : 1) * e.mantissa *
std::pow(2.0, -30 + e.mantissa_len);
}
}
bool double_to_depth_representation_element(
double v, DepthRepresentationElement *element) {
const double orig = v;
if (v == 0.0) {
*element = { 0, 0, 0, 1 };
return true;
}
const bool sign = v < 0.0;
if (sign) {
v = -v;
}
int exp = 0;
if (v >= 1.0) {
while (v >= 2.0) {
++exp;
v /= 2;
}
} else {
while (v < 1.0) {
++exp;
v *= 2.0;
}
exp = -exp;
}
if ((exp + 31) <= 0 || (exp + 31) > 126) {
fprintf(stderr,
"Error: Floating point value %f out of range (too large or too "
"small)\n",
orig);
return false;
}
assert(v >= 1.0 && v < 2.0);
v -= 1.0;
uint32_t mantissa = 0;
uint8_t mantissa_len = 0;
constexpr uint8_t kMaxMantissaLen = 32;
do {
const int bit = (v >= 0.5);
mantissa = (mantissa << 1) + bit;
v -= bit * 0.5;
++mantissa_len;
v *= 2.0;
} while (mantissa_len < kMaxMantissaLen && v > 0.0);
*element = { sign, static_cast<uint8_t>(exp + 31), mantissa_len, mantissa };
return true;
}
bool parse_multilayer_file(const char *metadata_path,
MultilayerMetadata *multilayer) {
std::fstream file(metadata_path);
if (!file.is_open()) {
fprintf(stderr, "Error: Failed to open %s\n", metadata_path);
return false;
}
if (!parse_multilayer_metadata(file, multilayer) ||
!validate_multilayer_metadata(*multilayer)) {
return false;
}
return multilayer;
}
void print_multilayer_metadata(const MultilayerMetadata &multilayer) {
printf("=== Multilayer metadata ===\n");
printf("use_case: %d\n", multilayer.use_case);
for (size_t i = 0; i < multilayer.layers.size(); ++i) {
const LayerMetadata &layer = multilayer.layers[i];
printf("layer %zu\n", i);
printf(" layer_type: %d\n", layer.layer_type);
printf(" luma_plane_only_flag: %d\n", layer.luma_plane_only_flag);
printf(" layer_view_type: %d\n", layer.layer_view_type);
printf(" group_id: %d\n", layer.group_id);
printf(" layer_dependency_idc: %d\n", layer.layer_dependency_idc);
printf(" layer_metadata_scope: %d\n", layer.layer_metadata_scope);
printf(" layer_color_description: %s\n",
format_color_properties(layer.layer_color_description).c_str());
if (layer.layer_type == MULTILAYER_LAYER_TYPE_ALPHA) {
printf(" alpha:\n");
printf(" alpha_use_idc: %d\n", layer.global_alpha_info.alpha_use_idc);
printf(" alpha_bit_depth: %d\n",
layer.global_alpha_info.alpha_bit_depth);
printf(" alpha_clip_idc: %d\n",
layer.global_alpha_info.alpha_clip_idc);
printf(" alpha_incr_flag: %d\n",
layer.global_alpha_info.alpha_incr_flag);
printf(" alpha_transparent_value: %hu\n",
layer.global_alpha_info.alpha_transparent_value);
printf(" alpha_opaque_value: %hu\n",
layer.global_alpha_info.alpha_opaque_value);
printf(" alpha_color_description: %s\n",
format_color_properties(
layer.global_alpha_info.alpha_color_description)
.c_str());
printf(" label_type_id:");
for (uint16_t label_type_id : layer.global_alpha_info.label_type_id) {
printf(" %d", label_type_id);
}
printf("\n");
} else if (layer.layer_type == MULTILAYER_LAYER_TYPE_DEPTH) {
printf(" depth:\n");
printf(" z_near: %s\n",
format_depth_representation_element(layer.global_depth_info.z_near)
.c_str());
printf(" z_far: %s\n",
format_depth_representation_element(layer.global_depth_info.z_far)
.c_str());
printf(" d_min: %s\n",
format_depth_representation_element(layer.global_depth_info.d_min)
.c_str());
printf(" d_max: %s\n",
format_depth_representation_element(layer.global_depth_info.d_max)
.c_str());
printf(" depth_representation_type: %d\n",
layer.global_depth_info.depth_representation_type);
printf(" disparity_ref_view_id: %d\n",
layer.global_depth_info.disparity_ref_view_id);
printf(" depth_nonlinear_precision: %d\n",
layer.global_depth_info.depth_nonlinear_precision);
printf(" depth_nonlinear_representation_model:");
for (uint32_t depth_nonlinear_representation_model :
layer.global_depth_info.depth_nonlinear_representation_model) {
printf(" %d", depth_nonlinear_representation_model);
}
printf("\n");
}
}
printf("\n");
}
} // namespace libaom_examples