Send and receive color space information if available

Bug: webrtc:8651
Change-Id: I244647cb1ccbda66fce83ae925cf4273c5a6568b
Reviewed-on: https://webrtc-review.googlesource.com/c/112383
Commit-Queue: Johannes Kron <kron@webrtc.org>
Reviewed-by: Danil Chapovalov <danilchap@webrtc.org>
Reviewed-by: Erik Språng <sprang@webrtc.org>
Reviewed-by: Niels Moller <nisse@webrtc.org>
Reviewed-by: Karl Wiberg <kwiberg@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#25884}
diff --git a/api/rtpparameters.cc b/api/rtpparameters.cc
index 063d106..2b20c56 100644
--- a/api/rtpparameters.cc
+++ b/api/rtpparameters.cc
@@ -137,6 +137,10 @@
     "http://www.webrtc.org/experiments/rtp-hdrext/generic-frame-descriptor-00";
 const int RtpExtension::kGenericFrameDescriptorDefaultId = 11;
 
+const char RtpExtension::kColorSpaceUri[] =
+    "http://www.webrtc.org/experiments/rtp-hdrext/color-space";
+const int RtpExtension::kColorSpaceDefaultId = 12;
+
 const char RtpExtension::kEncryptHeaderExtensionsUri[] =
     "urn:ietf:params:rtp-hdrext:encrypt";
 
@@ -162,7 +166,8 @@
          uri == webrtc::RtpExtension::kVideoTimingUri ||
          uri == webrtc::RtpExtension::kMidUri ||
          uri == webrtc::RtpExtension::kFrameMarkingUri ||
-         uri == webrtc::RtpExtension::kGenericFrameDescriptorUri;
+         uri == webrtc::RtpExtension::kGenericFrameDescriptorUri ||
+         uri == webrtc::RtpExtension::kColorSpaceUri;
 }
 
 bool RtpExtension::IsEncryptionSupported(const std::string& uri) {
diff --git a/api/rtpparameters.h b/api/rtpparameters.h
index badda07..47df22e 100644
--- a/api/rtpparameters.h
+++ b/api/rtpparameters.h
@@ -309,6 +309,10 @@
   // https://tools.ietf.org/html/rfc6904
   static const char kEncryptHeaderExtensionsUri[];
 
+  // Header extension for color space information.
+  static const char kColorSpaceUri[];
+  static const int kColorSpaceDefaultId;
+
   // Inclusive min and max IDs for two-byte header extensions and one-byte
   // header extensions, per RFC8285 Section 4.2-4.3.
   static constexpr int kMinId = 1;
diff --git a/api/video/color_space.h b/api/video/color_space.h
index ff022fe..58a04eb 100644
--- a/api/video/color_space.h
+++ b/api/video/color_space.h
@@ -121,12 +121,13 @@
              MatrixID matrix,
              RangeID range,
              const HdrMetadata* hdr_metadata);
-  bool operator==(const ColorSpace& other) const {
-    return primaries_ == other.primaries() && transfer_ == other.transfer() &&
-           matrix_ == other.matrix() && range_ == other.range() &&
-           ((hdr_metadata_.has_value() && other.hdr_metadata() &&
-             *hdr_metadata_ == *other.hdr_metadata()) ||
-            (!hdr_metadata_.has_value() && other.hdr_metadata() == nullptr));
+  friend bool operator==(const ColorSpace& lhs, const ColorSpace& rhs) {
+    return lhs.primaries_ == rhs.primaries_ && lhs.transfer_ == rhs.transfer_ &&
+           lhs.matrix_ == rhs.matrix_ && lhs.range_ == rhs.range_ &&
+           lhs.hdr_metadata_ == rhs.hdr_metadata_;
+  }
+  friend bool operator!=(const ColorSpace& lhs, const ColorSpace& rhs) {
+    return !(lhs == rhs);
   }
 
   PrimaryID primaries() const;
diff --git a/call/rtp_payload_params.cc b/call/rtp_payload_params.cc
index 542e800..95c64b4 100644
--- a/call/rtp_payload_params.cc
+++ b/call/rtp_payload_params.cc
@@ -156,7 +156,9 @@
   rtp_video_header.playout_delay = image.playout_delay_;
   rtp_video_header.width = image._encodedWidth;
   rtp_video_header.height = image._encodedHeight;
-
+  rtp_video_header.color_space = image.ColorSpace()
+                                     ? absl::make_optional(*image.ColorSpace())
+                                     : absl::nullopt;
   SetVideoTiming(image, &rtp_video_header.video_timing);
 
   const bool is_keyframe = image._frameType == kVideoFrameKey;
diff --git a/call/rtp_payload_params_unittest.cc b/call/rtp_payload_params_unittest.cc
index 44cbcd5..5343cf9 100644
--- a/call/rtp_payload_params_unittest.cc
+++ b/call/rtp_payload_params_unittest.cc
@@ -106,6 +106,7 @@
   EXPECT_EQ(kVideoRotation_90, header.rotation);
   EXPECT_EQ(VideoContentType::SCREENSHARE, header.content_type);
   EXPECT_EQ(kVideoCodecVP9, header.codec);
+  EXPECT_FALSE(header.color_space);
   const auto& vp9_header =
       absl::get<RTPVideoHeaderVP9>(header.video_type_header);
   EXPECT_EQ(kPictureId + 1, vp9_header.picture_id);
@@ -122,11 +123,16 @@
   codec_info.codecSpecific.VP9.end_of_picture = true;
 
   encoded_image.SetSpatialIndex(1);
+  ColorSpace color_space(
+      ColorSpace::PrimaryID::kSMPTE170M, ColorSpace::TransferID::kSMPTE170M,
+      ColorSpace::MatrixID::kSMPTE170M, ColorSpace::RangeID::kFull);
+  encoded_image.SetColorSpace(&color_space);
   header = params.GetRtpVideoHeader(encoded_image, &codec_info, kDontCare);
 
   EXPECT_EQ(kVideoRotation_90, header.rotation);
   EXPECT_EQ(VideoContentType::SCREENSHARE, header.content_type);
   EXPECT_EQ(kVideoCodecVP9, header.codec);
+  EXPECT_EQ(absl::make_optional(color_space), header.color_space);
   EXPECT_EQ(kPictureId + 1, vp9_header.picture_id);
   EXPECT_EQ(kTl0PicIdx, vp9_header.tl0_pic_idx);
   EXPECT_EQ(vp9_header.temporal_idx, codec_info.codecSpecific.VP9.temporal_idx);
diff --git a/media/engine/webrtcvideoengine.cc b/media/engine/webrtcvideoengine.cc
index a75db73..b96f2ea 100644
--- a/media/engine/webrtcvideoengine.cc
+++ b/media/engine/webrtcvideoengine.cc
@@ -504,6 +504,9 @@
   capabilities.header_extensions.push_back(
       webrtc::RtpExtension(webrtc::RtpExtension::kFrameMarkingUri,
                            webrtc::RtpExtension::kFrameMarkingDefaultId));
+  capabilities.header_extensions.push_back(
+      webrtc::RtpExtension(webrtc::RtpExtension::kColorSpaceUri,
+                           webrtc::RtpExtension::kColorSpaceDefaultId));
   if (webrtc::field_trial::IsEnabled("WebRTC-GenericDescriptorAdvertised")) {
     capabilities.header_extensions.push_back(webrtc::RtpExtension(
         webrtc::RtpExtension::kGenericFrameDescriptorUri,
diff --git a/media/engine/webrtcvideoengine_unittest.cc b/media/engine/webrtcvideoengine_unittest.cc
index 543fa3a..4ba3c48 100644
--- a/media/engine/webrtcvideoengine_unittest.cc
+++ b/media/engine/webrtcvideoengine_unittest.cc
@@ -277,50 +277,42 @@
 
 TEST_F(WebRtcVideoEngineTest, SupportsTimestampOffsetHeaderExtension) {
   RtpCapabilities capabilities = engine_.GetCapabilities();
-  ASSERT_FALSE(capabilities.header_extensions.empty());
-  for (const RtpExtension& extension : capabilities.header_extensions) {
-    if (extension.uri == RtpExtension::kTimestampOffsetUri) {
-      EXPECT_EQ(RtpExtension::kTimestampOffsetDefaultId, extension.id);
-      return;
-    }
-  }
-  FAIL() << "Timestamp offset extension not in header-extension list.";
+  EXPECT_THAT(
+      capabilities.header_extensions,
+      testing::Contains(RtpExtension(RtpExtension::kTimestampOffsetUri,
+                                     RtpExtension::kTimestampOffsetDefaultId)));
 }
 
 TEST_F(WebRtcVideoEngineTest, SupportsAbsoluteSenderTimeHeaderExtension) {
   RtpCapabilities capabilities = engine_.GetCapabilities();
-  ASSERT_FALSE(capabilities.header_extensions.empty());
-  for (const RtpExtension& extension : capabilities.header_extensions) {
-    if (extension.uri == RtpExtension::kAbsSendTimeUri) {
-      EXPECT_EQ(RtpExtension::kAbsSendTimeDefaultId, extension.id);
-      return;
-    }
-  }
-  FAIL() << "Absolute Sender Time extension not in header-extension list.";
+  EXPECT_THAT(
+      capabilities.header_extensions,
+      testing::Contains(RtpExtension(RtpExtension::kAbsSendTimeUri,
+                                     RtpExtension::kAbsSendTimeDefaultId)));
 }
 
 TEST_F(WebRtcVideoEngineTest, SupportsTransportSequenceNumberHeaderExtension) {
   RtpCapabilities capabilities = engine_.GetCapabilities();
-  ASSERT_FALSE(capabilities.header_extensions.empty());
-  for (const RtpExtension& extension : capabilities.header_extensions) {
-    if (extension.uri == RtpExtension::kTransportSequenceNumberUri) {
-      EXPECT_EQ(RtpExtension::kTransportSequenceNumberDefaultId, extension.id);
-      return;
-    }
-  }
-  FAIL() << "Transport sequence number extension not in header-extension list.";
+  EXPECT_THAT(capabilities.header_extensions,
+              testing::Contains(RtpExtension(
+                  RtpExtension::kTransportSequenceNumberUri,
+                  RtpExtension::kTransportSequenceNumberDefaultId)));
 }
 
 TEST_F(WebRtcVideoEngineTest, SupportsVideoRotationHeaderExtension) {
   RtpCapabilities capabilities = engine_.GetCapabilities();
-  ASSERT_FALSE(capabilities.header_extensions.empty());
-  for (const RtpExtension& extension : capabilities.header_extensions) {
-    if (extension.uri == RtpExtension::kVideoRotationUri) {
-      EXPECT_EQ(RtpExtension::kVideoRotationDefaultId, extension.id);
-      return;
-    }
-  }
-  FAIL() << "Video Rotation extension not in header-extension list.";
+  EXPECT_THAT(
+      capabilities.header_extensions,
+      testing::Contains(RtpExtension(RtpExtension::kVideoRotationUri,
+                                     RtpExtension::kVideoRotationDefaultId)));
+}
+
+TEST_F(WebRtcVideoEngineTest, SupportsColorSpaceHeaderExtension) {
+  RtpCapabilities capabilities = engine_.GetCapabilities();
+  EXPECT_THAT(
+      capabilities.header_extensions,
+      testing::Contains(RtpExtension(RtpExtension::kColorSpaceUri,
+                                     RtpExtension::kColorSpaceDefaultId)));
 }
 
 class WebRtcVideoEngineTestWithGenericDescriptor
diff --git a/modules/rtp_rtcp/source/rtp_header_extensions.cc b/modules/rtp_rtcp/source/rtp_header_extensions.cc
index 92694cd..1711092 100644
--- a/modules/rtp_rtcp/source/rtp_header_extensions.cc
+++ b/modules/rtp_rtcp/source/rtp_header_extensions.cc
@@ -272,14 +272,14 @@
 // 255 = Invalid. The whole timing frame extension should be ignored.
 //
 //    0                   1                   2                   3
-//    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2
-//   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-//   |  ID   | len=12|     flags     |     encode start ms delta       |
-//   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-//   |    encode finish ms delta     |   packetizer finish ms delta    |
-//   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-//   |     pacer exit ms delta       |   network timestamp ms delta    |
-//   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+//    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+//   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+//   |  ID   | len=12|     flags     |     encode start ms delta     |
+//   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+//   |    encode finish ms delta     |  packetizer finish ms delta   |
+//   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+//   |     pacer exit ms delta       |  network timestamp ms delta   |
+//   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 //   |  network2 timestamp ms delta  |
 //   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 
@@ -436,38 +436,39 @@
 
 // Color space including HDR metadata as an optional field.
 //
-// RTP header extension to carry HDR metadata.
-// Float values are upscaled by a static factor and transmitted as integers.
+// RTP header extension to carry color space information and optionally HDR
+// metadata. The float values in the HDR metadata struct are upscaled by a
+// static factor and transmitted as unsigned integers.
 //
-// Data layout with HDR metadata
+// Data layout of color space with HDR metadata (two-byte RTP header extension)
 //    0                   1                   2                   3
-//    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2
-//   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-//   |       ID      |   length=30    |   Primaries   |    Transfer    |
-//   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-//   |     Matrix    |      Range     |                 luminance_max  |
-//   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-//   |               |                  luminance_min                  |
-//   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-//   |              mastering_metadata.primary_r.x and .y              |
-//   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-//   |              mastering_metadata.primary_g.x and .y              |
-//   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-//   |              mastering_metadata.primary_b.x and .y              |
-//   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-//   |                mastering_metadata.white.x and .y                |
-//   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-//   |     max_content_light_level    | max_frame_average_light_level  |
-//   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+//    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+//   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+//   |       ID      |   length=30   |   Primaries   |    Transfer   |
+//   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+//   |    Matrix     |     Range     |                luminance_max  |
+//   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+//   |               |                 luminance_min                 |
+//   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+//   |             mastering_metadata.primary_r.x and .y             |
+//   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+//   |             mastering_metadata.primary_g.x and .y             |
+//   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+//   |             mastering_metadata.primary_b.x and .y             |
+//   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+//   |               mastering_metadata.white.x and .y               |
+//   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+//   |    max_content_light_level    | max_frame_average_light_level |
+//   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 //
-// Data layout without HDR metadata
+// Data layout of color space w/o HDR metadata (one-byte RTP header extension)
 //    0                   1                   2                   3
-//    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2
-//   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-//   |       ID      |    length=4    |   Primaries   |    Transfer    |
-//   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-//   |     Matrix    |      Range     |
-//   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
+//    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+//   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+//   |  ID   | L = 3 |   Primaries   |   Transfer    |    Matrix     |
+//   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+//   |     Range     |
+//   +-+-+-+-+-+-+-+-+
 
 constexpr RTPExtensionType ColorSpaceExtension::kId;
 constexpr uint8_t ColorSpaceExtension::kValueSizeBytes;
@@ -524,7 +525,7 @@
 
 bool ColorSpaceExtension::Write(rtc::ArrayView<uint8_t> data,
                                 const ColorSpace& color_space) {
-  RTC_DCHECK(data.size() >= ValueSize(color_space));
+  RTC_DCHECK_EQ(data.size(), ValueSize(color_space));
   size_t offset = 0;
   // Write color space information.
   data.data()[offset++] = static_cast<uint8_t>(color_space.primaries());
diff --git a/modules/rtp_rtcp/source/rtp_header_extensions.h b/modules/rtp_rtcp/source/rtp_header_extensions.h
index 42a6216..c1eaf8c 100644
--- a/modules/rtp_rtcp/source/rtp_header_extensions.h
+++ b/modules/rtp_rtcp/source/rtp_header_extensions.h
@@ -188,8 +188,8 @@
   static constexpr RTPExtensionType kId = kRtpExtensionColorSpace;
   static constexpr uint8_t kValueSizeBytes = 30;
   static constexpr uint8_t kValueSizeBytesWithoutHdrMetadata = 4;
-  // TODO(webrtc:8651): Change to a valid uri.
-  static constexpr const char kUri[] = "rtp-colorspace-uri-placeholder";
+  static constexpr const char kUri[] =
+      "http://www.webrtc.org/experiments/rtp-hdrext/color-space";
 
   static bool Parse(rtc::ArrayView<const uint8_t> data,
                     ColorSpace* color_space);
diff --git a/modules/rtp_rtcp/source/rtp_sender_video.cc b/modules/rtp_rtcp/source/rtp_sender_video.cc
index cb0b665..b35e598 100644
--- a/modules/rtp_rtcp/source/rtp_sender_video.cc
+++ b/modules/rtp_rtcp/source/rtp_sender_video.cc
@@ -54,9 +54,17 @@
 void AddRtpHeaderExtensions(const RTPVideoHeader& video_header,
                             FrameType frame_type,
                             bool set_video_rotation,
+                            bool set_color_space,
                             bool first_packet,
                             bool last_packet,
                             RtpPacketToSend* packet) {
+  // Color space requires two-byte header extensions if HDR metadata is
+  // included. Therefore, it's best to add this extension first so that the
+  // other extensions in the same packet are written as two-byte headers at
+  // once.
+  if (last_packet && set_color_space && video_header.color_space)
+    packet->SetExtension<ColorSpaceExtension>(video_header.color_space.value());
+
   if (last_packet && set_video_rotation)
     packet->SetExtension<VideoOrientation>(video_header.rotation);
 
@@ -118,6 +126,28 @@
   return false;
 }
 
+bool IsBaseLayer(const RTPVideoHeader& video_header) {
+  switch (video_header.codec) {
+    case kVideoCodecVP8: {
+      const auto& vp8 =
+          absl::get<RTPVideoHeaderVP8>(video_header.video_type_header);
+      return (vp8.temporalIdx == 0 || vp8.temporalIdx == kNoTemporalIdx);
+    }
+    case kVideoCodecVP9: {
+      const auto& vp9 =
+          absl::get<RTPVideoHeaderVP9>(video_header.video_type_header);
+      return (vp9.temporal_idx == 0 || vp9.temporal_idx == kNoTemporalIdx);
+    }
+    case kVideoCodecH264:
+      // TODO(kron): Implement logic for H264 once WebRTC supports temporal
+      // layers for H264.
+      break;
+    default:
+      break;
+  }
+  return true;
+}
+
 }  // namespace
 
 RTPSenderVideo::RTPSenderVideo(Clock* clock,
@@ -131,6 +161,7 @@
       retransmission_settings_(kRetransmitBaseLayer |
                                kConditionallyRetransmitHigherLayers),
       last_rotation_(kVideoRotation_0),
+      transmit_color_space_next_frame_(false),
       red_payload_type_(-1),
       ulpfec_payload_type_(-1),
       flexfec_sender_(flexfec_sender),
@@ -361,6 +392,7 @@
   bool red_enabled;
   int32_t retransmission_settings;
   bool set_video_rotation;
+  bool set_color_space = false;
   {
     rtc::CritScope cs(&crit_);
     // According to
@@ -380,6 +412,21 @@
                          video_header->rotation != kVideoRotation_0;
     last_rotation_ = video_header->rotation;
 
+    // Send color space when changed or if the frame is a key frame. Keep
+    // sending color space information until the first base layer frame to
+    // guarantee that the information is retrieved by the receiver.
+    if (video_header->color_space != last_color_space_) {
+      last_color_space_ = video_header->color_space;
+      set_color_space = true;
+      transmit_color_space_next_frame_ = !IsBaseLayer(*video_header);
+    } else {
+      set_color_space =
+          frame_type == kVideoFrameKey || transmit_color_space_next_frame_;
+      transmit_color_space_next_frame_ = transmit_color_space_next_frame_
+                                             ? !IsBaseLayer(*video_header)
+                                             : false;
+    }
+
     // FEC settings.
     const FecProtectionParams& fec_params =
         frame_type == kVideoFrameKey ? key_fec_params_ : delta_fec_params_;
@@ -410,13 +457,17 @@
   auto last_packet = absl::make_unique<RtpPacketToSend>(*single_packet);
   // Simplest way to estimate how much extensions would occupy is to set them.
   AddRtpHeaderExtensions(*video_header, frame_type, set_video_rotation,
-                         /*first=*/true, /*last=*/true, single_packet.get());
+                         set_color_space, /*first=*/true, /*last=*/true,
+                         single_packet.get());
   AddRtpHeaderExtensions(*video_header, frame_type, set_video_rotation,
-                         /*first=*/true, /*last=*/false, first_packet.get());
+                         set_color_space, /*first=*/true, /*last=*/false,
+                         first_packet.get());
   AddRtpHeaderExtensions(*video_header, frame_type, set_video_rotation,
-                         /*first=*/false, /*last=*/false, middle_packet.get());
+                         set_color_space, /*first=*/false, /*last=*/false,
+                         middle_packet.get());
   AddRtpHeaderExtensions(*video_header, frame_type, set_video_rotation,
-                         /*first=*/false, /*last=*/true, last_packet.get());
+                         set_color_space, /*first=*/false, /*last=*/true,
+                         last_packet.get());
 
   RTC_DCHECK_GT(packet_capacity, single_packet->headers_size());
   RTC_DCHECK_GT(packet_capacity, first_packet->headers_size());
diff --git a/modules/rtp_rtcp/source/rtp_sender_video.h b/modules/rtp_rtcp/source/rtp_sender_video.h
index d3a898b..30f7674 100644
--- a/modules/rtp_rtcp/source/rtp_sender_video.h
+++ b/modules/rtp_rtcp/source/rtp_sender_video.h
@@ -138,6 +138,8 @@
   enum VideoCodecType video_type_;
   int32_t retransmission_settings_ RTC_GUARDED_BY(crit_);
   VideoRotation last_rotation_ RTC_GUARDED_BY(crit_);
+  absl::optional<ColorSpace> last_color_space_ RTC_GUARDED_BY(crit_);
+  bool transmit_color_space_next_frame_ RTC_GUARDED_BY(crit_);
 
   // RED/ULPFEC.
   int red_payload_type_ RTC_GUARDED_BY(crit_);
diff --git a/modules/rtp_rtcp/source/rtp_video_header.h b/modules/rtp_rtcp/source/rtp_video_header.h
index 1c75f53..b6c43ef 100644
--- a/modules/rtp_rtcp/source/rtp_video_header.h
+++ b/modules/rtp_rtcp/source/rtp_video_header.h
@@ -15,6 +15,7 @@
 #include "absl/container/inlined_vector.h"
 #include "absl/types/optional.h"
 #include "absl/types/variant.h"
+#include "api/video/color_space.h"
 #include "api/video/video_codec_type.h"
 #include "api/video/video_content_type.h"
 #include "api/video/video_frame_marking.h"
@@ -63,6 +64,7 @@
   PlayoutDelay playout_delay = {-1, -1};
   VideoSendTiming video_timing;
   FrameMarking frame_marking;
+  absl::optional<ColorSpace> color_space;
   RTPVideoTypeHeader video_type_header;
 };
 
diff --git a/modules/video_coding/frame_object.cc b/modules/video_coding/frame_object.cc
index d27b9e1..37fcef2 100644
--- a/modules/video_coding/frame_object.cc
+++ b/modules/video_coding/frame_object.cc
@@ -72,6 +72,9 @@
   // frame (I-frame or IDR frame in H.264 (AVC), or an IRAP picture in H.265
   // (HEVC)).
   rotation_ = last_packet->video_header.rotation;
+  SetColorSpace(last_packet->video_header.color_space
+                    ? &last_packet->video_header.color_space.value()
+                    : nullptr);
   _rotation_set = true;
   content_type_ = last_packet->video_header.content_type;
   if (last_packet->video_header.video_timing.flags !=
diff --git a/video/rtp_video_stream_receiver.cc b/video/rtp_video_stream_receiver.cc
index cfc5082..f872860 100644
--- a/video/rtp_video_stream_receiver.cc
+++ b/video/rtp_video_stream_receiver.cc
@@ -505,7 +505,17 @@
       &webrtc_rtp_header.video_header().video_timing);
   packet.GetExtension<PlayoutDelayLimits>(
       &webrtc_rtp_header.video_header().playout_delay);
-
+  webrtc_rtp_header.video_header().color_space =
+      packet.GetExtension<ColorSpaceExtension>();
+  if (webrtc_rtp_header.video_header().color_space ||
+      webrtc_rtp_header.frameType == kVideoFrameKey) {
+    // Store color space since it's only transmitted when changed or for key
+    // frames. Color space will be cleared if a key frame is transmitted without
+    // color space information.
+    last_color_space_ = webrtc_rtp_header.video_header().color_space;
+  } else if (last_color_space_) {
+    webrtc_rtp_header.video_header().color_space = last_color_space_;
+  }
   absl::optional<RtpGenericFrameDescriptor> generic_descriptor_wire;
   generic_descriptor_wire.emplace();
   if (packet.GetExtension<RtpGenericFrameDescriptorExtension>(
diff --git a/video/rtp_video_stream_receiver.h b/video/rtp_video_stream_receiver.h
index ec3f354..f4d71b4 100644
--- a/video/rtp_video_stream_receiver.h
+++ b/video/rtp_video_stream_receiver.h
@@ -20,6 +20,7 @@
 #include "absl/types/optional.h"
 
 #include "api/crypto/framedecryptorinterface.h"
+#include "api/video/color_space.h"
 #include "api/video_codecs/video_codec.h"
 #include "call/rtp_packet_sink_interface.h"
 #include "call/syncable.h"
@@ -222,6 +223,7 @@
   // rtp_reference_finder if they are decryptable.
   std::unique_ptr<BufferedFrameDecryptor> buffered_frame_decryptor_
       RTC_PT_GUARDED_BY(network_tc_);
+  absl::optional<ColorSpace> last_color_space_;
 };
 
 }  // namespace webrtc