code wiki / _hdl_build / nx_cam_poster.nx
nx_cam_poster.nx source
↩ module page · 288 lines · 14284 B
1// nx_cam_poster.nx -- SOVEREIGN cam-recording POSTER generator (STEP 4 glue), CAVLC path.
2// TS -> first IDR (reuse nx_ts2fmp4's PAT/PMT/vidPID demux logic) -> nx_h264_decode_iframe (CAVLC full-YUV
3// recon) -> YUV420->RGB (BT.601) -> integer box-average downscale (reuse nx_galx_thumb's scaler) ->
4// jcw_write_color -> baseline JPEG on disk. No ffmpeg, no external calls. For CABAC streams it skips
5// gracefully (the CABAC decoder is a separate rung). Usage: nx_cam_poster <in.ts> <out.jpg>
6// license_tier: ORIGINAL
7import "nx_syscalls.nx"
8import "nx_ts_nal.nx" // codec-aware stream_type dispatch (gated 16/16); HEVC named, not silently ignored
9import "nx_h264_sps.nx"
10import "nx_h264_pps.nx"
11import "nx_h264_iframe.nx"
12import "nx_h264_bitwriter.nx"
13import "nx_dct8.nx"
14import "nx_quant_table.nx"
15import "nx_zigzag.nx"
16import "nx_jpeg_huff_enc.nx"
17import "nx_jpeg_block_enc.nx"
18import "nx_jpeg_write.nx"
19import "nx_jpeg_color_write.nx"
20import "nx_ts_es.nx" // demux_video_es + find_first_nal + nal_to_rbsp, lifted verbatim 2026-07-31 (rule 15)
21import "nx_mp4_es.nx" // mp4 container path: SPS/PPS from avcC + first IDR of sample 0
22const POSTER_MAGIC_2026: i64 = 2026
23const POSTER_MAGIC_91881: i64 = 91881
24const POSTER_MAGIC_22554: i64 = 22554
25const POSTER_MAGIC_46802: i64 = 46802
26const POSTER_MAGIC_116130: i64 = 116130
27const POSTER_MAGIC_1024: i64 = 1024
28const POSTER_MAGIC_8192: i64 = 8192
29
30const PRECHECK: i64 = 262144 // phase-1 read: covers PAT/PMT/SPS/PPS -> fast CABAC skip (rule 11)
31const POSTER_MAX: i64 = 480 // longest poster side in px (data-driven cap, rule 11)
32const JPEG_QUALITY: i64 = 80
33// DEFENSIVE frame-dimension bounds (rule 12): the SPS is UNTRUSTED file input. A corrupt/hostile SPS decodes
34// to absurd mbW/mbH -> the yf/uf/vf sys_mmap + their zeroing loops below would fault in GIGABYTES of anon pages
35// -> OOM-kill the box (observed 2026-07-17: repeated 15-18GB anon-rss OOM-kills of nx_cam_poster during batch
36// runs; the giant mmap now SUCCEEDS lazily under vm.overcommit_memory=1 and the touch-loop balloons instead of
37// failing fast). Cap to the largest LEGAL H.264 frame (Level 6.2 MaxFS=696320 MBs, e.g. 8192x4352); skip beyond.
38const CP_MAX_MB_DIM: i64 = 1024 // per-axis MB cap (16384 px, above 8K) -- also prevents mbW*mbH i64 overflow
39const CP_MAX_MBS: i64 = 696320 // H.264 Level 6.2 MaxFS (largest legal coded frame, in macroblocks)
40
41func cp_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
42func cp_num(v: i64) -> i64 {
43 let bb: *u8 = sys_mmap(32)
44 var m: i64 = v
45 if m < 0 { sys_write(1, "-\x00" as *u8, 1); m = 0 - m }
46 let t: *u8 = sys_mmap(32)
47 var k: i64 = 0
48 if m == 0 { t[0] = 48 as u8; k = 1 }
49 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
50 var i: i64 = 0
51 while i < k { bb[i] = t[k - 1 - i]; i = i + 1 }
52 sys_write(1, bb, k)
53 return 0
54}
55
56func read_head(path: *u8, buf: *u8) -> i64 {
57 let fd: i64 = sys_openat_rd(path)
58 if fd < 0 { return 0 - 1 }
59 var total: i64 = 0
60 var go: i64 = 1
61 while go == 1 {
62 let r: i64 = sys_read(fd, ((buf as i64) + total) as *u8, MAXSCAN - total)
63 if r <= 0 { go = 0 }
64 if r > 0 { total = total + r; if total >= MAXSCAN { go = 0 } }
65 }
66 sys_close(fd)
67 return total
68}
69
70func build_poster_rgb(yf: *u8, uf: *u8, vf: *u8, W: i64, cpW: i64, cropW: i64, cropH: i64, dstW: i64, dstH: i64, dst: *u8) -> i64 {
71 var dy: i64 = 0
72 while dy < dstH {
73 let sy0: i64 = dy * cropH / dstH
74 var sy1: i64 = (dy + 1) * cropH / dstH
75 if sy1 <= sy0 { sy1 = sy0 + 1 }
76 if sy1 > cropH { sy1 = cropH }
77 var dx: i64 = 0
78 while dx < dstW {
79 let sx0: i64 = dx * cropW / dstW
80 var sx1: i64 = (dx + 1) * cropW / dstW
81 if sx1 <= sx0 { sx1 = sx0 + 1 }
82 if sx1 > cropW { sx1 = cropW }
83 var sr: i64 = 0
84 var sg: i64 = 0
85 var sb: i64 = 0
86 var cnt: i64 = 0
87 var yy: i64 = sy0
88 while yy < sy1 {
89 var xx: i64 = sx0
90 while xx < sx1 {
91 let Y: i64 = yf[yy*W + xx] as i64
92 let U: i64 = (uf[(yy/2)*cpW + (xx/2)] as i64) - 128
93 let V: i64 = (vf[(yy/2)*cpW + (xx/2)] as i64) - 128
94 var R: i64 = Y + ((POSTER_MAGIC_91881*V) >> 16)
95 var G: i64 = Y - ((POSTER_MAGIC_22554*U + POSTER_MAGIC_46802*V) >> 16)
96 var B: i64 = Y + ((POSTER_MAGIC_116130*U) >> 16)
97 if R < 0 { R = 0 } if R > 255 { R = 255 }
98 if G < 0 { G = 0 } if G > 255 { G = 255 }
99 if B < 0 { B = 0 } if B > 255 { B = 255 }
100 sr = sr + R; sg = sg + G; sb = sb + B; cnt = cnt + 1
101 xx = xx + 1
102 }
103 yy = yy + 1
104 }
105 if cnt < 1 { cnt = 1 }
106 let p: i64 = (dy*dstW + dx) * 3
107 dst[p] = (sr/cnt) as u8
108 dst[p+1] = (sg/cnt) as u8
109 dst[p+2] = (sb/cnt) as u8
110 dx = dx + 1
111 }
112 dy = dy + 1
113 }
114 return 0
115}
116
117// encode dst RGB (dstW*dstH*3) -> baseline JPEG into out; returns byte length.
118func encode_jpeg(out: *u8, dstW: i64, dstH: i64, rgb: *u8) -> i64 {
119 let dcB: *i64 = sys_mmap(20*8) as *i64
120 let dcV: *i64 = sys_mmap(20*8) as *i64
121 let acB: *i64 = sys_mmap(20*8) as *i64
122 let acV: *i64 = sys_mmap(200*8) as *i64
123 let ndc: i64 = jhe_dc_bits(dcB)
124 jhe_dc_val(dcV)
125 let nac: i64 = jhe_ac_bits(acB)
126 jhe_ac_val(acV)
127 let dcCO: *i64 = sys_mmap(256*8) as *i64
128 let dcSI: *i64 = sys_mmap(256*8) as *i64
129 let acCO: *i64 = sys_mmap(256*8) as *i64
130 let acSI: *i64 = sys_mmap(256*8) as *i64
131 jhe_gen(dcB, dcV, ndc, dcCO, dcSI)
132 jhe_gen(acB, acV, nac, acCO, acSI)
133 let qtL: *i64 = sys_mmap(64*8) as *i64
134 nx_qt_luma(qtL, JPEG_QUALITY)
135 let qtC: *i64 = sys_mmap(64*8) as *i64
136 nx_qt_chroma(qtC, JPEG_QUALITY)
137 let to_zz: *i64 = sys_mmap(64*8) as *i64
138 let from_zz: *i64 = sys_mmap(64*8) as *i64
139 nx_zigzag_init(to_zz, from_zz)
140 let DM: *i64 = sys_mmap(64*8) as *i64
141 nx_dct8_init(DM)
142 let scratch: *i64 = sys_mmap(64*8) as *i64
143 let ti: *i64 = sys_mmap(8*8) as *i64
144 let to: *i64 = sys_mmap(8*8) as *i64
145 return jcw_write_color(out, dstW, dstH, rgb, qtL, qtC, dcB, dcV, acB, acV, dcCO, dcSI, acCO, acSI, to_zz, from_zz, DM, scratch, ti, to)
146}
147
148func rd8(v: i64) -> i64 { var r: i64 = (v/8)*8; if r < 8 { r = 8 } return r }
149
150func main(argc: i64, argv: *i64) -> i64 {
151 if argc < 3 { cp_puts("usage: nx_cam_poster <in.ts> <out.jpg>\n\x00" as *u8); return 2 }
152 let inpath: *u8 = argv[1] as *u8
153 let outpath: *u8 = argv[2] as *u8
154
155 let fd: i64 = sys_openat_rd(inpath)
156 if fd < 0 { cp_puts("ERR: cannot open input\n\x00" as *u8); return 1 }
157 let filebuf: *u8 = sys_mmap(MAXSCAN + 512)
158 let es: *u8 = sys_mmap(MAXSCAN + 512)
159 let pidout: *i64 = sys_mmap(64) as *i64
160 let sps_nal: *u8 = sys_mmap(POSTER_MAGIC_1024)
161 let pps_nal: *u8 = sys_mmap(POSTER_MAGIC_1024)
162 let idr_nal: *u8 = sys_mmap(4 * POSTER_MAGIC_1024 * POSTER_MAGIC_1024)
163
164 // ---- PHASE 1: fast SPS/PPS pre-check (read only PRECHECK bytes -> ~30x faster CABAC skip) ----
165 var total: i64 = 0
166 var g1: i64 = 1
167 while g1 == 1 {
168 let r: i64 = sys_read(fd, ((filebuf as i64) + total) as *u8, PRECHECK - total)
169 if r <= 0 { g1 = 0 }
170 if r > 0 { total = total + r; if total >= PRECHECK { g1 = 0 } }
171 }
172 var eslen: i64 = demux_video_es(filebuf, total, es, pidout)
173 var spsnl: i64 = find_first_nal(es, eslen, 7, sps_nal, POSTER_MAGIC_1024)
174 var ppsnl: i64 = find_first_nal(es, eslen, 8, pps_nal, POSTER_MAGIC_1024)
175 if spsnl > 0 { if ppsnl > 0 {
176 let prb0: *u8 = sys_mmap(POSTER_MAGIC_1024)
177 let prl0: i64 = nal_to_rbsp(pps_nal, ppsnl, prb0)
178 let pp0: *i64 = sys_mmap(128) as *i64
179 nx_h264_parse_pps(prb0, prl0, pp0)
180 if pp0[2] == 1 { sys_close(fd); cp_puts("SKIP: CABAC stream (fast pre-check); CABAC decoder is a separate rung\n\x00" as *u8); return 10 }
181 } }
182
183 // ---- PHASE 2: CAVLC (or SPS/PPS not yet seen) -> read the rest up to MAXSCAN, then decode ----
184 var g2: i64 = 1
185 while g2 == 1 {
186 let r: i64 = sys_read(fd, ((filebuf as i64) + total) as *u8, MAXSCAN - total)
187 if r <= 0 { g2 = 0 }
188 if r > 0 { total = total + r; if total >= MAXSCAN { g2 = 0 } }
189 }
190 sys_close(fd)
191 eslen = demux_video_es(filebuf, total, es, pidout)
192 spsnl = find_first_nal(es, eslen, 7, sps_nal, POSTER_MAGIC_1024)
193 ppsnl = find_first_nal(es, eslen, 8, pps_nal, POSTER_MAGIC_1024)
194 let idrnl: i64 = find_first_nal(es, eslen, 5, idr_nal, 4 * POSTER_MAGIC_1024 * POSTER_MAGIC_1024)
195 // Name the real reason. Before this, an HEVC recording fell through to
196 // "no SPS" -- which reads as a CORRUPT FILE and sends you debugging the
197 // recording instead of the missing decoder.
198 if pidout[2] == 1 { if pidout[0] < 0 { cp_puts("SKIP: HEVC/H.265 stream; this poster path decodes H.264 only (H.265 decoder not built)\n\x00" as *u8); return 11 } }
199 if spsnl == 0 { cp_puts("ERR: no SPS in first 8MiB\n\x00" as *u8); return 3 }
200 if ppsnl == 0 { cp_puts("ERR: no PPS in first 8MiB\n\x00" as *u8); return 3 }
201 if idrnl == 0 { cp_puts("ERR: no IDR slice in first 8MiB\n\x00" as *u8); return 3 }
202
203 let sps_rbsp: *u8 = sys_mmap(POSTER_MAGIC_1024)
204 let srlen: i64 = nal_to_rbsp(sps_nal, spsnl, sps_rbsp)
205 let sps: *i64 = sys_mmap(128) as *i64
206 nx_h264_parse_sps(sps_rbsp, srlen, sps)
207 let pps_rbsp: *u8 = sys_mmap(POSTER_MAGIC_1024)
208 let prlen: i64 = nal_to_rbsp(pps_nal, ppsnl, pps_rbsp)
209 let pps: *i64 = sys_mmap(128) as *i64
210 nx_h264_parse_pps(pps_rbsp, prlen, pps)
211
212 if pps[2] == 1 { cp_puts("SKIP: CABAC stream (CAVLC poster decoder only); CABAC decoder is a separate rung\n\x00" as *u8); return 10 }
213 if sps[6] == 0 { cp_puts("SKIP: interlaced (frame_mbs_only=0) not supported\n\x00" as *u8); return 11 }
214
215 let mbW: i64 = sps[4] + 1
216 let mbH: i64 = sps[5] + 1
217 let Wcod: i64 = mbW * 16
218 let Hcod: i64 = mbH * 16
219 var cropW: i64 = sps[2]
220 var cropH: i64 = sps[3]
221 if cropW <= 0 { cropW = Wcod }
222 if cropH <= 0 { cropH = Hcod }
223 if cropW > Wcod { cropW = Wcod }
224 if cropH > Hcod { cropH = Hcod }
225 let cpW: i64 = mbW * 8
226 let cpH: i64 = mbH * 8
227
228 // reject a corrupt/oversized frame BEFORE any dimension-sized allocation (yf/uf/vf below). See CP_MAX_* above:
229 // an unvalidated mbW/mbH from a garbage SPS is the root of the 15-18GB anon-rss OOM-kills.
230 if mbW < 1 { cp_puts("ERR: bad SPS (mbW<1)\n\x00" as *u8); return 3 }
231 if mbH < 1 { cp_puts("ERR: bad SPS (mbH<1)\n\x00" as *u8); return 3 }
232 if mbW > CP_MAX_MB_DIM { cp_puts("SKIP: frame too wide (corrupt/unsupported SPS)\n\x00" as *u8); return 12 }
233 if mbH > CP_MAX_MB_DIM { cp_puts("SKIP: frame too tall (corrupt/unsupported SPS)\n\x00" as *u8); return 12 }
234 if mbW * mbH > CP_MAX_MBS { cp_puts("SKIP: frame too large (corrupt/unsupported SPS)\n\x00" as *u8); return 12 }
235
236 let idr_rbsp: *u8 = sys_mmap(4 * POSTER_MAGIC_1024 * POSTER_MAGIC_1024)
237 let irlen: i64 = nal_to_rbsp(idr_nal, idrnl, idr_rbsp)
238 let ntype: i64 = idr_nal[0] as i64
239
240 let yf: *u8 = sys_mmap(Wcod * Hcod + 64)
241 let uf: *u8 = sys_mmap(cpW * cpH + 64)
242 let vf: *u8 = sys_mmap(cpW * cpH + 64)
243 // ALLOCATION CHECK 2026-08-01: sys_mmap returns the RAW syscall value, so a
244 // failure is a negative errno cast to a pointer -- and the zeroing loop below
245 // writes straight through it. Under a ulimit -v cap that was a SIGSEGV that
246 // read as a codec limitation. Refuse with a distinct rc so a caller can retry
247 // at a higher cap instead of silently losing the recording.
248 if (yf as i64) < 0 { cp_puts("ERR: cannot allocate luma plane (raise the memory cap)\n\x00" as *u8); return 13 }
249 if (uf as i64) < 0 { cp_puts("ERR: cannot allocate chroma-U plane (raise the memory cap)\n\x00" as *u8); return 13 }
250 if (vf as i64) < 0 { cp_puts("ERR: cannot allocate chroma-V plane (raise the memory cap)\n\x00" as *u8); return 13 }
251 var zz: i64 = 0
252 while zz < Wcod * Hcod { yf[zz] = 0 as u8; zz = zz + 1 }
253 zz = 0
254 while zz < cpW * cpH { uf[zz] = 0 as u8; vf[zz] = 0 as u8; zz = zz + 1 }
255
256 let ok: i64 = nx_h264_decode_iframe(idr_rbsp, irlen, ntype, sps, pps, yf, uf, vf, mbW, mbH)
257
258 cp_puts("DECODE profile=\x00" as *u8); cp_num(sps[0]); cp_puts(" coded=\x00" as *u8); cp_num(Wcod); cp_puts("x\x00" as *u8); cp_num(Hcod)
259 cp_puts(" crop=\x00" as *u8); cp_num(cropW); cp_puts("x\x00" as *u8); cp_num(cropH); cp_puts(" mbs=\x00" as *u8); cp_num(mbW*mbH)
260 cp_puts(" idr_rbsp=\x00" as *u8); cp_num(irlen); cp_puts(" full_sync=\x00" as *u8); cp_num(ok); cp_puts("\n\x00" as *u8)
261
262 // HARDEN: never serve a partial/garbled frame -- suppress the poster write unless entropy fully synced.
263 if ok == 0 { cp_puts("PARTIAL: full_sync=0 -> poster SUPPRESSED (not written)\n\x00" as *u8); return 7 }
264
265 // poster dims (multiples of 8, aspect-preserving, no upscale)
266 var dstW: i64 = POSTER_MAX
267 var dstH: i64 = POSTER_MAX
268 if cropW >= cropH { dstW = POSTER_MAX; dstH = cropH * POSTER_MAX / cropW } else { dstH = POSTER_MAX; dstW = cropW * POSTER_MAX / cropH }
269 if cropW <= POSTER_MAX { if cropH <= POSTER_MAX { dstW = cropW; dstH = cropH } }
270 dstW = rd8(dstW)
271 dstH = rd8(dstH)
272
273 let dstrgb: *u8 = sys_mmap(dstW * dstH * 3 + 64)
274 build_poster_rgb(yf, uf, vf, Wcod, cpW, cropW, cropH, dstW, dstH, dstrgb)
275
276 let out: *u8 = sys_mmap(dstW * dstH * 4 + POSTER_MAGIC_8192)
277 let jlen: i64 = encode_jpeg(out, dstW, dstH, dstrgb)
278
279 let fd: i64 = sys_openat_wr(outpath, 0x1a4)
280 if fd < 0 { cp_puts("ERR: cannot open output\n\x00" as *u8); return 1 }
281 let wrote: i64 = sys_write(fd, out, jlen)
282 sys_close(fd)
283
284 cp_puts("POSTER wrote=\x00" as *u8); cp_puts(outpath); cp_puts(" dims=\x00" as *u8); cp_num(dstW); cp_puts("x\x00" as *u8); cp_num(dstH)
285 cp_puts(" jpeg_bytes=\x00" as *u8); cp_num(jlen); cp_puts(" written=\x00" as *u8); cp_num(wrote); cp_puts("\n\x00" as *u8)
286 cp_puts("POSTER-OK\n\x00" as *u8)
287 return 0
288}