@alessmicrosystems/mpegts.js 1.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/README.md +158 -0
- package/README_ja.md +153 -0
- package/README_zh.md +157 -0
- package/d.ts/mpegts.d.ts +524 -0
- package/d.ts/src/core/mse-events.d.ts +9 -0
- package/d.ts/src/core/transmuxing-events.d.ts +24 -0
- package/d.ts/src/demux/aac.d.ts +44 -0
- package/d.ts/src/demux/ac3.d.ts +70 -0
- package/d.ts/src/demux/av1-parser.d.ts +77 -0
- package/d.ts/src/demux/av1.d.ts +11 -0
- package/d.ts/src/demux/base-demuxer.d.ts +55 -0
- package/d.ts/src/demux/h264.d.ts +40 -0
- package/d.ts/src/demux/h265.d.ts +65 -0
- package/d.ts/src/demux/klv.d.ts +17 -0
- package/d.ts/src/demux/mp3.d.ts +6 -0
- package/d.ts/src/demux/mpeg4-audio.d.ts +28 -0
- package/d.ts/src/demux/pat-pmt-pes.d.ts +106 -0
- package/d.ts/src/demux/patpmt.d.ts +40 -0
- package/d.ts/src/demux/pes-private-data.d.ts +14 -0
- package/d.ts/src/demux/pgs-data.d.ts +9 -0
- package/d.ts/src/demux/scte35.d.ts +250 -0
- package/d.ts/src/demux/sei.d.ts +8 -0
- package/d.ts/src/demux/smpte2038.d.ts +22 -0
- package/d.ts/src/demux/ts-demuxer.d.ts +124 -0
- package/d.ts/src/player/live-latency-chaser.d.ts +10 -0
- package/d.ts/src/player/live-latency-synchronizer.d.ts +10 -0
- package/d.ts/src/player/loading-controller.d.ts +19 -0
- package/d.ts/src/player/mse-player.d.ts +30 -0
- package/d.ts/src/player/player-engine-dedicated-thread-worker.d.ts +2 -0
- package/d.ts/src/player/player-engine-dedicated-thread.d.ts +48 -0
- package/d.ts/src/player/player-engine-main-thread.d.ts +50 -0
- package/d.ts/src/player/player-engine-worker-cmd-def.d.ts +25 -0
- package/d.ts/src/player/player-engine-worker-msg-def.d.ts +54 -0
- package/d.ts/src/player/player-engine-worker.d.ts +2 -0
- package/d.ts/src/player/player-engine.d.ts +16 -0
- package/d.ts/src/player/player-events.d.ts +21 -0
- package/d.ts/src/player/seeking-handler.d.ts +22 -0
- package/d.ts/src/player/startup-stall-jumper.d.ts +14 -0
- package/d.ts/src/utils/typedarray-equality.d.ts +2 -0
- package/dist/mpegts.js +3 -0
- package/dist/mpegts.js.LICENSE.txt +7 -0
- package/dist/mpegts.js.map +1 -0
- package/package.json +53 -0
- package/src/config.js +67 -0
- package/src/core/features.js +88 -0
- package/src/core/media-info.js +127 -0
- package/src/core/media-segment-info.js +230 -0
- package/src/core/mse-controller.js +599 -0
- package/src/core/mse-events.ts +28 -0
- package/src/core/transmuxer.js +346 -0
- package/src/core/transmuxing-controller.js +628 -0
- package/src/core/transmuxing-events.ts +43 -0
- package/src/core/transmuxing-worker.js +286 -0
- package/src/demux/aac.ts +397 -0
- package/src/demux/ac3.ts +335 -0
- package/src/demux/amf-parser.js +243 -0
- package/src/demux/av1-parser.ts +629 -0
- package/src/demux/av1.ts +103 -0
- package/src/demux/base-demuxer.ts +69 -0
- package/src/demux/demux-errors.js +26 -0
- package/src/demux/exp-golomb.js +116 -0
- package/src/demux/flv-demuxer.js +1854 -0
- package/src/demux/h264.ts +187 -0
- package/src/demux/h265-parser.js +501 -0
- package/src/demux/h265.ts +214 -0
- package/src/demux/klv.ts +40 -0
- package/src/demux/mp3.ts +7 -0
- package/src/demux/mpeg4-audio.ts +45 -0
- package/src/demux/pat-pmt-pes.ts +132 -0
- package/src/demux/pes-private-data.ts +16 -0
- package/src/demux/pgs-data.ts +11 -0
- package/src/demux/scte35.ts +723 -0
- package/src/demux/sei.ts +99 -0
- package/src/demux/smpte2038.ts +89 -0
- package/src/demux/sps-parser.js +298 -0
- package/src/demux/ts-demuxer.ts +2405 -0
- package/src/index.js +4 -0
- package/src/io/fetch-stream-loader.js +266 -0
- package/src/io/io-controller.js +647 -0
- package/src/io/loader.js +134 -0
- package/src/io/param-seek-handler.js +85 -0
- package/src/io/range-seek-handler.js +52 -0
- package/src/io/speed-sampler.js +93 -0
- package/src/io/websocket-loader.js +151 -0
- package/src/io/xhr-moz-chunked-loader.js +211 -0
- package/src/io/xhr-msstream-loader.js +307 -0
- package/src/io/xhr-range-loader.js +366 -0
- package/src/mpegts.js +95 -0
- package/src/player/live-latency-chaser.ts +66 -0
- package/src/player/live-latency-synchronizer.ts +79 -0
- package/src/player/loading-controller.ts +142 -0
- package/src/player/mse-player.ts +150 -0
- package/src/player/native-player.js +262 -0
- package/src/player/player-engine-dedicated-thread.ts +479 -0
- package/src/player/player-engine-main-thread.ts +463 -0
- package/src/player/player-engine-worker-cmd-def.ts +62 -0
- package/src/player/player-engine-worker-msg-def.ts +102 -0
- package/src/player/player-engine-worker.ts +370 -0
- package/src/player/player-engine.ts +35 -0
- package/src/player/player-errors.js +39 -0
- package/src/player/player-events.ts +40 -0
- package/src/player/seeking-handler.ts +205 -0
- package/src/player/startup-stall-jumper.ts +86 -0
- package/src/remux/aac-silent.js +56 -0
- package/src/remux/mp4-generator.js +866 -0
- package/src/remux/mp4-remuxer.js +778 -0
- package/src/utils/browser.js +128 -0
- package/src/utils/exception.js +73 -0
- package/src/utils/logger.js +140 -0
- package/src/utils/logging-control.js +165 -0
- package/src/utils/polyfill.js +68 -0
- package/src/utils/typedarray-equality.ts +69 -0
- package/src/utils/utf8-conv.js +84 -0
- package/src/utils/webworkify-webpack.js +202 -0
- package/tsconfig.json +16 -0
- package/tslint.json +1 -0
- package/types/index.d.ts +3 -0
- package/types/test-flv.ts +8 -0
- package/types/tsconfig.json +24 -0
- package/webpack.config.js +55 -0
|
@@ -0,0 +1,1854 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (C) 2016 Bilibili. All Rights Reserved.
|
|
3
|
+
*
|
|
4
|
+
* @author zheng qian <xqq@xqq.im>
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this file except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import Log from '../utils/logger.js';
|
|
20
|
+
import AMF from './amf-parser.js';
|
|
21
|
+
import SPSParser from './sps-parser.js';
|
|
22
|
+
import DemuxErrors from './demux-errors.js';
|
|
23
|
+
import MediaInfo from '../core/media-info.js';
|
|
24
|
+
import {IllegalStateException} from '../utils/exception.js';
|
|
25
|
+
import H265Parser from './h265-parser.js';
|
|
26
|
+
import buffersAreEqual from '../utils/typedarray-equality.ts';
|
|
27
|
+
import AV1OBUParser from './av1-parser.ts';
|
|
28
|
+
import ExpGolomb from './exp-golomb.js';
|
|
29
|
+
import { parseSEI } from './sei';
|
|
30
|
+
|
|
31
|
+
function Swap16(src) {
|
|
32
|
+
return (((src >>> 8) & 0xFF) |
|
|
33
|
+
((src & 0xFF) << 8));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function Swap32(src) {
|
|
37
|
+
return (((src & 0xFF000000) >>> 24) |
|
|
38
|
+
((src & 0x00FF0000) >>> 8) |
|
|
39
|
+
((src & 0x0000FF00) << 8) |
|
|
40
|
+
((src & 0x000000FF) << 24));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function ReadBig32(array, index) {
|
|
44
|
+
return ((array[index] << 24) |
|
|
45
|
+
(array[index + 1] << 16) |
|
|
46
|
+
(array[index + 2] << 8) |
|
|
47
|
+
(array[index + 3]));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class FLVDemuxer {
|
|
52
|
+
|
|
53
|
+
constructor(probeData, config) {
|
|
54
|
+
this.TAG = 'FLVDemuxer';
|
|
55
|
+
|
|
56
|
+
this._config = config;
|
|
57
|
+
|
|
58
|
+
this._onError = null;
|
|
59
|
+
this._onMediaInfo = null;
|
|
60
|
+
this._onMetaDataArrived = null;
|
|
61
|
+
this._onScriptDataArrived = null;
|
|
62
|
+
this._onTrackMetadata = null;
|
|
63
|
+
this._onDataAvailable = null;
|
|
64
|
+
this._onSeiArrived = null;
|
|
65
|
+
|
|
66
|
+
this._dataOffset = probeData.dataOffset;
|
|
67
|
+
this._firstParse = true;
|
|
68
|
+
this._dispatch = false;
|
|
69
|
+
|
|
70
|
+
this._hasAudio = probeData.hasAudioTrack;
|
|
71
|
+
this._hasVideo = probeData.hasVideoTrack;
|
|
72
|
+
|
|
73
|
+
this._hasAudioFlagOverrided = false;
|
|
74
|
+
this._hasVideoFlagOverrided = false;
|
|
75
|
+
|
|
76
|
+
this._audioInitialMetadataDispatched = false;
|
|
77
|
+
this._videoInitialMetadataDispatched = false;
|
|
78
|
+
|
|
79
|
+
this._mediaInfo = new MediaInfo();
|
|
80
|
+
this._mediaInfo.hasAudio = this._hasAudio;
|
|
81
|
+
this._mediaInfo.hasVideo = this._hasVideo;
|
|
82
|
+
this._metadata = null;
|
|
83
|
+
this._audioMetadata = null;
|
|
84
|
+
this._videoMetadata = null;
|
|
85
|
+
|
|
86
|
+
this._naluLengthSize = 4;
|
|
87
|
+
this._timestampBase = 0; // int32, in milliseconds
|
|
88
|
+
this._timescale = 1000;
|
|
89
|
+
this._duration = 0; // int32, in milliseconds
|
|
90
|
+
this._durationOverrided = false;
|
|
91
|
+
this._referenceFrameRate = {
|
|
92
|
+
fixed: true,
|
|
93
|
+
fps: 23.976,
|
|
94
|
+
fps_num: 23976,
|
|
95
|
+
fps_den: 1000
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
this._flvSoundRateTable = [5500, 11025, 22050, 44100, 48000];
|
|
99
|
+
|
|
100
|
+
this._mpegSamplingRates = [
|
|
101
|
+
96000, 88200, 64000, 48000, 44100, 32000,
|
|
102
|
+
24000, 22050, 16000, 12000, 11025, 8000, 7350
|
|
103
|
+
];
|
|
104
|
+
|
|
105
|
+
this._mpegAudioV10SampleRateTable = [44100, 48000, 32000, 0];
|
|
106
|
+
this._mpegAudioV20SampleRateTable = [22050, 24000, 16000, 0];
|
|
107
|
+
this._mpegAudioV25SampleRateTable = [11025, 12000, 8000, 0];
|
|
108
|
+
|
|
109
|
+
this._mpegAudioL1BitRateTable = [0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, -1];
|
|
110
|
+
this._mpegAudioL2BitRateTable = [0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, -1];
|
|
111
|
+
this._mpegAudioL3BitRateTable = [0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, -1];
|
|
112
|
+
|
|
113
|
+
this._videoTrack = {type: 'video', id: 1, sequenceNumber: 0, samples: [], length: 0};
|
|
114
|
+
this._audioTrack = {type: 'audio', id: 2, sequenceNumber: 0, samples: [], length: 0};
|
|
115
|
+
|
|
116
|
+
this._littleEndian = (function () {
|
|
117
|
+
let buf = new ArrayBuffer(2);
|
|
118
|
+
(new DataView(buf)).setInt16(0, 256, true); // little-endian write
|
|
119
|
+
return (new Int16Array(buf))[0] === 256; // platform-spec read, if equal then LE
|
|
120
|
+
})();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
destroy() {
|
|
124
|
+
this._mediaInfo = null;
|
|
125
|
+
this._metadata = null;
|
|
126
|
+
this._audioMetadata = null;
|
|
127
|
+
this._videoMetadata = null;
|
|
128
|
+
this._videoTrack = null;
|
|
129
|
+
this._audioTrack = null;
|
|
130
|
+
|
|
131
|
+
this._onError = null;
|
|
132
|
+
this._onMediaInfo = null;
|
|
133
|
+
this._onMetaDataArrived = null;
|
|
134
|
+
this._onScriptDataArrived = null;
|
|
135
|
+
this._onTrackMetadata = null;
|
|
136
|
+
this._onDataAvailable = null;
|
|
137
|
+
this._onSeiArrived = null;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
static probe(buffer) {
|
|
141
|
+
let data = new Uint8Array(buffer);
|
|
142
|
+
if (data.byteLength < 9) {
|
|
143
|
+
return {needMoreData: true};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
let mismatch = {match: false};
|
|
147
|
+
|
|
148
|
+
if (data[0] !== 0x46 || data[1] !== 0x4C || data[2] !== 0x56 || data[3] !== 0x01) {
|
|
149
|
+
return mismatch;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
let hasAudio = ((data[4] & 4) >>> 2) !== 0;
|
|
153
|
+
let hasVideo = (data[4] & 1) !== 0;
|
|
154
|
+
|
|
155
|
+
let offset = ReadBig32(data, 5);
|
|
156
|
+
|
|
157
|
+
if (offset < 9) {
|
|
158
|
+
return mismatch;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
match: true,
|
|
163
|
+
consumed: offset,
|
|
164
|
+
dataOffset: offset,
|
|
165
|
+
hasAudioTrack: hasAudio,
|
|
166
|
+
hasVideoTrack: hasVideo
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
bindDataSource(loader) {
|
|
171
|
+
loader.onDataArrival = this.parseChunks.bind(this);
|
|
172
|
+
return this;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// prototype: function(type: string, metadata: any): void
|
|
176
|
+
get onTrackMetadata() {
|
|
177
|
+
return this._onTrackMetadata;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
set onTrackMetadata(callback) {
|
|
181
|
+
this._onTrackMetadata = callback;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// prototype: function(mediaInfo: MediaInfo): void
|
|
185
|
+
get onMediaInfo() {
|
|
186
|
+
return this._onMediaInfo;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
set onMediaInfo(callback) {
|
|
190
|
+
this._onMediaInfo = callback;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
get onMetaDataArrived() {
|
|
194
|
+
return this._onMetaDataArrived;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
set onMetaDataArrived(callback) {
|
|
198
|
+
this._onMetaDataArrived = callback;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
get onScriptDataArrived() {
|
|
202
|
+
return this._onScriptDataArrived;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
set onScriptDataArrived(callback) {
|
|
206
|
+
this._onScriptDataArrived = callback;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
get onSeiArrived() {
|
|
210
|
+
return this._onSeiArrived
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
set onSeiArrived(callback) {
|
|
214
|
+
this._onSeiArrived = callback;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// prototype: function(type: number, info: string): void
|
|
218
|
+
get onError() {
|
|
219
|
+
return this._onError;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
set onError(callback) {
|
|
223
|
+
this._onError = callback;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// prototype: function(videoTrack: any, audioTrack: any): void
|
|
227
|
+
get onDataAvailable() {
|
|
228
|
+
return this._onDataAvailable;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
set onDataAvailable(callback) {
|
|
232
|
+
this._onDataAvailable = callback;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// timestamp base for output samples, must be in milliseconds
|
|
236
|
+
get timestampBase() {
|
|
237
|
+
return this._timestampBase;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
set timestampBase(base) {
|
|
241
|
+
this._timestampBase = base;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
get overridedDuration() {
|
|
245
|
+
return this._duration;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Force-override media duration. Must be in milliseconds, int32
|
|
249
|
+
set overridedDuration(duration) {
|
|
250
|
+
this._durationOverrided = true;
|
|
251
|
+
this._duration = duration;
|
|
252
|
+
this._mediaInfo.duration = duration;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Force-override audio track present flag, boolean
|
|
256
|
+
set overridedHasAudio(hasAudio) {
|
|
257
|
+
this._hasAudioFlagOverrided = true;
|
|
258
|
+
this._hasAudio = hasAudio;
|
|
259
|
+
this._mediaInfo.hasAudio = hasAudio;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Force-override video track present flag, boolean
|
|
263
|
+
set overridedHasVideo(hasVideo) {
|
|
264
|
+
this._hasVideoFlagOverrided = true;
|
|
265
|
+
this._hasVideo = hasVideo;
|
|
266
|
+
this._mediaInfo.hasVideo = hasVideo;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
resetMediaInfo() {
|
|
270
|
+
this._mediaInfo = new MediaInfo();
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
_isInitialMetadataDispatched() {
|
|
274
|
+
if (this._hasAudio && this._hasVideo) { // both audio & video
|
|
275
|
+
return this._audioInitialMetadataDispatched && this._videoInitialMetadataDispatched;
|
|
276
|
+
}
|
|
277
|
+
if (this._hasAudio && !this._hasVideo) { // audio only
|
|
278
|
+
return this._audioInitialMetadataDispatched;
|
|
279
|
+
}
|
|
280
|
+
if (!this._hasAudio && this._hasVideo) { // video only
|
|
281
|
+
return this._videoInitialMetadataDispatched;
|
|
282
|
+
}
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// function parseChunks(chunk: ArrayBuffer, byteStart: number): number;
|
|
287
|
+
parseChunks(chunk, byteStart) {
|
|
288
|
+
if (!this._onError || !this._onMediaInfo || !this._onTrackMetadata || !this._onDataAvailable) {
|
|
289
|
+
throw new IllegalStateException('Flv: onError & onMediaInfo & onTrackMetadata & onDataAvailable callback must be specified');
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
let offset = 0;
|
|
293
|
+
let le = this._littleEndian;
|
|
294
|
+
|
|
295
|
+
if (byteStart === 0) { // buffer with FLV header
|
|
296
|
+
if (chunk.byteLength > 13) {
|
|
297
|
+
let probeData = FLVDemuxer.probe(chunk);
|
|
298
|
+
offset = probeData.dataOffset;
|
|
299
|
+
} else {
|
|
300
|
+
return 0;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (this._firstParse) { // handle PreviousTagSize0 before Tag1
|
|
305
|
+
this._firstParse = false;
|
|
306
|
+
if (byteStart + offset !== this._dataOffset) {
|
|
307
|
+
Log.w(this.TAG, 'First time parsing but chunk byteStart invalid!');
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
let v = new DataView(chunk, offset);
|
|
311
|
+
let prevTagSize0 = v.getUint32(0, !le);
|
|
312
|
+
if (prevTagSize0 !== 0) {
|
|
313
|
+
Log.w(this.TAG, 'PrevTagSize0 !== 0 !!!');
|
|
314
|
+
}
|
|
315
|
+
offset += 4;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
while (offset < chunk.byteLength) {
|
|
319
|
+
this._dispatch = true;
|
|
320
|
+
|
|
321
|
+
let v = new DataView(chunk, offset);
|
|
322
|
+
|
|
323
|
+
if (offset + 11 + 4 > chunk.byteLength) {
|
|
324
|
+
// data not enough for parsing an flv tag
|
|
325
|
+
break;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
let tagType = v.getUint8(0);
|
|
329
|
+
let dataSize = v.getUint32(0, !le) & 0x00FFFFFF;
|
|
330
|
+
|
|
331
|
+
if (offset + 11 + dataSize + 4 > chunk.byteLength) {
|
|
332
|
+
// data not enough for parsing actual data body
|
|
333
|
+
break;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (tagType !== 8 && tagType !== 9 && tagType !== 18) {
|
|
337
|
+
Log.w(this.TAG, `Unsupported tag type ${tagType}, skipped`);
|
|
338
|
+
// consume the whole tag (skip it)
|
|
339
|
+
offset += 11 + dataSize + 4;
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
let ts2 = v.getUint8(4);
|
|
344
|
+
let ts1 = v.getUint8(5);
|
|
345
|
+
let ts0 = v.getUint8(6);
|
|
346
|
+
let ts3 = v.getUint8(7);
|
|
347
|
+
|
|
348
|
+
let timestamp = ts0 | (ts1 << 8) | (ts2 << 16) | (ts3 << 24);
|
|
349
|
+
|
|
350
|
+
let streamId = v.getUint32(7, !le) & 0x00FFFFFF;
|
|
351
|
+
if (streamId !== 0) {
|
|
352
|
+
Log.w(this.TAG, 'Meet tag which has StreamID != 0!');
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
let dataOffset = offset + 11;
|
|
356
|
+
|
|
357
|
+
switch (tagType) {
|
|
358
|
+
case 8: // Audio
|
|
359
|
+
this._parseAudioData(chunk, dataOffset, dataSize, timestamp);
|
|
360
|
+
break;
|
|
361
|
+
case 9: // Video
|
|
362
|
+
this._parseVideoData(chunk, dataOffset, dataSize, timestamp, byteStart + offset);
|
|
363
|
+
break;
|
|
364
|
+
case 18: // ScriptDataObject
|
|
365
|
+
this._parseScriptData(chunk, dataOffset, dataSize);
|
|
366
|
+
break;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
let prevTagSize = v.getUint32(11 + dataSize, !le);
|
|
370
|
+
if (prevTagSize !== 11 + dataSize) {
|
|
371
|
+
Log.w(this.TAG, `Invalid PrevTagSize ${prevTagSize}`);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
offset += 11 + dataSize + 4; // tagBody + dataSize + prevTagSize
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// dispatch parsed frames to consumer (typically, the remuxer)
|
|
378
|
+
if (this._isInitialMetadataDispatched()) {
|
|
379
|
+
if (this._dispatch && (this._audioTrack.length || this._videoTrack.length)) {
|
|
380
|
+
this._onDataAvailable(this._audioTrack, this._videoTrack);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
return offset; // consumed bytes, just equals latest offset index
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
_parseScriptData(arrayBuffer, dataOffset, dataSize) {
|
|
388
|
+
let scriptData = AMF.parseScriptData(arrayBuffer, dataOffset, dataSize);
|
|
389
|
+
|
|
390
|
+
if (scriptData.hasOwnProperty('onMetaData')) {
|
|
391
|
+
if (scriptData.onMetaData == null || typeof scriptData.onMetaData !== 'object') {
|
|
392
|
+
Log.w(this.TAG, 'Invalid onMetaData structure!');
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
if (this._metadata) {
|
|
396
|
+
Log.w(this.TAG, 'Found another onMetaData tag!');
|
|
397
|
+
}
|
|
398
|
+
this._metadata = scriptData;
|
|
399
|
+
let onMetaData = this._metadata.onMetaData;
|
|
400
|
+
|
|
401
|
+
if (this._onMetaDataArrived) {
|
|
402
|
+
this._onMetaDataArrived(Object.assign({}, onMetaData));
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
if (typeof onMetaData.hasAudio === 'boolean') { // hasAudio
|
|
406
|
+
if (this._hasAudioFlagOverrided === false) {
|
|
407
|
+
this._hasAudio = onMetaData.hasAudio;
|
|
408
|
+
this._mediaInfo.hasAudio = this._hasAudio;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
if (typeof onMetaData.hasVideo === 'boolean') { // hasVideo
|
|
412
|
+
if (this._hasVideoFlagOverrided === false) {
|
|
413
|
+
this._hasVideo = onMetaData.hasVideo;
|
|
414
|
+
this._mediaInfo.hasVideo = this._hasVideo;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
if (typeof onMetaData.audiodatarate === 'number') { // audiodatarate
|
|
418
|
+
this._mediaInfo.audioDataRate = onMetaData.audiodatarate;
|
|
419
|
+
}
|
|
420
|
+
if (typeof onMetaData.videodatarate === 'number') { // videodatarate
|
|
421
|
+
this._mediaInfo.videoDataRate = onMetaData.videodatarate;
|
|
422
|
+
}
|
|
423
|
+
if (typeof onMetaData.width === 'number') { // width
|
|
424
|
+
this._mediaInfo.width = onMetaData.width;
|
|
425
|
+
}
|
|
426
|
+
if (typeof onMetaData.height === 'number') { // height
|
|
427
|
+
this._mediaInfo.height = onMetaData.height;
|
|
428
|
+
}
|
|
429
|
+
if (typeof onMetaData.duration === 'number') { // duration
|
|
430
|
+
if (!this._durationOverrided) {
|
|
431
|
+
let duration = Math.floor(onMetaData.duration * this._timescale);
|
|
432
|
+
this._duration = duration;
|
|
433
|
+
this._mediaInfo.duration = duration;
|
|
434
|
+
}
|
|
435
|
+
} else {
|
|
436
|
+
this._mediaInfo.duration = 0;
|
|
437
|
+
}
|
|
438
|
+
if (typeof onMetaData.framerate === 'number') { // framerate
|
|
439
|
+
let fps_num = Math.floor(onMetaData.framerate * 1000);
|
|
440
|
+
if (fps_num > 0) {
|
|
441
|
+
let fps = fps_num / 1000;
|
|
442
|
+
this._referenceFrameRate.fixed = true;
|
|
443
|
+
this._referenceFrameRate.fps = fps;
|
|
444
|
+
this._referenceFrameRate.fps_num = fps_num;
|
|
445
|
+
this._referenceFrameRate.fps_den = 1000;
|
|
446
|
+
this._mediaInfo.fps = fps;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
if (typeof onMetaData.keyframes === 'object') { // keyframes
|
|
450
|
+
this._mediaInfo.hasKeyframesIndex = true;
|
|
451
|
+
let keyframes = onMetaData.keyframes;
|
|
452
|
+
this._mediaInfo.keyframesIndex = this._parseKeyframesIndex(keyframes);
|
|
453
|
+
onMetaData.keyframes = null; // keyframes has been extracted, remove it
|
|
454
|
+
} else {
|
|
455
|
+
this._mediaInfo.hasKeyframesIndex = false;
|
|
456
|
+
}
|
|
457
|
+
this._dispatch = false;
|
|
458
|
+
this._mediaInfo.metadata = onMetaData;
|
|
459
|
+
Log.v(this.TAG, 'Parsed onMetaData');
|
|
460
|
+
if (this._mediaInfo.isComplete()) {
|
|
461
|
+
this._onMediaInfo(this._mediaInfo);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
if (Object.keys(scriptData).length > 0) {
|
|
466
|
+
if (this._onScriptDataArrived) {
|
|
467
|
+
this._onScriptDataArrived(Object.assign({}, scriptData));
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
_parseSEIPayload(data, pts, codec) {
|
|
473
|
+
let sei_data = parseSEI(data, pts, codec);
|
|
474
|
+
|
|
475
|
+
if (sei_data && typeof this._onSeiArrived === 'function') {
|
|
476
|
+
this._onSeiArrived(sei_data);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
_parseKeyframesIndex(keyframes) {
|
|
481
|
+
let times = [];
|
|
482
|
+
let filepositions = [];
|
|
483
|
+
|
|
484
|
+
// ignore first keyframe which is actually AVC/HEVC Sequence Header (AVCDecoderConfigurationRecord or HEVCDecoderConfigurationRecord)
|
|
485
|
+
for (let i = 1; i < keyframes.times.length; i++) {
|
|
486
|
+
let time = this._timestampBase + Math.floor(keyframes.times[i] * 1000);
|
|
487
|
+
times.push(time);
|
|
488
|
+
filepositions.push(keyframes.filepositions[i]);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
return {
|
|
492
|
+
times: times,
|
|
493
|
+
filepositions: filepositions
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
_parseAudioData(arrayBuffer, dataOffset, dataSize, tagTimestamp) {
|
|
498
|
+
if (dataSize <= 1) {
|
|
499
|
+
Log.w(this.TAG, 'Flv: Invalid audio packet, missing SoundData payload!');
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
if (this._hasAudioFlagOverrided === true && this._hasAudio === false) {
|
|
504
|
+
// If hasAudio: false indicated explicitly in MediaDataSource,
|
|
505
|
+
// Ignore all the audio packets
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
let le = this._littleEndian;
|
|
510
|
+
let v = new DataView(arrayBuffer, dataOffset, dataSize);
|
|
511
|
+
|
|
512
|
+
let soundSpec = v.getUint8(0);
|
|
513
|
+
|
|
514
|
+
let soundFormat = soundSpec >>> 4;
|
|
515
|
+
if (soundFormat === 9) { // Enhanced FLV
|
|
516
|
+
if (dataSize <= 5) {
|
|
517
|
+
Log.w(this.TAG, 'Flv: Invalid audio packet, missing AudioFourCC in Ehnanced FLV payload!');
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
let packetType = soundSpec & 0x0F;
|
|
521
|
+
let fourcc = String.fromCharCode(... (new Uint8Array(arrayBuffer, dataOffset, dataSize)).slice(1, 5));
|
|
522
|
+
|
|
523
|
+
switch(fourcc){
|
|
524
|
+
case 'Opus':
|
|
525
|
+
this._parseOpusAudioPacket(arrayBuffer, dataOffset + 5, dataSize - 5, tagTimestamp, packetType);
|
|
526
|
+
break;
|
|
527
|
+
case 'fLaC':
|
|
528
|
+
this._parseFlacAudioPacket(arrayBuffer, dataOffset + 5, dataSize - 5, tagTimestamp, packetType);
|
|
529
|
+
break;
|
|
530
|
+
default:
|
|
531
|
+
this._onError(DemuxErrors.CODEC_UNSUPPORTED, 'Flv: Unsupported audio codec: ' + fourcc);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
// Legacy FLV
|
|
537
|
+
|
|
538
|
+
if (soundFormat !== 2 && soundFormat !== 3 && soundFormat !== 10) { // PCM or MP3 or AAC
|
|
539
|
+
this._onError(DemuxErrors.CODEC_UNSUPPORTED, 'Flv: Unsupported audio codec idx: ' + soundFormat);
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
let soundRate = 0;
|
|
544
|
+
let soundRateIndex = (soundSpec & 12) >>> 2;
|
|
545
|
+
if (soundRateIndex >= 0 && soundRateIndex <= 4) {
|
|
546
|
+
soundRate = this._flvSoundRateTable[soundRateIndex];
|
|
547
|
+
} else {
|
|
548
|
+
this._onError(DemuxErrors.FORMAT_ERROR, 'Flv: Invalid audio sample rate idx: ' + soundRateIndex);
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
let soundSize = (soundSpec & 2) >>> 1; // unused
|
|
553
|
+
let soundType = (soundSpec & 1);
|
|
554
|
+
|
|
555
|
+
let meta = this._audioMetadata;
|
|
556
|
+
let track = this._audioTrack;
|
|
557
|
+
|
|
558
|
+
if (!meta) {
|
|
559
|
+
if (this._hasAudio === false && this._hasAudioFlagOverrided === false) {
|
|
560
|
+
this._hasAudio = true;
|
|
561
|
+
this._mediaInfo.hasAudio = true;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// initial metadata
|
|
565
|
+
meta = this._audioMetadata = {};
|
|
566
|
+
meta.type = 'audio';
|
|
567
|
+
meta.id = track.id;
|
|
568
|
+
meta.timescale = this._timescale;
|
|
569
|
+
meta.duration = this._duration;
|
|
570
|
+
meta.audioSampleRate = soundRate;
|
|
571
|
+
meta.channelCount = (soundType === 0 ? 1 : 2);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
if (soundFormat === 10) { // AAC
|
|
575
|
+
let aacData = this._parseAACAudioData(arrayBuffer, dataOffset + 1, dataSize - 1);
|
|
576
|
+
if (aacData == undefined) {
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
if (aacData.packetType === 0) { // AAC sequence header (AudioSpecificConfig)
|
|
581
|
+
if (meta.config) {
|
|
582
|
+
if (buffersAreEqual(aacData.data.config, meta.config)) {
|
|
583
|
+
// If AudioSpecificConfig is not changed, ignore it to avoid generating initialization segment repeatedly
|
|
584
|
+
return;
|
|
585
|
+
} else {
|
|
586
|
+
Log.w(this.TAG, 'AudioSpecificConfig has been changed, re-generate initialization segment');
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
let misc = aacData.data;
|
|
590
|
+
meta.audioSampleRate = misc.samplingRate;
|
|
591
|
+
meta.channelCount = misc.channelCount;
|
|
592
|
+
meta.codec = misc.codec;
|
|
593
|
+
meta.originalCodec = misc.originalCodec;
|
|
594
|
+
meta.config = misc.config;
|
|
595
|
+
// The decode result of an aac sample is 1024 PCM samples
|
|
596
|
+
meta.refSampleDuration = 1024 / meta.audioSampleRate * meta.timescale;
|
|
597
|
+
Log.v(this.TAG, 'Parsed AudioSpecificConfig');
|
|
598
|
+
|
|
599
|
+
if (this._isInitialMetadataDispatched()) {
|
|
600
|
+
// Non-initial metadata, force dispatch (or flush) parsed frames to remuxer
|
|
601
|
+
if (this._dispatch && (this._audioTrack.length || this._videoTrack.length)) {
|
|
602
|
+
this._onDataAvailable(this._audioTrack, this._videoTrack);
|
|
603
|
+
}
|
|
604
|
+
} else {
|
|
605
|
+
this._audioInitialMetadataDispatched = true;
|
|
606
|
+
}
|
|
607
|
+
// then notify new metadata
|
|
608
|
+
this._dispatch = false;
|
|
609
|
+
this._onTrackMetadata('audio', meta);
|
|
610
|
+
|
|
611
|
+
let mi = this._mediaInfo;
|
|
612
|
+
mi.audioCodec = meta.originalCodec;
|
|
613
|
+
mi.audioSampleRate = meta.audioSampleRate;
|
|
614
|
+
mi.audioChannelCount = meta.channelCount;
|
|
615
|
+
if (mi.hasVideo) {
|
|
616
|
+
if (mi.videoCodec != null) {
|
|
617
|
+
mi.mimeType = 'video/x-flv; codecs="' + mi.videoCodec + ',' + mi.audioCodec + '"';
|
|
618
|
+
}
|
|
619
|
+
} else {
|
|
620
|
+
mi.mimeType = 'video/x-flv; codecs="' + mi.audioCodec + '"';
|
|
621
|
+
}
|
|
622
|
+
if (mi.isComplete()) {
|
|
623
|
+
this._onMediaInfo(mi);
|
|
624
|
+
}
|
|
625
|
+
} else if (aacData.packetType === 1) { // AAC raw frame data
|
|
626
|
+
let dts = this._timestampBase + tagTimestamp;
|
|
627
|
+
let aacSample = {unit: aacData.data, length: aacData.data.byteLength, dts: dts, pts: dts};
|
|
628
|
+
track.samples.push(aacSample);
|
|
629
|
+
track.length += aacData.data.length;
|
|
630
|
+
} else {
|
|
631
|
+
Log.e(this.TAG, `Flv: Unsupported AAC data type ${aacData.packetType}`);
|
|
632
|
+
}
|
|
633
|
+
} else if (soundFormat === 2) { // MP3
|
|
634
|
+
if (!meta.codec) {
|
|
635
|
+
// We need metadata for mp3 audio track, extract info from frame header
|
|
636
|
+
let misc = this._parseMP3AudioData(arrayBuffer, dataOffset + 1, dataSize - 1, true);
|
|
637
|
+
if (misc == undefined) {
|
|
638
|
+
return;
|
|
639
|
+
}
|
|
640
|
+
meta.audioSampleRate = misc.samplingRate;
|
|
641
|
+
meta.channelCount = misc.channelCount;
|
|
642
|
+
meta.codec = misc.codec;
|
|
643
|
+
meta.originalCodec = misc.originalCodec;
|
|
644
|
+
// The decode result of an mp3 sample is 1152 PCM samples
|
|
645
|
+
meta.refSampleDuration = 1152 / meta.audioSampleRate * meta.timescale;
|
|
646
|
+
Log.v(this.TAG, 'Parsed MPEG Audio Frame Header');
|
|
647
|
+
|
|
648
|
+
this._audioInitialMetadataDispatched = true;
|
|
649
|
+
this._onTrackMetadata('audio', meta);
|
|
650
|
+
|
|
651
|
+
let mi = this._mediaInfo;
|
|
652
|
+
mi.audioCodec = meta.codec;
|
|
653
|
+
mi.audioSampleRate = meta.audioSampleRate;
|
|
654
|
+
mi.audioChannelCount = meta.channelCount;
|
|
655
|
+
mi.audioDataRate = misc.bitRate;
|
|
656
|
+
if (mi.hasVideo) {
|
|
657
|
+
if (mi.videoCodec != null) {
|
|
658
|
+
mi.mimeType = 'video/x-flv; codecs="' + mi.videoCodec + ',' + mi.audioCodec + '"';
|
|
659
|
+
}
|
|
660
|
+
} else {
|
|
661
|
+
mi.mimeType = 'video/x-flv; codecs="' + mi.audioCodec + '"';
|
|
662
|
+
}
|
|
663
|
+
if (mi.isComplete()) {
|
|
664
|
+
this._onMediaInfo(mi);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// This packet is always a valid audio packet, extract it
|
|
669
|
+
let data = this._parseMP3AudioData(arrayBuffer, dataOffset + 1, dataSize - 1, false);
|
|
670
|
+
if (data == undefined) {
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
let dts = this._timestampBase + tagTimestamp;
|
|
674
|
+
let mp3Sample = {unit: data, length: data.byteLength, dts: dts, pts: dts};
|
|
675
|
+
track.samples.push(mp3Sample);
|
|
676
|
+
track.length += data.length;
|
|
677
|
+
} else if (soundFormat === 3) {
|
|
678
|
+
if (!meta.codec) {
|
|
679
|
+
meta.audioSampleRate = soundRate;
|
|
680
|
+
meta.sampleSize = (soundSize + 1) * 8;
|
|
681
|
+
meta.littleEndian = true;
|
|
682
|
+
meta.codec = 'ipcm';
|
|
683
|
+
meta.originalCodec = 'ipcm';
|
|
684
|
+
|
|
685
|
+
this._audioInitialMetadataDispatched = true;
|
|
686
|
+
this._onTrackMetadata('audio', meta);
|
|
687
|
+
|
|
688
|
+
let mi = this._mediaInfo;
|
|
689
|
+
mi.audioCodec = meta.codec;
|
|
690
|
+
mi.audioSampleRate = meta.audioSampleRate;
|
|
691
|
+
mi.audioChannelCount = meta.channelCount;
|
|
692
|
+
mi.audioDataRate = meta.sampleSize * meta.audioSampleRate;
|
|
693
|
+
if (mi.hasVideo) {
|
|
694
|
+
if (mi.videoCodec != null) {
|
|
695
|
+
mi.mimeType = 'video/x-flv; codecs="' + mi.videoCodec + ',' + mi.audioCodec + '"';
|
|
696
|
+
}
|
|
697
|
+
} else {
|
|
698
|
+
mi.mimeType = 'video/x-flv; codecs="' + mi.audioCodec + '"';
|
|
699
|
+
}
|
|
700
|
+
if (mi.isComplete()) {
|
|
701
|
+
this._onMediaInfo(mi);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
let data = new Uint8Array(arrayBuffer, dataOffset + 1, dataSize - 1);
|
|
706
|
+
let dts = this._timestampBase + tagTimestamp;
|
|
707
|
+
let pcmSample = {unit: data, length: data.byteLength, dts: dts, pts: dts};
|
|
708
|
+
track.samples.push(pcmSample);
|
|
709
|
+
track.length += data.length;
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
_parseAACAudioData(arrayBuffer, dataOffset, dataSize) {
|
|
714
|
+
if (dataSize <= 1) {
|
|
715
|
+
Log.w(this.TAG, 'Flv: Invalid AAC packet, missing AACPacketType or/and Data!');
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
let result = {};
|
|
720
|
+
let array = new Uint8Array(arrayBuffer, dataOffset, dataSize);
|
|
721
|
+
|
|
722
|
+
result.packetType = array[0];
|
|
723
|
+
|
|
724
|
+
if (array[0] === 0) {
|
|
725
|
+
result.data = this._parseAACAudioSpecificConfig(arrayBuffer, dataOffset + 1, dataSize - 1);
|
|
726
|
+
} else {
|
|
727
|
+
result.data = array.subarray(1);
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
return result;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
_parseAACAudioSpecificConfig(arrayBuffer, dataOffset, dataSize) {
|
|
734
|
+
let array = new Uint8Array(arrayBuffer, dataOffset, dataSize);
|
|
735
|
+
let config = null;
|
|
736
|
+
|
|
737
|
+
/* Audio Object Type:
|
|
738
|
+
0: Null
|
|
739
|
+
1: AAC Main
|
|
740
|
+
2: AAC LC
|
|
741
|
+
3: AAC SSR (Scalable Sample Rate)
|
|
742
|
+
4: AAC LTP (Long Term Prediction)
|
|
743
|
+
5: HE-AAC / SBR (Spectral Band Replication)
|
|
744
|
+
6: AAC Scalable
|
|
745
|
+
*/
|
|
746
|
+
|
|
747
|
+
let audioObjectType = 0;
|
|
748
|
+
let originalAudioObjectType = 0;
|
|
749
|
+
let audioExtensionObjectType = null;
|
|
750
|
+
let samplingIndex = 0;
|
|
751
|
+
let extensionSamplingIndex = null;
|
|
752
|
+
|
|
753
|
+
// 5 bits
|
|
754
|
+
audioObjectType = originalAudioObjectType = array[0] >>> 3;
|
|
755
|
+
// 4 bits
|
|
756
|
+
samplingIndex = ((array[0] & 0x07) << 1) | (array[1] >>> 7);
|
|
757
|
+
if (samplingIndex < 0 || samplingIndex >= this._mpegSamplingRates.length) {
|
|
758
|
+
this._onError(DemuxErrors.FORMAT_ERROR, 'Flv: AAC invalid sampling frequency index!');
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
let samplingFrequence = this._mpegSamplingRates[samplingIndex];
|
|
763
|
+
|
|
764
|
+
// 4 bits
|
|
765
|
+
let channelConfig = (array[1] & 0x78) >>> 3;
|
|
766
|
+
if (channelConfig < 0 || channelConfig >= 8) {
|
|
767
|
+
this._onError(DemuxErrors.FORMAT_ERROR, 'Flv: AAC invalid channel configuration');
|
|
768
|
+
return;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
if (audioObjectType === 5) { // HE-AAC?
|
|
772
|
+
// 4 bits
|
|
773
|
+
extensionSamplingIndex = ((array[1] & 0x07) << 1) | (array[2] >>> 7);
|
|
774
|
+
// 5 bits
|
|
775
|
+
audioExtensionObjectType = (array[2] & 0x7C) >>> 2;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
// workarounds for various browsers
|
|
779
|
+
let userAgent = self.navigator.userAgent.toLowerCase();
|
|
780
|
+
|
|
781
|
+
if (userAgent.indexOf('firefox') !== -1) {
|
|
782
|
+
// firefox: use SBR (HE-AAC) if freq less than 24kHz
|
|
783
|
+
if (samplingIndex >= 6) {
|
|
784
|
+
audioObjectType = 5;
|
|
785
|
+
config = new Array(4);
|
|
786
|
+
extensionSamplingIndex = samplingIndex - 3;
|
|
787
|
+
} else { // use LC-AAC
|
|
788
|
+
audioObjectType = 2;
|
|
789
|
+
config = new Array(2);
|
|
790
|
+
extensionSamplingIndex = samplingIndex;
|
|
791
|
+
}
|
|
792
|
+
} else if (userAgent.indexOf('android') !== -1) {
|
|
793
|
+
// android: always use LC-AAC
|
|
794
|
+
audioObjectType = 2;
|
|
795
|
+
config = new Array(2);
|
|
796
|
+
extensionSamplingIndex = samplingIndex;
|
|
797
|
+
} else {
|
|
798
|
+
// for other browsers, e.g. chrome...
|
|
799
|
+
// Always use HE-AAC to make it easier to switch aac codec profile
|
|
800
|
+
audioObjectType = 5;
|
|
801
|
+
extensionSamplingIndex = samplingIndex;
|
|
802
|
+
config = new Array(4);
|
|
803
|
+
|
|
804
|
+
if (samplingIndex >= 6) {
|
|
805
|
+
extensionSamplingIndex = samplingIndex - 3;
|
|
806
|
+
} else if (channelConfig === 1) { // Mono channel
|
|
807
|
+
audioObjectType = 2;
|
|
808
|
+
config = new Array(2);
|
|
809
|
+
extensionSamplingIndex = samplingIndex;
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
config[0] = audioObjectType << 3;
|
|
814
|
+
config[0] |= (samplingIndex & 0x0F) >>> 1;
|
|
815
|
+
config[1] = (samplingIndex & 0x0F) << 7;
|
|
816
|
+
config[1] |= (channelConfig & 0x0F) << 3;
|
|
817
|
+
if (audioObjectType === 5) {
|
|
818
|
+
config[1] |= ((extensionSamplingIndex & 0x0F) >>> 1);
|
|
819
|
+
config[2] = (extensionSamplingIndex & 0x01) << 7;
|
|
820
|
+
// extended audio object type: force to 2 (LC-AAC)
|
|
821
|
+
config[2] |= (2 << 2);
|
|
822
|
+
config[3] = 0;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
return {
|
|
826
|
+
config: config,
|
|
827
|
+
samplingRate: samplingFrequence,
|
|
828
|
+
channelCount: channelConfig,
|
|
829
|
+
codec: 'mp4a.40.' + audioObjectType,
|
|
830
|
+
originalCodec: 'mp4a.40.' + originalAudioObjectType
|
|
831
|
+
};
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
_parseMP3AudioData(arrayBuffer, dataOffset, dataSize, requestHeader) {
|
|
835
|
+
if (dataSize < 4) {
|
|
836
|
+
Log.w(this.TAG, 'Flv: Invalid MP3 packet, header missing!');
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
let le = this._littleEndian;
|
|
841
|
+
let array = new Uint8Array(arrayBuffer, dataOffset, dataSize);
|
|
842
|
+
let result = null;
|
|
843
|
+
|
|
844
|
+
if (requestHeader) {
|
|
845
|
+
if (array[0] !== 0xFF) {
|
|
846
|
+
return;
|
|
847
|
+
}
|
|
848
|
+
let ver = (array[1] >>> 3) & 0x03;
|
|
849
|
+
let layer = (array[1] & 0x06) >> 1;
|
|
850
|
+
|
|
851
|
+
let bitrate_index = (array[2] & 0xF0) >>> 4;
|
|
852
|
+
let sampling_freq_index = (array[2] & 0x0C) >>> 2;
|
|
853
|
+
|
|
854
|
+
let channel_mode = (array[3] >>> 6) & 0x03;
|
|
855
|
+
let channel_count = channel_mode !== 3 ? 2 : 1;
|
|
856
|
+
|
|
857
|
+
let sample_rate = 0;
|
|
858
|
+
let bit_rate = 0;
|
|
859
|
+
let object_type = 34; // Layer-3, listed in MPEG-4 Audio Object Types
|
|
860
|
+
|
|
861
|
+
let codec = 'mp3';
|
|
862
|
+
|
|
863
|
+
switch (ver) {
|
|
864
|
+
case 0: // MPEG 2.5
|
|
865
|
+
sample_rate = this._mpegAudioV25SampleRateTable[sampling_freq_index];
|
|
866
|
+
break;
|
|
867
|
+
case 2: // MPEG 2
|
|
868
|
+
sample_rate = this._mpegAudioV20SampleRateTable[sampling_freq_index];
|
|
869
|
+
break;
|
|
870
|
+
case 3: // MPEG 1
|
|
871
|
+
sample_rate = this._mpegAudioV10SampleRateTable[sampling_freq_index];
|
|
872
|
+
break;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
switch (layer) {
|
|
876
|
+
case 1: // Layer 3
|
|
877
|
+
object_type = 34;
|
|
878
|
+
if (bitrate_index < this._mpegAudioL3BitRateTable.length) {
|
|
879
|
+
bit_rate = this._mpegAudioL3BitRateTable[bitrate_index];
|
|
880
|
+
}
|
|
881
|
+
break;
|
|
882
|
+
case 2: // Layer 2
|
|
883
|
+
object_type = 33;
|
|
884
|
+
if (bitrate_index < this._mpegAudioL2BitRateTable.length) {
|
|
885
|
+
bit_rate = this._mpegAudioL2BitRateTable[bitrate_index];
|
|
886
|
+
}
|
|
887
|
+
break;
|
|
888
|
+
case 3: // Layer 1
|
|
889
|
+
object_type = 32;
|
|
890
|
+
if (bitrate_index < this._mpegAudioL1BitRateTable.length) {
|
|
891
|
+
bit_rate = this._mpegAudioL1BitRateTable[bitrate_index];
|
|
892
|
+
}
|
|
893
|
+
break;
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
result = {
|
|
897
|
+
bitRate: bit_rate,
|
|
898
|
+
samplingRate: sample_rate,
|
|
899
|
+
channelCount: channel_count,
|
|
900
|
+
codec: codec,
|
|
901
|
+
originalCodec: codec
|
|
902
|
+
};
|
|
903
|
+
} else {
|
|
904
|
+
result = array;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
return result;
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
_parseOpusAudioPacket(arrayBuffer, dataOffset, dataSize, tagTimestamp, packetType) {
|
|
911
|
+
if (packetType === 0) { // OpusSequenceHeader
|
|
912
|
+
this._parseOpusSequenceHeader(arrayBuffer, dataOffset, dataSize);
|
|
913
|
+
} else if (packetType === 1) { // OpusCodedData
|
|
914
|
+
this._parseOpusAudioData(arrayBuffer, dataOffset, dataSize, tagTimestamp);
|
|
915
|
+
} else if (packetType === 2) {
|
|
916
|
+
// empty, Opus end of sequence
|
|
917
|
+
} else {
|
|
918
|
+
this._onError(DemuxErrors.FORMAT_ERROR, `Flv: Invalid video packet type ${packetType}`);
|
|
919
|
+
return;
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
_parseOpusSequenceHeader(arrayBuffer, dataOffset, dataSize) {
|
|
924
|
+
if (dataSize <= 16) {
|
|
925
|
+
Log.w(this.TAG, 'Flv: Invalid OpusSequenceHeader, lack of data!');
|
|
926
|
+
return;
|
|
927
|
+
}
|
|
928
|
+
let meta = this._audioMetadata;
|
|
929
|
+
let track = this._audioTrack;
|
|
930
|
+
|
|
931
|
+
if (!meta) {
|
|
932
|
+
if (this._hasAudio === false && this._hasAudioFlagOverrided === false) {
|
|
933
|
+
this._hasAudio = true;
|
|
934
|
+
this._mediaInfo.hasAudio = true;
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
// initial metadata
|
|
938
|
+
meta = this._audioMetadata = {};
|
|
939
|
+
meta.type = 'audio';
|
|
940
|
+
meta.id = track.id;
|
|
941
|
+
meta.timescale = this._timescale;
|
|
942
|
+
meta.duration = this._duration;
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
// Identification Header
|
|
946
|
+
let v = new DataView(arrayBuffer, dataOffset, dataSize);
|
|
947
|
+
v.setUint8(8 + 0, 0); // set version to 0
|
|
948
|
+
let channelCount = v.getUint8(8 + 1); // Opus Header + 1
|
|
949
|
+
v.setUint16(8 + 2, v.getUint16(8 + 2, true), false); // Big Endian to Little Endian for Pre-skip
|
|
950
|
+
let samplingFrequence = v.getUint32(8 + 4, true); // Opus Header + 4
|
|
951
|
+
v.setUint32(8 + 4, v.getUint32(8 + 4, true), false); // Big Endian to Little Endian for Input Sample Rate
|
|
952
|
+
let config = new Uint8Array(arrayBuffer, dataOffset + 8, dataSize - 8);
|
|
953
|
+
|
|
954
|
+
let misc = {
|
|
955
|
+
config,
|
|
956
|
+
channelCount,
|
|
957
|
+
samplingFrequence,
|
|
958
|
+
codec: 'opus',
|
|
959
|
+
originalCodec: 'opus',
|
|
960
|
+
};
|
|
961
|
+
if (meta.config) {
|
|
962
|
+
if (buffersAreEqual(misc.config, meta.config)) {
|
|
963
|
+
// If OpusSequenceHeader is not changed, ignore it to avoid generating initialization segment repeatedly
|
|
964
|
+
return;
|
|
965
|
+
} else {
|
|
966
|
+
Log.w(this.TAG, 'OpusSequenceHeader has been changed, re-generate initialization segment');
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
meta.audioSampleRate = misc.samplingFrequence;
|
|
970
|
+
meta.channelCount = misc.channelCount;
|
|
971
|
+
meta.codec = misc.codec;
|
|
972
|
+
meta.originalCodec = misc.originalCodec;
|
|
973
|
+
meta.config = misc.config;
|
|
974
|
+
// The decode result of an opus sample is 20ms
|
|
975
|
+
meta.refSampleDuration = 20;
|
|
976
|
+
Log.v(this.TAG, 'Parsed OpusSequenceHeader');
|
|
977
|
+
|
|
978
|
+
if (this._isInitialMetadataDispatched()) {
|
|
979
|
+
// Non-initial metadata, force dispatch (or flush) parsed frames to remuxer
|
|
980
|
+
if (this._dispatch && (this._audioTrack.length || this._videoTrack.length)) {
|
|
981
|
+
this._onDataAvailable(this._audioTrack, this._videoTrack);
|
|
982
|
+
}
|
|
983
|
+
} else {
|
|
984
|
+
this._audioInitialMetadataDispatched = true;
|
|
985
|
+
}
|
|
986
|
+
// then notify new metadata
|
|
987
|
+
this._dispatch = false;
|
|
988
|
+
this._onTrackMetadata('audio', meta);
|
|
989
|
+
|
|
990
|
+
let mi = this._mediaInfo;
|
|
991
|
+
mi.audioCodec = meta.originalCodec;
|
|
992
|
+
mi.audioSampleRate = meta.audioSampleRate;
|
|
993
|
+
mi.audioChannelCount = meta.channelCount;
|
|
994
|
+
if (mi.hasVideo) {
|
|
995
|
+
if (mi.videoCodec != null) {
|
|
996
|
+
mi.mimeType = 'video/x-flv; codecs="' + mi.videoCodec + ',' + mi.audioCodec + '"';
|
|
997
|
+
}
|
|
998
|
+
} else {
|
|
999
|
+
mi.mimeType = 'video/x-flv; codecs="' + mi.audioCodec + '"';
|
|
1000
|
+
}
|
|
1001
|
+
if (mi.isComplete()) {
|
|
1002
|
+
this._onMediaInfo(mi);
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
_parseOpusAudioData(arrayBuffer, dataOffset, dataSize, tagTimestamp) {
|
|
1007
|
+
let track = this._audioTrack;
|
|
1008
|
+
|
|
1009
|
+
let data = new Uint8Array(arrayBuffer, dataOffset, dataSize);
|
|
1010
|
+
let dts = this._timestampBase + tagTimestamp;
|
|
1011
|
+
let opusSample = {unit: data, length: data.byteLength, dts: dts, pts: dts};
|
|
1012
|
+
|
|
1013
|
+
track.samples.push(opusSample);
|
|
1014
|
+
track.length += data.length;
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
_parseFlacAudioPacket(arrayBuffer, dataOffset, dataSize, tagTimestamp, packetType) {
|
|
1018
|
+
if (packetType === 0) { // FlacSequenceHeader
|
|
1019
|
+
this._parseFlacSequenceHeader(arrayBuffer, dataOffset, dataSize);
|
|
1020
|
+
} else if (packetType === 1) { // FlacCodedData
|
|
1021
|
+
this._parseFlacAudioData(arrayBuffer, dataOffset, dataSize, tagTimestamp);
|
|
1022
|
+
} else if (packetType === 2) {
|
|
1023
|
+
// empty, Flac end of sequence
|
|
1024
|
+
} else {
|
|
1025
|
+
this._onError(DemuxErrors.FORMAT_ERROR, `Flv: Invalid Flac audio packet type ${packetType}`);
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
_parseFlacSequenceHeader(arrayBuffer, dataOffset, dataSize) {
|
|
1031
|
+
let meta = this._audioMetadata;
|
|
1032
|
+
let track = this._audioTrack;
|
|
1033
|
+
|
|
1034
|
+
if (!meta) {
|
|
1035
|
+
if (this._hasAudio === false && this._hasAudioFlagOverrided === false) {
|
|
1036
|
+
this._hasAudio = true;
|
|
1037
|
+
this._mediaInfo.hasAudio = true;
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
// initial metadata
|
|
1041
|
+
meta = this._audioMetadata = {};
|
|
1042
|
+
meta.type = 'audio';
|
|
1043
|
+
meta.id = track.id;
|
|
1044
|
+
meta.timescale = this._timescale;
|
|
1045
|
+
meta.duration = this._duration;
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
// METADATA_BLOCK_HEADER
|
|
1049
|
+
let header = new Uint8Array(arrayBuffer, dataOffset + 4, dataSize - 4);
|
|
1050
|
+
let gb = new ExpGolomb(header);
|
|
1051
|
+
let minimum_block_size = gb.readBits(16); // minimum_block_size
|
|
1052
|
+
let maximum_block_size = gb.readBits(16); // maximum_block_size
|
|
1053
|
+
let block_size = maximum_block_size === minimum_block_size ? maximum_block_size : null;
|
|
1054
|
+
gb.readBits(24); // minimum_frame_size
|
|
1055
|
+
gb.readBits(24); // maximum_frame_size
|
|
1056
|
+
let samplingFrequence = gb.readBits(20);
|
|
1057
|
+
let channelCount = gb.readBits(3) + 1;
|
|
1058
|
+
let sampleSize = gb.readBits(5) + 1;
|
|
1059
|
+
gb.destroy();
|
|
1060
|
+
|
|
1061
|
+
let config = new Uint8Array(header.byteLength + 4);
|
|
1062
|
+
config.set(header, 4);
|
|
1063
|
+
config[0] = 1 << 7;
|
|
1064
|
+
config[1] = (header.byteLength >>> 16) & 0xFF;
|
|
1065
|
+
config[2] = (header.byteLength >>> 8) & 0xFF;
|
|
1066
|
+
config[3] = (header.byteLength >>> 0) & 0xFF;
|
|
1067
|
+
|
|
1068
|
+
let misc = {
|
|
1069
|
+
config,
|
|
1070
|
+
channelCount,
|
|
1071
|
+
samplingFrequence,
|
|
1072
|
+
sampleSize,
|
|
1073
|
+
codec: 'flac',
|
|
1074
|
+
originalCodec: 'flac',
|
|
1075
|
+
};
|
|
1076
|
+
if (meta.config) {
|
|
1077
|
+
if (buffersAreEqual(misc.config, meta.config)) {
|
|
1078
|
+
// If FlacSequenceHeader is not changed, ignore it to avoid generating initialization segment repeatedly
|
|
1079
|
+
return;
|
|
1080
|
+
} else {
|
|
1081
|
+
Log.w(this.TAG, 'FlacSequenceHeader has been changed, re-generate initialization segment');
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
meta.audioSampleRate = misc.samplingFrequence;
|
|
1085
|
+
meta.channelCount = misc.channelCount;
|
|
1086
|
+
meta.sampleSize = misc.sampleSize;
|
|
1087
|
+
meta.codec = misc.codec;
|
|
1088
|
+
meta.originalCodec = misc.originalCodec;
|
|
1089
|
+
meta.config = misc.config;
|
|
1090
|
+
meta.refSampleDuration = block_size != null ? block_size * 1000 / misc.samplingFrequence : null; // practical encoder sends 4608 blobksize (lower bound limitation)
|
|
1091
|
+
|
|
1092
|
+
Log.v(this.TAG, 'Parsed FlacSequenceHeader');
|
|
1093
|
+
|
|
1094
|
+
if (this._isInitialMetadataDispatched()) {
|
|
1095
|
+
// Non-initial metadata, force dispatch (or flush) parsed frames to remuxer
|
|
1096
|
+
if (this._dispatch && (this._audioTrack.length || this._videoTrack.length)) {
|
|
1097
|
+
this._onDataAvailable(this._audioTrack, this._videoTrack);
|
|
1098
|
+
}
|
|
1099
|
+
} else {
|
|
1100
|
+
this._audioInitialMetadataDispatched = true;
|
|
1101
|
+
}
|
|
1102
|
+
// then notify new metadata
|
|
1103
|
+
this._dispatch = false;
|
|
1104
|
+
this._onTrackMetadata('audio', meta);
|
|
1105
|
+
|
|
1106
|
+
let mi = this._mediaInfo;
|
|
1107
|
+
mi.audioCodec = meta.originalCodec;
|
|
1108
|
+
mi.audioSampleRate = meta.audioSampleRate;
|
|
1109
|
+
mi.audioChannelCount = meta.channelCount;
|
|
1110
|
+
if (mi.hasVideo) {
|
|
1111
|
+
if (mi.videoCodec != null) {
|
|
1112
|
+
mi.mimeType = 'video/x-flv; codecs="' + mi.videoCodec + ',' + mi.audioCodec + '"';
|
|
1113
|
+
}
|
|
1114
|
+
} else {
|
|
1115
|
+
mi.mimeType = 'video/x-flv; codecs="' + mi.audioCodec + '"';
|
|
1116
|
+
}
|
|
1117
|
+
if (mi.isComplete()) {
|
|
1118
|
+
this._onMediaInfo(mi);
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
_parseFlacAudioData(arrayBuffer, dataOffset, dataSize, tagTimestamp) {
|
|
1123
|
+
let track = this._audioTrack;
|
|
1124
|
+
|
|
1125
|
+
let data = new Uint8Array(arrayBuffer, dataOffset, dataSize);
|
|
1126
|
+
let dts = this._timestampBase + tagTimestamp;
|
|
1127
|
+
let flacSample = {unit: data, length: data.byteLength, dts: dts, pts: dts};
|
|
1128
|
+
|
|
1129
|
+
track.samples.push(flacSample);
|
|
1130
|
+
track.length += data.length;
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
_parseVideoData(arrayBuffer, dataOffset, dataSize, tagTimestamp, tagPosition) {
|
|
1134
|
+
if (dataSize <= 1) {
|
|
1135
|
+
Log.w(this.TAG, 'Flv: Invalid video packet, missing VideoData payload!');
|
|
1136
|
+
return;
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
if (this._hasVideoFlagOverrided === true && this._hasVideo === false) {
|
|
1140
|
+
// If hasVideo: false indicated explicitly in MediaDataSource,
|
|
1141
|
+
// Ignore all the video packets
|
|
1142
|
+
return;
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
let spec = (new Uint8Array(arrayBuffer, dataOffset, dataSize))[0];
|
|
1146
|
+
|
|
1147
|
+
let isExHeader = (spec & 0b10000000) !== 0;
|
|
1148
|
+
let frameType = (spec & 0b01110000) >>> 4;
|
|
1149
|
+
|
|
1150
|
+
if (!isExHeader) {
|
|
1151
|
+
let codecId = spec & 0b00001111;
|
|
1152
|
+
if (codecId === 7) { // AVC
|
|
1153
|
+
this._parseAVCVideoPacket(arrayBuffer, dataOffset + 1, dataSize - 1, tagTimestamp, tagPosition, frameType);
|
|
1154
|
+
} else if (codecId === 12) { // HEVC
|
|
1155
|
+
this._parseHEVCVideoPacket(arrayBuffer, dataOffset + 1, dataSize - 1, tagTimestamp, tagPosition, frameType);
|
|
1156
|
+
} else {
|
|
1157
|
+
this._onError(DemuxErrors.CODEC_UNSUPPORTED, `Flv: Unsupported codec in video frame: ${codecId}`);
|
|
1158
|
+
return;
|
|
1159
|
+
}
|
|
1160
|
+
} else {
|
|
1161
|
+
let packetType = spec & 0b00001111;
|
|
1162
|
+
let fourcc = String.fromCharCode(... (new Uint8Array(arrayBuffer, dataOffset, dataSize)).slice(1, 5));
|
|
1163
|
+
|
|
1164
|
+
if (fourcc === 'hvc1') { // HEVC
|
|
1165
|
+
this._parseEnhancedHEVCVideoPacket(arrayBuffer, dataOffset + 5, dataSize - 5, tagTimestamp, tagPosition, frameType, packetType);
|
|
1166
|
+
} else if (fourcc === 'av01') { // HEVC
|
|
1167
|
+
this._parseEnhancedAV1VideoPacket(arrayBuffer, dataOffset + 5, dataSize - 5, tagTimestamp, tagPosition, frameType, packetType);
|
|
1168
|
+
} else {
|
|
1169
|
+
this._onError(DemuxErrors.CODEC_UNSUPPORTED, `Flv: Unsupported codec in video frame: ${fourcc}`);
|
|
1170
|
+
return;
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
_parseAVCVideoPacket(arrayBuffer, dataOffset, dataSize, tagTimestamp, tagPosition, frameType) {
|
|
1176
|
+
if (dataSize < 4) {
|
|
1177
|
+
Log.w(this.TAG, 'Flv: Invalid AVC packet, missing AVCPacketType or/and CompositionTime');
|
|
1178
|
+
return;
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
let le = this._littleEndian;
|
|
1182
|
+
let v = new DataView(arrayBuffer, dataOffset, dataSize);
|
|
1183
|
+
|
|
1184
|
+
let packetType = v.getUint8(0);
|
|
1185
|
+
let cts_unsigned = v.getUint32(0, !le) & 0x00FFFFFF;
|
|
1186
|
+
let cts = (cts_unsigned << 8) >> 8; // convert to 24-bit signed int
|
|
1187
|
+
|
|
1188
|
+
if (packetType === 0) { // AVCDecoderConfigurationRecord
|
|
1189
|
+
this._parseAVCDecoderConfigurationRecord(arrayBuffer, dataOffset + 4, dataSize - 4);
|
|
1190
|
+
} else if (packetType === 1) { // One or more Nalus
|
|
1191
|
+
this._parseAVCVideoData(arrayBuffer, dataOffset + 4, dataSize - 4, tagTimestamp, tagPosition, frameType, cts);
|
|
1192
|
+
} else if (packetType === 2) {
|
|
1193
|
+
// empty, AVC end of sequence
|
|
1194
|
+
} else {
|
|
1195
|
+
this._onError(DemuxErrors.FORMAT_ERROR, `Flv: Invalid video packet type ${packetType}`);
|
|
1196
|
+
return;
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
_parseHEVCVideoPacket(arrayBuffer, dataOffset, dataSize, tagTimestamp, tagPosition, frameType) {
|
|
1201
|
+
if (dataSize < 4) {
|
|
1202
|
+
Log.w(this.TAG, 'Flv: Invalid HEVC packet, missing HEVCPacketType or/and CompositionTime');
|
|
1203
|
+
return;
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
let le = this._littleEndian;
|
|
1207
|
+
let v = new DataView(arrayBuffer, dataOffset, dataSize);
|
|
1208
|
+
|
|
1209
|
+
let packetType = v.getUint8(0);
|
|
1210
|
+
let cts_unsigned = v.getUint32(0, !le) & 0x00FFFFFF;
|
|
1211
|
+
let cts = (cts_unsigned << 8) >> 8; // convert to 24-bit signed int
|
|
1212
|
+
|
|
1213
|
+
if (packetType === 0) { // HEVCDecoderConfigurationRecord
|
|
1214
|
+
this._parseHEVCDecoderConfigurationRecord(arrayBuffer, dataOffset + 4, dataSize - 4);
|
|
1215
|
+
} else if (packetType === 1) { // One or more Nalus
|
|
1216
|
+
this._parseHEVCVideoData(arrayBuffer, dataOffset + 4, dataSize - 4, tagTimestamp, tagPosition, frameType, cts);
|
|
1217
|
+
} else if (packetType === 2) {
|
|
1218
|
+
// empty, HEVC end of sequence
|
|
1219
|
+
} else {
|
|
1220
|
+
this._onError(DemuxErrors.FORMAT_ERROR, `Flv: Invalid video packet type ${packetType}`);
|
|
1221
|
+
return;
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
_parseEnhancedHEVCVideoPacket(arrayBuffer, dataOffset, dataSize, tagTimestamp, tagPosition, frameType, packetType) {
|
|
1226
|
+
let le = this._littleEndian;
|
|
1227
|
+
let v = new DataView(arrayBuffer, dataOffset, dataSize);
|
|
1228
|
+
|
|
1229
|
+
if (packetType === 0) { // HEVCDecoderConfigurationRecord
|
|
1230
|
+
this._parseHEVCDecoderConfigurationRecord(arrayBuffer, dataOffset, dataSize);
|
|
1231
|
+
} else if (packetType === 1) { // One or more Nalus
|
|
1232
|
+
let cts_unsigned = v.getUint32(0, !le) & 0xFFFFFF00;
|
|
1233
|
+
let cts = cts_unsigned >> 8; // convert to 24-bit signed int
|
|
1234
|
+
|
|
1235
|
+
this._parseHEVCVideoData(arrayBuffer, dataOffset + 3, dataSize - 3, tagTimestamp, tagPosition, frameType, cts);
|
|
1236
|
+
} else if (packetType === 3) {
|
|
1237
|
+
this._parseHEVCVideoData(arrayBuffer, dataOffset, dataSize, tagTimestamp, tagPosition, frameType, 0);
|
|
1238
|
+
} else if (packetType === 2) {
|
|
1239
|
+
// empty, HEVC end of sequence
|
|
1240
|
+
} else {
|
|
1241
|
+
this._onError(DemuxErrors.FORMAT_ERROR, `Flv: Invalid video packet type ${packetType}`);
|
|
1242
|
+
return;
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
_parseEnhancedAV1VideoPacket(arrayBuffer, dataOffset, dataSize, tagTimestamp, tagPosition, frameType, packetType) {
|
|
1247
|
+
let le = this._littleEndian;
|
|
1248
|
+
let v = new DataView(arrayBuffer, dataOffset, dataSize);
|
|
1249
|
+
|
|
1250
|
+
if (packetType === 0) { // AV1CodecConfigurationRecord
|
|
1251
|
+
this._parseAV1CodecConfigurationRecord(arrayBuffer, dataOffset, dataSize);
|
|
1252
|
+
} else if (packetType === 1) { // One or more OBUs
|
|
1253
|
+
this._parseAV1VideoData(arrayBuffer, dataOffset, dataSize, tagTimestamp, tagPosition, frameType, 0);
|
|
1254
|
+
} else if (packetType === 5) {
|
|
1255
|
+
this._onError(DemuxErrors.FORMAT_ERROR, `Flv: Not Supported MP2T AV1 video packet type ${packetType}`);
|
|
1256
|
+
return;
|
|
1257
|
+
} else if (packetType === 2) {
|
|
1258
|
+
// empty, AV1 end of sequence
|
|
1259
|
+
} else {
|
|
1260
|
+
this._onError(DemuxErrors.FORMAT_ERROR, `Flv: Invalid video packet type ${packetType}`);
|
|
1261
|
+
return;
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
_parseAVCDecoderConfigurationRecord(arrayBuffer, dataOffset, dataSize) {
|
|
1266
|
+
if (dataSize < 7) {
|
|
1267
|
+
Log.w(this.TAG, 'Flv: Invalid AVCDecoderConfigurationRecord, lack of data!');
|
|
1268
|
+
return;
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
let meta = this._videoMetadata;
|
|
1272
|
+
let track = this._videoTrack;
|
|
1273
|
+
let le = this._littleEndian;
|
|
1274
|
+
let v = new DataView(arrayBuffer, dataOffset, dataSize);
|
|
1275
|
+
|
|
1276
|
+
if (!meta) {
|
|
1277
|
+
if (this._hasVideo === false && this._hasVideoFlagOverrided === false) {
|
|
1278
|
+
this._hasVideo = true;
|
|
1279
|
+
this._mediaInfo.hasVideo = true;
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
meta = this._videoMetadata = {};
|
|
1283
|
+
meta.type = 'video';
|
|
1284
|
+
meta.id = track.id;
|
|
1285
|
+
meta.timescale = this._timescale;
|
|
1286
|
+
meta.duration = this._duration;
|
|
1287
|
+
} else {
|
|
1288
|
+
if (typeof meta.avcc !== 'undefined') {
|
|
1289
|
+
let new_avcc = new Uint8Array(arrayBuffer, dataOffset, dataSize);
|
|
1290
|
+
if (buffersAreEqual(new_avcc, meta.avcc)) {
|
|
1291
|
+
// AVCDecoderConfigurationRecord is not changed, ignore it to avoid initialization segment re-generating
|
|
1292
|
+
return;
|
|
1293
|
+
} else {
|
|
1294
|
+
Log.w(this.TAG, 'AVCDecoderConfigurationRecord has been changed, re-generate initialization segment');
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
let version = v.getUint8(0); // configurationVersion
|
|
1300
|
+
let avcProfile = v.getUint8(1); // avcProfileIndication
|
|
1301
|
+
let profileCompatibility = v.getUint8(2); // profile_compatibility
|
|
1302
|
+
let avcLevel = v.getUint8(3); // AVCLevelIndication
|
|
1303
|
+
|
|
1304
|
+
if (version !== 1 || avcProfile === 0) {
|
|
1305
|
+
this._onError(DemuxErrors.FORMAT_ERROR, 'Flv: Invalid AVCDecoderConfigurationRecord');
|
|
1306
|
+
return;
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
this._naluLengthSize = (v.getUint8(4) & 3) + 1; // lengthSizeMinusOne
|
|
1310
|
+
if (this._naluLengthSize !== 3 && this._naluLengthSize !== 4) { // holy shit!!!
|
|
1311
|
+
this._onError(DemuxErrors.FORMAT_ERROR, `Flv: Strange NaluLengthSizeMinusOne: ${this._naluLengthSize - 1}`);
|
|
1312
|
+
return;
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
let spsCount = v.getUint8(5) & 31; // numOfSequenceParameterSets
|
|
1316
|
+
if (spsCount === 0) {
|
|
1317
|
+
this._onError(DemuxErrors.FORMAT_ERROR, 'Flv: Invalid AVCDecoderConfigurationRecord: No SPS');
|
|
1318
|
+
return;
|
|
1319
|
+
} else if (spsCount > 1) {
|
|
1320
|
+
Log.w(this.TAG, `Flv: Strange AVCDecoderConfigurationRecord: SPS Count = ${spsCount}`);
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
let offset = 6;
|
|
1324
|
+
|
|
1325
|
+
for (let i = 0; i < spsCount; i++) {
|
|
1326
|
+
let len = v.getUint16(offset, !le); // sequenceParameterSetLength
|
|
1327
|
+
offset += 2;
|
|
1328
|
+
|
|
1329
|
+
if (len === 0) {
|
|
1330
|
+
continue;
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
// Notice: Nalu without startcode header (00 00 00 01)
|
|
1334
|
+
let sps = new Uint8Array(arrayBuffer, dataOffset + offset, len);
|
|
1335
|
+
offset += len;
|
|
1336
|
+
|
|
1337
|
+
let config = SPSParser.parseSPS(sps);
|
|
1338
|
+
if (i !== 0) {
|
|
1339
|
+
// ignore other sps's config
|
|
1340
|
+
continue;
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
meta.codecWidth = config.codec_size.width;
|
|
1344
|
+
meta.codecHeight = config.codec_size.height;
|
|
1345
|
+
meta.presentWidth = config.present_size.width;
|
|
1346
|
+
meta.presentHeight = config.present_size.height;
|
|
1347
|
+
|
|
1348
|
+
meta.profile = config.profile_string;
|
|
1349
|
+
meta.level = config.level_string;
|
|
1350
|
+
meta.bitDepth = config.bit_depth;
|
|
1351
|
+
meta.chromaFormat = config.chroma_format;
|
|
1352
|
+
meta.sarRatio = config.sar_ratio;
|
|
1353
|
+
meta.frameRate = config.frame_rate;
|
|
1354
|
+
|
|
1355
|
+
if (config.frame_rate.fixed === false ||
|
|
1356
|
+
config.frame_rate.fps_num === 0 ||
|
|
1357
|
+
config.frame_rate.fps_den === 0) {
|
|
1358
|
+
meta.frameRate = this._referenceFrameRate;
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
let fps_den = meta.frameRate.fps_den;
|
|
1362
|
+
let fps_num = meta.frameRate.fps_num;
|
|
1363
|
+
meta.refSampleDuration = meta.timescale * (fps_den / fps_num);
|
|
1364
|
+
|
|
1365
|
+
let codecArray = sps.subarray(1, 4);
|
|
1366
|
+
let codecString = 'avc1.';
|
|
1367
|
+
for (let j = 0; j < 3; j++) {
|
|
1368
|
+
let h = codecArray[j].toString(16);
|
|
1369
|
+
if (h.length < 2) {
|
|
1370
|
+
h = '0' + h;
|
|
1371
|
+
}
|
|
1372
|
+
codecString += h;
|
|
1373
|
+
}
|
|
1374
|
+
meta.codec = codecString;
|
|
1375
|
+
|
|
1376
|
+
let mi = this._mediaInfo;
|
|
1377
|
+
mi.width = meta.codecWidth;
|
|
1378
|
+
mi.height = meta.codecHeight;
|
|
1379
|
+
mi.fps = meta.frameRate.fps;
|
|
1380
|
+
mi.profile = meta.profile;
|
|
1381
|
+
mi.level = meta.level;
|
|
1382
|
+
mi.refFrames = config.ref_frames;
|
|
1383
|
+
mi.chromaFormat = config.chroma_format_string;
|
|
1384
|
+
mi.sarNum = meta.sarRatio.width;
|
|
1385
|
+
mi.sarDen = meta.sarRatio.height;
|
|
1386
|
+
mi.videoCodec = codecString;
|
|
1387
|
+
|
|
1388
|
+
if (mi.hasAudio) {
|
|
1389
|
+
if (mi.audioCodec != null) {
|
|
1390
|
+
mi.mimeType = 'video/x-flv; codecs="' + mi.videoCodec + ',' + mi.audioCodec + '"';
|
|
1391
|
+
}
|
|
1392
|
+
} else {
|
|
1393
|
+
mi.mimeType = 'video/x-flv; codecs="' + mi.videoCodec + '"';
|
|
1394
|
+
}
|
|
1395
|
+
if (mi.isComplete()) {
|
|
1396
|
+
this._onMediaInfo(mi);
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
let ppsCount = v.getUint8(offset); // numOfPictureParameterSets
|
|
1401
|
+
if (ppsCount === 0) {
|
|
1402
|
+
this._onError(DemuxErrors.FORMAT_ERROR, 'Flv: Invalid AVCDecoderConfigurationRecord: No PPS');
|
|
1403
|
+
return;
|
|
1404
|
+
} else if (ppsCount > 1) {
|
|
1405
|
+
Log.w(this.TAG, `Flv: Strange AVCDecoderConfigurationRecord: PPS Count = ${ppsCount}`);
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
offset++;
|
|
1409
|
+
|
|
1410
|
+
for (let i = 0; i < ppsCount; i++) {
|
|
1411
|
+
let len = v.getUint16(offset, !le); // pictureParameterSetLength
|
|
1412
|
+
offset += 2;
|
|
1413
|
+
|
|
1414
|
+
if (len === 0) {
|
|
1415
|
+
continue;
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
// pps is useless for extracting video information
|
|
1419
|
+
offset += len;
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
meta.avcc = new Uint8Array(dataSize);
|
|
1423
|
+
meta.avcc.set(new Uint8Array(arrayBuffer, dataOffset, dataSize), 0);
|
|
1424
|
+
Log.v(this.TAG, 'Parsed AVCDecoderConfigurationRecord');
|
|
1425
|
+
|
|
1426
|
+
if (this._isInitialMetadataDispatched()) {
|
|
1427
|
+
// flush parsed frames
|
|
1428
|
+
if (this._dispatch && (this._audioTrack.length || this._videoTrack.length)) {
|
|
1429
|
+
this._onDataAvailable(this._audioTrack, this._videoTrack);
|
|
1430
|
+
}
|
|
1431
|
+
} else {
|
|
1432
|
+
this._videoInitialMetadataDispatched = true;
|
|
1433
|
+
}
|
|
1434
|
+
// notify new metadata
|
|
1435
|
+
this._dispatch = false;
|
|
1436
|
+
this._onTrackMetadata('video', meta);
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
_parseHEVCDecoderConfigurationRecord(arrayBuffer, dataOffset, dataSize) {
|
|
1440
|
+
if (dataSize < 22) {
|
|
1441
|
+
Log.w(this.TAG, 'Flv: Invalid HEVCDecoderConfigurationRecord, lack of data!');
|
|
1442
|
+
return;
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
let meta = this._videoMetadata;
|
|
1446
|
+
let track = this._videoTrack;
|
|
1447
|
+
let le = this._littleEndian;
|
|
1448
|
+
let v = new DataView(arrayBuffer, dataOffset, dataSize);
|
|
1449
|
+
|
|
1450
|
+
if (!meta) {
|
|
1451
|
+
if (this._hasVideo === false && this._hasVideoFlagOverrided === false) {
|
|
1452
|
+
this._hasVideo = true;
|
|
1453
|
+
this._mediaInfo.hasVideo = true;
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
meta = this._videoMetadata = {};
|
|
1457
|
+
meta.type = 'video';
|
|
1458
|
+
meta.id = track.id;
|
|
1459
|
+
meta.timescale = this._timescale;
|
|
1460
|
+
meta.duration = this._duration;
|
|
1461
|
+
} else {
|
|
1462
|
+
if (typeof meta.hvcc !== 'undefined') {
|
|
1463
|
+
let new_hvcc = new Uint8Array(arrayBuffer, dataOffset, dataSize);
|
|
1464
|
+
if (buffersAreEqual(new_hvcc, meta.hvcc)) {
|
|
1465
|
+
// HEVCDecoderConfigurationRecord not changed, ignore it to avoid initialization segment re-generating
|
|
1466
|
+
return;
|
|
1467
|
+
} else {
|
|
1468
|
+
Log.w(this.TAG, 'HEVCDecoderConfigurationRecord has been changed, re-generate initialization segment');
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
let version = v.getUint8(0); // configurationVersion
|
|
1474
|
+
let hevcProfile = v.getUint8(1) & 0x1F; // hevcProfileIndication
|
|
1475
|
+
|
|
1476
|
+
if ((version !== 0 && version !== 1) || hevcProfile === 0) {
|
|
1477
|
+
this._onError(DemuxErrors.FORMAT_ERROR, 'Flv: Invalid HEVCDecoderConfigurationRecord');
|
|
1478
|
+
return;
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
this._naluLengthSize = (v.getUint8(21) & 3) + 1; // lengthSizeMinusOne
|
|
1482
|
+
if (this._naluLengthSize !== 3 && this._naluLengthSize !== 4) { // holy shit!!!
|
|
1483
|
+
this._onError(DemuxErrors.FORMAT_ERROR, `Flv: Strange NaluLengthSizeMinusOne: ${this._naluLengthSize - 1}`);
|
|
1484
|
+
return;
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
let numOfArrays = v.getUint8(22);
|
|
1488
|
+
for (let i = 0, offset = 23; i < numOfArrays; i++) {
|
|
1489
|
+
let nalUnitType = v.getUint8(offset + 0) & 0x3F;
|
|
1490
|
+
let numNalus = v.getUint16(offset + 1, !le);
|
|
1491
|
+
|
|
1492
|
+
offset += 3;
|
|
1493
|
+
for (let j = 0; j < numNalus; j++) {
|
|
1494
|
+
let len = v.getUint16(offset + 0, !le);
|
|
1495
|
+
if (j !== 0) {
|
|
1496
|
+
offset += 2 + len;
|
|
1497
|
+
continue;
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
if (nalUnitType === 33) {
|
|
1501
|
+
offset += 2;
|
|
1502
|
+
let sps = new Uint8Array(arrayBuffer, dataOffset + offset, len);
|
|
1503
|
+
|
|
1504
|
+
let config = H265Parser.parseSPS(sps);
|
|
1505
|
+
meta.codecWidth = config.codec_size.width;
|
|
1506
|
+
meta.codecHeight = config.codec_size.height;
|
|
1507
|
+
meta.presentWidth = config.present_size.width;
|
|
1508
|
+
meta.presentHeight = config.present_size.height;
|
|
1509
|
+
|
|
1510
|
+
meta.profile = config.profile_string;
|
|
1511
|
+
meta.level = config.level_string;
|
|
1512
|
+
meta.bitDepth = config.bit_depth;
|
|
1513
|
+
meta.chromaFormat = config.chroma_format;
|
|
1514
|
+
meta.sarRatio = config.sar_ratio;
|
|
1515
|
+
meta.frameRate = config.frame_rate;
|
|
1516
|
+
|
|
1517
|
+
if (config.frame_rate.fixed === false ||
|
|
1518
|
+
config.frame_rate.fps_num === 0 ||
|
|
1519
|
+
config.frame_rate.fps_den === 0) {
|
|
1520
|
+
meta.frameRate = this._referenceFrameRate;
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
let fps_den = meta.frameRate.fps_den;
|
|
1524
|
+
let fps_num = meta.frameRate.fps_num;
|
|
1525
|
+
meta.refSampleDuration = meta.timescale * (fps_den / fps_num);
|
|
1526
|
+
meta.codec = config.codec_mimetype;
|
|
1527
|
+
|
|
1528
|
+
let mi = this._mediaInfo;
|
|
1529
|
+
mi.width = meta.codecWidth;
|
|
1530
|
+
mi.height = meta.codecHeight;
|
|
1531
|
+
mi.fps = meta.frameRate.fps;
|
|
1532
|
+
mi.profile = meta.profile;
|
|
1533
|
+
mi.level = meta.level;
|
|
1534
|
+
mi.refFrames = config.ref_frames;
|
|
1535
|
+
mi.chromaFormat = config.chroma_format_string;
|
|
1536
|
+
mi.sarNum = meta.sarRatio.width;
|
|
1537
|
+
mi.sarDen = meta.sarRatio.height;
|
|
1538
|
+
mi.videoCodec = config.codec_mimetype;
|
|
1539
|
+
|
|
1540
|
+
if (mi.hasAudio) {
|
|
1541
|
+
if (mi.audioCodec != null) {
|
|
1542
|
+
mi.mimeType = 'video/x-flv; codecs="' + mi.videoCodec + ',' + mi.audioCodec + '"';
|
|
1543
|
+
}
|
|
1544
|
+
} else {
|
|
1545
|
+
mi.mimeType = 'video/x-flv; codecs="' + mi.videoCodec + '"';
|
|
1546
|
+
}
|
|
1547
|
+
if (mi.isComplete()) {
|
|
1548
|
+
this._onMediaInfo(mi);
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
offset += len;
|
|
1552
|
+
} else {
|
|
1553
|
+
offset += 2 + len;
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
meta.hvcc = new Uint8Array(dataSize);
|
|
1559
|
+
meta.hvcc.set(new Uint8Array(arrayBuffer, dataOffset, dataSize), 0);
|
|
1560
|
+
Log.v(this.TAG, 'Parsed HEVCDecoderConfigurationRecord');
|
|
1561
|
+
|
|
1562
|
+
if (this._isInitialMetadataDispatched()) {
|
|
1563
|
+
// flush parsed frames
|
|
1564
|
+
if (this._dispatch && (this._audioTrack.length || this._videoTrack.length)) {
|
|
1565
|
+
this._onDataAvailable(this._audioTrack, this._videoTrack);
|
|
1566
|
+
}
|
|
1567
|
+
} else {
|
|
1568
|
+
this._videoInitialMetadataDispatched = true;
|
|
1569
|
+
}
|
|
1570
|
+
// notify new metadata
|
|
1571
|
+
this._dispatch = false;
|
|
1572
|
+
this._onTrackMetadata('video', meta);
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
_parseAV1CodecConfigurationRecord(arrayBuffer, dataOffset, dataSize) {
|
|
1576
|
+
if (dataSize < 4) {
|
|
1577
|
+
Log.w(this.TAG, 'Flv: Invalid AV1CodecConfigurationRecord, lack of data!');
|
|
1578
|
+
return;
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
let meta = this._videoMetadata;
|
|
1582
|
+
let track = this._videoTrack;
|
|
1583
|
+
let le = this._littleEndian;
|
|
1584
|
+
let v = new DataView(arrayBuffer, dataOffset, dataSize);
|
|
1585
|
+
|
|
1586
|
+
if (!meta) {
|
|
1587
|
+
if (this._hasVideo === false && this._hasVideoFlagOverrided === false) {
|
|
1588
|
+
this._hasVideo = true;
|
|
1589
|
+
this._mediaInfo.hasVideo = true;
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
meta = this._videoMetadata = {};
|
|
1593
|
+
meta.type = 'video';
|
|
1594
|
+
meta.id = track.id;
|
|
1595
|
+
meta.timescale = this._timescale;
|
|
1596
|
+
meta.duration = this._duration;
|
|
1597
|
+
} else {
|
|
1598
|
+
if (typeof meta.av1c !== 'undefined') {
|
|
1599
|
+
Log.w(this.TAG, 'Found another AV1CodecConfigurationRecord!');
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
let version = v.getUint8(0) & 0x7F;
|
|
1604
|
+
let seq_profile = (v.getUint8(1) & 0xE0) >> 5;
|
|
1605
|
+
let seq_level_idx = (v.getUint8(1) & 0x8F) >> 0;
|
|
1606
|
+
let seq_tier = (v.getUint8(2) & 0x80) >> 7;
|
|
1607
|
+
|
|
1608
|
+
if (version !== 1) {
|
|
1609
|
+
this._onError(DemuxErrors.FORMAT_ERROR, 'Flv: Invalid AV1CodecConfigurationRecord');
|
|
1610
|
+
return;
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
const config = AV1OBUParser.parseOBUs(new Uint8Array(arrayBuffer, dataOffset + 4, dataSize - 4));
|
|
1614
|
+
if (config == null) {
|
|
1615
|
+
this._onError(DemuxErrors.FORMAT_ERROR, 'Flv: Invalid AV1CodecConfigurationRecord');
|
|
1616
|
+
return;
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
meta.profile = config.profile_string;
|
|
1620
|
+
meta.level = config.level_string;
|
|
1621
|
+
meta.bitDepth = config.bit_depth;
|
|
1622
|
+
meta.chromaFormat = config.chroma_format;
|
|
1623
|
+
meta.frameRate = config.frame_rate;
|
|
1624
|
+
if (config.frame_rate.fixed === false ||
|
|
1625
|
+
config.frame_rate.fps_num === 0 ||
|
|
1626
|
+
config.frame_rate.fps_den === 0) {
|
|
1627
|
+
meta.frameRate = this._referenceFrameRate;
|
|
1628
|
+
}
|
|
1629
|
+
let fps_den = meta.frameRate.fps_den;
|
|
1630
|
+
let fps_num = meta.frameRate.fps_num;
|
|
1631
|
+
meta.refSampleDuration = meta.timescale * (fps_den / fps_num);
|
|
1632
|
+
meta.codec = config.codec_mimetype;
|
|
1633
|
+
meta.extra = config;
|
|
1634
|
+
|
|
1635
|
+
let mi = this._mediaInfo;
|
|
1636
|
+
mi.fps = meta.frameRate.fps;
|
|
1637
|
+
mi.profile = meta.profile;
|
|
1638
|
+
mi.level = meta.level;
|
|
1639
|
+
mi.refFrames = config.ref_frames;
|
|
1640
|
+
mi.chromaFormat = config.chroma_format_string;
|
|
1641
|
+
mi.videoCodec = config.codec_mimetype;
|
|
1642
|
+
|
|
1643
|
+
if (mi.hasAudio) {
|
|
1644
|
+
if (mi.audioCodec != null) {
|
|
1645
|
+
mi.mimeType = 'video/x-flv; codecs="' + mi.videoCodec + ',' + mi.audioCodec + '"';
|
|
1646
|
+
}
|
|
1647
|
+
} else {
|
|
1648
|
+
mi.mimeType = 'video/x-flv; codecs="' + mi.videoCodec + '"';
|
|
1649
|
+
}
|
|
1650
|
+
if (mi.isComplete()) {
|
|
1651
|
+
this._onMediaInfo(mi);
|
|
1652
|
+
}
|
|
1653
|
+
meta.av1c = new Uint8Array(dataSize);
|
|
1654
|
+
meta.av1c.set(new Uint8Array(arrayBuffer, dataOffset, dataSize), 0);
|
|
1655
|
+
Log.v(this.TAG, 'Preparing AV1CodecConfigurationRecord');
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
_parseAVCVideoData(arrayBuffer, dataOffset, dataSize, tagTimestamp, tagPosition, frameType, cts) {
|
|
1659
|
+
let le = this._littleEndian;
|
|
1660
|
+
let v = new DataView(arrayBuffer, dataOffset, dataSize);
|
|
1661
|
+
|
|
1662
|
+
let units = [], length = 0;
|
|
1663
|
+
|
|
1664
|
+
let offset = 0;
|
|
1665
|
+
const lengthSize = this._naluLengthSize;
|
|
1666
|
+
let dts = this._timestampBase + tagTimestamp;
|
|
1667
|
+
let keyframe = (frameType === 1); // from FLV Frame Type constants
|
|
1668
|
+
|
|
1669
|
+
while (offset < dataSize) {
|
|
1670
|
+
if (offset + 4 >= dataSize) {
|
|
1671
|
+
Log.w(this.TAG, `Malformed Nalu near timestamp ${dts}, offset = ${offset}, dataSize = ${dataSize}`);
|
|
1672
|
+
break; // data not enough for next Nalu
|
|
1673
|
+
}
|
|
1674
|
+
// Nalu with length-header (AVC1)
|
|
1675
|
+
let naluSize = v.getUint32(offset, !le); // Big-Endian read
|
|
1676
|
+
if (lengthSize === 3) {
|
|
1677
|
+
naluSize >>>= 8;
|
|
1678
|
+
}
|
|
1679
|
+
if (naluSize > dataSize - lengthSize) {
|
|
1680
|
+
Log.w(this.TAG, `Malformed Nalus near timestamp ${dts}, NaluSize > DataSize!`);
|
|
1681
|
+
return;
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1684
|
+
let unitType = v.getUint8(offset + lengthSize) & 0x1F;
|
|
1685
|
+
|
|
1686
|
+
if (unitType === 5) { // IDR
|
|
1687
|
+
keyframe = true;
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1690
|
+
let data = new Uint8Array(arrayBuffer, dataOffset + offset, lengthSize + naluSize);
|
|
1691
|
+
let unit = {type: unitType, data: data};
|
|
1692
|
+
units.push(unit);
|
|
1693
|
+
length += data.byteLength;
|
|
1694
|
+
|
|
1695
|
+
if (unitType === 6) { // SEI
|
|
1696
|
+
this._parseSEIPayload(data.subarray(lengthSize), dts + cts, 'h264');
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
offset += lengthSize + naluSize;
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1702
|
+
if (units.length) {
|
|
1703
|
+
let track = this._videoTrack;
|
|
1704
|
+
let avcSample = {
|
|
1705
|
+
units: units,
|
|
1706
|
+
length: length,
|
|
1707
|
+
isKeyframe: keyframe,
|
|
1708
|
+
dts: dts,
|
|
1709
|
+
cts: cts,
|
|
1710
|
+
pts: (dts + cts)
|
|
1711
|
+
};
|
|
1712
|
+
if (keyframe) {
|
|
1713
|
+
avcSample.fileposition = tagPosition;
|
|
1714
|
+
}
|
|
1715
|
+
track.samples.push(avcSample);
|
|
1716
|
+
track.length += length;
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1720
|
+
_parseHEVCVideoData(arrayBuffer, dataOffset, dataSize, tagTimestamp, tagPosition, frameType, cts) {
|
|
1721
|
+
let le = this._littleEndian;
|
|
1722
|
+
let v = new DataView(arrayBuffer, dataOffset, dataSize);
|
|
1723
|
+
|
|
1724
|
+
let units = [], length = 0;
|
|
1725
|
+
|
|
1726
|
+
let offset = 0;
|
|
1727
|
+
const lengthSize = this._naluLengthSize;
|
|
1728
|
+
let dts = this._timestampBase + tagTimestamp;
|
|
1729
|
+
let keyframe = (frameType === 1); // from FLV Frame Type constants
|
|
1730
|
+
|
|
1731
|
+
while (offset < dataSize) {
|
|
1732
|
+
if (offset + 4 >= dataSize) {
|
|
1733
|
+
Log.w(this.TAG, `Malformed Nalu near timestamp ${dts}, offset = ${offset}, dataSize = ${dataSize}`);
|
|
1734
|
+
break; // data not enough for next Nalu
|
|
1735
|
+
}
|
|
1736
|
+
// Nalu with length-header (HVC1)
|
|
1737
|
+
let naluSize = v.getUint32(offset, !le); // Big-Endian read
|
|
1738
|
+
if (lengthSize === 3) {
|
|
1739
|
+
naluSize >>>= 8;
|
|
1740
|
+
}
|
|
1741
|
+
if (naluSize > dataSize - lengthSize) {
|
|
1742
|
+
Log.w(this.TAG, `Malformed Nalus near timestamp ${dts}, NaluSize > DataSize!`);
|
|
1743
|
+
return;
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
let unitType = (v.getUint8(offset + lengthSize) >> 1) & 0x3F;
|
|
1747
|
+
|
|
1748
|
+
if (unitType === 19 || unitType === 20 || unitType === 21) { // IRAP
|
|
1749
|
+
keyframe = true;
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
let data = new Uint8Array(arrayBuffer, dataOffset + offset, lengthSize + naluSize);
|
|
1753
|
+
let unit = {type: unitType, data: data};
|
|
1754
|
+
units.push(unit);
|
|
1755
|
+
length += data.byteLength;
|
|
1756
|
+
|
|
1757
|
+
if (unitType === 39 || unitType === 40) { // Prefix / Suffix SEI
|
|
1758
|
+
this._parseSEIPayload(data.subarray(lengthSize), dts + cts, 'h265');
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
offset += lengthSize + naluSize;
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1764
|
+
if (units.length) {
|
|
1765
|
+
let track = this._videoTrack;
|
|
1766
|
+
let hevcSample = {
|
|
1767
|
+
units: units,
|
|
1768
|
+
length: length,
|
|
1769
|
+
isKeyframe: keyframe,
|
|
1770
|
+
dts: dts,
|
|
1771
|
+
cts: cts,
|
|
1772
|
+
pts: (dts + cts)
|
|
1773
|
+
};
|
|
1774
|
+
if (keyframe) {
|
|
1775
|
+
hevcSample.fileposition = tagPosition;
|
|
1776
|
+
}
|
|
1777
|
+
track.samples.push(hevcSample);
|
|
1778
|
+
track.length += length;
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1782
|
+
_parseAV1VideoData(arrayBuffer, dataOffset, dataSize, tagTimestamp, tagPosition, frameType, cts) {
|
|
1783
|
+
let le = this._littleEndian;
|
|
1784
|
+
let v = new DataView(arrayBuffer, dataOffset, dataSize);
|
|
1785
|
+
|
|
1786
|
+
let units = [], length = 0;
|
|
1787
|
+
|
|
1788
|
+
let offset = 0;
|
|
1789
|
+
let dts = this._timestampBase + tagTimestamp;
|
|
1790
|
+
let keyframe = (frameType === 1); // from FLV Frame Type constants
|
|
1791
|
+
|
|
1792
|
+
if (keyframe) {
|
|
1793
|
+
let meta = this._videoMetadata;
|
|
1794
|
+
|
|
1795
|
+
const config = AV1OBUParser.parseOBUs(new Uint8Array(arrayBuffer, dataOffset, dataSize), meta.extra);
|
|
1796
|
+
if (config == null) {
|
|
1797
|
+
this._onError(DemuxErrors.FORMAT_ERROR, 'Flv: Invalid AV1 VideoData');
|
|
1798
|
+
return;
|
|
1799
|
+
}
|
|
1800
|
+
console.log(config);
|
|
1801
|
+
meta.codecWidth = config.codec_size.width;
|
|
1802
|
+
meta.codecHeight = config.codec_size.height;
|
|
1803
|
+
meta.presentWidth = config.present_size.width;
|
|
1804
|
+
meta.presentHeight = config.present_size.height;
|
|
1805
|
+
meta.sarRatio = config.sar_ratio;
|
|
1806
|
+
|
|
1807
|
+
let mi = this._mediaInfo;
|
|
1808
|
+
mi.width = meta.codecWidth;
|
|
1809
|
+
mi.height = meta.codecHeight;
|
|
1810
|
+
mi.sarNum = meta.sarRatio.width;
|
|
1811
|
+
mi.sarDen = meta.sarRatio.height;
|
|
1812
|
+
|
|
1813
|
+
Log.v(this.TAG, 'Parsed AV1DecoderConfigurationRecord');
|
|
1814
|
+
|
|
1815
|
+
if (this._isInitialMetadataDispatched()) {
|
|
1816
|
+
// flush parsed frames
|
|
1817
|
+
if (this._dispatch && (this._audioTrack.length || this._videoTrack.length)) {
|
|
1818
|
+
this._onDataAvailable(this._audioTrack, this._videoTrack);
|
|
1819
|
+
}
|
|
1820
|
+
} else {
|
|
1821
|
+
this._videoInitialMetadataDispatched = true;
|
|
1822
|
+
}
|
|
1823
|
+
// notify new metadata
|
|
1824
|
+
this._dispatch = false;
|
|
1825
|
+
this._onTrackMetadata('video', meta);
|
|
1826
|
+
}
|
|
1827
|
+
|
|
1828
|
+
/* FIXME: NEEDS Inspect Per OBUs */
|
|
1829
|
+
length = dataSize;
|
|
1830
|
+
units.push({
|
|
1831
|
+
unitType: 0,
|
|
1832
|
+
data: new Uint8Array(arrayBuffer, dataOffset + offset, dataSize)
|
|
1833
|
+
});
|
|
1834
|
+
|
|
1835
|
+
if (units.length) {
|
|
1836
|
+
let track = this._videoTrack;
|
|
1837
|
+
let av1Sample = {
|
|
1838
|
+
units: units,
|
|
1839
|
+
length: length,
|
|
1840
|
+
isKeyframe: keyframe,
|
|
1841
|
+
dts: dts,
|
|
1842
|
+
cts: cts,
|
|
1843
|
+
pts: (dts + cts)
|
|
1844
|
+
};
|
|
1845
|
+
if (keyframe) {
|
|
1846
|
+
av1Sample.fileposition = tagPosition;
|
|
1847
|
+
}
|
|
1848
|
+
track.samples.push(av1Sample);
|
|
1849
|
+
track.length += length;
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1854
|
+
export default FLVDemuxer;
|