370 lines
12 KiB
JavaScript
370 lines
12 KiB
JavaScript
'use strict';
|
|
|
|
Object.defineProperty(exports, "__esModule", {
|
|
value: true
|
|
});
|
|
|
|
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); /**
|
|
* Parser for exponential Golomb codes, a variable-bitwidth number encoding scheme used by h264.
|
|
*/
|
|
|
|
var _logger = require('../utils/logger');
|
|
|
|
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
|
|
|
var ExpGolomb = function () {
|
|
function ExpGolomb(data) {
|
|
_classCallCheck(this, ExpGolomb);
|
|
|
|
this.data = data;
|
|
// the number of bytes left to examine in this.data
|
|
this.bytesAvailable = data.byteLength;
|
|
// the current word being examined
|
|
this.word = 0; // :uint
|
|
// the number of bits left to examine in the current word
|
|
this.bitsAvailable = 0; // :uint
|
|
}
|
|
|
|
// ():void
|
|
|
|
|
|
_createClass(ExpGolomb, [{
|
|
key: 'loadWord',
|
|
value: function loadWord() {
|
|
var data = this.data,
|
|
bytesAvailable = this.bytesAvailable,
|
|
position = data.byteLength - bytesAvailable,
|
|
workingBytes = new Uint8Array(4),
|
|
availableBytes = Math.min(4, bytesAvailable);
|
|
if (availableBytes === 0) {
|
|
throw new Error('no bytes available');
|
|
}
|
|
workingBytes.set(data.subarray(position, position + availableBytes));
|
|
this.word = new DataView(workingBytes.buffer).getUint32(0);
|
|
// track the amount of this.data that has been processed
|
|
this.bitsAvailable = availableBytes * 8;
|
|
this.bytesAvailable -= availableBytes;
|
|
}
|
|
|
|
// (count:int):void
|
|
|
|
}, {
|
|
key: 'skipBits',
|
|
value: function skipBits(count) {
|
|
var skipBytes; // :int
|
|
if (this.bitsAvailable > count) {
|
|
this.word <<= count;
|
|
this.bitsAvailable -= count;
|
|
} else {
|
|
count -= this.bitsAvailable;
|
|
skipBytes = count >> 3;
|
|
count -= skipBytes >> 3;
|
|
this.bytesAvailable -= skipBytes;
|
|
this.loadWord();
|
|
this.word <<= count;
|
|
this.bitsAvailable -= count;
|
|
}
|
|
}
|
|
|
|
// (size:int):uint
|
|
|
|
}, {
|
|
key: 'readBits',
|
|
value: function readBits(size) {
|
|
var bits = Math.min(this.bitsAvailable, size),
|
|
// :uint
|
|
valu = this.word >>> 32 - bits; // :uint
|
|
if (size > 32) {
|
|
_logger.logger.error('Cannot read more than 32 bits at a time');
|
|
}
|
|
this.bitsAvailable -= bits;
|
|
if (this.bitsAvailable > 0) {
|
|
this.word <<= bits;
|
|
} else if (this.bytesAvailable > 0) {
|
|
this.loadWord();
|
|
}
|
|
bits = size - bits;
|
|
if (bits > 0 && this.bitsAvailable) {
|
|
return valu << bits | this.readBits(bits);
|
|
} else {
|
|
return valu;
|
|
}
|
|
}
|
|
|
|
// ():uint
|
|
|
|
}, {
|
|
key: 'skipLZ',
|
|
value: function skipLZ() {
|
|
var leadingZeroCount; // :uint
|
|
for (leadingZeroCount = 0; leadingZeroCount < this.bitsAvailable; ++leadingZeroCount) {
|
|
if (0 !== (this.word & 0x80000000 >>> leadingZeroCount)) {
|
|
// the first bit of working word is 1
|
|
this.word <<= leadingZeroCount;
|
|
this.bitsAvailable -= leadingZeroCount;
|
|
return leadingZeroCount;
|
|
}
|
|
}
|
|
// we exhausted word and still have not found a 1
|
|
this.loadWord();
|
|
return leadingZeroCount + this.skipLZ();
|
|
}
|
|
|
|
// ():void
|
|
|
|
}, {
|
|
key: 'skipUEG',
|
|
value: function skipUEG() {
|
|
this.skipBits(1 + this.skipLZ());
|
|
}
|
|
|
|
// ():void
|
|
|
|
}, {
|
|
key: 'skipEG',
|
|
value: function skipEG() {
|
|
this.skipBits(1 + this.skipLZ());
|
|
}
|
|
|
|
// ():uint
|
|
|
|
}, {
|
|
key: 'readUEG',
|
|
value: function readUEG() {
|
|
var clz = this.skipLZ(); // :uint
|
|
return this.readBits(clz + 1) - 1;
|
|
}
|
|
|
|
// ():int
|
|
|
|
}, {
|
|
key: 'readEG',
|
|
value: function readEG() {
|
|
var valu = this.readUEG(); // :int
|
|
if (0x01 & valu) {
|
|
// the number is odd if the low order bit is set
|
|
return 1 + valu >>> 1; // add 1 to make it even, and divide by 2
|
|
} else {
|
|
return -1 * (valu >>> 1); // divide by two then make it negative
|
|
}
|
|
}
|
|
|
|
// Some convenience functions
|
|
// :Boolean
|
|
|
|
}, {
|
|
key: 'readBoolean',
|
|
value: function readBoolean() {
|
|
return 1 === this.readBits(1);
|
|
}
|
|
|
|
// ():int
|
|
|
|
}, {
|
|
key: 'readUByte',
|
|
value: function readUByte() {
|
|
return this.readBits(8);
|
|
}
|
|
|
|
// ():int
|
|
|
|
}, {
|
|
key: 'readUShort',
|
|
value: function readUShort() {
|
|
return this.readBits(16);
|
|
}
|
|
// ():int
|
|
|
|
}, {
|
|
key: 'readUInt',
|
|
value: function readUInt() {
|
|
return this.readBits(32);
|
|
}
|
|
|
|
/**
|
|
* Advance the ExpGolomb decoder past a scaling list. The scaling
|
|
* list is optionally transmitted as part of a sequence parameter
|
|
* set and is not relevant to transmuxing.
|
|
* @param count {number} the number of entries in this scaling list
|
|
* @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1
|
|
*/
|
|
|
|
}, {
|
|
key: 'skipScalingList',
|
|
value: function skipScalingList(count) {
|
|
var lastScale = 8,
|
|
nextScale = 8,
|
|
j,
|
|
deltaScale;
|
|
for (j = 0; j < count; j++) {
|
|
if (nextScale !== 0) {
|
|
deltaScale = this.readEG();
|
|
nextScale = (lastScale + deltaScale + 256) % 256;
|
|
}
|
|
lastScale = nextScale === 0 ? lastScale : nextScale;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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 data {Uint8Array} the bytes of a sequence parameter set
|
|
* @return {object} an object with configuration parsed from the
|
|
* sequence parameter set, including the dimensions of the
|
|
* associated video frames.
|
|
*/
|
|
|
|
}, {
|
|
key: 'readSPS',
|
|
value: function readSPS() {
|
|
var frameCropLeftOffset = 0,
|
|
frameCropRightOffset = 0,
|
|
frameCropTopOffset = 0,
|
|
frameCropBottomOffset = 0,
|
|
profileIdc,
|
|
profileCompat,
|
|
levelIdc,
|
|
numRefFramesInPicOrderCntCycle,
|
|
picWidthInMbsMinus1,
|
|
picHeightInMapUnitsMinus1,
|
|
frameMbsOnlyFlag,
|
|
scalingListCount,
|
|
i,
|
|
readUByte = this.readUByte.bind(this),
|
|
readBits = this.readBits.bind(this),
|
|
readUEG = this.readUEG.bind(this),
|
|
readBoolean = this.readBoolean.bind(this),
|
|
skipBits = this.skipBits.bind(this),
|
|
skipEG = this.skipEG.bind(this),
|
|
skipUEG = this.skipUEG.bind(this),
|
|
skipScalingList = this.skipScalingList.bind(this);
|
|
|
|
readUByte();
|
|
profileIdc = readUByte(); // profile_idc
|
|
profileCompat = readBits(5); // constraint_set[0-4]_flag, u(5)
|
|
skipBits(3); // reserved_zero_3bits u(3),
|
|
levelIdc = readUByte(); //level_idc u(8)
|
|
skipUEG(); // seq_parameter_set_id
|
|
// some profiles have more optional data we don't need
|
|
if (profileIdc === 100 || profileIdc === 110 || profileIdc === 122 || profileIdc === 244 || profileIdc === 44 || profileIdc === 83 || profileIdc === 86 || profileIdc === 118 || profileIdc === 128) {
|
|
var chromaFormatIdc = readUEG();
|
|
if (chromaFormatIdc === 3) {
|
|
skipBits(1); // separate_colour_plane_flag
|
|
}
|
|
skipUEG(); // bit_depth_luma_minus8
|
|
skipUEG(); // bit_depth_chroma_minus8
|
|
skipBits(1); // qpprime_y_zero_transform_bypass_flag
|
|
if (readBoolean()) {
|
|
// seq_scaling_matrix_present_flag
|
|
scalingListCount = chromaFormatIdc !== 3 ? 8 : 12;
|
|
for (i = 0; i < scalingListCount; i++) {
|
|
if (readBoolean()) {
|
|
// seq_scaling_list_present_flag[ i ]
|
|
if (i < 6) {
|
|
skipScalingList(16);
|
|
} else {
|
|
skipScalingList(64);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
skipUEG(); // log2_max_frame_num_minus4
|
|
var picOrderCntType = readUEG();
|
|
if (picOrderCntType === 0) {
|
|
readUEG(); //log2_max_pic_order_cnt_lsb_minus4
|
|
} else if (picOrderCntType === 1) {
|
|
skipBits(1); // delta_pic_order_always_zero_flag
|
|
skipEG(); // offset_for_non_ref_pic
|
|
skipEG(); // offset_for_top_to_bottom_field
|
|
numRefFramesInPicOrderCntCycle = readUEG();
|
|
for (i = 0; i < numRefFramesInPicOrderCntCycle; i++) {
|
|
skipEG(); // offset_for_ref_frame[ i ]
|
|
}
|
|
}
|
|
skipUEG(); // max_num_ref_frames
|
|
skipBits(1); // gaps_in_frame_num_value_allowed_flag
|
|
picWidthInMbsMinus1 = readUEG();
|
|
picHeightInMapUnitsMinus1 = readUEG();
|
|
frameMbsOnlyFlag = readBits(1);
|
|
if (frameMbsOnlyFlag === 0) {
|
|
skipBits(1); // mb_adaptive_frame_field_flag
|
|
}
|
|
skipBits(1); // direct_8x8_inference_flag
|
|
if (readBoolean()) {
|
|
// frame_cropping_flag
|
|
frameCropLeftOffset = readUEG();
|
|
frameCropRightOffset = readUEG();
|
|
frameCropTopOffset = readUEG();
|
|
frameCropBottomOffset = readUEG();
|
|
}
|
|
var pixelRatio = [1, 1];
|
|
if (readBoolean()) {
|
|
// vui_parameters_present_flag
|
|
if (readBoolean()) {
|
|
// aspect_ratio_info_present_flag
|
|
var aspectRatioIdc = readUByte();
|
|
switch (aspectRatioIdc) {
|
|
case 1:
|
|
pixelRatio = [1, 1];break;
|
|
case 2:
|
|
pixelRatio = [12, 11];break;
|
|
case 3:
|
|
pixelRatio = [10, 11];break;
|
|
case 4:
|
|
pixelRatio = [16, 11];break;
|
|
case 5:
|
|
pixelRatio = [40, 33];break;
|
|
case 6:
|
|
pixelRatio = [24, 11];break;
|
|
case 7:
|
|
pixelRatio = [20, 11];break;
|
|
case 8:
|
|
pixelRatio = [32, 11];break;
|
|
case 9:
|
|
pixelRatio = [80, 33];break;
|
|
case 10:
|
|
pixelRatio = [18, 11];break;
|
|
case 11:
|
|
pixelRatio = [15, 11];break;
|
|
case 12:
|
|
pixelRatio = [64, 33];break;
|
|
case 13:
|
|
pixelRatio = [160, 99];break;
|
|
case 14:
|
|
pixelRatio = [4, 3];break;
|
|
case 15:
|
|
pixelRatio = [3, 2];break;
|
|
case 16:
|
|
pixelRatio = [2, 1];break;
|
|
case 255:
|
|
{
|
|
pixelRatio = [readUByte() << 8 | readUByte(), readUByte() << 8 | readUByte()];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return {
|
|
width: Math.ceil((picWidthInMbsMinus1 + 1) * 16 - frameCropLeftOffset * 2 - frameCropRightOffset * 2),
|
|
height: (2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16 - (frameMbsOnlyFlag ? 2 : 4) * (frameCropTopOffset + frameCropBottomOffset),
|
|
pixelRatio: pixelRatio
|
|
};
|
|
}
|
|
}, {
|
|
key: 'readSliceType',
|
|
value: function readSliceType() {
|
|
// skip NALu type
|
|
this.readUByte();
|
|
// discard first_mb_in_slice
|
|
this.readUEG();
|
|
// return slice_type
|
|
return this.readUEG();
|
|
}
|
|
}]);
|
|
|
|
return ExpGolomb;
|
|
}();
|
|
|
|
exports.default = ExpGolomb; |