diff --git a/lib/mss/mss_parser.js b/lib/mss/mss_parser.js index 87e75832eb..fe58d41242 100644 --- a/lib/mss/mss_parser.js +++ b/lib/mss/mss_parser.js @@ -661,6 +661,8 @@ shaka.mss.MssParser = class { videoNalus: videoNalus, audioConfig: new Uint8Array([]), videoConfig: new Uint8Array([]), + hSpacing: 0, + vSpacing: 0, data: null, // Data is not necessary for init segement. stream: stream, }; diff --git a/lib/transmuxer/aac_transmuxer.js b/lib/transmuxer/aac_transmuxer.js index feb0183ebe..97e29bede4 100644 --- a/lib/transmuxer/aac_transmuxer.js +++ b/lib/transmuxer/aac_transmuxer.js @@ -195,6 +195,8 @@ shaka.transmuxer.AacTransmuxer = class { videoNalus: [], audioConfig: new Uint8Array([]), videoConfig: new Uint8Array([]), + hSpacing: 0, + vSpacing: 0, data: { sequenceNumber: this.frameIndex_, baseMediaDecodeTime: baseMediaDecodeTime, diff --git a/lib/transmuxer/ac3_transmuxer.js b/lib/transmuxer/ac3_transmuxer.js index 061433ae36..dfedfe2196 100644 --- a/lib/transmuxer/ac3_transmuxer.js +++ b/lib/transmuxer/ac3_transmuxer.js @@ -184,6 +184,8 @@ shaka.transmuxer.Ac3Transmuxer = class { videoNalus: [], audioConfig: audioConfig, videoConfig: new Uint8Array([]), + hSpacing: 0, + vSpacing: 0, data: { sequenceNumber: this.frameIndex_, baseMediaDecodeTime: baseMediaDecodeTime, diff --git a/lib/transmuxer/ec3_transmuxer.js b/lib/transmuxer/ec3_transmuxer.js index 42c1402c81..388d12086a 100644 --- a/lib/transmuxer/ec3_transmuxer.js +++ b/lib/transmuxer/ec3_transmuxer.js @@ -184,6 +184,8 @@ shaka.transmuxer.Ec3Transmuxer = class { videoNalus: [], audioConfig: audioConfig, videoConfig: new Uint8Array([]), + hSpacing: 0, + vSpacing: 0, data: { sequenceNumber: this.frameIndex_, baseMediaDecodeTime: baseMediaDecodeTime, diff --git a/lib/transmuxer/h264.js b/lib/transmuxer/h264.js index 88f3e919e4..ad7f9cd879 100644 --- a/lib/transmuxer/h264.js +++ b/lib/transmuxer/h264.js @@ -20,7 +20,8 @@ shaka.transmuxer.H264 = class { * describes the properties of upcoming video frames. * * @param {!Array.} nalus - * @return {?{height: number, width: number, videoConfig: !Uint8Array}} + * @return {?{height: number, width: number, videoConfig: !Uint8Array, + * hSpacing: number, vSpacing: number}} */ static parseInfo(nalus) { const H264 = shaka.transmuxer.H264; @@ -129,6 +130,30 @@ shaka.transmuxer.H264 = class { 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); @@ -165,6 +190,8 @@ shaka.transmuxer.H264 = class { height, width, videoConfig, + hSpacing, + vSpacing, }; } diff --git a/lib/transmuxer/h265.js b/lib/transmuxer/h265.js index 601a9cc907..64cfd3e5b9 100644 --- a/lib/transmuxer/h265.js +++ b/lib/transmuxer/h265.js @@ -20,7 +20,8 @@ shaka.transmuxer.H265 = class { * describes the properties of upcoming video frames. * * @param {!Array.} nalus - * @return {?{height: number, width: number, videoConfig: !Uint8Array}} + * @return {?{height: number, width: number, videoConfig: !Uint8Array, + * hSpacing: number, vSpacing: number}} */ static parseInfo(nalus) { const H265 = shaka.transmuxer.H265; @@ -87,6 +88,8 @@ shaka.transmuxer.H265 = class { height: spsConfiguration.height, width: spsConfiguration.width, videoConfig, + hSpacing: spsConfiguration.sarWidth, + vSpacing: spsConfiguration.sarHeight, }; } @@ -298,6 +301,8 @@ shaka.transmuxer.H265 = class { } } 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 @@ -306,9 +311,18 @@ shaka.transmuxer.H265 = class { const aspectRatioInfoPresentFlag = gb.readBoolean(); if (aspectRatioInfoPresentFlag) { const aspectRatioIdc = gb.readUnsignedByte(); - if (aspectRatioIdc === 255) { - gb.readBits(16); // sar_width - gb.readBits(16); // sar_height + 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(); @@ -459,6 +473,8 @@ shaka.transmuxer.H265 = class { bitDepthChromaMinus8, width: codecWidth, height: codecHeight, + sarWidth: sarWidth, + sarHeight: sarHeight, }; } @@ -773,7 +789,9 @@ shaka.transmuxer.H265.VPSConfiguration; * bitDepthLumaMinus8: number, * bitDepthChromaMinus8: number, * width: number, - * height: number + * height: number, + * sarWidth: number, + * sarHeight: number * }} * * @property {number} generalProfileSpace @@ -797,6 +815,8 @@ shaka.transmuxer.H265.VPSConfiguration; * @property {number} bitDepthChromaMinus8 * @property {number} width * @property {number} height + * @property {number} sarWidth + * @property {number} sarHeight */ shaka.transmuxer.H265.SPSConfiguration; diff --git a/lib/transmuxer/mp3_transmuxer.js b/lib/transmuxer/mp3_transmuxer.js index a21ca22960..5cb2e3bae9 100644 --- a/lib/transmuxer/mp3_transmuxer.js +++ b/lib/transmuxer/mp3_transmuxer.js @@ -176,6 +176,8 @@ shaka.transmuxer.Mp3Transmuxer = class { videoNalus: [], audioConfig: new Uint8Array([]), videoConfig: new Uint8Array([]), + hSpacing: 0, + vSpacing: 0, data: { sequenceNumber: this.frameIndex_, baseMediaDecodeTime: baseMediaDecodeTime, diff --git a/lib/transmuxer/ts_transmuxer.js b/lib/transmuxer/ts_transmuxer.js index 2af666a3e7..7008541e76 100644 --- a/lib/transmuxer/ts_transmuxer.js +++ b/lib/transmuxer/ts_transmuxer.js @@ -409,6 +409,8 @@ shaka.transmuxer.TsTransmuxer = class { videoNalus: [], audioConfig: new Uint8Array([]), videoConfig: new Uint8Array([]), + hSpacing: 0, + vSpacing: 0, data: { sequenceNumber: this.frameIndex_, baseMediaDecodeTime: baseMediaDecodeTime, @@ -500,6 +502,8 @@ shaka.transmuxer.TsTransmuxer = class { videoNalus: [], audioConfig: audioConfig, videoConfig: new Uint8Array([]), + hSpacing: 0, + vSpacing: 0, data: { sequenceNumber: this.frameIndex_, baseMediaDecodeTime: baseMediaDecodeTime, @@ -591,6 +595,8 @@ shaka.transmuxer.TsTransmuxer = class { videoNalus: [], audioConfig: audioConfig, videoConfig: new Uint8Array([]), + hSpacing: 0, + vSpacing: 0, data: { sequenceNumber: this.frameIndex_, baseMediaDecodeTime: baseMediaDecodeTime, @@ -677,6 +683,8 @@ shaka.transmuxer.TsTransmuxer = class { videoNalus: [], audioConfig: new Uint8Array([]), videoConfig: new Uint8Array([]), + hSpacing: 0, + vSpacing: 0, data: { sequenceNumber: this.frameIndex_, baseMediaDecodeTime: baseMediaDecodeTime, @@ -769,6 +777,8 @@ shaka.transmuxer.TsTransmuxer = class { videoNalus: [], audioConfig: new Uint8Array([]), videoConfig: info.videoConfig, + hSpacing: info.hSpacing, + vSpacing: info.vSpacing, data: { sequenceNumber: this.frameIndex_, baseMediaDecodeTime: baseMediaDecodeTime, @@ -861,6 +871,8 @@ shaka.transmuxer.TsTransmuxer = class { videoNalus: [], audioConfig: new Uint8Array([]), videoConfig: info.videoConfig, + hSpacing: info.hSpacing, + vSpacing: info.vSpacing, data: { sequenceNumber: this.frameIndex_, baseMediaDecodeTime: baseMediaDecodeTime, diff --git a/lib/util/mp4_generator.js b/lib/util/mp4_generator.js index b8d72cacdc..e151989a17 100644 --- a/lib/util/mp4_generator.js +++ b/lib/util/mp4_generator.js @@ -371,12 +371,13 @@ shaka.util.Mp4Generator = class { ]); let boxName = 'avc1'; + const paspBox = this.pasp_(streamInfo); let sinfBox = new Uint8Array([]); if (streamInfo.encrypted) { sinfBox = this.sinf_(streamInfo); boxName = 'encv'; } - return Mp4Generator.box(boxName, avc1Bytes, avcCBox, sinfBox); + return Mp4Generator.box(boxName, avc1Bytes, avcCBox, paspBox, sinfBox); } /** @@ -500,12 +501,34 @@ shaka.util.Mp4Generator = class { ]); let boxName = 'hvc1'; + const paspBox = this.pasp_(streamInfo); let sinfBox = new Uint8Array([]); if (streamInfo.encrypted) { sinfBox = this.sinf_(streamInfo); boxName = 'encv'; } - return Mp4Generator.box(boxName, hvc1Bytes, hvcCBox, sinfBox); + return Mp4Generator.box(boxName, hvc1Bytes, hvcCBox, paspBox, sinfBox); + } + + /** + * Generate a PASP box + * + * @param {shaka.util.Mp4Generator.StreamInfo} streamInfo + * @return {!Uint8Array} + * @private + */ + pasp_(streamInfo) { + if (!streamInfo.hSpacing && !streamInfo.vSpacing) { + return new Uint8Array([]); + } + const Mp4Generator = shaka.util.Mp4Generator; + const hSpacing = streamInfo.hSpacing; + const vSpacing = streamInfo.vSpacing; + const bytes = new Uint8Array([ + ...this.breakNumberIntoBytes_(hSpacing, 4), // hSpacing + ...this.breakNumberIntoBytes_(vSpacing, 4), // vSpacing + ]); + return Mp4Generator.box('pasp', bytes); } /** @@ -1301,6 +1324,8 @@ shaka.util.Mp4Generator.DINF_ = new Uint8Array([]); * videoNalus: !Array., * audioConfig: !Uint8Array, * videoConfig: !Uint8Array, + * hSpacing: number, + * vSpacing: number, * data: ?shaka.util.Mp4Generator.Data, * stream: !shaka.extern.Stream * }} @@ -1325,6 +1350,10 @@ shaka.util.Mp4Generator.DINF_ = new Uint8Array([]); * The stream's audio config. * @property {!Uint8Array} videoConfig * The stream's video config. + * @property {number} hSpacing + * The stream's video horizontal spacing of pixels. + * @property {number} vSpacing + * The stream's video vertial spacing of pixels. * @property {?shaka.util.Mp4Generator.Data} data * The stream's data. * @property {!shaka.extern.Stream} stream