From 8692b19471d5c257bc8d0811d563ef4fe5b5c80c Mon Sep 17 00:00:00 2001 From: Ilya Doroshenko Date: Sat, 6 Jun 2026 17:08:18 +0200 Subject: [PATCH 1/3] Added several functions from the AV1 spec Added context Added incomplete frame header --- include/mbmff/av1.hpp | 848 ++++++++++++++++++++++++++++++++++++---- include/mbmff/mbmff.hpp | 6 +- test/entry_main.cpp | 43 +- test/formatters.hpp | 16 +- 4 files changed, 812 insertions(+), 101 deletions(-) diff --git a/include/mbmff/av1.hpp b/include/mbmff/av1.hpp index e664980..a3b1766 100644 --- a/include/mbmff/av1.hpp +++ b/include/mbmff/av1.hpp @@ -4,8 +4,8 @@ #include #include "common.hpp" -namespace mbmff { -enum class obu_type : uint8_t { +namespace mbmff::av1 { +enum class obu_type : std::uint8_t { sequence_header = 1, temporal_delimiter = 2, frame_header = 3, @@ -53,7 +53,7 @@ constexpr static auto to_string(obu_type type) noexcept -> std::string_view } } -enum class av1_color_primaries : uint8_t { +enum class color_primaries : std::uint8_t { CP_RESERVED_0 = 0, CP_BT_709 = 1, CP_UNSPECIFIED = 2, @@ -70,43 +70,43 @@ enum class av1_color_primaries : uint8_t { CP_EBU_3213 = 22, }; -constexpr auto to_string(av1_color_primaries primaries) noexcept -> std::string_view +constexpr auto to_string(color_primaries primaries) noexcept -> std::string_view { switch (primaries) { - case mbmff::av1_color_primaries::CP_RESERVED_0: + case av1::color_primaries::CP_RESERVED_0: return "Reserved (0)"; - case mbmff::av1_color_primaries::CP_BT_709: + case av1::color_primaries::CP_BT_709: return "BT.709"; - case mbmff::av1_color_primaries::CP_UNSPECIFIED: + case av1::color_primaries::CP_UNSPECIFIED: return "Unspecified"; - case mbmff::av1_color_primaries::CP_RESERVED_3: + case av1::color_primaries::CP_RESERVED_3: return "Reserved (3)"; - case mbmff::av1_color_primaries::CP_BT_470_M: + case av1::color_primaries::CP_BT_470_M: return "BT.470 System M (historical)"; - case mbmff::av1_color_primaries::CP_BT_470_B_G: + case av1::color_primaries::CP_BT_470_B_G: return "BT.470 System B/G (historical)"; - case mbmff::av1_color_primaries::CP_BT_601: + case av1::color_primaries::CP_BT_601: return "BT.601"; - case mbmff::av1_color_primaries::CP_SMPTE_240: + case av1::color_primaries::CP_SMPTE_240: return "SMPTE 240M"; - case mbmff::av1_color_primaries::CP_GENERIC_FILM: + case av1::color_primaries::CP_GENERIC_FILM: return "Generic film (color filters using illuminant C)"; - case mbmff::av1_color_primaries::CP_BT_2020: + case av1::color_primaries::CP_BT_2020: return "BT.2020, BT.2100"; - case mbmff::av1_color_primaries::CP_XYZ: + case av1::color_primaries::CP_XYZ: return "SMPTE 428 (CIE 1921 XYZ)"; - case mbmff::av1_color_primaries::CP_SMPTE_431: + case av1::color_primaries::CP_SMPTE_431: return "SMPTE RP 431-2"; - case mbmff::av1_color_primaries::CP_SMPTE_432: + case av1::color_primaries::CP_SMPTE_432: return "SMPTE EG 432-1"; - case mbmff::av1_color_primaries::CP_EBU_3213: + case av1::color_primaries::CP_EBU_3213: return "EBU Tech 3213-E"; default: return "Unknown color primaries"; } } -enum class av1_transfer_characteristics : uint8_t { +enum class transfer_characteristics : std::uint8_t { TC_RESERVED_0 = 0, TC_BT_709 = 1, TC_UNSPECIFIED = 2, @@ -128,53 +128,53 @@ enum class av1_transfer_characteristics : uint8_t { TC_HLG = 18, }; -constexpr auto to_string(av1_transfer_characteristics tc) noexcept -> std::string_view +constexpr auto to_string(transfer_characteristics tc) noexcept -> std::string_view { switch (tc) { - case mbmff::av1_transfer_characteristics::TC_RESERVED_0: + case av1::transfer_characteristics::TC_RESERVED_0: return "Reserved (0)"; - case mbmff::av1_transfer_characteristics::TC_BT_709: + case av1::transfer_characteristics::TC_BT_709: return "BT.709"; - case mbmff::av1_transfer_characteristics::TC_UNSPECIFIED: + case av1::transfer_characteristics::TC_UNSPECIFIED: return "Unspecified"; - case mbmff::av1_transfer_characteristics::TC_RESERVED_3: + case av1::transfer_characteristics::TC_RESERVED_3: return "Reserved (3)"; - case mbmff::av1_transfer_characteristics::TC_BT_470_M: + case av1::transfer_characteristics::TC_BT_470_M: return "BT.470 System M (historical)"; - case mbmff::av1_transfer_characteristics::TC_BT_470_B_G: + case av1::transfer_characteristics::TC_BT_470_B_G: return "BT.470 System B/G (historical)"; - case mbmff::av1_transfer_characteristics::TC_BT_601: + case av1::transfer_characteristics::TC_BT_601: return "BT.601"; - case mbmff::av1_transfer_characteristics::TC_SMPTE_240: + case av1::transfer_characteristics::TC_SMPTE_240: return "SMPTE 240M"; - case mbmff::av1_transfer_characteristics::TC_LINEAR: + case av1::transfer_characteristics::TC_LINEAR: return "Linear"; - case mbmff::av1_transfer_characteristics::TC_LOG_100: + case av1::transfer_characteristics::TC_LOG_100: return "Logarithmic (100:1 range)"; - case mbmff::av1_transfer_characteristics::TC_LOG_100_SQRT10: + case av1::transfer_characteristics::TC_LOG_100_SQRT10: return "Logarithmic (100 * sqrt(10):1 range)"; - case mbmff::av1_transfer_characteristics::TC_IEC_61966: + case av1::transfer_characteristics::TC_IEC_61966: return "IEC 61966-2-4"; - case mbmff::av1_transfer_characteristics::TC_BT_1361: + case av1::transfer_characteristics::TC_BT_1361: return "BT.1361"; - case mbmff::av1_transfer_characteristics::TC_SRGB: + case av1::transfer_characteristics::TC_SRGB: return "sRGB or sYCC"; - case mbmff::av1_transfer_characteristics::TC_BT_2020_10_BIT: + case av1::transfer_characteristics::TC_BT_2020_10_BIT: return "BT.2020 10-bit system"; - case mbmff::av1_transfer_characteristics::TC_BT_2020_12_BIT: + case av1::transfer_characteristics::TC_BT_2020_12_BIT: return "BT.2020 12-bit system"; - case mbmff::av1_transfer_characteristics::TC_SMPTE_2084: + case av1::transfer_characteristics::TC_SMPTE_2084: return "SMPTE ST 2084, ITU BT.2100 PQ"; - case mbmff::av1_transfer_characteristics::TC_SMPTE_428: + case av1::transfer_characteristics::TC_SMPTE_428: return "SMPTE ST 428"; - case mbmff::av1_transfer_characteristics::TC_HLG: + case av1::transfer_characteristics::TC_HLG: return "BT.2100 HLG, ARIB STD-B67"; default: return "Unknown transfer characteristics"; } } -enum class av1_matrix_coefficients : uint8_t { +enum class matrix_coefficients : std::uint8_t { MC_IDENTITY = 0, MC_BT_709 = 1, MC_UNSPECIFIED = 2, @@ -192,73 +192,180 @@ enum class av1_matrix_coefficients : uint8_t { MC_ICTCP = 14, }; -constexpr auto to_string(av1_matrix_coefficients mc) noexcept -> std::string_view +constexpr auto to_string(matrix_coefficients mc) noexcept -> std::string_view { switch (mc) { - case mbmff::av1_matrix_coefficients::MC_IDENTITY: + case av1::matrix_coefficients::MC_IDENTITY: return "Identity"; - case mbmff::av1_matrix_coefficients::MC_BT_709: + case av1::matrix_coefficients::MC_BT_709: return "BT.709"; - case mbmff::av1_matrix_coefficients::MC_UNSPECIFIED: + case av1::matrix_coefficients::MC_UNSPECIFIED: return "Unspecified"; - case mbmff::av1_matrix_coefficients::MC_RESERVED_3: + case av1::matrix_coefficients::MC_RESERVED_3: return "Reserved (3)"; - case mbmff::av1_matrix_coefficients::MC_FCC: + case av1::matrix_coefficients::MC_FCC: return "US FCC 73.628"; - case mbmff::av1_matrix_coefficients::MC_BT_470_B_G: + case av1::matrix_coefficients::MC_BT_470_B_G: return "BT.470 System B/G (historical)"; - case mbmff::av1_matrix_coefficients::MC_BT_601: + case av1::matrix_coefficients::MC_BT_601: return "BT.601"; - case mbmff::av1_matrix_coefficients::MC_SMPTE_240: + case av1::matrix_coefficients::MC_SMPTE_240: return "SMPTE 240M"; - case mbmff::av1_matrix_coefficients::MC_SMPTE_YCGCO: + case av1::matrix_coefficients::MC_SMPTE_YCGCO: return "YCgCo"; - case mbmff::av1_matrix_coefficients::MC_BT_2020_NCL: + case av1::matrix_coefficients::MC_BT_2020_NCL: return "BT.2020 non-constant luminance system"; - case mbmff::av1_matrix_coefficients::MC_BT_2020_CL: + case av1::matrix_coefficients::MC_BT_2020_CL: return "BT.2020 constant luminance system"; - case mbmff::av1_matrix_coefficients::MC_SMPTE_2085: + case av1::matrix_coefficients::MC_SMPTE_2085: return "SMPTE ST 2085 YDzDx"; - case mbmff::av1_matrix_coefficients::MC_CHROMAT_NCL: + case av1::matrix_coefficients::MC_CHROMAT_NCL: return "Chromaticity-derived non-constant luminance system"; - case mbmff::av1_matrix_coefficients::MC_CHROMAT_CL: + case av1::matrix_coefficients::MC_CHROMAT_CL: return "Chromaticity-derived constant luminance system"; - case mbmff::av1_matrix_coefficients::MC_ICTCP: + case av1::matrix_coefficients::MC_ICTCP: return "ICTCP (BT.2100)"; default: return "Unknown matrix coefficients"; } } -enum class av1_chroma_sample_position : uint8_t { +enum class chroma_sample_position : std::uint8_t { CSP_UNKNOWN = 0, CSP_VERTICAL = 1, CSP_COLOCATED = 2, CSP_RESERVED = 3, }; -constexpr auto to_string(av1_chroma_sample_position csp) noexcept -> std::string_view +constexpr auto to_string(chroma_sample_position csp) noexcept -> std::string_view { switch (csp) { - case mbmff::av1_chroma_sample_position::CSP_UNKNOWN: + case av1::chroma_sample_position::CSP_UNKNOWN: return "Unknown"; - case mbmff::av1_chroma_sample_position::CSP_VERTICAL: + case av1::chroma_sample_position::CSP_VERTICAL: return "Vertical"; - case mbmff::av1_chroma_sample_position::CSP_COLOCATED: + case av1::chroma_sample_position::CSP_COLOCATED: return "Colocated"; - case mbmff::av1_chroma_sample_position::CSP_RESERVED: + case av1::chroma_sample_position::CSP_RESERVED: return "Reserved"; default: return "Invalid chroma sample position"; } } +enum class metadata_type : uint32_t { + METADATA_TYPE_HDR_CLL = 1, + METADATA_TYPE_HDR_MDCV = 2, + METADATA_TYPE_SCALABILITY = 3, + METADATA_TYPE_ITUT_T35 = 4, + METADATA_TYPE_TIMECODE = 5, +}; + +constexpr auto to_string(metadata_type type) noexcept -> std::string_view +{ + switch (type) { + case av1::metadata_type::METADATA_TYPE_ITUT_T35: + return "ITU-T T.35"; + case av1::metadata_type::METADATA_TYPE_HDR_CLL: + return "HDR Content Light Level (CLL)"; + case av1::metadata_type::METADATA_TYPE_HDR_MDCV: + return "HDR Mastering Display Color Volume (MDCV)"; + case av1::metadata_type::METADATA_TYPE_SCALABILITY: + return "Scalability"; + case av1::metadata_type::METADATA_TYPE_TIMECODE: + return "Timecode"; + default: + return "Unknown metadata type"; + } +} + +enum class frame_type : std::uint8_t { + KEY_FRAME, + INTER_FRAME, + INTRA_ONLY_FRAME, + SWITCH_FRAME, +}; + +constexpr auto to_string(frame_type type) noexcept -> std::string_view +{ + switch (type) { + case frame_type::KEY_FRAME: + return "Key Frame"; + case frame_type::INTER_FRAME: + return "Inter Frame"; + case frame_type::INTRA_ONLY_FRAME: + return "Intra-only Frame"; + case frame_type::SWITCH_FRAME: + return "Switch Frame"; + default: + return "Unknown frame type"; + } +} + +enum class ref_frame : std::int8_t { + NONE = -1, + INTRA_FRAME = 0, + LAST_FRAME = 1, + LAST2_FRAME = 2, + LAST3_FRAME = 3, + GOLDEN_FRAME = 4, + BWDREF_FRAME = 5, + ALTREF2_FRAME = 6, + ALTREF_FRAME = 7, +}; + +constexpr auto to_string(ref_frame ref) noexcept -> std::string_view +{ + switch (ref) { + case ref_frame::NONE: + return "None"; + case ref_frame::INTRA_FRAME: + return "Intra Frame"; + case ref_frame::LAST_FRAME: + return "Last Frame"; + case ref_frame::LAST2_FRAME: + return "Last2 Frame"; + case ref_frame::LAST3_FRAME: + return "Last3 Frame"; + case ref_frame::GOLDEN_FRAME: + return "Golden Frame"; + case ref_frame::BWDREF_FRAME: + return "BWDREF Frame"; + case ref_frame::ALTREF2_FRAME: + return "ALTREF2 Frame"; + case ref_frame::ALTREF_FRAME: + return "ALTREF Frame"; + default: + return "Unknown reference frame"; + } +} + +constexpr std::int8_t operator+(ref_frame ref) noexcept +{ + return std::to_underlying(ref); +} + // Value that indicates the allow_screen_content_tools syntax element is coded static constexpr std::uint8_t SELECT_SCREEN_CONTENT_TOOLS = 2; // Value that indicates the force_integer_mv syntax element is coded static constexpr std::uint8_t SELECT_INTEGER_MV = 2; +// The maximum number of reference frames that can be used for inter prediction in AV1. +static constexpr std::uint32_t NUM_REF_FRAMES = 8; + +// Value of primary_ref_frame indicating that there is no primary reference frame +static constexpr std::uint8_t PRIMARY_REF_NONE = 7; + +// Smallest denominator for upscaling ratio +static constexpr std::uint8_t SUPERRES_DENOM_MIN = 9; + +// Smallest denominator for upscaling ratio +static constexpr std::uint8_t SUPERRES_NUM = 8; + +// Smallest denominator for upscaling ratio +static constexpr std::uint8_t REFS_PER_FRAME = 8; + //------------------------------------------------------------------------------------------------------------ struct timing_info { std::uint32_t num_units_in_display_tick = 0; @@ -292,9 +399,9 @@ struct operating_point { }; struct color_description { - mbmff::av1_color_primaries color_primaries = mbmff::av1_color_primaries::CP_UNSPECIFIED; - mbmff::av1_transfer_characteristics transfer_characteristics = mbmff::av1_transfer_characteristics::TC_UNSPECIFIED; - mbmff::av1_matrix_coefficients matrix_coefficients = mbmff::av1_matrix_coefficients::MC_UNSPECIFIED; + av1::color_primaries color_primaries = av1::color_primaries::CP_UNSPECIFIED; + av1::transfer_characteristics transfer_characteristics = av1::transfer_characteristics::TC_UNSPECIFIED; + av1::matrix_coefficients matrix_coefficients = av1::matrix_coefficients::MC_UNSPECIFIED; }; struct color_config { @@ -308,8 +415,8 @@ struct color_config { std::uint8_t subsampling_y : 1 = 0; std::uint8_t separate_uv_delta_q : 1 = 0; - mbmff::av1_chroma_sample_position chroma_sample_position = mbmff::av1_chroma_sample_position::CSP_UNKNOWN; - mbmff::color_description color_description_data{}; + av1::chroma_sample_position chroma_sample_position = av1::chroma_sample_position::CSP_UNKNOWN; + av1::color_description color_description_data{}; }; struct obu_sequence_header { @@ -341,11 +448,10 @@ struct obu_sequence_header { std::uint8_t seq_choose_integer_mv : 1 = 0; std::uint8_t seq_force_integer_mv : 2 = SELECT_INTEGER_MV; - std::uint8_t order_hint_bits_minus_1_present : 1 = 0; - std::uint8_t enable_superres : 1 = 0; - std::uint8_t enable_cdef : 1 = 0; - std::uint8_t enable_restoration : 1 = 0; - std::uint8_t film_grain_params_present : 1 = 0; + std::uint8_t enable_superres : 1 = 0; + std::uint8_t enable_cdef : 1 = 0; + std::uint8_t enable_restoration : 1 = 0; + std::uint8_t film_grain_params_present : 1 = 0; std::uint8_t operating_points_cnt_minus_1 : 5 = 0; std::array operating_points{}; // max 32 operating points (as per spec) @@ -358,6 +464,105 @@ struct obu_sequence_header { uint8_t frame_height_bits_minus_1 : 4 = 0; uint16_t max_frame_width_minus_1 = 0; uint16_t max_frame_height_minus_1 = 0; + +public: + constexpr std::uint8_t order_hint_bits() const noexcept + { + if (reduced_still_picture_header) { + return 0; + } + + if (enable_order_hint) { + return order_hint_bits_minus_1 + 1; + } + return 0; + } +}; + +struct obu_metadata { + av1::metadata_type type = {}; + std::span payload{}; +}; + +struct frame_indices { + std::uint32_t ref_frame_indices; + +public: + constexpr auto ref_frame_index(std::size_t i) const noexcept -> std::uint8_t + { + return (ref_frame_indices >> (i * 3)) & 0x07; + } + constexpr auto set_ref_frame_index(std::size_t i, std::uint8_t index) noexcept -> void + { + ref_frame_indices &= ~(0x07 << (i * 3)); // Clear the existing index at position i + ref_frame_indices |= (index & 0x07) << (i * 3); // Set the new index at position i + } +}; + +struct ref_frame_state { + std::uint8_t valid; // RefValid + std::uint8_t order_hint; // RefOrderHint + std::uint8_t bit_depth; // RefBitDepth + av1::frame_type frame_type; // RefFrameType + + std::uint32_t upscaled_width; // RefUpscaledWidth + std::uint64_t frame_id; // RefFrameId + + std::uint32_t frame_width; // RefFrameWidth + std::uint32_t frame_height; // RefFrameHeight + std::uint32_t render_width; // RefRenderWidth + std::uint32_t render_height; // RefRenderHeight + + std::uint32_t subsampling_x; // RefSubsamplingX + std::uint32_t subsampling_y; // RefSubsamplingY +}; + +struct obu_frame_header { + std::uint8_t show_existing_frame : 1 = 0; + std::uint8_t frame_to_show_map_idx : 3 = 0; + std::uint8_t show_frame : 1 = 1; + std::uint8_t showable_frame : 1 = 0; + std::uint8_t error_resilient_mode : 1 = 1; + std::uint8_t disable_cdf_update : 1 = 0; + + std::uint8_t allow_screen_content_tools : 1 = 0; + std::uint8_t force_integer_mv : 1 = 0; + std::uint8_t frame_size_override_flag : 1 = 0; + std::uint8_t buffer_removal_time_present_flag : 1 = 0; + std::uint8_t allow_high_precision_mv : 1 = 0; + std::uint8_t primary_ref_frame : 3 = 0; + + std::uint8_t use_ref_frame_mvs : 1 = 0; + std::uint8_t allow_intrabc : 1 = 0; + std::uint8_t frame_refs_short_signaling : 1 = 0; + std::uint8_t last_frame_idx : 3 = 0; + std::uint8_t gold_frame_idx : 3 = 0; + + std::uint8_t refresh_frame_flags = 0; + av1::frame_type frame_type = av1::frame_type::KEY_FRAME; + std::uint8_t order_hint = 0; + + // frame size + std::uint16_t frame_width_minus_1 = 0; + std::uint16_t frame_height_minus_1 = 0; + + // superres params + std::uint8_t use_superres : 1 = 0; + std::uint8_t coded_denom : 3 = 0; + + // render size + std::uint8_t render_and_frame_size_different : 1 = 0; + std::uint16_t render_width_minus_1 = 0; + std::uint16_t render_height_minus_1 = 0; + + av1::frame_indices ref_frame_indices_data{}; + + std::uint64_t display_frame_id = 0; + std::uint64_t current_frame_id = 0; + std::uint64_t frame_presentation_time = 0; + std::array buffer_removal_time{}; + std::array ref_order_hint{}; + std::array expectedFrameId{}; }; //------------------------------------------------------------------------------------------------------------ @@ -385,6 +590,34 @@ struct obu_view_base { } }; +//------------------------------------------------------------------------------------------------------------ +struct obu_context { + // Selected by the decoder + std::uint32_t operating_point = 0; + + // Sequence header OBU, needed for parsing frame headers. + av1::obu_view_base sequence_obu_header{}; + + // Most recently parsed sequence header, needed for parsing frame headers + av1::obu_sequence_header sequence_header{}; + + std::uint8_t seen_frame_header : 1 = false; + + std::uint64_t prev_frame_id = 0; + std::uint64_t current_frame_id = 0; + std::uint32_t frame_width = 0; + std::uint32_t frame_height = 0; + std::uint32_t render_width = 0; + std::uint32_t render_height = 0; + std::uint32_t upscaled_width = 0; + std::uint32_t mi_cols = 0; + std::uint32_t mi_rows = 0; + + std::array order_hints{}; + av1::ref_frame_state ref[NUM_REF_FRAMES]{}; +}; + +//------------------------------------------------------------------------------------------------------------ using any_obu_view = struct obu_view_base; template @@ -394,6 +627,14 @@ template <> struct basic_obu_view : public obu_view_base { constexpr auto header() const noexcept -> obu_sequence_header; }; +template <> +struct basic_obu_view : public obu_view_base { + constexpr auto header() const noexcept -> obu_metadata; +}; +template <> +struct basic_obu_view : public obu_view_base { + constexpr auto header(obu_context& context) const noexcept -> obu_frame_header; +}; //------------------------------------------------------------------------------------------------------------ using obu_sequence_header_view = basic_obu_view; @@ -519,7 +760,7 @@ constexpr auto basic_obu_view::header() const noexcep result.reduced_still_picture_header = reader.read_bits(1); if (result.reduced_still_picture_header) { - result.operating_points[0].operating_point_idc = reader.read_bits(5); + result.operating_points[0].seq_level_idx = reader.read_bits(5); } else { result.timing_info_present_flag = reader.read_bits(1); if (result.timing_info_present_flag) { @@ -648,13 +889,13 @@ constexpr auto basic_obu_view::header() const noexcep cc.color_description_present_flag = reader.read_bits(1); if (cc.color_description_present_flag) { - cc.color_description_data.color_primaries = static_cast( + cc.color_description_data.color_primaries = static_cast( reader.read_bits(8) ); - cc.color_description_data.transfer_characteristics = static_cast( + cc.color_description_data.transfer_characteristics = static_cast( reader.read_bits(8) ); - cc.color_description_data.matrix_coefficients = static_cast( + cc.color_description_data.matrix_coefficients = static_cast( reader.read_bits(8) ); } @@ -666,9 +907,9 @@ constexpr auto basic_obu_view::header() const noexcep result.film_grain_params_present = reader.read_bits(1); return result; } - if (cc.color_description_data.color_primaries == mbmff::av1_color_primaries::CP_BT_709 - && cc.color_description_data.transfer_characteristics == mbmff::av1_transfer_characteristics::TC_SRGB - && cc.color_description_data.matrix_coefficients == mbmff::av1_matrix_coefficients::MC_IDENTITY) { + if (cc.color_description_data.color_primaries == av1::color_primaries::CP_BT_709 + && cc.color_description_data.transfer_characteristics == av1::transfer_characteristics::TC_SRGB + && cc.color_description_data.matrix_coefficients == av1::matrix_coefficients::MC_IDENTITY) { cc.color_range = 1; cc.subsampling_x = 0; cc.subsampling_y = 0; @@ -692,9 +933,7 @@ constexpr auto basic_obu_view::header() const noexcep } } if (cc.subsampling_x && cc.subsampling_y) { - cc.chroma_sample_position = static_cast( - reader.read_bits(2) - ); + cc.chroma_sample_position = static_cast(reader.read_bits(2)); } } cc.separate_uv_delta_q = reader.read_bits(1); @@ -704,6 +943,449 @@ constexpr auto basic_obu_view::header() const noexcep return result; } +//------------------------------------------------------------------------------------------------------------ +// Metadata +constexpr auto basic_obu_view::header() const noexcept -> obu_metadata +{ + obu_metadata result{}; + auto [value, bytes] = leb128(payload); + result.type = static_cast(value); + result.payload = payload.subspan(bytes); + return result; +} + +//------------------------------------------------------------------------------------------------------------ +// Frame header +constexpr void mark_ref_frames(obu_context& context, std::size_t id_len) noexcept +{ + auto diff_len = std::uint32_t(context.sequence_header.delta_frame_id_length_minus_2) + 2; + for (std::uint32_t i = 0; i < NUM_REF_FRAMES; i++) { + if (context.current_frame_id > (1ull << diff_len)) { + if (context.ref[i].frame_id > context.current_frame_id + || context.ref[i].frame_id < context.current_frame_id - (1ull << diff_len)) { + context.ref[i].valid = false; + } + } else { + if (context.ref[i].frame_id > context.current_frame_id + && context.ref[i].frame_id < ((1ull << id_len) + context.current_frame_id - (1ull << diff_len))) { + context.ref[i].valid = false; + } + } + } +} + +constexpr void compute_image_size(obu_context& context) noexcept +{ + context.mi_cols = 2u * ((context.frame_width + 7) >> 3); + context.mi_rows = 2u * ((context.frame_height + 7) >> 3); +} + +constexpr void superres_params(obu_frame_header& frame_header, obu_context& context, bit_reader& reader) noexcept +{ + if (context.sequence_header.enable_superres) { + frame_header.use_superres = reader.read_bits(1); + } + + std::uint32_t superres_denom = SUPERRES_NUM; + if (frame_header.use_superres) { + frame_header.coded_denom = reader.read_bits(3); + superres_denom = std::uint32_t(frame_header.coded_denom) + SUPERRES_DENOM_MIN; + } + + context.upscaled_width = context.frame_width; + context.frame_width = (context.upscaled_width * SUPERRES_NUM + (superres_denom / 2)) / superres_denom; +} + +constexpr void frame_size(obu_frame_header& frame_header, obu_context& context, bit_reader& reader) noexcept +{ + if (frame_header.frame_size_override_flag) { + frame_header.frame_width_minus_1 = reader.read_bits( + std::uint16_t(context.sequence_header.frame_width_bits_minus_1) + 1 + ); + frame_header.frame_height_minus_1 = reader.read_bits( + std::uint16_t(context.sequence_header.frame_height_bits_minus_1) + 1 + ); + } else { + frame_header.frame_width_minus_1 = context.sequence_header.max_frame_width_minus_1; + frame_header.frame_height_minus_1 = context.sequence_header.max_frame_height_minus_1; + } + + context.frame_width = std::uint32_t(frame_header.frame_width_minus_1) + 1; + context.frame_height = std::uint32_t(frame_header.frame_height_minus_1) + 1; + + superres_params(frame_header, context, reader); + compute_image_size(context); +} + +constexpr void render_size(obu_frame_header& frame_header, obu_context& context, bit_reader& reader) noexcept +{ + frame_header.render_and_frame_size_different = reader.read_bits(1); + if (frame_header.render_and_frame_size_different) { + frame_header.render_width_minus_1 = reader.read_bits(16); + frame_header.render_height_minus_1 = reader.read_bits(16); + + context.render_width = std::uint32_t(frame_header.render_width_minus_1) + 1; + context.render_height = std::uint32_t(frame_header.render_height_minus_1) + 1; + } else { + context.render_width = context.upscaled_width; + context.render_height = context.frame_height; + } +} + +// Converted from cbs_syntax_template.c of ffmpeg +constexpr auto get_relative_dist(const obu_sequence_header& seq, std::uint32_t a, std::uint32_t b) noexcept + -> std::int32_t +{ + if (!seq.enable_order_hint) { + return 0; + } + std::uint32_t diff = a - b; + std::uint32_t m = 1 << seq.order_hint_bits_minus_1; + diff = (diff & (m - 1)) - (diff & m); + return diff; +} + +constexpr void set_frame_refs(obu_frame_header& frame_header, obu_context& context) noexcept +{ + const obu_sequence_header& seq = context.sequence_header; + constexpr ref_frame ref_frame_list[NUM_REF_FRAMES - 2] = { + ref_frame::LAST2_FRAME, + ref_frame::LAST3_FRAME, + ref_frame::BWDREF_FRAME, + ref_frame::ALTREF2_FRAME, + ref_frame::ALTREF_FRAME, + }; + + std::int8_t ref_frame_idx[REFS_PER_FRAME]{}; + std::int8_t used_frame[NUM_REF_FRAMES]{}; + + ref_frame_idx[+ref_frame::LAST_FRAME - +ref_frame::LAST_FRAME] = frame_header.last_frame_idx; + ref_frame_idx[+ref_frame::GOLDEN_FRAME - +ref_frame::LAST_FRAME] = frame_header.gold_frame_idx; + + used_frame[frame_header.last_frame_idx] = 1; + used_frame[frame_header.gold_frame_idx] = 1; + + std::uint32_t cur_frame_hint = 1u << (seq.order_hint_bits_minus_1); + std::int16_t shifted_order_hints[NUM_REF_FRAMES]; + for (std::uint32_t i = 0; i < NUM_REF_FRAMES; i++) { + shifted_order_hints[i] = cur_frame_hint + + get_relative_dist(seq, context.ref[i].order_hint, frame_header.order_hint); + } + + std::int16_t latest_order_hint = shifted_order_hints[frame_header.last_frame_idx]; + std::int16_t earliest_order_hint = shifted_order_hints[frame_header.gold_frame_idx]; + + std::int8_t ref = 0; + for (std::uint32_t i = 0; i < NUM_REF_FRAMES; i++) { + int hint = shifted_order_hints[i]; + if (!used_frame[i] && hint >= cur_frame_hint && (ref < 0 || hint >= latest_order_hint)) { + ref = i; + latest_order_hint = hint; + } + } + if (ref >= 0) { + ref_frame_idx[+ref_frame::ALTREF_FRAME - +ref_frame::LAST_FRAME] = ref; + used_frame[ref] = 1; + } + + ref = 0; + for (std::uint32_t i = 0; i < NUM_REF_FRAMES; i++) { + int hint = shifted_order_hints[i]; + if (!used_frame[i] && hint >= cur_frame_hint && (ref < 0 || hint < earliest_order_hint)) { + ref = i; + earliest_order_hint = hint; + } + } + if (ref >= 0) { + ref_frame_idx[+ref_frame::BWDREF_FRAME - +ref_frame::LAST_FRAME] = ref; + used_frame[ref] = 1; + } + + ref = 0; + for (std::uint32_t i = 0; i < NUM_REF_FRAMES; i++) { + int hint = shifted_order_hints[i]; + if (!used_frame[i] && hint >= cur_frame_hint && (ref < 0 || hint < earliest_order_hint)) { + ref = i; + earliest_order_hint = hint; + } + } + if (ref >= 0) { + ref_frame_idx[+ref_frame::ALTREF2_FRAME - +ref_frame::LAST_FRAME] = ref; + used_frame[ref] = 1; + } + + for (std::uint32_t i = 0; i < REFS_PER_FRAME - 2; i++) { + auto ref_frame = ref_frame_list[i]; + if (ref_frame_idx[+ref_frame - +ref_frame::LAST_FRAME] < 0) { + ref = 0; + for (std::uint32_t j = 0; j < NUM_REF_FRAMES; j++) { + int hint = shifted_order_hints[j]; + if (!used_frame[j] && hint < cur_frame_hint && (ref < 0 || hint >= latest_order_hint)) { + ref = j; + latest_order_hint = hint; + } + } + if (ref >= 0) { + ref_frame_idx[+ref_frame - +ref_frame::LAST_FRAME] = ref; + used_frame[ref] = 1; + } + } + } + + ref = 0; + for (std::uint32_t i = 0; i < NUM_REF_FRAMES; i++) { + int hint = shifted_order_hints[i]; + if (ref < 0 || hint < earliest_order_hint) { + ref = i; + earliest_order_hint = hint; + } + } + for (std::uint32_t i = 0; i < REFS_PER_FRAME; i++) { + if (ref_frame_idx[i] < 0) { + ref_frame_idx[i] = ref; + } + frame_header.ref_frame_indices_data.set_ref_frame_index(i, ref_frame_idx[i]); + } +} + +constexpr void frame_size_with_refs(obu_frame_header& frame_header, obu_context& context, bit_reader& reader) noexcept +{ + std::uint8_t found_ref = 0; + for (std::uint32_t i = 0; i < REFS_PER_FRAME; i++) { + if (found_ref = reader.read_bits(1)) { + auto& ref_frame = context.ref[frame_header.ref_frame_indices_data.ref_frame_index(i)]; + frame_header.frame_width_minus_1 = ref_frame.upscaled_width - 1; + frame_header.frame_height_minus_1 = ref_frame.frame_height - 1; + frame_header.render_width_minus_1 = ref_frame.render_width - 1; + frame_header.render_height_minus_1 = ref_frame.render_height - 1; + + context.upscaled_width = ref_frame.upscaled_width; + context.frame_width = ref_frame.upscaled_width; + context.frame_height = ref_frame.frame_height; + context.render_width = ref_frame.render_width; + context.render_height = ref_frame.render_height; + break; + } + } + + if (!found_ref) { + frame_size(frame_header, context, reader); + render_size(frame_header, context, reader); + } else { + superres_params(frame_header, context, reader); + compute_image_size(context); + } +} + +constexpr auto basic_obu_view::header(obu_context& context) const noexcept -> obu_frame_header +{ + obu_frame_header result{}; + bit_reader reader{payload}; + const auto& seq_view = context.sequence_obu_header; + const auto& sequence_header = context.sequence_header; + + std::uint32_t id_len = 0; + if (sequence_header.frame_id_numbers_present_flag) { + id_len = std::uint32_t(sequence_header.delta_frame_id_length_minus_2) + 2u + + sequence_header.additional_frame_id_length_minus_1 + 1u; + } + std::uint32_t all_frames = (1u << NUM_REF_FRAMES) - 1; + bool frame_is_intra = true; + + if (!sequence_header.reduced_still_picture_header) { + result.show_existing_frame = reader.read_bits(1); + if (result.show_existing_frame) { + result.frame_to_show_map_idx = reader.read_bits(3); + + // Temporal point info + if (sequence_header.decoder_model_info_present_flag && sequence_header.timing_info_present_flag + && !sequence_header.timing_info_data.equal_picture_interval) { + result.frame_presentation_time = reader.read_bits( + sequence_header.decoder_model_info_data.frame_presentation_time_length_minus_1 + 1 + ); + } + + result.refresh_frame_flags = 0; + if (sequence_header.frame_id_numbers_present_flag) { + result.display_frame_id = reader.read_bits(id_len); + } + + result.frame_type = context.ref[result.frame_to_show_map_idx].frame_type; + if (result.frame_type == frame_type::KEY_FRAME) { + result.refresh_frame_flags = all_frames; // refresh all reference frames + } + + if (sequence_header.film_grain_params_present) { + // load_grain_params( frame_to_show_map_idx ) + } + + return result; + } + + result.frame_type = static_cast(reader.read_bits(2)); + bool frame_is_intra = (result.frame_type == frame_type::KEY_FRAME) + || (result.frame_type == frame_type::INTRA_ONLY_FRAME); + + result.show_frame = reader.read_bits(1); + + // Temporal point info + if (result.show_frame && sequence_header.decoder_model_info_present_flag + && sequence_header.timing_info_present_flag && !sequence_header.timing_info_data.equal_picture_interval) { + result.frame_presentation_time = reader.read_bits( + sequence_header.decoder_model_info_data.frame_presentation_time_length_minus_1 + 1 + ); + } + + if (result.show_frame) { + result.showable_frame = result.frame_type != frame_type::KEY_FRAME; + } else { + result.showable_frame = reader.read_bits(1); + } + + if (result.frame_type == frame_type::SWITCH_FRAME + || (result.frame_type == frame_type::KEY_FRAME && result.show_frame)) { + result.error_resilient_mode = 1; + } else { + result.error_resilient_mode = reader.read_bits(1); + } + } + + if (result.frame_type == frame_type::KEY_FRAME && result.show_frame) { + for (std::uint32_t i = 0; i < NUM_REF_FRAMES; i++) { + context.ref[i].valid = false; + context.ref[i].order_hint = 0; + } + for (std::uint32_t i = 0; i < REFS_PER_FRAME; i++) { + context.order_hints[+ref_frame::LAST_FRAME + i] = 0; + } + } + + result.disable_cdf_update = reader.read_bits(1); + if (sequence_header.seq_force_screen_content_tools == SELECT_SCREEN_CONTENT_TOOLS) { + result.allow_screen_content_tools = reader.read_bits(1); + } else { + result.allow_screen_content_tools = sequence_header.seq_force_screen_content_tools; + } + + if (result.allow_screen_content_tools) { + if (sequence_header.seq_force_integer_mv == SELECT_INTEGER_MV) { + result.force_integer_mv = reader.read_bits(1); + } else { + result.force_integer_mv = sequence_header.seq_force_integer_mv; + } + } + + if (frame_is_intra) { + result.force_integer_mv = 1; // force_integer_mv must be 1 for intra frames + } + + if (sequence_header.frame_id_numbers_present_flag) { + context.prev_frame_id = context.current_frame_id; + context.current_frame_id = result.current_frame_id = reader.read_bits(id_len); + mark_ref_frames(context, id_len); + } + + if (result.frame_type == frame_type::SWITCH_FRAME) { + result.frame_size_override_flag = reader.read_bits(1); + } else if (sequence_header.reduced_still_picture_header) { + result.frame_size_override_flag = 0; + } else { + result.frame_size_override_flag = reader.read_bits(1); + } + + std::uint8_t order_hint_bits = sequence_header.order_hint_bits(); + if (order_hint_bits > 0) { + result.order_hint = reader.read_bits(order_hint_bits); + } + + if (frame_is_intra || result.error_resilient_mode) { + result.primary_ref_frame = PRIMARY_REF_NONE; + } else { + result.primary_ref_frame = reader.read_bits(3); + } + + // Buffer removal time + if (sequence_header.decoder_model_info_present_flag) { + if (result.buffer_removal_time_present_flag = reader.read_bits(1)) { + for (std::size_t i = 0; i <= sequence_header.operating_points_cnt_minus_1; i++) { + if (sequence_header.operating_points[i].decoder_model_present_for_this_op) { + auto op_idc = sequence_header.operating_points[i].operating_point_idc; + auto in_temporal_layer = (op_idc >> seq_view.temporal_id) & 1; + auto in_spatial_layer = (op_idc >> (8 + seq_view.spatial_id)) & 1; + + if (op_idc == 0 || (in_temporal_layer && in_spatial_layer)) { + result.buffer_removal_time[i] = reader.read_bits( + sequence_header.decoder_model_info_data.buffer_removal_time_length_minus_1 + 1 + ); + } + } + } + } + } + + if (result.frame_type == frame_type::SWITCH_FRAME + || (result.frame_type == frame_type::KEY_FRAME && result.show_frame)) { + result.refresh_frame_flags = all_frames; // refresh all reference frames + } else { + result.refresh_frame_flags = reader.read_bits(8); + } + + if (!frame_is_intra || result.refresh_frame_flags != all_frames) { + if (result.error_resilient_mode && sequence_header.enable_order_hint) { + for (std::size_t i = 0; i < NUM_REF_FRAMES; i++) { + result.ref_order_hint[i] = reader.read_bits(order_hint_bits); + if (result.ref_order_hint[i] != context.ref[i].order_hint) { + context.ref[i].valid = false; + } + } + } + } + + if (frame_is_intra) { + frame_size(result, context, reader); + render_size(result, context, reader); + + if (result.allow_screen_content_tools && context.upscaled_width == context.frame_width) { + result.allow_intrabc = reader.read_bits(1); + } + } else { + if (sequence_header.enable_order_hint) { + if (result.frame_refs_short_signaling = reader.read_bits(1)) { + result.last_frame_idx = reader.read_bits(3); + result.gold_frame_idx = reader.read_bits(3); + set_frame_refs(result, context); + } + } + + for (std::size_t i = 0; i < REFS_PER_FRAME; i++) { + if (!result.frame_refs_short_signaling) { + result.ref_frame_indices_data.set_ref_frame_index(i, reader.read_bits(3)); + } + if (sequence_header.frame_id_numbers_present_flag) { + std::uint32_t delta_frame_id = reader.read_bits( + std::uint32_t(sequence_header.delta_frame_id_length_minus_2) + 2 + ) + + 1; + result + .expectedFrameId[i] = ((result.current_frame_id + (1ull << id_len) - delta_frame_id) % (1ull << id_len)); + } + } + + if (result.frame_size_override_flag && !result.error_resilient_mode) { + frame_size_with_refs(result, context, reader); + } else { + frame_size(result, context, reader); + render_size(result, context, reader); + } + + if (!result.force_integer_mv) { + result.allow_high_precision_mv = reader.read_bits(1); + } + + // read interpolation filter + } +} + //------------------------------------------------------------------------------------------------------------ // iterator @@ -783,4 +1465,4 @@ struct obu_iterator { } }; -} // namespace mbmff +} // namespace mbmff::av1 diff --git a/include/mbmff/mbmff.hpp b/include/mbmff/mbmff.hpp index b60c7f4..321dea1 100644 --- a/include/mbmff/mbmff.hpp +++ b/include/mbmff/mbmff.hpp @@ -333,7 +333,7 @@ struct basic_box_view : public box_view_base { template <> struct basic_box_view : public box_view_base { constexpr static box_properties properties = box_properties::none; - constexpr auto header() const noexcept -> av1C_header; + constexpr auto header() const noexcept -> av1::av1C_header; }; template <> @@ -749,9 +749,9 @@ constexpr auto basic_box_view::header() const noexcept -> ispe_h //------------------------------------------------------------------------------------------------------------ // AV1C -constexpr auto basic_box_view::header() const noexcept -> av1C_header +constexpr auto basic_box_view::header() const noexcept -> av1::av1C_header { - av1C_header result{}; + av1::av1C_header result{}; if (payload.size() < 3) { return result; } diff --git a/test/entry_main.cpp b/test/entry_main.cpp index 6a95d84..adf1f2a 100644 --- a/test/entry_main.cpp +++ b/test/entry_main.cpp @@ -18,6 +18,7 @@ int main() std::span file_data_span(reinterpret_cast(file_data.data()), file_data.size()); uint32_t primary_item_id = 0; + mbmff::iloc_box iloc_box; for (const auto& box_expected : mbmff::box_iterator(file_data_span, mbmff::iterator_flags::recursive)) { if (!box_expected) { @@ -55,10 +56,7 @@ int main() case mbmff::box_type::iloc: { auto iloc = mbmff::box_cast(box); std::cout << std::format("{}\n", iloc); - - for (const auto& item : mbmff::iloc_item_iterator(iloc)) { - std::cout << std::format(" {}\n", item); - } + iloc_box = iloc; } break; case mbmff::box_type::iprp: { auto iprp = mbmff::box_cast(box); @@ -81,7 +79,7 @@ int main() std::cout << std::format("{}\n", av1C); // scan OBUs in the av1C payload - for (std::uint32_t i = 0; const auto& obu : mbmff::obu_iterator(av1C.header())) { + for (std::uint32_t i = 0; const auto& obu : mbmff::av1::obu_iterator(av1C.header())) { if (!obu) { std::cout << std::format( " OBU parsing error: code={}, needed={}\n", @@ -91,8 +89,8 @@ int main() break; } - if (obu->type == mbmff::obu_type::sequence_header) { - auto seq_header_obu = mbmff::obu_cast(*obu); + if (obu->type == mbmff::av1::obu_type::sequence_header) { + auto seq_header_obu = mbmff::av1::obu_cast(*obu); std::cout << std::format(" Sequence Header OBU: {}\n", seq_header_obu); break; } @@ -125,5 +123,36 @@ int main() } } + // parse iloc box to find the offset and size of the primary item in the mdat box + uint64_t item_data_offset = 0; + uint64_t item_data_size = 0; + for (const auto& item : mbmff::iloc_item_iterator(iloc_box)) { + if (item.item_id == primary_item_id) { + std::cout << std::format( + "Primary item found in iloc box: {}\n", + item + ); + item_data_offset = item.base_offset; + item_data_size = item[0].length; + break; + } + } + + // Seek to the primary item in the mdat box and print the first few bytes of its data + auto primary_item_span = file_data_span.subspan(item_data_offset, item_data_size); + auto primary_obu = mbmff::av1::obu_iterator(primary_item_span); + + for (std::uint32_t i = 0; const auto& obu : primary_obu) { + if (!obu) { + std::cout << std::format( + "Primary item OBU parsing error: code={}, needed={}\n", + obu.error().code, + obu.error().needed + ); + break; + } + std::cout << std::format("Primary item OBU {}: {}\n", i++, obu.value()); + } + return 0; } diff --git a/test/formatters.hpp b/test/formatters.hpp index e94e709..cac53d7 100644 --- a/test/formatters.hpp +++ b/test/formatters.hpp @@ -4,8 +4,8 @@ #include template<> -struct std::formatter : std::formatter { - auto format(const mbmff::obu_sequence_header_view& view, std::format_context& ctx) const +struct std::formatter : std::formatter { + auto format(const mbmff::av1::obu_sequence_header_view& view, std::format_context& ctx) const { auto header = view.header(); std::string output = std::format( @@ -46,9 +46,9 @@ struct std::formatter : std::formatter : std::formatter -struct std::formatter : std::formatter { - auto format(const mbmff::any_obu_view& box, format_context& ctx) const -> format_context::iterator +struct std::formatter : std::formatter { + auto format(const mbmff::av1::any_obu_view& box, format_context& ctx) const -> format_context::iterator { std::string output = std::format( "OBU: type={}", - mbmff::to_string(static_cast(box.type)) + mbmff::av1::to_string(static_cast(box.type)) ); // Add size field if present From 2dd5e535aff60dc5d4344ce92a73f15a23772554 Mon Sep 17 00:00:00 2001 From: Ilya Doroshenko Date: Sun, 7 Jun 2026 15:07:16 +0200 Subject: [PATCH 2/3] quantization and tile info changed to multi-header structure, since AV1 is huge --- include/mbmff/av1.hpp | 926 ++---------------------------- include/mbmff/av1/av1_enums.hpp | 416 ++++++++++++++ include/mbmff/av1/av1_funcs.hpp | 447 ++++++++++++++ include/mbmff/av1/av1_structs.hpp | 299 ++++++++++ include/mbmff/common.hpp | 40 ++ 5 files changed, 1261 insertions(+), 867 deletions(-) create mode 100644 include/mbmff/av1/av1_enums.hpp create mode 100644 include/mbmff/av1/av1_funcs.hpp create mode 100644 include/mbmff/av1/av1_structs.hpp diff --git a/include/mbmff/av1.hpp b/include/mbmff/av1.hpp index a3b1766..a50d1fd 100644 --- a/include/mbmff/av1.hpp +++ b/include/mbmff/av1.hpp @@ -1,651 +1,40 @@ #pragma once +#include #include #include #include -#include "common.hpp" +#include "av1/av1_funcs.hpp" namespace mbmff::av1 { -enum class obu_type : std::uint8_t { - sequence_header = 1, - temporal_delimiter = 2, - frame_header = 3, - tile_group = 4, - metadata = 5, - frame = 6, - redundant_frame_header = 7, - tile_list = 8, - // 9-14 are reserved - padding = 15, // special type used for padding OBUs that may be present in the stream. These should be ignored by - // decoders. -}; -constexpr bool operator==(obu_type type, uint8_t value) noexcept -{ - return static_cast(type) == value; -} -constexpr bool operator==(uint8_t value, obu_type type) noexcept -{ - return type == value; -} - -constexpr static auto to_string(obu_type type) noexcept -> std::string_view -{ - switch (type) { - case obu_type::sequence_header: - return "Sequence Header"; - case obu_type::temporal_delimiter: - return "Temporal Delimiter"; - case obu_type::frame_header: - return "Frame Header"; - case obu_type::tile_group: - return "Tile Group"; - case obu_type::metadata: - return "Metadata"; - case obu_type::frame: - return "Frame"; - case obu_type::redundant_frame_header: - return "Redundant Frame Header"; - case obu_type::tile_list: - return "Tile List"; - case obu_type::padding: - return "Padding"; - default: - return "Unknown OBU Type"; - } -} - -enum class color_primaries : std::uint8_t { - CP_RESERVED_0 = 0, - CP_BT_709 = 1, - CP_UNSPECIFIED = 2, - CP_RESERVED_3 = 3, - CP_BT_470_M = 4, - CP_BT_470_B_G = 5, - CP_BT_601 = 6, - CP_SMPTE_240 = 7, - CP_GENERIC_FILM = 8, - CP_BT_2020 = 9, - CP_XYZ = 10, - CP_SMPTE_431 = 11, - CP_SMPTE_432 = 12, - CP_EBU_3213 = 22, -}; - -constexpr auto to_string(color_primaries primaries) noexcept -> std::string_view -{ - switch (primaries) { - case av1::color_primaries::CP_RESERVED_0: - return "Reserved (0)"; - case av1::color_primaries::CP_BT_709: - return "BT.709"; - case av1::color_primaries::CP_UNSPECIFIED: - return "Unspecified"; - case av1::color_primaries::CP_RESERVED_3: - return "Reserved (3)"; - case av1::color_primaries::CP_BT_470_M: - return "BT.470 System M (historical)"; - case av1::color_primaries::CP_BT_470_B_G: - return "BT.470 System B/G (historical)"; - case av1::color_primaries::CP_BT_601: - return "BT.601"; - case av1::color_primaries::CP_SMPTE_240: - return "SMPTE 240M"; - case av1::color_primaries::CP_GENERIC_FILM: - return "Generic film (color filters using illuminant C)"; - case av1::color_primaries::CP_BT_2020: - return "BT.2020, BT.2100"; - case av1::color_primaries::CP_XYZ: - return "SMPTE 428 (CIE 1921 XYZ)"; - case av1::color_primaries::CP_SMPTE_431: - return "SMPTE RP 431-2"; - case av1::color_primaries::CP_SMPTE_432: - return "SMPTE EG 432-1"; - case av1::color_primaries::CP_EBU_3213: - return "EBU Tech 3213-E"; - default: - return "Unknown color primaries"; - } -} - -enum class transfer_characteristics : std::uint8_t { - TC_RESERVED_0 = 0, - TC_BT_709 = 1, - TC_UNSPECIFIED = 2, - TC_RESERVED_3 = 3, - TC_BT_470_M = 4, - TC_BT_470_B_G = 5, - TC_BT_601 = 6, - TC_SMPTE_240 = 7, - TC_LINEAR = 8, - TC_LOG_100 = 9, - TC_LOG_100_SQRT10 = 10, - TC_IEC_61966 = 11, - TC_BT_1361 = 12, - TC_SRGB = 13, - TC_BT_2020_10_BIT = 14, - TC_BT_2020_12_BIT = 15, - TC_SMPTE_2084 = 16, - TC_SMPTE_428 = 17, - TC_HLG = 18, -}; - -constexpr auto to_string(transfer_characteristics tc) noexcept -> std::string_view -{ - switch (tc) { - case av1::transfer_characteristics::TC_RESERVED_0: - return "Reserved (0)"; - case av1::transfer_characteristics::TC_BT_709: - return "BT.709"; - case av1::transfer_characteristics::TC_UNSPECIFIED: - return "Unspecified"; - case av1::transfer_characteristics::TC_RESERVED_3: - return "Reserved (3)"; - case av1::transfer_characteristics::TC_BT_470_M: - return "BT.470 System M (historical)"; - case av1::transfer_characteristics::TC_BT_470_B_G: - return "BT.470 System B/G (historical)"; - case av1::transfer_characteristics::TC_BT_601: - return "BT.601"; - case av1::transfer_characteristics::TC_SMPTE_240: - return "SMPTE 240M"; - case av1::transfer_characteristics::TC_LINEAR: - return "Linear"; - case av1::transfer_characteristics::TC_LOG_100: - return "Logarithmic (100:1 range)"; - case av1::transfer_characteristics::TC_LOG_100_SQRT10: - return "Logarithmic (100 * sqrt(10):1 range)"; - case av1::transfer_characteristics::TC_IEC_61966: - return "IEC 61966-2-4"; - case av1::transfer_characteristics::TC_BT_1361: - return "BT.1361"; - case av1::transfer_characteristics::TC_SRGB: - return "sRGB or sYCC"; - case av1::transfer_characteristics::TC_BT_2020_10_BIT: - return "BT.2020 10-bit system"; - case av1::transfer_characteristics::TC_BT_2020_12_BIT: - return "BT.2020 12-bit system"; - case av1::transfer_characteristics::TC_SMPTE_2084: - return "SMPTE ST 2084, ITU BT.2100 PQ"; - case av1::transfer_characteristics::TC_SMPTE_428: - return "SMPTE ST 428"; - case av1::transfer_characteristics::TC_HLG: - return "BT.2100 HLG, ARIB STD-B67"; - default: - return "Unknown transfer characteristics"; - } -} - -enum class matrix_coefficients : std::uint8_t { - MC_IDENTITY = 0, - MC_BT_709 = 1, - MC_UNSPECIFIED = 2, - MC_RESERVED_3 = 3, - MC_FCC = 4, - MC_BT_470_B_G = 5, - MC_BT_601 = 6, - MC_SMPTE_240 = 7, - MC_SMPTE_YCGCO = 8, - MC_BT_2020_NCL = 9, - MC_BT_2020_CL = 10, - MC_SMPTE_2085 = 11, - MC_CHROMAT_NCL = 12, - MC_CHROMAT_CL = 13, - MC_ICTCP = 14, -}; - -constexpr auto to_string(matrix_coefficients mc) noexcept -> std::string_view -{ - switch (mc) { - case av1::matrix_coefficients::MC_IDENTITY: - return "Identity"; - case av1::matrix_coefficients::MC_BT_709: - return "BT.709"; - case av1::matrix_coefficients::MC_UNSPECIFIED: - return "Unspecified"; - case av1::matrix_coefficients::MC_RESERVED_3: - return "Reserved (3)"; - case av1::matrix_coefficients::MC_FCC: - return "US FCC 73.628"; - case av1::matrix_coefficients::MC_BT_470_B_G: - return "BT.470 System B/G (historical)"; - case av1::matrix_coefficients::MC_BT_601: - return "BT.601"; - case av1::matrix_coefficients::MC_SMPTE_240: - return "SMPTE 240M"; - case av1::matrix_coefficients::MC_SMPTE_YCGCO: - return "YCgCo"; - case av1::matrix_coefficients::MC_BT_2020_NCL: - return "BT.2020 non-constant luminance system"; - case av1::matrix_coefficients::MC_BT_2020_CL: - return "BT.2020 constant luminance system"; - case av1::matrix_coefficients::MC_SMPTE_2085: - return "SMPTE ST 2085 YDzDx"; - case av1::matrix_coefficients::MC_CHROMAT_NCL: - return "Chromaticity-derived non-constant luminance system"; - case av1::matrix_coefficients::MC_CHROMAT_CL: - return "Chromaticity-derived constant luminance system"; - case av1::matrix_coefficients::MC_ICTCP: - return "ICTCP (BT.2100)"; - default: - return "Unknown matrix coefficients"; - } -} - -enum class chroma_sample_position : std::uint8_t { - CSP_UNKNOWN = 0, - CSP_VERTICAL = 1, - CSP_COLOCATED = 2, - CSP_RESERVED = 3, -}; - -constexpr auto to_string(chroma_sample_position csp) noexcept -> std::string_view -{ - switch (csp) { - case av1::chroma_sample_position::CSP_UNKNOWN: - return "Unknown"; - case av1::chroma_sample_position::CSP_VERTICAL: - return "Vertical"; - case av1::chroma_sample_position::CSP_COLOCATED: - return "Colocated"; - case av1::chroma_sample_position::CSP_RESERVED: - return "Reserved"; - default: - return "Invalid chroma sample position"; - } -} - -enum class metadata_type : uint32_t { - METADATA_TYPE_HDR_CLL = 1, - METADATA_TYPE_HDR_MDCV = 2, - METADATA_TYPE_SCALABILITY = 3, - METADATA_TYPE_ITUT_T35 = 4, - METADATA_TYPE_TIMECODE = 5, -}; - -constexpr auto to_string(metadata_type type) noexcept -> std::string_view -{ - switch (type) { - case av1::metadata_type::METADATA_TYPE_ITUT_T35: - return "ITU-T T.35"; - case av1::metadata_type::METADATA_TYPE_HDR_CLL: - return "HDR Content Light Level (CLL)"; - case av1::metadata_type::METADATA_TYPE_HDR_MDCV: - return "HDR Mastering Display Color Volume (MDCV)"; - case av1::metadata_type::METADATA_TYPE_SCALABILITY: - return "Scalability"; - case av1::metadata_type::METADATA_TYPE_TIMECODE: - return "Timecode"; - default: - return "Unknown metadata type"; - } -} - -enum class frame_type : std::uint8_t { - KEY_FRAME, - INTER_FRAME, - INTRA_ONLY_FRAME, - SWITCH_FRAME, -}; - -constexpr auto to_string(frame_type type) noexcept -> std::string_view -{ - switch (type) { - case frame_type::KEY_FRAME: - return "Key Frame"; - case frame_type::INTER_FRAME: - return "Inter Frame"; - case frame_type::INTRA_ONLY_FRAME: - return "Intra-only Frame"; - case frame_type::SWITCH_FRAME: - return "Switch Frame"; - default: - return "Unknown frame type"; - } -} - -enum class ref_frame : std::int8_t { - NONE = -1, - INTRA_FRAME = 0, - LAST_FRAME = 1, - LAST2_FRAME = 2, - LAST3_FRAME = 3, - GOLDEN_FRAME = 4, - BWDREF_FRAME = 5, - ALTREF2_FRAME = 6, - ALTREF_FRAME = 7, -}; - -constexpr auto to_string(ref_frame ref) noexcept -> std::string_view -{ - switch (ref) { - case ref_frame::NONE: - return "None"; - case ref_frame::INTRA_FRAME: - return "Intra Frame"; - case ref_frame::LAST_FRAME: - return "Last Frame"; - case ref_frame::LAST2_FRAME: - return "Last2 Frame"; - case ref_frame::LAST3_FRAME: - return "Last3 Frame"; - case ref_frame::GOLDEN_FRAME: - return "Golden Frame"; - case ref_frame::BWDREF_FRAME: - return "BWDREF Frame"; - case ref_frame::ALTREF2_FRAME: - return "ALTREF2 Frame"; - case ref_frame::ALTREF_FRAME: - return "ALTREF Frame"; - default: - return "Unknown reference frame"; - } -} - -constexpr std::int8_t operator+(ref_frame ref) noexcept -{ - return std::to_underlying(ref); -} - -// Value that indicates the allow_screen_content_tools syntax element is coded -static constexpr std::uint8_t SELECT_SCREEN_CONTENT_TOOLS = 2; - -// Value that indicates the force_integer_mv syntax element is coded -static constexpr std::uint8_t SELECT_INTEGER_MV = 2; - -// The maximum number of reference frames that can be used for inter prediction in AV1. -static constexpr std::uint32_t NUM_REF_FRAMES = 8; - -// Value of primary_ref_frame indicating that there is no primary reference frame -static constexpr std::uint8_t PRIMARY_REF_NONE = 7; - -// Smallest denominator for upscaling ratio -static constexpr std::uint8_t SUPERRES_DENOM_MIN = 9; - -// Smallest denominator for upscaling ratio -static constexpr std::uint8_t SUPERRES_NUM = 8; - -// Smallest denominator for upscaling ratio -static constexpr std::uint8_t REFS_PER_FRAME = 8; - -//------------------------------------------------------------------------------------------------------------ -struct timing_info { - std::uint32_t num_units_in_display_tick = 0; - std::uint32_t time_scale = 0; - std::uint32_t equal_picture_interval = 0; - std::uint32_t num_ticks_per_picture_minus_1 = 0; -}; - -struct decoder_model_info { - std::uint8_t buffer_delay_length_minus_1 = 0; - std::uint32_t num_units_in_decoding_tick = 0; - std::uint8_t buffer_removal_time_length_minus_1 = 0; - std::uint8_t frame_presentation_time_length_minus_1 = 0; -}; - -struct operating_parameters_info { - std::uint32_t decoder_buffer_delay = 0; - std::uint32_t encoder_buffer_delay = 0; - std::uint32_t low_delay_mode_flag = 0; -}; - -struct operating_point { - std::uint16_t operating_point_idc = 0; - std::uint8_t seq_level_idx : 5 = 0; - std::uint8_t seq_tier : 1 = 0; - std::uint8_t decoder_model_present_for_this_op : 1 = 0; - std::uint8_t initial_display_delay_present_for_this_op : 1 = 0; - std::uint8_t initial_display_delay_minus_1 : 4 = 0; - - operating_parameters_info operating_parameters{}; -}; - -struct color_description { - av1::color_primaries color_primaries = av1::color_primaries::CP_UNSPECIFIED; - av1::transfer_characteristics transfer_characteristics = av1::transfer_characteristics::TC_UNSPECIFIED; - av1::matrix_coefficients matrix_coefficients = av1::matrix_coefficients::MC_UNSPECIFIED; -}; - -struct color_config { - std::uint8_t high_bitdepth : 1 = 0; - std::uint8_t twelve_bit : 1 = 0; - std::uint8_t monochrome : 1 = 0; - std::uint8_t color_description_present_flag : 1 = 0; - - std::uint8_t color_range : 1 = 0; - std::uint8_t subsampling_x : 1 = 0; - std::uint8_t subsampling_y : 1 = 0; - std::uint8_t separate_uv_delta_q : 1 = 0; - - av1::chroma_sample_position chroma_sample_position = av1::chroma_sample_position::CSP_UNKNOWN; - av1::color_description color_description_data{}; -}; - -struct obu_sequence_header { - std::uint8_t seq_profile : 3 = 0; - std::uint8_t still_picture : 1 = 0; - std::uint8_t reduced_still_picture_header : 1 = 0; - std::uint8_t timing_info_present_flag : 1 = 0; - std::uint8_t decoder_model_info_present_flag : 1 = 0; - std::uint8_t initial_display_delay_present_flag : 1 = 0; - - std::uint8_t frame_id_numbers_present_flag : 1 = 0; - std::uint8_t delta_frame_id_length_minus_2 : 4 = 0; - std::uint8_t additional_frame_id_length_minus_1 : 3 = 0; - - std::uint8_t use_128x128_superblock : 1 = 0; - std::uint8_t enable_filter_intra : 1 = 0; - std::uint8_t enable_intra_edge_filter : 1 = 0; - std::uint8_t enable_interintra_compound : 1 = 0; - std::uint8_t enable_masked_compound : 1 = 0; - std::uint8_t enable_warped_motion : 1 = 0; - std::uint8_t enable_dual_filter : 1 = 0; - std::uint8_t enable_order_hint : 1 = 0; - - std::uint8_t order_hint_bits_minus_1 : 3 = 0; - std::uint8_t enable_jnt_comp : 1 = 0; - std::uint8_t enable_ref_frame_mvs : 1 = 0; - std::uint8_t seq_choose_screen_content_tools : 1 = 0; - std::uint8_t seq_force_screen_content_tools : 2 = SELECT_SCREEN_CONTENT_TOOLS; - std::uint8_t seq_choose_integer_mv : 1 = 0; - std::uint8_t seq_force_integer_mv : 2 = SELECT_INTEGER_MV; - - std::uint8_t enable_superres : 1 = 0; - std::uint8_t enable_cdef : 1 = 0; - std::uint8_t enable_restoration : 1 = 0; - std::uint8_t film_grain_params_present : 1 = 0; - - std::uint8_t operating_points_cnt_minus_1 : 5 = 0; - std::array operating_points{}; // max 32 operating points (as per spec) - - timing_info timing_info_data{}; - decoder_model_info decoder_model_info_data{}; - color_config color_config_data{}; - - uint8_t frame_width_bits_minus_1 : 4 = 0; - uint8_t frame_height_bits_minus_1 : 4 = 0; - uint16_t max_frame_width_minus_1 = 0; - uint16_t max_frame_height_minus_1 = 0; - -public: - constexpr std::uint8_t order_hint_bits() const noexcept - { - if (reduced_still_picture_header) { - return 0; - } - - if (enable_order_hint) { - return order_hint_bits_minus_1 + 1; - } - return 0; - } -}; - -struct obu_metadata { - av1::metadata_type type = {}; - std::span payload{}; -}; - -struct frame_indices { - std::uint32_t ref_frame_indices; - -public: - constexpr auto ref_frame_index(std::size_t i) const noexcept -> std::uint8_t - { - return (ref_frame_indices >> (i * 3)) & 0x07; - } - constexpr auto set_ref_frame_index(std::size_t i, std::uint8_t index) noexcept -> void - { - ref_frame_indices &= ~(0x07 << (i * 3)); // Clear the existing index at position i - ref_frame_indices |= (index & 0x07) << (i * 3); // Set the new index at position i - } -}; - -struct ref_frame_state { - std::uint8_t valid; // RefValid - std::uint8_t order_hint; // RefOrderHint - std::uint8_t bit_depth; // RefBitDepth - av1::frame_type frame_type; // RefFrameType - - std::uint32_t upscaled_width; // RefUpscaledWidth - std::uint64_t frame_id; // RefFrameId - - std::uint32_t frame_width; // RefFrameWidth - std::uint32_t frame_height; // RefFrameHeight - std::uint32_t render_width; // RefRenderWidth - std::uint32_t render_height; // RefRenderHeight - - std::uint32_t subsampling_x; // RefSubsamplingX - std::uint32_t subsampling_y; // RefSubsamplingY -}; - -struct obu_frame_header { - std::uint8_t show_existing_frame : 1 = 0; - std::uint8_t frame_to_show_map_idx : 3 = 0; - std::uint8_t show_frame : 1 = 1; - std::uint8_t showable_frame : 1 = 0; - std::uint8_t error_resilient_mode : 1 = 1; - std::uint8_t disable_cdf_update : 1 = 0; - - std::uint8_t allow_screen_content_tools : 1 = 0; - std::uint8_t force_integer_mv : 1 = 0; - std::uint8_t frame_size_override_flag : 1 = 0; - std::uint8_t buffer_removal_time_present_flag : 1 = 0; - std::uint8_t allow_high_precision_mv : 1 = 0; - std::uint8_t primary_ref_frame : 3 = 0; - - std::uint8_t use_ref_frame_mvs : 1 = 0; - std::uint8_t allow_intrabc : 1 = 0; - std::uint8_t frame_refs_short_signaling : 1 = 0; - std::uint8_t last_frame_idx : 3 = 0; - std::uint8_t gold_frame_idx : 3 = 0; - - std::uint8_t refresh_frame_flags = 0; - av1::frame_type frame_type = av1::frame_type::KEY_FRAME; - std::uint8_t order_hint = 0; - - // frame size - std::uint16_t frame_width_minus_1 = 0; - std::uint16_t frame_height_minus_1 = 0; - - // superres params - std::uint8_t use_superres : 1 = 0; - std::uint8_t coded_denom : 3 = 0; - - // render size - std::uint8_t render_and_frame_size_different : 1 = 0; - std::uint16_t render_width_minus_1 = 0; - std::uint16_t render_height_minus_1 = 0; - - av1::frame_indices ref_frame_indices_data{}; - - std::uint64_t display_frame_id = 0; - std::uint64_t current_frame_id = 0; - std::uint64_t frame_presentation_time = 0; - std::array buffer_removal_time{}; - std::array ref_order_hint{}; - std::array expectedFrameId{}; -}; - -//------------------------------------------------------------------------------------------------------------ -struct obu_view_base { - constexpr static std::uint64_t max_obu_size = (1ULL << 32) - 1; // 4GB - 1 byte -public: - std::uint8_t forbidden_bit : 1 = 0; // should be 0 - std::uint8_t type : 4 = 0; - std::uint8_t extension_flag : 1 = 0; - std::uint8_t has_size_field : 1 = 0; - std::uint8_t reserved : 1 = 0; // should be 0 - - // obu extension header fields (if extension_flag is set) - std::uint8_t temporal_id : 3 = 0; - std::uint8_t spatial_id : 2 = 0; - std::uint8_t extension_reserved_1 : 3 = 0; // should be 0 - - // followed by payload - std::span payload{}; - -public: - constexpr operator bool() const noexcept - { - return payload.data() != nullptr; - } -}; - -//------------------------------------------------------------------------------------------------------------ -struct obu_context { - // Selected by the decoder - std::uint32_t operating_point = 0; - - // Sequence header OBU, needed for parsing frame headers. - av1::obu_view_base sequence_obu_header{}; - - // Most recently parsed sequence header, needed for parsing frame headers - av1::obu_sequence_header sequence_header{}; - - std::uint8_t seen_frame_header : 1 = false; - - std::uint64_t prev_frame_id = 0; - std::uint64_t current_frame_id = 0; - std::uint32_t frame_width = 0; - std::uint32_t frame_height = 0; - std::uint32_t render_width = 0; - std::uint32_t render_height = 0; - std::uint32_t upscaled_width = 0; - std::uint32_t mi_cols = 0; - std::uint32_t mi_rows = 0; - - std::array order_hints{}; - av1::ref_frame_state ref[NUM_REF_FRAMES]{}; -}; - //------------------------------------------------------------------------------------------------------------ using any_obu_view = struct obu_view_base; -template +template struct basic_obu_view : public obu_view_base {}; template <> -struct basic_obu_view : public obu_view_base { - constexpr auto header() const noexcept -> obu_sequence_header; +struct basic_obu_view : public av1::obu_view_base { + constexpr auto header() const noexcept -> av1::obu_sequence_header; }; template <> -struct basic_obu_view : public obu_view_base { - constexpr auto header() const noexcept -> obu_metadata; +struct basic_obu_view : public av1::obu_view_base { + constexpr auto header() const noexcept -> av1::obu_metadata; }; template <> -struct basic_obu_view : public obu_view_base { - constexpr auto header(obu_context& context) const noexcept -> obu_frame_header; +struct basic_obu_view : public av1::obu_view_base { + constexpr auto header(obu_context& context) const noexcept -> av1::obu_frame_header; }; //------------------------------------------------------------------------------------------------------------ -using obu_sequence_header_view = basic_obu_view; +using obu_sequence_header_view = av1::basic_obu_view; //------------------------------------------------------------------------------------------------------------ -template -constexpr auto obu_cast(const any_obu_view& obu) noexcept -> basic_obu_view +template +constexpr auto obu_cast(const av1::any_obu_view& obu) noexcept -> av1::basic_obu_view { - if (static_cast(obu.type) != Obu) { + if (static_cast(obu.type) != Obu) { return {}; } - return static_cast>(obu); + return static_cast>(obu); } struct av1C_header { @@ -676,22 +65,6 @@ struct av1C_header { }; //------------------------------------------------------------------------------------------------------------ -constexpr auto leb128(std::span data) noexcept -> mbmff::parsed -{ - std::uint64_t value = 0; - std::uint32_t leb128_bytes = 0; - for (int i = 0; i < 8; i++) { - std::uint8_t leb128_byte = static_cast(data[i]); - - value |= static_cast(leb128_byte & 0x7f) << (i * 7); - leb128_bytes += 1; - if (!(leb128_byte & 0x80)) { - break; - } - } - return {value, leb128_bytes}; -} - constexpr auto parse_obu(std::span data) noexcept -> std::expected { any_obu_view header{}; @@ -956,227 +329,6 @@ constexpr auto basic_obu_view::header() const noexcept -> ob //------------------------------------------------------------------------------------------------------------ // Frame header -constexpr void mark_ref_frames(obu_context& context, std::size_t id_len) noexcept -{ - auto diff_len = std::uint32_t(context.sequence_header.delta_frame_id_length_minus_2) + 2; - for (std::uint32_t i = 0; i < NUM_REF_FRAMES; i++) { - if (context.current_frame_id > (1ull << diff_len)) { - if (context.ref[i].frame_id > context.current_frame_id - || context.ref[i].frame_id < context.current_frame_id - (1ull << diff_len)) { - context.ref[i].valid = false; - } - } else { - if (context.ref[i].frame_id > context.current_frame_id - && context.ref[i].frame_id < ((1ull << id_len) + context.current_frame_id - (1ull << diff_len))) { - context.ref[i].valid = false; - } - } - } -} - -constexpr void compute_image_size(obu_context& context) noexcept -{ - context.mi_cols = 2u * ((context.frame_width + 7) >> 3); - context.mi_rows = 2u * ((context.frame_height + 7) >> 3); -} - -constexpr void superres_params(obu_frame_header& frame_header, obu_context& context, bit_reader& reader) noexcept -{ - if (context.sequence_header.enable_superres) { - frame_header.use_superres = reader.read_bits(1); - } - - std::uint32_t superres_denom = SUPERRES_NUM; - if (frame_header.use_superres) { - frame_header.coded_denom = reader.read_bits(3); - superres_denom = std::uint32_t(frame_header.coded_denom) + SUPERRES_DENOM_MIN; - } - - context.upscaled_width = context.frame_width; - context.frame_width = (context.upscaled_width * SUPERRES_NUM + (superres_denom / 2)) / superres_denom; -} - -constexpr void frame_size(obu_frame_header& frame_header, obu_context& context, bit_reader& reader) noexcept -{ - if (frame_header.frame_size_override_flag) { - frame_header.frame_width_minus_1 = reader.read_bits( - std::uint16_t(context.sequence_header.frame_width_bits_minus_1) + 1 - ); - frame_header.frame_height_minus_1 = reader.read_bits( - std::uint16_t(context.sequence_header.frame_height_bits_minus_1) + 1 - ); - } else { - frame_header.frame_width_minus_1 = context.sequence_header.max_frame_width_minus_1; - frame_header.frame_height_minus_1 = context.sequence_header.max_frame_height_minus_1; - } - - context.frame_width = std::uint32_t(frame_header.frame_width_minus_1) + 1; - context.frame_height = std::uint32_t(frame_header.frame_height_minus_1) + 1; - - superres_params(frame_header, context, reader); - compute_image_size(context); -} - -constexpr void render_size(obu_frame_header& frame_header, obu_context& context, bit_reader& reader) noexcept -{ - frame_header.render_and_frame_size_different = reader.read_bits(1); - if (frame_header.render_and_frame_size_different) { - frame_header.render_width_minus_1 = reader.read_bits(16); - frame_header.render_height_minus_1 = reader.read_bits(16); - - context.render_width = std::uint32_t(frame_header.render_width_minus_1) + 1; - context.render_height = std::uint32_t(frame_header.render_height_minus_1) + 1; - } else { - context.render_width = context.upscaled_width; - context.render_height = context.frame_height; - } -} - -// Converted from cbs_syntax_template.c of ffmpeg -constexpr auto get_relative_dist(const obu_sequence_header& seq, std::uint32_t a, std::uint32_t b) noexcept - -> std::int32_t -{ - if (!seq.enable_order_hint) { - return 0; - } - std::uint32_t diff = a - b; - std::uint32_t m = 1 << seq.order_hint_bits_minus_1; - diff = (diff & (m - 1)) - (diff & m); - return diff; -} - -constexpr void set_frame_refs(obu_frame_header& frame_header, obu_context& context) noexcept -{ - const obu_sequence_header& seq = context.sequence_header; - constexpr ref_frame ref_frame_list[NUM_REF_FRAMES - 2] = { - ref_frame::LAST2_FRAME, - ref_frame::LAST3_FRAME, - ref_frame::BWDREF_FRAME, - ref_frame::ALTREF2_FRAME, - ref_frame::ALTREF_FRAME, - }; - - std::int8_t ref_frame_idx[REFS_PER_FRAME]{}; - std::int8_t used_frame[NUM_REF_FRAMES]{}; - - ref_frame_idx[+ref_frame::LAST_FRAME - +ref_frame::LAST_FRAME] = frame_header.last_frame_idx; - ref_frame_idx[+ref_frame::GOLDEN_FRAME - +ref_frame::LAST_FRAME] = frame_header.gold_frame_idx; - - used_frame[frame_header.last_frame_idx] = 1; - used_frame[frame_header.gold_frame_idx] = 1; - - std::uint32_t cur_frame_hint = 1u << (seq.order_hint_bits_minus_1); - std::int16_t shifted_order_hints[NUM_REF_FRAMES]; - for (std::uint32_t i = 0; i < NUM_REF_FRAMES; i++) { - shifted_order_hints[i] = cur_frame_hint - + get_relative_dist(seq, context.ref[i].order_hint, frame_header.order_hint); - } - - std::int16_t latest_order_hint = shifted_order_hints[frame_header.last_frame_idx]; - std::int16_t earliest_order_hint = shifted_order_hints[frame_header.gold_frame_idx]; - - std::int8_t ref = 0; - for (std::uint32_t i = 0; i < NUM_REF_FRAMES; i++) { - int hint = shifted_order_hints[i]; - if (!used_frame[i] && hint >= cur_frame_hint && (ref < 0 || hint >= latest_order_hint)) { - ref = i; - latest_order_hint = hint; - } - } - if (ref >= 0) { - ref_frame_idx[+ref_frame::ALTREF_FRAME - +ref_frame::LAST_FRAME] = ref; - used_frame[ref] = 1; - } - - ref = 0; - for (std::uint32_t i = 0; i < NUM_REF_FRAMES; i++) { - int hint = shifted_order_hints[i]; - if (!used_frame[i] && hint >= cur_frame_hint && (ref < 0 || hint < earliest_order_hint)) { - ref = i; - earliest_order_hint = hint; - } - } - if (ref >= 0) { - ref_frame_idx[+ref_frame::BWDREF_FRAME - +ref_frame::LAST_FRAME] = ref; - used_frame[ref] = 1; - } - - ref = 0; - for (std::uint32_t i = 0; i < NUM_REF_FRAMES; i++) { - int hint = shifted_order_hints[i]; - if (!used_frame[i] && hint >= cur_frame_hint && (ref < 0 || hint < earliest_order_hint)) { - ref = i; - earliest_order_hint = hint; - } - } - if (ref >= 0) { - ref_frame_idx[+ref_frame::ALTREF2_FRAME - +ref_frame::LAST_FRAME] = ref; - used_frame[ref] = 1; - } - - for (std::uint32_t i = 0; i < REFS_PER_FRAME - 2; i++) { - auto ref_frame = ref_frame_list[i]; - if (ref_frame_idx[+ref_frame - +ref_frame::LAST_FRAME] < 0) { - ref = 0; - for (std::uint32_t j = 0; j < NUM_REF_FRAMES; j++) { - int hint = shifted_order_hints[j]; - if (!used_frame[j] && hint < cur_frame_hint && (ref < 0 || hint >= latest_order_hint)) { - ref = j; - latest_order_hint = hint; - } - } - if (ref >= 0) { - ref_frame_idx[+ref_frame - +ref_frame::LAST_FRAME] = ref; - used_frame[ref] = 1; - } - } - } - - ref = 0; - for (std::uint32_t i = 0; i < NUM_REF_FRAMES; i++) { - int hint = shifted_order_hints[i]; - if (ref < 0 || hint < earliest_order_hint) { - ref = i; - earliest_order_hint = hint; - } - } - for (std::uint32_t i = 0; i < REFS_PER_FRAME; i++) { - if (ref_frame_idx[i] < 0) { - ref_frame_idx[i] = ref; - } - frame_header.ref_frame_indices_data.set_ref_frame_index(i, ref_frame_idx[i]); - } -} - -constexpr void frame_size_with_refs(obu_frame_header& frame_header, obu_context& context, bit_reader& reader) noexcept -{ - std::uint8_t found_ref = 0; - for (std::uint32_t i = 0; i < REFS_PER_FRAME; i++) { - if (found_ref = reader.read_bits(1)) { - auto& ref_frame = context.ref[frame_header.ref_frame_indices_data.ref_frame_index(i)]; - frame_header.frame_width_minus_1 = ref_frame.upscaled_width - 1; - frame_header.frame_height_minus_1 = ref_frame.frame_height - 1; - frame_header.render_width_minus_1 = ref_frame.render_width - 1; - frame_header.render_height_minus_1 = ref_frame.render_height - 1; - - context.upscaled_width = ref_frame.upscaled_width; - context.frame_width = ref_frame.upscaled_width; - context.frame_height = ref_frame.frame_height; - context.render_width = ref_frame.render_width; - context.render_height = ref_frame.render_height; - break; - } - } - - if (!found_ref) { - frame_size(frame_header, context, reader); - render_size(frame_header, context, reader); - } else { - superres_params(frame_header, context, reader); - compute_image_size(context); - } -} - constexpr auto basic_obu_view::header(obu_context& context) const noexcept -> obu_frame_header { obu_frame_header result{}; @@ -1224,7 +376,7 @@ constexpr auto basic_obu_view::header(obu_context& conte result.frame_type = static_cast(reader.read_bits(2)); bool frame_is_intra = (result.frame_type == frame_type::KEY_FRAME) - || (result.frame_type == frame_type::INTRA_ONLY_FRAME); + || (result.frame_type == frame_type::INTRA_ONLY_FRAME); result.show_frame = reader.read_bits(1); @@ -1366,8 +518,8 @@ constexpr auto basic_obu_view::header(obu_context& conte std::uint32_t(sequence_header.delta_frame_id_length_minus_2) + 2 ) + 1; - result - .expectedFrameId[i] = ((result.current_frame_id + (1ull << id_len) - delta_frame_id) % (1ull << id_len)); + result.expectedFrameId + [i] = ((result.current_frame_id + (1ull << id_len) - delta_frame_id) % (1ull << id_len)); } } @@ -1379,11 +531,51 @@ constexpr auto basic_obu_view::header(obu_context& conte } if (!result.force_integer_mv) { - result.allow_high_precision_mv = reader.read_bits(1); + result.allow_high_precision_mv = reader.read_flag(); + } + + read_interpolation_filter(result, context, reader); + result.is_motion_mode_switchable = reader.read_flag(); + + if (!result.error_resilient_mode && sequence_header.enable_ref_frame_mvs) { + result.use_ref_frame_mvs = reader.read_flag(); } - // read interpolation filter + for (std::uint32_t i = 0; i < REFS_PER_FRAME; i++) { + std::uint32_t ref_frame = +ref_frame::LAST_FRAME + i; + std::uint32_t hint = context.ref[result.ref_frame_indices_data.ref_frame_index(i)].order_hint; + context.order_hints[ref_frame] = hint; + + if (!sequence_header.enable_order_hint) { + context.ref_frame_sign_bias[ref_frame] = false; + } else { + context.ref_frame_sign_bias[ref_frame] = get_relative_dist(sequence_header, hint, result.order_hint) + > 0; + } + } + } + + if (!sequence_header.reduced_still_picture_header && !result.disable_cdf_update) { + result.disable_frame_end_update_cdf = reader.read_flag(); } + + // We don't care + if (result.primary_ref_frame == PRIMARY_REF_NONE) { + // init_non_coeff_cdfs + // setup_past_independence + } else { + // load_cdfs(ref_frame_idx[primary_ref_frame]) + // load_previous( ) + } + + // Also don't care + if (result.use_ref_frame_mvs) { + // motion_field_estimation() + } + + tile_info(result, context, reader); + quantization_params(result, context, reader); + segmentation_params(result, context, reader); } //------------------------------------------------------------------------------------------------------------ diff --git a/include/mbmff/av1/av1_enums.hpp b/include/mbmff/av1/av1_enums.hpp new file mode 100644 index 0000000..37eee1e --- /dev/null +++ b/include/mbmff/av1/av1_enums.hpp @@ -0,0 +1,416 @@ +#pragma once +#include +#include + +namespace mbmff::av1 { +enum class obu_type : std::uint8_t { + sequence_header = 1, + temporal_delimiter = 2, + frame_header = 3, + tile_group = 4, + metadata = 5, + frame = 6, + redundant_frame_header = 7, + tile_list = 8, + // 9-14 are reserved + padding = 15, // special type used for padding OBUs that may be present in the stream. These should be ignored by + // decoders. +}; +constexpr bool operator==(obu_type type, std::uint8_t value) noexcept +{ + return static_cast(type) == value; +} +constexpr bool operator==(std::uint8_t value, av1::obu_type type) noexcept +{ + return type == value; +} + +constexpr static auto to_string(av1::obu_type type) noexcept -> std::string_view +{ + switch (type) { + case obu_type::sequence_header: + return "Sequence Header"; + case obu_type::temporal_delimiter: + return "Temporal Delimiter"; + case obu_type::frame_header: + return "Frame Header"; + case obu_type::tile_group: + return "Tile Group"; + case obu_type::metadata: + return "Metadata"; + case obu_type::frame: + return "Frame"; + case obu_type::redundant_frame_header: + return "Redundant Frame Header"; + case obu_type::tile_list: + return "Tile List"; + case obu_type::padding: + return "Padding"; + default: + return "Unknown OBU Type"; + } +} + +enum class color_primaries : std::uint8_t { + CP_RESERVED_0 = 0, + CP_BT_709 = 1, + CP_UNSPECIFIED = 2, + CP_RESERVED_3 = 3, + CP_BT_470_M = 4, + CP_BT_470_B_G = 5, + CP_BT_601 = 6, + CP_SMPTE_240 = 7, + CP_GENERIC_FILM = 8, + CP_BT_2020 = 9, + CP_XYZ = 10, + CP_SMPTE_431 = 11, + CP_SMPTE_432 = 12, + CP_EBU_3213 = 22, +}; + +constexpr auto to_string(av1::color_primaries primaries) noexcept -> std::string_view +{ + switch (primaries) { + case av1::color_primaries::CP_RESERVED_0: + return "Reserved (0)"; + case av1::color_primaries::CP_BT_709: + return "BT.709"; + case av1::color_primaries::CP_UNSPECIFIED: + return "Unspecified"; + case av1::color_primaries::CP_RESERVED_3: + return "Reserved (3)"; + case av1::color_primaries::CP_BT_470_M: + return "BT.470 System M (historical)"; + case av1::color_primaries::CP_BT_470_B_G: + return "BT.470 System B/G (historical)"; + case av1::color_primaries::CP_BT_601: + return "BT.601"; + case av1::color_primaries::CP_SMPTE_240: + return "SMPTE 240M"; + case av1::color_primaries::CP_GENERIC_FILM: + return "Generic film (color filters using illuminant C)"; + case av1::color_primaries::CP_BT_2020: + return "BT.2020, BT.2100"; + case av1::color_primaries::CP_XYZ: + return "SMPTE 428 (CIE 1921 XYZ)"; + case av1::color_primaries::CP_SMPTE_431: + return "SMPTE RP 431-2"; + case av1::color_primaries::CP_SMPTE_432: + return "SMPTE EG 432-1"; + case av1::color_primaries::CP_EBU_3213: + return "EBU Tech 3213-E"; + default: + return "Unknown color primaries"; + } +} + +enum class transfer_characteristics : std::uint8_t { + TC_RESERVED_0 = 0, + TC_BT_709 = 1, + TC_UNSPECIFIED = 2, + TC_RESERVED_3 = 3, + TC_BT_470_M = 4, + TC_BT_470_B_G = 5, + TC_BT_601 = 6, + TC_SMPTE_240 = 7, + TC_LINEAR = 8, + TC_LOG_100 = 9, + TC_LOG_100_SQRT10 = 10, + TC_IEC_61966 = 11, + TC_BT_1361 = 12, + TC_SRGB = 13, + TC_BT_2020_10_BIT = 14, + TC_BT_2020_12_BIT = 15, + TC_SMPTE_2084 = 16, + TC_SMPTE_428 = 17, + TC_HLG = 18, +}; + +constexpr auto to_string(av1::transfer_characteristics tc) noexcept -> std::string_view +{ + switch (tc) { + case av1::transfer_characteristics::TC_RESERVED_0: + return "Reserved (0)"; + case av1::transfer_characteristics::TC_BT_709: + return "BT.709"; + case av1::transfer_characteristics::TC_UNSPECIFIED: + return "Unspecified"; + case av1::transfer_characteristics::TC_RESERVED_3: + return "Reserved (3)"; + case av1::transfer_characteristics::TC_BT_470_M: + return "BT.470 System M (historical)"; + case av1::transfer_characteristics::TC_BT_470_B_G: + return "BT.470 System B/G (historical)"; + case av1::transfer_characteristics::TC_BT_601: + return "BT.601"; + case av1::transfer_characteristics::TC_SMPTE_240: + return "SMPTE 240M"; + case av1::transfer_characteristics::TC_LINEAR: + return "Linear"; + case av1::transfer_characteristics::TC_LOG_100: + return "Logarithmic (100:1 range)"; + case av1::transfer_characteristics::TC_LOG_100_SQRT10: + return "Logarithmic (100 * sqrt(10):1 range)"; + case av1::transfer_characteristics::TC_IEC_61966: + return "IEC 61966-2-4"; + case av1::transfer_characteristics::TC_BT_1361: + return "BT.1361"; + case av1::transfer_characteristics::TC_SRGB: + return "sRGB or sYCC"; + case av1::transfer_characteristics::TC_BT_2020_10_BIT: + return "BT.2020 10-bit system"; + case av1::transfer_characteristics::TC_BT_2020_12_BIT: + return "BT.2020 12-bit system"; + case av1::transfer_characteristics::TC_SMPTE_2084: + return "SMPTE ST 2084, ITU BT.2100 PQ"; + case av1::transfer_characteristics::TC_SMPTE_428: + return "SMPTE ST 428"; + case av1::transfer_characteristics::TC_HLG: + return "BT.2100 HLG, ARIB STD-B67"; + default: + return "Unknown transfer characteristics"; + } +} + +enum class matrix_coefficients : std::uint8_t { + MC_IDENTITY = 0, + MC_BT_709 = 1, + MC_UNSPECIFIED = 2, + MC_RESERVED_3 = 3, + MC_FCC = 4, + MC_BT_470_B_G = 5, + MC_BT_601 = 6, + MC_SMPTE_240 = 7, + MC_SMPTE_YCGCO = 8, + MC_BT_2020_NCL = 9, + MC_BT_2020_CL = 10, + MC_SMPTE_2085 = 11, + MC_CHROMAT_NCL = 12, + MC_CHROMAT_CL = 13, + MC_ICTCP = 14, +}; + +constexpr auto to_string(av1::matrix_coefficients mc) noexcept -> std::string_view +{ + switch (mc) { + case av1::matrix_coefficients::MC_IDENTITY: + return "Identity"; + case av1::matrix_coefficients::MC_BT_709: + return "BT.709"; + case av1::matrix_coefficients::MC_UNSPECIFIED: + return "Unspecified"; + case av1::matrix_coefficients::MC_RESERVED_3: + return "Reserved (3)"; + case av1::matrix_coefficients::MC_FCC: + return "US FCC 73.628"; + case av1::matrix_coefficients::MC_BT_470_B_G: + return "BT.470 System B/G (historical)"; + case av1::matrix_coefficients::MC_BT_601: + return "BT.601"; + case av1::matrix_coefficients::MC_SMPTE_240: + return "SMPTE 240M"; + case av1::matrix_coefficients::MC_SMPTE_YCGCO: + return "YCgCo"; + case av1::matrix_coefficients::MC_BT_2020_NCL: + return "BT.2020 non-constant luminance system"; + case av1::matrix_coefficients::MC_BT_2020_CL: + return "BT.2020 constant luminance system"; + case av1::matrix_coefficients::MC_SMPTE_2085: + return "SMPTE ST 2085 YDzDx"; + case av1::matrix_coefficients::MC_CHROMAT_NCL: + return "Chromaticity-derived non-constant luminance system"; + case av1::matrix_coefficients::MC_CHROMAT_CL: + return "Chromaticity-derived constant luminance system"; + case av1::matrix_coefficients::MC_ICTCP: + return "ICTCP (BT.2100)"; + default: + return "Unknown matrix coefficients"; + } +} + +enum class chroma_sample_position : std::uint8_t { + CSP_UNKNOWN = 0, + CSP_VERTICAL = 1, + CSP_COLOCATED = 2, + CSP_RESERVED = 3, +}; + +constexpr auto to_string(av1::chroma_sample_position csp) noexcept -> std::string_view +{ + switch (csp) { + case av1::chroma_sample_position::CSP_UNKNOWN: + return "Unknown"; + case av1::chroma_sample_position::CSP_VERTICAL: + return "Vertical"; + case av1::chroma_sample_position::CSP_COLOCATED: + return "Colocated"; + case av1::chroma_sample_position::CSP_RESERVED: + return "Reserved"; + default: + return "Invalid chroma sample position"; + } +} + +enum class metadata_type : std::uint32_t { + METADATA_TYPE_HDR_CLL = 1, + METADATA_TYPE_HDR_MDCV = 2, + METADATA_TYPE_SCALABILITY = 3, + METADATA_TYPE_ITUT_T35 = 4, + METADATA_TYPE_TIMECODE = 5, +}; + +constexpr auto to_string(av1::metadata_type type) noexcept -> std::string_view +{ + switch (type) { + case av1::metadata_type::METADATA_TYPE_ITUT_T35: + return "ITU-T T.35"; + case av1::metadata_type::METADATA_TYPE_HDR_CLL: + return "HDR Content Light Level (CLL)"; + case av1::metadata_type::METADATA_TYPE_HDR_MDCV: + return "HDR Mastering Display Color Volume (MDCV)"; + case av1::metadata_type::METADATA_TYPE_SCALABILITY: + return "Scalability"; + case av1::metadata_type::METADATA_TYPE_TIMECODE: + return "Timecode"; + default: + return "Unknown metadata type"; + } +} + +enum class frame_type : std::uint8_t { + KEY_FRAME, + INTER_FRAME, + INTRA_ONLY_FRAME, + SWITCH_FRAME, +}; + +constexpr auto to_string(av1::frame_type type) noexcept -> std::string_view +{ + switch (type) { + case frame_type::KEY_FRAME: + return "Key Frame"; + case frame_type::INTER_FRAME: + return "Inter Frame"; + case frame_type::INTRA_ONLY_FRAME: + return "Intra-only Frame"; + case frame_type::SWITCH_FRAME: + return "Switch Frame"; + default: + return "Unknown frame type"; + } +} + +enum class ref_frame : std::int8_t { + NONE = -1, + INTRA_FRAME = 0, + LAST_FRAME = 1, + LAST2_FRAME = 2, + LAST3_FRAME = 3, + GOLDEN_FRAME = 4, + BWDREF_FRAME = 5, + ALTREF2_FRAME = 6, + ALTREF_FRAME = 7, +}; + +constexpr auto to_string(av1::ref_frame ref) noexcept -> std::string_view +{ + switch (ref) { + case av1::ref_frame::NONE: + return "None"; + case av1::ref_frame::INTRA_FRAME: + return "Intra Frame"; + case av1::ref_frame::LAST_FRAME: + return "Last Frame"; + case av1::ref_frame::LAST2_FRAME: + return "Last2 Frame"; + case av1::ref_frame::LAST3_FRAME: + return "Last3 Frame"; + case av1::ref_frame::GOLDEN_FRAME: + return "Golden Frame"; + case av1::ref_frame::BWDREF_FRAME: + return "BWDREF Frame"; + case av1::ref_frame::ALTREF2_FRAME: + return "ALTREF2 Frame"; + case av1::ref_frame::ALTREF_FRAME: + return "ALTREF Frame"; + default: + return "Unknown reference frame"; + } +} + +constexpr std::int8_t operator+(av1::ref_frame ref) noexcept +{ + return std::to_underlying(ref); +} + +enum class interpolation_filter : std::uint8_t { + EIGHTTAP = 0, + EIGHTTAP_SMOOTH = 1, + EIGHTTAP_SHARP = 2, + BILINEAR = 3, + SWITCHABLE = 4, +}; + +constexpr auto to_string(av1::interpolation_filter filter) noexcept -> std::string_view +{ + switch (filter) { + case av1::interpolation_filter::EIGHTTAP: + return "8-tap"; + case av1::interpolation_filter::EIGHTTAP_SMOOTH: + return "8-tap smooth"; + case av1::interpolation_filter::EIGHTTAP_SHARP: + return "8-tap sharp"; + case av1::interpolation_filter::BILINEAR: + return "Bilinear"; + case av1::interpolation_filter::SWITCHABLE: + return "Switchable"; + default: + return "Unknown interpolation filter"; + } +} + +// Value that indicates the allow_screen_content_tools syntax element is coded +static constexpr std::uint8_t SELECT_SCREEN_CONTENT_TOOLS = 2; + +// Value that indicates the force_integer_mv syntax element is coded +static constexpr std::uint8_t SELECT_INTEGER_MV = 2; + +// The maximum number of reference frames that can be used for inter prediction in AV1. +static constexpr std::uint32_t NUM_REF_FRAMES = 8; + +// Value of primary_ref_frame indicating that there is no primary reference frame +static constexpr std::uint8_t PRIMARY_REF_NONE = 7; + +// Smallest denominator for upscaling ratio +static constexpr std::uint8_t SUPERRES_DENOM_MIN = 9; + +// Smallest denominator for upscaling ratio +static constexpr std::uint8_t SUPERRES_NUM = 8; + +// Smallest denominator for upscaling ratio +static constexpr std::uint8_t REFS_PER_FRAME = 8; + +// Maximum width of a tile in units of luma samples +static constexpr std::uint32_t MAX_TILE_WIDTH = 4096; + +// Maximum area of a tile in units of luma samples +static constexpr std::uint32_t MAX_TILE_AREA = 4096 * 2304; + +// Maximum number of tile rows +static constexpr std::uint32_t MAX_TILE_ROWS = 64; + +// Maximum number of tile columns +static constexpr std::uint32_t MAX_TILE_COLS = 64; + +// Number of segments allowed in segmentation map +static constexpr std::uint32_t MAX_SEGMENTS = 8; + +// Number of segment features +static constexpr std::uint32_t SEG_LVL_MAX = 8; + +} // namespace mbmff::av1 + +// Bring to_string functions into mbmff namespace for easier access when formatting +namespace mbmff { +using av1::to_string; +} diff --git a/include/mbmff/av1/av1_funcs.hpp b/include/mbmff/av1/av1_funcs.hpp new file mode 100644 index 0000000..4b16712 --- /dev/null +++ b/include/mbmff/av1/av1_funcs.hpp @@ -0,0 +1,447 @@ +#pragma once +#include "../common.hpp" +#include "av1_structs.hpp" + +namespace mbmff::av1 { +constexpr auto leb128(std::span data) noexcept -> mbmff::parsed +{ + std::uint64_t value = 0; + std::uint32_t leb128_bytes = 0; + for (int i = 0; i < 8; i++) { + std::uint8_t leb128_byte = static_cast(data[i]); + + value |= static_cast(leb128_byte & 0x7f) << (i * 7); + leb128_bytes += 1; + if (!(leb128_byte & 0x80)) { + break; + } + } + return {value, leb128_bytes}; +} + +static std::uint32_t tile_log2(std::uint32_t blksize, std::uint32_t target) +{ + std::uint32_t k = 0; + for (; (blksize << k) < target; k++) + ; + return k; +} + +constexpr void mark_ref_frames(av1::obu_context& context, std::size_t id_len) noexcept +{ + auto diff_len = std::uint32_t(context.sequence_header.delta_frame_id_length_minus_2) + 2; + for (std::uint32_t i = 0; i < NUM_REF_FRAMES; i++) { + if (context.current_frame_id > (1ull << diff_len)) { + if (context.ref[i].frame_id > context.current_frame_id + || context.ref[i].frame_id < context.current_frame_id - (1ull << diff_len)) { + context.ref[i].valid = false; + } + } else { + if (context.ref[i].frame_id > context.current_frame_id + && context.ref[i].frame_id < ((1ull << id_len) + context.current_frame_id - (1ull << diff_len))) { + context.ref[i].valid = false; + } + } + } +} + +constexpr void compute_image_size(av1::obu_context& context) noexcept +{ + context.mi_cols = 2u * ((context.frame_width + 7) >> 3); + context.mi_rows = 2u * ((context.frame_height + 7) >> 3); +} + +constexpr void superres_params( + av1::obu_frame_header& frame_header, + av1::obu_context& context, + mbmff::bit_reader& reader +) noexcept +{ + if (context.sequence_header.enable_superres) { + frame_header.use_superres = reader.read_bits(1); + } + + std::uint32_t superres_denom = SUPERRES_NUM; + if (frame_header.use_superres) { + frame_header.coded_denom = reader.read_bits(3); + superres_denom = std::uint32_t(frame_header.coded_denom) + SUPERRES_DENOM_MIN; + } + + context.upscaled_width = context.frame_width; + context.frame_width = (context.upscaled_width * SUPERRES_NUM + (superres_denom / 2)) / superres_denom; +} + +constexpr void frame_size( + av1::obu_frame_header& frame_header, + av1::obu_context& context, + mbmff::bit_reader& reader +) noexcept +{ + if (frame_header.frame_size_override_flag) { + frame_header.frame_width_minus_1 = reader.read_bits( + std::uint16_t(context.sequence_header.frame_width_bits_minus_1) + 1 + ); + frame_header.frame_height_minus_1 = reader.read_bits( + std::uint16_t(context.sequence_header.frame_height_bits_minus_1) + 1 + ); + } else { + frame_header.frame_width_minus_1 = context.sequence_header.max_frame_width_minus_1; + frame_header.frame_height_minus_1 = context.sequence_header.max_frame_height_minus_1; + } + + context.frame_width = std::uint32_t(frame_header.frame_width_minus_1) + 1; + context.frame_height = std::uint32_t(frame_header.frame_height_minus_1) + 1; + + superres_params(frame_header, context, reader); + compute_image_size(context); +} + +constexpr void render_size( + av1::obu_frame_header& frame_header, + av1::obu_context& context, + mbmff::bit_reader& reader +) noexcept +{ + frame_header.render_and_frame_size_different = reader.read_bits(1); + if (frame_header.render_and_frame_size_different) { + frame_header.render_width_minus_1 = reader.read_bits(16); + frame_header.render_height_minus_1 = reader.read_bits(16); + + context.render_width = std::uint32_t(frame_header.render_width_minus_1) + 1; + context.render_height = std::uint32_t(frame_header.render_height_minus_1) + 1; + } else { + context.render_width = context.upscaled_width; + context.render_height = context.frame_height; + } +} + +// Converted from cbs_syntax_template.c of ffmpeg +constexpr auto get_relative_dist(const av1::obu_sequence_header& seq, std::uint32_t a, std::uint32_t b) noexcept + -> std::int32_t +{ + if (!seq.enable_order_hint) { + return 0; + } + std::uint32_t diff = a - b; + std::uint32_t m = 1 << seq.order_hint_bits_minus_1; + diff = (diff & (m - 1)) - (diff & m); + return diff; +} + +constexpr void set_frame_refs(av1::obu_frame_header& frame_header, av1::obu_context& context) noexcept +{ + const obu_sequence_header& seq = context.sequence_header; + constexpr ref_frame ref_frame_list[NUM_REF_FRAMES - 2] = { + ref_frame::LAST2_FRAME, + ref_frame::LAST3_FRAME, + ref_frame::BWDREF_FRAME, + ref_frame::ALTREF2_FRAME, + ref_frame::ALTREF_FRAME, + }; + + std::int8_t ref_frame_idx[REFS_PER_FRAME]{}; + std::int8_t used_frame[NUM_REF_FRAMES]{}; + + ref_frame_idx[+ref_frame::LAST_FRAME - +ref_frame::LAST_FRAME] = frame_header.last_frame_idx; + ref_frame_idx[+ref_frame::GOLDEN_FRAME - +ref_frame::LAST_FRAME] = frame_header.gold_frame_idx; + + used_frame[frame_header.last_frame_idx] = 1; + used_frame[frame_header.gold_frame_idx] = 1; + + std::uint32_t cur_frame_hint = 1u << (seq.order_hint_bits_minus_1); + std::int16_t shifted_order_hints[NUM_REF_FRAMES]; + for (std::uint32_t i = 0; i < NUM_REF_FRAMES; i++) { + shifted_order_hints[i] = cur_frame_hint + + get_relative_dist(seq, context.ref[i].order_hint, frame_header.order_hint); + } + + std::int16_t latest_order_hint = shifted_order_hints[frame_header.last_frame_idx]; + std::int16_t earliest_order_hint = shifted_order_hints[frame_header.gold_frame_idx]; + + std::int8_t ref = 0; + for (std::uint32_t i = 0; i < NUM_REF_FRAMES; i++) { + int hint = shifted_order_hints[i]; + if (!used_frame[i] && hint >= cur_frame_hint && (ref < 0 || hint >= latest_order_hint)) { + ref = i; + latest_order_hint = hint; + } + } + if (ref >= 0) { + ref_frame_idx[+ref_frame::ALTREF_FRAME - +ref_frame::LAST_FRAME] = ref; + used_frame[ref] = 1; + } + + ref = 0; + for (std::uint32_t i = 0; i < NUM_REF_FRAMES; i++) { + int hint = shifted_order_hints[i]; + if (!used_frame[i] && hint >= cur_frame_hint && (ref < 0 || hint < earliest_order_hint)) { + ref = i; + earliest_order_hint = hint; + } + } + if (ref >= 0) { + ref_frame_idx[+ref_frame::BWDREF_FRAME - +ref_frame::LAST_FRAME] = ref; + used_frame[ref] = 1; + } + + ref = 0; + for (std::uint32_t i = 0; i < NUM_REF_FRAMES; i++) { + int hint = shifted_order_hints[i]; + if (!used_frame[i] && hint >= cur_frame_hint && (ref < 0 || hint < earliest_order_hint)) { + ref = i; + earliest_order_hint = hint; + } + } + if (ref >= 0) { + ref_frame_idx[+ref_frame::ALTREF2_FRAME - +ref_frame::LAST_FRAME] = ref; + used_frame[ref] = 1; + } + + for (std::uint32_t i = 0; i < REFS_PER_FRAME - 2; i++) { + auto ref_frame = ref_frame_list[i]; + if (ref_frame_idx[+ref_frame - +ref_frame::LAST_FRAME] < 0) { + ref = 0; + for (std::uint32_t j = 0; j < NUM_REF_FRAMES; j++) { + int hint = shifted_order_hints[j]; + if (!used_frame[j] && hint < cur_frame_hint && (ref < 0 || hint >= latest_order_hint)) { + ref = j; + latest_order_hint = hint; + } + } + if (ref >= 0) { + ref_frame_idx[+ref_frame - +ref_frame::LAST_FRAME] = ref; + used_frame[ref] = 1; + } + } + } + + ref = 0; + for (std::uint32_t i = 0; i < NUM_REF_FRAMES; i++) { + int hint = shifted_order_hints[i]; + if (ref < 0 || hint < earliest_order_hint) { + ref = i; + earliest_order_hint = hint; + } + } + for (std::uint32_t i = 0; i < REFS_PER_FRAME; i++) { + if (ref_frame_idx[i] < 0) { + ref_frame_idx[i] = ref; + } + frame_header.ref_frame_indices_data.set_ref_frame_index(i, ref_frame_idx[i]); + } +} + +constexpr void frame_size_with_refs( + av1::obu_frame_header& frame_header, + av1::obu_context& context, + mbmff::bit_reader& reader +) noexcept +{ + std::uint8_t found_ref = 0; + for (std::uint32_t i = 0; i < REFS_PER_FRAME; i++) { + if (found_ref = reader.read_bits(1)) { + auto& ref_frame = context.ref[frame_header.ref_frame_indices_data.ref_frame_index(i)]; + frame_header.frame_width_minus_1 = ref_frame.upscaled_width - 1; + frame_header.frame_height_minus_1 = ref_frame.frame_height - 1; + frame_header.render_width_minus_1 = ref_frame.render_width - 1; + frame_header.render_height_minus_1 = ref_frame.render_height - 1; + + context.upscaled_width = ref_frame.upscaled_width; + context.frame_width = ref_frame.upscaled_width; + context.frame_height = ref_frame.frame_height; + context.render_width = ref_frame.render_width; + context.render_height = ref_frame.render_height; + break; + } + } + + if (!found_ref) { + frame_size(frame_header, context, reader); + render_size(frame_header, context, reader); + } else { + superres_params(frame_header, context, reader); + compute_image_size(context); + } +} + +constexpr void read_interpolation_filter( + av1::obu_frame_header& frame_header, + av1::obu_context& context, + mbmff::bit_reader& reader +) noexcept +{ + std::uint8_t is_filter_switchable = reader.read_flag(); + if (!is_filter_switchable) { + frame_header.interpolation_filter = static_cast(reader.read_bits(2)); + } +} + +constexpr void tile_info( + av1::obu_frame_header& frame_header, + av1::obu_context& context, + mbmff::bit_reader& reader +) noexcept +{ + const auto& seq = context.sequence_header; + std::uint32_t sb_cols = seq.use_128x128_superblock ? ((context.mi_cols + 31) >> 5) : ((context.mi_cols + 15) >> 4); + std::uint32_t sb_rows = seq.use_128x128_superblock ? ((context.mi_rows + 31) >> 5) : ((context.mi_rows + 15) >> 4); + std::uint32_t sb_shift = seq.use_128x128_superblock ? 5 : 4; + + std::uint32_t sb_size = sb_shift + 2; + std::uint32_t max_tile_width_sb = MAX_TILE_WIDTH >> sb_size; + std::uint32_t max_tile_area_sb = MAX_TILE_AREA >> (2 * sb_size); + + std::uint32_t min_log2_tile_cols = tile_log2(max_tile_width_sb, sb_cols); + std::uint32_t max_log2_tile_cols = tile_log2(1, std::min(sb_cols, MAX_TILE_COLS)); + + std::uint32_t max_log2_tile_rows = tile_log2(1, std::min(sb_rows, MAX_TILE_ROWS)); + std::uint32_t min_log2_tiles = std::max(min_log2_tile_cols, tile_log2(max_tile_area_sb, sb_rows * sb_cols)); + + if (frame_header.uniform_tile_spacing_flag = reader.read_flag()) { + frame_header.tile_cols_log2 = min_log2_tile_cols; + while (frame_header.tile_cols_log2 < max_log2_tile_cols) { + if (bool increment_tile_cols_log2 = reader.read_flag()) { + frame_header.tile_cols_log2++; + } else { + break; + } + } + + std::uint32_t tile_width_sb = (sb_cols + (1u << frame_header.tile_cols_log2) - 1) + >> frame_header.tile_cols_log2; + std::uint32_t i = 0; + for (std::uint32_t start_sb = 0; start_sb < sb_cols; start_sb += tile_width_sb) { + frame_header.mi_col_starts[i++] = start_sb << sb_shift; + } + frame_header.mi_col_starts[i] = context.mi_cols; + frame_header.tile_cols = i; + + std::uint32_t min_log2_tile_rows = std::max(min_log2_tiles - frame_header.tile_cols_log2, 0u); + frame_header.tile_rows_log2 = min_log2_tile_rows; + while (frame_header.tile_rows_log2 < max_log2_tile_rows) { + if (bool increment_tile_cols_log2 = reader.read_flag()) { + frame_header.tile_rows_log2++; + } else { + break; + } + } + + std::uint32_t tile_height_sb = (sb_rows + (1 << frame_header.tile_rows_log2) - 1) + >> frame_header.tile_rows_log2; + i = 0; + for (std::uint32_t start_sb = 0; start_sb < sb_rows; start_sb += tile_height_sb) { + frame_header.mi_row_starts[i++] = start_sb << sb_shift; + } + + frame_header.mi_row_starts[i] = context.mi_rows; + frame_header.tile_rows = i; + } else { + std::uint32_t widest_tile_sb = 0; + std::uint32_t start_sb = 0; + std::uint32_t i = 0; + for (; start_sb < sb_cols; i++) { + frame_header.mi_col_starts[i] = start_sb << sb_shift; + std::uint32_t max_width = std::min(sb_cols - start_sb, max_tile_width_sb); + + frame_header.width_in_sbs_minus_1[i] = reader.ns(max_width); + std::uint32_t size_sb = frame_header.width_in_sbs_minus_1[i] + 1; + + std::uint32_t widest_tile_sb = std::max(size_sb, widest_tile_sb); + start_sb += size_sb; + } + + frame_header.mi_col_starts[i] = context.mi_cols; + frame_header.tile_cols = i; + frame_header.tile_cols_log2 = tile_log2(1, frame_header.tile_cols); + + if (min_log2_tiles > 0) { + max_tile_area_sb = (sb_rows * sb_cols) >> (min_log2_tiles + 1); + } else { + max_tile_area_sb = sb_rows * sb_cols; + } + std::uint32_t max_tile_height_sb = std::max(max_tile_area_sb / widest_tile_sb, 1u); + + + start_sb = 0; + i = 0; + for (; start_sb < sb_rows; i++) { + frame_header.mi_row_starts[i] = start_sb << sb_shift; + std::uint32_t max_height = std::min(sb_rows - start_sb, max_tile_height_sb); + frame_header.height_in_sbs_minus_1[i] = reader.ns(max_height); + std::uint32_t size_sb = frame_header.height_in_sbs_minus_1[i] + 1; + start_sb += size_sb; + } + + frame_header.mi_row_starts[i] = context.mi_rows; + frame_header.tile_rows = i; + frame_header.tile_rows_log2 = tile_log2(1, frame_header.tile_rows); + } + + if (frame_header.tile_cols_log2 > 0 || frame_header.tile_rows_log2 > 0) { + frame_header.context_update_tile_id = reader.read_bits( + frame_header.tile_cols_log2 + frame_header.tile_rows_log2 + ); + frame_header.tile_size_bytes_minus_1 = reader.read_bits(2); + } +} + +constexpr auto read_delta_q(mbmff::bit_reader& reader) -> std::int8_t +{ + std::int8_t delta_q = 0; + if (bool delta_coded = reader.read_flag()) { + delta_q = reader.su(7); + } + return delta_q; +} + + +constexpr void quantization_params( + av1::obu_frame_header& frame_header, + av1::obu_context& context, + mbmff::bit_reader& reader +) noexcept +{ + const auto& seq = context.sequence_header; + frame_header.base_q_idx = reader.read_bits(8); + frame_header.delta_q_y_dc = read_delta_q(reader); + if (seq.num_planes() > 1) { + if (seq.color_config_data.separate_uv_delta_q) { + frame_header.diff_uv_delta = reader.read_flag(); + } + + frame_header.delta_q_u_dc = read_delta_q(reader); + frame_header.delta_q_u_ac = read_delta_q(reader); + if (frame_header.diff_uv_delta) { + frame_header.delta_q_v_dc = read_delta_q(reader); + frame_header.delta_q_v_ac = read_delta_q(reader); + } else { + frame_header.delta_q_v_dc = frame_header.delta_q_u_dc; + frame_header.delta_q_v_ac = frame_header.delta_q_u_ac; + } + } + + if (frame_header.using_qmatrix = reader.read_flag()) { + frame_header.qm_y = reader.read_bits(4); + frame_header.qm_u = reader.read_bits(4); + if (!seq.color_config_data.separate_uv_delta_q) { + frame_header.qm_v = frame_header.qm_u; + } else { + frame_header.qm_v = reader.read_bits(4); + } + } +} + +constexpr void segmentation_params( + av1::obu_frame_header& frame_header, + av1::obu_context& context, + mbmff::bit_reader& reader +) noexcept +{ + frame_header.segmentation_enabled = reader.read_flag(); + if (frame_header.segmentation_enabled) { + + } +} + + +} // namespace mbmff::av1 diff --git a/include/mbmff/av1/av1_structs.hpp b/include/mbmff/av1/av1_structs.hpp new file mode 100644 index 0000000..a1c1366 --- /dev/null +++ b/include/mbmff/av1/av1_structs.hpp @@ -0,0 +1,299 @@ +#pragma once +#include "av1_enums.hpp" + +namespace mbmff::av1 { +//------------------------------------------------------------------------------------------------------------ +struct timing_info { + std::uint32_t num_units_in_display_tick = 0; + std::uint32_t time_scale = 0; + std::uint32_t equal_picture_interval = 0; + std::uint32_t num_ticks_per_picture_minus_1 = 0; +}; + +struct decoder_model_info { + std::uint8_t buffer_delay_length_minus_1 = 0; + std::uint32_t num_units_in_decoding_tick = 0; + std::uint8_t buffer_removal_time_length_minus_1 = 0; + std::uint8_t frame_presentation_time_length_minus_1 = 0; +}; + +struct operating_parameters_info { + std::uint32_t decoder_buffer_delay = 0; + std::uint32_t encoder_buffer_delay = 0; + std::uint32_t low_delay_mode_flag = 0; +}; + +struct operating_point { + std::uint16_t operating_point_idc = 0; + std::uint8_t seq_level_idx : 5 = 0; + std::uint8_t seq_tier : 1 = 0; + std::uint8_t decoder_model_present_for_this_op : 1 = 0; + std::uint8_t initial_display_delay_present_for_this_op : 1 = 0; + std::uint8_t initial_display_delay_minus_1 : 4 = 0; + + operating_parameters_info operating_parameters{}; +}; + +struct color_description { + av1::color_primaries color_primaries = av1::color_primaries::CP_UNSPECIFIED; + av1::transfer_characteristics transfer_characteristics = av1::transfer_characteristics::TC_UNSPECIFIED; + av1::matrix_coefficients matrix_coefficients = av1::matrix_coefficients::MC_UNSPECIFIED; +}; + +struct color_config { + std::uint8_t high_bitdepth : 1 = 0; + std::uint8_t twelve_bit : 1 = 0; + std::uint8_t monochrome : 1 = 0; + std::uint8_t color_description_present_flag : 1 = 0; + + std::uint8_t color_range : 1 = 0; + std::uint8_t subsampling_x : 1 = 0; + std::uint8_t subsampling_y : 1 = 0; + std::uint8_t separate_uv_delta_q : 1 = 0; + + av1::chroma_sample_position chroma_sample_position = av1::chroma_sample_position::CSP_UNKNOWN; + av1::color_description color_description_data{}; +}; + +struct obu_sequence_header { + std::uint8_t seq_profile : 3 = 0; + std::uint8_t still_picture : 1 = 0; + std::uint8_t reduced_still_picture_header : 1 = 0; + std::uint8_t timing_info_present_flag : 1 = 0; + std::uint8_t decoder_model_info_present_flag : 1 = 0; + std::uint8_t initial_display_delay_present_flag : 1 = 0; + + std::uint8_t frame_id_numbers_present_flag : 1 = 0; + std::uint8_t delta_frame_id_length_minus_2 : 4 = 0; + std::uint8_t additional_frame_id_length_minus_1 : 3 = 0; + + std::uint8_t use_128x128_superblock : 1 = 0; + std::uint8_t enable_filter_intra : 1 = 0; + std::uint8_t enable_intra_edge_filter : 1 = 0; + std::uint8_t enable_interintra_compound : 1 = 0; + std::uint8_t enable_masked_compound : 1 = 0; + std::uint8_t enable_warped_motion : 1 = 0; + std::uint8_t enable_dual_filter : 1 = 0; + std::uint8_t enable_order_hint : 1 = 0; + + std::uint8_t order_hint_bits_minus_1 : 3 = 0; + std::uint8_t enable_jnt_comp : 1 = 0; + std::uint8_t enable_ref_frame_mvs : 1 = 0; + std::uint8_t seq_choose_screen_content_tools : 1 = 0; + std::uint8_t seq_force_screen_content_tools : 2 = SELECT_SCREEN_CONTENT_TOOLS; + std::uint8_t seq_choose_integer_mv : 1 = 0; + std::uint8_t seq_force_integer_mv : 2 = SELECT_INTEGER_MV; + + std::uint8_t enable_superres : 1 = 0; + std::uint8_t enable_cdef : 1 = 0; + std::uint8_t enable_restoration : 1 = 0; + std::uint8_t film_grain_params_present : 1 = 0; + + std::uint8_t operating_points_cnt_minus_1 : 5 = 0; + std::array operating_points{}; // max 32 operating points (as per spec) + + timing_info timing_info_data{}; + decoder_model_info decoder_model_info_data{}; + color_config color_config_data{}; + + uint8_t frame_width_bits_minus_1 : 4 = 0; + uint8_t frame_height_bits_minus_1 : 4 = 0; + uint16_t max_frame_width_minus_1 = 0; + uint16_t max_frame_height_minus_1 = 0; + +public: + constexpr auto order_hint_bits() const noexcept -> std::uint8_t + { + if (reduced_still_picture_header) { + return 0; + } + + if (enable_order_hint) { + return order_hint_bits_minus_1 + 1; + } + return 0; + } + + constexpr auto num_planes() const noexcept -> std::uint8_t + { + return color_config_data.monochrome ? 1 : 3; + } +}; + +struct obu_metadata { + av1::metadata_type type = {}; + std::span payload{}; +}; + +struct frame_indices { + std::uint32_t ref_frame_indices; + +public: + constexpr auto ref_frame_index(std::size_t i) const noexcept -> std::uint8_t + { + return (ref_frame_indices >> (i * 3)) & 0x07; + } + constexpr auto set_ref_frame_index(std::size_t i, std::uint8_t index) noexcept -> void + { + ref_frame_indices &= ~(0x07 << (i * 3)); // Clear the existing index at position i + ref_frame_indices |= (index & 0x07) << (i * 3); // Set the new index at position i + } +}; + +struct ref_frame_state { + std::uint8_t valid; // RefValid + std::uint8_t order_hint; // RefOrderHint + std::uint8_t bit_depth; // RefBitDepth + av1::frame_type frame_type; // RefFrameType + + std::uint32_t upscaled_width; // RefUpscaledWidth + std::uint64_t frame_id; // RefFrameId + + std::uint32_t frame_width; // RefFrameWidth + std::uint32_t frame_height; // RefFrameHeight + std::uint32_t render_width; // RefRenderWidth + std::uint32_t render_height; // RefRenderHeight + + std::uint32_t subsampling_x; // RefSubsamplingX + std::uint32_t subsampling_y; // RefSubsamplingY +}; + +struct obu_frame_header { + std::uint8_t show_existing_frame : 1 = 0; + std::uint8_t frame_to_show_map_idx : 3 = 0; + std::uint8_t show_frame : 1 = 1; + std::uint8_t showable_frame : 1 = 0; + std::uint8_t error_resilient_mode : 1 = 1; + std::uint8_t disable_cdf_update : 1 = 0; + + std::uint8_t allow_screen_content_tools : 1 = 0; + std::uint8_t force_integer_mv : 1 = 0; + std::uint8_t frame_size_override_flag : 1 = 0; + std::uint8_t buffer_removal_time_present_flag : 1 = 0; + std::uint8_t allow_high_precision_mv : 1 = 0; + std::uint8_t primary_ref_frame : 3 = 0; + + std::uint8_t use_ref_frame_mvs : 1 = 0; + std::uint8_t allow_intrabc : 1 = 0; + std::uint8_t frame_refs_short_signaling : 1 = 0; + std::uint8_t last_frame_idx : 3 = 0; + std::uint8_t gold_frame_idx : 3 = 0; + av1::interpolation_filter interpolation_filter : 2 = av1::interpolation_filter::SWITCHABLE; + std::uint8_t is_motion_mode_switchable : 1 = 0; + std::uint8_t disable_frame_end_update_cdf : 1 = 1; + std::uint8_t uniform_tile_spacing_flag : 1 = 0; + std::uint8_t tile_size_bytes_minus_1 : 2 = 0; + std::uint8_t using_qmatrix : 1 = 0; + std::uint8_t diff_uv_delta : 1 = 0; + + std::uint8_t refresh_frame_flags = 0; + av1::frame_type frame_type = av1::frame_type::KEY_FRAME; + std::uint8_t order_hint = 0; + + // tile info + std::uint8_t tile_cols_log2 = 0; + std::uint8_t tile_rows_log2 = 0; + std::uint8_t mi_col_starts[MAX_TILE_COLS]{}; + std::uint8_t mi_row_starts[MAX_TILE_ROWS]{}; + std::uint8_t width_in_sbs_minus_1[MAX_TILE_COLS]{}; + std::uint8_t height_in_sbs_minus_1[MAX_TILE_ROWS]{}; + + std::uint16_t tile_cols = 0; + std::uint16_t tile_rows = 0; + std::uint16_t context_update_tile_id = 0; + + // quantization params + std::uint8_t base_q_idx = 0; + std::int8_t delta_q_y_dc = 0; + std::int8_t delta_q_u_dc = 0; + std::int8_t delta_q_u_ac = 0; + std::int8_t delta_q_v_dc = 0; + std::int8_t delta_q_v_ac = 0; + std::uint8_t qm_y : 4 = 0; + std::uint8_t qm_u : 4 = 0; + std::uint8_t qm_v : 4 = 0; + + // segmrentation params + std::uint8_t segmentation_enabled : 1 = 0; + std::uint8_t segmentation_update_map : 1 = 0; + std::uint8_t segmentation_temporal_update : 1 = 0; + std::uint8_t segmentation_update_data : 1 = 0; + std::bitset feature_enabled[SEG_LVL_MAX]; + std::int16_t feature_value[MAX_SEGMENTS][SEG_LVL_MAX]; + + // frame size + std::uint16_t frame_width_minus_1 = 0; + std::uint16_t frame_height_minus_1 = 0; + + // superres params + std::uint8_t use_superres : 1 = 0; + std::uint8_t coded_denom : 3 = 0; + + // render size + std::uint8_t render_and_frame_size_different : 1 = 0; + std::uint16_t render_width_minus_1 = 0; + std::uint16_t render_height_minus_1 = 0; + + av1::frame_indices ref_frame_indices_data{}; + + std::uint64_t display_frame_id = 0; + std::uint64_t current_frame_id = 0; + std::uint64_t frame_presentation_time = 0; + std::array buffer_removal_time{}; + std::array ref_order_hint{}; + std::array expectedFrameId{}; +}; + +//------------------------------------------------------------------------------------------------------------ +struct obu_view_base { + constexpr static std::uint64_t max_obu_size = (1ULL << 32) - 1; // 4GB - 1 byte +public: + std::uint8_t forbidden_bit : 1 = 0; // should be 0 + std::uint8_t type : 4 = 0; + std::uint8_t extension_flag : 1 = 0; + std::uint8_t has_size_field : 1 = 0; + std::uint8_t reserved : 1 = 0; // should be 0 + + // obu extension header fields (if extension_flag is set) + std::uint8_t temporal_id : 3 = 0; + std::uint8_t spatial_id : 2 = 0; + std::uint8_t extension_reserved_1 : 3 = 0; // should be 0 + + // followed by payload + std::span payload{}; + +public: + constexpr operator bool() const noexcept + { + return payload.data() != nullptr; + } +}; + +//------------------------------------------------------------------------------------------------------------ +struct obu_context { + // Selected by the decoder + std::uint32_t operating_point = 0; + + // Sequence header OBU, needed for parsing frame headers. + av1::obu_view_base sequence_obu_header{}; + + // Most recently parsed sequence header, needed for parsing frame headers + av1::obu_sequence_header sequence_header{}; + + std::uint8_t seen_frame_header : 1 = false; + + std::uint64_t prev_frame_id = 0; + std::uint64_t current_frame_id = 0; + std::uint32_t frame_width = 0; + std::uint32_t frame_height = 0; + std::uint32_t render_width = 0; + std::uint32_t render_height = 0; + std::uint32_t upscaled_width = 0; + std::uint32_t mi_cols = 0; + std::uint32_t mi_rows = 0; + + std::array order_hints{}; + std::bitset ref_frame_sign_bias{}; + av1::ref_frame_state ref[NUM_REF_FRAMES]{}; +}; +} // namespace mbmff::av1 diff --git a/include/mbmff/common.hpp b/include/mbmff/common.hpp index 333c55f..692b5aa 100644 --- a/include/mbmff/common.hpp +++ b/include/mbmff/common.hpp @@ -35,6 +35,16 @@ struct unexpected { std::size_t needed; }; +constexpr auto floor_log2(std::uint64_t value) noexcept -> std::uint32_t +{ + std::uint32_t log = 0; + while (value != 0) { + value >>= 1; + log++; + } + return log - 1; +} + //------------------------------------------------------------------------------------------------------------ struct bit_reader { std::span data{}; @@ -66,6 +76,11 @@ struct bit_reader { return value; } + constexpr auto read_flag() noexcept -> bool + { + return read_bits(1) != 0; + } + constexpr auto uvlc() noexcept -> std::uint32_t { std::size_t leading_zero_bits = 0; @@ -77,5 +92,30 @@ struct bit_reader { std::size_t value = read_bits(leading_zero_bits); return value + ((1u << leading_zero_bits) - 1); } + + // non-symmetric + constexpr auto ns(std::size_t n) -> std::uint32_t + { + std::uint32_t w = floor_log2(n) + 1; + std::uint32_t m = (1u << w) - n; + std::uint64_t v = read_bits(w - 1); + + if (v < m) { + return v; + } + + std::uint32_t extra_bit = read_bits(1); + return (v << 1) - m + extra_bit; + } + + constexpr auto su(std::size_t n) -> std::int32_t + { + std::uint32_t v = read_bits(n); + std::uint32_t sign_bit = 1u << (n - 1); + if (v & sign_bit) { + return static_cast(v) - 2 * sign_bit; + } + return static_cast(v); + } }; } // namespace mbmff From 18fb61ca4a3cea545b74ff8bfcf4fc7161f07651 Mon Sep 17 00:00:00 2001 From: Ilya Doroshenko Date: Mon, 15 Jun 2026 00:19:39 +0200 Subject: [PATCH 3/3] Refactored full library --- .github/workflows/ci.yml | 29 + .github/workflows/release.yml | 42 + .gitignore | 6 +- CMakeLists.txt | 53 +- README.md | 106 +-- VERSION | 1 + conanfile.py | 8 +- include/mbmff/av1.hpp | 660 ---------------- include/mbmff/av1/av1_enums.hpp | 416 ---------- include/mbmff/av1/av1_funcs.hpp | 447 ----------- include/mbmff/av1/av1_structs.hpp | 299 -------- include/mbmff/boxes/av1C.hpp | 85 +++ include/mbmff/boxes/box_view.hpp | 203 +++++ include/mbmff/boxes/common.hpp | 237 ++++++ include/mbmff/boxes/containers.hpp | 42 + include/mbmff/boxes/ftyp.hpp | 59 ++ include/mbmff/boxes/hdlr.hpp | 45 ++ include/mbmff/boxes/iinf.hpp | 51 ++ include/mbmff/boxes/iloc.hpp | 268 +++++++ include/mbmff/boxes/infe.hpp | 85 +++ include/mbmff/boxes/ipma.hpp | 170 +++++ include/mbmff/boxes/iref.hpp | 24 + include/mbmff/boxes/ispe.hpp | 41 + include/mbmff/boxes/mdat.hpp | 17 + include/mbmff/boxes/meta.hpp | 46 ++ include/mbmff/boxes/pasp.hpp | 35 + include/mbmff/boxes/pitm.hpp | 44 ++ include/mbmff/boxes/pixi.hpp | 45 ++ include/mbmff/common.hpp | 121 --- include/mbmff/mbmff.hpp | 1139 ++-------------------------- test/CMakeLists.txt | 7 - test/entry_main.cpp | 310 ++++---- test/formatters.hpp | 408 +++------- tools/amalgamate.py | 180 +++++ 34 files changed, 2222 insertions(+), 3507 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/release.yml create mode 100644 VERSION delete mode 100644 include/mbmff/av1.hpp delete mode 100644 include/mbmff/av1/av1_enums.hpp delete mode 100644 include/mbmff/av1/av1_funcs.hpp delete mode 100644 include/mbmff/av1/av1_structs.hpp create mode 100644 include/mbmff/boxes/av1C.hpp create mode 100644 include/mbmff/boxes/box_view.hpp create mode 100644 include/mbmff/boxes/common.hpp create mode 100644 include/mbmff/boxes/containers.hpp create mode 100644 include/mbmff/boxes/ftyp.hpp create mode 100644 include/mbmff/boxes/hdlr.hpp create mode 100644 include/mbmff/boxes/iinf.hpp create mode 100644 include/mbmff/boxes/iloc.hpp create mode 100644 include/mbmff/boxes/infe.hpp create mode 100644 include/mbmff/boxes/ipma.hpp create mode 100644 include/mbmff/boxes/iref.hpp create mode 100644 include/mbmff/boxes/ispe.hpp create mode 100644 include/mbmff/boxes/mdat.hpp create mode 100644 include/mbmff/boxes/meta.hpp create mode 100644 include/mbmff/boxes/pasp.hpp create mode 100644 include/mbmff/boxes/pitm.hpp create mode 100644 include/mbmff/boxes/pixi.hpp delete mode 100644 include/mbmff/common.hpp create mode 100644 tools/amalgamate.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..31a687b --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,29 @@ +name: CI + +on: + pull_request: + branches: [master] + +jobs: + build: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies + run: | + sudo apt-get update -q + sudo apt-get install -yq ninja-build + + - name: Configure + run: cmake -B build -G Ninja + + - name: Build (includes constexpr tests) + run: cmake --build build + + - name: Run tests on AVIF samples + run: | + for f in test/assets/*.avif; do + echo "=== $f ===" + ./build/test/mbmff-test "$f" + done diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..518bb56 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,42 @@ +name: Release + +on: + workflow_dispatch: + +jobs: + release: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies + run: | + sudo apt-get update -q + sudo apt-get install -yq ninja-build python3 + + - name: Configure + run: cmake -B build -G Ninja + + - name: Read version + id: version + run: echo "version=$(cat VERSION)" >> $GITHUB_OUTPUT + + - name: Build amalgamated header + run: cmake --build build --target amalgamate + + - name: Upload single header + uses: actions/upload-artifact@v4 + with: + name: mbmff-single-header + path: build/mbmff.hpp + + - name: Create Release + env: + GITHUB_TOKEN: ${{ github.token }} + run: | + VERSION="${{ steps.version.outputs.version }}" + gh release create "v${VERSION}" \ + --title "Release v${VERSION}" \ + --notes "mBMFF single-header release." \ + --draft \ + build/mbmff.hpp diff --git a/.gitignore b/.gitignore index 9491a2f..9dd0f75 100644 --- a/.gitignore +++ b/.gitignore @@ -360,4 +360,8 @@ MigrationBackup/ .ionide/ # Fody - auto-generated XML schema -FodyWeavers.xsd \ No newline at end of file +FodyWeavers.xsd + +ISOBMFF-master/ +include/mbmff/ignored/ +build/ \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 9300a17..93a5303 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,8 +1,35 @@ cmake_minimum_required(VERSION 3.22) -project(mBMFF VERSION 0.1.0 LANGUAGES CXX) +file(READ "${CMAKE_CURRENT_SOURCE_DIR}/VERSION" MBMFF_VERSION_STRING) +string(STRIP "${MBMFF_VERSION_STRING}" MBMFF_VERSION_STRING) +project(mBMFF VERSION ${MBMFF_VERSION_STRING} LANGUAGES CXX) include(GNUInstallDirs) +# --- Single-header amalgamation --- +find_package(Python3 REQUIRED COMPONENTS Interpreter) + +file(GLOB MBMFF_SOURCE_HEADERS + ${CMAKE_CURRENT_SOURCE_DIR}/include/mbmff/*.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/mbmff/boxes/*.hpp +) + +set(MBMFF_SINGLE_HEADER "${CMAKE_BINARY_DIR}/mbmff.hpp") + +add_custom_command( + OUTPUT ${MBMFF_SINGLE_HEADER} + COMMAND "${Python3_EXECUTABLE}" + "${CMAKE_CURRENT_SOURCE_DIR}/tools/amalgamate.py" + --input "${CMAKE_CURRENT_SOURCE_DIR}/include/mbmff" + --output "${MBMFF_SINGLE_HEADER}" + DEPENDS + ${MBMFF_SOURCE_HEADERS} + "${CMAKE_CURRENT_SOURCE_DIR}/tools/amalgamate.py" + COMMENT "Generating single-header mbmff.hpp" +) + +add_custom_target(amalgamate DEPENDS ${MBMFF_SINGLE_HEADER}) + +# --- Library --- add_library(mbmff INTERFACE) add_library(mbmff::mbmff ALIAS mbmff) @@ -15,7 +42,12 @@ install(TARGETS mbmff EXPORT mbmffTargets INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} ) -install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) + +# Install only the amalgamated single header for distribution +install( + FILES ${MBMFF_SINGLE_HEADER} + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/mbmff +) install(EXPORT mbmffTargets FILE mbmffTargets.cmake @@ -23,14 +55,15 @@ install(EXPORT mbmffTargets DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/mbmff ) -# Determine if this is the top-level project -if (CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) - set(MBMFF_TOP_LEVEL ON) -else() - set(MBMFF_TOP_LEVEL OFF) -endif() - -option(MBMFF_BUILD_TEST "Build tests" ${MBMFF_TOP_LEVEL}) +option(MBMFF_BUILD_TEST "Build tests" ${PROJECT_IS_TOP_LEVEL}) if (MBMFF_BUILD_TEST) add_subdirectory(test) +endif() + +option(MBMFF_ENABLE_CONSTEXPR_TEST + "Enable compile-time constexpr tests in headers" + ${PROJECT_IS_TOP_LEVEL} +) +if (MBMFF_ENABLE_CONSTEXPR_TEST) + target_compile_definitions(mbmff INTERFACE MBMFF_ENABLE_CONSTEXPR_TEST) endif() \ No newline at end of file diff --git a/README.md b/README.md index 2762922..1212b59 100644 --- a/README.md +++ b/README.md @@ -1,69 +1,77 @@ # mBMFF -mBMFF is a library that primarily serves as an ISO-BMFF parser. -For now it only supportss AVIF. -It is designed with extensibility in mind and can be extended to support additional media formats and features in future versions. +[![CI](https://img.shields.io/github/actions/workflow/status/Agrael1/mBMFF/ci.yml?branch=master&label=CI&logo=github)](https://github.com/Agrael1/mBMFF/actions/workflows/ci.yml) +[![License](https://img.shields.io/github/license/Agrael1/mBMFF)](LICENSE.txt) +[![Version](https://img.shields.io/badge/version-0.1.0-blue)](VERSION) +[![C++](https://img.shields.io/badge/C%2B%2B-20-00599C?logo=cplusplus)](CMakeLists.txt) +[![Header-only](https://img.shields.io/badge/header--only-yellowgreen)](include/mbmff) -## How it works +A lightweight, dependency-free ISOBMFF (ISO Base Media File Format) parser written in modern C++20. Parses AVIF, HEIF, MP4, and any other ISO-BMFF container. -The library operates without any dependencies, making it lightweight and easy to integrate into various projects. -It never allocates memory on its own, instead it relies on the caller to provide memory to parse. +## Features -The API is built around iterators. Boxes are parsed lazily, meaning that the library only parses the boxes that are requested by the caller. -Parsing is done in a resursive manner: +- **Zero dependencies** — no external libraries required +- **Zero allocations** — operates directly on caller-provided memory +- **Lazy parsing** — boxes are parsed on demand, skip what you don't need +- **Single-header amalgamation** — generate a single `mbmff.hpp` via the `amalgamate` CMake target for easy drop-in +- **Constexpr validation** — box structures are validated at compile time where possible (behind `MBMFF_ENABLE_CONSTEXPR_TEST`) +- **Iterators** — traverse boxes with `box_iterator` (flat or recursive) -- The caller uses box_iterator to iterate over box headers. This allows the caller to quickly skip over boxes that are not of interest. -- Then the caller can use box_cast to cast the box header to a specific box type. -- After the box is cast to the type of interest, the caller can use the box's API to access its contents, which are also parsed lazily. - -## Requirements - -- C\+\+23 or later (use of std::span, std::expected and other C++23 features) -- yes, all you need is a C++23 compliant compiler and the standard library. - -## Example - -The example is in the `test` directory, but here is a simple example of how to use the library to parse an AVIF file: +## Quick start ```cpp #include - -int main() { - std::ifstream avif_file("assets/avif_sample_8_420.avif", std::ios::binary); - if (!avif_file) { - std::cerr << "Failed to open the AVIF file.\n"; - return 1; // Failed to open the file - } - - // Read the entire file into a vector of chars - std::vector file_data((std::istreambuf_iterator(avif_file)), std::istreambuf_iterator()); - std::span file_data_span(reinterpret_cast(file_data.data()), file_data.size()); - - for (const auto& box_expected : mbmff::box_iterator(file_data_span, mbmff::iterator_flags::recursive)) { - if (!box_expected) { +#include +#include +#include + +int main(int argc, char* argv[]) +{ + std::ifstream file(argv[1], std::ios::binary); + std::vector data((std::istreambuf_iterator(file)), {}); + + for (const auto& result : mbmff::box_iterator( + std::span(reinterpret_cast(data.data()), data.size()), + mbmff::iterator_flags::recursive)) + { + if (!result) break; + auto& box = *result; + + switch (box.type()) { + case mbmff::box_type::ftyp: + std::cout << "ftyp: " << mbmff::box_cast(box).value().major_brand.view() << '\n'; + break; + case mbmff::box_type::hdlr: + std::cout << "hdlr: " << mbmff::box_cast(box).value().handler_type.view() << '\n'; + break; + default: break; - } - const auto& box = box_expected.value(); - - // Get concrete types - switch (box.box_header.type) { - case mbmff::box_type::ftyp: { - auto ftyp = mbmff::box_cast(box); - // Access the contents of the FTYP box using the ftyp variable - std::cout << "Found FTYP box!" << std::endl; - } break; } } } ``` +## Requirements + +- C++20 compiler (tested with MSVC 2022, GCC 14, Clang 18) +- C++23 for the test runner (`std::format`) + ## Consumption -the library is header only. You can: -- Copy headers directly -- Use FetchContent/CPM and consume via CMake -- Use Conan package manager +The library is header-only. Choose your path: + +| Method | Instructions | +|---|---| +| **Copy headers** | Grab `include/mbmff/` and include `` | +| **Single header** | `cmake --build build --target amalgamate` → `build/mbmff.hpp` | +| **CMake** | `add_subdirectory` or `FetchContent` → `target_link_libraries(foo PRIVATE mbmff::mbmff)` | +| **Conan** | `conan create .` → `requires = "mbmff/1.0.0"` | +| **Release** | Download single-header release | ## Project and contribution -I can only work on this project on my free time, but if there is an actual need for a super lean parsing with blazing speeds, for example for Vulkan Video, feel free to extend the library. +Built in my free time. If you need a lean, fast ISOBMFF parser — for Vulkan Video or anything else — PRs are welcome. + +## License + +MIT — see [LICENSE.txt](LICENSE.txt). diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..6e8bf73 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.1.0 diff --git a/conanfile.py b/conanfile.py index 1be128c..e845d39 100644 --- a/conanfile.py +++ b/conanfile.py @@ -11,15 +11,14 @@ class MBMFFConan(ConanFile): topics = ("bmff", "iso-bmff", "media", "header-only") settings = "os", "compiler", "build_type", "arch" - exports_sources = "CMakeLists.txt", "include/*" + exports_sources = "CMakeLists.txt", "include/*", "tools/*" def layout(self): cmake_layout(self) def generate(self): tc = CMakeToolchain(self) - # Disable building tests for conan package - tc.variables["BUILD_TESTING"] = False + tc.variables["MBMFF_BUILD_TEST"] = False tc.generate() deps = CMakeDeps(self) deps.generate() @@ -27,14 +26,13 @@ def generate(self): def build(self): cmake = CMake(self) cmake.configure() - cmake.build() + cmake.build(target="amalgamate") def package(self): cmake = CMake(self) cmake.install() def package_info(self): - # Indicate this is a header-only library self.cpp_info.bindirs = [] self.cpp_info.libdirs = [] diff --git a/include/mbmff/av1.hpp b/include/mbmff/av1.hpp deleted file mode 100644 index a50d1fd..0000000 --- a/include/mbmff/av1.hpp +++ /dev/null @@ -1,660 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include "av1/av1_funcs.hpp" - -namespace mbmff::av1 { -//------------------------------------------------------------------------------------------------------------ -using any_obu_view = struct obu_view_base; - -template -struct basic_obu_view : public obu_view_base {}; - -template <> -struct basic_obu_view : public av1::obu_view_base { - constexpr auto header() const noexcept -> av1::obu_sequence_header; -}; -template <> -struct basic_obu_view : public av1::obu_view_base { - constexpr auto header() const noexcept -> av1::obu_metadata; -}; -template <> -struct basic_obu_view : public av1::obu_view_base { - constexpr auto header(obu_context& context) const noexcept -> av1::obu_frame_header; -}; -//------------------------------------------------------------------------------------------------------------ -using obu_sequence_header_view = av1::basic_obu_view; - -//------------------------------------------------------------------------------------------------------------ -template -constexpr auto obu_cast(const av1::any_obu_view& obu) noexcept -> av1::basic_obu_view -{ - if (static_cast(obu.type) != Obu) { - return {}; - } - return static_cast>(obu); -} - -struct av1C_header { - // Byte 0 - std::uint8_t marker : 1 = 0; // should be 1 - std::uint8_t version : 7 = 0; // should be 1 - - // Byte 1 - std::uint8_t seq_profile : 3 = 0; - std::uint8_t seq_level_idx_0 : 5 = 0; - - // Byte 2 - std::uint8_t seq_tier_0 : 1 = 0; - std::uint8_t high_bitdepth : 1 = 0; - std::uint8_t twelve_bit : 1 = 0; - std::uint8_t monochrome : 1 = 0; - std::uint8_t chroma_subsampling_x : 1 = 0; - std::uint8_t chroma_subsampling_y : 1 = 0; - std::uint8_t chroma_sample_position : 2 = 0; - - // Byte 3 - std::uint8_t reserved : 3 = 0; // should be 0 - std::uint8_t initial_presentation_delay_present : 1 = 0; - std::uint8_t initial_presentation_delay_minus_one : 4 = 0; - - // followed by config OBUs - std::span config_obus{}; -}; - -//------------------------------------------------------------------------------------------------------------ -constexpr auto parse_obu(std::span data) noexcept -> std::expected -{ - any_obu_view header{}; - std::uint64_t offset = 0; - - // Byte 0: - std::uint8_t byte0 = static_cast(data[offset++]); - header.forbidden_bit = (byte0 >> 7) & 0x01; - header.type = (byte0 >> 3) & 0x0F; - header.extension_flag = (byte0 >> 2) & 0x01; - header.has_size_field = (byte0 >> 1) & 0x01; - header.reserved = byte0 & 0x01; - - if (header.forbidden_bit) { - return std::unexpected(unexpected{error_code::invalid_format}); - } - - // read extension header if present - if (header.extension_flag) { - if (data.size() < offset + 1) { - return std::unexpected(unexpected{error_code::need_more_data, offset + 1}); - } - - std::uint8_t ext_byte = static_cast(data[offset++]); - header.temporal_id = (ext_byte >> 5) & 0x07; - header.spatial_id = (ext_byte >> 3) & 0x03; - header.extension_reserved_1 = ext_byte & 0x07; - if (header.extension_reserved_1 || header.reserved) { - return std::unexpected(unexpected{error_code::invalid_format}); - } - } - - std::uint64_t obu_size = 0; - // read OBU size if present - if (header.has_size_field) { - if (data.size() < offset + 1) { - return std::unexpected(unexpected{error_code::need_more_data, offset + 1}); - } - auto [xobu_size, leb128_bytes] = leb128(data.subspan(offset)); - if (obu_size > any_obu_view::max_obu_size) { - return std::unexpected(unexpected{error_code::invalid_format}); - } - obu_size = xobu_size; - offset += leb128_bytes; - } else { - // If no size field is present, the OBU extends to the end of the data - obu_size = data.size() - offset; - } - - // place the payload span after the header and any size field - header.payload = data.subspan(offset, obu_size); - return std::expected{header}; -} - -//------------------------------------------------------------------------------------------------------------ -// Sequence Header -constexpr auto basic_obu_view::header() const noexcept -> obu_sequence_header -{ - obu_sequence_header result{}; - bit_reader reader{payload}; - - // Byte 0 - std::uint8_t read_byte = static_cast(payload[0]); - result.seq_profile = reader.read_bits(3); - result.still_picture = reader.read_bits(1); - result.reduced_still_picture_header = reader.read_bits(1); - - if (result.reduced_still_picture_header) { - result.operating_points[0].seq_level_idx = reader.read_bits(5); - } else { - result.timing_info_present_flag = reader.read_bits(1); - if (result.timing_info_present_flag) { - // Timing info - result.timing_info_data.num_units_in_display_tick = reader.read_bits(32); - result.timing_info_data.time_scale = reader.read_bits(32); - result.timing_info_data.equal_picture_interval = reader.read_bits(1); - if (result.timing_info_data.equal_picture_interval) { - result.timing_info_data.num_ticks_per_picture_minus_1 = reader.uvlc(); - } - - // Decoder model info - result.decoder_model_info_present_flag = reader.read_bits(1); - if (result.decoder_model_info_present_flag) { - result.decoder_model_info_data.buffer_delay_length_minus_1 = reader.read_bits(5); - result.decoder_model_info_data.num_units_in_decoding_tick = reader.read_bits(32); - result.decoder_model_info_data.buffer_removal_time_length_minus_1 = reader.read_bits(5); - result.decoder_model_info_data.frame_presentation_time_length_minus_1 = reader.read_bits( - 5 - ); - } - - result.initial_display_delay_present_flag = reader.read_bits(1); - - // Operating points - result.operating_points_cnt_minus_1 = reader.read_bits(5); - for (std::size_t i = 0; i <= result.operating_points_cnt_minus_1; i++) { - auto& op = result.operating_points[i]; - op.operating_point_idc = reader.read_bits(12); - op.seq_level_idx = reader.read_bits(5); - if (op.seq_level_idx > 7) { - op.seq_tier = reader.read_bits(1); - } - - // Decoding parameters info - if (result.decoder_model_info_present_flag) { - op.decoder_model_present_for_this_op = reader.read_bits(1); - if (op.decoder_model_present_for_this_op) { - std::size_t bits_for_buffer_delay = result.decoder_model_info_data.buffer_delay_length_minus_1 - + 1; - - op.operating_parameters.decoder_buffer_delay = reader.read_bits( - bits_for_buffer_delay - ); - op.operating_parameters.encoder_buffer_delay = reader.read_bits( - bits_for_buffer_delay - ); - op.operating_parameters.low_delay_mode_flag = reader.read_bits(1); - } - } - - // Initial display delay - if (result.initial_display_delay_present_flag) { - op.initial_display_delay_present_for_this_op = reader.read_bits(1); - if (op.initial_display_delay_present_for_this_op) { - op.initial_display_delay_minus_1 = reader.read_bits(4); - } - } - } - } - } - - // here by the spec we should select - // operatingPoint = choose_operating_point( ) - // OperatingPointIdc = operating_point_idc[operatingPoint] - - result.frame_width_bits_minus_1 = reader.read_bits(4); - result.frame_height_bits_minus_1 = reader.read_bits(4); - result.max_frame_width_minus_1 = reader.read_bits(result.frame_width_bits_minus_1 + 1); - result.max_frame_height_minus_1 = reader.read_bits(result.frame_height_bits_minus_1 + 1); - - if (!result.reduced_still_picture_header) { - result.frame_id_numbers_present_flag = reader.read_bits(1); - } - - if (result.frame_id_numbers_present_flag) { - result.delta_frame_id_length_minus_2 = reader.read_bits(4); - result.additional_frame_id_length_minus_1 = reader.read_bits(3); - } - - result.use_128x128_superblock = reader.read_bits(1); - result.enable_filter_intra = reader.read_bits(1); - result.enable_intra_edge_filter = reader.read_bits(1); - - if (!result.reduced_still_picture_header) { - result.enable_interintra_compound = reader.read_bits(1); - result.enable_masked_compound = reader.read_bits(1); - result.enable_warped_motion = reader.read_bits(1); - result.enable_dual_filter = reader.read_bits(1); - result.enable_order_hint = reader.read_bits(1); - - if (result.enable_order_hint) { - result.enable_jnt_comp = reader.read_bits(1); - result.enable_ref_frame_mvs = reader.read_bits(1); - } - result.seq_choose_screen_content_tools = reader.read_bits(1); - if (result.seq_choose_screen_content_tools) { - result.seq_force_screen_content_tools = reader.read_bits(1); - } - - if (result.seq_force_screen_content_tools > 0) { - result.seq_choose_integer_mv = reader.read_bits(1); - if (result.seq_choose_integer_mv) { - result.seq_force_integer_mv = reader.read_bits(1); - } - } - - if (result.enable_order_hint) { - result.order_hint_bits_minus_1 = reader.read_bits(3); - } - } - result.enable_superres = reader.read_bits(1); - result.enable_cdef = reader.read_bits(1); - result.enable_restoration = reader.read_bits(1); - - // Color config - auto& cc = result.color_config_data; - cc.high_bitdepth = reader.read_bits(1); - if (result.seq_profile == 2 && cc.high_bitdepth) { - cc.twelve_bit = reader.read_bits(1); - } - - if (result.seq_profile != 1) { - cc.monochrome = reader.read_bits(1); - } - - cc.color_description_present_flag = reader.read_bits(1); - if (cc.color_description_present_flag) { - cc.color_description_data.color_primaries = static_cast( - reader.read_bits(8) - ); - cc.color_description_data.transfer_characteristics = static_cast( - reader.read_bits(8) - ); - cc.color_description_data.matrix_coefficients = static_cast( - reader.read_bits(8) - ); - } - - if (!cc.monochrome) { - cc.color_range = reader.read_bits(1); - cc.subsampling_x = 1; - cc.subsampling_y = 1; - result.film_grain_params_present = reader.read_bits(1); - return result; - } - if (cc.color_description_data.color_primaries == av1::color_primaries::CP_BT_709 - && cc.color_description_data.transfer_characteristics == av1::transfer_characteristics::TC_SRGB - && cc.color_description_data.matrix_coefficients == av1::matrix_coefficients::MC_IDENTITY) { - cc.color_range = 1; - cc.subsampling_x = 0; - cc.subsampling_y = 0; - } else { - cc.color_range = reader.read_bits(1); - if (result.seq_profile == 0) { - cc.subsampling_x = 1; - cc.subsampling_y = 1; - } else if (result.seq_profile == 1) { - cc.subsampling_x = 0; - cc.subsampling_y = 0; - } else { - if (cc.twelve_bit) { - cc.subsampling_x = reader.read_bits(1); - if (cc.subsampling_x) { - cc.subsampling_y = reader.read_bits(1); - } - } else { - cc.subsampling_x = 1; - cc.subsampling_y = 0; - } - } - if (cc.subsampling_x && cc.subsampling_y) { - cc.chroma_sample_position = static_cast(reader.read_bits(2)); - } - } - cc.separate_uv_delta_q = reader.read_bits(1); - // End color config - - result.film_grain_params_present = reader.read_bits(1); - return result; -} - -//------------------------------------------------------------------------------------------------------------ -// Metadata -constexpr auto basic_obu_view::header() const noexcept -> obu_metadata -{ - obu_metadata result{}; - auto [value, bytes] = leb128(payload); - result.type = static_cast(value); - result.payload = payload.subspan(bytes); - return result; -} - -//------------------------------------------------------------------------------------------------------------ -// Frame header -constexpr auto basic_obu_view::header(obu_context& context) const noexcept -> obu_frame_header -{ - obu_frame_header result{}; - bit_reader reader{payload}; - const auto& seq_view = context.sequence_obu_header; - const auto& sequence_header = context.sequence_header; - - std::uint32_t id_len = 0; - if (sequence_header.frame_id_numbers_present_flag) { - id_len = std::uint32_t(sequence_header.delta_frame_id_length_minus_2) + 2u - + sequence_header.additional_frame_id_length_minus_1 + 1u; - } - std::uint32_t all_frames = (1u << NUM_REF_FRAMES) - 1; - bool frame_is_intra = true; - - if (!sequence_header.reduced_still_picture_header) { - result.show_existing_frame = reader.read_bits(1); - if (result.show_existing_frame) { - result.frame_to_show_map_idx = reader.read_bits(3); - - // Temporal point info - if (sequence_header.decoder_model_info_present_flag && sequence_header.timing_info_present_flag - && !sequence_header.timing_info_data.equal_picture_interval) { - result.frame_presentation_time = reader.read_bits( - sequence_header.decoder_model_info_data.frame_presentation_time_length_minus_1 + 1 - ); - } - - result.refresh_frame_flags = 0; - if (sequence_header.frame_id_numbers_present_flag) { - result.display_frame_id = reader.read_bits(id_len); - } - - result.frame_type = context.ref[result.frame_to_show_map_idx].frame_type; - if (result.frame_type == frame_type::KEY_FRAME) { - result.refresh_frame_flags = all_frames; // refresh all reference frames - } - - if (sequence_header.film_grain_params_present) { - // load_grain_params( frame_to_show_map_idx ) - } - - return result; - } - - result.frame_type = static_cast(reader.read_bits(2)); - bool frame_is_intra = (result.frame_type == frame_type::KEY_FRAME) - || (result.frame_type == frame_type::INTRA_ONLY_FRAME); - - result.show_frame = reader.read_bits(1); - - // Temporal point info - if (result.show_frame && sequence_header.decoder_model_info_present_flag - && sequence_header.timing_info_present_flag && !sequence_header.timing_info_data.equal_picture_interval) { - result.frame_presentation_time = reader.read_bits( - sequence_header.decoder_model_info_data.frame_presentation_time_length_minus_1 + 1 - ); - } - - if (result.show_frame) { - result.showable_frame = result.frame_type != frame_type::KEY_FRAME; - } else { - result.showable_frame = reader.read_bits(1); - } - - if (result.frame_type == frame_type::SWITCH_FRAME - || (result.frame_type == frame_type::KEY_FRAME && result.show_frame)) { - result.error_resilient_mode = 1; - } else { - result.error_resilient_mode = reader.read_bits(1); - } - } - - if (result.frame_type == frame_type::KEY_FRAME && result.show_frame) { - for (std::uint32_t i = 0; i < NUM_REF_FRAMES; i++) { - context.ref[i].valid = false; - context.ref[i].order_hint = 0; - } - for (std::uint32_t i = 0; i < REFS_PER_FRAME; i++) { - context.order_hints[+ref_frame::LAST_FRAME + i] = 0; - } - } - - result.disable_cdf_update = reader.read_bits(1); - if (sequence_header.seq_force_screen_content_tools == SELECT_SCREEN_CONTENT_TOOLS) { - result.allow_screen_content_tools = reader.read_bits(1); - } else { - result.allow_screen_content_tools = sequence_header.seq_force_screen_content_tools; - } - - if (result.allow_screen_content_tools) { - if (sequence_header.seq_force_integer_mv == SELECT_INTEGER_MV) { - result.force_integer_mv = reader.read_bits(1); - } else { - result.force_integer_mv = sequence_header.seq_force_integer_mv; - } - } - - if (frame_is_intra) { - result.force_integer_mv = 1; // force_integer_mv must be 1 for intra frames - } - - if (sequence_header.frame_id_numbers_present_flag) { - context.prev_frame_id = context.current_frame_id; - context.current_frame_id = result.current_frame_id = reader.read_bits(id_len); - mark_ref_frames(context, id_len); - } - - if (result.frame_type == frame_type::SWITCH_FRAME) { - result.frame_size_override_flag = reader.read_bits(1); - } else if (sequence_header.reduced_still_picture_header) { - result.frame_size_override_flag = 0; - } else { - result.frame_size_override_flag = reader.read_bits(1); - } - - std::uint8_t order_hint_bits = sequence_header.order_hint_bits(); - if (order_hint_bits > 0) { - result.order_hint = reader.read_bits(order_hint_bits); - } - - if (frame_is_intra || result.error_resilient_mode) { - result.primary_ref_frame = PRIMARY_REF_NONE; - } else { - result.primary_ref_frame = reader.read_bits(3); - } - - // Buffer removal time - if (sequence_header.decoder_model_info_present_flag) { - if (result.buffer_removal_time_present_flag = reader.read_bits(1)) { - for (std::size_t i = 0; i <= sequence_header.operating_points_cnt_minus_1; i++) { - if (sequence_header.operating_points[i].decoder_model_present_for_this_op) { - auto op_idc = sequence_header.operating_points[i].operating_point_idc; - auto in_temporal_layer = (op_idc >> seq_view.temporal_id) & 1; - auto in_spatial_layer = (op_idc >> (8 + seq_view.spatial_id)) & 1; - - if (op_idc == 0 || (in_temporal_layer && in_spatial_layer)) { - result.buffer_removal_time[i] = reader.read_bits( - sequence_header.decoder_model_info_data.buffer_removal_time_length_minus_1 + 1 - ); - } - } - } - } - } - - if (result.frame_type == frame_type::SWITCH_FRAME - || (result.frame_type == frame_type::KEY_FRAME && result.show_frame)) { - result.refresh_frame_flags = all_frames; // refresh all reference frames - } else { - result.refresh_frame_flags = reader.read_bits(8); - } - - if (!frame_is_intra || result.refresh_frame_flags != all_frames) { - if (result.error_resilient_mode && sequence_header.enable_order_hint) { - for (std::size_t i = 0; i < NUM_REF_FRAMES; i++) { - result.ref_order_hint[i] = reader.read_bits(order_hint_bits); - if (result.ref_order_hint[i] != context.ref[i].order_hint) { - context.ref[i].valid = false; - } - } - } - } - - if (frame_is_intra) { - frame_size(result, context, reader); - render_size(result, context, reader); - - if (result.allow_screen_content_tools && context.upscaled_width == context.frame_width) { - result.allow_intrabc = reader.read_bits(1); - } - } else { - if (sequence_header.enable_order_hint) { - if (result.frame_refs_short_signaling = reader.read_bits(1)) { - result.last_frame_idx = reader.read_bits(3); - result.gold_frame_idx = reader.read_bits(3); - set_frame_refs(result, context); - } - } - - for (std::size_t i = 0; i < REFS_PER_FRAME; i++) { - if (!result.frame_refs_short_signaling) { - result.ref_frame_indices_data.set_ref_frame_index(i, reader.read_bits(3)); - } - if (sequence_header.frame_id_numbers_present_flag) { - std::uint32_t delta_frame_id = reader.read_bits( - std::uint32_t(sequence_header.delta_frame_id_length_minus_2) + 2 - ) - + 1; - result.expectedFrameId - [i] = ((result.current_frame_id + (1ull << id_len) - delta_frame_id) % (1ull << id_len)); - } - } - - if (result.frame_size_override_flag && !result.error_resilient_mode) { - frame_size_with_refs(result, context, reader); - } else { - frame_size(result, context, reader); - render_size(result, context, reader); - } - - if (!result.force_integer_mv) { - result.allow_high_precision_mv = reader.read_flag(); - } - - read_interpolation_filter(result, context, reader); - result.is_motion_mode_switchable = reader.read_flag(); - - if (!result.error_resilient_mode && sequence_header.enable_ref_frame_mvs) { - result.use_ref_frame_mvs = reader.read_flag(); - } - - for (std::uint32_t i = 0; i < REFS_PER_FRAME; i++) { - std::uint32_t ref_frame = +ref_frame::LAST_FRAME + i; - std::uint32_t hint = context.ref[result.ref_frame_indices_data.ref_frame_index(i)].order_hint; - context.order_hints[ref_frame] = hint; - - if (!sequence_header.enable_order_hint) { - context.ref_frame_sign_bias[ref_frame] = false; - } else { - context.ref_frame_sign_bias[ref_frame] = get_relative_dist(sequence_header, hint, result.order_hint) - > 0; - } - } - } - - if (!sequence_header.reduced_still_picture_header && !result.disable_cdf_update) { - result.disable_frame_end_update_cdf = reader.read_flag(); - } - - // We don't care - if (result.primary_ref_frame == PRIMARY_REF_NONE) { - // init_non_coeff_cdfs - // setup_past_independence - } else { - // load_cdfs(ref_frame_idx[primary_ref_frame]) - // load_previous( ) - } - - // Also don't care - if (result.use_ref_frame_mvs) { - // motion_field_estimation() - } - - tile_info(result, context, reader); - quantization_params(result, context, reader); - segmentation_params(result, context, reader); -} - -//------------------------------------------------------------------------------------------------------------ -// iterator - -struct obu_iterator { - using iterator_category = std::forward_iterator_tag; - using value_type = std::expected; - using difference_type = std::ptrdiff_t; - - std::span data{}; - -public: - constexpr obu_iterator() noexcept = default; - constexpr explicit obu_iterator(std::span data) noexcept - : data(data) - {} - constexpr explicit obu_iterator(const av1C_header& header) noexcept - : obu_iterator(header.config_obus) - {} - -public: - constexpr auto begin() const noexcept -> obu_iterator - { - return *this; - } - constexpr auto end() const noexcept -> obu_iterator - { - return {}; - } - constexpr auto try_get() const noexcept -> std::expected - { - return parse_obu(data); - } - constexpr auto operator*() const noexcept -> std::expected - { - return try_get(); - } - constexpr auto operator++() noexcept -> obu_iterator& - { - if (data.empty()) { - data = {}; - return *this; - } - auto obu = try_get(); - if (!obu) { - // If parsing fails mid-stream, terminate the iteration - data = {}; - return *this; - } - - // Calculate total bytes consumed by this OBU (Header + LEB128 size + Payload) - // By taking the end of the payload and subtracting the start of our data block, - // we get the exact number of bytes this specific OBU occupied. - std::size_t bytes_consumed = (obu->payload.data() + obu->payload.size()) - data.data(); - - if (bytes_consumed >= data.size()) { - data = {}; // We reached the end - } else { - // Shrink the span from the front, moving to the next OBU - data = data.subspan(bytes_consumed); - } - - return *this; - } - constexpr auto operator++(int) noexcept -> obu_iterator - { - auto tmp = *this; - ++(*this); - return tmp; - } - constexpr bool operator==(const obu_iterator& other) const noexcept - { - // special case: both iterators are at the end (empty) - if (data.empty() && other.data.empty()) { - return true; - } - return data.data() == other.data.data() && data.size() == other.data.size(); - } -}; - -} // namespace mbmff::av1 diff --git a/include/mbmff/av1/av1_enums.hpp b/include/mbmff/av1/av1_enums.hpp deleted file mode 100644 index 37eee1e..0000000 --- a/include/mbmff/av1/av1_enums.hpp +++ /dev/null @@ -1,416 +0,0 @@ -#pragma once -#include -#include - -namespace mbmff::av1 { -enum class obu_type : std::uint8_t { - sequence_header = 1, - temporal_delimiter = 2, - frame_header = 3, - tile_group = 4, - metadata = 5, - frame = 6, - redundant_frame_header = 7, - tile_list = 8, - // 9-14 are reserved - padding = 15, // special type used for padding OBUs that may be present in the stream. These should be ignored by - // decoders. -}; -constexpr bool operator==(obu_type type, std::uint8_t value) noexcept -{ - return static_cast(type) == value; -} -constexpr bool operator==(std::uint8_t value, av1::obu_type type) noexcept -{ - return type == value; -} - -constexpr static auto to_string(av1::obu_type type) noexcept -> std::string_view -{ - switch (type) { - case obu_type::sequence_header: - return "Sequence Header"; - case obu_type::temporal_delimiter: - return "Temporal Delimiter"; - case obu_type::frame_header: - return "Frame Header"; - case obu_type::tile_group: - return "Tile Group"; - case obu_type::metadata: - return "Metadata"; - case obu_type::frame: - return "Frame"; - case obu_type::redundant_frame_header: - return "Redundant Frame Header"; - case obu_type::tile_list: - return "Tile List"; - case obu_type::padding: - return "Padding"; - default: - return "Unknown OBU Type"; - } -} - -enum class color_primaries : std::uint8_t { - CP_RESERVED_0 = 0, - CP_BT_709 = 1, - CP_UNSPECIFIED = 2, - CP_RESERVED_3 = 3, - CP_BT_470_M = 4, - CP_BT_470_B_G = 5, - CP_BT_601 = 6, - CP_SMPTE_240 = 7, - CP_GENERIC_FILM = 8, - CP_BT_2020 = 9, - CP_XYZ = 10, - CP_SMPTE_431 = 11, - CP_SMPTE_432 = 12, - CP_EBU_3213 = 22, -}; - -constexpr auto to_string(av1::color_primaries primaries) noexcept -> std::string_view -{ - switch (primaries) { - case av1::color_primaries::CP_RESERVED_0: - return "Reserved (0)"; - case av1::color_primaries::CP_BT_709: - return "BT.709"; - case av1::color_primaries::CP_UNSPECIFIED: - return "Unspecified"; - case av1::color_primaries::CP_RESERVED_3: - return "Reserved (3)"; - case av1::color_primaries::CP_BT_470_M: - return "BT.470 System M (historical)"; - case av1::color_primaries::CP_BT_470_B_G: - return "BT.470 System B/G (historical)"; - case av1::color_primaries::CP_BT_601: - return "BT.601"; - case av1::color_primaries::CP_SMPTE_240: - return "SMPTE 240M"; - case av1::color_primaries::CP_GENERIC_FILM: - return "Generic film (color filters using illuminant C)"; - case av1::color_primaries::CP_BT_2020: - return "BT.2020, BT.2100"; - case av1::color_primaries::CP_XYZ: - return "SMPTE 428 (CIE 1921 XYZ)"; - case av1::color_primaries::CP_SMPTE_431: - return "SMPTE RP 431-2"; - case av1::color_primaries::CP_SMPTE_432: - return "SMPTE EG 432-1"; - case av1::color_primaries::CP_EBU_3213: - return "EBU Tech 3213-E"; - default: - return "Unknown color primaries"; - } -} - -enum class transfer_characteristics : std::uint8_t { - TC_RESERVED_0 = 0, - TC_BT_709 = 1, - TC_UNSPECIFIED = 2, - TC_RESERVED_3 = 3, - TC_BT_470_M = 4, - TC_BT_470_B_G = 5, - TC_BT_601 = 6, - TC_SMPTE_240 = 7, - TC_LINEAR = 8, - TC_LOG_100 = 9, - TC_LOG_100_SQRT10 = 10, - TC_IEC_61966 = 11, - TC_BT_1361 = 12, - TC_SRGB = 13, - TC_BT_2020_10_BIT = 14, - TC_BT_2020_12_BIT = 15, - TC_SMPTE_2084 = 16, - TC_SMPTE_428 = 17, - TC_HLG = 18, -}; - -constexpr auto to_string(av1::transfer_characteristics tc) noexcept -> std::string_view -{ - switch (tc) { - case av1::transfer_characteristics::TC_RESERVED_0: - return "Reserved (0)"; - case av1::transfer_characteristics::TC_BT_709: - return "BT.709"; - case av1::transfer_characteristics::TC_UNSPECIFIED: - return "Unspecified"; - case av1::transfer_characteristics::TC_RESERVED_3: - return "Reserved (3)"; - case av1::transfer_characteristics::TC_BT_470_M: - return "BT.470 System M (historical)"; - case av1::transfer_characteristics::TC_BT_470_B_G: - return "BT.470 System B/G (historical)"; - case av1::transfer_characteristics::TC_BT_601: - return "BT.601"; - case av1::transfer_characteristics::TC_SMPTE_240: - return "SMPTE 240M"; - case av1::transfer_characteristics::TC_LINEAR: - return "Linear"; - case av1::transfer_characteristics::TC_LOG_100: - return "Logarithmic (100:1 range)"; - case av1::transfer_characteristics::TC_LOG_100_SQRT10: - return "Logarithmic (100 * sqrt(10):1 range)"; - case av1::transfer_characteristics::TC_IEC_61966: - return "IEC 61966-2-4"; - case av1::transfer_characteristics::TC_BT_1361: - return "BT.1361"; - case av1::transfer_characteristics::TC_SRGB: - return "sRGB or sYCC"; - case av1::transfer_characteristics::TC_BT_2020_10_BIT: - return "BT.2020 10-bit system"; - case av1::transfer_characteristics::TC_BT_2020_12_BIT: - return "BT.2020 12-bit system"; - case av1::transfer_characteristics::TC_SMPTE_2084: - return "SMPTE ST 2084, ITU BT.2100 PQ"; - case av1::transfer_characteristics::TC_SMPTE_428: - return "SMPTE ST 428"; - case av1::transfer_characteristics::TC_HLG: - return "BT.2100 HLG, ARIB STD-B67"; - default: - return "Unknown transfer characteristics"; - } -} - -enum class matrix_coefficients : std::uint8_t { - MC_IDENTITY = 0, - MC_BT_709 = 1, - MC_UNSPECIFIED = 2, - MC_RESERVED_3 = 3, - MC_FCC = 4, - MC_BT_470_B_G = 5, - MC_BT_601 = 6, - MC_SMPTE_240 = 7, - MC_SMPTE_YCGCO = 8, - MC_BT_2020_NCL = 9, - MC_BT_2020_CL = 10, - MC_SMPTE_2085 = 11, - MC_CHROMAT_NCL = 12, - MC_CHROMAT_CL = 13, - MC_ICTCP = 14, -}; - -constexpr auto to_string(av1::matrix_coefficients mc) noexcept -> std::string_view -{ - switch (mc) { - case av1::matrix_coefficients::MC_IDENTITY: - return "Identity"; - case av1::matrix_coefficients::MC_BT_709: - return "BT.709"; - case av1::matrix_coefficients::MC_UNSPECIFIED: - return "Unspecified"; - case av1::matrix_coefficients::MC_RESERVED_3: - return "Reserved (3)"; - case av1::matrix_coefficients::MC_FCC: - return "US FCC 73.628"; - case av1::matrix_coefficients::MC_BT_470_B_G: - return "BT.470 System B/G (historical)"; - case av1::matrix_coefficients::MC_BT_601: - return "BT.601"; - case av1::matrix_coefficients::MC_SMPTE_240: - return "SMPTE 240M"; - case av1::matrix_coefficients::MC_SMPTE_YCGCO: - return "YCgCo"; - case av1::matrix_coefficients::MC_BT_2020_NCL: - return "BT.2020 non-constant luminance system"; - case av1::matrix_coefficients::MC_BT_2020_CL: - return "BT.2020 constant luminance system"; - case av1::matrix_coefficients::MC_SMPTE_2085: - return "SMPTE ST 2085 YDzDx"; - case av1::matrix_coefficients::MC_CHROMAT_NCL: - return "Chromaticity-derived non-constant luminance system"; - case av1::matrix_coefficients::MC_CHROMAT_CL: - return "Chromaticity-derived constant luminance system"; - case av1::matrix_coefficients::MC_ICTCP: - return "ICTCP (BT.2100)"; - default: - return "Unknown matrix coefficients"; - } -} - -enum class chroma_sample_position : std::uint8_t { - CSP_UNKNOWN = 0, - CSP_VERTICAL = 1, - CSP_COLOCATED = 2, - CSP_RESERVED = 3, -}; - -constexpr auto to_string(av1::chroma_sample_position csp) noexcept -> std::string_view -{ - switch (csp) { - case av1::chroma_sample_position::CSP_UNKNOWN: - return "Unknown"; - case av1::chroma_sample_position::CSP_VERTICAL: - return "Vertical"; - case av1::chroma_sample_position::CSP_COLOCATED: - return "Colocated"; - case av1::chroma_sample_position::CSP_RESERVED: - return "Reserved"; - default: - return "Invalid chroma sample position"; - } -} - -enum class metadata_type : std::uint32_t { - METADATA_TYPE_HDR_CLL = 1, - METADATA_TYPE_HDR_MDCV = 2, - METADATA_TYPE_SCALABILITY = 3, - METADATA_TYPE_ITUT_T35 = 4, - METADATA_TYPE_TIMECODE = 5, -}; - -constexpr auto to_string(av1::metadata_type type) noexcept -> std::string_view -{ - switch (type) { - case av1::metadata_type::METADATA_TYPE_ITUT_T35: - return "ITU-T T.35"; - case av1::metadata_type::METADATA_TYPE_HDR_CLL: - return "HDR Content Light Level (CLL)"; - case av1::metadata_type::METADATA_TYPE_HDR_MDCV: - return "HDR Mastering Display Color Volume (MDCV)"; - case av1::metadata_type::METADATA_TYPE_SCALABILITY: - return "Scalability"; - case av1::metadata_type::METADATA_TYPE_TIMECODE: - return "Timecode"; - default: - return "Unknown metadata type"; - } -} - -enum class frame_type : std::uint8_t { - KEY_FRAME, - INTER_FRAME, - INTRA_ONLY_FRAME, - SWITCH_FRAME, -}; - -constexpr auto to_string(av1::frame_type type) noexcept -> std::string_view -{ - switch (type) { - case frame_type::KEY_FRAME: - return "Key Frame"; - case frame_type::INTER_FRAME: - return "Inter Frame"; - case frame_type::INTRA_ONLY_FRAME: - return "Intra-only Frame"; - case frame_type::SWITCH_FRAME: - return "Switch Frame"; - default: - return "Unknown frame type"; - } -} - -enum class ref_frame : std::int8_t { - NONE = -1, - INTRA_FRAME = 0, - LAST_FRAME = 1, - LAST2_FRAME = 2, - LAST3_FRAME = 3, - GOLDEN_FRAME = 4, - BWDREF_FRAME = 5, - ALTREF2_FRAME = 6, - ALTREF_FRAME = 7, -}; - -constexpr auto to_string(av1::ref_frame ref) noexcept -> std::string_view -{ - switch (ref) { - case av1::ref_frame::NONE: - return "None"; - case av1::ref_frame::INTRA_FRAME: - return "Intra Frame"; - case av1::ref_frame::LAST_FRAME: - return "Last Frame"; - case av1::ref_frame::LAST2_FRAME: - return "Last2 Frame"; - case av1::ref_frame::LAST3_FRAME: - return "Last3 Frame"; - case av1::ref_frame::GOLDEN_FRAME: - return "Golden Frame"; - case av1::ref_frame::BWDREF_FRAME: - return "BWDREF Frame"; - case av1::ref_frame::ALTREF2_FRAME: - return "ALTREF2 Frame"; - case av1::ref_frame::ALTREF_FRAME: - return "ALTREF Frame"; - default: - return "Unknown reference frame"; - } -} - -constexpr std::int8_t operator+(av1::ref_frame ref) noexcept -{ - return std::to_underlying(ref); -} - -enum class interpolation_filter : std::uint8_t { - EIGHTTAP = 0, - EIGHTTAP_SMOOTH = 1, - EIGHTTAP_SHARP = 2, - BILINEAR = 3, - SWITCHABLE = 4, -}; - -constexpr auto to_string(av1::interpolation_filter filter) noexcept -> std::string_view -{ - switch (filter) { - case av1::interpolation_filter::EIGHTTAP: - return "8-tap"; - case av1::interpolation_filter::EIGHTTAP_SMOOTH: - return "8-tap smooth"; - case av1::interpolation_filter::EIGHTTAP_SHARP: - return "8-tap sharp"; - case av1::interpolation_filter::BILINEAR: - return "Bilinear"; - case av1::interpolation_filter::SWITCHABLE: - return "Switchable"; - default: - return "Unknown interpolation filter"; - } -} - -// Value that indicates the allow_screen_content_tools syntax element is coded -static constexpr std::uint8_t SELECT_SCREEN_CONTENT_TOOLS = 2; - -// Value that indicates the force_integer_mv syntax element is coded -static constexpr std::uint8_t SELECT_INTEGER_MV = 2; - -// The maximum number of reference frames that can be used for inter prediction in AV1. -static constexpr std::uint32_t NUM_REF_FRAMES = 8; - -// Value of primary_ref_frame indicating that there is no primary reference frame -static constexpr std::uint8_t PRIMARY_REF_NONE = 7; - -// Smallest denominator for upscaling ratio -static constexpr std::uint8_t SUPERRES_DENOM_MIN = 9; - -// Smallest denominator for upscaling ratio -static constexpr std::uint8_t SUPERRES_NUM = 8; - -// Smallest denominator for upscaling ratio -static constexpr std::uint8_t REFS_PER_FRAME = 8; - -// Maximum width of a tile in units of luma samples -static constexpr std::uint32_t MAX_TILE_WIDTH = 4096; - -// Maximum area of a tile in units of luma samples -static constexpr std::uint32_t MAX_TILE_AREA = 4096 * 2304; - -// Maximum number of tile rows -static constexpr std::uint32_t MAX_TILE_ROWS = 64; - -// Maximum number of tile columns -static constexpr std::uint32_t MAX_TILE_COLS = 64; - -// Number of segments allowed in segmentation map -static constexpr std::uint32_t MAX_SEGMENTS = 8; - -// Number of segment features -static constexpr std::uint32_t SEG_LVL_MAX = 8; - -} // namespace mbmff::av1 - -// Bring to_string functions into mbmff namespace for easier access when formatting -namespace mbmff { -using av1::to_string; -} diff --git a/include/mbmff/av1/av1_funcs.hpp b/include/mbmff/av1/av1_funcs.hpp deleted file mode 100644 index 4b16712..0000000 --- a/include/mbmff/av1/av1_funcs.hpp +++ /dev/null @@ -1,447 +0,0 @@ -#pragma once -#include "../common.hpp" -#include "av1_structs.hpp" - -namespace mbmff::av1 { -constexpr auto leb128(std::span data) noexcept -> mbmff::parsed -{ - std::uint64_t value = 0; - std::uint32_t leb128_bytes = 0; - for (int i = 0; i < 8; i++) { - std::uint8_t leb128_byte = static_cast(data[i]); - - value |= static_cast(leb128_byte & 0x7f) << (i * 7); - leb128_bytes += 1; - if (!(leb128_byte & 0x80)) { - break; - } - } - return {value, leb128_bytes}; -} - -static std::uint32_t tile_log2(std::uint32_t blksize, std::uint32_t target) -{ - std::uint32_t k = 0; - for (; (blksize << k) < target; k++) - ; - return k; -} - -constexpr void mark_ref_frames(av1::obu_context& context, std::size_t id_len) noexcept -{ - auto diff_len = std::uint32_t(context.sequence_header.delta_frame_id_length_minus_2) + 2; - for (std::uint32_t i = 0; i < NUM_REF_FRAMES; i++) { - if (context.current_frame_id > (1ull << diff_len)) { - if (context.ref[i].frame_id > context.current_frame_id - || context.ref[i].frame_id < context.current_frame_id - (1ull << diff_len)) { - context.ref[i].valid = false; - } - } else { - if (context.ref[i].frame_id > context.current_frame_id - && context.ref[i].frame_id < ((1ull << id_len) + context.current_frame_id - (1ull << diff_len))) { - context.ref[i].valid = false; - } - } - } -} - -constexpr void compute_image_size(av1::obu_context& context) noexcept -{ - context.mi_cols = 2u * ((context.frame_width + 7) >> 3); - context.mi_rows = 2u * ((context.frame_height + 7) >> 3); -} - -constexpr void superres_params( - av1::obu_frame_header& frame_header, - av1::obu_context& context, - mbmff::bit_reader& reader -) noexcept -{ - if (context.sequence_header.enable_superres) { - frame_header.use_superres = reader.read_bits(1); - } - - std::uint32_t superres_denom = SUPERRES_NUM; - if (frame_header.use_superres) { - frame_header.coded_denom = reader.read_bits(3); - superres_denom = std::uint32_t(frame_header.coded_denom) + SUPERRES_DENOM_MIN; - } - - context.upscaled_width = context.frame_width; - context.frame_width = (context.upscaled_width * SUPERRES_NUM + (superres_denom / 2)) / superres_denom; -} - -constexpr void frame_size( - av1::obu_frame_header& frame_header, - av1::obu_context& context, - mbmff::bit_reader& reader -) noexcept -{ - if (frame_header.frame_size_override_flag) { - frame_header.frame_width_minus_1 = reader.read_bits( - std::uint16_t(context.sequence_header.frame_width_bits_minus_1) + 1 - ); - frame_header.frame_height_minus_1 = reader.read_bits( - std::uint16_t(context.sequence_header.frame_height_bits_minus_1) + 1 - ); - } else { - frame_header.frame_width_minus_1 = context.sequence_header.max_frame_width_minus_1; - frame_header.frame_height_minus_1 = context.sequence_header.max_frame_height_minus_1; - } - - context.frame_width = std::uint32_t(frame_header.frame_width_minus_1) + 1; - context.frame_height = std::uint32_t(frame_header.frame_height_minus_1) + 1; - - superres_params(frame_header, context, reader); - compute_image_size(context); -} - -constexpr void render_size( - av1::obu_frame_header& frame_header, - av1::obu_context& context, - mbmff::bit_reader& reader -) noexcept -{ - frame_header.render_and_frame_size_different = reader.read_bits(1); - if (frame_header.render_and_frame_size_different) { - frame_header.render_width_minus_1 = reader.read_bits(16); - frame_header.render_height_minus_1 = reader.read_bits(16); - - context.render_width = std::uint32_t(frame_header.render_width_minus_1) + 1; - context.render_height = std::uint32_t(frame_header.render_height_minus_1) + 1; - } else { - context.render_width = context.upscaled_width; - context.render_height = context.frame_height; - } -} - -// Converted from cbs_syntax_template.c of ffmpeg -constexpr auto get_relative_dist(const av1::obu_sequence_header& seq, std::uint32_t a, std::uint32_t b) noexcept - -> std::int32_t -{ - if (!seq.enable_order_hint) { - return 0; - } - std::uint32_t diff = a - b; - std::uint32_t m = 1 << seq.order_hint_bits_minus_1; - diff = (diff & (m - 1)) - (diff & m); - return diff; -} - -constexpr void set_frame_refs(av1::obu_frame_header& frame_header, av1::obu_context& context) noexcept -{ - const obu_sequence_header& seq = context.sequence_header; - constexpr ref_frame ref_frame_list[NUM_REF_FRAMES - 2] = { - ref_frame::LAST2_FRAME, - ref_frame::LAST3_FRAME, - ref_frame::BWDREF_FRAME, - ref_frame::ALTREF2_FRAME, - ref_frame::ALTREF_FRAME, - }; - - std::int8_t ref_frame_idx[REFS_PER_FRAME]{}; - std::int8_t used_frame[NUM_REF_FRAMES]{}; - - ref_frame_idx[+ref_frame::LAST_FRAME - +ref_frame::LAST_FRAME] = frame_header.last_frame_idx; - ref_frame_idx[+ref_frame::GOLDEN_FRAME - +ref_frame::LAST_FRAME] = frame_header.gold_frame_idx; - - used_frame[frame_header.last_frame_idx] = 1; - used_frame[frame_header.gold_frame_idx] = 1; - - std::uint32_t cur_frame_hint = 1u << (seq.order_hint_bits_minus_1); - std::int16_t shifted_order_hints[NUM_REF_FRAMES]; - for (std::uint32_t i = 0; i < NUM_REF_FRAMES; i++) { - shifted_order_hints[i] = cur_frame_hint - + get_relative_dist(seq, context.ref[i].order_hint, frame_header.order_hint); - } - - std::int16_t latest_order_hint = shifted_order_hints[frame_header.last_frame_idx]; - std::int16_t earliest_order_hint = shifted_order_hints[frame_header.gold_frame_idx]; - - std::int8_t ref = 0; - for (std::uint32_t i = 0; i < NUM_REF_FRAMES; i++) { - int hint = shifted_order_hints[i]; - if (!used_frame[i] && hint >= cur_frame_hint && (ref < 0 || hint >= latest_order_hint)) { - ref = i; - latest_order_hint = hint; - } - } - if (ref >= 0) { - ref_frame_idx[+ref_frame::ALTREF_FRAME - +ref_frame::LAST_FRAME] = ref; - used_frame[ref] = 1; - } - - ref = 0; - for (std::uint32_t i = 0; i < NUM_REF_FRAMES; i++) { - int hint = shifted_order_hints[i]; - if (!used_frame[i] && hint >= cur_frame_hint && (ref < 0 || hint < earliest_order_hint)) { - ref = i; - earliest_order_hint = hint; - } - } - if (ref >= 0) { - ref_frame_idx[+ref_frame::BWDREF_FRAME - +ref_frame::LAST_FRAME] = ref; - used_frame[ref] = 1; - } - - ref = 0; - for (std::uint32_t i = 0; i < NUM_REF_FRAMES; i++) { - int hint = shifted_order_hints[i]; - if (!used_frame[i] && hint >= cur_frame_hint && (ref < 0 || hint < earliest_order_hint)) { - ref = i; - earliest_order_hint = hint; - } - } - if (ref >= 0) { - ref_frame_idx[+ref_frame::ALTREF2_FRAME - +ref_frame::LAST_FRAME] = ref; - used_frame[ref] = 1; - } - - for (std::uint32_t i = 0; i < REFS_PER_FRAME - 2; i++) { - auto ref_frame = ref_frame_list[i]; - if (ref_frame_idx[+ref_frame - +ref_frame::LAST_FRAME] < 0) { - ref = 0; - for (std::uint32_t j = 0; j < NUM_REF_FRAMES; j++) { - int hint = shifted_order_hints[j]; - if (!used_frame[j] && hint < cur_frame_hint && (ref < 0 || hint >= latest_order_hint)) { - ref = j; - latest_order_hint = hint; - } - } - if (ref >= 0) { - ref_frame_idx[+ref_frame - +ref_frame::LAST_FRAME] = ref; - used_frame[ref] = 1; - } - } - } - - ref = 0; - for (std::uint32_t i = 0; i < NUM_REF_FRAMES; i++) { - int hint = shifted_order_hints[i]; - if (ref < 0 || hint < earliest_order_hint) { - ref = i; - earliest_order_hint = hint; - } - } - for (std::uint32_t i = 0; i < REFS_PER_FRAME; i++) { - if (ref_frame_idx[i] < 0) { - ref_frame_idx[i] = ref; - } - frame_header.ref_frame_indices_data.set_ref_frame_index(i, ref_frame_idx[i]); - } -} - -constexpr void frame_size_with_refs( - av1::obu_frame_header& frame_header, - av1::obu_context& context, - mbmff::bit_reader& reader -) noexcept -{ - std::uint8_t found_ref = 0; - for (std::uint32_t i = 0; i < REFS_PER_FRAME; i++) { - if (found_ref = reader.read_bits(1)) { - auto& ref_frame = context.ref[frame_header.ref_frame_indices_data.ref_frame_index(i)]; - frame_header.frame_width_minus_1 = ref_frame.upscaled_width - 1; - frame_header.frame_height_minus_1 = ref_frame.frame_height - 1; - frame_header.render_width_minus_1 = ref_frame.render_width - 1; - frame_header.render_height_minus_1 = ref_frame.render_height - 1; - - context.upscaled_width = ref_frame.upscaled_width; - context.frame_width = ref_frame.upscaled_width; - context.frame_height = ref_frame.frame_height; - context.render_width = ref_frame.render_width; - context.render_height = ref_frame.render_height; - break; - } - } - - if (!found_ref) { - frame_size(frame_header, context, reader); - render_size(frame_header, context, reader); - } else { - superres_params(frame_header, context, reader); - compute_image_size(context); - } -} - -constexpr void read_interpolation_filter( - av1::obu_frame_header& frame_header, - av1::obu_context& context, - mbmff::bit_reader& reader -) noexcept -{ - std::uint8_t is_filter_switchable = reader.read_flag(); - if (!is_filter_switchable) { - frame_header.interpolation_filter = static_cast(reader.read_bits(2)); - } -} - -constexpr void tile_info( - av1::obu_frame_header& frame_header, - av1::obu_context& context, - mbmff::bit_reader& reader -) noexcept -{ - const auto& seq = context.sequence_header; - std::uint32_t sb_cols = seq.use_128x128_superblock ? ((context.mi_cols + 31) >> 5) : ((context.mi_cols + 15) >> 4); - std::uint32_t sb_rows = seq.use_128x128_superblock ? ((context.mi_rows + 31) >> 5) : ((context.mi_rows + 15) >> 4); - std::uint32_t sb_shift = seq.use_128x128_superblock ? 5 : 4; - - std::uint32_t sb_size = sb_shift + 2; - std::uint32_t max_tile_width_sb = MAX_TILE_WIDTH >> sb_size; - std::uint32_t max_tile_area_sb = MAX_TILE_AREA >> (2 * sb_size); - - std::uint32_t min_log2_tile_cols = tile_log2(max_tile_width_sb, sb_cols); - std::uint32_t max_log2_tile_cols = tile_log2(1, std::min(sb_cols, MAX_TILE_COLS)); - - std::uint32_t max_log2_tile_rows = tile_log2(1, std::min(sb_rows, MAX_TILE_ROWS)); - std::uint32_t min_log2_tiles = std::max(min_log2_tile_cols, tile_log2(max_tile_area_sb, sb_rows * sb_cols)); - - if (frame_header.uniform_tile_spacing_flag = reader.read_flag()) { - frame_header.tile_cols_log2 = min_log2_tile_cols; - while (frame_header.tile_cols_log2 < max_log2_tile_cols) { - if (bool increment_tile_cols_log2 = reader.read_flag()) { - frame_header.tile_cols_log2++; - } else { - break; - } - } - - std::uint32_t tile_width_sb = (sb_cols + (1u << frame_header.tile_cols_log2) - 1) - >> frame_header.tile_cols_log2; - std::uint32_t i = 0; - for (std::uint32_t start_sb = 0; start_sb < sb_cols; start_sb += tile_width_sb) { - frame_header.mi_col_starts[i++] = start_sb << sb_shift; - } - frame_header.mi_col_starts[i] = context.mi_cols; - frame_header.tile_cols = i; - - std::uint32_t min_log2_tile_rows = std::max(min_log2_tiles - frame_header.tile_cols_log2, 0u); - frame_header.tile_rows_log2 = min_log2_tile_rows; - while (frame_header.tile_rows_log2 < max_log2_tile_rows) { - if (bool increment_tile_cols_log2 = reader.read_flag()) { - frame_header.tile_rows_log2++; - } else { - break; - } - } - - std::uint32_t tile_height_sb = (sb_rows + (1 << frame_header.tile_rows_log2) - 1) - >> frame_header.tile_rows_log2; - i = 0; - for (std::uint32_t start_sb = 0; start_sb < sb_rows; start_sb += tile_height_sb) { - frame_header.mi_row_starts[i++] = start_sb << sb_shift; - } - - frame_header.mi_row_starts[i] = context.mi_rows; - frame_header.tile_rows = i; - } else { - std::uint32_t widest_tile_sb = 0; - std::uint32_t start_sb = 0; - std::uint32_t i = 0; - for (; start_sb < sb_cols; i++) { - frame_header.mi_col_starts[i] = start_sb << sb_shift; - std::uint32_t max_width = std::min(sb_cols - start_sb, max_tile_width_sb); - - frame_header.width_in_sbs_minus_1[i] = reader.ns(max_width); - std::uint32_t size_sb = frame_header.width_in_sbs_minus_1[i] + 1; - - std::uint32_t widest_tile_sb = std::max(size_sb, widest_tile_sb); - start_sb += size_sb; - } - - frame_header.mi_col_starts[i] = context.mi_cols; - frame_header.tile_cols = i; - frame_header.tile_cols_log2 = tile_log2(1, frame_header.tile_cols); - - if (min_log2_tiles > 0) { - max_tile_area_sb = (sb_rows * sb_cols) >> (min_log2_tiles + 1); - } else { - max_tile_area_sb = sb_rows * sb_cols; - } - std::uint32_t max_tile_height_sb = std::max(max_tile_area_sb / widest_tile_sb, 1u); - - - start_sb = 0; - i = 0; - for (; start_sb < sb_rows; i++) { - frame_header.mi_row_starts[i] = start_sb << sb_shift; - std::uint32_t max_height = std::min(sb_rows - start_sb, max_tile_height_sb); - frame_header.height_in_sbs_minus_1[i] = reader.ns(max_height); - std::uint32_t size_sb = frame_header.height_in_sbs_minus_1[i] + 1; - start_sb += size_sb; - } - - frame_header.mi_row_starts[i] = context.mi_rows; - frame_header.tile_rows = i; - frame_header.tile_rows_log2 = tile_log2(1, frame_header.tile_rows); - } - - if (frame_header.tile_cols_log2 > 0 || frame_header.tile_rows_log2 > 0) { - frame_header.context_update_tile_id = reader.read_bits( - frame_header.tile_cols_log2 + frame_header.tile_rows_log2 - ); - frame_header.tile_size_bytes_minus_1 = reader.read_bits(2); - } -} - -constexpr auto read_delta_q(mbmff::bit_reader& reader) -> std::int8_t -{ - std::int8_t delta_q = 0; - if (bool delta_coded = reader.read_flag()) { - delta_q = reader.su(7); - } - return delta_q; -} - - -constexpr void quantization_params( - av1::obu_frame_header& frame_header, - av1::obu_context& context, - mbmff::bit_reader& reader -) noexcept -{ - const auto& seq = context.sequence_header; - frame_header.base_q_idx = reader.read_bits(8); - frame_header.delta_q_y_dc = read_delta_q(reader); - if (seq.num_planes() > 1) { - if (seq.color_config_data.separate_uv_delta_q) { - frame_header.diff_uv_delta = reader.read_flag(); - } - - frame_header.delta_q_u_dc = read_delta_q(reader); - frame_header.delta_q_u_ac = read_delta_q(reader); - if (frame_header.diff_uv_delta) { - frame_header.delta_q_v_dc = read_delta_q(reader); - frame_header.delta_q_v_ac = read_delta_q(reader); - } else { - frame_header.delta_q_v_dc = frame_header.delta_q_u_dc; - frame_header.delta_q_v_ac = frame_header.delta_q_u_ac; - } - } - - if (frame_header.using_qmatrix = reader.read_flag()) { - frame_header.qm_y = reader.read_bits(4); - frame_header.qm_u = reader.read_bits(4); - if (!seq.color_config_data.separate_uv_delta_q) { - frame_header.qm_v = frame_header.qm_u; - } else { - frame_header.qm_v = reader.read_bits(4); - } - } -} - -constexpr void segmentation_params( - av1::obu_frame_header& frame_header, - av1::obu_context& context, - mbmff::bit_reader& reader -) noexcept -{ - frame_header.segmentation_enabled = reader.read_flag(); - if (frame_header.segmentation_enabled) { - - } -} - - -} // namespace mbmff::av1 diff --git a/include/mbmff/av1/av1_structs.hpp b/include/mbmff/av1/av1_structs.hpp deleted file mode 100644 index a1c1366..0000000 --- a/include/mbmff/av1/av1_structs.hpp +++ /dev/null @@ -1,299 +0,0 @@ -#pragma once -#include "av1_enums.hpp" - -namespace mbmff::av1 { -//------------------------------------------------------------------------------------------------------------ -struct timing_info { - std::uint32_t num_units_in_display_tick = 0; - std::uint32_t time_scale = 0; - std::uint32_t equal_picture_interval = 0; - std::uint32_t num_ticks_per_picture_minus_1 = 0; -}; - -struct decoder_model_info { - std::uint8_t buffer_delay_length_minus_1 = 0; - std::uint32_t num_units_in_decoding_tick = 0; - std::uint8_t buffer_removal_time_length_minus_1 = 0; - std::uint8_t frame_presentation_time_length_minus_1 = 0; -}; - -struct operating_parameters_info { - std::uint32_t decoder_buffer_delay = 0; - std::uint32_t encoder_buffer_delay = 0; - std::uint32_t low_delay_mode_flag = 0; -}; - -struct operating_point { - std::uint16_t operating_point_idc = 0; - std::uint8_t seq_level_idx : 5 = 0; - std::uint8_t seq_tier : 1 = 0; - std::uint8_t decoder_model_present_for_this_op : 1 = 0; - std::uint8_t initial_display_delay_present_for_this_op : 1 = 0; - std::uint8_t initial_display_delay_minus_1 : 4 = 0; - - operating_parameters_info operating_parameters{}; -}; - -struct color_description { - av1::color_primaries color_primaries = av1::color_primaries::CP_UNSPECIFIED; - av1::transfer_characteristics transfer_characteristics = av1::transfer_characteristics::TC_UNSPECIFIED; - av1::matrix_coefficients matrix_coefficients = av1::matrix_coefficients::MC_UNSPECIFIED; -}; - -struct color_config { - std::uint8_t high_bitdepth : 1 = 0; - std::uint8_t twelve_bit : 1 = 0; - std::uint8_t monochrome : 1 = 0; - std::uint8_t color_description_present_flag : 1 = 0; - - std::uint8_t color_range : 1 = 0; - std::uint8_t subsampling_x : 1 = 0; - std::uint8_t subsampling_y : 1 = 0; - std::uint8_t separate_uv_delta_q : 1 = 0; - - av1::chroma_sample_position chroma_sample_position = av1::chroma_sample_position::CSP_UNKNOWN; - av1::color_description color_description_data{}; -}; - -struct obu_sequence_header { - std::uint8_t seq_profile : 3 = 0; - std::uint8_t still_picture : 1 = 0; - std::uint8_t reduced_still_picture_header : 1 = 0; - std::uint8_t timing_info_present_flag : 1 = 0; - std::uint8_t decoder_model_info_present_flag : 1 = 0; - std::uint8_t initial_display_delay_present_flag : 1 = 0; - - std::uint8_t frame_id_numbers_present_flag : 1 = 0; - std::uint8_t delta_frame_id_length_minus_2 : 4 = 0; - std::uint8_t additional_frame_id_length_minus_1 : 3 = 0; - - std::uint8_t use_128x128_superblock : 1 = 0; - std::uint8_t enable_filter_intra : 1 = 0; - std::uint8_t enable_intra_edge_filter : 1 = 0; - std::uint8_t enable_interintra_compound : 1 = 0; - std::uint8_t enable_masked_compound : 1 = 0; - std::uint8_t enable_warped_motion : 1 = 0; - std::uint8_t enable_dual_filter : 1 = 0; - std::uint8_t enable_order_hint : 1 = 0; - - std::uint8_t order_hint_bits_minus_1 : 3 = 0; - std::uint8_t enable_jnt_comp : 1 = 0; - std::uint8_t enable_ref_frame_mvs : 1 = 0; - std::uint8_t seq_choose_screen_content_tools : 1 = 0; - std::uint8_t seq_force_screen_content_tools : 2 = SELECT_SCREEN_CONTENT_TOOLS; - std::uint8_t seq_choose_integer_mv : 1 = 0; - std::uint8_t seq_force_integer_mv : 2 = SELECT_INTEGER_MV; - - std::uint8_t enable_superres : 1 = 0; - std::uint8_t enable_cdef : 1 = 0; - std::uint8_t enable_restoration : 1 = 0; - std::uint8_t film_grain_params_present : 1 = 0; - - std::uint8_t operating_points_cnt_minus_1 : 5 = 0; - std::array operating_points{}; // max 32 operating points (as per spec) - - timing_info timing_info_data{}; - decoder_model_info decoder_model_info_data{}; - color_config color_config_data{}; - - uint8_t frame_width_bits_minus_1 : 4 = 0; - uint8_t frame_height_bits_minus_1 : 4 = 0; - uint16_t max_frame_width_minus_1 = 0; - uint16_t max_frame_height_minus_1 = 0; - -public: - constexpr auto order_hint_bits() const noexcept -> std::uint8_t - { - if (reduced_still_picture_header) { - return 0; - } - - if (enable_order_hint) { - return order_hint_bits_minus_1 + 1; - } - return 0; - } - - constexpr auto num_planes() const noexcept -> std::uint8_t - { - return color_config_data.monochrome ? 1 : 3; - } -}; - -struct obu_metadata { - av1::metadata_type type = {}; - std::span payload{}; -}; - -struct frame_indices { - std::uint32_t ref_frame_indices; - -public: - constexpr auto ref_frame_index(std::size_t i) const noexcept -> std::uint8_t - { - return (ref_frame_indices >> (i * 3)) & 0x07; - } - constexpr auto set_ref_frame_index(std::size_t i, std::uint8_t index) noexcept -> void - { - ref_frame_indices &= ~(0x07 << (i * 3)); // Clear the existing index at position i - ref_frame_indices |= (index & 0x07) << (i * 3); // Set the new index at position i - } -}; - -struct ref_frame_state { - std::uint8_t valid; // RefValid - std::uint8_t order_hint; // RefOrderHint - std::uint8_t bit_depth; // RefBitDepth - av1::frame_type frame_type; // RefFrameType - - std::uint32_t upscaled_width; // RefUpscaledWidth - std::uint64_t frame_id; // RefFrameId - - std::uint32_t frame_width; // RefFrameWidth - std::uint32_t frame_height; // RefFrameHeight - std::uint32_t render_width; // RefRenderWidth - std::uint32_t render_height; // RefRenderHeight - - std::uint32_t subsampling_x; // RefSubsamplingX - std::uint32_t subsampling_y; // RefSubsamplingY -}; - -struct obu_frame_header { - std::uint8_t show_existing_frame : 1 = 0; - std::uint8_t frame_to_show_map_idx : 3 = 0; - std::uint8_t show_frame : 1 = 1; - std::uint8_t showable_frame : 1 = 0; - std::uint8_t error_resilient_mode : 1 = 1; - std::uint8_t disable_cdf_update : 1 = 0; - - std::uint8_t allow_screen_content_tools : 1 = 0; - std::uint8_t force_integer_mv : 1 = 0; - std::uint8_t frame_size_override_flag : 1 = 0; - std::uint8_t buffer_removal_time_present_flag : 1 = 0; - std::uint8_t allow_high_precision_mv : 1 = 0; - std::uint8_t primary_ref_frame : 3 = 0; - - std::uint8_t use_ref_frame_mvs : 1 = 0; - std::uint8_t allow_intrabc : 1 = 0; - std::uint8_t frame_refs_short_signaling : 1 = 0; - std::uint8_t last_frame_idx : 3 = 0; - std::uint8_t gold_frame_idx : 3 = 0; - av1::interpolation_filter interpolation_filter : 2 = av1::interpolation_filter::SWITCHABLE; - std::uint8_t is_motion_mode_switchable : 1 = 0; - std::uint8_t disable_frame_end_update_cdf : 1 = 1; - std::uint8_t uniform_tile_spacing_flag : 1 = 0; - std::uint8_t tile_size_bytes_minus_1 : 2 = 0; - std::uint8_t using_qmatrix : 1 = 0; - std::uint8_t diff_uv_delta : 1 = 0; - - std::uint8_t refresh_frame_flags = 0; - av1::frame_type frame_type = av1::frame_type::KEY_FRAME; - std::uint8_t order_hint = 0; - - // tile info - std::uint8_t tile_cols_log2 = 0; - std::uint8_t tile_rows_log2 = 0; - std::uint8_t mi_col_starts[MAX_TILE_COLS]{}; - std::uint8_t mi_row_starts[MAX_TILE_ROWS]{}; - std::uint8_t width_in_sbs_minus_1[MAX_TILE_COLS]{}; - std::uint8_t height_in_sbs_minus_1[MAX_TILE_ROWS]{}; - - std::uint16_t tile_cols = 0; - std::uint16_t tile_rows = 0; - std::uint16_t context_update_tile_id = 0; - - // quantization params - std::uint8_t base_q_idx = 0; - std::int8_t delta_q_y_dc = 0; - std::int8_t delta_q_u_dc = 0; - std::int8_t delta_q_u_ac = 0; - std::int8_t delta_q_v_dc = 0; - std::int8_t delta_q_v_ac = 0; - std::uint8_t qm_y : 4 = 0; - std::uint8_t qm_u : 4 = 0; - std::uint8_t qm_v : 4 = 0; - - // segmrentation params - std::uint8_t segmentation_enabled : 1 = 0; - std::uint8_t segmentation_update_map : 1 = 0; - std::uint8_t segmentation_temporal_update : 1 = 0; - std::uint8_t segmentation_update_data : 1 = 0; - std::bitset feature_enabled[SEG_LVL_MAX]; - std::int16_t feature_value[MAX_SEGMENTS][SEG_LVL_MAX]; - - // frame size - std::uint16_t frame_width_minus_1 = 0; - std::uint16_t frame_height_minus_1 = 0; - - // superres params - std::uint8_t use_superres : 1 = 0; - std::uint8_t coded_denom : 3 = 0; - - // render size - std::uint8_t render_and_frame_size_different : 1 = 0; - std::uint16_t render_width_minus_1 = 0; - std::uint16_t render_height_minus_1 = 0; - - av1::frame_indices ref_frame_indices_data{}; - - std::uint64_t display_frame_id = 0; - std::uint64_t current_frame_id = 0; - std::uint64_t frame_presentation_time = 0; - std::array buffer_removal_time{}; - std::array ref_order_hint{}; - std::array expectedFrameId{}; -}; - -//------------------------------------------------------------------------------------------------------------ -struct obu_view_base { - constexpr static std::uint64_t max_obu_size = (1ULL << 32) - 1; // 4GB - 1 byte -public: - std::uint8_t forbidden_bit : 1 = 0; // should be 0 - std::uint8_t type : 4 = 0; - std::uint8_t extension_flag : 1 = 0; - std::uint8_t has_size_field : 1 = 0; - std::uint8_t reserved : 1 = 0; // should be 0 - - // obu extension header fields (if extension_flag is set) - std::uint8_t temporal_id : 3 = 0; - std::uint8_t spatial_id : 2 = 0; - std::uint8_t extension_reserved_1 : 3 = 0; // should be 0 - - // followed by payload - std::span payload{}; - -public: - constexpr operator bool() const noexcept - { - return payload.data() != nullptr; - } -}; - -//------------------------------------------------------------------------------------------------------------ -struct obu_context { - // Selected by the decoder - std::uint32_t operating_point = 0; - - // Sequence header OBU, needed for parsing frame headers. - av1::obu_view_base sequence_obu_header{}; - - // Most recently parsed sequence header, needed for parsing frame headers - av1::obu_sequence_header sequence_header{}; - - std::uint8_t seen_frame_header : 1 = false; - - std::uint64_t prev_frame_id = 0; - std::uint64_t current_frame_id = 0; - std::uint32_t frame_width = 0; - std::uint32_t frame_height = 0; - std::uint32_t render_width = 0; - std::uint32_t render_height = 0; - std::uint32_t upscaled_width = 0; - std::uint32_t mi_cols = 0; - std::uint32_t mi_rows = 0; - - std::array order_hints{}; - std::bitset ref_frame_sign_bias{}; - av1::ref_frame_state ref[NUM_REF_FRAMES]{}; -}; -} // namespace mbmff::av1 diff --git a/include/mbmff/boxes/av1C.hpp b/include/mbmff/boxes/av1C.hpp new file mode 100644 index 0000000..16d02c1 --- /dev/null +++ b/include/mbmff/boxes/av1C.hpp @@ -0,0 +1,85 @@ +#pragma once +#include "box_view.hpp" + +namespace mbmff { +struct av1C_data { + std::uint8_t marker : 1 = 0; + std::uint8_t version : 7 = 0; + std::uint8_t seq_profile : 3 = 0; + std::uint8_t seq_level_idx_0 : 5 = 0; + std::uint8_t seq_tier_0 : 1 = 0; + std::uint8_t high_bitdepth : 1 = 0; + std::uint8_t twelve_bit : 1 = 0; + std::uint8_t monochrome : 1 = 0; + std::uint8_t chroma_subsampling_x : 1 = 0; + std::uint8_t chroma_subsampling_y : 1 = 0; + std::uint8_t chroma_sample_position : 2 = 0; + std::uint8_t reserved : 3 = 0; + std::uint8_t initial_presentation_delay_present : 1 = 0; + std::uint8_t initial_presentation_delay_minus_one : 4 = 0; + + std::span config_obus{}; +}; + +template <> +struct basic_box_view : public box_view_base { + constexpr static box_properties properties = box_properties::none; + constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; + constexpr auto value() const noexcept -> mbmff::av1C_data; +}; + +inline constexpr auto basic_box_view::validate(mbmff::any_box_view box) noexcept + -> mbmff::result +{ + if (box.payload.size() < 4) { + return mbmff::make_result(mbmff::error_code::need_more_data, 4); + } + return {box}; +} + +constexpr auto basic_box_view::value() const noexcept -> mbmff::av1C_data +{ + av1C_data result{}; + std::uint8_t read_byte = static_cast(payload[0]); + result.marker = (read_byte >> 7) & 0x01; + result.version = read_byte & 0x7F; + + read_byte = static_cast(payload[1]); + result.seq_profile = (read_byte >> 5) & 0x07; + result.seq_level_idx_0 = read_byte & 0x1F; + + read_byte = static_cast(payload[2]); + result.seq_tier_0 = (read_byte >> 7) & 0x01; + result.high_bitdepth = (read_byte >> 6) & 0x01; + result.twelve_bit = (read_byte >> 5) & 0x01; + result.monochrome = (read_byte >> 4) & 0x01; + result.chroma_subsampling_x = (read_byte >> 3) & 0x01; + result.chroma_subsampling_y = (read_byte >> 2) & 0x01; + result.chroma_sample_position = read_byte & 0x03; + + read_byte = static_cast(payload[3]); + result.reserved = (read_byte >> 5) & 0x07; + result.initial_presentation_delay_present = (read_byte >> 4) & 0x01; + if (result.initial_presentation_delay_present) { + result.initial_presentation_delay_minus_one = read_byte & 0x0F; + } + + result.config_obus = payload.subspan(4); + return result; +} + +#ifdef MBMFF_ENABLE_CONSTEXPR_TEST +// validate must reject empty payload +static_assert(!mbmff::basic_box_view::validate( + {mbmff::box_header{}, std::span{}})); + +// validate must accept 4-byte payload +static_assert([] { + constexpr std::byte min_data[4]{}; + auto r = mbmff::basic_box_view::validate( + {mbmff::box_header{}, std::span(min_data)}); + return static_cast(r); +}()); +#endif + +} // namespace mbmff diff --git a/include/mbmff/boxes/box_view.hpp b/include/mbmff/boxes/box_view.hpp new file mode 100644 index 0000000..e67e583 --- /dev/null +++ b/include/mbmff/boxes/box_view.hpp @@ -0,0 +1,203 @@ +#pragma once +#include +#include "common.hpp" + +#define MBMFF_ITERATE_BOX_TYPES(MACRO) \ + MACRO(ftyp) \ + MACRO(meta) \ + MACRO(mdat) \ + MACRO(moov) \ + MACRO(trak) \ + MACRO(mdia) \ + MACRO(minf) \ + MACRO(stbl) \ + MACRO(dinf) \ + MACRO(edts) \ + MACRO(udta) \ + MACRO(mvex) \ + MACRO(moof) \ + MACRO(traf) \ + MACRO(mfra) \ + MACRO(iprp) \ + MACRO(ipco) \ + MACRO(iinf) \ + MACRO(iref) \ + MACRO(iloc) \ + MACRO(hdlr) \ + MACRO(pitm) \ + MACRO(ispe) \ + MACRO(av1C) \ + MACRO(pixi) \ + MACRO(ipma) \ + MACRO(pasp) \ + MACRO(infe) + +#define MBMFF_FLAG_OPERATORS(EnumType) \ + constexpr auto operator|(EnumType a, EnumType b) noexcept -> EnumType \ + { \ + return static_cast(mbmff::to_underlying(a) | mbmff::to_underlying(b)); \ + } \ + constexpr auto operator&(EnumType a, EnumType b) noexcept -> EnumType \ + { \ + return static_cast(mbmff::to_underlying(a) & mbmff::to_underlying(b)); \ + } \ + constexpr auto operator+(EnumType a) noexcept -> std::underlying_type_t \ + { \ + return mbmff::to_underlying(a); \ + } \ + constexpr auto has(EnumType a, EnumType check) noexcept -> bool \ + { \ + return (a & check) != EnumType::none; \ + } + +namespace mbmff { +#define MBMFF_ITERATE_ENUM(name) name = mbmff::fourcc(#name), +enum class box_type : std::uint32_t { unknown, MBMFF_ITERATE_BOX_TYPES(MBMFF_ITERATE_ENUM) }; + +//------------------------------------------------------------------------------------------------------------ +enum class box_properties : std::uint32_t { + none = 0, + full_box = 1 << 0, + container = 1 << 1, +}; +MBMFF_FLAG_OPERATORS(box_properties) + +//------------------------------------------------------------------------------------------------------------ +struct box_header { + std::uint64_t size_ = 0; + mbmff::box_type type_ = mbmff::box_type::unknown; + std::uint8_t version_ = 0; + std::array flags_{}; + +public: + constexpr auto type_string() const noexcept -> mbmff::fourcc_string + { + return mbmff::fourcc_string::from_uint32(mbmff::to_underlying(type_)); + } + constexpr auto size() const noexcept -> std::uint64_t + { + return size_; + } + constexpr auto type() const noexcept -> mbmff::box_type + { + return type_; + } + constexpr auto flags() const noexcept -> std::uint32_t + { + return (static_cast(flags_[0]) << 16) | (static_cast(flags_[1]) << 8) + | static_cast(flags_[2]); + } + constexpr auto version() const noexcept -> std::uint8_t + { + return version_; + } + constexpr auto valid() const noexcept -> bool + { + return type_ != mbmff::box_type::unknown; + } + constexpr operator bool() const noexcept + { + return valid(); + } + +public: + constexpr void fill_full_header(std::span data) noexcept + { + version_ = static_cast(data[0]); + flags_[0] = static_cast(data[1]); + flags_[1] = static_cast(data[2]); + flags_[2] = static_cast(data[3]); + } +}; + +using any_box_view = struct box_view_base; + +struct box_view_base : mbmff::box_header { + std::span payload{}; + +public: + constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result + { + return {box}; + } +}; + +template +struct basic_box_view : public mbmff::box_view_base { + constexpr static mbmff::box_properties properties = mbmff::box_properties::none; +}; + +//------------------------------------------------------------------------------------------------------------ +constexpr inline auto parse_box_header(std::span data) noexcept + -> mbmff::result> +{ + if (data.size() < 8) { + return mbmff::make_result>(mbmff::error_code::need_more_data, 8); + } + + std::uint64_t size = mbmff::read_be(data); + if (size == 0) { + return mbmff::make_result>(mbmff::error_code::invalid_format); + } + + char type_str[4]{ + static_cast(data[4]), + static_cast(data[5]), + static_cast(data[6]), + static_cast(data[7]), + }; + mbmff::box_type type = mbmff::box_type(mbmff::fourcc(type_str)); + if (size == 1) { + if (data.size() < 16) { + return mbmff::make_result>(mbmff::error_code::need_more_data, 16); + } + size = mbmff::read_be(data.subspan(8)); + } + + return {mbmff::parsed{{size, type}, size == 1 ? 16u : 8u}}; +} + +#ifdef MBMFF_ENABLE_CONSTEXPR_TEST +// parse_box_header must reject data < 8 bytes +static_assert(!mbmff::parse_box_header(std::span{}), "parse_box_header should reject empty data"); +static_assert( + []() { + std::array data{}; + return !mbmff::parse_box_header(std::span(data)); + }(), + "parse_box_header should reject data with less than 8 bytes" +); +static_assert( + []() { + std::array data{ + std::byte(1), + }; + mbmff::parse_box_header(std::span(data)); + return true; + }(), + "parse_box_header should accept data with 16 bytes (for large size)" +); + +// Try parsing a valid box header +static_assert( + []() { + std::array data{ + std::byte(12), // size = 12 + std::byte(0), + std::byte(0), + std::byte(0), + std::byte('m'), + std::byte('d'), + std::byte('a'), + std::byte('t'), // type = "mdat" + }; + auto result = mbmff::parse_box_header(std::span(data)); + return result && result->value.size() == 12 && result->value.type() == mbmff::box_type::mdat + && result->consumed == 8; + }(), + "parse_box_header should parse a valid box header correctly" +); + +#endif + +} // namespace mbmff diff --git a/include/mbmff/boxes/common.hpp b/include/mbmff/boxes/common.hpp new file mode 100644 index 0000000..32feb59 --- /dev/null +++ b/include/mbmff/boxes/common.hpp @@ -0,0 +1,237 @@ +#pragma once +#include +#include +#include +#include +#include +#include + +namespace mbmff { +enum class error_code { + success = 0, + invalid_format, + need_more_data, +}; + +//------------------------------------------------------------------------------------------------------------ +static constexpr auto to_string(mbmff::error_code code) noexcept -> std::string_view +{ + switch (code) { + case mbmff::error_code::success: + return "Success"; + case mbmff::error_code::invalid_format: + return "Invalid format"; + case mbmff::error_code::need_more_data: + return "Need more data"; + default: + return "Unknown error code"; + } +} +//------------------------------------------------------------------------------------------------------------ +template +struct parsed { + Type value; + std::size_t consumed = 0; +}; + +template +struct result { + Type value{}; + mbmff::error_code code = mbmff::error_code::success; + std::size_t needed = 0; + +public: + constexpr explicit operator bool() const noexcept + { + return code == mbmff::error_code::success; + } + constexpr auto operator->() noexcept -> Type* + { + return &value; + } + constexpr auto operator*() noexcept -> Type& + { + return value; + } + constexpr auto operator*() const noexcept -> const Type& + { + return value; + } +}; + +// Error result — Type must be specified explicitly (e.g. make_result(code, n)) +template +constexpr auto make_result(mbmff::error_code code, std::size_t needed = 0) noexcept -> mbmff::result +{ + return {{}, code, needed}; +} + +template + requires(!std::same_as) +constexpr auto make_result(Type value) noexcept -> mbmff::result +{ + return {value}; +} + +//------------------------------------------------------------------------------------------------------------ + +constexpr auto fourcc(const char* str) noexcept -> std::uint32_t +{ + return (static_cast(str[3]) << 24) | (static_cast(str[2]) << 16) + | (static_cast(str[1]) << 8) | static_cast(str[0]); +} + +struct fourcc_string { + std::array data_{}; + +public: + constexpr auto view() const noexcept -> std::string_view + { + return std::string_view(data_.data(), data_.size()); + } + constexpr auto data() const noexcept -> const std::span + { + return data_; + } + constexpr auto data() noexcept -> std::span + { + return data_; + } + constexpr auto to_uint32() const noexcept -> std::uint32_t + { + return fourcc(data_.data()); + } + + constexpr auto operator==(const mbmff::fourcc_string& other) const noexcept -> bool + { + return data_ == other.data_; + } + constexpr auto operator[](std::size_t index) const noexcept -> char + { + return data_[index]; + } + constexpr operator std::string_view() const noexcept + { + return view(); + } + +public: + constexpr static auto from_data(std::span data) noexcept -> mbmff::fourcc_string + { + return mbmff::fourcc_string{std::array{ + static_cast(data[0]), + static_cast(data[1]), + static_cast(data[2]), + static_cast(data[3]), + }}; + } + constexpr static auto from_uint32(std::uint32_t value) noexcept -> mbmff::fourcc_string + { + return fourcc_string{std::array{ + static_cast(value & 0xFF), + static_cast((value >> 8) & 0xFF), + static_cast((value >> 16) & 0xFF), + static_cast((value >> 24) & 0xFF), + }}; + } +}; + +//------------------------------------------------------------------------------------------------------------ +template +[[nodiscard]] constexpr auto to_underlying(Type value) noexcept -> std::underlying_type_t +{ + return static_cast>(value); +} + +template +constexpr auto byteswap(T value) noexcept -> T +{ + if constexpr (sizeof(T) == 1) { + return value; + } else if constexpr (sizeof(T) == 2) { + return static_cast(((value & 0x00FF) << 8) | ((value & 0xFF00) >> 8)); + } else if constexpr (sizeof(T) == 4) { + return static_cast( + ((value & 0x000000FF) << 24) | ((value & 0x0000FF00) << 8) | ((value & 0x00FF0000) >> 8) + | ((value & 0xFF000000) >> 24) + ); + } else if constexpr (sizeof(T) == 8) { + return static_cast( + ((value & 0x00000000000000FFULL) << 56) | ((value & 0x000000000000FF00ULL) << 40) + | ((value & 0x0000000000FF0000ULL) << 24) | ((value & 0x00000000FF000000ULL) << 8) + | ((value & 0x000000FF00000000ULL) >> 8) | ((value & 0x0000FF0000000000ULL) >> 24) + | ((value & 0x00FF000000000000ULL) >> 40) | ((value & 0xFF00000000000000ULL) >> 56) + ); + } +} + +template +constexpr auto read_be(std::span data) noexcept -> T +{ + T value{}; + if (std::is_constant_evaluated()) { + for (std::size_t i = 0; i < sizeof(T); ++i) { + value <<= 8; + value |= static_cast(data[i]); + } + } else { + std::memcpy(&value, data.data(), sizeof(value)); + } + return mbmff::byteswap(value); +} + +//------------------------------------------------------------------------------------------------------------ +struct parsed_cstr { + std::string_view value{}; + std::size_t next = 0; +}; + +constexpr auto read_cstr(std::span data, std::size_t offset) noexcept -> parsed_cstr +{ + if (offset >= data.size()) { + return {}; + } + + const auto* begin = reinterpret_cast(data.data() + offset); + std::size_t length = 0; + for (std::size_t i = offset; i < data.size(); ++i) { + if (data[i] == std::byte{0}) { + break; + } + ++length; + } + + std::size_t next = offset + length; + if (next < data.size() && data[next] == std::byte{0}) { + ++next; + } + + return {std::string_view(begin, length), next}; +} + +} // namespace mbmff + +#ifdef MBMFF_ENABLE_CONSTEXPR_TEST +// result tests +static_assert(mbmff::make_result(42), "make_result(42) should be sucessful (result)"); +static_assert(!mbmff::make_result(mbmff::error_code::invalid_format), "make_result(code) should be an error"); + +// fourcc tests +static_assert(mbmff::fourcc("abcd") == 0x64636261, "fourcc(\"abcd\") should be 0x64636261"); +static_assert( + []() { + constexpr auto str = mbmff::fourcc_string::from_uint32(0x64636261); + constexpr auto d = std::array{'a', 'b', 'c', 'd'}; + return str[0] == d[0] && str[1] == d[1] && str[2] == d[2] && str[3] == d[3]; + }(), + "fourcc_string::from_uint32(0x64636261) should return 'abcd'" +); + +// byteswap tests +static_assert(mbmff::byteswap(std::uint16_t{0x1234}) == 0x3412, "byteswap(0x1234) should be 0x3412"); +static_assert(mbmff::byteswap(std::uint32_t{0x12345678}) == 0x78563412, "byteswap(0x12345678) should be 0x78563412"); +static_assert( + mbmff::byteswap(std::uint64_t{0x0123456789ABCDEF}) == 0xEFCDAB8967452301, + "byteswap(0x0123456789ABCDEF) should be 0xEFCDAB8967452301" +); +#endif diff --git a/include/mbmff/boxes/containers.hpp b/include/mbmff/boxes/containers.hpp new file mode 100644 index 0000000..073bd28 --- /dev/null +++ b/include/mbmff/boxes/containers.hpp @@ -0,0 +1,42 @@ +#pragma once +#include "box_view.hpp" + +namespace mbmff { + +//------------------------------------------------------------------------------------------------------------ +// Pure container boxes — no payload fields, just child boxes. +// All share the same pattern: container property, always-passes validate, empty value(). +//------------------------------------------------------------------------------------------------------------ + +#define MBMFF_DEFINE_PURE_CONTAINER(box) \ + struct box##_data {}; \ + \ + template <> \ + struct basic_box_view : public mbmff::box_view_base { \ + constexpr static mbmff::box_properties properties = mbmff::box_properties::container; \ + } + +MBMFF_DEFINE_PURE_CONTAINER(moov); +MBMFF_DEFINE_PURE_CONTAINER(trak); +MBMFF_DEFINE_PURE_CONTAINER(mdia); +MBMFF_DEFINE_PURE_CONTAINER(minf); +MBMFF_DEFINE_PURE_CONTAINER(stbl); +MBMFF_DEFINE_PURE_CONTAINER(dinf); +MBMFF_DEFINE_PURE_CONTAINER(edts); +MBMFF_DEFINE_PURE_CONTAINER(mvex); +MBMFF_DEFINE_PURE_CONTAINER(udta); +MBMFF_DEFINE_PURE_CONTAINER(moof); +MBMFF_DEFINE_PURE_CONTAINER(traf); +MBMFF_DEFINE_PURE_CONTAINER(mfra); +MBMFF_DEFINE_PURE_CONTAINER(iprp); +MBMFF_DEFINE_PURE_CONTAINER(ipco); + +#undef MBMFF_DEFINE_PURE_CONTAINER + +#ifdef MBMFF_ENABLE_CONSTEXPR_TEST +static_assert( + mbmff::basic_box_view::validate({mbmff::box_header{}, std::span{}}) +); +#endif + +} // namespace mbmff diff --git a/include/mbmff/boxes/ftyp.hpp b/include/mbmff/boxes/ftyp.hpp new file mode 100644 index 0000000..b5a90d2 --- /dev/null +++ b/include/mbmff/boxes/ftyp.hpp @@ -0,0 +1,59 @@ +#pragma once +#include "box_view.hpp" + +namespace mbmff { +struct ftyp_data { + mbmff::fourcc_string major_brand{}; + std::uint32_t minor_version = 0; + std::span compatible_brands{}; +}; + +template <> +struct basic_box_view : public mbmff::box_view_base { + constexpr static box_properties properties = box_properties::none; + constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; + constexpr auto value() const noexcept -> mbmff::ftyp_data; +}; + +inline constexpr auto mbmff::basic_box_view::validate(mbmff::any_box_view box) noexcept + -> mbmff::result +{ + if (box.payload.size() < 8) { + return mbmff::make_result(mbmff::error_code::need_more_data, 8); + } + + if (box.payload.size() % 4 != 0) { + return mbmff::make_result(mbmff::error_code::invalid_format); + } + + return {box}; +} + +constexpr auto mbmff::basic_box_view::value() const noexcept -> mbmff::ftyp_data +{ + const auto compatible_data = payload.subspan(8); + std::span compatible_brands( + reinterpret_cast(compatible_data.data()), + compatible_data.size() / 4 + ); + return mbmff::ftyp_data{ + fourcc_string::from_data(payload), + mbmff::read_be(payload.subspan(4)), + compatible_brands + }; +} +#ifdef MBMFF_ENABLE_CONSTEXPR_TEST +// validate must reject payload < 8 bytes +static_assert(!mbmff::basic_box_view::validate( + {mbmff::box_header{}, std::span{}})); + +// validate must reject payload not divisible by 4 +static_assert([] { + constexpr std::byte bad_div[10]{}; + auto r = mbmff::basic_box_view::validate( + {mbmff::box_header{}, std::span(bad_div)}); + return !r; +}()); +#endif + +} // namespace mbmff diff --git a/include/mbmff/boxes/hdlr.hpp b/include/mbmff/boxes/hdlr.hpp new file mode 100644 index 0000000..712a6f6 --- /dev/null +++ b/include/mbmff/boxes/hdlr.hpp @@ -0,0 +1,45 @@ +#pragma once +#include "box_view.hpp" + +namespace mbmff { + +struct hdlr_data { + mbmff::fourcc_string handler_type{}; + std::string_view name{}; +}; + +template <> +struct basic_box_view : public mbmff::box_view_base { + constexpr static mbmff::box_properties properties = mbmff::box_properties::full_box; + constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; + constexpr auto value() const noexcept -> mbmff::hdlr_data; +}; + +inline constexpr auto basic_box_view::validate(mbmff::any_box_view box) noexcept + -> mbmff::result +{ + if (box.payload.size() < 4) { + return mbmff::make_result(mbmff::error_code::need_more_data, 4); + } + box.fill_full_header(box.payload); + box.payload = box.payload.subspan(4); + + if (box.payload.size() < 20) { + return mbmff::make_result(mbmff::error_code::need_more_data, 20); + } + return {box}; +} + +inline constexpr auto basic_box_view::value() const noexcept -> mbmff::hdlr_data +{ + mbmff::hdlr_data result{}; + result.handler_type = mbmff::fourcc_string::from_data(payload.subspan(4)); + auto name_span = payload.subspan(16); + if (!name_span.empty()) { + auto name = mbmff::read_cstr(name_span, 0); + result.name = name.value; + } + return result; +} + +} // namespace mbmff diff --git a/include/mbmff/boxes/iinf.hpp b/include/mbmff/boxes/iinf.hpp new file mode 100644 index 0000000..b2d1663 --- /dev/null +++ b/include/mbmff/boxes/iinf.hpp @@ -0,0 +1,51 @@ +#pragma once +#include "box_view.hpp" + +namespace mbmff { + +struct iinf_data { + std::uint32_t entry_count = 0; +}; + +template <> +struct basic_box_view : public mbmff::box_view_base { + constexpr static mbmff::box_properties properties + = mbmff::box_properties::full_box | mbmff::box_properties::container; + constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; + constexpr auto value() const noexcept -> mbmff::iinf_data; +}; + +inline constexpr auto basic_box_view::validate(mbmff::any_box_view box) noexcept + -> mbmff::result +{ + if (box.payload.size() < 4) { + return mbmff::make_result(mbmff::error_code::need_more_data, 4); + } + box.fill_full_header(box.payload); + box.payload = box.payload.subspan(4); + + auto entry_size = (box.version() == 0) ? std::size_t{2} : std::size_t{4}; + if (box.payload.size() < entry_size) { + return mbmff::make_result(mbmff::error_code::need_more_data, entry_size); + } + box.payload = box.payload.subspan(entry_size); + return {box}; +} + +inline constexpr auto basic_box_view::value() const noexcept -> mbmff::iinf_data +{ + mbmff::iinf_data result{}; + auto entry_size = (version() == 0) ? std::size_t{2} : std::size_t{4}; + if (payload.size() == 0 && payload.data() == nullptr) { + return result; + } + auto* data = payload.data() - static_cast(entry_size); + if (version() == 0) { + result.entry_count = mbmff::read_be(std::span(data, 2)); + } else { + result.entry_count = mbmff::read_be(std::span(data, 4)); + } + return result; +} + +} // namespace mbmff diff --git a/include/mbmff/boxes/iloc.hpp b/include/mbmff/boxes/iloc.hpp new file mode 100644 index 0000000..69362d8 --- /dev/null +++ b/include/mbmff/boxes/iloc.hpp @@ -0,0 +1,268 @@ +#pragma once +#include "box_view.hpp" + +namespace mbmff { + +struct iloc_extent { + std::uint64_t index = 0; + std::uint64_t offset = 0; + std::uint64_t length = 0; +}; + +struct iloc_item { + std::uint64_t base_offset = 0; + std::uint32_t item_id = 0; + + std::uint8_t iloc_version = 0; + std::uint8_t offset_size = 0; + std::uint8_t length_size = 0; + std::uint8_t index_size = 0; + + std::uint8_t construction_method = 0; + + std::uint16_t data_reference_index = 0; + std::uint32_t extent_count = 0; + + std::span extent_data{}; + +public: + constexpr auto get_extent(std::size_t index) const noexcept -> mbmff::iloc_extent; + constexpr auto size() const noexcept -> std::size_t + { + return extent_count; + } + constexpr auto operator[](std::size_t index) const noexcept -> mbmff::iloc_extent + { + return get_extent(index); + } + constexpr auto extent_size() const noexcept -> std::size_t + { + return std::size_t(offset_size) + length_size + index_size; + } +}; + +constexpr auto mbmff::iloc_item::get_extent(std::size_t index) const noexcept -> mbmff::iloc_extent +{ + auto entry_span = extent_data.subspan(index * extent_size(), extent_size()); + mbmff::iloc_extent extent{}; + std::uint32_t offset = 0; + + if ((iloc_version == 1 || iloc_version == 2) && index_size > 0) { + if (index_size == 2) { + extent.index = mbmff::read_be(entry_span); + } else if (index_size == 4) { + extent.index = mbmff::read_be(entry_span); + } else if (index_size == 8) { + extent.index = mbmff::read_be(entry_span); + } + offset += index_size; + } + + if (offset_size == 2) { + extent.offset = mbmff::read_be(entry_span.subspan(offset)); + } else if (offset_size == 4) { + extent.offset = mbmff::read_be(entry_span.subspan(offset)); + } else if (offset_size == 8) { + extent.offset = mbmff::read_be(entry_span.subspan(offset)); + } + offset += offset_size; + + if (length_size == 2) { + extent.length = mbmff::read_be(entry_span.subspan(offset)); + } else if (length_size == 4) { + extent.length = mbmff::read_be(entry_span.subspan(offset)); + } else if (length_size == 8) { + extent.length = mbmff::read_be(entry_span.subspan(offset)); + } + return extent; +} + +struct iloc_data { + std::uint8_t offset_size = 0; + std::uint8_t length_size = 0; + std::uint8_t base_offset_size = 0; + std::uint8_t index_size = 0; + std::uint32_t item_count = 0; + std::span item_data{}; +}; + +template <> +struct basic_box_view : public mbmff::box_view_base { + constexpr static mbmff::box_properties properties = mbmff::box_properties::full_box; + constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; + constexpr auto value() const noexcept -> mbmff::iloc_data; +}; + +inline constexpr auto mbmff::basic_box_view::validate(mbmff::any_box_view box) noexcept + -> mbmff::result +{ + if (box.payload.size() < 4) { + return mbmff::make_result(mbmff::error_code::need_more_data, 4); + } + box.fill_full_header(box.payload); + box.payload = box.payload.subspan(4); + + if (box.payload.size() < 2) { + return mbmff::make_result(mbmff::error_code::need_more_data, 2); + } + auto entry_size = (box.version() < 2) ? std::size_t{2} : std::size_t{4}; + if (box.payload.size() < 2 + entry_size) { + return mbmff::make_result(mbmff::error_code::need_more_data, 2 + entry_size); + } + return {box}; +} + +inline constexpr auto mbmff::basic_box_view::value() const noexcept -> mbmff::iloc_data +{ + mbmff::iloc_data result{}; + if (payload.size() < 2) return result; + + auto first_byte = static_cast(payload[0]); + result.offset_size = (first_byte >> 4) & 0x0F; + result.length_size = first_byte & 0x0F; + auto second_byte = static_cast(payload[1]); + result.base_offset_size = (second_byte >> 4) & 0x0F; + result.index_size = second_byte & 0x0F; + + auto data = payload.subspan(2); + if (version() < 2) { + result.item_count = mbmff::read_be(data); + result.item_data = data.subspan(2); + } else { + result.item_count = mbmff::read_be(data); + result.item_data = data.subspan(4); + } + return result; +} + +class iloc_item_iterator { + using iterator_category = std::forward_iterator_tag; + using value_type = mbmff::iloc_item; + using difference_type = std::ptrdiff_t; + +private: + std::span remaining_; + std::uint8_t iloc_version_ = 0; + std::uint8_t offset_size_ = 0; + std::uint8_t length_size_ = 0; + std::uint8_t index_size_ = 0; + std::uint8_t base_offset_size_ = 0; + +public: + constexpr iloc_item_iterator() noexcept = default; + constexpr iloc_item_iterator(const mbmff::iloc_data& header, std::uint8_t version) noexcept + : remaining_(header.item_data) + , iloc_version_(version) + , offset_size_(header.offset_size) + , length_size_(header.length_size) + , index_size_(header.index_size) + , base_offset_size_(header.base_offset_size) + {} + constexpr explicit iloc_item_iterator(const mbmff::basic_box_view& box) noexcept + : iloc_item_iterator(box.value(), box.version()) + {} + +public: + constexpr auto extent_size() const noexcept -> std::size_t + { + return std::size_t(offset_size_) + length_size_ + (index_size_ > 0 ? index_size_ : 0); + } + constexpr auto begin() const noexcept -> mbmff::iloc_item_iterator + { + return *this; + } + constexpr auto end() const noexcept -> mbmff::iloc_item_iterator + { + return {}; + } + constexpr auto get() const noexcept -> mbmff::iloc_item + { + mbmff::iloc_item item{}; + if (remaining_.empty()) { + return item; + } + + std::size_t offset = 0; + + if (iloc_version_ < 2) { + item.item_id = mbmff::read_be(remaining_); + offset += 2; + } else { + item.item_id = mbmff::read_be(remaining_); + offset += 4; + } + + if (iloc_version_ == 1 || iloc_version_ == 2) { + auto value = mbmff::read_be(remaining_.subspan(offset)); + item.construction_method = static_cast(value & 0x000F); + offset += 2; + } + + item.data_reference_index = mbmff::read_be(remaining_.subspan(offset)); + offset += 2; + + switch (base_offset_size_) { + default: + case 0: + item.base_offset = 0; + break; + case 2: + item.base_offset = mbmff::read_be(remaining_.subspan(offset)); + break; + case 4: + item.base_offset = mbmff::read_be(remaining_.subspan(offset)); + break; + case 8: + item.base_offset = mbmff::read_be(remaining_.subspan(offset)); + break; + } + offset += base_offset_size_; + + item.extent_count = mbmff::read_be(remaining_.subspan(offset)); + item.extent_data = remaining_.subspan(offset + 2); + + item.iloc_version = iloc_version_; + item.offset_size = offset_size_; + item.length_size = length_size_; + item.index_size = index_size_; + return item; + } + + constexpr auto operator*() const noexcept -> mbmff::iloc_item + { + return get(); + } + + constexpr auto operator++() noexcept -> mbmff::iloc_item_iterator& + { + if (remaining_.empty()) { + remaining_ = {}; + return *this; + } + auto current_item = get(); + const std::byte* data = current_item.extent_data.data(); + std::size_t total_extent_size = current_item.extent_count * extent_size(); + + remaining_ = {data + total_extent_size, + remaining_.size() - (data - remaining_.data()) - total_extent_size}; + + return *this; + } + + constexpr auto operator++(int) noexcept -> mbmff::iloc_item_iterator + { + auto tmp = *this; + ++(*this); + return tmp; + } + constexpr bool operator==(const mbmff::iloc_item_iterator& other) const noexcept + { + if (remaining_.empty() && other.remaining_.empty()) { + return true; + } + return remaining_.data() == other.remaining_.data() + && remaining_.size() == other.remaining_.size(); + } +}; + +} // namespace mbmff diff --git a/include/mbmff/boxes/infe.hpp b/include/mbmff/boxes/infe.hpp new file mode 100644 index 0000000..5448acd --- /dev/null +++ b/include/mbmff/boxes/infe.hpp @@ -0,0 +1,85 @@ +#pragma once +#include "box_view.hpp" + +namespace mbmff { + +struct infe_data { + std::uint32_t item_id = 0; + std::uint16_t item_protection_index = 0; + mbmff::fourcc_string item_type{}; + std::string_view item_name{}; + std::string_view content_type{}; + std::string_view content_encoding{}; + std::string_view item_uri_type{}; +}; + +template <> +struct basic_box_view : public mbmff::box_view_base { + constexpr static mbmff::box_properties properties = mbmff::box_properties::full_box; + constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; + constexpr auto value() const noexcept -> mbmff::infe_data; +}; + +inline constexpr auto basic_box_view::validate(mbmff::any_box_view box) noexcept + -> mbmff::result +{ + if (box.payload.size() < 4) { + return mbmff::make_result(mbmff::error_code::need_more_data, 4); + } + box.fill_full_header(box.payload); + box.payload = box.payload.subspan(4); + + auto need = (box.version() == 0 || box.version() == 1 || box.version() == 2) + ? std::size_t{4} : std::size_t{6}; + if (box.payload.size() < need) { + return mbmff::make_result(mbmff::error_code::need_more_data, need); + } + return {box}; +} + +inline constexpr auto basic_box_view::value() const noexcept -> mbmff::infe_data +{ + mbmff::infe_data result{}; + auto xversion = version(); + std::size_t offset = 0; + + if (xversion == 0 || xversion == 1 || xversion == 2) { + result.item_id = mbmff::read_be(payload); + offset = 2; + } else { + result.item_id = mbmff::read_be(payload); + offset = 4; + } + + result.item_protection_index = mbmff::read_be(payload.subspan(offset)); + offset += 2; + + if (xversion >= 2) { + result.item_type = mbmff::fourcc_string::from_data(payload.subspan(offset)); + offset += 4; + } + + auto name = mbmff::read_cstr(payload, offset); + result.item_name = name.value; + offset = name.next; + + if (xversion >= 2) { + if (result.item_type.view() == "mime") { + auto type = mbmff::read_cstr(payload, offset); + result.content_type = type.value; + offset = type.next; + + auto encoding = mbmff::read_cstr(payload, offset); + result.content_encoding = encoding.value; + offset = encoding.next; + } else if (result.item_type.view() == "uri ") { + auto uri = mbmff::read_cstr(payload, offset); + result.item_uri_type = uri.value; + offset = uri.next; + } + } + + return result; +} + +} // namespace mbmff diff --git a/include/mbmff/boxes/ipma.hpp b/include/mbmff/boxes/ipma.hpp new file mode 100644 index 0000000..a0d97e4 --- /dev/null +++ b/include/mbmff/boxes/ipma.hpp @@ -0,0 +1,170 @@ +#pragma once +#include "box_view.hpp" + +namespace mbmff { + +struct ipma_association { + std::uint16_t essential : 1 = false; + std::uint16_t property_index : 15 = 0; +}; + +struct ipma_entry { + std::uint32_t item_id = 0; + std::uint8_t entry_count = 0; + std::uint8_t entry_size = 0; + std::span association_values{}; + +public: + constexpr auto operator[](std::size_t index) const noexcept -> mbmff::ipma_association; + constexpr auto size() const noexcept -> std::size_t + { + return entry_count; + } +}; + +constexpr auto ipma_entry::operator[](std::size_t index) const noexcept -> mbmff::ipma_association +{ + if (index >= entry_count) { + return {}; + } + mbmff::ipma_association association; + if (entry_size == 1) { + auto value = static_cast(association_values[index]); + association.essential = (value >> 7) & 0x01; + association.property_index = value & 0x7F; + } else { + auto value = mbmff::read_be(association_values.subspan(index * 2, 2)); + association.essential = (value >> 15) & 0x01; + association.property_index = value & 0x7FFF; + } + return association; +} + +struct ipma_data { + std::uint32_t entry_count = 0; + std::uint8_t index_size = 2; + std::uint8_t asoc_size = 1; + std::span entry_data{}; +}; + +template <> +struct basic_box_view : public mbmff::box_view_base { + constexpr static mbmff::box_properties properties = mbmff::box_properties::full_box; + constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; + constexpr auto value() const noexcept -> mbmff::ipma_data; +}; + +inline constexpr auto basic_box_view::validate(mbmff::any_box_view box) noexcept + -> mbmff::result +{ + if (box.payload.size() < 4) { + return mbmff::make_result(mbmff::error_code::need_more_data, 4); + } + box.fill_full_header(box.payload); + box.payload = box.payload.subspan(4); + + // entry_count (4 bytes) + at least one entry + if (box.payload.size() < 4) { + return mbmff::make_result(mbmff::error_code::need_more_data, 4); + } + return {box}; +} + +inline constexpr auto basic_box_view::value() const noexcept -> mbmff::ipma_data +{ + mbmff::ipma_data result{}; + result.entry_count = mbmff::read_be(payload); + result.index_size = (version() == 0) ? std::uint8_t{2} : std::uint8_t{4}; + result.asoc_size = (flags() & 0x01) ? std::uint8_t{2} : std::uint8_t{1}; + result.entry_data = payload.subspan(4); + return result; +} + +class ipma_entry_iterator { + using iterator_category = std::forward_iterator_tag; + using value_type = mbmff::ipma_entry; + using difference_type = std::ptrdiff_t; + +private: + std::uint32_t entry_count_ = 0; + std::uint8_t index_size_ = 0; + std::uint8_t asoc_size_ = 0; + std::span remaining_; + +public: + constexpr ipma_entry_iterator() noexcept = default; + constexpr explicit ipma_entry_iterator(const mbmff::ipma_data& data) noexcept + : entry_count_(data.entry_count) + , index_size_(data.index_size) + , asoc_size_(data.asoc_size) + , remaining_(data.entry_data) + {} + constexpr explicit ipma_entry_iterator(const mbmff::basic_box_view& box) noexcept + : ipma_entry_iterator(box.value()) + {} + +public: + constexpr auto begin() const noexcept -> mbmff::ipma_entry_iterator + { + return *this; + } + constexpr auto end() const noexcept -> mbmff::ipma_entry_iterator + { + return {}; + } + constexpr auto get() const noexcept -> mbmff::ipma_entry + { + mbmff::ipma_entry entry{}; + if (entry_count_ == 0 || remaining_.empty()) { + return entry; + } + if (index_size_ == 2) { + entry.item_id = mbmff::read_be(remaining_); + entry.association_values = remaining_.subspan(2); + } else { + entry.item_id = mbmff::read_be(remaining_); + entry.association_values = remaining_.subspan(4); + } + + entry.entry_count = static_cast(entry.association_values[0]); + entry.association_values = entry.association_values.subspan(1); + entry.entry_size = asoc_size_; + return entry; + } + constexpr auto operator*() const noexcept -> mbmff::ipma_entry + { + return get(); + } + constexpr auto operator++() noexcept -> mbmff::ipma_entry_iterator& + { + if (entry_count_ == 0 || remaining_.empty()) { + entry_count_ = 0; + remaining_ = {}; + return *this; + } + entry_count_--; + auto current_entry = get(); + + std::size_t offset = static_cast(current_entry.entry_count) * current_entry.entry_size + + index_size_ + 1; + + remaining_ = (offset >= remaining_.size()) ? std::span{} : remaining_.subspan(offset); + return *this; + } + constexpr auto operator++(int) noexcept -> mbmff::ipma_entry_iterator + { + auto tmp = *this; + ++(*this); + return tmp; + } + constexpr bool operator==(const mbmff::ipma_entry_iterator& other) const noexcept + { + if (entry_count_ == 0 && other.entry_count_ == 0) { + return true; + } + return entry_count_ == other.entry_count_ && remaining_.data() == other.remaining_.data() + && remaining_.size() == other.remaining_.size(); + } +}; + +} // namespace mbmff diff --git a/include/mbmff/boxes/iref.hpp b/include/mbmff/boxes/iref.hpp new file mode 100644 index 0000000..3134af9 --- /dev/null +++ b/include/mbmff/boxes/iref.hpp @@ -0,0 +1,24 @@ +#pragma once +#include "box_view.hpp" + +namespace mbmff { + +template <> +struct basic_box_view : public mbmff::box_view_base { + constexpr static mbmff::box_properties properties + = mbmff::box_properties::full_box | mbmff::box_properties::container; + constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; +}; + +inline constexpr auto basic_box_view::validate(mbmff::any_box_view box) noexcept + -> mbmff::result +{ + if (box.payload.size() < 4) { + return mbmff::make_result(mbmff::error_code::need_more_data, 4); + } + box.fill_full_header(box.payload); + box.payload = box.payload.subspan(4); + return {box}; +} + +} // namespace mbmff diff --git a/include/mbmff/boxes/ispe.hpp b/include/mbmff/boxes/ispe.hpp new file mode 100644 index 0000000..1137581 --- /dev/null +++ b/include/mbmff/boxes/ispe.hpp @@ -0,0 +1,41 @@ +#pragma once +#include "box_view.hpp" + +namespace mbmff { + +struct ispe_data { + std::uint32_t image_width = 0; + std::uint32_t image_height = 0; +}; + +template <> +struct basic_box_view : public mbmff::box_view_base { + constexpr static mbmff::box_properties properties = mbmff::box_properties::full_box; + constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; + constexpr auto value() const noexcept -> mbmff::ispe_data; +}; + +inline constexpr auto basic_box_view::validate(mbmff::any_box_view box) noexcept + -> mbmff::result +{ + if (box.payload.size() < 4) { + return mbmff::make_result(mbmff::error_code::need_more_data, 4); + } + box.fill_full_header(box.payload); + box.payload = box.payload.subspan(4); + + if (box.payload.size() < 8) { + return mbmff::make_result(mbmff::error_code::need_more_data, 8); + } + return {box}; +} + +inline constexpr auto basic_box_view::value() const noexcept -> mbmff::ispe_data +{ + mbmff::ispe_data result{}; + result.image_width = mbmff::read_be(payload); + result.image_height = mbmff::read_be(payload.subspan(4)); + return result; +} + +} // namespace mbmff diff --git a/include/mbmff/boxes/mdat.hpp b/include/mbmff/boxes/mdat.hpp new file mode 100644 index 0000000..1be213f --- /dev/null +++ b/include/mbmff/boxes/mdat.hpp @@ -0,0 +1,17 @@ +#pragma once +#include "box_view.hpp" + +namespace mbmff { +template <> +struct basic_box_view : public mbmff::box_view_base { + constexpr static mbmff::box_properties properties = mbmff::box_properties::none; +}; + +#ifdef MBMFF_ENABLE_CONSTEXPR_TEST +// mdat always accepts any payload +static_assert( + mbmff::basic_box_view::validate({mbmff::box_header{}, std::span{}}) +); +#endif + +} // namespace mbmff diff --git a/include/mbmff/boxes/meta.hpp b/include/mbmff/boxes/meta.hpp new file mode 100644 index 0000000..fba5273 --- /dev/null +++ b/include/mbmff/boxes/meta.hpp @@ -0,0 +1,46 @@ +#pragma once +#include "box_view.hpp" + +namespace mbmff { +template <> +struct basic_box_view : public mbmff::box_view_base { + // full_box is not ticked here because `meta` can + // be a regular box in some formats (e.g. QTFF) + constexpr static mbmff::box_properties properties = mbmff::box_properties::container; + constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; +}; + +inline constexpr auto basic_box_view::validate(mbmff::any_box_view box) noexcept + -> mbmff::result +{ + constexpr auto hdlr_type = "hdlr"; + bool has_full_box = false; + + if (box.payload.size() < 4) { + return mbmff::make_result(mbmff::error_code::need_more_data, 4); + } + + for (int i = 0; i < 4; ++i) { + if (box.payload[i] != std::byte(hdlr_type[i])) { + has_full_box = true; + break; + } + } + + if (has_full_box) { + box.fill_full_header(box.payload); + box.payload = box.payload.subspan(4); + } + return {box}; +} + +#ifdef MBMFF_ENABLE_CONSTEXPR_TEST +// payload starting with "hdlr" → not full_box → passes validation +static_assert([] { + constexpr std::byte no_fullbox[4]{std::byte{'h'}, std::byte{'d'}, std::byte{'l'}, std::byte{'r'}}; + auto r = mbmff::basic_box_view::validate({mbmff::box_header{}, std::span(no_fullbox)}); + return static_cast(r); +}()); +#endif + +} // namespace mbmff diff --git a/include/mbmff/boxes/pasp.hpp b/include/mbmff/boxes/pasp.hpp new file mode 100644 index 0000000..8f759e8 --- /dev/null +++ b/include/mbmff/boxes/pasp.hpp @@ -0,0 +1,35 @@ +#pragma once +#include "box_view.hpp" + +namespace mbmff { + +struct pasp_data { + std::uint32_t h_spacing = 0; + std::uint32_t v_spacing = 0; +}; + +template <> +struct basic_box_view : public mbmff::box_view_base { + constexpr static mbmff::box_properties properties = mbmff::box_properties::none; + constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; + constexpr auto value() const noexcept -> mbmff::pasp_data; +}; + +inline constexpr auto basic_box_view::validate(mbmff::any_box_view box) noexcept + -> mbmff::result +{ + if (box.payload.size() < 8) { + return mbmff::make_result(mbmff::error_code::need_more_data, 8); + } + return {box}; +} + +inline constexpr auto basic_box_view::value() const noexcept -> mbmff::pasp_data +{ + mbmff::pasp_data result{}; + result.h_spacing = mbmff::read_be(payload); + result.v_spacing = mbmff::read_be(payload.subspan(4)); + return result; +} + +} // namespace mbmff diff --git a/include/mbmff/boxes/pitm.hpp b/include/mbmff/boxes/pitm.hpp new file mode 100644 index 0000000..6adbfb0 --- /dev/null +++ b/include/mbmff/boxes/pitm.hpp @@ -0,0 +1,44 @@ +#pragma once +#include "box_view.hpp" + +namespace mbmff { + +struct pitm_data { + std::uint32_t item_id = 0; +}; + +template <> +struct basic_box_view : public mbmff::box_view_base { + constexpr static mbmff::box_properties properties = mbmff::box_properties::full_box; + constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; + constexpr auto value() const noexcept -> mbmff::pitm_data; +}; + +inline constexpr auto basic_box_view::validate(mbmff::any_box_view box) noexcept + -> mbmff::result +{ + if (box.payload.size() < 4) { + return mbmff::make_result(mbmff::error_code::need_more_data, 4); + } + box.fill_full_header(box.payload); + box.payload = box.payload.subspan(4); + + auto need = (box.version() == 0) ? std::size_t{2} : std::size_t{4}; + if (box.payload.size() < need) { + return mbmff::make_result(mbmff::error_code::need_more_data, need); + } + return {box}; +} + +inline constexpr auto basic_box_view::value() const noexcept -> mbmff::pitm_data +{ + mbmff::pitm_data result{}; + if (version() == 0) { + result.item_id = mbmff::read_be(payload); + } else { + result.item_id = mbmff::read_be(payload); + } + return result; +} + +} // namespace mbmff diff --git a/include/mbmff/boxes/pixi.hpp b/include/mbmff/boxes/pixi.hpp new file mode 100644 index 0000000..01aa101 --- /dev/null +++ b/include/mbmff/boxes/pixi.hpp @@ -0,0 +1,45 @@ +#pragma once +#include "box_view.hpp" + +namespace mbmff { + +struct pixi_data { + std::span bits_per_channel{}; +}; + +template <> +struct basic_box_view : public mbmff::box_view_base { + constexpr static mbmff::box_properties properties = mbmff::box_properties::full_box; + constexpr static auto validate(mbmff::any_box_view box) noexcept -> mbmff::result; + constexpr auto value() const noexcept -> mbmff::pixi_data; +}; + +inline constexpr auto basic_box_view::validate(mbmff::any_box_view box) noexcept + -> mbmff::result +{ + if (box.payload.size() < 4) { + return mbmff::make_result(mbmff::error_code::need_more_data, 4); + } + box.fill_full_header(box.payload); + box.payload = box.payload.subspan(4); + + if (box.payload.size() < 1) { + return mbmff::make_result(mbmff::error_code::need_more_data, 1); + } + return {box}; +} + +inline constexpr auto basic_box_view::value() const noexcept -> mbmff::pixi_data +{ + mbmff::pixi_data result{}; + auto num_channels = static_cast(payload[0]); + auto remaining = payload.size() - 1; + auto count = (num_channels < remaining) ? num_channels : static_cast(remaining); + result.bits_per_channel = std::span( + static_cast(static_cast(payload.data() + 1)), + count + ); + return result; +} + +} // namespace mbmff diff --git a/include/mbmff/common.hpp b/include/mbmff/common.hpp deleted file mode 100644 index 692b5aa..0000000 --- a/include/mbmff/common.hpp +++ /dev/null @@ -1,121 +0,0 @@ -#pragma once -#include -#include -#include - -namespace mbmff { -enum class error_code { - success = 0, - invalid_format, - need_more_data, -}; - -//------------------------------------------------------------------------------------------------------------ -static constexpr auto get_error_message(error_code code) noexcept -> std::string_view -{ - switch (code) { - case error_code::success: - return "Success"; - case error_code::invalid_format: - return "Invalid format"; - case error_code::need_more_data: - return "Need more data"; - default: - return "Unknown error code"; - } -} -//------------------------------------------------------------------------------------------------------------ -template -struct parsed { - Type value; - std::size_t consumed; -}; -struct unexpected { - mbmff::error_code code; - std::size_t needed; -}; - -constexpr auto floor_log2(std::uint64_t value) noexcept -> std::uint32_t -{ - std::uint32_t log = 0; - while (value != 0) { - value >>= 1; - log++; - } - return log - 1; -} - -//------------------------------------------------------------------------------------------------------------ -struct bit_reader { - std::span data{}; - std::size_t bit_offset = 0; - -public: - template - constexpr auto read_bits(std::size_t num_bits) noexcept -> T - { - T value = 0; - while (num_bits > 0) { - std::size_t byte_index = bit_offset / 8; - std::size_t bit_index = bit_offset % 8; - - std::size_t bits_avail = 8 - bit_index; - std::size_t bits_to_read = std::min(num_bits, bits_avail); - - std::uint8_t byte_val = static_cast(data[byte_index]); - - std::size_t shift = bits_avail - bits_to_read; - std::size_t mask = (1ULL << bits_to_read) - 1; - - value <<= bits_to_read; - value |= static_cast((byte_val >> shift) & mask); - - bit_offset += bits_to_read; - num_bits -= bits_to_read; - } - return value; - } - - constexpr auto read_flag() noexcept -> bool - { - return read_bits(1) != 0; - } - - constexpr auto uvlc() noexcept -> std::uint32_t - { - std::size_t leading_zero_bits = 0; - while (!read_bits(1)) { - if (++leading_zero_bits >= 32) { - return (1ull << 32) - 1; - } - } - std::size_t value = read_bits(leading_zero_bits); - return value + ((1u << leading_zero_bits) - 1); - } - - // non-symmetric - constexpr auto ns(std::size_t n) -> std::uint32_t - { - std::uint32_t w = floor_log2(n) + 1; - std::uint32_t m = (1u << w) - n; - std::uint64_t v = read_bits(w - 1); - - if (v < m) { - return v; - } - - std::uint32_t extra_bit = read_bits(1); - return (v << 1) - m + extra_bit; - } - - constexpr auto su(std::size_t n) -> std::int32_t - { - std::uint32_t v = read_bits(n); - std::uint32_t sign_bit = 1u << (n - 1); - if (v & sign_bit) { - return static_cast(v) - 2 * sign_bit; - } - return static_cast(v); - } -}; -} // namespace mbmff diff --git a/include/mbmff/mbmff.hpp b/include/mbmff/mbmff.hpp index 321dea1..5833c0d 100644 --- a/include/mbmff/mbmff.hpp +++ b/include/mbmff/mbmff.hpp @@ -1,930 +1,149 @@ #pragma once -#include -#include -#include -#include -#include -#include -#include -#include "av1.hpp" - -#define MBMFF_ITERATE_BOX_TYPES(MACRO) \ - MACRO(ftyp) \ - MACRO(meta) \ - MACRO(mdat) \ - MACRO(moov) \ - MACRO(trak) \ - MACRO(mdia) \ - MACRO(minf) \ - MACRO(stbl) \ - MACRO(dinf) \ - MACRO(edts) \ - MACRO(udta) \ - MACRO(mvex) \ - MACRO(moof) \ - MACRO(traf) \ - MACRO(mfra) \ - MACRO(iprp) \ - MACRO(ipco) \ - MACRO(iinf) \ - MACRO(iref) \ - MACRO(iloc) \ - MACRO(hdlr) \ - MACRO(pitm) \ - MACRO(ispe) \ - MACRO(av1C) \ - MACRO(pixi) \ - MACRO(ipma) \ - MACRO(pasp) \ - MACRO(infe) +#include "boxes/ftyp.hpp" +#include "boxes/meta.hpp" +#include "boxes/mdat.hpp" +#include "boxes/containers.hpp" +#include "boxes/av1C.hpp" +#include "boxes/iinf.hpp" +#include "boxes/iref.hpp" +#include "boxes/iloc.hpp" +#include "boxes/hdlr.hpp" +#include "boxes/pitm.hpp" +#include "boxes/ispe.hpp" +#include "boxes/pixi.hpp" +#include "boxes/pasp.hpp" +#include "boxes/ipma.hpp" +#include "boxes/infe.hpp" namespace mbmff { -//------------------------------------------------------------------------------------------------------------ -constexpr auto fourcc(const char* str) noexcept -> uint32_t -{ - return (static_cast(str[3]) << 24) | (static_cast(str[2]) << 16) - | (static_cast(str[1]) << 8) | static_cast(str[0]); -} - -#define MBMFF_ITERATE_ENUM(name) name = fourcc(#name), - -enum class box_type : uint32_t { unknown, MBMFF_ITERATE_BOX_TYPES(MBMFF_ITERATE_ENUM) }; - -//------------------------------------------------------------------------------------------------------------ -enum class box_properties : uint32_t { - none = 0, - full_box = 1 << 0, - container = 1 << 1, -}; -constexpr auto operator|(box_properties a, box_properties b) noexcept -> box_properties -{ - return static_cast(std::to_underlying(a) | std::to_underlying(b)); -} -constexpr auto operator&(box_properties a, box_properties b) noexcept -> box_properties -{ - return static_cast(std::to_underlying(a) & std::to_underlying(b)); -} -constexpr auto operator+(box_properties a) noexcept -> std::underlying_type_t -{ - return std::to_underlying(a); -} -constexpr auto has(box_properties a, box_properties check) noexcept -> bool -{ - return (a & check) != box_properties::none; -} - -//------------------------------------------------------------------------------------------------------------ -struct fourcc_string { - std::array data{}; - -public: - constexpr auto view() const noexcept -> std::string_view - { - return std::string_view(data.data(), data.size()); - } - constexpr operator std::string_view() const noexcept - { - return view(); - } - constexpr static auto from_data(std::span data) noexcept -> fourcc_string - { - return fourcc_string{std::array{ - static_cast(data[0]), - static_cast(data[1]), - static_cast(data[2]), - static_cast(data[3]), - }}; - } -}; - -//------------------------------------------------------------------------------------------------------------ -struct box_header { - std::uint64_t size = 0; - mbmff::box_type type{}; - std::uint8_t version = 0; - std::array flags{}; - -public: - constexpr auto type_string() const noexcept -> fourcc_string - { - std::uint32_t value = std::to_underlying(type); - std::array str{ - char(value & 0xFF), - char((value >> 8) & 0xFF), - char((value >> 16) & 0xFF), - char((value >> 24) & 0xFF), - }; - return {str}; - } - constexpr auto flags_value() const noexcept -> std::uint32_t - { - return (static_cast(flags[0]) << 16) | (static_cast(flags[1]) << 8) - | static_cast(flags[2]); - } - - // Assumes there is enough data in the buffer (i.e. the caller should have already checked that the full header is - // present) - constexpr void fill_full_header(std::span data) noexcept - { - version = static_cast(data[0]); - flags[0] = static_cast(data[1]); - flags[1] = static_cast(data[2]); - flags[2] = static_cast(data[3]); - } -}; - -struct infe_header { - std::uint32_t item_id = 0; - std::uint16_t item_protection_index = 0; - mbmff::fourcc_string item_type{}; - std::string_view item_name{}; - std::string_view content_type{}; - std::string_view content_encoding{}; - std::string_view item_uri_type{}; -}; - -struct ftyp_header { - mbmff::fourcc_string major_brand{}; - std::uint32_t minor_version = 0; - std::span compatible_brands{}; -}; - -struct hdlr_header { - mbmff::fourcc_string handler_type{}; - std::string_view name{}; -}; - -struct iloc_extent { - std::uint64_t index = 0; - std::uint64_t offset = 0; - std::uint64_t length = 0; -}; - -struct iloc_item { - std::uint64_t base_offset = 0; - std::uint32_t item_id = 0; - - // Redundant fields for easier parsing - std::uint8_t iloc_version = 0; - std::uint8_t offset_size = 0; - std::uint8_t length_size = 0; - std::uint8_t index_size = 0; - - std::uint8_t construction_method = 0; - - std::uint16_t data_reference_index = 0; - std::uint32_t extent_count = 0; - - std::span extent_data{}; - -public: - constexpr auto get_extent(std::size_t index) const noexcept -> iloc_extent; - constexpr auto size() const noexcept -> std::size_t - { - return extent_count; - } - constexpr auto operator[](std::size_t index) const noexcept -> iloc_extent - { - return get_extent(index); - } - constexpr auto extent_size() const noexcept -> std::size_t - { - return std::size_t(offset_size) + length_size + index_size; - } -}; - -struct iloc_header { - std::uint8_t offset_size = 0; - std::uint8_t length_size = 0; - std::uint8_t base_offset_size = 0; - std::uint8_t index_size = 0; - std::uint32_t item_count = 0; - std::span item_data{}; -}; - -struct ispe_header { - std::uint32_t image_width = 0; - std::uint32_t image_height = 0; -}; - -struct ipma_association { - std::uint16_t essential : 1 = false; - std::uint16_t property_index : 15 = 0; -}; - -struct ipma_entry { - std::uint32_t item_id = 0; - std::uint8_t entry_count = 0; - std::uint8_t entry_size = 0; // size in bytes of each association entry (1 or 2) - std::span association_values{}; - -public: - constexpr auto operator[](std::size_t index) const noexcept -> ipma_association; - constexpr auto size() const noexcept -> std::size_t; -}; - -//------------------------------------------------------------------------------------------------------------ - -using any_box_view = struct box_view_base; - -struct box_view_base { - mbmff::box_header box_header{}; - std::span payload{}; - -public: - constexpr operator bool() const noexcept - { - return box_header.size != 0; - } - constexpr static auto parse(any_box_view box) noexcept -> std::expected - { - return box; - } - constexpr auto version() const noexcept -> std::uint8_t - { - return box_header.version; - } -}; - -template -struct basic_box_view : public box_view_base { - constexpr static box_properties properties = box_properties::none; -}; - -//------------------------------------------------------------------------------------------------------------ -// Box-specific views -//------------------------------------------------------------------------------------------------------------ -template <> -struct basic_box_view : public box_view_base { - constexpr static box_properties properties = box_properties::none; - constexpr static auto parse(any_box_view box) noexcept -> std::expected; - -public: - constexpr auto header() const noexcept -> ftyp_header; - constexpr auto major_brand() const noexcept -> fourcc_string; - constexpr auto minor_version() const noexcept -> std::uint32_t; - constexpr auto compatible_brands() const noexcept -> std::span; -}; - -template <> -struct basic_box_view : public box_view_base { - // full_box is not ticked here because `meta` can - // be a regular box in some formats (e.g. QTFF) - constexpr static box_properties properties = box_properties::container; - constexpr static auto parse(any_box_view box) noexcept -> std::expected; -}; - -template <> -struct basic_box_view : public box_view_base { - constexpr auto data_size() const noexcept -> std::size_t; - constexpr static box_properties properties = box_properties::none; -}; - -template <> -struct basic_box_view : public box_view_base { - constexpr static box_properties properties = box_properties::container | box_properties::full_box; - constexpr static auto parse(any_box_view box) noexcept -> std::expected; - constexpr auto entry_count() const noexcept -> std::uint32_t; -}; - -template <> -struct basic_box_view : public box_view_base { - constexpr static box_properties properties = box_properties::full_box; - constexpr auto header() const noexcept -> infe_header; - constexpr auto item_id() const noexcept -> std::uint32_t; - constexpr auto item_protection_index() const noexcept -> std::uint16_t; -}; - -template <> -struct basic_box_view : public box_view_base { - constexpr static box_properties properties = box_properties::full_box; - constexpr auto header() const noexcept -> hdlr_header; -}; - -template <> -struct basic_box_view : public box_view_base { - constexpr static box_properties properties = box_properties::full_box; - constexpr auto item_id() const noexcept -> uint32_t; -}; - -template <> -struct basic_box_view : public box_view_base { - constexpr static box_properties properties = box_properties::full_box; - constexpr auto header() const noexcept -> iloc_header; -}; - -template <> -struct basic_box_view : public box_view_base { - constexpr static box_properties properties = box_properties::container; -}; - -template <> -struct basic_box_view : public box_view_base { - constexpr static box_properties properties = box_properties::container; -}; - -template <> -struct basic_box_view : public box_view_base { - constexpr static box_properties properties = box_properties::full_box; - constexpr auto header() const noexcept -> ispe_header; -}; - -template <> -struct basic_box_view : public box_view_base { - constexpr static box_properties properties = box_properties::none; - constexpr auto header() const noexcept -> av1::av1C_header; -}; - -template <> -struct basic_box_view : public box_view_base { - constexpr static box_properties properties = box_properties::full_box; - constexpr auto bits_per_channel() const noexcept -> std::span; -}; - -template <> -struct basic_box_view : public box_view_base { - constexpr static box_properties properties = box_properties::full_box; -}; - -template <> -struct basic_box_view : public box_view_base { - constexpr static box_properties properties = box_properties::none; - constexpr auto aspect_ratio() const noexcept -> std::pair; -}; - -//------------------------------------------------------------------------------------------------------------ -#define MBMFF_ITERATE_USING(box) using box##_box = basic_box_view; +#define MBMFF_ITERATE_USING(name) using name##_box = basic_box_view; MBMFF_ITERATE_BOX_TYPES(MBMFF_ITERATE_USING) #undef MBMFF_ITERATE_USING -//------------------------------------------------------------------------------------------------------------ -// Implementation -//------------------------------------------------------------------------------------------------------------ -template -constexpr auto read_be(std::span data) noexcept -> T -{ - T value{}; - if (std::is_constant_evaluated()) { - for (std::size_t i = 0; i < sizeof(T); ++i) { - value <<= 8; - value |= static_cast(data[i]); - } - } else { - std::memcpy(&value, data.data(), sizeof(value)); // strict aliasing - } - return std::byteswap(value); -} - -struct parsed_cstr { - std::string_view value{}; - std::size_t next = 0; -}; - -constexpr auto read_cstr(std::span data, std::size_t offset) noexcept -> parsed_cstr -{ - if (offset >= data.size()) { - return {}; - } - - const auto* begin = reinterpret_cast(data.data() + offset); - std::size_t length = 0; - for (std::size_t i = offset; i < data.size(); ++i) { - if (data[i] == std::byte{0}) { - break; - } - ++length; - } - - std::size_t next = offset + length; - if (next < data.size() && data[next] == std::byte{0}) { - ++next; - } - - return {std::string_view(begin, length), next}; -} - -//------------------------------------------------------------------------------------------------------------ -constexpr inline auto parse_box_header(std::span data) noexcept - -> std::expected, unexpected> -{ - if (data.size() < 8) { - return std::unexpected(unexpected{error_code::need_more_data, 8}); - } - - std::uint64_t size = read_be(data); - if (size == 0) { - return std::unexpected(unexpected{error_code::invalid_format, 8}); - } - - // Read the header type (4 bytes) - box_type type = box_type(fourcc(reinterpret_cast(data.data() + 4))); - if (size == 1) { - if (data.size() < 16) { - return std::unexpected(unexpected{error_code::need_more_data, 16}); - } - size = read_be(data.subspan(8)); - } - - return parsed{box_header{size, type}, size == 1 ? 16u : 8u}; -} - -inline constexpr auto get_box_properties(box_type type) noexcept -> box_properties +inline constexpr auto get_box_properties(mbmff::box_type type) noexcept -> mbmff::box_properties { -#define MBMFF_CASE_ITERATE(box) \ - case box_type::box: \ - return basic_box_view::properties; - +#define MBMFF_CASE_PROP(name) \ + case mbmff::box_type::name: return mbmff::basic_box_view::properties; switch (type) { - MBMFF_ITERATE_BOX_TYPES(MBMFF_CASE_ITERATE) + MBMFF_ITERATE_BOX_TYPES(MBMFF_CASE_PROP) default: - return box_properties::none; + return mbmff::box_properties::none; } -#undef MBMFF_CASE_ITERATE +#undef MBMFF_CASE_PROP } -inline constexpr auto parse(std::span data) noexcept -> std::expected +inline constexpr auto parse(std::span data) noexcept -> mbmff::result { - auto header_result = parse_box_header(data); + auto header_result = mbmff::parse_box_header(data); if (!header_result) { - return std::unexpected(header_result.error()); + return mbmff::make_result(header_result.code, header_result.needed); } - auto header = header_result->value; - if (header.size != 0 && data.size() < header.size) { - return std::unexpected(unexpected{error_code::need_more_data, static_cast(header.size)}); - } - if (header.size != 0 && header.size < header_result->consumed) { - return std::unexpected(unexpected{error_code::invalid_format, header_result->consumed}); - } - - std::size_t payload_size = (header.size == 0) ? (data.size() - header_result->consumed) - : (static_cast(header.size) - header_result->consumed); - auto payload_span = data.subspan(header_result->consumed, payload_size); - - box_properties properties = get_box_properties(header.type); - if ((properties & box_properties::full_box) != box_properties::none) { - if (payload_span.size() < 4) { - return std::unexpected(unexpected{error_code::need_more_data, 4}); - } - - header.fill_full_header(payload_span); - payload_span = payload_span.subspan(4); - } - -#define MBMFF_CASE_PARSE(box) \ - case box_type::box: \ - return basic_box_view::parse(any_box_view{header, payload_span}); - - switch (header.type) { - MBMFF_ITERATE_BOX_TYPES(MBMFF_CASE_PARSE) - default: - return basic_box_view::parse(any_box_view{header, payload_span}); - } -#undef MBMFF_CASE_PARSE -} - -//------------------------------------------------------------------------------------------------------------ -// FTYP -inline constexpr auto basic_box_view::parse(any_box_view box) noexcept - -> std::expected -{ - if (box.payload.size() < 8) { - return std::unexpected(unexpected{error_code::need_more_data, 8}); - } - - if (box.payload.size() % 4 != 0) { - return std::unexpected(unexpected{error_code::invalid_format}); - } - - return box; -} -constexpr auto basic_box_view::header() const noexcept -> ftyp_header -{ - return ftyp_header{major_brand(), minor_version(), compatible_brands()}; -} -constexpr auto basic_box_view::major_brand() const noexcept -> fourcc_string -{ - return fourcc_string::from_data(payload); -} - -constexpr auto basic_box_view::minor_version() const noexcept -> std::uint32_t -{ - return read_be(payload.subspan(4)); -} - -constexpr auto basic_box_view::compatible_brands() const noexcept -> std::span -{ - const auto compatible_data = payload.subspan(8); - - return std::span( - reinterpret_cast(compatible_data.data()), - compatible_data.size() / 4 - ); -} - -//------------------------------------------------------------------------------------------------------------ -// META -inline constexpr auto basic_box_view::parse(any_box_view box) noexcept - -> std::expected -{ - // In a QT File, `meta` is a regular box. Its payload starts directly with a child box (e.g. `[size][hdlr]`). - // In ISOBMFF, `meta` is a Full Box. Its payload starts with 4 bytes (version+flags), then the child box - // `[size][hdlr]`. - constexpr auto hdlr_type = "hdlr"; - bool full_box = false; - - if (std::is_constant_evaluated()) { - for (int i = 0; i < 4; ++i) { - if (box.payload.size() < 4 || box.payload[i] != std::byte(hdlr_type[i])) { - full_box = true; - break; - } - } - } else { - full_box = std::memcmp(box.payload.data() + 4, hdlr_type, 4) != 0; - } - - if (full_box) { - box.box_header.fill_full_header(box.payload); - box.payload = box.payload.subspan(4); - } - return box; -} - -//------------------------------------------------------------------------------------------------------------ -// MDAT -constexpr auto basic_box_view::data_size() const noexcept -> std::size_t -{ - return payload.size(); -} + auto header_size = header_result->consumed; -//------------------------------------------------------------------------------------------------------------ -// IINF -inline constexpr auto basic_box_view::parse(any_box_view box) noexcept - -> std::expected -{ - if (box.version() == 0) { - box.payload = box.payload.subspan(2); // skip 2 bytes of reserved data - } else { - box.payload = box.payload.subspan(4); // skip 4 bytes of reserved data - } - return box; -} - -inline constexpr auto basic_box_view::entry_count() const noexcept -> std::uint32_t -{ - std::size_t reserved_size = (version() == 0) ? 2 : 4; - const std::byte* reserved_end = payload.data() - reserved_size; - std::span reserved_span(reserved_end, reserved_size); - - if (version() == 0) { - return read_be(reserved_span); - } - return read_be(reserved_span); -} - -//------------------------------------------------------------------------------------------------------------ -// INFE -inline constexpr auto basic_box_view::item_id() const noexcept -> std::uint32_t -{ - auto xversion = version(); - if (xversion == 0 || xversion == 1 || xversion == 2) { - return read_be(payload); - } else { - return read_be(payload); - } -} - -inline constexpr auto basic_box_view::item_protection_index() const noexcept -> std::uint16_t -{ - auto xversion = version(); - std::size_t offset = (xversion == 0 || xversion == 1 || xversion == 2) ? 2 : 4; - return read_be(payload.subspan(offset)); -} - -inline constexpr auto basic_box_view::header() const noexcept -> infe_header -{ - infe_header result{}; - auto xversion = version(); - std::size_t offset = 0; - - if (xversion == 0 || xversion == 1 || xversion == 2) { - result.item_id = read_be(payload); - offset = 2; - } else { - result.item_id = read_be(payload); - offset = 4; - } - - result.item_protection_index = read_be(payload.subspan(offset)); - offset += 2; - - if (xversion >= 2) { - result.item_type = fourcc_string::from_data(payload.subspan(offset)); - offset += 4; - } - - auto name = read_cstr(payload, offset); - result.item_name = name.value; - offset = name.next; - - if (xversion >= 2) { - if (result.item_type.view() == "mime") { - auto type = read_cstr(payload, offset); - result.content_type = type.value; - offset = type.next; - - auto encoding = read_cstr(payload, offset); - result.content_encoding = encoding.value; - offset = encoding.next; - } else if (result.item_type.view() == "uri ") { - auto uri = read_cstr(payload, offset); - result.item_uri_type = uri.value; - offset = uri.next; - } - } - - return result; -} - -//------------------------------------------------------------------------------------------------------------ -// HDLR -constexpr auto basic_box_view::header() const noexcept -> hdlr_header -{ - hdlr_header result{}; - // Skip archaic pre-ISOBMFF reserved fields (4 bytes) "mhlr" or "alis" - uint32_t reserved = read_be(payload); - - // Handler type is at offset 4 - result.handler_type = fourcc_string::from_data(payload.subspan(4)); - - // skip another 3x4 bytes of reserved data - auto subspan = payload.subspan(16); - if (!subspan.empty()) { - auto name = read_cstr(subspan, 0); - result.name = name.value; - } - return result; -} - -//------------------------------------------------------------------------------------------------------------ -// PITM -constexpr auto basic_box_view::item_id() const noexcept -> uint32_t -{ - if (version() == 0) { - return read_be(payload); - } - return read_be(payload); -} - -//------------------------------------------------------------------------------------------------------------ -// ILOC -constexpr auto basic_box_view::header() const noexcept -> iloc_header -{ - iloc_header result{}; - uint8_t first_byte = static_cast(payload[0]); - result.offset_size = (first_byte >> 4) & 0x0F; - result.length_size = first_byte & 0x0F; - uint8_t second_byte = static_cast(payload[1]); - result.base_offset_size = (second_byte >> 4) & 0x0F; - result.index_size = second_byte & 0x0F; - - auto xpayload = this->payload.subspan(2); - if (version() < 2) { - result.item_count = read_be(xpayload); - result.item_data = xpayload.subspan(2); - } else { - result.item_count = read_be(xpayload); - result.item_data = xpayload.subspan(4); - } - return result; -} - -constexpr auto mbmff::iloc_item::get_extent(std::size_t index) const noexcept -> iloc_extent -{ - std::span entry_span = extent_data.subspan(index * extent_size(), extent_size()); - iloc_extent extent{}; - - std::uint32_t offset = 0; - if ((iloc_version == 1 || iloc_version == 2) && index_size > 0) { - if (index_size == 2) { - extent.index = read_be(entry_span); - } else if (index_size == 4) { - extent.index = read_be(entry_span); - } else if (index_size == 8) { - extent.index = read_be(entry_span); - } - offset += index_size; - } - - if (offset_size == 2) { - extent.offset = read_be(entry_span.subspan(offset)); - } else if (offset_size == 4) { - extent.offset = read_be(entry_span.subspan(offset)); - } else if (offset_size == 8) { - extent.offset = read_be(entry_span.subspan(offset)); + if (header.size() != 0 && data.size() < header.size()) { + return mbmff::make_result( + mbmff::error_code::need_more_data, + static_cast(header.size()) + ); } - offset += offset_size; - - if (length_size == 2) { - extent.length = read_be(entry_span.subspan(offset)); - } else if (length_size == 4) { - extent.length = read_be(entry_span.subspan(offset)); - } else if (length_size == 8) { - extent.length = read_be(entry_span.subspan(offset)); - } - return extent; -} - -//------------------------------------------------------------------------------------------------------------ -// ISPE -constexpr auto basic_box_view::header() const noexcept -> ispe_header -{ - ispe_header result{}; - result.image_width = read_be(payload); - result.image_height = read_be(payload.subspan(4)); - return result; -} - -//------------------------------------------------------------------------------------------------------------ -// AV1C -constexpr auto basic_box_view::header() const noexcept -> av1::av1C_header -{ - av1::av1C_header result{}; - if (payload.size() < 3) { - return result; + if (header.size() != 0 && header.size() < header_size) { + return mbmff::make_result(mbmff::error_code::invalid_format); } - // Byte 0 - std::uint8_t read_byte = static_cast(payload[0]); - result.marker = (read_byte >> 7) & 0x01; - result.version = read_byte & 0x7F; + auto payload_size = (header.size() == 0) ? (data.size() - header_size) + : (static_cast(header.size()) - header_size); + auto payload_span = data.subspan(header_size, payload_size); - // Byte 1 - read_byte = static_cast(payload[1]); - result.seq_profile = (read_byte >> 5) & 0x07; - result.seq_level_idx_0 = read_byte & 0x1F; + mbmff::any_box_view box{{header.size_, header.type_}, payload_span}; - // Byte 2 - read_byte = static_cast(payload[2]); - result.seq_tier_0 = (read_byte >> 7) & 0x01; - result.high_bitdepth = (read_byte >> 6) & 0x01; - result.twelve_bit = (read_byte >> 5) & 0x01; - result.monochrome = (read_byte >> 4) & 0x01; - result.chroma_subsampling_x = (read_byte >> 3) & 0x01; - result.chroma_subsampling_y = (read_byte >> 2) & 0x01; - result.chroma_sample_position = read_byte & 0x03; +#define MBMFF_CASE_VALIDATE(name) \ + case mbmff::box_type::name: \ + return mbmff::basic_box_view::validate(box); - // Byte 3 - read_byte = static_cast(payload[3]); - result.reserved = (read_byte >> 5) & 0x07; - result.initial_presentation_delay_present = (read_byte >> 4) & 0x01; - if (result.initial_presentation_delay_present) { - result.initial_presentation_delay_minus_one = read_byte & 0x0F; + switch (header.type_) { + MBMFF_ITERATE_BOX_TYPES(MBMFF_CASE_VALIDATE) + default: + return {box}; } - - result.config_obus = payload.subspan(4); - return result; -} - -//------------------------------------------------------------------------------------------------------------ -// PIXI -constexpr auto basic_box_view::bits_per_channel() const noexcept -> std::span -{ - std::uint8_t size = static_cast(payload[0]); - return std::span( - static_cast(static_cast(payload.data() + 1)), - size - ); +#undef MBMFF_CASE_VALIDATE } -//------------------------------------------------------------------------------------------------------------ -// IPMA -constexpr auto ipma_entry::operator[](std::size_t index) const noexcept -> ipma_association +template +constexpr auto box_cast(const mbmff::any_box_view& box) noexcept -> mbmff::basic_box_view { - if (index >= entry_count) { + if (box.type() != Box) { return {}; } - ipma_association association; - if (entry_size == 1) { - std::uint8_t value = static_cast(association_values[index]); - association.essential = (value >> 7) & 0x01; - association.property_index = value & 0x7F; - } else { - std::uint16_t value = read_be(association_values.subspan(index * 2, 2)); - association.essential = (value >> 15) & 0x01; - association.property_index = value & 0x7FFF; - } - return association; -} -constexpr auto ipma_entry::size() const noexcept -> std::size_t -{ - return entry_count; -} - -//------------------------------------------------------------------------------------------------------------ -// PASP -inline constexpr auto basic_box_view::aspect_ratio() const noexcept -> std::pair -{ - return {read_be(payload), read_be(payload.subspan(4))}; + return static_cast>(box); } -//------------------------------------------------------------------------------------------------------------ -template -constexpr auto box_cast(const any_box_view& box) noexcept -> basic_box_view -{ - if (box.box_header.type != Box) { - return {}; - } - return static_cast>(box); -} - -//------------------------------------------------------------------------------------------------------------ -enum class iterator_flags : uint32_t { +enum class iterator_flags : std::uint32_t { none = 0, recursive = 1 << 0, }; -constexpr auto operator|(iterator_flags a, iterator_flags b) noexcept -> iterator_flags -{ - return static_cast(std::to_underlying(a) | std::to_underlying(b)); -} -constexpr auto operator&(iterator_flags a, iterator_flags b) noexcept -> iterator_flags -{ - return static_cast(std::to_underlying(a) & std::to_underlying(b)); -} -constexpr auto operator+(iterator_flags a) noexcept -> std::underlying_type_t -{ - return std::to_underlying(a); -} -constexpr auto has(iterator_flags a, iterator_flags check) noexcept -> bool -{ - return (a & check) != iterator_flags::none; -} +MBMFF_FLAG_OPERATORS(iterator_flags) struct box_iterator { using iterator_category = std::forward_iterator_tag; - using value_type = std::expected; + using value_type = mbmff::result; using difference_type = std::ptrdiff_t; private: std::span remaining_; - iterator_flags flags_ = iterator_flags::none; + mbmff::iterator_flags flags_ = mbmff::iterator_flags::none; public: constexpr box_iterator() noexcept = default; constexpr explicit box_iterator( std::span data, - iterator_flags flags = iterator_flags::none + mbmff::iterator_flags flags = mbmff::iterator_flags::none ) noexcept : remaining_(data) , flags_(flags) {} public: - constexpr auto begin() const noexcept -> box_iterator + constexpr auto begin() const noexcept -> mbmff::box_iterator { return *this; } - constexpr auto end() const noexcept -> box_iterator + constexpr auto end() const noexcept -> mbmff::box_iterator { return {}; } - constexpr auto try_get() const noexcept -> std::expected + constexpr auto try_get() const noexcept -> mbmff::result { - return parse(remaining_); + return mbmff::parse(remaining_); } - constexpr auto operator*() const noexcept -> std::expected + constexpr auto operator*() const noexcept -> mbmff::result { return try_get(); } - constexpr auto operator++() noexcept -> box_iterator& + constexpr auto operator++() noexcept -> mbmff::box_iterator& { if (remaining_.empty()) { remaining_ = {}; return *this; } - auto result = parse(remaining_); - if (!result) { + auto op_result = mbmff::parse(remaining_); + if (!op_result) { remaining_ = {}; return *this; } - // If recursive flag is set and the box is a container, iterate into it instead of moving to the next sibling - if (has(flags_, iterator_flags::recursive)) { - auto properties = get_box_properties(result->box_header.type); - - if (has(properties, box_properties::container)) { + if (mbmff::has(flags_, mbmff::iterator_flags::recursive)) { + auto properties = mbmff::get_box_properties(op_result->type_); + if (mbmff::has(properties, mbmff::box_properties::container)) { auto* current_end = &remaining_.back() + 1; - auto* payload_start = &result->payload[0]; - + auto* payload_start = op_result->payload.data(); remaining_ = std::span(payload_start, current_end); return *this; } } - std::size_t box_size = (result->box_header.size == 0) ? remaining_.size() - : static_cast(result->box_header.size); + auto box_size = (op_result->size_ == 0) ? remaining_.size() : static_cast(op_result->size_); if (box_size == 0 || box_size > remaining_.size()) { remaining_ = {}; } else { @@ -932,234 +151,14 @@ struct box_iterator { } return *this; } - constexpr auto operator++(int) noexcept -> box_iterator - { - auto tmp = *this; - ++(*this); - return tmp; - } - constexpr bool operator==(const box_iterator& other) const noexcept - { - // special case: both iterators are at the end (empty) - if (remaining_.empty() && other.remaining_.empty()) { - return true; - } - return remaining_.data() == other.remaining_.data() && remaining_.size() == other.remaining_.size(); - } -}; - -struct ipma_entry_iterator { - using iterator_category = std::forward_iterator_tag; - using value_type = mbmff::ipma_entry; - using difference_type = std::ptrdiff_t; - -private: - std::uint32_t entry_count_ = 0; - std::uint8_t index_size_ = 0; - std::uint8_t asoc_size_ = 0; - std::span remaining_; - -public: - constexpr ipma_entry_iterator() noexcept = default; - constexpr explicit ipma_entry_iterator(const basic_box_view& box) noexcept - : entry_count_(read_be(box.payload)) - , index_size_(box.version() == 0 ? 2 : 4) - , asoc_size_(box.box_header.flags_value() & 0x01 ? 2 : 1) - , remaining_(box.payload.subspan(4)) - {} - -public: - constexpr auto begin() const noexcept -> ipma_entry_iterator - { - return *this; - } - constexpr auto end() const noexcept -> ipma_entry_iterator - { - return {}; - } - constexpr auto get() const noexcept -> ipma_entry - { - ipma_entry entry{}; - if (entry_count_ == 0 || remaining_.empty()) { - return entry; - } - if (index_size_ == 2) { - entry.item_id = read_be(remaining_); - entry.association_values = remaining_.subspan(2); - } else { - entry.item_id = read_be(remaining_); - entry.association_values = remaining_.subspan(4); - } - - entry.entry_count = static_cast(entry.association_values[0]); - entry.association_values = entry.association_values.subspan(1); - entry.entry_size = asoc_size_; - return entry; - } - constexpr auto operator*() const noexcept -> ipma_entry - { - return get(); - } - constexpr auto operator++() noexcept -> ipma_entry_iterator& - { - if (entry_count_ == 0 || remaining_.empty()) { - entry_count_ = 0; - remaining_ = {}; - return *this; - } - entry_count_--; - auto current_entry = get(); - - // Get the size of the current entry - std::size_t offset = static_cast(current_entry.entry_count) * current_entry.entry_size - + index_size_ + 1; - - remaining_ = (offset >= remaining_.size()) ? std::span{} : remaining_.subspan(offset); - return *this; - } - constexpr auto operator++(int) noexcept -> ipma_entry_iterator - { - auto tmp = *this; - ++(*this); - return tmp; - } - constexpr bool operator==(const ipma_entry_iterator& other) const noexcept - { - // special case: both iterators are at the end (empty) - if (entry_count_ == 0 && other.entry_count_ == 0) { - return true; - } - return entry_count_ == other.entry_count_ && remaining_.data() == other.remaining_.data() - && remaining_.size() == other.remaining_.size(); - } -}; - -struct iloc_item_iterator { - using iterator_category = std::forward_iterator_tag; - using value_type = mbmff::iloc_item; - using difference_type = std::ptrdiff_t; - -private: - std::span remaining_; - std::uint8_t iloc_version_ = 0; - std::uint8_t offset_size_ = 0; - std::uint8_t length_size_ = 0; - std::uint8_t index_size_ = 0; - std::uint8_t base_offset_size_ = 0; - -public: - constexpr iloc_item_iterator() noexcept = default; - constexpr iloc_item_iterator(const iloc_header& header, std::uint8_t version) noexcept - : remaining_(header.item_data) - , iloc_version_(version) - , offset_size_(header.offset_size) - , length_size_(header.length_size) - , index_size_(header.index_size) - , base_offset_size_(header.base_offset_size) - {} - constexpr explicit iloc_item_iterator(const basic_box_view& box) noexcept - : iloc_item_iterator(box.header(), box.version()) - {} - -public: - constexpr auto extent_size() const noexcept -> std::size_t - { - return std::size_t(offset_size_) + length_size_ + (index_size_ > 0 ? index_size_ : 0); - } - constexpr auto begin() const noexcept -> iloc_item_iterator - { - return *this; - } - constexpr auto end() const noexcept -> iloc_item_iterator - { - return {}; - } - constexpr auto get() const noexcept -> iloc_item - { - iloc_item item{}; - if (remaining_.empty()) { - return item; - } - - std::size_t offset = 0; - - // Read item_id - if (iloc_version_ < 2) { - item.item_id = read_be(remaining_); - offset += 2; - } else { - item.item_id = read_be(remaining_); - offset += 4; - } - - // Read construction_method - if (iloc_version_ == 1 || iloc_version_ == 2) { - std::uint16_t value = read_be(remaining_.subspan(offset)); - item.construction_method = static_cast(value & 0x000F); // lower 4 bits - offset += 2; - } - - // Read data_reference_index - item.data_reference_index = read_be(remaining_.subspan(offset)); - offset += 2; - - switch (base_offset_size_) { - default: - case 0: - item.base_offset = 0; - break; - case 2: - item.base_offset = read_be(remaining_.subspan(offset)); - break; - case 4: - item.base_offset = read_be(remaining_.subspan(offset)); - break; - case 8: - item.base_offset = read_be(remaining_.subspan(offset)); - break; - } - offset += base_offset_size_; - - item.extent_count = read_be(remaining_.subspan(offset)); - item.extent_data = remaining_.subspan(offset + 2); - - // fill redundant fields for easier access - item.iloc_version = iloc_version_; - item.offset_size = offset_size_; - item.length_size = length_size_; - item.index_size = index_size_; - return item; - } - - constexpr auto operator*() const noexcept -> iloc_item - { - return get(); - } - - constexpr auto operator++() noexcept -> iloc_item_iterator& - { - if (remaining_.empty()) { - remaining_ = {}; - return *this; - } - auto current_item = get(); - const std::byte* data = current_item.extent_data.data(); - std::size_t total_extent_size = current_item.extent_count * extent_size(); - - remaining_ = {data + total_extent_size, remaining_.size() - (data - remaining_.data()) - total_extent_size}; - - return *this; - } - - constexpr auto operator++(int) noexcept -> iloc_item_iterator + constexpr auto operator++(int) noexcept -> mbmff::box_iterator { auto tmp = *this; ++(*this); return tmp; } - constexpr bool operator==(const iloc_item_iterator& other) const noexcept + constexpr bool operator==(const mbmff::box_iterator& other) const noexcept { - // special case: both iterators are at the end (empty) if (remaining_.empty() && other.remaining_.empty()) { return true; } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 90d317f..03c9e64 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -7,10 +7,3 @@ set_target_properties(mbmff-test PROPERTIES CXX_EXTENSIONS OFF ) target_link_libraries(mbmff-test PRIVATE mbmff) - -# Copy file to output directory -add_custom_command(TARGET mbmff-test POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_directory - "${CMAKE_CURRENT_SOURCE_DIR}/assets" - "$/assets" -) \ No newline at end of file diff --git a/test/entry_main.cpp b/test/entry_main.cpp index adf1f2a..54b1bb4 100644 --- a/test/entry_main.cpp +++ b/test/entry_main.cpp @@ -1,158 +1,208 @@ -#include +#include #include #include #include #include "formatters.hpp" - - -int main() +static constexpr auto is_box_implemented(mbmff::box_type type) noexcept -> bool { - std::ifstream avif_file("assets/avif_sample_8_420.avif", std::ios::binary); - if (!avif_file) { - std::cerr << "Failed to open the AVIF file.\n"; - return 1; // Failed to open the file + switch (type) { + case mbmff::box_type::ftyp: + case mbmff::box_type::meta: + case mbmff::box_type::mdat: + case mbmff::box_type::av1C: + case mbmff::box_type::moov: + case mbmff::box_type::trak: + case mbmff::box_type::mdia: + case mbmff::box_type::minf: + case mbmff::box_type::stbl: + case mbmff::box_type::dinf: + case mbmff::box_type::edts: + case mbmff::box_type::udta: + case mbmff::box_type::mvex: + case mbmff::box_type::moof: + case mbmff::box_type::traf: + case mbmff::box_type::mfra: + case mbmff::box_type::ipco: + case mbmff::box_type::iprp: + case mbmff::box_type::iinf: + case mbmff::box_type::iref: + case mbmff::box_type::iloc: + case mbmff::box_type::hdlr: + case mbmff::box_type::pitm: + case mbmff::box_type::ispe: + case mbmff::box_type::pixi: + case mbmff::box_type::pasp: + case mbmff::box_type::ipma: + case mbmff::box_type::infe: + return true; + default: + return false; } +} - std::vector file_data((std::istreambuf_iterator(avif_file)), std::istreambuf_iterator()); - std::span file_data_span(reinterpret_cast(file_data.data()), file_data.size()); - - uint32_t primary_item_id = 0; - mbmff::iloc_box iloc_box; - - for (const auto& box_expected : mbmff::box_iterator(file_data_span, mbmff::iterator_flags::recursive)) { - if (!box_expected) { +static auto display_fourcc(const mbmff::fourcc_string& fc) -> std::string +{ + std::string out; + out.reserve(12); + bool all_printable = true; + for (std::size_t i = 0; i < 4; ++i) { + auto c = fc[i]; + if (static_cast(c) < 0x20 || static_cast(c) > 0x7e) { + all_printable = false; break; } - const auto& box = box_expected.value(); + } + if (all_printable) { + out.append(fc.view()); + } else { + out += '['; + for (std::size_t i = 0; i < 4; ++i) { + if (i) out += ' '; + auto uc = static_cast(fc[i]); + auto hex = "0123456789abcdef"; + out += "0x"; + out += hex[uc >> 4]; + out += hex[uc & 0xf]; + } + out += ']'; + } + return out; +} - // Get concrete types - switch (box.box_header.type) { - case mbmff::box_type::ftyp: { - auto ftyp = mbmff::box_cast(box); - std::cout << std::format("{}\n", ftyp); - } break; - case mbmff::box_type::infe: { - auto infe = mbmff::box_cast(box); - std::cout << std::format("{}\n", infe); - } break; - case mbmff::box_type::meta: { - auto meta = mbmff::box_cast(box); - std::cout << std::format("{}\n", meta); - } break; - case mbmff::box_type::hdlr: { - auto hdlr = mbmff::box_cast(box); - std::cout << std::format("{}\n", hdlr); - } break; - case mbmff::box_type::pitm: { - auto pitm = mbmff::box_cast(box); - std::cout << std::format("{}\n", pitm); - primary_item_id = pitm.item_id(); - } break; - case mbmff::box_type::iinf: { - auto iinf = mbmff::box_cast(box); - std::cout << std::format("{}\n", iinf); - } break; - case mbmff::box_type::iloc: { - auto iloc = mbmff::box_cast(box); - std::cout << std::format("{}\n", iloc); - iloc_box = iloc; - } break; - case mbmff::box_type::iprp: { - auto iprp = mbmff::box_cast(box); - std::cout << std::format("{}\n", iprp); - } break; - case mbmff::box_type::ipco: { - auto ipco = mbmff::box_cast(box); - std::cout << std::format("{}\n", ipco); - } break; - case mbmff::box_type::mdat: { - auto mdat = mbmff::box_cast(box); - std::cout << std::format("{}\n", mdat); - } break; - case mbmff::box_type::ispe: { - auto ispe = mbmff::box_cast(box); - std::cout << std::format("{}\n", ispe); - } break; - case mbmff::box_type::av1C: { - auto av1C = mbmff::box_cast(box); - std::cout << std::format("{}\n", av1C); +static void print_box(const mbmff::any_box_view& box, std::size_t depth) +{ + for (std::size_t i = 0; i < depth; ++i) std::cout << " "; - // scan OBUs in the av1C payload - for (std::uint32_t i = 0; const auto& obu : mbmff::av1::obu_iterator(av1C.header())) { - if (!obu) { - std::cout << std::format( - " OBU parsing error: code={}, needed={}\n", - obu.error().code, - obu.error().needed - ); - break; - } + if (!is_box_implemented(box.type_)) { + std::cout << "\x1b[33m[!]\x1b[0m "; + } - if (obu->type == mbmff::av1::obu_type::sequence_header) { - auto seq_header_obu = mbmff::av1::obu_cast(*obu); - std::cout << std::format(" Sequence Header OBU: {}\n", seq_header_obu); - break; - } + std::cout << '<' << display_fourcc(box.type_string()); + + switch (box.type_) { + case mbmff::box_type::ftyp: + std::cout << std::format("{}", mbmff::box_cast(box)); + break; + case mbmff::box_type::meta: + std::cout << std::format("{}", mbmff::box_cast(box)); + break; + case mbmff::box_type::mdat: + std::cout << std::format("{}", mbmff::box_cast(box)); + break; + case mbmff::box_type::iinf: + std::cout << std::format("{}", mbmff::box_cast(box)); + break; + case mbmff::box_type::iloc: + std::cout << std::format("{}", mbmff::box_cast(box)); + break; + case mbmff::box_type::hdlr: + std::cout << std::format("{}", mbmff::box_cast(box)); + break; + case mbmff::box_type::pitm: + std::cout << std::format("{}", mbmff::box_cast(box)); + break; + case mbmff::box_type::ispe: + std::cout << std::format("{}", mbmff::box_cast(box)); + break; + case mbmff::box_type::pixi: + std::cout << std::format("{}", mbmff::box_cast(box)); + break; + case mbmff::box_type::pasp: + std::cout << std::format("{}", mbmff::box_cast(box)); + break; + case mbmff::box_type::ipma: + std::cout << std::format("{}", mbmff::box_cast(box)); + break; + case mbmff::box_type::infe: + std::cout << std::format("{}", mbmff::box_cast(box)); + break; + case mbmff::box_type::av1C: + std::cout << std::format("{}", mbmff::box_cast(box)); + break; + default: + std::cout << " size=\"" << box.size_ << '"'; + break; + } - std::cout << std::format(" {}:{}\n", i++, obu.value()); + bool is_container = mbmff::has(mbmff::get_box_properties(box.type_), mbmff::box_properties::container); + std::cout << (is_container ? ">" : " />") << '\n'; + + if (box.type_ == mbmff::box_type::iloc) { + auto iloc = mbmff::box_cast(box); + auto data = iloc.value(); + auto iter = mbmff::iloc_item_iterator(data, iloc.version()); + for (; iter != mbmff::iloc_item_iterator{}; ++iter) { + auto item = *iter; + for (std::size_t i = 0; i < depth + 1; ++i) std::cout << " "; + std::cout << std::format("\n", + item.item_id, item.base_offset, + item.construction_method, item.data_reference_index, item.size()); + for (std::size_t e = 0; e < item.size(); ++e) { + auto ext = item[e]; + for (std::size_t i = 0; i < depth + 2; ++i) std::cout << " "; + std::cout << std::format("\n"; } + for (std::size_t i = 0; i < depth + 1; ++i) std::cout << " "; + std::cout << "\n"; + } + } +} - } break; - case mbmff::box_type::pixi: { - auto pixi = mbmff::box_cast(box); - std::cout << std::format("{}\n", pixi); - } break; - case mbmff::box_type::ipma: { - auto ipma = mbmff::box_cast(box); - // ipma doesn't have a custom formatter, because we need to intrude into its payload to parse the actual - // entries, so we print it manually here - - for (auto entry : mbmff::ipma_entry_iterator(ipma)) { - std::cout << std::format("{}\n", entry); +static void walk_boxes_impl(const mbmff::any_box_view& box, std::size_t depth) +{ + print_box(box, depth); + auto props = mbmff::get_box_properties(box.type_); + if (mbmff::has(props, mbmff::box_properties::container) && !box.payload.empty()) { + for (const auto& child : mbmff::box_iterator(box.payload)) { + if (!child) { + std::cerr << "\x1b[31;1m" << std::string(depth * 2, ' ') + << "Parse error: " << mbmff::to_string(child.code) + << " (needed " << child.needed << " bytes)\x1b[0m\n"; + return; } - } break; - case mbmff::box_type::pasp: { - auto pasp = mbmff::box_cast(box); - std::cout << std::format("{}\n", pasp); - } break; - default: - std::cout << "Box type: " << box.box_header.type_string().view() << ", size: " << box.box_header.size - << '\n'; - break; + walk_boxes_impl(*child, depth + 1); } } +} - // parse iloc box to find the offset and size of the primary item in the mdat box - uint64_t item_data_offset = 0; - uint64_t item_data_size = 0; - for (const auto& item : mbmff::iloc_item_iterator(iloc_box)) { - if (item.item_id == primary_item_id) { - std::cout << std::format( - "Primary item found in iloc box: {}\n", - item - ); - item_data_offset = item.base_offset; - item_data_size = item[0].length; - break; +static void walk_boxes(std::span data) +{ + for (const auto& result : mbmff::box_iterator(data)) { + if (!result) { + std::cerr << "\x1b[31;1mParse error at top level: " + << mbmff::to_string(result.code) + << " (needed " << result.needed << " bytes)\x1b[0m\n"; + return; } + walk_boxes_impl(*result, 0); } +} - // Seek to the primary item in the mdat box and print the first few bytes of its data - auto primary_item_span = file_data_span.subspan(item_data_offset, item_data_size); - auto primary_obu = mbmff::av1::obu_iterator(primary_item_span); +int main(int argc, char* argv[]) +{ + if (argc < 2) { + std::cerr << "Usage: mbmff-test \n"; + return 1; + } - for (std::uint32_t i = 0; const auto& obu : primary_obu) { - if (!obu) { - std::cout << std::format( - "Primary item OBU parsing error: code={}, needed={}\n", - obu.error().code, - obu.error().needed - ); - break; - } - std::cout << std::format("Primary item OBU {}: {}\n", i++, obu.value()); + std::ifstream file(argv[1], std::ios::binary); + if (!file) { + std::cerr << "Failed to open file: " << argv[1] << '\n'; + return 1; } + std::vector file_data((std::istreambuf_iterator(file)), std::istreambuf_iterator()); + std::span file_span( + reinterpret_cast(file_data.data()), + file_data.size() + ); + + walk_boxes(file_span); return 0; } diff --git a/test/formatters.hpp b/test/formatters.hpp index cac53d7..1c06dcb 100644 --- a/test/formatters.hpp +++ b/test/formatters.hpp @@ -1,401 +1,225 @@ #pragma once +#include #include #include -#include - -template<> -struct std::formatter : std::formatter { - auto format(const mbmff::av1::obu_sequence_header_view& view, std::format_context& ctx) const - { - auto header = view.header(); - std::string output = std::format( - "obu_sequence_header: seq_profile={}, still_picture={}, reduced_still_picture_header={}, " - "timing_info_present_flag={}, decoder_model_info_present_flag={}, " - "initial_display_delay_present_flag={}, operating_points_cnt_minus_1={}", - uint8_t(header.seq_profile), - uint8_t(header.still_picture), - uint8_t(header.reduced_still_picture_header), - uint8_t(header.timing_info_present_flag), - uint8_t(header.decoder_model_info_present_flag), - uint8_t(header.initial_display_delay_present_flag), - uint8_t(header.operating_points_cnt_minus_1) - ); - - if (header.operating_points_cnt_minus_1 > 0 || true) { - output += "\n Operating Points:"; - for (std::uint8_t i = 0; i <= header.operating_points_cnt_minus_1; ++i) { - const auto& op = header.operating_points[i]; - output += std::format( - "\n [{}] idc={}, level_idx={}, tier={}, decoder_model_present={}, initial_display_delay_present={}", - i, op.operating_point_idc, static_cast(op.seq_level_idx), - op.seq_tier ? "high" : "main", - static_cast(op.decoder_model_present_for_this_op), - static_cast(op.initial_display_delay_present_for_this_op) - ); - } - } - - output += std::format("\n Color Config:"); - output += std::format("\n high_bitdepth={}, twelve_bit={}, monochrome={}, color_description_present={}", - static_cast(header.color_config_data.high_bitdepth), - static_cast(header.color_config_data.twelve_bit), - static_cast(header.color_config_data.monochrome), - static_cast(header.color_config_data.color_description_present_flag) - ); - - if (header.color_config_data.color_description_present_flag) { - const auto& cd = header.color_config_data.color_description_data; - output += std::format("\n color_primaries={}, transfer_characteristics={}, matrix_coefficients={}", - mbmff::av1::to_string(cd.color_primaries), - mbmff::av1::to_string(cd.transfer_characteristics), - mbmff::av1::to_string(cd.matrix_coefficients) - ); - } - - output += std::format("\n Frame Max Dimensions: {}x{}", - header.max_frame_width_minus_1 + 1, - header.max_frame_height_minus_1 + 1 - ); - - return std::formatter::format(output, ctx); - } -}; //------------------------------------------------------------------------------------------------------------ -template <> -struct std::formatter : std::formatter { - auto format(const mbmff::av1::any_obu_view& box, format_context& ctx) const -> format_context::iterator - { - std::string output = std::format( - "OBU: type={}", - mbmff::av1::to_string(static_cast(box.type)) - ); - - // Add size field if present - if (box.has_size_field) { - output += std::format(", size_field=1"); - } - - // Add extension fields - if (box.extension_flag) { - output += std::format( - ", temporal_id={}, spatial_id={}", - box.temporal_id, - box.spatial_id - ); - } - - output += "}"; - return std::formatter::format(output, ctx); - } -}; - -//------------------------------------------------------------------------------------------------------------ -template <> -struct std::formatter : std::formatter { - auto format(mbmff::error_code code, std::format_context& ctx) const - { - return std::formatter::format(mbmff::get_error_message(code), ctx); - } -}; - -template <> -struct std::formatter : std::formatter { - auto format(const mbmff::box_header& header, std::format_context& ctx) const - { - return std::formatter::format( - std::format("{} ({} bytes)", header.type_string().view(), header.size), - ctx - ); - } -}; - +// ftyp template <> struct std::formatter : std::formatter { auto format(const mbmff::ftyp_box& box, std::format_context& ctx) const { - std::string output = std::format( - "FTYP [{} bytes]: {} minor={} compatible=[", - box.box_header.size, - box.major_brand().view(), - box.minor_version() - ); - - auto compatible_brands = box.compatible_brands(); - - for (std::size_t i = 0; i < compatible_brands.size(); ++i) { - if (i != 0) { - output.append(", "); - } - output.append(compatible_brands[i].view()); + auto v = box.value(); + std::string out; + out += std::format(" size=\"{}\" major=\"{}\"", box.size_, v.major_brand.view()); + out += std::format(" minor=\"{}\" compatible=\"[", v.minor_version); + for (std::size_t i = 0; i < v.compatible_brands.size(); ++i) { + if (i) out += ','; + out += v.compatible_brands[i].view(); } - output.push_back(']'); - return std::formatter::format(output, ctx); + out += "]\""; + return std::formatter::format(out, ctx); } }; +//------------------------------------------------------------------------------------------------------------ +// meta template <> struct std::formatter : std::formatter { auto format(const mbmff::meta_box& box, std::format_context& ctx) const { return std::formatter::format( - std::format( - "META [{} bytes]: version={} flags=0x{:06X}", - box.box_header.size, - box.box_header.version, - box.box_header.flags_value() - ), + std::format(" size=\"{}\"", box.size_), ctx ); } }; +//------------------------------------------------------------------------------------------------------------ +// mdat template <> struct std::formatter : std::formatter { auto format(const mbmff::mdat_box& box, std::format_context& ctx) const { return std::formatter::format( - std::format("MDAT [{} bytes]: payload={} bytes", box.box_header.size, box.data_size()), + std::format(" size=\"{}\" data_size=\"{}\"", box.size_, box.payload.size()), ctx ); } }; +//------------------------------------------------------------------------------------------------------------ +// iinf template <> struct std::formatter : std::formatter { auto format(const mbmff::iinf_box& box, std::format_context& ctx) const { + auto v = box.value(); return std::formatter::format( - std::format( - "IINF [{} bytes]: version={} flags=0x{:06X}, entries={}", - box.box_header.size, - box.box_header.version, - box.box_header.flags_value(), - box.entry_count() - ), + std::format(" size=\"{}\" version=\"{}\" entry_count=\"{}\"", box.size_, box.version(), v.entry_count), ctx ); } }; -template <> -struct std::formatter : std::formatter { - auto format(const mbmff::infe_box& box, std::format_context& ctx) const - { - auto header = box.header(); - auto version = box.version(); - std::string output = std::format( - "INFE [{} bytes]: version={} id={} protection={}", - box.box_header.size, - version, - header.item_id, - header.item_protection_index - ); - - if (version < 2) { - output.append(std::format(" name={}", header.item_name)); - } - - if (version >= 2) { - output.append(std::format(" type={}", header.item_type.view())); - if (header.item_type.view() == "mime") { - output.append(std::format(" content_type={}", header.content_type)); - if (!header.content_encoding.empty()) { - output.append(std::format(" content_encoding={}", header.content_encoding)); - } - } else if (header.item_type.view() == "uri ") { - output.append(std::format(" uri={}", header.item_uri_type)); - } - } - - return std::formatter::format(output, ctx); - } -}; - +//------------------------------------------------------------------------------------------------------------ +// hdlr template <> struct std::formatter : std::formatter { auto format(const mbmff::hdlr_box& box, std::format_context& ctx) const { - auto header = box.header(); - std::string output = std::format( - "HDLR [{} bytes]: handler_type={}", - box.box_header.size, - header.handler_type.view() - ); - if (!header.name.empty()) { - output.append(std::format(" name={}", header.name)); + auto v = box.value(); + std::string out = std::format(" size=\"{}\" handler_type=\"{}\"", box.size_, v.handler_type.view()); + if (!v.name.empty()) { + out += std::format(" name=\"{}\"", v.name); } - return std::formatter::format(output, ctx); + return std::formatter::format(out, ctx); } }; +//------------------------------------------------------------------------------------------------------------ +// pitm template <> struct std::formatter : std::formatter { auto format(const mbmff::pitm_box& box, std::format_context& ctx) const { + auto v = box.value(); return std::formatter::format( - std::format("PITM [{} bytes]: item_id={}", box.box_header.size, box.item_id()), - ctx - ); - } -}; - -template <> -struct std::formatter : std::formatter { - auto format(const mbmff::iloc_box& box, std::format_context& ctx) const - { - auto header = box.header(); - std::string output = std::format( - "ILOC [{} bytes]: version={} offset_size={} length_size={} base_offset_size={} index_size={} item_count={}", - box.box_header.size, - box.version(), - header.offset_size, - header.length_size, - header.base_offset_size, - header.index_size, - header.item_count - ); - return std::formatter::format(output, ctx); - } -}; - -template <> -struct std::formatter : std::formatter { - auto format(const mbmff::iprp_box& box, std::format_context& ctx) const - { - return std::formatter::format( - std::format("IPRP [{} bytes]: Container", box.box_header.size), - ctx - ); - } -}; - -template <> -struct std::formatter : std::formatter { - auto format(const mbmff::ipco_box& box, std::format_context& ctx) const - { - return std::formatter::format( - std::format("IPCO [{} bytes]: Container", box.box_header.size), + std::format(" size=\"{}\" item_id=\"{}\"", box.size_, v.item_id), ctx ); } }; +//------------------------------------------------------------------------------------------------------------ +// ispe template <> struct std::formatter : std::formatter { auto format(const mbmff::ispe_box& box, std::format_context& ctx) const { - auto header = box.header(); + auto v = box.value(); return std::formatter::format( - std::format( - "ISPE [{} bytes]: image_width={} image_height={}", - box.box_header.size, - header.image_width, - header.image_height - ), + std::format(" size=\"{}\" image_width=\"{}\" image_height=\"{}\"", box.size_, v.image_width, v.image_height), ctx ); } }; -template <> -struct std::formatter : std::formatter { - auto format(const mbmff::av1C_box& box, std::format_context& ctx) const - { - auto header = box.header(); - std::string output = std::format( - "AV1C [{} bytes]: profile={} level={} tier={} bitdepth={} monochrome={} chroma_subsampling=({}, {})", - box.box_header.size, - std::uint8_t(header.seq_profile), - std::uint8_t(header.seq_level_idx_0), - header.seq_tier_0 ? "high" : "main", - header.high_bitdepth ? (header.twelve_bit ? 12 : 10) : 8, - header.monochrome ? "yes" : "no", - header.chroma_subsampling_x ? "1" : "0", - header.chroma_subsampling_y ? "1" : "0" - ); - return std::formatter::format(output, ctx); - } -}; - +//------------------------------------------------------------------------------------------------------------ +// pixi template <> struct std::formatter : std::formatter { auto format(const mbmff::pixi_box& box, std::format_context& ctx) const { - auto bits = box.bits_per_channel(); - std::string output = std::format("PIXI [{} bytes]: bits_per_channel=[", box.box_header.size); - for (std::size_t i = 0; i < bits.size(); ++i) { - if (i != 0) { - output.append(", "); - } - output.append(std::format("{}", bits[i])); + auto v = box.value(); + std::string out = std::format(" size=\"{}\" bits_per_channel=\"[", box.size_); + for (std::size_t i = 0; i < v.bits_per_channel.size(); ++i) { + if (i) out += ','; + out += std::format("{}", v.bits_per_channel[i]); } - return std::formatter::format(output.append("]"), ctx); + out += "]\""; + return std::formatter::format(out, ctx); } }; +//------------------------------------------------------------------------------------------------------------ +// pasp template <> -struct std::formatter : std::formatter { - auto format(const mbmff::ipma_association& box, std::format_context& ctx) const +struct std::formatter : std::formatter { + auto format(const mbmff::pasp_box& box, std::format_context& ctx) const { + auto v = box.value(); return std::formatter::format( - std::format("ipco_property={} essential={}", box.property_index, box.essential ? "yes" : "no"), + std::format(" size=\"{}\" h_spacing=\"{}\" v_spacing=\"{}\"", box.size_, v.h_spacing, v.v_spacing), ctx ); } }; +//------------------------------------------------------------------------------------------------------------ +// ipma template <> -struct std::formatter : std::formatter { - auto format(const mbmff::ipma_entry& entry, std::format_context& ctx) const +struct std::formatter : std::formatter { + auto format(const mbmff::ipma_box& box, std::format_context& ctx) const { - std::string output = std::format("IPMA Entry: item_id={} associations=[", entry.item_id); - for (std::size_t i = 0; i < entry.size(); ++i) { - if (i != 0) { - output.append(", "); - } - output += std::format("{{ {} }}", entry[i]); - } - return std::formatter::format(output.append("]"), ctx); + auto v = box.value(); + return std::formatter::format( + std::format(" size=\"{}\" entry_count=\"{}\"", box.size_, v.entry_count), + ctx + ); } }; +//------------------------------------------------------------------------------------------------------------ +// infe template <> -struct std::formatter : std::formatter { - auto format(const mbmff::iloc_extent& extent, std::format_context& ctx) const +struct std::formatter : std::formatter { + auto format(const mbmff::infe_box& box, std::format_context& ctx) const { - return std::formatter::format( - std::format("extent(index={} offset={} length={})", extent.index, extent.offset, extent.length), - ctx - ); + auto v = box.value(); + std::string out = std::format(" size=\"{}\" id=\"{}\" protection=\"{}\"", + box.size_, v.item_id, v.item_protection_index); + if (!v.item_name.empty()) { + out += std::format(" name=\"{}\"", v.item_name); + } + if (v.item_type.view().size() == 4) { + out += std::format(" type=\"{}\"", v.item_type.view()); + } + if (!v.content_type.empty()) { + out += std::format(" content_type=\"{}\"", v.content_type); + } + if (!v.content_encoding.empty()) { + out += std::format(" content_encoding=\"{}\"", v.content_encoding); + } + if (!v.item_uri_type.empty()) { + out += std::format(" uri=\"{}\"", v.item_uri_type); + } + return std::formatter::format(out, ctx); } }; +//------------------------------------------------------------------------------------------------------------ +// av1C template <> -struct std::formatter : std::formatter { - auto format(const mbmff::iloc_item& item, std::format_context& ctx) const +struct std::formatter : std::formatter { + auto format(const mbmff::av1C_box& box, std::format_context& ctx) const { - std::string output = std::format( - "ILOC Item: item_id={} base_offset={} construction_method={} data_reference_index={} extents=[", - item.item_id, item.base_offset, item.construction_method, item.data_reference_index - ); - for (std::size_t i = 0; i < item.size(); ++i) { - if (i != 0) { - output.append(", "); - } - output += std::format("{}", item[i]); + auto v = box.value(); + std::string out = std::format( + " size=\"{}\" seq_profile=\"{}\" seq_level_idx_0=\"{}\" seq_tier_0=\"{}\"" + " high_bitdepth=\"{}\" twelve_bit=\"{}\" monochrome=\"{}\"" + " chroma_subsampling_x=\"{}\" chroma_subsampling_y=\"{}\"" + " chroma_sample_position=\"{}\"", + box.size_, + static_cast(v.seq_profile), static_cast(v.seq_level_idx_0), + static_cast(v.seq_tier_0), + static_cast(v.high_bitdepth), static_cast(v.twelve_bit), + static_cast(v.monochrome), + static_cast(v.chroma_subsampling_x), static_cast(v.chroma_subsampling_y), + static_cast(v.chroma_sample_position)); + if (v.initial_presentation_delay_present) { + out += std::format(" initial_presentation_delay_minus_one=\"{}\"", + static_cast(v.initial_presentation_delay_minus_one)); + } + if (!v.config_obus.empty()) { + out += std::format(" config_obus_size=\"{}\"", v.config_obus.size()); } - return std::formatter::format(output.append("]"), ctx); + return std::formatter::format(out, ctx); } }; +//------------------------------------------------------------------------------------------------------------ +// iloc template <> -struct std::formatter : std::formatter { - auto format(const mbmff::pasp_box& box, std::format_context& ctx) const +struct std::formatter : std::formatter { + auto format(const mbmff::iloc_box& box, std::format_context& ctx) const { - auto [x, y] = box.aspect_ratio(); + auto v = box.value(); return std::formatter::format( - std::format("PASP [{} bytes]: aspect_ratio=({}x{})", box.box_header.size, x, y), + std::format(" size=\"{}\" version=\"{}\" offset_size=\"{}\" length_size=\"{}\"" + " base_offset_size=\"{}\" index_size=\"{}\" item_count=\"{}\"", + box.size_, box.version(), + v.offset_size, v.length_size, v.base_offset_size, v.index_size, v.item_count), ctx ); } diff --git a/tools/amalgamate.py b/tools/amalgamate.py new file mode 100644 index 0000000..f6d8b57 --- /dev/null +++ b/tools/amalgamate.py @@ -0,0 +1,180 @@ +#!/usr/bin/env python3 +"""Amalgamate mBMFF headers into a single header file.""" + +import argparse +import os +import re +import sys + +REPO_URL = "https://github.com/Agrael1/mBMFF" +COPYRIGHT_YEAR = "2026" +COPYRIGHT_HOLDER = "Ilya \"Agrael\" Doroshenko" + + +def read_version(repo_root: str) -> str: + path = os.path.join(repo_root, "VERSION") + with open(path, "r", encoding="utf-8") as f: + return f.read().strip() + + +def read_license(repo_root: str) -> str: + path = os.path.join(repo_root, "LICENSE.txt") + with open(path, "r", encoding="utf-8") as f: + text = f.read() + text = text.replace("[year]", COPYRIGHT_YEAR) + text = text.replace("[fullname]", COPYRIGHT_HOLDER) + return text + + +def build_comment_block(lines: list[str]) -> str: + return "\n".join("// " + line if line else "//" for line in lines) + + +def resolve_include(path: str, search_dirs: list[str]) -> str | None: + for d in search_dirs: + candidate = os.path.join(d, path) + if os.path.isfile(candidate): + return os.path.normpath(candidate) + return None + + +def amalgamate(entry_point: str, search_dirs: list[str], version: str) -> str: + inlined: set[str] = set() + guard_name = "MBMFF_AMALGAMATED_HPP" + + def process_file(filepath: str, depth: int = 0) -> list[str]: + norm = os.path.normpath(filepath) + if norm in inlined: + return [] + inlined.add(norm) + + lines: list[str] = [] + with open(filepath, "r", encoding="utf-8") as f: + for line in f: + stripped = line.strip() + + if stripped == "#pragma once": + continue + + m = re.match(r'#include\s+"([^"]+)"', stripped) + if m: + included = m.group(1) + resolved = resolve_include(included, [os.path.dirname(filepath)] + search_dirs) + if resolved is None: + print( + f"Warning: could not resolve '{included}' (from {filepath})", + file=sys.stderr, + ) + lines.append(line) + continue + content = process_file(resolved, depth + 1) + if content: + if lines and lines[-1].strip(): + lines.append("\n") + lines.extend(content) + if not lines[-1].endswith("\n"): + lines.append("\n") + continue + + lines.append(line) + + return lines + + resolved = resolve_include(entry_point, search_dirs) + if resolved is None: + print(f"Error: entry point '{entry_point}' not found", file=sys.stderr) + sys.exit(1) + + body = process_file(resolved) + + header = build_comment_block([ + "MIT License", + "", + f"Copyright (c) {COPYRIGHT_YEAR} {COPYRIGHT_HOLDER}", + "", + "Permission is hereby granted, free of charge, to any person obtaining a copy", + "of this software and associated documentation files (the \"Software\"), to deal", + "in the Software without restriction, including without limitation the rights", + "to use, copy, modify, merge, publish, distribute, sublicense, and/or sell", + "copies of the Software, and to permit persons to whom the Software is", + "furnished to do so, subject to the following conditions:", + "", + "The above copyright notice and this permission notice shall be included in all", + "copies or substantial portions of the Software.", + "", + 'THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR', + "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,", + "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE", + "AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER", + "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM", + "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE", + "SOFTWARE.", + "", + f"Repository: {REPO_URL}", + f"Version: {version}", + "", + "mBMFF -- single-header amalgamation", + "Generated by tools/amalgamate.py", + ]) + + result = [ + f"#ifndef {guard_name}\n", + f"#define {guard_name}\n", + "\n", + header + "\n", + "\n", + ] + result.extend(body) + result.append(f"\n#endif // {guard_name}\n") + return "".join(result) + + +def main() -> None: + parser = argparse.ArgumentParser(description="Amalgamate mBMFF headers") + parser.add_argument( + "--input", + default=None, + help="Input directory containing mbmff.hpp (default: include/mbmff)", + ) + parser.add_argument( + "--entry", + default="mbmff.hpp", + help="Entry point header (default: mbmff.hpp)", + ) + parser.add_argument( + "--output", + default=None, + help="Output file (default: stdout)", + ) + args = parser.parse_args() + + script_dir = os.path.dirname(os.path.abspath(__file__)) + repo_root = os.path.abspath(os.path.join(script_dir, "..")) + + version = read_version(repo_root) + _ = read_license(repo_root) # validate it exists + + input_dir = args.input + if input_dir is None: + input_dir = os.path.join(repo_root, "include", "mbmff") + + entry_path = args.entry + if not os.path.isabs(entry_path): + entry_path = os.path.join(input_dir, entry_path) + + search_dirs = [input_dir, os.path.join(input_dir, "boxes")] + + output = amalgamate(entry_path, search_dirs, version) + + if args.output: + out_dir = os.path.dirname(args.output) + if out_dir: + os.makedirs(out_dir, exist_ok=True) + with open(args.output, "w", encoding="utf-8", newline="\n") as f: + f.write(output) + else: + sys.stdout.write(output) + + +if __name__ == "__main__": + main()