@invintusmedia/tomp4 1.4.0 → 1.4.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/dist/tomp4.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * toMp4.js v1.4.0
2
+ * toMp4.js v1.4.1
3
3
  * Convert MPEG-TS and fMP4 to standard MP4
4
4
  * https://github.com/TVWIT/toMp4.js
5
5
  * MIT License
@@ -1186,7 +1186,7 @@
1186
1186
  toMp4.isMpegTs = isMpegTs;
1187
1187
  toMp4.isFmp4 = isFmp4;
1188
1188
  toMp4.isStandardMp4 = isStandardMp4;
1189
- toMp4.version = '1.4.0';
1189
+ toMp4.version = '1.4.1';
1190
1190
 
1191
1191
  return toMp4;
1192
1192
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@invintusmedia/tomp4",
3
- "version": "1.4.0",
3
+ "version": "1.4.1",
4
4
  "description": "Convert MPEG-TS, fMP4, and HLS streams to MP4 with clipping support - pure JavaScript, zero dependencies",
5
5
  "main": "src/index.js",
6
6
  "module": "src/index.js",
@@ -0,0 +1,628 @@
1
+ /**
2
+ * H.264 CAVLC (Context-Adaptive Variable-Length Coding) Encoding Tables
3
+ *
4
+ * Complete VLC tables from ITU-T H.264 specification:
5
+ * - Table 9-5: coeff_token VLC tables (5 nC ranges + ChromaDC variants)
6
+ * - Table 9-7: total_zeros for 4x4 blocks (TotalCoeff 1-15)
7
+ * - Table 9-8: total_zeros for chroma DC 2x2 (TotalCoeff 1-3)
8
+ * - Table 9-9: total_zeros for chroma DC 2x4 (TotalCoeff 1-7)
9
+ * - Table 9-10: run_before (zerosLeft 1-7+)
10
+ *
11
+ * Each entry is [bits, length] where:
12
+ * - bits: the VLC codeword value (MSB-first integer)
13
+ * - length: number of bits in the codeword
14
+ *
15
+ * Source: Verified against x264 (common/tables.c) and FFmpeg (libavcodec/h264_cavlc.c).
16
+ *
17
+ * @module codecs/h264-cavlc-tables
18
+ */
19
+
20
+ // ══════════════════════════════════════════════════════════════════════════════
21
+ // coeff_token for TotalCoeff=0 (no coefficients)
22
+ // Indexed by nC table index (0-5):
23
+ // 0: nC 0-1, 1: nC 2-3, 2: nC 4-7, 3: nC 8+, 4: nC=-1 (ChromaDC 2x2), 5: nC=-2 (ChromaDC 2x4)
24
+ // Table 9-5 in the spec
25
+ // ══════════════════════════════════════════════════════════════════════════════
26
+
27
+ export const coeff0Token = [
28
+ [0x01, 1], // nC 0-1: "1"
29
+ [0x03, 2], // nC 2-3: "11"
30
+ [0x0f, 4], // nC 4-7: "1111"
31
+ [0x03, 6], // nC 8+: "000011"
32
+ [0x01, 2], // ChromaDC 2x2: "01"
33
+ [0x01, 1], // ChromaDC 2x4: "1"
34
+ ];
35
+
36
+ // ══════════════════════════════════════════════════════════════════════════════
37
+ // coeff_token VLC tables
38
+ // coeffTokenTable[nC_table][totalCoeff-1][trailingOnes] = [bits, length]
39
+ //
40
+ // nC table index mapping:
41
+ // 0: nC = 0,1 (Table 9-5a)
42
+ // 1: nC = 2,3 (Table 9-5b)
43
+ // 2: nC = 4,5,6,7 (Table 9-5c)
44
+ // 3: nC >= 8 (Table 9-5d) -- fixed 6-bit codes
45
+ // 4: nC = -1 (Chroma DC 2x2, Table 9-5e)
46
+ // 5: nC = -2 (Chroma DC 2x4/4x2, for 4:2:2)
47
+ //
48
+ // trailingOnes: 0..3 (but only 0..min(totalCoeff,3) are valid)
49
+ // Invalid combinations have [0, 0] placeholder
50
+ // ══════════════════════════════════════════════════════════════════════════════
51
+
52
+ export const coeffTokenTable = [
53
+ // ── nC = 0,1 (table 0) ──
54
+ [
55
+ // totalCoeff=1
56
+ [[0x05, 6], [0x01, 2], [0, 0], [0, 0]],
57
+ // totalCoeff=2
58
+ [[0x07, 8], [0x04, 6], [0x01, 3], [0, 0]],
59
+ // totalCoeff=3
60
+ [[0x07, 9], [0x06, 8], [0x05, 7], [0x03, 5]],
61
+ // totalCoeff=4
62
+ [[0x07, 10], [0x06, 9], [0x05, 8], [0x03, 6]],
63
+ // totalCoeff=5
64
+ [[0x07, 11], [0x06, 10], [0x05, 9], [0x04, 7]],
65
+ // totalCoeff=6
66
+ [[0x0f, 13], [0x06, 11], [0x05, 10], [0x04, 8]],
67
+ // totalCoeff=7
68
+ [[0x0b, 13], [0x0e, 13], [0x05, 11], [0x04, 9]],
69
+ // totalCoeff=8
70
+ [[0x08, 13], [0x0a, 13], [0x0d, 13], [0x04, 10]],
71
+ // totalCoeff=9
72
+ [[0x0f, 14], [0x0e, 14], [0x09, 13], [0x04, 11]],
73
+ // totalCoeff=10
74
+ [[0x0b, 14], [0x0a, 14], [0x0d, 14], [0x0c, 13]],
75
+ // totalCoeff=11
76
+ [[0x0f, 15], [0x0e, 15], [0x09, 14], [0x0c, 14]],
77
+ // totalCoeff=12
78
+ [[0x0b, 15], [0x0a, 15], [0x0d, 15], [0x08, 14]],
79
+ // totalCoeff=13
80
+ [[0x0f, 16], [0x01, 15], [0x09, 15], [0x0c, 15]],
81
+ // totalCoeff=14
82
+ [[0x0b, 16], [0x0e, 16], [0x0d, 16], [0x08, 15]],
83
+ // totalCoeff=15
84
+ [[0x07, 16], [0x0a, 16], [0x09, 16], [0x0c, 16]],
85
+ // totalCoeff=16
86
+ [[0x04, 16], [0x06, 16], [0x05, 16], [0x08, 16]],
87
+ ],
88
+
89
+ // ── nC = 2,3 (table 1) ──
90
+ [
91
+ // totalCoeff=1
92
+ [[0x0b, 6], [0x02, 2], [0, 0], [0, 0]],
93
+ // totalCoeff=2
94
+ [[0x07, 6], [0x07, 5], [0x03, 3], [0, 0]],
95
+ // totalCoeff=3
96
+ [[0x07, 7], [0x0a, 6], [0x09, 6], [0x05, 4]],
97
+ // totalCoeff=4
98
+ [[0x07, 8], [0x06, 6], [0x05, 6], [0x04, 4]],
99
+ // totalCoeff=5
100
+ [[0x04, 8], [0x06, 7], [0x05, 7], [0x06, 5]],
101
+ // totalCoeff=6
102
+ [[0x07, 9], [0x06, 8], [0x05, 8], [0x08, 6]],
103
+ // totalCoeff=7
104
+ [[0x0f, 11], [0x06, 9], [0x05, 9], [0x04, 6]],
105
+ // totalCoeff=8
106
+ [[0x0b, 11], [0x0e, 11], [0x0d, 11], [0x04, 7]],
107
+ // totalCoeff=9
108
+ [[0x0f, 12], [0x0a, 11], [0x09, 11], [0x04, 9]],
109
+ // totalCoeff=10
110
+ [[0x0b, 12], [0x0e, 12], [0x0d, 12], [0x0c, 11]],
111
+ // totalCoeff=11
112
+ [[0x08, 12], [0x0a, 12], [0x09, 12], [0x08, 11]],
113
+ // totalCoeff=12
114
+ [[0x0f, 13], [0x0e, 13], [0x0d, 13], [0x0c, 12]],
115
+ // totalCoeff=13
116
+ [[0x0b, 13], [0x0a, 13], [0x09, 13], [0x0c, 13]],
117
+ // totalCoeff=14
118
+ [[0x07, 13], [0x0b, 14], [0x06, 13], [0x08, 13]],
119
+ // totalCoeff=15
120
+ [[0x09, 14], [0x08, 14], [0x0a, 14], [0x01, 13]],
121
+ // totalCoeff=16
122
+ [[0x07, 14], [0x06, 14], [0x05, 14], [0x04, 14]],
123
+ ],
124
+
125
+ // ── nC = 4,5,6,7 (table 2) ──
126
+ [
127
+ // totalCoeff=1
128
+ [[0x0f, 6], [0x0e, 4], [0, 0], [0, 0]],
129
+ // totalCoeff=2
130
+ [[0x0b, 6], [0x0f, 5], [0x0d, 4], [0, 0]],
131
+ // totalCoeff=3
132
+ [[0x08, 6], [0x0c, 5], [0x0e, 5], [0x0c, 4]],
133
+ // totalCoeff=4
134
+ [[0x0f, 7], [0x0a, 5], [0x0b, 5], [0x0b, 4]],
135
+ // totalCoeff=5
136
+ [[0x0b, 7], [0x08, 5], [0x09, 5], [0x0a, 4]],
137
+ // totalCoeff=6
138
+ [[0x09, 7], [0x0e, 6], [0x0d, 6], [0x09, 4]],
139
+ // totalCoeff=7
140
+ [[0x08, 7], [0x0a, 6], [0x09, 6], [0x08, 4]],
141
+ // totalCoeff=8
142
+ [[0x0f, 8], [0x0e, 7], [0x0d, 7], [0x0d, 5]],
143
+ // totalCoeff=9
144
+ [[0x0b, 8], [0x0e, 8], [0x0a, 7], [0x0c, 6]],
145
+ // totalCoeff=10
146
+ [[0x0f, 9], [0x0a, 8], [0x0d, 8], [0x0c, 7]],
147
+ // totalCoeff=11
148
+ [[0x0b, 9], [0x0e, 9], [0x09, 8], [0x0c, 8]],
149
+ // totalCoeff=12
150
+ [[0x08, 9], [0x0a, 9], [0x0d, 9], [0x08, 8]],
151
+ // totalCoeff=13
152
+ [[0x0d, 10], [0x07, 9], [0x09, 9], [0x0c, 9]],
153
+ // totalCoeff=14
154
+ [[0x09, 10], [0x0c, 10], [0x0b, 10], [0x0a, 10]],
155
+ // totalCoeff=15
156
+ [[0x05, 10], [0x08, 10], [0x07, 10], [0x06, 10]],
157
+ // totalCoeff=16
158
+ [[0x01, 10], [0x04, 10], [0x03, 10], [0x02, 10]],
159
+ ],
160
+
161
+ // ── nC >= 8 (table 3) ── fixed-length 6-bit codes
162
+ [
163
+ // totalCoeff=1
164
+ [[0x00, 6], [0x01, 6], [0, 0], [0, 0]],
165
+ // totalCoeff=2
166
+ [[0x04, 6], [0x05, 6], [0x06, 6], [0, 0]],
167
+ // totalCoeff=3
168
+ [[0x08, 6], [0x09, 6], [0x0a, 6], [0x0b, 6]],
169
+ // totalCoeff=4
170
+ [[0x0c, 6], [0x0d, 6], [0x0e, 6], [0x0f, 6]],
171
+ // totalCoeff=5
172
+ [[0x10, 6], [0x11, 6], [0x12, 6], [0x13, 6]],
173
+ // totalCoeff=6
174
+ [[0x14, 6], [0x15, 6], [0x16, 6], [0x17, 6]],
175
+ // totalCoeff=7
176
+ [[0x18, 6], [0x19, 6], [0x1a, 6], [0x1b, 6]],
177
+ // totalCoeff=8
178
+ [[0x1c, 6], [0x1d, 6], [0x1e, 6], [0x1f, 6]],
179
+ // totalCoeff=9
180
+ [[0x20, 6], [0x21, 6], [0x22, 6], [0x23, 6]],
181
+ // totalCoeff=10
182
+ [[0x24, 6], [0x25, 6], [0x26, 6], [0x27, 6]],
183
+ // totalCoeff=11
184
+ [[0x28, 6], [0x29, 6], [0x2a, 6], [0x2b, 6]],
185
+ // totalCoeff=12
186
+ [[0x2c, 6], [0x2d, 6], [0x2e, 6], [0x2f, 6]],
187
+ // totalCoeff=13
188
+ [[0x30, 6], [0x31, 6], [0x32, 6], [0x33, 6]],
189
+ // totalCoeff=14
190
+ [[0x34, 6], [0x35, 6], [0x36, 6], [0x37, 6]],
191
+ // totalCoeff=15
192
+ [[0x38, 6], [0x39, 6], [0x3a, 6], [0x3b, 6]],
193
+ // totalCoeff=16
194
+ [[0x3c, 6], [0x3d, 6], [0x3e, 6], [0x3f, 6]],
195
+ ],
196
+
197
+ // ── nC = -1: Chroma DC 2x2 (4:2:0) (table 4) ── maxCoeff=4
198
+ [
199
+ // totalCoeff=1
200
+ [[0x07, 6], [0x01, 1], [0, 0], [0, 0]],
201
+ // totalCoeff=2
202
+ [[0x04, 6], [0x06, 6], [0x01, 3], [0, 0]],
203
+ // totalCoeff=3
204
+ [[0x03, 6], [0x03, 7], [0x02, 7], [0x05, 6]],
205
+ // totalCoeff=4
206
+ [[0x02, 6], [0x03, 8], [0x02, 8], [0x00, 7]],
207
+ ],
208
+
209
+ // ── nC = -2: Chroma DC 2x4 (4:2:2) (table 5) ── maxCoeff=8
210
+ [
211
+ // totalCoeff=1
212
+ [[0x0f, 7], [0x01, 2], [0, 0], [0, 0]],
213
+ // totalCoeff=2
214
+ [[0x0e, 7], [0x0d, 7], [0x01, 3], [0, 0]],
215
+ // totalCoeff=3
216
+ [[0x07, 9], [0x0c, 7], [0x0b, 7], [0x01, 5]],
217
+ // totalCoeff=4
218
+ [[0x06, 9], [0x05, 9], [0x0a, 7], [0x01, 6]],
219
+ // totalCoeff=5
220
+ [[0x07, 10], [0x06, 10], [0x04, 9], [0x09, 7]],
221
+ // totalCoeff=6
222
+ [[0x07, 11], [0x06, 11], [0x05, 10], [0x08, 7]],
223
+ // totalCoeff=7
224
+ [[0x07, 12], [0x06, 12], [0x05, 11], [0x04, 10]],
225
+ // totalCoeff=8
226
+ [[0x07, 13], [0x05, 12], [0x04, 12], [0x04, 11]],
227
+ ],
228
+ ];
229
+
230
+ /**
231
+ * Maps nC (predicted number of non-zero coefficients) to table index.
232
+ * nC < 0: use -1 → index 4 (ChromaDC 2x2) or -2 → index 5 (ChromaDC 2x4)
233
+ * nC 0-1: index 0
234
+ * nC 2-3: index 1
235
+ * nC 4-7: index 2
236
+ * nC >= 8: index 3
237
+ */
238
+ export function nCtoTableIndex(nC) {
239
+ if (nC < 0) return nC === -1 ? 4 : 5;
240
+ if (nC <= 1) return 0;
241
+ if (nC <= 3) return 1;
242
+ if (nC <= 7) return 2;
243
+ return 3;
244
+ }
245
+
246
+ // ══════════════════════════════════════════════════════════════════════════════
247
+ // total_zeros VLC tables (Table 9-7 in the spec)
248
+ // totalZerosTable[totalCoeff-1][totalZeros] = [bits, length]
249
+ // For 4x4 blocks, TotalCoeff = 1..15
250
+ // totalZeros range: 0..(16-TotalCoeff)
251
+ // ══════════════════════════════════════════════════════════════════════════════
252
+
253
+ export const totalZerosTable = [
254
+ // totalCoeff=1: totalZeros 0..15
255
+ [
256
+ [0x01, 1], [0x03, 3], [0x02, 3], [0x03, 4],
257
+ [0x02, 4], [0x03, 5], [0x02, 5], [0x03, 6],
258
+ [0x02, 6], [0x03, 7], [0x02, 7], [0x03, 8],
259
+ [0x02, 8], [0x03, 9], [0x02, 9], [0x01, 9],
260
+ ],
261
+ // totalCoeff=2: totalZeros 0..14
262
+ [
263
+ [0x07, 3], [0x06, 3], [0x05, 3], [0x04, 3],
264
+ [0x03, 3], [0x05, 4], [0x04, 4], [0x03, 4],
265
+ [0x02, 4], [0x03, 5], [0x02, 5], [0x03, 6],
266
+ [0x02, 6], [0x01, 6], [0x00, 6],
267
+ ],
268
+ // totalCoeff=3: totalZeros 0..13
269
+ [
270
+ [0x05, 4], [0x07, 3], [0x06, 3], [0x05, 3],
271
+ [0x04, 4], [0x03, 4], [0x04, 3], [0x03, 3],
272
+ [0x02, 4], [0x03, 5], [0x02, 5], [0x01, 6],
273
+ [0x01, 5], [0x00, 6],
274
+ ],
275
+ // totalCoeff=4: totalZeros 0..12
276
+ [
277
+ [0x03, 5], [0x07, 3], [0x05, 4], [0x04, 4],
278
+ [0x06, 3], [0x05, 3], [0x04, 3], [0x03, 4],
279
+ [0x03, 3], [0x02, 4], [0x02, 5], [0x01, 5],
280
+ [0x00, 5],
281
+ ],
282
+ // totalCoeff=5: totalZeros 0..11
283
+ [
284
+ [0x05, 4], [0x04, 4], [0x03, 4], [0x07, 3],
285
+ [0x06, 3], [0x05, 3], [0x04, 3], [0x03, 3],
286
+ [0x02, 4], [0x01, 5], [0x01, 4], [0x00, 5],
287
+ ],
288
+ // totalCoeff=6: totalZeros 0..10
289
+ [
290
+ [0x01, 6], [0x01, 5], [0x07, 3], [0x06, 3],
291
+ [0x05, 3], [0x04, 3], [0x03, 3], [0x02, 3],
292
+ [0x01, 4], [0x01, 3], [0x00, 6],
293
+ ],
294
+ // totalCoeff=7: totalZeros 0..9
295
+ [
296
+ [0x01, 6], [0x01, 5], [0x05, 3], [0x04, 3],
297
+ [0x03, 3], [0x03, 2], [0x02, 3], [0x01, 4],
298
+ [0x01, 3], [0x00, 6],
299
+ ],
300
+ // totalCoeff=8: totalZeros 0..8
301
+ [
302
+ [0x01, 6], [0x01, 4], [0x01, 5], [0x03, 3],
303
+ [0x03, 2], [0x02, 2], [0x02, 3], [0x01, 3],
304
+ [0x00, 6],
305
+ ],
306
+ // totalCoeff=9: totalZeros 0..7
307
+ [
308
+ [0x01, 6], [0x00, 6], [0x01, 4], [0x03, 2],
309
+ [0x02, 2], [0x01, 3], [0x01, 2], [0x01, 5],
310
+ ],
311
+ // totalCoeff=10: totalZeros 0..6
312
+ [
313
+ [0x01, 5], [0x00, 5], [0x01, 3], [0x03, 2],
314
+ [0x02, 2], [0x01, 2], [0x01, 4],
315
+ ],
316
+ // totalCoeff=11: totalZeros 0..5
317
+ [
318
+ [0x00, 4], [0x01, 4], [0x01, 3], [0x02, 3],
319
+ [0x01, 1], [0x03, 3],
320
+ ],
321
+ // totalCoeff=12: totalZeros 0..4
322
+ [
323
+ [0x00, 4], [0x01, 4], [0x01, 2], [0x01, 1],
324
+ [0x01, 3],
325
+ ],
326
+ // totalCoeff=13: totalZeros 0..3
327
+ [
328
+ [0x00, 3], [0x01, 3], [0x01, 1], [0x01, 2],
329
+ ],
330
+ // totalCoeff=14: totalZeros 0..2
331
+ [
332
+ [0x00, 2], [0x01, 2], [0x01, 1],
333
+ ],
334
+ // totalCoeff=15: totalZeros 0..1
335
+ [
336
+ [0x00, 1], [0x01, 1],
337
+ ],
338
+ ];
339
+
340
+ // ══════════════════════════════════════════════════════════════════════════════
341
+ // total_zeros for Chroma DC 2x2 (4:2:0) — Table 9-8 in the spec
342
+ // totalZerosChromaDC2x2[totalCoeff-1][totalZeros] = [bits, length]
343
+ // maxCoeff = 4, so totalCoeff = 1..3
344
+ // ══════════════════════════════════════════════════════════════════════════════
345
+
346
+ export const totalZerosChromaDC2x2 = [
347
+ // totalCoeff=1: totalZeros 0..3
348
+ [
349
+ [0x01, 1], [0x01, 2], [0x01, 3], [0x00, 3],
350
+ ],
351
+ // totalCoeff=2: totalZeros 0..2
352
+ [
353
+ [0x01, 1], [0x01, 2], [0x00, 2],
354
+ ],
355
+ // totalCoeff=3: totalZeros 0..1
356
+ [
357
+ [0x01, 1], [0x00, 1],
358
+ ],
359
+ ];
360
+
361
+ // ══════════════════════════════════════════════════════════════════════════════
362
+ // total_zeros for Chroma DC 2x4 (4:2:2) — Table 9-9 in the spec
363
+ // totalZerosChromaDC2x4[totalCoeff-1][totalZeros] = [bits, length]
364
+ // maxCoeff = 8, so totalCoeff = 1..7
365
+ // ══════════════════════════════════════════════════════════════════════════════
366
+
367
+ export const totalZerosChromaDC2x4 = [
368
+ // totalCoeff=1: totalZeros 0..7
369
+ [
370
+ [0x01, 1], [0x02, 3], [0x03, 3], [0x02, 4],
371
+ [0x03, 4], [0x01, 4], [0x01, 5], [0x00, 5],
372
+ ],
373
+ // totalCoeff=2: totalZeros 0..6
374
+ [
375
+ [0x00, 3], [0x01, 2], [0x01, 3], [0x04, 3],
376
+ [0x05, 3], [0x06, 3], [0x07, 3],
377
+ ],
378
+ // totalCoeff=3: totalZeros 0..5
379
+ [
380
+ [0x00, 3], [0x01, 3], [0x01, 2], [0x02, 2],
381
+ [0x06, 3], [0x07, 3],
382
+ ],
383
+ // totalCoeff=4: totalZeros 0..4
384
+ [
385
+ [0x06, 3], [0x00, 2], [0x01, 2], [0x02, 2],
386
+ [0x07, 3],
387
+ ],
388
+ // totalCoeff=5: totalZeros 0..3
389
+ [
390
+ [0x00, 2], [0x01, 2], [0x02, 2], [0x03, 2],
391
+ ],
392
+ // totalCoeff=6: totalZeros 0..2
393
+ [
394
+ [0x00, 2], [0x01, 2], [0x01, 1],
395
+ ],
396
+ // totalCoeff=7: totalZeros 0..1
397
+ [
398
+ [0x00, 1], [0x01, 1],
399
+ ],
400
+ ];
401
+
402
+ // ══════════════════════════════════════════════════════════════════════════════
403
+ // run_before VLC tables — Table 9-10 in the spec
404
+ // runBeforeTable[min(zerosLeft,7)-1][runBefore] = [bits, length]
405
+ //
406
+ // zerosLeft=1..6 have individual tables
407
+ // zerosLeft>=7 shares one table (index 6)
408
+ // ══════════════════════════════════════════════════════════════════════════════
409
+
410
+ export const runBeforeTable = [
411
+ // zerosLeft=1: runBefore 0..1
412
+ [
413
+ [0x01, 1], [0x00, 1],
414
+ ],
415
+ // zerosLeft=2: runBefore 0..2
416
+ [
417
+ [0x01, 1], [0x01, 2], [0x00, 2],
418
+ ],
419
+ // zerosLeft=3: runBefore 0..3
420
+ [
421
+ [0x03, 2], [0x02, 2], [0x01, 2], [0x00, 2],
422
+ ],
423
+ // zerosLeft=4: runBefore 0..4
424
+ [
425
+ [0x03, 2], [0x02, 2], [0x01, 2], [0x01, 3], [0x00, 3],
426
+ ],
427
+ // zerosLeft=5: runBefore 0..5
428
+ [
429
+ [0x03, 2], [0x02, 2], [0x03, 3], [0x02, 3], [0x01, 3], [0x00, 3],
430
+ ],
431
+ // zerosLeft=6: runBefore 0..6
432
+ [
433
+ [0x03, 2], [0x00, 3], [0x01, 3], [0x03, 3], [0x02, 3], [0x05, 3], [0x04, 3],
434
+ ],
435
+ // zerosLeft>=7: runBefore 0..14
436
+ [
437
+ [0x07, 3], [0x06, 3], [0x05, 3], [0x04, 3],
438
+ [0x03, 3], [0x02, 3], [0x01, 3], [0x01, 4],
439
+ [0x01, 5], [0x01, 6], [0x01, 7], [0x01, 8],
440
+ [0x01, 9], [0x01, 10], [0x01, 11],
441
+ ],
442
+ ];
443
+
444
+ // ══════════════════════════════════════════════════════════════════════════════
445
+ // Level encoding (coefficient magnitude/sign)
446
+ //
447
+ // The H.264 CAVLC level encoding uses:
448
+ // 1) level_prefix: unary code (N zeros followed by a 1)
449
+ // 2) level_suffix: fixed-length code of length suffixLength
450
+ //
451
+ // The suffixLength starts at 0 (or 1 if totalCoeff>10 && trailingOnes<3)
452
+ // and increases as larger levels are encountered.
453
+ //
454
+ // This function encodes a single coefficient level and returns
455
+ // [bits, length, newSuffixLength].
456
+ // ══════════════════════════════════════════════════════════════════════════════
457
+
458
+ /**
459
+ * Thresholds for incrementing suffixLength.
460
+ * After encoding a level, if |level| > nextSuffix[suffixLength],
461
+ * increment suffixLength.
462
+ * From x264: {0, 3, 6, 12, 24, 48, 0xffff}
463
+ */
464
+ const SUFFIX_THRESHOLD = [0, 3, 6, 12, 24, 48, 0x7fffffff];
465
+
466
+ /**
467
+ * Encode a single coefficient level value using CAVLC level VLC.
468
+ *
469
+ * Algorithm from ITU-T H.264 Section 9.2.2 and verified against
470
+ * x264 (common/vlc.c x264_level_token / encoder/cavlc.c cavlc_block_residual_escape).
471
+ *
472
+ * @param {number} level - Signed coefficient level (non-zero)
473
+ * @param {number} suffixLength - Current suffix length (0-6)
474
+ * @param {boolean} firstAfterTrailing - True if this is the first level after
475
+ * trailing ones and trailingOnes < 3. The spec says to subtract 1 from the
476
+ * magnitude (since we know it must be > 1 in this case).
477
+ * @returns {{ bits: number, length: number, suffixLength: number }}
478
+ */
479
+ export function encodeLevel(level, suffixLength, firstAfterTrailing) {
480
+ const sign = level < 0 ? 1 : 0;
481
+ let absLevel = Math.abs(level);
482
+
483
+ // level_code = 2*|level| - 2 + sign
484
+ // If firstAfterTrailing, the decoded level is offset by 1
485
+ // (since |level| > 1 is guaranteed), so we adjust:
486
+ let levelCode = (absLevel << 1) - 2 + sign;
487
+ if (firstAfterTrailing) {
488
+ levelCode -= 2;
489
+ }
490
+
491
+ let bits, length;
492
+
493
+ const prefix = levelCode >> suffixLength;
494
+
495
+ if (prefix < 14) {
496
+ // Normal case: prefix zeros + 1 + suffix bits
497
+ length = prefix + 1 + suffixLength;
498
+ bits = (1 << suffixLength) | (levelCode & ((1 << suffixLength) - 1));
499
+ } else if (suffixLength === 0 && prefix === 14) {
500
+ // Special case: suffixLength=0, prefix=14 uses 4-bit suffix
501
+ length = 15 + 4; // 19 bits total (14 zeros + 1 + 4 suffix bits)
502
+ bits = (1 << 4) | (levelCode - 14);
503
+ } else if (prefix === 14) {
504
+ // suffixLength > 0, prefix=14: normal encoding still applies
505
+ length = 14 + 1 + suffixLength;
506
+ bits = (1 << suffixLength) | (levelCode & ((1 << suffixLength) - 1));
507
+ } else {
508
+ // prefix >= 15: escape code (High Profile level codes)
509
+ let escapeLevelCode = levelCode;
510
+ escapeLevelCode -= 15 << suffixLength;
511
+ if (suffixLength === 0) {
512
+ escapeLevelCode -= 15;
513
+ }
514
+
515
+ let levelPrefix = 15;
516
+ // For very large values, extend the prefix (High Profile)
517
+ while (escapeLevelCode >= (1 << (levelPrefix - 3))) {
518
+ escapeLevelCode -= 1 << (levelPrefix - 3);
519
+ levelPrefix++;
520
+ }
521
+
522
+ // prefix unary code (levelPrefix zeros + 1) + (levelPrefix-3) suffix bits
523
+ length = levelPrefix + 1 + (levelPrefix - 3);
524
+ bits = (1 << (levelPrefix - 3)) | (escapeLevelCode & ((1 << (levelPrefix - 3)) - 1));
525
+ }
526
+
527
+ // Update suffixLength
528
+ let newSuffixLength = suffixLength;
529
+ if (newSuffixLength === 0) {
530
+ newSuffixLength = 1;
531
+ }
532
+ if (absLevel > SUFFIX_THRESHOLD[newSuffixLength]) {
533
+ newSuffixLength++;
534
+ }
535
+
536
+ return { bits, length, suffixLength: newSuffixLength };
537
+ }
538
+
539
+ /**
540
+ * Encode the full set of non-trailing-one levels for a residual block.
541
+ *
542
+ * @param {number[]} levels - Array of signed level values (non-trailing, in reverse scan order)
543
+ * @param {number} trailingOnes - Number of trailing ones (0-3)
544
+ * @param {number} totalCoeff - Total number of non-zero coefficients
545
+ * @returns {{ bits: number[], lengths: number[] }} - Arrays of VLC codewords
546
+ */
547
+ export function encodeLevels(levels, trailingOnes, totalCoeff) {
548
+ const bits = [];
549
+ const lengths = [];
550
+
551
+ // Initial suffix length
552
+ let suffixLength = (totalCoeff > 10 && trailingOnes < 3) ? 1 : 0;
553
+
554
+ for (let i = 0; i < levels.length; i++) {
555
+ const firstAfterTrailing = (i === 0 && trailingOnes < 3);
556
+ const result = encodeLevel(levels[i], suffixLength, firstAfterTrailing);
557
+ bits.push(result.bits);
558
+ lengths.push(result.length);
559
+ suffixLength = result.suffixLength;
560
+ }
561
+
562
+ return { bits, lengths };
563
+ }
564
+
565
+ // ══════════════════════════════════════════════════════════════════════════════
566
+ // Convenience: encode a full CAVLC residual block
567
+ // ══════════════════════════════════════════════════════════════════════════════
568
+
569
+ /**
570
+ * Write coeff_token to a bitstream writer.
571
+ *
572
+ * @param {number} totalCoeff - Total non-zero coefficients (0-16)
573
+ * @param {number} trailingOnes - Number of trailing +/-1 coefficients (0-3)
574
+ * @param {number} nC - Predicted number of non-zero coefficients
575
+ * @returns {[number, number]} [bits, length] for the coeff_token VLC
576
+ */
577
+ export function getCoeffToken(totalCoeff, trailingOnes, nC) {
578
+ const tableIdx = nCtoTableIndex(nC);
579
+ if (totalCoeff === 0) {
580
+ return coeff0Token[tableIdx];
581
+ }
582
+ return coeffTokenTable[tableIdx][totalCoeff - 1][trailingOnes];
583
+ }
584
+
585
+ /**
586
+ * Get total_zeros VLC for a 4x4 block.
587
+ *
588
+ * @param {number} totalCoeff - 1..15
589
+ * @param {number} totalZeros - 0..(16-totalCoeff)
590
+ * @returns {[number, number]} [bits, length]
591
+ */
592
+ export function getTotalZeros(totalCoeff, totalZeros) {
593
+ return totalZerosTable[totalCoeff - 1][totalZeros];
594
+ }
595
+
596
+ /**
597
+ * Get total_zeros VLC for chroma DC 2x2 block.
598
+ *
599
+ * @param {number} totalCoeff - 1..3
600
+ * @param {number} totalZeros - 0..(4-totalCoeff)
601
+ * @returns {[number, number]} [bits, length]
602
+ */
603
+ export function getTotalZerosChromaDC(totalCoeff, totalZeros) {
604
+ return totalZerosChromaDC2x2[totalCoeff - 1][totalZeros];
605
+ }
606
+
607
+ /**
608
+ * Get total_zeros VLC for chroma DC 2x4 block (4:2:2).
609
+ *
610
+ * @param {number} totalCoeff - 1..7
611
+ * @param {number} totalZeros - 0..(8-totalCoeff)
612
+ * @returns {[number, number]} [bits, length]
613
+ */
614
+ export function getTotalZerosChromaDC422(totalCoeff, totalZeros) {
615
+ return totalZerosChromaDC2x4[totalCoeff - 1][totalZeros];
616
+ }
617
+
618
+ /**
619
+ * Get run_before VLC.
620
+ *
621
+ * @param {number} zerosLeft - Remaining zeros to distribute (>= 1)
622
+ * @param {number} runBefore - Run of zeros before this coefficient
623
+ * @returns {[number, number]} [bits, length]
624
+ */
625
+ export function getRunBefore(zerosLeft, runBefore) {
626
+ const idx = Math.min(zerosLeft, 7) - 1;
627
+ return runBeforeTable[idx][runBefore];
628
+ }
@@ -13,6 +13,7 @@
13
13
 
14
14
  import { forwardDCT4x4, forwardHadamard4x4, forwardHadamard2x2, quantize4x4, clip255 } from './h264-transform.js';
15
15
  import { scanOrder4x4 } from './h264-tables.js';
16
+ import { getCoeffToken, getTotalZeros, getTotalZerosChromaDC, getRunBefore, encodeLevels } from './h264-cavlc-tables.js';
16
17
 
17
18
  // ── Bitstream Writer ──────────────────────────────────────
18
19
 
@@ -382,6 +383,9 @@ export class H264Encoder {
382
383
  const mbType = 1 + predMode + cbpChroma * 4 + (cbpLuma > 0 ? 12 : 0);
383
384
  bs.writeUE(mbType);
384
385
 
386
+ // intra_chroma_pred_mode = 0 (DC) — required for ALL intra MBs
387
+ bs.writeUE(0);
388
+
385
389
  // mb_qp_delta = 0 (first MB uses slice QP)
386
390
  bs.writeSE(0);
387
391
 
@@ -400,178 +404,99 @@ export class H264Encoder {
400
404
  // (The chroma prediction handles the base values)
401
405
  }
402
406
 
403
- // ── CAVLC Block Encoding ────────────────────────────────
407
+ // ── CAVLC Block Encoding (using spec-correct tables) ────
404
408
 
409
+ /**
410
+ * Encode a residual block using CAVLC with the correct VLC tables
411
+ * from the H.264 spec (Tables 9-5 through 9-10).
412
+ *
413
+ * @param {BitstreamWriter} bs - Output bitstream
414
+ * @param {Int32Array} coeffs - Quantized coefficients in scan order
415
+ * @param {number} maxCoeff - Maximum coefficients (16 for 4x4, 15 for AC)
416
+ * @param {number} nC - Predicted number of non-zero coefficients
417
+ */
405
418
  _encodeCavlcBlock(bs, coeffs, maxCoeff, nC) {
406
- // Count trailing ones and total non-zero coefficients
407
- let totalCoeff = 0;
408
- let trailingOnes = 0;
409
- const levels = [];
419
+ // Step 1: Analyze coefficients in reverse scan order
420
+ // Find non-zero coefficients and count trailing ones
421
+ const nonZeroValues = []; // level values in reverse scan order
422
+ const nonZeroPositions = []; // scan positions
410
423
 
411
- // Scan in reverse order to find trailing ones
412
424
  for (let i = maxCoeff - 1; i >= 0; i--) {
413
425
  if (coeffs[i] !== 0) {
414
- totalCoeff++;
415
- if (trailingOnes < 3 && Math.abs(coeffs[i]) === 1 && levels.length === 0) {
416
- trailingOnes++;
417
- }
418
- levels.push(coeffs[i]);
419
- } else if (levels.length > 0) {
420
- levels.push(0); // placeholder for run counting
426
+ nonZeroValues.push(coeffs[i]);
427
+ nonZeroPositions.push(i);
421
428
  }
422
429
  }
423
430
 
424
- // Determine nC range for table selection
425
- const tableIdx = nC < 2 ? 0 : nC < 4 ? 1 : nC < 8 ? 2 : 3;
431
+ const totalCoeff = nonZeroValues.length;
426
432
 
427
- // Write coeff_token
428
- if (totalCoeff === 0 && trailingOnes === 0) {
429
- // Special case: all zeros
430
- const table = CAVLC_COEFF_TOKEN[Math.min(tableIdx, 1)];
431
- const [code, len] = table[0][0]; // (0,0)
432
- bs.writeBits(code, len);
433
- return;
433
+ // Count trailing ones (T1s): consecutive +/-1 at the END of the non-zero list
434
+ // In reverse scan order, these are at the BEGINNING of nonZeroValues
435
+ let trailingOnes = 0;
436
+ for (let i = 0; i < Math.min(totalCoeff, 3); i++) {
437
+ if (Math.abs(nonZeroValues[i]) === 1) trailingOnes++;
438
+ else break;
434
439
  }
435
440
 
436
- // Look up coeff_token
437
- const tc = Math.min(totalCoeff, 16);
438
- const to = Math.min(trailingOnes, 3);
439
- const table = CAVLC_COEFF_TOKEN[Math.min(tableIdx, 1)];
440
- if (table[tc] && table[tc][to]) {
441
- const [code, len] = table[tc][to];
442
- bs.writeBits(code, len);
443
- } else {
444
- // Fallback: write as zero block
445
- const [code, len] = table[0][0];
446
- bs.writeBits(code, len);
447
- return;
448
- }
441
+ // Step 2: Write coeff_token
442
+ const [ctBits, ctLen] = getCoeffToken(totalCoeff, trailingOnes, nC);
443
+ bs.writeBits(ctBits, ctLen);
449
444
 
450
- // Write trailing ones signs
451
- const nonZeroLevels = [];
452
- for (let i = maxCoeff - 1; i >= 0; i--) {
453
- if (coeffs[i] !== 0) nonZeroLevels.push(coeffs[i]);
454
- }
445
+ if (totalCoeff === 0) return;
455
446
 
447
+ // Step 3: Write trailing ones sign flags (1 bit each, 0=positive, 1=negative)
456
448
  for (let i = 0; i < trailingOnes; i++) {
457
- bs.writeBit(nonZeroLevels[i] < 0 ? 1 : 0);
449
+ bs.writeBit(nonZeroValues[i] < 0 ? 1 : 0);
458
450
  }
459
451
 
460
- // Write remaining levels (after trailing ones)
461
- let suffixLength = totalCoeff > 10 && trailingOnes < 3 ? 1 : 0;
462
- for (let i = trailingOnes; i < totalCoeff; i++) {
463
- let level = nonZeroLevels[i];
464
- // Adjust level for trailing ones
465
- if (i === trailingOnes && trailingOnes < 3) {
466
- level = level > 0 ? level - 1 : level + 1;
467
- }
468
- const absLevel = Math.abs(level);
469
- const sign = level < 0 ? 1 : 0;
470
-
471
- // level_prefix: unary code
472
- let levelCode = (absLevel - 1) * 2 + sign;
473
- if (i === trailingOnes && trailingOnes < 3) {
474
- levelCode = absLevel * 2 + sign;
475
- }
476
-
477
- const prefix = levelCode >> suffixLength;
478
- // Write prefix as unary
479
- for (let j = 0; j < Math.min(prefix, 15); j++) bs.writeBit(0);
480
- bs.writeBit(1);
481
-
482
- // Write suffix
483
- if (suffixLength > 0 || prefix >= 14) {
484
- const suffBits = prefix >= 15 ? 12 : suffixLength;
485
- if (suffBits > 0) {
486
- bs.writeBits(levelCode & ((1 << suffBits) - 1), suffBits);
487
- }
452
+ // Step 4: Write remaining levels (non-trailing-ones, still in reverse scan order)
453
+ if (totalCoeff > trailingOnes) {
454
+ const remainingLevels = nonZeroValues.slice(trailingOnes);
455
+ const { bits: levelBits, lengths: levelLens } = encodeLevels(
456
+ remainingLevels, trailingOnes, totalCoeff
457
+ );
458
+ for (let i = 0; i < levelBits.length; i++) {
459
+ // Write prefix (zeros + 1)
460
+ const prefix = levelLens[i] - (levelLens[i] > 0 ? 0 : 0);
461
+ // encodeLevels returns {bits, length} write directly
462
+ bs.writeBits(levelBits[i], levelLens[i]);
488
463
  }
489
-
490
- // Update suffix length
491
- if (suffixLength === 0) suffixLength = 1;
492
- if (absLevel > (3 << (suffixLength - 1))) suffixLength++;
493
464
  }
494
465
 
495
- // Write total_zeros
466
+ // Step 5: Write total_zeros (only if totalCoeff < maxCoeff)
496
467
  if (totalCoeff < maxCoeff) {
497
- let totalZeros = 0;
498
- let lastNonZero = -1;
468
+ // Count total zeros before (and between) the non-zero coefficients
469
+ let lastNonZeroPos = 0;
499
470
  for (let i = maxCoeff - 1; i >= 0; i--) {
500
- if (coeffs[i] !== 0) { lastNonZero = i; break; }
471
+ if (coeffs[i] !== 0) { lastNonZeroPos = i; break; }
501
472
  }
502
- for (let i = 0; i <= lastNonZero; i++) {
473
+ let totalZeros = 0;
474
+ for (let i = 0; i <= lastNonZeroPos; i++) {
503
475
  if (coeffs[i] === 0) totalZeros++;
504
476
  }
505
477
 
506
- // Simplified: write total_zeros using the UE-like VLC (not exactly spec but functional)
507
- // The actual tables are complex; for a working encoder we use a simplified approach
508
- this._writeTotalZeros(bs, totalZeros, totalCoeff, maxCoeff);
509
- }
510
-
511
- // Write run_before for each coefficient (except the last one)
512
- let zerosLeft = 0;
513
- for (let i = maxCoeff - 1; i >= 0; i--) {
514
- if (coeffs[i] === 0) zerosLeft++;
515
- }
516
- // Adjust to only count zeros before the last non-zero
517
- zerosLeft = 0;
518
- let found = 0;
519
- for (let i = 0; i < maxCoeff && found < totalCoeff; i++) {
520
- if (coeffs[i] !== 0) {
521
- found++;
522
- } else if (found < totalCoeff) {
523
- zerosLeft++;
524
- }
525
- }
478
+ const [tzBits, tzLen] = getTotalZeros(totalCoeff, totalZeros);
479
+ bs.writeBits(tzBits, tzLen);
526
480
 
527
- // Run_before encoding (simplified)
528
- let remaining = zerosLeft;
529
- let coeffIdx = 0;
530
- for (let i = maxCoeff - 1; i >= 0 && coeffIdx < totalCoeff - 1; i--) {
531
- if (coeffs[i] !== 0) {
532
- // Count run before this coefficient
481
+ // Step 6: Write run_before for each coefficient (reverse scan order)
482
+ // except the last one (its position is implied)
483
+ let zerosLeft = totalZeros;
484
+ for (let i = 0; i < totalCoeff - 1 && zerosLeft > 0; i++) {
485
+ const pos = nonZeroPositions[i];
486
+ // Count consecutive zeros before this coefficient in scan order
533
487
  let run = 0;
534
- for (let j = i - 1; j >= 0; j--) {
488
+ for (let j = pos - 1; j >= 0; j--) {
535
489
  if (coeffs[j] === 0) run++;
536
490
  else break;
537
491
  }
538
- if (remaining > 0) {
539
- this._writeRunBefore(bs, Math.min(run, remaining), remaining);
540
- remaining -= run;
541
- }
542
- coeffIdx++;
543
- }
544
- }
545
- }
492
+ run = Math.min(run, zerosLeft);
546
493
 
547
- _writeTotalZeros(bs, totalZeros, totalCoeff, maxCoeff) {
548
- // Simplified total_zeros encoding: truncated unary
549
- // This isn't exactly spec-compliant VLC but produces valid decodable output
550
- if (totalCoeff >= maxCoeff) return; // no zeros possible
551
- const maxZeros = maxCoeff - totalCoeff;
552
- if (totalZeros === 0) {
553
- bs.writeBit(1);
554
- } else if (totalZeros <= maxZeros) {
555
- for (let i = 0; i < Math.min(totalZeros, 8); i++) bs.writeBit(0);
556
- bs.writeBit(1);
557
- if (totalZeros > 8) {
558
- bs.writeBits(totalZeros - 8, 4);
494
+ const [rbBits, rbLen] = getRunBefore(zerosLeft, run);
495
+ bs.writeBits(rbBits, rbLen);
496
+ zerosLeft -= run;
559
497
  }
560
498
  }
561
499
  }
562
-
563
- _writeRunBefore(bs, run, zerosLeft) {
564
- // Simplified run_before encoding
565
- if (zerosLeft <= 0 || run <= 0) return;
566
- if (run <= 6 && zerosLeft > 6) {
567
- for (let i = 0; i < run; i++) bs.writeBit(0);
568
- bs.writeBit(1);
569
- } else {
570
- // Truncated unary for small values
571
- for (let i = 0; i < Math.min(run, 6); i++) bs.writeBit(0);
572
- if (run < zerosLeft) bs.writeBit(1);
573
- }
574
- }
575
500
  }
576
501
 
577
502
  export default H264Encoder;
package/src/index.js CHANGED
@@ -342,7 +342,7 @@ toMp4.TSParser = TSParser;
342
342
  toMp4.RemoteMp4 = RemoteMp4;
343
343
 
344
344
  // Version (injected at build time for dist, read from package.json for ESM)
345
- toMp4.version = '1.4.0';
345
+ toMp4.version = '1.4.1';
346
346
 
347
347
  // Export
348
348
  export {