Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: h264 level selection algorithm #1049

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 15 additions & 8 deletions Plugin~/WebRTCPlugin/Codec/H264ProfileLevelId.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,20 +38,23 @@ namespace webrtc
{ 2073600, 36864, 240000, H264Level::kLevel5_2 },
};

static const int kPixelsPerMacroblock = 16 * 16;
static const int kPixelsPerMacroblockSide = 16;
static const int kUnitMaxBRWithNAL = 1200;

absl::optional<webrtc::H264Level> H264SupportedLevel(int maxFramePixelCount, int maxFramerate, int maxBitrate)
absl::optional<webrtc::H264Level> H264SupportedLevel(int maxFrameWidthPixelCount, int maxFrameHeightPixelCount, int maxFramerate, int maxBitrate)
{
if (maxFramePixelCount <= 0 || maxFramerate <= 0 || maxBitrate <= 0)
if (maxFrameWidthPixelCount <= 0 || maxFrameHeightPixelCount <= 0 || maxFramerate <= 0 || maxBitrate <= 0)
return absl::nullopt;

int maxFrameMacroblockCount =
((maxFrameWidthPixelCount + kPixelsPerMacroblockSide - 1) / kPixelsPerMacroblockSide) *
((maxFrameHeightPixelCount + kPixelsPerMacroblockSide - 1) / kPixelsPerMacroblockSide);

for (size_t i = 0; i < arraysize(kLevelConstraints); i++)
{
const LevelConstraint& level_constraint = kLevelConstraints[i];
if (level_constraint.max_macroblock_frame_size * kPixelsPerMacroblock >= maxFramePixelCount &&
level_constraint.max_macroblocks_per_second >=
maxFramerate * maxFramePixelCount / kPixelsPerMacroblock &&
if (level_constraint.max_macroblock_frame_size >= maxFrameMacroblockCount &&
level_constraint.max_macroblocks_per_second >= maxFramerate * maxFrameMacroblockCount &&
level_constraint.max_video_bitrate * kUnitMaxBRWithNAL >= maxBitrate)
{
return level_constraint.level;
Expand All @@ -62,14 +65,18 @@ namespace webrtc
return absl::nullopt;
}

int SupportedMaxFramerate(H264Level level, int maxFramePixelCount)
int SupportedMaxFramerate(H264Level level, int maxFrameWidthPixelCount, int maxFrameHeightPixelCount)
{
int maxFrameMacroblockCount =
((maxFrameWidthPixelCount + kPixelsPerMacroblockSide - 1) / kPixelsPerMacroblockSide) *
((maxFrameHeightPixelCount + kPixelsPerMacroblockSide - 1) / kPixelsPerMacroblockSide);

for (size_t i = 0; i < arraysize(kLevelConstraints); i++)
{
const LevelConstraint& level_constraint = kLevelConstraints[i];
if (level_constraint.level == level)
{
return level_constraint.max_macroblocks_per_second * kPixelsPerMacroblock / maxFramePixelCount;
return level_constraint.max_macroblocks_per_second / maxFrameMacroblockCount;
}
}

Expand Down
6 changes: 3 additions & 3 deletions Plugin~/WebRTCPlugin/Codec/H264ProfileLevelId.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ namespace webrtc

// Returns the minumum level which can supports given parameters.
// webrtc::H264SupportedLevel function is defined in libwebrtc, but that is for decoder.
absl::optional<H264Level> H264SupportedLevel(int maxFramePixelCount, int maxFramerate, int maxBitrate);
absl::optional<H264Level> H264SupportedLevel(int maxFrameWidthPixelCount, int maxFrameHeightPixelCount, int maxFramerate, int maxBitrate);

// Returns the max framerate that calclated by maxFramePixelCount.
int SupportedMaxFramerate(H264Level level, int maxFramePixelCount);
// Returns the max framerate that calclated by maxFrameWidthPixelCount and maxFrameHeightPixelCount.
int SupportedMaxFramerate(H264Level level, int maxFrameWidthPixelCount, int maxFrameHeightPixelCount);

} // end namespace webrtc
} // end namespace unity
7 changes: 3 additions & 4 deletions Plugin~/WebRTCPlugin/Codec/NvCodec/NvEncoderImpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,8 @@ namespace webrtc
inline absl::optional<NV_ENC_LEVEL>
NvEncRequiredLevel(const VideoCodec& codec, std::vector<SdpVideoFormat>& formats, const GUID& guid)
{
int pixelCount = codec.width * codec.height;
auto requiredLevel = unity::webrtc::H264SupportedLevel(
pixelCount, static_cast<int>(codec.maxFramerate), static_cast<int>(codec.maxBitrate));
codec.width, codec.height, static_cast<int>(codec.maxFramerate), static_cast<int>(codec.maxBitrate));

if (!requiredLevel)
{
Expand Down Expand Up @@ -223,7 +222,7 @@ namespace webrtc
// workaround
// Use supported max framerate that calculated by h264 level define.
m_codec.maxFramerate = static_cast<uint32_t>(
SupportedMaxFramerate(s_maxSupportedH264Level.value(), m_codec.width * m_codec.height));
SupportedMaxFramerate(s_maxSupportedH264Level.value(), m_codec.width, m_codec.height));
requiredLevel = NvEncRequiredLevel(m_codec, s_formats, m_profileGuid);
if (!requiredLevel)
{
Expand Down Expand Up @@ -567,7 +566,7 @@ namespace webrtc
// workaround
// Use supported max framerate that calculated by h264 level define.
m_codec.maxFramerate = static_cast<uint32_t>(
SupportedMaxFramerate(s_maxSupportedH264Level.value(), m_codec.width * m_codec.height));
SupportedMaxFramerate(s_maxSupportedH264Level.value(), m_codec.width, m_codec.height));
requiredLevel = NvEncRequiredLevel(m_codec, s_formats, m_profileGuid);
if (!requiredLevel)
{
Expand Down
22 changes: 11 additions & 11 deletions Plugin~/WebRTCPluginTest/H264ProfileLevelIdTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,25 @@ namespace webrtc

TEST(H264ProfileLevelId, TestSupportedLevel)
{
EXPECT_EQ(H264Level::kLevel2_1, *H264SupportedLevel(320 * 240, 25, 4000 * 1200));
EXPECT_EQ(H264Level::kLevel3_1, *H264SupportedLevel(1280 * 720, 30, 14000 * 1200));
EXPECT_EQ(H264Level::kLevel4_2, *H264SupportedLevel(1920 * 1080, 60, 50000 * 1200));
EXPECT_EQ(H264Level::kLevel5_2, *H264SupportedLevel(3840 * 2160, 60, 50000 * 1200));
EXPECT_EQ(H264Level::kLevel2_1, *H264SupportedLevel(320, 240, 25, 4000 * 1200));
EXPECT_EQ(H264Level::kLevel3_1, *H264SupportedLevel(1280, 720, 30, 14000 * 1200));
EXPECT_EQ(H264Level::kLevel4_2, *H264SupportedLevel(1920, 1080, 60, 50000 * 1200));
EXPECT_EQ(H264Level::kLevel5_2, *H264SupportedLevel(3840, 2160, 60, 50000 * 1200));
}

TEST(H264ProfileLevelId, TestSupportedLevelInvalid)
{
EXPECT_FALSE(H264SupportedLevel(0, 0, 0));
EXPECT_FALSE(H264SupportedLevel(3840 * 2160, 90, 50000 * 1200));
EXPECT_FALSE(H264SupportedLevel(0, 0, 0, 0));
EXPECT_FALSE(H264SupportedLevel(3840, 2160, 90, 50000 * 1200));
}

TEST(H264ProfileLevelId, TestSupportedFramerate)
{
EXPECT_GE(SupportedMaxFramerate(H264Level::kLevel2_1, 320 * 240), 25);
EXPECT_GE(SupportedMaxFramerate(H264Level::kLevel3_1, 1280 * 720), 30);
EXPECT_GE(SupportedMaxFramerate(H264Level::kLevel4_2, 1920 * 1080), 60);
EXPECT_GE(SupportedMaxFramerate(H264Level::kLevel5_2, 2560 * 1440), 90);
EXPECT_GE(SupportedMaxFramerate(H264Level::kLevel5_2, 3840 * 2160), 60);
EXPECT_GE(SupportedMaxFramerate(H264Level::kLevel2_1, 320, 240), 25);
EXPECT_GE(SupportedMaxFramerate(H264Level::kLevel3_1, 1280, 720), 30);
EXPECT_GE(SupportedMaxFramerate(H264Level::kLevel4_2, 1920, 1080), 60);
EXPECT_GE(SupportedMaxFramerate(H264Level::kLevel5_2, 2560, 1440), 90);
EXPECT_GE(SupportedMaxFramerate(H264Level::kLevel5_2, 3840, 2160), 60);
}

const char kProfileLevelId[] = "profile-level-id";
Expand Down