| /* | |
| * Copyright (c) 2018 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 <optional> | |
| #include <string> | |
| #include "absl/container/inlined_vector.h" | |
| #include "absl/strings/match.h" | |
| #include "api/array_view.h" | |
| #include "api/rtp_parameters.h" | |
| #include "api/video/video_codec_type.h" | |
| #include "api/video_codecs/av1_profile.h" | |
| #include "api/video_codecs/h264_profile_level_id.h" | |
| #include "api/video_codecs/scalability_mode.h" | |
| #ifdef RTC_ENABLE_H265 | |
| #include "api/video_codecs/h265_profile_tier_level.h" | |
| #endif | |
| #include "api/video_codecs/video_codec.h" | |
| #include "api/video_codecs/vp9_profile.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 webrtc { | |
| namespace { | |
| // TODO(bugs.webrtc.org/15847): remove code duplication of IsSameCodecSpecific | |
| // in media/base/codec.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; | |
| } | |
| 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, kH264FmtpPacketizationMode, "0"); | |
| } | |
| bool H264IsSamePacketizationMode(const CodecParameterMap& left, | |
| const CodecParameterMap& right) { | |
| return H264GetPacketizationModeOrDefault(left) == | |
| H264GetPacketizationModeOrDefault(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, 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 assumption when calling this function is that the two formats have the | |
| // same name. | |
| RTC_DCHECK(absl::EqualsIgnoreCase(name1, name2)); | |
| VideoCodecType codec_type = PayloadStringToCodecType(name1); | |
| switch (codec_type) { | |
| case kVideoCodecH264: | |
| return H264IsSameProfile(params1, params2) && | |
| H264IsSamePacketizationMode(params1, params2); | |
| case kVideoCodecVP9: | |
| return VP9IsSameProfile(params1, params2); | |
| case kVideoCodecAV1: | |
| // https://aomediacodec.github.io/av1-rtp-spec/#723-usage-with-the-sdp-offeranswer-model | |
| // These media configuration parameters are asymmetrical and the | |
| // answerer MAY declare its own media configuration | |
| // TODO(bugs.webrtc.org/396434695): for backward compability we currently | |
| // compare profile. | |
| return AV1IsSameProfile(params1, params2); | |
| #ifdef RTC_ENABLE_H265 | |
| case kVideoCodecH265: | |
| return H265IsSameProfile(params1, params2) && | |
| H265IsSameTier(params1, params2) && | |
| IsSameH265TxMode(params1, params2); | |
| #endif | |
| default: | |
| return true; | |
| } | |
| } | |
| } // namespace | |
| SdpVideoFormat::SdpVideoFormat(const std::string& name) : name(name) {} | |
| SdpVideoFormat::SdpVideoFormat(const std::string& name, | |
| const CodecParameterMap& parameters) | |
| : name(name), parameters(parameters) {} | |
| SdpVideoFormat::SdpVideoFormat( | |
| const std::string& name, | |
| const CodecParameterMap& parameters, | |
| const absl::InlinedVector<ScalabilityMode, kScalabilityModeCount>& | |
| scalability_modes) | |
| : name(name), | |
| parameters(parameters), | |
| scalability_modes(scalability_modes) {} | |
| SdpVideoFormat::SdpVideoFormat( | |
| const SdpVideoFormat& format, | |
| const absl::InlinedVector<ScalabilityMode, kScalabilityModeCount>& modes) | |
| : SdpVideoFormat(format) { | |
| scalability_modes = modes; | |
| } | |
| SdpVideoFormat::SdpVideoFormat(const SdpVideoFormat&) = default; | |
| SdpVideoFormat::SdpVideoFormat(SdpVideoFormat&&) = default; | |
| SdpVideoFormat& SdpVideoFormat::operator=(const SdpVideoFormat&) = default; | |
| SdpVideoFormat& SdpVideoFormat::operator=(SdpVideoFormat&&) = default; | |
| SdpVideoFormat::~SdpVideoFormat() = default; | |
| std::string SdpVideoFormat::ToString() const { | |
| StringBuilder builder; | |
| builder << "Codec name: " << name << ", parameters: {"; | |
| for (const auto& kv : parameters) { | |
| builder << " " << kv.first << "=" << kv.second; | |
| } | |
| builder << " }"; | |
| if (!scalability_modes.empty()) { | |
| builder << ", scalability_modes: ["; | |
| bool first = true; | |
| for (const auto scalability_mode : scalability_modes) { | |
| if (first) { | |
| first = false; | |
| } else { | |
| builder << ", "; | |
| } | |
| builder << ScalabilityModeToString(scalability_mode); | |
| } | |
| builder << "]"; | |
| } | |
| return builder.Release(); | |
| } | |
| 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(name, parameters, other.name, other.parameters); | |
| } | |
| bool SdpVideoFormat::IsCodecInList( | |
| ArrayView<const SdpVideoFormat> 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 && | |
| a.scalability_modes == b.scalability_modes; | |
| } | |
| const SdpVideoFormat SdpVideoFormat::VP8() { | |
| return SdpVideoFormat(kVp8CodecName, {}); | |
| } | |
| const SdpVideoFormat SdpVideoFormat::H264() { | |
| // H264 will typically require more tweaking like setting | |
| // * packetization-mode (which defaults to 0 but 1 is more common) | |
| // * level-asymmetry-allowed (which defaults to 0 but 1 is more common) | |
| // * profile-level-id of which there are many. | |
| return SdpVideoFormat(kH264CodecName, {}); | |
| } | |
| const SdpVideoFormat SdpVideoFormat::H265() { | |
| return SdpVideoFormat(kH265CodecName, {}); | |
| } | |
| const SdpVideoFormat SdpVideoFormat::VP9Profile0() { | |
| return SdpVideoFormat( | |
| kVp9CodecName, | |
| {{kVP9FmtpProfileId, VP9ProfileToString(VP9Profile::kProfile0)}}); | |
| } | |
| const SdpVideoFormat SdpVideoFormat::VP9Profile1() { | |
| return SdpVideoFormat( | |
| kVp9CodecName, | |
| {{kVP9FmtpProfileId, VP9ProfileToString(VP9Profile::kProfile1)}}); | |
| } | |
| const SdpVideoFormat SdpVideoFormat::VP9Profile2() { | |
| return SdpVideoFormat( | |
| kVp9CodecName, | |
| {{kVP9FmtpProfileId, VP9ProfileToString(VP9Profile::kProfile2)}}); | |
| } | |
| const SdpVideoFormat SdpVideoFormat::VP9Profile3() { | |
| return SdpVideoFormat( | |
| kVp9CodecName, | |
| {{kVP9FmtpProfileId, VP9ProfileToString(VP9Profile::kProfile3)}}); | |
| } | |
| const SdpVideoFormat SdpVideoFormat::AV1Profile0() { | |
| // https://aomediacodec.github.io/av1-rtp-spec/#72-sdp-parameters | |
| return SdpVideoFormat( | |
| kAv1CodecName, | |
| {{kAv1FmtpProfile, AV1ProfileToString(AV1Profile::kProfile0).data()}, | |
| {kAv1FmtpLevelIdx, "5"}, | |
| {kAv1FmtpTier, "0"}}); | |
| } | |
| const SdpVideoFormat SdpVideoFormat::AV1Profile1() { | |
| // https://aomediacodec.github.io/av1-rtp-spec/#72-sdp-parameters | |
| return SdpVideoFormat( | |
| kAv1CodecName, | |
| {{kAv1FmtpProfile, AV1ProfileToString(AV1Profile::kProfile1).data()}, | |
| {kAv1FmtpLevelIdx, "5"}, | |
| {kAv1FmtpTier, "0"}}); | |
| } | |
| std::optional<SdpVideoFormat> FuzzyMatchSdpVideoFormat( | |
| ArrayView<const SdpVideoFormat> supported_formats, | |
| const SdpVideoFormat& format) { | |
| std::optional<SdpVideoFormat> res; | |
| int best_parameter_match = 0; | |
| for (const auto& supported_format : supported_formats) { | |
| if (absl::EqualsIgnoreCase(supported_format.name, format.name)) { | |
| int matching_parameters = 0; | |
| for (const auto& kv : supported_format.parameters) { | |
| auto it = format.parameters.find(kv.first); | |
| if (it != format.parameters.end() && it->second == kv.second) { | |
| matching_parameters += 1; | |
| } | |
| } | |
| if (!res || matching_parameters > best_parameter_match) { | |
| res = supported_format; | |
| best_parameter_match = matching_parameters; | |
| } | |
| } | |
| } | |
| if (!res) { | |
| RTC_LOG(LS_INFO) << "Failed to match SdpVideoFormat " << format.ToString(); | |
| } else if (*res != format) { | |
| RTC_LOG(LS_INFO) << "Matched SdpVideoFormat " << format.ToString() | |
| << " with " << res->ToString(); | |
| } | |
| return res; | |
| } | |
| } // namespace webrtc |