Support h265 streams with weighted prediction tables.
Some H265 encoders may encode with weighted prediction turned on. Allow
such streams to be parsed.
Bug: chromium:41480904
Change-Id: I25e7ca7b8151f264eeb9a4eae3cd49719dfeef9c
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/378703
Reviewed-by: Henrik Boström <hbos@webrtc.org>
Reviewed-by: Sergey Silkin <ssilkin@webrtc.org>
Commit-Queue: Jianlin Qiu <jianlin.qiu@intel.com>
Cr-Commit-Position: refs/heads/main@{#43991}
diff --git a/common_video/h265/h265_bitstream_parser.cc b/common_video/h265/h265_bitstream_parser.cc
index f39cb51..5cae087 100644
--- a/common_video/h265/h265_bitstream_parser.cc
+++ b/common_video/h265/h265_bitstream_parser.cc
@@ -364,13 +364,109 @@
}
}
}
- if (!slice_reader.Ok() ||
- ((pps->weighted_pred_flag && slice_type == H265::SliceType::kP) ||
- (pps->weighted_bipred_flag && slice_type == H265::SliceType::kB))) {
- // pred_weight_table()
- RTC_LOG(LS_ERROR) << "Streams with pred_weight_table unsupported.";
- return kUnsupportedStream;
+
+ // pred_weight_table()
+ if ((pps->weighted_pred_flag && slice_type == H265::SliceType::kP) ||
+ (pps->weighted_bipred_flag && slice_type == H265::SliceType::kB)) {
+ uint32_t luma_log2_weight_denom = slice_reader.ReadExponentialGolomb();
+ IN_RANGE_OR_RETURN(luma_log2_weight_denom, 0, 7);
+ uint32_t chroma_array_type =
+ sps->separate_colour_plane_flag == 0 ? sps->chroma_format_idc : 0;
+ int32_t chroma_log2_weight_denom = luma_log2_weight_denom;
+ // wp_offset_half_range_c and wp_offset_half_range_y depends on
+ // sps.high_precision_offsets_enable_flag. Since range extension is not
+ // supported, so for now below two are fixed to 128 instead of 1 <<
+ // (sps.bit_depth_luma|chroma_minus8 + 7).
+ int32_t wp_offset_half_range_c = (1 << 7);
+ int32_t wp_offset_half_range_y = (1 << 7);
+ if (chroma_array_type != 0) {
+ // delta_chroma_log2_weight_denom: se(v)
+ chroma_log2_weight_denom +=
+ slice_reader.ReadSignedExponentialGolomb();
+ }
+ IN_RANGE_OR_RETURN(chroma_log2_weight_denom, 0, 7);
+
+ bool luma_weight_flag_l0[kMaxRefIdxActive] = {};
+ bool chroma_weight_flag_l0[kMaxRefIdxActive] = {};
+ int32_t delta_chroma_weight_l0[kMaxRefIdxActive][2] = {};
+ int32_t luma_offset_l0[kMaxRefIdxActive] = {};
+ int32_t delta_chroma_offset_l0[kMaxRefIdxActive][2] = {};
+ for (uint32_t i = 0; i <= num_ref_idx_l0_active_minus1; i++) {
+ // luma_weight_l0_flag: u(1). By syntax this should conditionally
+ // check if the POC or layer ID of the reference picture is different,
+ // but we don't support encoding referencing different layers in the
+ // same AU. Skip the check for now.
+ luma_weight_flag_l0[i] = slice_reader.Read<bool>();
+ }
+ if (chroma_array_type != 0) {
+ for (uint32_t i = 0; i <= num_ref_idx_l0_active_minus1; i++) {
+ // chroma_weight_l0_flag: u(1)
+ chroma_weight_flag_l0[i] = slice_reader.Read<bool>();
+ }
+ }
+ for (uint32_t i = 0; i <= num_ref_idx_l0_active_minus1; i++) {
+ if (luma_weight_flag_l0[i]) {
+ int32_t delta_luma_weight_l0[kMaxRefIdxActive] = {};
+ delta_luma_weight_l0[i] =
+ slice_reader.ReadSignedExponentialGolomb();
+ IN_RANGE_OR_RETURN(delta_luma_weight_l0[i], -128, 127);
+ luma_offset_l0[i] = slice_reader.ReadSignedExponentialGolomb();
+ IN_RANGE_OR_RETURN(luma_offset_l0[i], -wp_offset_half_range_y,
+ wp_offset_half_range_y - 1);
+ }
+ if (chroma_weight_flag_l0[i]) {
+ for (uint32_t j = 0; j < 2; j++) {
+ delta_chroma_weight_l0[i][j] =
+ slice_reader.ReadSignedExponentialGolomb();
+ IN_RANGE_OR_RETURN(delta_chroma_weight_l0[i][j], -128, 127);
+ delta_chroma_offset_l0[i][j] =
+ slice_reader.ReadSignedExponentialGolomb();
+ IN_RANGE_OR_RETURN(delta_chroma_offset_l0[i][j],
+ -4 * wp_offset_half_range_c,
+ 4 * wp_offset_half_range_c - 1);
+ }
+ }
+ }
+ if (slice_type == H265::SliceType::kB) {
+ bool luma_weight_flag_l1[kMaxRefIdxActive] = {};
+ bool chroma_weight_flag_l1[kMaxRefIdxActive] = {};
+ int32_t delta_chroma_weight_l1[kMaxRefIdxActive][2] = {};
+ int32_t luma_offset_l1[kMaxRefIdxActive] = {};
+ int32_t delta_chroma_offset_l1[kMaxRefIdxActive][2] = {};
+ for (uint32_t i = 0; i < num_ref_idx_l1_active_minus1; i++) {
+ luma_weight_flag_l1[i] = slice_reader.Read<bool>();
+ }
+ if (chroma_array_type != 0) {
+ for (uint32_t i = 0; i <= num_ref_idx_l1_active_minus1; i++) {
+ chroma_weight_flag_l1[i] = slice_reader.Read<bool>();
+ }
+ }
+ for (uint32_t i = 0; i <= num_ref_idx_l1_active_minus1; i++) {
+ if (luma_weight_flag_l1[i]) {
+ int32_t delta_luma_weight_l1[kMaxRefIdxActive] = {};
+ delta_luma_weight_l1[i] =
+ slice_reader.ReadSignedExponentialGolomb();
+ IN_RANGE_OR_RETURN(delta_luma_weight_l1[i], -128, 127);
+ luma_offset_l1[i] = slice_reader.ReadSignedExponentialGolomb();
+ IN_RANGE_OR_RETURN(luma_offset_l1[i], -wp_offset_half_range_y,
+ wp_offset_half_range_y - 1);
+ }
+ if (chroma_weight_flag_l1[i]) {
+ for (uint32_t j = 0; j < 2; j++) {
+ delta_chroma_weight_l1[i][j] =
+ slice_reader.ReadSignedExponentialGolomb();
+ IN_RANGE_OR_RETURN(delta_chroma_weight_l1[i][j], -128, 127);
+ delta_chroma_offset_l1[i][j] =
+ slice_reader.ReadSignedExponentialGolomb();
+ IN_RANGE_OR_RETURN(delta_chroma_offset_l1[i][j],
+ -4 * wp_offset_half_range_c,
+ 4 * wp_offset_half_range_c - 1);
+ }
+ }
+ }
+ }
}
+
// five_minus_max_num_merge_cand: ue(v)
uint32_t five_minus_max_num_merge_cand =
slice_reader.ReadExponentialGolomb();
diff --git a/common_video/h265/h265_bitstream_parser_unittest.cc b/common_video/h265/h265_bitstream_parser_unittest.cc
index 087d0e0..3fdb904 100644
--- a/common_video/h265/h265_bitstream_parser_unittest.cc
+++ b/common_video/h265/h265_bitstream_parser_unittest.cc
@@ -101,6 +101,24 @@
0x00, 0x01, 0x26, 0x01, 0xaf, 0x03, 0x44,
};
+// Bitstream that contains pred_weight_table. Contains enough data to parse
+// over pred_weight_table for slice QP. This is bear.hevc from Chromium source,
+// used for H265 hardware decoder's parser test, with some slices truncated.
+const uint8_t kH265BitstreamWithPredWeightTable[] = {
+ 0x00, 0x00, 0x00, 0x01, 0x40, 0x01, 0x0c, 0x01, 0xff, 0xff, 0x01, 0x60,
+ 0x00, 0x00, 0x03, 0x00, 0x80, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00,
+ 0x3c, 0x95, 0xc0, 0x90, 0x00, 0x00, 0x00, 0x01, 0x42, 0x01, 0x01, 0x01,
+ 0x60, 0x00, 0x00, 0x03, 0x00, 0x80, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03,
+ 0x00, 0x3c, 0xa0, 0x0a, 0x08, 0x0b, 0x9f, 0x79, 0x65, 0x79, 0x24, 0xca,
+ 0xe0, 0x10, 0x00, 0x00, 0x06, 0x40, 0x00, 0x00, 0xbb, 0x50, 0x80, 0x00,
+ 0x00, 0x00, 0x01, 0x44, 0x01, 0xc1, 0x73, 0xd1, 0x89, 0x00, 0x00, 0x00,
+ 0x01, 0x02, 0x01, 0xd0, 0x21, 0x49, 0xe8, 0xee, 0x50, 0x9c, 0x27, 0x20,
+ 0x42, 0xc4, 0xcd, 0x33, 0xf0, 0xb1, 0x23, 0x7b, 0xfe, 0x4d, 0xcf, 0x40,
+ 0xeb, 0x17, 0x37, 0x91, 0x1c, 0xb6, 0xba, 0x21, 0x42, 0xf7, 0xef, 0x01,
+ 0x08, 0x90, 0x49, 0xdc, 0xfc, 0x10, 0x1f, 0x5e, 0x02, 0xd9, 0xaa, 0xe8,
+ 0x32, 0xeb, 0x74, 0xbc, 0xdb, 0x2c, 0xa3, 0xec,
+};
+
TEST(H265BitstreamParserTest, ReportsNoQpWithoutParsedSlices) {
H265BitstreamParser h265_parser;
EXPECT_FALSE(h265_parser.GetLastSliceQp().has_value());
@@ -112,6 +130,14 @@
EXPECT_FALSE(h265_parser.GetLastSliceQp().has_value());
}
+TEST(H265BitstreamParserTest, ReportQpWithPredWeightTable) {
+ H265BitstreamParser h265_parser;
+ h265_parser.ParseBitstream(kH265BitstreamWithPredWeightTable);
+ std::optional<int> qp = h265_parser.GetLastSliceQp();
+ ASSERT_TRUE(qp.has_value());
+ EXPECT_EQ(34, *qp);
+}
+
TEST(H265BitstreamParserTest, ReportsLastSliceQpForImageSlices) {
H265BitstreamParser h265_parser;
h265_parser.ParseBitstream(kH265BitstreamChunk);