/* eslint-disable */
|
|
import Event from '../events';
|
import ExpGolomb from './exp-golomb';
|
import EventHandler from '../event-handler';
|
import MP4Remuxer from '../remux/mp4-remuxer';
|
import { logger } from '../utils/logger';
|
|
class h264Demuxer extends EventHandler {
|
|
constructor(wfs, config = null) {
|
super(wfs,
|
Event.H264_DATA_PARSING);
|
|
this.config = this.wfs.config || config;
|
this.wfs = wfs;
|
this.id = 'main';
|
|
this.remuxer = new MP4Remuxer(this.wfs, this.id, this.config);
|
this.contiguous = true;
|
this.timeOffset = 1;
|
this.sn = 0;
|
this.TIMESCALE = 90000;
|
this.timestamp = 0;
|
this.scaleFactor = this.TIMESCALE / 1000;
|
this.H264_TIMEBASE = 3000;
|
this._avcTrack = {
|
container: 'video/mp2t', type: 'video', id: 1, sequenceNumber: 0,
|
samples: [], len: 0, nbNalu: 0, dropped: 0, count: 0
|
};
|
this.browserType = 0;
|
if (navigator.userAgent.toLowerCase().indexOf('firefox') !== -1) {
|
this.browserType = 1;
|
}
|
}
|
|
destroy() {
|
EventHandler.prototype.destroy.call(this);
|
}
|
|
getTimestampM() {
|
this.timestamp += this.H264_TIMEBASE;
|
return this.timestamp;
|
}
|
|
onH264DataParsing(event) {
|
this._parseAVCTrack(event.data);
|
// console.log(this.browserType,'onH264DataParsing')
|
this.remuxer.pushVideo(0, this.sn, this._avcTrack, this.timeOffset, this.contiguous);
|
this.sn += 1;
|
}
|
|
_parseAVCTrack(array) {
|
var track = this._avcTrack,
|
samples = track.samples,
|
units = this._parseAVCNALu(array),
|
units2 = [],
|
debug = false,
|
key = false,
|
length = 0,
|
expGolombDecoder,
|
avcSample,
|
push,
|
i;
|
var debugString = '';
|
var pushAccesUnit = function () {
|
if (units2.length) {
|
if (!this.config.forceKeyFrameOnDiscontinuity ||
|
key === true ||
|
(track.sps && (samples.length || this.contiguous))) {
|
var tss = this.getTimestampM();
|
avcSample = { units: { units: units2, length: length }, pts: tss, dts: tss, key: key };
|
samples.push(avcSample);
|
track.len += length;
|
track.nbNalu += units2.length;
|
} else {
|
track.dropped++;
|
}
|
units2 = [];
|
length = 0;
|
}
|
}.bind(this);
|
|
units.forEach(unit => {
|
switch (unit.type) {
|
//NDR
|
case 1:
|
push = true;
|
if (debug) {
|
debugString += 'NDR ';
|
}
|
break;
|
//IDR
|
case 5:
|
push = true;
|
if (debug) {
|
debugString += 'IDR ';
|
}
|
key = true;
|
break;
|
//SEI
|
case 6:
|
unit.data = this.discardEPB(unit.data);
|
expGolombDecoder = new ExpGolomb(unit.data);
|
// skip frameType
|
expGolombDecoder.readUByte();
|
break;
|
//SPS
|
case 7:
|
push = false;
|
if (debug) {
|
debugString += 'SPS ';
|
}
|
if (!track.sps) {
|
expGolombDecoder = new ExpGolomb(unit.data);
|
var config = expGolombDecoder.readSPS();
|
track.width = config.width;
|
track.height = config.height;
|
track.sps = [unit.data];
|
track.duration = 0;
|
var codecarray = unit.data.subarray(1, 4);
|
var codecstring = 'avc1.';
|
for (i = 0; i < 3; i++) {
|
var h = codecarray[i].toString(16);
|
if (h.length < 2) {
|
h = '0' + h;
|
}
|
codecstring += h;
|
}
|
track.codec = codecstring;
|
this.wfs.trigger(Event.BUFFER_RESET, { mimeType: track.codec });
|
push = true;
|
}
|
break;
|
//PPS
|
case 8:
|
push = false;
|
if (debug) {
|
debugString += 'PPS ';
|
}
|
if (!track.pps) {
|
track.pps = [unit.data];
|
push = true;
|
}
|
break;
|
case 9:
|
push = false;
|
if (debug) {
|
debugString += 'AUD ';
|
}
|
pushAccesUnit();
|
break;
|
default:
|
push = false;
|
debugString += 'unknown NAL ' + unit.type + ' ';
|
break;
|
}
|
|
if (push) {
|
units2.push(unit);
|
length += unit.data.byteLength;
|
}
|
|
});
|
|
if (debug || debugString.length) {
|
logger.log(debugString);
|
}
|
|
pushAccesUnit();
|
|
}
|
|
_parseAVCNALu(array) {
|
var i = 0, len = array.byteLength, value, overflow, state = 0; //state = this.avcNaluState;
|
var units = [], unit, unitType, lastUnitStart, lastUnitType;
|
while (i < len) {
|
value = array[i++];
|
// finding 3 or 4-byte start codes (00 00 01 OR 00 00 00 01)
|
switch (state) {
|
case 0:
|
if (value === 0) {
|
state = 1;
|
}
|
break;
|
case 1:
|
if (value === 0) {
|
state = 2;
|
} else {
|
state = 0;
|
}
|
break;
|
case 2:
|
case 3:
|
if (value === 0) {
|
state = 3;
|
} else if (value === 1 && i < len) {
|
unitType = array[i] & 0x1f;
|
if (lastUnitStart) {
|
unit = { data: array.subarray(lastUnitStart, i - state - 1), type: lastUnitType };
|
units.push(unit);
|
} else {
|
}
|
lastUnitStart = i;
|
lastUnitType = unitType;
|
state = 0;
|
} else {
|
state = 0;
|
}
|
break;
|
default:
|
break;
|
}
|
}
|
|
if (lastUnitStart) {
|
unit = { data: array.subarray(lastUnitStart, len), type: lastUnitType, state: state };
|
units.push(unit);
|
}
|
|
return units;
|
}
|
|
/**
|
* remove Emulation Prevention bytes from a RBSP
|
*/
|
discardEPB(data) {
|
var length = data.byteLength,
|
EPBPositions = [],
|
i = 1,
|
newLength, newData;
|
// Find all `Emulation Prevention Bytes`
|
while (i < length - 2) {
|
if (data[i] === 0 &&
|
data[i + 1] === 0 &&
|
data[i + 2] === 0x03) {
|
EPBPositions.push(i + 2);
|
i += 2;
|
} else {
|
i++;
|
}
|
}
|
// If no Emulation Prevention Bytes were found just return the original
|
// array
|
if (EPBPositions.length === 0) {
|
return data;
|
}
|
// Create a new array to hold the NAL unit data
|
newLength = length - EPBPositions.length;
|
newData = new Uint8Array(newLength);
|
var sourceIndex = 0;
|
|
for (i = 0; i < newLength; sourceIndex++ , i++) {
|
if (sourceIndex === EPBPositions[0]) {
|
// Skip this byte
|
sourceIndex++;
|
// Remove this position index
|
EPBPositions.shift();
|
}
|
newData[i] = data[sourceIndex];
|
}
|
return newData;
|
}
|
|
|
}
|
export default h264Demuxer;
|