blob: be76c193ab56acf0cad2b43f0cbbd758d85a0946 [file] [log] [blame] [edit]
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif
#include "media/base/video_codec_string_parsers.h"
#include <array>
#include <string_view>
#include "base/logging.h"
#include "base/notreached.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "media/base/video_color_space.h"
#include "ui/gfx/color_space.h"
namespace {
bool IsDolbyVisionAVCCodecId(std::string_view codec_id) {
return base::StartsWith(codec_id, "dva1.", base::CompareCase::SENSITIVE) ||
base::StartsWith(codec_id, "dvav.", base::CompareCase::SENSITIVE);
}
bool IsDolbyVisionHEVCCodecId(std::string_view codec_id) {
return base::StartsWith(codec_id, "dvh1.", base::CompareCase::SENSITIVE) ||
base::StartsWith(codec_id, "dvhe.", base::CompareCase::SENSITIVE);
}
} // namespace
namespace media {
// TODO(crbug.com/40232176): Remove after rollout.
// Allow parsing HEVC range extension codec string.
BASE_FEATURE(kHEVCRextCodecStringParsing,
"HEVCRextCodecStringParsing",
base::FEATURE_ENABLED_BY_DEFAULT);
std::optional<VideoType> ParseNewStyleVp9CodecID(std::string_view codec_id) {
// Initialize optional fields to their defaults.
VideoType result = {
.codec = VideoCodec::kVP9,
.color_space = VideoColorSpace::REC709(),
.subsampling = VideoChromaSampling::k420,
};
std::vector<std::string> fields = base::SplitString(
codec_id, ".", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
// First four fields are mandatory. No more than 9 fields are expected.
if (fields.size() < 4 || fields.size() > 9) {
DVLOG(3) << __func__ << " Invalid number of fields (" << fields.size()
<< ")";
return std::nullopt;
}
if (fields[0] != "vp09") {
DVLOG(3) << __func__ << " Invalid 4CC (" << fields[0] << ")";
return std::nullopt;
}
std::vector<int> values;
for (size_t i = 1; i < fields.size(); ++i) {
// Missing value is not allowed.
if (fields[i] == "") {
DVLOG(3) << __func__ << " Invalid missing field (position:" << i << ")";
return std::nullopt;
}
int value;
if (!base::StringToInt(fields[i], &value) || value < 0) {
DVLOG(3) << __func__ << " Invalid field value (" << value << ")";
return std::nullopt;
}
values.push_back(value);
}
const int profile_idc = values[0];
switch (profile_idc) {
case 0:
result.profile = VP9PROFILE_PROFILE0;
break;
case 1:
result.profile = VP9PROFILE_PROFILE1;
break;
case 2:
result.profile = VP9PROFILE_PROFILE2;
break;
case 3:
result.profile = VP9PROFILE_PROFILE3;
break;
default:
DVLOG(3) << __func__ << " Invalid profile (" << profile_idc << ")";
return std::nullopt;
}
result.level = values[1];
switch (result.level) {
case 10:
case 11:
case 20:
case 21:
case 30:
case 31:
case 40:
case 41:
case 50:
case 51:
case 52:
case 60:
case 61:
case 62:
break;
default:
DVLOG(3) << __func__ << " Invalid level (" << result.level << ")";
return std::nullopt;
}
result.bit_depth = values[2];
if (result.bit_depth != 8 && result.bit_depth != 10 &&
result.bit_depth != 12) {
DVLOG(3) << __func__ << " Invalid bit-depth (" << *result.bit_depth << ")";
return std::nullopt;
}
// 4:2:0 isn't supported in profiles 1, 3.
if (profile_idc == 1 || profile_idc == 3) {
result.subsampling = VideoChromaSampling::k422;
}
if (values.size() < 4) {
return result;
}
const int chroma_subsampling = values[3];
switch (chroma_subsampling) {
case 0:
case 1:
result.subsampling = VideoChromaSampling::k420;
break;
case 2:
result.subsampling = VideoChromaSampling::k422;
break;
case 3:
result.subsampling = VideoChromaSampling::k444;
break;
default:
DVLOG(3) << __func__ << " Invalid chroma subsampling ("
<< chroma_subsampling << ")";
return std::nullopt;
}
if (result.subsampling != VideoChromaSampling::k420 && profile_idc != 1 &&
profile_idc != 3) {
DVLOG(3) << __func__
<< " 4:2:2 and 4:4:4 are only supported in profile 1, 3";
// Ideally this would be an error, but even Netflix broke when we tried...
result.subsampling = VideoChromaSampling::k420;
}
if (values.size() < 5) {
return result;
}
result.color_space.primaries = VideoColorSpace::GetPrimaryID(values[4]);
if (result.color_space.primaries == VideoColorSpace::PrimaryID::INVALID) {
DVLOG(3) << __func__ << " Invalid color primaries (" << values[4] << ")";
return std::nullopt;
}
if (values.size() < 6) {
return result;
}
result.color_space.transfer = VideoColorSpace::GetTransferID(values[5]);
if (result.color_space.transfer == VideoColorSpace::TransferID::INVALID) {
DVLOG(3) << __func__ << " Invalid transfer function (" << values[5] << ")";
return std::nullopt;
}
if (values.size() < 7) {
return result;
}
result.color_space.matrix = VideoColorSpace::GetMatrixID(values[6]);
if (result.color_space.matrix == VideoColorSpace::MatrixID::INVALID) {
DVLOG(3) << __func__ << " Invalid matrix coefficients (" << values[6]
<< ")";
return std::nullopt;
}
if (result.color_space.matrix == VideoColorSpace::MatrixID::RGB &&
chroma_subsampling != 3) {
DVLOG(3) << __func__ << " Invalid combination of chroma_subsampling ("
<< ") and matrix coefficients (" << values[6] << ")";
}
if (values.size() < 8) {
return result;
}
const int video_full_range_flag = values[7];
if (video_full_range_flag > 1) {
DVLOG(3) << __func__ << " Invalid full range flag ("
<< video_full_range_flag << ")";
return std::nullopt;
}
result.color_space.range = video_full_range_flag == 1
? gfx::ColorSpace::RangeID::FULL
: gfx::ColorSpace::RangeID::LIMITED;
return result;
}
std::optional<VideoType> ParseLegacyVp9CodecID(std::string_view codec_id) {
if (codec_id == "vp9" || codec_id == "vp9.0") {
// Profile is not included in the codec string. Consumers of parsed codec
// should handle by rejecting ambiguous string or resolving to a default
// profile.
VideoType result{.codec = VideoCodec::kVP9,
.profile = VIDEO_CODEC_PROFILE_UNKNOWN,
.level = kNoVideoCodecLevel};
return result;
}
return std::nullopt;
}
std::optional<VideoType> ParseAv1CodecId(std::string_view codec_id) {
// The codecs parameter string for the AOM AV1 codec is as follows:
// See https://aomediacodec.github.io/av1-isobmff/#codecsparam.
//
// <sample entry4CC>.<profile>.<level><tier>.<bitDepth>.<monochrome>.
// <chromaSubsampling>.<colorPrimaries>.<transferCharacteristics>.
// <matrixCoefficients>.<videoFullRangeFlag>
std::vector<std::string> fields = base::SplitString(
codec_id, ".", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
// The parameters sample entry 4CC, profile, level, tier, and bitDepth are all
// mandatory fields. If any of these fields are empty, or not within their
// allowed range, the processing device SHOULD treat it as an error.
if (fields.size() < 4 || fields.size() > 10) {
DVLOG(3) << __func__ << " Invalid number of fields (" << fields.size()
<< ")";
return std::nullopt;
}
// All the other fields (including their leading '.') are optional, mutually
// inclusive (all or none) fields. If not specified then the values listed in
// the table below are assumed.
//
// mono_chrome 0
// chromaSubsampling 112 (4:2:0 colocated with luma (0,0))
// colorPrimaries 1 (ITU-R BT.709)
// transferCharacteristics 1 (ITU-R BT.709)
// matrixCoefficients 1 (ITU-R BT.709)
// videoFullRangeFlag 0 (studio swing representation)
// Initialize optional fields to their defaults.
VideoType result = {
.codec = VideoCodec::kAV1,
.color_space = VideoColorSpace::REC709(),
.subsampling = VideoChromaSampling::k420,
};
if (fields[0] != "av01") {
DVLOG(3) << __func__ << " Invalid AV1 4CC (" << fields[0] << ")";
return std::nullopt;
}
// The level parameter value SHALL equal the first level value indicated by
// seq_level_idx in the Sequence Header. The tier parameter value SHALL be
// equal to M when the first seq_tier value in the Sequence Header is equal to
// 0, and H when it is equal to 1.
if (fields[2].size() != 3 || (fields[2][2] != 'M' && fields[2][2] != 'H')) {
DVLOG(3) << __func__ << " Invalid level+tier (" << fields[2] << ")";
return std::nullopt;
}
// Since tier has been validated, strip the trailing tier indicator to allow
// int conversion below.
fields[2].resize(2);
// Fill with dummy values to ensure parallel indices with fields.
std::vector<int> values(fields.size(), 0);
for (size_t i = 1; i < fields.size(); ++i) {
if (fields[i].empty()) {
DVLOG(3) << __func__ << " Invalid empty field (position:" << i << ")";
return std::nullopt;
}
if (!base::StringToInt(fields[i], &values[i]) || values[i] < 0) {
DVLOG(3) << __func__ << " Invalid field value (" << values[i] << ")";
return std::nullopt;
}
}
// The profile parameter value, represented by a single digit decimal, SHALL
// equal the value of seq_profile in the Sequence Header.
const int profile_idc = fields[1].size() == 1 ? values[1] : -1;
switch (profile_idc) {
case 0:
result.profile = AV1PROFILE_PROFILE_MAIN;
break;
case 1:
result.subsampling = VideoChromaSampling::k444;
result.profile = AV1PROFILE_PROFILE_HIGH;
break;
case 2:
result.profile = AV1PROFILE_PROFILE_PRO;
break;
default:
DVLOG(3) << __func__ << " Invalid profile (" << fields[1] << ")";
return std::nullopt;
}
// The level parameter value SHALL equal the first level value indicated by
// seq_level_idx in the Sequence Header. Note: We validate that this field has
// the required leading zeros above.
result.level = values[2];
if (result.level > 31) {
DVLOG(3) << __func__ << " Invalid level (" << result.level << ")";
return std::nullopt;
}
// The bitDepth parameter value SHALL equal the value of BitDepth variable as
// defined in [AV1] derived from the Sequence Header. Leading zeros required.
result.bit_depth = values[3];
if (fields[3].size() != 2 ||
(result.bit_depth != 8 && result.bit_depth != 10 &&
result.bit_depth != 12)) {
DVLOG(3) << __func__ << " Invalid bit-depth (" << fields[3] << ")";
return std::nullopt;
}
if (values.size() <= 4) {
return result;
}
// The monochrome parameter value, represented by a single digit decimal,
// SHALL equal the value of mono_chrome in the Sequence Header.
const int monochrome = values[4];
if (fields[4].size() != 1 || monochrome > 1) {
DVLOG(3) << __func__ << " Invalid monochrome (" << fields[4] << ")";
return std::nullopt;
}
if (monochrome == 1 && result.profile == AV1PROFILE_PROFILE_HIGH) {
DVLOG(3) << "Monochrome isn't supported in high profile.";
return std::nullopt;
}
if (values.size() <= 5) {
if (monochrome == 1) {
result.subsampling = VideoChromaSampling::k400;
}
return result;
}
// The chromaSubsampling parameter value, represented by a three-digit
// decimal, SHALL have its first digit equal to subsampling_x and its second
// digit equal to subsampling_y. If both subsampling_x and subsampling_y are
// set to 1, then the third digit SHALL be equal to chroma_sample_position,
// otherwise it SHALL be set to 0.
if (fields[5].size() != 3) {
DVLOG(3) << __func__ << " Invalid chroma subsampling (" << fields[5] << ")";
return std::nullopt;
}
const char subsampling_x = fields[5][0];
const char subsampling_y = fields[5][1];
const char chroma_sample_position = fields[5][2];
if ((subsampling_x < '0' || subsampling_x > '1') ||
(subsampling_y < '0' || subsampling_y > '1') ||
(chroma_sample_position < '0' || chroma_sample_position > '3')) {
DVLOG(3) << __func__ << " Invalid chroma subsampling (" << fields[5] << ")";
return std::nullopt;
}
if (((subsampling_x == '0' || subsampling_y == '0') &&
chroma_sample_position != '0')) {
DVLOG(3) << __func__ << " Invalid chroma subsampling (" << fields[5] << ")";
return std::nullopt;
}
if (subsampling_x == '0' && subsampling_y == '0' && monochrome == 0) {
if (result.profile == AV1PROFILE_PROFILE_MAIN) {
DVLOG(3) << __func__ << "4:4:4 isn't supported in main profile.";
} else {
result.subsampling = VideoChromaSampling::k444;
}
} else if (subsampling_x == '1' && subsampling_y == '0' && monochrome == 0) {
if (result.profile != AV1PROFILE_PROFILE_PRO) {
DVLOG(3) << __func__ << "4:2:2 is only supported in pro profile.";
} else {
result.subsampling = VideoChromaSampling::k422;
}
} else if (subsampling_x == '1' && subsampling_y == '1' && monochrome == 0) {
result.subsampling = VideoChromaSampling::k420;
} else if (subsampling_x == '1' && subsampling_y == '1' && monochrome == 1) {
result.subsampling = VideoChromaSampling::k400;
}
if (values.size() <= 6) {
return result;
}
// The colorPrimaries, transferCharacteristics, matrixCoefficients and
// videoFullRangeFlag parameter values SHALL equal the value of matching
// fields in the Sequence Header, if color_description_present_flag is set to
// 1, otherwise they SHOULD not be set, defaulting to the values below. The
// videoFullRangeFlag is represented by a single digit.
result.color_space.primaries = VideoColorSpace::GetPrimaryID(values[6]);
if (fields[6].size() != 2 ||
result.color_space.primaries == VideoColorSpace::PrimaryID::INVALID) {
DVLOG(3) << __func__ << " Invalid color primaries (" << fields[6] << ")";
return std::nullopt;
}
if (values.size() <= 7) {
return result;
}
result.color_space.transfer = VideoColorSpace::GetTransferID(values[7]);
if (fields[7].size() != 2 ||
result.color_space.transfer == VideoColorSpace::TransferID::INVALID) {
DVLOG(3) << __func__ << " Invalid transfer function (" << fields[7] << ")";
return std::nullopt;
}
if (values.size() <= 8) {
return result;
}
result.color_space.matrix = VideoColorSpace::GetMatrixID(values[8]);
if (fields[8].size() != 2 ||
result.color_space.matrix == VideoColorSpace::MatrixID::INVALID) {
// TODO(dalecurtis): AV1 allows a few matrices we don't support yet.
// https://crbug.com/854290
if (values[8] == 12 || values[8] == 13 || values[8] == 14) {
DVLOG(3) << __func__ << " Unsupported matrix coefficients (" << fields[8]
<< ")";
} else {
DVLOG(3) << __func__ << " Invalid matrix coefficients (" << fields[8]
<< ")";
}
return std::nullopt;
}
if (values.size() <= 9) {
return result;
}
const int video_full_range_flag = values[9];
if (fields[9].size() != 1 || video_full_range_flag > 1) {
DVLOG(3) << __func__ << " Invalid full range flag (" << fields[9] << ")";
return std::nullopt;
}
result.color_space.range = video_full_range_flag == 1
? gfx::ColorSpace::RangeID::FULL
: gfx::ColorSpace::RangeID::LIMITED;
return result;
}
std::optional<VideoType> ParseAVCCodecId(std::string_view codec_id) {
// Make sure we have avc1.xxxxxx or avc3.xxxxxx , where xxxxxx are hex digits
if (!base::StartsWith(codec_id, "avc1.", base::CompareCase::SENSITIVE) &&
!base::StartsWith(codec_id, "avc3.", base::CompareCase::SENSITIVE)) {
return std::nullopt;
}
uint32_t elem = 0;
if (codec_id.size() != 11 ||
!base::HexStringToUInt(codec_id.substr(5), &elem)) {
DVLOG(4) << __func__ << ": invalid avc codec id (" << codec_id << ")";
return std::nullopt;
}
uint8_t level_byte = elem & 0xFF;
uint8_t constraints_byte = (elem >> 8) & 0xFF;
uint8_t profile_idc = (elem >> 16) & 0xFF;
// Check that the lower two bits of |constraints_byte| are zero (those are
// reserved and must be zero according to ISO IEC 14496-10).
if (constraints_byte & 3) {
DVLOG(4) << __func__ << ": non-zero reserved bits in codec id " << codec_id;
return std::nullopt;
}
VideoCodecProfile out_profile = VIDEO_CODEC_PROFILE_UNKNOWN;
// profile_idc values for each profile are taken from ISO IEC 14496-10 and
// https://en.wikipedia.org/wiki/H.264/MPEG-4_AVC#Profiles
switch (profile_idc) {
case 66:
out_profile = H264PROFILE_BASELINE;
break;
case 77:
out_profile = H264PROFILE_MAIN;
break;
case 83:
out_profile = H264PROFILE_SCALABLEBASELINE;
break;
case 86:
out_profile = H264PROFILE_SCALABLEHIGH;
break;
case 88:
out_profile = H264PROFILE_EXTENDED;
break;
case 100:
out_profile = H264PROFILE_HIGH;
break;
case 110:
out_profile = H264PROFILE_HIGH10PROFILE;
break;
case 118:
out_profile = H264PROFILE_MULTIVIEWHIGH;
break;
case 122:
out_profile = H264PROFILE_HIGH422PROFILE;
break;
case 128:
out_profile = H264PROFILE_STEREOHIGH;
break;
case 244:
out_profile = H264PROFILE_HIGH444PREDICTIVEPROFILE;
break;
default:
DVLOG(1) << "Warning: unrecognized AVC/H.264 profile " << profile_idc;
return std::nullopt;
}
// TODO(servolk): Take into account also constraint set flags 3 through 5.
uint8_t constraint_set0_flag = (constraints_byte >> 7) & 1;
uint8_t constraint_set1_flag = (constraints_byte >> 6) & 1;
uint8_t constraint_set2_flag = (constraints_byte >> 5) & 1;
if (constraint_set2_flag && out_profile > H264PROFILE_EXTENDED) {
out_profile = H264PROFILE_EXTENDED;
}
if (constraint_set1_flag && out_profile > H264PROFILE_MAIN) {
out_profile = H264PROFILE_MAIN;
}
if (constraint_set0_flag && out_profile > H264PROFILE_BASELINE) {
out_profile = H264PROFILE_BASELINE;
}
VideoType result = {
.codec = VideoCodec::kH264,
.profile = out_profile,
.level = level_byte,
};
return result;
}
// The specification for HEVC codec id strings can be found in ISO IEC 14496-15
// dated 2012 or newer in the Annex E.3
std::optional<VideoType> ParseHEVCCodecId(std::string_view codec_id) {
if (!base::StartsWith(codec_id, "hev1.", base::CompareCase::SENSITIVE) &&
!base::StartsWith(codec_id, "hvc1.", base::CompareCase::SENSITIVE)) {
return std::nullopt;
}
// HEVC codec id consists of:
const int kMaxHevcCodecIdLength =
5 + // 'hev1.' or 'hvc1.' prefix (5 chars)
4 + // profile, e.g. '.A12' (max 4 chars)
9 + // profile_compatibility, dot + 32-bit hex number (max 9 chars)
5 + // tier and level, e.g. '.H120' (max 5 chars)
18; // up to 6 constraint bytes, bytes are dot-separated and hex-encoded.
if (codec_id.size() > kMaxHevcCodecIdLength) {
DVLOG(4) << __func__ << ": Codec id is too long (" << codec_id << ")";
return std::nullopt;
}
std::vector<std::string> elem = base::SplitString(
codec_id, ".", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
DCHECK(elem[0] == "hev1" || elem[0] == "hvc1");
if (elem.size() < 4) {
DVLOG(4) << __func__ << ": invalid HEVC codec id " << codec_id;
return std::nullopt;
}
uint8_t general_profile_space = 0;
if (elem[1].size() > 0 &&
(elem[1][0] == 'A' || elem[1][0] == 'B' || elem[1][0] == 'C')) {
general_profile_space = 1 + (elem[1][0] - 'A');
elem[1].erase(0, 1);
}
DCHECK(general_profile_space >= 0 && general_profile_space <= 3);
unsigned general_profile_idc = 0;
if (!base::StringToUint(elem[1], &general_profile_idc) ||
general_profile_idc > 0x1f) {
DVLOG(4) << __func__ << ": invalid general_profile_idc=" << elem[1];
return std::nullopt;
}
uint32_t general_profile_compatibility_flags = 0;
if (!base::HexStringToUInt(elem[2], &general_profile_compatibility_flags)) {
DVLOG(4) << __func__
<< ": invalid general_profile_compatibility_flags=" << elem[2];
return std::nullopt;
}
VideoCodecProfile out_profile = VIDEO_CODEC_PROFILE_UNKNOWN;
// Spec A.3.8
if (general_profile_idc == 11 ||
(general_profile_compatibility_flags & 2048)) {
out_profile = HEVCPROFILE_HIGH_THROUGHPUT_SCREEN_EXTENDED;
}
// Spec H.11.1.2
if (general_profile_idc == 10 ||
(general_profile_compatibility_flags & 1024)) {
out_profile = HEVCPROFILE_SCALABLE_REXT;
}
// Spec A.3.7
if (general_profile_idc == 9 || (general_profile_compatibility_flags & 512)) {
out_profile = HEVCPROFILE_SCREEN_EXTENDED;
}
// Spec I.11.1.1
if (general_profile_idc == 8 || (general_profile_compatibility_flags & 256)) {
out_profile = HEVCPROFILE_3D_MAIN;
}
// Spec H.11.1.1
if (general_profile_idc == 7 || (general_profile_compatibility_flags & 128)) {
out_profile = HEVCPROFILE_SCALABLE_MAIN;
}
// Spec G.11.1.1
if (general_profile_idc == 6 || (general_profile_compatibility_flags & 64)) {
out_profile = HEVCPROFILE_MULTIVIEW_MAIN;
}
// Spec A.3.6
if (general_profile_idc == 5 || (general_profile_compatibility_flags & 32)) {
out_profile = HEVCPROFILE_HIGH_THROUGHPUT;
}
// Spec A.3.5
if (general_profile_idc == 4 || (general_profile_compatibility_flags & 16)) {
out_profile = HEVCPROFILE_REXT;
}
// Spec A.3.3
// NOTICE: Do not change the order of below sections
if (general_profile_idc == 2 || (general_profile_compatibility_flags & 4)) {
out_profile = HEVCPROFILE_MAIN10;
}
// Spec A.3.2
// When general_profile_compatibility_flag[1] is equal to 1,
// general_profile_compatibility_flag[2] should be equal to 1 as well.
if (general_profile_idc == 1 || (general_profile_compatibility_flags & 2)) {
out_profile = HEVCPROFILE_MAIN;
}
// Spec A.3.4
// When general_profile_compatibility_flag[3] is equal to 1,
// general_profile_compatibility_flag[1] and
// general_profile_compatibility_flag[2] should be equal to 1 as well.
if (general_profile_idc == 3 || (general_profile_compatibility_flags & 8)) {
out_profile = HEVCPROFILE_MAIN_STILL_PICTURE;
}
if (out_profile == VIDEO_CODEC_PROFILE_UNKNOWN) {
DVLOG(1) << "Warning: unrecognized HEVC/H.265 general_profile_idc: "
<< general_profile_idc << ", general_profile_compatibility_flags: "
<< general_profile_compatibility_flags;
return std::nullopt;
}
uint8_t general_tier_flag;
if (elem[3].size() > 0 && (elem[3][0] == 'L' || elem[3][0] == 'H')) {
general_tier_flag = (elem[3][0] == 'L') ? 0 : 1;
elem[3].erase(0, 1);
} else {
DVLOG(4) << __func__ << ": invalid general_tier_flag=" << elem[3];
return std::nullopt;
}
DCHECK(general_tier_flag == 0 || general_tier_flag == 1);
unsigned general_level_idc = 0;
if (!base::StringToUint(elem[3], &general_level_idc) ||
general_level_idc > 0xff) {
DVLOG(4) << __func__ << ": invalid general_level_idc=" << elem[3];
return std::nullopt;
}
std::array<uint8_t, 6> constraint_flags;
memset(constraint_flags.data(), 0,
(constraint_flags.size() *
sizeof(decltype(constraint_flags)::value_type)));
if (elem.size() > 10) {
DVLOG(4) << __func__ << ": unexpected number of trailing bytes in HEVC "
<< "codec id " << codec_id;
return std::nullopt;
}
// ISO/IEC 14496-15, section E.3:
// Each of the 6 bytes of the constraint flags from SPS profile-tier-level
// syntax, starting from the byte containing the
// general_progressive_source_flag, each encoded as a hex number, and the
// encoding of each byte is separated by a period; trailing bytes that are
// zero may be omitted.
for (size_t i = 4; i < elem.size(); ++i) {
unsigned constr_byte = 0;
if (!base::HexStringToUInt(elem[i], &constr_byte) || constr_byte > 0xFF) {
DVLOG(4) << __func__ << ": invalid constraint byte=" << elem[i];
return std::nullopt;
}
constraint_flags[i - 4] = constr_byte;
}
std::optional<uint8_t> bit_depth;
std::optional<VideoChromaSampling> subsampling;
// HEVC spec 7.3.3:
// For range extension, low 4-bits of constraint_flags[0] contain the max
// bit constraint flags for 12b/10b/8b, followed by yuv422 chroma sampling
// constraint flag; constraint_flags[1] contains yuv420 chroma sampling, and
// monochrome constraint flags, which we use to determine the bit depth and
// chroma subsampling formats.
if (base::FeatureList::IsEnabled(kHEVCRextCodecStringParsing) &&
out_profile == HEVCPROFILE_REXT) {
uint8_t max_bits_flag = (constraint_flags[0] >> 1) & 0b111;
bool max_422_flag = constraint_flags[0] & 0b1;
bool max_420_flag = constraint_flags[1] >> 7;
bool max_monochrome_flag = (constraint_flags[1] >> 6) & 0b1;
// Spec Table A.2
switch (max_bits_flag) {
case 0b000:
bit_depth = 16;
break;
case 0b100:
bit_depth = 12;
break;
case 0b110:
bit_depth = 10;
break;
case 0b111:
bit_depth = 8;
break;
default:
DVLOG(4) << __func__ << "Invalid max_bits_flag=" << max_bits_flag;
return std::nullopt;
}
if (max_monochrome_flag && (!max_422_flag || !max_420_flag)) {
DVLOG(4) << __func__ << "When max_monochrome_flag is set, both "
<< "max_422_flag and max_420_flag should be set.";
return std::nullopt;
}
if (max_420_flag && !max_422_flag) {
DVLOG(4) << __func__ << "When max_420_flag is set, max_422_flag must "
<< "be set.";
return std::nullopt;
}
if (max_monochrome_flag) {
subsampling = VideoChromaSampling::k400;
} else if (max_420_flag) {
subsampling = VideoChromaSampling::k420;
} else if (max_422_flag) {
subsampling = VideoChromaSampling::k422;
} else {
subsampling = VideoChromaSampling::k444;
}
}
// We only configure bit depth and chroma subsampling for range extension, as
// it is only for range extension that we further check platform support on
// specific bit depth and chroma subsampling formats for decode/encode; and we
// don't support general_profile_idc > 4 with hardware encoders/decoders.
// TODO(crbug.com/413659852) HEVC has color space information that should be
// parsed. It's specified in the ISO 14496-15:2024 spec.
VideoType result = {
.codec = VideoCodec::kHEVC,
.profile = out_profile,
.level = general_level_idc,
.color_space = VideoColorSpace(VideoColorSpace::PrimaryID::UNSPECIFIED,
VideoColorSpace::TransferID::UNSPECIFIED,
VideoColorSpace::MatrixID::UNSPECIFIED,
gfx::ColorSpace::RangeID::DERIVED),
.subsampling = subsampling,
.bit_depth = bit_depth,
};
return result;
}
// The specification for VVC codec id strings can be found in ISO/IEC 14496-15
// 2022, annex E.6.
// In detail it would be:
// <sample entry FourCC> ("vvi1: if config is inband, or "vvc1" otherwise.)
// .<general_profile_idc> (base10)
// .<general_tier_flag> ("L" or "H")
// <op_level_idc> (base10. <= general_level_idc in SPS)
// .C<ptl_frame_only_constraint_flag><ptl_multi_layer_enabled_flag> (optional)
// <general_constraint_info) (base32 with "=" might be omitted.)
// .S<general_sub_profile_idc1> (Optional, base32 with "=" might be omitted.)
// <+general_sub_profile_
// .O<ols_idx>+<max_tid> (Optional, base10 OlsIdx & MaxTid)
std::optional<VideoType> ParseVVCCodecId(std::string_view codec_id) {
if (!base::StartsWith(codec_id, "vvc1.", base::CompareCase::SENSITIVE) &&
!base::StartsWith(codec_id, "vvi1.", base::CompareCase::SENSITIVE)) {
return std::nullopt;
}
std::vector<std::string> elem = base::SplitString(
codec_id, ".", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
DCHECK(elem[0] == "vvc1" || elem[0] == "vvi1");
if (elem.size() < 3 || elem.size() > 6) {
DVLOG(4) << __func__ << ": invalid VVC codec id " << codec_id;
return std::nullopt;
}
for (auto& item : elem) {
if (item.size() < 1 ||
((item[0] == 'C' || item[0] == 'S' || item[0] == 'O') &&
item.size() < 2)) {
DVLOG(4) << __func__ << ": subelement of VVC codec id invalid.";
return std::nullopt;
}
if (item[0] == 'O' && item.back() == '+') {
DVLOG(4) << __func__ << ": invalid OlxIdx and MaxTid string.";
return std::nullopt;
}
}
unsigned general_profile_idc = 0;
if (!base::StringToUint(elem[1], &general_profile_idc) ||
general_profile_idc > 0x63) {
DVLOG(4) << __func__ << ": invalid general_profile_idc=" << elem[1];
return std::nullopt;
}
VideoCodecProfile out_profile = VIDEO_CODEC_PROFILE_UNKNOWN;
switch (general_profile_idc) {
case 99: // Spec A.3.5
out_profile = VVCPROFILE_MAIN16_444_STILL_PICTURE;
break;
case 98: // Spec A.3.5
out_profile = VVCPROFILE_MAIN12_444_STILL_PICTURE;
break;
case 97: // Spec A.3.2
out_profile = VVCPROFILE_MAIN10_444_STILL_PICTURE;
break;
case 66: // Spec A.3.5
out_profile = VVCPROFILE_MAIN12_STILL_PICTURE;
break;
case 65: // Spec A.3.1
out_profile = VVCPROFILE_MAIN10_STILL_PICTURE;
break;
case 49: // Spec A.3.4
out_profile = VVCPROFILE_MULTILAYER_MAIN10_444;
break;
case 43: // Spec A.3.5
out_profile = VVCPROFILE_MAIN16_444_INTRA;
break;
case 42: // Spec A.3.5
out_profile = VVCPROFILE_MAIN12_444_INTRA;
break;
case 35: // Spec A.3.5
out_profile = VVCPROFILE_MAIN16_444;
break;
case 34: // Spec A.3.5
out_profile = VVCPROFILE_MAIN12_444;
break;
case 33: // Spec A.3.2
out_profile = VVCPROFILE_MAIN10_444;
break;
case 17: // Spec A.3.3
out_profile = VVCPROIFLE_MULTILAYER_MAIN10;
break;
case 10: // Spec A.3.5
out_profile = VVCPROFILE_MAIN12_INTRA;
break;
case 2: // Spec A.3.5
out_profile = VVCPROFILE_MAIN12;
break;
case 1: // Spec A.3.1
out_profile = VVCPROFILE_MAIN10;
break;
default:
break;
}
if (out_profile == VIDEO_CODEC_PROFILE_UNKNOWN) {
DVLOG(1) << "Warning: unrecognized VVC/H.266 general_profile_idc: "
<< general_profile_idc;
return std::nullopt;
}
uint8_t general_tier_flag;
if (elem[2][0] == 'L' || elem[2][0] == 'H') {
general_tier_flag = (elem[2][0] == 'L') ? 0 : 1;
elem[2].erase(0, 1);
} else {
DVLOG(4) << __func__ << ": invalid general_tier_flag=" << elem[2];
return std::nullopt;
}
DCHECK(general_tier_flag == 0 || general_tier_flag == 1);
unsigned general_level_idc = 0;
if (!base::StringToUint(elem[2], &general_level_idc) ||
general_level_idc > 0xff) {
DVLOG(4) << __func__ << ": invalid general_level_idc=" << elem[2];
return std::nullopt;
}
// C-string, if existing, should proceed S-string and O-string.
// Similarly, S-string should proceed O-string.
bool trailing_valid = true;
if (elem.size() == 4) {
if (elem[3][0] != 'C' && elem[3][0] != 'S' && elem[3][0] != 'O') {
trailing_valid = false;
}
} else if (elem.size() == 5) {
if (!((elem[3][0] == 'C' && elem[4][0] == 'S') ||
(elem[3][0] == 'C' && elem[4][0] == 'O') ||
(elem[3][0] == 'S' && elem[4][0] == 'O'))) {
trailing_valid = false;
}
} else if (elem.size() == 6) {
if (elem[3][0] != 'C' || elem[4][0] != 'S' || elem[5][0] != 'O') {
trailing_valid = false;
}
}
if (!trailing_valid) {
DVLOG(4) << __func__ << ": invalid traing codec string.";
return std::nullopt;
}
// TODO(crbug.com/40257449): Add VideoCodec::kVVC here when its ready.
VideoType result = {
.codec = VideoCodec::kUnknown,
.profile = out_profile,
.level = general_level_idc,
};
return result;
}
// The specification for Dolby Vision codec id strings can be found in Dolby
// Vision streams within the MPEG-DASH format:
// https://professional.dolby.com/siteassets/content-creation/dolby-vision-for-content-creators/dolbyvisioninmpegdashspecification_v2_0_public_20190107.pdf
std::optional<VideoType> ParseDolbyVisionCodecId(std::string_view codec_id) {
if (!IsDolbyVisionAVCCodecId(codec_id) &&
!IsDolbyVisionHEVCCodecId(codec_id)) {
return std::nullopt;
}
const int kMaxDvCodecIdLength = 5 // FOURCC string
+ 1 // delimiting period
+ 2 // profile id as 2 digit string
+ 1 // delimiting period
+ 2; // level id as 2 digit string.
if (codec_id.size() > kMaxDvCodecIdLength) {
DVLOG(4) << __func__ << ": Codec id is too long (" << codec_id << ")";
return std::nullopt;
}
std::vector<std::string> elem = base::SplitString(
codec_id, ".", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
DCHECK(elem[0] == "dvh1" || elem[0] == "dvhe" || elem[0] == "dva1" ||
elem[0] == "dvav");
if (elem.size() != 3) {
DVLOG(4) << __func__ << ": invalid dolby vision codec id " << codec_id;
return std::nullopt;
}
// Profile string should be two digits.
unsigned profile_id = 0;
if (elem[1].size() != 2 || !base::StringToUint(elem[1], &profile_id) ||
profile_id > 9) {
DVLOG(4) << __func__ << ": invalid format or profile_id=" << elem[1];
return std::nullopt;
}
VideoType result = {
.codec = VideoCodec::kDolbyVision,
};
// Only profiles 0, 4, 5, 7, 8 and 9 are valid. Profile 0 and 9 are encoded
// based on AVC while profile 4, 5, 7 and 8 are based on HEVC.
switch (profile_id) {
case 0:
case 9:
if (!IsDolbyVisionAVCCodecId(codec_id)) {
DVLOG(4) << __func__
<< ": codec id is mismatched with profile_id=" << profile_id;
return std::nullopt;
}
if (profile_id == 0) {
result.profile = DOLBYVISION_PROFILE0;
} else if (profile_id == 9) {
result.profile = DOLBYVISION_PROFILE9;
}
break;
case 5:
case 7:
case 8:
if (!IsDolbyVisionHEVCCodecId(codec_id)) {
DVLOG(4) << __func__
<< ": codec id is mismatched with profile_id=" << profile_id;
return std::nullopt;
}
if (profile_id == 5) {
result.profile = DOLBYVISION_PROFILE5;
} else if (profile_id == 7) {
result.profile = DOLBYVISION_PROFILE7;
} else if (profile_id == 8) {
result.profile = DOLBYVISION_PROFILE8;
}
break;
default:
DVLOG(4) << __func__
<< ": depecrated and not supported profile_id=" << profile_id;
return std::nullopt;
}
// Level string should be two digits.
unsigned level_id = 0;
if (elem[2].size() != 2 || !base::StringToUint(elem[2], &level_id) ||
level_id > 13 || level_id < 1) {
DVLOG(4) << __func__ << ": invalid format level_id=" << elem[2];
return std::nullopt;
}
result.level = level_id;
return result;
}
std::optional<VideoType> ParseCodec(std::string_view codec_id) {
std::vector<std::string> elem = base::SplitString(
codec_id, ".", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
if (elem.empty()) {
return std::nullopt;
}
if (codec_id == "vp8" || codec_id == "vp8.0") {
VideoType result = {
.codec = VideoCodec::kVP8,
.profile = VP8PROFILE_ANY,
.level = kNoVideoCodecLevel,
};
return result;
}
if (codec_id == "theora") {
VideoType result = {
.codec = VideoCodec::kTheora,
.profile = THEORAPROFILE_ANY,
.level = kNoVideoCodecLevel,
};
return result;
}
if (auto result = ParseNewStyleVp9CodecID(codec_id)) {
return result;
}
if (auto result = ParseLegacyVp9CodecID(codec_id)) {
return result;
}
if (auto result = ParseAv1CodecId(codec_id)) {
return result;
}
if (auto result = ParseAVCCodecId(TranslateLegacyAvc1CodecIds(codec_id))) {
return result;
}
if (auto result = ParseHEVCCodecId(codec_id)) {
return result;
}
if (auto result = ParseDolbyVisionCodecId(codec_id)) {
return result;
}
return std::nullopt;
}
VideoCodec StringToVideoCodec(std::string_view codec_id) {
auto result = ParseCodec(codec_id);
return result ? result->codec : VideoCodec::kUnknown;
}
std::string TranslateLegacyAvc1CodecIds(std::string_view codec_id) {
// Special handling for old, pre-RFC 6381 format avc1 strings, which are still
// being used by some HLS apps to preserve backward compatibility with older
// iOS devices. The old format was avc1.<profile>.<level>
// Where <profile> is H.264 profile_idc encoded as a decimal number, i.e.
// 66 is baseline profile (0x42)
// 77 is main profile (0x4d)
// 100 is high profile (0x64)
// And <level> is H.264 level multiplied by 10, also encoded as decimal number
// E.g. <level> 31 corresponds to H.264 level 3.1
// See, for example, http://qtdevseed.apple.com/qadrift/testcases/tc-0133.php
uint32_t level_start = 0;
std::string result;
if (base::StartsWith(codec_id, "avc1.66.", base::CompareCase::SENSITIVE)) {
level_start = 8;
result = "avc1.4200";
} else if (base::StartsWith(codec_id, "avc1.77.",
base::CompareCase::SENSITIVE)) {
level_start = 8;
result = "avc1.4D00";
} else if (base::StartsWith(codec_id, "avc1.100.",
base::CompareCase::SENSITIVE)) {
level_start = 9;
result = "avc1.6400";
}
uint32_t level = 0;
if (level_start > 0 &&
base::StringToUint(codec_id.substr(level_start), &level) && level < 256) {
// This is a valid legacy avc1 codec id - return the codec id translated
// into RFC 6381 format.
base::AppendHexEncodedByte(static_cast<uint8_t>(level), result);
return result;
}
// This is not a valid legacy avc1 codec id - return the original codec id.
return std::string(codec_id);
}
} // namespace media