From 0ead85e128a7fea1165fbdbd0dee4af0aeb96e2f Mon Sep 17 00:00:00 2001 From: Joel Sole Date: Sat, 13 Jun 2026 15:35:40 +0000 Subject: [PATCH 1/2] Add ATLAS_SEGMENT_OBU support --- include/av2_obu/obus/atlas_segment_obu.h | 75 ++++++- src/obus/atlas_segment_obu.cpp | 253 ++++++++++++++++++++++- 2 files changed, 323 insertions(+), 5 deletions(-) diff --git a/include/av2_obu/obus/atlas_segment_obu.h b/include/av2_obu/obus/atlas_segment_obu.h index 76c7e1e..bbf9999 100644 --- a/include/av2_obu/obus/atlas_segment_obu.h +++ b/include/av2_obu/obus/atlas_segment_obu.h @@ -13,9 +13,10 @@ #include +#include + namespace av2_obu { -// Atlas Segment OBU class AtlasSegmentOBU : public BaseOBU { public: explicit AtlasSegmentOBU(const OBUPosition& pos) : BaseOBU(pos) {} @@ -23,8 +24,80 @@ class AtlasSegmentOBU : public BaseOBU { json to_json() const override; std::string type_name() const override { return "ATLAS_SEGMENT"; } + enum class ModeIdc : uint32_t { + ENHANCED_ATLAS = 0, + BASIC_ATLAS = 1, + SINGLE_ATLAS = 2, + MULTISTREAM_ATLAS = 3, + MULTISTREAM_ALPHA_ATLAS = 4, + }; + + struct Segment { + uint32_t input_stream_id = 0; + uint32_t top_left_x = 0; + uint32_t top_left_y = 0; + uint32_t width = 0; + uint32_t height = 0; + uint32_t alpha_segment_flag = 0; + }; + + struct EnhancedAtlasInfo { + uint32_t num_region_columns_minus1 = 0; + uint32_t num_region_rows_minus1 = 0; + uint32_t uniform_spacing_flag = 0; + std::vector column_width_minus1; + std::vector row_height_minus1; + uint32_t region_width_minus1 = 0; + uint32_t region_height_minus1 = 0; + uint32_t single_region_per_segment_flag = 0; + uint32_t num_atlas_segments_minus1 = 0; + struct SegmentRegion { + uint32_t top_left_col = 0; + uint32_t top_left_row = 0; + uint32_t bottom_right_col_off = 0; + uint32_t bottom_right_row_off = 0; + }; + std::vector segment_regions; + }; + + struct LabelSegmentInfo { + uint32_t signaled_ids_flag = 0; + std::vector segment_ids; + }; + protected: bool parse_payload(std::ifstream& ifs) override; + +private: + uint32_t atlas_segment_id_ = 0; + ModeIdc mode_idc_ = ModeIdc::SINGLE_ATLAS; + + // Mode 0 — ENHANCED_ATLAS + EnhancedAtlasInfo enhanced_; + + // Mode 1 — BASIC_ATLAS + uint32_t basic_stream_id_present_ = 0; + uint32_t basic_width_ = 0; + uint32_t basic_height_ = 0; + + // Mode 2 — SINGLE_ATLAS + uint32_t nominal_width_minus1_ = 0; + uint32_t nominal_height_minus1_ = 0; + + // Modes 3 & 4 — MULTISTREAM_ATLAS / MULTISTREAM_ALPHA_ATLAS + uint32_t msi_width_ = 0; + uint32_t msi_height_ = 0; + uint32_t msi_background_present_ = 0; + uint32_t msi_bg_r_ = 0; + uint32_t msi_bg_g_ = 0; + uint32_t msi_bg_b_ = 0; + uint32_t msi_alpha_segments_present_ = 0; + + // All modes — per-segment data + std::vector segments_; + + // All modes — label info + LabelSegmentInfo label_; }; } // namespace av2_obu diff --git a/src/obus/atlas_segment_obu.cpp b/src/obus/atlas_segment_obu.cpp index 88f233e..83a6885 100644 --- a/src/obus/atlas_segment_obu.cpp +++ b/src/obus/atlas_segment_obu.cpp @@ -11,29 +11,274 @@ #include +#include #include namespace av2_obu { +namespace { + +static const char* mode_name(AtlasSegmentOBU::ModeIdc m) { + switch (m) { + case AtlasSegmentOBU::ModeIdc::ENHANCED_ATLAS: return "ENHANCED_ATLAS"; + case AtlasSegmentOBU::ModeIdc::BASIC_ATLAS: return "BASIC_ATLAS"; + case AtlasSegmentOBU::ModeIdc::SINGLE_ATLAS: return "SINGLE_ATLAS"; + case AtlasSegmentOBU::ModeIdc::MULTISTREAM_ATLAS: return "MULTISTREAM_ATLAS"; + case AtlasSegmentOBU::ModeIdc::MULTISTREAM_ALPHA_ATLAS: return "MULTISTREAM_ALPHA_ATLAS"; + default: return "UNKNOWN"; + } +} + +// ats_region_info() — returns NumRegionsInAtlas +static uint32_t parse_region_info(BitstreamReader& br, + AtlasSegmentOBU::EnhancedAtlasInfo& info) { + info.num_region_columns_minus1 = br.read_uvlc(); + info.num_region_rows_minus1 = br.read_uvlc(); + info.uniform_spacing_flag = br.read_bit(); + + if (!info.uniform_spacing_flag) { + uint32_t num_cols = info.num_region_columns_minus1 + 1; + uint32_t num_rows = info.num_region_rows_minus1 + 1; + info.column_width_minus1.resize(num_cols); + info.row_height_minus1.resize(num_rows); + for (uint32_t i = 0; i < num_cols; i++) + info.column_width_minus1[i] = br.read_uvlc(); + for (uint32_t i = 0; i < num_rows; i++) + info.row_height_minus1[i] = br.read_uvlc(); + } else { + info.region_width_minus1 = br.read_uvlc(); + info.region_height_minus1 = br.read_uvlc(); + } + return (info.num_region_columns_minus1 + 1) * (info.num_region_rows_minus1 + 1); +} + +// ats_region_to_segment_mapping() — returns numSegments +static uint32_t parse_region_to_segment_mapping(BitstreamReader& br, + AtlasSegmentOBU::EnhancedAtlasInfo& info, + uint32_t num_regions) { + info.single_region_per_segment_flag = br.read_bit(); + if (!info.single_region_per_segment_flag) { + info.num_atlas_segments_minus1 = br.read_uvlc(); + uint32_t n = info.num_atlas_segments_minus1 + 1; + info.segment_regions.resize(n); + for (uint32_t i = 0; i < n; i++) { + info.segment_regions[i].top_left_col = br.read_uvlc(); + info.segment_regions[i].top_left_row = br.read_uvlc(); + info.segment_regions[i].bottom_right_col_off = br.read_uvlc(); + info.segment_regions[i].bottom_right_row_off = br.read_uvlc(); + } + return n; + } else { + info.num_atlas_segments_minus1 = num_regions - 1; + return num_regions; + } +} + +// ats_label_segment_info() +static void parse_label_segment_info(BitstreamReader& br, AtlasSegmentOBU::LabelSegmentInfo& label, + uint32_t num_segments) { + label.signaled_ids_flag = br.read_bit(); + if (label.signaled_ids_flag) { + label.segment_ids.resize(num_segments); + for (uint32_t i = 0; i < num_segments; i++) + label.segment_ids[i] = br.read_bits(8); + } +} + +} // namespace + bool AtlasSegmentOBU::parse_payload(std::ifstream& ifs) { spdlog::debug("Parsing ATLAS_SEGMENT payload ({} bytes)", position_.payload_size); - // Read raw payload raw_payload_.resize(position_.payload_size); if (!ifs.read(reinterpret_cast(raw_payload_.data()), position_.payload_size)) { spdlog::error("Failed to read ATLAS_SEGMENT payload"); return false; } - // TODO: Implement ATLAS_SEGMENT parsing - spdlog::warn("ATLAS_SEGMENT parsing not yet implemented"); + try { + BitstreamReader br(raw_payload_); + + atlas_segment_id_ = br.read_bits(3); + mode_idc_ = static_cast(br.read_uvlc()); + + uint32_t num_segments = 0; + + switch (mode_idc_) { + case ModeIdc::ENHANCED_ATLAS: { + uint32_t num_regions = parse_region_info(br, enhanced_); + num_segments = parse_region_to_segment_mapping(br, enhanced_, num_regions); + break; + } + case ModeIdc::BASIC_ATLAS: { + basic_stream_id_present_ = br.read_bit(); + basic_width_ = br.read_uvlc(); + basic_height_ = br.read_uvlc(); + uint32_t n = br.read_uvlc() + 1; // ats_num_atlas_segments_minus1 + 1 + num_segments = n; + segments_.resize(n); + for (uint32_t i = 0; i < n; i++) { + if (basic_stream_id_present_) + segments_[i].input_stream_id = br.read_bits(5); + segments_[i].top_left_x = br.read_uvlc(); + segments_[i].top_left_y = br.read_uvlc(); + segments_[i].width = br.read_uvlc(); + segments_[i].height = br.read_uvlc(); + } + break; + } + case ModeIdc::SINGLE_ATLAS: { + nominal_width_minus1_ = br.read_uvlc(); + nominal_height_minus1_ = br.read_uvlc(); + num_segments = 1; + break; + } + case ModeIdc::MULTISTREAM_ATLAS: + case ModeIdc::MULTISTREAM_ALPHA_ATLAS: { + bool with_alpha = (mode_idc_ == ModeIdc::MULTISTREAM_ALPHA_ATLAS); + msi_width_ = br.read_uvlc(); + msi_height_ = br.read_uvlc(); + uint32_t n = br.read_uvlc() + 1; // ats_msi_num_atlas_segments_minus1 + 1 + num_segments = n; + if (with_alpha) + msi_alpha_segments_present_ = br.read_bit(); + msi_background_present_ = br.read_bit(); + if (msi_background_present_) { + msi_bg_r_ = br.read_bits(8); + msi_bg_g_ = br.read_bits(8); + msi_bg_b_ = br.read_bits(8); + } + segments_.resize(n); + for (uint32_t i = 0; i < n; i++) { + segments_[i].input_stream_id = br.read_bits(5); + segments_[i].top_left_x = br.read_uvlc(); + segments_[i].top_left_y = br.read_uvlc(); + segments_[i].width = br.read_uvlc(); + segments_[i].height = br.read_uvlc(); + if (with_alpha && msi_alpha_segments_present_ && i != n - 1) + segments_[i].alpha_segment_flag = br.read_bit(); + } + break; + } + default: + spdlog::warn("ATLAS_SEGMENT: unknown mode_idc={}", static_cast(mode_idc_)); + return true; + } + + parse_label_segment_info(br, label_, num_segments); + + if (!parse_obu_trailing_bits(br)) + return false; + + } catch (const std::runtime_error& e) { + spdlog::error("ATLAS_SEGMENT parse error: {}", e.what()); + return false; + } + + spdlog::debug("ATLAS_SEGMENT: id={}, mode={}", atlas_segment_id_, mode_name(mode_idc_)); return true; } json AtlasSegmentOBU::to_json() const { json j = BaseOBU::to_json(); j["type_name"] = "ATLAS_SEGMENT"; - // TODO: Add ATLAS_SEGMENT-specific fields + + if (!parsed_) return j; + + j["atlas_segment_id"] = atlas_segment_id_; + j["ats_atlas_segment_mode_idc"] = static_cast(mode_idc_); + j["ats_atlas_segment_mode_name"] = mode_name(mode_idc_); + + switch (mode_idc_) { + case ModeIdc::ENHANCED_ATLAS: { + json e; + e["ats_num_region_columns_minus1"] = enhanced_.num_region_columns_minus1; + e["ats_num_region_rows_minus1"] = enhanced_.num_region_rows_minus1; + e["ats_uniform_spacing_flag"] = enhanced_.uniform_spacing_flag; + if (!enhanced_.uniform_spacing_flag) { + e["ats_column_width_minus1"] = enhanced_.column_width_minus1; + e["ats_row_height_minus1"] = enhanced_.row_height_minus1; + } else { + e["ats_region_width_minus1"] = enhanced_.region_width_minus1; + e["ats_region_height_minus1"] = enhanced_.region_height_minus1; + } + e["ats_single_region_per_atlas_segment_flag"] = enhanced_.single_region_per_segment_flag; + e["ats_num_atlas_segments_minus1"] = enhanced_.num_atlas_segments_minus1; + if (!enhanced_.single_region_per_segment_flag) { + json regions = json::array(); + for (const auto& r : enhanced_.segment_regions) { + regions.push_back({{"ats_top_left_region_column", r.top_left_col}, + {"ats_top_left_region_row", r.top_left_row}, + {"ats_bottom_right_region_column_off", r.bottom_right_col_off}, + {"ats_bottom_right_region_row_off", r.bottom_right_row_off}}); + } + e["segment_regions"] = regions; + } + j["enhanced_atlas_info"] = e; + break; + } + case ModeIdc::BASIC_ATLAS: { + j["ats_stream_id_present"] = basic_stream_id_present_; + j["ats_width"] = basic_width_; + j["ats_height"] = basic_height_; + j["ats_num_atlas_segments_minus1"] = segments_.empty() ? 0 : (uint32_t)segments_.size() - 1; + json segs = json::array(); + for (const auto& s : segments_) { + json seg; + if (basic_stream_id_present_) + seg["ats_input_stream_id"] = s.input_stream_id; + seg["ats_segment_top_left_pos_x"] = s.top_left_x; + seg["ats_segment_top_left_pos_y"] = s.top_left_y; + seg["ats_segment_width"] = s.width; + seg["ats_segment_height"] = s.height; + segs.push_back(seg); + } + j["segments"] = segs; + break; + } + case ModeIdc::SINGLE_ATLAS: + j["ats_nominal_width_minus1"] = nominal_width_minus1_; + j["ats_nominal_height_minus1"] = nominal_height_minus1_; + break; + case ModeIdc::MULTISTREAM_ATLAS: + case ModeIdc::MULTISTREAM_ALPHA_ATLAS: { + j["ats_msi_width"] = msi_width_; + j["ats_msi_height"] = msi_height_; + j["ats_msi_num_atlas_segments_minus1"] = + segments_.empty() ? 0 : (uint32_t)segments_.size() - 1; + if (mode_idc_ == ModeIdc::MULTISTREAM_ALPHA_ATLAS) + j["ats_msi_alpha_segments_present_flag"] = msi_alpha_segments_present_; + j["ats_msi_background_info_present_flag"] = msi_background_present_; + if (msi_background_present_) { + j["ats_msi_background_red_value"] = msi_bg_r_; + j["ats_msi_background_green_value"] = msi_bg_g_; + j["ats_msi_background_blue_value"] = msi_bg_b_; + } + json segs = json::array(); + for (const auto& s : segments_) { + json seg; + seg["ats_msi_input_stream_id"] = s.input_stream_id; + seg["ats_msi_segment_top_left_pos_x"] = s.top_left_x; + seg["ats_msi_segment_top_left_pos_y"] = s.top_left_y; + seg["ats_msi_segment_width"] = s.width; + seg["ats_msi_segment_height"] = s.height; + if (mode_idc_ == ModeIdc::MULTISTREAM_ALPHA_ATLAS) + seg["ats_msi_alpha_segment_flag"] = s.alpha_segment_flag; + segs.push_back(seg); + } + j["segments"] = segs; + break; + } + default: + break; + } + + json label; + label["ats_signaled_atlas_segment_ids_flag"] = label_.signaled_ids_flag; + if (label_.signaled_ids_flag) + label["ats_atlas_segment_ids"] = label_.segment_ids; + j["label_segment_info"] = label; + return j; } From fd19b44e8ad217a05a894bea9ffe79c905b17d80 Mon Sep 17 00:00:00 2001 From: Dimitri Podborski Date: Tue, 16 Jun 2026 11:04:38 -0700 Subject: [PATCH 2/2] atlas_segment_obu: align JSON keys with v1.0.0 spec, fail unknown mode_idc --- src/obus/atlas_segment_obu.cpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/obus/atlas_segment_obu.cpp b/src/obus/atlas_segment_obu.cpp index 83a6885..96b1244 100644 --- a/src/obus/atlas_segment_obu.cpp +++ b/src/obus/atlas_segment_obu.cpp @@ -162,7 +162,7 @@ bool AtlasSegmentOBU::parse_payload(std::ifstream& ifs) { } default: spdlog::warn("ATLAS_SEGMENT: unknown mode_idc={}", static_cast(mode_idc_)); - return true; + return false; } parse_label_segment_info(br, label_, num_segments); @@ -192,18 +192,18 @@ json AtlasSegmentOBU::to_json() const { switch (mode_idc_) { case ModeIdc::ENHANCED_ATLAS: { json e; - e["ats_num_region_columns_minus1"] = enhanced_.num_region_columns_minus1; - e["ats_num_region_rows_minus1"] = enhanced_.num_region_rows_minus1; + e["ats_num_region_columns_minus_1"] = enhanced_.num_region_columns_minus1; + e["ats_num_region_rows_minus_1"] = enhanced_.num_region_rows_minus1; e["ats_uniform_spacing_flag"] = enhanced_.uniform_spacing_flag; if (!enhanced_.uniform_spacing_flag) { - e["ats_column_width_minus1"] = enhanced_.column_width_minus1; - e["ats_row_height_minus1"] = enhanced_.row_height_minus1; + e["ats_column_width_minus_1"] = enhanced_.column_width_minus1; + e["ats_row_height_minus_1"] = enhanced_.row_height_minus1; } else { - e["ats_region_width_minus1"] = enhanced_.region_width_minus1; - e["ats_region_height_minus1"] = enhanced_.region_height_minus1; + e["ats_region_width_minus_1"] = enhanced_.region_width_minus1; + e["ats_region_height_minus_1"] = enhanced_.region_height_minus1; } e["ats_single_region_per_atlas_segment_flag"] = enhanced_.single_region_per_segment_flag; - e["ats_num_atlas_segments_minus1"] = enhanced_.num_atlas_segments_minus1; + e["ats_num_atlas_segments_minus_1"] = enhanced_.num_atlas_segments_minus1; if (!enhanced_.single_region_per_segment_flag) { json regions = json::array(); for (const auto& r : enhanced_.segment_regions) { @@ -221,7 +221,7 @@ json AtlasSegmentOBU::to_json() const { j["ats_stream_id_present"] = basic_stream_id_present_; j["ats_width"] = basic_width_; j["ats_height"] = basic_height_; - j["ats_num_atlas_segments_minus1"] = segments_.empty() ? 0 : (uint32_t)segments_.size() - 1; + j["ats_num_atlas_segments_minus_1"] = segments_.empty() ? 0 : (uint32_t)segments_.size() - 1; json segs = json::array(); for (const auto& s : segments_) { json seg; @@ -237,14 +237,14 @@ json AtlasSegmentOBU::to_json() const { break; } case ModeIdc::SINGLE_ATLAS: - j["ats_nominal_width_minus1"] = nominal_width_minus1_; - j["ats_nominal_height_minus1"] = nominal_height_minus1_; + j["ats_nominal_width_minus_1"] = nominal_width_minus1_; + j["ats_nominal_height_minus_1"] = nominal_height_minus1_; break; case ModeIdc::MULTISTREAM_ATLAS: case ModeIdc::MULTISTREAM_ALPHA_ATLAS: { j["ats_msi_width"] = msi_width_; j["ats_msi_height"] = msi_height_; - j["ats_msi_num_atlas_segments_minus1"] = + j["ats_msi_num_atlas_segments_minus_1"] = segments_.empty() ? 0 : (uint32_t)segments_.size() - 1; if (mode_idc_ == ModeIdc::MULTISTREAM_ALPHA_ATLAS) j["ats_msi_alpha_segments_present_flag"] = msi_alpha_segments_present_;