Add field trial to force playout delay

This CL adds the field trial WebRTC-ForcePlayoutDelay with parameters
min_ms and max_ms. If both of these values are set, the playout delay
of any received packet will be overridden by the specified values.

Bug: None
Change-Id: I353282097e3ffa437dfc5affdfdf7780b09474e7
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/174180
Reviewed-by: Ilya Nikolaevskiy <ilnik@webrtc.org>
Commit-Queue: Johannes Kron <kron@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#31149}
diff --git a/video/rtp_video_stream_receiver.cc b/video/rtp_video_stream_receiver.cc
index 1ae5c5e..e1dd736 100644
--- a/video/rtp_video_stream_receiver.cc
+++ b/video/rtp_video_stream_receiver.cc
@@ -237,6 +237,8 @@
       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 @@
   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 @@
   rtp_packet.GetExtension<VideoContentTypeExtension>(
       &video_header.content_type);
   rtp_packet.GetExtension<VideoTimingExtension>(&video_header.video_timing);
-  rtp_packet.GetExtension<PlayoutDelayLimits>(&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<PlayoutDelayLimits>(&video_header.playout_delay);
+  }
   rtp_packet.GetExtension<FrameMarkingExtension>(&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 3e07df9..0289f23 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 @@
   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<int> forced_playout_delay_max_ms_;
+  FieldTrialOptional<int> forced_playout_delay_min_ms_;
   ReceiveStatistics* const rtp_receive_statistics_;
   std::unique_ptr<UlpfecReceiver> ulpfec_receiver_;
 
diff --git a/video/rtp_video_stream_receiver_unittest.cc b/video/rtp_video_stream_receiver_unittest.cc
index 40602f7..40d63ae 100644
--- a/video/rtp_video_stream_receiver_unittest.cc
+++ b/video/rtp_video_stream_receiver_unittest.cc
@@ -61,6 +61,15 @@
   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, 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 @@
   extension_map.Register<AbsoluteCaptureTimeExtension>(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 @@
       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 @@
   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 @@
       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, 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, 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 @@
 
   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 @@
 
   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 @@
   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<PlayoutDelayOptions> {
+ 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<PlayoutDelayLimits>(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<PlayoutDelayLimits>(
+      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