nx_h264_bits.nx source
↩ module page · 106 lines · 3725 B
1// nx_h264_bits.nx -- H.264 bitstream reader + Exp-Golomb (rung 2 keystone).
2// EVERY field in H.264 SPS/PPS/slice headers is coded as ue(v) (unsigned
3// Exp-Golomb) or se(v) (signed), read MSB-first. This is THE primitive the
4// whole decoder stands on -- so it is built and KAT-gated against the canonical
5// Exp-Golomb code words before anything above it is attempted.
6//
7// ue(v): count leadingZeros z (zeros before the first 1, which is consumed),
8// read z more bits 'rest', value = (1<<z) - 1 + rest.
9// codeword "1"->0, "010"->1, "011"->2, "00100"->3, "00101"->4 ...
10// se(v): map k=ue(v): 0->0, 1->+1, 2->-1, 3->+2, 4->-2 (sign + ceil(k/2)).
11//
12// genealogy_id: itu_t_h264_annexB_exp_golomb + teuhola_1978_exp_golomb
13// lineage_id: msb_bitreader + ue_se_exp_golomb
14// license_tier: ORIGINAL
15import "nx_syscalls.nx"
16
17struct BitReader {
18 data: *u8,
19 byte_pos: i64,
20 bit_pos: i64, // 0..7, 0 = MSB of the current byte
21 len: i64,
22 // PER-BLOCK SCRATCH (2026-08-01). These were mmapped INSIDE the residual
23 // helpers, which run once per 4x4 block (~196,000 calls per 1080p frame).
24 // This toolchain has page-granular mmap with NO free, so that leaked GBs and
25 // near-wedged the host. Every helper already takes br, and br_init runs ONCE
26 // per decode, so carrying scratch here fixes it with no caller changes.
27 ct: *i64,
28 level: *i64,
29 run: *i64,
30 pos: *i64,
31 val: *i64,
32 // coeff_token table buffers (2026-08-01): the decode wrappers allocated four
33 // 64-word buffers PER CALL to hand to ct_table_*, once per 4x4 block.
34 ctln: *i64,
35 ctcd: *i64,
36 cttc: *i64,
37 ctto: *i64,
38}
39const NX_BR_BYTES: i64 = 128 // 9 fields x 8 = 72; 128 leaves headroom. All 14
40 // allocation sites use this const, so they grow too.
41
42func br_init(br: *BitReader, data: *u8, len: i64) -> i64 {
43 br.data = data
44 br.byte_pos = 0
45 br.bit_pos = 0
46 br.len = len
47 br.ct = sys_mmap(64) as *i64
48 br.level = sys_mmap(32 * 8) as *i64
49 br.run = sys_mmap(32 * 8) as *i64
50 br.pos = sys_mmap(32 * 8) as *i64
51 br.val = sys_mmap(32 * 8) as *i64
52 br.ctln = sys_mmap(64 * 8) as *i64
53 br.ctcd = sys_mmap(64 * 8) as *i64
54 br.cttc = sys_mmap(64 * 8) as *i64
55 br.ctto = sys_mmap(64 * 8) as *i64
56 return 0
57}
58
59// read one bit MSB-first; 0 past end (caller guards real streams)
60func br_read_bit(br: *BitReader) -> i64 {
61 if br.byte_pos >= br.len { return 0 }
62 let bit: i64 = ((br.data[br.byte_pos] as i64) >> (7 - br.bit_pos)) & 1
63 br.bit_pos = br.bit_pos + 1
64 if br.bit_pos == 8 { br.bit_pos = 0; br.byte_pos = br.byte_pos + 1 }
65 return bit
66}
67
68// read n bits MSB-first as an unsigned value
69func br_read_bits(br: *BitReader, n: i64) -> i64 {
70 var v: i64 = 0
71 var i: i64 = 0
72 while i < n {
73 v = (v << 1) | br_read_bit(br)
74 i = i + 1
75 }
76 return v
77}
78
79// unsigned Exp-Golomb ue(v)
80func br_read_ue(br: *BitReader) -> i64 {
81 var z: i64 = 0
82 var seen: i64 = 0
83 while seen == 0 {
84 if br.byte_pos >= br.len { return 0 }
85 let b: i64 = br_read_bit(br)
86 if b == 1 { seen = 1 }
87 if b == 0 { z = z + 1 }
88 }
89 let rest: i64 = br_read_bits(br, z)
90 return (1 << z) - 1 + rest
91}
92
93// signed Exp-Golomb se(v)
94func br_read_se(br: *BitReader) -> i64 {
95 let k: i64 = br_read_ue(br)
96 if k == 0 { return 0 }
97 let mag: i64 = (k + 1) / 2
98 if (k & 1) == 1 { return mag }
99 return 0 - mag
100}
101
102// byte-align the reader (used between NAL parse stages)
103func br_align(br: *BitReader) -> i64 {
104 if br.bit_pos != 0 { br.bit_pos = 0; br.byte_pos = br.byte_pos + 1 }
105 return 0
106}