@invintusmedia/tomp4 1.3.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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-decoder.js +940 -0
- package/src/codecs/h264-encoder.js +577 -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 +58 -28
- package/src/index.js +1 -1
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* H.264 CABAC Decoder
|
|
3
|
+
*
|
|
4
|
+
* Context-Adaptive Binary Arithmetic Coding decoder for H.264/AVC.
|
|
5
|
+
* Implements the decoding engine, context model management, and
|
|
6
|
+
* binarization of syntax elements.
|
|
7
|
+
*
|
|
8
|
+
* Reference: ITU-T H.264, Section 9.3
|
|
9
|
+
*
|
|
10
|
+
* @module codecs/h264-cabac
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { rangeTabLPS, transIdxLPS, transIdxMPS } from './h264-tables.js';
|
|
14
|
+
|
|
15
|
+
// ══════════════════════════════════════════════════════════
|
|
16
|
+
// Bitstream Reader (Exp-Golomb + raw bits)
|
|
17
|
+
// ══════════════════════════════════════════════════════════
|
|
18
|
+
|
|
19
|
+
export class BitstreamReader {
|
|
20
|
+
/**
|
|
21
|
+
* @param {Uint8Array} data - NAL unit data (RBSP, after emulation prevention removal)
|
|
22
|
+
* @param {number} [startBit=0] - Starting bit position
|
|
23
|
+
*/
|
|
24
|
+
constructor(data, startBit = 0) {
|
|
25
|
+
this.data = data;
|
|
26
|
+
this.bitPos = startBit;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
get bitsLeft() {
|
|
30
|
+
return this.data.length * 8 - this.bitPos;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
readBit() {
|
|
34
|
+
if (this.bitPos >= this.data.length * 8) return 0;
|
|
35
|
+
const byteIdx = this.bitPos >> 3;
|
|
36
|
+
const bitIdx = 7 - (this.bitPos & 7);
|
|
37
|
+
this.bitPos++;
|
|
38
|
+
return (this.data[byteIdx] >> bitIdx) & 1;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
readBits(n) {
|
|
42
|
+
let val = 0;
|
|
43
|
+
for (let i = 0; i < n; i++) {
|
|
44
|
+
val = (val << 1) | this.readBit();
|
|
45
|
+
}
|
|
46
|
+
return val;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Unsigned Exp-Golomb */
|
|
50
|
+
readUE() {
|
|
51
|
+
let zeros = 0;
|
|
52
|
+
while (this.readBit() === 0 && zeros < 32) zeros++;
|
|
53
|
+
if (zeros === 0) return 0;
|
|
54
|
+
return (1 << zeros) - 1 + this.readBits(zeros);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Signed Exp-Golomb */
|
|
58
|
+
readSE() {
|
|
59
|
+
const val = this.readUE();
|
|
60
|
+
return (val & 1) ? (val + 1) >> 1 : -(val >> 1);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Truncated Exp-Golomb (for CABAC init) */
|
|
64
|
+
readTE(max) {
|
|
65
|
+
if (max > 1) return this.readUE();
|
|
66
|
+
return 1 - this.readBit();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Align to byte boundary */
|
|
70
|
+
alignByte() {
|
|
71
|
+
this.bitPos = ((this.bitPos + 7) >> 3) << 3;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Read raw bytes (after byte alignment) */
|
|
75
|
+
readBytes(n) {
|
|
76
|
+
this.alignByte();
|
|
77
|
+
const start = this.bitPos >> 3;
|
|
78
|
+
const result = this.data.slice(start, start + n);
|
|
79
|
+
this.bitPos += n * 8;
|
|
80
|
+
return result;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Check if more RBSP data remains */
|
|
84
|
+
moreRbspData() {
|
|
85
|
+
if (this.bitsLeft <= 0) return false;
|
|
86
|
+
// Look for RBSP stop bit (1 followed by zero padding)
|
|
87
|
+
const saved = this.bitPos;
|
|
88
|
+
let lastOneBit = -1;
|
|
89
|
+
for (let i = this.bitPos; i < this.data.length * 8; i++) {
|
|
90
|
+
const byteIdx = i >> 3;
|
|
91
|
+
const bitIdx = 7 - (i & 7);
|
|
92
|
+
if ((this.data[byteIdx] >> bitIdx) & 1) lastOneBit = i;
|
|
93
|
+
}
|
|
94
|
+
return lastOneBit > this.bitPos;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ══════════════════════════════════════════════════════════
|
|
99
|
+
// Remove Emulation Prevention Bytes
|
|
100
|
+
// ══════════════════════════════════════════════════════════
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Convert NAL unit from RBSP to SODB by removing emulation prevention bytes.
|
|
104
|
+
* 0x00 0x00 0x03 → 0x00 0x00
|
|
105
|
+
*/
|
|
106
|
+
export function removeEmulationPrevention(nalData) {
|
|
107
|
+
const result = [];
|
|
108
|
+
for (let i = 0; i < nalData.length; i++) {
|
|
109
|
+
if (i + 2 < nalData.length &&
|
|
110
|
+
nalData[i] === 0x00 && nalData[i + 1] === 0x00 && nalData[i + 2] === 0x03) {
|
|
111
|
+
result.push(0x00, 0x00);
|
|
112
|
+
i += 2; // skip the 0x03
|
|
113
|
+
} else {
|
|
114
|
+
result.push(nalData[i]);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return new Uint8Array(result);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ══════════════════════════════════════════════════════════
|
|
121
|
+
// CABAC Context
|
|
122
|
+
// ══════════════════════════════════════════════════════════
|
|
123
|
+
|
|
124
|
+
class CabacContext {
|
|
125
|
+
constructor() {
|
|
126
|
+
this.pStateIdx = 0;
|
|
127
|
+
this.valMPS = 0;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
init(sliceQPy, m, n) {
|
|
131
|
+
const preCtxState = Math.max(1, Math.min(126, ((m * sliceQPy) >> 4) + n));
|
|
132
|
+
if (preCtxState <= 63) {
|
|
133
|
+
this.pStateIdx = 63 - preCtxState;
|
|
134
|
+
this.valMPS = 0;
|
|
135
|
+
} else {
|
|
136
|
+
this.pStateIdx = preCtxState - 64;
|
|
137
|
+
this.valMPS = 1;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ══════════════════════════════════════════════════════════
|
|
143
|
+
// CABAC Decoder Engine
|
|
144
|
+
// ══════════════════════════════════════════════════════════
|
|
145
|
+
|
|
146
|
+
export class CabacDecoder {
|
|
147
|
+
/**
|
|
148
|
+
* Spec-compliant CABAC decoder (ITU-T H.264 Section 9.3).
|
|
149
|
+
* Uses separate pStateIdx/valMPS per context.
|
|
150
|
+
*
|
|
151
|
+
* @param {Uint8Array} data - RBSP data (emulation prevention removed)
|
|
152
|
+
* @param {number} startBit - Bit position where CABAC data begins
|
|
153
|
+
*/
|
|
154
|
+
constructor(data, startBit) {
|
|
155
|
+
this.data = data;
|
|
156
|
+
// Byte-align the start position (CABAC starts at byte boundary after slice header)
|
|
157
|
+
this.bytePos = (startBit + 7) >> 3;
|
|
158
|
+
this.bitInByte = 0;
|
|
159
|
+
|
|
160
|
+
// Arithmetic decoder state
|
|
161
|
+
this.codIRange = 510;
|
|
162
|
+
this.codIOffset = 0;
|
|
163
|
+
|
|
164
|
+
// Context models
|
|
165
|
+
this.contexts = [];
|
|
166
|
+
for (let i = 0; i < 1024; i++) {
|
|
167
|
+
this.contexts.push(new CabacContext());
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Read 9 bits to initialize codIOffset
|
|
171
|
+
for (let i = 0; i < 9; i++) {
|
|
172
|
+
this.codIOffset = (this.codIOffset << 1) | this._readBit();
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
_readBit() {
|
|
177
|
+
if (this.bytePos >= this.data.length) return 0;
|
|
178
|
+
const bit = (this.data[this.bytePos] >> (7 - this.bitInByte)) & 1;
|
|
179
|
+
this.bitInByte++;
|
|
180
|
+
if (this.bitInByte === 8) {
|
|
181
|
+
this.bitInByte = 0;
|
|
182
|
+
this.bytePos++;
|
|
183
|
+
}
|
|
184
|
+
return bit;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Initialize context models for a slice.
|
|
189
|
+
*/
|
|
190
|
+
initContexts(sliceType, sliceQPy, cabacInitIdc, initTable) {
|
|
191
|
+
for (let i = 0; i < initTable.length; i++) {
|
|
192
|
+
const [m, n] = initTable[i];
|
|
193
|
+
this.contexts[i].init(sliceQPy, m, n);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Decode a single bin using a context model (Section 9.3.3.2.1).
|
|
199
|
+
*/
|
|
200
|
+
decodeBin(ctxIdx) {
|
|
201
|
+
const ctx = this.contexts[ctxIdx];
|
|
202
|
+
const qCodIRangeIdx = (this.codIRange >> 6) & 3;
|
|
203
|
+
const codIRangeLPS = rangeTabLPS[ctx.pStateIdx][qCodIRangeIdx];
|
|
204
|
+
this.codIRange -= codIRangeLPS;
|
|
205
|
+
|
|
206
|
+
let binVal;
|
|
207
|
+
if (this.codIOffset >= this.codIRange) {
|
|
208
|
+
// LPS path
|
|
209
|
+
binVal = 1 - ctx.valMPS;
|
|
210
|
+
this.codIOffset -= this.codIRange;
|
|
211
|
+
this.codIRange = codIRangeLPS;
|
|
212
|
+
if (ctx.pStateIdx === 0) ctx.valMPS = 1 - ctx.valMPS;
|
|
213
|
+
ctx.pStateIdx = transIdxLPS[ctx.pStateIdx];
|
|
214
|
+
} else {
|
|
215
|
+
// MPS path
|
|
216
|
+
binVal = ctx.valMPS;
|
|
217
|
+
ctx.pStateIdx = transIdxMPS[ctx.pStateIdx];
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
this._renorm();
|
|
221
|
+
return binVal;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Decode a bin in bypass mode (Section 9.3.3.2.3).
|
|
226
|
+
*/
|
|
227
|
+
decodeBypass() {
|
|
228
|
+
this.codIOffset = (this.codIOffset << 1) | this._readBit();
|
|
229
|
+
if (this.codIOffset >= this.codIRange) {
|
|
230
|
+
this.codIOffset -= this.codIRange;
|
|
231
|
+
return 1;
|
|
232
|
+
}
|
|
233
|
+
return 0;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Decode the terminate bin (Section 9.3.3.2.4).
|
|
238
|
+
*/
|
|
239
|
+
decodeTerminate() {
|
|
240
|
+
this.codIRange -= 2;
|
|
241
|
+
if (this.codIOffset >= this.codIRange) {
|
|
242
|
+
return 1; // end of slice
|
|
243
|
+
}
|
|
244
|
+
this._renorm();
|
|
245
|
+
return 0;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Renormalization (Section 9.3.3.2.2).
|
|
250
|
+
*/
|
|
251
|
+
_renorm() {
|
|
252
|
+
while (this.codIRange < 256) {
|
|
253
|
+
this.codIRange <<= 1;
|
|
254
|
+
this.codIOffset = (this.codIOffset << 1) | this._readBit();
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// ── High-level syntax element decoding ────────────────
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Decode an unsigned value using truncated unary + exp-golomb binarization.
|
|
262
|
+
* Used for mb_type, sub_mb_type, etc.
|
|
263
|
+
*/
|
|
264
|
+
decodeUnary(ctxOffset, maxVal, ctxIncFn) {
|
|
265
|
+
let val = 0;
|
|
266
|
+
while (val < maxVal) {
|
|
267
|
+
const ctxInc = ctxIncFn ? ctxIncFn(val) : Math.min(val, 1);
|
|
268
|
+
const bin = this.decodeBin(ctxOffset + ctxInc);
|
|
269
|
+
if (bin === 0) break;
|
|
270
|
+
val++;
|
|
271
|
+
}
|
|
272
|
+
return val;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Decode a signed value using unary/exp-golomb + sign bypass bin.
|
|
277
|
+
*/
|
|
278
|
+
decodeSigned(ctxOffset, maxVal, ctxIncFn) {
|
|
279
|
+
const absVal = this.decodeUnary(ctxOffset, maxVal, ctxIncFn);
|
|
280
|
+
if (absVal === 0) return 0;
|
|
281
|
+
const sign = this.decodeBypass();
|
|
282
|
+
return sign ? -absVal : absVal;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Decode a bypass-coded unsigned integer of n bits.
|
|
287
|
+
*/
|
|
288
|
+
decodeBypassBits(n) {
|
|
289
|
+
let val = 0;
|
|
290
|
+
for (let i = 0; i < n; i++) {
|
|
291
|
+
val = (val << 1) | this.decodeBypass();
|
|
292
|
+
}
|
|
293
|
+
return val;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Decode an unsigned Exp-Golomb coded value (UEG) in CABAC.
|
|
298
|
+
* Used for mvd, residual levels, etc.
|
|
299
|
+
* prefix is unary coded, suffix is bypass Exp-Golomb.
|
|
300
|
+
*/
|
|
301
|
+
decodeUEG(ctxOffset, maxPrefix, ctxIncFn) {
|
|
302
|
+
// Prefix: unary coded with contexts
|
|
303
|
+
let prefix = 0;
|
|
304
|
+
while (prefix < maxPrefix) {
|
|
305
|
+
const ctxInc = ctxIncFn ? ctxIncFn(prefix) : Math.min(prefix, maxPrefix - 1);
|
|
306
|
+
const bin = this.decodeBin(ctxOffset + ctxInc);
|
|
307
|
+
if (bin === 0) break;
|
|
308
|
+
prefix++;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (prefix < maxPrefix) return prefix;
|
|
312
|
+
|
|
313
|
+
// Suffix: bypass Exp-Golomb (k=0)
|
|
314
|
+
let k = 0;
|
|
315
|
+
while (this.decodeBypass() === 1) k++;
|
|
316
|
+
let suffix = 0;
|
|
317
|
+
for (let i = 0; i < k; i++) {
|
|
318
|
+
suffix = (suffix << 1) | this.decodeBypass();
|
|
319
|
+
}
|
|
320
|
+
return prefix + (1 << k) - 1 + suffix;
|
|
321
|
+
}
|
|
322
|
+
}
|