[WebRTC] Add unidirectional codec support 02/324702/3
authorMichal Jurkiewicz <m.jurkiewicz@samsung.com>
Thu, 22 May 2025 08:23:03 +0000 (10:23 +0200)
committerBot Blink <blinkbot@samsung.com>
Sat, 24 May 2025 05:17:28 +0000 (05:17 +0000)
Fix for TCT WebRTC RTCRtpTransceiver-setCodecPreferences_8 failure

Port of upstream patches:
* Move some codec-comparing functions to a single file.
  https://webrtc-review.googlesource.com/c/src/+/365100
* Add comparators unittest, and abandon MatchesForSdp
  https://webrtc-review.googlesource.com/c/src/+/365102
* Compare only profile & tier when matching HEVC codec.
  https://webrtc-review.googlesource.com/c/src/+/363205
* Add unidirectional codec support ("offer to send" use case).
  https://webrtc-review.googlesource.com/c/src/+/374520

Change-Id: I5f2977e434e98f253feb9bcb9f2488ceea368a4d
Bug: https://jira-eu.sec.samsung.net/browse/VDWASM-2425
Signed-off-by: Michal Jurkiewicz <m.jurkiewicz@samsung.com>
21 files changed:
third_party/webrtc/api/video_codecs/h265_profile_tier_level.cc
third_party/webrtc/api/video_codecs/h265_profile_tier_level.h
third_party/webrtc/api/video_codecs/sdp_video_format.cc
third_party/webrtc/api/video_codecs/test/h265_profile_tier_level_unittest.cc
third_party/webrtc/api/video_codecs/test/sdp_video_format_unittest.cc
third_party/webrtc/call/DEPS
third_party/webrtc/call/payload_type_picker.cc
third_party/webrtc/call/payload_type_picker_unittest.cc
third_party/webrtc/media/BUILD.gn
third_party/webrtc/media/base/codec.cc
third_party/webrtc/media/base/codec_comparators.cc [new file with mode: 0644]
third_party/webrtc/media/base/codec_comparators.h [new file with mode: 0644]
third_party/webrtc/media/base/codec_comparators_unittest.cc [new file with mode: 0644]
third_party/webrtc/media/base/codec_unittest.cc
third_party/webrtc/media/engine/webrtc_video_engine.cc
third_party/webrtc/pc/media_session.cc
third_party/webrtc/pc/peer_connection_media_unittest.cc
third_party/webrtc/pc/rtp_transceiver.cc
third_party/webrtc/pc/rtp_transceiver.h
third_party/webrtc/pc/rtp_transceiver_unittest.cc
third_party/webrtc/pc/sdp_offer_answer.cc

index 2bfc8a3a9cd50c34c4dd239399c8e517f8f94927..92ea0c84bcbad51f33839fb186129a045bb99d05 100644 (file)
@@ -285,7 +285,6 @@ bool H265IsSameProfileTierLevel(const CodecParameterMap& params1,
          ptl1->tier == ptl2->tier && ptl1->level == ptl2->level;
 }
 
-#if defined(WEBRTC_TIZEN_TV) && defined(WEBRTC_USE_HEVC)
 bool H265IsSameProfile(const CodecParameterMap& params1,
                        const CodecParameterMap& params2) {
   const std::optional<H265ProfileTierLevel> ptl1 =
@@ -294,7 +293,15 @@ bool H265IsSameProfile(const CodecParameterMap& params1,
       ParseSdpForH265ProfileTierLevel(params2);
   return ptl1 && ptl2 && ptl1->profile == ptl2->profile;
 }
-#endif
+
+bool H265IsSameTier(const CodecParameterMap& params1,
+                    const CodecParameterMap& params2) {
+  const std::optional<H265ProfileTierLevel> ptl1 =
+      ParseSdpForH265ProfileTierLevel(params1);
+  const std::optional<H265ProfileTierLevel> ptl2 =
+      ParseSdpForH265ProfileTierLevel(params2);
+  return ptl1 && ptl2 && ptl1->tier == ptl2->tier;
+}
 
 std::optional<H265Level> GetSupportedH265Level(const Resolution& resolution,
                                                float max_fps) {
index 0253b694a68280b09b2acdae63cd4b2452a862c6..670f37933a8432a95967c21435cc0dff4d25a8ba 100644 (file)
@@ -112,12 +112,16 @@ RTC_EXPORT std::optional<H265ProfileTierLevel> ParseSdpForH265ProfileTierLevel(
 RTC_EXPORT bool H265IsSameProfileTierLevel(const CodecParameterMap& params1,
                                            const CodecParameterMap& params2);
 
-#if defined(WEBRTC_TIZEN_TV) && defined(WEBRTC_USE_HEVC)
-// Returns true if the parameters have the same H265 profile or neither contains
-// an H265 profile, otherwise false.
+// Returns true if the parameters have the same H265 profile, or neither
+// contains an H265 profile, otherwise false.
 RTC_EXPORT bool H265IsSameProfile(const CodecParameterMap& params1,
                                   const CodecParameterMap& params2);
-#endif
+
+// Returns true if the parameters have the same H265 tier, or neither
+// contains an H265 tier, otherwise false.
+RTC_EXPORT bool H265IsSameTier(const CodecParameterMap& params1,
+                               const CodecParameterMap& params2);
+
 }  // namespace webrtc
 
 #endif  // API_VIDEO_CODECS_H265_PROFILE_TIER_LEVEL_H_
index 115eb16304813d52b1087d798110a4477550112b..4156bde165a7918a4522d07eec9830d3db8447ef 100644 (file)
@@ -83,7 +83,6 @@ bool AV1IsSameLevelIdx(const CodecParameterMap& left,
 }
 
 #ifdef RTC_ENABLE_H265
-#if !defined(WEBRTC_TIZEN_TV) || !defined(WEBRTC_USE_HEVC)
 std::string GetH265TxModeOrDefault(const CodecParameterMap& params) {
   // If TxMode is not present, a value of "SRST" must be inferred.
   // https://tools.ietf.org/html/rfc7798@section-7.1
@@ -96,7 +95,6 @@ bool IsSameH265TxMode(const CodecParameterMap& left,
                                 GetH265TxModeOrDefault(right));
 }
 #endif
-#endif
 
 // Some (video) codecs are actually families of codecs and rely on parameters
 // to distinguish different incompatible family members.
@@ -121,12 +119,10 @@ bool IsSameCodecSpecific(const std::string& name1,
              AV1IsSameLevelIdx(params1, params2);
 #ifdef RTC_ENABLE_H265
     case kVideoCodecH265:
-#if defined(WEBRTC_TIZEN_TV) && defined(WEBRTC_USE_HEVC)
-      return webrtc::H265IsSameProfile(params1, params2);
-#else
-      return H265IsSameProfileTierLevel(params1, params2) &&
+      return H265IsSameProfile(params1, params2) &&
+             H265IsSameTier(params1, params2) &&
              IsSameH265TxMode(params1, params2);
-#endif  // defined(WEBRTC_TIZEN_TV) && defined(WEBRTC_USE_HEVC)
+
 #endif  // RTC_ENABLE_H265
     default:
       return true;
index e9095e2b972e628b361151fde9e3051f4f525f19..59c75f2ff3eca72d15b4e6851a0162158676f263 100644 (file)
@@ -231,8 +231,9 @@ TEST(H265ProfileTierLevel, TestProfileTierLevelCompare) {
   params2.clear();
   params1["profile-id"] = "1";
   params2["profile-id"] = "1";
-  params1["level-id"] = "93";
-  params2["level-id"] = "93";
+  params1["level-id"] = "180";
+  // Level 3.1 is not allowed for tier 1.
+  params2["level-id"] = "180";
   params1["tier-flag"] = "0";
   params2["tier-flag"] = "1";
   EXPECT_FALSE(H265IsSameProfileTierLevel(params1, params2));
@@ -247,6 +248,114 @@ TEST(H265ProfileTierLevel, TestProfileTierLevelCompare) {
   EXPECT_FALSE(H265IsSameProfileTierLevel(params1, params2));
 }
 
+TEST(H265ProfileTierLevel, TestProfileCompare) {
+  CodecParameterMap params1;
+  CodecParameterMap params2;
+
+  // None of profile-id/tier-flag/level-id is specified,
+  EXPECT_TRUE(H265IsSameProfile(params1, params2));
+
+  // Same non-empty PTL
+  params1["profile-id"] = "1";
+  params1["tier-flag"] = "0";
+  params1["level-id"] = "120";
+  params2["profile-id"] = "1";
+  params2["tier-flag"] = "0";
+  params2["level-id"] = "120";
+  EXPECT_TRUE(H265IsSameProfile(params1, params2));
+
+  // Different profiles.
+  params1.clear();
+  params2.clear();
+  params1["profile-id"] = "1";
+  params2["profile-id"] = "2";
+  EXPECT_FALSE(H265IsSameProfile(params1, params2));
+
+  // Different levels. We do not compare HEVC levels.
+  params1.clear();
+  params2.clear();
+  params1["profile-id"] = "1";
+  params2["profile-id"] = "1";
+  params1["level-id"] = "93";
+  params2["level-id"] = "183";
+  EXPECT_TRUE(H265IsSameProfile(params1, params2));
+
+  // Different tiers.
+  params1.clear();
+  params2.clear();
+  params1["profile-id"] = "1";
+  params2["profile-id"] = "1";
+  params1["level-id"] = "180";
+  // level 3.1 is not allowed for tier 1.
+  params2["level-id"] = "180";
+  params1["tier-flag"] = "0";
+  params2["tier-flag"] = "1";
+  EXPECT_TRUE(H265IsSameProfile(params1, params2));
+
+  // One of the CodecParameterMap is invalid.
+  params1.clear();
+  params2.clear();
+  params1["profile-id"] = "1";
+  params2["profile-id"] = "1";
+  params1["tier-flag"] = "0";
+  params2["tier-flag"] = "4";
+  EXPECT_FALSE(H265IsSameProfile(params1, params2));
+}
+
+TEST(H265ProfileTierLevel, TestTierCompare) {
+  CodecParameterMap params1;
+  CodecParameterMap params2;
+
+  // None of profile-id/tier-flag/level-id is specified,
+  EXPECT_TRUE(H265IsSameTier(params1, params2));
+
+  // Same non-empty PTL
+  params1["profile-id"] = "1";
+  params1["tier-flag"] = "0";
+  params1["level-id"] = "120";
+  params2["profile-id"] = "1";
+  params2["tier-flag"] = "0";
+  params2["level-id"] = "120";
+  EXPECT_TRUE(H265IsSameTier(params1, params2));
+
+  // Different profiles.
+  params1.clear();
+  params2.clear();
+  params1["profile-id"] = "1";
+  params2["profile-id"] = "2";
+  EXPECT_TRUE(H265IsSameTier(params1, params2));
+
+  // Different levels. We do not compare HEVC levels.
+  params1.clear();
+  params2.clear();
+  params1["profile-id"] = "1";
+  params2["profile-id"] = "1";
+  params1["level-id"] = "93";
+  params2["level-id"] = "183";
+  EXPECT_TRUE(H265IsSameTier(params1, params2));
+
+  // Different tiers.
+  params1.clear();
+  params2.clear();
+  params1["profile-id"] = "1";
+  params2["profile-id"] = "1";
+  params1["level-id"] = "180";
+  // level 3.1 is not allowed for tier 1.
+  params2["level-id"] = "180";
+  params1["tier-flag"] = "0";
+  params2["tier-flag"] = "1";
+  EXPECT_FALSE(H265IsSameTier(params1, params2));
+
+  // One of the CodecParameterMap is invalid.
+  params1.clear();
+  params2.clear();
+  params1["profile-id"] = "1";
+  params2["profile-id"] = "1";
+  params1["tier-flag"] = "0";
+  params2["tier-flag"] = "4";
+  EXPECT_FALSE(H265IsSameTier(params1, params2));
+}
+
 TEST(H265ProfileTierLevel, TestGetSupportedH265Level) {
   // Test with 720p at 30fps
   Resolution r{.width = 1280, .height = 720};
index bf6e95a40ed2c785dee4157e97b2a1569889c62a..13c45cd22556265243917548500b585917d409ff 100644 (file)
@@ -92,7 +92,7 @@ TEST(SdpVideoFormatTest, SameCodecNameDifferentParameters) {
   EXPECT_FALSE(Sdp("H265").IsSameCodec(Sdp(
       "H265",
       Params{{"profile-id", "1"}, {"tier-flag", "1"}, {"level-id", "93"}})));
-  EXPECT_FALSE(Sdp("H265").IsSameCodec(Sdp(
+  EXPECT_TRUE(Sdp("H265").IsSameCodec(Sdp(
       "H265",
       Params{{"profile-id", "1"}, {"tier-flag", "0"}, {"level-id", "90"}})));
   EXPECT_FALSE(
@@ -107,7 +107,7 @@ TEST(SdpVideoFormatTest, SameCodecNameDifferentParameters) {
           .IsSameCodec(Sdp("H265", Params{{"profile-id", "1"},
                                           {"tier-flag", "0"},
                                           {"level-id", "120"}})));
-  EXPECT_FALSE(
+  EXPECT_TRUE(
       Sdp("H265",
           Params{{"profile-id", "1"}, {"tier-flag", "0"}, {"level-id", "93"}})
           .IsSameCodec(Sdp("H265", Params{{"profile-id", "1"},
index 1fe352db209706585f5bcbfd1030bfeb490adf2c..e74b22f67715591f19d4c66c3b65a65fd9188e6a 100644 (file)
@@ -61,6 +61,7 @@ specific_include_rules = {
   ],
   "payload_type_picker\.cc": [
     "+media/base/codec.h",
+    "+media/base/codec_comparators.h",
     "+media/base/media_constants.h",
   ],
   "payload_type_picker_unittest\.cc": [
index 765d716cd6c7d406490e83c5e1237a2023cc859a..12854cfec3fb86dce9f5d3590dc87ca8d8154331 100644 (file)
@@ -20,6 +20,7 @@
 #include "api/rtc_error.h"
 #include "call/payload_type.h"
 #include "media/base/codec.h"
+#include "media/base/codec_comparators.h"
 #include "media/base/media_constants.h"
 #include "rtc_base/logging.h"
 
@@ -40,14 +41,6 @@ static const int kLastDynamicPayloadTypeUpperRange = 127;
 // fmtp parameters. The use of cricket::Codec, which contains more fields,
 // is only a temporary measure.
 
-bool MatchesForSdp(const cricket::Codec& codec_1,
-                   const cricket::Codec& codec_2) {
-  return absl::EqualsIgnoreCase(codec_1.name, codec_2.name) &&
-         codec_1.type == codec_2.type && codec_1.channels == codec_2.channels &&
-         codec_1.clockrate == codec_2.clockrate &&
-         codec_1.params == codec_2.params;
-}
-
 struct MapTableEntry {
   webrtc::SdpAudioFormat format;
   int payload_type;
@@ -147,10 +140,10 @@ RTCErrorOr<PayloadType> PayloadTypePicker::SuggestMapping(
   // The first matching entry is returned, unless excluder
   // maps it to something different.
   for (auto entry : entries_) {
-    if (MatchesForSdp(entry.codec(), codec)) {
+    if (MatchesWithCodecRules(entry.codec(), codec)) {
       if (excluder) {
         auto result = excluder->LookupCodec(entry.payload_type());
-        if (result.ok() && !MatchesForSdp(result.value(), codec)) {
+        if (result.ok() && !MatchesWithCodecRules(result.value(), codec)) {
           continue;
         }
       }
@@ -171,7 +164,7 @@ RTCError PayloadTypePicker::AddMapping(PayloadType payload_type,
   // Multiple mappings for the same codec and the same PT are legal;
   for (auto entry : entries_) {
     if (payload_type == entry.payload_type() &&
-        MatchesForSdp(codec, entry.codec())) {
+        MatchesWithCodecRules(codec, entry.codec())) {
       return RTCError::OK();
     }
   }
@@ -184,7 +177,7 @@ RTCError PayloadTypeRecorder::AddMapping(PayloadType payload_type,
                                          cricket::Codec codec) {
   auto existing_codec_it = payload_type_to_codec_.find(payload_type);
   if (existing_codec_it != payload_type_to_codec_.end() &&
-      !MatchesForSdp(codec, existing_codec_it->second)) {
+      !MatchesWithCodecRules(codec, existing_codec_it->second)) {
     if (absl::EqualsIgnoreCase(codec.name, existing_codec_it->second.name)) {
       // The difference is in clock rate, channels or FMTP parameters.
       RTC_LOG(LS_INFO) << "Warning: Attempt to change a codec's parameters";
@@ -215,9 +208,11 @@ RTCErrorOr<PayloadType> PayloadTypeRecorder::LookupPayloadType(
     cricket::Codec codec) const {
   // Note that having multiple PTs mapping to the same codec is NOT an error.
   // In this case, we return the first found (not deterministic).
-  auto result = std::find_if(
-      payload_type_to_codec_.begin(), payload_type_to_codec_.end(),
-      [codec](const auto& iter) { return MatchesForSdp(iter.second, codec); });
+  auto result =
+      std::find_if(payload_type_to_codec_.begin(), payload_type_to_codec_.end(),
+                   [codec](const auto& iter) {
+                     return MatchesWithCodecRules(iter.second, codec);
+                   });
   if (result == payload_type_to_codec_.end()) {
     return RTCError(RTCErrorType::INVALID_PARAMETER,
                     "No payload type found for codec");
index e1704cf72a068432edef7348b81ac5c956471ab2..0ecb80e24aebf34ec979ea4cc6e46a7184003d48 100644 (file)
@@ -55,8 +55,10 @@ TEST(PayloadTypePicker, ModifyingPtIsIgnored) {
   PayloadTypePicker picker;
   PayloadTypeRecorder recorder(picker);
   const PayloadType a_payload_type(123);
-  cricket::Codec a_codec = cricket::CreateVideoCodec(0, "vp8");
-  cricket::Codec b_codec = cricket::CreateVideoCodec(0, "vp9");
+  cricket::Codec a_codec =
+      cricket::CreateVideoCodec(cricket::Codec::kIdNotSet, "vp8");
+  cricket::Codec b_codec =
+      cricket::CreateVideoCodec(cricket::Codec::kIdNotSet, "vp9");
   recorder.AddMapping(a_payload_type, a_codec);
   auto error = recorder.AddMapping(a_payload_type, b_codec);
   EXPECT_TRUE(error.ok());
index 08e924a91b6d5669cf8680a2c841ccb6f9e07434..31b2d5d00a2d913d35bafe8a18ffeebfbd75b64b 100644 (file)
@@ -353,6 +353,11 @@ rtc_library("codec") {
   sources = [
     "base/codec.cc",
     "base/codec.h",
+
+    # Because Codec::Matches uses a function from codec_comparators,
+    # there's a mutual dependency between these two files.
+    "base/codec_comparators.cc",
+    "base/codec_comparators.h",
   ]
   deps = [
     ":media_constants",
@@ -1026,6 +1031,7 @@ if (rtc_include_tests) {
       }
 
       sources = [
+        "base/codec_comparators_unittest.cc",
         "base/codec_unittest.cc",
         "base/media_engine_unittest.cc",
         "base/rtp_utils_unittest.cc",
index ec62d880bf01738261129cc84b1166664e1e79c6..21f02f57046119cfea947533099e9a751e065481 100644 (file)
 #include "api/audio_codecs/audio_format.h"
 #include "api/media_types.h"
 #include "api/rtp_parameters.h"
-#include "api/video_codecs/av1_profile.h"
 #include "api/video_codecs/h264_profile_level_id.h"
 #include "api/video_codecs/sdp_video_format.h"
 #ifdef RTC_ENABLE_H265
 #include "api/video_codecs/h265_profile_tier_level.h"
 #endif
-#include "api/video_codecs/vp9_profile.h"
+#include "media/base/codec_comparators.h"
 #include "media/base/media_constants.h"
 #include "rtc_base/checks.h"
 #include "rtc_base/logging.h"
 #include "rtc_base/strings/string_builder.h"
 
 namespace cricket {
-namespace {
-
-// TODO(bugs.webrtc.org/15847): remove code duplication of IsSameCodecSpecific
-// in api/video_codecs/sdp_video_format.cc
-std::string GetFmtpParameterOrDefault(const webrtc::CodecParameterMap& params,
-                                      const std::string& name,
-                                      const std::string& default_value) {
-  const auto it = params.find(name);
-  if (it != params.end()) {
-    return it->second;
-  }
-  return default_value;
-}
-
-std::string H264GetPacketizationModeOrDefault(
-    const webrtc::CodecParameterMap& params) {
-  // If packetization-mode is not present, default to "0".
-  // https://tools.ietf.org/html/rfc6184#section-6.2
-  return GetFmtpParameterOrDefault(params, cricket::kH264FmtpPacketizationMode,
-                                   "0");
-}
-
-bool H264IsSamePacketizationMode(const webrtc::CodecParameterMap& left,
-                                 const webrtc::CodecParameterMap& right) {
-  return H264GetPacketizationModeOrDefault(left) ==
-         H264GetPacketizationModeOrDefault(right);
-}
-
-std::string AV1GetTierOrDefault(const webrtc::CodecParameterMap& params) {
-  // If the parameter is not present, the tier MUST be inferred to be 0.
-  // https://aomediacodec.github.io/av1-rtp-spec/#72-sdp-parameters
-  return GetFmtpParameterOrDefault(params, cricket::kAv1FmtpTier, "0");
-}
-
-bool AV1IsSameTier(const webrtc::CodecParameterMap& left,
-                   const webrtc::CodecParameterMap& right) {
-  return AV1GetTierOrDefault(left) == AV1GetTierOrDefault(right);
-}
-
-std::string AV1GetLevelIdxOrDefault(const webrtc::CodecParameterMap& params) {
-  // If the parameter is not present, it MUST be inferred to be 5 (level 3.1).
-  // https://aomediacodec.github.io/av1-rtp-spec/#72-sdp-parameters
-  return GetFmtpParameterOrDefault(params, cricket::kAv1FmtpLevelIdx, "5");
-}
-
-bool AV1IsSameLevelIdx(const webrtc::CodecParameterMap& left,
-                       const webrtc::CodecParameterMap& right) {
-  return AV1GetLevelIdxOrDefault(left) == AV1GetLevelIdxOrDefault(right);
-}
-
-#ifdef RTC_ENABLE_H265
-std::string GetH265TxModeOrDefault(const webrtc::CodecParameterMap& params) {
-  // If TxMode is not present, a value of "SRST" must be inferred.
-  // https://tools.ietf.org/html/rfc7798@section-7.1
-  return GetFmtpParameterOrDefault(params, kH265FmtpTxMode, "SRST");
-}
-
-bool IsSameH265TxMode(const webrtc::CodecParameterMap& left,
-                      const webrtc::CodecParameterMap& right) {
-  return absl::EqualsIgnoreCase(GetH265TxModeOrDefault(left),
-                                GetH265TxModeOrDefault(right));
-}
-#endif
-
-// Some (video) codecs are actually families of codecs and rely on parameters
-// to distinguish different incompatible family members.
-bool IsSameCodecSpecific(const std::string& name1,
-                         const webrtc::CodecParameterMap& params1,
-                         const std::string& name2,
-                         const webrtc::CodecParameterMap& params2) {
-  // The names might not necessarily match, so check both.
-  auto either_name_matches = [&](const std::string name) {
-    return absl::EqualsIgnoreCase(name, name1) ||
-           absl::EqualsIgnoreCase(name, name2);
-  };
-  if (either_name_matches(kH264CodecName))
-    return webrtc::H264IsSameProfile(params1, params2) &&
-           H264IsSamePacketizationMode(params1, params2);
-  if (either_name_matches(kVp9CodecName))
-    return webrtc::VP9IsSameProfile(params1, params2);
-  if (either_name_matches(kAv1CodecName))
-    return webrtc::AV1IsSameProfile(params1, params2) &&
-           AV1IsSameTier(params1, params2) &&
-           AV1IsSameLevelIdx(params1, params2);
-#ifdef RTC_ENABLE_H265
-  if (either_name_matches(kH265CodecName)) {
-    return
-#if defined(WEBRTC_TIZEN_TV) && defined(WEBRTC_USE_HEVC)
-        webrtc::H265IsSameProfile(params1, params2)
-#else
-        webrtc::H265IsSameProfileTierLevel(params1, params2)
-#endif  // defined(WEBRTC_TIZEN_TV) && defined(WEBRTC_USE_HEVC)
-        && IsSameH265TxMode(params1, params2);
-  }
-#endif  // RTC_ENABLE_H265
-  return true;
-}
-
-}  // namespace
+namespace {}  // namespace
 
 FeedbackParams::FeedbackParams() = default;
 FeedbackParams::~FeedbackParams() = default;
@@ -231,55 +132,7 @@ bool Codec::operator==(const Codec& c) const {
 }
 
 bool Codec::Matches(const Codec& codec) const {
-  // Match the codec id/name based on the typical static/dynamic name rules.
-  // Matching is case-insensitive.
-
-  // We support the ranges [96, 127] and more recently [35, 65].
-  // https://www.iana.org/assignments/rtp-parameters/rtp-parameters.xhtml#rtp-parameters-1
-  // Within those ranges we match by codec name, outside by codec id.
-  // Since no codecs are assigned an id in the range [66, 95] by us, these will
-  // never match.
-  const int kLowerDynamicRangeMin = 35;
-  const int kLowerDynamicRangeMax = 65;
-  const int kUpperDynamicRangeMin = 96;
-  const int kUpperDynamicRangeMax = 127;
-  const bool is_id_in_dynamic_range =
-      (id >= kLowerDynamicRangeMin && id <= kLowerDynamicRangeMax) ||
-      (id >= kUpperDynamicRangeMin && id <= kUpperDynamicRangeMax);
-  const bool is_codec_id_in_dynamic_range =
-      (codec.id >= kLowerDynamicRangeMin &&
-       codec.id <= kLowerDynamicRangeMax) ||
-      (codec.id >= kUpperDynamicRangeMin && codec.id <= kUpperDynamicRangeMax);
-  bool matches_id = is_id_in_dynamic_range && is_codec_id_in_dynamic_range
-                        ? (absl::EqualsIgnoreCase(name, codec.name))
-                        : (id == codec.id);
-
-  auto matches_type_specific = [&]() {
-    switch (type) {
-      case Type::kAudio:
-        // If a nonzero clockrate is specified, it must match the actual
-        // clockrate. If a nonzero bitrate is specified, it must match the
-        // actual bitrate, unless the codec is VBR (0), where we just force the
-        // supplied value. The number of channels must match exactly, with the
-        // exception that channels=0 is treated synonymously as channels=1, per
-        // RFC 4566 section 6: " [The channels] parameter is OPTIONAL and may be
-        // omitted if the number of channels is one."
-        // Preference is ignored.
-        // TODO(juberti): Treat a zero clockrate as 8000Hz, the RTP default
-        // clockrate.
-        return ((codec.clockrate == 0 /*&& clockrate == 8000*/) ||
-                clockrate == codec.clockrate) &&
-               (codec.bitrate == 0 || bitrate <= 0 ||
-                bitrate == codec.bitrate) &&
-               ((codec.channels < 2 && channels < 2) ||
-                channels == codec.channels);
-
-      case Type::kVideo:
-        return IsSameCodecSpecific(name, params, codec.name, codec.params);
-    }
-  };
-
-  return matches_id && matches_type_specific();
+  return webrtc::MatchesWithCodecRules(*this, codec);
 }
 
 bool Codec::MatchesRtpCodec(const webrtc::RtpCodec& codec_capability) const {
diff --git a/third_party/webrtc/media/base/codec_comparators.cc b/third_party/webrtc/media/base/codec_comparators.cc
new file mode 100644 (file)
index 0000000..bf26f17
--- /dev/null
@@ -0,0 +1,413 @@
+/*
+ *  Copyright 2024 The WebRTC Project Authors. All rights reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+#include "media/base/codec_comparators.h"
+
+#include <algorithm>
+#include <cstddef>
+#include <optional>
+#include <string>
+#include <vector>
+
+#include "absl/algorithm/container.h"
+#include "absl/functional/any_invocable.h"
+#include "absl/strings/match.h"
+#include "absl/strings/string_view.h"
+#include "api/rtp_parameters.h"
+#include "api/video_codecs/av1_profile.h"
+#include "api/video_codecs/h264_profile_level_id.h"
+#ifdef RTC_ENABLE_H265
+#include "api/video_codecs/h265_profile_tier_level.h"
+#endif
+#include "api/video_codecs/vp9_profile.h"
+#include "media/base/codec.h"
+#include "media/base/media_constants.h"
+#include "rtc_base/checks.h"
+#include "rtc_base/logging.h"
+#include "rtc_base/string_encode.h"
+
+namespace webrtc {
+
+namespace {
+
+using cricket::Codec;
+
+// TODO(bugs.webrtc.org/15847): remove code duplication of IsSameCodecSpecific
+// in api/video_codecs/sdp_video_format.cc
+std::string GetFmtpParameterOrDefault(const CodecParameterMap& params,
+                                      const std::string& name,
+                                      const std::string& default_value) {
+  const auto it = params.find(name);
+  if (it != params.end()) {
+    return it->second;
+  }
+  return default_value;
+}
+
+bool HasParameter(const CodecParameterMap& params, const std::string& name) {
+  return params.find(name) != params.end();
+}
+
+std::string H264GetPacketizationModeOrDefault(const CodecParameterMap& params) {
+  // If packetization-mode is not present, default to "0".
+  // https://tools.ietf.org/html/rfc6184#section-6.2
+  return GetFmtpParameterOrDefault(params, cricket::kH264FmtpPacketizationMode,
+                                   "0");
+}
+
+bool H264IsSamePacketizationMode(const CodecParameterMap& left,
+                                 const CodecParameterMap& right) {
+  return H264GetPacketizationModeOrDefault(left) ==
+         H264GetPacketizationModeOrDefault(right);
+}
+
+std::string AV1GetTierOrDefault(const CodecParameterMap& params) {
+  // If the parameter is not present, the tier MUST be inferred to be 0.
+  // https://aomediacodec.github.io/av1-rtp-spec/#72-sdp-parameters
+  return GetFmtpParameterOrDefault(params, cricket::kAv1FmtpTier, "0");
+}
+
+bool AV1IsSameTier(const CodecParameterMap& left,
+                   const CodecParameterMap& right) {
+  return AV1GetTierOrDefault(left) == AV1GetTierOrDefault(right);
+}
+
+std::string AV1GetLevelIdxOrDefault(const CodecParameterMap& params) {
+  // If the parameter is not present, it MUST be inferred to be 5 (level 3.1).
+  // https://aomediacodec.github.io/av1-rtp-spec/#72-sdp-parameters
+  return GetFmtpParameterOrDefault(params, cricket::kAv1FmtpLevelIdx, "5");
+}
+
+bool AV1IsSameLevelIdx(const CodecParameterMap& left,
+                       const CodecParameterMap& right) {
+  return AV1GetLevelIdxOrDefault(left) == AV1GetLevelIdxOrDefault(right);
+}
+
+#ifdef RTC_ENABLE_H265
+std::string GetH265TxModeOrDefault(const CodecParameterMap& params) {
+  // If TxMode is not present, a value of "SRST" must be inferred.
+  // https://tools.ietf.org/html/rfc7798@section-7.1
+  return GetFmtpParameterOrDefault(params, cricket::kH265FmtpTxMode, "SRST");
+}
+
+bool IsSameH265TxMode(const CodecParameterMap& left,
+                      const CodecParameterMap& right) {
+  return absl::EqualsIgnoreCase(GetH265TxModeOrDefault(left),
+                                GetH265TxModeOrDefault(right));
+}
+#endif
+
+// Some (video) codecs are actually families of codecs and rely on parameters
+// to distinguish different incompatible family members.
+bool IsSameCodecSpecific(const std::string& name1,
+                         const CodecParameterMap& params1,
+                         const std::string& name2,
+                         const CodecParameterMap& params2) {
+  // The names might not necessarily match, so check both.
+  auto either_name_matches = [&](const std::string name) {
+    return absl::EqualsIgnoreCase(name, name1) ||
+           absl::EqualsIgnoreCase(name, name2);
+  };
+  if (either_name_matches(cricket::kH264CodecName))
+    return H264IsSameProfile(params1, params2) &&
+           H264IsSamePacketizationMode(params1, params2);
+  if (either_name_matches(cricket::kVp9CodecName))
+    return VP9IsSameProfile(params1, params2);
+  if (either_name_matches(cricket::kAv1CodecName))
+    return AV1IsSameProfile(params1, params2) &&
+           AV1IsSameTier(params1, params2) &&
+           AV1IsSameLevelIdx(params1, params2);
+#ifdef RTC_ENABLE_H265
+  if (either_name_matches(cricket::kH265CodecName)) {
+    return H265IsSameProfile(params1, params2) &&
+           H265IsSameTier(params1, params2) &&
+           IsSameH265TxMode(params1, params2);
+  }
+#endif
+  return true;
+}
+
+bool ReferencedCodecsMatch(const std::vector<Codec>& codecs1,
+                           const int codec1_id,
+                           const std::vector<Codec>& codecs2,
+                           const int codec2_id) {
+  const Codec* codec1 = FindCodecById(codecs1, codec1_id);
+  const Codec* codec2 = FindCodecById(codecs2, codec2_id);
+  return codec1 != nullptr && codec2 != nullptr && codec1->Matches(*codec2);
+}
+
+bool MatchesWithReferenceAttributesAndComparator(
+    const Codec& codec_to_match,
+    const Codec& potential_match,
+    absl::AnyInvocable<bool(int, int)> reference_comparator) {
+  if (!MatchesWithCodecRules(codec_to_match, potential_match)) {
+    return false;
+  }
+  Codec::ResiliencyType resiliency_type = codec_to_match.GetResiliencyType();
+  if (resiliency_type == Codec::ResiliencyType::kRtx) {
+    int apt_value_1 = 0;
+    int apt_value_2 = 0;
+    if (!codec_to_match.GetParam(cricket::kCodecParamAssociatedPayloadType,
+                                 &apt_value_1) ||
+        !potential_match.GetParam(cricket::kCodecParamAssociatedPayloadType,
+                                  &apt_value_2)) {
+      RTC_LOG(LS_WARNING) << "RTX missing associated payload type.";
+      return false;
+    }
+    if (reference_comparator(apt_value_1, apt_value_2)) {
+      return true;
+    }
+    return false;
+  }
+  if (resiliency_type == Codec::ResiliencyType::kRed) {
+    auto red_parameters_1 =
+        codec_to_match.params.find(cricket::kCodecParamNotInNameValueFormat);
+    auto red_parameters_2 =
+        potential_match.params.find(cricket::kCodecParamNotInNameValueFormat);
+    bool has_parameters_1 = red_parameters_1 != codec_to_match.params.end();
+    bool has_parameters_2 = red_parameters_2 != potential_match.params.end();
+    // If codec_to_match has unassigned PT and no parameter,
+    // we assume that it'll be assigned later and return a match.
+    // Note - this should be deleted. It's untidy.
+    if (potential_match.id == Codec::kIdNotSet && !has_parameters_2) {
+      return true;
+    }
+    if (codec_to_match.id == Codec::kIdNotSet && !has_parameters_1) {
+      return true;
+    }
+    if (has_parameters_1 && has_parameters_2) {
+      // Different levels of redundancy between offer and answer are OK
+      // since RED is considered to be declarative.
+      std::vector<absl::string_view> redundant_payloads_1 =
+          rtc::split(red_parameters_1->second, '/');
+      std::vector<absl::string_view> redundant_payloads_2 =
+          rtc::split(red_parameters_2->second, '/');
+      // note: rtc::split returns at least 1 string even on empty strings.
+      size_t smallest_size =
+          std::min(redundant_payloads_1.size(), redundant_payloads_2.size());
+      // If the smaller list is equivalent to the longer list, we consider them
+      // equivalent even if size differs.
+      for (size_t i = 0; i < smallest_size; i++) {
+        int red_value_1;
+        int red_value_2;
+        if (rtc::FromString(redundant_payloads_1[i], &red_value_1) &&
+            rtc::FromString(redundant_payloads_2[i], &red_value_2)) {
+          if (!reference_comparator(red_value_1, red_value_2)) {
+            return false;
+          }
+        } else {
+          // At least one parameter was not an integer.
+          // This is a syntax error, but we allow it here if the whole parameter
+          // equals the other parameter, in order to not generate more errors
+          // by duplicating the bad parameter.
+          return red_parameters_1->second == red_parameters_2->second;
+        }
+      }
+      return true;
+    }
+    if (!has_parameters_1 && !has_parameters_2) {
+      // Both parameters are missing. Happens for video RED.
+      return true;
+    }
+    return false;
+  }
+  return true;  // Not a codec with a PT-valued reference.
+}
+
+CodecParameterMap InsertDefaultParams(const std::string& name,
+                                      const CodecParameterMap& params) {
+  CodecParameterMap updated_params = params;
+  if (absl::EqualsIgnoreCase(name, cricket::kVp9CodecName)) {
+    if (!HasParameter(params, kVP9FmtpProfileId)) {
+      if (std::optional<VP9Profile> default_profile =
+              ParseSdpForVP9Profile({})) {
+        updated_params.insert(
+            {kVP9FmtpProfileId, VP9ProfileToString(*default_profile)});
+      }
+    }
+  }
+  if (absl::EqualsIgnoreCase(name, cricket::kAv1CodecName)) {
+    if (!HasParameter(params, cricket::kAv1FmtpProfile)) {
+      if (std::optional<AV1Profile> default_profile =
+              ParseSdpForAV1Profile({})) {
+        updated_params.insert({cricket::kAv1FmtpProfile,
+                               AV1ProfileToString(*default_profile).data()});
+      }
+    }
+    if (!HasParameter(params, cricket::kAv1FmtpTier)) {
+      updated_params.insert({cricket::kAv1FmtpTier, AV1GetTierOrDefault({})});
+    }
+    if (!HasParameter(params, cricket::kAv1FmtpLevelIdx)) {
+      updated_params.insert(
+          {cricket::kAv1FmtpLevelIdx, AV1GetLevelIdxOrDefault({})});
+    }
+  }
+  if (absl::EqualsIgnoreCase(name, cricket::kH264CodecName)) {
+    if (!HasParameter(params, cricket::kH264FmtpPacketizationMode)) {
+      updated_params.insert({cricket::kH264FmtpPacketizationMode,
+                             H264GetPacketizationModeOrDefault({})});
+    }
+  }
+#ifdef RTC_ENABLE_H265
+  if (absl::EqualsIgnoreCase(name, cricket::kH265CodecName)) {
+    if (std::optional<H265ProfileTierLevel> default_params =
+            ParseSdpForH265ProfileTierLevel({})) {
+      if (!HasParameter(params, cricket::kH265FmtpProfileId)) {
+        updated_params.insert({cricket::kH265FmtpProfileId,
+                               H265ProfileToString(default_params->profile)});
+      }
+      if (!HasParameter(params, cricket::kH265FmtpLevelId)) {
+        updated_params.insert({cricket::kH265FmtpLevelId,
+                               H265LevelToString(default_params->level)});
+      }
+      if (!HasParameter(params, cricket::kH265FmtpTierFlag)) {
+        updated_params.insert({cricket::kH265FmtpTierFlag,
+                               H265TierToString(default_params->tier)});
+      }
+    }
+    if (!HasParameter(params, cricket::kH265FmtpTxMode)) {
+      updated_params.insert(
+          {cricket::kH265FmtpTxMode, GetH265TxModeOrDefault({})});
+    }
+  }
+#endif
+  return updated_params;
+}
+
+}  // namespace
+
+bool MatchesWithCodecRules(const Codec& left_codec, const Codec& right_codec) {
+  // Match the codec id/name based on the typical static/dynamic name rules.
+  // Matching is case-insensitive.
+
+  // We support the ranges [96, 127] and more recently [35, 65].
+  // https://www.iana.org/assignments/rtp-parameters/rtp-parameters.xhtml#rtp-parameters-1
+  // Within those ranges we match by codec name, outside by codec id.
+  // We also match by name if either ID is unassigned.
+  // Since no codecs are assigned an id in the range [66, 95] by us, these will
+  // never match.
+  const int kLowerDynamicRangeMin = 35;
+  const int kLowerDynamicRangeMax = 65;
+  const int kUpperDynamicRangeMin = 96;
+  const int kUpperDynamicRangeMax = 127;
+  const bool is_id_in_dynamic_range =
+      (left_codec.id >= kLowerDynamicRangeMin &&
+       left_codec.id <= kLowerDynamicRangeMax) ||
+      (left_codec.id >= kUpperDynamicRangeMin &&
+       left_codec.id <= kUpperDynamicRangeMax);
+  const bool is_codec_id_in_dynamic_range =
+      (right_codec.id >= kLowerDynamicRangeMin &&
+       right_codec.id <= kLowerDynamicRangeMax) ||
+      (right_codec.id >= kUpperDynamicRangeMin &&
+       right_codec.id <= kUpperDynamicRangeMax);
+  bool matches_id;
+  if ((is_id_in_dynamic_range && is_codec_id_in_dynamic_range) ||
+      left_codec.id == Codec::kIdNotSet || right_codec.id == Codec::kIdNotSet) {
+    matches_id = absl::EqualsIgnoreCase(left_codec.name, right_codec.name);
+  } else {
+    matches_id = (left_codec.id == right_codec.id);
+  }
+
+  auto matches_type_specific = [&]() {
+    switch (left_codec.type) {
+      case Codec::Type::kAudio:
+        // If a nonzero clockrate is specified, it must match the actual
+        // clockrate. If a nonzero bitrate is specified, it must match the
+        // actual bitrate, unless the codec is VBR (0), where we just force the
+        // supplied value. The number of channels must match exactly, with the
+        // exception that channels=0 is treated synonymously as channels=1, per
+        // RFC 4566 section 6: " [The channels] parameter is OPTIONAL and may be
+        // omitted if the number of channels is one."
+        // Preference is ignored.
+        // TODO(juberti): Treat a zero clockrate as 8000Hz, the RTP default
+        // clockrate.
+        return ((right_codec.clockrate == 0 /*&& clockrate == 8000*/) ||
+                left_codec.clockrate == right_codec.clockrate) &&
+               (right_codec.bitrate == 0 || left_codec.bitrate <= 0 ||
+                left_codec.bitrate == right_codec.bitrate) &&
+               ((right_codec.channels < 2 && left_codec.channels < 2) ||
+                left_codec.channels == right_codec.channels);
+
+      case Codec::Type::kVideo:
+        return IsSameCodecSpecific(left_codec.name, left_codec.params,
+                                   right_codec.name, right_codec.params);
+    }
+  };
+
+  return matches_id && matches_type_specific();
+}
+
+bool MatchesWithReferenceAttributes(const Codec& codec1, const Codec& codec2) {
+  return MatchesWithReferenceAttributesAndComparator(
+      codec1, codec2, [](int a, int b) { return a == b; });
+}
+
+// Finds a codec in `codecs2` that matches `codec_to_match`, which is
+// a member of `codecs1`. If `codec_to_match` is an RED or RTX codec, both
+// the codecs themselves and their associated codecs must match.
+std::optional<Codec> FindMatchingCodec(const std::vector<Codec>& codecs1,
+                                       const std::vector<Codec>& codecs2,
+                                       const Codec& codec_to_match) {
+  // `codec_to_match` should be a member of `codecs1`, in order to look up
+  // RED/RTX codecs' associated codecs correctly. If not, that's a programming
+  // error.
+  RTC_DCHECK(absl::c_any_of(codecs1, [&codec_to_match](const Codec& codec) {
+    return &codec == &codec_to_match;
+  }));
+  for (const Codec& potential_match : codecs2) {
+    if (MatchesWithReferenceAttributesAndComparator(
+            codec_to_match, potential_match,
+            [&codecs1, &codecs2](int a, int b) {
+              return ReferencedCodecsMatch(codecs1, a, codecs2, b);
+            })) {
+      return potential_match;
+    }
+  }
+  return std::nullopt;
+}
+
+bool IsSameRtpCodec(const Codec& codec, const RtpCodec& rtp_codec) {
+  RtpCodecParameters rtp_codec2 = codec.ToCodecParameters();
+
+  return absl::EqualsIgnoreCase(rtp_codec.name, rtp_codec2.name) &&
+         rtp_codec.kind == rtp_codec2.kind &&
+         rtp_codec.num_channels == rtp_codec2.num_channels &&
+         rtp_codec.clock_rate == rtp_codec2.clock_rate &&
+         InsertDefaultParams(rtp_codec.name, rtp_codec.parameters) ==
+             InsertDefaultParams(rtp_codec2.name, rtp_codec2.parameters);
+}
+
+bool IsSameRtpCodecIgnoringLevel(const Codec& codec,
+                                 const RtpCodec& rtp_codec) {
+  RtpCodecParameters rtp_codec2 = codec.ToCodecParameters();
+
+  if (!absl::EqualsIgnoreCase(rtp_codec.name, rtp_codec2.name) ||
+      rtp_codec.kind != rtp_codec2.kind ||
+      rtp_codec.num_channels != rtp_codec2.num_channels ||
+      rtp_codec.clock_rate != rtp_codec2.clock_rate) {
+    return false;
+  }
+
+  CodecParameterMap params1 =
+      InsertDefaultParams(rtp_codec.name, rtp_codec.parameters);
+  CodecParameterMap params2 =
+      InsertDefaultParams(rtp_codec2.name, rtp_codec2.parameters);
+
+  // Currently we only ignore H.265 level-id parameter.
+#ifdef RTC_ENABLE_H265
+  if (absl::EqualsIgnoreCase(rtp_codec.name, cricket::kH265CodecName)) {
+    params1.erase(cricket::kH265FmtpLevelId);
+    params2.erase(cricket::kH265FmtpLevelId);
+  }
+#endif
+
+  return params1 == params2;
+}
+
+}  // namespace webrtc
diff --git a/third_party/webrtc/media/base/codec_comparators.h b/third_party/webrtc/media/base/codec_comparators.h
new file mode 100644 (file)
index 0000000..64423c7
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ *  Copyright 2024 The WebRTC Project Authors. All rights reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef MEDIA_BASE_CODEC_COMPARATORS_H_
+#define MEDIA_BASE_CODEC_COMPARATORS_H_
+
+#include <optional>
+#include <vector>
+
+#include "api/rtp_parameters.h"
+#include "media/base/codec.h"
+
+namespace webrtc {
+
+// Comparison used for the Codec::Matches function
+bool MatchesWithCodecRules(const cricket::Codec& left_codec,
+                           const cricket::Codec& codec);
+
+// Comparison that also checks on codecs referenced by PT in the
+// fmtp line, as used with RED and RTX "codecs".
+bool MatchesWithReferenceAttributes(const cricket::Codec& left_codec,
+                                    const cricket::Codec& right_codec);
+
+// Finds a codec in `codecs2` that matches `codec_to_match`, which is
+// a member of `codecs1`. If `codec_to_match` is an RED or RTX codec, both
+// the codecs themselves and their associated codecs must match.
+// The purpose of this function is that codecs1 and codecs2 are different
+// PT numbering spaces, and it is trying to find the codec in codecs2
+// that has the same functionality as `codec_to_match` so that its PT
+// can be used in place of the original.
+std::optional<cricket::Codec> FindMatchingCodec(
+    const std::vector<cricket::Codec>& codecs1,
+    const std::vector<cricket::Codec>& codecs2,
+    const cricket::Codec& codec_to_match);
+
+// Similar to `Codec::MatchesRtpCodec` but not an exact match of parameters.
+// Unspecified parameters are treated as default.
+bool IsSameRtpCodec(const cricket::Codec& codec, const RtpCodec& rtp_codec);
+
+// Similar to `IsSameRtpCodec` but ignoring the level related parameter.
+bool IsSameRtpCodecIgnoringLevel(const cricket::Codec& codec,
+                                 const RtpCodec& rtp_codec);
+}  // namespace webrtc
+
+#endif  // MEDIA_BASE_CODEC_COMPARATORS_H_
diff --git a/third_party/webrtc/media/base/codec_comparators_unittest.cc b/third_party/webrtc/media/base/codec_comparators_unittest.cc
new file mode 100644 (file)
index 0000000..f1286de
--- /dev/null
@@ -0,0 +1,542 @@
+/*
+ *  Copyright (c) 2024 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+#include "media/base/codec_comparators.h"
+
+#include <string>
+
+#include "api/audio_codecs/audio_format.h"
+#include "api/rtp_parameters.h"
+#include "api/video_codecs/sdp_video_format.h"
+#include "api/video_codecs/vp9_profile.h"
+#include "media/base/codec.h"
+#include "media/base/media_constants.h"
+#include "test/gmock.h"
+#include "test/gtest.h"
+
+namespace webrtc {
+
+using cricket::Codec;
+using cricket::CreateAudioCodec;
+using cricket::CreateVideoCodec;
+using cricket::kH264CodecName;
+using cricket::kH264FmtpPacketizationMode;
+using ::testing::TestWithParam;
+using ::testing::ValuesIn;
+
+TEST(CodecComparatorsTest, CodecMatchesItself) {
+  Codec codec = cricket::CreateVideoCodec("custom");
+  EXPECT_TRUE(MatchesWithCodecRules(codec, codec));
+}
+
+TEST(CodecComparatorsTest, MismatchedBasicParameters) {
+  Codec codec = CreateAudioCodec(SdpAudioFormat("opus", 48000, 2));
+  Codec nonmatch_codec = codec;
+  nonmatch_codec.name = "g711";
+  EXPECT_FALSE(MatchesWithCodecRules(nonmatch_codec, codec));
+  nonmatch_codec = codec;
+  nonmatch_codec.clockrate = 8000;
+  EXPECT_FALSE(MatchesWithCodecRules(nonmatch_codec, codec));
+  nonmatch_codec = codec;
+  nonmatch_codec.channels = 1;
+  EXPECT_FALSE(MatchesWithCodecRules(nonmatch_codec, codec));
+}
+
+TEST(CodecComparatorsTest, H264PacketizationModeMismatch) {
+  Codec pt_mode_1 = CreateVideoCodec(kH264CodecName);
+  Codec pt_mode_0 = pt_mode_1;
+  pt_mode_0.SetParam(kH264FmtpPacketizationMode, "0");
+  EXPECT_FALSE(MatchesWithCodecRules(pt_mode_1, pt_mode_0));
+  EXPECT_FALSE(MatchesWithCodecRules(pt_mode_0, pt_mode_1));
+  Codec no_pt_mode = pt_mode_1;
+  no_pt_mode.RemoveParam(kH264FmtpPacketizationMode);
+  EXPECT_TRUE(MatchesWithCodecRules(pt_mode_0, no_pt_mode));
+  EXPECT_TRUE(MatchesWithCodecRules(no_pt_mode, pt_mode_0));
+  EXPECT_FALSE(MatchesWithCodecRules(no_pt_mode, pt_mode_1));
+}
+
+TEST(CodecComparatorsTest, AudioParametersIgnored) {
+  // Currently, all parameters on audio codecs are ignored for matching.
+  Codec basic_opus = CreateAudioCodec(SdpAudioFormat("opus", 48000, 2));
+  Codec opus_with_parameters = basic_opus;
+  opus_with_parameters.SetParam("stereo", "0");
+  EXPECT_TRUE(MatchesWithCodecRules(basic_opus, opus_with_parameters));
+  EXPECT_TRUE(MatchesWithCodecRules(opus_with_parameters, basic_opus));
+  opus_with_parameters.SetParam("nonsense", "stuff");
+  EXPECT_TRUE(MatchesWithCodecRules(basic_opus, opus_with_parameters));
+  EXPECT_TRUE(MatchesWithCodecRules(opus_with_parameters, basic_opus));
+}
+
+TEST(CodecComparatorsTest, StaticPayloadTypesIgnoreName) {
+  // This is the IANA registered format for PT 8
+  Codec codec_1 = CreateAudioCodec(8, "pcma", 8000, 1);
+  Codec codec_2 = CreateAudioCodec(8, "nonsense", 8000, 1);
+  EXPECT_TRUE(MatchesWithCodecRules(codec_1, codec_2));
+}
+
+TEST(CodecComparatorsTest, MatchesWithReferenceAttributesRed) {
+  // Test that RED codecs' reference attributes get parsed correctly.
+  Codec codec_1 =
+      cricket::CreateAudioCodec(101, cricket::kRedCodecName, 48000, 2);
+  codec_1.SetParam(cricket::kCodecParamNotInNameValueFormat, "100/100");
+  Codec codec_2 =
+      cricket::CreateAudioCodec(102, cricket::kRedCodecName, 48000, 2);
+  codec_2.SetParam(cricket::kCodecParamNotInNameValueFormat, "101/101");
+  // Mixed codecs in RED
+  Codec codec_3 =
+      cricket::CreateAudioCodec(103, cricket::kRedCodecName, 48000, 2);
+  codec_3.SetParam(cricket::kCodecParamNotInNameValueFormat, "100/101");
+  // Identical codecs always match.
+  EXPECT_TRUE(MatchesWithReferenceAttributes(codec_1, codec_1));
+  EXPECT_TRUE(MatchesWithReferenceAttributes(codec_2, codec_2));
+  EXPECT_TRUE(MatchesWithReferenceAttributes(codec_3, codec_3));
+  // Mismatched reference codec lists.
+  EXPECT_FALSE(MatchesWithReferenceAttributes(codec_1, codec_2));
+  EXPECT_FALSE(MatchesWithReferenceAttributes(codec_1, codec_3));
+  EXPECT_FALSE(MatchesWithReferenceAttributes(codec_2, codec_3));
+  // Overflow of longer lists are ignored.
+  // Overlong list - overflow should be ignored.
+  Codec codec_4 =
+      cricket::CreateAudioCodec(103, cricket::kRedCodecName, 48000, 2);
+  codec_4.SetParam(cricket::kCodecParamNotInNameValueFormat, "100/100/101/102");
+  EXPECT_TRUE(MatchesWithReferenceAttributes(codec_4, codec_4));
+  EXPECT_TRUE(MatchesWithReferenceAttributes(codec_1, codec_4));
+  // Broken syntax will cause a non-match with anything except itself.
+  Codec codec_5 =
+      cricket::CreateAudioCodec(103, cricket::kRedCodecName, 48000, 2);
+  codec_5.SetParam(cricket::kCodecParamNotInNameValueFormat, "");
+  EXPECT_TRUE(MatchesWithReferenceAttributes(codec_5, codec_5));
+  EXPECT_FALSE(MatchesWithReferenceAttributes(codec_1, codec_5));
+}
+
+struct TestParams {
+  std::string name;
+  SdpVideoFormat codec1;
+  SdpVideoFormat codec2;
+  bool expected_result;
+};
+
+using IsSameRtpCodecTest = TestWithParam<TestParams>;
+
+TEST_P(IsSameRtpCodecTest, IsSameRtpCodec) {
+  TestParams param = GetParam();
+  Codec codec1 = cricket::CreateVideoCodec(param.codec1);
+  Codec codec2 = cricket::CreateVideoCodec(param.codec2);
+
+  EXPECT_EQ(IsSameRtpCodec(codec1, codec2.ToCodecParameters()),
+            param.expected_result);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    CodecTest,
+    IsSameRtpCodecTest,
+    ValuesIn<TestParams>({
+        {.name = "CodecWithDifferentName",
+         .codec1 = {"VP9", {}},
+         .codec2 = {"VP8", {}},
+         .expected_result = false},
+        {.name = "Vp8WithoutParameters",
+         .codec1 = {"vp8", {}},
+         .codec2 = {"VP8", {}},
+         .expected_result = true},
+        {.name = "Vp8WithSameParameters",
+         .codec1 = {"VP8", {{"x", "1"}}},
+         .codec2 = {"VP8", {{"x", "1"}}},
+         .expected_result = true},
+        {.name = "Vp8WithDifferentParameters",
+         .codec1 = {"VP8", {}},
+         .codec2 = {"VP8", {{"x", "1"}}},
+         .expected_result = false},
+        {.name = "Av1WithoutParameters",
+         .codec1 = {"AV1", {}},
+         .codec2 = {"AV1", {}},
+         .expected_result = true},
+        {.name = "Av1WithSameProfile",
+         .codec1 = {"AV1", SdpVideoFormat::AV1Profile0().parameters},
+         .codec2 = {"AV1", SdpVideoFormat::AV1Profile0().parameters},
+         .expected_result = true},
+        {.name = "Av1WithoutParametersTreatedAsProfile0",
+         .codec1 = {"AV1", SdpVideoFormat::AV1Profile0().parameters},
+         .codec2 = {"AV1", {}},
+         .expected_result = true},
+        {.name = "Av1WithoutProfileTreatedAsProfile0",
+         .codec1 = {"AV1", {{cricket::kAv1FmtpProfile, "0"}, {"x", "1"}}},
+         .codec2 = {"AV1", {{"x", "1"}}},
+         .expected_result = true},
+        {.name = "Av1WithDifferentProfile",
+         .codec1 = {"AV1", SdpVideoFormat::AV1Profile0().parameters},
+         .codec2 = {"AV1", SdpVideoFormat::AV1Profile1().parameters},
+         .expected_result = false},
+        {.name = "Av1WithDifferentParameters",
+         .codec1 = {"AV1", {{cricket::kAv1FmtpProfile, "0"}, {"x", "1"}}},
+         .codec2 = {"AV1", {{cricket::kAv1FmtpProfile, "0"}, {"x", "2"}}},
+         .expected_result = false},
+        {.name = "Vp9WithSameProfile",
+         .codec1 = {"VP9", SdpVideoFormat::VP9Profile0().parameters},
+         .codec2 = {"VP9", SdpVideoFormat::VP9Profile0().parameters},
+         .expected_result = true},
+        {.name = "Vp9WithoutProfileTreatedAsProfile0",
+         .codec1 = {"VP9", {{kVP9FmtpProfileId, "0"}, {"x", "1"}}},
+         .codec2 = {"VP9", {{"x", "1"}}},
+         .expected_result = true},
+        {.name = "Vp9WithDifferentProfile",
+         .codec1 = {"VP9", SdpVideoFormat::VP9Profile0().parameters},
+         .codec2 = {"VP9", SdpVideoFormat::VP9Profile1().parameters},
+         .expected_result = false},
+        {.name = "H264WithSamePacketizationMode",
+         .codec1 = {"H264", {{kH264FmtpPacketizationMode, "0"}}},
+         .codec2 = {"H264", {{kH264FmtpPacketizationMode, "0"}}},
+         .expected_result = true},
+        {.name = "H264WithoutPacketizationModeTreatedAsMode0",
+         .codec1 = {"H264", {{kH264FmtpPacketizationMode, "0"}, {"x", "1"}}},
+         .codec2 = {"H264", {{"x", "1"}}},
+         .expected_result = true},
+        {.name = "H264WithDifferentPacketizationMode",
+         .codec1 = {"H264", {{kH264FmtpPacketizationMode, "0"}}},
+         .codec2 = {"H264", {{kH264FmtpPacketizationMode, "1"}}},
+         .expected_result = false},
+#ifdef RTC_ENABLE_H265
+        {.name = "H265WithSameProfile",
+         .codec1 = {"H265",
+                    {{cricket::kH265FmtpProfileId, "1"},
+                     {cricket::kH265FmtpTierFlag, "0"},
+                     {cricket::kH265FmtpLevelId, "93"},
+                     {cricket::kH265FmtpTxMode, "SRST"}}},
+         .codec2 = {"H265",
+                    {{cricket::kH265FmtpProfileId, "1"},
+                     {cricket::kH265FmtpTierFlag, "0"},
+                     {cricket::kH265FmtpLevelId, "93"},
+                     {cricket::kH265FmtpTxMode, "SRST"}}},
+         .expected_result = true},
+        {.name = "H265WithoutParametersTreatedAsDefault",
+         .codec1 = {"H265",
+                    {{cricket::kH265FmtpProfileId, "1"},
+                     {cricket::kH265FmtpTierFlag, "0"},
+                     {cricket::kH265FmtpLevelId, "93"},
+                     {cricket::kH265FmtpTxMode, "SRST"}}},
+         .codec2 = {"H265", {}},
+         .expected_result = true},
+        {.name = "H265WithDifferentProfile",
+         .codec1 = {"H265",
+                    {{cricket::kH265FmtpProfileId, "1"},
+                     {cricket::kH265FmtpTierFlag, "0"},
+                     {cricket::kH265FmtpLevelId, "93"},
+                     {cricket::kH265FmtpTxMode, "SRST"}}},
+         .codec2 = {"H265",
+                    {{cricket::kH265FmtpProfileId, "1"},
+                     {cricket::kH265FmtpTierFlag, "1"},
+                     {cricket::kH265FmtpLevelId, "93"},
+                     {cricket::kH265FmtpTxMode, "SRST"}}},
+         .expected_result = false},
+#endif
+    }),
+    [](const testing::TestParamInfo<IsSameRtpCodecTest::ParamType>& info) {
+      return info.param.name;
+    });
+
+TEST(CodecTest, TestCodecMatches) {
+  // Test a codec with a static payload type.
+  Codec c0 = cricket::CreateAudioCodec(34, "A", 44100, 1);
+  EXPECT_TRUE(c0.Matches(cricket::CreateAudioCodec(34, "", 44100, 1)));
+  EXPECT_TRUE(c0.Matches(cricket::CreateAudioCodec(34, "", 44100, 0)));
+  EXPECT_TRUE(c0.Matches(cricket::CreateAudioCodec(34, "", 44100, 0)));
+  EXPECT_TRUE(c0.Matches(cricket::CreateAudioCodec(34, "", 0, 0)));
+  EXPECT_FALSE(c0.Matches(cricket::CreateAudioCodec(96, "A", 44100, 1)));
+  EXPECT_FALSE(c0.Matches(cricket::CreateAudioCodec(96, "", 44100, 1)));
+  EXPECT_FALSE(c0.Matches(cricket::CreateAudioCodec(95, "", 55100, 1)));
+  EXPECT_FALSE(c0.Matches(cricket::CreateAudioCodec(95, "", 44100, 1)));
+  EXPECT_FALSE(c0.Matches(cricket::CreateAudioCodec(95, "", 44100, 2)));
+  EXPECT_FALSE(c0.Matches(cricket::CreateAudioCodec(95, "", 55100, 2)));
+
+  // Test a codec with a dynamic payload type.
+  Codec c1 = cricket::CreateAudioCodec(96, "A", 44100, 1);
+  EXPECT_TRUE(c1.Matches(cricket::CreateAudioCodec(96, "A", 0, 0)));
+  EXPECT_TRUE(c1.Matches(cricket::CreateAudioCodec(97, "A", 0, 0)));
+  EXPECT_TRUE(c1.Matches(cricket::CreateAudioCodec(96, "a", 0, 0)));
+  EXPECT_TRUE(c1.Matches(cricket::CreateAudioCodec(97, "a", 0, 0)));
+  EXPECT_TRUE(c1.Matches(cricket::CreateAudioCodec(35, "a", 0, 0)));
+  EXPECT_TRUE(c1.Matches(cricket::CreateAudioCodec(42, "a", 0, 0)));
+  EXPECT_TRUE(c1.Matches(cricket::CreateAudioCodec(65, "a", 0, 0)));
+  EXPECT_FALSE(c1.Matches(cricket::CreateAudioCodec(95, "A", 0, 0)));
+  EXPECT_FALSE(c1.Matches(cricket::CreateAudioCodec(34, "A", 0, 0)));
+  EXPECT_FALSE(c1.Matches(cricket::CreateAudioCodec(96, "", 44100, 2)));
+  EXPECT_FALSE(c1.Matches(cricket::CreateAudioCodec(96, "A", 55100, 1)));
+
+  // Test a codec with a dynamic payload type, and auto bitrate.
+  Codec c2 = cricket::CreateAudioCodec(97, "A", 16000, 1);
+  // Use default bitrate.
+  EXPECT_TRUE(c2.Matches(cricket::CreateAudioCodec(97, "A", 16000, 1)));
+  EXPECT_TRUE(c2.Matches(cricket::CreateAudioCodec(97, "A", 16000, 0)));
+  // Use explicit bitrate.
+  EXPECT_TRUE(c2.Matches(cricket::CreateAudioCodec(97, "A", 16000, 1)));
+  // Backward compatibility with clients that might send "-1" (for default).
+  EXPECT_TRUE(c2.Matches(cricket::CreateAudioCodec(97, "A", 16000, 1)));
+
+  // Stereo doesn't match channels = 0.
+  Codec c3 = cricket::CreateAudioCodec(96, "A", 44100, 2);
+  EXPECT_TRUE(c3.Matches(cricket::CreateAudioCodec(96, "A", 44100, 2)));
+  EXPECT_FALSE(c3.Matches(cricket::CreateAudioCodec(96, "A", 44100, 1)));
+  EXPECT_FALSE(c3.Matches(cricket::CreateAudioCodec(96, "A", 44100, 0)));
+}
+
+TEST(CodecTest, TestOpusAudioCodecWithDifferentParameters) {
+  Codec opus_with_fec = cricket::CreateAudioCodec(96, "opus", 48000, 2);
+  opus_with_fec.params["useinbandfec"] = "1";
+  Codec opus_without_fec = cricket::CreateAudioCodec(96, "opus", 48000, 2);
+
+  EXPECT_TRUE(opus_with_fec != opus_without_fec);
+  // Matches does not compare parameters for audio.
+  EXPECT_TRUE(opus_with_fec.Matches(opus_without_fec));
+
+  webrtc::RtpCodecParameters rtp_opus_with_fec =
+      opus_with_fec.ToCodecParameters();
+  // MatchesRtpCodec takes parameters into account.
+  EXPECT_TRUE(opus_with_fec.MatchesRtpCodec(rtp_opus_with_fec));
+  EXPECT_FALSE(opus_without_fec.MatchesRtpCodec(rtp_opus_with_fec));
+}
+
+TEST(CodecTest, TestVideoCodecMatches) {
+  // Test a codec with a static payload type.
+  Codec c0 = cricket::CreateVideoCodec(34, "V");
+  EXPECT_TRUE(c0.Matches(cricket::CreateVideoCodec(34, "")));
+  EXPECT_FALSE(c0.Matches(cricket::CreateVideoCodec(96, "")));
+  EXPECT_FALSE(c0.Matches(cricket::CreateVideoCodec(96, "V")));
+
+  // Test a codec with a dynamic payload type.
+  Codec c1 = cricket::CreateVideoCodec(96, "V");
+  EXPECT_TRUE(c1.Matches(cricket::CreateVideoCodec(96, "V")));
+  EXPECT_TRUE(c1.Matches(cricket::CreateVideoCodec(97, "V")));
+  EXPECT_TRUE(c1.Matches(cricket::CreateVideoCodec(96, "v")));
+  EXPECT_TRUE(c1.Matches(cricket::CreateVideoCodec(97, "v")));
+  EXPECT_TRUE(c1.Matches(cricket::CreateVideoCodec(35, "v")));
+  EXPECT_TRUE(c1.Matches(cricket::CreateVideoCodec(42, "v")));
+  EXPECT_TRUE(c1.Matches(cricket::CreateVideoCodec(65, "v")));
+  EXPECT_FALSE(c1.Matches(cricket::CreateVideoCodec(96, "")));
+  EXPECT_FALSE(c1.Matches(cricket::CreateVideoCodec(95, "V")));
+  EXPECT_FALSE(c1.Matches(cricket::CreateVideoCodec(34, "V")));
+}
+
+TEST(CodecTest, TestVideoCodecMatchesWithDifferentPacketization) {
+  Codec c0 = cricket::CreateVideoCodec(100, cricket::kVp8CodecName);
+  Codec c1 = cricket::CreateVideoCodec(101, cricket::kVp8CodecName);
+  c1.packetization = "raw";
+
+  EXPECT_TRUE(c0.Matches(c1));
+  EXPECT_TRUE(c1.Matches(c0));
+}
+
+// AV1 codecs compare profile information.
+TEST(CodecTest, TestAV1CodecMatches) {
+  const char kProfile0[] = "0";
+  const char kProfile1[] = "1";
+  const char kProfile2[] = "2";
+
+  Codec c_no_profile = cricket::CreateVideoCodec(95, cricket::kAv1CodecName);
+  Codec c_profile0 = cricket::CreateVideoCodec(95, cricket::kAv1CodecName);
+  c_profile0.params[cricket::kAv1FmtpProfile] = kProfile0;
+  Codec c_profile1 = cricket::CreateVideoCodec(95, cricket::kAv1CodecName);
+  c_profile1.params[cricket::kAv1FmtpProfile] = kProfile1;
+  Codec c_profile2 = cricket::CreateVideoCodec(95, cricket::kAv1CodecName);
+  c_profile2.params[cricket::kAv1FmtpProfile] = kProfile2;
+
+  // An AV1 entry with no profile specified should be treated as profile-0.
+  EXPECT_TRUE(c_profile0.Matches(c_no_profile));
+
+  {
+    // Two AV1 entries without a profile specified are treated as duplicates.
+    Codec c_no_profile_eq =
+        cricket::CreateVideoCodec(95, cricket::kAv1CodecName);
+    EXPECT_TRUE(c_no_profile.Matches(c_no_profile_eq));
+  }
+
+  {
+    // Two AV1 entries with profile 0 specified are treated as duplicates.
+    Codec c_profile0_eq = cricket::CreateVideoCodec(95, cricket::kAv1CodecName);
+    c_profile0_eq.params[cricket::kAv1FmtpProfile] = kProfile0;
+    EXPECT_TRUE(c_profile0.Matches(c_profile0_eq));
+  }
+
+  {
+    // Two AV1 entries with profile 1 specified are treated as duplicates.
+    Codec c_profile1_eq = cricket::CreateVideoCodec(95, cricket::kAv1CodecName);
+    c_profile1_eq.params[cricket::kAv1FmtpProfile] = kProfile1;
+    EXPECT_TRUE(c_profile1.Matches(c_profile1_eq));
+  }
+
+  // AV1 entries with different profiles (0 and 1) are seen as distinct.
+  EXPECT_FALSE(c_profile0.Matches(c_profile1));
+  EXPECT_FALSE(c_no_profile.Matches(c_profile1));
+
+  // AV1 entries with different profiles (0 and 2) are seen as distinct.
+  EXPECT_FALSE(c_profile0.Matches(c_profile2));
+  EXPECT_FALSE(c_no_profile.Matches(c_profile2));
+}
+
+// VP9 codecs compare profile information.
+TEST(CodecTest, TestVP9CodecMatches) {
+  const char kProfile0[] = "0";
+  const char kProfile2[] = "2";
+
+  Codec c_no_profile = cricket::CreateVideoCodec(95, cricket::kVp9CodecName);
+  Codec c_profile0 = cricket::CreateVideoCodec(95, cricket::kVp9CodecName);
+  c_profile0.params[webrtc::kVP9FmtpProfileId] = kProfile0;
+
+  EXPECT_TRUE(c_profile0.Matches(c_no_profile));
+
+  {
+    Codec c_profile0_eq = cricket::CreateVideoCodec(95, cricket::kVp9CodecName);
+    c_profile0_eq.params[webrtc::kVP9FmtpProfileId] = kProfile0;
+    EXPECT_TRUE(c_profile0.Matches(c_profile0_eq));
+  }
+
+  {
+    Codec c_profile2 = cricket::CreateVideoCodec(95, cricket::kVp9CodecName);
+    c_profile2.params[webrtc::kVP9FmtpProfileId] = kProfile2;
+    EXPECT_FALSE(c_profile0.Matches(c_profile2));
+    EXPECT_FALSE(c_no_profile.Matches(c_profile2));
+  }
+
+  {
+    Codec c_no_profile_eq =
+        cricket::CreateVideoCodec(95, cricket::kVp9CodecName);
+    EXPECT_TRUE(c_no_profile.Matches(c_no_profile_eq));
+  }
+}
+
+// Matching H264 codecs also need to have matching profile-level-id and
+// packetization-mode.
+TEST(CodecTest, TestH264CodecMatches) {
+  const char kProfileLevelId1[] = "42e01f";
+  const char kProfileLevelId2[] = "42a01e";
+  const char kProfileLevelId3[] = "42e01e";
+
+  Codec pli_1_pm_0 = cricket::CreateVideoCodec(95, "H264");
+  pli_1_pm_0.params[cricket::kH264FmtpProfileLevelId] = kProfileLevelId1;
+  pli_1_pm_0.params[cricket::kH264FmtpPacketizationMode] = "0";
+
+  {
+    Codec pli_1_pm_blank = cricket::CreateVideoCodec(95, "H264");
+    pli_1_pm_blank.params[cricket::kH264FmtpProfileLevelId] = kProfileLevelId1;
+    pli_1_pm_blank.params.erase(
+        pli_1_pm_blank.params.find(cricket::kH264FmtpPacketizationMode));
+
+    // Matches since if packetization-mode is not specified it defaults to "0".
+    EXPECT_TRUE(pli_1_pm_0.Matches(pli_1_pm_blank));
+
+    // MatchesRtpCodec does exact comparison of parameters.
+    EXPECT_FALSE(
+        pli_1_pm_0.MatchesRtpCodec(pli_1_pm_blank.ToCodecParameters()));
+  }
+
+  {
+    Codec pli_1_pm_1 = cricket::CreateVideoCodec(95, "H264");
+    pli_1_pm_1.params[cricket::kH264FmtpProfileLevelId] = kProfileLevelId1;
+    pli_1_pm_1.params[cricket::kH264FmtpPacketizationMode] = "1";
+
+    // Does not match since packetization-mode is different.
+    EXPECT_FALSE(pli_1_pm_0.Matches(pli_1_pm_1));
+
+    EXPECT_FALSE(pli_1_pm_0.MatchesRtpCodec(pli_1_pm_1.ToCodecParameters()));
+  }
+
+  {
+    Codec pli_2_pm_0 = cricket::CreateVideoCodec(95, "H264");
+    pli_2_pm_0.params[cricket::kH264FmtpProfileLevelId] = kProfileLevelId2;
+    pli_2_pm_0.params[cricket::kH264FmtpPacketizationMode] = "0";
+
+    // Does not match since profile-level-id is different.
+    EXPECT_FALSE(pli_1_pm_0.Matches(pli_2_pm_0));
+
+    EXPECT_FALSE(pli_1_pm_0.MatchesRtpCodec(pli_2_pm_0.ToCodecParameters()));
+  }
+
+  {
+    Codec pli_3_pm_0_asym = cricket::CreateVideoCodec(95, "H264");
+    pli_3_pm_0_asym.params[cricket::kH264FmtpProfileLevelId] = kProfileLevelId3;
+    pli_3_pm_0_asym.params[cricket::kH264FmtpPacketizationMode] = "0";
+
+    // Does match, profile-level-id is different but the level is not compared.
+    // and the profile matches.
+    EXPECT_TRUE(pli_1_pm_0.Matches(pli_3_pm_0_asym));
+
+    EXPECT_FALSE(
+        pli_1_pm_0.MatchesRtpCodec(pli_3_pm_0_asym.ToCodecParameters()));
+
+    //
+  }
+}
+
+#ifdef RTC_ENABLE_H265
+// Matching H.265 codecs should have matching profile/tier/level and tx-mode.
+TEST(CodecTest, TestH265CodecMatches) {
+  constexpr char kProfile1[] = "1";
+  constexpr char kTier1[] = "1";
+  constexpr char kLevel3_1[] = "93";
+  constexpr char kLevel4[] = "120";
+  constexpr char kTxMrst[] = "MRST";
+
+  Codec c_ptl_blank = cricket::CreateVideoCodec(95, cricket::kH265CodecName);
+
+  {
+    Codec c_profile_1 = cricket::CreateVideoCodec(95, cricket::kH265CodecName);
+    c_profile_1.params[cricket::kH265FmtpProfileId] = kProfile1;
+
+    // Matches since profile-id unspecified defaults to "1".
+    EXPECT_TRUE(c_ptl_blank.Matches(c_profile_1));
+  }
+
+  {
+    Codec c_tier_flag_1 =
+        cricket::CreateVideoCodec(95, cricket::kH265CodecName);
+    c_tier_flag_1.params[cricket::kH265FmtpTierFlag] = kTier1;
+
+    // Does not match since profile-space unspecified defaults to "0".
+    EXPECT_FALSE(c_ptl_blank.Matches(c_tier_flag_1));
+  }
+
+  {
+    Codec c_level_id_3_1 =
+        cricket::CreateVideoCodec(95, cricket::kH265CodecName);
+    c_level_id_3_1.params[cricket::kH265FmtpLevelId] = kLevel3_1;
+
+    // Matches since level-id unspecified defaults to "93".
+    EXPECT_TRUE(c_ptl_blank.Matches(c_level_id_3_1));
+  }
+
+  {
+    Codec c_level_id_4 = cricket::CreateVideoCodec(95, cricket::kH265CodecName);
+    c_level_id_4.params[cricket::kH265FmtpLevelId] = kLevel4;
+
+    // Matches since we ignore level-id when matching H.265 codecs.
+    EXPECT_TRUE(c_ptl_blank.Matches(c_level_id_4));
+  }
+
+  {
+    Codec c_tx_mode_mrst =
+        cricket::CreateVideoCodec(95, cricket::kH265CodecName);
+    c_tx_mode_mrst.params[cricket::kH265FmtpTxMode] = kTxMrst;
+
+    // Does not match since tx-mode implies to "SRST" and must be not specified
+    // when it is the only mode supported:
+    // https://datatracker.ietf.org/doc/html/draft-ietf-avtcore-hevc-webrtc
+    EXPECT_FALSE(c_ptl_blank.Matches(c_tx_mode_mrst));
+  }
+}
+#endif
+
+TEST(CodecTest, TestMatchesRtpCodecRtx) {
+  const Codec rtx_codec_1 = cricket::CreateVideoRtxCodec(96, 120);
+  const Codec rtx_codec_2 = cricket::CreateVideoRtxCodec(96, 121);
+  EXPECT_TRUE(rtx_codec_1.Matches(rtx_codec_2));
+  // MatchesRtpCodec ignores the different associated payload type (apt) for
+  // RTX.
+  EXPECT_TRUE(rtx_codec_1.MatchesRtpCodec(rtx_codec_2.ToCodecParameters()));
+}
+
+}  // namespace webrtc
index 84aa9ff0c1f6b22dd3c5d1d4eee1b96b4ed2b657..c13cf1aca06f0f596a9eca6c07b5e5830004b612 100644 (file)
@@ -420,8 +420,8 @@ TEST(CodecTest, TestH265CodecMatches) {
     Codec c_level_id_4 = cricket::CreateVideoCodec(95, cricket::kH265CodecName);
     c_level_id_4.params[cricket::kH265FmtpLevelId] = kLevel4;
 
-    // Does not match since different level-ids are specified.
-    EXPECT_FALSE(c_ptl_blank.Matches(c_level_id_4));
+    // Matches since we ignore level-id when matching H.265 codecs.
+    EXPECT_TRUE(c_ptl_blank.Matches(c_level_id_4));
   }
 
   {
index 52416c964d8ec55ed894b4020e2f61010dca5211..97715be66ff30881de1df48c183080854878fd0b 100644 (file)
@@ -1047,6 +1047,8 @@ std::vector<VideoCodecSettings> WebRtcVideoSendChannel::SelectSendVideoCodecs(
       // following the spec in https://tools.ietf.org/html/rfc6184#section-8.2.2
       // since we should limit the encode level to the lower of local and remote
       // level when level asymmetry is not allowed.
+      // For H.265, the level asymmetry is implicitly allowed. We need to make
+      // sure the encode level is set to the remote offered level.
       if (format_it->IsSameCodec(
               {remote_codec.codec.name, remote_codec.codec.params})) {
         encoders.push_back(remote_codec);
index 9287155abd8558192095ea04d083ccfc70bf2cd4..38f9a7754b137f16b186680bc363c542fcebe58b 100644 (file)
@@ -30,6 +30,7 @@
 #include "api/rtp_parameters.h"
 #include "api/rtp_transceiver_direction.h"
 #include "media/base/codec.h"
+#include "media/base/codec_comparators.h"
 #include "media/base/media_constants.h"
 #include "media/base/media_engine.h"
 #include "media/base/rid_description.h"
@@ -437,15 +438,6 @@ RTCError CreateMediaContentOffer(
                             offer);
 }
 
-bool ReferencedCodecsMatch(const std::vector<Codec>& codecs1,
-                           const int codec1_id,
-                           const std::vector<Codec>& codecs2,
-                           const int codec2_id) {
-  const Codec* codec1 = FindCodecById(codecs1, codec1_id);
-  const Codec* codec2 = FindCodecById(codecs2, codec2_id);
-  return codec1 != nullptr && codec2 != nullptr && codec1->Matches(*codec2);
-}
-
 void NegotiatePacketization(const Codec& local_codec,
                             const Codec& remote_codec,
                             Codec* negotiated_codec) {
@@ -465,97 +457,13 @@ void NegotiateTxMode(const Codec& local_codec,
 }
 #endif
 
-// Finds a codec in `codecs2` that matches `codec_to_match`, which is
-// a member of `codecs1`. If `codec_to_match` is an RED or RTX codec, both
-// the codecs themselves and their associated codecs must match.
-std::optional<Codec> FindMatchingCodec(const std::vector<Codec>& codecs1,
-                                       const std::vector<Codec>& codecs2,
-                                       const Codec& codec_to_match) {
-  // `codec_to_match` should be a member of `codecs1`, in order to look up
-  // RED/RTX codecs' associated codecs correctly. If not, that's a programming
-  // error.
-  RTC_DCHECK(absl::c_any_of(codecs1, [&codec_to_match](const Codec& codec) {
-    return &codec == &codec_to_match;
-  }));
-  for (const Codec& potential_match : codecs2) {
-    if (potential_match.Matches(codec_to_match)) {
-      if (codec_to_match.GetResiliencyType() == Codec::ResiliencyType::kRtx) {
-        int apt_value_1 = 0;
-        int apt_value_2 = 0;
-        if (!codec_to_match.GetParam(kCodecParamAssociatedPayloadType,
-                                     &apt_value_1) ||
-            !potential_match.GetParam(kCodecParamAssociatedPayloadType,
-                                      &apt_value_2)) {
-          RTC_LOG(LS_WARNING) << "RTX missing associated payload type.";
-          continue;
-        }
-        if (!ReferencedCodecsMatch(codecs1, apt_value_1, codecs2,
-                                   apt_value_2)) {
-          continue;
-        }
-      } else if (codec_to_match.GetResiliencyType() ==
-                 Codec::ResiliencyType::kRed) {
-        auto red_parameters_1 =
-            codec_to_match.params.find(kCodecParamNotInNameValueFormat);
-        auto red_parameters_2 =
-            potential_match.params.find(kCodecParamNotInNameValueFormat);
-        bool has_parameters_1 = red_parameters_1 != codec_to_match.params.end();
-        bool has_parameters_2 =
-            red_parameters_2 != potential_match.params.end();
-        if (has_parameters_1 && has_parameters_2) {
-          // Mixed reference codecs (i.e. 111/112) are not supported.
-          // Different levels of redundancy between offer and answer are
-          // since RED is considered to be declarative.
-          std::vector<absl::string_view> redundant_payloads_1 =
-              rtc::split(red_parameters_1->second, '/');
-          std::vector<absl::string_view> redundant_payloads_2 =
-              rtc::split(red_parameters_2->second, '/');
-          if (redundant_payloads_1.size() > 0 &&
-              redundant_payloads_2.size() > 0) {
-            bool consistent = true;
-            for (size_t i = 1; i < redundant_payloads_1.size(); i++) {
-              if (redundant_payloads_1[i] != redundant_payloads_1[0]) {
-                consistent = false;
-                break;
-              }
-            }
-            for (size_t i = 1; i < redundant_payloads_2.size(); i++) {
-              if (redundant_payloads_2[i] != redundant_payloads_2[0]) {
-                consistent = false;
-                break;
-              }
-            }
-            if (!consistent) {
-              continue;
-            }
-
-            int red_value_1;
-            int red_value_2;
-            if (rtc::FromString(redundant_payloads_1[0], &red_value_1) &&
-                rtc::FromString(redundant_payloads_2[0], &red_value_2)) {
-              if (!ReferencedCodecsMatch(codecs1, red_value_1, codecs2,
-                                         red_value_2)) {
-                continue;
-              }
-            }
-          }
-        } else if (has_parameters_1 != has_parameters_2) {
-          continue;
-        }
-      }
-      return potential_match;
-    }
-  }
-  return std::nullopt;
-}
-
 void NegotiateCodecs(const std::vector<Codec>& local_codecs,
                      const std::vector<Codec>& offered_codecs,
                      std::vector<Codec>* negotiated_codecs,
                      bool keep_offer_order) {
   for (const Codec& ours : local_codecs) {
     std::optional<Codec> theirs =
-        FindMatchingCodec(local_codecs, offered_codecs, ours);
+        webrtc::FindMatchingCodec(local_codecs, offered_codecs, ours);
     // Note that we intentionally only find one matching codec for each of our
     // local codecs, in case the remote offer contains duplicate codecs.
     if (theirs) {
@@ -565,7 +473,8 @@ void NegotiateCodecs(const std::vector<Codec>& local_codecs,
       if (negotiated.GetResiliencyType() == Codec::ResiliencyType::kRtx) {
         const auto apt_it =
             theirs->params.find(kCodecParamAssociatedPayloadType);
-        // FindMatchingCodec shouldn't return something with no apt value.
+        // webrtc::FindMatchingCodec shouldn't return something with no apt
+        // value.
         RTC_DCHECK(apt_it != theirs->params.end());
         negotiated.SetParam(kCodecParamAssociatedPayloadType, apt_it->second);
 
@@ -694,8 +603,8 @@ void MergeCodecs(const std::vector<Codec>& reference_codecs,
   for (const Codec& reference_codec : reference_codecs) {
     if (reference_codec.GetResiliencyType() != Codec::ResiliencyType::kRtx &&
         reference_codec.GetResiliencyType() != Codec::ResiliencyType::kRed &&
-        !FindMatchingCodec(reference_codecs, *offered_codecs,
-                           reference_codec)) {
+        !webrtc::FindMatchingCodec(reference_codecs, *offered_codecs,
+                                   reference_codec)) {
       Codec codec = reference_codec;
       used_pltypes->FindAndSetIdUsed(&codec);
       offered_codecs->push_back(codec);
@@ -705,8 +614,8 @@ void MergeCodecs(const std::vector<Codec>& reference_codecs,
   // Add all new RTX or RED codecs.
   for (const Codec& reference_codec : reference_codecs) {
     if (reference_codec.GetResiliencyType() == Codec::ResiliencyType::kRtx &&
-        !FindMatchingCodec(reference_codecs, *offered_codecs,
-                           reference_codec)) {
+        !webrtc::FindMatchingCodec(reference_codecs, *offered_codecs,
+                                   reference_codec)) {
       Codec rtx_codec = reference_codec;
       const Codec* associated_codec =
           GetAssociatedCodecForRtx(reference_codecs, rtx_codec);
@@ -715,7 +624,7 @@ void MergeCodecs(const std::vector<Codec>& reference_codecs,
       }
       // Find a codec in the offered list that matches the reference codec.
       // Its payload type may be different than the reference codec.
-      std::optional<Codec> matching_codec = FindMatchingCodec(
+      std::optional<Codec> matching_codec = webrtc::FindMatchingCodec(
           reference_codecs, *offered_codecs, *associated_codec);
       if (!matching_codec) {
         RTC_LOG(LS_WARNING)
@@ -729,13 +638,13 @@ void MergeCodecs(const std::vector<Codec>& reference_codecs,
       offered_codecs->push_back(rtx_codec);
     } else if (reference_codec.GetResiliencyType() ==
                    Codec::ResiliencyType::kRed &&
-               !FindMatchingCodec(reference_codecs, *offered_codecs,
-                                  reference_codec)) {
+               !webrtc::FindMatchingCodec(reference_codecs, *offered_codecs,
+                                          reference_codec)) {
       Codec red_codec = reference_codec;
       const Codec* associated_codec =
           GetAssociatedCodecForRed(reference_codecs, red_codec);
       if (associated_codec) {
-        std::optional<Codec> matching_codec = FindMatchingCodec(
+        std::optional<Codec> matching_codec = webrtc::FindMatchingCodec(
             reference_codecs, *offered_codecs, *associated_codec);
         if (!matching_codec) {
           RTC_LOG(LS_WARNING) << "Couldn't find matching "
@@ -788,7 +697,7 @@ std::vector<Codec> MatchCodecPreference(
 
     if (found_codec != supported_codecs.end()) {
       std::optional<Codec> found_codec_with_correct_pt =
-          FindMatchingCodec(supported_codecs, codecs, *found_codec);
+          webrtc::FindMatchingCodec(supported_codecs, codecs, *found_codec);
       if (found_codec_with_correct_pt) {
         // RED may already have been added if its primary codec is before RED
         // in the codec list.
@@ -1191,7 +1100,7 @@ webrtc::RTCErrorOr<Codecs> GetNegotiatedCodecsForOffer(
       }
       const MediaContentDescription* mcd = current_content->media_description();
       for (const Codec& codec : mcd->codecs()) {
-        if (FindMatchingCodec(mcd->codecs(), codecs, codec)) {
+        if (webrtc::FindMatchingCodec(mcd->codecs(), codecs, codec)) {
           filtered_codecs.push_back(codec);
         }
       }
@@ -1199,9 +1108,9 @@ webrtc::RTCErrorOr<Codecs> GetNegotiatedCodecsForOffer(
     // Add other supported codecs.
     for (const Codec& codec : supported_codecs) {
       std::optional<Codec> found_codec =
-          FindMatchingCodec(supported_codecs, codecs, codec);
-      if (found_codec &&
-          !FindMatchingCodec(supported_codecs, filtered_codecs, codec)) {
+          webrtc::FindMatchingCodec(supported_codecs, codecs, codec);
+      if (found_codec && !webrtc::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.
@@ -1214,8 +1123,9 @@ webrtc::RTCErrorOr<Codecs> GetNegotiatedCodecsForOffer(
           RTC_DCHECK(referenced_codec);
 
           // Find the codec we should be referencing and point to it.
-          std::optional<Codec> changed_referenced_codec = FindMatchingCodec(
-              supported_codecs, filtered_codecs, *referenced_codec);
+          std::optional<Codec> changed_referenced_codec =
+              webrtc::FindMatchingCodec(supported_codecs, filtered_codecs,
+                                        *referenced_codec);
           if (changed_referenced_codec) {
             found_codec->SetParam(kCodecParamAssociatedPayloadType,
                                   changed_referenced_codec->id);
@@ -1267,7 +1177,7 @@ webrtc::RTCErrorOr<Codecs> GetNegotiatedCodecsForAnswer(
       }
       const MediaContentDescription* mcd = current_content->media_description();
       for (const Codec& codec : mcd->codecs()) {
-        if (FindMatchingCodec(mcd->codecs(), codecs, codec)) {
+        if (webrtc::FindMatchingCodec(mcd->codecs(), codecs, codec)) {
           filtered_codecs.push_back(codec);
         }
       }
@@ -1275,8 +1185,9 @@ webrtc::RTCErrorOr<Codecs> GetNegotiatedCodecsForAnswer(
     // Add other supported video codecs.
     std::vector<Codec> other_codecs;
     for (const Codec& codec : supported_codecs) {
-      if (FindMatchingCodec(supported_codecs, codecs, codec) &&
-          !FindMatchingCodec(supported_codecs, filtered_codecs, codec)) {
+      if (webrtc::FindMatchingCodec(supported_codecs, codecs, codec) &&
+          !webrtc::FindMatchingCodec(supported_codecs, filtered_codecs,
+                                     codec)) {
         // We should use the local codec with local parameters and the codec id
         // would be correctly mapped in `NegotiateCodecs`.
         other_codecs.push_back(codec);
@@ -1882,20 +1793,22 @@ void MediaSessionDescriptionFactory::GetCodecsForAnswer(
     if (IsMediaContentOfType(&content, MEDIA_TYPE_AUDIO)) {
       std::vector<Codec> offered_codecs = content.media_description()->codecs();
       for (const Codec& offered_audio_codec : offered_codecs) {
-        if (!FindMatchingCodec(offered_codecs, filtered_offered_audio_codecs,
-                               offered_audio_codec) &&
-            FindMatchingCodec(offered_codecs, all_audio_codecs_,
-                              offered_audio_codec)) {
+        if (!webrtc::FindMatchingCodec(offered_codecs,
+                                       filtered_offered_audio_codecs,
+                                       offered_audio_codec) &&
+            webrtc::FindMatchingCodec(offered_codecs, all_audio_codecs_,
+                                      offered_audio_codec)) {
           filtered_offered_audio_codecs.push_back(offered_audio_codec);
         }
       }
     } else if (IsMediaContentOfType(&content, MEDIA_TYPE_VIDEO)) {
       std::vector<Codec> offered_codecs = content.media_description()->codecs();
       for (const Codec& offered_video_codec : offered_codecs) {
-        if (!FindMatchingCodec(offered_codecs, filtered_offered_video_codecs,
-                               offered_video_codec) &&
-            FindMatchingCodec(offered_codecs, all_video_codecs_,
-                              offered_video_codec)) {
+        if (!webrtc::FindMatchingCodec(offered_codecs,
+                                       filtered_offered_video_codecs,
+                                       offered_video_codec) &&
+            webrtc::FindMatchingCodec(offered_codecs, all_video_codecs_,
+                                      offered_video_codec)) {
           filtered_offered_video_codecs.push_back(offered_video_codec);
         }
       }
@@ -2413,14 +2326,16 @@ void MediaSessionDescriptionFactory::ComputeAudioCodecsIntersectionAndUnion() {
   // Compute the audio codecs union.
   for (const Codec& send : audio_send_codecs_) {
     all_audio_codecs_.push_back(send);
-    if (!FindMatchingCodec(audio_send_codecs_, audio_recv_codecs_, send)) {
+    if (!webrtc::FindMatchingCodec(audio_send_codecs_, audio_recv_codecs_,
+                                   send)) {
       // It doesn't make sense to have an RTX codec we support sending but not
       // receiving.
       RTC_DCHECK(send.GetResiliencyType() != Codec::ResiliencyType::kRtx);
     }
   }
   for (const Codec& recv : audio_recv_codecs_) {
-    if (!FindMatchingCodec(audio_recv_codecs_, audio_send_codecs_, recv)) {
+    if (!webrtc::FindMatchingCodec(audio_recv_codecs_, audio_send_codecs_,
+                                   recv)) {
       all_audio_codecs_.push_back(recv);
     }
   }
index d9093f4c4b66ea31562dd2e5d18edc93e1c5ea8a..cdc9d0e22e44e9891eb26a12acf740452ca4f65d 100644 (file)
 #include <algorithm>
 #include <functional>
 #include <iterator>
-#include <map>
 #include <memory>
 #include <optional>
 #include <set>
 #include <string>
 #include <tuple>
-#include <type_traits>
 #include <utility>
 #include <vector>
 
 #include "absl/algorithm/container.h"
-#include "api/audio_options.h"
 #include "api/jsep.h"
 #include "api/media_types.h"
 #include "api/peer_connection_interface.h"
 #include "api/rtc_error.h"
 #include "api/rtc_event_log/rtc_event_log_factory.h"
-#include "api/rtc_event_log/rtc_event_log_factory_interface.h"
 #include "api/rtp_parameters.h"
 #include "api/rtp_sender_interface.h"
 #include "api/rtp_transceiver_direction.h"
 #include "api/rtp_transceiver_interface.h"
 #include "api/scoped_refptr.h"
 #include "api/task_queue/default_task_queue_factory.h"
-#include "api/task_queue/task_queue_factory.h"
 #include "media/base/codec.h"
 #include "media/base/fake_media_engine.h"
 #include "media/base/media_channel.h"
 #include "media/base/media_constants.h"
 #include "media/base/media_engine.h"
 #include "media/base/stream_params.h"
+#include "p2p/base/basic_packet_socket_factory.h"
 #include "p2p/base/fake_port_allocator.h"
 #include "p2p/base/p2p_constants.h"
-#include "p2p/base/port_allocator.h"
 #include "p2p/base/transport_info.h"
 #include "pc/channel_interface.h"
 #include "pc/media_session.h"
 #include "pc/test/enable_fake_media.h"
 #include "pc/test/mock_peer_connection_observers.h"
 #include "rtc_base/checks.h"
-#include "rtc_base/rtc_certificate_generator.h"
+#include "rtc_base/ref_counted_object.h"
 #include "rtc_base/thread.h"
 #include "test/gtest.h"
-#include "test/scoped_key_value_config.h"
 #ifdef WEBRTC_ANDROID
 #include "pc/test/android_test_initializer.h"
 #endif
+#include "api/test/rtc_error_matchers.h"
 #include "rtc_base/virtual_socket_server.h"
 #include "test/gmock.h"
 
@@ -176,7 +171,6 @@ class PeerConnectionMediaBaseTest : public ::testing::Test {
     EnableFakeMedia(factory_dependencies, std::move(media_engine));
     factory_dependencies.event_log_factory =
         std::make_unique<RtcEventLogFactory>();
-    factory_dependencies.trials = std::move(field_trials_);
     auto pc_factory =
         CreateModularPeerConnectionFactory(std::move(factory_dependencies));
 
@@ -253,7 +247,6 @@ class PeerConnectionMediaBaseTest : public ::testing::Test {
     return sdp_semantics_ == SdpSemantics::kUnifiedPlan;
   }
 
-  std::unique_ptr<test::ScopedKeyValueConfig> field_trials_;
   std::unique_ptr<rtc::VirtualSocketServer> vss_;
   rtc::AutoSocketServerThread main_;
   const SdpSemantics sdp_semantics_;
@@ -278,29 +271,6 @@ class PeerConnectionMediaTestPlanB : public PeerConnectionMediaBaseTest {
       : PeerConnectionMediaBaseTest(SdpSemantics::kPlanB_DEPRECATED) {}
 };
 
-TEST_P(PeerConnectionMediaTest,
-       FailToSetRemoteDescriptionIfCreateMediaChannelFails) {
-  auto caller = CreatePeerConnectionWithAudioVideo();
-  auto callee = CreatePeerConnectionWithAudioVideo();
-  callee->media_engine()->set_fail_create_channel(true);
-
-  std::string error;
-  ASSERT_FALSE(callee->SetRemoteDescription(caller->CreateOffer(), &error));
-  EXPECT_THAT(error,
-              HasSubstr("Failed to set remote offer sdp: Failed to create"));
-}
-
-TEST_P(PeerConnectionMediaTest,
-       FailToSetLocalDescriptionIfCreateMediaChannelFails) {
-  auto caller = CreatePeerConnectionWithAudioVideo();
-  caller->media_engine()->set_fail_create_channel(true);
-
-  std::string error;
-  ASSERT_FALSE(caller->SetLocalDescription(caller->CreateOffer(), &error));
-  EXPECT_THAT(error,
-              HasSubstr("Failed to set local offer sdp: Failed to create"));
-}
-
 std::vector<std::string> GetIds(
     const std::vector<cricket::StreamParams>& streams) {
   std::vector<std::string> ids;
@@ -1068,7 +1038,7 @@ TEST_P(PeerConnectionMediaInvalidMediaTest, FailToSetLocalAnswer) {
 void RemoveVideoContentAndUnbundle(cricket::SessionDescription* desc) {
   // Removing BUNDLE is easier than removing the content in there.
   desc->RemoveGroupByName("BUNDLE");
-  auto content_name = cricket::GetFirstVideoContent(desc)->name;
+  auto content_name = cricket::GetFirstVideoContent(desc)->mid();
   desc->RemoveContentByName(content_name);
   desc->RemoveTransportInfoByName(content_name);
 }
@@ -1077,9 +1047,9 @@ void RenameVideoContentAndUnbundle(cricket::SessionDescription* desc) {
   // Removing BUNDLE is easier than renaming the content in there.
   desc->RemoveGroupByName("BUNDLE");
   auto* video_content = cricket::GetFirstVideoContent(desc);
-  auto* transport_info = desc->GetTransportInfoByName(video_content->name);
-  video_content->name = "video_renamed";
-  transport_info->content_name = video_content->name;
+  auto* transport_info = desc->GetTransportInfoByName(video_content->mid());
+  video_content->set_mid("video_renamed");
+  transport_info->content_name = video_content->mid();
 }
 
 void ReverseMediaContent(cricket::SessionDescription* desc) {
@@ -1088,7 +1058,7 @@ void ReverseMediaContent(cricket::SessionDescription* desc) {
 }
 
 void ChangeMediaTypeAudioToVideo(cricket::SessionDescription* desc) {
-  std::string audio_mid = cricket::GetFirstAudioContent(desc)->name;
+  auto audio_mid = cricket::GetFirstAudioContent(desc)->mid();
   desc->RemoveContentByName(audio_mid);
   auto* video_content = cricket::GetFirstVideoContent(desc);
   desc->AddContent(audio_mid, video_content->type,
@@ -1168,8 +1138,8 @@ void RenameContent(cricket::SessionDescription* desc,
                    const std::string& new_name) {
   auto* content = cricket::GetFirstMediaContent(desc, media_type);
   RTC_DCHECK(content);
-  std::string old_name = content->name;
-  content->name = new_name;
+  std::string old_name(content->mid());
+  content->set_mid(new_name);
   auto* transport = desc->GetTransportInfoByName(old_name);
   RTC_DCHECK(transport);
   transport->content_name = new_name;
@@ -1198,9 +1168,9 @@ TEST_P(PeerConnectionMediaTest, AnswerHasSameMidsAsOffer) {
 
   auto answer = callee->CreateAnswer();
   EXPECT_EQ(kAudioMid,
-            cricket::GetFirstAudioContent(answer->description())->name);
+            cricket::GetFirstAudioContent(answer->description())->mid());
   EXPECT_EQ(kVideoMid,
-            cricket::GetFirstVideoContent(answer->description())->name);
+            cricket::GetFirstVideoContent(answer->description())->mid());
 }
 
 // Test that if the callee creates a re-offer, the MIDs are the same as the
@@ -1220,9 +1190,9 @@ TEST_P(PeerConnectionMediaTest, ReOfferHasSameMidsAsFirstOffer) {
 
   auto reoffer = callee->CreateOffer();
   EXPECT_EQ(kAudioMid,
-            cricket::GetFirstAudioContent(reoffer->description())->name);
+            cricket::GetFirstAudioContent(reoffer->description())->mid());
   EXPECT_EQ(kVideoMid,
-            cricket::GetFirstVideoContent(reoffer->description())->name);
+            cricket::GetFirstVideoContent(reoffer->description())->mid());
 }
 
 // Test that SetRemoteDescription returns an error if there are two m= sections
@@ -1543,8 +1513,9 @@ TEST_F(PeerConnectionMediaTestUnifiedPlan,
                     return codec.name.find("_only_") != std::string::npos;
                   });
 
-  auto result = transceiver->SetCodecPreferences(codecs);
-  EXPECT_EQ(RTCErrorType::INVALID_MODIFICATION, result.type());
+  // This is OK, however because the codec is send-only and the transciever is
+  // not send-only, it would get filtered out during negotiation.
+  EXPECT_THAT(transceiver->SetCodecPreferences(codecs), IsRtcOk());
 }
 
 TEST_F(PeerConnectionMediaTestUnifiedPlan,
@@ -1611,10 +1582,8 @@ TEST_F(PeerConnectionMediaTestUnifiedPlan, SetCodecPreferencesAllAudioCodecs) {
   // Normal case, set all capabilities as preferences
   EXPECT_TRUE(audio_transceiver->SetCodecPreferences(sender_audio_codecs).ok());
   auto offer = caller->CreateOffer();
-  auto codecs = offer->description()
-                    ->contents()[0]
-                    .media_description()
-                    ->codecs();
+  auto codecs =
+      offer->description()->contents()[0].media_description()->codecs();
   EXPECT_TRUE(CompareCodecs(sender_audio_codecs, codecs));
 }
 
@@ -1633,32 +1602,11 @@ TEST_F(PeerConnectionMediaTestUnifiedPlan,
   // Normal case, reset codec preferences
   EXPECT_TRUE(audio_transceiver->SetCodecPreferences(empty_codecs).ok());
   auto offer = caller->CreateOffer();
-  auto codecs = offer->description()
-                    ->contents()[0]
-                    .media_description()
-                    ->codecs();
+  auto codecs =
+      offer->description()->contents()[0].media_description()->codecs();
   EXPECT_TRUE(CompareCodecs(sender_audio_codecs, codecs));
 }
 
-TEST_F(PeerConnectionMediaTestUnifiedPlan,
-       SetCodecPreferencesAudioSendOnlyKillswitch) {
-  field_trials_ = std::make_unique<test::ScopedKeyValueConfig>(
-      "WebRTC-SetCodecPreferences-ReceiveOnlyFilterInsteadOfThrow/Disabled/");
-  auto fake_engine = std::make_unique<FakeMediaEngine>();
-  auto send_codecs = fake_engine->voice().send_codecs();
-  send_codecs.push_back(cricket::CreateAudioCodec(send_codecs.back().id + 1,
-                                                  "send_only_codec", 0, 1));
-  fake_engine->SetAudioSendCodecs(send_codecs);
-
-  auto caller = CreatePeerConnectionWithAudio(std::move(fake_engine));
-
-  auto transceiver = caller->pc()->GetTransceivers().front();
-  auto send_capabilities = caller->pc_factory()->GetRtpSenderCapabilities(
-      cricket::MediaType::MEDIA_TYPE_AUDIO);
-
-  EXPECT_TRUE(transceiver->SetCodecPreferences(send_capabilities.codecs).ok());
-}
-
 TEST_F(PeerConnectionMediaTestUnifiedPlan,
        SetCodecPreferencesVideoRejectsAudioCodec) {
   auto caller = CreatePeerConnectionWithVideo();
@@ -1723,10 +1671,8 @@ TEST_F(PeerConnectionMediaTestUnifiedPlan, SetCodecPreferencesAllVideoCodecs) {
   // Normal case, setting preferences to normal capabilities
   EXPECT_TRUE(video_transceiver->SetCodecPreferences(sender_video_codecs).ok());
   auto offer = caller->CreateOffer();
-  auto codecs = offer->description()
-                    ->contents()[0]
-                    .media_description()
-                    ->codecs();
+  auto codecs =
+      offer->description()->contents()[0].media_description()->codecs();
   EXPECT_TRUE(CompareCodecs(sender_video_codecs, codecs));
 }
 
@@ -1746,32 +1692,11 @@ TEST_F(PeerConnectionMediaTestUnifiedPlan,
   // Normal case, resetting preferences with empty list of codecs
   EXPECT_TRUE(video_transceiver->SetCodecPreferences(empty_codecs).ok());
   auto offer = caller->CreateOffer();
-  auto codecs = offer->description()
-                    ->contents()[0]
-                    .media_description()
-                    ->codecs();
+  auto codecs =
+      offer->description()->contents()[0].media_description()->codecs();
   EXPECT_TRUE(CompareCodecs(sender_video_codecs, codecs));
 }
 
-TEST_F(PeerConnectionMediaTestUnifiedPlan,
-       SetCodecPreferencesVideoSendOnlyKillswitch) {
-  field_trials_ = std::make_unique<test::ScopedKeyValueConfig>(
-      "WebRTC-SetCodecPreferences-ReceiveOnlyFilterInsteadOfThrow/Disabled/");
-  auto fake_engine = std::make_unique<FakeMediaEngine>();
-  auto send_codecs = fake_engine->voice().send_codecs();
-  send_codecs.push_back(
-      cricket::CreateVideoCodec(send_codecs.back().id + 1, "send_only_codec"));
-  fake_engine->SetAudioSendCodecs(send_codecs);
-
-  auto caller = CreatePeerConnectionWithAudio(std::move(fake_engine));
-
-  auto transceiver = caller->pc()->GetTransceivers().front();
-  auto send_capabilities = caller->pc_factory()->GetRtpSenderCapabilities(
-      cricket::MediaType::MEDIA_TYPE_AUDIO);
-
-  EXPECT_TRUE(transceiver->SetCodecPreferences(send_capabilities.codecs).ok());
-}
-
 TEST_F(PeerConnectionMediaTestUnifiedPlan,
        SetCodecPreferencesVideoCodecDuplicatesRemoved) {
   auto caller = CreatePeerConnectionWithVideo();
@@ -1793,10 +1718,8 @@ TEST_F(PeerConnectionMediaTestUnifiedPlan,
 
   EXPECT_TRUE(video_transceiver->SetCodecPreferences(duplicate_codec).ok());
   auto offer = caller->CreateOffer();
-  auto codecs = offer->description()
-                    ->contents()[0]
-                    .media_description()
-                    ->codecs();
+  auto codecs =
+      offer->description()->contents()[0].media_description()->codecs();
   EXPECT_TRUE(CompareCodecs(single_codec, codecs));
 }
 
@@ -1837,10 +1760,8 @@ TEST_F(PeerConnectionMediaTestUnifiedPlan, SetCodecPreferencesVideoWithRtx) {
   EXPECT_TRUE(
       video_transceiver->SetCodecPreferences(video_codecs_vpx_rtx).ok());
   auto offer = caller->CreateOffer();
-  auto codecs = offer->description()
-                    ->contents()[0]
-                    .media_description()
-                    ->codecs();
+  auto codecs =
+      offer->description()->contents()[0].media_description()->codecs();
 
   EXPECT_TRUE(CompareCodecs(video_codecs_vpx_rtx, codecs));
   EXPECT_EQ(codecs.size(), 4u);
@@ -1883,10 +1804,8 @@ TEST_F(PeerConnectionMediaTestUnifiedPlan,
   EXPECT_TRUE(send_transceiver->SetCodecPreferences(video_codecs_vpx).ok());
 
   auto offer = caller->CreateOfferAndSetAsLocal();
-  auto codecs = offer->description()
-                    ->contents()[0]
-                    .media_description()
-                    ->codecs();
+  auto codecs =
+      offer->description()->contents()[0].media_description()->codecs();
 
   EXPECT_EQ(codecs.size(), 2u);  // VP8, VP9
   EXPECT_TRUE(CompareCodecs(video_codecs_vpx, codecs));
@@ -1907,10 +1826,8 @@ TEST_F(PeerConnectionMediaTestUnifiedPlan,
 
   auto answer = callee->CreateAnswerAndSetAsLocal();
 
-  auto recv_codecs = answer->description()
-                         ->contents()[0]
-                         .media_description()
-                         ->codecs();
+  auto recv_codecs =
+      answer->description()->contents()[0].media_description()->codecs();
   EXPECT_EQ(recv_codecs.size(), 1u);  // VP8
 }
 
@@ -1954,10 +1871,8 @@ TEST_F(PeerConnectionMediaTestUnifiedPlan,
   absl::c_reverse(video_codecs_vpx_reverse);
 
   auto offer = caller->CreateOfferAndSetAsLocal();
-  auto codecs = offer->description()
-                    ->contents()[0]
-                    .media_description()
-                    ->codecs();
+  auto codecs =
+      offer->description()->contents()[0].media_description()->codecs();
   EXPECT_EQ(codecs.size(), 2u);  // VP9, VP8
   EXPECT_TRUE(CompareCodecs(video_codecs_vpx, codecs));
 
@@ -1968,10 +1883,8 @@ TEST_F(PeerConnectionMediaTestUnifiedPlan,
 
   auto answer = callee->CreateAnswerAndSetAsLocal();
 
-  auto recv_codecs = answer->description()
-                         ->contents()[0]
-                         .media_description()
-                         ->codecs();
+  auto recv_codecs =
+      answer->description()->contents()[0].media_description()->codecs();
 
   EXPECT_TRUE(CompareCodecs(video_codecs_vpx_reverse, recv_codecs));
 }
@@ -2133,7 +2046,7 @@ TEST_F(PeerConnectionMediaTestUnifiedPlan,
 }
 
 TEST_F(PeerConnectionMediaTestUnifiedPlan,
-       SetCodecPreferencesReceiveOnlyWithSendOnlyTransceiverStops) {
+       SetCodecPreferencesRecvOnlyCodecOnSendOnlyTransceiver) {
   auto fake_engine = std::make_unique<cricket::FakeMediaEngine>();
 
   std::vector<cricket::Codec> audio_codecs;
@@ -2154,7 +2067,49 @@ TEST_F(PeerConnectionMediaTestUnifiedPlan,
   EXPECT_TRUE(audio_transceiver->SetCodecPreferences(capabilities.codecs).ok());
   RTCOfferAnswerOptions options;
   EXPECT_TRUE(caller->SetLocalDescription(caller->CreateOffer(options)));
-  EXPECT_EQ(audio_transceiver->direction(), RtpTransceiverDirection::kStopped);
+  // The transceiver is still sendonly (not stopped) because preferring a codec
+  // that is not applicable to the sendonly use case is the same as not having
+  // any codec preferences.
+  EXPECT_EQ(audio_transceiver->direction(), RtpTransceiverDirection::kSendOnly);
+}
+
+TEST_F(PeerConnectionMediaTestUnifiedPlan, SetCodecPreferencesVideoNoRtx) {
+  auto fake_engine = std::make_unique<cricket::FakeMediaEngine>();
+
+  std::vector<cricket::Codec> video_codecs;
+  video_codecs.emplace_back(cricket::CreateVideoCodec(100, "bar"));
+  video_codecs.emplace_back(cricket::CreateVideoRtxCodec(101, 100));
+  video_codecs.emplace_back(
+      cricket::CreateVideoCodec(102, cricket::kRedCodecName));
+  fake_engine->SetVideoCodecs(video_codecs);
+
+  auto caller = CreatePeerConnectionWithVideo(std::move(fake_engine));
+
+  auto transceivers = caller->pc()->GetTransceivers();
+  ASSERT_EQ(1u, transceivers.size());
+
+  auto video_transceiver = caller->pc()->GetTransceivers()[0];
+  EXPECT_TRUE(video_transceiver
+                  ->SetDirectionWithError(RtpTransceiverDirection::kRecvOnly)
+                  .ok());
+  auto capabilities = caller->pc_factory()->GetRtpSenderCapabilities(
+      cricket::MediaType::MEDIA_TYPE_VIDEO);
+  auto it =
+      std::remove_if(capabilities.codecs.begin(), capabilities.codecs.end(),
+                     [](const RtpCodecCapability& codec) {
+                       return codec.name == cricket::kRtxCodecName;
+                     });
+  capabilities.codecs.erase(it, capabilities.codecs.end());
+  EXPECT_EQ(capabilities.codecs.size(), 2u);
+  EXPECT_TRUE(video_transceiver->SetCodecPreferences(capabilities.codecs).ok());
+
+  RTCOfferAnswerOptions options;
+  auto offer = caller->CreateOffer(options);
+  const auto& content = offer->description()->contents()[0];
+  auto& codecs = content.media_description()->codecs();
+  ASSERT_EQ(codecs.size(), 2u);
+  EXPECT_EQ(codecs[0].name, "bar");
+  EXPECT_EQ(codecs[1].name, cricket::kRedCodecName);
 }
 
 INSTANTIATE_TEST_SUITE_P(PeerConnectionMediaTest,
index c2755041af14640a5df4f709344b0edeb435e547..5999f259eba4f0c77fa89869e0cd12366f6e4567 100644 (file)
@@ -44,6 +44,7 @@
 #include "api/video/video_bitrate_allocator_factory.h"
 #include "api/video_codecs/scalability_mode.h"
 #include "media/base/codec.h"
+#include "media/base/codec_comparators.h"
 #include "media/base/media_channel.h"
 #include "media/base/media_config.h"
 #include "media/base/media_engine.h"
 namespace webrtc {
 namespace {
 
+bool HasAnyMediaCodec(const std::vector<RtpCodecCapability>& codecs) {
+  return absl::c_any_of(codecs, [](const RtpCodecCapability& codec) {
+    return codec.IsMediaCodec();
+  });
+}
 RTCError VerifyCodecPreferences(
-    const std::vector<RtpCodecCapability>& unfiltered_codecs,
-    const std::vector<cricket::Codec>& recv_codecs,
-    const FieldTrialsView& field_trials) {
-  // If the intersection between codecs and
-  // RTCRtpReceiver.getCapabilities(kind).codecs only contains RTX, RED, FEC
-  // codecs or Comfort Noise codecs or is an empty set, throw
+    const std::vector<RtpCodecCapability>& codecs,
+    const std::vector<cricket::Codec>& send_codecs,
+    const std::vector<cricket::Codec>& recv_codecs) {
+  // `codec_capabilities` is the union of `send_codecs` and `recv_codecs`.
+  std::vector<cricket::Codec> codec_capabilities;
+  codec_capabilities.reserve(send_codecs.size() + recv_codecs.size());
+  codec_capabilities.insert(codec_capabilities.end(), send_codecs.begin(),
+                            send_codecs.end());
+  codec_capabilities.insert(codec_capabilities.end(), recv_codecs.begin(),
+                            recv_codecs.end());
+  // If a media codec is not recognized from `codec_capabilities`, throw
   // InvalidModificationError.
-  // This ensures that we always have something to offer, regardless of
-  // transceiver.direction.
-  // TODO(fippo): clean up the filtering killswitch
-  std::vector<RtpCodecCapability> codecs = unfiltered_codecs;
-  if (!absl::c_any_of(codecs, [&recv_codecs](const RtpCodecCapability& codec) {
-        return codec.IsMediaCodec() &&
-               absl::c_any_of(recv_codecs,
-                              [&codec](const cricket::Codec& recv_codec) {
-                                return recv_codec.MatchesRtpCodec(codec);
+  if (!absl::c_all_of(codecs, [&codec_capabilities](
+                                  const RtpCodecCapability& codec) {
+        return !codec.IsMediaCodec() ||
+               absl::c_any_of(codec_capabilities,
+                              [&codec](const cricket::Codec& codec_capability) {
+                                return IsSameRtpCodec(codec_capability, codec);
                               });
       })) {
     LOG_AND_RETURN_ERROR(RTCErrorType::INVALID_MODIFICATION,
-                         "Invalid codec preferences: Missing codec from recv "
-                         "codec capabilities.");
-  }
-
-  // Let codecCapabilities RTCRtpReceiver.getCapabilities(kind).codecs.
-  // For each codec in codecs, If
-  // codec is not in codecCapabilities, throw InvalidModificationError.
-  for (const auto& codec_preference : codecs) {
-    bool is_recv_codec = absl::c_any_of(
-        recv_codecs, [&codec_preference](const cricket::Codec& codec) {
-          return codec.MatchesRtpCodec(codec_preference);
-        });
-    if (!is_recv_codec) {
-      if (!field_trials.IsDisabled(
-              "WebRTC-SetCodecPreferences-ReceiveOnlyFilterInsteadOfThrow")) {
-        LOG_AND_RETURN_ERROR(
-            RTCErrorType::INVALID_MODIFICATION,
-            std::string(
-                "Invalid codec preferences: invalid codec with name \"") +
-                codec_preference.name + "\".");
-      } else {
-        // Killswitch behavior: filter out any codec not in receive codecs.
-        codecs.erase(std::remove_if(
-            codecs.begin(), codecs.end(),
-            [&recv_codecs](const RtpCodecCapability& codec) {
-              return codec.IsMediaCodec() &&
-                     !absl::c_any_of(
-                         recv_codecs,
-                         [&codec](const cricket::Codec& recv_codec) {
-                           return recv_codec.MatchesRtpCodec(codec);
-                         });
-            }));
-      }
-    }
+                         "Invalid codec preferences: Missing codec from codec "
+                         "capabilities.");
   }
-
-  // Check we have a real codec (not just rtx, red, fec or CN)
-  if (absl::c_all_of(codecs, [](const RtpCodecCapability& codec) {
-        return !codec.IsMediaCodec();
-      })) {
+  // If `codecs` only contains entries for RTX, RED, FEC or Comfort Noise, throw
+  // InvalidModificationError.
+  if (!HasAnyMediaCodec(codecs)) {
     LOG_AND_RETURN_ERROR(
         RTCErrorType::INVALID_MODIFICATION,
         "Invalid codec preferences: codec list must have a non "
-        "RTX, RED or FEC entry.");
+        "RTX, RED, FEC or Comfort Noise entry.");
   }
-
   return RTCError::OK();
 }
 
@@ -693,6 +666,9 @@ RTCError RtpTransceiver::SetCodecPreferences(
   // to codecs and abort these steps.
   if (codec_capabilities.empty()) {
     codec_preferences_.clear();
+    sendrecv_codec_preferences_.clear();
+    sendonly_codec_preferences_.clear();
+    recvonly_codec_preferences_.clear();
     return RTCError::OK();
   }
 
@@ -702,23 +678,92 @@ RTCError RtpTransceiver::SetCodecPreferences(
                          [&codecs](const RtpCodecCapability& codec) {
                            return absl::c_linear_search(codecs, codec);
                          });
+  // TODO(https://crbug.com/webrtc/391530822): Move logic in
+  // MediaSessionDescriptionFactory to this level.
+  return UpdateCodecPreferencesCaches(codecs);
+}
 
-  // 6. to 8.
-  RTCError result;
-  std::vector<cricket::Codec> recv_codecs;
+RTCError RtpTransceiver::UpdateCodecPreferencesCaches(
+    const std::vector<RtpCodecCapability>& codecs) {
+  // Get codec capabilities from media engine.
+  std::vector<cricket::Codec> send_codecs, recv_codecs;
   if (media_type_ == cricket::MEDIA_TYPE_AUDIO) {
+    send_codecs = media_engine()->voice().send_codecs();
     recv_codecs = media_engine()->voice().recv_codecs();
   } else if (media_type_ == cricket::MEDIA_TYPE_VIDEO) {
+    send_codecs = media_engine()->video().send_codecs();
     recv_codecs = media_engine()->video().recv_codecs(context()->use_rtx());
   }
-  result = VerifyCodecPreferences(codecs, recv_codecs,
-                                  context()->env().field_trials());
-
-  if (result.ok()) {
-    codec_preferences_ = codecs;
+  RTCError error = VerifyCodecPreferences(codecs, send_codecs, recv_codecs);
+  if (!error.ok()) {
+    return error;
   }
-
-  return result;
+  codec_preferences_ = codecs;
+  // Update the filtered views of `codec_preferences_` so that we don't have
+  // to query codec capabilities when calling filtered_codec_preferences() or
+  // every time the direction changes.
+  sendrecv_codec_preferences_.clear();
+  sendonly_codec_preferences_.clear();
+  recvonly_codec_preferences_.clear();
+  for (const RtpCodecCapability& codec : codec_preferences_) {
+    if (!codec.IsMediaCodec()) {
+      // Non-media codecs don't need to be filtered at this level.
+      sendrecv_codec_preferences_.push_back(codec);
+      sendonly_codec_preferences_.push_back(codec);
+      recvonly_codec_preferences_.push_back(codec);
+      continue;
+    }
+    // Is this a send codec, receive codec or both?
+    bool is_send_codec =
+        absl::c_any_of(send_codecs, [&codec](const cricket::Codec& send_codec) {
+          return IsSameRtpCodecIgnoringLevel(send_codec, codec);
+        });
+    bool is_recv_codec =
+        absl::c_any_of(recv_codecs, [&codec](const cricket::Codec& recv_codec) {
+          return IsSameRtpCodecIgnoringLevel(recv_codec, codec);
+        });
+    // The codec being neither for sending or receving is not possible because
+    // of prior validation by VerifyCodecPreferences().
+    RTC_CHECK(is_send_codec || is_recv_codec);
+    if (is_send_codec && is_recv_codec) {
+      sendrecv_codec_preferences_.push_back(codec);
+    }
+    if (is_send_codec) {
+      sendonly_codec_preferences_.push_back(codec);
+    }
+    if (is_recv_codec) {
+      recvonly_codec_preferences_.push_back(codec);
+    }
+  }
+  // If filtering results in an empty list this is the same as not having any
+  // preferences.
+  if (!HasAnyMediaCodec(sendrecv_codec_preferences_)) {
+    sendrecv_codec_preferences_.clear();
+  }
+  if (!HasAnyMediaCodec(sendonly_codec_preferences_)) {
+    sendonly_codec_preferences_.clear();
+  }
+  if (!HasAnyMediaCodec(recvonly_codec_preferences_)) {
+    recvonly_codec_preferences_.clear();
+  }
+  return RTCError::OK();
+}
+std::vector<RtpCodecCapability> RtpTransceiver::codec_preferences() const {
+  return codec_preferences_;
+}
+std::vector<RtpCodecCapability> RtpTransceiver::filtered_codec_preferences()
+    const {
+  switch (direction_) {
+    case RtpTransceiverDirection::kSendRecv:
+    case RtpTransceiverDirection::kInactive:
+    case RtpTransceiverDirection::kStopped:
+      return sendrecv_codec_preferences_;
+    case RtpTransceiverDirection::kSendOnly:
+      return sendonly_codec_preferences_;
+    case RtpTransceiverDirection::kRecvOnly:
+      return recvonly_codec_preferences_;
+  }
+  return codec_preferences_;
 }
 
 std::vector<RtpHeaderExtensionCapability>
@@ -776,10 +821,10 @@ RTCError RtpTransceiver::SetHeaderExtensionsToNegotiate(
                       "Attempted to stop a mandatory extension.");
     }
 
-    // TODO(bugs.webrtc.org/7477): Currently there are no recvonly extensions so
-    // this can not be checked: "When there exists header extension capabilities
-    // that have directions other than kSendRecv, restrict extension.direction
-    // as to not exceed that capability."
+    // TODO(bugs.webrtc.org/7477): Currently there are no recvonly extensions
+    // so this can not be checked: "When there exists header extension
+    // capabilities that have directions other than kSendRecv, restrict
+    // extension.direction as to not exceed that capability."
   }
 
   // Apply mutation after error checking.
index 610b842db78cbc4b5219806e2c4ab5070893e620..8fdb2ce3dacad30a92637f6c421a441594218318 100644 (file)
@@ -275,9 +275,13 @@ class RtpTransceiver : public RtpTransceiverInterface {
   void StopInternal() override;
   RTCError SetCodecPreferences(
       rtc::ArrayView<RtpCodecCapability> codecs) override;
-  std::vector<RtpCodecCapability> codec_preferences() const override {
-    return codec_preferences_;
-  }
+  // TODO(https://crbug.com/webrtc/391275081): Delete codec_preferences() in
+  // favor of filtered_codec_preferences() because it's not used anywhere.
+  std::vector<RtpCodecCapability> codec_preferences() const override;
+  // A direction()-filtered view of codec_preferences(). If this filtering
+  // results in not having any media codecs, an empty list is returned to mean
+  // "no preferences".
+  std::vector<RtpCodecCapability> filtered_codec_preferences() const;
   std::vector<RtpHeaderExtensionCapability> GetHeaderExtensionsToNegotiate()
       const override;
   std::vector<RtpHeaderExtensionCapability> GetNegotiatedHeaderExtensions()
@@ -308,6 +312,9 @@ class RtpTransceiver : public RtpTransceiverInterface {
   void PushNewMediaChannelAndDeleteChannel(
       std::unique_ptr<cricket::ChannelInterface> channel_to_delete);
 
+  RTCError UpdateCodecPreferencesCaches(
+      const std::vector<RtpCodecCapability>& codecs);
+
   // Enforce that this object is created, used and destroyed on one thread.
   TaskQueueBase* const thread_;
   const bool unified_plan_;
@@ -337,6 +344,9 @@ class RtpTransceiver : public RtpTransceiverInterface {
   std::unique_ptr<cricket::ChannelInterface> channel_ = nullptr;
   ConnectionContext* const context_;
   std::vector<RtpCodecCapability> codec_preferences_;
+  std::vector<RtpCodecCapability> sendrecv_codec_preferences_;
+  std::vector<RtpCodecCapability> sendonly_codec_preferences_;
+  std::vector<RtpCodecCapability> recvonly_codec_preferences_;
   std::vector<RtpHeaderExtensionCapability> header_extensions_to_negotiate_;
 
   // `negotiated_header_extensions_` is read and written to on the signaling
index 997915438856a97f542cca3acea0ecec8b78c9d0..bc01313887da0d143a43a7a1998c84394d22cb62 100644 (file)
@@ -20,7 +20,9 @@
 #include "api/environment/environment_factory.h"
 #include "api/peer_connection_interface.h"
 #include "api/rtp_parameters.h"
-#include "media/base/media_engine.h"
+#include "api/test/rtc_error_matchers.h"
+#include "media/base/fake_media_engine.h"
+#include "pc/rtp_parameters_conversion.h"
 #include "pc/test/enable_fake_media.h"
 #include "pc/test/mock_channel_interface.h"
 #include "pc/test/mock_rtp_receiver_internal.h"
 using ::testing::_;
 using ::testing::ElementsAre;
 using ::testing::Field;
+using ::testing::NiceMock;
 using ::testing::Optional;
 using ::testing::Property;
 using ::testing::Return;
 using ::testing::ReturnRef;
+using ::testing::SizeIs;
 
 namespace webrtc {
 
@@ -49,8 +53,10 @@ class RtpTransceiverTest : public testing::Test {
             ConnectionContext::Create(CreateEnvironment(), &dependencies_)) {}
 
  protected:
-  cricket::MediaEngineInterface* media_engine() {
-    return context_->media_engine();
+  cricket::FakeMediaEngine* media_engine() {
+    // We know this cast is safe because we supplied the fake implementation
+    // in MakeDependencies().
+    return static_cast<cricket::FakeMediaEngine*>(context_->media_engine());
   }
   ConnectionContext* context() { return context_.get(); }
 
@@ -62,7 +68,7 @@ class RtpTransceiverTest : public testing::Test {
     d.network_thread = rtc::Thread::Current();
     d.worker_thread = rtc::Thread::Current();
     d.signaling_thread = rtc::Thread::Current();
-    EnableFakeMedia(d);
+    EnableFakeMedia(d, std::make_unique<cricket::FakeMediaEngine>());
     return d;
   }
 
@@ -75,7 +81,7 @@ TEST_F(RtpTransceiverTest, CannotSetChannelOnStoppedTransceiver) {
   const std::string content_name("my_mid");
   auto transceiver = rtc::make_ref_counted<RtpTransceiver>(
       cricket::MediaType::MEDIA_TYPE_AUDIO, context());
-  auto channel1 = std::make_unique<cricket::MockChannelInterface>();
+  auto channel1 = std::make_unique<NiceMock<cricket::MockChannelInterface>>();
   EXPECT_CALL(*channel1, media_type())
       .WillRepeatedly(Return(cricket::MediaType::MEDIA_TYPE_AUDIO));
   EXPECT_CALL(*channel1, mid()).WillRepeatedly(ReturnRef(content_name));
@@ -92,13 +98,14 @@ TEST_F(RtpTransceiverTest, CannotSetChannelOnStoppedTransceiver) {
   transceiver->StopInternal();
   EXPECT_EQ(channel1_ptr, transceiver->channel());
 
-  auto channel2 = std::make_unique<cricket::MockChannelInterface>();
+  auto channel2 = std::make_unique<NiceMock<cricket::MockChannelInterface>>();
   EXPECT_CALL(*channel2, media_type())
       .WillRepeatedly(Return(cricket::MediaType::MEDIA_TYPE_AUDIO));
 
   // Clear the current channel - required to allow SetChannel()
   EXPECT_CALL(*channel1_ptr, SetFirstPacketReceivedCallback(_));
   transceiver->ClearChannel();
+  ASSERT_EQ(nullptr, transceiver->channel());
   // Channel can no longer be set, so this call should be a no-op.
   transceiver->SetChannel(std::move(channel2),
                           [](const std::string&) { return nullptr; });
@@ -110,7 +117,7 @@ TEST_F(RtpTransceiverTest, CanUnsetChannelOnStoppedTransceiver) {
   const std::string content_name("my_mid");
   auto transceiver = rtc::make_ref_counted<RtpTransceiver>(
       cricket::MediaType::MEDIA_TYPE_VIDEO, context());
-  auto channel = std::make_unique<cricket::MockChannelInterface>();
+  auto channel = std::make_unique<NiceMock<cricket::MockChannelInterface>>();
   EXPECT_CALL(*channel, media_type())
       .WillRepeatedly(Return(cricket::MediaType::MEDIA_TYPE_VIDEO));
   EXPECT_CALL(*channel, mid()).WillRepeatedly(ReturnRef(content_name));
@@ -136,59 +143,323 @@ TEST_F(RtpTransceiverTest, CanUnsetChannelOnStoppedTransceiver) {
 
 class RtpTransceiverUnifiedPlanTest : public RtpTransceiverTest {
  public:
-  RtpTransceiverUnifiedPlanTest()
-      : transceiver_(rtc::make_ref_counted<RtpTransceiver>(
-            RtpSenderProxyWithInternal<RtpSenderInternal>::Create(
-                rtc::Thread::Current(),
-                sender_),
-            RtpReceiverProxyWithInternal<RtpReceiverInternal>::Create(
-                rtc::Thread::Current(),
-                rtc::Thread::Current(),
-                receiver_),
-            context(),
-            media_engine()->voice().GetRtpHeaderExtensions(),
-            /* on_negotiation_needed= */ [] {})) {}
-
-  static rtc::scoped_refptr<MockRtpReceiverInternal> MockReceiver() {
-    auto receiver = rtc::make_ref_counted<MockRtpReceiverInternal>();
+  static rtc::scoped_refptr<MockRtpReceiverInternal> MockReceiver(
+      cricket::MediaType media_type) {
+    auto receiver = rtc::make_ref_counted<NiceMock<MockRtpReceiverInternal>>();
     EXPECT_CALL(*receiver.get(), media_type())
-        .WillRepeatedly(Return(cricket::MediaType::MEDIA_TYPE_AUDIO));
+        .WillRepeatedly(Return(media_type));
     return receiver;
   }
 
-  static rtc::scoped_refptr<MockRtpSenderInternal> MockSender() {
-    auto sender = rtc::make_ref_counted<MockRtpSenderInternal>();
-    EXPECT_CALL(*sender.get(), media_type())
-        .WillRepeatedly(Return(cricket::MediaType::MEDIA_TYPE_AUDIO));
+  static rtc::scoped_refptr<MockRtpSenderInternal> MockSender(
+      cricket::MediaType media_type) {
+    auto sender = rtc::make_ref_counted<NiceMock<MockRtpSenderInternal>>();
+    EXPECT_CALL(*sender.get(), media_type()).WillRepeatedly(Return(media_type));
     return sender;
   }
 
+  rtc::scoped_refptr<RtpTransceiver> CreateTransceiver(
+      rtc::scoped_refptr<RtpSenderInternal> sender,
+      rtc::scoped_refptr<RtpReceiverInternal> receiver) {
+    return rtc::make_ref_counted<RtpTransceiver>(
+        RtpSenderProxyWithInternal<RtpSenderInternal>::Create(
+            rtc::Thread::Current(), std::move(sender)),
+        RtpReceiverProxyWithInternal<RtpReceiverInternal>::Create(
+            rtc::Thread::Current(), rtc::Thread::Current(),
+            std::move(receiver)),
+        context(), media_engine()->voice().GetRtpHeaderExtensions(),
+        /* on_negotiation_needed= */ [] {});
+  }
+
+ protected:
   rtc::AutoThread main_thread_;
-  rtc::scoped_refptr<MockRtpReceiverInternal> receiver_ = MockReceiver();
-  rtc::scoped_refptr<MockRtpSenderInternal> sender_ = MockSender();
-  rtc::scoped_refptr<RtpTransceiver> transceiver_;
 };
 
 // Basic tests for Stop()
 TEST_F(RtpTransceiverUnifiedPlanTest, StopSetsDirection) {
-  EXPECT_CALL(*receiver_.get(), Stop());
-  EXPECT_CALL(*receiver_.get(), SetMediaChannel(_));
-  EXPECT_CALL(*sender_.get(), SetTransceiverAsStopped());
-  EXPECT_CALL(*sender_.get(), Stop());
-
-  EXPECT_EQ(RtpTransceiverDirection::kInactive, transceiver_->direction());
-  EXPECT_FALSE(transceiver_->current_direction());
-  transceiver_->StopStandard();
-  EXPECT_EQ(RtpTransceiverDirection::kStopped, transceiver_->direction());
-  EXPECT_FALSE(transceiver_->current_direction());
-  transceiver_->StopTransceiverProcedure();
-  EXPECT_TRUE(transceiver_->current_direction());
-  EXPECT_EQ(RtpTransceiverDirection::kStopped, transceiver_->direction());
+  rtc::scoped_refptr<MockRtpReceiverInternal> receiver =
+      MockReceiver(cricket::MediaType::MEDIA_TYPE_AUDIO);
+  rtc::scoped_refptr<MockRtpSenderInternal> sender =
+      MockSender(cricket::MediaType::MEDIA_TYPE_AUDIO);
+  rtc::scoped_refptr<RtpTransceiver> transceiver =
+      CreateTransceiver(sender, receiver);
+
+  EXPECT_CALL(*receiver.get(), Stop());
+  EXPECT_CALL(*receiver.get(), SetMediaChannel(_));
+  EXPECT_CALL(*sender.get(), SetTransceiverAsStopped());
+  EXPECT_CALL(*sender.get(), Stop());
+
+  EXPECT_EQ(RtpTransceiverDirection::kInactive, transceiver->direction());
+  EXPECT_FALSE(transceiver->current_direction());
+  transceiver->StopStandard();
+  EXPECT_EQ(RtpTransceiverDirection::kStopped, transceiver->direction());
+  EXPECT_FALSE(transceiver->current_direction());
+  transceiver->StopTransceiverProcedure();
+  EXPECT_TRUE(transceiver->current_direction());
+  EXPECT_EQ(RtpTransceiverDirection::kStopped, transceiver->direction());
   EXPECT_EQ(RtpTransceiverDirection::kStopped,
-            *transceiver_->current_direction());
+            *transceiver->current_direction());
+}
+
+class RtpTransceiverFilteredCodecPreferencesTest
+    : public RtpTransceiverUnifiedPlanTest {
+ public:
+  RtpTransceiverFilteredCodecPreferencesTest()
+      : transceiver_(CreateTransceiver(
+            MockSender(cricket::MediaType::MEDIA_TYPE_VIDEO),
+            MockReceiver(cricket::MediaType::MEDIA_TYPE_VIDEO))) {}
+
+  struct H264CodecCapabilities {
+    cricket::Codec cricket_sendrecv_codec;
+    RtpCodecCapability sendrecv_codec;
+    cricket::Codec cricket_sendonly_codec;
+    RtpCodecCapability sendonly_codec;
+    cricket::Codec cricket_recvonly_codec;
+    RtpCodecCapability recvonly_codec;
+    cricket::Codec cricket_rtx_codec;
+    RtpCodecCapability rtx_codec;
+  };
+
+  // For H264, the profile and level IDs are entangled and not ignored by
+  // IsSameRtpCodecIgnoringLevel().
+  H264CodecCapabilities ConfigureH264CodecCapabilities() {
+    cricket::Codec cricket_sendrecv_codec = cricket::CreateVideoCodec(
+        SdpVideoFormat("H264",
+                       {{"level-asymmetry-allowed", "1"},
+                        {"packetization-mode", "1"},
+                        {"profile-level-id", "42f00b"}},
+                       {ScalabilityMode::kL1T1}));
+    cricket::Codec cricket_sendonly_codec = cricket::CreateVideoCodec(
+        SdpVideoFormat("H264",
+                       {{"level-asymmetry-allowed", "1"},
+                        {"packetization-mode", "1"},
+                        {"profile-level-id", "640034"}},
+                       {ScalabilityMode::kL1T1}));
+    cricket::Codec cricket_recvonly_codec = cricket::CreateVideoCodec(
+        SdpVideoFormat("H264",
+                       {{"level-asymmetry-allowed", "1"},
+                        {"packetization-mode", "1"},
+                        {"profile-level-id", "f4001f"}},
+                       {ScalabilityMode::kL1T1}));
+    cricket::Codec cricket_rtx_codec = cricket::CreateVideoRtxCodec(
+        cricket::Codec::kIdNotSet, cricket::Codec::kIdNotSet);
+    media_engine()->SetVideoSendCodecs(
+        {cricket_sendrecv_codec, cricket_sendonly_codec, cricket_rtx_codec});
+    media_engine()->SetVideoRecvCodecs(
+        {cricket_sendrecv_codec, cricket_recvonly_codec, cricket_rtx_codec});
+    return {
+        .cricket_sendrecv_codec = cricket_sendrecv_codec,
+        .sendrecv_codec = ToRtpCodecCapability(cricket_sendrecv_codec),
+        .cricket_sendonly_codec = cricket_sendonly_codec,
+        .sendonly_codec = ToRtpCodecCapability(cricket_sendonly_codec),
+        .cricket_recvonly_codec = cricket_recvonly_codec,
+        .recvonly_codec = ToRtpCodecCapability(cricket_recvonly_codec),
+        .cricket_rtx_codec = cricket_rtx_codec,
+        .rtx_codec = ToRtpCodecCapability(cricket_rtx_codec),
+    };
+  }
+
+#ifdef RTC_ENABLE_H265
+  struct H265CodecCapabilities {
+    // The level-id from sender getCapabilities() or receiver getCapabilities().
+    static constexpr const char* kSendOnlyLevel = "180";
+    static constexpr const char* kRecvOnlyLevel = "156";
+    // A valid H265 level-id, but one not present in either getCapabilities().
+    static constexpr const char* kLevelNotInCapabilities = "135";
+
+    cricket::Codec cricket_sendonly_codec;
+    RtpCodecCapability sendonly_codec;
+    cricket::Codec cricket_recvonly_codec;
+    RtpCodecCapability recvonly_codec;
+  };
+
+  // For H265, the profile and level IDs are separate and are ignored by
+  // IsSameRtpCodecIgnoringLevel().
+  H265CodecCapabilities ConfigureH265CodecCapabilities() {
+    cricket::Codec cricket_sendonly_codec = cricket::CreateVideoCodec(
+        SdpVideoFormat("H265",
+                       {{"profile-id", "1"},
+                        {"tier-flag", "0"},
+                        {"level-id", H265CodecCapabilities::kSendOnlyLevel},
+                        {"tx-mode", "SRST"}},
+                       {ScalabilityMode::kL1T1}));
+    cricket::Codec cricket_recvonly_codec = cricket::CreateVideoCodec(
+        SdpVideoFormat("H265",
+                       {{"profile-id", "1"},
+                        {"tier-flag", "0"},
+                        {"level-id", H265CodecCapabilities::kRecvOnlyLevel},
+                        {"tx-mode", "SRST"}},
+                       {ScalabilityMode::kL1T1}));
+    media_engine()->SetVideoSendCodecs({cricket_sendonly_codec});
+    media_engine()->SetVideoRecvCodecs({cricket_recvonly_codec});
+    return {
+        .cricket_sendonly_codec = cricket_sendonly_codec,
+        .sendonly_codec = ToRtpCodecCapability(cricket_sendonly_codec),
+        .cricket_recvonly_codec = cricket_recvonly_codec,
+        .recvonly_codec = ToRtpCodecCapability(cricket_recvonly_codec),
+    };
+  }
+#endif  // RTC_ENABLE_H265
+
+ protected:
+  rtc::scoped_refptr<RtpTransceiver> transceiver_;
+};
+
+TEST_F(RtpTransceiverFilteredCodecPreferencesTest, EmptyByDefault) {
+  ConfigureH264CodecCapabilities();
+
+  EXPECT_THAT(
+      transceiver_->SetDirectionWithError(RtpTransceiverDirection::kSendRecv),
+      IsRtcOk());
+  EXPECT_THAT(transceiver_->filtered_codec_preferences(), SizeIs(0));
+
+  EXPECT_THAT(
+      transceiver_->SetDirectionWithError(RtpTransceiverDirection::kSendOnly),
+      IsRtcOk());
+  EXPECT_THAT(transceiver_->filtered_codec_preferences(), SizeIs(0));
+
+  EXPECT_THAT(
+      transceiver_->SetDirectionWithError(RtpTransceiverDirection::kRecvOnly),
+      IsRtcOk());
+  EXPECT_THAT(transceiver_->filtered_codec_preferences(), SizeIs(0));
+
+  EXPECT_THAT(
+      transceiver_->SetDirectionWithError(RtpTransceiverDirection::kInactive),
+      IsRtcOk());
+  EXPECT_THAT(transceiver_->filtered_codec_preferences(), SizeIs(0));
+}
+
+TEST_F(RtpTransceiverFilteredCodecPreferencesTest, OrderIsMaintained) {
+  const auto codecs = ConfigureH264CodecCapabilities();
+  std::vector<RtpCodecCapability> codec_capabilities = {codecs.sendrecv_codec,
+                                                        codecs.rtx_codec};
+  EXPECT_THAT(transceiver_->SetCodecPreferences(codec_capabilities), IsRtcOk());
+  EXPECT_THAT(transceiver_->filtered_codec_preferences(),
+              ElementsAre(codec_capabilities[0], codec_capabilities[1]));
+  // Reverse order.
+  codec_capabilities = {codecs.rtx_codec, codecs.sendrecv_codec};
+  EXPECT_THAT(transceiver_->SetCodecPreferences(codec_capabilities), IsRtcOk());
+  EXPECT_THAT(transceiver_->filtered_codec_preferences(),
+              ElementsAre(codec_capabilities[0], codec_capabilities[1]));
+}
+
+TEST_F(RtpTransceiverFilteredCodecPreferencesTest,
+       FiltersCodecsBasedOnDirection) {
+  const auto codecs = ConfigureH264CodecCapabilities();
+  std::vector<RtpCodecCapability> codec_capabilities = {
+      codecs.sendonly_codec, codecs.sendrecv_codec, codecs.recvonly_codec};
+  EXPECT_THAT(transceiver_->SetCodecPreferences(codec_capabilities), IsRtcOk());
+
+  EXPECT_THAT(
+      transceiver_->SetDirectionWithError(RtpTransceiverDirection::kSendRecv),
+      IsRtcOk());
+  EXPECT_THAT(transceiver_->filtered_codec_preferences(),
+              ElementsAre(codecs.sendrecv_codec));
+
+  EXPECT_THAT(
+      transceiver_->SetDirectionWithError(RtpTransceiverDirection::kSendOnly),
+      IsRtcOk());
+  EXPECT_THAT(transceiver_->filtered_codec_preferences(),
+              ElementsAre(codecs.sendonly_codec, codecs.sendrecv_codec));
+
+  EXPECT_THAT(
+      transceiver_->SetDirectionWithError(RtpTransceiverDirection::kRecvOnly),
+      IsRtcOk());
+  EXPECT_THAT(transceiver_->filtered_codec_preferences(),
+              ElementsAre(codecs.sendrecv_codec, codecs.recvonly_codec));
+
+  EXPECT_THAT(
+      transceiver_->SetDirectionWithError(RtpTransceiverDirection::kInactive),
+      IsRtcOk());
+  EXPECT_THAT(transceiver_->filtered_codec_preferences(),
+              ElementsAre(codecs.sendrecv_codec));
+}
+
+TEST_F(RtpTransceiverFilteredCodecPreferencesTest,
+       RtxIsIncludedAfterFiltering) {
+  const auto codecs = ConfigureH264CodecCapabilities();
+  std::vector<RtpCodecCapability> codec_capabilities = {codecs.recvonly_codec,
+                                                        codecs.rtx_codec};
+  EXPECT_THAT(transceiver_->SetCodecPreferences(codec_capabilities), IsRtcOk());
+
+  EXPECT_THAT(
+      transceiver_->SetDirectionWithError(RtpTransceiverDirection::kRecvOnly),
+      IsRtcOk());
+  EXPECT_THAT(transceiver_->filtered_codec_preferences(),
+              ElementsAre(codecs.recvonly_codec, codecs.rtx_codec));
 }
 
-class RtpTransceiverTestForHeaderExtensions : public RtpTransceiverTest {
+TEST_F(RtpTransceiverFilteredCodecPreferencesTest,
+       NoMediaIsTheSameAsNoPreference) {
+  const auto codecs = ConfigureH264CodecCapabilities();
+  std::vector<RtpCodecCapability> codec_capabilities = {codecs.recvonly_codec,
+                                                        codecs.rtx_codec};
+  EXPECT_THAT(transceiver_->SetCodecPreferences(codec_capabilities), IsRtcOk());
+
+  EXPECT_THAT(
+      transceiver_->SetDirectionWithError(RtpTransceiverDirection::kSendOnly),
+      IsRtcOk());
+  // After filtering the only codec that remains is RTX which is not a media
+  // codec, this is the same as not having any preferences.
+  EXPECT_THAT(transceiver_->filtered_codec_preferences(), SizeIs(0));
+
+  // But the preferences are remembered in case the direction changes such that
+  // we do have a media codec.
+  EXPECT_THAT(
+      transceiver_->SetDirectionWithError(RtpTransceiverDirection::kRecvOnly),
+      IsRtcOk());
+  EXPECT_THAT(transceiver_->filtered_codec_preferences(),
+              ElementsAre(codecs.recvonly_codec, codecs.rtx_codec));
+}
+
+#ifdef RTC_ENABLE_H265
+TEST_F(RtpTransceiverFilteredCodecPreferencesTest,
+       H265LevelIdIsIgnoredByFilter) {
+  const auto codecs = ConfigureH265CodecCapabilities();
+  std::vector<RtpCodecCapability> codec_capabilities = {codecs.sendonly_codec,
+                                                        codecs.recvonly_codec};
+  EXPECT_THAT(transceiver_->SetCodecPreferences(codec_capabilities), IsRtcOk());
+  // Regardless of direction, both codecs are preferred due to ignoring levels.
+  EXPECT_THAT(
+      transceiver_->SetDirectionWithError(RtpTransceiverDirection::kSendOnly),
+      IsRtcOk());
+  EXPECT_THAT(transceiver_->filtered_codec_preferences(),
+              ElementsAre(codec_capabilities[0], codec_capabilities[1]));
+  EXPECT_THAT(
+      transceiver_->SetDirectionWithError(RtpTransceiverDirection::kRecvOnly),
+      IsRtcOk());
+  EXPECT_THAT(transceiver_->filtered_codec_preferences(),
+              ElementsAre(codec_capabilities[0], codec_capabilities[1]));
+  EXPECT_THAT(
+      transceiver_->SetDirectionWithError(RtpTransceiverDirection::kSendRecv),
+      IsRtcOk());
+  EXPECT_THAT(transceiver_->filtered_codec_preferences(),
+              ElementsAre(codec_capabilities[0], codec_capabilities[1]));
+}
+
+TEST_F(RtpTransceiverFilteredCodecPreferencesTest,
+       H265LevelIdHasToFromSenderOrReceiverCapabilities) {
+  ConfigureH265CodecCapabilities();
+  cricket::Codec cricket_codec = cricket::CreateVideoCodec(SdpVideoFormat(
+      "H265",
+      {{"profile-id", "1"},
+       {"tier-flag", "0"},
+       {"level-id", H265CodecCapabilities::kLevelNotInCapabilities},
+       {"tx-mode", "SRST"}},
+      {ScalabilityMode::kL1T1}));
+
+  std::vector<RtpCodecCapability> codec_capabilities = {
+      ToRtpCodecCapability(cricket_codec)};
+  EXPECT_THAT(transceiver_->SetCodecPreferences(codec_capabilities),
+              IsRtcErrorWithTypeAndMessage(
+                  RTCErrorType::INVALID_MODIFICATION,
+                  "Invalid codec preferences: Missing codec from codec "
+                  "capabilities."));
+}
+#endif  // RTC_ENABLE_H265
+
+class RtpTransceiverTestForHeaderExtensions
+    : public RtpTransceiverUnifiedPlanTest {
  public:
   RtpTransceiverTestForHeaderExtensions()
       : extensions_(
@@ -216,28 +487,15 @@ class RtpTransceiverTestForHeaderExtensions : public RtpTransceiverTest {
             extensions_,
             /* on_negotiation_needed= */ [] {})) {}
 
-  static rtc::scoped_refptr<MockRtpReceiverInternal> MockReceiver() {
-    auto receiver = rtc::make_ref_counted<MockRtpReceiverInternal>();
-    EXPECT_CALL(*receiver.get(), media_type())
-        .WillRepeatedly(Return(cricket::MediaType::MEDIA_TYPE_AUDIO));
-    return receiver;
-  }
-
-  static rtc::scoped_refptr<MockRtpSenderInternal> MockSender() {
-    auto sender = rtc::make_ref_counted<MockRtpSenderInternal>();
-    EXPECT_CALL(*sender.get(), media_type())
-        .WillRepeatedly(Return(cricket::MediaType::MEDIA_TYPE_AUDIO));
-    return sender;
-  }
-
   void ClearChannel() {
     EXPECT_CALL(*sender_.get(), SetMediaChannel(_));
     transceiver_->ClearChannel();
   }
 
-  rtc::AutoThread main_thread_;
-  rtc::scoped_refptr<MockRtpReceiverInternal> receiver_ = MockReceiver();
-  rtc::scoped_refptr<MockRtpSenderInternal> sender_ = MockSender();
+  rtc::scoped_refptr<MockRtpReceiverInternal> receiver_ =
+      MockReceiver(cricket::MediaType::MEDIA_TYPE_AUDIO);
+  rtc::scoped_refptr<MockRtpSenderInternal> sender_ =
+      MockSender(cricket::MediaType::MEDIA_TYPE_AUDIO);
 
   std::vector<RtpHeaderExtensionCapability> extensions_;
   rtc::scoped_refptr<RtpTransceiver> transceiver_;
@@ -379,7 +637,8 @@ TEST_F(RtpTransceiverTestForHeaderExtensions,
   EXPECT_CALL(*sender_.get(), SetMediaChannel(_));
   EXPECT_CALL(*sender_.get(), SetTransceiverAsStopped());
   EXPECT_CALL(*sender_.get(), Stop());
-  auto mock_channel = std::make_unique<cricket::MockChannelInterface>();
+  auto mock_channel =
+      std::make_unique<NiceMock<cricket::MockChannelInterface>>();
   auto mock_channel_ptr = mock_channel.get();
   EXPECT_CALL(*mock_channel, SetFirstPacketReceivedCallback(_));
   EXPECT_CALL(*mock_channel, media_type())
@@ -412,7 +671,8 @@ TEST_F(RtpTransceiverTestForHeaderExtensions, ReturnsNegotiatedHdrExts) {
   EXPECT_CALL(*sender_.get(), SetTransceiverAsStopped());
   EXPECT_CALL(*sender_.get(), Stop());
 
-  auto mock_channel = std::make_unique<cricket::MockChannelInterface>();
+  auto mock_channel =
+      std::make_unique<NiceMock<cricket::MockChannelInterface>>();
   auto mock_channel_ptr = mock_channel.get();
   EXPECT_CALL(*mock_channel, SetFirstPacketReceivedCallback(_));
   EXPECT_CALL(*mock_channel, media_type())
@@ -491,7 +751,7 @@ TEST_F(RtpTransceiverTestForHeaderExtensions,
   };
 
   // Default is stopped.
-  auto sender = rtc::make_ref_counted<MockRtpSenderInternal>();
+  auto sender = rtc::make_ref_counted<NiceMock<MockRtpSenderInternal>>();
   auto transceiver = rtc::make_ref_counted<RtpTransceiver>(
       RtpSenderProxyWithInternal<RtpSenderInternal>::Create(
           rtc::Thread::Current(), sender),
@@ -510,7 +770,8 @@ TEST_F(RtpTransceiverTestForHeaderExtensions,
   // Simulcast, i.e. more than one encoding.
   RtpParameters simulcast_parameters;
   simulcast_parameters.encodings.resize(2);
-  auto simulcast_sender = rtc::make_ref_counted<MockRtpSenderInternal>();
+  auto simulcast_sender =
+      rtc::make_ref_counted<NiceMock<MockRtpSenderInternal>>();
   EXPECT_CALL(*simulcast_sender, GetParametersInternal())
       .WillRepeatedly(Return(simulcast_parameters));
   auto simulcast_transceiver = rtc::make_ref_counted<RtpTransceiver>(
@@ -537,7 +798,7 @@ TEST_F(RtpTransceiverTestForHeaderExtensions,
   svc_parameters.encodings.resize(1);
   svc_parameters.encodings[0].scalability_mode = "L3T3";
 
-  auto svc_sender = rtc::make_ref_counted<MockRtpSenderInternal>();
+  auto svc_sender = rtc::make_ref_counted<NiceMock<MockRtpSenderInternal>>();
   EXPECT_CALL(*svc_sender, GetParametersInternal())
       .WillRepeatedly(Return(svc_parameters));
   auto svc_transceiver = rtc::make_ref_counted<RtpTransceiver>(
index 0bff20de70649969f7e5b36babfd19f9b1d3c9d3..950efca528f7fed9071c8f023adbbc31191285e2 100644 (file)
@@ -799,7 +799,7 @@ cricket::MediaDescriptionOptions GetMediaDescriptionOptionsForTransceiver(
   cricket::MediaDescriptionOptions media_description_options(
       transceiver->media_type(), mid, transceiver->direction(), stopped);
   media_description_options.codec_preferences =
-      transceiver->codec_preferences();
+      transceiver->filtered_codec_preferences();
   media_description_options.header_extensions =
       transceiver->GetHeaderExtensionsToNegotiate();
   // This behavior is specified in JSEP. The gist is that: