From fffc1e5578b051ca8d4e210fc59435d2b4b2b42a Mon Sep 17 00:00:00 2001 From: magjed Date: Mon, 31 Oct 2016 05:55:57 -0700 Subject: [PATCH] Add functionality for parsing H264 profile-level-id The new code is only exercised in tests so far. The H264 profile-level-id parsing is not complete, but it should be enough for our purposes for now. BUG=webrtc:6400,webrtc:6337 Review-Url: https://codereview.webrtc.org/2459633002 Cr-Commit-Position: refs/heads/master@{#14850} --- webrtc/common_types.h | 3 - webrtc/common_video/BUILD.gn | 3 + webrtc/common_video/common_video.gyp | 2 + webrtc/common_video/h264/profile_level_id.cc | 132 ++++++++++++++++++ webrtc/common_video/h264/profile_level_id.h | 62 ++++++++ .../h264/profile_level_id_unittest.cc | 74 ++++++++++ webrtc/modules/video_coding/codec_database.cc | 1 - 7 files changed, 273 insertions(+), 4 deletions(-) create mode 100644 webrtc/common_video/h264/profile_level_id.cc create mode 100644 webrtc/common_video/h264/profile_level_id.h create mode 100644 webrtc/common_video/h264/profile_level_id_unittest.cc diff --git a/webrtc/common_types.h b/webrtc/common_types.h index c3e9a84c3c..678b856628 100644 --- a/webrtc/common_types.h +++ b/webrtc/common_types.h @@ -498,8 +498,6 @@ enum VideoCodecComplexity { kComplexityMax = 3 }; -enum VideoCodecProfile { kProfileBase = 0x00, kProfileMain = 0x01 }; - enum VP8ResilienceMode { kResilienceOff, // The stream produced by the encoder requires a // recovery frame (typically a key frame) to be @@ -543,7 +541,6 @@ struct VideoCodecVP9 { // H264 specific. struct VideoCodecH264 { - VideoCodecProfile profile; bool frameDroppingOn; int keyFrameInterval; // These are NULL/0 if not externally negotiated. diff --git a/webrtc/common_video/BUILD.gn b/webrtc/common_video/BUILD.gn index 9830c8b942..ed98e02611 100644 --- a/webrtc/common_video/BUILD.gn +++ b/webrtc/common_video/BUILD.gn @@ -24,6 +24,8 @@ rtc_static_library("common_video") { "h264/h264_common.h", "h264/pps_parser.cc", "h264/pps_parser.h", + "h264/profile_level_id.cc", + "h264/profile_level_id.h", "h264/sps_parser.cc", "h264/sps_parser.h", "h264/sps_vui_rewriter.cc", @@ -97,6 +99,7 @@ if (rtc_include_tests) { "bitrate_adjuster_unittest.cc", "h264/h264_bitstream_parser_unittest.cc", "h264/pps_parser_unittest.cc", + "h264/profile_level_id_unittest.cc", "h264/sps_parser_unittest.cc", "h264/sps_vui_rewriter_unittest.cc", "i420_buffer_pool_unittest.cc", diff --git a/webrtc/common_video/common_video.gyp b/webrtc/common_video/common_video.gyp index cb9b7037cb..58b2462fa8 100644 --- a/webrtc/common_video/common_video.gyp +++ b/webrtc/common_video/common_video.gyp @@ -60,6 +60,8 @@ 'h264/h264_common.h', 'h264/pps_parser.cc', 'h264/pps_parser.h', + 'h264/profile_level_id.cc', + 'h264/profile_level_id.h', 'h264/sps_parser.cc', 'h264/sps_parser.h', 'h264/h264_bitstream_parser.cc', diff --git a/webrtc/common_video/h264/profile_level_id.cc b/webrtc/common_video/h264/profile_level_id.cc new file mode 100644 index 0000000000..9f9aef9f61 --- /dev/null +++ b/webrtc/common_video/h264/profile_level_id.cc @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/common_video/h264/profile_level_id.h" + +#include +#include + +namespace { + +// For level_idc=11 and profile_idc=0x42, 0x4D, or 0x58, the constraint set3 +// flag specifies if level 1b or level 1.1 is used. +const uint8_t kConstraintSet3Flag = 0x10; + +// Convert a string of 8 characters into a byte where the positions containing +// character c will have their bit set. For example, c = 'x', str = "x1xx0000" +// will return 0b10110000. constexpr is used so that the pattern table in +// kProfilePatterns is statically initialized. +constexpr uint8_t ByteMaskString(char c, const char (&str)[9]) { + return (str[0] == c) << 7 + | (str[1] == c) << 6 + | (str[2] == c) << 5 + | (str[3] == c) << 4 + | (str[4] == c) << 3 + | (str[5] == c) << 2 + | (str[6] == c) << 1 + | (str[7] == c) << 0; +} + +// Class for matching bit patterns such as "x1xx0000" where 'x' is allowed to be +// either 0 or 1. +class BitPattern { + public: + constexpr BitPattern(const char (&str)[9]) + : mask_(~ByteMaskString('x', str)), + masked_value_(ByteMaskString('1', str)) {} + + bool IsMatch(uint8_t value) const { return masked_value_ == (value & mask_); } + + private: + const uint8_t mask_; + const uint8_t masked_value_; +}; + +// Table for converting between profile_idc/profile_iop to H264::Profile. +struct ProfilePattern { + const uint8_t profile_idc; + const BitPattern profile_iop; + const webrtc::H264::Profile profile; +}; + +// This is from https://tools.ietf.org/html/rfc6184#section-8.1. +constexpr ProfilePattern kProfilePatterns[] = { + {0x42, BitPattern("x1xx0000"), webrtc::H264::kProfileConstrainedBaseline}, + {0x4D, BitPattern("1xxx0000"), webrtc::H264::kProfileConstrainedBaseline}, + {0x58, BitPattern("11xx0000"), webrtc::H264::kProfileConstrainedBaseline}, + {0x42, BitPattern("x0xx0000"), webrtc::H264::kProfileBaseline}, + {0x58, BitPattern("10xx0000"), webrtc::H264::kProfileBaseline}, + {0x4D, BitPattern("0x0x0000"), webrtc::H264::kProfileMain}, + {0x64, BitPattern("00000000"), webrtc::H264::kProfileHigh}, + {0x64, BitPattern("00001100"), webrtc::H264::kProfileConstrainedHigh}}; + +} // anonymous namespace + +namespace webrtc { +namespace H264 { + +rtc::Optional ParseProfileLevelId(const char* str) { + // The string should consist of 3 bytes in hexadecimal format. + if (strlen(str) != 6u) + return rtc::Optional(); + const uint32_t profile_level_id_numeric = strtol(str, nullptr, 16); + if (profile_level_id_numeric == 0) + return rtc::Optional(); + + // Separate into three bytes. + const uint8_t level_idc = + static_cast(profile_level_id_numeric & 0xFF); + const uint8_t profile_iop = + static_cast((profile_level_id_numeric >> 8) & 0xFF); + const uint8_t profile_idc = + static_cast((profile_level_id_numeric >> 16) & 0xFF); + + // Parse level based on level_idc and constraint set 3 flag. + Level level; + switch (level_idc) { + case kLevel1_1: + level = (profile_iop & kConstraintSet3Flag) != 0 ? kLevel1_b : kLevel1_1; + break; + case kLevel1: + case kLevel1_2: + case kLevel1_3: + case kLevel2: + case kLevel2_1: + case kLevel2_2: + case kLevel3: + case kLevel3_1: + case kLevel3_2: + case kLevel4: + case kLevel4_1: + case kLevel4_2: + case kLevel5: + case kLevel5_1: + case kLevel5_2: + level = static_cast(level_idc); + break; + default: + // Unrecognized level_idc. + return rtc::Optional(); + } + + // Parse profile_idc/profile_iop into a Profile enum. + for (const ProfilePattern& pattern : kProfilePatterns) { + if (profile_idc == pattern.profile_idc && + pattern.profile_iop.IsMatch(profile_iop)) { + return rtc::Optional({pattern.profile, level}); + } + } + + // Unrecognized profile_idc/profile_iop combination. + return rtc::Optional(); +} + +} // namespace H264 +} // namespace webrtc diff --git a/webrtc/common_video/h264/profile_level_id.h b/webrtc/common_video/h264/profile_level_id.h new file mode 100644 index 0000000000..84f0005785 --- /dev/null +++ b/webrtc/common_video/h264/profile_level_id.h @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_COMMON_VIDEO_H264_PROFILE_LEVEL_ID_H_ +#define WEBRTC_COMMON_VIDEO_H264_PROFILE_LEVEL_ID_H_ + +#include "webrtc/base/optional.h" + +namespace webrtc { +namespace H264 { + +enum Profile { + kProfileConstrainedBaseline, + kProfileBaseline, + kProfileMain, + kProfileConstrainedHigh, + kProfileHigh, +}; + +// All values are equal to ten times the level number, except level 1b which is +// special. +enum Level { + kLevel1_b = 0, + kLevel1 = 10, + kLevel1_1 = 11, + kLevel1_2 = 12, + kLevel1_3 = 13, + kLevel2 = 20, + kLevel2_1 = 21, + kLevel2_2 = 22, + kLevel3 = 30, + kLevel3_1 = 31, + kLevel3_2 = 32, + kLevel4 = 40, + kLevel4_1 = 41, + kLevel4_2 = 42, + kLevel5 = 50, + kLevel5_1 = 51, + kLevel5_2 = 52 +}; + +struct ProfileLevelId { + Profile profile; + Level level; +}; + +// Parse profile level id that is represented as a string of 3 hex bytes. +// Nothing will be returned if the string is not a recognized H264 +// profile level id. +rtc::Optional ParseProfileLevelId(const char* str); + +} // namespace H264 +} // namespace webrtc + +#endif // WEBRTC_COMMON_VIDEO_H264_PROFILE_LEVEL_ID_H_ diff --git a/webrtc/common_video/h264/profile_level_id_unittest.cc b/webrtc/common_video/h264/profile_level_id_unittest.cc new file mode 100644 index 0000000000..772f5b038e --- /dev/null +++ b/webrtc/common_video/h264/profile_level_id_unittest.cc @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/common_video/h264/profile_level_id.h" + +#include "webrtc/test/gtest.h" + +namespace webrtc { +namespace H264 { + +TEST(H264ProfileLevelIdParsing, TestInvalid) { + // Malformed strings. + EXPECT_FALSE(ParseProfileLevelId("")); + EXPECT_FALSE(ParseProfileLevelId(" 42e01f")); + EXPECT_FALSE(ParseProfileLevelId("4242e01f")); + EXPECT_FALSE(ParseProfileLevelId("e01f")); + EXPECT_FALSE(ParseProfileLevelId("gggggg")); + + // Invalid level. + EXPECT_FALSE(ParseProfileLevelId("42e000")); + EXPECT_FALSE(ParseProfileLevelId("42e00f")); + EXPECT_FALSE(ParseProfileLevelId("42e0ff")); + + // Invalid profile. + EXPECT_FALSE(ParseProfileLevelId("42e11f")); + EXPECT_FALSE(ParseProfileLevelId("58601f")); + EXPECT_FALSE(ParseProfileLevelId("64e01f")); +} + +TEST(H264ProfileLevelIdParsing, TestLevel) { + EXPECT_EQ(kLevel3_1, ParseProfileLevelId("42e01f")->level); + EXPECT_EQ(kLevel1_1, ParseProfileLevelId("42e00b")->level); + EXPECT_EQ(kLevel1_b, ParseProfileLevelId("42f00b")->level); + EXPECT_EQ(kLevel4_2, ParseProfileLevelId("42C02A")->level); + EXPECT_EQ(kLevel5_2, ParseProfileLevelId("640c34")->level); +} + +TEST(H264ProfileLevelIdParsing, TestConstrainedBaseline) { + EXPECT_EQ(kProfileConstrainedBaseline, + ParseProfileLevelId("42e01f")->profile); + EXPECT_EQ(kProfileConstrainedBaseline, + ParseProfileLevelId("42C02A")->profile); + EXPECT_EQ(kProfileConstrainedBaseline, + ParseProfileLevelId("4de01f")->profile); + EXPECT_EQ(kProfileConstrainedBaseline, + ParseProfileLevelId("58f01f")->profile); +} + +TEST(H264ProfileLevelIdParsing, TestBaseline) { + EXPECT_EQ(kProfileBaseline, ParseProfileLevelId("42a01f")->profile); + EXPECT_EQ(kProfileBaseline, ParseProfileLevelId("58A01F")->profile); +} + +TEST(H264ProfileLevelIdParsing, TestMain) { + EXPECT_EQ(kProfileMain, ParseProfileLevelId("4D401f")->profile); +} + +TEST(H264ProfileLevelIdParsing, TestHigh) { + EXPECT_EQ(kProfileHigh, ParseProfileLevelId("64001f")->profile); +} + +TEST(H264ProfileLevelIdParsing, TestConstrainedHigh) { + EXPECT_EQ(kProfileConstrainedHigh, ParseProfileLevelId("640c1f")->profile); +} + +} // namespace H264 +} // namespace webrtc diff --git a/webrtc/modules/video_coding/codec_database.cc b/webrtc/modules/video_coding/codec_database.cc index 48ca1fbe25..24a29298e3 100644 --- a/webrtc/modules/video_coding/codec_database.cc +++ b/webrtc/modules/video_coding/codec_database.cc @@ -62,7 +62,6 @@ VideoCodecH264 VideoEncoder::GetDefaultH264Settings() { VideoCodecH264 h264_settings; memset(&h264_settings, 0, sizeof(h264_settings)); - h264_settings.profile = kProfileBase; h264_settings.frameDroppingOn = true; h264_settings.keyFrameInterval = 3000; h264_settings.spsData = nullptr;