Add/rewrite H264 VUI video signal type description.

The rewriter updates video signal parameters in VUI such that they
match to given webrtc::ColorSpace.

Bug: webrtc:10723
Change-Id: I8d0593e3cb727bfee7eb00e3f9ff0b41b93b78bf
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/140881
Reviewed-by: Erik Språng <sprang@webrtc.org>
Reviewed-by: Åsa Persson <asapersson@webrtc.org>
Reviewed-by: Magnus Jedvert <magjed@webrtc.org>
Commit-Queue: Sergey Silkin <ssilkin@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#28306}
diff --git a/common_video/h264/sps_vui_rewriter.cc b/common_video/h264/sps_vui_rewriter.cc
index b6cb4ed..8c62495 100644
--- a/common_video/h264/sps_vui_rewriter.cc
+++ b/common_video/h264/sps_vui_rewriter.cc
@@ -15,6 +15,7 @@
 #include <cstdint>
 #include <vector>
 
+#include "api/video/color_space.h"
 #include "common_video/h264/h264_common.h"
 #include "common_video/h264/sps_parser.h"
 #include "rtc_base/bit_buffer.h"
@@ -73,14 +74,22 @@
 bool CopyAndRewriteVui(const SpsParser::SpsState& sps,
                        rtc::BitBuffer* source,
                        rtc::BitBufferWriter* destination,
+                       const webrtc::ColorSpace* color_space,
                        SpsVuiRewriter::ParseResult* out_vui_rewritten);
 bool CopyHrdParameters(rtc::BitBuffer* source,
                        rtc::BitBufferWriter* destination);
 bool AddBitstreamRestriction(rtc::BitBufferWriter* destination,
                              uint32_t max_num_ref_frames);
+bool IsDefaultColorSpace(const ColorSpace& color_space);
+bool AddVideoSignalTypeInfo(rtc::BitBufferWriter* destination,
+                            const ColorSpace& color_space);
+bool CopyOrRewriteVideoSignalTypeInfo(
+    rtc::BitBuffer* source,
+    rtc::BitBufferWriter* destination,
+    const ColorSpace* color_space,
+    SpsVuiRewriter::ParseResult* out_vui_rewritten);
 bool CopyRemainingBits(rtc::BitBuffer* source,
                        rtc::BitBufferWriter* destination);
-
 }  // namespace
 
 void SpsVuiRewriter::UpdateStats(ParseResult result, Direction direction) {
@@ -116,6 +125,7 @@
     const uint8_t* buffer,
     size_t length,
     absl::optional<SpsParser::SpsState>* sps,
+    const webrtc::ColorSpace* color_space,
     rtc::Buffer* destination) {
   // Create temporary RBSP decoded buffer of the payload (exlcuding the
   // leading nalu type header byte (the SpsParser uses only the payload).
@@ -151,7 +161,7 @@
   sps_writer.Seek(byte_offset, bit_offset);
 
   ParseResult vui_updated;
-  if (!CopyAndRewriteVui(*sps_state, &source_buffer, &sps_writer,
+  if (!CopyAndRewriteVui(*sps_state, &source_buffer, &sps_writer, color_space,
                          &vui_updated)) {
     RTC_LOG(LS_ERROR) << "Failed to parse/copy SPS VUI.";
     return ParseResult::kFailure;
@@ -190,9 +200,11 @@
     const uint8_t* buffer,
     size_t length,
     absl::optional<SpsParser::SpsState>* sps,
+    const webrtc::ColorSpace* color_space,
     rtc::Buffer* destination,
     Direction direction) {
-  ParseResult result = ParseAndRewriteSps(buffer, length, sps, destination);
+  ParseResult result =
+      ParseAndRewriteSps(buffer, length, sps, color_space, destination);
   UpdateStats(result, direction);
   return result;
 }
@@ -202,6 +214,7 @@
     size_t num_nalus,
     const size_t* nalu_offsets,
     const size_t* nalu_lengths,
+    const webrtc::ColorSpace* color_space,
     rtc::CopyOnWriteBuffer* output_buffer,
     size_t* output_nalu_offsets,
     size_t* output_nalu_lengths) {
@@ -244,7 +257,7 @@
 
       ParseResult result = ParseAndRewriteSps(
           nalu_ptr + H264::kNaluTypeSize, nalu_length - H264::kNaluTypeSize,
-          &sps, &output_nalu, Direction::kOutgoing);
+          &sps, color_space, &output_nalu, Direction::kOutgoing);
       if (result == ParseResult::kVuiRewritten) {
         updated_sps = true;
         output_nalu_offsets[i] = output_buffer->size();
@@ -265,14 +278,16 @@
 }
 
 namespace {
-
 bool CopyAndRewriteVui(const SpsParser::SpsState& sps,
                        rtc::BitBuffer* source,
                        rtc::BitBufferWriter* destination,
+                       const webrtc::ColorSpace* color_space,
                        SpsVuiRewriter::ParseResult* out_vui_rewritten) {
   uint32_t golomb_tmp;
   uint32_t bits_tmp;
 
+  *out_vui_rewritten = SpsVuiRewriter::ParseResult::kVuiOk;
+
   //
   // vui_parameters_present_flag: u(1)
   //
@@ -283,12 +298,27 @@
   // (2) rewrite frame reordering values so no reordering is allowed.
   if (!sps.vui_params_present) {
     // Write a simple VUI with the parameters we want and 0 for all other flags.
-    // There are 8 flags to be off before the bitstream restriction flag.
-    RETURN_FALSE_ON_FAIL(destination->WriteBits(0, 8));
+
+    // aspect_ratio_info_present_flag, overscan_info_present_flag. Both u(1).
+    RETURN_FALSE_ON_FAIL(destination->WriteBits(0, 2));
+
+    uint32_t video_signal_type_present_flag =
+        (color_space && !IsDefaultColorSpace(*color_space)) ? 1 : 0;
+    RETURN_FALSE_ON_FAIL(
+        destination->WriteBits(video_signal_type_present_flag, 1));
+    if (video_signal_type_present_flag) {
+      RETURN_FALSE_ON_FAIL(AddVideoSignalTypeInfo(destination, *color_space));
+    }
+    // chroma_loc_info_present_flag, timing_info_present_flag,
+    // nal_hrd_parameters_present_flag, vcl_hrd_parameters_present_flag,
+    // pic_struct_present_flag, All u(1)
+    RETURN_FALSE_ON_FAIL(destination->WriteBits(0, 5));
     // bitstream_restriction_flag: u(1)
     RETURN_FALSE_ON_FAIL(destination->WriteBits(1, 1));
     RETURN_FALSE_ON_FAIL(
         AddBitstreamRestriction(destination, sps.max_num_ref_frames));
+
+    *out_vui_rewritten = SpsVuiRewriter::ParseResult::kVuiRewritten;
   } else {
     // Parse out the full VUI.
     // aspect_ratio_info_present_flag: u(1)
@@ -307,19 +337,10 @@
       // overscan_appropriate_flag: u(1)
       COPY_BITS(source, destination, bits_tmp, 1);
     }
-    // video_signal_type_present_flag: u(1)
-    COPY_BITS(source, destination, bits_tmp, 1);
-    if (bits_tmp == 1) {
-      // video_format + video_full_range_flag: u(3) + u(1)
-      COPY_BITS(source, destination, bits_tmp, 4);
-      // colour_description_present_flag: u(1)
-      COPY_BITS(source, destination, bits_tmp, 1);
-      if (bits_tmp == 1) {
-        // colour_primaries, transfer_characteristics, matrix_coefficients:
-        // u(8) each.
-        COPY_BITS(source, destination, bits_tmp, 24);
-      }
-    }
+
+    CopyOrRewriteVideoSignalTypeInfo(source, destination, color_space,
+                                     out_vui_rewritten);
+
     // chroma_loc_info_present_flag: u(1)
     COPY_BITS(source, destination, bits_tmp, 1);
     if (bits_tmp == 1) {
@@ -364,6 +385,7 @@
       // We're adding one from scratch.
       RETURN_FALSE_ON_FAIL(
           AddBitstreamRestriction(destination, sps.max_num_ref_frames));
+      *out_vui_rewritten = SpsVuiRewriter::ParseResult::kVuiRewritten;
     } else {
       // We're replacing.
       // motion_vectors_over_pic_boundaries_flag: u(1)
@@ -387,18 +409,15 @@
           source->ReadExponentialGolomb(&max_num_reorder_frames));
       RETURN_FALSE_ON_FAIL(
           source->ReadExponentialGolomb(&max_dec_frame_buffering));
-      if (max_num_reorder_frames == 0 &&
-          max_dec_frame_buffering <= sps.max_num_ref_frames) {
-        RTC_LOG(LS_INFO) << "VUI bitstream already contains an optimal VUI.";
-        *out_vui_rewritten = SpsVuiRewriter::ParseResult::kVuiOk;
-        return true;
-      }
       RETURN_FALSE_ON_FAIL(destination->WriteExponentialGolomb(0));
       RETURN_FALSE_ON_FAIL(
           destination->WriteExponentialGolomb(sps.max_num_ref_frames));
+      if (max_num_reorder_frames != 0 ||
+          max_dec_frame_buffering > sps.max_num_ref_frames) {
+        *out_vui_rewritten = SpsVuiRewriter::ParseResult::kVuiRewritten;
+      }
     }
   }
-  *out_vui_rewritten = SpsVuiRewriter::ParseResult::kVuiRewritten;
   return true;
 }
 
@@ -461,6 +480,129 @@
   return true;
 }
 
+bool IsDefaultColorSpace(const ColorSpace& color_space) {
+  return color_space.range() != ColorSpace::RangeID::kFull &&
+         color_space.primaries() == ColorSpace::PrimaryID::kUnspecified &&
+         color_space.transfer() == ColorSpace::TransferID::kUnspecified &&
+         color_space.matrix() == ColorSpace::MatrixID::kUnspecified;
+}
+
+bool AddVideoSignalTypeInfo(rtc::BitBufferWriter* destination,
+                            const ColorSpace& color_space) {
+  // video_format: u(3).
+  RETURN_FALSE_ON_FAIL(destination->WriteBits(5, 3));  // 5 = Unspecified
+  // video_full_range_flag: u(1)
+  RETURN_FALSE_ON_FAIL(destination->WriteBits(
+      color_space.range() == ColorSpace::RangeID::kFull ? 1 : 0, 1));
+  // colour_description_present_flag: u(1)
+  RETURN_FALSE_ON_FAIL(destination->WriteBits(1, 1));
+  // colour_primaries: u(8)
+  RETURN_FALSE_ON_FAIL(
+      destination->WriteUInt8(static_cast<uint8_t>(color_space.primaries())));
+  // transfer_characteristics: u(8)
+  RETURN_FALSE_ON_FAIL(
+      destination->WriteUInt8(static_cast<uint8_t>(color_space.transfer())));
+  // matrix_coefficients: u(8)
+  RETURN_FALSE_ON_FAIL(
+      destination->WriteUInt8(static_cast<uint8_t>(color_space.matrix())));
+  return true;
+}
+
+bool CopyOrRewriteVideoSignalTypeInfo(
+    rtc::BitBuffer* source,
+    rtc::BitBufferWriter* destination,
+    const ColorSpace* color_space,
+    SpsVuiRewriter::ParseResult* out_vui_rewritten) {
+  // Read.
+  uint32_t video_signal_type_present_flag;
+  uint32_t video_format = 5;           // H264 default: unspecified
+  uint32_t video_full_range_flag = 0;  // H264 default: limited
+  uint32_t colour_description_present_flag = 0;
+  uint8_t colour_primaries = 3;          // H264 default: unspecified
+  uint8_t transfer_characteristics = 3;  // H264 default: unspecified
+  uint8_t matrix_coefficients = 3;       // H264 default: unspecified
+  RETURN_FALSE_ON_FAIL(source->ReadBits(&video_signal_type_present_flag, 1));
+  if (video_signal_type_present_flag) {
+    RETURN_FALSE_ON_FAIL(source->ReadBits(&video_format, 3));
+    RETURN_FALSE_ON_FAIL(source->ReadBits(&video_full_range_flag, 1));
+    RETURN_FALSE_ON_FAIL(source->ReadBits(&colour_description_present_flag, 1));
+    if (colour_description_present_flag) {
+      RETURN_FALSE_ON_FAIL(source->ReadUInt8(&colour_primaries));
+      RETURN_FALSE_ON_FAIL(source->ReadUInt8(&transfer_characteristics));
+      RETURN_FALSE_ON_FAIL(source->ReadUInt8(&matrix_coefficients));
+    }
+  }
+
+  // Update.
+  uint32_t video_signal_type_present_flag_override =
+      video_signal_type_present_flag;
+  uint32_t video_format_override = video_format;
+  uint32_t video_full_range_flag_override = video_full_range_flag;
+  uint32_t colour_description_present_flag_override =
+      colour_description_present_flag;
+  uint8_t colour_primaries_override = colour_primaries;
+  uint8_t transfer_characteristics_override = transfer_characteristics;
+  uint8_t matrix_coefficients_override = matrix_coefficients;
+  if (color_space) {
+    if (IsDefaultColorSpace(*color_space)) {
+      video_signal_type_present_flag_override = 0;
+    } else {
+      video_signal_type_present_flag_override = 1;
+      video_format_override = 5;  // unspecified
+
+      if (color_space->range() == ColorSpace::RangeID::kFull) {
+        video_full_range_flag_override = 1;
+      } else {
+        // ColorSpace::RangeID::kInvalid and kDerived are treated as limited.
+        video_full_range_flag_override = 0;
+      }
+
+      colour_description_present_flag_override =
+          color_space->primaries() != ColorSpace::PrimaryID::kUnspecified ||
+          color_space->transfer() != ColorSpace::TransferID::kUnspecified ||
+          color_space->matrix() != ColorSpace::MatrixID::kUnspecified;
+      colour_primaries_override =
+          static_cast<uint8_t>(color_space->primaries());
+      transfer_characteristics_override =
+          static_cast<uint8_t>(color_space->transfer());
+      matrix_coefficients_override =
+          static_cast<uint8_t>(color_space->matrix());
+    }
+  }
+
+  // Write.
+  RETURN_FALSE_ON_FAIL(
+      destination->WriteBits(video_signal_type_present_flag_override, 1));
+  if (video_signal_type_present_flag_override) {
+    RETURN_FALSE_ON_FAIL(destination->WriteBits(video_format_override, 3));
+    RETURN_FALSE_ON_FAIL(
+        destination->WriteBits(video_full_range_flag_override, 1));
+    RETURN_FALSE_ON_FAIL(
+        destination->WriteBits(colour_description_present_flag_override, 1));
+    if (colour_description_present_flag_override) {
+      RETURN_FALSE_ON_FAIL(destination->WriteUInt8(colour_primaries_override));
+      RETURN_FALSE_ON_FAIL(
+          destination->WriteUInt8(transfer_characteristics_override));
+      RETURN_FALSE_ON_FAIL(
+          destination->WriteUInt8(matrix_coefficients_override));
+    }
+  }
+
+  if (video_signal_type_present_flag_override !=
+          video_signal_type_present_flag ||
+      video_format_override != video_format ||
+      video_full_range_flag_override != video_full_range_flag ||
+      colour_description_present_flag_override !=
+          colour_description_present_flag ||
+      colour_primaries_override != colour_primaries ||
+      transfer_characteristics_override != transfer_characteristics ||
+      matrix_coefficients_override != matrix_coefficients) {
+    *out_vui_rewritten = SpsVuiRewriter::ParseResult::kVuiRewritten;
+  }
+
+  return true;
+}
+
 bool CopyRemainingBits(rtc::BitBuffer* source,
                        rtc::BitBufferWriter* destination) {
   uint32_t bits_tmp;
diff --git a/common_video/h264/sps_vui_rewriter.h b/common_video/h264/sps_vui_rewriter.h
index 250b641..0590b5a 100644
--- a/common_video/h264/sps_vui_rewriter.h
+++ b/common_video/h264/sps_vui_rewriter.h
@@ -16,20 +16,19 @@
 #include <stdint.h>
 
 #include "absl/types/optional.h"
+#include "api/video/color_space.h"
 #include "common_video/h264/sps_parser.h"
 #include "rtc_base/buffer.h"
 #include "rtc_base/copy_on_write_buffer.h"
 
 namespace webrtc {
 
-// A class that can parse an SPS block of a NAL unit and if necessary
-// creates a copy with updated settings to allow for faster decoding for streams
-// that use picture order count type 0. Streams in that format incur additional
-// delay because it allows decode order to differ from render order.
-// The mechanism used is to rewrite (edit or add) the SPS's VUI to contain
-// restrictions on the maximum number of reordered pictures. This reduces
-// latency significantly, though it still adds about a frame of latency to
-// decoding.
+// A class that can parse an SPS+VUI and if necessary creates a copy with
+// updated parameters.
+// The rewriter disables frame buffering. This should force decoders to deliver
+// decoded frame immediately and, thus, reduce latency.
+// The rewriter updates video signal type parameters if external parameters are
+// provided.
 class SpsVuiRewriter : private SpsParser {
  public:
   enum class ParseResult { kFailure, kVuiOk, kVuiRewritten };
@@ -48,6 +47,7 @@
       const uint8_t* buffer,
       size_t length,
       absl::optional<SpsParser::SpsState>* sps,
+      const ColorSpace* color_space,
       rtc::Buffer* destination,
       Direction Direction);
 
@@ -61,6 +61,7 @@
       size_t num_nalus,
       const size_t* nalu_offsets,
       const size_t* nalu_lengths,
+      const ColorSpace* color_space,
       rtc::CopyOnWriteBuffer* output_buffer,
       size_t* output_nalu_offsets,
       size_t* output_nalu_lengths);
@@ -70,6 +71,7 @@
       const uint8_t* buffer,
       size_t length,
       absl::optional<SpsParser::SpsState>* sps,
+      const ColorSpace* color_space,
       rtc::Buffer* destination);
 
   static void UpdateStats(ParseResult result, Direction direction);
diff --git a/common_video/h264/sps_vui_rewriter_unittest.cc b/common_video/h264/sps_vui_rewriter_unittest.cc
index 263bfef..870981f 100644
--- a/common_video/h264/sps_vui_rewriter_unittest.cc
+++ b/common_video/h264/sps_vui_rewriter_unittest.cc
@@ -11,6 +11,7 @@
 #include <cstdint>
 #include <vector>
 
+#include "api/video/color_space.h"
 #include "common_video/h264/h264_common.h"
 #include "common_video/h264/sps_vui_rewriter.h"
 #include "rtc_base/bit_buffer.h"
@@ -21,6 +22,7 @@
 
 namespace webrtc {
 
+namespace {
 enum SpsMode {
   kNoRewriteRequired_VuiOptimal,
   kRewriteRequired_NoVui,
@@ -37,13 +39,159 @@
 static const uint8_t kIdr1[] = {H264::NaluType::kIdr, 0xFF, 0x00, 0x00, 0x04};
 static const uint8_t kIdr2[] = {H264::NaluType::kIdr, 0xFF, 0x00, 0x11};
 
+struct VuiHeader {
+  uint32_t vui_parameters_present_flag;
+  uint32_t bitstream_restriction_flag;
+  uint32_t max_num_reorder_frames;
+  uint32_t max_dec_frame_buffering;
+  uint32_t video_signal_type_present_flag;
+  uint32_t video_full_range_flag;
+  uint32_t colour_description_present_flag;
+  uint8_t colour_primaries;
+  uint8_t transfer_characteristics;
+  uint8_t matrix_coefficients;
+};
+
+static const VuiHeader kVuiNotPresent = {
+    /* vui_parameters_present_flag= */ 0,
+    /* bitstream_restriction_flag= */ 0,
+    /* max_num_reorder_frames= */ 0,
+    /* max_dec_frame_buffering= */ 0,
+    /* video_signal_type_present_flag= */ 0,
+    /* video_full_range_flag= */ 0,
+    /* colour_description_present_flag= */ 0,
+    /* colour_primaries= */ 0,
+    /* transfer_characteristics= */ 0,
+    /* matrix_coefficients= */ 0};
+
+static const VuiHeader kVuiNoBitstreamRestriction = {
+    /* vui_parameters_present_flag= */ 1,
+    /* bitstream_restriction_flag= */ 0,
+    /* max_num_reorder_frames= */ 0,
+    /* max_dec_frame_buffering= */ 0,
+    /* video_signal_type_present_flag= */ 0,
+    /* video_full_range_flag= */ 0,
+    /* colour_description_present_flag= */ 0,
+    /* colour_primaries= */ 0,
+    /* transfer_characteristics= */ 0,
+    /* matrix_coefficients= */ 0};
+
+static const VuiHeader kVuiNoFrameBuffering = {
+    /* vui_parameters_present_flag= */ 1,
+    /* bitstream_restriction_flag= */ 1,
+    /* max_num_reorder_frames= */ 0,
+    /* max_dec_frame_buffering= */ 1,
+    /* video_signal_type_present_flag= */ 0,
+    /* video_full_range_flag= */ 0,
+    /* colour_description_present_flag= */ 0,
+    /* colour_primaries= */ 0,
+    /* transfer_characteristics= */ 0,
+    /* matrix_coefficients= */ 0};
+
+static const VuiHeader kVuiFrameBuffering = {
+    /* vui_parameters_present_flag= */ 1,
+    /* bitstream_restriction_flag= */ 1,
+    /* max_num_reorder_frames= */ 3,
+    /* max_dec_frame_buffering= */ 3,
+    /* video_signal_type_present_flag= */ 0,
+    /* video_full_range_flag= */ 0,
+    /* colour_description_present_flag= */ 0,
+    /* colour_primaries= */ 0,
+    /* transfer_characteristics= */ 0,
+    /* matrix_coefficients= */ 0};
+
+static const VuiHeader kVuiNoVideoSignalType = {
+    /* vui_parameters_present_flag= */ 1,
+    /* bitstream_restriction_flag= */ 1,
+    /* max_num_reorder_frames= */ 0,
+    /* max_dec_frame_buffering= */ 1,
+    /* video_signal_type_present_flag= */ 0,
+    /* video_full_range_flag= */ 0,
+    /* colour_description_present_flag= */ 0,
+    /* colour_primaries= */ 0,
+    /* transfer_characteristics= */ 0,
+    /* matrix_coefficients= */ 0};
+
+static const VuiHeader kVuiLimitedRangeNoColourDescription = {
+    /* vui_parameters_present_flag= */ 1,
+    /* bitstream_restriction_flag= */ 1,
+    /* max_num_reorder_frames= */ 0,
+    /* max_dec_frame_buffering= */ 1,
+    /* video_signal_type_present_flag= */ 1,
+    /* video_full_range_flag= */ 0,
+    /* colour_description_present_flag= */ 0,
+    /* colour_primaries= */ 0,
+    /* transfer_characteristics= */ 0,
+    /* matrix_coefficients= */ 0};
+
+static const VuiHeader kVuiFullRangeNoColourDescription = {
+    /* vui_parameters_present_flag= */ 1,
+    /* bitstream_restriction_flag= */ 1,
+    /* max_num_reorder_frames= */ 0,
+    /* max_dec_frame_buffering= */ 1,
+    /* video_signal_type_present_flag= */ 1,
+    /* video_full_range_flag= */ 1,
+    /* colour_description_present_flag= */ 0,
+    /* colour_primaries= */ 0,
+    /* transfer_characteristics= */ 0,
+    /* matrix_coefficients= */ 0};
+
+static const VuiHeader kVuiLimitedRangeBt709Color = {
+    /* vui_parameters_present_flag= */ 1,
+    /* bitstream_restriction_flag= */ 1,
+    /* max_num_reorder_frames= */ 0,
+    /* max_dec_frame_buffering= */ 1,
+    /* video_signal_type_present_flag= */ 1,
+    /* video_full_range_flag= */ 0,
+    /* colour_description_present_flag= */ 1,
+    /* colour_primaries= */ 1,
+    /* transfer_characteristics= */ 1,
+    /* matrix_coefficients= */ 1};
+
+static const webrtc::ColorSpace kColorSpaceH264Default(
+    ColorSpace::PrimaryID::kUnspecified,
+    ColorSpace::TransferID::kUnspecified,
+    ColorSpace::MatrixID::kUnspecified,
+    ColorSpace::RangeID::kLimited);
+
+static const webrtc::ColorSpace kColorSpacePrimariesBt709(
+    ColorSpace::PrimaryID::kBT709,
+    ColorSpace::TransferID::kUnspecified,
+    ColorSpace::MatrixID::kUnspecified,
+    ColorSpace::RangeID::kLimited);
+
+static const webrtc::ColorSpace kColorSpaceTransferBt709(
+    ColorSpace::PrimaryID::kUnspecified,
+    ColorSpace::TransferID::kBT709,
+    ColorSpace::MatrixID::kUnspecified,
+    ColorSpace::RangeID::kLimited);
+
+static const webrtc::ColorSpace kColorSpaceMatrixBt709(
+    ColorSpace::PrimaryID::kUnspecified,
+    ColorSpace::TransferID::kUnspecified,
+    ColorSpace::MatrixID::kBT709,
+    ColorSpace::RangeID::kLimited);
+
+static const webrtc::ColorSpace kColorSpaceFullRange(
+    ColorSpace::PrimaryID::kBT709,
+    ColorSpace::TransferID::kUnspecified,
+    ColorSpace::MatrixID::kUnspecified,
+    ColorSpace::RangeID::kFull);
+
+static const webrtc::ColorSpace kColorSpaceBt709LimitedRange(
+    ColorSpace::PrimaryID::kBT709,
+    ColorSpace::TransferID::kBT709,
+    ColorSpace::MatrixID::kBT709,
+    ColorSpace::RangeID::kLimited);
+}  // namespace
+
 // Generates a fake SPS with basically everything empty and with characteristics
 // based off SpsMode.
 // Pass in a buffer of at least kSpsBufferMaxSize.
 // The fake SPS that this generates also always has at least one emulation byte
 // at offset 2, since the first two bytes are always 0, and has a 0x3 as the
 // level_idc, to make sure the parser doesn't eat all 0x3 bytes.
-void GenerateFakeSps(SpsMode mode, rtc::Buffer* out_buffer) {
+void GenerateFakeSps(const VuiHeader& vui, rtc::Buffer* out_buffer) {
   uint8_t rbsp[kSpsBufferMaxSize] = {0};
   rtc::BitBufferWriter writer(rbsp, kSpsBufferMaxSize);
   // Profile byte.
@@ -97,16 +245,31 @@
 
   // Finally! The VUI.
   // vui_parameters_present_flag: u(1)
-  if (mode == kRewriteRequired_NoVui) {
-    writer.WriteBits(0, 1);
-  } else {
-    writer.WriteBits(1, 1);
-    // VUI time. 8 flags to ignore followed by the bitstream restriction flag.
-    writer.WriteBits(0, 8);
-    if (mode == kRewriteRequired_NoBitstreamRestriction) {
-      writer.WriteBits(0, 1);
-    } else {
-      writer.WriteBits(1, 1);
+  writer.WriteBits(vui.vui_parameters_present_flag, 1);
+  if (vui.vui_parameters_present_flag) {
+    // aspect_ratio_info_present_flag, overscan_info_present_flag. Both u(1).
+    writer.WriteBits(0, 2);
+
+    writer.WriteBits(vui.video_signal_type_present_flag, 1);
+    if (vui.video_signal_type_present_flag) {
+      // video_format: u(3). 5 = Unspecified
+      writer.WriteBits(5, 3);
+      writer.WriteBits(vui.video_full_range_flag, 1);
+      writer.WriteBits(vui.colour_description_present_flag, 1);
+      if (vui.colour_description_present_flag) {
+        writer.WriteUInt8(vui.colour_primaries);
+        writer.WriteUInt8(vui.transfer_characteristics);
+        writer.WriteUInt8(vui.matrix_coefficients);
+      }
+    }
+
+    // chroma_loc_info_present_flag, timing_info_present_flag,
+    // nal_hrd_parameters_present_flag, vcl_hrd_parameters_present_flag,
+    // pic_struct_present_flag, All u(1)
+    writer.WriteBits(0, 5);
+
+    writer.WriteBits(vui.bitstream_restriction_flag, 1);
+    if (vui.bitstream_restriction_flag) {
       // Write some defaults. Shouldn't matter for parsing, though.
       // motion_vectors_over_pic_boundaries_flag: u(1)
       writer.WriteBits(1, 1);
@@ -120,15 +283,8 @@
       writer.WriteExponentialGolomb(16);
 
       // Next are the limits we care about.
-      // max_num_reorder_frames: ue(v)
-      // max_dec_frame_buffering: ue(v)
-      if (mode == kRewriteRequired_VuiSuboptimal) {
-        writer.WriteExponentialGolomb(4);
-        writer.WriteExponentialGolomb(4);
-      } else {
-        writer.WriteExponentialGolomb(0);
-        writer.WriteExponentialGolomb(1);
-      }
+      writer.WriteExponentialGolomb(vui.max_num_reorder_frames);
+      writer.WriteExponentialGolomb(vui.max_dec_frame_buffering);
     }
   }
 
@@ -142,21 +298,23 @@
   H264::WriteRbsp(rbsp, byte_count, out_buffer);
 }
 
-void TestSps(SpsMode mode, SpsVuiRewriter::ParseResult expected_parse_result) {
+void TestSps(const VuiHeader& vui,
+             const ColorSpace* color_space,
+             SpsVuiRewriter::ParseResult expected_parse_result) {
   rtc::LogMessage::LogToDebug(rtc::LS_VERBOSE);
   rtc::Buffer original_sps;
-  GenerateFakeSps(mode, &original_sps);
+  GenerateFakeSps(vui, &original_sps);
 
   absl::optional<SpsParser::SpsState> sps;
   rtc::Buffer rewritten_sps;
   SpsVuiRewriter::ParseResult result = SpsVuiRewriter::ParseAndRewriteSps(
-      original_sps.data(), original_sps.size(), &sps, &rewritten_sps,
-      SpsVuiRewriter::Direction::kIncoming);
+      original_sps.data(), original_sps.size(), &sps, color_space,
+      &rewritten_sps, SpsVuiRewriter::Direction::kIncoming);
   EXPECT_EQ(expected_parse_result, result);
   ASSERT_TRUE(sps);
   EXPECT_EQ(sps->width, kWidth);
   EXPECT_EQ(sps->height, kHeight);
-  if (mode != kRewriteRequired_NoVui) {
+  if (vui.vui_parameters_present_flag) {
     EXPECT_EQ(sps->vui_params_present, 1u);
   }
 
@@ -164,7 +322,7 @@
     // Ensure that added/rewritten SPS is parsable.
     rtc::Buffer tmp;
     result = SpsVuiRewriter::ParseAndRewriteSps(
-        rewritten_sps.data(), rewritten_sps.size(), &sps, &tmp,
+        rewritten_sps.data(), rewritten_sps.size(), &sps, nullptr, &tmp,
         SpsVuiRewriter::Direction::kIncoming);
     EXPECT_EQ(SpsVuiRewriter::ParseResult::kVuiOk, result);
     ASSERT_TRUE(sps);
@@ -174,27 +332,67 @@
   }
 }
 
-#define REWRITE_TEST(test_name, mode, expected_parse_result) \
-  TEST(SpsVuiRewriterTest, test_name) { TestSps(mode, expected_parse_result); }
+class SpsVuiRewriterTest : public ::testing::Test,
+                           public ::testing::WithParamInterface<
+                               ::testing::tuple<VuiHeader,
+                                                const ColorSpace*,
+                                                SpsVuiRewriter::ParseResult>> {
+};
 
-REWRITE_TEST(VuiAlreadyOptimal,
-             kNoRewriteRequired_VuiOptimal,
-             SpsVuiRewriter::ParseResult::kVuiOk)
-REWRITE_TEST(RewriteFullVui,
-             kRewriteRequired_NoVui,
-             SpsVuiRewriter::ParseResult::kVuiRewritten)
-REWRITE_TEST(AddBitstreamRestriction,
-             kRewriteRequired_NoBitstreamRestriction,
-             SpsVuiRewriter::ParseResult::kVuiRewritten)
-REWRITE_TEST(RewriteSuboptimalVui,
-             kRewriteRequired_VuiSuboptimal,
-             SpsVuiRewriter::ParseResult::kVuiRewritten)
+TEST_P(SpsVuiRewriterTest, RewriteVui) {
+  VuiHeader vui = ::testing::get<0>(GetParam());
+  const ColorSpace* color_space = ::testing::get<1>(GetParam());
+  SpsVuiRewriter::ParseResult expected_parse_result =
+      ::testing::get<2>(GetParam());
+  TestSps(vui, color_space, expected_parse_result);
+}
 
-TEST(SpsVuiRewriterTest, ParseOutgoingBitstreamOptimalVui) {
+INSTANTIATE_TEST_SUITE_P(
+    ,
+    SpsVuiRewriterTest,
+    ::testing::Values(
+        std::make_tuple(kVuiNoFrameBuffering,
+                        nullptr,
+                        SpsVuiRewriter::ParseResult::kVuiOk),
+        std::make_tuple(kVuiNoVideoSignalType,
+                        &kColorSpaceH264Default,
+                        SpsVuiRewriter::ParseResult::kVuiOk),
+        std::make_tuple(kVuiLimitedRangeBt709Color,
+                        &kColorSpaceBt709LimitedRange,
+                        SpsVuiRewriter::ParseResult::kVuiOk),
+        std::make_tuple(kVuiNotPresent,
+                        nullptr,
+                        SpsVuiRewriter::ParseResult::kVuiRewritten),
+        std::make_tuple(kVuiNoBitstreamRestriction,
+                        nullptr,
+                        SpsVuiRewriter::ParseResult::kVuiRewritten),
+        std::make_tuple(kVuiFrameBuffering,
+                        nullptr,
+                        SpsVuiRewriter::ParseResult::kVuiRewritten),
+        std::make_tuple(kVuiLimitedRangeNoColourDescription,
+                        &kColorSpaceFullRange,
+                        SpsVuiRewriter::ParseResult::kVuiRewritten),
+        std::make_tuple(kVuiNoVideoSignalType,
+                        &kColorSpacePrimariesBt709,
+                        SpsVuiRewriter::ParseResult::kVuiRewritten),
+        std::make_tuple(kVuiNoVideoSignalType,
+                        &kColorSpaceTransferBt709,
+                        SpsVuiRewriter::ParseResult::kVuiRewritten),
+        std::make_tuple(kVuiNoVideoSignalType,
+                        &kColorSpaceMatrixBt709,
+                        SpsVuiRewriter::ParseResult::kVuiRewritten),
+        std::make_tuple(kVuiFullRangeNoColourDescription,
+                        &kColorSpaceH264Default,
+                        SpsVuiRewriter::ParseResult::kVuiRewritten),
+        std::make_tuple(kVuiLimitedRangeBt709Color,
+                        &kColorSpaceH264Default,
+                        SpsVuiRewriter::ParseResult::kVuiRewritten)));
+
+TEST(SpsVuiRewriterOutgoingVuiTest, ParseOutgoingBitstreamOptimalVui) {
   rtc::LogMessage::LogToDebug(rtc::LS_VERBOSE);
 
   rtc::Buffer optimal_sps;
-  GenerateFakeSps(kNoRewriteRequired_VuiOptimal, &optimal_sps);
+  GenerateFakeSps(kVuiNoFrameBuffering, &optimal_sps);
 
   rtc::Buffer buffer;
   const size_t kNumNalus = 2;
@@ -214,7 +412,7 @@
   size_t modified_nalu_lengths[kNumNalus];
 
   SpsVuiRewriter::ParseOutgoingBitstreamAndRewriteSps(
-      buffer, kNumNalus, nalu_offsets, nalu_lengths, &modified_buffer,
+      buffer, kNumNalus, nalu_offsets, nalu_lengths, nullptr, &modified_buffer,
       modified_nalu_offsets, modified_nalu_lengths);
 
   EXPECT_THAT(
@@ -229,11 +427,11 @@
               ::testing::ElementsAreArray(nalu_lengths, kNumNalus));
 }
 
-TEST(SpsVuiRewriterTest, ParseOutgoingBitstreamNoVui) {
+TEST(SpsVuiRewriterOutgoingVuiTest, ParseOutgoingBitstreamNoVui) {
   rtc::LogMessage::LogToDebug(rtc::LS_VERBOSE);
 
   rtc::Buffer sps;
-  GenerateFakeSps(kRewriteRequired_NoVui, &sps);
+  GenerateFakeSps(kVuiNotPresent, &sps);
 
   rtc::Buffer buffer;
   const size_t kNumNalus = 3;
@@ -254,7 +452,7 @@
   buffer.AppendData(kIdr2);
 
   rtc::Buffer optimal_sps;
-  GenerateFakeSps(kNoRewriteRequired_VuiOptimal, &optimal_sps);
+  GenerateFakeSps(kVuiNoFrameBuffering, &optimal_sps);
 
   rtc::Buffer expected_buffer;
   size_t expected_nalu_offsets[kNumNalus];
@@ -278,7 +476,7 @@
   size_t modified_nalu_lengths[kNumNalus];
 
   SpsVuiRewriter::ParseOutgoingBitstreamAndRewriteSps(
-      buffer, kNumNalus, nalu_offsets, nalu_lengths, &modified_buffer,
+      buffer, kNumNalus, nalu_offsets, nalu_lengths, nullptr, &modified_buffer,
       modified_nalu_offsets, modified_nalu_lengths);
 
   EXPECT_THAT(
diff --git a/modules/rtp_rtcp/source/rtp_format_h264.cc b/modules/rtp_rtcp/source/rtp_format_h264.cc
index 0922935..28cc2fb 100644
--- a/modules/rtp_rtcp/source/rtp_format_h264.cc
+++ b/modules/rtp_rtcp/source/rtp_format_h264.cc
@@ -458,7 +458,7 @@
 
         SpsVuiRewriter::ParseResult result = SpsVuiRewriter::ParseAndRewriteSps(
             &payload_data[start_offset], end_offset - start_offset, &sps,
-            output_buffer.get(), SpsVuiRewriter::Direction::kIncoming);
+            nullptr, output_buffer.get(), SpsVuiRewriter::Direction::kIncoming);
 
         if (result == SpsVuiRewriter::ParseResult::kVuiRewritten) {
           if (modified_buffer_) {
diff --git a/video/frame_encode_metadata_writer.cc b/video/frame_encode_metadata_writer.cc
index 13d6372..950e714 100644
--- a/video/frame_encode_metadata_writer.cc
+++ b/video/frame_encode_metadata_writer.cc
@@ -207,7 +207,8 @@
   SpsVuiRewriter::ParseOutgoingBitstreamAndRewriteSps(
       buffer, fragmentation->fragmentationVectorSize,
       fragmentation->fragmentationOffset, fragmentation->fragmentationLength,
-      &modified_buffer, modified_fragmentation->fragmentationOffset,
+      encoded_image->ColorSpace(), &modified_buffer,
+      modified_fragmentation->fragmentationOffset,
       modified_fragmentation->fragmentationLength);
 
   encoded_image->SetEncodedData(modified_buffer);