diff --git a/media/base/codec_comparators.cc b/media/base/codec_comparators.cc index 52dd23221e..4ed48b96b3 100644 --- a/media/base/codec_comparators.cc +++ b/media/base/codec_comparators.cc @@ -49,6 +49,10 @@ std::string GetFmtpParameterOrDefault(const CodecParameterMap& params, 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 @@ -215,6 +219,66 @@ bool MatchesWithReferenceAttributesAndComparator( 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 default_profile = + ParseSdpForVP9Profile({})) { + updated_params.insert( + {kVP9FmtpProfileId, VP9ProfileToString(*default_profile)}); + } + } + } + if (absl::EqualsIgnoreCase(name, cricket::kAv1CodecName)) { + if (!HasParameter(params, cricket::kAv1FmtpProfile)) { + if (std::optional 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 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) { @@ -307,4 +371,15 @@ std::optional FindMatchingCodec(const std::vector& codecs1, 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); +} + } // namespace webrtc diff --git a/media/base/codec_comparators.h b/media/base/codec_comparators.h index a8b425566b..d832d151ef 100644 --- a/media/base/codec_comparators.h +++ b/media/base/codec_comparators.h @@ -14,6 +14,7 @@ #include #include +#include "api/rtp_parameters.h" #include "media/base/codec.h" namespace webrtc { @@ -39,6 +40,10 @@ std::optional FindMatchingCodec( const std::vector& 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); + } // namespace webrtc #endif // MEDIA_BASE_CODEC_COMPARATORS_H_ diff --git a/media/base/codec_comparators_unittest.cc b/media/base/codec_comparators_unittest.cc index dcbe08a318..9b99785dd3 100644 --- a/media/base/codec_comparators_unittest.cc +++ b/media/base/codec_comparators_unittest.cc @@ -9,9 +9,14 @@ */ #include "media/base/codec_comparators.h" +#include + #include "api/audio_codecs/audio_format.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 { @@ -21,6 +26,8 @@ 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"); @@ -72,4 +79,129 @@ TEST(CodecComparatorsTest, StaticPayloadTypesIgnoreName) { EXPECT_TRUE(MatchesWithCodecRules(codec_1, codec_2)); } +struct TestParams { + std::string name; + SdpVideoFormat codec1; + SdpVideoFormat codec2; + bool expected_result; +}; + +using IsSameRtpCodecTest = TestWithParam; + +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({ + {.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& info) { + return info.param.name; + }); + } // namespace webrtc diff --git a/media/base/media_engine.cc b/media/base/media_engine.cc index c4b4f817ac..9aeac42b64 100644 --- a/media/base/media_engine.cc +++ b/media/base/media_engine.cc @@ -19,10 +19,24 @@ #include "absl/algorithm/container.h" #include "api/field_trials_view.h" #include "api/video/video_bitrate_allocation.h" +#include "media/base/codec_comparators.h" #include "rtc_base/checks.h" #include "rtc_base/string_encode.h" namespace cricket { +namespace { +bool SupportsMode(const cricket::Codec& codec, + std::optional scalability_mode) { + if (!scalability_mode.has_value()) { + return true; + } + return absl::c_any_of( + codec.scalability_modes, [&](webrtc::ScalabilityMode mode) { + return ScalabilityModeToString(mode) == *scalability_mode; + }); +} + +} // namespace RtpCapabilities::RtpCapabilities() = default; RtpCapabilities::~RtpCapabilities() = default; @@ -82,7 +96,8 @@ webrtc::RTCError CheckScalabilityModeValues( if (rtp_parameters.encodings[i].codec) { bool codecFound = false; for (const cricket::Codec& codec : send_codecs) { - if (codec.MatchesRtpCodec(*rtp_parameters.encodings[i].codec)) { + if (IsSameRtpCodec(codec, *rtp_parameters.encodings[i].codec) && + SupportsMode(codec, rtp_parameters.encodings[i].scalability_mode)) { codecFound = true; send_codec = codec; break; diff --git a/media/engine/webrtc_video_engine.cc b/media/engine/webrtc_video_engine.cc index 4fee59c6df..bfb2e22b46 100644 --- a/media/engine/webrtc_video_engine.cc +++ b/media/engine/webrtc_video_engine.cc @@ -70,6 +70,7 @@ #include "call/video_send_stream.h" #include "common_video/frame_counts.h" #include "media/base/codec.h" +#include "media/base/codec_comparators.h" #include "media/base/media_channel.h" #include "media/base/media_channel_impl.h" #include "media/base/media_config.h" @@ -1115,8 +1116,8 @@ bool WebRtcVideoSendChannel::GetChangedSenderParameters( if (rtp_parameters.encodings[0].codec) { auto matched_codec = absl::c_find_if(negotiated_codecs, [&](auto negotiated_codec) { - return negotiated_codec.codec.MatchesRtpCodec( - *rtp_parameters.encodings[0].codec); + return IsSameRtpCodec(negotiated_codec.codec, + *rtp_parameters.encodings[0].codec); }); if (matched_codec != negotiated_codecs.end()) { force_codec = *matched_codec; @@ -1152,7 +1153,7 @@ bool WebRtcVideoSendChannel::GetChangedSenderParameters( if (encoding.codec) { auto matched_codec = absl::c_find_if(negotiated_codecs, [&](auto negotiated_codec) { - return negotiated_codec.codec.MatchesRtpCodec(*encoding.codec); + return IsSameRtpCodec(negotiated_codec.codec, *encoding.codec); }); if (matched_codec != negotiated_codecs.end()) { send_codecs.push_back(*matched_codec); @@ -1446,13 +1447,13 @@ webrtc::RTCError WebRtcVideoSendChannel::SetRtpSendParameters( // the first layer. // TODO(orphis): Support mixed-codec simulcast if (parameters.encodings[0].codec && send_codec_ && - !send_codec_->codec.MatchesRtpCodec(*parameters.encodings[0].codec)) { + !IsSameRtpCodec(send_codec_->codec, *parameters.encodings[0].codec)) { RTC_LOG(LS_VERBOSE) << "Trying to change codec to " << parameters.encodings[0].codec->name; auto matched_codec = absl::c_find_if(negotiated_codecs_, [&](auto negotiated_codec) { - return negotiated_codec.codec.MatchesRtpCodec( - *parameters.encodings[0].codec); + return IsSameRtpCodec(negotiated_codec.codec, + *parameters.encodings[0].codec); }); if (matched_codec == negotiated_codecs_.end()) { return webrtc::InvokeSetParametersCallback( diff --git a/pc/peer_connection_rtp_unittest.cc b/pc/peer_connection_rtp_unittest.cc index ec9f215c1f..0bf396135a 100644 --- a/pc/peer_connection_rtp_unittest.cc +++ b/pc/peer_connection_rtp_unittest.cc @@ -1786,6 +1786,36 @@ TEST_F(PeerConnectionRtpTestUnifiedPlan, CheckForInvalidEncodingParameters) { .error() .type()); init.send_encodings = default_send_encodings; + + init.send_encodings[0].scalability_mode = std::nullopt; + init.send_encodings[0].codec = + cricket::CreateVideoCodec(SdpVideoFormat("VP8", {})).ToCodecParameters(); + EXPECT_EQ(RTCErrorType::NONE, + caller->pc() + ->AddTransceiver(cricket::MEDIA_TYPE_VIDEO, init) + .error() + .type()); + init.send_encodings = default_send_encodings; + + init.send_encodings[0].scalability_mode = "L1T2"; + init.send_encodings[0].codec = + cricket::CreateVideoCodec(SdpVideoFormat("VP8", {})).ToCodecParameters(); + EXPECT_EQ(RTCErrorType::NONE, + caller->pc() + ->AddTransceiver(cricket::MEDIA_TYPE_VIDEO, init) + .error() + .type()); + init.send_encodings = default_send_encodings; + + init.send_encodings[0].scalability_mode = "L2T2"; + init.send_encodings[0].codec = + cricket::CreateVideoCodec(SdpVideoFormat("VP8", {})).ToCodecParameters(); + EXPECT_EQ(RTCErrorType::UNSUPPORTED_OPERATION, + caller->pc() + ->AddTransceiver(cricket::MEDIA_TYPE_VIDEO, init) + .error() + .type()); + init.send_encodings = default_send_encodings; } // Test that AddTransceiver transfers the send_encodings to the sender and they