/* * 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 #include #include #include #include #include #include #include #include #include #include #include #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(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(int_value_); return true; } template 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(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::ifstream &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::ifstream::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 bool parse_integer_list(std::ifstream &file, int min_indent, int *line_idx, std::vector *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(std::numeric_limits::min()), static_cast(std::numeric_limits::max()), *line_idx, &v)); result->push_back(v); } } if (syntax_error) return false; return true; } template std::pair value_present(const T &v) { return std::make_pair(v, true); } bool parse_color_properties(std::ifstream &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::ifstream &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_simple_flag") { RETURN_IF_FALSE(value.IntegerValueInRange( /*min=*/0, /*max=*/1, *line_idx, &alpha_info->alpha_simple_flag)); } 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::min(), std::numeric_limits::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::min(), std::numeric_limits::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 { 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->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::ifstream &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 { 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 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::ifstream &file, int min_indent, int *line_idx, std::vector &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(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::ifstream &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 &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 &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(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(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(e.mantissa) / (static_cast(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(exp + 31), mantissa_len, mantissa }; return true; } bool parse_multilayer_file(const char *metadata_path, MultilayerMetadata *multilayer) { std::ifstream 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_simple_flag: %d\n", layer.global_alpha_info.alpha_simple_flag); if (!layer.global_alpha_info.alpha_simple_flag) { 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()); } } 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("\n"); } } printf("\n"); } } // namespace libaom_examples