Adds scenario test for transport wide feedback based retransmission.

This ensures more end to end test coverage of the feature and captures
a wider class of regression then the existing unit test.

Bug: webrtc:9883
Change-Id: I6e74e571500c5c5d74caf8f661cac08bee8934f6
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/164461
Commit-Queue: Sebastian Jansson <srte@webrtc.org>
Reviewed-by: Erik Språng <sprang@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#30252}
diff --git a/call/BUILD.gn b/call/BUILD.gn
index 388ff06..85d9cb8 100644
--- a/call/BUILD.gn
+++ b/call/BUILD.gn
@@ -433,6 +433,7 @@
       "../test:video_test_common",
       "../test/time_controller:time_controller",
       "../video",
+      "//test/scenario:scenario",
       "//testing/gmock",
       "//testing/gtest",
       "//third_party/abseil-cpp/absl/container:inlined_vector",
diff --git a/call/rtp_video_sender_unittest.cc b/call/rtp_video_sender_unittest.cc
index 8190eea..7935fac 100644
--- a/call/rtp_video_sender_unittest.cc
+++ b/call/rtp_video_sender_unittest.cc
@@ -10,8 +10,10 @@
 
 #include "call/rtp_video_sender.h"
 
+#include <atomic>
 #include <memory>
 #include <string>
+
 #include "call/rtp_transport_controller_send.h"
 #include "modules/rtp_rtcp/include/rtp_rtcp_defines.h"
 #include "modules/rtp_rtcp/source/byte_io.h"
@@ -25,6 +27,7 @@
 #include "test/gmock.h"
 #include "test/gtest.h"
 #include "test/mock_transport.h"
+#include "test/scenario/scenario.h"
 #include "test/time_controller/simulated_time_controller.h"
 #include "video/call_stats.h"
 #include "video/send_delay_stats.h"
@@ -505,6 +508,62 @@
   ASSERT_TRUE(event.Wait(kTimeoutMs));
 }
 
+// This tests that we utilize transport wide feedback to retransmit lost
+// packets. This is tested by dropping all ordirary packets from a "lossy"
+// stream send along with an secondary untouched stream. The transport wide
+// feedback packets from the secondary stream allows the sending side to
+// detect and retreansmit the lost packets from the lossy stream.
+TEST(RtpVideoSenderTest, RetransmitsOnTransportWideLossInfo) {
+  int rtx_packets;
+  test::Scenario s(test_info_);
+  test::CallClientConfig call_conf;
+  // Keeping the bitrate fixed to avoid RTX due to probing.
+  call_conf.transport.rates.max_rate = DataRate::kbps(300);
+  call_conf.transport.rates.start_rate = DataRate::kbps(300);
+  test::NetworkSimulationConfig net_conf;
+  net_conf.bandwidth = DataRate::kbps(300);
+  auto send_node = s.CreateSimulationNode(net_conf);
+  auto* route = s.CreateRoutes(s.CreateClient("send", call_conf), {send_node},
+                               s.CreateClient("return", call_conf),
+                               {s.CreateSimulationNode(net_conf)});
+
+  test::VideoStreamConfig lossy_config;
+  lossy_config.source.framerate = 5;
+  auto* lossy = s.CreateVideoStream(route->forward(), lossy_config);
+  // The secondary stream acts a driver for transport feedback messages,
+  // ensuring that lost packets on the lossy stream are retransmitted.
+  s.CreateVideoStream(route->forward(), test::VideoStreamConfig());
+
+  send_node->router()->SetFilter([&](const EmulatedIpPacket& packet) {
+    RtpPacket rtp;
+    if (rtp.Parse(packet.data)) {
+      // Drops all regular packets for the lossy stream and counts all RTX
+      // packets. Since no packets are let trough, NACKs can't be triggered
+      // by the receiving side.
+      if (lossy->send()->UsingSsrc(rtp.Ssrc())) {
+        return false;
+      } else if (lossy->send()->UsingRtxSsrc(rtp.Ssrc())) {
+        ++rtx_packets;
+      }
+    }
+    return true;
+  });
+
+  // Run for a short duration and reset counters to avoid counting RTX packets
+  // from initial probing.
+  s.RunFor(TimeDelta::seconds(1));
+  rtx_packets = 0;
+  int decoded_baseline = lossy->receive()->GetStats().frames_decoded;
+  s.RunFor(TimeDelta::seconds(1));
+  // We expect both that RTX packets were sent and that an appropriate number of
+  // frames were received. This is somewhat redundant but reduces the risk of
+  // false positives in future regressions (e.g. RTX is send due to probing).
+  EXPECT_GE(rtx_packets, 1);
+  int frames_decoded =
+      lossy->receive()->GetStats().frames_decoded - decoded_baseline;
+  EXPECT_EQ(frames_decoded, 5);
+}
+
 // Integration test verifying that retransmissions are sent for packets which
 // can be detected as lost early, using transport wide feedback.
 TEST(RtpVideoSenderTest, EarlyRetransmits) {
diff --git a/test/network/network_emulation.cc b/test/network/network_emulation.cc
index b13c6a9..f21b0eb 100644
--- a/test/network/network_emulation.cc
+++ b/test/network/network_emulation.cc
@@ -91,6 +91,10 @@
   if (watcher_) {
     watcher_(packet);
   }
+  if (filter_) {
+    if (!filter_(packet))
+      return;
+  }
   auto receiver_it = routing_.find(packet.to.ipaddr());
   if (receiver_it == routing_.end()) {
     return;
@@ -125,6 +129,14 @@
   });
 }
 
+void NetworkRouterNode::SetFilter(
+    std::function<bool(const EmulatedIpPacket&)> filter) {
+  task_queue_->PostTask([=] {
+    RTC_DCHECK_RUN_ON(task_queue_);
+    filter_ = filter;
+  });
+}
+
 EmulatedNetworkNode::EmulatedNetworkNode(
     Clock* clock,
     rtc::TaskQueue* task_queue,
diff --git a/test/network/network_emulation.h b/test/network/network_emulation.h
index a37954e..b5e8164 100644
--- a/test/network/network_emulation.h
+++ b/test/network/network_emulation.h
@@ -73,6 +73,7 @@
                    EmulatedNetworkReceiverInterface* receiver);
   void RemoveReceiver(const rtc::IPAddress& dest_ip);
   void SetWatcher(std::function<void(const EmulatedIpPacket&)> watcher);
+  void SetFilter(std::function<bool(const EmulatedIpPacket&)> filter);
 
  private:
   rtc::TaskQueue* const task_queue_;
@@ -80,6 +81,8 @@
       RTC_GUARDED_BY(task_queue_);
   std::function<void(const EmulatedIpPacket&)> watcher_
       RTC_GUARDED_BY(task_queue_);
+  std::function<bool(const EmulatedIpPacket&)> filter_
+      RTC_GUARDED_BY(task_queue_);
 };
 
 // Represents node in the emulated network. Nodes can be connected with each
diff --git a/test/scenario/scenario.cc b/test/scenario/scenario.cc
index 29a9cea..ad382bd 100644
--- a/test/scenario/scenario.cc
+++ b/test/scenario/scenario.cc
@@ -60,6 +60,10 @@
     : Scenario(std::unique_ptr<LogWriterFactoryInterface>(),
                /*real_time=*/false) {}
 
+Scenario::Scenario(const testing::TestInfo* test_info)
+    : Scenario(std::string(test_info->test_suite_name()) + "/" +
+               test_info->name()) {}
+
 Scenario::Scenario(std::string file_name)
     : Scenario(file_name, /*real_time=*/false) {}
 
@@ -264,6 +268,10 @@
                                     });
 }
 
+void Scenario::Post(std::function<void()> function) {
+  task_queue_.PostTask(function);
+}
+
 void Scenario::At(TimeDelta offset, std::function<void()> function) {
   RTC_DCHECK_GT(offset, TimeSinceStart());
   task_queue_.PostDelayedTask(function, TimeUntilTarget(offset).ms());
diff --git a/test/scenario/scenario.h b/test/scenario/scenario.h
index b8b56d8..a4dc471 100644
--- a/test/scenario/scenario.h
+++ b/test/scenario/scenario.h
@@ -19,6 +19,7 @@
 #include "rtc_base/fake_clock.h"
 #include "rtc_base/task_queue.h"
 #include "rtc_base/task_utils/repeating_task.h"
+#include "test/gtest.h"
 #include "test/logging/log_writer.h"
 #include "test/network/network_emulation_manager.h"
 #include "test/scenario/audio_stream.h"
@@ -41,6 +42,7 @@
 class Scenario {
  public:
   Scenario();
+  explicit Scenario(const testing::TestInfo* test_info);
   explicit Scenario(std::string file_name);
   Scenario(std::string file_name, bool real_time);
   Scenario(std::unique_ptr<LogWriterFactoryInterface> log_writer_manager,
@@ -100,6 +102,10 @@
   void Every(TimeDelta interval, std::function<void(TimeDelta)> function);
   void Every(TimeDelta interval, std::function<void()> function);
 
+  // Runs the provided function on the internal task queue. This ensure that
+  // it's run on the main thread for simulated time tests.
+  void Post(std::function<void()> function);
+
   // Runs the provided function after given duration has passed. For real time
   // tests, |function| is called after |target_time_since_start| from the call
   // to Every().
diff --git a/test/scenario/video_stream.cc b/test/scenario/video_stream.cc
index 370b225..def6c20 100644
--- a/test/scenario/video_stream.cc
+++ b/test/scenario/video_stream.cc
@@ -486,6 +486,22 @@
   });
 }
 
+bool SendVideoStream::UsingSsrc(uint32_t ssrc) const {
+  for (uint32_t owned : ssrcs_) {
+    if (owned == ssrc)
+      return true;
+  }
+  return false;
+}
+
+bool SendVideoStream::UsingRtxSsrc(uint32_t ssrc) const {
+  for (uint32_t owned : rtx_ssrcs_) {
+    if (owned == ssrc)
+      return true;
+  }
+  return false;
+}
+
 void SendVideoStream::SetCaptureFramerate(int framerate) {
   sender_->SendTask([&] { video_capturer_->ChangeFramerate(framerate); });
 }
@@ -520,7 +536,8 @@
                                        VideoFrameMatcher* matcher)
     : receiver_(receiver), config_(config) {
   if (config.encoder.codec ==
-      VideoStreamConfig::Encoder::Codec::kVideoCodecGeneric) {
+          VideoStreamConfig::Encoder::Codec::kVideoCodecGeneric ||
+      config.encoder.implementation == VideoStreamConfig::Encoder::kFake) {
     decoder_factory_ = std::make_unique<FunctionVideoDecoderFactory>(
         []() { return std::make_unique<FakeDecoder>(); });
   } else {
diff --git a/test/scenario/video_stream.h b/test/scenario/video_stream.h
index ef98679..f0b99db 100644
--- a/test/scenario/video_stream.h
+++ b/test/scenario/video_stream.h
@@ -40,6 +40,8 @@
   void Stop();
   void UpdateConfig(std::function<void(VideoStreamConfig*)> modifier);
   void UpdateActiveLayers(std::vector<bool> active_layers);
+  bool UsingSsrc(uint32_t ssrc) const;
+  bool UsingRtxSsrc(uint32_t ssrc) const;
 
  private:
   friend class Scenario;