@yume-chan/media-codec 3.0.0-beta.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/src/format.ts ADDED
@@ -0,0 +1,53 @@
1
+ export function hexDigits(value: number) {
2
+ if (value % 1 !== 0) {
3
+ // This also checks NaN and Infinity
4
+ throw new Error("Value must be an integer");
5
+ }
6
+
7
+ if (value < 0) {
8
+ throw new Error("Value must be positive");
9
+ }
10
+
11
+ return value.toString(16).toUpperCase();
12
+ }
13
+
14
+ export function hexTwoDigits(value: number) {
15
+ if (value % 1 !== 0) {
16
+ // This also checks NaN and Infinity
17
+ throw new Error("Value must be an integer");
18
+ }
19
+
20
+ if (value < 0) {
21
+ throw new Error("Value must be positive");
22
+ }
23
+
24
+ if (value >= 256) {
25
+ throw new Error("Value must be less than 256");
26
+ }
27
+
28
+ // Small optimization
29
+ if (value < 16) {
30
+ return "0" + value.toString(16).toUpperCase();
31
+ }
32
+ return value.toString(16).toUpperCase();
33
+ }
34
+
35
+ export function decimalTwoDigits(value: number) {
36
+ if (value % 1 !== 0) {
37
+ // This also checks NaN and Infinity
38
+ throw new Error("Value must be an integer");
39
+ }
40
+
41
+ if (value < 0) {
42
+ throw new Error("Value must be positive");
43
+ }
44
+
45
+ if (value >= 100) {
46
+ throw new Error("Value must be less than 256");
47
+ }
48
+
49
+ if (value < 10) {
50
+ return "0" + value.toString(10);
51
+ }
52
+ return value.toString(10);
53
+ }
package/src/h264.ts ADDED
@@ -0,0 +1,306 @@
1
+ // cspell: ignore golomb
2
+ // cspell: ignore qpprime
3
+ // cspell: ignore colour
4
+
5
+ import { hexTwoDigits } from "./format.js";
6
+ import { NaluSodbBitReader, annexBSplitNalu } from "./nalu.js";
7
+
8
+ // H.264 has two standards: ITU-T H.264 and ISO/IEC 14496-10
9
+ // they have the same content, and refer themselves as "H.264".
10
+ // The name "AVC" (Advanced Video Coding) is only used in ISO spec name,
11
+ // and other ISO specs referring to H.264.
12
+ // Because this module parses H.264 Annex B format,
13
+ // it's named "h264" instead of "avc".
14
+
15
+ // 7.3.2.1.1 Sequence parameter set data syntax
16
+ // Variable names in this method uses the snake_case convention as in the spec for easier referencing.
17
+ export function parseSequenceParameterSet(nalu: Uint8Array) {
18
+ const reader = new NaluSodbBitReader(nalu);
19
+ if (reader.next() !== 0) {
20
+ throw new Error("Invalid data");
21
+ }
22
+
23
+ const nal_ref_idc = reader.read(2);
24
+ const nal_unit_type = reader.read(5);
25
+
26
+ if (nal_unit_type !== 7) {
27
+ throw new Error("Invalid data");
28
+ }
29
+
30
+ if (nal_ref_idc === 0) {
31
+ throw new Error("Invalid data");
32
+ }
33
+
34
+ const profile_idc = reader.read(8);
35
+ const constraint_set = reader.peek(8);
36
+
37
+ const constraint_set0_flag = !!reader.next();
38
+ const constraint_set1_flag = !!reader.next();
39
+ const constraint_set2_flag = !!reader.next();
40
+ const constraint_set3_flag = !!reader.next();
41
+ const constraint_set4_flag = !!reader.next();
42
+ const constraint_set5_flag = !!reader.next();
43
+
44
+ // reserved_zero_2bits
45
+ if (reader.read(2) !== 0) {
46
+ throw new Error("Invalid data");
47
+ }
48
+
49
+ const level_idc = reader.read(8);
50
+ const seq_parameter_set_id = reader.decodeExponentialGolombNumber();
51
+
52
+ if (
53
+ profile_idc === 100 ||
54
+ profile_idc === 110 ||
55
+ profile_idc === 122 ||
56
+ profile_idc === 244 ||
57
+ profile_idc === 44 ||
58
+ profile_idc === 83 ||
59
+ profile_idc === 86 ||
60
+ profile_idc === 118 ||
61
+ profile_idc === 128 ||
62
+ profile_idc === 138 ||
63
+ profile_idc === 139 ||
64
+ profile_idc === 134
65
+ ) {
66
+ const chroma_format_idc = reader.decodeExponentialGolombNumber();
67
+ if (chroma_format_idc === 3) {
68
+ // separate_colour_plane_flag
69
+ reader.next();
70
+ }
71
+
72
+ // bit_depth_luma_minus8
73
+ reader.decodeExponentialGolombNumber();
74
+ // bit_depth_chroma_minus8
75
+ reader.decodeExponentialGolombNumber();
76
+
77
+ // qpprime_y_zero_transform_bypass_flag
78
+ reader.next();
79
+
80
+ const seq_scaling_matrix_present_flag = !!reader.next();
81
+ if (seq_scaling_matrix_present_flag) {
82
+ const seq_scaling_list_present_flag: boolean[] = [];
83
+ for (let i = 0; i < (chroma_format_idc !== 3 ? 8 : 12); i += 1) {
84
+ seq_scaling_list_present_flag[i] = !!reader.next();
85
+ if (seq_scaling_list_present_flag[i])
86
+ if (i < 6) {
87
+ // TODO
88
+ // scaling_list( ScalingList4x4[ i ], 16,
89
+ // UseDefaultScalingMatrix4x4Flag[ i ])
90
+ } else {
91
+ // TODO
92
+ // scaling_list( ScalingList8x8[ i − 6 ], 64,
93
+ // UseDefaultScalingMatrix8x8Flag[ i − 6 ] )
94
+ }
95
+ }
96
+ }
97
+ }
98
+
99
+ // log2_max_frame_num_minus4
100
+ reader.decodeExponentialGolombNumber();
101
+ const pic_order_cnt_type = reader.decodeExponentialGolombNumber();
102
+ if (pic_order_cnt_type === 0) {
103
+ // log2_max_pic_order_cnt_lsb_minus4
104
+ reader.decodeExponentialGolombNumber();
105
+ } else if (pic_order_cnt_type === 1) {
106
+ // delta_pic_order_always_zero_flag
107
+ reader.next();
108
+ // offset_for_non_ref_pic
109
+ reader.decodeExponentialGolombNumber();
110
+ // offset_for_top_to_bottom_field
111
+ reader.decodeExponentialGolombNumber();
112
+ const num_ref_frames_in_pic_order_cnt_cycle =
113
+ reader.decodeExponentialGolombNumber();
114
+ const offset_for_ref_frame: number[] = [];
115
+ for (let i = 0; i < num_ref_frames_in_pic_order_cnt_cycle; i += 1) {
116
+ offset_for_ref_frame[i] = reader.decodeExponentialGolombNumber();
117
+ }
118
+ }
119
+
120
+ // max_num_ref_frames
121
+ reader.decodeExponentialGolombNumber();
122
+ // gaps_in_frame_num_value_allowed_flag
123
+ reader.next();
124
+ const pic_width_in_mbs_minus1 = reader.decodeExponentialGolombNumber();
125
+ const pic_height_in_map_units_minus1 =
126
+ reader.decodeExponentialGolombNumber();
127
+
128
+ const frame_mbs_only_flag = reader.next();
129
+ if (!frame_mbs_only_flag) {
130
+ // mb_adaptive_frame_field_flag
131
+ reader.next();
132
+ }
133
+
134
+ // direct_8x8_inference_flag
135
+ reader.next();
136
+
137
+ const frame_cropping_flag = !!reader.next();
138
+ let frame_crop_left_offset: number;
139
+ let frame_crop_right_offset: number;
140
+ let frame_crop_top_offset: number;
141
+ let frame_crop_bottom_offset: number;
142
+ if (frame_cropping_flag) {
143
+ frame_crop_left_offset = reader.decodeExponentialGolombNumber();
144
+ frame_crop_right_offset = reader.decodeExponentialGolombNumber();
145
+ frame_crop_top_offset = reader.decodeExponentialGolombNumber();
146
+ frame_crop_bottom_offset = reader.decodeExponentialGolombNumber();
147
+ } else {
148
+ frame_crop_left_offset = 0;
149
+ frame_crop_right_offset = 0;
150
+ frame_crop_top_offset = 0;
151
+ frame_crop_bottom_offset = 0;
152
+ }
153
+
154
+ const vui_parameters_present_flag = !!reader.next();
155
+ if (vui_parameters_present_flag) {
156
+ // TODO
157
+ // vui_parameters( )
158
+ }
159
+
160
+ return {
161
+ profile_idc,
162
+ constraint_set,
163
+ constraint_set0_flag,
164
+ constraint_set1_flag,
165
+ constraint_set2_flag,
166
+ constraint_set3_flag,
167
+ constraint_set4_flag,
168
+ constraint_set5_flag,
169
+ level_idc,
170
+ seq_parameter_set_id,
171
+ pic_width_in_mbs_minus1,
172
+ pic_height_in_map_units_minus1,
173
+ frame_mbs_only_flag,
174
+ frame_cropping_flag,
175
+ frame_crop_left_offset,
176
+ frame_crop_right_offset,
177
+ frame_crop_top_offset,
178
+ frame_crop_bottom_offset,
179
+ };
180
+ }
181
+
182
+ /**
183
+ * Find Sequence Parameter Set (SPS) and Picture Parameter Set (PPS)
184
+ * from H.264 Annex B formatted data.
185
+ */
186
+ export function searchConfiguration(buffer: Uint8Array) {
187
+ let sequenceParameterSet: Uint8Array | undefined;
188
+ let pictureParameterSet: Uint8Array | undefined;
189
+
190
+ for (const nalu of annexBSplitNalu(buffer)) {
191
+ const naluType = nalu[0]! & 0x1f;
192
+ switch (naluType) {
193
+ case 7: // Sequence parameter set
194
+ sequenceParameterSet = nalu;
195
+ if (pictureParameterSet) {
196
+ return {
197
+ sequenceParameterSet,
198
+ pictureParameterSet,
199
+ };
200
+ }
201
+ break;
202
+ case 8: // Picture parameter set
203
+ pictureParameterSet = nalu;
204
+ if (sequenceParameterSet) {
205
+ return {
206
+ sequenceParameterSet,
207
+ pictureParameterSet,
208
+ };
209
+ }
210
+ break;
211
+ default:
212
+ // ignore
213
+ break;
214
+ }
215
+ }
216
+
217
+ throw new Error("Invalid data");
218
+ }
219
+
220
+ export function containsKeyFrame(buffer: Uint8Array) {
221
+ for (const nalu of annexBSplitNalu(buffer)) {
222
+ const naluType = nalu[0]! & 0x1f;
223
+ if (naluType === 5) {
224
+ // Coded slice of an IDR picture
225
+ return true;
226
+ }
227
+ }
228
+ return false;
229
+ }
230
+
231
+ export interface Configuration {
232
+ pictureParameterSet: Uint8Array;
233
+ sequenceParameterSet: Uint8Array;
234
+
235
+ profileIndex: number;
236
+ constraintSet: number;
237
+ levelIndex: number;
238
+
239
+ encodedWidth: number;
240
+ encodedHeight: number;
241
+
242
+ cropLeft: number;
243
+ cropRight: number;
244
+ cropTop: number;
245
+ cropBottom: number;
246
+ croppedWidth: number;
247
+ croppedHeight: number;
248
+ }
249
+
250
+ export function parseConfiguration(data: Uint8Array): Configuration {
251
+ const { sequenceParameterSet, pictureParameterSet } =
252
+ searchConfiguration(data);
253
+
254
+ const {
255
+ profile_idc: profileIndex,
256
+ constraint_set: constraintSet,
257
+ level_idc: levelIndex,
258
+ pic_width_in_mbs_minus1,
259
+ pic_height_in_map_units_minus1,
260
+ frame_mbs_only_flag,
261
+ frame_crop_left_offset,
262
+ frame_crop_right_offset,
263
+ frame_crop_top_offset,
264
+ frame_crop_bottom_offset,
265
+ } = parseSequenceParameterSet(sequenceParameterSet);
266
+
267
+ const encodedWidth = (pic_width_in_mbs_minus1 + 1) * 16;
268
+ const encodedHeight =
269
+ (pic_height_in_map_units_minus1 + 1) * (2 - frame_mbs_only_flag) * 16;
270
+ const cropLeft = frame_crop_left_offset * 2;
271
+ const cropRight = frame_crop_right_offset * 2;
272
+ const cropTop = frame_crop_top_offset * 2;
273
+ const cropBottom = frame_crop_bottom_offset * 2;
274
+
275
+ const croppedWidth = encodedWidth - cropLeft - cropRight;
276
+ const croppedHeight = encodedHeight - cropTop - cropBottom;
277
+
278
+ return {
279
+ pictureParameterSet,
280
+ sequenceParameterSet,
281
+ profileIndex,
282
+ constraintSet,
283
+ levelIndex,
284
+ encodedWidth,
285
+ encodedHeight,
286
+ cropLeft,
287
+ cropRight,
288
+ cropTop,
289
+ cropBottom,
290
+ croppedWidth,
291
+ croppedHeight,
292
+ };
293
+ }
294
+
295
+ export function toCodecString(configuration: Configuration) {
296
+ const { profileIndex, constraintSet, levelIndex } = configuration;
297
+
298
+ // https://www.rfc-editor.org/rfc/rfc6381#section-3.3
299
+ // ISO Base Media File Format Name Space
300
+ return (
301
+ "avc1." +
302
+ hexTwoDigits(profileIndex) +
303
+ hexTwoDigits(constraintSet) +
304
+ hexTwoDigits(levelIndex)
305
+ );
306
+ }