From 9c95a4f704e999785bc5de756de97fb0bec374b7 Mon Sep 17 00:00:00 2001 From: Qiu Jianlin Date: Tue, 9 Apr 2024 10:26:05 +0800 Subject: [PATCH] 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 Reviewed-by: Philip Eliasson Reviewed-by: Sergey Silkin Cr-Commit-Position: refs/heads/main@{#42079} --- api/video/BUILD.gn | 1 + api/video/resolution.h | 4 +- api/video_codecs/h265_profile_tier_level.cc | 58 +++++++++++++++++++ api/video_codecs/h265_profile_tier_level.h | 9 +++ .../test/h265_profile_tier_level_unittest.cc | 49 ++++++++++++++++ 5 files changed, 120 insertions(+), 1 deletion(-) diff --git a/api/video/BUILD.gn b/api/video/BUILD.gn index 5ec689c096..72befb522c 100644 --- a/api/video/BUILD.gn +++ b/api/video/BUILD.gn @@ -139,6 +139,7 @@ rtc_source_set("render_resolution") { 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 11ffef0b03..1a3c97c458 100644 --- a/api/video/resolution.h +++ b/api/video/resolution.h @@ -13,10 +13,12 @@ #include +#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 f4dcebb25a..9705f46431 100644 --- a/api/video_codecs/h265_profile_tier_level.cc +++ b/api/video_codecs/h265_profile_tier_level.cc @@ -12,6 +12,7 @@ #include +#include "rtc_base/arraysize.h" #include "rtc_base/string_to_number.h" namespace webrtc { @@ -22,6 +23,42 @@ const char kH265FmtpProfile[] = "profile-id"; 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 @@ bool H265IsSameProfileTierLevel(const CodecParameterMap& params1, ptl1->tier == ptl2->tier && ptl1->level == ptl2->level; } +absl::optional 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 efbdf287ed..28a35ae093 100644 --- a/api/video_codecs/h265_profile_tier_level.h +++ b/api/video_codecs/h265_profile_tier_level.h @@ -14,6 +14,7 @@ #include #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 StringToH265Tier(const std::string& tier); RTC_EXPORT absl::optional 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 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 85c0f09cd0..aacfee9d55 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 @@ TEST(H265ProfileTierLevel, TestProfileTierLevelCompare) { 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