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.
@@ -1,56 +1,42 @@
1
1
  import { ExpGolomb } from '../util/exp-golomb.js';
2
- import { NALU } from '../util/nalu.js';
3
2
  import * as debug from '../util/debug';
4
3
 
4
+ // spec https://www.itu.int/rec/T-REC-H.264/
5
+
5
6
  export class H264Parser {
6
7
 
7
8
  static extractNALu(buffer) {
8
9
  let i = 0,
9
10
  length = buffer.byteLength,
10
- value,
11
- state = 0,
12
11
  result = [],
13
- left,
14
- lastIndex = 0;
12
+ lastIndex = 0,
13
+ zeroCount = 0;
15
14
 
16
15
  while (i < length) {
17
- value = buffer[i++];
18
- // finding 3 or 4-byte start codes (00 00 01 OR 00 00 00 01)
19
- switch (state) {
20
- case 0:
21
- if (value === 0) {
22
- state = 1;
23
- }
24
- break;
25
- case 1:
26
- if (value === 0) {
27
- state = 2;
28
- } else {
29
- state = 0;
30
- }
31
- break;
32
- case 2:
33
- case 3:
34
- if (value === 0) {
35
- state = 3;
36
- } else if (value === 1 && i < length) {
37
- if (lastIndex != i - state -1) {
38
- result.push(buffer.subarray(lastIndex, i - state -1));
39
- }
40
- lastIndex = i;
41
- state = 0;
42
- } else {
43
- state = 0;
44
- }
45
- break;
46
- default:
47
- break;
16
+ let value = buffer[i++];
17
+
18
+ if (value === 0) {
19
+ zeroCount++;
20
+ } else if (value === 1 && zeroCount >= 2) {
21
+ let startCodeLength = zeroCount + 1;
22
+
23
+ if (lastIndex !== i - startCodeLength) {
24
+ result.push(buffer.subarray(lastIndex, i - startCodeLength));
25
+ }
26
+
27
+ lastIndex = i;
28
+ zeroCount = 0;
29
+ } else {
30
+ zeroCount = 0;
48
31
  }
49
32
  }
50
33
 
34
+ // Remaining data after last start code
35
+ let left = null;
51
36
  if (lastIndex < length) {
52
37
  left = buffer.subarray(lastIndex, length);
53
38
  }
39
+
54
40
  return [result, left];
55
41
  }
56
42
 
@@ -246,76 +232,95 @@ export class H264Parser {
246
232
  height: ((2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16) - ((frameMbsOnlyFlag ? 2 : 4) * (frameCropTopOffset + frameCropBottomOffset)),
247
233
  };
248
234
  }
249
- static parseHeader(unit) {
250
- let decoder = new ExpGolomb(unit.getPayload());
251
- // skip NALu type
252
- decoder.readUByte();
253
- unit.isfmb = decoder.readUEG() === 0;
254
- unit.stype = decoder.readUEG();
235
+
236
+ }
237
+
238
+ export class NALU264 {
239
+ static get NDR() { return 1; }
240
+ static get IDR() { return 5; }
241
+ static get SEI() { return 6; }
242
+ static get SPS() { return 7; }
243
+ static get PPS() { return 8; }
244
+ static get AUD() { return 9; }
245
+
246
+ static get TYPES() {
247
+ return {
248
+ [NALU264.IDR]: 'IDR',
249
+ [NALU264.SEI]: 'SEI',
250
+ [NALU264.SPS]: 'SPS',
251
+ [NALU264.PPS]: 'PPS',
252
+ [NALU264.NDR]: 'NDR',
253
+ [NALU264.AUD]: 'AUD',
254
+ };
255
255
  }
256
- constructor(remuxer) {
257
- this.remuxer = remuxer;
258
- this.track = remuxer.mp4track;
256
+
257
+ constructor(data) {
258
+ this.payload = data;
259
+ this.nri = (this.payload[0] & 0x60) >> 5; // nal_ref_idc
260
+ this.nalUnitType = this.payload[0] & 0x1f;
261
+ this._sliceType = null;
262
+ this._isFirstSlice = false;
259
263
  }
260
264
 
261
- parseSPS(sps) {
262
- var config = H264Parser.readSPS(new Uint8Array(sps));
265
+ toString() {
266
+ return `${NALU264.TYPES[this.type()] || 'UNKNOWN'}: NRI: ${this.getNri()}`;
267
+ }
263
268
 
264
- this.track.fps = config.fps;
265
- this.track.width = config.width;
266
- this.track.height = config.height;
267
- this.track.sps = [new Uint8Array(sps)];
268
- this.track.codec = 'avc1.';
269
+ getNri() {
270
+ return this.nri;
271
+ }
269
272
 
270
- let codecarray = new DataView(sps.buffer, sps.byteOffset + 1, 4);
271
- for (let i = 0; i < 3; ++i) {
272
- var h = codecarray.getUint8(i).toString(16);
273
- if (h.length < 2) {
274
- h = '0' + h;
275
- }
276
- this.track.codec += h;
277
- }
273
+ type() {
274
+ return this.nalUnitType;
278
275
  }
279
276
 
280
- parsePPS(pps) {
281
- this.track.pps = [new Uint8Array(pps)];
277
+ get isKeyframe() {
278
+ return this.nalUnitType === NALU264.IDR;
282
279
  }
283
280
 
284
- parseNAL(unit) {
285
- if (!unit) return false;
281
+ get isVCL() {
282
+ return this.nalUnitType == NALU264.IDR || this.nalUnitType == NALU264.NDR;
283
+ }
286
284
 
287
- let push = false;
288
- switch (unit.type()) {
289
- case NALU.IDR:
290
- case NALU.NDR:
291
- push = true;
292
- break;
293
- case NALU.PPS:
294
- if (!this.track.pps) {
295
- this.parsePPS(unit.getPayload());
296
- if (!this.remuxer.readyToDecode && this.track.pps && this.track.sps) {
297
- this.remuxer.readyToDecode = true;
298
- }
299
- }
300
- push = true;
301
- break;
302
- case NALU.SPS:
303
- if (!this.track.sps) {
304
- this.parseSPS(unit.getPayload());
305
- if (!this.remuxer.readyToDecode && this.track.pps && this.track.sps) {
306
- this.remuxer.readyToDecode = true;
307
- }
308
- }
309
- push = true;
310
- break;
311
- case NALU.AUD:
312
- debug.log('AUD - ignoing');
313
- break;
314
- case NALU.SEI:
315
- debug.log('SEI - ignoing');
316
- break;
317
- default:
285
+ parseHeader() {
286
+ let decoder = new ExpGolomb(this.getPayload());
287
+ // skip NALu type
288
+ decoder.readUByte();
289
+ this._isFirstSlice = decoder.readUEG() === 0;
290
+ this._sliceType = decoder.readUEG();
291
+ }
292
+
293
+ get isFirstSlice() {
294
+ if (!this._isFirstSlice) {
295
+ this.parseHeader();
318
296
  }
319
- return push;
297
+ return this._isFirstSlice;
298
+ }
299
+
300
+ get sliceType() {
301
+ if (!this._sliceType) {
302
+ this.parseHeader();
303
+ }
304
+ return this._sliceType;
305
+ }
306
+
307
+ getPayload() {
308
+ return this.payload;
309
+ }
310
+
311
+ getPayloadSize() {
312
+ return this.payload.byteLength;
313
+ }
314
+
315
+ getSize() {
316
+ return 4 + this.getPayloadSize();
317
+ }
318
+
319
+ getData() {
320
+ const result = new Uint8Array(this.getSize());
321
+ const view = new DataView(result.buffer);
322
+ view.setUint32(0, this.getSize() - 4);
323
+ result.set(this.getPayload(), 4);
324
+ return result;
320
325
  }
321
326
  }
@@ -0,0 +1,303 @@
1
+ import { ExpGolomb } from '../util/exp-golomb.js';
2
+ import * as debug from '../util/debug';
3
+
4
+ // spec https://www.itu.int/rec/T-REC-H.265/
5
+
6
+ export class H265Parser {
7
+
8
+ static extractNALu(buffer) {
9
+ let i = 0,
10
+ length = buffer.byteLength,
11
+ result = [],
12
+ lastIndex = 0,
13
+ zeroCount = 0;
14
+
15
+ while (i < length) {
16
+ let value = buffer[i++];
17
+
18
+ if (value === 0) {
19
+ zeroCount++;
20
+ } else if (value === 1 && zeroCount >= 2) {
21
+ let startCodeLength = zeroCount + 1;
22
+
23
+ if (lastIndex !== i - startCodeLength) {
24
+ result.push(buffer.subarray(lastIndex, i - startCodeLength));
25
+ }
26
+
27
+ lastIndex = i;
28
+ zeroCount = 0;
29
+ } else {
30
+ zeroCount = 0;
31
+ }
32
+ }
33
+
34
+ // Remaining data after last start code
35
+ let left = null;
36
+ if (lastIndex < length) {
37
+ left = buffer.subarray(lastIndex, length);
38
+ }
39
+
40
+ return [result, left];
41
+ }
42
+
43
+ static removeEmulationPreventionBytes(nal) {
44
+ const rbsp = [];
45
+ let zeroCount = 0;
46
+
47
+ for (let i = 0; i < nal.length; i++) {
48
+ const value = nal[i];
49
+
50
+ if (zeroCount === 2 && value === 0x03) {
51
+ // Skip this emulation prevention byte
52
+ zeroCount = 0;
53
+ continue;
54
+ }
55
+
56
+ rbsp.push(value);
57
+
58
+ if (value === 0x00) {
59
+ zeroCount++;
60
+ } else {
61
+ zeroCount = 0;
62
+ }
63
+ }
64
+
65
+ return new Uint8Array(rbsp);
66
+ }
67
+
68
+
69
+ /**
70
+ * Read a sequence parameter set and return some interesting video
71
+ * properties. A sequence parameter set is the H265 metadata that
72
+ * describes the properties of upcoming video frames.
73
+ * @param data {Uint8Array} the bytes of a sequence parameter set
74
+ * @return {object} an object with configuration parsed from the
75
+ * sequence parameter set, including the dimensions of the
76
+ * associated video frames.
77
+ */
78
+ static readSPS(data) {
79
+ let decoder = new ExpGolomb(data);
80
+
81
+ // Skip NALU header (2 bytes for HEVC)
82
+ decoder.readUByte();
83
+ decoder.readUByte();
84
+
85
+ decoder.readBits(4); // sps_video_parameter_set_id
86
+ decoder.readBits(3); // sps_max_sub_layers_minus1
87
+ decoder.readBits(1); // sps_temporal_id_nesting_flag
88
+
89
+ // --- profile_tier_level() ---
90
+ let profile_space = decoder.readBits(2);
91
+ let tier_flag = decoder.readBits(1);
92
+ let profile_idc = decoder.readBits(5);
93
+
94
+ let profile_compatibility_flags = decoder.readUInt(); // 32 bits
95
+ let constraint_indicator_flags = new Uint8Array(6);
96
+ for (let i = 0; i < 6; i++) {
97
+ constraint_indicator_flags[i] = decoder.readUByte();
98
+ }
99
+
100
+ let level_idc = decoder.readUByte();
101
+
102
+ decoder.readUEG(); // seq_parameter_set_id
103
+
104
+ let chroma_format_idc = decoder.readUEG();
105
+ if (chroma_format_idc === 3) {
106
+ decoder.readBits(1); // separate_colour_plane_flag
107
+ }
108
+
109
+ let pic_width_in_luma_samples = decoder.readUEG();
110
+ let pic_height_in_luma_samples = decoder.readUEG();
111
+
112
+ // Cropping
113
+ let conformance_window_flag = decoder.readBoolean();
114
+ let conf_win_left_offset = 0, conf_win_right_offset = 0, conf_win_top_offset = 0, conf_win_bottom_offset = 0;
115
+ if (conformance_window_flag) {
116
+ conf_win_left_offset = decoder.readUEG();
117
+ conf_win_right_offset = decoder.readUEG();
118
+ conf_win_top_offset = decoder.readUEG();
119
+ conf_win_bottom_offset = decoder.readUEG();
120
+ }
121
+
122
+ let fps = null;
123
+ let vui_parameters_present_flag = decoder.readBoolean();
124
+ if (vui_parameters_present_flag) {
125
+ let aspect_ratio_info_present_flag = decoder.readBoolean();
126
+ if (aspect_ratio_info_present_flag) {
127
+ let aspect_ratio_idc = decoder.readUByte();
128
+ if (aspect_ratio_idc === 255) { // Extended_SAR
129
+ decoder.readUShort(); // sar_width
130
+ decoder.readUShort(); // sar_height
131
+ }
132
+ }
133
+ let overscan_info_present_flag = decoder.readBoolean();
134
+ if (overscan_info_present_flag) {
135
+ decoder.readBoolean(); // overscan_appropriate_flag
136
+ }
137
+ let video_signal_type_present_flag = decoder.readBoolean();
138
+ if (video_signal_type_present_flag) {
139
+ decoder.readBits(3); // video_format
140
+ decoder.readBoolean(); // video_full_range_flag
141
+ let colour_description_present_flag = decoder.readBoolean();
142
+ if (colour_description_present_flag) {
143
+ decoder.readUByte(); // colour_primaries
144
+ decoder.readUByte(); // transfer_characteristics
145
+ decoder.readUByte(); // matrix_coeffs
146
+ }
147
+ }
148
+ let chroma_loc_info_present_flag = decoder.readBoolean();
149
+ if (chroma_loc_info_present_flag) {
150
+ decoder.readUEG(); // chroma_sample_loc_type_top_field
151
+ decoder.readUEG(); // chroma_sample_loc_type_bottom_field
152
+ }
153
+ decoder.readBoolean(); // neutral_chroma_indication_flag
154
+ decoder.readBoolean(); // field_seq_flag
155
+ decoder.readBoolean(); // frame_field_info_present_flag
156
+
157
+ // Timing info
158
+ let timing_info_present_flag = decoder.readBoolean();
159
+ if (timing_info_present_flag) {
160
+ let num_units_in_tick = decoder.readUInt();
161
+ let time_scale = decoder.readUInt();
162
+ decoder.readBoolean(); // poc_proportional_to_timing_flag
163
+ if (num_units_in_tick) {
164
+ fps = time_scale / (2 * num_units_in_tick);
165
+ }
166
+ }
167
+ }
168
+
169
+ // Final width/height
170
+ let sub_width_c = (chroma_format_idc === 1 || chroma_format_idc === 2) ? 2 : 1;
171
+ let sub_height_c = (chroma_format_idc === 1) ? 2 : 1;
172
+ let width = pic_width_in_luma_samples - sub_width_c * (conf_win_right_offset + conf_win_left_offset);
173
+ let height = pic_height_in_luma_samples - sub_height_c * (conf_win_top_offset + conf_win_bottom_offset);
174
+
175
+ return {
176
+ width,
177
+ height,
178
+ profile_space,
179
+ tier_flag,
180
+ profile_idc,
181
+ profile_compatibility_flags,
182
+ constraint_indicator_flags,
183
+ level_idc,
184
+ chroma_format_idc,
185
+ fps,
186
+ };
187
+ }
188
+
189
+ }
190
+
191
+ export class NALU265 {
192
+ static get TRAIL_N() { return 0; }
193
+ static get TRAIL_R() { return 1; }
194
+ static get IDR_W_RADL() { return 19; }
195
+ static get IDR_N_LP() { return 20; }
196
+ static get CRA() { return 21; }
197
+ static get VPS() { return 32; }
198
+ static get SPS() { return 33; }
199
+ static get PPS() { return 34; }
200
+ static get AUD() { return 35; }
201
+ static get SEI() { return 39; }
202
+ static get SEI2() { return 40; }
203
+
204
+ static get TYPES() {
205
+ return {
206
+ [NALU265.TRAIL_N]: 'TRAIL_N',
207
+ [NALU265.TRAIL_R]: 'TRAIL_R',
208
+ [NALU265.IDR_W_RADL]: 'IDR',
209
+ [NALU265.IDR_N_LP]: 'IDR2',
210
+ [NALU265.CRA]: 'CRA',
211
+ [NALU265.VPS]: 'VPS',
212
+ [NALU265.SPS]: 'SPS',
213
+ [NALU265.PPS]: 'PPS',
214
+ [NALU265.AUD]: 'AUD',
215
+ [NALU265.SEI]: 'SEI',
216
+ [NALU265.SEI2]: 'SEI2',
217
+ };
218
+ }
219
+
220
+ constructor(data) {
221
+ this.payload = data;
222
+ this.nalUnitType = (data[0] & 0b01111110) >> 1;
223
+ this.nuhLayerId = ((data[0] & 0b00000001) << 5) | ((data[1] & 0b11111000) >> 3);
224
+ this.nuhTemporalIdPlus1 = data[1] & 0b00000111;
225
+ this._isFirstSlice = null;
226
+ this._sliceType = null;
227
+ }
228
+
229
+ toString() {
230
+ return `${NALU265.TYPES[this.type()] || 'UNKNOWN (' + this.type() + ')'}: Layer: ${this.nuhLayerId}, Temporal Id: ${this.nuhTemporalIdPlus1}`;
231
+ }
232
+
233
+ type() {
234
+ return this.nalUnitType;
235
+ }
236
+
237
+ get isKeyframe() {
238
+ return [
239
+ NALU265.IDR_W_RADL,
240
+ NALU265.IDR_N_LP,
241
+ NALU265.CRA
242
+ ].includes(this.nalUnitType);
243
+ }
244
+
245
+ get isVCL() {
246
+ return this.nalUnitType <=31;
247
+ }
248
+
249
+ parseHeader() {
250
+ let decoder = new ExpGolomb(this.getPayload());
251
+ // skip NALu type
252
+ decoder.readUByte();
253
+ decoder.readUByte();
254
+
255
+ // first_slice_segment_in_pic_flag
256
+ this._isFirstSlice = decoder.readBoolean();
257
+
258
+ // if NALU is not IDR/CRA/other IRAP, next comes no_output_of_prior_pics_flag
259
+ if (this.isKeyframe) {
260
+ decoder.readBits(1); // no_output_of_prior_pics_flag
261
+ }
262
+
263
+ // slice_pic_parameter_set_id
264
+ decoder.readUEG();
265
+
266
+ // slice_type (only for some NALU types, but useful for debugging)
267
+ this._sliceType = decoder.readUEG();
268
+ }
269
+
270
+ get isFirstSlice() {
271
+ if (!this._isFirstSlice) {
272
+ this.parseHeader();
273
+ }
274
+ return this._isFirstSlice;
275
+ }
276
+
277
+ get sliceType() {
278
+ if (!this._sliceType) {
279
+ this.parseHeader();
280
+ }
281
+ return this._sliceType;
282
+ }
283
+
284
+ getPayload() {
285
+ return this.payload;
286
+ }
287
+
288
+ getPayloadSize() {
289
+ return this.payload.byteLength;
290
+ }
291
+
292
+ getSize() {
293
+ return 4 + this.getPayloadSize();
294
+ }
295
+
296
+ getData() {
297
+ const result = new Uint8Array(this.getSize());
298
+ const view = new DataView(result.buffer);
299
+ view.setUint32(0, this.getSize() - 4);
300
+ result.set(this.getPayload(), 4);
301
+ return result;
302
+ }
303
+ }
@@ -4,9 +4,11 @@ import { BaseRemuxer } from './base.js';
4
4
 
5
5
  export class AACRemuxer extends BaseRemuxer {
6
6
 
7
- constructor(timescale) {
8
- super();
7
+ constructor(timescale, duration, frameDuration) {
8
+ super('AACRemuxer');
9
+ this.frameDuration = frameDuration;
9
10
  this.readyToDecode = false;
11
+ this.header = null;
10
12
  this.nextDts = 0;
11
13
  this.dts = 0;
12
14
  this.mp4track = {
@@ -16,17 +18,17 @@ export class AACRemuxer extends BaseRemuxer {
16
18
  len: 0,
17
19
  fragmented: true,
18
20
  timescale: timescale,
19
- duration: timescale,
21
+ duration: duration,
20
22
  samples: [],
21
23
  config: '',
22
24
  codec: '',
23
25
  };
24
26
  this.samples = [];
25
- this.aac = new AACParser(this);
26
27
  }
27
28
 
28
29
  resetTrack() {
29
30
  this.readyToDecode = false;
31
+ this.header = null;
30
32
  this.mp4track.codec = '';
31
33
  this.mp4track.channelCount = '';
32
34
  this.mp4track.config = '';
@@ -35,6 +37,39 @@ export class AACRemuxer extends BaseRemuxer {
35
37
  this.dts = 0;
36
38
  }
37
39
 
40
+ feed(data, duration) {
41
+ const { valid, header, slices } = AACParser.extractAAC(data);
42
+ if (!this.header) this.header = header;
43
+ if (valid && slices.length > 0) {
44
+ this.remux(this.getAudioFrames(slices, duration));
45
+ return true;
46
+ } else {
47
+ debug.error('Failed to extract audio data from:', data);
48
+ this.dispatch('outOfData');
49
+ return false;
50
+ }
51
+ }
52
+
53
+ getAudioFrames(aacFrames, duration) {
54
+ let frames = [],
55
+ fd = 0,
56
+ tt = 0;
57
+
58
+ for (let units of aacFrames) {
59
+ frames.push({ units });
60
+ }
61
+ fd = duration ? duration / frames.length | 0 : this.frameDuration;
62
+ tt = duration ? (duration - (fd * frames.length)) : 0;
63
+ frames.map((frame) => {
64
+ frame.duration = fd;
65
+ if (tt > 0) {
66
+ frame.duration++;
67
+ tt--;
68
+ }
69
+ });
70
+ return frames;
71
+ }
72
+
38
73
  remux(frames) {
39
74
  if (frames.length > 0) {
40
75
  for (let i = 0; i < frames.length; i++) {
@@ -48,7 +83,7 @@ export class AACRemuxer extends BaseRemuxer {
48
83
  });
49
84
  this.mp4track.len += size;
50
85
  if (!this.readyToDecode) {
51
- this.aac.setAACConfig();
86
+ this.setAACConfig();
52
87
  }
53
88
  }
54
89
  }
@@ -103,7 +138,28 @@ export class AACRemuxer extends BaseRemuxer {
103
138
  return new Uint8Array(payload.buffer, 0, this.mp4track.len);
104
139
  }
105
140
 
106
- getAacParser() {
107
- return this.aac;
141
+ setAACConfig() {
142
+ let objectType,
143
+ sampleIndex,
144
+ channelCount,
145
+ config = new Uint8Array(2);
146
+
147
+ if (!this.header) return;
148
+
149
+ objectType = ((this.header[2] & 0xC0) >>> 6) + 1;
150
+ sampleIndex = ((this.header[2] & 0x3C) >>> 2);
151
+ channelCount = ((this.header[2] & 0x01) << 2);
152
+ channelCount |= ((this.header[3] & 0xC0) >>> 6);
153
+
154
+ /* refer to http://wiki.multimedia.cx/index.php?title=MPEG-4_Audio#Audio_Specific_Config */
155
+ config[0] = objectType << 3;
156
+ config[0] |= (sampleIndex & 0x0E) >> 1;
157
+ config[1] |= (sampleIndex & 0x01) << 7;
158
+ config[1] |= channelCount << 3;
159
+
160
+ this.mp4track.codec = 'mp4a.40.' + objectType;
161
+ this.mp4track.channelCount = channelCount;
162
+ this.mp4track.config = config;
163
+ this.readyToDecode = true;
108
164
  }
109
165
  }
@@ -1,7 +1,8 @@
1
1
  import * as debug from '../util/debug';
2
+ import Event from '../util/event';
2
3
 
3
4
  let track_id = 1;
4
- export class BaseRemuxer {
5
+ export class BaseRemuxer extends Event {
5
6
 
6
7
  static getTrackID() {
7
8
  return track_id++;