@invintusmedia/tomp4 1.4.3 → 1.5.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.
@@ -1,885 +0,0 @@
1
- # H.264 Decoder Implementation Reference
2
-
3
- Last updated: 2026-04-01
4
-
5
- ## Reference Source Code URLs
6
-
7
- ### FFmpeg H.264 Decoder (LGPL 2.1, libavcodec)
8
- - **CABAC decoding**: https://github.com/FFmpeg/FFmpeg/blob/master/libavcodec/h264_cabac.c
9
- - **CABAC engine**: https://github.com/FFmpeg/FFmpeg/blob/master/libavcodec/cabac.c
10
- - **CABAC engine header**: https://github.com/FFmpeg/FFmpeg/blob/master/libavcodec/cabac.h
11
- - **CABAC inline functions**: https://github.com/FFmpeg/FFmpeg/blob/master/libavcodec/cabac_functions.h
12
- - **Macroblock decoding**: https://github.com/FFmpeg/FFmpeg/blob/master/libavcodec/h264_mb.c
13
- - **Deblocking filter**: https://github.com/FFmpeg/FFmpeg/blob/master/libavcodec/h264_loopfilter.c
14
- - **Direct mode**: https://github.com/FFmpeg/FFmpeg/blob/master/libavcodec/h264_direct.c
15
- - **Parameter sets**: https://github.com/FFmpeg/FFmpeg/blob/master/libavcodec/h264_ps.c
16
- - **H.264 tables**: https://github.com/FFmpeg/FFmpeg/blob/master/libavcodec/h264data.h
17
-
18
- ### JavaScript H.264 Implementations
19
- - **Broadway.js** (Emscripten/C->WASM, Baseline only, CAVLC only): https://github.com/mbebenita/Broadway
20
- - Source in `Decoder/src/` - h264bsd_*.c files
21
- - Key files: h264bsd_cavlc.c, h264bsd_deblocking.c, h264bsd_intra_prediction.c, h264bsd_inter_prediction.c
22
- - NOTE: No CABAC support (Baseline profile only)
23
- - **prism (de264.js)** (Pure JS, partial): https://github.com/guodong/prism
24
- - Pure JS, AMD modules, Baseline profile, CAVLC only
25
- - Files: src/macroblock_layer.js, src/slice.js, src/sps.js, src/pps.js, src/dpb.js
26
- - Has working CAVLC coefficient decoding with VLC lookup tables
27
- - **FFmpeg H.264 decoder extract**: https://github.com/shengbinmeng/ffmpeg-h264-dec (C, extracted from FFmpeg)
28
-
29
- ### H.264 Specification (ITU-T H.264 / ISO 14496-10)
30
- - Free draft: https://www.itu.int/rec/T-REC-H.264
31
- - CABAC tables: Sections 9.3.1.1, Tables 9-12 through 9-23
32
-
33
-
34
- ---
35
-
36
- ## 1. CABAC Engine Implementation
37
-
38
- ### Core Data Structure
39
- ```js
40
- class CABACDecoder {
41
- constructor(data, offset, length) {
42
- this.buffer = data; // Uint8Array
43
- this.offset = offset; // current byte position
44
- this.range = 0x1FE; // 9-bit range [256..510]
45
- this.low = 0; // accumulated value (scaled)
46
- // Initialize: read first 2 bytes
47
- this.low = (data[offset] << 10) | (data[offset + 1] << 2) | 2;
48
- this.offset = offset + 2;
49
- }
50
- }
51
- ```
52
-
53
- ### Core decode operation (get_cabac)
54
- From FFmpeg cabac_functions.h - the essential CABAC binary arithmetic decode:
55
- ```js
56
- decodeBin(ctxState) {
57
- // ctxState is a Uint8Array element, stores pStateIdx*2 + valMPS packed
58
- const state = ctxState[0];
59
- const qRangeIdx = (this.range >> 6) & 3; // 2-bit quantized range index
60
- const rLPS = LPS_RANGE_TABLE[state >> 1][qRangeIdx];
61
-
62
- this.range -= rLPS;
63
-
64
- if (this.low < (this.range << 10)) {
65
- // MPS path
66
- ctxState[0] = MPS_TRANS_TABLE[state]; // state transition for MPS
67
- this.renormalize();
68
- return state & 1; // valMPS
69
- } else {
70
- // LPS path
71
- this.low -= this.range << 10;
72
- this.range = rLPS;
73
- ctxState[0] = LPS_TRANS_TABLE[state]; // state transition for LPS
74
- this.renormalize();
75
- return (state & 1) ^ 1; // 1 - valMPS
76
- }
77
- }
78
-
79
- renormalize() {
80
- while (this.range < 256) {
81
- this.range <<= 1;
82
- this.low <<= 1;
83
- if (!(this.low & 0xFFFF)) {
84
- this.low |= (this.buffer[this.offset++] << 9) | (this.buffer[this.offset++] << 1);
85
- this.low -= 0xFFFF;
86
- }
87
- }
88
- }
89
-
90
- decodeBypass() {
91
- this.low <<= 1;
92
- if (!(this.low & 0xFFFF)) this.refill();
93
- if (this.low < this.range << 10) return 0;
94
- this.low -= this.range << 10;
95
- return 1;
96
- }
97
-
98
- decodeTerminate() {
99
- this.range -= 2;
100
- if (this.low < this.range << 10) {
101
- this.renormalize();
102
- return 0;
103
- }
104
- return 1; // end of slice
105
- }
106
- ```
107
-
108
- ### CABAC LPS Range Table (spec Table 9-48)
109
- 4 columns for qRangeIdx 0..3, 64 rows for pStateIdx 0..63:
110
- ```js
111
- const LPS_RANGE = [
112
- // pStateIdx: [qRangeIdx 0, 1, 2, 3]
113
- /* 0*/ [128,176,208,240], /* 1*/ [128,167,197,227],
114
- /* 2*/ [128,158,187,216], /* 3*/ [123,150,178,205],
115
- /* 4*/ [116,142,169,195], /* 5*/ [111,135,160,185],
116
- /* 6*/ [105,128,152,175], /* 7*/ [100,122,144,166],
117
- /* 8*/ [ 95,116,137,158], /* 9*/ [ 90,110,130,150],
118
- /*10*/ [ 85,104,123,142], /*11*/ [ 81, 99,117,135],
119
- /*12*/ [ 77, 94,111,128], /*13*/ [ 73, 89,105,122],
120
- /*14*/ [ 69, 85,100,116], /*15*/ [ 66, 80, 95,110],
121
- /*16*/ [ 62, 76, 90,104], /*17*/ [ 59, 72, 86, 99],
122
- /*18*/ [ 56, 69, 81, 94], /*19*/ [ 53, 65, 77, 89],
123
- /*20*/ [ 51, 62, 73, 85], /*21*/ [ 48, 59, 69, 80],
124
- /*22*/ [ 46, 56, 66, 76], /*23*/ [ 43, 53, 63, 72],
125
- /*24*/ [ 41, 50, 59, 69], /*25*/ [ 39, 48, 56, 65],
126
- /*26*/ [ 37, 45, 54, 62], /*27*/ [ 35, 43, 51, 59],
127
- /*28*/ [ 33, 41, 48, 56], /*29*/ [ 32, 39, 46, 53],
128
- /*30*/ [ 30, 37, 43, 50], /*31*/ [ 29, 35, 41, 48],
129
- /*32*/ [ 27, 33, 39, 45], /*33*/ [ 26, 31, 37, 43],
130
- /*34*/ [ 24, 30, 35, 41], /*35*/ [ 23, 28, 33, 39],
131
- /*36*/ [ 22, 27, 32, 37], /*37*/ [ 21, 26, 30, 35],
132
- /*38*/ [ 20, 24, 29, 33], /*39*/ [ 19, 23, 27, 31],
133
- /*40*/ [ 18, 22, 26, 30], /*41*/ [ 17, 21, 25, 28],
134
- /*42*/ [ 16, 20, 23, 27], /*43*/ [ 15, 19, 22, 25],
135
- /*44*/ [ 14, 18, 21, 24], /*45*/ [ 14, 17, 20, 23],
136
- /*46*/ [ 13, 16, 19, 22], /*47*/ [ 12, 15, 18, 21],
137
- /*48*/ [ 12, 14, 17, 20], /*49*/ [ 11, 14, 16, 19],
138
- /*50*/ [ 11, 13, 15, 18], /*51*/ [ 10, 12, 15, 17],
139
- /*52*/ [ 10, 12, 14, 16], /*53*/ [ 9, 11, 13, 15],
140
- /*54*/ [ 9, 11, 12, 14], /*55*/ [ 8, 10, 12, 14],
141
- /*56*/ [ 8, 9, 11, 13], /*57*/ [ 7, 9, 11, 12],
142
- /*58*/ [ 7, 9, 10, 12], /*59*/ [ 7, 8, 10, 11],
143
- /*60*/ [ 6, 8, 9, 11], /*61*/ [ 6, 7, 9, 10],
144
- /*62*/ [ 6, 7, 8, 9], /*63*/ [ 2, 2, 2, 2],
145
- ];
146
- ```
147
-
148
- ### CABAC State Transition Tables (spec Table 9-45)
149
- ```js
150
- // transIdxMPS[pStateIdx] - next state after MPS
151
- const TRANS_MPS = [
152
- 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,16,
153
- 17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,
154
- 33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,
155
- 49,50,51,52,53,54,55,56,57,58,59,60,61,62,62,63
156
- ];
157
-
158
- // transIdxLPS[pStateIdx] - next state after LPS
159
- const TRANS_LPS = [
160
- 0, 0, 1, 2, 2, 4, 4, 5, 6, 7, 8, 9, 9,11,11,12,
161
- 13,13,15,15,16,16,18,18,19,19,21,21,22,22,23,24,
162
- 24,25,26,26,27,27,28,29,29,30,30,30,31,32,32,33,
163
- 33,33,34,34,35,35,35,36,36,36,37,37,37,38,38,63
164
- ];
165
- ```
166
-
167
- ### CABAC Context Initialization
168
- From FFmpeg `ff_h264_init_cabac_states`:
169
- ```js
170
- function initCabacContexts(sliceQP, sliceType, cabacInitIdc) {
171
- const states = new Uint8Array(1024);
172
- // Choose init table: I-slice vs P/B-slice
173
- const tab = (sliceType === SLICE_I) ? CABAC_INIT_I : CABAC_INIT_PB[cabacInitIdc];
174
-
175
- for (let i = 0; i < 1024; i++) {
176
- const m = tab[i][0], n = tab[i][1];
177
- let preCtxState = Math.max(1, Math.min(126, ((m * sliceQP) >> 4) + n));
178
- // Pack: pStateIdx = abs(preCtxState - 64), valMPS = (preCtxState >= 64) ? 1 : 0
179
- if (preCtxState <= 63) {
180
- states[i] = (63 - preCtxState) << 1; // valMPS = 0
181
- } else {
182
- states[i] = ((preCtxState - 64) << 1) | 1; // valMPS = 1
183
- }
184
- }
185
- return states;
186
- }
187
- ```
188
-
189
- FFmpeg packs this differently. The actual FFmpeg code:
190
- ```c
191
- int pre = 2*(((tab[i][0] * slice_qp) >> 4) + tab[i][1]) - 127;
192
- pre ^= pre >> 31; // abs(pre)
193
- if (pre > 124) pre = 124 + (pre & 1);
194
- state[i] = pre; // packed: pStateIdx*2 + valMPS
195
- ```
196
-
197
- ---
198
-
199
- ## 2. CABAC Context Init Tables (m,n pairs)
200
-
201
- ### Context Index Assignment (where each syntax element lives)
202
- | Syntax Element | Context Indices | Notes |
203
- |---|---|---|
204
- | mb_skip_flag | 11-13 (P), 24-26 (B) | Uses neighbor skip status |
205
- | mb_type (I) | 3-10 | Intra slice: prefix at 3-5, suffix at 5-10 |
206
- | mb_type (P) | 14-17 | |
207
- | mb_type (B) | 27-35 | |
208
- | sub_mb_type (P) | 21-23 | |
209
- | sub_mb_type (B) | 36-39 | |
210
- | mvd_lx[0] | 40-46 | Horizontal MV difference |
211
- | mvd_lx[1] | 47-53 | Vertical MV difference |
212
- | ref_idx | 54-59 | Reference frame index |
213
- | mb_qp_delta | 60-63 | QP delta |
214
- | prev_intra4x4_pred_mode | 68 | |
215
- | rem_intra4x4_pred_mode | 69 | |
216
- | intra_chroma_pred_mode | 64-67 | |
217
- | coded_block_pattern (luma) | 73-76 | |
218
- | coded_block_pattern (chroma) | 77-84 | |
219
- | coded_block_flag | 85-104, 460-483, 1012-1023 | Per-category bases |
220
- | significant_coeff_flag (frame) | 105-165 | |
221
- | significant_coeff_flag (field) | 277-337 | |
222
- | last_significant_coeff_flag (frame) | 166-226 | |
223
- | last_significant_coeff_flag (field) | 338-398 | |
224
- | coeff_abs_level_minus1 | 227-275 | |
225
- | transform_size_8x8_flag | 399-401 | |
226
- | coeff_abs_level_minus1 (8x8, etc.) | 402-459, 952+ | High profile contexts |
227
-
228
- ### I-slice init table (first 460 contexts, m,n pairs)
229
- Full table from FFmpeg `cabac_context_init_I[1024][2]`:
230
- ```js
231
- const CABAC_INIT_I = [
232
- // 0-10: mb_type I, mb_skip (unused for I)
233
- [20,-15],[2,54],[3,74],[20,-15],[2,54],[3,74],[-28,127],[-23,104],[-6,53],[-1,54],[7,51],
234
- // 11-23: mb_skip, mb_type P/B (unused for I, all zero)
235
- [0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],
236
- // 24-59: more unused for I
237
- [0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],
238
- [0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],
239
- [0,0],[0,0],[0,0],[0,0],[0,0],[0,0],
240
- // 60-69: mb_qp_delta, intra_chroma_pred_mode, prev/rem intra4x4
241
- [0,41],[0,63],[0,63],[0,63],[-9,83],[4,86],[0,97],[-7,72],[13,41],[3,62],
242
- // 70-87: coded_block_flag bases
243
- [0,11],[1,55],[0,69],[-17,127],[-13,102],[0,82],[-7,74],[-21,107],
244
- [-27,127],[-31,127],[-24,127],[-18,95],[-27,127],[-21,114],[-30,127],[-17,123],[-12,115],[-16,122],
245
- // 88-104: more coded_block_flag
246
- [-11,115],[-12,63],[-2,68],[-15,84],[-13,104],[-3,70],[-8,93],[-10,90],
247
- [-30,127],[-1,74],[-6,97],[-7,91],[-20,127],[-4,56],[-5,82],[-7,76],[-22,125],
248
- // 105-135: significant_coeff_flag (frame, 4x4 luma)
249
- [-7,93],[-11,87],[-3,77],[-5,71],[-4,63],[-4,68],[-12,84],[-7,62],
250
- [-7,65],[8,61],[5,56],[-2,66],[1,64],[0,61],[-2,78],[1,50],
251
- [7,52],[10,35],[0,44],[11,38],[1,45],[0,46],[5,44],[31,17],
252
- [1,51],[7,50],[28,19],[16,33],[14,62],[-13,108],[-15,100],
253
- // 136-165: significant_coeff_flag (frame, 4x4 chroma, 8x8)
254
- [-13,101],[-13,91],[-12,94],[-10,88],[-16,84],[-10,86],[-7,83],[-13,87],
255
- [-19,94],[1,70],[0,72],[-5,74],[18,59],[-8,102],[-15,100],[0,95],
256
- [-4,75],[2,72],[-11,75],[-3,71],[15,46],[-13,69],[0,62],[0,65],
257
- [21,37],[-15,72],[9,57],[16,54],[0,62],[12,72],
258
- // 166-196: last_significant_coeff_flag (frame)
259
- [24,0],[15,9],[8,25],[13,18],[15,9],[13,19],[10,37],[12,18],
260
- [6,29],[20,33],[15,30],[4,45],[1,58],[0,62],[7,61],[12,38],
261
- [11,45],[15,39],[11,42],[13,44],[16,45],[12,41],[10,49],[30,34],
262
- [18,42],[10,55],[17,51],[17,46],[0,89],[26,-19],[22,-17],
263
- // 197-226: last_significant_coeff_flag (frame, cont)
264
- [26,-17],[30,-25],[28,-20],[33,-23],[37,-27],[33,-23],[40,-28],[38,-17],
265
- [33,-11],[40,-15],[41,-6],[38,1],[41,17],[30,-6],[27,3],[26,22],
266
- [37,-16],[35,-4],[38,-8],[38,-3],[37,3],[38,5],[42,0],[35,16],
267
- [39,22],[14,48],[27,37],[21,60],[12,68],[2,97],
268
- // 227-251: coeff_abs_level_minus1
269
- [-3,71],[-6,42],[-5,50],[-3,54],[-2,62],[0,58],[1,63],[-2,72],
270
- [-1,74],[-9,91],[-5,67],[-5,27],[-3,39],[-2,44],[0,46],[-16,64],
271
- [-8,68],[-10,78],[-6,77],[-10,86],[-12,92],[-15,55],[-10,60],[-6,62],[-4,65],
272
- // 252-275: coeff_abs_level_minus1 (cont)
273
- [-12,73],[-8,76],[-7,80],[-9,88],[-17,110],[-11,97],[-20,84],[-11,79],
274
- [-6,73],[-4,74],[-13,86],[-13,96],[-11,97],[-19,117],[-8,78],[-5,33],
275
- [-4,48],[-2,53],[-3,62],[-13,71],[-10,79],[-12,86],[-13,90],[-14,97],
276
- // 276: bypass (unused)
277
- [0,0],
278
- // 277-337: significant_coeff_flag (field)
279
- [-6,93],[-6,84],[-8,79],[0,66],[-1,71],[0,62],[-2,60],[-2,59],
280
- [-5,75],[-3,62],[-4,58],[-9,66],[-1,79],[0,71],[3,68],[10,44],
281
- [-7,62],[15,36],[14,40],[16,27],[12,29],[1,44],[20,36],[18,32],
282
- [5,42],[1,48],[10,62],[17,46],[9,64],[-12,104],[-11,97],
283
- [-16,96],[-7,88],[-8,85],[-7,85],[-9,85],[-13,88],[4,66],[-3,77],
284
- [-3,76],[-6,76],[10,58],[-1,76],[-1,83],[-7,99],[-14,95],[2,95],
285
- [0,76],[-5,74],[0,70],[-11,75],[1,68],[0,65],[-14,73],[3,62],
286
- [4,62],[-1,68],[-13,75],[11,55],[5,64],[12,70],
287
- // 338-398: last_significant_coeff_flag (field) + coeff_abs_level_minus1 (field)
288
- [15,6],[6,19],[7,16],[12,14],[18,13],[13,11],[13,15],[15,16],
289
- [12,23],[13,23],[15,20],[14,26],[14,44],[17,40],[17,47],[24,17],
290
- [21,21],[25,22],[31,27],[22,29],[19,35],[14,50],[10,57],[7,63],
291
- [-2,77],[-4,82],[-3,94],[9,69],[-12,109],[36,-35],[36,-34],
292
- [32,-26],[37,-30],[44,-32],[34,-18],[34,-15],[40,-15],[33,-7],[35,-5],
293
- [33,0],[38,2],[33,13],[23,35],[13,58],[29,-3],[26,0],[22,30],
294
- [31,-7],[35,-15],[34,-3],[34,3],[36,-1],[34,5],[32,11],[35,5],
295
- [34,12],[39,11],[30,29],[34,26],[29,39],[19,66],
296
- // 399-401: transform_size_8x8_flag
297
- [31,21],[31,31],[25,50],
298
- // 402-435: High Profile 8x8 contexts
299
- [-17,120],[-20,112],[-18,114],[-11,85],[-15,92],[-14,89],[-26,71],[-15,81],
300
- [-14,80],[0,68],[-14,70],[-24,56],[-23,68],[-24,50],[-11,74],[23,-13],
301
- [26,-13],[40,-15],[49,-14],[44,3],[45,6],[44,34],[33,54],[19,82],
302
- [-3,75],[-1,23],[1,34],[1,43],[0,54],[-2,55],[0,61],[1,64],[0,68],[-9,92],
303
- // 436-459: High Profile level contexts
304
- [-14,106],[-13,97],[-15,90],[-12,90],[-18,88],[-10,73],[-9,79],[-14,86],
305
- [-10,73],[-10,70],[-10,69],[-5,66],[-9,64],[-5,58],[2,59],[21,-10],
306
- [24,-11],[28,-8],[28,-1],[29,3],[29,9],[35,20],[29,36],[14,67],
307
- ];
308
- ```
309
-
310
- ### P/B-slice init table (cabac_init_idc=0, first 460 contexts)
311
- ```js
312
- const CABAC_INIT_PB_0 = [
313
- // 0-10
314
- [20,-15],[2,54],[3,74],[20,-15],[2,54],[3,74],[-28,127],[-23,104],[-6,53],[-1,54],[7,51],
315
- // 11-23: mb_skip P, mb_type P
316
- [23,33],[23,2],[21,0],[1,9],[0,49],[-37,118],[5,57],[-13,78],[-11,65],[1,62],[12,49],[-4,73],[17,50],
317
- // 24-39: mb_type B, sub_mb_type
318
- [18,64],[9,43],[29,0],[26,67],[16,90],[9,104],[-46,127],[-20,104],[1,67],[-13,78],[-11,65],[1,62],[-6,86],[-17,95],[-6,61],[9,45],
319
- // 40-53: mvd_lx[0], mvd_lx[1]
320
- [-3,69],[-6,81],[-11,96],[6,55],[7,67],[-5,86],[2,88],[0,58],[-3,76],[-10,94],[5,54],[4,69],[-3,81],[0,88],
321
- // 54-59: ref_idx
322
- [-7,67],[-5,74],[-4,74],[-5,80],[-7,72],[1,58],
323
- // 60-69: mb_qp_delta, chroma_pred_mode, intra4x4_pred_mode
324
- [0,41],[0,63],[0,63],[0,63],[-9,83],[4,86],[0,97],[-7,72],[13,41],[3,62],
325
- // 70-104: coded_block_flag
326
- [0,45],[-4,78],[-3,96],[-27,126],[-28,98],[-25,101],[-23,67],[-28,82],
327
- [-20,94],[-16,83],[-22,110],[-21,91],[-18,102],[-13,93],[-29,127],[-7,92],
328
- [-5,89],[-7,96],[-13,108],[-3,46],[-1,65],[-1,57],[-9,93],[-3,74],
329
- [-9,92],[-8,87],[-23,126],[5,54],[6,60],[6,59],[6,69],[-1,48],[0,68],[-4,69],[-8,88],
330
- // 105-165: significant_coeff_flag + last_significant_coeff (frame)
331
- [-2,85],[-6,78],[-1,75],[-7,77],[2,54],[5,50],[-3,68],[1,50],
332
- [6,42],[-4,81],[1,63],[-4,70],[0,67],[2,57],[-2,76],[11,35],
333
- [4,64],[1,61],[11,35],[18,25],[12,24],[13,29],[13,36],[-10,93],
334
- [-7,73],[-2,73],[13,46],[9,49],[-7,100],[9,53],[2,53],[5,53],
335
- [-2,61],[0,56],[0,56],[-13,63],[-5,60],[-1,62],[4,57],[-6,69],
336
- [4,57],[14,39],[4,51],[13,68],[3,64],[1,61],[9,63],[7,50],
337
- [16,39],[5,44],[4,52],[11,48],[-5,60],[-1,59],[0,59],[22,33],
338
- [5,44],[14,43],[-1,78],[0,60],[9,69],
339
- // 166-226: last_significant_coeff_flag (frame) + coeff_abs_level_minus1
340
- [11,28],[2,40],[3,44],[0,49],[0,46],[2,44],[2,51],[0,47],
341
- [4,39],[2,62],[6,46],[0,54],[3,54],[2,58],[4,63],[6,51],
342
- [6,57],[7,53],[6,52],[6,55],[11,45],[14,36],[8,53],[-1,82],
343
- [7,55],[-3,78],[15,46],[22,31],[-1,84],[25,7],[30,-7],[28,3],
344
- [28,4],[32,0],[34,-1],[30,6],[30,6],[32,9],[31,19],[26,27],
345
- [26,30],[37,20],[28,34],[17,70],[1,67],[5,59],[9,67],[16,30],
346
- [18,32],[18,35],[22,29],[24,31],[23,38],[18,43],[20,41],[11,63],
347
- [9,59],[9,64],[-1,94],[-2,89],[-9,108],
348
- // 227-275: coeff_abs_level_minus1
349
- [-6,76],[-2,44],[0,45],[0,52],[-3,64],[-2,59],[-4,70],[-4,75],
350
- [-8,82],[-17,102],[-9,77],[3,24],[0,42],[0,48],[0,55],[-6,59],
351
- [-7,71],[-12,83],[-11,87],[-30,119],[1,58],[-3,29],[-1,36],[1,38],
352
- [2,43],[-6,55],[0,58],[0,64],[-3,74],[-10,90],[0,70],[-4,29],
353
- [5,31],[7,42],[1,59],[-2,58],[-3,72],[-3,81],[-11,97],[0,58],
354
- [8,5],[10,14],[14,18],[13,27],[2,40],[0,58],[-3,70],[-6,79],[-8,85],
355
- // 276: bypass
356
- [0,0],
357
- // 277-337: coded_block_flag (field) + significant_coeff_flag (field)
358
- [-13,106],[-16,106],[-10,87],[-21,114],[-18,110],[-14,98],[-22,110],[-21,106],
359
- [-18,103],[-21,107],[-23,108],[-26,112],[-10,96],[-12,95],[-5,91],[-9,93],
360
- [-22,94],[-5,86],[9,67],[-4,80],[-10,85],[-1,70],[7,60],[9,58],
361
- [5,61],[12,50],[15,50],[18,49],[17,54],[10,41],[7,46],[-1,51],
362
- [7,49],[8,52],[9,41],[6,47],[2,55],[13,41],[10,44],[6,50],
363
- [5,53],[13,49],[4,63],[6,64],[-2,69],[-2,59],[6,70],[10,44],
364
- [9,31],[12,43],[3,53],[14,34],[10,38],[-3,52],[13,40],[17,32],
365
- [7,44],[7,38],[13,50],[10,57],[26,43],
366
- // 338-398: last_significant_coeff_flag (field)
367
- [14,11],[11,14],[9,11],[18,11],[21,9],[23,-2],[32,-15],[32,-15],
368
- [34,-21],[39,-23],[42,-33],[41,-31],[46,-28],[38,-12],[21,29],[45,-24],
369
- [53,-45],[48,-26],[65,-43],[43,-19],[39,-10],[30,9],[18,26],[20,27],
370
- [0,57],[-14,82],[-5,75],[-19,97],[-35,125],[27,0],[28,0],[31,-4],
371
- [27,6],[34,8],[30,10],[24,22],[33,19],[22,32],[26,31],[21,41],
372
- [26,44],[23,47],[16,65],[14,71],[8,60],[6,63],[17,65],[21,24],
373
- [23,20],[26,23],[27,32],[28,23],[28,24],[23,40],[24,32],[28,29],
374
- [23,42],[19,57],[22,53],[22,61],[11,86],
375
- // 399-401: transform_size_8x8_flag
376
- [12,40],[11,51],[14,59],
377
- // 402-435: High profile
378
- [-4,79],[-7,71],[-5,69],[-9,70],[-8,66],[-10,68],[-19,73],[-12,69],
379
- [-16,70],[-15,67],[-20,62],[-19,70],[-16,66],[-22,65],[-20,63],[9,-2],
380
- [26,-9],[33,-9],[39,-7],[41,-2],[45,3],[49,9],[45,27],[36,59],
381
- [-6,66],[-7,35],[-7,42],[-8,45],[-5,48],[-12,56],[-6,60],[-5,62],[-8,66],[-8,76],
382
- // 436-459
383
- [-5,85],[-6,81],[-10,77],[-7,81],[-17,80],[-18,73],[-4,74],[-10,83],
384
- [-9,71],[-9,67],[-1,61],[-8,66],[-14,66],[0,59],[2,59],[21,-13],
385
- [33,-14],[39,-7],[46,-2],[51,2],[60,6],[61,17],[55,34],[42,62],
386
- ];
387
- ```
388
-
389
- Note: cabac_init_idc=1 and cabac_init_idc=2 tables follow the same structure.
390
- Full tables are in FFmpeg h264_cabac.c `cabac_context_init_PB[3][1024][2]`.
391
-
392
-
393
- ---
394
-
395
- ## 3. CABAC Binarization Rules
396
-
397
- ### mb_type (I-slice) - Context indices 3-10
398
- ```
399
- if (ctxIdxInc from neighbors) == 0 -> I_4x4
400
- 1 + terminate -> I_PCM
401
- 1 + 0 + ... -> I_16x16 subtypes
402
- bit at ctx[state+1]: cbp_luma!=0 (0 or 12 added)
403
- bit at ctx[state+2]: cbp_chroma>0
404
- if yes: bit at ctx[state+2+1]: cbp_chroma==2
405
- adds 4 or 8
406
- bits at ctx[state+3+1], ctx[state+3+2]: pred_mode (0-3)
407
- ```
408
-
409
- From FFmpeg `decode_cabac_intra_mb_type`:
410
- ```c
411
- // For I-slice (intra_slice=1):
412
- ctx = 0;
413
- if (left_type & (INTRA16x16|INTRA_PCM)) ctx++;
414
- if (top_type & (INTRA16x16|INTRA_PCM)) ctx++;
415
- if (get_cabac(state[ctx]) == 0) return 0; // I_4x4
416
- if (get_cabac_terminate()) return 25; // I_PCM
417
- mb_type = 1;
418
- mb_type += 12 * get_cabac(state[1]); // cbp_luma != 0
419
- if (get_cabac(state[2])) // cbp_chroma > 0
420
- mb_type += 4 + 4 * get_cabac(state[3]); // cbp_chroma == 2
421
- mb_type += 2 * get_cabac(state[4]); // pred_mode bit 1
422
- mb_type += 1 * get_cabac(state[5]); // pred_mode bit 0
423
- return mb_type; // 1..24 maps to I_16x16 subtypes
424
- ```
425
-
426
- ### mb_type (P-slice) - Context indices 14-17
427
- ```
428
- 0 -> ctx14=0: P_L0_16x16 or P_8x8
429
- ctx15=0: check ctx16 -> 0=P_L0_16x16, 1=P_8x8
430
- ctx15=1: ctx17 -> 0=P_L0_16x8, 1=P_L0_8x16
431
- 1xxx -> intra (reuse I-slice decode at ctx_base=17)
432
- ```
433
-
434
- From FFmpeg:
435
- ```c
436
- if (get_cabac(state[14]) == 0) {
437
- if (get_cabac(state[15]) == 0)
438
- mb_type = 3 * get_cabac(state[16]); // 0=P_L0_16x16, 3=P_8x8
439
- else
440
- mb_type = 2 - get_cabac(state[17]); // 1=P_L0_16x8, 2=P_L0_8x16
441
- } else {
442
- mb_type = decode_cabac_intra_mb_type(sl, 17, 0); // intra in P-slice
443
- }
444
- ```
445
-
446
- ### mb_type (B-slice) - Context indices 27-35
447
- ```
448
- ctx[27+ctxInc]=0 -> B_Direct_16x16
449
- ctx[27+ctxInc]=1:
450
- ctx[27+3]=0 -> 1 + ctx[27+5] (B_L0_16x16 or B_L1_16x16)
451
- ctx[27+3]=1:
452
- read 4 bits from ctx[27+4], ctx[27+5], ctx[27+5], ctx[27+5]
453
- maps to B_Bi_16x16 through B_8x8, or intra
454
- ```
455
-
456
- ### sub_mb_type (P-slice) - Context indices 21-23
457
- ```c
458
- if (get_cabac(state[21])) return 0; // 8x8
459
- if (!get_cabac(state[22])) return 1; // 8x4
460
- if (get_cabac(state[23])) return 2; // 4x8
461
- return 3; // 4x4
462
- ```
463
-
464
- ### sub_mb_type (B-slice) - Context indices 36-39
465
- ```c
466
- if (!get_cabac(state[36])) return 0; // B_Direct_8x8
467
- if (!get_cabac(state[37]))
468
- return 1 + get_cabac(state[39]); // B_L0_8x8 or B_L1_8x8
469
- type = 3;
470
- if (get_cabac(state[38])) {
471
- if (get_cabac(state[39]))
472
- return 11 + get_cabac(state[39]); // B_L1_4x4 or B_Bi_4x4
473
- type += 4;
474
- }
475
- type += 2 * get_cabac(state[39]);
476
- type += get_cabac(state[39]);
477
- return type; // 3..10
478
- ```
479
-
480
- ### coded_block_pattern (luma) - Context indices 73-76
481
- 4 bins, each conditioned on left/top CBP:
482
- ```c
483
- ctx = !(cbp_a & 0x02) + 2 * !(cbp_b & 0x04); // for bit 0
484
- cbp += get_cabac(state[73 + ctx]);
485
- ctx = !(cbp & 0x01) + 2 * !(cbp_b & 0x08); // for bit 1
486
- cbp += get_cabac(state[73 + ctx]) << 1;
487
- ctx = !(cbp_a & 0x08) + 2 * !(cbp & 0x01); // for bit 2
488
- cbp += get_cabac(state[73 + ctx]) << 2;
489
- ctx = !(cbp & 0x04) + 2 * !(cbp & 0x02); // for bit 3
490
- cbp += get_cabac(state[73 + ctx]) << 3;
491
- ```
492
-
493
- ### coded_block_pattern (chroma) - Context indices 77-84
494
- ```c
495
- ctx = (cbp_a_chroma > 0) + 2*(cbp_b_chroma > 0);
496
- if (get_cabac(state[77 + ctx]) == 0) return 0;
497
- ctx = 4 + (cbp_a_chroma == 2) + 2*(cbp_b_chroma == 2);
498
- return 1 + get_cabac(state[77 + ctx]);
499
- ```
500
-
501
- ### mvd (motion vector difference) - Context indices 40-46 (horiz), 47-53 (vert)
502
- Uses UEG (Unary/Exp-Golomb) binarization:
503
- ```c
504
- // amvd = |mvd_left| + |mvd_top| (sum of absolute neighbor MVDs)
505
- ctx = ctxbase + (amvd > 2) + (amvd > 32) + 2; // actually: ((amvd-3)>>31) + ((amvd-33)>>31) + 2
506
- // Prefix: unary up to 9 with contexts ctxbase+3..ctxbase+6
507
- if (!get_cabac(state[ctx])) { mvd = 0; return 0; }
508
- mvd = 1;
509
- ctx = ctxbase + 3;
510
- while (mvd < 9 && get_cabac(state[ctx])) {
511
- if (mvd < 4) ctx++;
512
- mvd++;
513
- }
514
- // Suffix: Exp-Golomb k=3 coded with bypass bins
515
- if (mvd >= 9) {
516
- int k = 3;
517
- while (get_cabac_bypass()) { mvd += 1 << k; k++; }
518
- while (k--) { mvd += get_cabac_bypass() << k; }
519
- }
520
- // Sign: bypass bin
521
- sign = get_cabac_bypass();
522
- return sign ? -mvd : mvd;
523
- ```
524
-
525
- ### coded_block_flag - Context bases per category
526
- ```js
527
- const CBF_CTX_BASE = {
528
- 0: 85, // DC 16x16 luma
529
- 1: 89, // AC 16x16 luma
530
- 2: 93, // Luma 4x4
531
- 3: 97, // DC chroma
532
- 4: 101, // AC chroma
533
- 5: 1012, // Luma 8x8
534
- 6: 460, // (High profile additions...)
535
- 7: 464,
536
- 8: 468,
537
- 9: 1016,
538
- 10: 472,
539
- 11: 476,
540
- 12: 480,
541
- 13: 1020,
542
- };
543
- // ctx = base + (nz_left > 0) + 2*(nz_top > 0)
544
- ```
545
-
546
- ### significant_coeff_flag / last_significant_coeff_flag
547
- Context offsets per category (frame MB / field MB):
548
- ```js
549
- const SIG_COEFF_OFFSET = {
550
- frame: [105, 120, 134, 149, 152, 402, 484, 499, 513, 660, 528, 543, 557, 718],
551
- field: [277, 292, 306, 321, 324, 436, 776, 791, 805, 675, 820, 835, 849, 733]
552
- };
553
- const LAST_COEFF_OFFSET = {
554
- frame: [166, 181, 195, 210, 213, 417, 572, 587, 601, 690, 616, 631, 645, 748],
555
- field: [338, 353, 367, 382, 385, 451, 864, 879, 893, 699, 908, 923, 937, 757]
556
- };
557
- ```
558
-
559
- For 4x4 blocks: ctx = base + scan_position (0..14)
560
- For 8x8 blocks: ctx = base + offset_table[scan_position] (non-linear mapping)
561
-
562
- 8x8 significant_coeff_flag context offset (frame):
563
- ```js
564
- const SIG_COEFF_8x8_FRAME = [
565
- 0,1,2,3,4,5,5,4,4,3,3,4,4,4,5,5,
566
- 4,4,4,4,3,3,6,7,7,7,8,9,10,9,8,7,
567
- 7,6,11,12,13,11,6,7,8,9,14,10,9,8,6,11,
568
- 12,13,11,6,9,14,10,9,11,12,13,11,14,10,12
569
- ];
570
- ```
571
-
572
- ### coeff_abs_level_minus1 - Context indices 227+
573
- Uses unary + Exp-Golomb k=0 (bypass):
574
- ```c
575
- // Node-based context selection (state machine)
576
- const coeff_abs_level1_ctx = [1,2,3,4,0,0,0,0]; // for |level|==1
577
- const coeff_abs_levelgt1_ctx = [5,5,5,5,6,7,8,9]; // for |level|>1
578
- const transition_on_1 = [1,2,3,3,4,5,6,7];
579
- const transition_on_gt1 = [4,4,4,4,5,6,7,7];
580
-
581
- node_ctx = 0;
582
- for each coefficient (reverse scan order):
583
- ctx = abs_level_m1_base + coeff_abs_level1_ctx[node_ctx];
584
- if (get_cabac(ctx) == 0) {
585
- level = 1;
586
- node_ctx = transition_on_1[node_ctx];
587
- } else {
588
- ctx = abs_level_m1_base + coeff_abs_levelgt1_ctx[node_ctx];
589
- node_ctx = transition_on_gt1[node_ctx];
590
- level = 2;
591
- while (level < 15 && get_cabac(ctx)) level++;
592
- if (level >= 15) {
593
- // Exp-Golomb k=0 suffix with bypass bins
594
- k = 0;
595
- while (get_cabac_bypass()) { level += 1 << k; k++; }
596
- while (k--) level += get_cabac_bypass() << k;
597
- level += 14;
598
- }
599
- }
600
- sign = get_cabac_bypass(); // 0=positive, 1=negative
601
- ```
602
-
603
-
604
- ---
605
-
606
- ## 4. Deblocking Filter
607
-
608
- ### Filter tables (from FFmpeg h264_loopfilter.c)
609
- ```js
610
- // Alpha threshold table, indexed by QP + slice_alpha_c0_offset (clamped to 0..51)
611
- const ALPHA_TABLE = [
612
- 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
613
- 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
614
- 0,0,0,0,0,0,0,0,0,0,
615
- 0,0,0,0,0,0,4,4,5,6,7,8,9,10,12,13,15,17,20,22,
616
- 25,28,32,36,40,45,50,56,63,71,80,90,101,113,127,144,162,182,203,226,255,255,
617
- ];
618
- // Beta threshold table
619
- const BETA_TABLE = [
620
- 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
621
- 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
622
- 0,0,0,0,0,0,0,0,0,0,
623
- 0,0,0,0,0,0,2,2,2,3,3,3,3,4,4,4,6,6,7,7,
624
- 8,8,9,9,10,10,11,11,12,12,13,13,14,14,15,15,16,16,17,17,18,18,
625
- ];
626
- // tc0 table, indexed by [QP + alpha_offset][bS], bS=0..3
627
- // bS=0: always -1 (no filter), bS=1..3: see table
628
- const TC0_TABLE = [
629
- // QP 0..51, each row: [bS=0, bS=1, bS=2, bS=3]
630
- [-1,0,0,0], /*...repeated for QP 0-15...*/ [-1,0,0,0],
631
- [-1,0,0,1], [-1,0,0,1], [-1,0,0,1], [-1,0,0,1],
632
- [-1,0,1,1], [-1,0,1,1], [-1,1,1,1], [-1,1,1,1],
633
- [-1,1,1,1], [-1,1,1,1], [-1,1,1,2], [-1,1,1,2],
634
- [-1,1,1,2], [-1,1,1,2], [-1,1,2,3], [-1,1,2,3],
635
- [-1,2,2,3], [-1,2,2,4], [-1,2,3,4], [-1,2,3,4],
636
- [-1,3,3,5], [-1,3,4,6], [-1,3,4,6], [-1,4,5,7],
637
- [-1,4,5,8], [-1,4,6,9], [-1,5,7,10],[-1,6,8,11],
638
- [-1,6,8,13],[-1,7,10,14],[-1,8,11,16],[-1,9,12,18],
639
- [-1,10,13,20],[-1,11,15,23],[-1,13,17,25],
640
- ];
641
- ```
642
-
643
- ### Boundary Strength (bS) calculation
644
- ```
645
- bS = 4: either p or q is intra, AND it's a macroblock edge
646
- bS = 3: either p or q is intra (internal edge)
647
- bS = 2: either p or q has coded coefficients
648
- bS = 1: different reference frames, or |mvdiff| >= 4
649
- bS = 0: no filtering
650
- ```
651
-
652
- ### Filter operation (luma, bS < 4)
653
- ```js
654
- function filterLuma(p, q, alpha, beta, tc0) {
655
- // p[0..2]: pixels on P side (p[0] nearest boundary)
656
- // q[0..2]: pixels on Q side
657
- if (Math.abs(p[0] - q[0]) >= alpha) return;
658
- if (Math.abs(p[1] - p[0]) >= beta) return;
659
- if (Math.abs(q[1] - q[0]) >= beta) return;
660
-
661
- let tc = tc0;
662
- let ap = Math.abs(p[2] - p[0]);
663
- let aq = Math.abs(q[2] - q[0]);
664
- if (ap < beta) tc++;
665
- if (aq < beta) tc++;
666
-
667
- let delta = Math.max(-tc, Math.min(tc,
668
- ((((q[0] - p[0]) << 2) + (p[1] - q[1]) + 4) >> 3)));
669
- p[0] = clip(p[0] + delta, 0, 255);
670
- q[0] = clip(q[0] - delta, 0, 255);
671
-
672
- if (ap < beta) {
673
- p[1] += Math.max(-tc0, Math.min(tc0,
674
- (p[2] + ((p[0] + q[0] + 1) >> 1) - 2*p[1]) >> 1));
675
- }
676
- if (aq < beta) {
677
- q[1] += Math.max(-tc0, Math.min(tc0,
678
- (q[2] + ((p[0] + q[0] + 1) >> 1) - 2*q[1]) >> 1));
679
- }
680
- }
681
- ```
682
-
683
- ### Filter operation (luma, bS == 4, intra edge)
684
- ```js
685
- function filterLumaIntra(p, q, alpha, beta) {
686
- if (Math.abs(p[0] - q[0]) >= alpha) return;
687
- if (Math.abs(p[1] - p[0]) >= beta) return;
688
- if (Math.abs(q[1] - q[0]) >= beta) return;
689
-
690
- if (Math.abs(p[0] - q[0]) < ((alpha >> 2) + 2)) {
691
- // Strong filtering
692
- if (Math.abs(p[2] - p[0]) < beta) {
693
- p[0] = (p[2] + 2*p[1] + 2*p[0] + 2*q[0] + q[1] + 4) >> 3;
694
- p[1] = (p[2] + p[1] + p[0] + q[0] + 2) >> 2;
695
- p[2] = (2*p[3] + 3*p[2] + p[1] + p[0] + q[0] + 4) >> 3;
696
- } else {
697
- p[0] = (2*p[1] + p[0] + q[1] + 2) >> 2;
698
- }
699
- // Same for q side...
700
- } else {
701
- // Weak filtering (same as bS<4 with tc=1)
702
- p[0] = (2*p[1] + p[0] + q[1] + 2) >> 2;
703
- q[0] = (2*q[1] + q[0] + p[1] + 2) >> 2;
704
- }
705
- }
706
- ```
707
-
708
-
709
- ---
710
-
711
- ## 5. Motion Compensation
712
-
713
- ### Quarter-pel interpolation (6-tap Wiener filter)
714
- Luma uses a 6-tap filter for half-pel, then bilinear for quarter-pel:
715
- ```js
716
- // Half-pel filter coefficients: [1, -5, 20, 20, -5, 1] / 32 (with rounding)
717
- function halfPel(A, B, C, D, E, F) {
718
- return clip((A - 5*B + 20*C + 20*D - 5*E + F + 16) >> 5, 0, 255);
719
- }
720
- ```
721
-
722
- Quarter-pel positions are averages of adjacent full/half-pel positions.
723
-
724
- ### Chroma interpolation (bilinear, 1/8 pel)
725
- ```js
726
- function chromaInterp(src, xFrac, yFrac, stride) {
727
- // xFrac, yFrac are 1/8 pel offsets (0..7)
728
- return ((8-xFrac)*(8-yFrac)*src[0] + xFrac*(8-yFrac)*src[1] +
729
- (8-xFrac)*yFrac*src[stride] + xFrac*yFrac*src[stride+1] + 32) >> 6;
730
- }
731
- ```
732
-
733
- ### Motion vector prediction (median)
734
- ```js
735
- function predMV(mvA, mvB, mvC) {
736
- // A=left, B=top, C=top-right (or top-left if unavailable)
737
- // Median prediction for 16x16, 16x8, 8x16 has special cases
738
- if (!availA && !availB && !availC) return [0, 0];
739
- if (availA && !availB && !availC) return mvA;
740
- // General: median of three
741
- return [median(mvA[0], mvB[0], mvC[0]), median(mvA[1], mvB[1], mvC[1])];
742
- }
743
- ```
744
-
745
-
746
- ---
747
-
748
- ## 6. Intra Prediction Modes
749
-
750
- ### 4x4 Luma (9 modes)
751
- ```
752
- Mode 0: Vertical - copy top row down
753
- Mode 1: Horizontal - copy left column right
754
- Mode 2: DC - average of top + left
755
- Mode 3: Diagonal Down-Left
756
- Mode 4: Diagonal Down-Right
757
- Mode 5: Vertical-Right
758
- Mode 6: Horizontal-Down
759
- Mode 7: Vertical-Left
760
- Mode 8: Horizontal-Up
761
- ```
762
-
763
- ### 16x16 Luma (4 modes)
764
- ```
765
- Mode 0: Vertical - replicate top 16 pixels
766
- Mode 1: Horizontal - replicate left 16 pixels
767
- Mode 2: DC - average of top 16 + left 16
768
- Mode 3: Plane - linear gradient
769
- ```
770
-
771
- ### 8x8 Chroma (4 modes, same as 16x16 but reordered)
772
- ```
773
- Mode 0: DC
774
- Mode 1: Horizontal
775
- Mode 2: Vertical
776
- Mode 3: Plane
777
- ```
778
-
779
- ### Intra 4x4 pred_mode CABAC decoding
780
- ```c
781
- // prev_intra4x4_pred_mode_flag at context 68
782
- // rem_intra4x4_pred_mode at context 69 (3 bypass-like bins)
783
- if (get_cabac(state[68])) return pred_mode; // use predicted mode
784
- mode = get_cabac(state[69]);
785
- mode += get_cabac(state[69]) << 1;
786
- mode += get_cabac(state[69]) << 2;
787
- return mode + (mode >= pred_mode); // skip predicted mode in enumeration
788
- ```
789
-
790
-
791
- ---
792
-
793
- ## 7. Scaling/Quantization
794
-
795
- ### Default scaling matrices (from h264_ps.c)
796
- ```js
797
- const DEFAULT_SCALING_4x4_INTRA = [
798
- 6,13,20,28,13,20,28,32,20,28,32,37,28,32,37,42
799
- ];
800
- const DEFAULT_SCALING_4x4_INTER = [
801
- 10,14,20,24,14,20,24,27,20,24,27,30,24,27,30,34
802
- ];
803
- const DEFAULT_SCALING_8x8_INTRA = [
804
- 6,10,13,16,18,23,25,27,10,11,16,18,23,25,27,29,
805
- 13,16,18,23,25,27,29,31,16,18,23,25,27,29,31,33,
806
- 18,23,25,27,29,31,33,36,23,25,27,29,31,33,36,38,
807
- 25,27,29,31,33,36,38,40,27,29,31,33,36,38,40,42
808
- ];
809
- ```
810
-
811
- ### Inverse quantization
812
- ```js
813
- // For 4x4 transform:
814
- // level[i] = (coeffLevel[i] * dequantScale[qp%6][i] * (1 << (qp/6))) >> 4
815
- // For DC (Intra16x16): additional Hadamard inverse transform
816
- ```
817
-
818
-
819
- ---
820
-
821
- ## 8. Scan Orders
822
-
823
- ### Zigzag scan (4x4)
824
- ```js
825
- const ZIGZAG_4x4 = [0,1,4,8,5,2,3,6,9,12,13,10,7,11,14,15];
826
- ```
827
-
828
- ### Zigzag scan (8x8)
829
- ```js
830
- const ZIGZAG_8x8 = [
831
- 0, 1, 8,16, 9, 2, 3,10,17,24,32,25,18,11, 4, 5,
832
- 12,19,26,33,40,48,41,34,27,20,13, 6, 7,14,21,28,
833
- 35,42,49,56,57,50,43,36,29,22,15,23,30,37,44,51,
834
- 58,59,52,45,38,31,39,46,53,60,61,54,47,55,62,63
835
- ];
836
- ```
837
-
838
- ### scan8 (macroblock cache index mapping, from FFmpeg)
839
- Maps 4x4 block index to position in 8-wide cache arrays:
840
- ```js
841
- const SCAN8 = [
842
- 4+ 1*8, 5+ 1*8, 4+ 2*8, 5+ 2*8, // blocks 0-3 (top-left 8x8)
843
- 6+ 1*8, 7+ 1*8, 6+ 2*8, 7+ 2*8, // blocks 4-7 (top-right 8x8)
844
- 4+ 3*8, 5+ 3*8, 4+ 4*8, 5+ 4*8, // blocks 8-11 (bottom-left 8x8)
845
- 6+ 3*8, 7+ 3*8, 6+ 4*8, 7+ 4*8, // blocks 12-15 (bottom-right 8x8)
846
- // 16-19: Cb 4x4, 20-23: Cr 4x4, 24-25: Cb/Cr DC
847
- ];
848
- ```
849
-
850
-
851
- ---
852
-
853
- ## 9. Key Implementation Notes
854
-
855
- ### FFmpeg's packed CABAC state
856
- FFmpeg packs `pStateIdx` and `valMPS` into a single byte: `state = pStateIdx * 2 + valMPS`.
857
- The transition tables in `ff_h264_cabac_tables` operate on this packed representation.
858
- The `ff_h264_mlps_state` table (at offset 1024 in `ff_h264_cabac_tables`) encodes both
859
- MPS and LPS transitions in 256 entries (128 for MPS result, 128 for LPS result).
860
-
861
- ### Broadway.js limitations
862
- - Baseline profile only (no CABAC, no B-frames, no 8x8 transform)
863
- - Compiled from C to JavaScript/WASM via Emscripten
864
- - Not useful as a JS reference for CABAC implementation
865
- - Good reference for CAVLC, intra prediction, and deblocking in C
866
-
867
- ### prism/de264.js
868
- - Pure JavaScript, AMD modules
869
- - Implements Baseline profile subset
870
- - Uses CAVLC with hardcoded VLC lookup tables
871
- - Has working implementations of:
872
- - SPS/PPS parsing
873
- - Slice header parsing
874
- - Macroblock layer with CAVLC coefficient decoding
875
- - POC (picture order count) calculation
876
- - DPB (decoded picture buffer)
877
- - YUV to RGB conversion for canvas rendering
878
- - No CABAC, no B-frames, no interlacing
879
-
880
- ### Performance considerations for JS
881
- - Use TypedArrays (Uint8Array for state, Int16Array for coefficients)
882
- - The CABAC engine's inner loop (get_cabac) is the hottest path
883
- - Consider using a single packed state byte (like FFmpeg) to minimize memory access
884
- - Pre-compute the LPS range table as a flat array indexed by `(range_quantized << 7) | state`
885
- - Batch bypass bin reads for MVD suffix and coefficient sign/level