2018-06-24 16:28:37 +08:00

321 lines
10 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import Demuxer from './Demuxer';
import SPSParser from '../SPSParser';
import DataView4Read from '../../utils/DataView4Read';
import { EventTypes } from '../../constants/types';
import Buffer from '../../write/Buffer';
export default class VideoDemuxer extends Demuxer {
constructor (store) {
super(store);
this.CLASS_NAME = this.constructor.name;
this.readOffset = 0;
this.data = new Uint8Array(0);
this.currentTag = null;
this._store.videoMetaData = null;
}
resetStatus () {
this.readOffset = 0;
this.data = new Uint8Array(0);
this.currentTag = null;
}
resolve (tag) {
this.data = tag.body;
this.currentTag = tag;
const firstUI8 = this.readData(1)[0];
const frameType = (firstUI8 & 0xF0) >>> 4;
const codecId = firstUI8 & 0x0F;
if (codecId !== 7) {
/** 1: JPEG
* 2: H263
* 3: Screen video
* 4: On2 VP6
* 5: On2 VP6
* 6: Screen videoversion 2
* 7: AVC
*/
this.error(`unsupported codecId: ${codecId}`);
return;
}
this._parseAVCPacket(frameType);
this.resetStatus();
}
_parseAVCPacket (frameType) {
if (this.unreadLength < 4) {
this.error('Invalid Avc Tag');
}
const isLe = this._store.isLe;
const { buffer } = this.data;
const dv = new DataView(buffer, this.readOffset, this.unreadLength);
const packageType = dv.getUint8(0);
let cpsTime = dv.getUint32(0, !isLe) & 0x00FFFFFF;
cpsTime = (cpsTime << 8) >> 8;
this.readOffset += 4;
switch (packageType) {
case 0: {
const { position, tagSize } = this.currentTag;
this._store.metaEndPosition = position + Buffer.readAsInt(tagSize) + 4; // 缓存scriptTag结束的位置replay使用
this._parseAVCDecoderConfigurationRecord();
break;
}
case 1: {
this._parseAVCVideoData(frameType, cpsTime);
break;
}
case 2: {
break;
}
default: {
// 报错
}
}
}
_parseAVCDecoderConfigurationRecord () {
if (this.unreadLength < 7) {
this.error('Invalid AVCDecoderConfigurationRecord, lack of data!');
return;
}
const { mediaInfo: mi } = this._store;
// stash offset&unreadSize before parsing sps&pps
const tempOffset = this.readOffset;
const tempUnreadLength = this.unreadLength;
const { _store: store } = this;
let meta = this._store.videoMetaData;
let track = this._store.videoTrack;
const dv = new DataView4Read(this.data.buffer, this);
if (meta) {
if (meta.avcc !== undefined) {
this.error('found another AVCDecoderConfigurationRecord!');
}
} else {
if (!store.state._hasVideo && !store.state.hasVideoFlagOverrided) {
store.state._hasVideo = true;
store._mediaInfo.hasVideo = true;
}
meta = store.videoMetaData = {};
meta.type = 'video';
meta.id = track.id;
meta.timeScale = store.videoTimeScale;
meta.duration = store.state.duration;
mi.timescale = store.videoTimeScale;
}
const version = dv.getUint8();
const avcProfile = dv.getUint8();
dv.getUint8();
dv.getUint8();
if (version !== 1 || avcProfile === 0) {
// 处理错误
return;
}
const naluLengthSize = store.state.naluLengthSize = dv.getUint(2, this.readOffset, false) + 1;
if (naluLengthSize !== 3 && naluLengthSize !== 4) {
// 处理错误
return;
}
const spsLength = dv.getUint(5, null, false);
if (spsLength === 0) {
// 处理错误
return;
} else if (spsLength > 1) {
this.warn('AVCDecoderConfigurationRecord: spsLength > 1');
}
let sps;
for (let i = 0; i < spsLength; i++) {
const len = dv.getUint16();
if (len === 0) {
continue;
}
sps = new Uint8Array(this.data.buffer, this.readOffset, len);
this.readOffset += len;
const spsConfig = SPSParser.parseSPS(sps);
if (i !== 0) {
continue;
}
const {
codecSize,
presentSize,
profileString,
levelString,
chromaFormat,
pixelRatio,
frameRate,
refFrames,
bitDepth,
} = spsConfig;
meta.width = codecSize.width;
meta.height = codecSize.height;
meta.presentWidth = presentSize.width;
meta.presentHeight = presentSize.height;
meta.profile = profileString;
meta.level = levelString;
// meta.profileCompatibility = profileCompatibility;
// meta.naluLengthSize = naluLengthSize;
meta.bitDepth = bitDepth;
meta.chromaFormat = chromaFormat;
meta.pixelRatio = pixelRatio;
meta.frameRate = frameRate;
if (!frameRate.fixed
|| frameRate.fpsNum === 0
|| frameRate.fpsDen === 0) {
meta.frameRate = store.referFrameRate;
}
let { fpsDen, fpsNum } = meta.frameRate;
meta.refSampleDuration = meta.timeScale * (fpsDen / fpsNum);
let codecArr = sps.subarray(1, 4);
let codecStr = 'avc1.';
for (let j = 0; j < 3; j++) {
let hex = codecArr[j].toString(16);
hex = hex.padStart(2, '0');
codecStr += hex;
}
meta.codec = codecStr;
const { mediaInfo: mi } = this._store;
mi.width = meta.width;
mi.height = meta.height;
mi.fps = meta.frameRate.fps;
mi.profile = meta.profile;
mi.level = meta.level;
mi.refFrames = refFrames;
mi.pixelRatio = pixelRatio;
mi.videoCodec = codecStr;
mi.chromaFormat = chromaFormat;
if (mi.hasAudio) {
if (mi.audioCodec) {
mi.mimeType = `video/x-flv; codecs="${mi.videoCodec},${mi.audioCodec}"`;
mi.codec = mi.mimeType.replace('x-flv', 'mp4');
}
} else {
mi.mimeType = `video/x-flv; codecs="${mi.videoCodec}"`;
mi.codec = mi.mimeType.replace('x-flv', 'mp4');
}
if (mi.isComplete) {
this.dispatch(EventTypes.MEDIA_INFO_READY, mi);
}
}
let pps;
const ppsCount = dv.getUint8();
if (!ppsCount) {
this.dispatch(EventTypes.ERROR, 'no pps in AVCDecoderConfigurationRecord');
return;
} else if (ppsCount > 1) {
this.warn(`AVCDecoderConfigurationRecord has ppsCount: ${ppsCount}`);
}
for (let i = 0; i < ppsCount; i++) {
let ppsSize = dv.getUint16();
if (!ppsSize) {
continue;
}
pps = new Uint8Array(this.data.buffer, this.readOffset, ppsSize);
this.readOffset += ppsSize;
}
mi.sps = meta.sps = sps;
mi.pps = meta.pps = pps;
if (store.hasInitialMetaDispatched) {
if (store.videoTrack.length || store.audioTrack.length) {
this.dispatch(EventTypes.DATA_READY, store.videoTrack, store.audioTrack);
}
} else {
store.state._videoInitialMetadataDispatched = true;
}
this.dispatch(EventTypes.META_DATA_READY, 'video', meta);
}
_parseAVCVideoData (frameType, cpsTime) {
let dv = new DataView4Read(this.data.buffer, this);
let naluList = [], dataLen = 0;
const { naluLengthSize: naluLenSize } = this._store.state;
let ts = this._store.state.timeStampBase + this.currentTag.getTime();
let isKeyframe = (frameType === 1);
while (this.unreadLength > 0) {
if (this.unreadLength < 4) {
this.warn('not enough data for parsing AVC');
break;
}
const tempReadOffset = this.readOffset;
let naluSize = naluLenSize === 4 ? dv.getUint32() : dv.getUint24();
if (naluSize > this.unreadLength) {
return;
}
let unitType = dv.getUint(5, this.readOffset, false);
if (unitType === 5) {
isKeyframe = true;
}
let data = new Uint8Array(this.data.buffer, tempReadOffset, naluLenSize + naluSize);
this.readOffset = tempReadOffset + naluLenSize + naluSize;
const naluUnit = {
type: unitType,
data,
};
naluList.push(naluUnit);
dataLen += data.byteLength;
}
dv = null;
if (naluList.length) {
const { videoTrack } = this._store;
const videoSample = {
units: naluList,
length: dataLen,
dts: ts,
cps: cpsTime,
pts: (ts + cpsTime),
isKeyframe,
position: isKeyframe ? this.currentTag.position : undefined,
};
videoTrack.samples.push(videoSample);
videoTrack.length += dataLen;
}
}
readData (num) {
const { data, readOffset } = this;
if (this.dataSize > readOffset + num) {
this.readOffset += num;
return data.slice(readOffset, num);
}
return [];
}
get dataSize () {
return this.data.length;
}
get unreadLength () {
return this.dataSize - this.readOffset;
}
}