blob: 15ab363a1552c63da128d7ae9e3616e4c25d58e7 [file] [log] [blame]
// Copyright 2017 The Cobalt Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "starboard/shared/starboard/media/codec_util.h"
#include <algorithm>
#include <cctype>
#include <string>
#include "starboard/common/log.h"
#include "starboard/common/string.h"
#include "starboard/configuration.h"
#include "starboard/configuration_constants.h"
namespace starboard {
namespace shared {
namespace starboard {
namespace media {
namespace {
int hex_to_int(char hex) {
if (hex >= '0' && hex <= '9') {
return hex - '0';
}
if (hex >= 'A' && hex <= 'F') {
return hex - 'A' + 10;
}
if (hex >= 'a' && hex <= 'f') {
return hex - 'a' + 10;
}
SB_NOTREACHED();
return 0;
}
// Read one digit hex from |str| and store into |output|. Return false if
// the character is not a digit, return true otherwise.
template <typename T>
bool ReadOneDigitHex(const char* str, T* output) {
SB_DCHECK(str);
if (str[0] >= 'A' && str[0] <= 'F') {
*output = static_cast<T>((str[0] - 'A' + 10));
return true;
}
if (str[0] >= 'a' && str[0] <= 'f') {
*output = static_cast<T>((str[0] - 'a' + 10));
return true;
}
if (!isdigit(str[0])) {
return false;
}
*output = static_cast<T>((str[0] - '0'));
return true;
}
// Read multi digit decimal from |str| until the next '.' character or end of
// string, and store into |output|. Return false if one of the character is not
// a digit, return true otherwise.
template <typename T>
bool ReadDecimalUntilDot(const char* str, T* output) {
SB_DCHECK(str);
*output = 0;
while (*str != 0 && *str != '.') {
if (!isdigit(*str)) {
return false;
}
*output = *output * 10 + (*str - '0');
++str;
}
return true;
}
// Read two digit decimal from |str| and store into |output|. Return false if
// any character is not a digit, return true otherwise.
template <typename T>
bool ReadTwoDigitDecimal(const char* str, T* output) {
SB_DCHECK(str);
if (!isdigit(str[0]) || !isdigit(str[1])) {
return false;
}
*output = static_cast<T>((str[0] - '0') * 10 + (str[1] - '0'));
return true;
}
// Verify the format against a reference using the following rules:
// 1. They must have the same size.
// 2. If reference[i] is a letter, format[i] must contain the *same* letter.
// 3. If reference[i] is a number, format[i] can contain *any* number.
// 4. If reference[i] is '.', format[i] must also be '.'.
// 5. If reference[i] is ?, format[i] can contain *any* character.
// 6. If |format| is longer than |reference|, then |format[reference_size]| can
// not be '.' or digit.
// For example, both "av01.0.05M.08" and "av01.0.05M.08****" match reference
// "av01.0.05?.08", but "vp09.0.05M.08" or "vp09.0.05M.08." don't.
// The function returns true when |format| matches |reference|.
bool VerifyFormat(const char* format, const char* reference) {
auto format_size = SbStringGetLength(format);
auto reference_size = SbStringGetLength(reference);
if (format_size < reference_size) {
return false;
}
for (size_t i = 0; i < reference_size; ++i) {
if (isdigit(reference[i])) {
if (!isdigit(format[i])) {
return false;
}
} else if (std::isalpha(reference[i])) {
if (reference[i] != format[i]) {
return false;
}
} else if (reference[i] == '.') {
if (format[i] != '.') {
return false;
}
} else if (reference[i] != '?') {
return false;
}
}
if (format_size == reference_size) {
return true;
}
return format[reference_size] != '.' && !isdigit(format[reference_size]);
}
// It works exactly the same as the above function, except that the size of
// |format| has to be exactly the same as the size of |reference|.
bool VerifyFormatStrictly(const char* format, const char* reference) {
if (SbStringGetLength(format) != SbStringGetLength(reference)) {
return false;
}
return VerifyFormat(format, reference);
}
// This function parses an av01 codec in the form of "av01.0.05M.08" or
// "av01.0.04M.10.0.110.09.16.09.0" as
// specified by https://aomediacodec.github.io/av1-isobmff/#codecsparam.
//
// Note that the spec also supports of different chroma subsamplings but the
// implementation always assume that it is 4:2:0 and returns false when it
// isn't.
bool ParseAv1Info(std::string codec,
int* profile,
int* level,
int* bit_depth,
SbMediaPrimaryId* primary_id,
SbMediaTransferId* transfer_id,
SbMediaMatrixId* matrix_id) {
// The codec can only in one of the following formats:
// Full: av01.0.04M.10.0.110.09.16.09.0
// Short: av01.0.05M.08
// When short format is used, it is assumed that the omitted parts are
// "0.110.01.01.01.0".
// All fields are fixed size and leading zero cannot be omitted, so the
// expected sizes are known.
const char kShortFormReference[] = "av01.0.05M.08";
const char kLongFormReference[] = "av01.0.04M.10.0.110.09.16.09.0";
const size_t kShortFormSize = SbStringGetLength(kShortFormReference);
const size_t kLongFormSize = SbStringGetLength(kLongFormReference);
// 1. Sanity check the format.
if (SbStringCompare(codec.c_str(), "av01.", 5) != 0) {
return false;
}
if (VerifyFormat(codec.c_str(), kLongFormReference)) {
codec.resize(kLongFormSize);
} else if (VerifyFormat(codec.c_str(), kShortFormReference)) {
codec.resize(kShortFormSize);
} else {
return false;
}
// 2. Parse profile, which can only be 0, 1, or 2.
if (codec[5] < '0' || codec[5] > '2') {
return false;
}
*profile = codec[5] - '0';
// 3. Parse level, which is two digit value from 0 to 23, maps to level 2.0,
// 2.1, 2.2, 2.3, 3.0, 3.1, 3.2, 3.3, ..., 7.0, 7.1, 7.2, 7.3.
int level_value;
if (!ReadTwoDigitDecimal(codec.c_str() + 7, &level_value)) {
return false;
}
if (level_value > 23) {
return false;
}
// Level x.y is represented by integer |xy|, for example, 23 means level 2.3.
*level = (level_value / 4 + 2) * 10 + (level_value % 4);
// 4. Parse tier, which can only be 'M' or 'H'
if (codec[9] != 'M' && codec[9] != 'H') {
return false;
}
// 5. Parse bit depth, which can be value 08, 10, or 12.
if (!ReadTwoDigitDecimal(codec.c_str() + 11, bit_depth)) {
return false;
}
if (*bit_depth != 8 && *bit_depth != 10 && *bit_depth != 12) {
return false;
}
// 6. Return now if it is a well-formed short form codec string.
*primary_id = kSbMediaPrimaryIdBt709;
*transfer_id = kSbMediaTransferIdBt709;
*matrix_id = kSbMediaMatrixIdBt709;
if (codec.size() == kShortFormSize) {
return true;
}
// 7. Parse monochrome, which can only be 0 or 1.
// Note that this value is not returned.
if (codec[14] != '0' && codec[14] != '1') {
return false;
}
// 8. Parse chroma subsampling, which we only support 110.
// Note that this value is not returned.
if (SbStringCompare(codec.c_str() + 16, "110", 3) != 0) {
return false;
}
// 9. Parse color primaries, which can be 1 to 12, and 22 (EBU Tech. 3213-E).
// Note that 22 is not currently supported by Cobalt.
if (!ReadTwoDigitDecimal(codec.c_str() + 20, primary_id)) {
return false;
}
SB_LOG_IF(WARNING, *primary_id == 22)
<< codec << " uses primary id 22 (EBU Tech. 3213-E)."
<< " It is rejected because Cobalt doesn't support primary id 22.";
if (*primary_id < 1 || *primary_id > 12) {
return false;
}
// 10. Parse transfer characteristics, which can be 0 to 18.
if (!ReadTwoDigitDecimal(codec.c_str() + 23, transfer_id)) {
return false;
}
if (*transfer_id > 18) {
return false;
}
// 11. Parse matrix coefficients, which can be 0 to 14.
// Note that 12, 13, and 14 are not currently supported by Cobalt.
if (!ReadTwoDigitDecimal(codec.c_str() + 26, matrix_id)) {
return false;
}
if (*matrix_id > 11) {
return false;
}
// 12. Parse color range, which can only be 0 or 1.
if (codec[29] != '0' && codec[29] != '1') {
return false;
}
// 13. Return
return true;
}
// This function parses an h264 codec in the form of {avc1|avc3}.PPCCLL as
// specified by https://tools.ietf.org/html/rfc6381#section-3.3.
//
// Note that the leading codec is not necessarily to be "avc1" or "avc3" per
// spec but this function only parses "avc1" and "avc3". This function returns
// false when |codec| doesn't contain a valid codec string.
bool ParseH264Info(const char* codec, int* profile, int* level) {
if (SbStringCompare(codec, "avc1.", 5) != 0 &&
SbStringCompare(codec, "avc3.", 5) != 0) {
return false;
}
if (SbStringGetLength(codec) != 11 || !isxdigit(codec[9]) ||
!isxdigit(codec[10])) {
return false;
}
*profile = hex_to_int(codec[5]) * 16 + hex_to_int(codec[6]);
*level = hex_to_int(codec[9]) * 16 + hex_to_int(codec[10]);
return true;
}
// This function parses an h265 codec as specified by ISO IEC 14496-15 dated
// 2012 or newer in the Annex E.3. The codec will be in the form of:
// hvc1.PPP.PCPCPCPC.TLLL.CB.CB.CB.CB.CB.CB, where
// PPP: 0 or 1 byte general_profile_space ('', 'A', 'B', or 'C') +
// up to two bytes profile idc.
// PCPCPCPC: Profile compatibility, up to 32 bits hex, with leading 0 omitted.
// TLLL: One byte tier ('L' or 'H') + up to three bytes level.
// CB: Up to 6 constraint bytes, separated by '.'.
// Note that the above level in decimal = 30 * real level, i.e. 93 means level
// 3.1, 120 mean level 4.
// Please see the comment in the code for interactions between the various
// parts.
bool ParseH265Info(const char* codec, int* profile, int* level) {
if (SbStringCompare(codec, "hev1.", 5) != 0 &&
SbStringCompare(codec, "hvc1.", 5) != 0) {
return false;
}
codec += 5;
// Read profile space
if (codec[0] == 'A' || codec[0] == 'B' || codec[0] == 'C') {
++codec;
}
if (SbStringGetLength(codec) < 3) {
return false;
}
// Read profile
int general_profile_idc;
if (codec[1] == '.') {
if (!ReadDecimalUntilDot(codec, &general_profile_idc)) {
return false;
}
codec += 2;
} else if (codec[2] == '.') {
if (!ReadDecimalUntilDot(codec, &general_profile_idc)) {
return false;
}
codec += 3;
} else {
return false;
}
// Read profile compatibility, up to 32 bits hex.
const char* dot = SbStringFindCharacter(codec, '.');
if (dot == NULL || dot - codec == 0 || dot - codec > 8) {
return false;
}
uint32_t general_profile_compatibility_flags = 0;
for (int i = 0; i < 9; ++i) {
if (codec[0] == '.') {
++codec;
break;
}
uint32_t hex = 0;
if (!ReadOneDigitHex(codec, &hex)) {
return false;
}
general_profile_compatibility_flags *= 16;
general_profile_compatibility_flags += hex;
++codec;
}
*profile = -1;
if (general_profile_idc == 3 || (general_profile_compatibility_flags & 4)) {
*profile = 3;
}
if (general_profile_idc == 2 || (general_profile_compatibility_flags & 2)) {
*profile = 2;
}
if (general_profile_idc == 1 || (general_profile_compatibility_flags & 1)) {
*profile = 1;
}
if (*profile == -1) {
return false;
}
// Parse tier
if (codec[0] != 'L' && codec[0] != 'H') {
return false;
}
++codec;
// Parse level in 2 or 3 digits decimal.
if (SbStringGetLength(codec) < 2) {
return false;
}
if (!ReadDecimalUntilDot(codec, level)) {
return false;
}
if (*level % 3 != 0) {
return false;
}
*level /= 3;
if (codec[2] == 0 || codec[2] == '.') {
codec += 2;
} else if (codec[3] == 0 || codec[3] == '.') {
codec += 3;
} else {
return false;
}
// Parse up to 6 constraint flags in the form of ".HH".
for (int i = 0; i < 6; ++i) {
if (codec[0] == 0) {
return true;
}
if (codec[0] != '.' || !isxdigit(codec[1]) || !isxdigit(codec[2])) {
return false;
}
codec += 3;
}
return *codec == 0;
}
// This function parses an vp09 codec in the form of "vp09.00.41.08" or
// "vp09.02.10.10.01.09.16.09.01" as specified by
// https://www.webmproject.org/vp9/mp4/. YouTube also uses the long form
// without the last part (color range), so we also support it.
//
// Note that the spec also supports of different chroma subsamplings but the
// implementation always assume that it is 4:2:0 and returns false when it
// isn't.
bool ParseVp09Info(const char* codec,
int* profile,
int* level,
int* bit_depth,
SbMediaPrimaryId* primary_id,
SbMediaTransferId* transfer_id,
SbMediaMatrixId* matrix_id) {
// The codec can only in one of the following formats:
// Full: vp09.02.10.10.01.09.16.09.01
// Short: vp09.00.41.08
// Note that currently the player also uses the following form:
// Medium: vp09.02.10.10.01.09.16.09
// When short format is used, it is assumed that the omitted parts are
// "01.01.01.01.00". When medium format is used, the omitted part is "00".
// All fields are fixed size and leading zero cannot be omitted, so the
// expected sizes are known.
const char kShortFormReference[] = "vp09.00.41.08";
const char kMediumFormReference[] = "vp09.02.10.10.01.09.16.09";
const char kLongFormReference[] = "vp09.02.10.10.01.09.16.09.01";
const size_t kShortFormSize = SbStringGetLength(kShortFormReference);
const size_t kMediumFormSize = SbStringGetLength(kMediumFormReference);
const size_t kLongFormSize = SbStringGetLength(kLongFormReference);
// 1. Sanity check the format.
if (SbStringCompare(codec, "vp09.", 5) != 0) {
return false;
}
if (!VerifyFormatStrictly(codec, kLongFormReference) &&
!VerifyFormatStrictly(codec, kMediumFormReference) &&
!VerifyFormatStrictly(codec, kShortFormReference)) {
return false;
}
// 2. Parse profile, which can only be 00, 01, 02, or 03.
if (!ReadTwoDigitDecimal(codec + 5, profile)) {
return false;
}
if (*profile < 0 || *profile > 3) {
return false;
}
// 3. Parse level, which is two digit value in the following list:
const int kLevels[] = {10, 11, 20, 21, 30, 31, 40,
41, 50, 51, 52, 60, 61, 62};
if (!ReadTwoDigitDecimal(codec + 8, level)) {
return false;
}
auto end = kLevels + SB_ARRAY_SIZE(kLevels);
if (std::find(kLevels, end, *level) == end) {
return false;
}
// 4. Parse bit depth, which can be value 08, 10, or 12.
if (!ReadTwoDigitDecimal(codec + 11, bit_depth)) {
return false;
}
if (*bit_depth != 8 && *bit_depth != 10 && *bit_depth != 12) {
return false;
}
// 5. Return now if it is a well-formed short form codec string.
*primary_id = kSbMediaPrimaryIdBt709;
*transfer_id = kSbMediaTransferIdBt709;
*matrix_id = kSbMediaMatrixIdBt709;
if (SbStringGetLength(codec) == kShortFormSize) {
return true;
}
// 6. Parse chroma subsampling, which we only support 00 and 01.
// Note that this value is not returned.
int chroma;
if (!ReadTwoDigitDecimal(codec + 14, &chroma) ||
(chroma != 0 && chroma != 1)) {
return false;
}
// 7. Parse color primaries, which can be 1 to 12, and 22 (EBU Tech. 3213-E).
// Note that 22 is not currently supported by Cobalt.
if (!ReadTwoDigitDecimal(codec + 17, primary_id)) {
return false;
}
if (*primary_id < 1 || *primary_id > 12) {
return false;
}
// 8. Parse transfer characteristics, which can be 0 to 18.
if (!ReadTwoDigitDecimal(codec + 20, transfer_id)) {
return false;
}
if (*transfer_id > 18) {
return false;
}
// 9. Parse matrix coefficients, which can be 0 to 14.
// Note that 12, 13, and 14 are not currently supported by Cobalt.
if (!ReadTwoDigitDecimal(codec + 23, matrix_id)) {
return false;
}
if (*matrix_id > 11) {
return false;
}
// 10. Return now if it is a well-formed medium form codec string.
if (SbStringGetLength(codec) == kMediumFormSize) {
return true;
}
// 11. Parse color range, which can only be 0 or 1.
int color_range;
if (!ReadTwoDigitDecimal(codec + 26, &color_range)) {
return false;
}
if (color_range != 0 && color_range != 1) {
return false;
}
// 12. Return
return true;
}
// This function parses an vp9 codec in the form of "vp9", "vp9.0", "vp9.1", or
// "vp9.2".
bool ParseVp9Info(const char* codec, int* profile) {
SB_DCHECK(profile);
if (SbStringCompareAll(codec, "vp9") == 0) {
*profile = -1;
return true;
}
if (SbStringCompareAll(codec, "vp9.0") == 0) {
*profile = 0;
return true;
}
if (SbStringCompareAll(codec, "vp9.1") == 0) {
*profile = 1;
return true;
}
if (SbStringCompareAll(codec, "vp9.2") == 0) {
*profile = 2;
return true;
}
return false;
}
} // namespace
VideoConfig::VideoConfig(SbMediaVideoCodec video_codec,
int width,
int height,
const uint8_t* data,
size_t size)
: width_(width), height_(height) {
if (video_codec == kSbMediaVideoCodecVp9) {
video_codec_ = video_codec;
}
#if SB_API_VERSION >= 11
else if (video_codec == kSbMediaVideoCodecAv1) {
video_codec_ = video_codec;
}
#endif // SB_API_VERSION >= 11
else if (video_codec == kSbMediaVideoCodecH264) {
avc_parameter_sets_ =
AvcParameterSets(AvcParameterSets::kAnnexB, data, size);
if (avc_parameter_sets_->is_valid()) {
video_codec_ = video_codec;
}
} else {
SB_NOTREACHED();
}
}
#if SB_API_VERSION >= 11
VideoConfig::VideoConfig(const SbMediaVideoSampleInfo& video_sample_info,
const uint8_t* data,
size_t size)
: VideoConfig(video_sample_info.codec,
video_sample_info.frame_width,
video_sample_info.frame_height,
data,
size) {
SB_DCHECK(video_sample_info.is_key_frame);
}
#endif // SB_API_VERSION >= 11
bool VideoConfig::operator==(const VideoConfig& that) const {
if (video_codec_ == kSbMediaVideoCodecNone &&
that.video_codec_ == kSbMediaVideoCodecNone) {
return true;
}
return video_codec_ == that.video_codec_ &&
avc_parameter_sets_ == that.avc_parameter_sets_ &&
width_ == that.width_ && height_ == that.height_;
}
bool VideoConfig::operator!=(const VideoConfig& that) const {
return !(*this == that);
}
SbMediaAudioCodec GetAudioCodecFromString(const char* codec) {
if (SbStringCompare(codec, "mp4a.40.", 8) == 0) {
return kSbMediaAudioCodecAac;
}
#if SB_API_VERSION >= 12 || defined(SB_HAS_AC3_AUDIO)
if (kSbHasAc3Audio) {
if (SbStringCompareAll(codec, "ac-3") == 0) {
return kSbMediaAudioCodecAc3;
}
if (SbStringCompareAll(codec, "ec-3") == 0) {
return kSbMediaAudioCodecEac3;
}
}
#endif // SB_API_VERSION >= 12 ||
// defined(SB_HAS_AC3_AUDIO)
if (SbStringCompare(codec, "opus", 4) == 0) {
return kSbMediaAudioCodecOpus;
}
if (SbStringCompare(codec, "vorbis", 6) == 0) {
return kSbMediaAudioCodecVorbis;
}
return kSbMediaAudioCodecNone;
}
bool ParseVideoCodec(const char* codec_string,
SbMediaVideoCodec* codec,
int* profile,
int* level,
int* bit_depth,
SbMediaPrimaryId* primary_id,
SbMediaTransferId* transfer_id,
SbMediaMatrixId* matrix_id) {
SB_DCHECK(codec_string);
SB_DCHECK(codec);
SB_DCHECK(profile);
SB_DCHECK(level);
SB_DCHECK(bit_depth);
SB_DCHECK(primary_id);
SB_DCHECK(transfer_id);
SB_DCHECK(matrix_id);
*codec = kSbMediaVideoCodecNone;
*profile = -1;
*level = -1;
*bit_depth = 8;
*primary_id = kSbMediaPrimaryIdUnspecified;
*transfer_id = kSbMediaTransferIdUnspecified;
*matrix_id = kSbMediaMatrixIdUnspecified;
if (SbStringCompare(codec_string, "av01.", 5) == 0) {
#if SB_API_VERSION < 11
*codec = kSbMediaVideoCodecVp10;
#else // SB_API_VERSION < 11
*codec = kSbMediaVideoCodecAv1;
#endif // SB_API_VERSION < 11
return ParseAv1Info(codec_string, profile, level, bit_depth, primary_id,
transfer_id, matrix_id);
}
if (SbStringCompare(codec_string, "avc1.", 5) == 0 ||
SbStringCompare(codec_string, "avc3.", 5) == 0) {
*codec = kSbMediaVideoCodecH264;
return ParseH264Info(codec_string, profile, level);
}
if (SbStringCompare(codec_string, "hev1.", 5) == 0 ||
SbStringCompare(codec_string, "hvc1.", 5) == 0) {
*codec = kSbMediaVideoCodecH265;
return ParseH265Info(codec_string, profile, level);
}
if (SbStringCompare(codec_string, "vp09.", 5) == 0) {
*codec = kSbMediaVideoCodecVp9;
return ParseVp09Info(codec_string, profile, level, bit_depth, primary_id,
transfer_id, matrix_id);
}
if (SbStringCompare(codec_string, "vp8", 3) == 0) {
*codec = kSbMediaVideoCodecVp8;
return true;
}
if (SbStringCompare(codec_string, "vp9", 3) == 0) {
*codec = kSbMediaVideoCodecVp9;
return ParseVp9Info(codec_string, profile);
}
return false;
}
} // namespace media
} // namespace starboard
} // namespace shared
} // namespace starboard