UNPKG

shaka-player

Version:
426 lines (380 loc) 13 kB
/*! @license * Shaka Player * Copyright 2016 Google LLC * SPDX-License-Identifier: Apache-2.0 */ goog.provide('shaka.transmuxer.H264'); goog.require('shaka.util.ExpGolomb'); goog.require('shaka.util.Uint8ArrayUtils'); /** * H.264 utils */ shaka.transmuxer.H264 = class { /** * Read a sequence parameter set and return some interesting video * properties. A sequence parameter set is the H264 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 H264 = shaka.transmuxer.H264; if (!nalus.length) { return null; } const spsNalu = nalus.find((nalu) => { return nalu.type == H264.NALU_TYPE_SPS_; }); const ppsNalu = nalus.find((nalu) => { return nalu.type == H264.NALU_TYPE_PPS_; }); if (!spsNalu || !ppsNalu) { return null; } const expGolombDecoder = new shaka.util.ExpGolomb(spsNalu.data); // profile_idc const profileIdc = expGolombDecoder.readUnsignedByte(); // constraint_set[0-5]_flag expGolombDecoder.readUnsignedByte(); // level_idc u(8) expGolombDecoder.readUnsignedByte(); // seq_parameter_set_id expGolombDecoder.skipExpGolomb(); // some profiles have more optional data we don't need if (H264.PROFILES_WITH_OPTIONAL_SPS_DATA_.includes(profileIdc)) { const chromaFormatIdc = expGolombDecoder.readUnsignedExpGolomb(); if (chromaFormatIdc === 3) { // separate_colour_plane_flag expGolombDecoder.skipBits(1); } // bit_depth_luma_minus8 expGolombDecoder.skipExpGolomb(); // bit_depth_chroma_minus8 expGolombDecoder.skipExpGolomb(); // qpprime_y_zero_transform_bypass_flag expGolombDecoder.skipBits(1); // seq_scaling_matrix_present_flag if (expGolombDecoder.readBoolean()) { const scalingListCount = (chromaFormatIdc !== 3) ? 8 : 12; for (let i = 0; i < scalingListCount; i++) { // seq_scaling_list_present_flag[ i ] if (expGolombDecoder.readBoolean()) { if (i < 6) { expGolombDecoder.skipScalingList(16); } else { expGolombDecoder.skipScalingList(64); } } } } } // log2_max_frame_num_minus4 expGolombDecoder.skipExpGolomb(); const picOrderCntType = expGolombDecoder.readUnsignedExpGolomb(); if (picOrderCntType === 0) { // log2_max_pic_order_cnt_lsb_minus4 expGolombDecoder.readUnsignedExpGolomb(); } else if (picOrderCntType === 1) { // delta_pic_order_always_zero_flag expGolombDecoder.skipBits(1); // offset_for_non_ref_pic expGolombDecoder.skipExpGolomb(); // offset_for_top_to_bottom_field expGolombDecoder.skipExpGolomb(); const numRefFramesInPicOrderCntCycle = expGolombDecoder.readUnsignedExpGolomb(); for (let i = 0; i < numRefFramesInPicOrderCntCycle; i++) { // offset_for_ref_frame[ i ] expGolombDecoder.skipExpGolomb(); } } // max_num_ref_frames expGolombDecoder.skipExpGolomb(); // gaps_in_frame_num_value_allowed_flag expGolombDecoder.skipBits(1); const picWidthInMbsMinus1 = expGolombDecoder.readUnsignedExpGolomb(); const picHeightInMapUnitsMinus1 = expGolombDecoder.readUnsignedExpGolomb(); const frameMbsOnlyFlag = expGolombDecoder.readBits(1); if (frameMbsOnlyFlag === 0) { // mb_adaptive_frame_field_flag expGolombDecoder.skipBits(1); } // direct_8x8_inference_flag expGolombDecoder.skipBits(1); let frameCropLeftOffset = 0; let frameCropRightOffset = 0; let frameCropTopOffset = 0; let frameCropBottomOffset = 0; // frame_cropping_flag if (expGolombDecoder.readBoolean()) { frameCropLeftOffset = expGolombDecoder.readUnsignedExpGolomb(); frameCropRightOffset = expGolombDecoder.readUnsignedExpGolomb(); frameCropTopOffset = expGolombDecoder.readUnsignedExpGolomb(); frameCropBottomOffset = expGolombDecoder.readUnsignedExpGolomb(); } let hSpacing = 1; let vSpacing = 1; // vui_parameters_present_flag if (expGolombDecoder.readBoolean()) { // aspect_ratio_info_present_flag if (expGolombDecoder.readBoolean()) { const aspectRatioIdc = expGolombDecoder.readUnsignedByte(); const hSpacingTable = [ 1, 12, 10, 16, 40, 24, 20, 32, 80, 18, 15, 64, 160, 4, 3, 2, ]; const vSpacingTable = [ 1, 11, 11, 11, 33, 11, 11, 11, 33, 11, 11, 33, 99, 3, 2, 1, ]; if (aspectRatioIdc > 0 && aspectRatioIdc <= 16) { hSpacing = hSpacingTable[aspectRatioIdc - 1]; vSpacing = vSpacingTable[aspectRatioIdc - 1]; } else if (aspectRatioIdc === 255) { hSpacing = expGolombDecoder.readBits(16); vSpacing = expGolombDecoder.readBits(16); } } } const height = ((2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16) - (frameCropTopOffset * 2) - (frameCropBottomOffset * 2); const width = ((picWidthInMbsMinus1 + 1) * 16) - frameCropLeftOffset * 2 - frameCropRightOffset * 2; // assemble the SPSs let sps = []; const spsData = spsNalu.fullData; sps.push((spsData.byteLength >>> 8) & 0xff); sps.push(spsData.byteLength & 0xff); sps = sps.concat(...spsData); // assemble the PPSs let pps = []; const ppsData = ppsNalu.fullData; pps.push((ppsData.byteLength >>> 8) & 0xff); pps.push(ppsData.byteLength & 0xff); pps = pps.concat(...ppsData); const videoConfig = new Uint8Array( [ 0x01, // version sps[3], // profile sps[4], // profile compat sps[5], // level 0xfc | 3, // lengthSizeMinusOne, hard-coded to 4 bytes 0xe0 | 1, // 3bit reserved (111) + numOfSequenceParameterSets ].concat(sps).concat([ 1, // numOfPictureParameterSets ]).concat(pps)); return { height, width, videoConfig, hSpacing, vSpacing, }; } /** * @param {!Array<shaka.extern.MPEG_PES>} videoData * @return {!Array<shaka.extern.VideoSample>} */ static getVideoSamples(videoData) { const H264 = shaka.transmuxer.H264; /** @type {!Array<shaka.extern.VideoSample>} */ const videoSamples = []; /** @type {?shaka.extern.VideoSample} */ let lastVideoSample = null; /** @type {boolean} */ let audFound = false; const addLastVideoSample = () => { if (!lastVideoSample) { return; } if (!lastVideoSample.nalus.length || !lastVideoSample.frame) { return; } const nalusData = []; for (const nalu of lastVideoSample.nalus) { 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); } lastVideoSample.data = shaka.util.Uint8ArrayUtils.concat(...nalusData); videoSamples.push(lastVideoSample); }; const createLastVideoSample = (pes) => { lastVideoSample = { data: new Uint8Array([]), frame: false, isKeyframe: false, pts: pes.pts, dts: pes.dts, nalus: [], }; }; for (let i = 0; i < videoData.length; i++) { const pes = videoData[i]; const nalus = pes.nalus; let spsFound = false; // If new NAL units found and last sample still there, let's push ... // This helps parsing streams with missing AUD // (only do this if AUD never found) if (lastVideoSample && nalus.length && !audFound) { addLastVideoSample(); createLastVideoSample(pes); } for (const nalu of pes.nalus) { let push = false; switch (nalu.type) { case H264.NALU_TYPE_NDR_: { let isKeyframe = false; push = true; const data = nalu.data; // Only check slice type to detect KF in case SPS found in same // packet (any keyframe is preceded by SPS ...) if (spsFound && data.length > 4) { // retrieve slice type by parsing beginning of NAL unit (follow // H264 spec,slice_header definition) to detect keyframe embedded // in NDR const sliceType = new shaka.util.ExpGolomb(data).readSliceType(); // 2 : I slice, 4 : SI slice, 7 : I slice, 9: SI slice // SI slice : A slice that is coded using intra prediction only // and using quantisation of the prediction samples. // An SI slice can be coded such that its decoded samples can be // constructed identically to an SP slice. // I slice: A slice that is not an SI slice that is decoded using // intra prediction only. if (sliceType === 2 || sliceType === 4 || sliceType === 7 || sliceType === 9) { isKeyframe = true; } } if (isKeyframe) { // If we have non-keyframe data already, that cannot belong to // the same frame as a keyframe, so force a push if (lastVideoSample && lastVideoSample.frame && !lastVideoSample.isKeyframe) { addLastVideoSample(); lastVideoSample = null; } } if (!lastVideoSample) { createLastVideoSample(pes); } lastVideoSample.frame = true; lastVideoSample.isKeyframe = isKeyframe; break; } case H264.NALU_TYPE_IDR_: { push = true; // Handle PES not starting with AUD // If we have frame data already, that cannot belong to the same // frame, so force a push if (lastVideoSample && lastVideoSample.frame && !lastVideoSample.isKeyframe) { addLastVideoSample(); lastVideoSample = null; } if (!lastVideoSample) { createLastVideoSample(pes); } lastVideoSample.frame = true; lastVideoSample.isKeyframe = true; break; } case H264.NALU_TYPE_SEI_: push = true; break; case H264.NALU_TYPE_SPS_: push = true; spsFound = true; break; case H264.NALU_TYPE_PPS_: push = true; break; case H264.NALU_TYPE_AUD_: push = true; audFound = true; if (lastVideoSample && lastVideoSample.frame) { addLastVideoSample(); lastVideoSample = null; } if (!lastVideoSample) { createLastVideoSample(pes); } break; case H264.NALU_TYPE_FILLER_DATA_: push = true; break; default: push = false; break; } if (lastVideoSample && push) { lastVideoSample.nalus.push(nalu); } } } // If last PES packet, push samples addLastVideoSample(); return videoSamples; } }; /** * NALU type for NDR for H.264. * @const {number} * @private */ shaka.transmuxer.H264.NALU_TYPE_NDR_ = 0x01; /** * NALU type for Instantaneous Decoder Refresh (IDR) for H.264. * @const {number} * @private */ shaka.transmuxer.H264.NALU_TYPE_IDR_ = 0x05; /** * NALU type for Supplemental Enhancement Information (SEI) for H.264. * @const {number} * @private */ shaka.transmuxer.H264.NALU_TYPE_SEI_ = 0x06; /** * NALU type for Sequence Parameter Set (SPS) for H.264. * @const {number} * @private */ shaka.transmuxer.H264.NALU_TYPE_SPS_ = 0x07; /** * NALU type for Picture Parameter Set (PPS) for H.264. * @const {number} * @private */ shaka.transmuxer.H264.NALU_TYPE_PPS_ = 0x08; /** * NALU type for Access Unit Delimiter (AUD) for H.264. * @const {number} * @private */ shaka.transmuxer.H264.NALU_TYPE_AUD_ = 0x09; /** * NALU type for Filler Data for H.264. * @const {number} * @private */ shaka.transmuxer.H264.NALU_TYPE_FILLER_DATA_ = 0x0c; /** * Values of profile_idc that indicate additional fields are included in the * SPS. * see Recommendation ITU-T H.264 (4/2013) * 7.3.2.1.1 Sequence parameter set data syntax * * @const {!Array<number>} * @private */ shaka.transmuxer.H264.PROFILES_WITH_OPTIONAL_SPS_DATA_ = [100, 110, 122, 244, 44, 83, 86, 118, 128, 138, 139, 134];