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);