Johannes Kron | c3fcee7 | 2021-04-19 07:09:26 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (c) 2019 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 | |
| 11 | #include "media/base/sdp_video_format_utils.h" |
| 12 | |
| 13 | #include <cstring> |
| 14 | #include <map> |
Philipp Hancke | 7c5f9cf | 2024-02-20 14:28:14 | [diff] [blame] | 15 | #include <string> |
Johannes Kron | c3fcee7 | 2021-04-19 07:09:26 | [diff] [blame] | 16 | #include <utility> |
| 17 | |
| 18 | #include "api/video_codecs/h264_profile_level_id.h" |
Qiu Jianlin | b3488d0 | 2023-12-07 00:12:12 | [diff] [blame] | 19 | #ifdef RTC_ENABLE_H265 |
| 20 | #include "api/video_codecs/h265_profile_tier_level.h" |
| 21 | #endif |
Philipp Hancke | 7c5f9cf | 2024-02-20 14:28:14 | [diff] [blame] | 22 | #include "absl/algorithm/container.h" |
| 23 | #include "media/base/media_constants.h" |
Johannes Kron | c3fcee7 | 2021-04-19 07:09:26 | [diff] [blame] | 24 | #include "rtc_base/checks.h" |
| 25 | #include "rtc_base/string_to_number.h" |
| 26 | |
| 27 | namespace webrtc { |
| 28 | namespace { |
| 29 | const char kProfileLevelId[] = "profile-level-id"; |
| 30 | const char kH264LevelAsymmetryAllowed[] = "level-asymmetry-allowed"; |
| 31 | // Max frame rate for VP8 and VP9 video. |
| 32 | const char kVPxFmtpMaxFrameRate[] = "max-fr"; |
| 33 | // Max frame size for VP8 and VP9 video. |
| 34 | const char kVPxFmtpMaxFrameSize[] = "max-fs"; |
| 35 | const int kVPxFmtpFrameSizeSubBlockPixels = 256; |
Qiu Jianlin | b3488d0 | 2023-12-07 00:12:12 | [diff] [blame] | 36 | #ifdef RTC_ENABLE_H265 |
| 37 | constexpr char kH265ProfileId[] = "profile-id"; |
| 38 | constexpr char kH265TierFlag[] = "tier-flag"; |
| 39 | constexpr char kH265LevelId[] = "level-id"; |
| 40 | #endif |
Johannes Kron | c3fcee7 | 2021-04-19 07:09:26 | [diff] [blame] | 41 | |
Philipp Hancke | de17252 | 2023-12-14 08:45:39 | [diff] [blame] | 42 | bool IsH264LevelAsymmetryAllowed(const CodecParameterMap& params) { |
Johannes Kron | c3fcee7 | 2021-04-19 07:09:26 | [diff] [blame] | 43 | const auto it = params.find(kH264LevelAsymmetryAllowed); |
| 44 | return it != params.end() && strcmp(it->second.c_str(), "1") == 0; |
| 45 | } |
| 46 | |
| 47 | // Compare H264 levels and handle the level 1b case. |
| 48 | bool H264LevelIsLess(H264Level a, H264Level b) { |
| 49 | if (a == H264Level::kLevel1_b) |
| 50 | return b != H264Level::kLevel1 && b != H264Level::kLevel1_b; |
| 51 | if (b == H264Level::kLevel1_b) |
| 52 | return a == H264Level::kLevel1; |
| 53 | return a < b; |
| 54 | } |
| 55 | |
| 56 | H264Level H264LevelMin(H264Level a, H264Level b) { |
| 57 | return H264LevelIsLess(a, b) ? a : b; |
| 58 | } |
| 59 | |
Florent Castelli | 8037fc6 | 2024-08-29 13:00:40 | [diff] [blame] | 60 | std::optional<int> ParsePositiveNumberFromParams( |
Philipp Hancke | de17252 | 2023-12-14 08:45:39 | [diff] [blame] | 61 | const CodecParameterMap& params, |
Johannes Kron | c3fcee7 | 2021-04-19 07:09:26 | [diff] [blame] | 62 | const char* parameter_name) { |
| 63 | const auto max_frame_rate_it = params.find(parameter_name); |
| 64 | if (max_frame_rate_it == params.end()) |
Florent Castelli | 8037fc6 | 2024-08-29 13:00:40 | [diff] [blame] | 65 | return std::nullopt; |
Johannes Kron | c3fcee7 | 2021-04-19 07:09:26 | [diff] [blame] | 66 | |
Florent Castelli | 8037fc6 | 2024-08-29 13:00:40 | [diff] [blame] | 67 | const std::optional<int> i = |
Johannes Kron | c3fcee7 | 2021-04-19 07:09:26 | [diff] [blame] | 68 | rtc::StringToNumber<int>(max_frame_rate_it->second); |
| 69 | if (!i.has_value() || i.value() <= 0) |
Florent Castelli | 8037fc6 | 2024-08-29 13:00:40 | [diff] [blame] | 70 | return std::nullopt; |
Johannes Kron | c3fcee7 | 2021-04-19 07:09:26 | [diff] [blame] | 71 | return i; |
| 72 | } |
| 73 | |
Qiu Jianlin | b3488d0 | 2023-12-07 00:12:12 | [diff] [blame] | 74 | #ifdef RTC_ENABLE_H265 |
| 75 | // Compares two H265Level and return the smaller. |
| 76 | H265Level H265LevelMin(H265Level a, H265Level b) { |
| 77 | return a <= b ? a : b; |
| 78 | } |
| 79 | |
| 80 | // Returns true if none of profile-id/tier-flag/level-id is specified |
| 81 | // explicitly in the param. |
Philipp Hancke | de17252 | 2023-12-14 08:45:39 | [diff] [blame] | 82 | bool IsDefaultH265PTL(const CodecParameterMap& params) { |
Qiu Jianlin | b3488d0 | 2023-12-07 00:12:12 | [diff] [blame] | 83 | return !params.count(kH265ProfileId) && !params.count(kH265TierFlag) && |
| 84 | !params.count(kH265LevelId); |
| 85 | } |
| 86 | #endif |
| 87 | |
Johannes Kron | c3fcee7 | 2021-04-19 07:09:26 | [diff] [blame] | 88 | } // namespace |
| 89 | |
Qiu Jianlin | b3488d0 | 2023-12-07 00:12:12 | [diff] [blame] | 90 | #ifdef RTC_ENABLE_H265 |
| 91 | // Set level according to https://tools.ietf.org/html/rfc7798#section-7.1 |
| 92 | void H265GenerateProfileTierLevelForAnswer( |
Philipp Hancke | de17252 | 2023-12-14 08:45:39 | [diff] [blame] | 93 | const CodecParameterMap& local_supported_params, |
| 94 | const CodecParameterMap& remote_offered_params, |
| 95 | CodecParameterMap* answer_params) { |
Qiu Jianlin | b3488d0 | 2023-12-07 00:12:12 | [diff] [blame] | 96 | // If local and remote haven't set profile-id/tier-flag/level-id, they |
| 97 | // are both using the default PTL In this case, don't set PTL in answer |
| 98 | // either. |
| 99 | if (IsDefaultH265PTL(local_supported_params) && |
| 100 | IsDefaultH265PTL(remote_offered_params)) { |
| 101 | return; |
| 102 | } |
| 103 | |
| 104 | // Parse profile-tier-level. |
Florent Castelli | 8037fc6 | 2024-08-29 13:00:40 | [diff] [blame] | 105 | const std::optional<H265ProfileTierLevel> local_profile_tier_level = |
Qiu Jianlin | b3488d0 | 2023-12-07 00:12:12 | [diff] [blame] | 106 | ParseSdpForH265ProfileTierLevel(local_supported_params); |
Florent Castelli | 8037fc6 | 2024-08-29 13:00:40 | [diff] [blame] | 107 | const std::optional<H265ProfileTierLevel> remote_profile_tier_level = |
Qiu Jianlin | b3488d0 | 2023-12-07 00:12:12 | [diff] [blame] | 108 | ParseSdpForH265ProfileTierLevel(remote_offered_params); |
| 109 | // Profile and tier for local and remote codec must be valid and equal. |
| 110 | RTC_DCHECK(local_profile_tier_level); |
| 111 | RTC_DCHECK(remote_profile_tier_level); |
| 112 | RTC_DCHECK_EQ(local_profile_tier_level->profile, |
| 113 | remote_profile_tier_level->profile); |
| 114 | RTC_DCHECK_EQ(local_profile_tier_level->tier, |
| 115 | remote_profile_tier_level->tier); |
| 116 | |
| 117 | const H265Level answer_level = H265LevelMin(local_profile_tier_level->level, |
| 118 | remote_profile_tier_level->level); |
| 119 | |
| 120 | // Level-id in answer is changable as long as the highest level indicated by |
| 121 | // the answer is not higher than that indicated by the offer. See |
| 122 | // https://tools.ietf.org/html/rfc7798#section-7.2.2, sub-clause 2. |
| 123 | (*answer_params)[kH265LevelId] = H265LevelToString(answer_level); |
| 124 | } |
| 125 | #endif |
| 126 | |
Johannes Kron | c3fcee7 | 2021-04-19 07:09:26 | [diff] [blame] | 127 | // Set level according to https://tools.ietf.org/html/rfc6184#section-8.2.2. |
| 128 | void H264GenerateProfileLevelIdForAnswer( |
Philipp Hancke | de17252 | 2023-12-14 08:45:39 | [diff] [blame] | 129 | const CodecParameterMap& local_supported_params, |
| 130 | const CodecParameterMap& remote_offered_params, |
| 131 | CodecParameterMap* answer_params) { |
Johannes Kron | c3fcee7 | 2021-04-19 07:09:26 | [diff] [blame] | 132 | // If both local and remote haven't set profile-level-id, they are both using |
| 133 | // the default profile. In this case, don't set profile-level-id in answer |
| 134 | // either. |
| 135 | if (!local_supported_params.count(kProfileLevelId) && |
| 136 | !remote_offered_params.count(kProfileLevelId)) { |
| 137 | return; |
| 138 | } |
| 139 | |
| 140 | // Parse profile-level-ids. |
Florent Castelli | 8037fc6 | 2024-08-29 13:00:40 | [diff] [blame] | 141 | const std::optional<H264ProfileLevelId> local_profile_level_id = |
Johannes Kron | c3fcee7 | 2021-04-19 07:09:26 | [diff] [blame] | 142 | ParseSdpForH264ProfileLevelId(local_supported_params); |
Florent Castelli | 8037fc6 | 2024-08-29 13:00:40 | [diff] [blame] | 143 | const std::optional<H264ProfileLevelId> remote_profile_level_id = |
Johannes Kron | c3fcee7 | 2021-04-19 07:09:26 | [diff] [blame] | 144 | ParseSdpForH264ProfileLevelId(remote_offered_params); |
| 145 | // The local and remote codec must have valid and equal H264 Profiles. |
| 146 | RTC_DCHECK(local_profile_level_id); |
| 147 | RTC_DCHECK(remote_profile_level_id); |
| 148 | RTC_DCHECK_EQ(local_profile_level_id->profile, |
| 149 | remote_profile_level_id->profile); |
| 150 | |
| 151 | // Parse level information. |
| 152 | const bool level_asymmetry_allowed = |
| 153 | IsH264LevelAsymmetryAllowed(local_supported_params) && |
| 154 | IsH264LevelAsymmetryAllowed(remote_offered_params); |
| 155 | const H264Level local_level = local_profile_level_id->level; |
| 156 | const H264Level remote_level = remote_profile_level_id->level; |
| 157 | const H264Level min_level = H264LevelMin(local_level, remote_level); |
| 158 | |
| 159 | // Determine answer level. When level asymmetry is not allowed, level upgrade |
| 160 | // is not allowed, i.e., the level in the answer must be equal to or lower |
| 161 | // than the level in the offer. |
| 162 | const H264Level answer_level = |
| 163 | level_asymmetry_allowed ? local_level : min_level; |
| 164 | |
| 165 | // Set the resulting profile-level-id in the answer parameters. |
| 166 | (*answer_params)[kProfileLevelId] = *H264ProfileLevelIdToString( |
| 167 | H264ProfileLevelId(local_profile_level_id->profile, answer_level)); |
| 168 | } |
| 169 | |
Florent Castelli | 8037fc6 | 2024-08-29 13:00:40 | [diff] [blame] | 170 | std::optional<int> ParseSdpForVPxMaxFrameRate(const CodecParameterMap& params) { |
Johannes Kron | c3fcee7 | 2021-04-19 07:09:26 | [diff] [blame] | 171 | return ParsePositiveNumberFromParams(params, kVPxFmtpMaxFrameRate); |
| 172 | } |
| 173 | |
Florent Castelli | 8037fc6 | 2024-08-29 13:00:40 | [diff] [blame] | 174 | std::optional<int> ParseSdpForVPxMaxFrameSize(const CodecParameterMap& params) { |
| 175 | const std::optional<int> i = |
Johannes Kron | c3fcee7 | 2021-04-19 07:09:26 | [diff] [blame] | 176 | ParsePositiveNumberFromParams(params, kVPxFmtpMaxFrameSize); |
Florent Castelli | 8037fc6 | 2024-08-29 13:00:40 | [diff] [blame] | 177 | return i ? std::make_optional(i.value() * kVPxFmtpFrameSizeSubBlockPixels) |
| 178 | : std::nullopt; |
Johannes Kron | c3fcee7 | 2021-04-19 07:09:26 | [diff] [blame] | 179 | } |
| 180 | |
Philipp Hancke | 7c5f9cf | 2024-02-20 14:28:14 | [diff] [blame] | 181 | bool SupportsPerLayerPictureLossIndication(const CodecParameterMap& params) { |
| 182 | return absl::c_find_if( |
| 183 | params, [](const std::pair<std::string, std::string>& kv) { |
| 184 | return kv.first == |
| 185 | cricket::kCodecParamPerLayerPictureLossIndication && |
| 186 | kv.second == "1"; |
| 187 | }) != params.end(); |
| 188 | } |
| 189 | |
Johannes Kron | c3fcee7 | 2021-04-19 07:09:26 | [diff] [blame] | 190 | } // namespace webrtc |