jmuxer 2.1.0 → 2.1.2
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/.idea/copilot.data.migration.agent.xml +6 -0
- package/README.md +6 -7
- package/dist/jmuxer.js +2770 -2020
- package/dist/jmuxer.min.js +1 -3416
- package/eslint.config.mjs +21 -0
- package/package.json +14 -27
- package/rollup.config.js +13 -4
- package/src/controller/remux.js +2 -2
- package/src/jmuxer.js +1 -1
- package/src/parsers/h264.js +5 -3
- package/src/parsers/h265.js +195 -9
- package/src/remuxer/h264.js +17 -13
- package/src/remuxer/h265.js +24 -17
- package/src/util/debug.js +0 -2
- package/src/util/utils.js +20 -0
- package/karma.conf.js +0 -77
- package/test/event.js +0 -30
- package/test/parser.js +0 -35
- package/test/utils.js +0 -19
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import js from "@eslint/js";
|
|
2
|
+
import globals from "globals";
|
|
3
|
+
import { defineConfig, globalIgnores } from "eslint/config";
|
|
4
|
+
|
|
5
|
+
export default defineConfig([
|
|
6
|
+
{
|
|
7
|
+
files: ["**/*.{js,mjs,cjs}"],
|
|
8
|
+
plugins: { js },
|
|
9
|
+
extends: ["js/recommended"],
|
|
10
|
+
languageOptions: { globals: { ...globals.browser, ...globals.node } },
|
|
11
|
+
rules: {
|
|
12
|
+
indent: ["error", 4, { SwitchCase: 1 }],
|
|
13
|
+
"no-unused-vars": "off",
|
|
14
|
+
"linebreak-style": ["error", "unix"],
|
|
15
|
+
quotes: ["error", "single"],
|
|
16
|
+
semi: ["error", "always"],
|
|
17
|
+
"no-useless-assignment": "off",
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
globalIgnores(["./src/util/mp4-generator.js"]),
|
|
21
|
+
]);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jmuxer",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.2",
|
|
4
4
|
"description": "jMuxer - a simple javascript mp4 muxer for non-standard streaming communications protocol",
|
|
5
5
|
"main": "dist/jmuxer.min.js",
|
|
6
6
|
"scripts": {
|
|
@@ -9,8 +9,7 @@
|
|
|
9
9
|
"server": "cd example && nodemon server.mjs",
|
|
10
10
|
"dev": "concurrently --names \"BUILD,SERVER\" -c \"bgBlue.bold,bgMagenta.bold\" \"npm run watch\" \"npm run server\"",
|
|
11
11
|
"pro": "NODE_ENV=production rollup -c",
|
|
12
|
-
"lint": "eslint
|
|
13
|
-
"test": "karma start karma.conf.js"
|
|
12
|
+
"lint": "eslint ./src/"
|
|
14
13
|
},
|
|
15
14
|
"keywords": [
|
|
16
15
|
"h264",
|
|
@@ -28,36 +27,24 @@
|
|
|
28
27
|
],
|
|
29
28
|
"repository": {
|
|
30
29
|
"type": "git",
|
|
31
|
-
"url": "
|
|
30
|
+
"url": "git@github.com:webstream-labs/jmuxer.git"
|
|
32
31
|
},
|
|
33
32
|
"author": "Samir Das",
|
|
34
33
|
"devDependencies": {
|
|
35
34
|
"@babel/core": "^7.10.5",
|
|
36
|
-
"@babel/
|
|
37
|
-
"@
|
|
38
|
-
"@
|
|
39
|
-
"@rollup/plugin-
|
|
40
|
-
"@rollup/plugin-
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"@rollup/plugin-replace": "^3.0.0",
|
|
44
|
-
"@rollup/pluginutils": "^4.0.0",
|
|
45
|
-
"caniuse-lite": "^1.0.30001223",
|
|
46
|
-
"chai": "^4.2.0",
|
|
47
|
-
"concurrently": "^9.2.1",
|
|
48
|
-
"eslint": "^8.2.0",
|
|
35
|
+
"@babel/preset-env": "^7.29.0",
|
|
36
|
+
"@eslint/js": "^10.0.1",
|
|
37
|
+
"@rollup/plugin-babel": "^6.1.0",
|
|
38
|
+
"@rollup/plugin-replace": "^6.0.3",
|
|
39
|
+
"@rollup/plugin-terser": "^1.0.0",
|
|
40
|
+
"concurrently": "^10.0.3",
|
|
41
|
+
"eslint": "^10.0.2",
|
|
49
42
|
"express": "^5.1.0",
|
|
50
43
|
"express-ws": "^5.0.2",
|
|
51
|
-
"
|
|
52
|
-
"karma-chai": "^0.1.0",
|
|
53
|
-
"karma-chrome-launcher": "^3.1.0",
|
|
54
|
-
"karma-mocha": "^2.0.1",
|
|
55
|
-
"karma-rollup-preprocessor": "^7.0.5",
|
|
56
|
-
"mocha": "^11.7.2",
|
|
44
|
+
"globals": "^17.4.0",
|
|
57
45
|
"msgpack-lite": "^0.1.26",
|
|
58
46
|
"nodemon": "^3.1.10",
|
|
59
|
-
"
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
47
|
+
"rollup": "^4.59.0"
|
|
48
|
+
},
|
|
49
|
+
"type": "module"
|
|
63
50
|
}
|
package/rollup.config.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
// Rollup plugins
|
|
2
2
|
import babel from '@rollup/plugin-babel';
|
|
3
|
-
import eslint from '@rollup/plugin-eslint';
|
|
4
3
|
import replace from '@rollup/plugin-replace';
|
|
5
|
-
import
|
|
4
|
+
import terser from '@rollup/plugin-terser';
|
|
6
5
|
export default {
|
|
7
6
|
input: 'src/jmuxer.js',
|
|
8
7
|
output: [
|
|
@@ -11,6 +10,7 @@ export default {
|
|
|
11
10
|
format: 'umd',
|
|
12
11
|
name: 'JMuxer',
|
|
13
12
|
sourcemap: false, // 'inline'
|
|
13
|
+
plugins: [terser()],
|
|
14
14
|
globals: {
|
|
15
15
|
stream: 'stream',
|
|
16
16
|
fs: 'fs'
|
|
@@ -21,6 +21,17 @@ export default {
|
|
|
21
21
|
format: 'umd',
|
|
22
22
|
name: 'JMuxer',
|
|
23
23
|
sourcemap: false,
|
|
24
|
+
plugins: [terser()],
|
|
25
|
+
globals: {
|
|
26
|
+
stream: 'stream',
|
|
27
|
+
fs: 'fs'
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
file: 'dist/jmuxer.js',
|
|
32
|
+
format: 'umd',
|
|
33
|
+
name: 'JMuxer',
|
|
34
|
+
sourcemap: false,
|
|
24
35
|
globals: {
|
|
25
36
|
stream: 'stream',
|
|
26
37
|
fs: 'fs'
|
|
@@ -34,7 +45,6 @@ export default {
|
|
|
34
45
|
console.error(message);
|
|
35
46
|
},
|
|
36
47
|
plugins: [
|
|
37
|
-
eslint(),
|
|
38
48
|
babel({
|
|
39
49
|
exclude: 'node_modules/**',
|
|
40
50
|
babelHelpers: 'bundled'
|
|
@@ -44,7 +54,6 @@ export default {
|
|
|
44
54
|
preventAssignment: true,
|
|
45
55
|
ENV: JSON.stringify(process.env.NODE_ENV || 'development'),
|
|
46
56
|
}),
|
|
47
|
-
(process.env.NODE_ENV === 'production' && terser()),
|
|
48
57
|
],
|
|
49
58
|
external: [ 'stream', 'fs' ],
|
|
50
59
|
};
|
package/src/controller/remux.js
CHANGED
|
@@ -37,7 +37,7 @@ export default class RemuxController extends Event {
|
|
|
37
37
|
if (type === 'audio' || type === 'both') {
|
|
38
38
|
const aacRemuxer = new AACRemuxer(this.timescale, this.mediaDuration, this.frameDuration);
|
|
39
39
|
this.tracks.audio = aacRemuxer;
|
|
40
|
-
this.tracks.
|
|
40
|
+
this.tracks.audio.on('outOfData', () => {
|
|
41
41
|
this.dispatch('missingAudioFrames');
|
|
42
42
|
});
|
|
43
43
|
}
|
|
@@ -123,7 +123,7 @@ export default class RemuxController extends Event {
|
|
|
123
123
|
let remux = false;
|
|
124
124
|
|
|
125
125
|
if (data.video && this.tracks.video) {
|
|
126
|
-
remux |= this.tracks.video.feed(data.video, data.duration, data.compositionTimeOffset);
|
|
126
|
+
remux |= this.tracks.video.feed(data.video, data.duration, data.compositionTimeOffset, data.isLastVideoFrameComplete);
|
|
127
127
|
}
|
|
128
128
|
if (data.audio && this.tracks.audio) {
|
|
129
129
|
remux |= this.tracks.audio.feed(data.audio, data.duration);
|
package/src/jmuxer.js
CHANGED
|
@@ -324,7 +324,7 @@ export default class JMuxer extends Event {
|
|
|
324
324
|
URL.revokeObjectURL(this.url);
|
|
325
325
|
// this.createBuffer();
|
|
326
326
|
if (typeof this.options.onReady === 'function') {
|
|
327
|
-
this.options.onReady.call(null, this.isReset);
|
|
327
|
+
this.options.onReady.call(null, this.isReset, this.mediaSource);
|
|
328
328
|
}
|
|
329
329
|
}
|
|
330
330
|
|
package/src/parsers/h264.js
CHANGED
|
@@ -221,9 +221,11 @@ export class H264Parser {
|
|
|
221
221
|
let fixedFrameRate = decoder.readBoolean();
|
|
222
222
|
let frameDuration = timeScale / (2 * unitsInTick);
|
|
223
223
|
|
|
224
|
-
if (fixedFrameRate) {
|
|
225
|
-
|
|
226
|
-
}
|
|
224
|
+
// if (fixedFrameRate) {
|
|
225
|
+
// fps = frameDuration;
|
|
226
|
+
// }
|
|
227
|
+
// Return the fps value even if fixedFrameRate is not set
|
|
228
|
+
fps = frameDuration;
|
|
227
229
|
}
|
|
228
230
|
}
|
|
229
231
|
return {
|
package/src/parsers/h265.js
CHANGED
|
@@ -83,7 +83,7 @@ export class H265Parser {
|
|
|
83
83
|
decoder.readUByte();
|
|
84
84
|
|
|
85
85
|
decoder.readBits(4); // sps_video_parameter_set_id
|
|
86
|
-
decoder.readBits(3); // sps_max_sub_layers_minus1
|
|
86
|
+
const sps_max_sub_layers_minus1 = decoder.readBits(3); // sps_max_sub_layers_minus1
|
|
87
87
|
decoder.readBits(1); // sps_temporal_id_nesting_flag
|
|
88
88
|
|
|
89
89
|
// --- profile_tier_level() ---
|
|
@@ -119,6 +119,96 @@ export class H265Parser {
|
|
|
119
119
|
conf_win_bottom_offset = decoder.readUEG();
|
|
120
120
|
}
|
|
121
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
|
+
|
|
122
212
|
let fps = null;
|
|
123
213
|
let vui_parameters_present_flag = decoder.readBoolean();
|
|
124
214
|
if (vui_parameters_present_flag) {
|
|
@@ -154,15 +244,111 @@ export class H265Parser {
|
|
|
154
244
|
decoder.readBoolean(); // field_seq_flag
|
|
155
245
|
decoder.readBoolean(); // frame_field_info_present_flag
|
|
156
246
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
decoder.
|
|
163
|
-
|
|
164
|
-
|
|
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
|
+
}
|
|
165
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
|
|
166
352
|
}
|
|
167
353
|
}
|
|
168
354
|
|
package/src/remuxer/h264.js
CHANGED
|
@@ -42,18 +42,27 @@ export class H264Remuxer extends BaseRemuxer {
|
|
|
42
42
|
this.pendingUnits = {};
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
feed(data, duration, compositionTimeOffset) {
|
|
45
|
+
feed(data, duration, compositionTimeOffset, isLastFrameComplete = false) {
|
|
46
46
|
let slices = [];
|
|
47
47
|
let left;
|
|
48
48
|
data = appendByteArray(this.remainingData, data);
|
|
49
49
|
[slices, left] = H264Parser.extractNALu(data);
|
|
50
|
-
|
|
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
|
+
}
|
|
51
60
|
|
|
52
61
|
if (slices.length > 0) {
|
|
53
62
|
this.remux(this.getVideoFrames(slices, duration, compositionTimeOffset));
|
|
54
63
|
return true;
|
|
55
64
|
} else {
|
|
56
|
-
debug.
|
|
65
|
+
debug.log('Failed to extract any NAL units from video data:', left);
|
|
57
66
|
this.dispatch('outOfData');
|
|
58
67
|
return false;
|
|
59
68
|
}
|
|
@@ -76,6 +85,8 @@ export class H264Remuxer extends BaseRemuxer {
|
|
|
76
85
|
for (let nalu of nalus) {
|
|
77
86
|
let unit = new NALU264(nalu);
|
|
78
87
|
|
|
88
|
+
if (!this.parseNAL(unit)) continue;
|
|
89
|
+
|
|
79
90
|
// frame boundary detection
|
|
80
91
|
if (units.length && vcl && (unit.isFirstSlice || !unit.isVCL)) {
|
|
81
92
|
frames.push({
|
|
@@ -134,18 +145,11 @@ export class H264Remuxer extends BaseRemuxer {
|
|
|
134
145
|
|
|
135
146
|
remux(frames) {
|
|
136
147
|
for (let frame of frames) {
|
|
137
|
-
let
|
|
138
|
-
|
|
139
|
-
for (let unit of frame.units) {
|
|
140
|
-
if (this.parseNAL(unit)) {
|
|
141
|
-
units.push(unit);
|
|
142
|
-
size += unit.getSize();
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
if (units.length > 0 && this.readyToDecode) {
|
|
148
|
+
let size = frame.units.reduce((acc, cur) => acc + cur.getSize(), 0);
|
|
149
|
+
if (frame.units.length > 0 && this.readyToDecode) {
|
|
146
150
|
this.mp4track.len += size;
|
|
147
151
|
this.samples.push({
|
|
148
|
-
units: units,
|
|
152
|
+
units: frame.units,
|
|
149
153
|
size: size,
|
|
150
154
|
keyFrame: frame.keyFrame,
|
|
151
155
|
duration: frame.duration,
|
package/src/remuxer/h265.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as debug from '../util/debug';
|
|
2
2
|
import { H265Parser, NALU265 } from '../parsers/h265.js';
|
|
3
3
|
import { BaseRemuxer } from './base.js';
|
|
4
|
-
import { appendByteArray } from '../util/utils.js';
|
|
4
|
+
import { appendByteArray, reverseBits, removeTrailingDotZero } from '../util/utils.js';
|
|
5
5
|
|
|
6
6
|
export class H265Remuxer extends BaseRemuxer {
|
|
7
7
|
|
|
@@ -46,18 +46,27 @@ export class H265Remuxer extends BaseRemuxer {
|
|
|
46
46
|
this.pendingUnits = {};
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
feed(data, duration, compositionTimeOffset) {
|
|
49
|
+
feed(data, duration, compositionTimeOffset, isLastFrameComplete = false) {
|
|
50
50
|
let slices = [];
|
|
51
51
|
let left;
|
|
52
52
|
data = appendByteArray(this.remainingData, data);
|
|
53
53
|
[slices, left] = H265Parser.extractNALu(data);
|
|
54
|
-
|
|
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
|
+
}
|
|
55
64
|
|
|
56
65
|
if (slices.length > 0) {
|
|
57
66
|
this.remux(this.getVideoFrames(slices, duration, compositionTimeOffset));
|
|
58
67
|
return true;
|
|
59
68
|
} else {
|
|
60
|
-
debug.
|
|
69
|
+
debug.log('Failed to extract any NAL units from video data:', left);
|
|
61
70
|
this.dispatch('outOfData');
|
|
62
71
|
return false;
|
|
63
72
|
}
|
|
@@ -80,6 +89,8 @@ export class H265Remuxer extends BaseRemuxer {
|
|
|
80
89
|
|
|
81
90
|
for (let nalu of nalus) {
|
|
82
91
|
let unit = new NALU265(nalu);
|
|
92
|
+
|
|
93
|
+
if (!this.parseNAL(unit)) continue;
|
|
83
94
|
|
|
84
95
|
// frame boundary detection
|
|
85
96
|
if (units.length && vcl && (unit.isFirstSlice || !unit.isVCL)) {
|
|
@@ -139,18 +150,11 @@ export class H265Remuxer extends BaseRemuxer {
|
|
|
139
150
|
|
|
140
151
|
remux(frames) {
|
|
141
152
|
for (let frame of frames) {
|
|
142
|
-
let
|
|
143
|
-
|
|
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) {
|
|
153
|
+
let size = frame.units.reduce((acc, cur) => acc + cur.getSize(), 0);
|
|
154
|
+
if (frame.units.length > 0 && this.readyToDecode) {
|
|
151
155
|
this.mp4track.len += size;
|
|
152
156
|
this.samples.push({
|
|
153
|
-
units: units,
|
|
157
|
+
units: frame.units,
|
|
154
158
|
size: size,
|
|
155
159
|
keyFrame: frame.keyFrame,
|
|
156
160
|
duration: frame.duration,
|
|
@@ -217,9 +221,12 @@ export class H265Remuxer extends BaseRemuxer {
|
|
|
217
221
|
this.mp4track.width = config.width;
|
|
218
222
|
this.mp4track.height = config.height;
|
|
219
223
|
|
|
220
|
-
this.mp4track.codec =
|
|
221
|
-
+
|
|
222
|
-
+
|
|
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());
|
|
223
230
|
|
|
224
231
|
this.mp4track.hvcC = {
|
|
225
232
|
profile_space: config.profile_space,
|
package/src/util/debug.js
CHANGED
package/src/util/utils.js
CHANGED
|
@@ -22,3 +22,23 @@ export function secToTime(sec) {
|
|
|
22
22
|
result += (minutes < 10 ? '0' + minutes : minutes) + ':' + (seconds < 10 ? '0' + seconds : seconds);
|
|
23
23
|
return result;
|
|
24
24
|
}
|
|
25
|
+
|
|
26
|
+
export function reverseBits(n) {
|
|
27
|
+
var result = 0;
|
|
28
|
+
for (var i = 0; i < 32; i++)
|
|
29
|
+
{
|
|
30
|
+
// Shift result left to make room
|
|
31
|
+
result <<= 1;
|
|
32
|
+
// Add the least significant bit of n
|
|
33
|
+
result |= (n & 1);
|
|
34
|
+
// Shift n right to process the next bit
|
|
35
|
+
n >>>= 1;
|
|
36
|
+
}
|
|
37
|
+
// Ensure result is treated as unsigned 32-bit
|
|
38
|
+
return result >>> 0;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function removeTrailingDotZero(input) {
|
|
42
|
+
// Use regex to strip all trailing ".0" sequences
|
|
43
|
+
return input.replace(/(?:\.0)+$/, '');
|
|
44
|
+
}
|