UNPKG

@jxstjh/jhvideo

Version:

HTML5 jhvideo base on MPEG2-TS Stream Player

1,045 lines 44.1 kB
/* * Copyright (C) 2016 Bilibili. All Rights Reserved. * * @author zheng qian <xqq@xqq.im> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Log from '../utils/logger.js'; import AMF from './amf-parser.js'; import SPSParser from './sps-parser.js'; import DemuxErrors from './demux-errors.js'; import MediaInfo from '../core/media-info.js'; import { IllegalStateException } from '../utils/exception.js'; function Swap16(src) { return (((src >>> 8) & 0xFF) | ((src & 0xFF) << 8)); } function Swap32(src) { return (((src & 0xFF000000) >>> 24) | ((src & 0x00FF0000) >>> 8) | ((src & 0x0000FF00) << 8) | ((src & 0x000000FF) << 24)); } function ReadBig32(array, index) { return ((array[index] << 24) | (array[index + 1] << 16) | (array[index + 2] << 8) | (array[index + 3])); } var FLVDemuxer = /** @class */ (function () { function FLVDemuxer(probeData, config) { this.TAG = 'FLVDemuxer'; this._config = config; this._onError = null; this._onMediaInfo = null; this._onMetaDataArrived = null; this._onScriptDataArrived = null; this._onTrackMetadata = null; this._onDataAvailable = null; this._onInformation = null; this._dataOffset = probeData.dataOffset; this._firstParse = true; this._onEsDataArrived = true; this._dispatch = false; this._hasAudio = false; this._hasVideo = probeData.hasVideoTrack; this._hasAudioFlagOverrided = false; this._hasVideoFlagOverrided = false; this._audioInitialMetadataDispatched = false; this._videoInitialMetadataDispatched = false; this._mediaInfo = new MediaInfo(); this._mediaInfo.hasAudio = this._hasAudio; this._mediaInfo.hasVideo = this._hasVideo; this._metadata = null; this._audioMetadata = null; this._videoMetadata = null; this._naluLengthSize = 4; this._timestampBase = 0; // int32, in milliseconds this._timescale = 1000; this._duration = 0; // int32, in milliseconds this._durationOverrided = false; this._referenceFrameRate = { fixed: true, fps: 23.976, fps_num: 23976, fps_den: 1000 }; this._flvSoundRateTable = [5500, 11025, 22050, 44100, 48000]; this._mpegSamplingRates = [ 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350 ]; this._mpegAudioV10SampleRateTable = [44100, 48000, 32000, 0]; this._mpegAudioV20SampleRateTable = [22050, 24000, 16000, 0]; this._mpegAudioV25SampleRateTable = [11025, 12000, 8000, 0]; this._mpegAudioL1BitRateTable = [0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, -1]; this._mpegAudioL2BitRateTable = [0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, -1]; this._mpegAudioL3BitRateTable = [0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, -1]; this._videoTrack = { type: 'video', id: 1, sequenceNumber: 0, samples: [], length: 0 }; this._audioTrack = { type: 'audio', id: 2, sequenceNumber: 0, samples: [], length: 0 }; this._littleEndian = (function () { var buf = new ArrayBuffer(2); (new DataView(buf)).setInt16(0, 256, true); // little-endian write return (new Int16Array(buf))[0] === 256; // platform-spec read, if equal then LE })(); } FLVDemuxer.prototype.destroy = function () { this._mediaInfo = null; this._metadata = null; this._audioMetadata = null; this._videoMetadata = null; this._videoTrack = null; this._audioTrack = null; this._onError = null; this._onMediaInfo = null; this._onMetaDataArrived = null; this._onScriptDataArrived = null; this._onEsDataArrived = null; this._onTrackMetadata = null; this._onDataAvailable = null; this._onInformation = null; }; FLVDemuxer.probe = function (buffer) { var data = new Uint8Array(buffer); var mismatch = { match: false }; // 70、76、86、1 if (data[0] !== 0x46 || data[1] !== 0x4C || data[2] !== 0x56 || data[3] !== 0x01) { return mismatch; } var hasAudio = ((data[4] & 4) >>> 2) !== 0; var hasVideo = (data[4] & 1) !== 0; var offset = ReadBig32(data, 5); if (offset < 9) { return mismatch; } return { match: true, consumed: offset, dataOffset: offset, hasAudioTrack: hasAudio, hasVideoTrack: hasVideo }; }; FLVDemuxer.prototype.bindDataSource = function (loader) { loader.onDataArrival = this.parseChunks.bind(this); return this; }; Object.defineProperty(FLVDemuxer.prototype, "onTrackMetadata", { // prototype: function(type: string, metadata: any): void get: function () { return this._onTrackMetadata; }, set: function (callback) { this._onTrackMetadata = callback; }, enumerable: false, configurable: true }); Object.defineProperty(FLVDemuxer.prototype, "onMediaInfo", { // prototype: function(mediaInfo: MediaInfo): void get: function () { return this._onMediaInfo; }, set: function (callback) { this._onMediaInfo = callback; }, enumerable: false, configurable: true }); Object.defineProperty(FLVDemuxer.prototype, "onMetaDataArrived", { get: function () { return this._onMetaDataArrived; }, set: function (callback) { this._onMetaDataArrived = callback; }, enumerable: false, configurable: true }); Object.defineProperty(FLVDemuxer.prototype, "onScriptDataArrived", { get: function () { return this._onScriptDataArrived; }, set: function (callback) { this._onScriptDataArrived = callback; }, enumerable: false, configurable: true }); Object.defineProperty(FLVDemuxer.prototype, "onInformation", { // 自定义JSON数据 _onInformation get: function () { return this._onInformation; }, set: function (callback) { this._onInformation = callback; }, enumerable: false, configurable: true }); Object.defineProperty(FLVDemuxer.prototype, "onEsDataArrived", { // h265 get: function () { return this._onEsDataArrived; }, set: function (callback) { this._onEsDataArrived = callback; }, enumerable: false, configurable: true }); Object.defineProperty(FLVDemuxer.prototype, "onError", { // prototype: function(type: number, info: string): void get: function () { return this._onError; }, set: function (callback) { this._onError = callback; }, enumerable: false, configurable: true }); Object.defineProperty(FLVDemuxer.prototype, "onDataAvailable", { // prototype: function(videoTrack: any, audioTrack: any): void get: function () { return this._onDataAvailable; }, set: function (callback) { this._onDataAvailable = callback; }, enumerable: false, configurable: true }); Object.defineProperty(FLVDemuxer.prototype, "timestampBase", { // timestamp base for output samples, must be in milliseconds get: function () { return this._timestampBase; }, set: function (base) { this._timestampBase = base; }, enumerable: false, configurable: true }); Object.defineProperty(FLVDemuxer.prototype, "overridedDuration", { get: function () { return this._duration; }, // Force-override media duration. Must be in milliseconds, int32 set: function (duration) { this._durationOverrided = true; this._duration = duration; this._mediaInfo.duration = duration; }, enumerable: false, configurable: true }); Object.defineProperty(FLVDemuxer.prototype, "overridedHasAudio", { // Force-override audio track present flag, boolean set: function (hasAudio) { this._hasAudioFlagOverrided = true; this._hasAudio = hasAudio; this._mediaInfo.hasAudio = hasAudio; }, enumerable: false, configurable: true }); Object.defineProperty(FLVDemuxer.prototype, "overridedHasVideo", { // Force-override video track present flag, boolean set: function (hasVideo) { this._hasVideoFlagOverrided = true; this._hasVideo = hasVideo; this._mediaInfo.hasVideo = hasVideo; }, enumerable: false, configurable: true }); FLVDemuxer.prototype.resetMediaInfo = function () { this._mediaInfo = new MediaInfo(); }; FLVDemuxer.prototype._isInitialMetadataDispatched = function () { if (this._hasAudio && this._hasVideo) { // both audio & video return this._audioInitialMetadataDispatched && this._videoInitialMetadataDispatched; } if (this._hasAudio && !this._hasVideo) { // audio only return this._audioInitialMetadataDispatched; } if (!this._hasAudio && this._hasVideo) { // video only return this._videoInitialMetadataDispatched; } return false; }; // function parseChunks(chunk: ArrayBuffer, byteStart: number): number; FLVDemuxer.prototype.parseChunks = function (chunk, byteStart) { if (!this._onError || !this._onMediaInfo || !this._onTrackMetadata || !this._onDataAvailable) { throw new IllegalStateException('Flv: onError & onMediaInfo & onTrackMetadata & onDataAvailable callback must be specified'); } var offset = 0; var le = this._littleEndian; if (byteStart === 0) { // buffer with FLV header if (chunk.byteLength > 13) { var probeData = FLVDemuxer.probe(chunk); offset = probeData.dataOffset; } else { return 0; } } if (this._firstParse) { // handle PreviousTagSize0 before Tag1 this._firstParse = false; if (byteStart + offset !== this._dataOffset) { Log.w(this.TAG, 'First time parsing but chunk byteStart invalid!'); } var v = new DataView(chunk, offset); var prevTagSize0 = v.getUint32(0, !le); if (prevTagSize0 !== 0) { Log.w(this.TAG, 'PrevTagSize0 !== 0 !!!'); } offset += 4; } while (offset < chunk.byteLength) { this._dispatch = true; var v = new DataView(chunk, offset); if (offset + 11 + 4 > chunk.byteLength) { // data not enough for parsing an flv tag break; } var tagType = v.getUint8(0); var dataSize = v.getUint32(0, !le) & 0x00FFFFFF; if (offset + 11 + dataSize + 4 > chunk.byteLength) { // data not enough for parsing actual data body break; } // 191 Private if (tagType !== 8 && tagType !== 9 && tagType !== 18 && tagType !== 191) { Log.w(this.TAG, "Unsupported tag type ".concat(tagType, ", skipped")); // consume the whole tag (skip it) offset += 11 + dataSize + 4; continue; } var ts2 = v.getUint8(4); var ts1 = v.getUint8(5); var ts0 = v.getUint8(6); var ts3 = v.getUint8(7); var timestamp = ts0 | (ts1 << 8) | (ts2 << 16) | (ts3 << 24); var streamId = v.getUint32(7, !le) & 0x00FFFFFF; if (streamId !== 0) { Log.w(this.TAG, 'Meet tag which has StreamID != 0!'); } var dataOffset = offset + 11; switch (tagType) { case 8: // Audio this._parseAudioData(chunk, dataOffset, dataSize, timestamp); break; case 9: // Video this._parseVideoData(chunk, dataOffset, dataSize, timestamp, byteStart + offset); break; case 18: // ScriptDataObject this._parseScriptData(chunk, dataOffset, dataSize); break; } var prevTagSize = v.getUint32(11 + dataSize, !le); if (prevTagSize !== 11 + dataSize) { if (prevTagSize > 2097152 || prevTagSize < 16) { Log.w(this.TAG, "Invalid PrevTagSize ".concat(prevTagSize)); } } offset += 11 + dataSize + 4; // tagBody + dataSize + prevTagSize } // dispatch parsed frames to consumer (typically, the remuxer) if (this._isInitialMetadataDispatched()) { if (this._dispatch && (this._audioTrack.length || this._videoTrack.length)) { this._onDataAvailable(this._audioTrack, this._videoTrack); } } return offset; // consumed bytes, just equals latest offset index }; FLVDemuxer.prototype._parseScriptData = function (arrayBuffer, dataOffset, dataSize) { var scriptData = AMF.parseScriptData(arrayBuffer, dataOffset, dataSize); if (scriptData.hasOwnProperty('onMetaData')) { if (scriptData.onMetaData == null || typeof scriptData.onMetaData !== 'object') { Log.w(this.TAG, 'Invalid onMetaData structure!'); return; } if (this._metadata) { Log.w(this.TAG, 'Found another onMetaData tag!'); } this._metadata = scriptData; var onMetaData = this._metadata.onMetaData; if (this._onMetaDataArrived) { this._onMetaDataArrived(Object.assign({}, onMetaData)); } if (typeof onMetaData.hasAudio === 'boolean') { // hasAudio if (this._hasAudioFlagOverrided === false) { this._hasAudio = onMetaData.hasAudio; this._mediaInfo.hasAudio = this._hasAudio; } } if (typeof onMetaData.hasVideo === 'boolean') { // hasVideo if (this._hasVideoFlagOverrided === false) { this._hasVideo = onMetaData.hasVideo; this._mediaInfo.hasVideo = this._hasVideo; } } if (typeof onMetaData.audiodatarate === 'number') { // audiodatarate this._mediaInfo.audioDataRate = onMetaData.audiodatarate; } if (typeof onMetaData.videodatarate === 'number') { // videodatarate this._mediaInfo.videoDataRate = onMetaData.videodatarate; } if (typeof onMetaData.width === 'number') { // width this._mediaInfo.width = onMetaData.width; } if (typeof onMetaData.height === 'number') { // height this._mediaInfo.height = onMetaData.height; } if (typeof onMetaData.duration === 'number') { // duration if (!this._durationOverrided) { var duration = Math.floor(onMetaData.duration * this._timescale); this._duration = duration; this._mediaInfo.duration = duration; } } else { this._mediaInfo.duration = 0; } if (typeof onMetaData.framerate === 'number') { // framerate var fps_num = Math.floor(onMetaData.framerate * 1000); if (fps_num > 0) { var fps = fps_num / 1000; this._referenceFrameRate.fixed = true; this._referenceFrameRate.fps = fps; this._referenceFrameRate.fps_num = fps_num; this._referenceFrameRate.fps_den = 1000; this._mediaInfo.fps = fps; } } if (typeof onMetaData.keyframes === 'object') { // keyframes this._mediaInfo.hasKeyframesIndex = true; var keyframes = onMetaData.keyframes; this._mediaInfo.keyframesIndex = this._parseKeyframesIndex(keyframes); onMetaData.keyframes = null; // keyframes has been extracted, remove it } else { this._mediaInfo.hasKeyframesIndex = false; } this._dispatch = false; this._mediaInfo.metadata = onMetaData; Log.v(this.TAG, 'Parsed onMetaData'); if (this._mediaInfo.isComplete()) { this._onMediaInfo(this._mediaInfo); } } if (Object.keys(scriptData).length > 0) { if (this._onScriptDataArrived) { this._onScriptDataArrived(Object.assign({}, scriptData)); } } }; FLVDemuxer.prototype._parseKeyframesIndex = function (keyframes) { var times = []; var filepositions = []; // ignore first keyframe which is actually AVC Sequence Header (AVCDecoderConfigurationRecord) for (var i = 1; i < keyframes.times.length; i++) { var time = this._timestampBase + Math.floor(keyframes.times[i] * 1000); times.push(time); filepositions.push(keyframes.filepositions[i]); } return { times: times, filepositions: filepositions }; }; FLVDemuxer.prototype._parseAudioData = function (arrayBuffer, dataOffset, dataSize, tagTimestamp) { if (dataSize <= 1) { Log.w(this.TAG, 'Flv: Invalid audio packet, missing SoundData payload!'); return; } if (this._hasAudioFlagOverrided === true && this._hasAudio === false) { // If hasAudio: false indicated explicitly in MediaDataSource, // Ignore all the audio packets return; } var le = this._littleEndian; var v = new DataView(arrayBuffer, dataOffset, dataSize); var soundSpec = v.getUint8(0); var soundFormat = soundSpec >>> 4; if (soundFormat !== 2 && soundFormat !== 10) { // MP3 or AAC this._onError(DemuxErrors.CODEC_UNSUPPORTED, 'Flv: Unsupported audio codec idx: ' + soundFormat); return; } var soundRate = 0; var soundRateIndex = (soundSpec & 12) >>> 2; if (soundRateIndex >= 0 && soundRateIndex <= 4) { soundRate = this._flvSoundRateTable[soundRateIndex]; } else { this._onError(DemuxErrors.FORMAT_ERROR, 'Flv: Invalid audio sample rate idx: ' + soundRateIndex); return; } var soundSize = (soundSpec & 2) >>> 1; // unused var soundType = (soundSpec & 1); var meta = this._audioMetadata; var track = this._audioTrack; if (!meta) { if (this._hasAudio === false && this._hasAudioFlagOverrided === false) { this._hasAudio = true; this._mediaInfo.hasAudio = true; } // initial metadata meta = this._audioMetadata = {}; meta.type = 'audio'; meta.id = track.id; meta.timescale = this._timescale; meta.duration = this._duration; meta.audioSampleRate = soundRate; meta.channelCount = (soundType === 0 ? 1 : 2); } if (soundFormat === 10) { // AAC var aacData = this._parseAACAudioData(arrayBuffer, dataOffset + 1, dataSize - 1); if (aacData == undefined) { return; } if (aacData.packetType === 0) { // AAC sequence header (AudioSpecificConfig) if (meta.config) { Log.w(this.TAG, 'Found another AudioSpecificConfig!'); } var misc = aacData.data; meta.audioSampleRate = misc.samplingRate; meta.channelCount = misc.channelCount; meta.codec = misc.codec; meta.originalCodec = misc.originalCodec; meta.config = misc.config; // The decode result of an aac sample is 1024 PCM samples meta.refSampleDuration = 1024 / meta.audioSampleRate * meta.timescale; Log.v(this.TAG, 'Parsed AudioSpecificConfig'); if (this._isInitialMetadataDispatched()) { // Non-initial metadata, force dispatch (or flush) parsed frames to remuxer if (this._dispatch && (this._audioTrack.length || this._videoTrack.length)) { this._onDataAvailable(this._audioTrack, this._videoTrack); } } else { this._audioInitialMetadataDispatched = true; } // then notify new metadata this._dispatch = false; this._onTrackMetadata('audio', meta); var mi = this._mediaInfo; mi.audioCodec = meta.originalCodec; mi.audioSampleRate = meta.audioSampleRate; mi.audioChannelCount = meta.channelCount; if (mi.hasVideo) { if (mi.videoCodec != null) { mi.mimeType = 'video/x-flv; codecs="' + mi.videoCodec + ',' + mi.audioCodec + '"'; } } else { mi.mimeType = 'video/x-flv; codecs="' + mi.audioCodec + '"'; } if (mi.isComplete()) { this._onMediaInfo(mi); } } else if (aacData.packetType === 1) { // AAC raw frame data var dts = this._timestampBase + tagTimestamp; var aacSample = { unit: aacData.data, length: aacData.data.byteLength, dts: dts, pts: dts }; track.samples.push(aacSample); track.length += aacData.data.length; } else { Log.e(this.TAG, "Flv: Unsupported AAC data type ".concat(aacData.packetType)); } } else if (soundFormat === 2) { // MP3 if (!meta.codec) { // We need metadata for mp3 audio track, extract info from frame header var misc = this._parseMP3AudioData(arrayBuffer, dataOffset + 1, dataSize - 1, true); if (misc == undefined) { return; } meta.audioSampleRate = misc.samplingRate; meta.channelCount = misc.channelCount; meta.codec = misc.codec; meta.originalCodec = misc.originalCodec; // The decode result of an mp3 sample is 1152 PCM samples meta.refSampleDuration = 1152 / meta.audioSampleRate * meta.timescale; Log.v(this.TAG, 'Parsed MPEG Audio Frame Header'); this._audioInitialMetadataDispatched = true; this._onTrackMetadata('audio', meta); var mi = this._mediaInfo; mi.audioCodec = meta.codec; mi.audioSampleRate = meta.audioSampleRate; mi.audioChannelCount = meta.channelCount; mi.audioDataRate = misc.bitRate; if (mi.hasVideo) { if (mi.videoCodec != null) { mi.mimeType = 'video/x-flv; codecs="' + mi.videoCodec + ',' + mi.audioCodec + '"'; } } else { mi.mimeType = 'video/x-flv; codecs="' + mi.audioCodec + '"'; } if (mi.isComplete()) { this._onMediaInfo(mi); } } // This packet is always a valid audio packet, extract it var data = this._parseMP3AudioData(arrayBuffer, dataOffset + 1, dataSize - 1, false); if (data == undefined) { return; } var dts = this._timestampBase + tagTimestamp; var mp3Sample = { unit: data, length: data.byteLength, dts: dts, pts: dts }; track.samples.push(mp3Sample); track.length += data.length; } }; FLVDemuxer.prototype._parseAACAudioData = function (arrayBuffer, dataOffset, dataSize) { if (dataSize <= 1) { Log.w(this.TAG, 'Flv: Invalid AAC packet, missing AACPacketType or/and Data!'); return; } var result = {}; var array = new Uint8Array(arrayBuffer, dataOffset, dataSize); result.packetType = array[0]; if (array[0] === 0) { result.data = this._parseAACAudioSpecificConfig(arrayBuffer, dataOffset + 1, dataSize - 1); } else { result.data = array.subarray(1); } return result; }; FLVDemuxer.prototype._parseAACAudioSpecificConfig = function (arrayBuffer, dataOffset, dataSize) { var array = new Uint8Array(arrayBuffer, dataOffset, dataSize); var config = null; /* Audio Object Type: 0: Null 1: AAC Main 2: AAC LC 3: AAC SSR (Scalable Sample Rate) 4: AAC LTP (Long Term Prediction) 5: HE-AAC / SBR (Spectral Band Replication) 6: AAC Scalable */ var audioObjectType = 0; var originalAudioObjectType = 0; var audioExtensionObjectType = null; var samplingIndex = 0; var extensionSamplingIndex = null; // 5 bits audioObjectType = originalAudioObjectType = array[0] >>> 3; // 4 bits samplingIndex = ((array[0] & 0x07) << 1) | (array[1] >>> 7); if (samplingIndex < 0 || samplingIndex >= this._mpegSamplingRates.length) { this._onError(DemuxErrors.FORMAT_ERROR, 'Flv: AAC invalid sampling frequency index!'); return; } var samplingFrequence = this._mpegSamplingRates[samplingIndex]; // 4 bits var channelConfig = (array[1] & 0x78) >>> 3; if (channelConfig < 0 || channelConfig >= 8) { this._onError(DemuxErrors.FORMAT_ERROR, 'Flv: AAC invalid channel configuration'); return; } if (audioObjectType === 5) { // HE-AAC? // 4 bits extensionSamplingIndex = ((array[1] & 0x07) << 1) | (array[2] >>> 7); // 5 bits audioExtensionObjectType = (array[2] & 0x7C) >>> 2; } // workarounds for various browsers var userAgent = self.navigator.userAgent.toLowerCase(); if (userAgent.indexOf('firefox') !== -1) { // firefox: use SBR (HE-AAC) if freq less than 24kHz if (samplingIndex >= 6) { audioObjectType = 5; config = new Array(4); extensionSamplingIndex = samplingIndex - 3; } else { // use LC-AAC audioObjectType = 2; config = new Array(2); extensionSamplingIndex = samplingIndex; } } else if (userAgent.indexOf('android') !== -1) { // android: always use LC-AAC audioObjectType = 2; config = new Array(2); extensionSamplingIndex = samplingIndex; } else { // for other browsers, e.g. chrome... // Always use HE-AAC to make it easier to switch aac codec profile audioObjectType = 5; extensionSamplingIndex = samplingIndex; config = new Array(4); if (samplingIndex >= 6) { extensionSamplingIndex = samplingIndex - 3; } else if (channelConfig === 1) { // Mono channel audioObjectType = 2; config = new Array(2); extensionSamplingIndex = samplingIndex; } } config[0] = audioObjectType << 3; config[0] |= (samplingIndex & 0x0F) >>> 1; config[1] = (samplingIndex & 0x0F) << 7; config[1] |= (channelConfig & 0x0F) << 3; if (audioObjectType === 5) { config[1] |= ((extensionSamplingIndex & 0x0F) >>> 1); config[2] = (extensionSamplingIndex & 0x01) << 7; // extended audio object type: force to 2 (LC-AAC) config[2] |= (2 << 2); config[3] = 0; } return { config: config, samplingRate: samplingFrequence, channelCount: channelConfig, codec: 'mp4a.40.' + audioObjectType, originalCodec: 'mp4a.40.' + originalAudioObjectType }; }; FLVDemuxer.prototype._parseMP3AudioData = function (arrayBuffer, dataOffset, dataSize, requestHeader) { if (dataSize < 4) { Log.w(this.TAG, 'Flv: Invalid MP3 packet, header missing!'); return; } var le = this._littleEndian; var array = new Uint8Array(arrayBuffer, dataOffset, dataSize); var result = null; if (requestHeader) { if (array[0] !== 0xFF) { return; } var ver = (array[1] >>> 3) & 0x03; var layer = (array[1] & 0x06) >> 1; var bitrate_index = (array[2] & 0xF0) >>> 4; var sampling_freq_index = (array[2] & 0x0C) >>> 2; var channel_mode = (array[3] >>> 6) & 0x03; var channel_count = channel_mode !== 3 ? 2 : 1; var sample_rate = 0; var bit_rate = 0; var object_type = 34; // Layer-3, listed in MPEG-4 Audio Object Types var codec = 'mp3'; switch (ver) { case 0: // MPEG 2.5 sample_rate = this._mpegAudioV25SampleRateTable[sampling_freq_index]; break; case 2: // MPEG 2 sample_rate = this._mpegAudioV20SampleRateTable[sampling_freq_index]; break; case 3: // MPEG 1 sample_rate = this._mpegAudioV10SampleRateTable[sampling_freq_index]; break; } switch (layer) { case 1: // Layer 3 object_type = 34; if (bitrate_index < this._mpegAudioL3BitRateTable.length) { bit_rate = this._mpegAudioL3BitRateTable[bitrate_index]; } break; case 2: // Layer 2 object_type = 33; if (bitrate_index < this._mpegAudioL2BitRateTable.length) { bit_rate = this._mpegAudioL2BitRateTable[bitrate_index]; } break; case 3: // Layer 1 object_type = 32; if (bitrate_index < this._mpegAudioL1BitRateTable.length) { bit_rate = this._mpegAudioL1BitRateTable[bitrate_index]; } break; } result = { bitRate: bit_rate, samplingRate: sample_rate, channelCount: channel_count, codec: codec, originalCodec: codec }; } else { result = array; } return result; }; FLVDemuxer.prototype._parseVideoData = function (arrayBuffer, dataOffset, dataSize, tagTimestamp, tagPosition) { if (dataSize <= 1) { Log.w(this.TAG, 'Flv: Invalid video packet, missing VideoData payload!'); return; } if (this._hasVideoFlagOverrided === true && this._hasVideo === false) { // If hasVideo: false indicated explicitly in MediaDataSource, // Ignore all the video packets return; } var spec = (new Uint8Array(arrayBuffer, dataOffset, dataSize))[0]; var frameType = (spec & 240) >>> 4; var codecId = spec & 15; if (codecId !== 7) { if (9 != codecId && 12 != codecId && 13 != codecId && 14 != codecId || !this._onEsDataArrived) { this._onError(DemuxErrors.CODEC_UNSUPPORTED, "Flv: Unsupported codec in video frame: " + codecId); return; } else { this._parseAVCVideoPacket(arrayBuffer, dataOffset + 1, dataSize - 1, tagTimestamp, tagPosition, frameType, codecId); } } else { this._parseAVCVideoPacket(arrayBuffer, dataOffset + 1, dataSize - 1, tagTimestamp, tagPosition, frameType); } }; FLVDemuxer.prototype._parseAVCVideoPacket = function (arrayBuffer, dataOffset, dataSize, tagTimestamp, tagPosition, frameType, codecId) { if (dataSize < 4) { Log.w(this.TAG, 'Flv: Invalid AVC packet, missing AVCPacketType or/and CompositionTime'); return; } var le = this._littleEndian; var v = new DataView(arrayBuffer, dataOffset, dataSize); var packetType = v.getUint8(0); var cts_unsigned = v.getUint32(0, !le) & 0x00FFFFFF; var cts = (cts_unsigned << 8) >> 8; // convert to 24-bit signed int if (packetType === 0) { // AVCDecoderConfigurationRecord this._parseAVCDecoderConfigurationRecord(arrayBuffer, dataOffset + 4, dataSize - 4, codecId); } else if (packetType === 1) { // One or more Nalus this._parseAVCVideoData(arrayBuffer, dataOffset + 4, dataSize - 4, tagTimestamp, tagPosition, frameType, cts, codecId); } else if (packetType === 2) { // empty, AVC end of sequence } else { this._onError(DemuxErrors.FORMAT_ERROR, "Flv: Invalid video packet type ".concat(packetType)); return; } }; FLVDemuxer.prototype._parseAVCDecoderConfigurationRecord = function (arrayBuffer, dataOffset, dataSize) { if (dataSize < 7) { Log.w(this.TAG, 'Flv: Invalid AVCDecoderConfigurationRecord, lack of data!'); return; } var meta = this._videoMetadata; var track = this._videoTrack; var le = this._littleEndian; var v = new DataView(arrayBuffer, dataOffset, dataSize); if (!meta) { if (this._hasVideo === false && this._hasVideoFlagOverrided === false) { this._hasVideo = true; this._mediaInfo.hasVideo = true; } meta = this._videoMetadata = {}; meta.type = 'video'; meta.id = track.id; meta.timescale = this._timescale; meta.duration = this._duration; } else { if (typeof meta.avcc !== 'undefined') { Log.w(this.TAG, 'Found another AVCDecoderConfigurationRecord!'); } } var version = v.getUint8(0); // configurationVersion var avcProfile = v.getUint8(1); // avcProfileIndication var profileCompatibility = v.getUint8(2); // profile_compatibility var avcLevel = v.getUint8(3); // AVCLevelIndication if (version !== 1 || avcProfile === 0) { this._onError(DemuxErrors.FORMAT_ERROR, 'Flv: Invalid AVCDecoderConfigurationRecord'); return; } this._naluLengthSize = (v.getUint8(4) & 3) + 1; // lengthSizeMinusOne if (this._naluLengthSize !== 3 && this._naluLengthSize !== 4) { // holy shit!!! this._onError(DemuxErrors.FORMAT_ERROR, "Flv: Strange NaluLengthSizeMinusOne: ".concat(this._naluLengthSize - 1)); return; } var spsCount = v.getUint8(5) & 31; // numOfSequenceParameterSets if (spsCount === 0) { this._onError(DemuxErrors.FORMAT_ERROR, 'Flv: Invalid AVCDecoderConfigurationRecord: No SPS'); return; } else if (spsCount > 1) { Log.w(this.TAG, "Flv: Strange AVCDecoderConfigurationRecord: SPS Count = ".concat(spsCount)); } var offset = 6; for (var i = 0; i < spsCount; i++) { var len = v.getUint16(offset, !le); // sequenceParameterSetLength offset += 2; if (len === 0) { continue; } // Notice: Nalu without startcode header (00 00 00 01) var sps = new Uint8Array(arrayBuffer, dataOffset + offset, len); offset += len; var config = SPSParser.parseSPS(sps); if (i !== 0) { // ignore other sps's config continue; } meta.codecWidth = config.codec_size.width; meta.codecHeight = config.codec_size.height; meta.presentWidth = config.present_size.width; meta.presentHeight = config.present_size.height; meta.profile = config.profile_string; meta.level = config.level_string; meta.bitDepth = config.bit_depth; meta.chromaFormat = config.chroma_format; meta.sarRatio = config.sar_ratio; meta.frameRate = config.frame_rate; if (config.frame_rate.fixed === false || config.frame_rate.fps_num === 0 || config.frame_rate.fps_den === 0) { meta.frameRate = this._referenceFrameRate; } var fps_den = meta.frameRate.fps_den; var fps_num = meta.frameRate.fps_num; meta.refSampleDuration = meta.timescale * (fps_den / fps_num); var codecArray = sps.subarray(1, 4); var codecString = 'avc1.'; for (var j = 0; j < 3; j++) { var h = codecArray[j].toString(16); if (h.length < 2) { h = '0' + h; } codecString += h; } meta.codec = codecString; var mi = this._mediaInfo; mi.width = meta.codecWidth; mi.height = meta.codecHeight; mi.fps = meta.frameRate.fps; mi.profile = meta.profile; mi.level = meta.level; mi.refFrames = config.ref_frames; mi.chromaFormat = config.chroma_format_string; mi.sarNum = meta.sarRatio.width; mi.sarDen = meta.sarRatio.height; mi.videoCodec = codecString; if (mi.hasAudio) { if (mi.audioCodec != null) { mi.mimeType = 'video/x-flv; codecs="' + mi.videoCodec + ',' + mi.audioCodec + '"'; } } else { mi.mimeType = 'video/x-flv; codecs="' + mi.videoCodec + '"'; } if (mi.isComplete()) { this._onMediaInfo(mi); } } var ppsCount = v.getUint8(offset); // numOfPictureParameterSets if (ppsCount === 0) { this._onError(DemuxErrors.FORMAT_ERROR, 'Flv: Invalid AVCDecoderConfigurationRecord: No PPS'); return; } else if (ppsCount > 1) { Log.w(this.TAG, "Flv: Strange AVCDecoderConfigurationRecord: PPS Count = ".concat(ppsCount)); } offset++; for (var i = 0; i < ppsCount; i++) { var len = v.getUint16(offset, !le); // pictureParameterSetLength offset += 2; if (len === 0) { continue; } // pps is useless for extracting video information offset += len; } meta.avcc = new Uint8Array(dataSize); meta.avcc.set(new Uint8Array(arrayBuffer, dataOffset, dataSize), 0); Log.v(this.TAG, 'Parsed AVCDecoderConfigurationRecord'); if (this._isInitialMetadataDispatched()) { // flush parsed frames if (this._dispatch && (this._audioTrack.length || this._videoTrack.length)) { this._onDataAvailable(this._audioTrack, this._videoTrack); } } else { this._videoInitialMetadataDispatched = true; } // notify new metadata this._dispatch = false; this._onTrackMetadata('video', meta); }; FLVDemuxer.prototype._parseAVCVideoData = function (arrayBuffer, dataOffset, dataSize, tagTimestamp, tagPosition, frameType, cts, codecId) { codecId = codecId || 7; var le = this._littleEndian; var v = new DataView(arrayBuffer, dataOffset, dataSize); var units = [], length = 0; var offset = 0; var lengthSize = this._naluLengthSize; var dts = this._timestampBase + tagTimestamp; var keyframe = (frameType === 1); // from FLV Frame Type constants while (offset < dataSize) { if (offset + 4 >= dataSize) { Log.w(this.TAG, "Malformed Nalu near timestamp ".concat(dts, ", offset = ").concat(offset, ", dataSize = ").concat(dataSize)); break; // data not enough for next Nalu } // Nalu with length-header (AVC1) var naluSize = v.getUint32(offset, !le); // Big-Endian read if (lengthSize === 3) { naluSize >>>= 8; } if (naluSize > dataSize - lengthSize) { Log.w(this.TAG, "Malformed Nalus near timestamp ".concat(dts, ", NaluSize > DataSize!")); return; } if (codecId === 7) { var unitType = v.getUint8(offset + lengthSize) & 0x1F; if (unitType === 5) { // IDR keyframe = true; } var data = new Uint8Array(arrayBuffer, dataOffset + offset, lengthSize + naluSize); var unit = { type: unitType, data: data }; units.push(unit); length += data.byteLength; offset += lengthSize + naluSize; } else { var _ = new Uint8Array(arrayBuffer, dataOffset + offset + lengthSize, naluSize); this._onEsDataArrived && this._onEsDataArrived("video", { codecId: codecId, pts: tagTimestamp, frameType: frameType, data: _ }); offset += lengthSize + naluSize; } } if (units.length) { var track = this._videoTrack; var avcSample = { units: units, length: length, isKeyframe: keyframe, dts: dts, cts: cts, pts: (dts + cts) }; if (keyframe) { avcSample.fileposition = tagPosition; } track.samples.push(avcSample); track.length += length; } }; return FLVDemuxer; }()); export default FLVDemuxer; //# sourceMappingURL=flv-demuxer.js.map