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.
- package/README.md +29 -36
- package/dist/jmuxer.js +2770 -2020
- package/dist/jmuxer.min.js +1 -1
- package/eslint.config.mjs +21 -0
- package/package.json +20 -28
- package/rollup.config.js +13 -4
- package/src/controller/remux.js +63 -46
- package/src/jmuxer.js +40 -145
- package/src/parsers/aac.js +11 -38
- package/src/parsers/h264.js +106 -99
- package/src/parsers/h265.js +489 -0
- package/src/remuxer/aac.js +63 -7
- package/src/remuxer/base.js +2 -1
- package/src/remuxer/h264.js +176 -16
- package/src/remuxer/h265.js +295 -0
- package/src/util/debug.js +3 -5
- package/src/util/event.js +2 -2
- package/src/util/mp4-generator.js +124 -9
- package/src/util/utils.js +20 -0
- package/.idea/codeStyles/Project.xml +0 -13
- package/.idea/codeStyles/codeStyleConfig.xml +0 -5
- package/.idea/inspectionProfiles/Project_Default.xml +0 -6
- package/.idea/jmuxer.iml +0 -9
- package/.idea/misc.xml +0 -6
- package/.idea/modules.xml +0 -8
- package/.idea/vcs.xml +0 -6
- package/karma.conf.js +0 -77
- package/loader-thumb.jpg +0 -0
- package/packet-format.png +0 -0
- package/src/util/nalu.js +0 -72
- package/test/event.js +0 -30
- package/test/parser.js +0 -35
- package/test/utils.js +0 -19
|
@@ -0,0 +1,489 @@
|
|
|
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
|
+
const sps_max_sub_layers_minus1 = 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
|
+
const bit_depth_luma_minus8 = decoder.readUEG();
|
|
123
|
+
const bit_depth_chroma_minus8 = decoder.readUEG();
|
|
124
|
+
|
|
125
|
+
const log2_max_pic_order_cnt_lsb_minus4 = decoder.readUEG();
|
|
126
|
+
|
|
127
|
+
const ordering_flag = decoder.readBits(1); // sps_sub_layer_ordering_info_present_flag
|
|
128
|
+
|
|
129
|
+
for (let i = (ordering_flag ? 0 : sps_max_sub_layers_minus1); i <= sps_max_sub_layers_minus1; i++) {
|
|
130
|
+
const max_dec = decoder.readUEG();
|
|
131
|
+
const max_num = decoder.readUEG();
|
|
132
|
+
const max_latency = decoder.readUEG();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
decoder.readUEG(); // log2_min_luma_coding_block_size_minus3
|
|
136
|
+
decoder.readUEG(); // log2_diff_max_min_luma_coding_block_size
|
|
137
|
+
decoder.readUEG(); // log2_min_luma_transform_block_size_minus2
|
|
138
|
+
decoder.readUEG(); // log2_diff_max_min_luma_transform_block_size
|
|
139
|
+
decoder.readUEG(); // max_transform_hierarchy_depth_inter
|
|
140
|
+
decoder.readUEG(); // max_transform_hierarchy_depth_intra
|
|
141
|
+
|
|
142
|
+
const scaling_list_enabled_flag = decoder.readBits(1);
|
|
143
|
+
if (scaling_list_enabled_flag) {
|
|
144
|
+
const sps_scaling_list_data_present_flag = decoder.readBits(1);
|
|
145
|
+
if (sps_scaling_list_data_present_flag) {
|
|
146
|
+
// inline skipScalingListH265
|
|
147
|
+
for (let sizeId = 0; sizeId < 4; sizeId++) {
|
|
148
|
+
for (let matrixId = 0; matrixId < (sizeId === 3 ? 2 : 6); matrixId++) {
|
|
149
|
+
const flag = decoder.readBits(1);
|
|
150
|
+
if (!flag) {
|
|
151
|
+
decoder.readUEG(); // scaling_list_pred_matrix_id_delta
|
|
152
|
+
} else {
|
|
153
|
+
const coefNum = Math.min(64, 1 << (4 + (sizeId << 1)));
|
|
154
|
+
if (sizeId > 1) decoder.readEG(); // scaling_list_dc_coef_minus8
|
|
155
|
+
for (let k = 0; k < coefNum; k++) decoder.readEG();
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const amp_enabled_flag = decoder.readBits(1);
|
|
163
|
+
const sample_adaptive_offset_enabled_flag = decoder.readBits(1);
|
|
164
|
+
|
|
165
|
+
const pcm_enabled_flag = decoder.readBits(1);
|
|
166
|
+
if (pcm_enabled_flag) {
|
|
167
|
+
decoder.readBits(4); // pcm_sample_bit_depth_luma_minus1
|
|
168
|
+
decoder.readBits(4); // pcm_sample_bit_depth_chroma_minus1
|
|
169
|
+
decoder.readUEG(); // log2_min_pcm_luma_coding_block_size_minus3
|
|
170
|
+
decoder.readUEG(); // log2_diff_max_min_pcm_luma_coding_block_size
|
|
171
|
+
decoder.readBits(1); // pcm_loop_filter_disabled_flag
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const num_short_term_ref_pic_sets = decoder.readUEG();
|
|
175
|
+
// inline simplified short-term RPS parsing
|
|
176
|
+
for (let stIdx = 0; stIdx < num_short_term_ref_pic_sets; stIdx++) {
|
|
177
|
+
let inter_ref_pic_set_prediction_flag = false;
|
|
178
|
+
if (stIdx !== 0) {
|
|
179
|
+
inter_ref_pic_set_prediction_flag = decoder.readBoolean();
|
|
180
|
+
}
|
|
181
|
+
if (inter_ref_pic_set_prediction_flag) {
|
|
182
|
+
decoder.readUEG(); // delta_idx_minus1 or related
|
|
183
|
+
decoder.readBits(1); // delta_rps_sign
|
|
184
|
+
decoder.readUEG(); // abs_delta_rps_minus1
|
|
185
|
+
// fine-grained details omitted for brevity
|
|
186
|
+
} else {
|
|
187
|
+
const num_negative_pics = decoder.readUEG();
|
|
188
|
+
const num_positive_pics = decoder.readUEG();
|
|
189
|
+
for (let i = 0; i < num_negative_pics; i++) {
|
|
190
|
+
decoder.readUEG(); // delta_poc_s0_minus1[i]
|
|
191
|
+
decoder.readBits(1); // used_by_curr_pic_s0_flag[i]
|
|
192
|
+
}
|
|
193
|
+
for (let i = 0; i < num_positive_pics; i++) {
|
|
194
|
+
decoder.readUEG(); // delta_poc_s1_minus1[i]
|
|
195
|
+
decoder.readBits(1); // used_by_curr_pic_s1_flag[i]
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const long_term_ref_pics_present_flag = decoder.readBits(1);
|
|
201
|
+
if (long_term_ref_pics_present_flag) {
|
|
202
|
+
const num_long_term_ref_pics_sps = decoder.readUEG();
|
|
203
|
+
for (let i = 0; i < num_long_term_ref_pics_sps; i++) {
|
|
204
|
+
decoder.readUEG(); // lt_ref_pic_poc_lsb_sps[i]
|
|
205
|
+
decoder.readBits(1); // used_by_curr_pic_lt_sps_flag[i]
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const sps_temporal_mvp_enabled_flag = decoder.readBits(1);
|
|
210
|
+
const strong_intra_smoothing_enabled_flag = decoder.readBits(1);
|
|
211
|
+
|
|
212
|
+
let fps = null;
|
|
213
|
+
let vui_parameters_present_flag = decoder.readBoolean();
|
|
214
|
+
if (vui_parameters_present_flag) {
|
|
215
|
+
let aspect_ratio_info_present_flag = decoder.readBoolean();
|
|
216
|
+
if (aspect_ratio_info_present_flag) {
|
|
217
|
+
let aspect_ratio_idc = decoder.readUByte();
|
|
218
|
+
if (aspect_ratio_idc === 255) { // Extended_SAR
|
|
219
|
+
decoder.readUShort(); // sar_width
|
|
220
|
+
decoder.readUShort(); // sar_height
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
let overscan_info_present_flag = decoder.readBoolean();
|
|
224
|
+
if (overscan_info_present_flag) {
|
|
225
|
+
decoder.readBoolean(); // overscan_appropriate_flag
|
|
226
|
+
}
|
|
227
|
+
let video_signal_type_present_flag = decoder.readBoolean();
|
|
228
|
+
if (video_signal_type_present_flag) {
|
|
229
|
+
decoder.readBits(3); // video_format
|
|
230
|
+
decoder.readBoolean(); // video_full_range_flag
|
|
231
|
+
let colour_description_present_flag = decoder.readBoolean();
|
|
232
|
+
if (colour_description_present_flag) {
|
|
233
|
+
decoder.readUByte(); // colour_primaries
|
|
234
|
+
decoder.readUByte(); // transfer_characteristics
|
|
235
|
+
decoder.readUByte(); // matrix_coeffs
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
let chroma_loc_info_present_flag = decoder.readBoolean();
|
|
239
|
+
if (chroma_loc_info_present_flag) {
|
|
240
|
+
decoder.readUEG(); // chroma_sample_loc_type_top_field
|
|
241
|
+
decoder.readUEG(); // chroma_sample_loc_type_bottom_field
|
|
242
|
+
}
|
|
243
|
+
decoder.readBoolean(); // neutral_chroma_indication_flag
|
|
244
|
+
decoder.readBoolean(); // field_seq_flag
|
|
245
|
+
decoder.readBoolean(); // frame_field_info_present_flag
|
|
246
|
+
|
|
247
|
+
let default_display_window_flag = decoder.readBoolean();
|
|
248
|
+
if (default_display_window_flag) {
|
|
249
|
+
decoder.readUEG(); // def_disp_win_left_offset
|
|
250
|
+
decoder.readUEG(); // def_disp_win_right_offset
|
|
251
|
+
decoder.readUEG(); // def_disp_win_top_offset
|
|
252
|
+
decoder.readUEG(); // def_disp_win_bottom_offset
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
let vui_timing_info_present_flag = decoder.readBoolean();
|
|
256
|
+
if (vui_timing_info_present_flag) {
|
|
257
|
+
let vui_num_units_in_tick = decoder.readUInt();
|
|
258
|
+
let vui_time_scale = decoder.readUInt();
|
|
259
|
+
const vui_poc_proportional_to_timing_flag = decoder.readBoolean();
|
|
260
|
+
if (vui_poc_proportional_to_timing_flag) {
|
|
261
|
+
decoder.readUEG(); // vui_num_ticks_poc_diff_one_minus1
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const vui_hrd_parameters_present_flag = decoder.readBoolean();
|
|
265
|
+
if (vui_hrd_parameters_present_flag) {
|
|
266
|
+
// inline simplified HRD parsing
|
|
267
|
+
const commonInfPresentFlag = true;
|
|
268
|
+
let nal_hrd_parameters_present_flag = false;
|
|
269
|
+
let vcl_hrd_parameters_present_flag = false;
|
|
270
|
+
let sub_pic_hrd_params_present_flag = false;
|
|
271
|
+
|
|
272
|
+
if (commonInfPresentFlag) {
|
|
273
|
+
nal_hrd_parameters_present_flag = decoder.readBoolean();
|
|
274
|
+
vcl_hrd_parameters_present_flag = decoder.readBoolean();
|
|
275
|
+
if (nal_hrd_parameters_present_flag || vcl_hrd_parameters_present_flag) {
|
|
276
|
+
sub_pic_hrd_params_present_flag = decoder.readBoolean();
|
|
277
|
+
if (sub_pic_hrd_params_present_flag) {
|
|
278
|
+
decoder.readBits(8); // tick_divisor_minus2
|
|
279
|
+
decoder.readBits(5); // du_cpb_removal_delay_increment_length_minus1
|
|
280
|
+
decoder.readBits(1); // sub_pic_cpb_params_in_pic_timing_sei_flag
|
|
281
|
+
decoder.readBits(5); // dpb_output_delay_du_length_minus1
|
|
282
|
+
}
|
|
283
|
+
decoder.readBits(4); // bit_rate_scale
|
|
284
|
+
decoder.readBits(4); // cpb_size_scale
|
|
285
|
+
if (sub_pic_hrd_params_present_flag) {
|
|
286
|
+
decoder.readBits(4); // cpb_size_du_scale
|
|
287
|
+
}
|
|
288
|
+
decoder.readBits(5); // initial_cpb_removal_delay_length_minus1
|
|
289
|
+
decoder.readBits(5); // au_cpb_removal_delay_length_minus1
|
|
290
|
+
decoder.readBits(5); // dpb_output_delay_length_minus1
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
for (let i = 0; i <= sps_max_sub_layers_minus1; i++) {
|
|
295
|
+
const fixed_pic_rate_general_flag = decoder.readBoolean();
|
|
296
|
+
let fixed_pic_rate_within_cvs_flag = false;
|
|
297
|
+
let low_delay_hrd_flag = false;
|
|
298
|
+
let cpb_cnt_minus1 = 0;
|
|
299
|
+
if (!fixed_pic_rate_general_flag) {
|
|
300
|
+
fixed_pic_rate_within_cvs_flag = decoder.readBoolean();
|
|
301
|
+
} else {
|
|
302
|
+
fixed_pic_rate_within_cvs_flag = true;
|
|
303
|
+
}
|
|
304
|
+
if (fixed_pic_rate_within_cvs_flag) {
|
|
305
|
+
decoder.readUEG(); // elemental_duration_in_tc_minus1
|
|
306
|
+
} else {
|
|
307
|
+
low_delay_hrd_flag = decoder.readBoolean();
|
|
308
|
+
}
|
|
309
|
+
if (!low_delay_hrd_flag) {
|
|
310
|
+
cpb_cnt_minus1 = decoder.readUEG();
|
|
311
|
+
}
|
|
312
|
+
if (nal_hrd_parameters_present_flag) {
|
|
313
|
+
for (let j = 0; j <= cpb_cnt_minus1; j++) {
|
|
314
|
+
decoder.readUEG(); // bit_rate_value_minus1
|
|
315
|
+
decoder.readUEG(); // cpb_size_value_minus1
|
|
316
|
+
if (sub_pic_hrd_params_present_flag) {
|
|
317
|
+
decoder.readUEG(); // cpb_size_du_value_minus1
|
|
318
|
+
decoder.readUEG(); // bit_rate_du_value_minus1
|
|
319
|
+
}
|
|
320
|
+
decoder.readBits(1); // cbr_flag
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
if (vcl_hrd_parameters_present_flag) {
|
|
324
|
+
for (let j = 0; j <= cpb_cnt_minus1; j++) {
|
|
325
|
+
decoder.readUEG(); // bit_rate_value_minus1
|
|
326
|
+
decoder.readUEG(); // cpb_size_value_minus1
|
|
327
|
+
if (sub_pic_hrd_params_present_flag) {
|
|
328
|
+
decoder.readUEG(); // cpb_size_du_value_minus1
|
|
329
|
+
decoder.readUEG(); // bit_rate_du_value_minus1
|
|
330
|
+
}
|
|
331
|
+
decoder.readBits(1); // cbr_flag
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (vui_num_units_in_tick > 0 && vui_time_scale > 0) {
|
|
338
|
+
fps = vui_time_scale / vui_num_units_in_tick;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const bitstream_restriction_flag = decoder.readBoolean();
|
|
343
|
+
if (bitstream_restriction_flag) {
|
|
344
|
+
decoder.readBoolean(); // tiles_fixed_structure_flag
|
|
345
|
+
decoder.readBoolean(); // motion_vectors_over_pic_boundaries_flag
|
|
346
|
+
decoder.readBoolean(); // restricted_ref_pic_lists_flag
|
|
347
|
+
decoder.readUEG(); // min_spatial_segmentation_idc
|
|
348
|
+
decoder.readUEG(); // max_bytes_per_pic_denom
|
|
349
|
+
decoder.readUEG(); // max_bits_per_min_cu_denom
|
|
350
|
+
decoder.readUEG(); // log2_max_mv_length_horizontal
|
|
351
|
+
decoder.readUEG(); // log2_max_mv_length_vertical
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Final width/height
|
|
356
|
+
let sub_width_c = (chroma_format_idc === 1 || chroma_format_idc === 2) ? 2 : 1;
|
|
357
|
+
let sub_height_c = (chroma_format_idc === 1) ? 2 : 1;
|
|
358
|
+
let width = pic_width_in_luma_samples - sub_width_c * (conf_win_right_offset + conf_win_left_offset);
|
|
359
|
+
let height = pic_height_in_luma_samples - sub_height_c * (conf_win_top_offset + conf_win_bottom_offset);
|
|
360
|
+
|
|
361
|
+
return {
|
|
362
|
+
width,
|
|
363
|
+
height,
|
|
364
|
+
profile_space,
|
|
365
|
+
tier_flag,
|
|
366
|
+
profile_idc,
|
|
367
|
+
profile_compatibility_flags,
|
|
368
|
+
constraint_indicator_flags,
|
|
369
|
+
level_idc,
|
|
370
|
+
chroma_format_idc,
|
|
371
|
+
fps,
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
export class NALU265 {
|
|
378
|
+
static get TRAIL_N() { return 0; }
|
|
379
|
+
static get TRAIL_R() { return 1; }
|
|
380
|
+
static get IDR_W_RADL() { return 19; }
|
|
381
|
+
static get IDR_N_LP() { return 20; }
|
|
382
|
+
static get CRA() { return 21; }
|
|
383
|
+
static get VPS() { return 32; }
|
|
384
|
+
static get SPS() { return 33; }
|
|
385
|
+
static get PPS() { return 34; }
|
|
386
|
+
static get AUD() { return 35; }
|
|
387
|
+
static get SEI() { return 39; }
|
|
388
|
+
static get SEI2() { return 40; }
|
|
389
|
+
|
|
390
|
+
static get TYPES() {
|
|
391
|
+
return {
|
|
392
|
+
[NALU265.TRAIL_N]: 'TRAIL_N',
|
|
393
|
+
[NALU265.TRAIL_R]: 'TRAIL_R',
|
|
394
|
+
[NALU265.IDR_W_RADL]: 'IDR',
|
|
395
|
+
[NALU265.IDR_N_LP]: 'IDR2',
|
|
396
|
+
[NALU265.CRA]: 'CRA',
|
|
397
|
+
[NALU265.VPS]: 'VPS',
|
|
398
|
+
[NALU265.SPS]: 'SPS',
|
|
399
|
+
[NALU265.PPS]: 'PPS',
|
|
400
|
+
[NALU265.AUD]: 'AUD',
|
|
401
|
+
[NALU265.SEI]: 'SEI',
|
|
402
|
+
[NALU265.SEI2]: 'SEI2',
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
constructor(data) {
|
|
407
|
+
this.payload = data;
|
|
408
|
+
this.nalUnitType = (data[0] & 0b01111110) >> 1;
|
|
409
|
+
this.nuhLayerId = ((data[0] & 0b00000001) << 5) | ((data[1] & 0b11111000) >> 3);
|
|
410
|
+
this.nuhTemporalIdPlus1 = data[1] & 0b00000111;
|
|
411
|
+
this._isFirstSlice = null;
|
|
412
|
+
this._sliceType = null;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
toString() {
|
|
416
|
+
return `${NALU265.TYPES[this.type()] || 'UNKNOWN (' + this.type() + ')'}: Layer: ${this.nuhLayerId}, Temporal Id: ${this.nuhTemporalIdPlus1}`;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
type() {
|
|
420
|
+
return this.nalUnitType;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
get isKeyframe() {
|
|
424
|
+
return [
|
|
425
|
+
NALU265.IDR_W_RADL,
|
|
426
|
+
NALU265.IDR_N_LP,
|
|
427
|
+
NALU265.CRA
|
|
428
|
+
].includes(this.nalUnitType);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
get isVCL() {
|
|
432
|
+
return this.nalUnitType <=31;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
parseHeader() {
|
|
436
|
+
let decoder = new ExpGolomb(this.getPayload());
|
|
437
|
+
// skip NALu type
|
|
438
|
+
decoder.readUByte();
|
|
439
|
+
decoder.readUByte();
|
|
440
|
+
|
|
441
|
+
// first_slice_segment_in_pic_flag
|
|
442
|
+
this._isFirstSlice = decoder.readBoolean();
|
|
443
|
+
|
|
444
|
+
// if NALU is not IDR/CRA/other IRAP, next comes no_output_of_prior_pics_flag
|
|
445
|
+
if (this.isKeyframe) {
|
|
446
|
+
decoder.readBits(1); // no_output_of_prior_pics_flag
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// slice_pic_parameter_set_id
|
|
450
|
+
decoder.readUEG();
|
|
451
|
+
|
|
452
|
+
// slice_type (only for some NALU types, but useful for debugging)
|
|
453
|
+
this._sliceType = decoder.readUEG();
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
get isFirstSlice() {
|
|
457
|
+
if (!this._isFirstSlice) {
|
|
458
|
+
this.parseHeader();
|
|
459
|
+
}
|
|
460
|
+
return this._isFirstSlice;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
get sliceType() {
|
|
464
|
+
if (!this._sliceType) {
|
|
465
|
+
this.parseHeader();
|
|
466
|
+
}
|
|
467
|
+
return this._sliceType;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
getPayload() {
|
|
471
|
+
return this.payload;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
getPayloadSize() {
|
|
475
|
+
return this.payload.byteLength;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
getSize() {
|
|
479
|
+
return 4 + this.getPayloadSize();
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
getData() {
|
|
483
|
+
const result = new Uint8Array(this.getSize());
|
|
484
|
+
const view = new DataView(result.buffer);
|
|
485
|
+
view.setUint32(0, this.getSize() - 4);
|
|
486
|
+
result.set(this.getPayload(), 4);
|
|
487
|
+
return result;
|
|
488
|
+
}
|
|
489
|
+
}
|
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
|
}
|