/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "H265.h"
#include <stdint.h>
#include <cmath>
#include <limits>
#include "AnnexB.h"
#include "BitReader.h"
#include "BitWriter.h"
#include "BufferReader.h"
#include "ByteStreamsUtils.h"
#include "ByteWriter.h"
#include "MediaData.h"
#include "MediaInfo.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/PodOperations.h"
#include "mozilla/ResultExtensions.h"
#include "mozilla/Span.h"
mozilla::LazyLogModule gH265(
"H265");
#define LOG(msg, ...) MOZ_LOG(gH265, LogLevel::Debug, (msg,
##__VA_ARGS__))
#define LOGV(msg, ...) MOZ_LOG(gH265, LogLevel::Verbose, (msg,
##__VA_ARGS__))
#define TRUE_OR_RETURN(condition) \
do { \
if (!(condition)) { \
LOG(
#condition " should be true!"); \
return mozilla::Err(NS_ERROR_FAILURE); \
} \
}
while (
0)
#define IN_RANGE_OR_RETURN(val, min, max) \
do { \
int64_t temp = AssertedCast<int64_t>(val); \
if ((temp) < (min) || (max) < (temp)) { \
LOG(
#val " is not in the range of [" #min "," #max "]"); \
return mozilla::Err(NS_ERROR_FAILURE); \
} \
}
while (
0)
#define NON_ZERO_OR_RETURN(dest, val) \
do { \
int64_t temp = AssertedCast<int64_t>(val); \
if ((temp) !=
0) { \
(dest) = (temp); \
}
else { \
LOG(
#dest " should be non-zero"); \
return mozilla::Err(NS_ERROR_FAILURE); \
} \
}
while (
0)
// For the comparison, we intended to not use memcpy due to its unreliability.
#define COMPARE_FIELD(field) ((field) == aOther.field)
#define COMPARE_ARRAY(field) \
std::equal(std::begin(field), std::end(field), std::begin(aOther.field))
namespace mozilla {
H265NALU::H265NALU(
const uint8_t* aData, uint32_t aByteSize)
: mNALU(aData, aByteSize) {
// Per 7.3.1 NAL unit syntax
BitReader reader(aData, aByteSize *
8);
Unused << reader.ReadBit();
// forbidden_zero_bit
mNalUnitType = reader.ReadBits(
6);
mNuhLayerId = reader.ReadBits(
6);
mNuhTemporalIdPlus1 = reader.ReadBits(
3);
LOGV(
"Created H265NALU, type=%hhu, size=%u", mNalUnitType, aByteSize);
}
/* static */ Result<HVCCConfig, nsresult> HVCCConfig::Parse(
const mozilla::MediaRawData* aSample) {
if (!aSample) {
LOG(
"No sample");
return mozilla::Err(NS_ERROR_FAILURE);
}
if (aSample->Size() <
3) {
LOG(
"Incorrect sample size %zu", aSample->Size());
return mozilla::Err(NS_ERROR_FAILURE);
}
if (aSample->mTrackInfo &&
!aSample->mTrackInfo->mMimeType.EqualsLiteral(
"video/hevc")) {
LOG(
"Only allow 'video/hevc' (mimeType=%s)",
aSample->mTrackInfo->mMimeType.get());
return mozilla::Err(NS_ERROR_FAILURE);
}
return HVCCConfig::Parse(aSample->mExtraData);
}
/* static */
Result<HVCCConfig, nsresult> HVCCConfig::Parse(
const mozilla::MediaByteBuffer* aExtraData) {
// From configurationVersion to numOfArrays, total 184 bits (23 bytes)
if (!aExtraData) {
LOG(
"No extra-data");
return mozilla::Err(NS_ERROR_FAILURE);
}
if (aExtraData->Length() <
23) {
LOG(
"Incorrect extra-data size %zu", aExtraData->Length());
return mozilla::Err(NS_ERROR_FAILURE);
}
const auto& byteBuffer = *aExtraData;
if (byteBuffer[
0] !=
1) {
LOG(
"Version should always be 1");
return mozilla::Err(NS_ERROR_FAILURE);
}
HVCCConfig hvcc;
BitReader reader(aExtraData);
hvcc.configurationVersion = reader.ReadBits(
8);
hvcc.general_profile_space = reader.ReadBits(
2);
hvcc.general_tier_flag = reader.ReadBit();
hvcc.general_profile_idc = reader.ReadBits(
5);
hvcc.general_profile_compatibility_flags = reader.ReadU32();
uint32_t flagHigh = reader.ReadU32();
uint16_t flagLow = reader.ReadBits(
16);
hvcc.general_constraint_indicator_flags =
((uint64_t)(flagHigh) <<
16) | (uint64_t)(flagLow);
hvcc.general_level_idc = reader.ReadBits(
8);
Unused << reader.ReadBits(
4);
// reserved
hvcc.min_spatial_segmentation_idc = reader.ReadBits(
12);
Unused << reader.ReadBits(
6);
// reserved
hvcc.parallelismType = reader.ReadBits(
2);
Unused << reader.ReadBits(
6);
// reserved
hvcc.chroma_format_idc = reader.ReadBits(
2);
Unused << reader.ReadBits(
5);
// reserved
hvcc.bit_depth_luma_minus8 = reader.ReadBits(
3);
Unused << reader.ReadBits(
5);
// reserved
hvcc.bit_depth_chroma_minus8 = reader.ReadBits(
3);
hvcc.avgFrameRate = reader.ReadBits(
16);
hvcc.constantFrameRate = reader.ReadBits(
2);
hvcc.numTemporalLayers = reader.ReadBits(
3);
hvcc.temporalIdNested = reader.ReadBit();
hvcc.lengthSizeMinusOne = reader.ReadBits(
2);
const uint8_t numOfArrays = reader.ReadBits(
8);
for (uint8_t idx =
0; idx < numOfArrays; idx++) {
Unused << reader.ReadBits(
2);
// array_completeness + reserved
const uint8_t nalUnitType = reader.ReadBits(
6);
const uint16_t numNalus = reader.ReadBits(
16);
LOGV(
"nalu-type=%u, nalu-num=%u", nalUnitType, numNalus);
for (uint16_t nIdx =
0; nIdx < numNalus; nIdx++) {
const uint16_t nalUnitLength = reader.ReadBits(
16);
if (reader.BitsLeft() < nalUnitLength *
8) {
LOG(
"Aborting parsing, NALU size (%u bits) is larger than remaining "
"(%zu bits)!",
nalUnitLength *
8u, reader.BitsLeft());
// We return what we've parsed so far and ignore the rest.
hvcc.mByteBuffer = aExtraData;
return hvcc;
}
const uint8_t* currentPtr =
aExtraData->Elements() + reader.BitCount() /
8;
H265NALU nalu(currentPtr, nalUnitLength);
// ReadBits can only read at most 32 bits at a time.
uint32_t nalSize = nalUnitLength *
8;
while (nalSize >
0) {
uint32_t readBits = nalSize >
32 ?
32 : nalSize;
reader.ReadBits(readBits);
nalSize -= readBits;
}
// Per ISO_IEC-14496-15-2022, 8.3.2.1.3 Semantics, NALU should only be
// SPS/PPS/VPS or SEI, ignore all the other types of NALU.
if (nalu.IsSPS() || nalu.IsPPS() || nalu.IsVPS() || nalu.IsSEI()) {
hvcc.mNALUs.AppendElement(nalu);
}
else {
LOG(
"Ignore NALU (%u) which is not SPS/PPS/VPS or SEI",
nalu.mNalUnitType);
}
}
}
hvcc.mByteBuffer = aExtraData;
return hvcc;
}
uint32_t HVCCConfig::NumSPS()
const {
uint32_t spsCounter =
0;
for (
const auto& nalu : mNALUs) {
if (nalu.IsSPS()) {
spsCounter++;
}
}
return spsCounter;
}
bool HVCCConfig::HasSPS()
const {
bool hasSPS =
false;
for (
const auto& nalu : mNALUs) {
if (nalu.IsSPS()) {
hasSPS =
true;
break;
}
}
return hasSPS;
}
Maybe<H265NALU> HVCCConfig::GetFirstAvaiableNALU(
H265NALU::NAL_TYPES aType)
const {
for (
const auto& nalu : mNALUs) {
if (nalu.mNalUnitType == aType) {
return Some(nalu);
}
}
return Nothing();
}
/* static */
Result<H265SPS, nsresult> H265::DecodeSPSFromSPSNALU(
const H265NALU& aSPSNALU) {
MOZ_ASSERT(aSPSNALU.IsSPS());
RefPtr<MediaByteBuffer> rbsp = H265::DecodeNALUnit(aSPSNALU.mNALU);
if (!rbsp) {
LOG(
"Failed to decode NALU");
return Err(NS_ERROR_FAILURE);
}
// H265 spec, 7.3.2.2.1 seq_parameter_set_rbsp
H265SPS sps;
BitReader reader(rbsp);
sps.sps_video_parameter_set_id = reader.ReadBits(
4);
IN_RANGE_OR_RETURN(sps.sps_video_parameter_set_id,
0,
15);
sps.sps_max_sub_layers_minus1 = reader.ReadBits(
3);
IN_RANGE_OR_RETURN(sps.sps_max_sub_layers_minus1,
0,
6);
sps.sps_temporal_id_nesting_flag = reader.ReadBit();
if (
auto rv = ParseProfileTierLevel(
reader,
true /* aProfilePresentFlag, true per spec*/,
sps.sps_max_sub_layers_minus1, sps.profile_tier_level);
rv.isErr()) {
LOG(
"Failed to parse the profile tier level.");
return Err(NS_ERROR_FAILURE);
}
sps.sps_seq_parameter_set_id = reader.ReadUE();
IN_RANGE_OR_RETURN(sps.sps_seq_parameter_set_id,
0,
15);
sps.chroma_format_idc = reader.ReadUE();
IN_RANGE_OR_RETURN(sps.chroma_format_idc,
0,
3);
if (sps.chroma_format_idc ==
3) {
sps.separate_colour_plane_flag = reader.ReadBit();
}
// From Table 6-1.
if (sps.chroma_format_idc ==
1) {
sps.subWidthC = sps.subHeightC =
2;
}
else if (sps.chroma_format_idc ==
2) {
sps.subWidthC =
2;
sps.subHeightC =
1;
}
else {
sps.subWidthC = sps.subHeightC =
1;
}
NON_ZERO_OR_RETURN(sps.pic_width_in_luma_samples, reader.ReadUE());
NON_ZERO_OR_RETURN(sps.pic_height_in_luma_samples, reader.ReadUE());
{
// (A-2) Calculate maxDpbSize
const auto maxLumaPs = sps.profile_tier_level.GetMaxLumaPs();
CheckedUint32 picSize = sps.pic_height_in_luma_samples;
picSize *= sps.pic_width_in_luma_samples;
if (!picSize.isValid()) {
LOG(
"Invalid picture size");
return Err(NS_ERROR_FAILURE);
}
const auto picSizeInSamplesY = picSize.value();
const auto maxDpbPicBuf = sps.profile_tier_level.GetDpbMaxPicBuf();
if (picSizeInSamplesY <= (maxLumaPs >>
2)) {
sps.maxDpbSize = std::min(
4 * maxDpbPicBuf,
16u);
}
else if (picSizeInSamplesY <= (maxLumaPs >>
1)) {
sps.maxDpbSize = std::min(
2 * maxDpbPicBuf,
16u);
}
else if (picSizeInSamplesY <= ((
3 * maxLumaPs) >>
2)) {
sps.maxDpbSize = std::min((
4 * maxDpbPicBuf) /
3,
16u);
}
else {
sps.maxDpbSize = maxDpbPicBuf;
}
}
sps.conformance_window_flag = reader.ReadBit();
if (sps.conformance_window_flag) {
sps.conf_win_left_offset = reader.ReadUE();
sps.conf_win_right_offset = reader.ReadUE();
sps.conf_win_top_offset = reader.ReadUE();
sps.conf_win_bottom_offset = reader.ReadUE();
}
sps.bit_depth_luma_minus8 = reader.ReadUE();
IN_RANGE_OR_RETURN(sps.bit_depth_luma_minus8,
0,
8);
sps.bit_depth_chroma_minus8 = reader.ReadUE();
IN_RANGE_OR_RETURN(sps.bit_depth_chroma_minus8,
0,
8);
sps.log2_max_pic_order_cnt_lsb_minus4 = reader.ReadUE();
IN_RANGE_OR_RETURN(sps.log2_max_pic_order_cnt_lsb_minus4,
0,
12);
sps.sps_sub_layer_ordering_info_present_flag = reader.ReadBit();
for (
auto i = sps.sps_sub_layer_ordering_info_present_flag
?
0
: sps.sps_max_sub_layers_minus1;
i <= sps.sps_max_sub_layers_minus1; i++) {
sps.sps_max_dec_pic_buffering_minus1[i] = reader.ReadUE();
IN_RANGE_OR_RETURN(sps.sps_max_dec_pic_buffering_minus1[i],
0,
sps.maxDpbSize -
1);
sps.sps_max_num_reorder_pics[i] = reader.ReadUE();
IN_RANGE_OR_RETURN(sps.sps_max_num_reorder_pics[i],
0,
sps.sps_max_dec_pic_buffering_minus1[i]);
// 7.4.3.2.1, see sps_max_dec_pic_buffering_minus1 and
// sps_max_num_reorder_pics, "When i is greater than 0, ....".
if (i >
0) {
TRUE_OR_RETURN(sps.sps_max_dec_pic_buffering_minus1[i] >=
sps.sps_max_dec_pic_buffering_minus1[i -
1]);
TRUE_OR_RETURN(sps.sps_max_num_reorder_pics[i] >=
sps.sps_max_num_reorder_pics[i -
1]);
}
sps.sps_max_latency_increase_plus1[i] = reader.ReadUE();
IN_RANGE_OR_RETURN(sps.sps_max_latency_increase_plus1[i],
0,
0xFFFFFFFE);
}
sps.log2_min_luma_coding_block_size_minus3 = reader.ReadUE();
sps.log2_diff_max_min_luma_coding_block_size = reader.ReadUE();
sps.log2_min_luma_transform_block_size_minus2 = reader.ReadUE();
sps.log2_diff_max_min_luma_transform_block_size = reader.ReadUE();
sps.max_transform_hierarchy_depth_inter = reader.ReadUE();
sps.max_transform_hierarchy_depth_intra = reader.ReadUE();
const auto scaling_list_enabled_flag = reader.ReadBit();
if (scaling_list_enabled_flag) {
Unused << reader.ReadBit();
// sps_scaling_list_data_present_flag
if (
auto rv = ParseAndIgnoreScalingListData(reader); rv.isErr()) {
LOG(
"Failed to parse scaling list data.");
return Err(NS_ERROR_FAILURE);
}
}
// amp_enabled_flag and sample_adaptive_offset_enabled_flag
Unused << reader.ReadBits(
2);
sps.pcm_enabled_flag = reader.ReadBit();
if (sps.pcm_enabled_flag) {
sps.pcm_sample_bit_depth_luma_minus1 = reader.ReadBits(
3);
IN_RANGE_OR_RETURN(sps.pcm_sample_bit_depth_luma_minus1,
0,
sps.BitDepthLuma());
sps.pcm_sample_bit_depth_chroma_minus1 = reader.ReadBits(
3);
IN_RANGE_OR_RETURN(sps.pcm_sample_bit_depth_chroma_minus1,
0,
sps.BitDepthChroma());
sps.log2_min_pcm_luma_coding_block_size_minus3 = reader.ReadUE();
IN_RANGE_OR_RETURN(sps.log2_min_pcm_luma_coding_block_size_minus3,
0,
2);
uint32_t log2MinIpcmCbSizeY{sps.log2_min_pcm_luma_coding_block_size_minus3 +
3};
sps.log2_diff_max_min_pcm_luma_coding_block_size = reader.ReadUE();
{
// Validate value
CheckedUint32 log2MaxIpcmCbSizeY{
sps.log2_diff_max_min_pcm_luma_coding_block_size};
log2MaxIpcmCbSizeY += log2MinIpcmCbSizeY;
CheckedUint32 minCbLog2SizeY{sps.log2_min_luma_coding_block_size_minus3};
minCbLog2SizeY +=
3;
// (7-10)
CheckedUint32 ctbLog2SizeY{minCbLog2SizeY};
ctbLog2SizeY += sps.log2_diff_max_min_luma_coding_block_size;
// (7-11)
IN_RANGE_OR_RETURN(log2MaxIpcmCbSizeY.value(),
0,
std::min(ctbLog2SizeY.value(), uint32_t(
5)));
}
sps.pcm_loop_filter_disabled_flag = reader.ReadBit();
}
sps.num_short_term_ref_pic_sets = reader.ReadUE();
IN_RANGE_OR_RETURN(sps.num_short_term_ref_pic_sets,
0,
kMaxShortTermRefPicSets);
for (
auto i =
0; i < sps.num_short_term_ref_pic_sets; i++) {
if (
auto rv = ParseStRefPicSet(reader, i, sps); rv.isErr()) {
LOG(
"Failed to parse short-term reference picture set.");
return Err(NS_ERROR_FAILURE);
}
}
const auto long_term_ref_pics_present_flag = reader.ReadBit();
if (long_term_ref_pics_present_flag) {
uint32_t num_long_term_ref_pics_sps;
num_long_term_ref_pics_sps = reader.ReadUE();
IN_RANGE_OR_RETURN(num_long_term_ref_pics_sps,
0, kMaxLongTermRefPicSets);
for (
auto i =
0; i < num_long_term_ref_pics_sps; i++) {
Unused << reader.ReadBits(sps.log2_max_pic_order_cnt_lsb_minus4 +
4);
// lt_ref_pic_poc_lsb_sps[i]
Unused << reader.ReadBit();
// used_by_curr_pic_lt_sps_flag
}
}
sps.sps_temporal_mvp_enabled_flag = reader.ReadBit();
sps.strong_intra_smoothing_enabled_flag = reader.ReadBit();
const auto vui_parameters_present_flag = reader.ReadBit();
if (vui_parameters_present_flag) {
if (
auto rv = ParseVuiParameters(reader, sps); rv.isErr()) {
LOG(
"Failed to parse VUI parameter.");
return Err(NS_ERROR_FAILURE);
}
}
// The rest is extension data we don't care about, so no need to parse them.
return sps;
}
/* static */
Result<H265SPS, nsresult> H265::DecodeSPSFromHVCCExtraData(
const mozilla::MediaByteBuffer* aExtraData) {
auto rv = HVCCConfig::Parse(aExtraData);
if (rv.isErr()) {
LOG(
"Only support HVCC extra-data");
return Err(NS_ERROR_FAILURE);
}
const auto& hvcc = rv.unwrap();
const H265NALU* spsNALU = nullptr;
for (
const auto& nalu : hvcc.mNALUs) {
if (nalu.IsSPS()) {
spsNALU = &nalu;
break;
}
}
if (!spsNALU) {
LOG(
"No sps found");
return Err(NS_ERROR_FAILURE);
}
return DecodeSPSFromSPSNALU(*spsNALU);
}
/* static */
Result<Ok, nsresult> H265::ParseProfileTierLevel(
BitReader& aReader,
bool aProfilePresentFlag,
uint8_t aMaxNumSubLayersMinus1, H265ProfileTierLevel& aProfile) {
// H265 spec, 7.3.3 Profile, tier and level syntax
if (aProfilePresentFlag) {
aProfile.general_profile_space = aReader.ReadBits(
2);
aProfile.general_tier_flag = aReader.ReadBit();
aProfile.general_profile_idc = aReader.ReadBits(
5);
IN_RANGE_OR_RETURN(aProfile.general_profile_idc,
0,
11);
aProfile.general_profile_compatibility_flags = aReader.ReadU32();
aProfile.general_progressive_source_flag = aReader.ReadBit();
aProfile.general_interlaced_source_flag = aReader.ReadBit();
aProfile.general_non_packed_constraint_flag = aReader.ReadBit();
aProfile.general_frame_only_constraint_flag = aReader.ReadBit();
// ignored attributes, in total general_reserved_zero_43bits
Unused << aReader.ReadBits(
32);
Unused << aReader.ReadBits(
11);
// general_inbld_flag or general_reserved_zero_bit
Unused << aReader.ReadBit();
}
aProfile.general_level_idc = aReader.ReadBits(
8);
// Following are all ignored attributes.
bool sub_layer_profile_present_flag[
8];
bool sub_layer_level_present_flag[
8];
for (
auto i =
0; i < aMaxNumSubLayersMinus1; i++) {
sub_layer_profile_present_flag[i] = aReader.ReadBit();
sub_layer_level_present_flag[i] = aReader.ReadBit();
}
if (aMaxNumSubLayersMinus1 >
0) {
for (
auto i = aMaxNumSubLayersMinus1; i <
8; i++) {
// reserved_zero_2bits
Unused << aReader.ReadBits(
2);
}
}
for (
auto i =
0; i < aMaxNumSubLayersMinus1; i++) {
if (sub_layer_profile_present_flag[i]) {
// sub_layer_profile_space, sub_layer_tier_flag, sub_layer_profile_idc
Unused << aReader.ReadBits(
8);
// sub_layer_profile_compatibility_flag
Unused << aReader.ReadBits(
32);
// sub_layer_progressive_source_flag, sub_layer_interlaced_source_flag,
// sub_layer_non_packed_constraint_flag,
// sub_layer_frame_only_constraint_flag
Unused << aReader.ReadBits(
4);
// ignored attributes, in total general_reserved_zero_43bits
Unused << aReader.ReadBits(
32);
Unused << aReader.ReadBits(
11);
// sub_layer_inbld_flag or reserved_zero_bit
Unused << aReader.ReadBit();
}
if (sub_layer_level_present_flag[i]) {
Unused << aReader.ReadBits(
8);
// sub_layer_level_idc
}
}
return Ok();
}
uint32_t H265ProfileTierLevel::GetMaxLumaPs()
const {
// From Table A.8 - General tier and level limits.
// "general_level_idc and sub_layer_level_idc[ i ] shall be set equal to a
// value of 30 times the level number specified in Table A.8".
if (general_level_idc <=
30) {
// level 1
return 36864;
}
if (general_level_idc <=
60) {
// level 2
return 122880;
}
if (general_level_idc <=
63) {
// level 2.1
return 245760;
}
if (general_level_idc <=
90) {
// level 3
return 552960;
}
if (general_level_idc <=
93) {
// level 3.1
return 983040;
}
if (general_level_idc <=
123) {
// level 4, 4.1
return 2228224;
}
if (general_level_idc <=
156) {
// level 5, 5.1, 5.2
return 8912896;
}
// level 6, 6.1, 6.2 - beyond that there's no actual limit.
return 35651584;
}
uint32_t H265ProfileTierLevel::GetDpbMaxPicBuf()
const {
// From A.4.2 - Profile-specific level limits for the video profiles.
// "maxDpbPicBuf is equal to 6 for all profiles where the value of
// sps_curr_pic_ref_enabled_flag is required to be equal to 0 and 7 for all
// profiles where the value of sps_curr_pic_ref_enabled_flag is not required
// to be equal to 0." From A.3 Profile, the flag in the main, main still,
// range extensions and high throughput is required to be zero.
return (general_profile_idc >= H265ProfileIdc::kProfileIdcMain &&
general_profile_idc <= H265ProfileIdc::kProfileIdcHighThroughput)
?
6
:
7;
}
bool H265ProfileTierLevel::
operator==(
const H265ProfileTierLevel& aOther)
const {
return COMPARE_FIELD(general_profile_space) &&
COMPARE_FIELD(general_tier_flag) &&
COMPARE_FIELD(general_profile_idc) &&
COMPARE_FIELD(general_profile_compatibility_flags) &&
COMPARE_FIELD(general_progressive_source_flag) &&
COMPARE_FIELD(general_interlaced_source_flag) &&
COMPARE_FIELD(general_non_packed_constraint_flag) &&
COMPARE_FIELD(general_frame_only_constraint_flag) &&
COMPARE_FIELD(general_level_idc);
}
bool H265StRefPicSet::
operator==(
const H265StRefPicSet& aOther)
const {
return COMPARE_FIELD(num_negative_pics) && COMPARE_FIELD(num_positive_pics) &&
COMPARE_FIELD(numDeltaPocs) && COMPARE_ARRAY(usedByCurrPicS0) &&
COMPARE_ARRAY(usedByCurrPicS1) && COMPARE_ARRAY(deltaPocS0) &&
COMPARE_ARRAY(deltaPocS1);
}
bool H265VUIParameters::
operator==(
const H265VUIParameters& aOther)
const {
return COMPARE_FIELD(sar_width) && COMPARE_FIELD(sar_height) &&
COMPARE_FIELD(video_full_range_flag) &&
COMPARE_FIELD(colour_primaries) &&
COMPARE_FIELD(transfer_characteristics) &&
COMPARE_FIELD(matrix_coeffs);
}
bool H265VUIParameters::HasValidAspectRatio()
const {
return aspect_ratio_info_present_flag && mIsSARValid;
}
double H265VUIParameters::GetPixelAspectRatio()
const {
MOZ_ASSERT(HasValidAspectRatio(),
"Shouldn't call this for an invalid ratio!");
if (MOZ_UNLIKELY(!sar_height)) {
return 0.
0;
}
// Sample Aspect Ratio (SAR) is equivalent to Pixel Aspect Ratio (PAR).
return static_cast<
double>(sar_width) /
static_cast<
double>(sar_height);
}
/* static */
Result<Ok, nsresult> H265::ParseAndIgnoreScalingListData(BitReader& aReader) {
// H265 spec, 7.3.4 Scaling list data syntax
for (
auto sizeIdx =
0; sizeIdx <
4; sizeIdx++) {
for (
auto matrixIdx =
0; matrixIdx <
6;
matrixIdx += (sizeIdx ==
3) ?
3 :
1) {
const auto scaling_list_pred_mode_flag = aReader.ReadBit();
if (!scaling_list_pred_mode_flag) {
Unused << aReader.ReadUE();
// scaling_list_pred_matrix_id_delta
}
else {
int32_t coefNum = std::min(
64, (
1 << (
4 + (sizeIdx <<
1))));
if (sizeIdx >
1) {
Unused << aReader.ReadSE();
// scaling_list_dc_coef_minus8
}
for (
auto i =
0; i < coefNum; i++) {
Unused << aReader.ReadSE();
// scaling_list_delta_coef
}
}
}
}
return Ok();
}
/* static */
Result<Ok, nsresult> H265::ParseStRefPicSet(BitReader& aReader,
uint32_t aStRpsIdx, H265SPS& aSPS) {
// H265 Spec, 7.3.7 Short-term reference picture set syntax
MOZ_ASSERT(aStRpsIdx < kMaxShortTermRefPicSets);
bool inter_ref_pic_set_prediction_flag =
false;
H265StRefPicSet& curStRefPicSet = aSPS.st_ref_pic_set[aStRpsIdx];
if (aStRpsIdx !=
0) {
inter_ref_pic_set_prediction_flag = aReader.ReadBit();
}
if (inter_ref_pic_set_prediction_flag) {
int delta_idx_minus1 =
0;
if (aStRpsIdx == aSPS.num_short_term_ref_pic_sets) {
delta_idx_minus1 = aReader.ReadUE();
IN_RANGE_OR_RETURN(delta_idx_minus1,
0, aStRpsIdx -
1);
}
const uint32_t RefRpsIdx = aStRpsIdx - (delta_idx_minus1 +
1);
// (7-59)
const bool delta_rps_sign = aReader.ReadBit();
const uint32_t abs_delta_rps_minus1 = aReader.ReadUE();
IN_RANGE_OR_RETURN(abs_delta_rps_minus1,
0,
0x7FFF);
const int32_t deltaRps =
(
1 -
2 * delta_rps_sign) *
AssertedCast<int32_t>(abs_delta_rps_minus1 +
1);
// (7-60)
bool used_by_curr_pic_flag[kMaxShortTermRefPicSets] = {};
bool use_delta_flag[kMaxShortTermRefPicSets] = {};
// 7.4.8 - use_delta_flag defaults to 1 if not present.
std::fill_n(use_delta_flag, kMaxShortTermRefPicSets,
true);
const H265StRefPicSet& refSet = aSPS.st_ref_pic_set[RefRpsIdx];
for (
auto j =
0; j <= refSet.numDeltaPocs; j++) {
used_by_curr_pic_flag[j] = aReader.ReadBit();
if (!used_by_curr_pic_flag[j]) {
use_delta_flag[j] = aReader.ReadBit();
}
}
// Calculate fields (7-61)
uint32_t i =
0;
for (int64_t j =
static_cast<int64_t>(refSet.num_positive_pics) -
1; j >=
0;
j--) {
MOZ_DIAGNOSTIC_ASSERT(j < kMaxShortTermRefPicSets);
int64_t d_poc = refSet.deltaPocS1[j] + deltaRps;
if (d_poc <
0 && use_delta_flag[refSet.num_negative_pics + j]) {
curStRefPicSet.deltaPocS0[i] = d_poc;
curStRefPicSet.usedByCurrPicS0[i++] =
used_by_curr_pic_flag[refSet.num_negative_pics + j];
}
}
if (deltaRps <
0 && use_delta_flag[refSet.numDeltaPocs]) {
curStRefPicSet.deltaPocS0[i] = deltaRps;
curStRefPicSet.usedByCurrPicS0[i++] =
used_by_curr_pic_flag[refSet.numDeltaPocs];
}
for (
auto j =
0; j < refSet.num_negative_pics; j++) {
MOZ_DIAGNOSTIC_ASSERT(j < kMaxShortTermRefPicSets);
int64_t d_poc = refSet.deltaPocS0[j] + deltaRps;
if (d_poc <
0 && use_delta_flag[j]) {
curStRefPicSet.deltaPocS0[i] = d_poc;
curStRefPicSet.usedByCurrPicS0[i++] = used_by_curr_pic_flag[j];
}
}
curStRefPicSet.num_negative_pics = i;
// Calculate fields (7-62)
i =
0;
for (int64_t j =
static_cast<int64_t>(refSet.num_negative_pics) -
1; j >=
0;
j--) {
MOZ_DIAGNOSTIC_ASSERT(j < kMaxShortTermRefPicSets);
int64_t d_poc = refSet.deltaPocS0[j] + deltaRps;
if (d_poc >
0 && use_delta_flag[j]) {
curStRefPicSet.deltaPocS1[i] = d_poc;
curStRefPicSet.usedByCurrPicS1[i++] = used_by_curr_pic_flag[j];
}
}
if (deltaRps >
0 && use_delta_flag[refSet.numDeltaPocs]) {
curStRefPicSet.deltaPocS1[i] = deltaRps;
curStRefPicSet.usedByCurrPicS1[i++] =
used_by_curr_pic_flag[refSet.numDeltaPocs];
}
for (
auto j =
0; j < refSet.num_positive_pics; j++) {
MOZ_DIAGNOSTIC_ASSERT(j < kMaxShortTermRefPicSets);
int64_t d_poc = refSet.deltaPocS1[j] + deltaRps;
if (d_poc >
0 && use_delta_flag[refSet.num_negative_pics + j]) {
curStRefPicSet.deltaPocS1[i] = d_poc;
curStRefPicSet.usedByCurrPicS1[i++] =
used_by_curr_pic_flag[refSet.num_negative_pics + j];
}
}
curStRefPicSet.num_positive_pics = i;
}
else {
curStRefPicSet.num_negative_pics = aReader.ReadUE();
curStRefPicSet.num_positive_pics = aReader.ReadUE();
const uint32_t spsMaxDecPicBufferingMinus1 =
aSPS.sps_max_dec_pic_buffering_minus1[aSPS.sps_max_sub_layers_minus1];
IN_RANGE_OR_RETURN(curStRefPicSet.num_negative_pics,
0,
spsMaxDecPicBufferingMinus1);
CheckedUint32 maxPositivePics{spsMaxDecPicBufferingMinus1};
maxPositivePics -= curStRefPicSet.num_negative_pics;
IN_RANGE_OR_RETURN(curStRefPicSet.num_positive_pics,
0,
maxPositivePics.value());
for (
auto i =
0; i < curStRefPicSet.num_negative_pics; i++) {
const uint32_t delta_poc_s0_minus1 = aReader.ReadUE();
IN_RANGE_OR_RETURN(delta_poc_s0_minus1,
0,
0x7FFF);
if (i ==
0) {
// (7-67)
curStRefPicSet.deltaPocS0[i] = -(delta_poc_s0_minus1 +
1);
}
else {
// (7-69)
curStRefPicSet.deltaPocS0[i] =
curStRefPicSet.deltaPocS0[i -
1] - (delta_poc_s0_minus1 +
1);
}
curStRefPicSet.usedByCurrPicS0[i] = aReader.ReadBit();
}
for (
auto i =
0; i < curStRefPicSet.num_positive_pics; i++) {
const int delta_poc_s1_minus1 = aReader.ReadUE();
IN_RANGE_OR_RETURN(delta_poc_s1_minus1,
0,
0x7FFF);
if (i ==
0) {
// (7-68)
curStRefPicSet.deltaPocS1[i] = delta_poc_s1_minus1 +
1;
}
else {
// (7-70)
curStRefPicSet.deltaPocS1[i] =
curStRefPicSet.deltaPocS1[i -
1] + delta_poc_s1_minus1 +
1;
}
curStRefPicSet.usedByCurrPicS1[i] = aReader.ReadBit();
}
}
// (7-71)
curStRefPicSet.numDeltaPocs =
curStRefPicSet.num_negative_pics + curStRefPicSet.num_positive_pics;
return Ok();
}
/* static */
Result<Ok, nsresult> H265::ParseVuiParameters(BitReader& aReader,
H265SPS& aSPS) {
// VUI parameters: Table E.1 "Interpretation of sample aspect ratio indicator"
static constexpr
int kTableSarWidth[] = {
0,
1,
12,
10,
16,
40,
24,
20,
32,
80,
18,
15,
64,
160,
4,
3,
2};
static constexpr
int kTableSarHeight[] = {
0,
1,
11,
11,
11,
33,
11,
11,
11,
33,
11,
11,
33,
99,
3,
2,
1};
static_assert(std::size(kTableSarWidth) == std::size(kTableSarHeight),
"sar tables must have the same size");
aSPS.vui_parameters = Some(H265VUIParameters());
H265VUIParameters* vui = aSPS.vui_parameters.ptr();
vui->aspect_ratio_info_present_flag = aReader.ReadBit();
if (vui->aspect_ratio_info_present_flag) {
const auto aspect_ratio_idc = aReader.ReadBits(
8);
constexpr
int kExtendedSar =
255;
if (aspect_ratio_idc == kExtendedSar) {
vui->sar_width = aReader.ReadBits(
16);
vui->sar_height = aReader.ReadBits(
16);
}
else {
const auto max_aspect_ratio_idc = std::size(kTableSarWidth) -
1;
IN_RANGE_OR_RETURN(aspect_ratio_idc,
0, max_aspect_ratio_idc);
vui->sar_width = kTableSarWidth[aspect_ratio_idc];
vui->sar_height = kTableSarHeight[aspect_ratio_idc];
}
// In E.3.1 VUI parameters semantics, "when aspect_ratio_idc is equal to 0
// or sar_width is equal to 0 or sar_height is equal to 0, the sample aspect
// ratio is unspecified in this Specification".
vui->mIsSARValid = vui->sar_width && vui->sar_height;
if (!vui->mIsSARValid) {
LOG(
"sar_width or sar_height should not be zero!");
}
}
const auto overscan_info_present_flag = aReader.ReadBit();
if (overscan_info_present_flag) {
Unused << aReader.ReadBit();
// overscan_appropriate_flag
}
const auto video_signal_type_present_flag = aReader.ReadBit();
if (video_signal_type_present_flag) {
Unused << aReader.ReadBits(
3);
// video_format
vui->video_full_range_flag = aReader.ReadBit();
const auto colour_description_present_flag = aReader.ReadBit();
if (colour_description_present_flag) {
vui->colour_primaries.emplace(aReader.ReadBits(
8));
vui->transfer_characteristics.emplace(aReader.ReadBits(
8));
vui->matrix_coeffs.emplace(aReader.ReadBits(
8));
}
}
const auto chroma_loc_info_present_flag = aReader.ReadBit();
if (chroma_loc_info_present_flag) {
Unused << aReader.ReadUE();
// chroma_sample_loc_type_top_field
Unused << aReader.ReadUE();
// chroma_sample_loc_type_bottom_field
}
// Ignore neutral_chroma_indication_flag, field_seq_flag and
// frame_field_info_present_flag.
Unused << aReader.ReadBits(
3);
const auto default_display_window_flag = aReader.ReadBit();
if (default_display_window_flag) {
uint32_t def_disp_win_left_offset = aReader.ReadUE();
uint32_t def_disp_win_right_offset = aReader.ReadUE();
uint32_t def_disp_win_top_offset = aReader.ReadUE();
uint32_t def_disp_win_bottom_offset = aReader.ReadUE();
// (E-68) + (E-69)
aSPS.mDisplayWidth = aSPS.subWidthC;
aSPS.mDisplayWidth *=
(aSPS.conf_win_left_offset + def_disp_win_left_offset);
aSPS.mDisplayWidth *=
(aSPS.conf_win_right_offset + def_disp_win_right_offset);
if (!aSPS.mDisplayWidth.isValid()) {
LOG(
"mDisplayWidth overflow!");
return Err(NS_ERROR_FAILURE);
}
IN_RANGE_OR_RETURN(aSPS.mDisplayWidth.value(),
0,
aSPS.pic_width_in_luma_samples);
// (E-70) + (E-71)
aSPS.mDisplayHeight = aSPS.subHeightC;
aSPS.mDisplayHeight *= (aSPS.conf_win_top_offset + def_disp_win_top_offset);
aSPS.mDisplayHeight *=
(aSPS.conf_win_bottom_offset + def_disp_win_bottom_offset);
if (!aSPS.mDisplayHeight.isValid()) {
LOG(
"mDisplayHeight overflow!");
return Err(NS_ERROR_FAILURE);
}
IN_RANGE_OR_RETURN(aSPS.mDisplayHeight.value(),
0,
aSPS.pic_height_in_luma_samples);
}
const auto vui_timing_info_present_flag = aReader.ReadBit();
if (vui_timing_info_present_flag) {
Unused << aReader.ReadU32();
// vui_num_units_in_tick
Unused << aReader.ReadU32();
// vui_time_scale
const auto vui_poc_proportional_to_timing_flag = aReader.ReadBit();
if (vui_poc_proportional_to_timing_flag) {
Unused << aReader.ReadUE();
// vui_num_ticks_poc_diff_one_minus1
}
const auto vui_hrd_parameters_present_flag = aReader.ReadBit();
if (vui_hrd_parameters_present_flag) {
if (
auto rv = ParseAndIgnoreHrdParameters(aReader,
true,
aSPS.sps_max_sub_layers_minus1);
rv.isErr()) {
LOG(
"Failed to parse Hrd parameters");
return rv;
}
}
}
const auto bitstream_restriction_flag = aReader.ReadBit();
if (bitstream_restriction_flag) {
// Skip tiles_fixed_structure_flag, motion_vectors_over_pic_boundaries_flag
// and restricted_ref_pic_lists_flag.
Unused << aReader.ReadBits(
3);
Unused << aReader.ReadUE();
// min_spatial_segmentation_idc
Unused << aReader.ReadUE();
// max_bytes_per_pic_denom
Unused << aReader.ReadUE();
// max_bits_per_min_cu_denom
Unused << aReader.ReadUE();
// log2_max_mv_length_horizontal
Unused << aReader.ReadUE();
// log2_max_mv_length_vertical
}
return Ok();
}
/* static */
Result<Ok, nsresult> H265::ParseAndIgnoreHrdParameters(
BitReader& aReader,
bool aCommonInfPresentFlag,
int aMaxNumSubLayersMinus1) {
// H265 Spec, E.2.2 HRD parameters syntax
bool nal_hrd_parameters_present_flag =
false;
bool vcl_hrd_parameters_present_flag =
false;
bool sub_pic_hrd_params_present_flag =
false;
if (aCommonInfPresentFlag) {
nal_hrd_parameters_present_flag = aReader.ReadBit();
vcl_hrd_parameters_present_flag = aReader.ReadBit();
if (nal_hrd_parameters_present_flag || vcl_hrd_parameters_present_flag) {
sub_pic_hrd_params_present_flag = aReader.ReadBit();
if (sub_pic_hrd_params_present_flag) {
Unused << aReader.ReadBits(
8);
// tick_divisor_minus2
// du_cpb_removal_delay_increment_length_minus1
Unused << aReader.ReadBits(
5);
// sub_pic_cpb_params_in_pic_timing_sei_flag
Unused << aReader.ReadBits(
1);
Unused << aReader.ReadBits(
5);
// dpb_output_delay_du_length_minus1
}
Unused << aReader.ReadBits(
4);
// bit_rate_scale
Unused << aReader.ReadBits(
4);
// cpb_size_scale
if (sub_pic_hrd_params_present_flag) {
Unused << aReader.ReadBits(
4);
// cpb_size_du_scale
}
Unused << aReader.ReadBits(
5);
// initial_cpb_removal_delay_length_minus1
Unused << aReader.ReadBits(
5);
// au_cpb_removal_delay_length_minus1
Unused << aReader.ReadBits(
5);
// dpb_output_delay_length_minus1
}
}
for (
int i =
0; i <= aMaxNumSubLayersMinus1; i++) {
bool fixed_pic_rate_within_cvs_flag =
false;
if (
auto fixed_pic_rate_general_flag = aReader.ReadBit();
!fixed_pic_rate_general_flag) {
fixed_pic_rate_within_cvs_flag = aReader.ReadBit();
}
bool low_delay_hrd_flag =
false;
if (fixed_pic_rate_within_cvs_flag) {
Unused << aReader.ReadUE();
// elemental_duration_in_tc_minus1
}
else {
low_delay_hrd_flag = aReader.ReadBit();
}
int cpb_cnt_minus1 =
0;
if (!low_delay_hrd_flag) {
cpb_cnt_minus1 = aReader.ReadUE();
IN_RANGE_OR_RETURN(cpb_cnt_minus1,
0,
31);
}
if (nal_hrd_parameters_present_flag) {
if (
auto rv = ParseAndIgnoreSubLayerHrdParameters(
aReader, cpb_cnt_minus1 +
1, sub_pic_hrd_params_present_flag);
rv.isErr()) {
LOG(
"Failed to parse nal Hrd parameters");
return rv;
};
}
if (vcl_hrd_parameters_present_flag) {
if (
auto rv = ParseAndIgnoreSubLayerHrdParameters(
aReader, cpb_cnt_minus1 +
1, sub_pic_hrd_params_present_flag);
rv.isErr()) {
LOG(
"Failed to parse vcl Hrd parameters");
return rv;
}
}
}
return Ok();
}
/* static */
Result<Ok, nsresult> H265::ParseAndIgnoreSubLayerHrdParameters(
BitReader& aReader,
int aCpbCnt,
bool aSubPicHrdParamsPresentFlag) {
// H265 Spec, E.2.3 Sub-layer HRD parameters syntax
for (
auto i =
0; i < aCpbCnt; i++) {
Unused << aReader.ReadUE();
// bit_rate_value_minus1
Unused << aReader.ReadUE();
// cpb_size_value_minus1
if (aSubPicHrdParamsPresentFlag) {
Unused << aReader.ReadUE();
// cpb_size_du_value_minus1
Unused << aReader.ReadUE();
// bit_rate_du_value_minus1
}
Unused << aReader.ReadBit();
// cbr_flag
}
return Ok();
}
bool H265SPS::
operator==(
const H265SPS& aOther)
const {
return COMPARE_FIELD(sps_video_parameter_set_id) &&
COMPARE_FIELD(sps_max_sub_layers_minus1) &&
COMPARE_FIELD(sps_temporal_id_nesting_flag) &&
COMPARE_FIELD(profile_tier_level) &&
COMPARE_FIELD(sps_seq_parameter_set_id) &&
COMPARE_FIELD(chroma_format_idc) &&
COMPARE_FIELD(separate_colour_plane_flag) &&
COMPARE_FIELD(pic_width_in_luma_samples) &&
COMPARE_FIELD(pic_height_in_luma_samples) &&
COMPARE_FIELD(conformance_window_flag) &&
COMPARE_FIELD(conf_win_left_offset) &&
COMPARE_FIELD(conf_win_right_offset) &&
COMPARE_FIELD(conf_win_top_offset) &&
COMPARE_FIELD(conf_win_bottom_offset) &&
COMPARE_FIELD(bit_depth_luma_minus8) &&
COMPARE_FIELD(bit_depth_chroma_minus8) &&
COMPARE_FIELD(log2_max_pic_order_cnt_lsb_minus4) &&
COMPARE_FIELD(sps_sub_layer_ordering_info_present_flag) &&
COMPARE_ARRAY(sps_max_dec_pic_buffering_minus1) &&
COMPARE_ARRAY(sps_max_num_reorder_pics) &&
COMPARE_ARRAY(sps_max_latency_increase_plus1) &&
COMPARE_FIELD(log2_min_luma_coding_block_size_minus3) &&
COMPARE_FIELD(log2_diff_max_min_luma_coding_block_size) &&
COMPARE_FIELD(log2_min_luma_transform_block_size_minus2) &&
COMPARE_FIELD(log2_diff_max_min_luma_transform_block_size) &&
COMPARE_FIELD(max_transform_hierarchy_depth_inter) &&
COMPARE_FIELD(max_transform_hierarchy_depth_intra) &&
COMPARE_FIELD(pcm_enabled_flag) &&
COMPARE_FIELD(pcm_sample_bit_depth_luma_minus1) &&
COMPARE_FIELD(pcm_sample_bit_depth_chroma_minus1) &&
COMPARE_FIELD(log2_min_pcm_luma_coding_block_size_minus3) &&
COMPARE_FIELD(log2_diff_max_min_pcm_luma_coding_block_size) &&
COMPARE_FIELD(pcm_loop_filter_disabled_flag) &&
COMPARE_FIELD(num_short_term_ref_pic_sets) &&
COMPARE_ARRAY(st_ref_pic_set) &&
COMPARE_FIELD(sps_temporal_mvp_enabled_flag) &&
COMPARE_FIELD(strong_intra_smoothing_enabled_flag) &&
COMPARE_FIELD(vui_parameters) && COMPARE_FIELD(subWidthC) &&
COMPARE_FIELD(subHeightC) && COMPARE_FIELD(mDisplayWidth) &&
COMPARE_FIELD(mDisplayHeight) && COMPARE_FIELD(maxDpbSize);
}
bool H265SPS::
operator!=(
const H265SPS& aOther)
const {
return !(
operator==(aOther));
}
gfx::IntSize H265SPS::GetImageSize()
const {
return gfx::IntSize(pic_width_in_luma_samples, pic_height_in_luma_samples);
}
gfx::IntSize H265SPS::GetDisplaySize()
const {
if (mDisplayWidth.value() ==
0 || mDisplayHeight.value() ==
0) {
return GetImageSize();
}
return gfx::IntSize(mDisplayWidth.value(), mDisplayHeight.value());
}
gfx::ColorDepth H265SPS::ColorDepth()
const {
if (bit_depth_luma_minus8 !=
0 && bit_depth_luma_minus8 !=
2 &&
bit_depth_luma_minus8 !=
4) {
// We don't know what that is, just assume 8 bits to prevent decoding
// regressions if we ever encounter those.
return gfx::ColorDepth::COLOR_8;
}
return gfx::ColorDepthForBitDepth(BitDepthLuma());
}
// PrimaryID, TransferID and MatrixID are defined in ByteStreamsUtils.h
static PrimaryID GetPrimaryID(
const Maybe<uint8_t>& aPrimary) {
if (!aPrimary || *aPrimary <
1 || *aPrimary >
22 || *aPrimary ==
3) {
return PrimaryID::INVALID;
}
if (*aPrimary >
12 && *aPrimary <
22) {
return PrimaryID::INVALID;
}
return static_cast<PrimaryID>(*aPrimary);
}
static TransferID GetTransferID(
const Maybe<uint8_t>& aTransfer) {
if (!aTransfer || *aTransfer <
1 || *aTransfer >
18 || *aTransfer ==
3) {
return TransferID::INVALID;
}
return static_cast<TransferID>(*aTransfer);
}
static MatrixID GetMatrixID(
const Maybe<uint8_t>& aMatrix) {
if (!aMatrix || *aMatrix >
11 || *aMatrix ==
3) {
return MatrixID::INVALID;
}
return static_cast<MatrixID>(*aMatrix);
}
gfx::YUVColorSpace H265SPS::ColorSpace()
const {
// Bitfield, note that guesses with higher values take precedence over
// guesses with lower values.
enum Guess {
GUESS_BT601 =
1 <<
0,
GUESS_BT709 =
1 <<
1,
GUESS_BT2020 =
1 <<
2,
};
uint32_t guess =
0;
if (vui_parameters) {
switch (GetPrimaryID(vui_parameters->colour_primaries)) {
case PrimaryID::BT709:
guess |= GUESS_BT709;
break;
case PrimaryID::BT470M:
case PrimaryID::BT470BG:
case PrimaryID::SMPTE170M:
case PrimaryID::SMPTE240M:
guess |= GUESS_BT601;
break;
case PrimaryID::BT2020:
guess |= GUESS_BT2020;
break;
case PrimaryID::FILM:
case PrimaryID::SMPTEST428_1:
case PrimaryID::SMPTEST431_2:
case PrimaryID::SMPTEST432_1:
case PrimaryID::EBU_3213_E:
case PrimaryID::INVALID:
case PrimaryID::UNSPECIFIED:
break;
}
switch (GetTransferID(vui_parameters->transfer_characteristics)) {
case TransferID::BT709:
guess |= GUESS_BT709;
break;
case TransferID::GAMMA22:
case TransferID::GAMMA28:
case TransferID::SMPTE170M:
case TransferID::SMPTE240M:
guess |= GUESS_BT601;
break;
case TransferID::BT2020_10:
case TransferID::BT2020_12:
guess |= GUESS_BT2020;
break;
case TransferID::LINEAR:
case TransferID::LOG:
case TransferID::LOG_SQRT:
case TransferID::IEC61966_2_4:
case TransferID::BT1361_ECG:
case TransferID::IEC61966_2_1:
case TransferID::SMPTEST2084:
case TransferID::SMPTEST428_1:
case TransferID::ARIB_STD_B67:
case TransferID::INVALID:
case TransferID::UNSPECIFIED:
break;
}
switch (GetMatrixID(vui_parameters->matrix_coeffs)) {
case MatrixID::BT709:
guess |= GUESS_BT709;
break;
case MatrixID::BT470BG:
case MatrixID::SMPTE170M:
case MatrixID::SMPTE240M:
guess |= GUESS_BT601;
break;
case MatrixID::BT2020_NCL:
case MatrixID::BT2020_CL:
guess |= GUESS_BT2020;
break;
case MatrixID::RGB:
case MatrixID::FCC:
case MatrixID::YCOCG:
case MatrixID::YDZDX:
case MatrixID::INVALID:
case MatrixID::UNSPECIFIED:
break;
}
}
// Removes lowest bit until only a single bit remains.
while (guess & (guess -
1)) {
guess &= guess -
1;
}
if (!guess) {
// A better default to BT601 which should die a slow death.
guess = GUESS_BT709;
}
switch (guess) {
case GUESS_BT601:
return gfx::YUVColorSpace::BT601;
case GUESS_BT709:
return gfx::YUVColorSpace::BT709;
default:
MOZ_DIAGNOSTIC_ASSERT(guess == GUESS_BT2020);
return gfx::YUVColorSpace::BT2020;
}
}
bool H265SPS::IsFullColorRange()
const {
return vui_parameters ? vui_parameters->video_full_range_flag :
false;
}
uint8_t H265SPS::ColorPrimaries()
const {
// Per H265 spec E.3.1, "When the colour_primaries syntax element is not
// present, the value of colour_primaries is inferred to be equal to 2 (the
// chromaticity is unspecified or is determined by the application).".
if (!vui_parameters || !vui_parameters->colour_primaries) {
return 2;
}
return vui_parameters->colour_primaries.value();
}
uint8_t H265SPS::TransferFunction()
const {
// Per H265 spec E.3.1, "When the transfer_characteristics syntax element is
// not present, the value of transfer_characteristics is inferred to be equal
// to 2 (the transfer characteristics are unspecified or are determined by the
// application)."
if (!vui_parameters || !vui_parameters->transfer_characteristics) {
return 2;
}
return vui_parameters->transfer_characteristics.value();
}
/* static */
already_AddRefed<mozilla::MediaByteBuffer> H265::DecodeNALUnit(
const Span<
const uint8_t>& aNALU) {
RefPtr<mozilla::MediaByteBuffer> rbsp =
new mozilla::MediaByteBuffer;
BufferReader reader(aNALU.Elements(), aNALU.Length());
auto header = reader.ReadU16();
if (header.isErr()) {
return nullptr;
}
uint32_t lastbytes =
0xffff;
while (reader.Remaining()) {
auto res = reader.ReadU8();
if (res.isErr()) {
return nullptr;
}
uint8_t byte = res.unwrap();
if ((lastbytes &
0xffff) ==
0 && byte ==
0x03) {
// reset last two bytes, to detect the 0x000003 sequence again.
lastbytes =
0xffff;
}
else {
rbsp->AppendElement(byte);
}
lastbytes = (lastbytes <<
8) | byte;
}
return rbsp.forget();
}
/* static */
already_AddRefed<mozilla::MediaByteBuffer> H265::ExtractHVCCExtraData(
const mozilla::MediaRawData* aSample) {
size_t sampleSize = aSample->Size();
if (aSample->mCrypto.IsEncrypted()) {
// The content is encrypted, we can only parse the non-encrypted data.
MOZ_ASSERT(aSample->mCrypto.mPlainSizes.Length() >
0);
if (aSample->mCrypto.mPlainSizes.Length() ==
0 ||
aSample->mCrypto.mPlainSizes[
0] > sampleSize) {
LOG(
"Invalid crypto content");
return nullptr;
}
sampleSize = aSample->mCrypto.mPlainSizes[
0];
}
auto hvcc = HVCCConfig::Parse(aSample);
if (hvcc.isErr()) {
LOG(
"Only support extracting extradata from HVCC");
return nullptr;
}
const auto nalLenSize = hvcc.unwrap().NALUSize();
BufferReader reader(aSample->Data(), sampleSize);
nsTArray<Maybe<H265SPS>> spsRefTable;
nsTArray<H265NALU> spsNALUs;
// If we encounter SPS with the same id but different content, we will stop
// attempting to detect duplicates.
bool checkDuplicate =
true;
const H265SPS* firstSPS = nullptr;
RefPtr<mozilla::MediaByteBuffer> extradata =
new mozilla::MediaByteBuffer;
while (reader.Remaining() > nalLenSize) {
// ISO/IEC 14496-15, 4.2.3.2 Syntax. (NALUSample) Reading the size of NALU.
uint32_t nalLen =
0;
switch (nalLenSize) {
case 1:
Unused << reader.ReadU8().map(
[&](uint8_t x)
mutable {
return nalLen = x; });
break;
case 2:
Unused << reader.ReadU16().map(
[&](uint16_t x)
mutable {
return nalLen = x; });
break;
case 3:
Unused << reader.ReadU24().map(
[&](uint32_t x)
mutable {
return nalLen = x; });
break;
default:
MOZ_DIAGNOSTIC_ASSERT(nalLenSize ==
4);
Unused << reader.ReadU32().map(
[&](uint32_t x)
mutable {
return nalLen = x; });
break;
}
const uint8_t* p = reader.Read(nalLen);
if (!p) {
// The read failed, but we may already have some SPS data so break out of
// reading and process what we have, if any.
break;
}
const H265NALU nalu(p, nalLen);
LOGV(
"Found NALU, type=%u", nalu.mNalUnitType);
if (nalu.IsSPS()) {
auto rv = H265::DecodeSPSFromSPSNALU(nalu);
if (rv.isErr()) {
// Invalid SPS, ignore.
LOG(
"Ignore invalid SPS");
continue;
}
const H265SPS sps = rv.unwrap();
const uint8_t spsId = sps.sps_seq_parameter_set_id;
// 0~15
if (spsId >= spsRefTable.Length()) {
if (!spsRefTable.SetLength(spsId +
1, fallible)) {
NS_WARNING(
"OOM while expanding spsRefTable!");
return nullptr;
}
}
if (checkDuplicate && spsRefTable[spsId] &&
*(spsRefTable[spsId]) == sps) {
// Duplicate ignore.
continue;
}
if (spsRefTable[spsId]) {
// We already have detected a SPS with this Id. Just to be safe we
// disable SPS duplicate detection.
checkDuplicate =
false;
}
else {
spsRefTable[spsId] = Some(sps);
spsNALUs.AppendElement(nalu);
if (!firstSPS) {
firstSPS = spsRefTable[spsId].ptr();
}
}
}
}
LOGV(
"Found %zu SPS NALU", spsNALUs.Length());
if (!spsNALUs.IsEmpty()) {
MOZ_ASSERT(firstSPS);
BitWriter writer(extradata);
// ISO/IEC 14496-15, HEVCDecoderConfigurationRecord. But we only append SPS.
writer.WriteBits(
1,
8);
// version
const auto& profile = firstSPS->profile_tier_level;
writer.WriteBits(profile.general_profile_space,
2);
writer.WriteBits(profile.general_tier_flag,
1);
writer.WriteBits(profile.general_profile_idc,
5);
writer.WriteU32(profile.general_profile_compatibility_flags);
// general_constraint_indicator_flags
writer.WriteBit(profile.general_progressive_source_flag);
writer.WriteBit(profile.general_interlaced_source_flag);
writer.WriteBit(profile.general_non_packed_constraint_flag);
writer.WriteBit(profile.general_frame_only_constraint_flag);
writer.WriteBits(
0,
44);
/* ignored 44 bits */
writer.WriteU8(profile.general_level_idc);
writer.WriteBits(
0,
4);
// reserved
writer.WriteBits(
0,
12);
// min_spatial_segmentation_idc
writer.WriteBits(
0,
6);
// reserved
writer.WriteBits(
0,
2);
// parallelismType
writer.WriteBits(
0,
6);
// reserved
writer.WriteBits(firstSPS->chroma_format_idc,
2);
writer.WriteBits(
0,
5);
// reserved
writer.WriteBits(firstSPS->bit_depth_luma_minus8,
3);
writer.WriteBits(
0,
5);
// reserved
writer.WriteBits(firstSPS->bit_depth_chroma_minus8,
3);
// avgFrameRate + constantFrameRate + numTemporalLayers + temporalIdNested
writer.WriteBits(
0,
22);
writer.WriteBits(nalLenSize -
1,
2);
// lengthSizeMinusOne
writer.WriteU8(
1);
// numOfArrays, only SPS
for (
auto j =
0; j <
1; j++) {
writer.WriteBits(
0,
2);
// array_completeness + reserved
writer.WriteBits(H265NALU::SPS_NUT,
6);
// NAL_unit_type
writer.WriteBits(spsNALUs.Length(),
16);
// numNalus
for (
auto i =
0; i < spsNALUs.Length(); i++) {
writer.WriteBits(spsNALUs[i].mNALU.Length(),
16);
// nalUnitLength
MOZ_ASSERT(writer.BitCount() %
8 ==
0);
extradata->AppendElements(spsNALUs[i].mNALU.Elements(),
spsNALUs[i].mNALU.Length());
writer.AdvanceBytes(spsNALUs[i].mNALU.Length());
}
}
}
return extradata.forget();
}
/* static */
bool AreTwoSPSIdentical(
const H265NALU& aLhs,
const H265NALU& aRhs) {
MOZ_ASSERT(aLhs.IsSPS() && aRhs.IsSPS());
auto rv1 = H265::DecodeSPSFromSPSNALU(aLhs);
auto rv2 = H265::DecodeSPSFromSPSNALU(aRhs);
if (rv1.isErr() || rv2.isErr()) {
return false;
}
return rv1.unwrap() == rv2.unwrap();
}
/* static */
bool H265::CompareExtraData(
const mozilla::MediaByteBuffer* aExtraData1,
const mozilla::MediaByteBuffer* aExtraData2) {
if (aExtraData1 == aExtraData2) {
return true;
}
auto rv1 = HVCCConfig::Parse(aExtraData1);
auto rv2 = HVCCConfig::Parse(aExtraData2);
if (rv1.isErr() || rv2.isErr()) {
return false;
}
const auto config1 = rv1.unwrap();
const auto config2 = rv2.unwrap();
uint8_t numSPS = config1.NumSPS();
if (numSPS ==
0 || numSPS != config2.NumSPS()) {
return false;
}
// We only compare if the SPS are the same as the various HEVC decoders can
// deal with in-band change of PPS.
SPSIterator it1(config1);
SPSIterator it2(config2);
while (it1 && it2) {
const H265NALU* nalu1 = *it1;
const H265NALU* nalu2 = *it2;
if (!nalu1 || !nalu2) {
return false;
}
if (!AreTwoSPSIdentical(*nalu1, *nalu2)) {
return false;
}
++it1;
++it2;
}
return true;
}
/* static */
uint32_t H265::ComputeMaxRefFrames(
const mozilla::MediaByteBuffer* aExtraData) {
auto rv = DecodeSPSFromHVCCExtraData(aExtraData);
if (rv.isErr()) {
return 0;
}
return rv.unwrap().sps_max_dec_pic_buffering_minus1[
0] +
1;
}
/* static */
already_AddRefed<mozilla::MediaByteBuffer> H265::CreateFakeExtraData() {
// Create fake VPS, SPS, PPS and append them into HVCC box
static const uint8_t sFakeVPS[] = {
0x40,
0x01,
0x0C,
0x01,
0xFF,
0xFF,
0x01,
0x60,
0x00,
0x00,
0x03,
0x00,
0x90,
0x00,
0x00,
0x03,
0x00,
0x00,
0x03,
0x00,
0x3F,
0x95,
0x98,
0x09};
static const uint8_t sFakeSPS[] = {
0x42,
0x01,
0x01,
0x01,
0x60,
0x00,
0x00,
0x03,
0x00,
0x90,
0x00,
0x00,
0x03,
0x00,
0x00,
0x03,
0x00,
0x3F,
0xA0,
0x05,
0x02,
0x01,
0x69,
0x65,
0x95,
0x9A,
0x49,
0x32,
0xBC,
0x04,
0x04,
0x00,
0x00,
0x03,
0x00,
0x04,
0x00,
0x00,
0x03,
0x00,
0x78,
0x20};
static const uint8_t sFakePPS[] = {
0x44,
0x01,
0xC1,
0x72,
0xB4,
0x62,
0x40};
nsTArray<H265NALU> nalus;
nalus.AppendElement(H265NALU{sFakeVPS,
sizeof(sFakeVPS)});
nalus.AppendElement(H265NALU{sFakeSPS,
sizeof(sFakeSPS)});
nalus.AppendElement(H265NALU{sFakePPS,
sizeof(sFakePPS)});
// HEVCDecoderConfigurationRecord (HVCC) is in ISO/IEC 14496-15 8.3.2.1.2
const uint8_t nalLenSize =
4;
auto extradata = MakeRefPtr<mozilla::MediaByteBuffer>();
BitWriter writer(extradata);
writer.WriteBits(
1,
8);
// version
writer.WriteBits(
0,
2);
// general_profile_space
writer.WriteBits(
0,
1);
// general_tier_flag
writer.WriteBits(
1 /* main */, 5); // general_profile_idc
writer.WriteU32(
0);
// general_profile_compatibility_flags
writer.WriteBits(
0,
48);
// general_constraint_indicator_flags
writer.WriteU8(
1 /* level 1 */); // general_level_idc
writer.WriteBits(
0,
4);
// reserved
writer.WriteBits(
0,
12);
// min_spatial_segmentation_idc
writer.WriteBits(
0,
6);
// reserved
writer.WriteBits(
0,
2);
// parallelismType
writer.WriteBits(
0,
6);
// reserved
writer.WriteBits(
0,
2);
// chroma_format_idc
writer.WriteBits(
0,
5);
// reserved
writer.WriteBits(
0,
3);
// bit_depth_luma_minus8
writer.WriteBits(
0,
5);
// reserved
writer.WriteBits(
0,
3);
// bit_depth_chroma_minus8
writer.WriteBits(
0,
22);
// avgFrameRate + constantFrameRate +
// numTemporalLayers + temporalIdNested
writer.WriteBits(nalLenSize -
1,
2);
// lengthSizeMinusOne
writer.WriteU8(nalus.Length());
// numOfArrays
for (
auto& nalu : nalus) {
writer.WriteBits(
0,
2);
// array_completeness + reserved
writer.WriteBits(nalu.mNalUnitType,
6);
// NAL_unit_type
writer.WriteBits(
1,
16);
// numNalus
writer.WriteBits(nalu.mNALU.Length(),
16);
// nalUnitLength
MOZ_ASSERT(writer.BitCount() %
8 ==
0);
extradata->AppendElements(nalu.mNALU.Elements(), nalu.mNALU.Length());
writer.AdvanceBytes(nalu.mNALU.Length());
}
MOZ_ASSERT(HVCCConfig::Parse(extradata).isOk());
return extradata.forget();
}
/* static */
already_AddRefed<mozilla::MediaByteBuffer> H265::CreateNewExtraData(
const HVCCConfig& aConfig,
const Maybe<H265NALU>& aSPS,
const Maybe<H265NALU>& aPPS,
const Maybe<H265NALU>& aVPS) {
// Append essential NALUs if they exist
nsTArray<H265NALU> nalus;
if (aSPS) {
nalus.AppendElement(*aSPS);
}
if (aPPS) {
nalus.AppendElement(*aPPS);
}
if (aVPS) {
nalus.AppendElement(*aVPS);
}
// HEVCDecoderConfigurationRecord (HVCC) is in ISO/IEC 14496-15 8.3.2.1.2
auto extradata = MakeRefPtr<mozilla::MediaByteBuffer>();
BitWriter writer(extradata);
writer.WriteBits(aConfig.configurationVersion,
8);
writer.WriteBits(aConfig.general_profile_space,
2);
writer.WriteBits(aConfig.general_tier_flag,
1);
writer.WriteBits(aConfig.general_profile_idc,
5);
writer.WriteU32(aConfig.general_profile_compatibility_flags);
writer.WriteBits(aConfig.general_constraint_indicator_flags,
48);
writer.WriteU8(aConfig.general_level_idc);
writer.WriteBits(
0,
4);
// reserved
writer.WriteBits(aConfig.min_spatial_segmentation_idc,
12);
writer.WriteBits(
0,
6);
// reserved
writer.WriteBits(aConfig.parallelismType,
2);
writer.WriteBits(
0,
6);
// reserved
writer.WriteBits(aConfig.chroma_format_idc,
2);
writer.WriteBits(
0,
5);
// reserved
writer.WriteBits(aConfig.bit_depth_luma_minus8,
3);
writer.WriteBits(
0,
5);
// reserved
writer.WriteBits(aConfig.bit_depth_chroma_minus8,
3);
writer.WriteBits(aConfig.avgFrameRate,
16);
writer.WriteBits(aConfig.constantFrameRate,
2);
writer.WriteBits(aConfig.numTemporalLayers,
3);
writer.WriteBits(aConfig.temporalIdNested,
1);
writer.WriteBits(aConfig.lengthSizeMinusOne,
2);
writer.WriteU8(nalus.Length());
// numOfArrays
for (
auto& nalu : nalus) {
writer.WriteBits(
0,
2);
// array_completeness + reserved
writer.WriteBits(nalu.mNalUnitType,
6);
// NAL_unit_type
writer.WriteBits(
1,
16);
// numNalus
writer.WriteBits(nalu.mNALU.Length(),
16);
// nalUnitLength
MOZ_ASSERT(writer.BitCount() %
8 ==
0);
extradata->AppendElements(nalu.mNALU.Elements(), nalu.mNALU.Length());
writer.AdvanceBytes(nalu.mNALU.Length());
}
MOZ_ASSERT(HVCCConfig::Parse(extradata).isOk());
return extradata.forget();
}
#undef LOG
#undef LOGV
}
// namespace mozilla