/* exported H265Session */ /* global Uint8Array, H265SPSParser, H265Decoder, inheritObject, RtpSession, VideoBufferList, ArrayBuffer, decodeMode */ /* eslint-disable no-magic-numbers */ var H265Session = function () { 'use strict'; var rtpTimeStamp = 0, inputLength = 0, size1K = 1024, size1M = size1K * size1K, playback = false, outputSize = 0, curSize = 0, inputBuffer = new Uint8Array(size1M), PREFIX = new Uint8Array(4), SPSParser = null, privIRtpTime = 0, // frameDiffTime = 0, // needDropCnt = 0, delayingTime = 0, DELAY_LIMIT = 8000, decodedData = { frameData: null, timeStamp: null, }, timeData = { 'timestamp': null, 'timezone': null, }, frameRate = 0; var width = 0, height = 0; PREFIX[0] = '0x00'; PREFIX[1] = '0x00'; PREFIX[2] = '0x00'; PREFIX[3] = '0x01'; var setBuffer = function (buffer1, buffer2) { var tempBuffer = buffer1; if ((inputLength + buffer2.length) > tempBuffer.length) { tempBuffer = new Uint8Array(tempBuffer.length + size1M); } tempBuffer.set(buffer2, inputLength); inputLength += buffer2.length; return tempBuffer; }; var decodeMode = 'canvas'; function Constructor() { this.decoder = new H265Decoder(); } Constructor.prototype = { init: function () { SPSParser = new H265SPSParser(); console.log(SPSParser.parse) //this.decoder.setIsFirstFrame(false); this.videoBufferList = new VideoBufferList(); this.firstDiffTime = 0; this.checkDelay = true; }, setFrameRate(fps) { frameRate = fps; //console.log('frameRate: ', frameRate) }, remuxRTPData: function (rtspInterleaved, rtpHeader, rtpPayload, isBackup) { var HEADER = rtpHeader, PAYLOAD = null, timeData = { 'timestamp': null, 'timezone': null, }, extensionHeaderLen = 0, PaddingSize = 0, data = {}; if (rtspInterleaved[0] !== 0x24) { console.log("H265Session::it is not valid interleave header (RTSP over TCP)"); return; } else if ((rtpHeader[0] & 0x0F) === 0x0F) { console.log("H265Session::There is additional CSRC which is not handled in this version"); return; } else if ((rtpHeader[0] & 0x20) === 0x20) { PaddingSize = rtpPayload[rtpPayload.length - 1]; console.log("H265Session::PaddingSize - " + PaddingSize); } //Extension bit check in RTPHeader if ((rtpHeader[0] & 0x10) === 0x10) { extensionHeaderLen = (((rtpPayload[2] << 8) | rtpPayload[3]) * 4) + 4; //Playback check if (rtpPayload[0] === 0xAB && rtpPayload[1] === 0xAD) { var startHeader = 4, NTPmsw = new Uint8Array(new ArrayBuffer(4)), NTPlsw = new Uint8Array(new ArrayBuffer(4)), gmt = new Uint8Array(new ArrayBuffer(2)), fsynctime = { 'seconds': null, 'useconds': null, }, microseconds = null; NTPmsw.set(rtpPayload.subarray(startHeader, startHeader + 4), 0); startHeader += 4; NTPlsw.set(rtpPayload.subarray(startHeader, startHeader + 4), 0); startHeader += 6; gmt.set(rtpPayload.subarray(startHeader, startHeader + 2), 0); microseconds = (this.ntohl(NTPlsw) / 0xffffffff) * 1000; fsynctime.seconds = ((this.ntohl(NTPmsw) - 0x83AA7E80) >>> 0); fsynctime.useconds = microseconds; gmt = (((gmt[0] << 8) | gmt[1]) << 16) >> 16; timeData = { timestamp: fsynctime.seconds, timestamp_usec: fsynctime.useconds, timezone: gmt, }; if ((this.getFramerate() === 0 || typeof this.getFramerate() === "undefined") && (typeof this.getTimeStamp() !== "undefined")) { var diffUsec = timeData.timestamp_usec - this.getTimeStamp().timestamp_usec; this.setFramerate(Math.round(1000 / (((timeData.timestamp - this.getTimeStamp().timestamp) === 0 ? 0 : 1000) + (diffUsec)))); } this.setTimeStamp(timeData); playback = true; } } PAYLOAD = rtpPayload.subarray(extensionHeaderLen, rtpPayload.length - PaddingSize); rtpTimeStamp = this.ntohl(rtpHeader.subarray(4, 8)); var nalType = (PAYLOAD[0] >> 1) & 0x3f; switch (nalType) { default: inputBuffer = setBuffer(inputBuffer, PREFIX); inputBuffer = setBuffer(inputBuffer, PAYLOAD); break; // Fragmentation unit(FU) case 49: var startBit = ((PAYLOAD[2] & 0x80) === 0x80); var endBit = ((PAYLOAD[2] & 0x40) === 0x40); var fuType = PAYLOAD[2] & 0x3f; var payloadStartIndex = 3; if (startBit === true && endBit === false) { var newNalHeader = new Uint8Array(2); newNalHeader[0] = (PAYLOAD[0] & 0x81) | (fuType << 1); newNalHeader[1] = PAYLOAD[1]; inputBuffer = setBuffer(inputBuffer, PREFIX); inputBuffer = setBuffer(inputBuffer, newNalHeader); inputBuffer = setBuffer(inputBuffer, PAYLOAD.subarray(payloadStartIndex, PAYLOAD.length)); } else { inputBuffer = setBuffer(inputBuffer, PAYLOAD.subarray(payloadStartIndex, PAYLOAD.length)); } break; //SPS case 33: SPSParser.parse(PAYLOAD); var resolution = SPSParser.getSizeInfo(); curSize = resolution.width * resolution.height; if (playback && !isBackup) { var limitSize = 1280 * 720; if (curSize > limitSize) { data.error = { errorCode: "998", description: "Resolution is too big", place: "h265Session.js", }; this.rtpReturnCallback(data); return; // data; } } inputBuffer = setBuffer(inputBuffer, PREFIX); inputBuffer = setBuffer(inputBuffer, PAYLOAD); width = resolution.width; height = resolution.height; break; } //end of switch(nalType) //case 48 Remove because not use more //check marker bit var frameType = ''; if ((HEADER[1] & 0x80) === 0x80) { // var DepacketizingTime = Date.now() - beforeDepacketizing; if (outputSize !== curSize) { outputSize = curSize; this.decoder.setOutputSize(outputSize); } var inputBufferSub = inputBuffer.subarray(0, inputLength); if (inputBufferSub[4] === 0x40) { frameType = 'I'; if (this.firstDiffTime == 0) { privIRtpTime = rtpTimeStamp; delayingTime = 0; this.firstDiffTime = (Date.now() - (rtpTimeStamp / 90).toFixed(0)); // needDropCnt = 0; } else { // frameDiffTime = Math.round(((rtpTimeStamp / 90).toFixed(0) - privIRtpTime) / this.getGovLength()); if((rtpTimeStamp - privIRtpTime) < 0){ this.firstDiffTime = delayingTime + (Date.now() - (rtpTimeStamp / 90).toFixed(0)); // console.log("firstDiffTime = " + this.firstDiffTime + " rtpTimeStamp = " + rtpTimeStamp + " privIRtpTime = " + privIRtpTime ); } delayingTime = (Date.now() - (rtpTimeStamp / 90).toFixed(0)) - this.firstDiffTime; privIRtpTime = rtpTimeStamp; if (delayingTime > DELAY_LIMIT) { if (this.checkDelay === true && playback === false) { data.error = { errorCode: "997", description: "Delay time is too long", place: "h265Session.js", }; // console.log("h265Session::Delay time is too long 997 error "); this.rtpReturnCallback(data); return; // data; } } // needDropCnt = (needDropCnt > 0) ? Math.round(needDropCnt / frameDiffTime) : 0; } } else { frameType = 'P'; } decodedData.frameData = null; if (isBackup !== true || playback !== true) { console.log('frameType', frameType) decodedData.frameData = this.decoder.decode(inputBufferSub); } decodedData.timeStamp = null; inputLength = 0; if (playback === true) { timeData = (timeData.timestamp === null ? this.getTimeStamp() : timeData); decodedData.timeStamp = timeData; } if (isBackup) { data.backupData = { 'stream': inputBufferSub, 'frameType': frameType, 'width': width, 'height': height, 'codecType': 'h265', }; if (timeData.timestamp !== null && typeof timeData.timestamp !== "undefined") { data.backupData.timestamp_usec = timeData.timestamp_usec; } else { data.backupData.timestamp = (rtpTimeStamp / 90).toFixed(0); } } data.decodedData = decodedData; if (decodeMode !== "canvas") { data.decodeMode = "canvas"; } console.log(data) this.rtpReturnCallback(data); return; // data; } }, bufferingRtpData: function (rtspInterleaved, rtpHeader, rtpPayload) { var HEADER = rtpHeader, PAYLOAD = null, extensionHeaderLen = 0, PaddingSize = 0; if (rtspInterleaved[0] !== 0x24) { console.log("H265Session::it is not valid interleave header (RTSP over TCP)"); return; } else if ((rtpHeader[0] & 0x0F) === 0x0F) { console.log("H265Session::There is additional CSRC which is not handled in this version"); return; } else if ((rtpHeader[0] & 0x20) === 0x20) { PaddingSize = rtpPayload[rtpPayload.length - 1]; console.log("H265Session::PaddingSize - " + PaddingSize); } //Extension bit check in RTPHeader if ((rtpHeader[0] & 0x10) === 0x10) { extensionHeaderLen = (((rtpPayload[2] << 8) | rtpPayload[3]) * 4) + 4; //Playback check if (rtpPayload[0] === 0xAB && rtpPayload[1] === 0xAD) { var startHeader = 4, NTPmsw = new Uint8Array(new ArrayBuffer(4)), NTPlsw = new Uint8Array(new ArrayBuffer(4)), gmt = new Uint8Array(new ArrayBuffer(2)), fsynctime = { 'seconds': null, 'useconds': null, }, microseconds = null; NTPmsw.set(rtpPayload.subarray(startHeader, startHeader + 4), 0); startHeader += 4; NTPlsw.set(rtpPayload.subarray(startHeader, startHeader + 4), 0); startHeader += 6; gmt.set(rtpPayload.subarray(startHeader, startHeader + 2), 0); microseconds = (this.ntohl(NTPlsw) / 0xffffffff) * 1000; fsynctime.seconds = ((this.ntohl(NTPmsw) - 0x83AA7E80) >>> 0); fsynctime.useconds = microseconds; gmt = (((gmt[0] << 8) | gmt[1]) << 16) >> 16; timeData = { timestamp: fsynctime.seconds, timestamp_usec: fsynctime.useconds, timezone: gmt, }; playback = true; } } PAYLOAD = rtpPayload.subarray(extensionHeaderLen, rtpPayload.length - PaddingSize); rtpTimeStamp = new Uint8Array(new ArrayBuffer(4)); rtpTimeStamp.set(rtpHeader.subarray(4, 8), 0); rtpTimeStamp = this.ntohl(rtpTimeStamp); var nalType = (PAYLOAD[0] >> 1) & 0x3f; switch (nalType) { default: inputBuffer = setBuffer(inputBuffer, PREFIX); inputBuffer = setBuffer(inputBuffer, PAYLOAD); break; // Fragmentation unit(FU) case 49: var startBit = ((PAYLOAD[2] & 0x80) === 0x80); var endBit = ((PAYLOAD[2] & 0x40) === 0x40); var fuType = PAYLOAD[2] & 0x3f; var payloadStartIndex = 3; if (startBit === true && endBit === false) { var newNalHeader = new Uint8Array(2); newNalHeader[0] = (PAYLOAD[0] & 0x81) | (fuType << 1); newNalHeader[1] = PAYLOAD[1]; inputBuffer = setBuffer(inputBuffer, PREFIX); inputBuffer = setBuffer(inputBuffer, newNalHeader); inputBuffer = setBuffer(inputBuffer, PAYLOAD.subarray(payloadStartIndex, PAYLOAD.length)); } else { inputBuffer = setBuffer(inputBuffer, PAYLOAD.subarray(payloadStartIndex, PAYLOAD.length)); } break; //SPS case 33: SPSParser.parse(PAYLOAD); var resolution = SPSParser.getSizeInfo(); curSize = resolution.width * resolution.height; inputBuffer = setBuffer(inputBuffer, PREFIX); inputBuffer = setBuffer(inputBuffer, PAYLOAD); width = resolution.width; height = resolution.height; break; } //end of switch(nalType) //case 48 Remove because not use more //check marker bit if ((HEADER[1] & 0x80) === 0x80) { if (outputSize !== curSize) { outputSize = curSize; this.decoder.setOutputSize(outputSize); } var stepBufferSub = new Uint8Array(inputBuffer.subarray(0, inputLength)); if (this.videoBufferList !== null) { this.videoBufferList.push(stepBufferSub, width, height, 'h265', (stepBufferSub[4] === 0x40) ? 'I' : 'P', timeData); } inputLength = 0; } }, findIFrame: function () { if (this.videoBufferList !== null) { var bufferNode = this.videoBufferList.findIFrame(); if (bufferNode === null || typeof bufferNode === "undefined") { return false; } else { var data = {}; this.setTimeStamp(bufferNode.timeStamp); data.frameData = this.decoder.decode(bufferNode.buffer); data.timeStamp = bufferNode.timeStamp; return data; } } }, ntohl: function (buffer) { return (((buffer[0] << 24) + (buffer[1] << 16) + (buffer[2] << 8) + buffer[3]) >>> 0); }, set rtpSessionCallback(func) { this.rtpReturnCallback = func; }, }; return new Constructor(); }; /* eslint-enable no-magic-numbers */