Enable test that there are no duplicates in a CodecList

This also required handling a case where an Android decoder factory
caused duplicate codec IDs to be generated. The video engine now
guards against this; the Android factory is not touched.

Bug: webrtc:42224689
Change-Id: Id448acb26914564b0ebb618f2ef12ee74941c793
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/379421
Commit-Queue: Tomas Gunnarsson <tommi@webrtc.org>
Reviewed-by: Tomas Gunnarsson <tommi@webrtc.org>
Auto-Submit: Harald Alvestrand <hta@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#44129}
diff --git a/api/video_codecs/sdp_video_format.h b/api/video_codecs/sdp_video_format.h
index 16b4fae..3fbcf0c 100644
--- a/api/video_codecs/sdp_video_format.h
+++ b/api/video_codecs/sdp_video_format.h
@@ -81,6 +81,11 @@
   static const SdpVideoFormat VP9Profile3();
   static const SdpVideoFormat AV1Profile0();
   static const SdpVideoFormat AV1Profile1();
+
+  template <typename Sink>
+  friend void AbslStringify(Sink& sink, const SdpVideoFormat& format) {
+    sink.Append(format.ToString());
+  }
 };
 
 // For not so good reasons sometimes additional parameters are added to an
diff --git a/media/BUILD.gn b/media/BUILD.gn
index 5933d9e..ab9316c 100644
--- a/media/BUILD.gn
+++ b/media/BUILD.gn
@@ -876,6 +876,7 @@
       "../rtc_base:copy_on_write_buffer",
       "../rtc_base:dscp",
       "../rtc_base:gunit_helpers",
+      "../rtc_base:logging",
       "../rtc_base:macromagic",
       "../rtc_base:network_route",
       "../rtc_base:rtc_event",
diff --git a/media/base/codec_list.cc b/media/base/codec_list.cc
index 9fa1b3c..8efc94c 100644
--- a/media/base/codec_list.cc
+++ b/media/base/codec_list.cc
@@ -36,18 +36,11 @@
   for (size_t i = 0; i < codecs.size(); i++) {
     const Codec& codec = codecs[i];
     if (codec.id != Codec::kIdNotSet) {
-      // Not true - the test PeerConnectionMediaTest.RedFmtpPayloadMixed
-      // fails this check. In that case, the duplicates are identical.
-      // TODO: https://issues.webrtc.org/384756621 - fix test and enable check.
-      // RTC_DCHECK(pt_to_index.count(codec.id) == 0);
-      if (pt_to_index.count(codec.id) != 0) {
-        RTC_LOG(LS_WARNING) << "Surprising condition: Two codecs on same PT. "
-                            << "First: " << codecs[pt_to_index[codec.id]]
-                            << " Second: " << codec;
-        // Skip this codec in the map, and go on.
-        continue;
+      bool inserted = pt_to_index.insert({codec.id, i}).second;
+      if (!inserted) {
+        LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_PARAMETER,
+                             "Duplicate payload type in codec list");
       }
-      pt_to_index.insert({codec.id, i});
     }
   }
   for (const Codec& codec : codecs) {
diff --git a/media/engine/fake_webrtc_video_engine.cc b/media/engine/fake_webrtc_video_engine.cc
index 18320f9..0fa87f5 100644
--- a/media/engine/fake_webrtc_video_engine.cc
+++ b/media/engine/fake_webrtc_video_engine.cc
@@ -19,6 +19,7 @@
 #include "media/base/media_constants.h"
 #include "media/engine/simulcast_encoder_adapter.h"
 #include "modules/video_coding/include/video_error_codes.h"
+#include "rtc_base/logging.h"
 #include "rtc_base/time_utils.h"
 
 namespace cricket {
@@ -89,9 +90,13 @@
   std::vector<webrtc::SdpVideoFormat> formats;
 
   for (const webrtc::SdpVideoFormat& format : supported_codec_formats_) {
-    // Don't add same codec twice.
-    if (!format.IsCodecInList(formats))
-      formats.push_back(format);
+    // We need to test erroneous scenarios, so just warn if there's
+    // a duplicate.
+    if (format.IsCodecInList(formats)) {
+      RTC_LOG(LS_WARNING) << "GetSupportedFormats found a duplicate format: "
+                          << format << ", check that this is expected.";
+    }
+    formats.push_back(format);
   }
 
   return formats;
diff --git a/media/engine/webrtc_video_engine.cc b/media/engine/webrtc_video_engine.cc
index d2e69f1..60b2634 100644
--- a/media/engine/webrtc_video_engine.cc
+++ b/media/engine/webrtc_video_engine.cc
@@ -21,6 +21,7 @@
 #include <optional>
 #include <set>
 #include <string>
+#include <unordered_set>
 #include <utility>
 #include <vector>
 
@@ -242,8 +243,8 @@
   auto supported_formats =
       GetDefaultSupportedFormats(factory, is_decoder_factory, trials);
 
-  // Temporary: Use PayloadTypePicker for assignments.
   webrtc::PayloadTypePicker pt_mapper;
+  std::unordered_set<int> used_payload_types;
   std::vector<Codec> output_codecs;
   for (const auto& supported_format : supported_formats) {
     webrtc::RTCErrorOr<Codec> result =
@@ -253,6 +254,13 @@
       // TODO: https://issues.webrtc.org/360058654 - Handle running out of IDs.
       continue;
     }
+    bool inserted = used_payload_types.insert(result.value().id).second;
+    if (!inserted) {
+      RTC_LOG(LS_WARNING) << "Factory produced duplicate codecs, ignoring "
+                          << result.value() << " produced from "
+                          << supported_format;
+      continue;
+    }
     output_codecs.push_back(result.value());
     if (include_rtx) {
       Codec::ResiliencyType resiliency_type =
diff --git a/media/engine/webrtc_video_engine_unittest.cc b/media/engine/webrtc_video_engine_unittest.cc
index d1657c4..23e85ab 100644
--- a/media/engine/webrtc_video_engine_unittest.cc
+++ b/media/engine/webrtc_video_engine_unittest.cc
@@ -21,6 +21,7 @@
 #include <set>
 #include <string>
 #include <tuple>
+#include <unordered_set>
 #include <utility>
 #include <vector>
 
@@ -429,6 +430,8 @@
   void AddSupportedVideoCodecType(
       const std::string& name,
       const std::vector<webrtc::ScalabilityMode>& scalability_modes = {});
+  void AddSupportedVideoCodec(webrtc::SdpVideoFormat format);
+
   std::unique_ptr<VideoMediaSendChannelInterface>
   SetSendParamsWithAllSupportedCodecs();
 
@@ -499,6 +502,32 @@
   }
 }
 
+MATCHER(HasUniquePtValues, "") {
+  std::unordered_set<int> seen_ids;
+  for (const auto& codec : arg) {
+    if (seen_ids.count(codec.id) > 0) {
+      *result_listener << "Duplicate id for " << absl::StrCat(codec);
+      return false;
+    }
+    seen_ids.insert(codec.id);
+  }
+  return true;
+}
+
+TEST_F(WebRtcVideoEngineTest, SupportingTwoKindsOfVp9IsOk) {
+  AddSupportedVideoCodecType("VP8");
+  AddSupportedVideoCodec(webrtc::SdpVideoFormat("VP9", {{"profile-id", "0"}}));
+  AddSupportedVideoCodec(webrtc::SdpVideoFormat("VP9", {{"profile-id", "1"}}));
+  AddSupportedVideoCodec(webrtc::SdpVideoFormat("VP9", {{"profile-id", "3"}}));
+  AddSupportedVideoCodec(webrtc::SdpVideoFormat(
+      "AV1", {{"level-idx", "5"}, {"profile", "1"}, {"tier", "0"}}));
+  AddSupportedVideoCodec(webrtc::SdpVideoFormat(
+      "AV1", {{"level-idx", "5"}, {"profile", "0"}, {"tier", "0"}}));
+  AddSupportedVideoCodec(webrtc::SdpVideoFormat("VP9"));  // No parameters
+  ASSERT_THAT(engine_.LegacySendCodecs(), HasUniquePtValues());
+  ASSERT_THAT(engine_.LegacyRecvCodecs(), HasUniquePtValues());
+}
+
 TEST_F(WebRtcVideoEngineTest, SupportsTimestampOffsetHeaderExtension) {
   ExpectRtpCapabilitySupport(RtpExtension::kTimestampOffsetUri, true);
 }
@@ -939,6 +968,12 @@
   decoder_factory_->AddSupportedVideoCodecType(name);
 }
 
+void WebRtcVideoEngineTest::AddSupportedVideoCodec(
+    webrtc::SdpVideoFormat format) {
+  encoder_factory_->AddSupportedVideoCodec(format);
+  decoder_factory_->AddSupportedVideoCodec(format);
+}
+
 std::unique_ptr<VideoMediaSendChannelInterface>
 WebRtcVideoEngineTest::SetSendParamsWithAllSupportedCodecs() {
   std::unique_ptr<VideoMediaSendChannelInterface> channel =
diff --git a/pc/peer_connection_signaling_unittest.cc b/pc/peer_connection_signaling_unittest.cc
index 2214557..a580cd8 100644
--- a/pc/peer_connection_signaling_unittest.cc
+++ b/pc/peer_connection_signaling_unittest.cc
@@ -23,6 +23,7 @@
 #include <utility>
 #include <vector>
 
+#include "absl/strings/str_replace.h"
 #include "api/audio/audio_device.h"
 #include "api/audio_codecs/builtin_audio_decoder_factory.h"
 #include "api/audio_codecs/builtin_audio_encoder_factory.h"
@@ -1372,4 +1373,22 @@
   EXPECT_EQ(apt_it->second, "102");
 }
 
+TEST_F(PeerConnectionSignalingUnifiedPlanTest, LoopbackSdpIsPossible) {
+  // This is not a recommended way of doing things.
+  // The test is added because an Android test tries to do it this way,
+  // and triggered surprising behavior.
+  auto caller = CreatePeerConnection();
+  auto transceiver =
+      caller->AddTransceiver(cricket::MEDIA_TYPE_AUDIO, RtpTransceiverInit());
+
+  auto offer = caller->CreateOffer(RTCOfferAnswerOptions());
+  std::string offer_sdp;
+  ASSERT_TRUE(offer->ToString(&offer_sdp));
+  std::string answer_sdp =
+      absl::StrReplaceAll(offer_sdp, {{"a=setup:actpass", "a=setup:active"}});
+  EXPECT_TRUE(caller->SetLocalDescription(std::move(offer)));
+  auto answer = CreateSessionDescription(SdpType::kAnswer, answer_sdp);
+  EXPECT_TRUE(caller->SetRemoteDescription(std::move(answer)));
+}
+
 }  // namespace webrtc