Fix codec collision on reoffer after munged codec on offer.

Bug: chromium:395077842
Change-Id: I7665e593fa0f6883150363cb75103facd62f4fea
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/377141
Reviewed-by: Henrik Boström <hbos@webrtc.org>
Commit-Queue: Harald Alvestrand <hta@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#43889}
diff --git a/pc/codec_vendor.cc b/pc/codec_vendor.cc
index 0ab676a..529246c 100644
--- a/pc/codec_vendor.cc
+++ b/pc/codec_vendor.cc
@@ -454,6 +454,14 @@
         }
       }
     }
+    // Note what PTs are already in use.
+    UsedPayloadTypes
+        used_pltypes;  // Used to avoid pt collisions in filtered_codecs
+    for (auto& codec : filtered_codecs) {
+      // Note: This may change PTs. Doing so woud indicate an error, but
+      // UsedPayloadTypes doesn't offer a means to make the distinction.
+      used_pltypes.FindAndSetIdUsed(&codec);
+    }
     // Add other supported codecs.
     for (const Codec& codec : supported_codecs) {
       std::optional<Codec> found_codec =
@@ -461,12 +469,12 @@
       if (found_codec &&
           !FindMatchingCodec(supported_codecs, filtered_codecs, codec)) {
         // Use the `found_codec` from `codecs` because it has the
-        // correctly mapped payload type.
-        // This is only done for video since we do not yet have rtx for audio.
+        // correctly mapped payload type (most of the time).
         if (media_description_options.type == MEDIA_TYPE_VIDEO &&
             found_codec->GetResiliencyType() == Codec::ResiliencyType::kRtx) {
           // For RTX we might need to adjust the apt parameter if we got a
           // remote offer without RTX for a codec for which we support RTX.
+          // This is only done for video since we do not yet have rtx for audio.
           auto referenced_codec =
               GetAssociatedCodecForRtx(supported_codecs, codec);
           RTC_DCHECK(referenced_codec);
@@ -479,6 +487,8 @@
                                   changed_referenced_codec->id);
           }
         }
+        // Quick fix for b/395077842: Remap the codec if it collides.
+        used_pltypes.FindAndSetIdUsed(&(*found_codec));
         filtered_codecs.push_back(*found_codec);
       }
     }
diff --git a/pc/peer_connection_integrationtest.cc b/pc/peer_connection_integrationtest.cc
index 20643c2..2e7786d 100644
--- a/pc/peer_connection_integrationtest.cc
+++ b/pc/peer_connection_integrationtest.cc
@@ -4731,6 +4731,37 @@
   EXPECT_LT(metrics::MinSample("WebRTC.Call.AbsCapture.Offset"), 2);
 }
 
+// Test that when SDP is munged to use a PT for a different codec,
+// the old codec is added to a subsequent offer with a different PT
+// Regression test for https://issues.chromium.org/395077824
+TEST_F(PeerConnectionIntegrationTestUnifiedPlan,
+       MungeOfferCodecAndReOfferWorks) {
+  ASSERT_TRUE(CreatePeerConnectionWrappers());
+  ConnectFakeSignaling();
+  caller()->AddVideoTrack();
+  auto munger = [](std::unique_ptr<SessionDescriptionInterface>& sdp) {
+    auto video = GetFirstVideoContentDescription(sdp->description());
+    auto codecs = video->codecs();
+    for (auto&& codec : codecs) {
+      if (codec.name == "VP9") {
+        RTC_LOG(LS_ERROR) << "Remapping VP9 codec " << codec << " to AV1";
+        codec.name = "AV1";
+      }
+    }
+    video->set_codecs(codecs);
+  };
+  caller()->SetGeneratedSdpMunger(munger);
+  caller()->CreateAndSetAndSignalOffer();
+  ASSERT_THAT(
+      WaitUntil([&] { return SignalingStateStable(); }, ::testing::IsTrue()),
+      IsRtcOk());
+  caller()->SetGeneratedSdpMunger(nullptr);
+  auto offer = caller()->CreateOfferAndWait();
+  ASSERT_NE(nullptr, offer);
+  // The offer should be acceptable.
+  EXPECT_TRUE(caller()->SetLocalDescriptionAndSendSdpMessage(std::move(offer)));
+}
+
 }  // namespace
 
 }  // namespace webrtc
diff --git a/pc/test/integration_test_helpers.h b/pc/test/integration_test_helpers.h
index 0b7c8b5..9aa7288 100644
--- a/pc/test/integration_test_helpers.h
+++ b/pc/test/integration_test_helpers.h
@@ -746,6 +746,30 @@
     });
   }
 
+  // Setting the local description and sending the SDP message over the fake
+  // signaling channel are combined into the same method because the SDP
+  // message needs to be sent as soon as SetLocalDescription finishes, without
+  // waiting for the observer to be called. This ensures that ICE candidates
+  // don't outrace the description.
+  bool SetLocalDescriptionAndSendSdpMessage(
+      std::unique_ptr<SessionDescriptionInterface> desc) {
+    auto observer = rtc::make_ref_counted<MockSetSessionDescriptionObserver>();
+    RTC_LOG(LS_INFO) << debug_name_ << ": SetLocalDescriptionAndSendSdpMessage";
+    SdpType type = desc->GetType();
+    std::string sdp;
+    EXPECT_TRUE(desc->ToString(&sdp));
+    RTC_LOG(LS_INFO) << debug_name_ << ": local SDP contents=\n" << sdp;
+    pc()->SetLocalDescription(observer.get(), desc.release());
+    RemoveUnusedVideoRenderers();
+    // As mentioned above, we need to send the message immediately after
+    // SetLocalDescription.
+    SendSdpMessage(type, sdp);
+    EXPECT_THAT(
+        WaitUntil([&] { return observer->called(); }, ::testing::IsTrue()),
+        IsRtcOk());
+    return true;
+  }
+
  private:
   // Constructor used by friend class PeerConnectionIntegrationBaseTest.
   explicit PeerConnectionIntegrationWrapper(const std::string& debug_name)
@@ -868,29 +892,6 @@
     return description;
   }
 
-  // Setting the local description and sending the SDP message over the fake
-  // signaling channel are combined into the same method because the SDP
-  // message needs to be sent as soon as SetLocalDescription finishes, without
-  // waiting for the observer to be called. This ensures that ICE candidates
-  // don't outrace the description.
-  bool SetLocalDescriptionAndSendSdpMessage(
-      std::unique_ptr<SessionDescriptionInterface> desc) {
-    auto observer = rtc::make_ref_counted<MockSetSessionDescriptionObserver>();
-    RTC_LOG(LS_INFO) << debug_name_ << ": SetLocalDescriptionAndSendSdpMessage";
-    SdpType type = desc->GetType();
-    std::string sdp;
-    EXPECT_TRUE(desc->ToString(&sdp));
-    RTC_LOG(LS_INFO) << debug_name_ << ": local SDP contents=\n" << sdp;
-    pc()->SetLocalDescription(observer.get(), desc.release());
-    RemoveUnusedVideoRenderers();
-    // As mentioned above, we need to send the message immediately after
-    // SetLocalDescription.
-    SendSdpMessage(type, sdp);
-    EXPECT_THAT(
-        WaitUntil([&] { return observer->called(); }, ::testing::IsTrue()),
-        IsRtcOk());
-    return true;
-  }
 
   // This is a work around to remove unused fake_video_renderers from
   // transceivers that have either stopped or are no longer receiving.