diff --git a/api/rtpparameters.cc b/api/rtpparameters.cc index 063d106a00..2b20c56d22 100644 --- a/api/rtpparameters.cc +++ b/api/rtpparameters.cc @@ -137,6 +137,10 @@ const char RtpExtension::kGenericFrameDescriptorUri[] = "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 @@ bool RtpExtension::IsSupportedForVideo(const std::string& uri) { 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 badda07459..47df22e7ff 100644 --- a/api/rtpparameters.h +++ b/api/rtpparameters.h @@ -309,6 +309,10 @@ struct RtpExtension { // 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 ff022fe42d..58a04eb1e0 100644 --- a/api/video/color_space.h +++ b/api/video/color_space.h @@ -121,12 +121,13 @@ class ColorSpace { 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 542e8005c7..95c64b4124 100644 --- a/call/rtp_payload_params.cc +++ b/call/rtp_payload_params.cc @@ -156,7 +156,9 @@ RTPVideoHeader RtpPayloadParams::GetRtpVideoHeader( 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 44cbcd5b64..5343cf974b 100644 --- a/call/rtp_payload_params_unittest.cc +++ b/call/rtp_payload_params_unittest.cc @@ -106,6 +106,7 @@ TEST(RtpPayloadParamsTest, InfoMappedToRtpVideoHeader_Vp9) { 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(header.video_type_header); EXPECT_EQ(kPictureId + 1, vp9_header.picture_id); @@ -122,11 +123,16 @@ TEST(RtpPayloadParamsTest, InfoMappedToRtpVideoHeader_Vp9) { 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 a75db73697..b96f2ea3d5 100644 --- a/media/engine/webrtcvideoengine.cc +++ b/media/engine/webrtcvideoengine.cc @@ -504,6 +504,9 @@ RtpCapabilities WebRtcVideoEngine::GetCapabilities() const { 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 543fa3a71b..4ba3c489dc 100644 --- a/media/engine/webrtcvideoengine_unittest.cc +++ b/media/engine/webrtcvideoengine_unittest.cc @@ -277,50 +277,42 @@ TEST_F(WebRtcVideoEngineTest, DefaultRtxCodecHasAssociatedPayloadTypeSet) { 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 92694cd5a3..171109237b 100644 --- a/modules/rtp_rtcp/source/rtp_header_extensions.cc +++ b/modules/rtp_rtcp/source/rtp_header_extensions.cc @@ -272,14 +272,14 @@ bool VideoContentTypeExtension::Write(rtc::ArrayView data, // 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 @@ bool FrameMarkingExtension::Write(rtc::ArrayView data, // 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::Parse(rtc::ArrayView data, bool ColorSpaceExtension::Write(rtc::ArrayView 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(color_space.primaries()); diff --git a/modules/rtp_rtcp/source/rtp_header_extensions.h b/modules/rtp_rtcp/source/rtp_header_extensions.h index 42a6216c7b..c1eaf8c92e 100644 --- a/modules/rtp_rtcp/source/rtp_header_extensions.h +++ b/modules/rtp_rtcp/source/rtp_header_extensions.h @@ -188,8 +188,8 @@ class ColorSpaceExtension { 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 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 cb0b665608..b35e598403 100644 --- a/modules/rtp_rtcp/source/rtp_sender_video.cc +++ b/modules/rtp_rtcp/source/rtp_sender_video.cc @@ -54,9 +54,17 @@ void BuildRedPayload(const RtpPacketToSend& media_packet, 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(video_header.color_space.value()); + if (last_packet && set_video_rotation) packet->SetExtension(video_header.rotation); @@ -118,6 +126,28 @@ bool MinimizeDescriptor(const RTPVideoHeader& full, RTPVideoHeader* minimized) { return false; } +bool IsBaseLayer(const RTPVideoHeader& video_header) { + switch (video_header.codec) { + case kVideoCodecVP8: { + const auto& vp8 = + absl::get(video_header.video_type_header); + return (vp8.temporalIdx == 0 || vp8.temporalIdx == kNoTemporalIdx); + } + case kVideoCodecVP9: { + const auto& vp9 = + absl::get(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 @@ RTPSenderVideo::RTPSenderVideo(Clock* clock, 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 RTPSenderVideo::SendVideo(enum VideoCodecType video_type, 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 @@ bool RTPSenderVideo::SendVideo(enum VideoCodecType video_type, 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 @@ bool RTPSenderVideo::SendVideo(enum VideoCodecType video_type, auto last_packet = absl::make_unique(*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 d3a898b40a..30f7674fe1 100644 --- a/modules/rtp_rtcp/source/rtp_sender_video.h +++ b/modules/rtp_rtcp/source/rtp_sender_video.h @@ -138,6 +138,8 @@ class RTPSenderVideo { enum VideoCodecType video_type_; int32_t retransmission_settings_ RTC_GUARDED_BY(crit_); VideoRotation last_rotation_ RTC_GUARDED_BY(crit_); + absl::optional 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 1c75f539a1..b6c43ef111 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 @@ struct RTPVideoHeader { PlayoutDelay playout_delay = {-1, -1}; VideoSendTiming video_timing; FrameMarking frame_marking; + absl::optional color_space; RTPVideoTypeHeader video_type_header; }; diff --git a/modules/video_coding/frame_object.cc b/modules/video_coding/frame_object.cc index d27b9e1aaa..37fcef2a46 100644 --- a/modules/video_coding/frame_object.cc +++ b/modules/video_coding/frame_object.cc @@ -72,6 +72,9 @@ RtpFrameObject::RtpFrameObject(PacketBuffer* packet_buffer, // 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 cfc508241b..f872860255 100644 --- a/video/rtp_video_stream_receiver.cc +++ b/video/rtp_video_stream_receiver.cc @@ -505,7 +505,17 @@ void RtpVideoStreamReceiver::ReceivePacket(const RtpPacketReceived& packet) { &webrtc_rtp_header.video_header().video_timing); packet.GetExtension( &webrtc_rtp_header.video_header().playout_delay); - + webrtc_rtp_header.video_header().color_space = + packet.GetExtension(); + 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 generic_descriptor_wire; generic_descriptor_wire.emplace(); if (packet.GetExtension( diff --git a/video/rtp_video_stream_receiver.h b/video/rtp_video_stream_receiver.h index ec3f354e26..f4d71b4aa2 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 @@ class RtpVideoStreamReceiver : public RecoveredPacketReceiver, // rtp_reference_finder if they are decryptable. std::unique_ptr buffered_frame_decryptor_ RTC_PT_GUARDED_BY(network_tc_); + absl::optional last_color_space_; }; } // namespace webrtc