jmuxer 2.0.7 → 2.1.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.
@@ -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: timescale,
25
+ duration: duration,
24
26
  samples: [],
25
27
  };
26
28
  this.samples = [];
27
- this.h264 = new H264Parser(this);
29
+ this.remainingData = new Uint8Array();
30
+ this.kfCounter = 0;
31
+ this.pendingUnits = {};
28
32
  }
29
33
 
30
34
  resetTrack() {
@@ -33,22 +37,119 @@ 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 = {};
36
43
  }
37
44
 
38
- remux(frames) {
39
- for (let frame of frames) {
40
- let units = [];
41
- let size = 0;
42
- for (let unit of frame.units) {
43
- if (this.h264.parseNAL(unit)) {
44
- units.push(unit);
45
- size += unit.getSize();
45
+ feed(data, duration, compositionTimeOffset, isLastFrameComplete = false) {
46
+ let slices = [];
47
+ let left;
48
+ data = appendByteArray(this.remainingData, data);
49
+ [slices, left] = H264Parser.extractNALu(data);
50
+ if (left) {
51
+ if (isLastFrameComplete) {
52
+ slices.push(left);
53
+ this.remainingData = new Uint8Array();
54
+ } else {
55
+ this.remainingData = left;
56
+ }
57
+ } else {
58
+ this.remainingData = new Uint8Array();
59
+ }
60
+
61
+ if (slices.length > 0) {
62
+ this.remux(this.getVideoFrames(slices, duration, compositionTimeOffset));
63
+ return true;
64
+ } else {
65
+ debug.log('Failed to extract any NAL units from video data:', left);
66
+ this.dispatch('outOfData');
67
+ return false;
68
+ }
69
+ }
70
+
71
+ getVideoFrames(nalus, duration, compositionTimeOffset) {
72
+ let units = [],
73
+ frames = [],
74
+ fd = 0, // frame duration
75
+ tt = 0, // time ticks (remainder adjustment counter)
76
+ keyFrame = false,
77
+ vcl = false; // Video Coding Layer data (i.e., a "real" frame)
78
+ if (this.pendingUnits.units) {
79
+ units = this.pendingUnits.units;
80
+ vcl = this.pendingUnits.vcl;
81
+ keyFrame = this.pendingUnits.keyFrame;
82
+ this.pendingUnits = {};
83
+ }
84
+
85
+ for (let nalu of nalus) {
86
+ let unit = new NALU264(nalu);
87
+
88
+ if (!this.parseNAL(unit)) continue;
89
+
90
+ // frame boundary detection
91
+ if (units.length && vcl && (unit.isFirstSlice || !unit.isVCL)) {
92
+ frames.push({
93
+ units,
94
+ keyFrame
95
+ });
96
+ units = [];
97
+ keyFrame = false;
98
+ vcl = false;
99
+ }
100
+
101
+ units.push(unit);
102
+ keyFrame = keyFrame || unit.isKeyframe;
103
+ vcl = vcl || unit.isVCL;
104
+ }
105
+
106
+ if (units.length) {
107
+ // lets keep indecisive nalus as pending in case of fixed fps
108
+ if (!duration) {
109
+ this.pendingUnits = {
110
+ units,
111
+ keyFrame,
112
+ vcl
113
+ };
114
+ } else if (vcl) {
115
+ frames.push({
116
+ units,
117
+ keyFrame
118
+ });
119
+ } else {
120
+ let last = frames.length - 1;
121
+ if (last >= 0) {
122
+ frames[last].units = frames[last].units.concat(units);
46
123
  }
47
124
  }
48
- if (units.length > 0 && this.readyToDecode) {
125
+ }
126
+
127
+ fd = duration ? duration / frames.length | 0 : this.frameDuration;
128
+ tt = duration ? (duration - (fd * frames.length)) : 0;
129
+
130
+ frames.map((frame) => {
131
+ frame.duration = fd;
132
+ frame.compositionTimeOffset = compositionTimeOffset;
133
+ if (tt > 0) {
134
+ frame.duration++;
135
+ tt--;
136
+ }
137
+ this.kfCounter++;
138
+ if (frame.keyFrame) {
139
+ this.dispatch('keyframePosition', (this.kfCounter * fd) / 1000);
140
+ }
141
+ });
142
+ debug.log(`jmuxer: No. of H264 frames of the last chunk: ${frames.length}`);
143
+ return frames;
144
+ }
145
+
146
+ remux(frames) {
147
+ for (let frame of frames) {
148
+ let size = frame.units.reduce((acc, cur) => acc + cur.getSize(), 0);
149
+ if (frame.units.length > 0 && this.readyToDecode) {
49
150
  this.mp4track.len += size;
50
151
  this.samples.push({
51
- units: units,
152
+ units: frame.units,
52
153
  size: size,
53
154
  keyFrame: frame.keyFrame,
54
155
  duration: frame.duration,
@@ -67,7 +168,6 @@ export class H264Remuxer extends BaseRemuxer {
67
168
  let samples = this.mp4track.samples;
68
169
  let mp4Sample,
69
170
  duration;
70
-
71
171
  this.dts = this.nextDts;
72
172
  while (this.samples.length) {
73
173
  let sample = this.samples.shift(),
@@ -105,4 +205,64 @@ export class H264Remuxer extends BaseRemuxer {
105
205
 
106
206
  return new Uint8Array(payload.buffer, 0, this.mp4track.len);
107
207
  }
208
+
209
+ parseSPS(sps) {
210
+ var config = H264Parser.readSPS(new Uint8Array(sps));
211
+
212
+ this.mp4track.fps = config.fps || this.mp4track.fps;
213
+ this.mp4track.width = config.width;
214
+ this.mp4track.height = config.height;
215
+ this.mp4track.sps = [new Uint8Array(sps)];
216
+ this.mp4track.codec = 'avc1.';
217
+
218
+ let codecarray = new DataView(sps.buffer, sps.byteOffset + 1, 4);
219
+ for (let i = 0; i < 3; ++i) {
220
+ var h = codecarray.getUint8(i).toString(16);
221
+ if (h.length < 2) {
222
+ h = '0' + h;
223
+ }
224
+ this.mp4track.codec += h;
225
+ }
226
+ }
227
+
228
+ parsePPS(pps) {
229
+ this.mp4track.pps = [new Uint8Array(pps)];
230
+ }
231
+
232
+ parseNAL(unit) {
233
+ if (!unit) return false;
234
+
235
+ if (unit.isVCL) {
236
+ return true;
237
+ }
238
+
239
+ let push = false;
240
+ switch (unit.type()) {
241
+ case NALU264.PPS:
242
+ if (!this.mp4track.pps) {
243
+ this.parsePPS(unit.getPayload());
244
+ }
245
+ push = true;
246
+ break;
247
+ case NALU264.SPS:
248
+ if (!this.mp4track.sps) {
249
+ this.parseSPS(unit.getPayload());
250
+ }
251
+ push = true;
252
+ break;
253
+ case NALU264.AUD:
254
+ debug.log('AUD - ignoing');
255
+ break;
256
+ case NALU264.SEI:
257
+ debug.log('SEI - ignoing');
258
+ break;
259
+ default:
260
+ }
261
+
262
+ if (!this.readyToDecode && this.mp4track.pps && this.mp4track.sps) {
263
+ this.readyToDecode = true;
264
+ }
265
+
266
+ return push;
267
+ }
108
268
  }
@@ -0,0 +1,295 @@
1
+ import * as debug from '../util/debug';
2
+ import { H265Parser, NALU265 } from '../parsers/h265.js';
3
+ import { BaseRemuxer } from './base.js';
4
+ import { appendByteArray, reverseBits, removeTrailingDotZero } 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, isLastFrameComplete = false) {
50
+ let slices = [];
51
+ let left;
52
+ data = appendByteArray(this.remainingData, data);
53
+ [slices, left] = H265Parser.extractNALu(data);
54
+ if (left) {
55
+ if (isLastFrameComplete) {
56
+ slices.push(left);
57
+ this.remainingData = new Uint8Array();
58
+ } else {
59
+ this.remainingData = left;
60
+ }
61
+ } else {
62
+ this.remainingData = new Uint8Array();
63
+ }
64
+
65
+ if (slices.length > 0) {
66
+ this.remux(this.getVideoFrames(slices, duration, compositionTimeOffset));
67
+ return true;
68
+ } else {
69
+ debug.log('Failed to extract any NAL units from video data:', left);
70
+ this.dispatch('outOfData');
71
+ return false;
72
+ }
73
+ }
74
+
75
+ getVideoFrames(nalus, duration, compositionTimeOffset) {
76
+ let units = [],
77
+ frames = [],
78
+ fd = 0,
79
+ tt = 0,
80
+ keyFrame = false,
81
+ vcl = false;
82
+
83
+ if (this.pendingUnits.units) {
84
+ units = this.pendingUnits.units;
85
+ vcl = this.pendingUnits.vcl;
86
+ keyFrame = this.pendingUnits.keyFrame;
87
+ this.pendingUnits = {};
88
+ }
89
+
90
+ for (let nalu of nalus) {
91
+ let unit = new NALU265(nalu);
92
+
93
+ if (!this.parseNAL(unit)) continue;
94
+
95
+ // frame boundary detection
96
+ if (units.length && vcl && (unit.isFirstSlice || !unit.isVCL)) {
97
+ frames.push({
98
+ units,
99
+ keyFrame
100
+ });
101
+ units = [];
102
+ keyFrame = false;
103
+ vcl = false;
104
+ }
105
+
106
+ units.push(unit);
107
+ keyFrame = keyFrame || unit.isKeyframe;
108
+ vcl = vcl || unit.isVCL;
109
+ }
110
+
111
+ if (units.length) {
112
+ if (!duration) {
113
+ this.pendingUnits = {
114
+ units,
115
+ keyFrame,
116
+ vcl
117
+ };
118
+ } else if (vcl) {
119
+ frames.push({
120
+ units,
121
+ keyFrame
122
+ });
123
+ } else {
124
+ let last = frames.length - 1;
125
+ if (last >= 0) {
126
+ frames[last].units = frames[last].units.concat(units);
127
+ }
128
+ }
129
+ }
130
+
131
+ fd = duration ? (duration / frames.length) | 0 : this.frameDuration;
132
+ tt = duration ? duration - fd * frames.length : 0;
133
+
134
+ frames.map((frame) => {
135
+ frame.duration = fd;
136
+ frame.compositionTimeOffset = compositionTimeOffset;
137
+ if (tt > 0) {
138
+ frame.duration++;
139
+ tt--;
140
+ }
141
+ this.kfCounter++;
142
+ if (frame.keyFrame) {
143
+ this.dispatch('keyframePosition', (this.kfCounter * fd) / 1000);
144
+ }
145
+ });
146
+
147
+ debug.log(`jmuxer: No. of H265 frames of the last chunk: ${frames.length}`);
148
+ return frames;
149
+ }
150
+
151
+ remux(frames) {
152
+ for (let frame of frames) {
153
+ let size = frame.units.reduce((acc, cur) => acc + cur.getSize(), 0);
154
+ if (frame.units.length > 0 && this.readyToDecode) {
155
+ this.mp4track.len += size;
156
+ this.samples.push({
157
+ units: frame.units,
158
+ size: size,
159
+ keyFrame: frame.keyFrame,
160
+ duration: frame.duration,
161
+ compositionTimeOffset: frame.compositionTimeOffset
162
+ });
163
+ }
164
+ }
165
+ }
166
+
167
+ getPayload() {
168
+ if (!this.isReady()) {
169
+ return null;
170
+ }
171
+ let payload = new Uint8Array(this.mp4track.len);
172
+ let offset = 0;
173
+ let samples = this.mp4track.samples;
174
+ let mp4Sample,
175
+ duration;
176
+ this.dts = this.nextDts;
177
+ while (this.samples.length) {
178
+ let sample = this.samples.shift(),
179
+ units = sample.units;
180
+
181
+ duration = sample.duration;
182
+ if (duration <= 0) {
183
+ debug.log(`remuxer: invalid sample duration at DTS: ${this.nextDts} :${duration}`);
184
+ this.mp4track.len -= sample.size;
185
+ continue;
186
+ }
187
+ this.nextDts += duration;
188
+ mp4Sample = {
189
+ size: sample.size,
190
+ duration: duration,
191
+ cts: sample.compositionTimeOffset || 0,
192
+ flags: {
193
+ isLeading: 0,
194
+ isDependedOn: 0,
195
+ hasRedundancy: 0,
196
+ degradPrio: 0,
197
+ isNonSync: sample.keyFrame ? 0 : 1,
198
+ dependsOn: sample.keyFrame ? 2 : 1,
199
+ },
200
+ };
201
+
202
+ for (const unit of units) {
203
+ payload.set(unit.getData(), offset);
204
+ offset += unit.getSize();
205
+ }
206
+ samples.push(mp4Sample);
207
+ }
208
+
209
+ if (!samples.length) return null;
210
+
211
+ return new Uint8Array(payload.buffer, 0, this.mp4track.len);
212
+ }
213
+
214
+ parseSPS(sps) {
215
+ this.mp4track.sps = [new Uint8Array(sps)];
216
+
217
+ sps = H265Parser.removeEmulationPreventionBytes(sps);
218
+ const config = H265Parser.readSPS(new Uint8Array(sps));
219
+
220
+ this.mp4track.fps = config.fps || this.mp4track.fps;
221
+ this.mp4track.width = config.width;
222
+ this.mp4track.height = config.height;
223
+
224
+ this.mp4track.codec = 'hvc1'
225
+ + '.' + (config.profile_space ? String.fromCharCode(64 + config.profile_space) : '') // Map [0,1,2,3] to ['','A','B','C']
226
+ + config.profile_idc
227
+ + '.' + reverseBits(config.profile_compatibility_flags).toString(16)
228
+ + '.' + (config.tier_flag ? 'H' : 'L') + config.level_idc
229
+ + '.' + removeTrailingDotZero(config.constraint_indicator_flags.map(function (b) { return b.toString(16); }).join('.').toUpperCase());
230
+
231
+ this.mp4track.hvcC = {
232
+ profile_space: config.profile_space,
233
+ tier_flag: config.tier_flag,
234
+ profile_idc: config.profile_idc,
235
+ profile_compatibility_flags: config.profile_compatibility_flags,
236
+ constraint_indicator_flags: config.constraint_indicator_flags,
237
+ level_idc: config.level_idc,
238
+ chroma_format_idc: config.chroma_format_idc
239
+ };
240
+ }
241
+
242
+ parsePPS(pps) {
243
+ this.mp4track.pps = [pps];
244
+ }
245
+
246
+ parseVPS(vps) {
247
+ this.mp4track.vps = [vps];
248
+ }
249
+
250
+ parseNAL(unit) {
251
+ if (!unit) return false;
252
+
253
+ if (unit.isVCL) {
254
+ return true;
255
+ }
256
+
257
+ let push = false;
258
+ switch (unit.type()) {
259
+ case NALU265.VPS:
260
+ if (!this.mp4track.vps) {
261
+ this.parseVPS(unit.getPayload());
262
+ }
263
+ push = true;
264
+ break;
265
+
266
+ case NALU265.SPS:
267
+ if (!this.mp4track.sps) {
268
+ this.parseSPS(unit.getPayload());
269
+ }
270
+ push = true;
271
+ break;
272
+
273
+ case NALU265.PPS:
274
+ if (!this.mp4track.pps) {
275
+ this.parsePPS(unit.getPayload());
276
+ }
277
+ push = true;
278
+ break;
279
+ case NALU265.AUD:
280
+ debug.log('AUD - ignoing');
281
+ break;
282
+ case NALU265.SEI:
283
+ case NALU265.SEI2:
284
+ debug.log('SEI - ignoing');
285
+ break;
286
+ default:
287
+ }
288
+
289
+ if (!this.readyToDecode && this.mp4track.vps && this.mp4track.sps && this.mp4track.pps) {
290
+ this.readyToDecode = true;
291
+ }
292
+
293
+ return push;
294
+ }
295
+ }
package/src/util/debug.js CHANGED
@@ -1,11 +1,9 @@
1
1
  let logger;
2
2
  let errorLogger;
3
3
 
4
- export function setLogger() {
5
- /*eslint-disable */
6
- logger = console.log;
7
- errorLogger = console.error;
8
- /*eslint-enable */
4
+ export function setLogger(log, err) {
5
+ logger = log;
6
+ errorLogger = err;
9
7
  }
10
8
 
11
9
  export function isEnable() {
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, [data]);
33
+ each.apply(null, data);
34
34
  });
35
35
  return true;
36
36
  }