| /* |
| * 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 |