Move h264_profile_level_id and vp9_profile to api/video_codecs
This is a refactor to simplify a follow-up CL of adding
SdpVideoFormat::IsSameCodec.
The original files media/base/h264_profile_level_id.* and
media/base/vp9_profile.h must be kept until downstream projects
stop using them.
Bug: chroimium:1187565
Change-Id: Ib39eca095a3d61939a914d9bffaf4b891ddd222f
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/215236
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Reviewed-by: Kári Helgason <kthelgason@webrtc.org>
Reviewed-by: Niels Moller <nisse@webrtc.org>
Reviewed-by: Mirko Bonadei <mbonadei@webrtc.org>
Commit-Queue: Johannes Kron <kron@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#33782}
diff --git a/api/test/DEPS b/api/test/DEPS
index d97ac49..2784fcb 100644
--- a/api/test/DEPS
+++ b/api/test/DEPS
@@ -35,7 +35,4 @@
"create_frame_generator\.h": [
"+system_wrappers/include/clock.h",
],
- "videocodec_test_fixture\.h": [
- "+media/base/h264_profile_level_id.h"
- ],
}
diff --git a/api/test/videocodec_test_fixture.h b/api/test/videocodec_test_fixture.h
index 379d46d..e0f804f 100644
--- a/api/test/videocodec_test_fixture.h
+++ b/api/test/videocodec_test_fixture.h
@@ -59,7 +59,7 @@
class EncodedFrameChecker {
public:
virtual ~EncodedFrameChecker() = default;
- virtual void CheckEncodedFrame(webrtc::VideoCodecType codec,
+ virtual void CheckEncodedFrame(VideoCodecType codec,
const EncodedImage& encoded_frame) const = 0;
};
@@ -123,16 +123,16 @@
bool encode_in_real_time = false;
// Codec settings to use.
- webrtc::VideoCodec codec_settings;
+ VideoCodec codec_settings;
// Name of the codec being tested.
std::string codec_name;
// H.264 specific settings.
struct H264CodecSettings {
- H264::Profile profile = H264::kProfileConstrainedBaseline;
+ H264Profile profile = H264Profile::kProfileConstrainedBaseline;
H264PacketizationMode packetization_mode =
- webrtc::H264PacketizationMode::NonInterleaved;
+ H264PacketizationMode::NonInterleaved;
} h264_codec_settings;
// Custom checker that will be called for each frame.
diff --git a/api/video_codecs/BUILD.gn b/api/video_codecs/BUILD.gn
index a990276..4e28cd7 100644
--- a/api/video_codecs/BUILD.gn
+++ b/api/video_codecs/BUILD.gn
@@ -15,6 +15,8 @@
rtc_library("video_codecs_api") {
visibility = [ "*" ]
sources = [
+ "h264_profile_level_id.cc",
+ "h264_profile_level_id.h",
"sdp_video_format.cc",
"sdp_video_format.h",
"spatial_layer.cc",
@@ -35,6 +37,8 @@
"vp8_frame_config.h",
"vp8_temporal_layers.cc",
"vp8_temporal_layers.h",
+ "vp9_profile.cc",
+ "vp9_profile.h",
]
deps = [
@@ -138,7 +142,6 @@
":video_codecs_api",
"..:fec_controller_api",
"../../api/video:video_frame",
- "../../media:rtc_h264_profile_id",
"../../media:rtc_media_base",
"../../modules/video_coding:video_codec_interface",
"../../modules/video_coding:video_coding_utility",
diff --git a/api/video_codecs/h264_profile_level_id.cc b/api/video_codecs/h264_profile_level_id.cc
new file mode 100644
index 0000000..fa47758
--- /dev/null
+++ b/api/video_codecs/h264_profile_level_id.cc
@@ -0,0 +1,252 @@
+/*
+ * Copyright (c) 2021 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 "api/video_codecs/h264_profile_level_id.h"
+
+#include <cstdio>
+#include <cstdlib>
+#include <string>
+
+#include "rtc_base/arraysize.h"
+#include "rtc_base/checks.h"
+
+namespace webrtc {
+
+namespace {
+
+const char kProfileLevelId[] = "profile-level-id";
+
+// 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:
+ explicit 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 H264Profile.
+struct ProfilePattern {
+ const uint8_t profile_idc;
+ const BitPattern profile_iop;
+ const H264Profile profile;
+};
+
+// This is from https://tools.ietf.org/html/rfc6184#section-8.1.
+constexpr ProfilePattern kProfilePatterns[] = {
+ {0x42, BitPattern("x1xx0000"), H264Profile::kProfileConstrainedBaseline},
+ {0x4D, BitPattern("1xxx0000"), H264Profile::kProfileConstrainedBaseline},
+ {0x58, BitPattern("11xx0000"), H264Profile::kProfileConstrainedBaseline},
+ {0x42, BitPattern("x0xx0000"), H264Profile::kProfileBaseline},
+ {0x58, BitPattern("10xx0000"), H264Profile::kProfileBaseline},
+ {0x4D, BitPattern("0x0x0000"), H264Profile::kProfileMain},
+ {0x64, BitPattern("00000000"), H264Profile::kProfileHigh},
+ {0x64, BitPattern("00001100"), H264Profile::kProfileConstrainedHigh}};
+
+struct LevelConstraint {
+ const int max_macroblocks_per_second;
+ const int max_macroblock_frame_size;
+ const H264Level level;
+};
+
+// This is from ITU-T H.264 (02/2016) Table A-1 – Level limits.
+static constexpr LevelConstraint kLevelConstraints[] = {
+ {1485, 99, H264Level::kLevel1},
+ {1485, 99, H264Level::kLevel1_b},
+ {3000, 396, H264Level::kLevel1_1},
+ {6000, 396, H264Level::kLevel1_2},
+ {11880, 396, H264Level::kLevel1_3},
+ {11880, 396, H264Level::kLevel2},
+ {19800, 792, H264Level::kLevel2_1},
+ {20250, 1620, H264Level::kLevel2_2},
+ {40500, 1620, H264Level::kLevel3},
+ {108000, 3600, H264Level::kLevel3_1},
+ {216000, 5120, H264Level::kLevel3_2},
+ {245760, 8192, H264Level::kLevel4},
+ {245760, 8192, H264Level::kLevel4_1},
+ {522240, 8704, H264Level::kLevel4_2},
+ {589824, 22080, H264Level::kLevel5},
+ {983040, 36864, H264Level::kLevel5_1},
+ {2073600, 36864, H264Level::kLevel5_2},
+};
+
+} // anonymous namespace
+
+absl::optional<H264ProfileLevelId> ParseH264ProfileLevelId(const char* str) {
+ // The string should consist of 3 bytes in hexadecimal format.
+ if (strlen(str) != 6u)
+ return absl::nullopt;
+ const uint32_t profile_level_id_numeric = strtol(str, nullptr, 16);
+ if (profile_level_id_numeric == 0)
+ return absl::nullopt;
+
+ // Separate into three bytes.
+ const uint8_t level_idc =
+ static_cast<uint8_t>(profile_level_id_numeric & 0xFF);
+ const uint8_t profile_iop =
+ static_cast<uint8_t>((profile_level_id_numeric >> 8) & 0xFF);
+ const uint8_t profile_idc =
+ static_cast<uint8_t>((profile_level_id_numeric >> 16) & 0xFF);
+
+ // Parse level based on level_idc and constraint set 3 flag.
+ H264Level level_casted = static_cast<H264Level>(level_idc);
+ H264Level level;
+
+ switch (level_casted) {
+ case H264Level::kLevel1_1:
+ level = (profile_iop & kConstraintSet3Flag) != 0 ? H264Level::kLevel1_b
+ : H264Level::kLevel1_1;
+ break;
+ case H264Level::kLevel1:
+ case H264Level::kLevel1_2:
+ case H264Level::kLevel1_3:
+ case H264Level::kLevel2:
+ case H264Level::kLevel2_1:
+ case H264Level::kLevel2_2:
+ case H264Level::kLevel3:
+ case H264Level::kLevel3_1:
+ case H264Level::kLevel3_2:
+ case H264Level::kLevel4:
+ case H264Level::kLevel4_1:
+ case H264Level::kLevel4_2:
+ case H264Level::kLevel5:
+ case H264Level::kLevel5_1:
+ case H264Level::kLevel5_2:
+ level = level_casted;
+ break;
+ default:
+ // Unrecognized level_idc.
+ return absl::nullopt;
+ }
+
+ // 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 H264ProfileLevelId(pattern.profile, level);
+ }
+ }
+
+ // Unrecognized profile_idc/profile_iop combination.
+ return absl::nullopt;
+}
+
+absl::optional<H264Level> H264SupportedLevel(int max_frame_pixel_count,
+ float max_fps) {
+ static const int kPixelsPerMacroblock = 16 * 16;
+
+ for (int i = arraysize(kLevelConstraints) - 1; i >= 0; --i) {
+ const LevelConstraint& level_constraint = kLevelConstraints[i];
+ if (level_constraint.max_macroblock_frame_size * kPixelsPerMacroblock <=
+ max_frame_pixel_count &&
+ level_constraint.max_macroblocks_per_second <=
+ max_fps * level_constraint.max_macroblock_frame_size) {
+ return level_constraint.level;
+ }
+ }
+
+ // No level supported.
+ return absl::nullopt;
+}
+
+absl::optional<H264ProfileLevelId> ParseSdpForH264ProfileLevelId(
+ const SdpVideoFormat::Parameters& params) {
+ // TODO(magjed): The default should really be kProfileBaseline and kLevel1
+ // according to the spec: https://tools.ietf.org/html/rfc6184#section-8.1. In
+ // order to not break backwards compatibility with older versions of WebRTC
+ // where external codecs don't have any parameters, use
+ // kProfileConstrainedBaseline kLevel3_1 instead. This workaround will only be
+ // done in an interim period to allow external clients to update their code.
+ // http://crbug/webrtc/6337.
+ static const H264ProfileLevelId kDefaultProfileLevelId(
+ H264Profile::kProfileConstrainedBaseline, H264Level::kLevel3_1);
+
+ const auto profile_level_id_it = params.find(kProfileLevelId);
+ return (profile_level_id_it == params.end())
+ ? kDefaultProfileLevelId
+ : ParseH264ProfileLevelId(profile_level_id_it->second.c_str());
+}
+
+absl::optional<std::string> H264ProfileLevelIdToString(
+ const H264ProfileLevelId& profile_level_id) {
+ // Handle special case level == 1b.
+ if (profile_level_id.level == H264Level::kLevel1_b) {
+ switch (profile_level_id.profile) {
+ case H264Profile::kProfileConstrainedBaseline:
+ return {"42f00b"};
+ case H264Profile::kProfileBaseline:
+ return {"42100b"};
+ case H264Profile::kProfileMain:
+ return {"4d100b"};
+ // Level 1b is not allowed for other profiles.
+ default:
+ return absl::nullopt;
+ }
+ }
+
+ const char* profile_idc_iop_string;
+ switch (profile_level_id.profile) {
+ case H264Profile::kProfileConstrainedBaseline:
+ profile_idc_iop_string = "42e0";
+ break;
+ case H264Profile::kProfileBaseline:
+ profile_idc_iop_string = "4200";
+ break;
+ case H264Profile::kProfileMain:
+ profile_idc_iop_string = "4d00";
+ break;
+ case H264Profile::kProfileConstrainedHigh:
+ profile_idc_iop_string = "640c";
+ break;
+ case H264Profile::kProfileHigh:
+ profile_idc_iop_string = "6400";
+ break;
+ // Unrecognized profile.
+ default:
+ return absl::nullopt;
+ }
+
+ char str[7];
+ snprintf(str, 7u, "%s%02x", profile_idc_iop_string, profile_level_id.level);
+ return {str};
+}
+
+bool H264IsSameProfile(const SdpVideoFormat::Parameters& params1,
+ const SdpVideoFormat::Parameters& params2) {
+ const absl::optional<H264ProfileLevelId> profile_level_id =
+ ParseSdpForH264ProfileLevelId(params1);
+ const absl::optional<H264ProfileLevelId> other_profile_level_id =
+ ParseSdpForH264ProfileLevelId(params2);
+ // Compare H264 profiles, but not levels.
+ return profile_level_id && other_profile_level_id &&
+ profile_level_id->profile == other_profile_level_id->profile;
+}
+
+} // namespace webrtc
diff --git a/api/video_codecs/h264_profile_level_id.h b/api/video_codecs/h264_profile_level_id.h
new file mode 100644
index 0000000..51d025c
--- /dev/null
+++ b/api/video_codecs/h264_profile_level_id.h
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2021 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 API_VIDEO_CODECS_H264_PROFILE_LEVEL_ID_H_
+#define API_VIDEO_CODECS_H264_PROFILE_LEVEL_ID_H_
+
+#include <string>
+
+#include "absl/types/optional.h"
+#include "api/video_codecs/sdp_video_format.h"
+#include "rtc_base/system/rtc_export.h"
+
+namespace webrtc {
+
+enum class H264Profile {
+ kProfileConstrainedBaseline,
+ kProfileBaseline,
+ kProfileMain,
+ kProfileConstrainedHigh,
+ kProfileHigh,
+};
+
+// All values are equal to ten times the level number, except level 1b which is
+// special.
+enum class H264Level {
+ 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 H264ProfileLevelId {
+ constexpr H264ProfileLevelId(H264Profile profile, H264Level level)
+ : profile(profile), level(level) {}
+ H264Profile profile;
+ H264Level 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.
+absl::optional<H264ProfileLevelId> ParseH264ProfileLevelId(const char* str);
+
+// Parse profile level id that is represented as a string of 3 hex bytes
+// contained in an SDP key-value map. A default profile level id will be
+// returned if the profile-level-id key is missing. Nothing will be returned if
+// the key is present but the string is invalid.
+RTC_EXPORT absl::optional<H264ProfileLevelId> ParseSdpForH264ProfileLevelId(
+ const SdpVideoFormat::Parameters& params);
+
+// Given that a decoder supports up to a given frame size (in pixels) at up to a
+// given number of frames per second, return the highest H.264 level where it
+// can guarantee that it will be able to support all valid encoded streams that
+// are within that level.
+RTC_EXPORT absl::optional<H264Level> H264SupportedLevel(
+ int max_frame_pixel_count,
+ float max_fps);
+
+// Returns canonical string representation as three hex bytes of the profile
+// level id, or returns nothing for invalid profile level ids.
+RTC_EXPORT absl::optional<std::string> H264ProfileLevelIdToString(
+ const H264ProfileLevelId& profile_level_id);
+
+// Returns true if the parameters have the same H264 profile (Baseline, High,
+// etc).
+RTC_EXPORT bool H264IsSameProfile(const SdpVideoFormat::Parameters& params1,
+ const SdpVideoFormat::Parameters& params2);
+
+} // namespace webrtc
+
+#endif // API_VIDEO_CODECS_H264_PROFILE_LEVEL_ID_H_
diff --git a/api/video_codecs/test/BUILD.gn b/api/video_codecs/test/BUILD.gn
index cb810fc..ea8e6d5 100644
--- a/api/video_codecs/test/BUILD.gn
+++ b/api/video_codecs/test/BUILD.gn
@@ -13,6 +13,7 @@
testonly = true
sources = [
"builtin_video_encoder_factory_unittest.cc",
+ "h264_profile_level_id_unittest.cc",
"video_decoder_software_fallback_wrapper_unittest.cc",
"video_encoder_software_fallback_wrapper_unittest.cc",
]
diff --git a/api/video_codecs/test/h264_profile_level_id_unittest.cc b/api/video_codecs/test/h264_profile_level_id_unittest.cc
new file mode 100644
index 0000000..47098d2
--- /dev/null
+++ b/api/video_codecs/test/h264_profile_level_id_unittest.cc
@@ -0,0 +1,171 @@
+/*
+ * Copyright (c) 2021 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 "api/video_codecs/h264_profile_level_id.h"
+
+#include <map>
+#include <string>
+
+#include "absl/types/optional.h"
+#include "test/gtest.h"
+
+namespace webrtc {
+
+TEST(H264ProfileLevelId, TestParsingInvalid) {
+ // Malformed strings.
+ EXPECT_FALSE(ParseH264ProfileLevelId(""));
+ EXPECT_FALSE(ParseH264ProfileLevelId(" 42e01f"));
+ EXPECT_FALSE(ParseH264ProfileLevelId("4242e01f"));
+ EXPECT_FALSE(ParseH264ProfileLevelId("e01f"));
+ EXPECT_FALSE(ParseH264ProfileLevelId("gggggg"));
+
+ // Invalid level.
+ EXPECT_FALSE(ParseH264ProfileLevelId("42e000"));
+ EXPECT_FALSE(ParseH264ProfileLevelId("42e00f"));
+ EXPECT_FALSE(ParseH264ProfileLevelId("42e0ff"));
+
+ // Invalid profile.
+ EXPECT_FALSE(ParseH264ProfileLevelId("42e11f"));
+ EXPECT_FALSE(ParseH264ProfileLevelId("58601f"));
+ EXPECT_FALSE(ParseH264ProfileLevelId("64e01f"));
+}
+
+TEST(H264ProfileLevelId, TestParsingLevel) {
+ EXPECT_EQ(H264Level::kLevel3_1, ParseH264ProfileLevelId("42e01f")->level);
+ EXPECT_EQ(H264Level::kLevel1_1, ParseH264ProfileLevelId("42e00b")->level);
+ EXPECT_EQ(H264Level::kLevel1_b, ParseH264ProfileLevelId("42f00b")->level);
+ EXPECT_EQ(H264Level::kLevel4_2, ParseH264ProfileLevelId("42C02A")->level);
+ EXPECT_EQ(H264Level::kLevel5_2, ParseH264ProfileLevelId("640c34")->level);
+}
+
+TEST(H264ProfileLevelId, TestParsingConstrainedBaseline) {
+ EXPECT_EQ(H264Profile::kProfileConstrainedBaseline,
+ ParseH264ProfileLevelId("42e01f")->profile);
+ EXPECT_EQ(H264Profile::kProfileConstrainedBaseline,
+ ParseH264ProfileLevelId("42C02A")->profile);
+ EXPECT_EQ(H264Profile::kProfileConstrainedBaseline,
+ ParseH264ProfileLevelId("4de01f")->profile);
+ EXPECT_EQ(H264Profile::kProfileConstrainedBaseline,
+ ParseH264ProfileLevelId("58f01f")->profile);
+}
+
+TEST(H264ProfileLevelId, TestParsingBaseline) {
+ EXPECT_EQ(H264Profile::kProfileBaseline,
+ ParseH264ProfileLevelId("42a01f")->profile);
+ EXPECT_EQ(H264Profile::kProfileBaseline,
+ ParseH264ProfileLevelId("58A01F")->profile);
+}
+
+TEST(H264ProfileLevelId, TestParsingMain) {
+ EXPECT_EQ(H264Profile::kProfileMain,
+ ParseH264ProfileLevelId("4D401f")->profile);
+}
+
+TEST(H264ProfileLevelId, TestParsingHigh) {
+ EXPECT_EQ(H264Profile::kProfileHigh,
+ ParseH264ProfileLevelId("64001f")->profile);
+}
+
+TEST(H264ProfileLevelId, TestParsingConstrainedHigh) {
+ EXPECT_EQ(H264Profile::kProfileConstrainedHigh,
+ ParseH264ProfileLevelId("640c1f")->profile);
+}
+
+TEST(H264ProfileLevelId, TestSupportedLevel) {
+ EXPECT_EQ(H264Level::kLevel2_1, *H264SupportedLevel(640 * 480, 25));
+ EXPECT_EQ(H264Level::kLevel3_1, *H264SupportedLevel(1280 * 720, 30));
+ EXPECT_EQ(H264Level::kLevel4_2, *H264SupportedLevel(1920 * 1280, 60));
+}
+
+// Test supported level below level 1 requirements.
+TEST(H264ProfileLevelId, TestSupportedLevelInvalid) {
+ EXPECT_FALSE(H264SupportedLevel(0, 0));
+ // All levels support fps > 5.
+ EXPECT_FALSE(H264SupportedLevel(1280 * 720, 5));
+ // All levels support frame sizes > 183 * 137.
+ EXPECT_FALSE(H264SupportedLevel(183 * 137, 30));
+}
+
+TEST(H264ProfileLevelId, TestToString) {
+ EXPECT_EQ("42e01f", *H264ProfileLevelIdToString(H264ProfileLevelId(
+ H264Profile::kProfileConstrainedBaseline,
+ H264Level::kLevel3_1)));
+ EXPECT_EQ("42000a", *H264ProfileLevelIdToString(H264ProfileLevelId(
+ H264Profile::kProfileBaseline, H264Level::kLevel1)));
+ EXPECT_EQ("4d001f", H264ProfileLevelIdToString(H264ProfileLevelId(
+ H264Profile::kProfileMain, H264Level::kLevel3_1)));
+ EXPECT_EQ("640c2a",
+ *H264ProfileLevelIdToString(H264ProfileLevelId(
+ H264Profile::kProfileConstrainedHigh, H264Level::kLevel4_2)));
+ EXPECT_EQ("64002a", *H264ProfileLevelIdToString(H264ProfileLevelId(
+ H264Profile::kProfileHigh, H264Level::kLevel4_2)));
+}
+
+TEST(H264ProfileLevelId, TestToStringLevel1b) {
+ EXPECT_EQ("42f00b", *H264ProfileLevelIdToString(H264ProfileLevelId(
+ H264Profile::kProfileConstrainedBaseline,
+ H264Level::kLevel1_b)));
+ EXPECT_EQ("42100b",
+ *H264ProfileLevelIdToString(H264ProfileLevelId(
+ H264Profile::kProfileBaseline, H264Level::kLevel1_b)));
+ EXPECT_EQ("4d100b", *H264ProfileLevelIdToString(H264ProfileLevelId(
+ H264Profile::kProfileMain, H264Level::kLevel1_b)));
+}
+
+TEST(H264ProfileLevelId, TestToStringRoundTrip) {
+ EXPECT_EQ("42e01f",
+ *H264ProfileLevelIdToString(*ParseH264ProfileLevelId("42e01f")));
+ EXPECT_EQ("42e01f",
+ *H264ProfileLevelIdToString(*ParseH264ProfileLevelId("42E01F")));
+ EXPECT_EQ("4d100b",
+ *H264ProfileLevelIdToString(*ParseH264ProfileLevelId("4d100b")));
+ EXPECT_EQ("4d100b",
+ *H264ProfileLevelIdToString(*ParseH264ProfileLevelId("4D100B")));
+ EXPECT_EQ("640c2a",
+ *H264ProfileLevelIdToString(*ParseH264ProfileLevelId("640c2a")));
+ EXPECT_EQ("640c2a",
+ *H264ProfileLevelIdToString(*ParseH264ProfileLevelId("640C2A")));
+}
+
+TEST(H264ProfileLevelId, TestToStringInvalid) {
+ EXPECT_FALSE(H264ProfileLevelIdToString(
+ H264ProfileLevelId(H264Profile::kProfileHigh, H264Level::kLevel1_b)));
+ EXPECT_FALSE(H264ProfileLevelIdToString(H264ProfileLevelId(
+ H264Profile::kProfileConstrainedHigh, H264Level::kLevel1_b)));
+ EXPECT_FALSE(H264ProfileLevelIdToString(
+ H264ProfileLevelId(static_cast<H264Profile>(255), H264Level::kLevel3_1)));
+}
+
+TEST(H264ProfileLevelId, TestParseSdpProfileLevelIdEmpty) {
+ const absl::optional<H264ProfileLevelId> profile_level_id =
+ ParseSdpForH264ProfileLevelId(SdpVideoFormat::Parameters());
+ EXPECT_TRUE(profile_level_id);
+ EXPECT_EQ(H264Profile::kProfileConstrainedBaseline,
+ profile_level_id->profile);
+ EXPECT_EQ(H264Level::kLevel3_1, profile_level_id->level);
+}
+
+TEST(H264ProfileLevelId, TestParseSdpProfileLevelIdConstrainedHigh) {
+ SdpVideoFormat::Parameters params;
+ params["profile-level-id"] = "640c2a";
+ const absl::optional<H264ProfileLevelId> profile_level_id =
+ ParseSdpForH264ProfileLevelId(params);
+ EXPECT_TRUE(profile_level_id);
+ EXPECT_EQ(H264Profile::kProfileConstrainedHigh, profile_level_id->profile);
+ EXPECT_EQ(H264Level::kLevel4_2, profile_level_id->level);
+}
+
+TEST(H264ProfileLevelId, TestParseSdpProfileLevelIdInvalid) {
+ SdpVideoFormat::Parameters params;
+ params["profile-level-id"] = "foobar";
+ EXPECT_FALSE(ParseSdpForH264ProfileLevelId(params));
+}
+
+} // namespace webrtc
diff --git a/api/video_codecs/vp9_profile.cc b/api/video_codecs/vp9_profile.cc
new file mode 100644
index 0000000..d69f566
--- /dev/null
+++ b/api/video_codecs/vp9_profile.cc
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2021 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 "api/video_codecs/vp9_profile.h"
+
+#include <map>
+#include <utility>
+
+#include "rtc_base/string_to_number.h"
+
+namespace webrtc {
+
+// Profile information for VP9 video.
+const char kVP9FmtpProfileId[] = "profile-id";
+
+std::string VP9ProfileToString(VP9Profile profile) {
+ switch (profile) {
+ case VP9Profile::kProfile0:
+ return "0";
+ case VP9Profile::kProfile1:
+ return "1";
+ case VP9Profile::kProfile2:
+ return "2";
+ }
+ return "0";
+}
+
+absl::optional<VP9Profile> StringToVP9Profile(const std::string& str) {
+ const absl::optional<int> i = rtc::StringToNumber<int>(str);
+ if (!i.has_value())
+ return absl::nullopt;
+
+ switch (i.value()) {
+ case 0:
+ return VP9Profile::kProfile0;
+ case 1:
+ return VP9Profile::kProfile1;
+ case 2:
+ return VP9Profile::kProfile2;
+ default:
+ return absl::nullopt;
+ }
+ return absl::nullopt;
+}
+
+absl::optional<VP9Profile> ParseSdpForVP9Profile(
+ const SdpVideoFormat::Parameters& params) {
+ const auto profile_it = params.find(kVP9FmtpProfileId);
+ if (profile_it == params.end())
+ return VP9Profile::kProfile0;
+ const std::string& profile_str = profile_it->second;
+ return StringToVP9Profile(profile_str);
+}
+
+bool VP9IsSameProfile(const SdpVideoFormat::Parameters& params1,
+ const SdpVideoFormat::Parameters& params2) {
+ const absl::optional<VP9Profile> profile = ParseSdpForVP9Profile(params1);
+ const absl::optional<VP9Profile> other_profile =
+ ParseSdpForVP9Profile(params2);
+ return profile && other_profile && profile == other_profile;
+}
+
+} // namespace webrtc
diff --git a/api/video_codecs/vp9_profile.h b/api/video_codecs/vp9_profile.h
new file mode 100644
index 0000000..e632df4
--- /dev/null
+++ b/api/video_codecs/vp9_profile.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2021 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 API_VIDEO_CODECS_VP9_PROFILE_H_
+#define API_VIDEO_CODECS_VP9_PROFILE_H_
+
+#include <string>
+
+#include "absl/types/optional.h"
+#include "api/video_codecs/sdp_video_format.h"
+#include "rtc_base/system/rtc_export.h"
+
+namespace webrtc {
+
+// Profile information for VP9 video.
+extern RTC_EXPORT const char kVP9FmtpProfileId[];
+
+enum class VP9Profile {
+ kProfile0,
+ kProfile1,
+ kProfile2,
+};
+
+// Helper functions to convert VP9Profile to std::string. Returns "0" by
+// default.
+RTC_EXPORT std::string VP9ProfileToString(VP9Profile profile);
+
+// Helper functions to convert std::string to VP9Profile. Returns null if given
+// an invalid profile string.
+absl::optional<VP9Profile> StringToVP9Profile(const std::string& str);
+
+// Parse profile that is represented as a string of single digit contained in an
+// SDP key-value map. A default profile(kProfile0) will be returned if the
+// profile key is missing. Nothing will be returned if the key is present but
+// the string is invalid.
+RTC_EXPORT absl::optional<VP9Profile> ParseSdpForVP9Profile(
+ const SdpVideoFormat::Parameters& params);
+
+// Returns true if the parameters have the same VP9 profile, or neither contains
+// VP9 profile.
+bool VP9IsSameProfile(const SdpVideoFormat::Parameters& params1,
+ const SdpVideoFormat::Parameters& params2);
+
+} // namespace webrtc
+
+#endif // API_VIDEO_CODECS_VP9_PROFILE_H_