@invintusmedia/tomp4 1.3.1 → 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 +2 -2
- package/package.json +1 -1
- package/src/codecs/REFERENCE.md +885 -0
- package/src/codecs/h264-cabac-init.js +546 -0
- package/src/codecs/h264-cabac.js +322 -0
- package/src/codecs/h264-cavlc-tables.js +628 -0
- package/src/codecs/h264-decoder.js +940 -0
- package/src/codecs/h264-encoder.js +502 -0
- package/src/codecs/h264-intra.js +292 -0
- package/src/codecs/h264-sps-pps.js +483 -0
- package/src/codecs/h264-tables.js +217 -0
- package/src/codecs/h264-transform.js +268 -0
- package/src/codecs/smart-render.js +169 -0
- package/src/hls-clip.js +50 -22
- package/src/index.js +1 -1
|
@@ -0,0 +1,885 @@
|
|
|
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
|