Helper API for codec factories to calculate supported H.265 levels.
This expose a new GetSupportedH265Level API for WebRTC external
factories to calculate H.265 levels to be use for SDP negotation.
Bug: webrtc:13485
Change-Id: Ib420da2b9b1b7af00129294be5b3efec172e8faf
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/345544
Commit-Queue: Sergey Silkin <ssilkin@webrtc.org>
Reviewed-by: Philip Eliasson <philipel@webrtc.org>
Reviewed-by: Sergey Silkin <ssilkin@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#42079}
diff --git a/api/video/BUILD.gn b/api/video/BUILD.gn
index 5ec689c..72befb5 100644
--- a/api/video/BUILD.gn
+++ b/api/video/BUILD.gn
@@ -139,6 +139,7 @@
rtc_source_set("resolution") {
visibility = [ "*" ]
public = [ "resolution.h" ]
+ deps = [ "../../rtc_base/system:rtc_export" ]
}
rtc_library("encoded_image") {
diff --git a/api/video/resolution.h b/api/video/resolution.h
index 11ffef0..1a3c97c 100644
--- a/api/video/resolution.h
+++ b/api/video/resolution.h
@@ -13,10 +13,12 @@
#include <utility>
+#include "rtc_base/system/rtc_export.h"
+
namespace webrtc {
// A struct representing a video resolution in pixels.
-struct Resolution {
+struct RTC_EXPORT Resolution {
int width = 0;
int height = 0;
diff --git a/api/video_codecs/h265_profile_tier_level.cc b/api/video_codecs/h265_profile_tier_level.cc
index f4dcebb..9705f46 100644
--- a/api/video_codecs/h265_profile_tier_level.cc
+++ b/api/video_codecs/h265_profile_tier_level.cc
@@ -12,6 +12,7 @@
#include <string>
+#include "rtc_base/arraysize.h"
#include "rtc_base/string_to_number.h"
namespace webrtc {
@@ -22,6 +23,42 @@
const char kH265FmtpTier[] = "tier-flag";
const char kH265FmtpLevel[] = "level-id";
+// Used to align frame width and height for luma picture size calculation.
+// Use the maximum value allowed by spec to get upper bound of luma picture
+// size for given resolution.
+static constexpr int kMinCbSizeYMax = 64;
+
+struct LevelConstraint {
+ const int max_luma_picture_size;
+ const double max_luma_sample_rate;
+ const int max_pic_width_or_height_in_pixels;
+ const H265Level level;
+};
+
+// This is from ITU-T H.265 (09/2023) Table A.8, A.9 & A.11 – Level limits.
+// The max_pic_width_or_height_in_luma_samples is pre-calculated following
+// ITU-T H.265 section A.4.1, that is, find the largest integer value that
+// is multiple of minimal MinCbSizeY(8 according to equation 7-10 and 7-12), is
+// less than sqrt(max_luma_picture_size * 8). For example, at level 1,
+// max_luma_picture_size is 36864, so pic_width_in_luma_samples <= sqrt(36864 *
+// 8) = 543.06. The largest integer that is multiple of 8 and less than 543.06
+// is 536.
+static constexpr LevelConstraint kLevelConstraints[] = {
+ {36864, 552960, 536, H265Level::kLevel1},
+ {122880, 3686400, 984, H265Level::kLevel2},
+ {245760, 7372800, 1400, H265Level::kLevel2_1},
+ {552960, 16588800, 2096, H265Level::kLevel3},
+ {983040, 33177600, 2800, H265Level::kLevel3_1},
+ {2228224, 66846720, 4216, H265Level::kLevel4},
+ {2228224, 133693400, 4216, H265Level::kLevel4_1},
+ {8912896, 267386880, 8440, H265Level::kLevel5},
+ {8912896, 534773760, 8440, H265Level::kLevel5_1},
+ {8912896, 1069547520, 8440, H265Level::kLevel5_2},
+ {35651584, 1069547520, 16888, H265Level::kLevel6},
+ {35651584, 2139095040, 16888, H265Level::kLevel6_1},
+ {35651584, 4278190080, 16888, H265Level::kLevel6_2},
+};
+
} // anonymous namespace
// Annex A of https://www.itu.int/rec/T-REC-H.265 (08/21), section A.3.
@@ -245,4 +282,25 @@
ptl1->tier == ptl2->tier && ptl1->level == ptl2->level;
}
+absl::optional<H265Level> GetSupportedH265Level(const Resolution& resolution,
+ float max_fps) {
+ int aligned_width =
+ (resolution.width + kMinCbSizeYMax - 1) & ~(kMinCbSizeYMax - 1);
+ int aligned_height =
+ (resolution.height + kMinCbSizeYMax - 1) & ~(kMinCbSizeYMax - 1);
+
+ for (int i = arraysize(kLevelConstraints) - 1; i >= 0; --i) {
+ const LevelConstraint& level_constraint = kLevelConstraints[i];
+ if (level_constraint.max_luma_picture_size <=
+ aligned_width * aligned_height &&
+ level_constraint.max_luma_sample_rate <=
+ aligned_width * aligned_height * max_fps &&
+ level_constraint.max_pic_width_or_height_in_pixels >= aligned_width &&
+ level_constraint.max_pic_width_or_height_in_pixels >= aligned_height) {
+ return level_constraint.level;
+ }
+ }
+ return absl::nullopt;
+}
+
} // namespace webrtc
diff --git a/api/video_codecs/h265_profile_tier_level.h b/api/video_codecs/h265_profile_tier_level.h
index efbdf28..28a35ae 100644
--- a/api/video_codecs/h265_profile_tier_level.h
+++ b/api/video_codecs/h265_profile_tier_level.h
@@ -14,6 +14,7 @@
#include <string>
#include "absl/types/optional.h"
+#include "api/video/resolution.h"
#include "api/video_codecs/sdp_video_format.h"
#include "rtc_base/system/rtc_export.h"
@@ -89,6 +90,14 @@
RTC_EXPORT absl::optional<H265Level> StringToH265Level(
const std::string& level);
+// Given that a decoder supports up to a give frame size(in pixels) at up to a
+// given number of frames per second, return the highest H.265 level where it
+// can guranatee that it will be able to support all valid encoded streams that
+// are within that level.
+RTC_EXPORT absl::optional<H265Level> GetSupportedH265Level(
+ const Resolution& resolution,
+ float max_fps);
+
// Parses an SDP key-value map of format parameters to retrive an H265
// profile/tier/level. Returns an H265ProfileTierlevel by setting its
// members. profile defaults to `kProfileMain` if no profile-id is specified.
diff --git a/api/video_codecs/test/h265_profile_tier_level_unittest.cc b/api/video_codecs/test/h265_profile_tier_level_unittest.cc
index 85c0f09..aacfee9 100644
--- a/api/video_codecs/test/h265_profile_tier_level_unittest.cc
+++ b/api/video_codecs/test/h265_profile_tier_level_unittest.cc
@@ -245,4 +245,53 @@
EXPECT_FALSE(H265IsSameProfileTierLevel(params1, params2));
}
+TEST(H265ProfileTierLevel, TestGetSupportedH265Level) {
+ // Test with 720p at 30fps
+ Resolution r{.width = 1280, .height = 720};
+ EXPECT_EQ(GetSupportedH265Level(r, 30).value_or(H265Level::kLevel1),
+ H265Level::kLevel3);
+
+ // Test with QCIF at 15fps
+ r.width = 176;
+ r.height = 144;
+ EXPECT_EQ(GetSupportedH265Level(r, 15).value_or(H265Level::kLevel2),
+ H265Level::kLevel1);
+
+ // Test with 1080p at 30fps
+ r.width = 1920;
+ r.height = 1080;
+ EXPECT_EQ(GetSupportedH265Level(r, 30).value_or(H265Level::kLevel1),
+ H265Level::kLevel3_1);
+
+ // Test with 1080p at 60fps
+ EXPECT_EQ(GetSupportedH265Level(r, 60).value_or(H265Level::kLevel1),
+ H265Level::kLevel3_1);
+
+ // Test with 4K at 30fps
+ r.width = 3840;
+ r.height = 2160;
+ EXPECT_EQ(GetSupportedH265Level(r, 30).value_or(H265Level::kLevel1),
+ H265Level::kLevel4_1);
+
+ // Test with 4K at 60fps
+ EXPECT_EQ(GetSupportedH265Level(r, 60).value_or(H265Level::kLevel1),
+ H265Level::kLevel4_1);
+
+ // Test with 8K at 30fps
+ r.width = 8192;
+ r.height = 4320;
+ EXPECT_EQ(GetSupportedH265Level(r, 30).value_or(H265Level::kLevel1),
+ H265Level::kLevel6);
+
+ // Test with 64x64 at 30fps
+ r.width = 64;
+ r.height = 64;
+ EXPECT_EQ(GetSupportedH265Level(r, 30), absl::nullopt);
+
+ // Test with extremly large width or height at 15fps
+ r.width = 16928;
+ r.height = 64;
+ EXPECT_EQ(GetSupportedH265Level(r, 15), absl::nullopt);
+}
+
} // namespace webrtc