blob: e70fd96ba64b76551697b70c6fb52027ec6e44ab [file] [log] [blame]
/*
* Copyright (C) 2018-2022 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "HEVCUtilities.h"
#include "FourCC.h"
#include "SharedBuffer.h"
#include <JavaScriptCore/DataView.h>
#include <wtf/HexNumber.h>
#include <wtf/MathExtras.h>
#include <wtf/NeverDestroyed.h>
#include <wtf/SortedArrayMap.h>
#include <wtf/text/StringToIntegerConversion.h>
namespace WebCore {
std::optional<AVCParameters> parseAVCCodecParameters(StringView codecString)
{
// The format of the 'avc1' codec string is specified in ISO/IEC 14496-15:2014, Annex E2.
StringView codecView(codecString);
auto codecSplit = codecView.split('.');
auto nextElement = codecSplit.begin();
if (nextElement == codecSplit.end())
return std::nullopt;
AVCParameters parameters;
// Codec identifier: legal values are specified in ISO/IEC 14496-15:2014, section 8:
auto codecName = *nextElement;
if (codecName != "avc1"_s)
return std::nullopt;
if (++nextElement == codecSplit.end())
return std::nullopt;
// First element: profile_idc
auto firstElement = *nextElement;
if (!firstElement.length())
return std::nullopt;
auto profileFlagsAndLevel = parseInteger<uint32_t>(*nextElement, 16);
if (!profileFlagsAndLevel)
return std::nullopt;
parameters.profileIDC = (*profileFlagsAndLevel & 0xF00) >> 16;
parameters.constraintsFlags = (*profileFlagsAndLevel & 0xF0) >> 8;
parameters.levelIDC = *profileFlagsAndLevel & 0xF;
return parameters;
}
String createAVCCodecParametersString(const AVCParameters& parameters)
{
// The format of the 'avc1' codec string is specified in ISO/IEC 14496-15:2014, Annex E.2.
return makeString("avc1."
, hex(parameters.profileIDC, 2)
, hex(parameters.constraintsFlags, 2)
, hex(parameters.levelIDC, 2));
}
std::optional<AVCParameters> parseAVCDecoderConfigurationRecord(const SharedBuffer& buffer)
{
// ISO/IEC 14496-10:2014
// 7.3.2.1.1 Sequence parameter set data syntax
// AVCDecoderConfigurationRecord is at a minimum 24 bytes long
if (buffer.size() < 24)
return std::nullopt;
// aligned(8) class AVCDecoderConfigurationRecord {
// unsigned int(8) configurationVersion = 1;
// unsigned int(8) AVCProfileIndication;
// unsigned int(8) profile_compatibility;
// unsigned int(8) AVCLevelIndication;
// ...
AVCParameters parameters;
auto arrayBuffer = buffer.tryCreateArrayBuffer();
if (!arrayBuffer)
return std::nullopt;
bool status = true;
auto view = JSC::DataView::create(WTFMove(arrayBuffer), 0, buffer.size());
// Byte 0 is a version flag
parameters.profileIDC = view->get<uint8_t>(1, false, &status);
if (!status)
return std::nullopt;
parameters.constraintsFlags = view->get<uint8_t>(2, false, &status);
if (!status)
return std::nullopt;
parameters.levelIDC = view->get<uint8_t>(3, false, &status);
if (!status)
return std::nullopt;
return parameters;
}
std::optional<HEVCParameters> parseHEVCCodecParameters(StringView codecString)
{
// The format of the 'hevc' codec string is specified in ISO/IEC 14496-15:2014, Annex E.3.
StringView codecView(codecString);
auto codecSplit = codecView.split('.');
auto nextElement = codecSplit.begin();
if (nextElement == codecSplit.end())
return std::nullopt;
HEVCParameters parameters;
// Codec identifier: legal values are specified in ISO/IEC 14496-15:2014, section 8:
auto codecName = *nextElement;
if (codecName == "hvc1"_s)
parameters.codec = HEVCParameters::Codec::Hvc1;
else if (codecName == "hev1"_s)
parameters.codec = HEVCParameters::Codec::Hev1;
else
return std::nullopt;
if (++nextElement == codecSplit.end())
return std::nullopt;
// First element: Optional General Profile Space parameter ['A', 'B', 'C'], mapping to [1, 2, 3]
// and [0] for absent, then General Profile IDC as a 5-bit decimal number.
auto profileSpace = *nextElement;
if (!profileSpace.length())
return std::nullopt;
auto firstCharacter = profileSpace[0];
bool hasProfileSpace = firstCharacter >= 'A' && firstCharacter <= 'C';
if (hasProfileSpace) {
parameters.generalProfileSpace = 1 + (firstCharacter - 'A');
profileSpace = profileSpace.substring(1);
}
auto profileIDC = parseInteger<uint8_t>(profileSpace);
if (!profileIDC)
return std::nullopt;
parameters.generalProfileIDC = *profileIDC;
if (++nextElement == codecSplit.end())
return std::nullopt;
// Second element: 32 bit of General Profile Compatibility Flags, in reverse bit order,
// in hex with leading zeros omitted.
auto compatibilityFlags = parseInteger<uint32_t>(*nextElement, 16);
if (!compatibilityFlags)
return std::nullopt;
parameters.generalProfileCompatibilityFlags = reverseBits32(*compatibilityFlags);
if (++nextElement == codecSplit.end())
return std::nullopt;
// Third element: General Tier Flag ['L', 'H'], mapping to [false, true], followed by
// General Level IDC as a 8-bit decimal number.
auto generalTier = *nextElement;
firstCharacter = generalTier[0];
if (firstCharacter != 'L' && firstCharacter != 'H')
return std::nullopt;
parameters.generalTierFlag = (firstCharacter == 'L' ? 0 : 1);
auto generalLevelIDC = parseInteger<uint8_t>(generalTier.substring(1));
if (!generalLevelIDC)
return std::nullopt;
parameters.generalLevelIDC = *generalLevelIDC;
// Optional fourth and remaining elements: a sequence of 6 1-byte constraint flags, each byte encoded
// in hex, and separated by a period, with trailing zero bytes omitted.
for (unsigned i = 0; i < 6; ++i) {
if (++nextElement == codecSplit.end())
break;
auto flag = parseInteger<uint8_t>(*nextElement, 16);
if (!flag)
return std::nullopt;
parameters.generalConstraintIndicatorFlags[i] = *flag;
}
return parameters;
}
String createHEVCCodecParametersString(const HEVCParameters& parameters)
{
// For the second parameter, from ISO/IEC 14496-15:2014, Annex E.3.
// * the 32 bits of the general_profile_compatibility_flags, but in reverse bit order, i.e. with
// general_profile_compatibility_flag[ 31 ] as the most significant bit, followed by, general_profile_compatibility_flag[ 30 ],
// and down to general_profile_compatibility_flag[ 0 ] as the least significant bit, where general_profile_compatibility_flag[ i ]
// for i in the range of 0 to 31, inclusive, are specified in ISO/IEC 23008‐2, encoded in hexadecimal (leading zeroes may be omitted)
auto compatFlagParameter = hex(reverseBits32(parameters.generalProfileCompatibilityFlags));
// * each of the 6 bytes of the constraint flags, starting from the byte containing the
// general_progressive_source_flag, each encoded as a hexadecimal number, and the encoding
// of each byte separated by a period; trailing bytes that are zero may be omitted.
StringBuilder compatibilityFlags;
auto lastFlagByte = parameters.generalConstraintIndicatorFlags.reverseFindIf([] (auto& flag) { return flag; });
for (size_t i = 0; lastFlagByte != notFound && i <= lastFlagByte; ++i) {
compatibilityFlags.append('.');
compatibilityFlags.append(hex(parameters.generalConstraintIndicatorFlags[i], 2));
}
StringBuilder resultBuilder;
resultBuilder.append(parameters.codec == HEVCParameters::Codec::Hev1 ? "hev1" : "hvc1", '.');
if (parameters.generalProfileSpace) {
// The format of the 'hevc' codec string is specified in ISO/IEC 14496-15:2014, Annex E.3.
char profileSpaceCharacter = 'A' + parameters.generalProfileSpace - 1;
resultBuilder.append(profileSpaceCharacter);
}
resultBuilder.append(parameters.generalProfileIDC, '.', compatFlagParameter, '.', parameters.generalTierFlag ? 'H' : 'L', parameters.generalLevelIDC);
resultBuilder.append(compatibilityFlags);
return resultBuilder.toString();
}
std::optional<HEVCParameters> parseHEVCDecoderConfigurationRecord(FourCC codecCode, const SharedBuffer& buffer)
{
// ISO/IEC 14496-15:2014
// 8.3.3.1 HEVC decoder configuration record
// HEVCDecoderConfigurationRecord is at a minimum 23 bytes long
if (buffer.size() < 23)
return std::nullopt;
HEVCParameters parameters;
if (codecCode == "hev1")
parameters.codec = HEVCParameters::Codec::Hev1;
else if (codecCode == "hvc1")
parameters.codec = HEVCParameters::Codec::Hvc1;
else
return std::nullopt;
// aligned(8) class HEVCDecoderConfigurationRecord {
// unsigned int(8) configurationVersion = 1;
// unsigned int(2) general_profile_space;
// unsigned int(1) general_tier_flag;
// unsigned int(5) general_profile_idc;
// unsigned int(32) general_profile_compatibility_flags;
// unsigned int(48) general_constraint_indicator_flags;
// unsigned int(8) general_level_idc;
// ...
auto arrayBuffer = buffer.tryCreateArrayBuffer();
if (!arrayBuffer)
return std::nullopt;
bool status = true;
auto view = JSC::DataView::create(WTFMove(arrayBuffer), 0, buffer.size());
uint32_t profileSpaceTierIDC = view->get<uint8_t>(1, false, &status);
if (!status)
return std::nullopt;
parameters.generalProfileSpace = (profileSpaceTierIDC & 0b11000000) >> 6;
parameters.generalTierFlag = (profileSpaceTierIDC & 0b00100000) >> 5;
parameters.generalProfileIDC = profileSpaceTierIDC & 0b00011111;
parameters.generalProfileCompatibilityFlags = view->get<uint32_t>(2, false, &status);
if (!status)
return std::nullopt;
for (unsigned i = 0; i < 6; ++i) {
parameters.generalConstraintIndicatorFlags[i] = view->get<uint8_t>(6 + i, false, &status);
if (!status)
return std::nullopt;
}
parameters.generalLevelIDC = view->get<uint8_t>(12, false, &status);
if (!status)
return std::nullopt;
return parameters;
}
static std::optional<DoViParameters::Codec> parseDoViCodecType(StringView string)
{
static constexpr std::pair<PackedASCIILowerCodes<uint32_t>, DoViParameters::Codec> typesArray[] = {
{ "dva1", DoViParameters::Codec::AVC1 },
{ "dvav", DoViParameters::Codec::AVC3 },
{ "dvh1", DoViParameters::Codec::HVC1 },
{ "dvhe", DoViParameters::Codec::HEV1 },
};
static constexpr SortedArrayMap typesMap { typesArray };
return makeOptionalFromPointer(typesMap.tryGet(string));
}
static std::optional<uint16_t> profileIDForAlphabeticDoViProfile(StringView profile)
{
// See Table 7 of "Dolby Vision Profiles and Levels Version 1.3.2"
static constexpr std::pair<PackedASCIILowerCodes<uint64_t>, uint16_t> profilesArray[] = {
{ "dvav.se", 9 },
{ "dvhe.dtb", 7 },
{ "dvhe.dtr", 4 },
{ "dvhe.st", 8 },
{ "dvhe.stn", 5 },
};
static constexpr SortedArrayMap profilesMap { profilesArray };
return makeOptionalFromPointer(profilesMap.tryGet(profile));
}
static bool isValidDoViProfileID(uint16_t profileID)
{
switch (profileID) {
case 4:
case 5:
case 7:
case 8:
case 9:
return true;
default:
return false;
}
}
static std::optional<uint16_t> maximumLevelIDForDoViProfileID(uint16_t profileID)
{
// See Section 4.1 of "Dolby Vision Profiles and Levels Version 1.3.2"
switch (profileID) {
case 4: return 9;
case 5: return 13;
case 7: return 9;
case 8: return 13;
case 9: return 5;
default: return std::nullopt;
}
}
static bool isValidProfileIDForCodec(uint16_t profileID, DoViParameters::Codec codec)
{
if (profileID == 9)
return codec == DoViParameters::Codec::AVC1 || codec == DoViParameters::Codec::AVC3;
return codec == DoViParameters::Codec::HVC1 || codec == DoViParameters::Codec::HEV1;
}
std::optional<DoViParameters> parseDoViCodecParameters(StringView codecView)
{
// The format of the DoVi codec string is specified in "Dolby Vision Profiles and Levels Version 1.3.2"
auto codecSplit = codecView.split('.');
auto nextElement = codecSplit.begin();
if (nextElement == codecSplit.end())
return std::nullopt;
DoViParameters parameters;
auto codec = parseDoViCodecType(*nextElement);
if (!codec)
return std::nullopt;
parameters.codec = *codec;
if (++nextElement == codecSplit.end())
return std::nullopt;
auto profileID = *nextElement;
if (!profileID.length())
return std::nullopt;
auto firstCharacter = profileID[0];
// Profile definition can either be numeric or alpha:
if (firstCharacter == '0') {
auto bitstreamProfileID = parseInteger<uint8_t>(profileID);
if (!bitstreamProfileID)
return std::nullopt;
parameters.bitstreamProfileID = *bitstreamProfileID;
} else {
auto bitstreamProfileID = profileIDForAlphabeticDoViProfile(codecView.left(5 + profileID.length()));
if (!bitstreamProfileID)
return std::nullopt;
parameters.bitstreamProfileID = *bitstreamProfileID;
}
if (!isValidDoViProfileID(parameters.bitstreamProfileID))
return std::nullopt;
if (!isValidProfileIDForCodec(parameters.bitstreamProfileID, parameters.codec))
return std::nullopt;
if (++nextElement == codecSplit.end())
return std::nullopt;
auto bitstreamLevelID = parseInteger<uint8_t>(*nextElement);
if (!bitstreamLevelID)
return std::nullopt;
parameters.bitstreamLevelID = *bitstreamLevelID;
auto maximumLevelID = maximumLevelIDForDoViProfileID(parameters.bitstreamProfileID);
if (!maximumLevelID || parameters.bitstreamLevelID > *maximumLevelID)
return std::nullopt;
return parameters;
}
std::optional<DoViParameters> parseDoViDecoderConfigurationRecord(const SharedBuffer& buffer)
{
// The format of the DoVi Configuration Record is contained in "Dolby Vision Streams Within
// the ISO Base Media File Format, Version 2.0"
// DoViDecoderConfigurationRecord is exacty 24 bytes long
if (buffer.size() < 24)
return std::nullopt;
// align (8) class DOVIDecoderConfigurationRecord
// {
// unsigned int (8) dv_version_major;
// unsigned int (8) dv_version_minor;
// unsigned int (7) dv_profile;
// unsigned int (6) dv_level;
// bit (1) rpu_present_flag;
// bit (1) el_present_flag;
// bit (1) bl_present_flag;
// ...
DoViParameters parameters;
auto arrayBuffer = buffer.tryCreateArrayBuffer();
if (!arrayBuffer)
return std::nullopt;
bool status = true;
auto view = JSC::DataView::create(WTFMove(arrayBuffer), 0, buffer.size());
auto profileLevelAndFlags = view->get<uint16_t>(2, false, &status);
if (!status)
return std::nullopt;
parameters.bitstreamProfileID = (profileLevelAndFlags & 0b1111111000000000) >> 9;
parameters.bitstreamLevelID = (profileLevelAndFlags & 0b0000000111111000) >> 3;
return parameters;
}
String createDoViCodecParametersString(const DoViParameters& parameters)
{
// The format of the DoVi codec string is specified in "Dolby Vision Profiles and Levels Version 1.3.2"
StringBuilder builder;
builder.append("dvh1.");
if (parameters.bitstreamProfileID < 10)
builder.append('0');
builder.append(parameters.bitstreamProfileID);
builder.append('.');
if (parameters.bitstreamLevelID < 10)
builder.append('0');
builder.append(parameters.bitstreamLevelID);
return builder.toString();
}
}