|
@@ -0,0 +1,667 @@
|
|
|
+function H264Session() {
|
|
|
+ let rtpTimeStamp = 0;
|
|
|
+ let size1M = 1048576; //1024 * 1024
|
|
|
+ let inputBuffer = new Uint8Array(size1M);
|
|
|
+ let spsSegment = null;
|
|
|
+ let ppsSegment = null;
|
|
|
+
|
|
|
+ let SPSParser = null;
|
|
|
+
|
|
|
+ let width = 0;
|
|
|
+ let height = 0;
|
|
|
+ let inputLength = 0;
|
|
|
+
|
|
|
+ let initalSegmentFlag = true; //用于确定是否是initSegment
|
|
|
+ let initalMediaFrameFlag = true;
|
|
|
+
|
|
|
+ let frameRate = null; //根据SDP或者SPS设置
|
|
|
+ let preSample = null; //上一个Sample
|
|
|
+ let durationTimeCount = 0;
|
|
|
+ let frameCount = 0;
|
|
|
+ let inputSegBufferSub = null;
|
|
|
+
|
|
|
+ //MSE使用的数据以及相关配置,顺序codecInfo -> initSegmentData -> mediaSample -> frameData
|
|
|
+ //时间戳用于绘制人脸框
|
|
|
+ let decodedData = {
|
|
|
+ frameData: null, //视频数据
|
|
|
+ timeStamp: null, //时间戳
|
|
|
+ initSegmentData: null, //MP4配置,用于initsegment
|
|
|
+ mediaSample: null, //使用duration控制每一帧的播放时间
|
|
|
+ codecInfo: "", //MSE init时传入,用于创建mediasource
|
|
|
+ };
|
|
|
+ let FRAMETYPE = {
|
|
|
+ 1: 'P',
|
|
|
+ 5: 'I',
|
|
|
+ 6: 'SEI',
|
|
|
+ 7: 'I'
|
|
|
+ };
|
|
|
+ let frameType = '';
|
|
|
+ let decodeMode = 'video';
|
|
|
+ let outputSize = 0;
|
|
|
+ let curSize = 0;
|
|
|
+
|
|
|
+ const PREFIX = new Uint8Array(['0x00', '0x00', '0x00', '0x01']);
|
|
|
+
|
|
|
+ let firstIframe = false;
|
|
|
+
|
|
|
+ let SEIInfo = {
|
|
|
+ ivs: null,
|
|
|
+ timestamp: null,
|
|
|
+ };
|
|
|
+
|
|
|
+ let preWidth = null,
|
|
|
+ preHeight = null;
|
|
|
+ let resetTimeCount = 0;
|
|
|
+ let lastTimeStamp = 0;
|
|
|
+ //const RESETTIME = 162000000;
|
|
|
+ const RESETTIME = 4320000;
|
|
|
+ let SEIBuffer = null;
|
|
|
+ let lastSEITime = 0;
|
|
|
+
|
|
|
+ function constructor() {
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ constructor.prototype = {
|
|
|
+ init() {
|
|
|
+ SPSParser = new H264SPSParser();
|
|
|
+ this.resolutionChangedCallback = () => {
|
|
|
+ };
|
|
|
+ },
|
|
|
+
|
|
|
+ remuxRTPData(rtspInterleaved, rtpHeader, rtpPayload) {
|
|
|
+ //console.log(rtspInterleaved)
|
|
|
+ //console.log(rtpHeader)
|
|
|
+ let PaddingSize = 0;
|
|
|
+ let extensionHeaderLen = 0; //如果RtpHeader.X=1,则在RTP报头后跟有一个扩展报头
|
|
|
+ let PAYLOAD = null;
|
|
|
+//console.log(rtpHeader)
|
|
|
+//console.log(rtspInterleaved, rtpHeader, rtpPayload.subarray(0,5))
|
|
|
+ let RtpHeader = {
|
|
|
+ V: rtpHeader[0] >>> 6,
|
|
|
+ P: rtpHeader[0] & 0x20,
|
|
|
+ X: rtpHeader[0] & 0x10,
|
|
|
+ CC: rtpHeader[0] & 0x0F,
|
|
|
+ M: (rtpHeader[1] & 0x80) >> 7,
|
|
|
+ PT: rtpHeader[1] & 127,
|
|
|
+ SN: (rtpHeader[2] << 8) + rtpHeader[3],
|
|
|
+ timeStamp: (rtpHeader[4] << 24) + (rtpHeader[5] << 16) + (rtpHeader[6] << 8) + rtpHeader[7],
|
|
|
+ SSRC: (rtpHeader[8] << 24) + (rtpHeader[9] << 16) + (rtpHeader[10] << 8) + rtpHeader[11],
|
|
|
+ };
|
|
|
+ if (RtpHeader.P) { //填充
|
|
|
+ PaddingSize = rtpPayload[rtpPayload.length - 1];
|
|
|
+ // console.log("Padding - " + PaddingSize);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (RtpHeader.X) { //扩展
|
|
|
+ extensionHeaderLen = (((rtpPayload[2] << 8) | rtpPayload[3]) * 4) + 4;
|
|
|
+ console.log('X: ' + rtpPayload[0])
|
|
|
+ }
|
|
|
+//console.log('extensionHeaderLen: '+ extensionHeaderLen)
|
|
|
+ PAYLOAD = rtpPayload.subarray(extensionHeaderLen, rtpPayload.length - PaddingSize);
|
|
|
+ rtpTimeStamp = RtpHeader.timeStamp;
|
|
|
+
|
|
|
+ //console.log(rtpTimeStamp, rtpHeader[4], rtpHeader[5], rtpHeader[6] , rtpHeader[7], PAYLOAD[0] & 0x1f)
|
|
|
+ /* 载荷结构(https://blog.csdn.net/davebobo/article/details/52994596)
|
|
|
+ +---------------+
|
|
|
+ |0|1|2|3|4|5|6|7|
|
|
|
+ +-+-+-+-+-+-+-+-+
|
|
|
+ |F|NRI| Type |
|
|
|
+ +---------------+
|
|
|
+ Type = 1-23 单个NAL单元包
|
|
|
+ Type = 24,25, 26, 27聚合包
|
|
|
+ Type = 28,29, 分片单元
|
|
|
+ */
|
|
|
+ //console.log(rtspInterleaved,rtpHeader, PAYLOAD[0]);
|
|
|
+ let nalType = (PAYLOAD[0] & 0x1f);
|
|
|
+ //console.log(PAYLOAD[0] + ' nalType: ' + nalType);
|
|
|
+
|
|
|
+//console.log('rtpPayload.length: ' + rtpPayload.length)
|
|
|
+//console.log(nalType, PAYLOAD[0])
|
|
|
+//console.log('nalType: ' + nalType, RtpHeader.M)
|
|
|
+ switch (nalType) {
|
|
|
+ case 6: //SEI
|
|
|
+ //console.log(PAYLOAD, String.fromCharCode.apply(null, PAYLOAD))
|
|
|
+ if (SEIParse(PAYLOAD) === null) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ inputBuffer = setBuffer(inputBuffer, PREFIX);
|
|
|
+ inputBuffer = setBuffer(inputBuffer, PAYLOAD);
|
|
|
+ break;
|
|
|
+ case 7: //SPS
|
|
|
+ //console.log('SPS');
|
|
|
+ SPSParser.parse(removeH264or5EmulationBytes(PAYLOAD));
|
|
|
+ let sizeInfo = SPSParser.getSizeInfo();
|
|
|
+ //console.log(SPSParser.getSpsMap())
|
|
|
+ width = sizeInfo.width;
|
|
|
+ height = sizeInfo.height;
|
|
|
+
|
|
|
+ if (preWidth !== width || preHeight !== height) {
|
|
|
+ console.log('resolution changed!');
|
|
|
+ console.log('preWidth: ', preWidth, ' preHeight: ', preHeight, ' width: ', width, ' height: ', height);
|
|
|
+ preWidth = width;
|
|
|
+ preHeight = height;
|
|
|
+ }
|
|
|
+ inputBuffer = setBuffer(inputBuffer, PREFIX);
|
|
|
+ inputBuffer = setBuffer(inputBuffer, PAYLOAD);
|
|
|
+ spsSegment = PAYLOAD;
|
|
|
+ //console.log('width: ',width, 'height: ', height)
|
|
|
+ curSize = sizeInfo.decodeSize;
|
|
|
+ firstIframe = true;
|
|
|
+//console.log(spsSegment)
|
|
|
+ if (frameRate === null) {
|
|
|
+ frameRate = SPSParser.getFPS();
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case 8: //PPS
|
|
|
+ //console.log('PPS')
|
|
|
+ inputBuffer = setBuffer(inputBuffer, PREFIX);
|
|
|
+ inputBuffer = setBuffer(inputBuffer, PAYLOAD);
|
|
|
+ ppsSegment = PAYLOAD;
|
|
|
+//console.log(ppsSegment)
|
|
|
+ break;
|
|
|
+ case 28: //FU
|
|
|
+ //console.log('FU');
|
|
|
+ let startBit = ((PAYLOAD[1] & 0x80) === 0x80),
|
|
|
+ endBit = ((PAYLOAD[1] & 0x40) === 0x40),
|
|
|
+ fuType = PAYLOAD[1] & 0x1f,
|
|
|
+ payloadStartIndex = 2;
|
|
|
+ //console.log('startBit: ' + startBit + ' endBit: ' + endBit)
|
|
|
+ //console.log('fuType: ' + fuType)
|
|
|
+ if (startBit === true && endBit === false) {
|
|
|
+ let newNalHeader = new Uint8Array(1);
|
|
|
+ newNalHeader[0] = ((PAYLOAD[0] & 0xe0) | fuType);
|
|
|
+ //console.log('newNalHeader: ', newNalHeader[0])
|
|
|
+ //console.log('fuType: ' + fuType)
|
|
|
+ //console.log((PAYLOAD[2] << 8) + PAYLOAD[3])
|
|
|
+//console.log(new Uint8Array(PAYLOAD.subarray(0, 100)));
|
|
|
+ if (false) { //赛兰摄像头,SPS,PPS,I帧打在一个RTP包中
|
|
|
+ PAYLOAD[1] = newNalHeader[0];
|
|
|
+ SPSParser.parse(removeH264or5EmulationBytes(PAYLOAD.subarray(1, 27)));
|
|
|
+ let sizeInfo = SPSParser.getSizeInfo();
|
|
|
+//console.log(sizeInfo, SPSParser.getSpsMap())
|
|
|
+ //SPS
|
|
|
+ width = sizeInfo.width;
|
|
|
+ height = sizeInfo.height;
|
|
|
+ inputBuffer = setBuffer(inputBuffer, PREFIX);
|
|
|
+ inputBuffer = setBuffer(inputBuffer, PAYLOAD.subarray(1, 27));
|
|
|
+ spsSegment = PAYLOAD.subarray(1, 27);
|
|
|
+ //console.log('width: ',width, 'height: ', height)
|
|
|
+ curSize = sizeInfo.decodeSize;
|
|
|
+ firstIframe = true;
|
|
|
+
|
|
|
+ //PPS
|
|
|
+ inputBuffer = setBuffer(inputBuffer, PREFIX);
|
|
|
+ inputBuffer = setBuffer(inputBuffer, PAYLOAD.subarray(30, 34));
|
|
|
+ ppsSegment = PAYLOAD.subarray(30, 34);
|
|
|
+
|
|
|
+ //I
|
|
|
+ inputBuffer = setBuffer(inputBuffer, PREFIX);
|
|
|
+ inputBuffer = setBuffer(inputBuffer, PAYLOAD.subarray(38, PAYLOAD.length));
|
|
|
+
|
|
|
+ } else {
|
|
|
+ //console.log(newNalHeader[0] & 0x1f)
|
|
|
+ inputBuffer = setBuffer(inputBuffer, PREFIX);
|
|
|
+ inputBuffer = setBuffer(inputBuffer, newNalHeader);
|
|
|
+ inputBuffer = setBuffer(inputBuffer, PAYLOAD.subarray(payloadStartIndex, PAYLOAD.length));
|
|
|
+ }
|
|
|
+
|
|
|
+ } else {
|
|
|
+ //console.log(startBit, endBit, 'endBit')
|
|
|
+ inputBuffer = setBuffer(inputBuffer,
|
|
|
+ PAYLOAD.subarray(payloadStartIndex, PAYLOAD.length));
|
|
|
+ }
|
|
|
+//console.log(startBit,endBit)
|
|
|
+ // if(endBit === true) {
|
|
|
+ // end = true;
|
|
|
+ // }
|
|
|
+ break;
|
|
|
+ case 1:
|
|
|
+ inputBuffer = setBuffer(inputBuffer, PREFIX);
|
|
|
+ inputBuffer = setBuffer(inputBuffer, PAYLOAD);
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ //console.log('nalType: ' + nalType);
|
|
|
+ //console.log(PAYLOAD)
|
|
|
+ break;
|
|
|
+ }
|
|
|
+//console.log('M: ' + RtpHeader.M + ' ' + (rtpHeader[1] & 0x80))
|
|
|
+ //check marker bit
|
|
|
+ if (RtpHeader.M) {
|
|
|
+ if (!firstIframe) {
|
|
|
+ inputLength = 0;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ let inputBufferSub = inputBuffer.subarray(0, inputLength);
|
|
|
+ frameType = FRAMETYPE[inputBufferSub[4] & 0x1f];
|
|
|
+
|
|
|
+ //只根据视频帧计算resetTimeCount
|
|
|
+ if (frameType !== 'SEI') {
|
|
|
+ // rtp时间戳周期为RESETTIME,如果单向递增,设为0
|
|
|
+ if (lastTimeStamp - rtpTimeStamp > (RESETTIME / 2)) { //判断lastTimeStamp远大于rtpTimeStamp,防止后一帧比前一帧先到的情况
|
|
|
+ //console.log(lastTimeStamp - rtpTimeStamp)
|
|
|
+ console.warn('时间戳重置', lastTimeStamp, rtpTimeStamp, frameType, new Date())
|
|
|
+ resetTimeCount++;
|
|
|
+ }
|
|
|
+ rtpTimeStamp = rtpTimeStamp + RESETTIME * resetTimeCount;
|
|
|
+ } else {
|
|
|
+ //同一帧的SEI比视频发送慢时
|
|
|
+ if (rtpTimeStamp - lastTimeStamp > (RESETTIME / 2)) {
|
|
|
+ console.warn('SEI翻转', rtpTimeStamp, lastTimeStamp);
|
|
|
+ rtpTimeStamp = rtpTimeStamp + RESETTIME * (resetTimeCount - 1);
|
|
|
+ } else {
|
|
|
+ rtpTimeStamp = rtpTimeStamp + RESETTIME * resetTimeCount;
|
|
|
+ }
|
|
|
+ //同一帧的SEI比视频发送快时
|
|
|
+ // if(rtpTimeStamp > lastTimeStamp) {
|
|
|
+ // rtpTimeStamp = rtpTimeStamp + RESETTIME * resetTimeCount;
|
|
|
+ // } else {
|
|
|
+ // rtpTimeStamp = rtpTimeStamp + RESETTIME * (resetTimeCount + 1);
|
|
|
+ // }
|
|
|
+ }
|
|
|
+//console.log('frameType: ', frameType, 'rtpTimeStamp: ', rtpTimeStamp)
|
|
|
+ if (frameType === 'SEI') {
|
|
|
+ //SEI被分片(nal === 28)时,分片包发送完后marker为1,不会和视频帧一起
|
|
|
+ SEIBuffer = inputBuffer.subarray(4, inputLength);
|
|
|
+ //console.log(SEIBuffer)
|
|
|
+ inputBufferSub = new Uint8Array();
|
|
|
+ }
|
|
|
+ if (SEIBuffer) {
|
|
|
+ let SEI = SEIParse(SEIBuffer);
|
|
|
+ if (SEI) {
|
|
|
+ SEIInfo.ivs = SEI;
|
|
|
+ SEIInfo.timestamp = rtpTimeStamp;
|
|
|
+ decodedData.SEIInfo = SEIInfo;
|
|
|
+ if ((rtpTimeStamp - lastSEITime) !== (90000 / frameRate)) {
|
|
|
+ //console.log('SEI 时间差:', (rtpTimeStamp - lastTime), rtpTimeStamp, lastTime)
|
|
|
+ }
|
|
|
+ lastSEITime = rtpTimeStamp;
|
|
|
+ SEIInfo = {
|
|
|
+ ivs: null,
|
|
|
+ timestamp: 0,
|
|
|
+ };
|
|
|
+ }
|
|
|
+ SEIBuffer = null;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!initalSegmentFlag) {
|
|
|
+ decodedData.initSegmentData = null;
|
|
|
+ decodedData.codecInfo = null;
|
|
|
+ } else {
|
|
|
+ initalSegmentFlag = false;
|
|
|
+ const info = {
|
|
|
+ id: 1,
|
|
|
+ width: width,
|
|
|
+ height: height,
|
|
|
+ type: "video",
|
|
|
+ profileIdc: SPSParser.getSpsValue("profile_idc"),
|
|
|
+ profileCompatibility: 0,
|
|
|
+ levelIdc: SPSParser.getSpsValue("level_idc"),
|
|
|
+ sps: [spsSegment],
|
|
|
+ pps: [ppsSegment],
|
|
|
+ timescale: 1e3,
|
|
|
+ fps: frameRate
|
|
|
+ };
|
|
|
+ decodedData.initSegmentData = info;
|
|
|
+ decodedData.codecInfo = SPSParser.getCodecInfo();
|
|
|
+ //console.log(info.pps)
|
|
|
+ }
|
|
|
+
|
|
|
+ if (frameType === 'I') {
|
|
|
+//console.log('ppsSegment: ', ppsSegment)
|
|
|
+ let h264parameterLength = spsSegment.length + ppsSegment.length + 8;
|
|
|
+ inputSegBufferSub = inputBufferSub.subarray(h264parameterLength, inputBufferSub.length);
|
|
|
+ } else {
|
|
|
+ inputSegBufferSub = inputBufferSub.subarray(0, inputBufferSub.length);
|
|
|
+ }
|
|
|
+ if (inputSegBufferSub.length) {
|
|
|
+ let segSize = inputSegBufferSub.length - 4;
|
|
|
+ //mp4 box头
|
|
|
+ inputSegBufferSub[0] = (segSize & 0xFF000000) >>> 24;
|
|
|
+ inputSegBufferSub[1] = (segSize & 0xFF0000) >>> 16;
|
|
|
+ inputSegBufferSub[2] = (segSize & 0xFF00) >>> 8;
|
|
|
+ inputSegBufferSub[3] = (segSize & 0xFF);
|
|
|
+
|
|
|
+ decodedData.frameData = new Uint8Array(inputSegBufferSub);
|
|
|
+
|
|
|
+ let sample = {
|
|
|
+ duration: Math.round((1 / frameRate) * 1000),
|
|
|
+ size: inputSegBufferSub.length,
|
|
|
+ frame_time_stamp: null,
|
|
|
+ frameDuration: null,
|
|
|
+ };
|
|
|
+ sample.frame_time_stamp = rtpTimeStamp; //Todo:暂时为null,通过帧率控制duration
|
|
|
+ if (initalMediaFrameFlag) {
|
|
|
+ sample.frameDuration = 0;
|
|
|
+ initalMediaFrameFlag = false;
|
|
|
+ } else {
|
|
|
+ if (frameRate) {
|
|
|
+ frameCount++;
|
|
|
+ if (!(frameCount % frameRate)) { //每秒最后一帧时
|
|
|
+ sample.frameDuration = 1000 - durationTimeCount;
|
|
|
+ frameCount = 0;
|
|
|
+ durationTimeCount = 0;
|
|
|
+ } else {
|
|
|
+ sample.frameDuration = Math.round(1000 / frameRate);
|
|
|
+ durationTimeCount += Math.round(1000 / frameRate);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ sample.frameDuration = (sample.frame_time_stamp - preSample.frame_time_stamp) / 90; // 时钟频率90000,timescale=1000
|
|
|
+ }
|
|
|
+ //console.log(sample.frameDuration)
|
|
|
+ }
|
|
|
+ preSample = sample;
|
|
|
+ decodedData.mediaSample = sample;
|
|
|
+ decodedData.timeStamp = rtpTimeStamp;
|
|
|
+ }
|
|
|
+ this.handleDecodedData(decodedData);
|
|
|
+ inputLength = 0;
|
|
|
+ decodedData.SEIInfo = null;
|
|
|
+ inputSegBufferSub = null;
|
|
|
+ if (frameType !== 'SEI') {
|
|
|
+ lastTimeStamp = RtpHeader.timeStamp;
|
|
|
+ }
|
|
|
+ frameType = '';
|
|
|
+ }
|
|
|
+ //console.log('xxxxxxxxxxxxxxxxxxxxxxxxx')
|
|
|
+ },
|
|
|
+
|
|
|
+ set rtpSessionCallback(func) {
|
|
|
+ this.handleDecodedData = func;
|
|
|
+ },
|
|
|
+
|
|
|
+ setFrameRate(fps) {
|
|
|
+ frameRate = fps;
|
|
|
+ //console.log('frameRate: ', frameRate)
|
|
|
+ },
|
|
|
+
|
|
|
+ setResolutionChangedCallback(callback) {
|
|
|
+ this.resolutionChangedCallback = callback;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return new constructor();
|
|
|
+
|
|
|
+ function setBuffer(buffer1, buffer2) {
|
|
|
+ let bufferTemp = buffer1;
|
|
|
+ if ((inputLength + buffer2.length) > buffer1.length) {
|
|
|
+ bufferTemp = new Uint8Array(buffer1.length + size1M);
|
|
|
+ }
|
|
|
+
|
|
|
+ bufferTemp.set(buffer2, inputLength);
|
|
|
+ inputLength += buffer2.length;
|
|
|
+ return bufferTemp;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+/**
|
|
|
+ * 去除SPS中的Emulation字节
|
|
|
+ * @param data SPS源数据
|
|
|
+ * @returns {Array} 去除后Emulation字节后的SPS
|
|
|
+ */
|
|
|
+function removeH264or5EmulationBytes(data) {
|
|
|
+ let toSize = 0;
|
|
|
+ let i = 0;
|
|
|
+ let to = [];
|
|
|
+ let dataLength = data.length;
|
|
|
+ while (i < dataLength) {
|
|
|
+ if (i + 2 < dataLength && data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 3) {
|
|
|
+ to[toSize] = to[toSize + 1] = 0;
|
|
|
+ toSize += 2;
|
|
|
+ i += 3;
|
|
|
+ } else {
|
|
|
+ to[toSize] = data[i];
|
|
|
+ toSize += 1;
|
|
|
+ i += 1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return to;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 解析SEI信息
|
|
|
+ * @param data
|
|
|
+ * @return {Array}
|
|
|
+ */
|
|
|
+function SEIParse(data) {
|
|
|
+ //console.log(data)
|
|
|
+ if ((data[0] & 0x1f) !== 6) {
|
|
|
+ //非SEI
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (data[1] !== 0x55 || data[2] !== 0x4C || data[3] !== 0x53 || data[4] !== 0x40) {
|
|
|
+ //ULS@开头
|
|
|
+ console.warn('unknown SEI type',data);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ let dataLength = data.length;
|
|
|
+ let type = (data[5] << 8) + data[6];
|
|
|
+ let checkSum = (data[7] << 8) + data[8];
|
|
|
+ // if(dataLength !== (checkSum + 1 + 4 + 4)) {
|
|
|
+ // console.log('SEI check fail!');
|
|
|
+ // return null;
|
|
|
+ // }
|
|
|
+ let result;
|
|
|
+ data = data.subarray(9);
|
|
|
+ switch (type) {
|
|
|
+ case 0:
|
|
|
+ result = parseFace(data);
|
|
|
+ break;
|
|
|
+ case 1:
|
|
|
+ //console.log(parseBody(data))
|
|
|
+ result = parseBody(data);
|
|
|
+ break;
|
|
|
+ case 2:
|
|
|
+ result = parseRegion(data);
|
|
|
+ break;
|
|
|
+ case 3:
|
|
|
+ result = parseBodyEx(data);
|
|
|
+ break;
|
|
|
+ case 4:
|
|
|
+ result = parseOverSpeed(data);
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ result = null;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+}
|
|
|
+
|
|
|
+function parseFace(data) {
|
|
|
+ //console.log(data)
|
|
|
+ let dataLength = data.length;
|
|
|
+ let contents = [];
|
|
|
+ while (dataLength > 0) {
|
|
|
+ //console.log('dataLength: ', dataLength)
|
|
|
+ let x0 = ((data[4] & 0x7f) << 8) + data[5],
|
|
|
+ y0 = ((data[6] & 0x7f) << 8) + data[7],
|
|
|
+ width = ((data[8] & 0x7f) << 8) + data[9] - x0,
|
|
|
+ height = ((data[10] & 0x7f) << 8) + data[11] - y0;
|
|
|
+ let content = {
|
|
|
+ type: 'rect',
|
|
|
+ id: (data[2] << 8) + data[3],
|
|
|
+ rect: [x0, y0, width, height],
|
|
|
+ state: data[1] & 0x01,
|
|
|
+ quality: (data[1] & 0x02) >> 1,
|
|
|
+ };
|
|
|
+ Array.prototype.push.apply(contents, [content]);
|
|
|
+ data = data.subarray(12);
|
|
|
+ dataLength = data.length;
|
|
|
+ }
|
|
|
+
|
|
|
+ return contents;
|
|
|
+}
|
|
|
+
|
|
|
+function parseBody(data) {
|
|
|
+ let dataLength = data.length;
|
|
|
+ let contents = [];
|
|
|
+ while (dataLength > 0) {
|
|
|
+ //console.log('dataLength: ', dataLength)
|
|
|
+ let x0 = ((data[4] & 0x7f) << 8) + data[5],
|
|
|
+ y0 = ((data[6] & 0x7f) << 8) + data[7],
|
|
|
+ width = ((data[8] & 0x7f) << 8) + data[9] - x0,
|
|
|
+ height = ((data[10] & 0x7f) << 8) + data[11] - y0,
|
|
|
+ boxConfidence = ((data[12] & 0x7f) << 8) + data[13];
|
|
|
+ let points = [];
|
|
|
+
|
|
|
+ for (let i = 0; i < 17; i++) {
|
|
|
+ let point = {
|
|
|
+ x: ((data[16 + i * 8] & 0x7f) << 8) + data[17 + i * 8],
|
|
|
+ y: ((data[18 + i * 8] & 0x7f) << 8) + data[19 + i * 8],
|
|
|
+ confidence: ((data[20 + i * 8] & 0x7f) << 8) + data[21 + i * 8],
|
|
|
+ // x: Math.random() * 8191,
|
|
|
+ // y: Math.random() * 8191,
|
|
|
+ // confidence: 1,
|
|
|
+ };
|
|
|
+ points.push(point);
|
|
|
+ }
|
|
|
+ let content = {
|
|
|
+ type: 'coco-pose',
|
|
|
+ id: (data[2] << 8) + data[3],
|
|
|
+ handsUp: data[1] & 0x04,
|
|
|
+ boundingBox: [x0, y0, width, height],
|
|
|
+ boxConfidence,
|
|
|
+ points: parseBodyToTree(points),
|
|
|
+ state: data[1] & 0x01,
|
|
|
+ };
|
|
|
+ Array.prototype.push.apply(contents, [content]);
|
|
|
+ data = data.subarray(152);
|
|
|
+ dataLength = data.length;
|
|
|
+
|
|
|
+ }
|
|
|
+ return contents;
|
|
|
+}
|
|
|
+
|
|
|
+function parseRegion(data) {
|
|
|
+ let dataLength = data.length;
|
|
|
+ let contents = [];
|
|
|
+ while (dataLength > 0) {
|
|
|
+ let pointNum = (data[0] << 8) + data[1],
|
|
|
+ state = data[3] & 0x03,
|
|
|
+ area = [];
|
|
|
+
|
|
|
+ for (let i = 0; i < pointNum; i++) {
|
|
|
+ let point = {
|
|
|
+ x: ((data[i * 4 + 4] & 0x7f) << 8) + data[i * 4 + 5],
|
|
|
+ y: ((data[i * 4 + 6] & 0x7f) << 8) + data[i * 4 + 7],
|
|
|
+ };
|
|
|
+ area.push(point);
|
|
|
+ }
|
|
|
+ let content = {
|
|
|
+ type: 'region-detect',
|
|
|
+ state,
|
|
|
+ area
|
|
|
+ };
|
|
|
+ Array.prototype.push.apply(contents, [content]);
|
|
|
+ data = data.subarray(pointNum * 4 + 4);
|
|
|
+ dataLength = data.length;
|
|
|
+ }
|
|
|
+ return contents;
|
|
|
+}
|
|
|
+
|
|
|
+function parseBodyEx(data) {
|
|
|
+ let dataLength = data.length;
|
|
|
+ let contents = [];
|
|
|
+ while (dataLength > 0) {
|
|
|
+ //console.log('dataLength: ', dataLength)
|
|
|
+ let x0 = ((data[4] & 0x7f) << 8) + data[5],
|
|
|
+ y0 = ((data[6] & 0x7f) << 8) + data[7],
|
|
|
+ width = ((data[8] & 0x7f) << 8) + data[9] - x0,
|
|
|
+ height = ((data[10] & 0x7f) << 8) + data[11] - y0,
|
|
|
+ boxConfidence = ((data[12] & 0x7f) << 8) + data[13];
|
|
|
+ let points = [];
|
|
|
+
|
|
|
+ for (let i = 0; i < 17; i++) {
|
|
|
+ let point = {
|
|
|
+ x: ((data[16 + i * 8] & 0x7f) << 8) + data[17 + i * 8],
|
|
|
+ y: ((data[18 + i * 8] & 0x7f) << 8) + data[19 + i * 8],
|
|
|
+ confidence: ((data[20 + i * 8] & 0x7f) << 8) + data[21 + i * 8],
|
|
|
+ };
|
|
|
+ points.push(point);
|
|
|
+ }
|
|
|
+ let guides = [
|
|
|
+ {
|
|
|
+ x: ((data[16 + 17 * 8] & 0x7f) << 8) + data[17 + 17 * 8],
|
|
|
+ y: ((data[18 + 17 * 8] & 0x7f) << 8) + data[19 + 17 * 8],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ x: ((data[20 + 17 * 8] & 0x7f) << 8) + data[21 + 17 * 8],
|
|
|
+ y: ((data[22 + 17 * 8] & 0x7f) << 8) + data[23 + 17 * 8],
|
|
|
+ }
|
|
|
+ ];
|
|
|
+ let content = {
|
|
|
+ type: 'coco-poseex',
|
|
|
+ id: (data[2] << 8) + data[3],
|
|
|
+ Loiter: data[1] & 0x03,
|
|
|
+ Standing: (data[1] & 0x04) >> 2,
|
|
|
+ Alone: (data[1] & 0x08) >> 3,
|
|
|
+ boundingBox: [x0, y0, width, height],
|
|
|
+ boxConfidence,
|
|
|
+ points: parseBodyToTree(points),
|
|
|
+ guides,
|
|
|
+ state: 1
|
|
|
+ };
|
|
|
+ Array.prototype.push.apply(contents, [content]);
|
|
|
+ data = data.subarray(160);
|
|
|
+ dataLength = data.length;
|
|
|
+
|
|
|
+ }
|
|
|
+ return contents;
|
|
|
+}
|
|
|
+
|
|
|
+function parseOverSpeed(data) {
|
|
|
+ let dataLength = data.length;
|
|
|
+ let contents = [];
|
|
|
+ while (dataLength > 0) {
|
|
|
+ //console.log('dataLength: ', dataLength)
|
|
|
+ let speed = ((data[4] & 0x7f) << 8) + data[5],
|
|
|
+ overSpeed = data[1] & 0x01;
|
|
|
+ let content = {
|
|
|
+ type: 'over-speed',
|
|
|
+ id: (data[2] << 8) + data[3],
|
|
|
+ speed,
|
|
|
+ overSpeed,
|
|
|
+ state: 1,
|
|
|
+ };
|
|
|
+ Array.prototype.push.apply(contents, [content]);
|
|
|
+ data = data.subarray(8);
|
|
|
+ dataLength = data.length;
|
|
|
+ }
|
|
|
+ return contents;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 将智能帧中的人体姿态点转化为树结构(双亲表示法)
|
|
|
+ * @param points
|
|
|
+ */
|
|
|
+function parseBodyToTree(points) {
|
|
|
+ let newPoints = [];
|
|
|
+ newPoints[0] = {...points[0], parent: -1, pointColor: '#FF0002', lineColor: '#FF0002'};
|
|
|
+ newPoints[1] = {...points[1], parent: 0, pointColor: '#FF0002', lineColor: '#FF0002'};
|
|
|
+ newPoints[2] = {...points[2], parent: 0, pointColor: '#FF0002', lineColor: '#FF0002'};
|
|
|
+ newPoints[3] = {...points[5], parent: 17, pointColor: '#D9E34F', lineColor: '#FF0002'};
|
|
|
+ newPoints[4] = {...points[6], parent: 17, pointColor: '#D9E34F', lineColor: '#FF0002'};
|
|
|
+ newPoints[5] = {...points[3], parent: 1, pointColor: '#00FF00', lineColor: '#FF0002'};
|
|
|
+ newPoints[6] = {...points[4], parent: 2, pointColor: '#FFAC00', lineColor: '#FF0002'};
|
|
|
+ newPoints[7] = {...points[7], parent: 3, pointColor: '#00FF45', lineColor: '#'};
|
|
|
+ newPoints[8] = {...points[11], parent: 17, pointColor: '#EEC446', lineColor: '#'};
|
|
|
+ newPoints[9] = {...points[8], parent: 4, pointColor: '#43D3AF', lineColor: '#'};
|
|
|
+ newPoints[10] = {...points[12], parent: 17, pointColor: '#7A93E8', lineColor: '#'};
|
|
|
+ newPoints[11] = {...points[9], parent: 7, pointColor: '#EFB842', lineColor: '#'};
|
|
|
+ newPoints[12] = {...points[13], parent: 8, pointColor: '#E56C00', lineColor: '#'};
|
|
|
+ newPoints[13] = {...points[10], parent: 9, pointColor: '#47CD43', lineColor: '#0096FF'};
|
|
|
+ newPoints[14] = {...points[14], parent: 10, pointColor: '#3868D2', lineColor: '#00FF51'};
|
|
|
+ newPoints[15] = {...points[15], parent: 12, pointColor: '#DF4D01', lineColor: '#5100FF'};
|
|
|
+ newPoints[16] = {...points[16], parent: 14, pointColor: '#1E48D4', lineColor: '#00FFA0'};
|
|
|
+ newPoints[17] = {
|
|
|
+ x: (points[5].x + points[6].x) / 2,
|
|
|
+ y: (points[5].y + points[6].y) / 2,
|
|
|
+ parent: -1,
|
|
|
+ pointColor: '#D9E34F',
|
|
|
+ lineColor: '#00FFA0'
|
|
|
+ };
|
|
|
+ return newPoints;
|
|
|
+}
|