Add integration test of PeerConnectionInterface::ReconfigureBandwidthEstimation

Test that BWE proving can be started without sending audio or video.

Bug: webrtc:14928
Change-Id: Ie55cb2de774f0c3b497b2636e7a6f5eac58d36a0
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/337322
Commit-Queue: Per Kjellander <perkj@webrtc.org>
Reviewed-by: Per Kjellander <perkj@webrtc.org>
Reviewed-by: Mirko Bonadei <mbonadei@webrtc.org>
Reviewed-by: Henrik Boström <hbos@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#41703}
diff --git a/test/peer_scenario/peer_scenario_client.cc b/test/peer_scenario/peer_scenario_client.cc
index 1397b32..3ba4fdb 100644
--- a/test/peer_scenario/peer_scenario_client.cc
+++ b/test/peer_scenario/peer_scenario_client.cc
@@ -370,10 +370,13 @@
 
 void PeerScenarioClient::SetSdpOfferAndGetAnswer(
     std::string remote_offer,
+    std::function<void()> remote_description_set,
     std::function<void(std::string)> answer_handler) {
   if (!signaling_thread_->IsCurrent()) {
-    signaling_thread_->PostTask(
-        [=] { SetSdpOfferAndGetAnswer(remote_offer, answer_handler); });
+    signaling_thread_->PostTask([=] {
+      SetSdpOfferAndGetAnswer(remote_offer, remote_description_set,
+                              answer_handler);
+    });
     return;
   }
   RTC_DCHECK_RUN_ON(signaling_thread_);
@@ -381,6 +384,11 @@
       CreateSessionDescription(SdpType::kOffer, remote_offer),
       rtc::make_ref_counted<LambdaSetRemoteDescriptionObserver>([=](RTCError) {
         RTC_DCHECK_RUN_ON(signaling_thread_);
+        if (remote_description_set) {
+          // Allow the caller to modify transceivers
+          // before creating the answer.
+          remote_description_set();
+        }
         peer_connection_->CreateAnswer(
             rtc::make_ref_counted<LambdaCreateSessionDescriptionObserver>(
                 [=](std::unique_ptr<SessionDescriptionInterface> answer) {
diff --git a/test/peer_scenario/peer_scenario_client.h b/test/peer_scenario/peer_scenario_client.h
index e863757..cb025e9 100644
--- a/test/peer_scenario/peer_scenario_client.h
+++ b/test/peer_scenario/peer_scenario_client.h
@@ -147,6 +147,7 @@
       std::function<void(SessionDescriptionInterface*)> munge_offer,
       std::function<void(std::string)> offer_handler);
   void SetSdpOfferAndGetAnswer(std::string remote_offer,
+                               std::function<void()> remote_description_set,
                                std::function<void(std::string)> answer_handler);
   void SetSdpAnswer(
       std::string remote_answer,
diff --git a/test/peer_scenario/signaling_route.cc b/test/peer_scenario/signaling_route.cc
index eeec7c8..8688c1a 100644
--- a/test/peer_scenario/signaling_route.cc
+++ b/test/peer_scenario/signaling_route.cc
@@ -59,6 +59,7 @@
     CrossTrafficRoute* ret_route,
     std::function<void(SessionDescriptionInterface* offer)> munge_offer,
     std::function<void(SessionDescriptionInterface*)> modify_offer,
+    std::function<void()> callee_remote_description_set,
     std::function<void(const SessionDescriptionInterface&)> exchange_finished) {
   caller->CreateAndSetSdp(munge_offer, [=](std::string sdp_offer) {
     if (modify_offer) {
@@ -67,11 +68,14 @@
       RTC_CHECK(offer->ToString(&sdp_offer));
     }
     send_route->NetworkDelayedAction(kSdpPacketSize, [=] {
-      callee->SetSdpOfferAndGetAnswer(sdp_offer, [=](std::string answer) {
-        ret_route->NetworkDelayedAction(kSdpPacketSize, [=] {
-          caller->SetSdpAnswer(std::move(answer), std::move(exchange_finished));
-        });
-      });
+      callee->SetSdpOfferAndGetAnswer(
+          sdp_offer, std::move(callee_remote_description_set),
+          [=](std::string answer) {
+            ret_route->NetworkDelayedAction(kSdpPacketSize, [=] {
+              caller->SetSdpAnswer(std::move(answer),
+                                   std::move(exchange_finished));
+            });
+          });
     });
   });
 }
@@ -92,22 +96,39 @@
 }
 
 void SignalingRoute::NegotiateSdp(
+    std::function<void(SessionDescriptionInterface* offer)> munge_offer,
+    std::function<void(SessionDescriptionInterface* offer)> modify_offer,
+    std::function<void()> callee_remote_description_set,
+    std::function<void(const SessionDescriptionInterface& answer)>
+        exchange_finished) {
+  StartSdpNegotiation(caller_, callee_, send_route_, ret_route_, munge_offer,
+                      modify_offer, callee_remote_description_set,
+                      exchange_finished);
+}
+
+void SignalingRoute::NegotiateSdp(
     std::function<void(SessionDescriptionInterface*)> munge_offer,
     std::function<void(SessionDescriptionInterface*)> modify_offer,
     std::function<void(const SessionDescriptionInterface&)> exchange_finished) {
-  StartSdpNegotiation(caller_, callee_, send_route_, ret_route_, munge_offer,
-                      modify_offer, exchange_finished);
+  NegotiateSdp(munge_offer, modify_offer, {}, exchange_finished);
 }
 
 void SignalingRoute::NegotiateSdp(
     std::function<void(SessionDescriptionInterface*)> modify_offer,
     std::function<void(const SessionDescriptionInterface&)> exchange_finished) {
-  NegotiateSdp({}, modify_offer, exchange_finished);
+  NegotiateSdp({}, modify_offer, {}, exchange_finished);
+}
+
+void SignalingRoute::NegotiateSdp(
+    std::function<void()> remote_description_set,
+    std::function<void(const SessionDescriptionInterface& answer)>
+        exchange_finished) {
+  NegotiateSdp({}, {}, remote_description_set, exchange_finished);
 }
 
 void SignalingRoute::NegotiateSdp(
     std::function<void(const SessionDescriptionInterface&)> exchange_finished) {
-  NegotiateSdp({}, {}, exchange_finished);
+  NegotiateSdp({}, {}, {}, exchange_finished);
 }
 
 }  // namespace test
diff --git a/test/peer_scenario/signaling_route.h b/test/peer_scenario/signaling_route.h
index a95ae5c..9b317d2 100644
--- a/test/peer_scenario/signaling_route.h
+++ b/test/peer_scenario/signaling_route.h
@@ -35,12 +35,21 @@
   // The `munge_offer` callback is used to modify an offer between its creation
   // and set local description. This behavior is forbidden according to the spec
   // but available here in order to allow test coverage on corner cases.
-  // The `exchange_finished` callback is called with the answer produced after
-  // SDP negotations has completed.
+  // `callee_remote_description_set` is invoked when callee has applied the
+  // offer but not yet created an answer. The purpose is to allow tests to
+  // modify transceivers created from the offer.  The `exchange_finished`
+  // callback is called with the answer produced after SDP negotations has
+  // completed.
   // TODO(srte): Handle lossy links.
   void NegotiateSdp(
       std::function<void(SessionDescriptionInterface* offer)> munge_offer,
       std::function<void(SessionDescriptionInterface* offer)> modify_offer,
+      std::function<void()> callee_remote_description_set,
+      std::function<void(const SessionDescriptionInterface& answer)>
+          exchange_finished);
+  void NegotiateSdp(
+      std::function<void(SessionDescriptionInterface* offer)> munge_offer,
+      std::function<void(SessionDescriptionInterface* offer)> modify_offer,
       std::function<void(const SessionDescriptionInterface& answer)>
           exchange_finished);
   void NegotiateSdp(
@@ -48,6 +57,10 @@
       std::function<void(const SessionDescriptionInterface& answer)>
           exchange_finished);
   void NegotiateSdp(
+      std::function<void()> remote_description_set,
+      std::function<void(const SessionDescriptionInterface& answer)>
+          exchange_finished);
+  void NegotiateSdp(
       std::function<void(const SessionDescriptionInterface& answer)>
           exchange_finished);
   SignalingRoute reverse() {
diff --git a/test/peer_scenario/tests/bwe_ramp_up_test.cc b/test/peer_scenario/tests/bwe_ramp_up_test.cc
index a7a17bb..f8eaa47 100644
--- a/test/peer_scenario/tests/bwe_ramp_up_test.cc
+++ b/test/peer_scenario/tests/bwe_ramp_up_test.cc
@@ -25,6 +25,9 @@
 namespace test {
 
 using ::testing::SizeIs;
+using ::testing::Test;
+using ::testing::ValuesIn;
+using ::testing::WithParamInterface;
 
 rtc::scoped_refptr<const RTCStatsReport> GetStatsAndProcess(
     PeerScenario& s,
@@ -124,5 +127,152 @@
   // ensure BWE has increased beyond noise levels.
   EXPECT_GT(final_bwe, initial_bwe + DataRate::KilobitsPerSec(345));
 }
+
+struct InitialProbeTestParams {
+  DataRate network_capacity;
+  DataRate expected_bwe_min;
+};
+class BweRampupWithInitialProbeTest
+    : public Test,
+      public WithParamInterface<InitialProbeTestParams> {};
+
+INSTANTIATE_TEST_SUITE_P(
+    BweRampupWithInitialProbeTest,
+    BweRampupWithInitialProbeTest,
+    ValuesIn<InitialProbeTestParams>(
+        {{
+             .network_capacity = DataRate::KilobitsPerSec(3000),
+             .expected_bwe_min = DataRate::KilobitsPerSec(2500),
+         },
+         {
+             .network_capacity = webrtc::DataRate::KilobitsPerSec(500),
+             .expected_bwe_min = webrtc::DataRate::KilobitsPerSec(400),
+         }}));
+
+// Test that caller and callee BWE rampup even if no media packets are sent.
+// - BandWidthEstimationSettings.allow_probe_without_media must be set.
+// - A Video RtpTransceiver with RTX support needs to be negotiated.
+TEST_P(BweRampupWithInitialProbeTest, BweRampUpBothDirectionsWithoutMedia) {
+  PeerScenario s(*::testing::UnitTest::GetInstance()->current_test_info());
+  InitialProbeTestParams test_params = GetParam();
+
+  PeerScenarioClient* caller = s.CreateClient({});
+  PeerScenarioClient* callee = s.CreateClient({});
+
+  auto video_result = caller->pc()->AddTransceiver(cricket::MEDIA_TYPE_VIDEO);
+  ASSERT_EQ(video_result.error().type(), RTCErrorType::NONE);
+
+  caller->pc()->ReconfigureBandwidthEstimation(
+      {.allow_probe_without_media = true});
+  callee->pc()->ReconfigureBandwidthEstimation(
+      {.allow_probe_without_media = true});
+
+  auto node_builder =
+      s.net()->NodeBuilder().capacity_kbps(test_params.network_capacity.kbps());
+  auto caller_node = node_builder.Build().node;
+  auto callee_node = node_builder.Build().node;
+  s.net()->CreateRoute(caller->endpoint(), {caller_node}, callee->endpoint());
+  s.net()->CreateRoute(callee->endpoint(), {callee_node}, caller->endpoint());
+
+  auto signaling =
+      s.ConnectSignaling(caller, callee, {caller_node}, {callee_node});
+  signaling.StartIceSignaling();
+
+  std::atomic<bool> offer_exchange_done(false);
+  signaling.NegotiateSdp(
+      [&]() {
+        // When remote description has been set, a transceiver is created.
+        // Set the diretion to sendrecv so that it can be used for BWE probing
+        // from callee -> caller.
+        ASSERT_THAT(callee->pc()->GetTransceivers(), SizeIs(1));
+        ASSERT_TRUE(
+            callee->pc()
+                ->GetTransceivers()[0]
+                ->SetDirectionWithError(RtpTransceiverDirection::kSendRecv)
+                .ok());
+      },
+      [&](const SessionDescriptionInterface& answer) {
+        offer_exchange_done = true;
+      });
+  // Wait for SDP negotiation.
+  s.WaitAndProcess(&offer_exchange_done);
+
+  // Test that 1s after offer/answer exchange finish, we have a BWE estimate,
+  // even though no video frames have been sent.
+  s.ProcessMessages(TimeDelta::Seconds(1));
+
+  auto callee_inbound_stats =
+      GetStatsAndProcess(s, callee)->GetStatsOfType<RTCInboundRtpStreamStats>();
+  ASSERT_THAT(callee_inbound_stats, SizeIs(1));
+  ASSERT_EQ(*callee_inbound_stats[0]->frames_received, 0u);
+  auto caller_inbound_stats =
+      GetStatsAndProcess(s, caller)->GetStatsOfType<RTCInboundRtpStreamStats>();
+  ASSERT_THAT(caller_inbound_stats, SizeIs(1));
+  ASSERT_EQ(*caller_inbound_stats[0]->frames_received, 0u);
+
+  DataRate caller_bwe = GetAvailableSendBitrate(GetStatsAndProcess(s, caller));
+  EXPECT_GT(caller_bwe.kbps(), test_params.expected_bwe_min.kbps());
+  EXPECT_LE(caller_bwe.kbps(), test_params.network_capacity.kbps());
+  DataRate callee_bwe = GetAvailableSendBitrate(GetStatsAndProcess(s, callee));
+  EXPECT_GT(callee_bwe.kbps(), test_params.expected_bwe_min.kbps());
+  EXPECT_LE(callee_bwe.kbps(), test_params.network_capacity.kbps());
+}
+
+// Test that we can reconfigure bandwidth estimation and send new BWE probes.
+// In this test, camera is stopped, and some times later, the app want to get a
+// new BWE estimate.
+TEST(BweRampupTest, CanReconfigureBweAfterStopingVideo) {
+  PeerScenario s(*::testing::UnitTest::GetInstance()->current_test_info());
+  PeerScenarioClient* caller = s.CreateClient({});
+  PeerScenarioClient* callee = s.CreateClient({});
+
+  auto node_builder = s.net()->NodeBuilder().capacity_kbps(1000);
+  auto caller_node = node_builder.Build().node;
+  auto callee_node = node_builder.Build().node;
+  s.net()->CreateRoute(caller->endpoint(), {caller_node}, callee->endpoint());
+  s.net()->CreateRoute(callee->endpoint(), {callee_node}, caller->endpoint());
+
+  PeerScenarioClient::VideoSendTrack track = caller->CreateVideo("VIDEO", {});
+
+  auto signaling =
+      s.ConnectSignaling(caller, callee, {caller_node}, {callee_node});
+
+  signaling.StartIceSignaling();
+
+  std::atomic<bool> offer_exchange_done(false);
+  signaling.NegotiateSdp([&](const SessionDescriptionInterface& answer) {
+    offer_exchange_done = true;
+  });
+  // Wait for SDP negotiation.
+  s.WaitAndProcess(&offer_exchange_done);
+
+  // Send a TCP messages to the receiver using the same downlink node.
+  // This is done just to force a lower BWE than the link capacity.
+  webrtc::TcpMessageRoute* tcp_route = s.net()->CreateTcpRoute(
+      s.net()->CreateRoute({caller_node}), s.net()->CreateRoute({callee_node}));
+  DataRate bwe_before_restart = DataRate::Zero();
+
+  std::atomic<bool> message_delivered(false);
+  tcp_route->SendMessage(
+      /*size=*/5'00'000,
+      /*on_received=*/[&]() { message_delivered = true; });
+  s.WaitAndProcess(&message_delivered);
+  bwe_before_restart = GetAvailableSendBitrate(GetStatsAndProcess(s, caller));
+
+  // Camera is stopped.
+  track.capturer->Stop();
+  s.ProcessMessages(TimeDelta::Seconds(2));
+
+  // Some time later, the app is interested in restarting BWE since we may want
+  // to resume video eventually.
+  caller->pc()->ReconfigureBandwidthEstimation(
+      {.allow_probe_without_media = true});
+  s.ProcessMessages(TimeDelta::Seconds(1));
+  DataRate bwe_after_restart =
+      GetAvailableSendBitrate(GetStatsAndProcess(s, caller));
+  EXPECT_GT(bwe_after_restart.kbps(), bwe_before_restart.kbps() + 300);
+  EXPECT_LT(bwe_after_restart.kbps(), 1000);
+}
+
 }  // namespace test
 }  // namespace webrtc