Add helper functions for negotiating H264 profile level id
BUG=webrtc:6337,webrtc:6601 Review-Url: https://codereview.webrtc.org/2481033003 Cr-Commit-Position: refs/heads/master@{#15012}
This commit is contained in:
parent
5ec208fc98
commit
59be5f77c6
@ -16,8 +16,14 @@
|
||||
|
||||
#include "webrtc/base/arraysize.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace H264 {
|
||||
|
||||
namespace {
|
||||
|
||||
const char kProfileLevelId[] = "profile-level-id";
|
||||
const char kLevelAsymmetryAllowed[] = "level-asymmetry-allowed";
|
||||
|
||||
// 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;
|
||||
@ -56,19 +62,37 @@ class BitPattern {
|
||||
struct ProfilePattern {
|
||||
const uint8_t profile_idc;
|
||||
const BitPattern profile_iop;
|
||||
const webrtc::H264::Profile profile;
|
||||
const 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}};
|
||||
{0x42, BitPattern("x1xx0000"), kProfileConstrainedBaseline},
|
||||
{0x4D, BitPattern("1xxx0000"), kProfileConstrainedBaseline},
|
||||
{0x58, BitPattern("11xx0000"), kProfileConstrainedBaseline},
|
||||
{0x42, BitPattern("x0xx0000"), kProfileBaseline},
|
||||
{0x58, BitPattern("10xx0000"), kProfileBaseline},
|
||||
{0x4D, BitPattern("0x0x0000"), kProfileMain},
|
||||
{0x64, BitPattern("00000000"), kProfileHigh},
|
||||
{0x64, BitPattern("00001100"), kProfileConstrainedHigh}};
|
||||
|
||||
// Compare H264 levels and handle the level 1b case.
|
||||
bool IsLess(Level a, Level b) {
|
||||
if (a == kLevel1_b)
|
||||
return b != kLevel1 && b != kLevel1_b;
|
||||
if (b == kLevel1_b)
|
||||
return a == kLevel1;
|
||||
return a < b;
|
||||
}
|
||||
|
||||
Level Min(Level a, Level b) {
|
||||
return IsLess(a, b) ? a : b;
|
||||
}
|
||||
|
||||
bool IsLevelAsymmetryAllowed(const CodecParameterMap& params) {
|
||||
const auto it = params.find(kLevelAsymmetryAllowed);
|
||||
return it != params.end() && strcmp(it->second.c_str(), "1") == 0;
|
||||
}
|
||||
|
||||
struct LevelConstraint {
|
||||
const int max_macroblocks_per_second;
|
||||
@ -99,9 +123,6 @@ static constexpr LevelConstraint kLevelConstraints[] = {
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
namespace webrtc {
|
||||
namespace H264 {
|
||||
|
||||
rtc::Optional<ProfileLevelId> ParseProfileLevelId(const char* str) {
|
||||
// The string should consist of 3 bytes in hexadecimal format.
|
||||
if (strlen(str) != 6u)
|
||||
@ -175,6 +196,24 @@ rtc::Optional<Level> SupportedLevel(int max_frame_pixel_count, float max_fps) {
|
||||
return rtc::Optional<Level>();
|
||||
}
|
||||
|
||||
rtc::Optional<ProfileLevelId> ParseSdpProfileLevelId(
|
||||
const CodecParameterMap& 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 ProfileLevelId kDefaultProfileLevelId(
|
||||
kProfileConstrainedBaseline, kLevel3_1);
|
||||
|
||||
const auto profile_level_id_it = params.find(kProfileLevelId);
|
||||
return (profile_level_id_it == params.end())
|
||||
? rtc::Optional<ProfileLevelId>(kDefaultProfileLevelId)
|
||||
: ParseProfileLevelId(profile_level_id_it->second.c_str());
|
||||
}
|
||||
|
||||
rtc::Optional<std::string> ProfileLevelIdToString(
|
||||
const ProfileLevelId& profile_level_id) {
|
||||
// Handle special case level == 1b.
|
||||
@ -219,5 +258,39 @@ rtc::Optional<std::string> ProfileLevelIdToString(
|
||||
return rtc::Optional<std::string>(str);
|
||||
}
|
||||
|
||||
// Set level according to https://tools.ietf.org/html/rfc6184#section-8.2.2.
|
||||
void GenerateProfileLevelIdForAnswer(
|
||||
const CodecParameterMap& local_supported_params,
|
||||
const CodecParameterMap& remote_offered_params,
|
||||
CodecParameterMap* answer_params) {
|
||||
// Parse profile-level-ids.
|
||||
const rtc::Optional<ProfileLevelId> local_profile_level_id =
|
||||
ParseSdpProfileLevelId(local_supported_params);
|
||||
const rtc::Optional<ProfileLevelId> remote_profile_level_id =
|
||||
ParseSdpProfileLevelId(remote_offered_params);
|
||||
// The local and remote codec must have valid and equal H264 Profiles.
|
||||
RTC_DCHECK(local_profile_level_id);
|
||||
RTC_DCHECK(remote_profile_level_id);
|
||||
RTC_DCHECK_EQ(local_profile_level_id->profile,
|
||||
remote_profile_level_id->profile);
|
||||
|
||||
// Parse level information.
|
||||
const bool level_asymmetry_allowed =
|
||||
IsLevelAsymmetryAllowed(local_supported_params) &&
|
||||
IsLevelAsymmetryAllowed(remote_offered_params);
|
||||
const Level local_level = local_profile_level_id->level;
|
||||
const Level remote_level = remote_profile_level_id->level;
|
||||
const Level min_level = Min(local_level, remote_level);
|
||||
|
||||
// Determine answer level. When level asymmetry is not allowed, level upgrade
|
||||
// is not allowed, i.e., the level in the answer must be equal to or lower
|
||||
// than the level in the offer.
|
||||
const Level answer_level = level_asymmetry_allowed ? local_level : min_level;
|
||||
|
||||
// Set the resulting profile-level-id in the answer parameters.
|
||||
(*answer_params)[kProfileLevelId] = *ProfileLevelIdToString(
|
||||
ProfileLevelId(local_profile_level_id->profile, answer_level));
|
||||
}
|
||||
|
||||
} // namespace H264
|
||||
} // namespace webrtc
|
||||
|
||||
@ -11,6 +11,7 @@
|
||||
#ifndef WEBRTC_COMMON_VIDEO_H264_PROFILE_LEVEL_ID_H_
|
||||
#define WEBRTC_COMMON_VIDEO_H264_PROFILE_LEVEL_ID_H_
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include "webrtc/base/optional.h"
|
||||
@ -18,6 +19,9 @@
|
||||
namespace webrtc {
|
||||
namespace H264 {
|
||||
|
||||
// Map containting SDP codec parameters.
|
||||
typedef std::map<std::string, std::string> CodecParameterMap;
|
||||
|
||||
enum Profile {
|
||||
kProfileConstrainedBaseline,
|
||||
kProfileBaseline,
|
||||
@ -60,6 +64,13 @@ struct ProfileLevelId {
|
||||
// profile level id.
|
||||
rtc::Optional<ProfileLevelId> ParseProfileLevelId(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::Optional<ProfileLevelId> ParseSdpProfileLevelId(
|
||||
const CodecParameterMap& 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
|
||||
@ -71,6 +82,27 @@ rtc::Optional<Level> SupportedLevel(int max_frame_pixel_count, float max_fps);
|
||||
rtc::Optional<std::string> ProfileLevelIdToString(
|
||||
const ProfileLevelId& profile_level_id);
|
||||
|
||||
// Generate codec parameters that will be used as answer in an SDP negotiation
|
||||
// based on local supported parameters and remote offered parameters. Both
|
||||
// |local_supported_params|, |remote_offered_params|, and |answer_params|
|
||||
// represent sendrecv media descriptions, i.e they are a mix of both encode and
|
||||
// decode capabilities. In theory, when the profile in |local_supported_params|
|
||||
// represent a strict superset of the profile in |remote_offered_params|, we
|
||||
// could limit the profile in |answer_params| to the profile in
|
||||
// |remote_offered_params|. However, to simplify the code, each supported H264
|
||||
// profile should be listed explicitly in the list of local supported codecs,
|
||||
// even if they are redundant. Then each local codec in the list should be
|
||||
// tested one at a time against the remote codec, and only when the profiles are
|
||||
// equal should this function be called. Therefore, this function does not need
|
||||
// to handle profile intersection, and the profile of |local_supported_params|
|
||||
// and |remote_offered_params| must be equal before calling this function. The
|
||||
// parameters that are used when negotiating are the level part of
|
||||
// profile-level-id and level-asymmetry-allowed.
|
||||
void GenerateProfileLevelIdForAnswer(
|
||||
const CodecParameterMap& local_supported_params,
|
||||
const CodecParameterMap& remote_offered_params,
|
||||
CodecParameterMap* answer_params);
|
||||
|
||||
} // namespace H264
|
||||
} // namespace webrtc
|
||||
|
||||
|
||||
@ -124,5 +124,73 @@ TEST(H264ProfileLevelId, TestToStringInvalid) {
|
||||
ProfileLevelId(static_cast<Profile>(255), kLevel3_1)));
|
||||
}
|
||||
|
||||
TEST(H264ProfileLevelId, TestParseSdpProfileLevelIdEmpty) {
|
||||
const rtc::Optional<ProfileLevelId> profile_level_id =
|
||||
ParseSdpProfileLevelId(CodecParameterMap());
|
||||
EXPECT_TRUE(profile_level_id);
|
||||
EXPECT_EQ(kProfileConstrainedBaseline, profile_level_id->profile);
|
||||
EXPECT_EQ(kLevel3_1, profile_level_id->level);
|
||||
}
|
||||
|
||||
TEST(H264ProfileLevelId, TestParseSdpProfileLevelIdConstrainedHigh) {
|
||||
CodecParameterMap params;
|
||||
params["profile-level-id"] = "640c2a";
|
||||
const rtc::Optional<ProfileLevelId> profile_level_id =
|
||||
ParseSdpProfileLevelId(params);
|
||||
EXPECT_TRUE(profile_level_id);
|
||||
EXPECT_EQ(kProfileConstrainedHigh, profile_level_id->profile);
|
||||
EXPECT_EQ(kLevel4_2, profile_level_id->level);
|
||||
}
|
||||
|
||||
TEST(H264ProfileLevelId, TestParseSdpProfileLevelIdInvalid) {
|
||||
CodecParameterMap params;
|
||||
params["profile-level-id"] = "foobar";
|
||||
EXPECT_FALSE(ParseSdpProfileLevelId(params));
|
||||
}
|
||||
|
||||
TEST(H264ProfileLevelId, TestGenerateProfileLevelIdForAnswerEmpty) {
|
||||
CodecParameterMap answer_params;
|
||||
GenerateProfileLevelIdForAnswer(CodecParameterMap(), CodecParameterMap(),
|
||||
&answer_params);
|
||||
EXPECT_EQ("42e01f", answer_params["profile-level-id"]);
|
||||
}
|
||||
|
||||
TEST(H264ProfileLevelId,
|
||||
TestGenerateProfileLevelIdForAnswerLevelSymmetryCapped) {
|
||||
CodecParameterMap low_level;
|
||||
low_level["profile-level-id"] = "42e015";
|
||||
CodecParameterMap high_level;
|
||||
high_level["profile-level-id"] = "42e01f";
|
||||
|
||||
// Level asymmetry is not allowed; test that answer level is the lower of the
|
||||
// local and remote levels.
|
||||
CodecParameterMap answer_params;
|
||||
GenerateProfileLevelIdForAnswer(low_level /* local_supported */,
|
||||
high_level /* remote_offered */,
|
||||
&answer_params);
|
||||
EXPECT_EQ("42e015", answer_params["profile-level-id"]);
|
||||
|
||||
CodecParameterMap answer_params2;
|
||||
GenerateProfileLevelIdForAnswer(high_level /* local_supported */,
|
||||
low_level /* remote_offered */,
|
||||
&answer_params2);
|
||||
EXPECT_EQ("42e015", answer_params2["profile-level-id"]);
|
||||
}
|
||||
|
||||
TEST(H264ProfileLevelId,
|
||||
TestGenerateProfileLevelIdForAnswerConstrainedBaselineLevelAsymmetry) {
|
||||
CodecParameterMap local_params;
|
||||
local_params["profile-level-id"] = "42e01f";
|
||||
local_params["level-asymmetry-allowed"] = "1";
|
||||
CodecParameterMap remote_params;
|
||||
remote_params["profile-level-id"] = "42e015";
|
||||
remote_params["level-asymmetry-allowed"] = "1";
|
||||
CodecParameterMap answer_params;
|
||||
GenerateProfileLevelIdForAnswer(local_params, remote_params, &answer_params);
|
||||
// When level asymmetry is allowed, we can answer a higher level than what was
|
||||
// offered.
|
||||
EXPECT_EQ("42e01f", answer_params["profile-level-id"]);
|
||||
}
|
||||
|
||||
} // namespace H264
|
||||
} // namespace webrtc
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user