Source: lib/transmuxer/h265.js

/*! @license
 * Shaka Player
 * Copyright 2016 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

goog.provide('shaka.transmuxer.H265');

goog.require('shaka.util.ExpGolomb');
goog.require('shaka.util.Uint8ArrayUtils');


/**
 * H.265 utils
 */
shaka.transmuxer.H265 = class {
  /**
   * Read a sequence parameter set and return some interesting video
   * properties. A sequence parameter set is the H265 metadata that
   * describes the properties of upcoming video frames.
   *
   * @param {!Array.<shaka.extern.VideoNalu>} nalus
   * @return {?{height: number, width: number, videoConfig: !Uint8Array,
   *            hSpacing: number, vSpacing: number}}
   */
  static parseInfo(nalus) {
    const H265 = shaka.transmuxer.H265;
    if (!nalus.length) {
      return null;
    }
    const vpsNalu = nalus.find((nalu) => {
      return nalu.type == H265.NALU_TYPE_VPS_;
    });
    const spsNalu = nalus.find((nalu) => {
      return nalu.type == H265.NALU_TYPE_SPS_;
    });
    const ppsNalu = nalus.find((nalu) => {
      return nalu.type == H265.NALU_TYPE_PPS_;
    });
    if (!vpsNalu || !spsNalu || !ppsNalu) {
      return null;
    }

    const vpsConfiguration = H265.parseVPS_(vpsNalu.fullData);
    const spsConfiguration = H265.parseSPS_(spsNalu.fullData);
    const ppsConfiguration = H265.parsePPS_(ppsNalu.fullData);

    /** @type {shaka.transmuxer.H265.DecoderConfigurationRecordType} */
    const detail = {
      numTemporalLayers: vpsConfiguration.numTemporalLayers,
      temporalIdNested: vpsConfiguration.temporalIdNested,
      generalProfileSpace: spsConfiguration.generalProfileSpace,
      generalTierFlag: spsConfiguration.generalTierFlag,
      generalLevelIdc: spsConfiguration.generalLevelIdc,
      generalProfileIdc: spsConfiguration.generalProfileIdc,
      generalProfileCompatibilityFlags1:
          spsConfiguration.generalProfileCompatibilityFlags1,
      generalProfileCompatibilityFlags2:
          spsConfiguration.generalProfileCompatibilityFlags2,
      generalProfileCompatibilityFlags3:
          spsConfiguration.generalProfileCompatibilityFlags3,
      generalProfileCompatibilityFlags4:
          spsConfiguration.generalProfileCompatibilityFlags4,
      generalConstraintIndicatorFlags1:
          spsConfiguration.generalConstraintIndicatorFlags1,
      generalConstraintIndicatorFlags2:
          spsConfiguration.generalConstraintIndicatorFlags2,
      generalConstraintIndicatorFlags3:
          spsConfiguration.generalConstraintIndicatorFlags3,
      generalConstraintIndicatorFlags4:
          spsConfiguration.generalConstraintIndicatorFlags4,
      generalConstraintIndicatorFlags5:
          spsConfiguration.generalConstraintIndicatorFlags5,
      generalConstraintIndicatorFlags6:
          spsConfiguration.generalConstraintIndicatorFlags6,
      constantFrameRate: spsConfiguration.constantFrameRate,
      minSpatialSegmentationIdc: spsConfiguration.minSpatialSegmentationIdc,
      chromaFormatIdc: spsConfiguration.chromaFormatIdc,
      bitDepthLumaMinus8: spsConfiguration.bitDepthLumaMinus8,
      bitDepthChromaMinus8: spsConfiguration.bitDepthChromaMinus8,
      parallelismType: ppsConfiguration.parallelismType,
    };

    const videoConfig = H265.getVideoConfiguration_(
        vpsNalu.fullData, spsNalu.fullData, ppsNalu.fullData, detail);

    return {
      height: spsConfiguration.height,
      width: spsConfiguration.width,
      videoConfig,
      hSpacing: spsConfiguration.sarWidth,
      vSpacing: spsConfiguration.sarHeight,
    };
  }

  /**
   * @param {!Uint8Array} data
   * @return {shaka.transmuxer.H265.VPSConfiguration}
   * @private
   */
  static parseVPS_(data) {
    const gb = new shaka.util.ExpGolomb(data, /* convertEbsp2rbsp= */ true);

    // remove NALu Header
    gb.readUnsignedByte();
    gb.readUnsignedByte();

    // VPS
    gb.readBits(4); // video_parameter_set_id
    gb.readBits(2);
    gb.readBits(6); // max_layers_minus1
    const maxSubLayersMinus1 = gb.readBits(3);
    const temporalIdNestingFlag = gb.readBoolean();

    return {
      numTemporalLayers: maxSubLayersMinus1 + 1,
      temporalIdNested: temporalIdNestingFlag,
    };
  }

  /**
   * The code is based on mpegts.js
   * https://github.com/xqq/mpegts.js/blob/master/src/demux/h265-parser.js#L65
   *
   * @param {!Uint8Array} data
   * @return {shaka.transmuxer.H265.SPSConfiguration}
   * @private
   */
  static parseSPS_(data) {
    const gb = new shaka.util.ExpGolomb(data, /* convertEbsp2rbsp= */ true);

    // remove NALu Header
    gb.readUnsignedByte();
    gb.readUnsignedByte();

    let leftOffset = 0;
    let rightOffset = 0;
    let topOffset = 0;
    let bottomOffset = 0;

    // SPS
    gb.readBits(4); // video_paramter_set_id
    const maxSubLayersMinus1 = gb.readBits(3);
    gb.readBoolean(); // temporal_id_nesting_flag

    // profile_tier_level begin
    const generalProfileSpace = gb.readBits(2);
    const generalTierFlag = gb.readBits(1);
    const generalProfileIdc = gb.readBits(5);
    const generalProfileCompatibilityFlags1 = gb.readUnsignedByte();
    const generalProfileCompatibilityFlags2 = gb.readUnsignedByte();
    const generalProfileCompatibilityFlags3 = gb.readUnsignedByte();
    const generalProfileCompatibilityFlags4 = gb.readUnsignedByte();
    const generalConstraintIndicatorFlags1 = gb.readUnsignedByte();
    const generalConstraintIndicatorFlags2 = gb.readUnsignedByte();
    const generalConstraintIndicatorFlags3 = gb.readUnsignedByte();
    const generalConstraintIndicatorFlags4 = gb.readUnsignedByte();
    const generalConstraintIndicatorFlags5 = gb.readUnsignedByte();
    const generalConstraintIndicatorFlags6 = gb.readUnsignedByte();
    const generalLevelIdc = gb.readUnsignedByte();
    const subLayerProfilePresentFlag = [];
    const subLayerLevelPresentFlag = [];
    for (let i = 0; i < maxSubLayersMinus1; i++) {
      subLayerProfilePresentFlag.push(gb.readBoolean());
      subLayerLevelPresentFlag.push(gb.readBoolean());
    }
    if (maxSubLayersMinus1 > 0) {
      for (let i = maxSubLayersMinus1; i < 8; i++) {
        gb.readBits(2);
      }
    }
    for (let i = 0; i < maxSubLayersMinus1; i++) {
      if (subLayerProfilePresentFlag[i]) {
        gb.readBits(88);
      }
      if (subLayerLevelPresentFlag[i]) {
        gb.readUnsignedByte();
      }
    }
    // profile_tier_level end

    gb.readUnsignedExpGolomb(); // seq_parameter_set_id
    const chromaFormatIdc = gb.readUnsignedExpGolomb();
    if (chromaFormatIdc == 3) {
      gb.readBits(1); // separate_colour_plane_flag
    }
    const picWidthInLumaSamples = gb.readUnsignedExpGolomb();
    const picHeightInLumaSamples = gb.readUnsignedExpGolomb();
    const conformanceWindowFlag = gb.readBoolean();
    if (conformanceWindowFlag) {
      leftOffset += gb.readUnsignedExpGolomb();
      rightOffset += gb.readUnsignedExpGolomb();
      topOffset += gb.readUnsignedExpGolomb();
      bottomOffset += gb.readUnsignedExpGolomb();
    }
    const bitDepthLumaMinus8 = gb.readUnsignedExpGolomb();
    const bitDepthChromaMinus8 = gb.readUnsignedExpGolomb();
    const log2MaxPicOrderCntLsbMinus4 = gb.readUnsignedExpGolomb();
    const subLayerOrderingInfoPresentFlag = gb.readBoolean();
    if (subLayerOrderingInfoPresentFlag) {
      // Skip each layer
      for (let i = 0; i <= maxSubLayersMinus1; i++) {
        gb.readUnsignedExpGolomb(); // max_dec_pic_buffering_minus1[i]
        gb.readUnsignedExpGolomb(); // max_num_reorder_pics[i]
        gb.readUnsignedExpGolomb(); // max_latency_increase_plus1[i]
      }
    } else {
      // Skip one layer
      gb.readUnsignedExpGolomb(); // max_dec_pic_buffering_minus1[i]
      gb.readUnsignedExpGolomb(); // max_num_reorder_pics[i]
      gb.readUnsignedExpGolomb(); // max_latency_increase_plus1[i]
    }
    gb.readUnsignedExpGolomb(); // log2_min_luma_coding_block_size_minus3
    gb.readUnsignedExpGolomb(); // log2_diff_max_min_luma_coding_block_size
    gb.readUnsignedExpGolomb(); // log2_min_transform_block_size_minus2
    gb.readUnsignedExpGolomb(); // log2_diff_max_min_transform_block_size
    gb.readUnsignedExpGolomb(); // max_transform_hierarchy_depth_inter
    gb.readUnsignedExpGolomb(); // max_transform_hierarchy_depth_intra
    const scalingListEnabledFlag = gb.readBoolean();
    if (scalingListEnabledFlag) {
      const spsScalingListDataPresentFlag = gb.readBoolean();
      if (spsScalingListDataPresentFlag) {
        for (let sizeId = 0; sizeId < 4; sizeId++) {
          for (
            let matrixId = 0;
            matrixId < (sizeId === 3 ? 2 : 6);
            matrixId++
          ) {
            const scalingListPredModeFlag = gb.readBoolean();
            if (!scalingListPredModeFlag) {
              gb.readUnsignedExpGolomb(); // scaling_list_pred_matrix_id_delta
            } else {
              const coefNum = Math.min(64, 1 << (4 + (sizeId << 1)));
              if (sizeId > 1) {
                gb.readExpGolomb();
              }
              for (let i = 0; i < coefNum; i++) {
                gb.readExpGolomb();
              }
            }
          }
        }
      }
    }
    gb.readBoolean(); // amp_enabled_flag
    gb.readBoolean(); // sample_adaptive_offset_enabled_flag
    const pcmEnabledFlag = gb.readBoolean();
    if (pcmEnabledFlag) {
      gb.readUnsignedByte();
      gb.readUnsignedExpGolomb();
      gb.readUnsignedExpGolomb();
      gb.readBoolean();
    }
    const numShortTermRefPicSets = gb.readUnsignedExpGolomb();
    let numDeltaPocs = 0;
    for (let i = 0; i < numShortTermRefPicSets; i++) {
      let interRefPicSetPredictionFlag = false;
      if (i !== 0) {
        interRefPicSetPredictionFlag = gb.readBoolean();
      }
      if (interRefPicSetPredictionFlag) {
        if (i === numShortTermRefPicSets) {
          gb.readUnsignedExpGolomb();
        }
        gb.readBoolean();
        gb.readUnsignedExpGolomb();
        let nextNumDeltaPocs = 0;
        for (let j = 0; j <= numDeltaPocs; j++) {
          const usedByCurrPicFlag = gb.readBoolean();
          let useDeltaFlag = false;
          if (!usedByCurrPicFlag) {
            useDeltaFlag = gb.readBoolean();
          }
          if (usedByCurrPicFlag || useDeltaFlag) {
            nextNumDeltaPocs++;
          }
        }
        numDeltaPocs = nextNumDeltaPocs;
      } else {
        const numNegativePics = gb.readUnsignedExpGolomb();
        const numPositivePics = gb.readUnsignedExpGolomb();
        numDeltaPocs = numNegativePics + numPositivePics;
        for (let j = 0; j < numNegativePics; j++) {
          gb.readUnsignedExpGolomb();
          gb.readBoolean();
        }
        for (let j = 0; j < numPositivePics; j++) {
          gb.readUnsignedExpGolomb();
          gb.readBoolean();
        }
      }
    }
    const longTermRefPicsPresentFlag = gb.readBoolean();
    if (longTermRefPicsPresentFlag) {
      const numLongTermRefPicsSps = gb.readUnsignedExpGolomb();
      for (let i = 0; i < numLongTermRefPicsSps; i++) {
        for (let j = 0; j < log2MaxPicOrderCntLsbMinus4 + 4; j++) {
          gb.readBits(1);
        }
        gb.readBits(1);
      }
    }
    let defaultDisplayWindowFlag = false; // for calc offset
    let sarWidth = 1;
    let sarHeight = 1;
    let minSpatialSegmentationIdc = 0; // for hvcC
    gb.readBoolean(); // sps_temporal_mvp_enabled_flag
    gb.readBoolean(); // strong_intra_smoothing_enabled_flag
    const vuiParametersPresentFlag = gb.readBoolean();
    if (vuiParametersPresentFlag) {
      const aspectRatioInfoPresentFlag = gb.readBoolean();
      if (aspectRatioInfoPresentFlag) {
        const aspectRatioIdc = gb.readUnsignedByte();
        const sarWidthTable = [
          1, 12, 10, 16, 40, 24, 20, 32, 80, 18, 15, 64, 160, 4, 3, 2,
        ];
        const sarHeightTable = [
          1, 11, 11, 11, 33, 11, 11, 11, 33, 11, 11, 33, 99, 3, 2, 1,
        ];
        if (aspectRatioIdc > 0 && aspectRatioIdc <= 16) {
          sarWidth = sarWidthTable[aspectRatioIdc - 1];
          sarHeight = sarHeightTable[aspectRatioIdc - 1];
        } else if (aspectRatioIdc === 255) {
          sarWidth = gb.readBits(16);
          sarHeight = gb.readBits(16);
        }
      }
      const overscanInfoPresentFlag = gb.readBoolean();
      if (overscanInfoPresentFlag) {
        gb.readBoolean();
      }
      const videoSignalTypePresentFlag = gb.readBoolean();
      if (videoSignalTypePresentFlag) {
        gb.readBits(3);
        gb.readBoolean();
        const colourDescriptionPresentFlag = gb.readBoolean();
        if (colourDescriptionPresentFlag) {
          gb.readUnsignedByte();
          gb.readUnsignedByte();
          gb.readUnsignedByte();
        }
      }
      const chromaLocInfoPresentFlag = gb.readBoolean();
      if (chromaLocInfoPresentFlag) {
        gb.readUnsignedExpGolomb();
        gb.readUnsignedExpGolomb();
      }
      gb.readBoolean(); // neutral_chroma_indication_flag
      gb.readBoolean(); // field_seq_flag
      gb.readBoolean(); // frame_field_info_present_flag
      defaultDisplayWindowFlag = gb.readBoolean();
      if (defaultDisplayWindowFlag) {
        gb.readUnsignedExpGolomb();
        gb.readUnsignedExpGolomb();
        gb.readUnsignedExpGolomb();
        gb.readUnsignedExpGolomb();
      }
      const vuiTimingInfoPresentFlag = gb.readBoolean();
      if (vuiTimingInfoPresentFlag) {
        gb.readBits(32); // fps_den
        gb.readBits(32); // fps_num
        const vuiPocProportionalToTimingFlag = gb.readBoolean();
        if (vuiPocProportionalToTimingFlag) {
          gb.readUnsignedExpGolomb();
        }
        const vuiHrdParametersPresentFlag = gb.readBoolean();
        if (vuiHrdParametersPresentFlag) {
          const commonInfPresentFlag = 1;
          let nalHrdParametersPresentFlag = false;
          let vclHrdParametersPresentFlag = false;
          let subPicHrdParamsPresentFlag = false;
          if (commonInfPresentFlag) {
            nalHrdParametersPresentFlag = gb.readBoolean();
            vclHrdParametersPresentFlag = gb.readBoolean();
            if (nalHrdParametersPresentFlag || vclHrdParametersPresentFlag) {
              subPicHrdParamsPresentFlag = gb.readBoolean();
              if (subPicHrdParamsPresentFlag) {
                gb.readUnsignedByte();
                gb.readBits(5);
                gb.readBoolean();
                gb.readBits(5);
              }
              gb.readBits(4); // bit_rate_scale
              gb.readBits(4); // cpb_size_scale
              if (subPicHrdParamsPresentFlag) {
                gb.readBits(4);
              }
              gb.readBits(5);
              gb.readBits(5);
              gb.readBits(5);
            }
          }
          for (let i = 0; i <= maxSubLayersMinus1; i++) {
            const fixedPicRateGeneralFlag = gb.readBoolean();
            let fixedPicRateWithinCvsFlag = true;
            let cpbCnt = 1;
            if (!fixedPicRateGeneralFlag) {
              fixedPicRateWithinCvsFlag = gb.readBoolean();
            }
            let lowDelayHrdFlag = false;
            if (fixedPicRateWithinCvsFlag) {
              gb.readUnsignedExpGolomb();
            } else {
              lowDelayHrdFlag = gb.readBoolean();
            }
            if (!lowDelayHrdFlag) {
              cpbCnt = gb.readUnsignedExpGolomb() + 1;
            }
            if (nalHrdParametersPresentFlag) {
              for (let j = 0; j < cpbCnt; j++) {
                gb.readUnsignedExpGolomb();
                gb.readUnsignedExpGolomb();
                if (subPicHrdParamsPresentFlag) {
                  gb.readUnsignedExpGolomb();
                  gb.readUnsignedExpGolomb();
                }
              }
              gb.readBoolean();
            }
            if (vclHrdParametersPresentFlag) {
              for (let j = 0; j < cpbCnt; j++) {
                gb.readUnsignedExpGolomb();
                gb.readUnsignedExpGolomb();
                if (subPicHrdParamsPresentFlag) {
                  gb.readUnsignedExpGolomb();
                  gb.readUnsignedExpGolomb();
                }
              }
              gb.readBoolean();
            }
          }
        }
      }
      const bitstreamRestrictionFlag = gb.readBoolean();
      if (bitstreamRestrictionFlag) {
        gb.readBoolean(); // tiles_fixed_structure_flag
        gb.readBoolean(); // motion_vectors_over_pic_boundaries_flag
        gb.readBoolean(); // restricted_ref_pic_lists_flag
        minSpatialSegmentationIdc = gb.readUnsignedExpGolomb();
        gb.readUnsignedExpGolomb(); // max_bytes_per_pic_denom
        gb.readUnsignedExpGolomb(); // max_bits_per_min_cu_denom
        gb.readUnsignedExpGolomb(); // log2_max_mv_length_horizontal
        gb.readUnsignedExpGolomb(); // log2_max_mv_length_vertical
      }
    }

    const subWc = chromaFormatIdc === 1 || chromaFormatIdc === 2 ? 2 : 1;
    const subHc = chromaFormatIdc === 1 ? 2 : 1;
    const codecWidth =
        picWidthInLumaSamples - (leftOffset + rightOffset) * subWc;
    const codecHeight =
        picHeightInLumaSamples - (topOffset + bottomOffset) * subHc;

    return {
      generalLevelIdc,
      generalProfileSpace,
      generalTierFlag,
      generalProfileIdc,
      generalProfileCompatibilityFlags1,
      generalProfileCompatibilityFlags2,
      generalProfileCompatibilityFlags3,
      generalProfileCompatibilityFlags4,
      generalConstraintIndicatorFlags1,
      generalConstraintIndicatorFlags2,
      generalConstraintIndicatorFlags3,
      generalConstraintIndicatorFlags4,
      generalConstraintIndicatorFlags5,
      generalConstraintIndicatorFlags6,
      minSpatialSegmentationIdc,
      constantFrameRate: 0, // FIXME!!!
      chromaFormatIdc,
      bitDepthLumaMinus8,
      bitDepthChromaMinus8,
      width: codecWidth,
      height: codecHeight,
      sarWidth: sarWidth,
      sarHeight: sarHeight,
    };
  }

  /**
   * @param {!Uint8Array} data
   * @return {shaka.transmuxer.H265.PPSConfiguration}
   * @private
   */
  static parsePPS_(data) {
    const gb = new shaka.util.ExpGolomb(data, /* convertEbsp2rbsp= */ true);

    // remove NALu Header
    gb.readUnsignedByte();
    gb.readUnsignedByte();

    // PPS
    gb.readUnsignedExpGolomb(); // pic_parameter_set_id
    gb.readUnsignedExpGolomb(); // seq_parameter_set_id
    gb.readBoolean(); // dependent_slice_segments_enabled_flag
    gb.readBoolean(); // output_flag_present_flag
    gb.readBits(3); // num_extra_slice_header_bits
    gb.readBoolean(); // sign_data_hiding_enabled_flag
    gb.readBoolean(); // cabac_init_present_flag
    gb.readUnsignedExpGolomb(); // num_ref_idx_l0_default_active_minus1
    gb.readUnsignedExpGolomb(); // num_ref_idx_l1_default_active_minus1
    gb.readExpGolomb(); // init_qp_minus26
    gb.readBoolean(); // constrained_intra_pred_flag
    gb.readBoolean(); // transform_skip_enabled_flag
    const cuQpDeltaEnabledFlag = gb.readBoolean();
    if (cuQpDeltaEnabledFlag) {
      gb.readUnsignedExpGolomb(); // diff_cu_qp_delta_depth
    }
    gb.readExpGolomb(); // cb_qp_offset
    gb.readExpGolomb(); // cr_qp_offset
    gb.readBoolean(); // pps_slice_chroma_qp_offsets_present_flag
    gb.readBoolean(); // weighted_pred_flag
    gb.readBoolean(); // weighted_bipred_flag
    gb.readBoolean(); // transquant_bypass_enabled_flag
    const tilesEnabledFlag = gb.readBoolean();
    const entropyCodingSyncEnabledFlag = gb.readBoolean();

    // needs hvcC
    let parallelismType = 1; // slice-based parallel decoding
    if (entropyCodingSyncEnabledFlag && tilesEnabledFlag) {
      parallelismType = 0; // mixed-type parallel decoding
    } else if (entropyCodingSyncEnabledFlag) {
      parallelismType = 3; // wavefront-based parallel decoding
    } else if (tilesEnabledFlag) {
      parallelismType = 2; // tile-based parallel decoding
    }

    return {
      parallelismType,
    };
  }

  /**
   * @param {!Uint8Array} vps
   * @param {!Uint8Array} sps
   * @param {!Uint8Array} pps
   * @param {shaka.transmuxer.H265.DecoderConfigurationRecordType} detail
   * @return {!Uint8Array}
   * @private
   */
  static getVideoConfiguration_(vps, sps, pps, detail) {
    const H265 = shaka.transmuxer.H265;

    const length = 23 + (3 + 2 + vps.byteLength) +
        (3 + 2 + sps.byteLength) + (3 + 2 + pps.byteLength);
    const data = new Uint8Array(length);

    data[0] = 0x01; // configurationVersion
    data[1] = ((detail.generalProfileSpace & 0x03) << 6) |
        ((detail.generalTierFlag ? 1 : 0) << 5) |
        ((detail.generalProfileIdc & 0x1F));
    data[2] = detail.generalProfileCompatibilityFlags1;
    data[3] = detail.generalProfileCompatibilityFlags2;
    data[4] = detail.generalProfileCompatibilityFlags3;
    data[5] = detail.generalProfileCompatibilityFlags4;
    data[6] = detail.generalConstraintIndicatorFlags1;
    data[7] = detail.generalConstraintIndicatorFlags2;
    data[8] = detail.generalConstraintIndicatorFlags3;
    data[9] = detail.generalConstraintIndicatorFlags4;
    data[10] = detail.generalConstraintIndicatorFlags5;
    data[11] = detail.generalConstraintIndicatorFlags6;
    data[12] = detail.generalLevelIdc;
    data[13] = 0xF0 |
        ((detail.minSpatialSegmentationIdc & 0x0F00) >> 8);
    data[14] = (detail.minSpatialSegmentationIdc & 0xFF);
    data[15] = 0xFC | (detail.parallelismType & 0x03);
    data[16] = 0xFC | (detail.chromaFormatIdc & 0x03);
    data[17] = 0xF8 | (detail.bitDepthLumaMinus8 & 0x07);
    data[18] = 0xF8 | (detail.bitDepthChromaMinus8 & 0x07);
    data[19] = 0;
    data[20] = 0;
    data[21] = ((detail.constantFrameRate & 0x03) << 6) |
        ((detail.numTemporalLayers & 0x07) << 3) |
        ((detail.temporalIdNested ? 1 : 0) << 2) | 3;
    data[22] = 3;
    data[23 + 0 + 0] = 0x80 | H265.NALU_TYPE_VPS_;
    data[23 + 0 + 1] = 0;
    data[23 + 0 + 2] = 1;
    data[23 + 0 + 3] = (vps.byteLength & 0xFF00) >> 8;
    data[23 + 0 + 4] = (vps.byteLength & 0x00FF) >> 0;
    data.set(vps, 23 + 0 + 5);
    data[23 + (5 + vps.byteLength) + 0] =
        0x80 | H265.NALU_TYPE_SPS_;
    data[23 + (5 + vps.byteLength) + 1] = 0;
    data[23 + (5 + vps.byteLength) + 2] = 1;
    data[23 + (5 + vps.byteLength) + 3] = (sps.byteLength & 0xFF00) >> 8;
    data[23 + (5 + vps.byteLength) + 4] = (sps.byteLength & 0x00FF) >> 0;
    data.set(sps, 23 + (5 + vps.byteLength) + 5);
    data[23 + (5 + vps.byteLength + 5 + sps.byteLength) + 0] =
        0x80 | H265.NALU_TYPE_PPS_;
    data[23 + (5 + vps.byteLength + 5 + sps.byteLength) + 1] = 0;
    data[23 + (5 + vps.byteLength + 5 + sps.byteLength) + 2] = 1;
    data[23 + (5 + vps.byteLength + 5 + sps.byteLength) + 3] =
        (pps.byteLength & 0xFF00) >> 8;
    data[23 + (5 + vps.byteLength + 5 + sps.byteLength) + 4] =
        (pps.byteLength & 0x00FF) >> 0;
    data.set(pps, 23 + (5 + vps.byteLength + 5 + sps.byteLength) + 5);
    return data;
  }

  /**
   * @param {!Array.<shaka.extern.VideoNalu>} nalus
   * @return {?{data: !Uint8Array, isKeyframe: boolean}}
   */
  static parseFrame(nalus) {
    const H265 = shaka.transmuxer.H265;
    let isKeyframe = false;
    const nalusData = [];
    let hvcSample = false;
    for (const nalu of nalus) {
      let push = false;
      switch (nalu.type) {
        case H265.NALU_TYPE_TRAIL_N_:
        case H265.NALU_TYPE_TRAIL_R_: {
          hvcSample = true;
          push = true;
          break;
        }
        case H265.NALU_TYPE_IDR_W_RADL_:
        case H265.NALU_TYPE_IDR_N_LP_:
        case H265.NALU_TYPE_CRA_NUT_:
          hvcSample = true;
          push = true;
          isKeyframe = true;
          break;
        case H265.NALU_TYPE_VPS_:
          push = true;
          break;
        case H265.NALU_TYPE_SPS_:
          push = true;
          break;
        case H265.NALU_TYPE_PPS_:
          push = true;
          break;
        case H265.NALU_TYPE_AUD_:
          push = true;
          hvcSample = true;
          break;
        case H265.NALU_TYPE_SEI_PREFIX_:
        case H265.NALU_TYPE_SEI_SUFFIX_:
          push = true;
          break;
        default:
          push = false;
          break;
      }
      if (hvcSample && push) {
        const size = nalu.fullData.byteLength;
        const naluLength = new Uint8Array(4);
        naluLength[0] = (size >> 24) & 0xff;
        naluLength[1] = (size >> 16) & 0xff;
        naluLength[2] = (size >> 8) & 0xff;
        naluLength[3] = size & 0xff;
        nalusData.push(naluLength);
        nalusData.push(nalu.fullData);
      }
    }
    if (!nalusData.length) {
      return null;
    }
    const data = shaka.util.Uint8ArrayUtils.concat(...nalusData);
    return {
      data,
      isKeyframe,
    };
  }
};


/**
 * NALU type for non-reference trailing picture for H.265.
 * @const {number}
 * @private
 */
shaka.transmuxer.H265.NALU_TYPE_TRAIL_N_ = 0x01;


/**
 * NALU type for reference trailing picture for H.265.
 * @const {number}
 * @private
 */
shaka.transmuxer.H265.NALU_TYPE_TRAIL_R_ = 0x00;


/**
 * NALU type for Instantaneous Decoder Refresh (IDR) for H.265.
 * @const {number}
 * @private
 */
shaka.transmuxer.H265.NALU_TYPE_IDR_W_RADL_ = 0x13;


/**
 * NALU type for Instantaneous Decoder Refresh (IDR) for H.265.
 * @const {number}
 * @private
 */
shaka.transmuxer.H265.NALU_TYPE_IDR_N_LP_ = 0x14;

/**
 * NALU type for Clean Random Access (CRA) for H.265.
 * @const {number}
 * @private
 */
shaka.transmuxer.H265.NALU_TYPE_CRA_NUT_ = 0x15;


/**
 * NALU type for Video Parameter Set (VPS) for H.265.
 * @const {number}
 * @private
 */
shaka.transmuxer.H265.NALU_TYPE_VPS_ = 0x20;


/**
 * NALU type for Sequence Parameter Set (SPS) for H.265.
 * @const {number}
 * @private
 */
shaka.transmuxer.H265.NALU_TYPE_SPS_ = 0x21;


/**
 * NALU type for Picture Parameter Set (PPS) for H.265.
 * @const {number}
 * @private
 */
shaka.transmuxer.H265.NALU_TYPE_PPS_ = 0x22;


/**
 * NALU type for Access Unit Delimiter (AUD) for H.265.
 * @const {number}
 * @private
 */
shaka.transmuxer.H265.NALU_TYPE_AUD_ = 0x23;


/**
 * NALU type for Supplemental Enhancement Information (SEI) for H.265.
 * @const {number}
 * @private
 */
shaka.transmuxer.H265.NALU_TYPE_SEI_PREFIX_ = 0x27;


/**
 * NALU type for Supplemental Enhancement Information (SEI) for H.265.
 * @const {number}
 * @private
 */
shaka.transmuxer.H265.NALU_TYPE_SEI_SUFFIX_ = 0x28;


/**
 * @typedef {{
 *  numTemporalLayers: number,
 *  temporalIdNested: boolean
 * }}
 *
 * @property {number} numTemporalLayers
 * @property {boolean} temporalIdNested
 */
shaka.transmuxer.H265.VPSConfiguration;


/**
 * @typedef {{
 *  generalProfileSpace: number,
 *  generalTierFlag: number,
 *  generalLevelIdc: number,
 *  generalProfileIdc: number,
 *  generalProfileCompatibilityFlags1: number,
 *  generalProfileCompatibilityFlags2: number,
 *  generalProfileCompatibilityFlags3: number,
 *  generalProfileCompatibilityFlags4: number,
 *  generalConstraintIndicatorFlags1: number,
 *  generalConstraintIndicatorFlags2: number,
 *  generalConstraintIndicatorFlags3: number,
 *  generalConstraintIndicatorFlags4: number,
 *  generalConstraintIndicatorFlags5: number,
 *  generalConstraintIndicatorFlags6: number,
 *  constantFrameRate: number,
 *  minSpatialSegmentationIdc: number,
 *  chromaFormatIdc: number,
 *  bitDepthLumaMinus8: number,
 *  bitDepthChromaMinus8: number,
 *  width: number,
 *  height: number,
 *  sarWidth: number,
 *  sarHeight: number
 * }}
 *
 * @property {number} generalProfileSpace
 * @property {number} generalTierFlag
 * @property {number} generalLevelIdc
 * @property {number} generalProfileIdc
 * @property {number} generalProfileCompatibilityFlags1
 * @property {number} generalProfileCompatibilityFlags2
 * @property {number} generalProfileCompatibilityFlags3
 * @property {number} generalProfileCompatibilityFlags4
 * @property {number} generalConstraintIndicatorFlags1
 * @property {number} generalConstraintIndicatorFlags2
 * @property {number} generalConstraintIndicatorFlags3
 * @property {number} generalConstraintIndicatorFlags4
 * @property {number} generalConstraintIndicatorFlags5
 * @property {number} generalConstraintIndicatorFlags6
 * @property {number} constantFrameRate
 * @property {number} minSpatialSegmentationIdc
 * @property {number} chromaFormatIdc
 * @property {number} bitDepthLumaMinus8
 * @property {number} bitDepthChromaMinus8
 * @property {number} width
 * @property {number} height
 * @property {number} sarWidth
 * @property {number} sarHeight
 */
shaka.transmuxer.H265.SPSConfiguration;


/**
 * @typedef {{
 *  parallelismType: number
 * }}
 *
 * @property {number} parallelismType
 */
shaka.transmuxer.H265.PPSConfiguration;


/**
 * @typedef {{
 *  numTemporalLayers: number,
 *  temporalIdNested: boolean,
 *  generalProfileSpace: number,
 *  generalTierFlag: number,
 *  generalLevelIdc: number,
 *  generalProfileIdc: number,
 *  generalProfileCompatibilityFlags1: number,
 *  generalProfileCompatibilityFlags2: number,
 *  generalProfileCompatibilityFlags3: number,
 *  generalProfileCompatibilityFlags4: number,
 *  generalConstraintIndicatorFlags1: number,
 *  generalConstraintIndicatorFlags2: number,
 *  generalConstraintIndicatorFlags3: number,
 *  generalConstraintIndicatorFlags4: number,
 *  generalConstraintIndicatorFlags5: number,
 *  generalConstraintIndicatorFlags6: number,
 *  constantFrameRate: number,
 *  minSpatialSegmentationIdc: number,
 *  chromaFormatIdc: number,
 *  bitDepthLumaMinus8: number,
 *  bitDepthChromaMinus8: number,
 *  parallelismType: number
 * }}
 *
 * @property {number} numTemporalLayers
 * @property {boolean} temporalIdNested
 * @property {number} generalProfileSpace
 * @property {number} generalTierFlag
 * @property {number} generalLevelIdc
 * @property {number} generalProfileIdc
 * @property {number} generalProfileCompatibilityFlags1
 * @property {number} generalProfileCompatibilityFlags2
 * @property {number} generalProfileCompatibilityFlags3
 * @property {number} generalProfileCompatibilityFlags4
 * @property {number} generalConstraintIndicatorFlags1
 * @property {number} generalConstraintIndicatorFlags2
 * @property {number} generalConstraintIndicatorFlags3
 * @property {number} generalConstraintIndicatorFlags4
 * @property {number} generalConstraintIndicatorFlags5
 * @property {number} generalConstraintIndicatorFlags6
 * @property {number} constantFrameRate
 * @property {number} minSpatialSegmentationIdc
 * @property {number} chromaFormatIdc
 * @property {number} bitDepthLumaMinus8
 * @property {number} bitDepthChromaMinus8
 * @property {number} parallelismType
 */
shaka.transmuxer.H265.DecoderConfigurationRecordType;