/*! @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;