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 =
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) {
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_
}
#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
GetH265TxModeOrDefault(right));
}
#endif
-#endif
// Some (video) codecs are actually families of codecs and rely on parameters
// to distinguish different incompatible family members.
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;
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));
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};
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(
.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"},
],
"payload_type_picker\.cc": [
"+media/base/codec.h",
+ "+media/base/codec_comparators.h",
"+media/base/media_constants.h",
],
"payload_type_picker_unittest\.cc": [
#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"
// 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;
// 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;
}
}
// 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();
}
}
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";
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");
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());
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",
}
sources = [
+ "base/codec_comparators_unittest.cc",
"base/codec_unittest.cc",
"base/media_engine_unittest.cc",
"base/rtp_utils_unittest.cc",
#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;
}
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 {
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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_
--- /dev/null
+/*
+ * 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
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));
}
{
// 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);
#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"
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) {
}
#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) {
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);
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);
// 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);
}
// 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)
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 "
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.
}
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);
}
}
// 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.
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);
}
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);
}
}
// 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);
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);
}
}
// 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);
}
}
#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"
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));
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_;
: 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;
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);
}
// 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) {
}
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,
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;
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
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
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,
// 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));
}
// 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();
// 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));
}
// 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();
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));
}
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);
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));
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
}
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));
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));
}
}
TEST_F(PeerConnectionMediaTestUnifiedPlan,
- SetCodecPreferencesReceiveOnlyWithSendOnlyTransceiverStops) {
+ SetCodecPreferencesRecvOnlyCodecOnSendOnlyTransceiver) {
auto fake_engine = std::make_unique<cricket::FakeMediaEngine>();
std::vector<cricket::Codec> audio_codecs;
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,
#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();
}
// 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();
}
[&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>
"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.
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()
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_;
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
#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 {
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(); }
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;
}
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));
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; });
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));
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_(
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_;
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())
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())
};
// 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),
// 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>(
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>(
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: