Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  H265.cpp

  Sprache: C
 

/* 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 "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/Span.h"
#include "nsFmtString.h"

mozilla::LazyLogModule gH265("H265");

#define LOG(msg, ...) MOZ_LOG_FMT(gH265, LogLevel::Debug, msg, ##__VA_ARGS__)
#define LOGV(msg, ...) MOZ_LOG_FMT(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);
  (void)reader.ReadBit();  // forbidden_zero_bit
  mNalUnitType = reader.ReadBits(6);
  mNuhLayerId = reader.ReadBits(6);
  mNuhTemporalIdPlus1 = reader.ReadBits(3);
  LOGV("Created H265NALU, type={}, size={}", 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 {}", aSample->Size());
    return mozilla::Err(NS_ERROR_FAILURE);
  }
  if (aSample->mTrackInfo &&
      !aSample->mTrackInfo->mMimeType.EqualsLiteral("video/hevc")) {
    LOG("Only allow 'video/hevc' (mimeType={})",
        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 {}", 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);
  (void)reader.ReadBits(4);  // reserved
  hvcc.min_spatial_segmentation_idc = reader.ReadBits(12);
  (void)reader.ReadBits(6);  // reserved
  hvcc.parallelismType = reader.ReadBits(2);
  (void)reader.ReadBits(6);  // reserved
  hvcc.chroma_format_idc = reader.ReadBits(2);
  (void)reader.ReadBits(5);  // reserved
  hvcc.bit_depth_luma_minus8 = reader.ReadBits(3);
  (void)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++) {
    (void)reader.ReadBits(2);  // array_completeness + reserved
    const uint8_t nalUnitType = reader.ReadBits(6);
    const uint16_t numNalus = reader.ReadBits(16);
    LOGV("nalu-type={}, nalu-num={}", 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 ({} bits) is larger than remaining "
            "({} 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);
      uint32_t nalBitsLength = nalUnitLength * 8;
      (void)reader.AdvanceBits(nalBitsLength);
      // 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 ({}) 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;
}

nsCString HVCCConfig::ToString() const {
  return nsFmtCString(
      "HVCCConfig - version={}, profile_space={}, tier={}, "
      "profile_idc={}, profile_compatibility_flags={:#08x}, "
      "constraint_indicator_flags={:#016x}, level_idc={}, "
      "min_spatial_segmentation_idc={}, parallelismType={}, "
      "chroma_format_idc={}, bit_depth_luma_minus8={}, "
      "bit_depth_chroma_minus8={}, avgFrameRate={}, constantFrameRate={}, "
      "numTemporalLayers={}, temporalIdNested={}, lengthSizeMinusOne={}, "
      "nalus={}, buffer={}(bytes), NaluSize={}, NumSPS={}",
      configurationVersion, general_profile_space, general_tier_flag,
      general_profile_idc, general_profile_compatibility_flags,
      general_constraint_indicator_flags, general_level_idc,
      min_spatial_segmentation_idc, parallelismType, chroma_format_idc,
      bit_depth_luma_minus8, bit_depth_chroma_minus8, avgFrameRate,
      constantFrameRate, numTemporalLayers, temporalIdNested,
      lengthSizeMinusOne, mNALUs.Length(),
      mByteBuffer ? mByteBuffer->Length() : 0, NALUSize(), NumSPS());
}

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, 015);
  sps.sps_max_sub_layers_minus1 = reader.ReadBits(3);
  IN_RANGE_OR_RETURN(sps.sps_max_sub_layers_minus1, 06);
  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, 015);
  sps.chroma_format_idc = reader.ReadUE();
  IN_RANGE_OR_RETURN(sps.chroma_format_idc, 03);

  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) / 316u);
    } 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();
    // The following formulas are specified under the definition of
    // `conf_win_xxx_offset` in the spec.
    CheckedUint32 width = sps.pic_width_in_luma_samples;
    width -=
        sps.subWidthC * (sps.conf_win_right_offset - sps.conf_win_left_offset);
    if (!width.isValid()) {
      LOG("width overflow when applying the conformance window!");
      return Err(NS_ERROR_FAILURE);
    }
    IN_RANGE_OR_RETURN(width.value(), 0, sps.pic_width_in_luma_samples);
    CheckedUint32 height = sps.pic_height_in_luma_samples;
    height -=
        sps.subHeightC * (sps.conf_win_bottom_offset - sps.conf_win_top_offset);
    if (!height.isValid()) {
      LOG("height overflow when applying the conformance window!");
      return Err(NS_ERROR_FAILURE);
    }
    IN_RANGE_OR_RETURN(height.value(), 0, sps.pic_height_in_luma_samples);
    // These values specify the width and height of the cropped image.
    sps.mCroppedWidth = Some(width.value());
    sps.mCroppedHeight = Some(height.value());
  }
  sps.bit_depth_luma_minus8 = reader.ReadUE();
  IN_RANGE_OR_RETURN(sps.bit_depth_luma_minus8, 08);
  sps.bit_depth_chroma_minus8 = reader.ReadUE();
  IN_RANGE_OR_RETURN(sps.bit_depth_chroma_minus8, 08);
  sps.log2_max_pic_order_cnt_lsb_minus4 = reader.ReadUE();
  IN_RANGE_OR_RETURN(sps.log2_max_pic_order_cnt_lsb_minus4, 012);
  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], 00xFFFFFFFE);
  }
  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) {
    const auto sps_scaling_list_data_present_flag = reader.ReadBit();
    if (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
  (void)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, 02);
    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++) {
      (void)reader.ReadBits(sps.log2_max_pic_order_cnt_lsb_minus4 +
                            4);  // lt_ref_pic_poc_lsb_sps[i]
      (void)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, 011);
    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
    (void)aReader.ReadBits(32);
    (void)aReader.ReadBits(11);
    // general_inbld_flag or general_reserved_zero_bit
    (void)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
      (void)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
      (void)aReader.ReadBits(8);
      // sub_layer_profile_compatibility_flag
      (void)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
      (void)aReader.ReadBits(4);
      // ignored attributes, in total general_reserved_zero_43bits
      (void)aReader.ReadBits(32);
      (void)aReader.ReadBits(11);
      // sub_layer_inbld_flag or reserved_zero_bit
      (void)aReader.ReadBit();
    }
    if (sub_layer_level_present_flag[i]) {
      (void)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) {
        (void)aReader.ReadUE();  // scaling_list_pred_matrix_id_delta
      } else {
        int32_t coefNum = std::min(64, (1 << (4 + (sizeIdx << 1))));
        if (sizeIdx > 1) {
          (void)aReader.ReadSE();  // scaling_list_dc_coef_minus8
        }
        for (auto i = 0; i < coefNum; i++) {
          (void)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();
  }
  const uint32_t spsMaxDecPicBufferingMinus1 =
      aSPS.sps_max_dec_pic_buffering_minus1[aSPS.sps_max_sub_layers_minus1];
  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, 00x7FFF);
    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;
    // 7.4.8 - num_negative_pics shall be in the range of 0 to
    // sps_max_dec_pic_buffering_minus1[sps_max_sub_layers_minus1], inclusive.
    // num_positive_pics shall be in the range of 0 to
    // sps_max_dec_pic_buffering_minus1[sps_max_sub_layers_minus1] -
    // num_negative_pics, inclusive.
    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());
  } else {
    curStRefPicSet.num_negative_pics = aReader.ReadUE();
    curStRefPicSet.num_positive_pics = aReader.ReadUE();
    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, 00x7FFF);
      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, 00x7FFF);
      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;
  // 7.4.8 - NumDeltaPocs = num_negative_pics + num_positive_pics counts DPB
  // entries, bounded by sps_max_dec_pic_buffering_minus1.
  IN_RANGE_OR_RETURN(curStRefPicSet.numDeltaPocs, 0,
                     spsMaxDecPicBufferingMinus1);
  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,  121016,  40242032,
                                           801815641604,  3,  2};
  static constexpr int kTableSarHeight[] = {0,  1,  11111133111111,
                                            33111133993,  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) {
    (void)aReader.ReadBit();  // overscan_appropriate_flag
  }

  const auto video_signal_type_present_flag = aReader.ReadBit();
  if (video_signal_type_present_flag) {
    (void)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) {
    (void)aReader.ReadUE();  // chroma_sample_loc_type_top_field
    (void)aReader.ReadUE();  // chroma_sample_loc_type_bottom_field
  }

  // Ignore neutral_chroma_indication_flag, field_seq_flag and
  // frame_field_info_present_flag.
  (void)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) {
    (void)aReader.ReadU32();  // vui_num_units_in_tick
    (void)aReader.ReadU32();  // vui_time_scale
    const auto vui_poc_proportional_to_timing_flag = aReader.ReadBit();
    if (vui_poc_proportional_to_timing_flag) {
      (void)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.
    (void)aReader.ReadBits(3);
    (void)aReader.ReadUE();  // min_spatial_segmentation_idc
    (void)aReader.ReadUE();  // max_bytes_per_pic_denom
    (void)aReader.ReadUE();  // max_bits_per_min_cu_denom
    (void)aReader.ReadUE();  // log2_max_mv_length_horizontal
    (void)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) {
        (void)aReader.ReadBits(8);  // tick_divisor_minus2
        // du_cpb_removal_delay_increment_length_minus1
        (void)aReader.ReadBits(5);
        // sub_pic_cpb_params_in_pic_timing_sei_flag
        (void)aReader.ReadBits(1);
        (void)aReader.ReadBits(5);  // dpb_output_delay_du_length_minus1
      }

      (void)aReader.ReadBits(4);  // bit_rate_scale
      (void)aReader.ReadBits(4);  // cpb_size_scale
      if (sub_pic_hrd_params_present_flag) {
        (void)aReader.ReadBits(4);  // cpb_size_du_scale
      }
      (void)aReader.ReadBits(5);  // initial_cpb_removal_delay_length_minus1
      (void)aReader.ReadBits(5);  // au_cpb_removal_delay_length_minus1
      (void)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) {
      (void)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, 031);
    }
    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++) {
    (void)aReader.ReadUE();  // bit_rate_value_minus1
    (void)aReader.ReadUE();  // cpb_size_value_minus1
    if (aSubPicHrdParamsPresentFlag) {
      (void)aReader.ReadUE();  // cpb_size_du_value_minus1
      (void)aReader.ReadUE();  // bit_rate_du_value_minus1
    }
    (void)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 {
  if (mCroppedWidth && mCroppedHeight) {
    return gfx::IntSize(*mCroppedWidth, *mCroppedHeight);
  }
  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 */
mozilla::Maybe<mozilla::gfx::HDRMetadata> H265::ParseSEIHDRMetadata(
    const H265NALU& aNALU) {
  MOZ_ASSERT(aNALU.mNalUnitType == H265NALU::NAL_TYPES::PREFIX_SEI_NUT ||
             aNALU.mNalUnitType == H265NALU::NAL_TYPES::SUFFIX_SEI_NUT);

  RefPtr<MediaByteBuffer> rbsp = H265::DecodeNALUnit(aNALU.mNALU);
  if (!rbsp) {
    return Nothing();
  }

  const Span<const uint8_t> data(rbsp->Elements(), rbsp->Length());
  size_t offset = 0;

  // SEI type 137 (mastering display colour volume) and type 144 (content light
  // level info) are defined in ITU-T H.265 (08/2021) §D.3.26 and §D.3.27.
  static constexpr uint8_t kSEIMasteringDisplayType = 137;
  static constexpr uint8_t kSEIContentLightLevelType = 144;
  // Fixed-point conversion factors per ITU-T H.265 (08/2021) §D.3.26:
  static constexpr float kPrimariesDivisor = 50000.0f;
  static constexpr float kLuminanceDivisor = 10000.0f;

  gfx::HDRMetadata hdr;
  bool hasMasteringDisplay = false;
  bool hasCLL = false;

  while (offset < data.Length()) {
    // rbsp_trailing_bits() ends with a stop bit (0x80) per H.265 §7.4.1.
    if (offset + 1 == data.Length() && data[offset] == 0x80) {
      break;
    }

    // SEI payloadType and payloadSize use 0xff-prefix multi-byte encoding
    // (ff_byte in ITU-T H.265 §7.3.2.4): accumulate 0xff-valued bytes, then
    // add the terminating non-0xff byte.
    size_t payloadType = 0;
    while (offset < data.Length() && data[offset] == 0xff) {
      payloadType += 0xff;
      offset++;
    }
    if (offset >= data.Length()) {
      break;
    }
    payloadType += data[offset++];

    size_t payloadSize = 0;
    while (offset < data.Length() && data[offset] == 0xff) {
      payloadSize += 0xff;
      offset++;
    }
    if (offset >= data.Length()) {
      break;
    }
    payloadSize += data[offset++];

    if (offset + payloadSize > data.Length()) {
      break;
    }

    if (payloadType == kSEIMasteringDisplayType) {
      if (payloadSize != 24) {
        NS_WARNING("H265 SEI mastering display: unexpected payload size");
        offset += payloadSize;
        continue;
      }
      // ITU-T H.265 (08/2021) §D.3.26 Table D.2 primary order: G[0], B[1], R[2]
      // Each chromaticity is u16 big-endian / 50000.0f
      BufferReader br(data.Elements() + offset, payloadSize);
      auto g0x = br.ReadU16();
      auto g0y = br.ReadU16();
      auto b1x = br.ReadU16();
      auto b1y = br.ReadU16();
      auto r2x = br.ReadU16();
      auto r2y = br.ReadU16();
      auto wpx = br.ReadU16();
      auto wpy = br.ReadU16();
      auto maxL = br.ReadU32();
      auto minL = br.ReadU32();
      if (g0x.isErr() || g0y.isErr() || b1x.isErr() || b1y.isErr() ||
          r2x.isErr() || r2y.isErr() || wpx.isErr() || wpy.isErr() ||
          maxL.isErr() || minL.isErr()) {
        LOG("H265 SEI mastering display: failed to read fields");
        offset += payloadSize;
        continue;
      }
      gfx::Chromaticity green{g0x.unwrap() / kPrimariesDivisor,
                              g0y.unwrap() / kPrimariesDivisor};
      gfx::Chromaticity blue{b1x.unwrap() / kPrimariesDivisor,
                             b1y.unwrap() / kPrimariesDivisor};
      gfx::Chromaticity red{r2x.unwrap() / kPrimariesDivisor,
                            r2y.unwrap() / kPrimariesDivisor};
      gfx::Chromaticity whitePoint{wpx.unwrap() / kPrimariesDivisor,
                                   wpy.unwrap() / kPrimariesDivisor};
      float maxLuminance = maxL.unwrap() / kLuminanceDivisor;
      float minLuminance = minL.unwrap() / kLuminanceDivisor;

      hdr.mSmpte2086 = Some(gfx::Smpte2086Metadata{red, green, blue, whitePoint,
                                                   maxLuminance, minLuminance});
      hasMasteringDisplay = true;
    } else if (payloadType == kSEIContentLightLevelType) {
      if (payloadSize != 4) {
        NS_WARNING("H265 SEI content light level: unexpected payload size");
        offset += payloadSize;
        continue;
      }
      BufferReader br(data.Elements() + offset, payloadSize);
      auto maxCLL = br.ReadU16();
      auto maxFALL = br.ReadU16();
      if (maxCLL.isErr() || maxFALL.isErr()) {
        LOG("H265 SEI content light level: failed to read fields");
        offset += payloadSize;
        continue;
      }
      hdr.mContentLightLevel =
          Some(gfx::ContentLightLevel{maxCLL.unwrap(), maxFALL.unwrap()});
      hasCLL = true;
    }

    offset += payloadSize;
  }

  if (!hasMasteringDisplay && !hasCLL) {
    return Nothing();
  }
  MOZ_ASSERT(hdr.IsValid());
  return Some(hdr);
}

#ifdef MOZ_WMF
static void AppendSEIInt(MediaByteBuffer* aDest, size_t aValue) {
  while (aValue >= 0xff) {
    aDest->AppendElement(0xff);
    aValue -= 0xff;
  }
  aDest->AppendElement(static_cast<uint8_t>(aValue));
}

static already_AddRefed<MediaByteBuffer> EncodeH265NALUnit(
    const uint8_t* aHeader, const MediaByteBuffer* aRBSP) {
  RefPtr<MediaByteBuffer> nalu = new MediaByteBuffer;
  nalu->AppendElements(aHeader, 2);
  BufferReader reader(aRBSP);
  while (reader.Remaining()) {
    auto res = reader.ReadU8();
    if (res.isErr()) {
      return nullptr;
    }
    uint8_t b = res.unwrap();
    if (b <= 0x03 && nalu->ElementAt(nalu->Length() - 2) == 0 &&
        nalu->ElementAt(nalu->Length() - 1) == 0) {
      nalu->AppendElement(0x03);
    }
    nalu->AppendElement(b);
  }
  return nalu.forget();
}

/* static */
already_AddRefed<mozilla::MediaByteBuffer> H265::FilterPrefixSEIForWindows(
    const H265NALU& aNALU) {
  MOZ_ASSERT(aNALU.mNalUnitType == H265NALU::NAL_TYPES::PREFIX_SEI_NUT);
  // user_data_unregistered (H.265 Annex D.2.1, payloadType=5) carries no
  // normative decoding information — H.265 Annex D.1 explicitly states that
  // decoders are not required to process SEI for output conformance, and
  // Table D.1 lists its persistence scope as "Unspecified". Stripping it is
  // safe while preserving other SEI types (e.g. HDR metadata, buffering
  // period) that do carry normative data.
  static constexpr uint8_t kSEIUserDataUnregisteredPayloadType = 5;

  RefPtr<MediaByteBuffer> rbsp = H265::DecodeNALUnit(aNALU.mNALU);
  if (!rbsp) {
    return nullptr;
  }

  const Span<const uint8_t> data(rbsp->Elements(), rbsp->Length());
  RefPtr<MediaByteBuffer> filteredRBSP = new MediaByteBuffer;
  size_t offset = 0;
  while (offset < data.Length()) {
    if (offset + 1 == data.Length() && data[offset] == 0x80) {
      break;
    }

    size_t payloadType = 0;
    while (offset < data.Length() && data[offset] == 0xff) {
      payloadType += 0xff;
      offset++;
    }
    if (offset >= data.Length()) {
      return nullptr;
    }
    payloadType += data[offset++];

    size_t payloadSize = 0;
    while (offset < data.Length() && data[offset] == 0xff) {
      payloadSize += 0xff;
      offset++;
    }
    if (offset >= data.Length()) {
      return nullptr;
    }
    payloadSize += data[offset++];

    if (offset + payloadSize > data.Length()) {
      return nullptr;
    }
    if (payloadType != kSEIUserDataUnregisteredPayloadType) {
      AppendSEIInt(filteredRBSP, payloadType);
      AppendSEIInt(filteredRBSP, payloadSize);
      filteredRBSP->AppendElements(data.Elements() + offset, payloadSize);
    }

    offset += payloadSize;
  }

  if (filteredRBSP->IsEmpty()) {
    return nullptr;
  }

  filteredRBSP->AppendElement(0x80);
  return EncodeH265NALUnit(aNALU.mNALU.Elements(), filteredRBSP);
}
#endif

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

  nsTHashMap<uint8_t, nsTArray<H265NALU>> nalusMap;

  nsTArray<Maybe<H265SPS>> spsRefTable;
  // If we encounter SPS with the same id but different content, we will stop
  // attempting to detect duplicates.
  bool checkDuplicate = true;
  Maybe<uint8_t> firstSPSId;

  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:
        (void)reader.ReadU8().map(
            [&](uint8_t x) mutable { return nalLen = x; });
        break;
      case 2:
        (void)reader.ReadU16().map(
            [&](uint16_t x) mutable { return nalLen = x; });
        break;
      case 3:
        (void)reader.ReadU24().map(
            [&](uint32_t x) mutable { return nalLen = x; });
        break;
      default:
        MOZ_DIAGNOSTIC_ASSERT(nalLenSize == 4);
        (void)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={}", 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);
        nalusMap.LookupOrInsert(nalu.mNalUnitType).AppendElement(nalu);
        if (!firstSPSId) {
          firstSPSId.emplace(spsId);
        }
      }
    } else if (nalu.IsVPS() || nalu.IsPPS()) {
      nalusMap.LookupOrInsert(nalu.mNalUnitType).AppendElement(nalu);
    }
  }

  auto spsEntry = nalusMap.Lookup(H265NALU::SPS_NUT);
  auto vpsEntry = nalusMap.Lookup(H265NALU::VPS_NUT);
  auto ppsEntry = nalusMap.Lookup(H265NALU::PPS_NUT);

  LOGV("Found {} SPS NALU, {} VPS NALU, {} PPS NALU",
       spsEntry ? spsEntry.Data().Length() : 0,
       vpsEntry ? vpsEntry.Data().Length() : 0,
       ppsEntry ? ppsEntry.Data().Length() : 0);
  if (firstSPSId) {
    BitWriter writer(extradata);
    const H265SPS* firstSPS = spsRefTable[*firstSPSId].ptr();
    MOZ_ASSERT(firstSPS);

    // ISO/IEC 14496-15, HEVCDecoderConfigurationRecord.
    writer.WriteBits(18);  // 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(044); /* ignored 44 bits */

    writer.WriteU8(profile.general_level_idc);
    writer.WriteBits(04);   // reserved
    writer.WriteBits(012);  // min_spatial_segmentation_idc
    writer.WriteBits(06);   // reserved
    writer.WriteBits(02);   // parallelismType
    writer.WriteBits(06);   // reserved
    writer.WriteBits(firstSPS->chroma_format_idc, 2);
    writer.WriteBits(05);  // reserved
    writer.WriteBits(firstSPS->bit_depth_luma_minus8, 3);
    writer.WriteBits(05);  // reserved
    writer.WriteBits(firstSPS->bit_depth_chroma_minus8, 3);
    // avgFrameRate + constantFrameRate + numTemporalLayers + temporalIdNested
    writer.WriteBits(022);
    writer.WriteBits(nalLenSize - 12);  // lengthSizeMinusOne
    writer.WriteU8(static_cast<uint8_t>(nalusMap.Count()));  // numOfArrays

    // Append NALUs sorted by key value for easier extradata verification in
    // tests
    auto keys = ToTArray<nsTArray<uint8_t>>(nalusMap.Keys());
    keys.Sort();

    for (const uint8_t& naluType : keys) {
      auto entry = nalusMap.Lookup(naluType);
      const auto& naluArray = entry.Data();
      writer.WriteBits(02);         // array_completeness + reserved
      writer.WriteBits(naluType, 6);  // NAL_unit_type
      writer.WriteBits(naluArray.Length(), 16);  // numNalus
      for (const auto& nalu : naluArray) {
        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());
      }
    }
  }

  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 != 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(18);               // version
  writer.WriteBits(02);               // general_profile_space
  writer.WriteBits(01);               // general_tier_flag
  writer.WriteBits(1 /* main */, 5);    // general_profile_idc
  writer.WriteU32(0);                   // general_profile_compatibility_flags
  writer.WriteBits(048);              // general_constraint_indicator_flags
  writer.WriteU8(1 /* level 1 */);      // general_level_idc
  writer.WriteBits(04);               // reserved
  writer.WriteBits(012);              // min_spatial_segmentation_idc
  writer.WriteBits(06);               // reserved
  writer.WriteBits(02);               // parallelismType
  writer.WriteBits(06);               // reserved
  writer.WriteBits(02);               // chroma_format_idc
  writer.WriteBits(05);               // reserved
  writer.WriteBits(03);               // bit_depth_luma_minus8
  writer.WriteBits(05);               // reserved
  writer.WriteBits(03);               // bit_depth_chroma_minus8
  writer.WriteBits(022);              // avgFrameRate + constantFrameRate +
                                        // numTemporalLayers + temporalIdNested
  writer.WriteBits(nalLenSize - 12);  // lengthSizeMinusOne
  writer.WriteU8(nalus.Length());       // numOfArrays
  for (auto& nalu : nalus) {
    writer.WriteBits(02);                     // array_completeness + reserved
    writer.WriteBits(nalu.mNalUnitType, 6);     // NAL_unit_type
    writer.WriteBits(116);                    // 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 nsTArray<H265NALU>& aNALUs) {
  // 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(04);  // reserved
  writer.WriteBits(aConfig.min_spatial_segmentation_idc, 12);
  writer.WriteBits(06);  // reserved
  writer.WriteBits(aConfig.parallelismType, 2);
  writer.WriteBits(06);  // reserved
  writer.WriteBits(aConfig.chroma_format_idc, 2);
  writer.WriteBits(05);  // reserved
  writer.WriteBits(aConfig.bit_depth_luma_minus8, 3);
  writer.WriteBits(05);  // 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(aNALUs.Length());  // numOfArrays
  for (auto& nalu : aNALUs) {
    writer.WriteBits(02);                     // array_completeness + reserved
    writer.WriteBits(nalu.mNalUnitType, 6);     // NAL_unit_type
    writer.WriteBits(116);                    // 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 */
Result<bool, nsresult> H265::IsKeyFrame(const mozilla::MediaRawData* aSample) {
  if (aSample->mCrypto.IsEncrypted()) {
    LOG("Can't check if encrypted sample is keyframe");
    return Err(NS_ERROR_DOM_MEDIA_DEMUXER_ERR);
  }

  size_t sampleSize = aSample->Size();
  auto hvcc = HVCCConfig::Parse(aSample);
  if (hvcc.isErr()) {
    LOG("Only support extracting extradata from HVCC");
    return Err(NS_ERROR_DOM_MEDIA_DEMUXER_ERR);
  }
  const auto nalLenSize = hvcc.unwrap().NALUSize();
  BufferReader reader(aSample->Data(), sampleSize);
  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:
        (void)reader.ReadU8().map(
            [&](uint8_t x) mutable { return nalLen = x; });
        break;
      case 2:
        (void)reader.ReadU16().map(
            [&](uint16_t x) mutable { return nalLen = x; });
        break;
      case 3:
        (void)reader.ReadU24().map(
            [&](uint32_t x) mutable { return nalLen = x; });
        break;
      default:
        MOZ_DIAGNOSTIC_ASSERT(nalLenSize == 4);
        (void)reader.ReadU32().map(
            [&](uint32_t x) mutable { return nalLen = x; });
        break;
    }
    const uint8_t* p = reader.Read(nalLen);
    if (!p) {
      break;
    }
    const H265NALU nalu(p, nalLen);
    if (nalu.IsIframe()) {
      return true;
    }
  }
  return false;
}

#undef LOG
#undef LOGV

}  // namespace mozilla

Messung V0.5 in Prozent
C=96 H=90 G=93

¤ Dauer der Verarbeitung: 0.26 Sekunden  (vorverarbeitet am  2026-08-26) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Open Source Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Statistik
#Sources=277311
#Domains=655579