Integrate RTP Header extension API with SDP munging

in order to not regress existing use-cases while following rules
described by the specification. This change now makes the existing
regression test pass after the spec-compliant modifications.

BUG=chromium:1051821

Change-Id: Ia384adf9a172ed88b5ec6a3cc5c478764a686cb9
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/299002
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Reviewed-by: Henrik Boström <hbos@webrtc.org>
Commit-Queue: Philipp Hancke <phancke@microsoft.com>
Cr-Commit-Position: refs/heads/main@{#39726}
diff --git a/pc/media_session.cc b/pc/media_session.cc
index e703b44..8aff4d9 100644
--- a/pc/media_session.cc
+++ b/pc/media_session.cc
@@ -685,7 +685,9 @@
         // TODO(crbug.com/1051821): Configure the extension direction from
         // the information in the media_description_options extension
         // capability.
-        extensions.push_back(extension_with_id);
+        if (extension.direction != RtpTransceiverDirection::kStopped) {
+          extensions.push_back(extension_with_id);
+        }
       }
     }
   }
diff --git a/pc/media_session_unittest.cc b/pc/media_session_unittest.cc
index 8f38f6a..6ba5032 100644
--- a/pc/media_session_unittest.cc
+++ b/pc/media_session_unittest.cc
@@ -2018,7 +2018,7 @@
 }
 
 TEST_F(MediaSessionDescriptionFactoryTest,
-       AppendsStoppedExtensionIfKnownAndPresentInTheOffer) {
+       AllowsStoppedExtensionsToBeRemovedFromSubsequentOffer) {
   MediaSessionOptions opts;
   AddMediaDescriptionOptions(MEDIA_TYPE_VIDEO, "video",
                              RtpTransceiverDirection::kSendRecv, kActive,
@@ -2026,12 +2026,12 @@
   opts.media_description_options.back().header_extensions = {
       webrtc::RtpHeaderExtensionCapability("uri1", 1,
                                            RtpTransceiverDirection::kSendRecv),
-      webrtc::RtpHeaderExtensionCapability("uri2", 1,
+      webrtc::RtpHeaderExtensionCapability("uri2", 2,
                                            RtpTransceiverDirection::kSendRecv)};
   auto offer = f1_.CreateOffer(opts, nullptr);
 
-  // Now add "uri2" as stopped to the options verify that the offer contains
-  // uri2 since it's already present since before.
+  // Check that a subsequent offer after setting "uri2" to stopped no longer
+  // contains the extension.
   opts.media_description_options.back().header_extensions = {
       webrtc::RtpHeaderExtensionCapability("uri1", 1,
                                            RtpTransceiverDirection::kSendRecv),
@@ -2043,8 +2043,7 @@
       ElementsAre(Property(
           &ContentInfo::media_description,
           Pointee(Property(&MediaContentDescription::rtp_header_extensions,
-                           ElementsAre(Field(&RtpExtension::uri, "uri1"),
-                                       Field(&RtpExtension::uri, "uri2")))))));
+                           ElementsAre(Field(&RtpExtension::uri, "uri1")))))));
 }
 
 TEST_F(MediaSessionDescriptionFactoryTest,
diff --git a/pc/peer_connection_header_extension_unittest.cc b/pc/peer_connection_header_extension_unittest.cc
index 42c58fd..405b7e0 100644
--- a/pc/peer_connection_header_extension_unittest.cc
+++ b/pc/peer_connection_header_extension_unittest.cc
@@ -321,11 +321,11 @@
   }
 }
 
-// This test is a regression test for behavior that the API
+// These tests are regression tests for behavior that the API
 // enables in a proper way. It conflicts with the behavior
 // of the API to only offer non-stopped extensions.
 TEST_P(PeerConnectionHeaderExtensionTest,
-       SdpMungingWithoutApiUsageEnablesExtensions) {
+       SdpMungingAnswerWithoutApiUsageEnablesExtensions) {
   cricket::MediaType media_type;
   SdpSemantics semantics;
   std::tie(media_type, semantics) = GetParam();
@@ -371,7 +371,6 @@
   ASSERT_TRUE(pc->SetLocalDescription(std::move(modified_answer)));
 
   auto session_description = pc->CreateOffer();
-  ASSERT_TRUE(session_description->ToString(&sdp));
   EXPECT_THAT(session_description->description()
                   ->contents()[0]
                   .media_description()
@@ -382,6 +381,37 @@
                           Field(&RtpExtension::uri, "uri4")));
 }
 
+TEST_P(PeerConnectionHeaderExtensionTest,
+       SdpMungingOfferWithoutApiUsageEnablesExtensions) {
+  cricket::MediaType media_type;
+  SdpSemantics semantics;
+  std::tie(media_type, semantics) = GetParam();
+  if (semantics != SdpSemantics::kUnifiedPlan)
+    return;
+  std::unique_ptr<PeerConnectionWrapper> pc =
+      CreatePeerConnection(media_type, semantics);
+  pc->AddTransceiver(media_type);
+
+  auto offer =
+      pc->CreateOffer(PeerConnectionInterface::RTCOfferAnswerOptions());
+  std::string modified_sdp;
+  ASSERT_TRUE(offer->ToString(&modified_sdp));
+  modified_sdp += "a=extmap:1 uri1\r\n";
+  auto modified_offer = CreateSessionDescription(SdpType::kOffer, modified_sdp);
+  ASSERT_TRUE(pc->SetLocalDescription(std::move(modified_offer)));
+
+  auto offer2 =
+      pc->CreateOffer(PeerConnectionInterface::RTCOfferAnswerOptions());
+  EXPECT_THAT(offer2->description()
+                  ->contents()[0]
+                  .media_description()
+                  ->rtp_header_extensions(),
+              ElementsAre(Field(&RtpExtension::uri, "uri2"),
+                          Field(&RtpExtension::uri, "uri3"),
+                          Field(&RtpExtension::uri, "uri4"),
+                          Field(&RtpExtension::uri, "uri1")));
+}
+
 TEST_P(PeerConnectionHeaderExtensionTest, EnablingExtensionsAfterRemoteOffer) {
   cricket::MediaType media_type;
   SdpSemantics semantics;
diff --git a/pc/sdp_offer_answer.cc b/pc/sdp_offer_answer.cc
index 9c0e7eb..c382d61 100644
--- a/pc/sdp_offer_answer.cc
+++ b/pc/sdp_offer_answer.cc
@@ -815,8 +815,37 @@
 
 }  // namespace
 
+void UpdateRtpHeaderExtensionPreferencesFromSdpMunging(
+    const cricket::SessionDescription* description,
+    TransceiverList* transceivers) {
+  // This integrates the RTP Header Extension Control API and local SDP munging
+  // for backward compability reasons. If something was enabled in the local
+  // description via SDP munging, consider it non-stopped in the API as well
+  // so that is shows up in subsequent offers/answers.
+  RTC_DCHECK(description);
+  RTC_DCHECK(transceivers);
+  for (const auto& content : description->contents()) {
+    auto transceiver = transceivers->FindByMid(content.name);
+    if (!transceiver) {
+      continue;
+    }
+    auto extension_capabilities = transceiver->GetHeaderExtensionsToNegotiate();
+    // Set the capability of every extension we see here to "sendrecv".
+    for (auto& ext : content.media_description()->rtp_header_extensions()) {
+      auto it = absl::c_find_if(extension_capabilities,
+                                [&ext](const RtpHeaderExtensionCapability c) {
+                                  return ext.uri == c.uri;
+                                });
+      if (it != extension_capabilities.end()) {
+        it->direction = RtpTransceiverDirection::kSendRecv;
+      }
+    }
+    transceiver->SetHeaderExtensionsToNegotiate(extension_capabilities);
+  }
+}
+
 // This class stores state related to a SetRemoteDescription operation, captures
-// and reports potential errors that migth occur and makes sure to notify the
+// and reports potential errors that might occur and makes sure to notify the
 // observer of the operation and the operations chain of completion.
 class SdpOfferAnswerHandler::RemoteDescriptionOperation {
  public:
@@ -1816,6 +1845,11 @@
     local_ice_credentials_to_replace_->ClearIceCredentials();
   }
 
+  if (IsUnifiedPlan()) {
+    UpdateRtpHeaderExtensionPreferencesFromSdpMunging(
+        local_description()->description(), transceivers());
+  }
+
   return RTCError::OK();
 }