Perform packetization verification until a match is found

If there happens to be an asymmetry between local and remote codecs we
shouldn't validate that there's a 1:1 packetization mapping for every
codec. It's sufficient to check that there's at least one matching
packetization per codec.

Bug: webrtc:15473
Change-Id: Ib4fc8fdd54bb4dccf96f0c802746c848e2deed83
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/320440
Commit-Queue: Stefan Holmer <stefan@webrtc.org>
Reviewed-by: Stefan Holmer <stefan@webrtc.org>
Auto-Submit: Emil Lundmark <lndmrk@webrtc.org>
Reviewed-by: Sergey Sukhanov <sergeysu@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#40760}
diff --git a/pc/BUILD.gn b/pc/BUILD.gn
index e8d1e86..9176cf9 100644
--- a/pc/BUILD.gn
+++ b/pc/BUILD.gn
@@ -105,6 +105,7 @@
     "../rtc_base/third_party/sigslot",
   ]
   absl_deps = [
+    "//third_party/abseil-cpp/absl/algorithm:container",
     "//third_party/abseil-cpp/absl/strings",
     "//third_party/abseil-cpp/absl/types:optional",
   ]
diff --git a/pc/channel.cc b/pc/channel.cc
index 82ca1a3..c763b6b 100644
--- a/pc/channel.cc
+++ b/pc/channel.cc
@@ -16,6 +16,7 @@
 #include <type_traits>
 #include <utility>
 
+#include "absl/algorithm/container.h"
 #include "absl/strings/string_view.h"
 #include "api/rtp_parameters.h"
 #include "api/sequence_checker.h"
@@ -1036,19 +1037,34 @@
 
   bool needs_send_params_update = false;
   if (type == SdpType::kAnswer || type == SdpType::kPrAnswer) {
-    for (auto& send_codec : send_params.codecs) {
-      auto* recv_codec = FindMatchingCodec(recv_params.codecs, send_codec);
-      if (recv_codec) {
-        if (!recv_codec->packetization && send_codec.packetization) {
-          send_codec.packetization.reset();
-          needs_send_params_update = true;
-        } else if (recv_codec->packetization != send_codec.packetization) {
-          error_desc = StringFormat(
-              "Failed to set local answer due to invalid codec packetization "
-              "specified in m-section with mid='%s'.",
-              mid().c_str());
-          return false;
-        }
+    webrtc::flat_set<const VideoCodec*> matched_codecs;
+    for (VideoCodec& send_codec : send_params.codecs) {
+      if (absl::c_any_of(matched_codecs, [&](const VideoCodec* c) {
+            return send_codec.Matches(*c);
+          })) {
+        continue;
+      }
+
+      const VideoCodec* recv_codec =
+          FindMatchingCodec(recv_params.codecs, send_codec);
+      if (recv_codec == nullptr) {
+        continue;
+      }
+
+      if (!recv_codec->packetization.has_value() &&
+          send_codec.packetization.has_value()) {
+        send_codec.packetization = absl::nullopt;
+        needs_send_params_update = true;
+      } else if (recv_codec->packetization != send_codec.packetization) {
+        error_desc = StringFormat(
+            "Failed to set local answer due to incompatible codec "
+            "packetization for pt='%d' specified in m-section with mid='%s'.",
+            send_codec.id, mid().c_str());
+        return false;
+      }
+
+      if (recv_codec->packetization == send_codec.packetization) {
+        matched_codecs.insert(&send_codec);
       }
     }
   }
@@ -1121,19 +1137,34 @@
 
   bool needs_recv_params_update = false;
   if (type == SdpType::kAnswer || type == SdpType::kPrAnswer) {
-    for (auto& recv_codec : recv_params.codecs) {
-      auto* send_codec = FindMatchingCodec(send_params.codecs, recv_codec);
-      if (send_codec) {
-        if (!send_codec->packetization && recv_codec.packetization) {
-          recv_codec.packetization.reset();
-          needs_recv_params_update = true;
-        } else if (send_codec->packetization != recv_codec.packetization) {
-          error_desc = StringFormat(
-              "Failed to set remote answer due to invalid codec packetization "
-              "specifid in m-section with mid='%s'.",
-              mid().c_str());
-          return false;
-        }
+    webrtc::flat_set<const VideoCodec*> matched_codecs;
+    for (VideoCodec& recv_codec : recv_params.codecs) {
+      if (absl::c_any_of(matched_codecs, [&](const VideoCodec* c) {
+            return recv_codec.Matches(*c);
+          })) {
+        continue;
+      }
+
+      const VideoCodec* send_codec =
+          FindMatchingCodec(send_params.codecs, recv_codec);
+      if (send_codec == nullptr) {
+        continue;
+      }
+
+      if (!send_codec->packetization.has_value() &&
+          recv_codec.packetization.has_value()) {
+        recv_codec.packetization = absl::nullopt;
+        needs_recv_params_update = true;
+      } else if (send_codec->packetization != recv_codec.packetization) {
+        error_desc = StringFormat(
+            "Failed to set remote answer due to incompatible codec "
+            "packetization for pt='%d' specified in m-section with mid='%s'.",
+            recv_codec.id, mid().c_str());
+        return false;
+      }
+
+      if (send_codec->packetization == recv_codec.packetization) {
+        matched_codecs.insert(&recv_codec);
       }
     }
   }
diff --git a/pc/channel_unittest.cc b/pc/channel_unittest.cc
index 0d7f0b0..c86f2cc 100644
--- a/pc/channel_unittest.cc
+++ b/pc/channel_unittest.cc
@@ -48,16 +48,20 @@
 #include "test/gtest.h"
 #include "test/scoped_key_value_config.h"
 
-using cricket::DtlsTransportInternal;
-using cricket::FakeVoiceMediaReceiveChannel;
-using cricket::FakeVoiceMediaSendChannel;
-using cricket::RidDescription;
-using cricket::RidDirection;
-using cricket::StreamParams;
-using webrtc::RtpTransceiverDirection;
-using webrtc::SdpType;
-
 namespace {
+
+using ::cricket::DtlsTransportInternal;
+using ::cricket::FakeVoiceMediaReceiveChannel;
+using ::cricket::FakeVoiceMediaSendChannel;
+using ::cricket::RidDescription;
+using ::cricket::RidDirection;
+using ::cricket::StreamParams;
+using ::testing::AllOf;
+using ::testing::ElementsAre;
+using ::testing::Field;
+using ::webrtc::RtpTransceiverDirection;
+using ::webrtc::SdpType;
+
 const cricket::AudioCodec kPcmuCodec =
     cricket::CreateAudioCodec(0, "PCMU", 64000, 1);
 const cricket::AudioCodec kPcmaCodec =
@@ -75,7 +79,6 @@
 const int kVideoPts[] = {97, 99};
 enum class NetworkIsWorker { Yes, No };
 
-}  // namespace
 
 template <class ChannelT,
           class MediaSendChannelT,
@@ -2274,6 +2277,78 @@
             absl::nullopt);
 }
 
+TEST_F(VideoChannelSingleThreadTest,
+       StopsPacketizationVerificationWhenMatchIsFoundInRemoteAnswer) {
+  cricket::VideoCodec vp8_foo = cricket::CreateVideoCodec(96, "VP8");
+  vp8_foo.packetization = "foo";
+  cricket::VideoCodec vp8_bar = cricket::CreateVideoCodec(97, "VP8");
+  vp8_bar.packetization = "bar";
+  cricket::VideoCodec vp9 = cricket::CreateVideoCodec(98, "VP9");
+  cricket::VideoCodec vp9_foo = cricket::CreateVideoCodec(99, "VP9");
+  vp9_foo.packetization = "bar";
+  cricket::VideoContentDescription local;
+  local.set_codecs({vp8_foo, vp8_bar, vp9_foo});
+  cricket::VideoContentDescription remote;
+  remote.set_codecs({vp8_foo, vp9});
+
+  CreateChannels(0, 0);
+  std::string err;
+  ASSERT_TRUE(channel1_->SetLocalContent(&local, SdpType::kOffer, err)) << err;
+  ASSERT_TRUE(channel1_->SetRemoteContent(&remote, SdpType::kAnswer, err))
+      << err;
+
+  EXPECT_THAT(
+      media_receive_channel1_impl()->recv_codecs(),
+      ElementsAre(AllOf(Field(&cricket::Codec::id, 96),
+                        Field(&cricket::Codec::packetization, "foo")),
+                  AllOf(Field(&cricket::Codec::id, 97),
+                        Field(&cricket::Codec::packetization, "bar")),
+                  AllOf(Field(&cricket::Codec::id, 99),
+                        Field(&cricket::Codec::packetization, absl::nullopt))));
+  EXPECT_THAT(
+      media_send_channel1_impl()->send_codecs(),
+      ElementsAre(AllOf(Field(&cricket::Codec::id, 96),
+                        Field(&cricket::Codec::packetization, "foo")),
+                  AllOf(Field(&cricket::Codec::id, 98),
+                        Field(&cricket::Codec::packetization, absl::nullopt))));
+}
+
+TEST_F(VideoChannelSingleThreadTest,
+       StopsPacketizationVerificationWhenMatchIsFoundInLocalAnswer) {
+  cricket::VideoCodec vp8_foo = cricket::CreateVideoCodec(96, "VP8");
+  vp8_foo.packetization = "foo";
+  cricket::VideoCodec vp8_bar = cricket::CreateVideoCodec(97, "VP8");
+  vp8_bar.packetization = "bar";
+  cricket::VideoCodec vp9 = cricket::CreateVideoCodec(98, "VP9");
+  cricket::VideoCodec vp9_foo = cricket::CreateVideoCodec(99, "VP9");
+  vp9_foo.packetization = "bar";
+  cricket::VideoContentDescription local;
+  local.set_codecs({vp8_foo, vp9});
+  cricket::VideoContentDescription remote;
+  remote.set_codecs({vp8_foo, vp8_bar, vp9_foo});
+
+  CreateChannels(0, 0);
+  std::string err;
+  ASSERT_TRUE(channel1_->SetRemoteContent(&remote, SdpType::kOffer, err))
+      << err;
+  ASSERT_TRUE(channel1_->SetLocalContent(&local, SdpType::kAnswer, err)) << err;
+
+  EXPECT_THAT(
+      media_receive_channel1_impl()->recv_codecs(),
+      ElementsAre(AllOf(Field(&cricket::Codec::id, 96),
+                        Field(&cricket::Codec::packetization, "foo")),
+                  AllOf(Field(&cricket::Codec::id, 98),
+                        Field(&cricket::Codec::packetization, absl::nullopt))));
+  EXPECT_THAT(
+      media_send_channel1_impl()->send_codecs(),
+      ElementsAre(AllOf(Field(&cricket::Codec::id, 96),
+                        Field(&cricket::Codec::packetization, "foo")),
+                  AllOf(Field(&cricket::Codec::id, 97),
+                        Field(&cricket::Codec::packetization, "bar")),
+                  AllOf(Field(&cricket::Codec::id, 99),
+                        Field(&cricket::Codec::packetization, absl::nullopt))));
+}
+
 // VideoChannelDoubleThreadTest
 TEST_F(VideoChannelDoubleThreadTest, TestInit) {
   Base::TestInit();
@@ -2409,4 +2484,4 @@
   Base::SocketOptionsMergedOnSetTransport();
 }
 
-// TODO(pthatcher): TestSetReceiver?
+}  // namespace