diff --git a/video/rtp_video_stream_receiver.cc b/video/rtp_video_stream_receiver.cc index 1ae5c5e93d..e1dd736be6 100644 --- a/video/rtp_video_stream_receiver.cc +++ b/video/rtp_video_stream_receiver.cc @@ -237,6 +237,8 @@ RtpVideoStreamReceiver::RtpVideoStreamReceiver( process_thread_(process_thread), ntp_estimator_(clock), rtp_header_extensions_(config_.rtp.extensions), + forced_playout_delay_max_ms_("max_ms", absl::nullopt), + forced_playout_delay_min_ms_("min_ms", absl::nullopt), rtp_receive_statistics_(rtp_receive_statistics), ulpfec_receiver_(UlpfecReceiver::Create(config->rtp.remote_ssrc, this, @@ -290,6 +292,10 @@ RtpVideoStreamReceiver::RtpVideoStreamReceiver( if (config_.rtp.rtcp_xr.receiver_reference_time_report) rtp_rtcp_->SetRtcpXrRrtrStatus(true); + ParseFieldTrial( + {&forced_playout_delay_max_ms_, &forced_playout_delay_min_ms_}, + field_trial::FindFullName("WebRTC-ForcePlayoutDelay")); + process_thread_->RegisterModule(rtp_rtcp_.get(), RTC_FROM_HERE); if (config_.rtp.lntf.enabled) { @@ -513,7 +519,12 @@ void RtpVideoStreamReceiver::OnReceivedPayloadData( rtp_packet.GetExtension( &video_header.content_type); rtp_packet.GetExtension(&video_header.video_timing); - rtp_packet.GetExtension(&video_header.playout_delay); + if (forced_playout_delay_max_ms_ && forced_playout_delay_min_ms_) { + video_header.playout_delay.max_ms = *forced_playout_delay_max_ms_; + video_header.playout_delay.min_ms = *forced_playout_delay_min_ms_; + } else { + rtp_packet.GetExtension(&video_header.playout_delay); + } rtp_packet.GetExtension(&video_header.frame_marking); ParseGenericDependenciesResult generic_descriptor_state = diff --git a/video/rtp_video_stream_receiver.h b/video/rtp_video_stream_receiver.h index 3e07df926c..0289f23a07 100644 --- a/video/rtp_video_stream_receiver.h +++ b/video/rtp_video_stream_receiver.h @@ -43,6 +43,7 @@ #include "modules/video_coding/unique_timestamp_counter.h" #include "rtc_base/constructor_magic.h" #include "rtc_base/critical_section.h" +#include "rtc_base/experiments/field_trial_parser.h" #include "rtc_base/numerics/sequence_number_util.h" #include "rtc_base/synchronization/sequence_checker.h" #include "rtc_base/thread_annotations.h" @@ -299,6 +300,10 @@ class RtpVideoStreamReceiver : public LossNotificationSender, RemoteNtpTimeEstimator ntp_estimator_; RtpHeaderExtensionMap rtp_header_extensions_; + // Set by the field trial WebRTC-ForcePlayoutDelay to override any playout + // delay that is specified in the received packets. + FieldTrialOptional forced_playout_delay_max_ms_; + FieldTrialOptional forced_playout_delay_min_ms_; ReceiveStatistics* const rtp_receive_statistics_; std::unique_ptr ulpfec_receiver_; diff --git a/video/rtp_video_stream_receiver_unittest.cc b/video/rtp_video_stream_receiver_unittest.cc index 40602f7754..40d63ae77f 100644 --- a/video/rtp_video_stream_receiver_unittest.cc +++ b/video/rtp_video_stream_receiver_unittest.cc @@ -61,6 +61,15 @@ std::vector GetAbsoluteCaptureTimestamps( return result; } +RTPVideoHeader GetGenericVideoHeader(VideoFrameType frame_type) { + RTPVideoHeader video_header; + video_header.is_first_packet_in_frame = true; + video_header.is_last_packet_in_frame = true; + video_header.codec = kVideoCodecGeneric; + video_header.frame_type = frame_type; + return video_header; +} + class MockTransport : public Transport { public: MOCK_METHOD3(SendRtp, @@ -358,14 +367,11 @@ TEST_F(RtpVideoStreamReceiverTest, CacheColorSpaceFromLastPacketOfKeyframe) { TEST_F(RtpVideoStreamReceiverTest, GenericKeyFrame) { RtpPacketReceived rtp_packet; - RTPVideoHeader video_header; rtc::CopyOnWriteBuffer data({1, 2, 3, 4}); rtp_packet.SetPayloadType(kPayloadType); rtp_packet.SetSequenceNumber(1); - video_header.is_first_packet_in_frame = true; - video_header.is_last_packet_in_frame = true; - video_header.codec = kVideoCodecGeneric; - video_header.frame_type = VideoFrameType::kVideoFrameKey; + RTPVideoHeader video_header = + GetGenericVideoHeader(VideoFrameType::kVideoFrameKey); mock_on_complete_frame_callback_.AppendExpectedBitstream(data.data(), data.size()); EXPECT_CALL(mock_on_complete_frame_callback_, DoOnCompleteFrame(_)); @@ -381,7 +387,6 @@ TEST_F(RtpVideoStreamReceiverTest, PacketInfoIsPropagatedIntoVideoFrames) { extension_map.Register(kId0); RtpPacketReceived rtp_packet(&extension_map); rtp_packet.SetPayloadType(kPayloadType); - RTPVideoHeader video_header; rtc::CopyOnWriteBuffer data({1, 2, 3, 4}); rtp_packet.SetSequenceNumber(1); rtp_packet.SetTimestamp(1); @@ -390,10 +395,8 @@ TEST_F(RtpVideoStreamReceiverTest, PacketInfoIsPropagatedIntoVideoFrames) { AbsoluteCaptureTime{kAbsoluteCaptureTimestamp, /*estimated_capture_clock_offset=*/absl::nullopt}); - video_header.is_first_packet_in_frame = true; - video_header.is_last_packet_in_frame = true; - video_header.codec = kVideoCodecGeneric; - video_header.frame_type = VideoFrameType::kVideoFrameKey; + RTPVideoHeader video_header = + GetGenericVideoHeader(VideoFrameType::kVideoFrameKey); mock_on_complete_frame_callback_.AppendExpectedBitstream(data.data(), data.size()); EXPECT_CALL(mock_on_complete_frame_callback_, DoOnCompleteFrame(_)) @@ -416,7 +419,6 @@ TEST_F(RtpVideoStreamReceiverTest, RtpPacketReceived rtp_packet(&extension_map); rtp_packet.SetPayloadType(kPayloadType); - RTPVideoHeader video_header; rtc::CopyOnWriteBuffer data({1, 2, 3, 4}); uint16_t sequence_number = 1; uint32_t rtp_timestamp = 1; @@ -427,10 +429,8 @@ TEST_F(RtpVideoStreamReceiverTest, AbsoluteCaptureTime{kAbsoluteCaptureTimestamp, /*estimated_capture_clock_offset=*/absl::nullopt}); - video_header.is_first_packet_in_frame = true; - video_header.is_last_packet_in_frame = true; - video_header.codec = kVideoCodecGeneric; - video_header.frame_type = VideoFrameType::kVideoFrameKey; + RTPVideoHeader video_header = + GetGenericVideoHeader(VideoFrameType::kVideoFrameKey); mock_on_complete_frame_callback_.AppendExpectedBitstream(data.data(), data.size()); EXPECT_CALL(mock_on_complete_frame_callback_, DoOnCompleteFrame(_)); @@ -496,13 +496,10 @@ TEST_F(RtpVideoStreamReceiverTest, TEST_F(RtpVideoStreamReceiverTest, GenericKeyFrameBitstreamError) { RtpPacketReceived rtp_packet; rtp_packet.SetPayloadType(kPayloadType); - RTPVideoHeader video_header; rtc::CopyOnWriteBuffer data({1, 2, 3, 4}); rtp_packet.SetSequenceNumber(1); - video_header.is_first_packet_in_frame = true; - video_header.is_last_packet_in_frame = true; - video_header.codec = kVideoCodecGeneric; - video_header.frame_type = VideoFrameType::kVideoFrameKey; + RTPVideoHeader video_header = + GetGenericVideoHeader(VideoFrameType::kVideoFrameKey); constexpr uint8_t expected_bitsteam[] = {1, 2, 3, 0xff}; mock_on_complete_frame_callback_.AppendExpectedBitstream( expected_bitsteam, sizeof(expected_bitsteam)); @@ -658,13 +655,10 @@ TEST_F(RtpVideoStreamReceiverTest, PaddingInMediaStream) { TEST_F(RtpVideoStreamReceiverTest, RequestKeyframeIfFirstFrameIsDelta) { RtpPacketReceived rtp_packet; rtp_packet.SetPayloadType(kPayloadType); - RTPVideoHeader video_header; rtc::CopyOnWriteBuffer data({1, 2, 3, 4}); rtp_packet.SetSequenceNumber(1); - video_header.is_first_packet_in_frame = true; - video_header.is_last_packet_in_frame = true; - video_header.codec = kVideoCodecGeneric; - video_header.frame_type = VideoFrameType::kVideoFrameDelta; + RTPVideoHeader video_header = + GetGenericVideoHeader(VideoFrameType::kVideoFrameDelta); EXPECT_CALL(mock_key_frame_request_sender_, RequestKeyFrame()); rtp_video_stream_receiver_->OnReceivedPayloadData(data, rtp_packet, video_header); @@ -675,13 +669,11 @@ TEST_F(RtpVideoStreamReceiverTest, RequestKeyframeWhenPacketBufferGetsFull) { RtpPacketReceived rtp_packet; rtp_packet.SetPayloadType(kPayloadType); - RTPVideoHeader video_header; rtc::CopyOnWriteBuffer data({1, 2, 3, 4}); - video_header.is_first_packet_in_frame = true; + RTPVideoHeader video_header = + GetGenericVideoHeader(VideoFrameType::kVideoFrameDelta); // Incomplete frames so that the packet buffer is filling up. video_header.is_last_packet_in_frame = false; - video_header.codec = kVideoCodecGeneric; - video_header.frame_type = VideoFrameType::kVideoFrameDelta; uint16_t start_sequence_number = 1234; rtp_packet.SetSequenceNumber(start_sequence_number); while (rtp_packet.SequenceNumber() - start_sequence_number < @@ -1149,13 +1141,10 @@ TEST_F(RtpVideoStreamReceiverTest, TransformFrame) { RtpPacketReceived rtp_packet; rtp_packet.SetPayloadType(kPayloadType); - RTPVideoHeader video_header; rtc::CopyOnWriteBuffer data({1, 2, 3, 4}); rtp_packet.SetSequenceNumber(1); - video_header.is_first_packet_in_frame = true; - video_header.is_last_packet_in_frame = true; - video_header.codec = kVideoCodecGeneric; - video_header.frame_type = VideoFrameType::kVideoFrameKey; + RTPVideoHeader video_header = + GetGenericVideoHeader(VideoFrameType::kVideoFrameKey); mock_on_complete_frame_callback_.AppendExpectedBitstream(data.data(), data.size()); EXPECT_CALL(*mock_frame_transformer, @@ -1168,4 +1157,61 @@ TEST_F(RtpVideoStreamReceiverTest, TransformFrame) { receiver = nullptr; } +// Test default behavior and when playout delay is overridden by field trial. +const PlayoutDelay kTransmittedPlayoutDelay = {100, 200}; +const PlayoutDelay kForcedPlayoutDelay = {70, 90}; +struct PlayoutDelayOptions { + std::string field_trial; + PlayoutDelay expected_delay; +}; +const PlayoutDelayOptions kDefaultBehavior = { + /*field_trial=*/"", /*expected_delay=*/kTransmittedPlayoutDelay}; +const PlayoutDelayOptions kOverridePlayoutDelay = { + /*field_trial=*/"WebRTC-ForcePlayoutDelay/min_ms:70,max_ms:90/", + /*expected_delay=*/kForcedPlayoutDelay}; + +class RtpVideoStreamReceiverTestPlayoutDelay + : public RtpVideoStreamReceiverTest, + public ::testing::WithParamInterface { + protected: + RtpVideoStreamReceiverTestPlayoutDelay() + : RtpVideoStreamReceiverTest(GetParam().field_trial) {} +}; + +INSTANTIATE_TEST_SUITE_P(PlayoutDelay, + RtpVideoStreamReceiverTestPlayoutDelay, + Values(kDefaultBehavior, kOverridePlayoutDelay)); + +TEST_P(RtpVideoStreamReceiverTestPlayoutDelay, PlayoutDelay) { + rtc::CopyOnWriteBuffer payload_data({1, 2, 3, 4}); + RtpHeaderExtensionMap extension_map; + extension_map.Register(1); + RtpPacketToSend packet_to_send(&extension_map); + packet_to_send.SetPayloadType(kPayloadType); + packet_to_send.SetSequenceNumber(1); + + // Set playout delay on outgoing packet. + EXPECT_TRUE(packet_to_send.SetExtension( + kTransmittedPlayoutDelay)); + uint8_t* payload = packet_to_send.AllocatePayload(payload_data.size()); + memcpy(payload, payload_data.data(), payload_data.size()); + + RtpPacketReceived received_packet(&extension_map); + received_packet.Parse(packet_to_send.data(), packet_to_send.size()); + + RTPVideoHeader video_header = + GetGenericVideoHeader(VideoFrameType::kVideoFrameKey); + mock_on_complete_frame_callback_.AppendExpectedBitstream(payload_data.data(), + payload_data.size()); + // Expect the playout delay of encoded frame to be the same as the transmitted + // playout delay unless it was overridden by a field trial. + EXPECT_CALL(mock_on_complete_frame_callback_, DoOnCompleteFrame(_)) + .WillOnce(Invoke([expected_playout_delay = GetParam().expected_delay]( + video_coding::EncodedFrame* frame) { + EXPECT_EQ(frame->EncodedImage().playout_delay_, expected_playout_delay); + })); + rtp_video_stream_receiver_->OnReceivedPayloadData( + received_packet.PayloadBuffer(), received_packet, video_header); +} + } // namespace webrtc