tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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