| /* |
| * 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(); |
| } |
| |
| } |