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/parsers/h264.js
CHANGED
|
@@ -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
|
-
|
|
14
|
-
|
|
12
|
+
lastIndex = 0,
|
|
13
|
+
zeroCount = 0;
|
|
15
14
|
|
|
16
15
|
while (i < length) {
|
|
17
|
-
value = buffer[i++];
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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
|
-
|
|
257
|
-
|
|
258
|
-
this.
|
|
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
|
-
|
|
262
|
-
|
|
265
|
+
toString() {
|
|
266
|
+
return `${NALU264.TYPES[this.type()] || 'UNKNOWN'}: NRI: ${this.getNri()}`;
|
|
267
|
+
}
|
|
263
268
|
|
|
264
|
-
|
|
265
|
-
this.
|
|
266
|
-
|
|
267
|
-
this.track.sps = [new Uint8Array(sps)];
|
|
268
|
-
this.track.codec = 'avc1.';
|
|
269
|
+
getNri() {
|
|
270
|
+
return this.nri;
|
|
271
|
+
}
|
|
269
272
|
|
|
270
|
-
|
|
271
|
-
|
|
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
|
-
|
|
281
|
-
this.
|
|
277
|
+
get isKeyframe() {
|
|
278
|
+
return this.nalUnitType === NALU264.IDR;
|
|
282
279
|
}
|
|
283
280
|
|
|
284
|
-
|
|
285
|
-
|
|
281
|
+
get isVCL() {
|
|
282
|
+
return this.nalUnitType == NALU264.IDR || this.nalUnitType == NALU264.NDR;
|
|
283
|
+
}
|
|
286
284
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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
|
|
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
|
+
}
|
package/src/remuxer/aac.js
CHANGED
|
@@ -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:
|
|
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.
|
|
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
|
-
|
|
107
|
-
|
|
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
|
}
|