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