diff --git a/api/video_codecs/BUILD.gn b/api/video_codecs/BUILD.gn index 4e28cd7e1c..90d3742382 100644 --- a/api/video_codecs/BUILD.gn +++ b/api/video_codecs/BUILD.gn @@ -44,6 +44,7 @@ rtc_library("video_codecs_api") { deps = [ "..:fec_controller_api", "..:scoped_refptr", + "../../api:array_view", "../../modules/video_coding:codec_globals_headers", "../../rtc_base:checks", "../../rtc_base:rtc_base_approved", diff --git a/api/video_codecs/builtin_video_encoder_factory.cc b/api/video_codecs/builtin_video_encoder_factory.cc index 2f722a4a5c..9463a9cdf2 100644 --- a/api/video_codecs/builtin_video_encoder_factory.cc +++ b/api/video_codecs/builtin_video_encoder_factory.cc @@ -26,18 +26,6 @@ namespace webrtc { namespace { -bool IsFormatSupported(const std::vector& supported_formats, - const SdpVideoFormat& format) { - for (const SdpVideoFormat& supported_format : supported_formats) { - if (cricket::IsSameCodec(format.name, format.parameters, - supported_format.name, - supported_format.parameters)) { - return true; - } - } - return false; -} - // This class wraps the internal factory and adds simulcast. class BuiltinVideoEncoderFactory : public VideoEncoderFactory { public: @@ -47,8 +35,8 @@ class BuiltinVideoEncoderFactory : public VideoEncoderFactory { VideoEncoderFactory::CodecInfo QueryVideoEncoder( const SdpVideoFormat& format) const override { // Format must be one of the internal formats. - RTC_DCHECK(IsFormatSupported( - internal_encoder_factory_->GetSupportedFormats(), format)); + RTC_DCHECK( + format.IsCodecInList(internal_encoder_factory_->GetSupportedFormats())); VideoEncoderFactory::CodecInfo info; return info; } @@ -57,8 +45,8 @@ class BuiltinVideoEncoderFactory : public VideoEncoderFactory { const SdpVideoFormat& format) override { // Try creating internal encoder. std::unique_ptr internal_encoder; - if (IsFormatSupported(internal_encoder_factory_->GetSupportedFormats(), - format)) { + if (format.IsCodecInList( + internal_encoder_factory_->GetSupportedFormats())) { internal_encoder = std::make_unique( internal_encoder_factory_.get(), format); } diff --git a/api/video_codecs/sdp_video_format.cc b/api/video_codecs/sdp_video_format.cc index f8901492ee..689c337ced 100644 --- a/api/video_codecs/sdp_video_format.cc +++ b/api/video_codecs/sdp_video_format.cc @@ -10,10 +10,57 @@ #include "api/video_codecs/sdp_video_format.h" +#include "absl/strings/match.h" +#include "api/video_codecs/h264_profile_level_id.h" +#include "api/video_codecs/video_codec.h" +#include "api/video_codecs/vp9_profile.h" +#include "rtc_base/checks.h" #include "rtc_base/strings/string_builder.h" namespace webrtc { +namespace { + +std::string H264GetPacketizationModeOrDefault( + const SdpVideoFormat::Parameters& params) { + constexpr char kH264FmtpPacketizationMode[] = "packetization-mode"; + const auto it = params.find(kH264FmtpPacketizationMode); + if (it != params.end()) { + return it->second; + } + // If packetization-mode is not present, default to "0". + // https://tools.ietf.org/html/rfc6184#section-6.2 + return "0"; +} + +bool H264IsSamePacketizationMode(const SdpVideoFormat::Parameters& left, + const SdpVideoFormat::Parameters& right) { + return H264GetPacketizationModeOrDefault(left) == + H264GetPacketizationModeOrDefault(right); +} + +// Some (video) codecs are actually families of codecs and rely on parameters +// to distinguish different incompatible family members. +bool IsSameCodecSpecific(const SdpVideoFormat& format1, + const SdpVideoFormat& format2) { + // The assumption when calling this function is that the two formats have the + // same name. + RTC_DCHECK(absl::EqualsIgnoreCase(format1.name, format2.name)); + + VideoCodecType codec_type = PayloadStringToCodecType(format1.name); + switch (codec_type) { + case kVideoCodecH264: + return H264IsSameProfile(format1.parameters, format2.parameters) && + H264IsSamePacketizationMode(format1.parameters, + format2.parameters); + case kVideoCodecVP9: + return VP9IsSameProfile(format1.parameters, format2.parameters); + default: + return true; + } +} +} // namespace + SdpVideoFormat::SdpVideoFormat(const std::string& name) : name(name) {} SdpVideoFormat::SdpVideoFormat(const std::string& name, @@ -37,6 +84,23 @@ std::string SdpVideoFormat::ToString() const { return builder.str(); } +bool SdpVideoFormat::IsSameCodec(const SdpVideoFormat& other) const { + // Two codecs are considered the same if the name matches (case insensitive) + // and certain codec-specific parameters match. + return absl::EqualsIgnoreCase(name, other.name) && + IsSameCodecSpecific(*this, other); +} + +bool SdpVideoFormat::IsCodecInList( + rtc::ArrayView formats) const { + for (const auto& format : formats) { + if (IsSameCodec(format)) { + return true; + } + } + return false; +} + bool operator==(const SdpVideoFormat& a, const SdpVideoFormat& b) { return a.name == b.name && a.parameters == b.parameters; } diff --git a/api/video_codecs/sdp_video_format.h b/api/video_codecs/sdp_video_format.h index 97bb75489d..a1e23f4f9c 100644 --- a/api/video_codecs/sdp_video_format.h +++ b/api/video_codecs/sdp_video_format.h @@ -14,6 +14,7 @@ #include #include +#include "api/array_view.h" #include "rtc_base/system/rtc_export.h" namespace webrtc { @@ -32,6 +33,13 @@ struct RTC_EXPORT SdpVideoFormat { ~SdpVideoFormat(); + // Returns true if the SdpVideoFormats have the same names as well as codec + // specific parameters. Please note that two SdpVideoFormats can represent the + // same codec even though not all parameters are the same. + bool IsSameCodec(const SdpVideoFormat& other) const; + bool IsCodecInList( + rtc::ArrayView formats) const; + std::string ToString() const; friend RTC_EXPORT bool operator==(const SdpVideoFormat& a, diff --git a/api/video_codecs/test/BUILD.gn b/api/video_codecs/test/BUILD.gn index ea8e6d5bee..c082dbc562 100644 --- a/api/video_codecs/test/BUILD.gn +++ b/api/video_codecs/test/BUILD.gn @@ -14,6 +14,7 @@ if (rtc_include_tests) { sources = [ "builtin_video_encoder_factory_unittest.cc", "h264_profile_level_id_unittest.cc", + "sdp_video_format_unittest.cc", "video_decoder_software_fallback_wrapper_unittest.cc", "video_encoder_software_fallback_wrapper_unittest.cc", ] diff --git a/api/video_codecs/test/sdp_video_format_unittest.cc b/api/video_codecs/test/sdp_video_format_unittest.cc new file mode 100644 index 0000000000..d55816690e --- /dev/null +++ b/api/video_codecs/test/sdp_video_format_unittest.cc @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2021 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 "api/video_codecs/sdp_video_format.h" + +#include + +#include "test/gtest.h" + +namespace webrtc { + +typedef SdpVideoFormat Sdp; +typedef SdpVideoFormat::Parameters Params; + +TEST(SdpVideoFormatTest, SameCodecNameNoParameters) { + EXPECT_TRUE(Sdp("H264").IsSameCodec(Sdp("h264"))); + EXPECT_TRUE(Sdp("VP8").IsSameCodec(Sdp("vp8"))); + EXPECT_TRUE(Sdp("Vp9").IsSameCodec(Sdp("vp9"))); + EXPECT_TRUE(Sdp("AV1").IsSameCodec(Sdp("Av1"))); +} +TEST(SdpVideoFormatTest, DifferentCodecNameNoParameters) { + EXPECT_FALSE(Sdp("H264").IsSameCodec(Sdp("VP8"))); + EXPECT_FALSE(Sdp("VP8").IsSameCodec(Sdp("VP9"))); + EXPECT_FALSE(Sdp("AV1").IsSameCodec(Sdp(""))); +} +TEST(SdpVideoFormatTest, SameCodecNameSameParameters) { + EXPECT_TRUE(Sdp("VP9").IsSameCodec(Sdp("VP9", Params{{"profile-id", "0"}}))); + EXPECT_TRUE(Sdp("VP9", Params{{"profile-id", "0"}}) + .IsSameCodec(Sdp("VP9", Params{{"profile-id", "0"}}))); + EXPECT_TRUE(Sdp("VP9", Params{{"profile-id", "2"}}) + .IsSameCodec(Sdp("VP9", Params{{"profile-id", "2"}}))); + EXPECT_TRUE( + Sdp("H264", Params{{"profile-level-id", "42e01f"}}) + .IsSameCodec(Sdp("H264", Params{{"profile-level-id", "42e01f"}}))); + EXPECT_TRUE( + Sdp("H264", Params{{"profile-level-id", "640c34"}}) + .IsSameCodec(Sdp("H264", Params{{"profile-level-id", "640c34"}}))); +} + +TEST(SdpVideoFormatTest, SameCodecNameDifferentParameters) { + EXPECT_FALSE(Sdp("VP9").IsSameCodec(Sdp("VP9", Params{{"profile-id", "2"}}))); + EXPECT_FALSE(Sdp("VP9", Params{{"profile-id", "0"}}) + .IsSameCodec(Sdp("VP9", Params{{"profile-id", "1"}}))); + EXPECT_FALSE(Sdp("VP9", Params{{"profile-id", "2"}}) + .IsSameCodec(Sdp("VP9", Params{{"profile-id", "0"}}))); + EXPECT_FALSE( + Sdp("H264", Params{{"profile-level-id", "42e01f"}}) + .IsSameCodec(Sdp("H264", Params{{"profile-level-id", "640c34"}}))); + EXPECT_FALSE( + Sdp("H264", Params{{"profile-level-id", "640c34"}}) + .IsSameCodec(Sdp("H264", Params{{"profile-level-id", "42f00b"}}))); +} + +TEST(SdpVideoFormatTest, DifferentCodecNameSameParameters) { + EXPECT_FALSE(Sdp("VP9", Params{{"profile-id", "0"}}) + .IsSameCodec(Sdp("H264", Params{{"profile-id", "0"}}))); + EXPECT_FALSE(Sdp("VP9", Params{{"profile-id", "2"}}) + .IsSameCodec(Sdp("VP8", Params{{"profile-id", "2"}}))); + EXPECT_FALSE( + Sdp("H264", Params{{"profile-level-id", "42e01f"}}) + .IsSameCodec(Sdp("VP9", Params{{"profile-level-id", "42e01f"}}))); + EXPECT_FALSE( + Sdp("H264", Params{{"profile-level-id", "640c34"}}) + .IsSameCodec(Sdp("VP8", Params{{"profile-level-id", "640c34"}}))); +} + +} // namespace webrtc diff --git a/media/base/codec.cc b/media/base/codec.cc index e8a591e44b..6b0e4886fc 100644 --- a/media/base/codec.cc +++ b/media/base/codec.cc @@ -58,18 +58,6 @@ bool IsSameCodecSpecific(const std::string& name1, return true; } -bool IsCodecInList( - const webrtc::SdpVideoFormat& format, - const std::vector& existing_formats) { - for (auto existing_format : existing_formats) { - if (IsSameCodec(format.name, format.parameters, existing_format.name, - existing_format.parameters)) { - return true; - } - } - return false; -} - } // namespace FeedbackParams::FeedbackParams() = default; @@ -452,6 +440,8 @@ const VideoCodec* FindMatchingCodec( return nullptr; } +// TODO(crbug.com/1187565): Remove once downstream projects stopped using this +// method in favor of SdpVideoFormat::IsSameCodec(). bool IsSameCodec(const std::string& name1, const CodecParameterMap& params1, const std::string& name2, @@ -493,7 +483,7 @@ void AddH264ConstrainedBaselineProfileToSupportedFormats( std::copy_if(cbr_supported_formats.begin(), cbr_supported_formats.end(), std::back_inserter(*supported_formats), [supported_formats](const webrtc::SdpVideoFormat& format) { - return !IsCodecInList(format, *supported_formats); + return !format.IsCodecInList(*supported_formats); }); if (supported_formats->size() > original_size) { diff --git a/media/engine/internal_decoder_factory.cc b/media/engine/internal_decoder_factory.cc index 1c084846a2..a8d1f00009 100644 --- a/media/engine/internal_decoder_factory.cc +++ b/media/engine/internal_decoder_factory.cc @@ -23,23 +23,6 @@ namespace webrtc { -namespace { - -bool IsFormatSupported( - const std::vector& supported_formats, - const webrtc::SdpVideoFormat& format) { - for (const webrtc::SdpVideoFormat& supported_format : supported_formats) { - if (cricket::IsSameCodec(format.name, format.parameters, - supported_format.name, - supported_format.parameters)) { - return true; - } - } - return false; -} - -} // namespace - std::vector InternalDecoderFactory::GetSupportedFormats() const { std::vector formats; @@ -55,7 +38,7 @@ std::vector InternalDecoderFactory::GetSupportedFormats() std::unique_ptr InternalDecoderFactory::CreateVideoDecoder( const SdpVideoFormat& format) { - if (!IsFormatSupported(GetSupportedFormats(), format)) { + if (!format.IsCodecInList(GetSupportedFormats())) { RTC_LOG(LS_WARNING) << "Trying to create decoder for unsupported format. " << format.ToString(); return nullptr; diff --git a/media/engine/webrtc_video_engine.cc b/media/engine/webrtc_video_engine.cc index cc2a181bbf..710e4fd6dc 100644 --- a/media/engine/webrtc_video_engine.cc +++ b/media/engine/webrtc_video_engine.cc @@ -759,8 +759,8 @@ WebRtcVideoChannel::SelectSendVideoCodecs( // following the spec in https://tools.ietf.org/html/rfc6184#section-8.2.2 // since we should limit the encode level to the lower of local and remote // level when level asymmetry is not allowed. - if (IsSameCodec(format_it->name, format_it->parameters, - remote_codec.codec.name, remote_codec.codec.params)) { + if (format_it->IsSameCodec( + {remote_codec.codec.name, remote_codec.codec.params})) { encoders.push_back(remote_codec); // To allow the VideoEncoderFactory to keep information about which @@ -954,8 +954,8 @@ void WebRtcVideoChannel::RequestEncoderSwitch( RTC_DCHECK_RUN_ON(&thread_checker_); for (const VideoCodecSettings& codec_setting : negotiated_codecs_) { - if (IsSameCodec(format.name, format.parameters, codec_setting.codec.name, - codec_setting.codec.params)) { + if (format.IsSameCodec( + {codec_setting.codec.name, codec_setting.codec.params})) { VideoCodecSettings new_codec_setting = codec_setting; for (const auto& kv : format.parameters) { new_codec_setting.codec.params[kv.first] = kv.second;