codec_comparators.cc (16469B)
1 /* 2 * Copyright 2024 The WebRTC Project Authors. All rights reserved. 3 * 4 * Use of this source code is governed by a BSD-style license 5 * that can be found in the LICENSE file in the root of the source 6 * tree. An additional intellectual property rights grant can be found 7 * in the file PATENTS. All contributing project authors may 8 * be found in the AUTHORS file in the root of the source tree. 9 */ 10 #include "media/base/codec_comparators.h" 11 12 #include <algorithm> 13 #include <cstddef> 14 #include <optional> 15 #include <string> 16 #include <vector> 17 18 #include "absl/algorithm/container.h" 19 #include "absl/functional/any_invocable.h" 20 #include "absl/strings/match.h" 21 #include "absl/strings/string_view.h" 22 #include "api/media_types.h" 23 #include "api/rtp_parameters.h" 24 #include "api/video_codecs/av1_profile.h" 25 #include "api/video_codecs/h264_profile_level_id.h" 26 #ifdef RTC_ENABLE_H265 27 #include "api/video_codecs/h265_profile_tier_level.h" 28 #endif 29 #include "api/video_codecs/vp9_profile.h" 30 #include "media/base/codec.h" 31 #include "media/base/media_constants.h" 32 #include "rtc_base/checks.h" 33 #include "rtc_base/logging.h" 34 #include "rtc_base/string_encode.h" 35 36 namespace webrtc { 37 38 namespace { 39 40 // TODO(bugs.webrtc.org/15847): remove code duplication of IsSameCodecSpecific 41 // in api/video_codecs/sdp_video_format.cc 42 std::string GetFmtpParameterOrDefault(const CodecParameterMap& params, 43 const std::string& name, 44 const std::string& default_value) { 45 const auto it = params.find(name); 46 if (it != params.end()) { 47 return it->second; 48 } 49 return default_value; 50 } 51 52 bool HasParameter(const CodecParameterMap& params, const std::string& name) { 53 return params.find(name) != params.end(); 54 } 55 56 std::string H264GetPacketizationModeOrDefault(const CodecParameterMap& params) { 57 // If packetization-mode is not present, default to "0". 58 // https://tools.ietf.org/html/rfc6184#section-6.2 59 return GetFmtpParameterOrDefault(params, kH264FmtpPacketizationMode, "0"); 60 } 61 62 bool H264IsSamePacketizationMode(const CodecParameterMap& left, 63 const CodecParameterMap& right) { 64 return H264GetPacketizationModeOrDefault(left) == 65 H264GetPacketizationModeOrDefault(right); 66 } 67 68 std::string AV1GetTierOrDefault(const CodecParameterMap& params) { 69 // If the parameter is not present, the tier MUST be inferred to be 0. 70 // https://aomediacodec.github.io/av1-rtp-spec/#72-sdp-parameters 71 return GetFmtpParameterOrDefault(params, kAv1FmtpTier, "0"); 72 } 73 74 std::string AV1GetLevelIdxOrDefault(const CodecParameterMap& params) { 75 // If the parameter is not present, it MUST be inferred to be 5 (level 3.1). 76 // https://aomediacodec.github.io/av1-rtp-spec/#72-sdp-parameters 77 return GetFmtpParameterOrDefault(params, kAv1FmtpLevelIdx, "5"); 78 } 79 80 #ifdef RTC_ENABLE_H265 81 std::string GetH265TxModeOrDefault(const CodecParameterMap& params) { 82 // If TxMode is not present, a value of "SRST" must be inferred. 83 // https://tools.ietf.org/html/rfc7798@section-7.1 84 return GetFmtpParameterOrDefault(params, kH265FmtpTxMode, "SRST"); 85 } 86 87 bool IsSameH265TxMode(const CodecParameterMap& left, 88 const CodecParameterMap& right) { 89 return absl::EqualsIgnoreCase(GetH265TxModeOrDefault(left), 90 GetH265TxModeOrDefault(right)); 91 } 92 #endif 93 94 // Some (video) codecs are actually families of codecs and rely on parameters 95 // to distinguish different incompatible family members. 96 bool IsSameCodecSpecific(const std::string& name1, 97 const CodecParameterMap& params1, 98 const std::string& name2, 99 const CodecParameterMap& params2) { 100 // The names might not necessarily match, so check both. 101 auto either_name_matches = [&](const std::string name) { 102 return absl::EqualsIgnoreCase(name, name1) || 103 absl::EqualsIgnoreCase(name, name2); 104 }; 105 if (either_name_matches(kH264CodecName)) 106 return H264IsSameProfile(params1, params2) && 107 H264IsSamePacketizationMode(params1, params2); 108 if (either_name_matches(kVp9CodecName)) 109 return VP9IsSameProfile(params1, params2); 110 // https://aomediacodec.github.io/av1-rtp-spec/#723-usage-with-the-sdp-offeranswer-model 111 // These media configuration parameters are asymmetrical and the answerer 112 // MAY declare its own media configuration 113 // TODO(bugs.webrtc.org/396434695): for backward compability we currently 114 // compare profile. 115 if (either_name_matches(kAv1CodecName)) 116 return AV1IsSameProfile(params1, params2); 117 #ifdef RTC_ENABLE_H265 118 if (either_name_matches(kH265CodecName)) { 119 return H265IsSameProfile(params1, params2) && 120 H265IsSameTier(params1, params2) && 121 IsSameH265TxMode(params1, params2); 122 } 123 #endif 124 return true; 125 } 126 127 bool ReferencedCodecsMatch(const std::vector<Codec>& codecs1, 128 const int codec1_id, 129 const std::vector<Codec>& codecs2, 130 const int codec2_id) { 131 const Codec* codec1 = FindCodecById(codecs1, codec1_id); 132 const Codec* codec2 = FindCodecById(codecs2, codec2_id); 133 return codec1 != nullptr && codec2 != nullptr && codec1->Matches(*codec2); 134 } 135 136 bool MatchesWithReferenceAttributesAndComparator( 137 const Codec& codec_to_match, 138 const Codec& potential_match, 139 absl::AnyInvocable<bool(int, int)> reference_comparator) { 140 if (!MatchesWithCodecRules(codec_to_match, potential_match)) { 141 return false; 142 } 143 Codec::ResiliencyType resiliency_type = codec_to_match.GetResiliencyType(); 144 if (resiliency_type == Codec::ResiliencyType::kRtx) { 145 int apt_value_1 = 0; 146 int apt_value_2 = 0; 147 if (!codec_to_match.GetParam(kCodecParamAssociatedPayloadType, 148 &apt_value_1) || 149 !potential_match.GetParam(kCodecParamAssociatedPayloadType, 150 &apt_value_2)) { 151 RTC_LOG(LS_WARNING) << "RTX missing associated payload type."; 152 return false; 153 } 154 if (reference_comparator(apt_value_1, apt_value_2)) { 155 return true; 156 } 157 return false; 158 } 159 if (resiliency_type == Codec::ResiliencyType::kRed) { 160 auto red_parameters_1 = 161 codec_to_match.params.find(kCodecParamNotInNameValueFormat); 162 auto red_parameters_2 = 163 potential_match.params.find(kCodecParamNotInNameValueFormat); 164 bool has_parameters_1 = red_parameters_1 != codec_to_match.params.end(); 165 bool has_parameters_2 = red_parameters_2 != potential_match.params.end(); 166 // If codec_to_match has unassigned PT and no parameter, 167 // we assume that it'll be assigned later and return a match. 168 // Note - this should be deleted. It's untidy. 169 if (potential_match.id == Codec::kIdNotSet && !has_parameters_2) { 170 return true; 171 } 172 if (codec_to_match.id == Codec::kIdNotSet && !has_parameters_1) { 173 return true; 174 } 175 if (has_parameters_1 && has_parameters_2) { 176 // Different levels of redundancy between offer and answer are OK 177 // since RED is considered to be declarative. 178 std::vector<absl::string_view> redundant_payloads_1 = 179 split(red_parameters_1->second, '/'); 180 std::vector<absl::string_view> redundant_payloads_2 = 181 split(red_parameters_2->second, '/'); 182 // note: split returns at least 1 string even on empty strings. 183 size_t smallest_size = 184 std::min(redundant_payloads_1.size(), redundant_payloads_2.size()); 185 // If the smaller list is equivalent to the longer list, we consider them 186 // equivalent even if size differs. 187 for (size_t i = 0; i < smallest_size; i++) { 188 int red_value_1; 189 int red_value_2; 190 if (FromString(redundant_payloads_1[i], &red_value_1) && 191 FromString(redundant_payloads_2[i], &red_value_2)) { 192 if (!reference_comparator(red_value_1, red_value_2)) { 193 return false; 194 } 195 } else { 196 // At least one parameter was not an integer. 197 // This is a syntax error, but we allow it here if the whole parameter 198 // equals the other parameter, in order to not generate more errors 199 // by duplicating the bad parameter. 200 return red_parameters_1->second == red_parameters_2->second; 201 } 202 } 203 return true; 204 } 205 if (!has_parameters_1 && !has_parameters_2) { 206 // Both parameters are missing. Happens for video RED. 207 return true; 208 } 209 return false; 210 } 211 return true; // Not a codec with a PT-valued reference. 212 } 213 214 CodecParameterMap InsertDefaultParams(const std::string& name, 215 const CodecParameterMap& params) { 216 CodecParameterMap updated_params = params; 217 if (absl::EqualsIgnoreCase(name, kVp9CodecName)) { 218 if (!HasParameter(params, kVP9FmtpProfileId)) { 219 if (std::optional<VP9Profile> default_profile = 220 ParseSdpForVP9Profile({})) { 221 updated_params.insert( 222 {kVP9FmtpProfileId, VP9ProfileToString(*default_profile)}); 223 } 224 } 225 } 226 if (absl::EqualsIgnoreCase(name, kAv1CodecName)) { 227 if (!HasParameter(params, kAv1FmtpProfile)) { 228 if (std::optional<AV1Profile> default_profile = 229 ParseSdpForAV1Profile({})) { 230 updated_params.insert( 231 {kAv1FmtpProfile, AV1ProfileToString(*default_profile).data()}); 232 } 233 } 234 if (!HasParameter(params, kAv1FmtpTier)) { 235 updated_params.insert({kAv1FmtpTier, AV1GetTierOrDefault({})}); 236 } 237 if (!HasParameter(params, kAv1FmtpLevelIdx)) { 238 updated_params.insert({kAv1FmtpLevelIdx, AV1GetLevelIdxOrDefault({})}); 239 } 240 } 241 if (absl::EqualsIgnoreCase(name, kH264CodecName)) { 242 if (!HasParameter(params, kH264FmtpPacketizationMode)) { 243 updated_params.insert( 244 {kH264FmtpPacketizationMode, H264GetPacketizationModeOrDefault({})}); 245 } 246 } 247 #ifdef RTC_ENABLE_H265 248 if (absl::EqualsIgnoreCase(name, kH265CodecName)) { 249 if (std::optional<H265ProfileTierLevel> default_params = 250 ParseSdpForH265ProfileTierLevel({})) { 251 if (!HasParameter(params, kH265FmtpProfileId)) { 252 updated_params.insert( 253 {kH265FmtpProfileId, H265ProfileToString(default_params->profile)}); 254 } 255 if (!HasParameter(params, kH265FmtpLevelId)) { 256 updated_params.insert( 257 {kH265FmtpLevelId, H265LevelToString(default_params->level)}); 258 } 259 if (!HasParameter(params, kH265FmtpTierFlag)) { 260 updated_params.insert( 261 {kH265FmtpTierFlag, H265TierToString(default_params->tier)}); 262 } 263 } 264 if (!HasParameter(params, kH265FmtpTxMode)) { 265 updated_params.insert({kH265FmtpTxMode, GetH265TxModeOrDefault({})}); 266 } 267 } 268 #endif 269 return updated_params; 270 } 271 272 } // namespace 273 274 bool MatchesWithCodecRules(const Codec& left_codec, const Codec& right_codec) { 275 // Match the codec id/name based on the typical static/dynamic name rules. 276 // Matching is case-insensitive. 277 278 // We support the ranges [96, 127] and more recently [35, 65]. 279 // https://www.iana.org/assignments/rtp-parameters/rtp-parameters.xhtml#rtp-parameters-1 280 // Within those ranges we match by codec name, outside by codec id. 281 // We also match by name if either ID is unassigned. 282 // Since no codecs are assigned an id in the range [66, 95] by us, these will 283 // never match. 284 const int kLowerDynamicRangeMin = 35; 285 const int kLowerDynamicRangeMax = 65; 286 const int kUpperDynamicRangeMin = 96; 287 const int kUpperDynamicRangeMax = 127; 288 const bool is_id_in_dynamic_range = 289 (left_codec.id >= kLowerDynamicRangeMin && 290 left_codec.id <= kLowerDynamicRangeMax) || 291 (left_codec.id >= kUpperDynamicRangeMin && 292 left_codec.id <= kUpperDynamicRangeMax); 293 const bool is_codec_id_in_dynamic_range = 294 (right_codec.id >= kLowerDynamicRangeMin && 295 right_codec.id <= kLowerDynamicRangeMax) || 296 (right_codec.id >= kUpperDynamicRangeMin && 297 right_codec.id <= kUpperDynamicRangeMax); 298 bool matches_id; 299 if ((is_id_in_dynamic_range && is_codec_id_in_dynamic_range) || 300 left_codec.id == Codec::kIdNotSet || right_codec.id == Codec::kIdNotSet) { 301 matches_id = absl::EqualsIgnoreCase(left_codec.name, right_codec.name); 302 } else { 303 matches_id = (left_codec.id == right_codec.id); 304 } 305 306 auto matches_type_specific = [&]() { 307 switch (left_codec.type) { 308 case Codec::Type::kAudio: 309 // If a nonzero bitrate is specified, it must match the 310 // actual bitrate, unless the codec is VBR (0), where we just force the 311 // supplied value. The number of channels must match exactly, with the 312 // exception that channels=0 is treated synonymously as channels=1, per 313 // RFC 4566 section 6: " [The channels] parameter is OPTIONAL and may be 314 // omitted if the number of channels is one." 315 // Preference is ignored. 316 return ((left_codec.clockrate == right_codec.clockrate) && 317 (right_codec.bitrate == 0 || left_codec.bitrate <= 0 || 318 left_codec.bitrate == right_codec.bitrate) && 319 ((right_codec.channels < 2 && left_codec.channels < 2) || 320 left_codec.channels == right_codec.channels)); 321 322 case Codec::Type::kVideo: 323 return IsSameCodecSpecific(left_codec.name, left_codec.params, 324 right_codec.name, right_codec.params); 325 } 326 }; 327 328 return matches_id && matches_type_specific(); 329 } 330 331 bool MatchesWithReferenceAttributes(const Codec& codec1, const Codec& codec2) { 332 return MatchesWithReferenceAttributesAndComparator( 333 codec1, codec2, [](int a, int b) { return a == b; }); 334 } 335 336 // Finds a codec in `codecs2` that matches `codec_to_match`, which is 337 // a member of `codecs1`. If `codec_to_match` is an RED or RTX codec, both 338 // the codecs themselves and their associated codecs must match. 339 std::optional<Codec> FindMatchingCodec(const std::vector<Codec>& codecs1, 340 const std::vector<Codec>& codecs2, 341 const Codec& codec_to_match) { 342 // `codec_to_match` should be a member of `codecs1`, in order to look up 343 // RED/RTX codecs' associated codecs correctly. If not, that's a programming 344 // error. 345 RTC_DCHECK(absl::c_any_of(codecs1, [&codec_to_match](const Codec& codec) { 346 return &codec == &codec_to_match; 347 })); 348 for (const Codec& potential_match : codecs2) { 349 if (MatchesWithReferenceAttributesAndComparator( 350 codec_to_match, potential_match, 351 [&codecs1, &codecs2](int a, int b) { 352 return ReferencedCodecsMatch(codecs1, a, codecs2, b); 353 })) { 354 return potential_match; 355 } 356 } 357 return std::nullopt; 358 } 359 360 bool IsSameRtpCodec(const Codec& codec, const RtpCodec& rtp_codec) { 361 RtpCodecParameters rtp_codec2 = codec.ToCodecParameters(); 362 363 return absl::EqualsIgnoreCase(rtp_codec.name, rtp_codec2.name) && 364 rtp_codec.kind == rtp_codec2.kind && 365 rtp_codec.num_channels == rtp_codec2.num_channels && 366 rtp_codec.clock_rate == rtp_codec2.clock_rate && 367 InsertDefaultParams(rtp_codec.name, rtp_codec.parameters) == 368 InsertDefaultParams(rtp_codec2.name, rtp_codec2.parameters); 369 } 370 371 bool IsSameRtpCodecIgnoringLevel(const Codec& codec, 372 const RtpCodec& rtp_codec) { 373 RtpCodecParameters rtp_codec2 = codec.ToCodecParameters(); 374 375 if (!absl::EqualsIgnoreCase(rtp_codec.name, rtp_codec2.name) || 376 rtp_codec.kind != rtp_codec2.kind || 377 rtp_codec.num_channels != rtp_codec2.num_channels || 378 rtp_codec.clock_rate != rtp_codec2.clock_rate) { 379 return false; 380 } 381 382 CodecParameterMap params1 = 383 InsertDefaultParams(rtp_codec.name, rtp_codec.parameters); 384 CodecParameterMap params2 = 385 InsertDefaultParams(rtp_codec2.name, rtp_codec2.parameters); 386 387 // Some video codecs are compatible with others (e.g. same profile but 388 // different level). This comparison looks at the relevant parameters, 389 // ignoring ones that are either irrelevant or unrecognized. 390 if (rtp_codec.kind == MediaType::VIDEO && rtp_codec.IsMediaCodec()) { 391 return IsSameCodecSpecific(rtp_codec.name, params1, rtp_codec2.name, 392 params2); 393 } 394 // audio/RED should ignore the parameters which specify payload types so 395 // can not be compared. 396 if (rtp_codec.kind == MediaType::AUDIO && rtp_codec.name == kRedCodecName) { 397 return true; 398 } 399 400 return params1 == params2; 401 } 402 403 } // namespace webrtc