jmuxer 2.0.7 → 2.1.0
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/README.md +24 -33
- package/dist/jmuxer.min.js +3416 -1
- package/package.json +11 -6
- package/src/controller/remux.js +63 -46
- package/src/jmuxer.js +39 -144
- package/src/parsers/aac.js +11 -38
- package/src/parsers/h264.js +101 -96
- package/src/parsers/h265.js +303 -0
- package/src/remuxer/aac.js +63 -7
- package/src/remuxer/base.js +2 -1
- package/src/remuxer/h264.js +163 -7
- package/src/remuxer/h265.js +288 -0
- package/src/util/debug.js +3 -3
- package/src/util/event.js +2 -2
- package/src/util/mp4-generator.js +124 -9
- package/loader-thumb.jpg +0 -0
- package/packet-format.png +0 -0
- package/src/util/nalu.js +0 -72
package/src/remuxer/h264.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import * as debug from '../util/debug';
|
|
2
|
-
import { H264Parser } from '../parsers/h264.js';
|
|
2
|
+
import { H264Parser, NALU264 } from '../parsers/h264.js';
|
|
3
3
|
import { BaseRemuxer } from './base.js';
|
|
4
|
+
import { appendByteArray } from '../util/utils.js';
|
|
4
5
|
|
|
5
6
|
export class H264Remuxer extends BaseRemuxer {
|
|
6
7
|
|
|
7
|
-
constructor(timescale) {
|
|
8
|
-
super();
|
|
8
|
+
constructor(timescale, duration, frameDuration) {
|
|
9
|
+
super('H264Remuxer');
|
|
10
|
+
this.frameDuration = frameDuration;
|
|
9
11
|
this.readyToDecode = false;
|
|
10
12
|
this.nextDts = 0;
|
|
11
13
|
this.dts = 0;
|
|
@@ -20,11 +22,13 @@ export class H264Remuxer extends BaseRemuxer {
|
|
|
20
22
|
width: 0,
|
|
21
23
|
height: 0,
|
|
22
24
|
timescale: timescale,
|
|
23
|
-
duration:
|
|
25
|
+
duration: duration,
|
|
24
26
|
samples: [],
|
|
25
27
|
};
|
|
26
28
|
this.samples = [];
|
|
27
|
-
this.
|
|
29
|
+
this.remainingData = new Uint8Array();
|
|
30
|
+
this.kfCounter = 0;
|
|
31
|
+
this.pendingUnits = {};
|
|
28
32
|
}
|
|
29
33
|
|
|
30
34
|
resetTrack() {
|
|
@@ -33,6 +37,99 @@ export class H264Remuxer extends BaseRemuxer {
|
|
|
33
37
|
this.mp4track.pps = '';
|
|
34
38
|
this.nextDts = 0;
|
|
35
39
|
this.dts = 0;
|
|
40
|
+
this.remainingData = new Uint8Array();
|
|
41
|
+
this.kfCounter = 0;
|
|
42
|
+
this.pendingUnits = {};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
feed(data, duration, compositionTimeOffset) {
|
|
46
|
+
let slices = [];
|
|
47
|
+
let left;
|
|
48
|
+
data = appendByteArray(this.remainingData, data);
|
|
49
|
+
[slices, left] = H264Parser.extractNALu(data);
|
|
50
|
+
this.remainingData = left || new Uint8Array();
|
|
51
|
+
|
|
52
|
+
if (slices.length > 0) {
|
|
53
|
+
this.remux(this.getVideoFrames(slices, duration, compositionTimeOffset));
|
|
54
|
+
return true;
|
|
55
|
+
} else {
|
|
56
|
+
debug.error('Failed to extract any NAL units from video data:', left);
|
|
57
|
+
this.dispatch('outOfData');
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
getVideoFrames(nalus, duration, compositionTimeOffset) {
|
|
63
|
+
let units = [],
|
|
64
|
+
frames = [],
|
|
65
|
+
fd = 0, // frame duration
|
|
66
|
+
tt = 0, // time ticks (remainder adjustment counter)
|
|
67
|
+
keyFrame = false,
|
|
68
|
+
vcl = false; // Video Coding Layer data (i.e., a "real" frame)
|
|
69
|
+
if (this.pendingUnits.units) {
|
|
70
|
+
units = this.pendingUnits.units;
|
|
71
|
+
vcl = this.pendingUnits.vcl;
|
|
72
|
+
keyFrame = this.pendingUnits.keyFrame;
|
|
73
|
+
this.pendingUnits = {};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
for (let nalu of nalus) {
|
|
77
|
+
let unit = new NALU264(nalu);
|
|
78
|
+
|
|
79
|
+
// frame boundary detection
|
|
80
|
+
if (units.length && vcl && (unit.isFirstSlice || !unit.isVCL)) {
|
|
81
|
+
frames.push({
|
|
82
|
+
units,
|
|
83
|
+
keyFrame
|
|
84
|
+
});
|
|
85
|
+
units = [];
|
|
86
|
+
keyFrame = false;
|
|
87
|
+
vcl = false;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
units.push(unit);
|
|
91
|
+
keyFrame = keyFrame || unit.isKeyframe;
|
|
92
|
+
vcl = vcl || unit.isVCL;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (units.length) {
|
|
96
|
+
// lets keep indecisive nalus as pending in case of fixed fps
|
|
97
|
+
if (!duration) {
|
|
98
|
+
this.pendingUnits = {
|
|
99
|
+
units,
|
|
100
|
+
keyFrame,
|
|
101
|
+
vcl
|
|
102
|
+
};
|
|
103
|
+
} else if (vcl) {
|
|
104
|
+
frames.push({
|
|
105
|
+
units,
|
|
106
|
+
keyFrame
|
|
107
|
+
});
|
|
108
|
+
} else {
|
|
109
|
+
let last = frames.length - 1;
|
|
110
|
+
if (last >= 0) {
|
|
111
|
+
frames[last].units = frames[last].units.concat(units);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
fd = duration ? duration / frames.length | 0 : this.frameDuration;
|
|
117
|
+
tt = duration ? (duration - (fd * frames.length)) : 0;
|
|
118
|
+
|
|
119
|
+
frames.map((frame) => {
|
|
120
|
+
frame.duration = fd;
|
|
121
|
+
frame.compositionTimeOffset = compositionTimeOffset;
|
|
122
|
+
if (tt > 0) {
|
|
123
|
+
frame.duration++;
|
|
124
|
+
tt--;
|
|
125
|
+
}
|
|
126
|
+
this.kfCounter++;
|
|
127
|
+
if (frame.keyFrame) {
|
|
128
|
+
this.dispatch('keyframePosition', (this.kfCounter * fd) / 1000);
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
debug.log(`jmuxer: No. of H264 frames of the last chunk: ${frames.length}`);
|
|
132
|
+
return frames;
|
|
36
133
|
}
|
|
37
134
|
|
|
38
135
|
remux(frames) {
|
|
@@ -40,7 +137,7 @@ export class H264Remuxer extends BaseRemuxer {
|
|
|
40
137
|
let units = [];
|
|
41
138
|
let size = 0;
|
|
42
139
|
for (let unit of frame.units) {
|
|
43
|
-
if (this.
|
|
140
|
+
if (this.parseNAL(unit)) {
|
|
44
141
|
units.push(unit);
|
|
45
142
|
size += unit.getSize();
|
|
46
143
|
}
|
|
@@ -67,7 +164,6 @@ export class H264Remuxer extends BaseRemuxer {
|
|
|
67
164
|
let samples = this.mp4track.samples;
|
|
68
165
|
let mp4Sample,
|
|
69
166
|
duration;
|
|
70
|
-
|
|
71
167
|
this.dts = this.nextDts;
|
|
72
168
|
while (this.samples.length) {
|
|
73
169
|
let sample = this.samples.shift(),
|
|
@@ -105,4 +201,64 @@ export class H264Remuxer extends BaseRemuxer {
|
|
|
105
201
|
|
|
106
202
|
return new Uint8Array(payload.buffer, 0, this.mp4track.len);
|
|
107
203
|
}
|
|
204
|
+
|
|
205
|
+
parseSPS(sps) {
|
|
206
|
+
var config = H264Parser.readSPS(new Uint8Array(sps));
|
|
207
|
+
|
|
208
|
+
this.mp4track.fps = config.fps || this.mp4track.fps;
|
|
209
|
+
this.mp4track.width = config.width;
|
|
210
|
+
this.mp4track.height = config.height;
|
|
211
|
+
this.mp4track.sps = [new Uint8Array(sps)];
|
|
212
|
+
this.mp4track.codec = 'avc1.';
|
|
213
|
+
|
|
214
|
+
let codecarray = new DataView(sps.buffer, sps.byteOffset + 1, 4);
|
|
215
|
+
for (let i = 0; i < 3; ++i) {
|
|
216
|
+
var h = codecarray.getUint8(i).toString(16);
|
|
217
|
+
if (h.length < 2) {
|
|
218
|
+
h = '0' + h;
|
|
219
|
+
}
|
|
220
|
+
this.mp4track.codec += h;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
parsePPS(pps) {
|
|
225
|
+
this.mp4track.pps = [new Uint8Array(pps)];
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
parseNAL(unit) {
|
|
229
|
+
if (!unit) return false;
|
|
230
|
+
|
|
231
|
+
if (unit.isVCL) {
|
|
232
|
+
return true;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
let push = false;
|
|
236
|
+
switch (unit.type()) {
|
|
237
|
+
case NALU264.PPS:
|
|
238
|
+
if (!this.mp4track.pps) {
|
|
239
|
+
this.parsePPS(unit.getPayload());
|
|
240
|
+
}
|
|
241
|
+
push = true;
|
|
242
|
+
break;
|
|
243
|
+
case NALU264.SPS:
|
|
244
|
+
if (!this.mp4track.sps) {
|
|
245
|
+
this.parseSPS(unit.getPayload());
|
|
246
|
+
}
|
|
247
|
+
push = true;
|
|
248
|
+
break;
|
|
249
|
+
case NALU264.AUD:
|
|
250
|
+
debug.log('AUD - ignoing');
|
|
251
|
+
break;
|
|
252
|
+
case NALU264.SEI:
|
|
253
|
+
debug.log('SEI - ignoing');
|
|
254
|
+
break;
|
|
255
|
+
default:
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (!this.readyToDecode && this.mp4track.pps && this.mp4track.sps) {
|
|
259
|
+
this.readyToDecode = true;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return push;
|
|
263
|
+
}
|
|
108
264
|
}
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import * as debug from '../util/debug';
|
|
2
|
+
import { H265Parser, NALU265 } from '../parsers/h265.js';
|
|
3
|
+
import { BaseRemuxer } from './base.js';
|
|
4
|
+
import { appendByteArray } from '../util/utils.js';
|
|
5
|
+
|
|
6
|
+
export class H265Remuxer extends BaseRemuxer {
|
|
7
|
+
|
|
8
|
+
constructor(timescale, duration, frameDuration) {
|
|
9
|
+
super('H264Remuxer');
|
|
10
|
+
this.frameDuration = frameDuration;
|
|
11
|
+
this.readyToDecode = false;
|
|
12
|
+
this.nextDts = 0;
|
|
13
|
+
this.dts = 0;
|
|
14
|
+
this.mp4track = {
|
|
15
|
+
id: BaseRemuxer.getTrackID(),
|
|
16
|
+
type: 'video',
|
|
17
|
+
len: 0,
|
|
18
|
+
fragmented: true,
|
|
19
|
+
vps: '',
|
|
20
|
+
sps: '',
|
|
21
|
+
pps: '',
|
|
22
|
+
hvcC: {},
|
|
23
|
+
fps: 30,
|
|
24
|
+
width: 0,
|
|
25
|
+
height: 0,
|
|
26
|
+
timescale: timescale,
|
|
27
|
+
duration: duration,
|
|
28
|
+
samples: [],
|
|
29
|
+
};
|
|
30
|
+
this.samples = [];
|
|
31
|
+
this.remainingData = new Uint8Array();
|
|
32
|
+
this.kfCounter = 0;
|
|
33
|
+
this.pendingUnits = {};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
resetTrack() {
|
|
37
|
+
this.readyToDecode = false;
|
|
38
|
+
this.mp4track.vps = '';
|
|
39
|
+
this.mp4track.sps = '';
|
|
40
|
+
this.mp4track.pps = '';
|
|
41
|
+
this.mp4track.hvcC = {};
|
|
42
|
+
this.nextDts = 0;
|
|
43
|
+
this.dts = 0;
|
|
44
|
+
this.remainingData = new Uint8Array();
|
|
45
|
+
this.kfCounter = 0;
|
|
46
|
+
this.pendingUnits = {};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
feed(data, duration, compositionTimeOffset) {
|
|
50
|
+
let slices = [];
|
|
51
|
+
let left;
|
|
52
|
+
data = appendByteArray(this.remainingData, data);
|
|
53
|
+
[slices, left] = H265Parser.extractNALu(data);
|
|
54
|
+
this.remainingData = left || new Uint8Array();
|
|
55
|
+
|
|
56
|
+
if (slices.length > 0) {
|
|
57
|
+
this.remux(this.getVideoFrames(slices, duration, compositionTimeOffset));
|
|
58
|
+
return true;
|
|
59
|
+
} else {
|
|
60
|
+
debug.error('Failed to extract any NAL units from video data:', left);
|
|
61
|
+
this.dispatch('outOfData');
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
getVideoFrames(nalus, duration, compositionTimeOffset) {
|
|
67
|
+
let units = [],
|
|
68
|
+
frames = [],
|
|
69
|
+
fd = 0,
|
|
70
|
+
tt = 0,
|
|
71
|
+
keyFrame = false,
|
|
72
|
+
vcl = false;
|
|
73
|
+
|
|
74
|
+
if (this.pendingUnits.units) {
|
|
75
|
+
units = this.pendingUnits.units;
|
|
76
|
+
vcl = this.pendingUnits.vcl;
|
|
77
|
+
keyFrame = this.pendingUnits.keyFrame;
|
|
78
|
+
this.pendingUnits = {};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
for (let nalu of nalus) {
|
|
82
|
+
let unit = new NALU265(nalu);
|
|
83
|
+
|
|
84
|
+
// frame boundary detection
|
|
85
|
+
if (units.length && vcl && (unit.isFirstSlice || !unit.isVCL)) {
|
|
86
|
+
frames.push({
|
|
87
|
+
units,
|
|
88
|
+
keyFrame
|
|
89
|
+
});
|
|
90
|
+
units = [];
|
|
91
|
+
keyFrame = false;
|
|
92
|
+
vcl = false;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
units.push(unit);
|
|
96
|
+
keyFrame = keyFrame || unit.isKeyframe;
|
|
97
|
+
vcl = vcl || unit.isVCL;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (units.length) {
|
|
101
|
+
if (!duration) {
|
|
102
|
+
this.pendingUnits = {
|
|
103
|
+
units,
|
|
104
|
+
keyFrame,
|
|
105
|
+
vcl
|
|
106
|
+
};
|
|
107
|
+
} else if (vcl) {
|
|
108
|
+
frames.push({
|
|
109
|
+
units,
|
|
110
|
+
keyFrame
|
|
111
|
+
});
|
|
112
|
+
} else {
|
|
113
|
+
let last = frames.length - 1;
|
|
114
|
+
if (last >= 0) {
|
|
115
|
+
frames[last].units = frames[last].units.concat(units);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
fd = duration ? (duration / frames.length) | 0 : this.frameDuration;
|
|
121
|
+
tt = duration ? duration - fd * frames.length : 0;
|
|
122
|
+
|
|
123
|
+
frames.map((frame) => {
|
|
124
|
+
frame.duration = fd;
|
|
125
|
+
frame.compositionTimeOffset = compositionTimeOffset;
|
|
126
|
+
if (tt > 0) {
|
|
127
|
+
frame.duration++;
|
|
128
|
+
tt--;
|
|
129
|
+
}
|
|
130
|
+
this.kfCounter++;
|
|
131
|
+
if (frame.keyFrame) {
|
|
132
|
+
this.dispatch('keyframePosition', (this.kfCounter * fd) / 1000);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
debug.log(`jmuxer: No. of H265 frames of the last chunk: ${frames.length}`);
|
|
137
|
+
return frames;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
remux(frames) {
|
|
141
|
+
for (let frame of frames) {
|
|
142
|
+
let units = [];
|
|
143
|
+
let size = 0;
|
|
144
|
+
for (let unit of frame.units) {
|
|
145
|
+
if (this.parseNAL(unit)) {
|
|
146
|
+
units.push(unit);
|
|
147
|
+
size += unit.getSize();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
if (units.length > 0 && this.readyToDecode) {
|
|
151
|
+
this.mp4track.len += size;
|
|
152
|
+
this.samples.push({
|
|
153
|
+
units: units,
|
|
154
|
+
size: size,
|
|
155
|
+
keyFrame: frame.keyFrame,
|
|
156
|
+
duration: frame.duration,
|
|
157
|
+
compositionTimeOffset: frame.compositionTimeOffset
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
getPayload() {
|
|
164
|
+
if (!this.isReady()) {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
let payload = new Uint8Array(this.mp4track.len);
|
|
168
|
+
let offset = 0;
|
|
169
|
+
let samples = this.mp4track.samples;
|
|
170
|
+
let mp4Sample,
|
|
171
|
+
duration;
|
|
172
|
+
this.dts = this.nextDts;
|
|
173
|
+
while (this.samples.length) {
|
|
174
|
+
let sample = this.samples.shift(),
|
|
175
|
+
units = sample.units;
|
|
176
|
+
|
|
177
|
+
duration = sample.duration;
|
|
178
|
+
if (duration <= 0) {
|
|
179
|
+
debug.log(`remuxer: invalid sample duration at DTS: ${this.nextDts} :${duration}`);
|
|
180
|
+
this.mp4track.len -= sample.size;
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
this.nextDts += duration;
|
|
184
|
+
mp4Sample = {
|
|
185
|
+
size: sample.size,
|
|
186
|
+
duration: duration,
|
|
187
|
+
cts: sample.compositionTimeOffset || 0,
|
|
188
|
+
flags: {
|
|
189
|
+
isLeading: 0,
|
|
190
|
+
isDependedOn: 0,
|
|
191
|
+
hasRedundancy: 0,
|
|
192
|
+
degradPrio: 0,
|
|
193
|
+
isNonSync: sample.keyFrame ? 0 : 1,
|
|
194
|
+
dependsOn: sample.keyFrame ? 2 : 1,
|
|
195
|
+
},
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
for (const unit of units) {
|
|
199
|
+
payload.set(unit.getData(), offset);
|
|
200
|
+
offset += unit.getSize();
|
|
201
|
+
}
|
|
202
|
+
samples.push(mp4Sample);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (!samples.length) return null;
|
|
206
|
+
|
|
207
|
+
return new Uint8Array(payload.buffer, 0, this.mp4track.len);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
parseSPS(sps) {
|
|
211
|
+
this.mp4track.sps = [new Uint8Array(sps)];
|
|
212
|
+
|
|
213
|
+
sps = H265Parser.removeEmulationPreventionBytes(sps);
|
|
214
|
+
const config = H265Parser.readSPS(new Uint8Array(sps));
|
|
215
|
+
|
|
216
|
+
this.mp4track.fps = config.fps || this.mp4track.fps;
|
|
217
|
+
this.mp4track.width = config.width;
|
|
218
|
+
this.mp4track.height = config.height;
|
|
219
|
+
|
|
220
|
+
this.mp4track.codec = `hvc1.${config.profile_idc}.${config.profile_compatibility_flags.toString(16)}`
|
|
221
|
+
+ `.L${config.level_idc}${config.tier_flag ? 'H' : 'L'}`
|
|
222
|
+
+ `.${config.constraint_indicator_flags.map(b => b.toString(16)).join('.').toUpperCase()}`;
|
|
223
|
+
|
|
224
|
+
this.mp4track.hvcC = {
|
|
225
|
+
profile_space: config.profile_space,
|
|
226
|
+
tier_flag: config.tier_flag,
|
|
227
|
+
profile_idc: config.profile_idc,
|
|
228
|
+
profile_compatibility_flags: config.profile_compatibility_flags,
|
|
229
|
+
constraint_indicator_flags: config.constraint_indicator_flags,
|
|
230
|
+
level_idc: config.level_idc,
|
|
231
|
+
chroma_format_idc: config.chroma_format_idc
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
parsePPS(pps) {
|
|
236
|
+
this.mp4track.pps = [pps];
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
parseVPS(vps) {
|
|
240
|
+
this.mp4track.vps = [vps];
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
parseNAL(unit) {
|
|
244
|
+
if (!unit) return false;
|
|
245
|
+
|
|
246
|
+
if (unit.isVCL) {
|
|
247
|
+
return true;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
let push = false;
|
|
251
|
+
switch (unit.type()) {
|
|
252
|
+
case NALU265.VPS:
|
|
253
|
+
if (!this.mp4track.vps) {
|
|
254
|
+
this.parseVPS(unit.getPayload());
|
|
255
|
+
}
|
|
256
|
+
push = true;
|
|
257
|
+
break;
|
|
258
|
+
|
|
259
|
+
case NALU265.SPS:
|
|
260
|
+
if (!this.mp4track.sps) {
|
|
261
|
+
this.parseSPS(unit.getPayload());
|
|
262
|
+
}
|
|
263
|
+
push = true;
|
|
264
|
+
break;
|
|
265
|
+
|
|
266
|
+
case NALU265.PPS:
|
|
267
|
+
if (!this.mp4track.pps) {
|
|
268
|
+
this.parsePPS(unit.getPayload());
|
|
269
|
+
}
|
|
270
|
+
push = true;
|
|
271
|
+
break;
|
|
272
|
+
case NALU265.AUD:
|
|
273
|
+
debug.log('AUD - ignoing');
|
|
274
|
+
break;
|
|
275
|
+
case NALU265.SEI:
|
|
276
|
+
case NALU265.SEI2:
|
|
277
|
+
debug.log('SEI - ignoing');
|
|
278
|
+
break;
|
|
279
|
+
default:
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (!this.readyToDecode && this.mp4track.vps && this.mp4track.sps && this.mp4track.pps) {
|
|
283
|
+
this.readyToDecode = true;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return push;
|
|
287
|
+
}
|
|
288
|
+
}
|
package/src/util/debug.js
CHANGED
package/src/util/event.js
CHANGED
|
@@ -27,10 +27,10 @@ export default class Event {
|
|
|
27
27
|
this.listener = {};
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
dispatch(event, data) {
|
|
30
|
+
dispatch(event, ...data) {
|
|
31
31
|
if (this.listener[event]) {
|
|
32
32
|
this.listener[event].map((each) => {
|
|
33
|
-
each.apply(null,
|
|
33
|
+
each.apply(null, data);
|
|
34
34
|
});
|
|
35
35
|
return true;
|
|
36
36
|
}
|
|
@@ -14,6 +14,8 @@ export class MP4 {
|
|
|
14
14
|
esds: [],
|
|
15
15
|
ftyp: [],
|
|
16
16
|
hdlr: [],
|
|
17
|
+
hev1: [],
|
|
18
|
+
hvcC: [],
|
|
17
19
|
mdat: [],
|
|
18
20
|
mdhd: [],
|
|
19
21
|
mdia: [],
|
|
@@ -179,9 +181,9 @@ export class MP4 {
|
|
|
179
181
|
(timescale >> 16) & 0xFF,
|
|
180
182
|
(timescale >> 8) & 0xFF,
|
|
181
183
|
timescale & 0xFF, // timescale
|
|
182
|
-
(duration
|
|
183
|
-
(duration
|
|
184
|
-
(duration
|
|
184
|
+
(duration >>> 24) & 0xFF,
|
|
185
|
+
(duration >>> 16) & 0xFF,
|
|
186
|
+
(duration >>> 8) & 0xFF,
|
|
185
187
|
duration & 0xFF, // duration
|
|
186
188
|
0x55, 0xc4, // 'und' language (undetermined)
|
|
187
189
|
0x00, 0x00,
|
|
@@ -251,9 +253,9 @@ export class MP4 {
|
|
|
251
253
|
(timescale >> 16) & 0xFF,
|
|
252
254
|
(timescale >> 8) & 0xFF,
|
|
253
255
|
timescale & 0xFF, // timescale
|
|
254
|
-
(duration
|
|
255
|
-
(duration
|
|
256
|
-
(duration
|
|
256
|
+
(duration >>> 24) & 0xFF,
|
|
257
|
+
(duration >>> 16) & 0xFF,
|
|
258
|
+
(duration >>> 8) & 0xFF,
|
|
257
259
|
duration & 0xFF, // duration
|
|
258
260
|
0x00, 0x01, 0x00, 0x00, // 1.0 rate
|
|
259
261
|
0x01, 0x00, // 1.0 volume
|
|
@@ -375,6 +377,116 @@ export class MP4 {
|
|
|
375
377
|
0x00, 0x2d, 0xc6, 0xc0])) // avgBitrate
|
|
376
378
|
);
|
|
377
379
|
}
|
|
380
|
+
|
|
381
|
+
static hev1(track) {
|
|
382
|
+
let vps = [],
|
|
383
|
+
sps = [],
|
|
384
|
+
pps = [],
|
|
385
|
+
data,
|
|
386
|
+
len;
|
|
387
|
+
|
|
388
|
+
// assemble the VPSs
|
|
389
|
+
for (let i = 0; i < (track.vps?.length || 0); i++) {
|
|
390
|
+
data = track.vps[i];
|
|
391
|
+
len = data.byteLength;
|
|
392
|
+
vps.push((len >>> 8) & 0xFF, len & 0xFF);
|
|
393
|
+
vps = vps.concat(Array.prototype.slice.call(data));
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// assemble the SPSs
|
|
397
|
+
for (let i = 0; i < (track.sps?.length || 0); i++) {
|
|
398
|
+
data = track.sps[i];
|
|
399
|
+
len = data.byteLength;
|
|
400
|
+
sps.push((len >>> 8) & 0xFF, len & 0xFF);
|
|
401
|
+
sps = sps.concat(Array.prototype.slice.call(data));
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// assemble the PPSs
|
|
405
|
+
for (let i = 0; i < (track.pps?.length || 0); i++) {
|
|
406
|
+
data = track.pps[i];
|
|
407
|
+
len = data.byteLength;
|
|
408
|
+
pps.push((len >>> 8) & 0xFF, len & 0xFF);
|
|
409
|
+
pps = pps.concat(Array.prototype.slice.call(data));
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
let {
|
|
413
|
+
profile_space,
|
|
414
|
+
tier_flag,
|
|
415
|
+
profile_idc,
|
|
416
|
+
profile_compatibility_flags,
|
|
417
|
+
constraint_indicator_flags,
|
|
418
|
+
level_idc,
|
|
419
|
+
chroma_format_idc
|
|
420
|
+
} = track.hvcC;
|
|
421
|
+
|
|
422
|
+
const hvcc = MP4.box(MP4.types.hvcC, new Uint8Array([
|
|
423
|
+
0x01, // configurationVersion
|
|
424
|
+
(profile_space << 6) | (tier_flag << 5) | profile_idc,
|
|
425
|
+
(profile_compatibility_flags >> 24) & 0xFF,
|
|
426
|
+
(profile_compatibility_flags >> 16) & 0xFF,
|
|
427
|
+
(profile_compatibility_flags >> 8) & 0xFF,
|
|
428
|
+
profile_compatibility_flags & 0xFF,
|
|
429
|
+
...constraint_indicator_flags,
|
|
430
|
+
level_idc,
|
|
431
|
+
0xF0, 0x00, // min_spatial_segmentation_idc = 0
|
|
432
|
+
0xFC | 0, // parallelismType = 0
|
|
433
|
+
0xFC | chroma_format_idc, // chromaFormat (from SPS)
|
|
434
|
+
0xF8 | 0, // bitDepthLumaMinus8 = 0 (8-bit)
|
|
435
|
+
0xF8 | 0, // bitDepthChromaMinus8 = 0
|
|
436
|
+
0x00, 0x00, // avgFrameRate = 0
|
|
437
|
+
0x03, // constantFrameRate = 0, numTemporalLayers = 0, lengthSizeMinusOne = 3 (AKA 4)
|
|
438
|
+
0x03, // numOfArrays
|
|
439
|
+
|
|
440
|
+
0x20, // array_completeness + NAL_unit_type (32 = VPS)
|
|
441
|
+
0x00, 0x01, // numNalus
|
|
442
|
+
...vps,
|
|
443
|
+
|
|
444
|
+
0x21, // NAL_unit_type (33 = SPS)
|
|
445
|
+
0x00, 0x01,
|
|
446
|
+
...sps,
|
|
447
|
+
|
|
448
|
+
0x22, // NAL_unit_type (34 = PPS)
|
|
449
|
+
0x00, 0x01,
|
|
450
|
+
...pps
|
|
451
|
+
]));
|
|
452
|
+
|
|
453
|
+
const width = track.width;
|
|
454
|
+
const height = track.height;
|
|
455
|
+
|
|
456
|
+
return MP4.box(MP4.types.hev1, new Uint8Array([
|
|
457
|
+
0x00, 0x00, 0x00, // reserved
|
|
458
|
+
0x00, 0x00, 0x00, // reserved
|
|
459
|
+
0x00, 0x01, // data_reference_index
|
|
460
|
+
0x00, 0x00, // pre_defined
|
|
461
|
+
0x00, 0x00, // reserved
|
|
462
|
+
0x00, 0x00, 0x00, 0x00,
|
|
463
|
+
0x00, 0x00, 0x00, 0x00,
|
|
464
|
+
0x00, 0x00, 0x00, 0x00, // pre_defined
|
|
465
|
+
(width >> 8) & 0xFF, width & 0xff,
|
|
466
|
+
(height >> 8) & 0xFF, height & 0xff,
|
|
467
|
+
0x00, 0x48, 0x00, 0x00, // horizresolution
|
|
468
|
+
0x00, 0x48, 0x00, 0x00, // vertresolution
|
|
469
|
+
0x00, 0x00, 0x00, 0x00, // reserved
|
|
470
|
+
0x00, 0x01, // frame_count
|
|
471
|
+
0x12,
|
|
472
|
+
0x62, 0x69, 0x6E, 0x65, // 'binelpro.ru'
|
|
473
|
+
0x6C, 0x70, 0x72, 0x6F,
|
|
474
|
+
0x2E, 0x72, 0x75, 0x00,
|
|
475
|
+
0x00, 0x00, 0x00, 0x00,
|
|
476
|
+
0x00, 0x00, 0x00, 0x00,
|
|
477
|
+
0x00, 0x00, 0x00, 0x00,
|
|
478
|
+
0x00, 0x00, 0x00, 0x00,
|
|
479
|
+
0x00, 0x00, 0x00, // compressorname padding
|
|
480
|
+
0x00, 0x18, // depth = 24
|
|
481
|
+
0x11, 0x11 // pre_defined = -1
|
|
482
|
+
]),
|
|
483
|
+
hvcc,
|
|
484
|
+
MP4.box(MP4.types.btrt, new Uint8Array([
|
|
485
|
+
0x00, 0x1c, 0x9c, 0x80, // bufferSizeDB
|
|
486
|
+
0x00, 0x2d, 0xc6, 0xc0, // maxBitrate
|
|
487
|
+
0x00, 0x2d, 0xc6, 0xc0 // avgBitrate
|
|
488
|
+
])));
|
|
489
|
+
}
|
|
378
490
|
|
|
379
491
|
static esds(track) {
|
|
380
492
|
var configlen = track.config.byteLength;
|
|
@@ -445,6 +557,9 @@ export class MP4 {
|
|
|
445
557
|
if (track.type === 'audio') {
|
|
446
558
|
return MP4.box(MP4.types.stsd, MP4.STSD, MP4.mp4a(track));
|
|
447
559
|
} else {
|
|
560
|
+
if (track.codec.startsWith('hvc1')) {
|
|
561
|
+
return MP4.box(MP4.types.stsd, MP4.STSD, MP4.hev1(track));
|
|
562
|
+
}
|
|
448
563
|
return MP4.box(MP4.types.stsd, MP4.STSD, MP4.avc1(track));
|
|
449
564
|
}
|
|
450
565
|
}
|
|
@@ -465,9 +580,9 @@ export class MP4 {
|
|
|
465
580
|
(id >> 8) & 0xFF,
|
|
466
581
|
id & 0xFF, // track_ID
|
|
467
582
|
0x00, 0x00, 0x00, 0x00, // reserved
|
|
468
|
-
(duration
|
|
469
|
-
(duration
|
|
470
|
-
(duration
|
|
583
|
+
(duration >>> 24) & 0xFF,
|
|
584
|
+
(duration >>> 16) & 0xFF,
|
|
585
|
+
(duration >>> 8) & 0xFF,
|
|
471
586
|
duration & 0xFF, // duration
|
|
472
587
|
0x00, 0x00, 0x00, 0x00,
|
|
473
588
|
0x00, 0x00, 0x00, 0x00, // reserved
|
package/loader-thumb.jpg
DELETED
|
Binary file
|
package/packet-format.png
DELETED
|
Binary file
|