diff --git a/core/expgolomb.cpp b/core/expgolomb.cpp index ff5a093..7cece55 100644 --- a/core/expgolomb.cpp +++ b/core/expgolomb.cpp @@ -71,6 +71,11 @@ uint64_t RbspBitReader::ReadBits(int n) { return v; } +int64_t RbspBitReader::ReadSe() { + const uint64_t ue = ReadUe(); + return (ue & 1) ? (int64_t)((ue + 1) / 2) : -(int64_t)(ue / 2); +} + uint64_t RbspBitReader::ReadUe() { int zeros = 0; while (!eof() && !ReadBit() && zeros < 32) { diff --git a/core/expgolomb.h b/core/expgolomb.h index 23083e2..20c2c6e 100644 --- a/core/expgolomb.h +++ b/core/expgolomb.h @@ -14,6 +14,7 @@ class RbspBitReader { bool ReadBit(); uint64_t ReadBits(int n); uint64_t ReadUe(); + int64_t ReadSe(); size_t BitsRead() const { return bits_read_; } bool eof() const { return eof_ && bits_left_ == 0; } diff --git a/core/h265types.cpp b/core/h265types.cpp index d706f8f..d50e070 100644 --- a/core/h265types.cpp +++ b/core/h265types.cpp @@ -239,7 +239,8 @@ struct ShortTermRps { } }; -void parseShortTermRefPicSet(BitReader& br, uint32_t idx, +template +void parseShortTermRefPicSet(Reader& br, uint32_t idx, const std::vector& rps_list, std::vector& out) { ShortTermRps rps; @@ -499,23 +500,213 @@ bool ParseH265SliceSegmentHeader(const NALUnit& nal, const uint8_t* data, br.ReadBits(addr_bits); // slice_segment_address } - if (!out.dependent) { - for (int i = 0; i < (int)pps.num_extra_slice_header_bits; i++) { - br.ReadBit(); + if (out.dependent) { + out.header_bits = (uint32_t)br.BitsRead(); + return true; + } + + for (int i = 0; i < (int)pps.num_extra_slice_header_bits; i++) { + br.ReadBit(); + } + out.slice_type = (uint32_t)br.ReadUe(); // 0=B, 1=P, 2=I + if (pps.output_flag_present_flag) { + out.pic_output_flag = br.ReadBit(); + } + if (sps.separate_colour_plane_flag) { + out.colour_plane_id = (uint8_t)br.ReadBits(2); + } + + const bool is_b = out.slice_type == 0; + const bool is_i = out.slice_type == 2; + + if (!out.is_idr) { + const uint32_t poc_bits = sps.log2_max_pic_order_cnt_lsb_minus4 + 4; + out.pic_order_cnt_lsb = (uint32_t)br.ReadBits(poc_bits); + out.poc_valid = true; + + out.short_term_ref_pic_set_sps_flag = br.ReadBit(); + if (out.short_term_ref_pic_set_sps_flag) { + uint32_t idx_bits = 0; + while ((1u << idx_bits) < std::max(1u, sps.num_short_term_ref_pic_sets)) { + idx_bits++; + } + out.short_term_ref_pic_set_idx = + idx_bits ? (uint32_t)br.ReadBits(idx_bits) : 0; + } else { + std::vector empty; + std::vector inline_rps; + parseShortTermRefPicSet(br, 0, empty, inline_rps); + } + } else { + out.poc_valid = true; + } + + if (sps.long_term_ref_pics_present_flag) { + if (sps.num_long_term_ref_pics_sps > 0) { + out.num_long_term_sps = (uint32_t)br.ReadUe(); } - out.slice_type = (uint32_t)br.ReadUe(); - if (pps.output_flag_present_flag) { - br.ReadBit(); + out.num_long_term_pics = (uint32_t)br.ReadUe(); + const uint32_t total = out.num_long_term_sps + out.num_long_term_pics; + const uint32_t poc_bits = sps.log2_max_pic_order_cnt_lsb_minus4 + 4; + uint32_t lt_idx_bits = 0; + while ((1u << lt_idx_bits) < std::max(1u, sps.num_long_term_ref_pics_sps)) { + lt_idx_bits++; } - if (sps.separate_colour_plane_flag) { - br.ReadBits(2); + for (uint32_t i = 0; i < total; i++) { + if (i < out.num_long_term_sps) { + if (lt_idx_bits) { + br.ReadBits(lt_idx_bits); // lt_idx_sps + } + } else { + br.ReadBits(poc_bits); // poc_lsb_lt + } + (void)br.ReadBit(); // used_by_curr_pic_lt_flag + bool msb_present = false; + if (total > 1) { + msb_present = br.ReadBit(); + } + if (msb_present) { + br.ReadUe(); // delta_poc_msb_cycle_lt + } + } + } + + if (sps.sps_temporal_mvp_enabled_flag) { + out.slice_temporal_mvp_enabled_flag = br.ReadBit(); + } + + if (sps.sample_adaptive_offset_enabled_flag) { + out.slice_sao_luma_flag = br.ReadBit(); + out.slice_sao_chroma_flag = br.ReadBit(); + } + + out.num_ref_idx_active_override_flag = br.ReadBit(); + if (out.num_ref_idx_active_override_flag) { + out.num_ref_idx_l0_active_minus1 = (uint32_t)br.ReadUe(); + if (is_b) { + out.num_ref_idx_l1_active_minus1 = (uint32_t)br.ReadUe(); + } + } else { + out.num_ref_idx_l0_active_minus1 = pps.num_ref_idx_l0_default_active_minus1; + out.num_ref_idx_l1_active_minus1 = pps.num_ref_idx_l1_default_active_minus1; + } + + if (is_b) { + out.mvd_l1_zero_flag = br.ReadBit(); + } + if (pps.cabac_init_present_flag && !is_i) { + out.cabac_init_flag = br.ReadBit(); + } + + if (out.slice_temporal_mvp_enabled_flag && !is_i) { + out.collocated_from_l0_flag = br.ReadBit(); + if ((out.collocated_from_l0_flag && out.num_ref_idx_l0_active_minus1 > 0) || + (!out.collocated_from_l0_flag && + out.num_ref_idx_l1_active_minus1 > 0)) { + out.collocated_ref_idx = (uint32_t)br.ReadUe(); } - if (!out.is_idr) { - const uint32_t poc_bits = sps.log2_max_pic_order_cnt_lsb_minus4 + 4; - out.pic_order_cnt_lsb = (uint32_t)br.ReadBits(poc_bits); + } + + const bool weighted = (pps.weighted_pred_flag && out.slice_type == 1) || + (pps.weighted_bipred_flag && is_b); + if (weighted) { + const bool chroma = sps.chroma_format_idc != 0; + const uint32_t n0 = out.num_ref_idx_l0_active_minus1 + 1; + const uint32_t n1 = is_b ? out.num_ref_idx_l1_active_minus1 + 1 : 0; + br.ReadUe(); // luma_log2_weight_denom + if (chroma) { + br.ReadSe(); // delta_chroma_log2_weight_denom + } + auto read_flags = [&](uint32_t n, std::vector& luma, + std::vector& chroma_flags) { + for (uint32_t i = 0; i < n; i++) { + luma[i] = br.ReadBit(); + } + if (chroma) { + for (uint32_t i = 0; i < n; i++) { + chroma_flags[i] = br.ReadBit(); + } + } + }; + auto read_weights = [&](uint32_t n, const std::vector& luma, + const std::vector& chroma_flags) { + for (uint32_t i = 0; i < n; i++) { + if (luma[i]) { + br.ReadSe(); // delta_luma_weight + br.ReadSe(); // luma_offset + } + if (chroma && chroma_flags[i]) { + br.ReadSe(); + br.ReadSe(); + br.ReadSe(); + br.ReadSe(); + } + } + }; + std::vector luma0(n0, false); + std::vector chroma0(n0, false); + read_flags(n0, luma0, chroma0); + std::vector luma1(n1, false); + std::vector chroma1(n1, false); + if (is_b) { + read_flags(n1, luma1, chroma1); + } + read_weights(n0, luma0, chroma0); + if (is_b) { + read_weights(n1, luma1, chroma1); + } + } + + if (!is_i) { + out.five_minus_max_num_merge_cand = (uint32_t)br.ReadUe(); + } + out.slice_qp_delta = (int32_t)br.ReadSe(); + if (pps.pps_slice_chroma_qp_offsets_present_flag) { + out.slice_cb_qp_offset = (int32_t)br.ReadSe(); + out.slice_cr_qp_offset = (int32_t)br.ReadSe(); + } + + if (pps.deblocking_filter_control_present_flag) { + bool disabled = pps.pps_deblocking_filter_disabled_flag; + out.slice_beta_offset_div2 = pps.pps_beta_offset_div2; + out.slice_tc_offset_div2 = pps.pps_tc_offset_div2; + if (pps.deblocking_filter_override_enabled_flag) { + const bool override_enabled = br.ReadBit(); + if (override_enabled) { + disabled = br.ReadBit(); + if (!disabled) { + out.slice_beta_offset_div2 = (int32_t)br.ReadSe(); + out.slice_tc_offset_div2 = (int32_t)br.ReadSe(); + } + } + } + out.slice_deblocking_filter_disabled_flag = disabled; + } + + if (pps.pps_loop_filter_across_slices_enabled_flag && + (out.slice_sao_luma_flag || out.slice_sao_chroma_flag || + !out.slice_deblocking_filter_disabled_flag)) { + out.slice_loop_filter_across_slices_enabled_flag = br.ReadBit(); + } + + if (pps.tiles_enabled_flag || pps.entropy_coding_sync_enabled_flag) { + out.num_entry_point_offsets = (uint32_t)br.ReadUe(); + if (out.num_entry_point_offsets > 0) { + const uint32_t offset_len = (uint32_t)br.ReadUe() + 1; + for (uint32_t i = 0; i < out.num_entry_point_offsets; i++) { + br.ReadBits((int)offset_len); + } } - out.poc_valid = true; // IDR pictures have POC 0 } + + if (pps.slice_segment_header_extension_present_flag) { + const uint32_t ext_len = (uint32_t)br.ReadUe(); + for (uint32_t i = 0; i < ext_len; i++) { + br.ReadBits(8); + } + } + + out.header_bits = (uint32_t)br.BitsRead(); return true; } diff --git a/core/h265types.h b/core/h265types.h index 24f0503..8333fb3 100644 --- a/core/h265types.h +++ b/core/h265types.h @@ -127,12 +127,41 @@ struct H265SliceSegmentHeader { uint32_t slice_type = 1; // 0 = B, 1 = P, 2 = I uint32_t pic_order_cnt_lsb = 0; bool poc_valid = false; + bool pic_output_flag = true; + uint8_t colour_plane_id = 0; + bool short_term_ref_pic_set_sps_flag = false; + uint32_t short_term_ref_pic_set_idx = 0; + uint32_t num_long_term_sps = 0; + uint32_t num_long_term_pics = 0; + bool slice_temporal_mvp_enabled_flag = false; + bool collocated_from_l0_flag = true; + uint32_t collocated_ref_idx = 0; + bool slice_sao_luma_flag = false; + bool slice_sao_chroma_flag = false; + bool num_ref_idx_active_override_flag = false; + uint32_t num_ref_idx_l0_active_minus1 = 0; + uint32_t num_ref_idx_l1_active_minus1 = 0; + bool mvd_l1_zero_flag = false; + bool cabac_init_flag = false; + uint32_t five_minus_max_num_merge_cand = 0; + int32_t slice_qp_delta = 0; + int32_t slice_cb_qp_offset = 0; + int32_t slice_cr_qp_offset = 0; + bool slice_deblocking_filter_disabled_flag = false; + int32_t slice_beta_offset_div2 = 0; + int32_t slice_tc_offset_div2 = 0; + bool slice_loop_filter_across_slices_enabled_flag = false; + uint32_t num_entry_point_offsets = 0; + uint32_t header_bits = 0; + bool partial = false; char FrameTypeChar() const { - if (is_irap || slice_type == 2) { return 'I'; -} - if (slice_type == 0) { return 'B'; -} + if (is_irap || slice_type == 2) { + return 'I'; + } + if (slice_type == 0) { + return 'B'; + } return 'P'; } }; @@ -141,7 +170,7 @@ bool ParseH265Vps(const NALUnit& nal, const uint8_t* data, H265VPS& out); bool ParseH265Sps(const NALUnit& nal, const uint8_t* data, H265SPS& out); bool ParseH265Pps(const NALUnit& nal, const uint8_t* data, H265PPS& out); bool ParseH265SliceSegmentHeader(const NALUnit& nal, const uint8_t* data, - const H265SPS& sps, const H265PPS& pps, - H265SliceSegmentHeader& out); + const H265SPS& sps, const H265PPS& pps, + H265SliceSegmentHeader& out); #endif // DIA_CORE_H265TYPES_H_ diff --git a/core/nalanalyzer.cpp b/core/nalanalyzer.cpp index e3c8ae3..95964dd 100644 --- a/core/nalanalyzer.cpp +++ b/core/nalanalyzer.cpp @@ -725,11 +725,6 @@ NALAnalysis NALAnalyzer::AnalyzeOne(const NALUnit& nal, } break; } - case 19: - case 20: - case 21: - oss << "=== H.265 Keyframe ===\nType: " << nal.TypeName() << "\n"; - break; case 35: oss << "=== H.265 AUD (Access Unit Delimiter) ===\n"; break; @@ -777,22 +772,96 @@ NALAnalysis NALAnalyzer::AnalyzeOne(const NALUnit& nal, } } + size_t header_bytes = 0; if (sps && pps) { H265SliceSegmentHeader sh; if (ParseH265SliceSegmentHeader(nal, data, *sps, *pps, sh)) { + header_bytes = (sh.header_bits + 7) / 8; static const char* const kSliceNames[3] = {"B", "P", "I"}; oss << "first_slice_segment_in_pic_flag: " << sh.first_slice_segment_in_pic_flag << "\n"; - oss << "slice_type: " << sh.slice_type << " (" - << kSliceNames[sh.slice_type % 3] << ")\n"; - oss << "slice_pic_parameter_set_id: " - << sh.slice_pic_parameter_set_id << "\n"; - if (sh.poc_valid) { - oss << "slice_pic_order_cnt_lsb: " << sh.pic_order_cnt_lsb - << "\n"; + oss << "dependent_slice_segment: " << sh.dependent << "\n"; + if (!sh.dependent) { + oss << "slice_type: " << sh.slice_type << " (" + << kSliceNames[sh.slice_type % 3] << ")\n"; + oss << "pic_output_flag: " << sh.pic_output_flag << "\n"; + if (sps->separate_colour_plane_flag) { + oss << "colour_plane_id: " << (int)sh.colour_plane_id << "\n"; + } + oss << "slice_pic_parameter_set_id: " + << sh.slice_pic_parameter_set_id << "\n"; + if (sh.poc_valid) { + oss << "slice_pic_order_cnt_lsb: " << sh.pic_order_cnt_lsb + << "\n"; + } + if (!sh.is_idr) { + oss << "short_term_ref_pic_set_sps_flag: " + << sh.short_term_ref_pic_set_sps_flag; + if (sh.short_term_ref_pic_set_sps_flag) { + oss << " idx: " << sh.short_term_ref_pic_set_idx; + } + oss << "\n"; + } + if (sps->long_term_ref_pics_present_flag) { + oss << "num_long_term_sps: " << sh.num_long_term_sps + << " num_long_term_pics: " << sh.num_long_term_pics + << "\n"; + } + if (sps->sps_temporal_mvp_enabled_flag) { + oss << "slice_temporal_mvp_enabled_flag: " + << sh.slice_temporal_mvp_enabled_flag; + if (sh.slice_temporal_mvp_enabled_flag && + sh.slice_type != 2) { + oss << " collocated_from_l0: " + << sh.collocated_from_l0_flag + << " collocated_ref_idx: " << sh.collocated_ref_idx; + } + oss << "\n"; + } + if (sps->sample_adaptive_offset_enabled_flag) { + oss << "slice_sao_luma_flag: " << sh.slice_sao_luma_flag + << " slice_sao_chroma_flag: " << sh.slice_sao_chroma_flag + << "\n"; + } + oss << "num_ref_idx_l0_active_minus1: " + << sh.num_ref_idx_l0_active_minus1 + << " l1: " << sh.num_ref_idx_l1_active_minus1 << "\n"; + if (sh.slice_type == 0) { + oss << "mvd_l1_zero_flag: " << sh.mvd_l1_zero_flag << "\n"; + } + if (pps->cabac_init_present_flag && sh.slice_type != 2) { + oss << "cabac_init_flag: " << sh.cabac_init_flag << "\n"; + } + if (sh.slice_type != 2) { + oss << "five_minus_max_num_merge_cand: " + << sh.five_minus_max_num_merge_cand << "\n"; + } + oss << "slice_qp_delta: " << sh.slice_qp_delta << "\n"; + if (pps->pps_slice_chroma_qp_offsets_present_flag) { + oss << "slice_cb_qp_offset: " << sh.slice_cb_qp_offset + << " slice_cr_qp_offset: " << sh.slice_cr_qp_offset + << "\n"; + } + if (pps->deblocking_filter_control_present_flag) { + oss << "slice_deblocking_filter_disabled_flag: " + << sh.slice_deblocking_filter_disabled_flag + << " beta: " << sh.slice_beta_offset_div2 + << " tc: " << sh.slice_tc_offset_div2 << "\n"; + } + if (pps->pps_loop_filter_across_slices_enabled_flag && + (sh.slice_sao_luma_flag || sh.slice_sao_chroma_flag || + !sh.slice_deblocking_filter_disabled_flag)) { + oss << "slice_loop_filter_across_slices_enabled_flag: " + << sh.slice_loop_filter_across_slices_enabled_flag + << "\n"; + } + if (pps->tiles_enabled_flag || + pps->entropy_coding_sync_enabled_flag) { + oss << "num_entry_point_offsets: " + << sh.num_entry_point_offsets << "\n"; + } } - oss << "IRAP: " << sh.is_irap << " IDR: " << sh.is_idr - << " dependent: " << sh.dependent << "\n"; + oss << "IRAP: " << sh.is_irap << " IDR: " << sh.is_idr << "\n"; } else { oss << "Failed to parse slice segment header\n"; } @@ -803,15 +872,22 @@ NALAnalysis NALAnalyzer::AnalyzeOne(const NALUnit& nal, const size_t payload_offset = nal.start_code_len + 1; const size_t payload_bytes = nal.size > payload_offset ? nal.size - payload_offset : 0; - oss << "\nNAL payload: " << payload_bytes << " bytes\n"; - if (payload_offset < nal.size) { + oss << "\nNAL payload: " << payload_bytes << " bytes"; + if (header_bytes > 0) { + const size_t data_bytes = + payload_bytes > header_bytes ? payload_bytes - header_bytes : 0; + oss << " (header ~" << header_bytes << " B, data " << data_bytes + << " B)"; + } + oss << "\n"; + const size_t dump_offset = payload_offset + header_bytes; + if (dump_offset < nal.size) { const size_t dump_len = - std::min(32, nal.size - payload_offset); + std::min(32, nal.size - dump_offset); oss << "Slice data (first " << dump_len << " bytes):\n "; for (size_t i = 0; i < dump_len; i++) { char byte[8]; - std::snprintf(byte, sizeof(byte), "%02X ", - data[payload_offset + i]); + std::snprintf(byte, sizeof(byte), "%02X ", data[dump_offset + i]); oss << byte; if ((i + 1) % 16 == 0 && i + 1 < dump_len) { oss << "\n "; diff --git a/test/test_h265_params.cpp b/test/test_h265_params.cpp index 303fadc..f5de910 100644 --- a/test/test_h265_params.cpp +++ b/test/test_h265_params.cpp @@ -5,6 +5,7 @@ #include "bitstreamparser.h" #include "h265types.h" +#include "nalanalyzer.h" namespace { @@ -77,3 +78,32 @@ TEST(H265ParamsTest, NamesIrapSlices) { EXPECT_EQ(idr->TypeName(), "IDR_N_LP"); EXPECT_EQ(cra->TypeName(), "CRA_NUT"); } + +TEST(H265ParamsTest, BuildsSliceDetailsText) { + if (!sample_exists()) { + GTEST_SKIP() << "sample stream missing"; + } + + BitstreamParser parser(CodecType::kH265); + ASSERT_TRUE(parser.ParseFile(sample_path())) << parser.LastError(); + + NALAnalyzer analyzer; + ASSERT_TRUE(analyzer.AnalyzeAll(parser)); + + const NALUnit* slice = nullptr; + for (const auto& nal : parser.NalUnits()) { + if (nal.nal_type <= 31) { + slice = &nal; + break; + } + } + ASSERT_NE(slice, nullptr); + + const NALAnalysis analysis = + analyzer.AnalyzeOne(*slice, parser.NalData(*slice)); + EXPECT_NE(analysis.details.find("slice_type:"), std::string::npos) + << analysis.details; + EXPECT_NE(analysis.details.find("slice_qp_delta:"), std::string::npos); + EXPECT_NE(analysis.details.find("NAL payload:"), std::string::npos) + << analysis.details; +}