nx_h264_ts_probe.nx source
↩ module page · 254 lines · 11384 B
1// nx_h264_ts_probe.nx -- STEP 0 decisive probe for the cam-poster build.
2// Reads a real MPEG-TS cam recording, demuxes the video PES (reusing the proven
3// nx_ts2fmp4 PAT->PMT->video-PID->ES logic), NAL-walks the elementary stream for
4// SPS (type 7) + PPS (type 8), and reports the two facts that LOCK the decode path:
5// PPS.entropy_coding_mode_flag (0=CAVLC, 1=CABAC) -> which entropy decoder
6// PPS.transform_8x8_mode_flag (High-profile 8x8) -> which inverse transform
7// plus SPS profile_idc/level/dims. main() is intentionally decomposed into small
8// functions (nx_cc SIGSEGVs on an over-large function body). license_tier: ORIGINAL
9import "nx_syscalls.nx"
10import "nx_h264_bits.nx"
11import "nx_h264_sps.nx"
12import "nx_h264_pps.nx"
13const K_MAGIC_1024: i64 = 1024
14
15const MAXSCAN: i64 = 8388608 // 8 MiB from head: covers the first IDR + its SPS/PPS
16
17func pp(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
18func pn(v: i64) -> i64 {
19 let bb: *u8 = sys_mmap(32)
20 var m: i64 = v
21 if m < 0 { sys_write(1, "-\x00" as *u8, 1); m = 0 - m }
22 let t: *u8 = sys_mmap(32)
23 var k: i64 = 0
24 if m == 0 { t[0] = 48 as u8; k = 1 }
25 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
26 var i: i64 = 0
27 while i < k { bb[i] = t[k - 1 - i]; i = i + 1 }
28 sys_write(1, bb, k)
29 return 0
30}
31
32// --- self-contained bit reader (explicit i64 cursor; NO imported struct) ---
33func lb_bit(data: *u8, len: i64, pos: *i64) -> i64 {
34 let bp: i64 = pos[0]
35 let byte: i64 = bp >> 3
36 pos[0] = bp + 1
37 if byte >= len { return 0 }
38 return ((data[byte] as i64) >> (7 - (bp & 7))) & 1
39}
40func lb_bits(data: *u8, len: i64, pos: *i64, n: i64) -> i64 {
41 var v: i64 = 0
42 var i: i64 = 0
43 while i < n { v = (v << 1) | lb_bit(data, len, pos); i = i + 1 }
44 return v
45}
46func lb_ue(data: *u8, len: i64, pos: *i64) -> i64 {
47 var z: i64 = 0
48 var seen: i64 = 0
49 while seen == 0 {
50 if (pos[0] >> 3) >= len { return (1 << z) - 1 }
51 let b: i64 = lb_bit(data, len, pos)
52 if b == 1 { seen = 1 } else { z = z + 1 }
53 }
54 let rest: i64 = lb_bits(data, len, pos, z)
55 return (1 << z) - 1 + rest
56}
57func lb_se(data: *u8, len: i64, pos: *i64) -> i64 {
58 let k: i64 = lb_ue(data, len, pos)
59 if k == 0 { return 0 }
60 let mag: i64 = (k + 1) / 2
61 if (k & 1) == 1 { return mag }
62 return 0 - mag
63}
64
65// H.264 more_rbsp_data(): cursor before the rbsp_stop_one_bit (last '1' bit).
66func probe_more_rbsp(data: *u8, len: i64, curpos: i64) -> i64 {
67 var last: i64 = len - 1
68 while last >= 0 {
69 if data[last] != (0 as u8) {
70 var bit: i64 = 0
71 while (((data[last] as i64) >> bit) & 1) == 0 { bit = bit + 1 }
72 let stop_pos: i64 = last * 8 + (7 - bit)
73 if curpos < stop_pos { return 1 }
74 return 0
75 }
76 last = last - 1
77 }
78 return 0
79}
80
81// Full PPS walk to reach transform_8x8_mode_flag. out[2]=entropy out[3]=pic_init_qp
82// out[4]=nsg out[5]=transform_8x8 out[6]=more_rbsp_present out[7]=slice_group_unparsed
83func probe_parse_pps_full(rbsp: *u8, len: i64, out: *i64) -> i64 {
84 var q: i64 = 0
85 while q < 8 { out[q] = 0; q = q + 1 }
86 let pos: *i64 = sys_mmap(16) as *i64
87 pos[0] = 0
88 out[0] = lb_ue(rbsp, len, pos)
89 out[1] = lb_ue(rbsp, len, pos)
90 out[2] = lb_bits(rbsp, len, pos, 1)
91 lb_bits(rbsp, len, pos, 1)
92 let nsg: i64 = lb_ue(rbsp, len, pos)
93 out[4] = nsg
94 if nsg > 0 { out[7] = 1; return 0 }
95 lb_ue(rbsp, len, pos)
96 lb_ue(rbsp, len, pos)
97 lb_bits(rbsp, len, pos, 1)
98 lb_bits(rbsp, len, pos, 2)
99 let qpd: i64 = lb_se(rbsp, len, pos)
100 out[3] = 26 + qpd
101 lb_se(rbsp, len, pos)
102 lb_se(rbsp, len, pos)
103 lb_bits(rbsp, len, pos, 1)
104 lb_bits(rbsp, len, pos, 1)
105 lb_bits(rbsp, len, pos, 1)
106 let mr: i64 = probe_more_rbsp(rbsp, len, pos[0])
107 out[6] = mr
108 if mr == 1 { out[5] = lb_bits(rbsp, len, pos, 1) }
109 return 0
110}
111
112// Read up to MAXSCAN bytes from path into buf; returns byte count.
113func read_head(path: *u8, buf: *u8) -> i64 {
114 let fd: i64 = sys_openat_rd(path)
115 if fd < 0 { return 0 - 1 }
116 var total: i64 = 0
117 var go: i64 = 1
118 while go == 1 {
119 let r: i64 = sys_read(fd, ((buf as i64) + total) as *u8, MAXSCAN - total)
120 if r <= 0 { go = 0 }
121 if r > 0 { total = total + r; if total >= MAXSCAN { go = 0 } }
122 }
123 sys_close(fd)
124 return total
125}
126
127// Demux the video elementary stream from a TS buffer. pidout[0]=vidpid pidout[1]=pmtpid.
128// Returns es length.
129func demux_video_es(filebuf: *u8, total: i64, es: *u8, pidout: *i64) -> i64 {
130 var eslen: i64 = 0
131 var pmtpid: i64 = 0 - 1
132 var vidpid: i64 = 0 - 1
133 var i: i64 = 0
134 while i + 188 <= total {
135 if filebuf[i] != (0x47 as u8) { i = i + 1 } else {
136 let b1: i64 = filebuf[i+1] as i64
137 let pusi: i64 = (b1 >> 6) & 1
138 let pid: i64 = ((b1 & 0x1f) << 8) | (filebuf[i+2] as i64)
139 let afc: i64 = ((filebuf[i+3] as i64) >> 4) & 3
140 var payoff: i64 = i + 4
141 if afc == 2 { payoff = i + 188 } else { if afc == 3 { payoff = i + 5 + (filebuf[i+4] as i64) } }
142 if payoff < i + 188 {
143 if pid == 0 { if pmtpid < 0 {
144 var p: i64 = payoff
145 if pusi == 1 { p = p + 1 + (filebuf[p] as i64) }
146 let sl: i64 = (((filebuf[p+1] as i64) & 0x0f) << 8) | (filebuf[p+2] as i64)
147 var pp2: i64 = p + 8
148 let pe: i64 = p + 3 + sl - 4
149 while pp2 + 4 <= pe { let pr: i64 = ((filebuf[pp2] as i64) << 8) | (filebuf[pp2+1] as i64); let pm: i64 = (((filebuf[pp2+2] as i64) & 0x1f) << 8) | (filebuf[pp2+3] as i64); if pr != 0 { if pmtpid < 0 { pmtpid = pm } } pp2 = pp2 + 4 }
150 } }
151 if pid == pmtpid { if pmtpid >= 0 { if vidpid < 0 {
152 var p: i64 = payoff
153 if pusi == 1 { p = p + 1 + (filebuf[p] as i64) }
154 let sl: i64 = (((filebuf[p+1] as i64) & 0x0f) << 8) | (filebuf[p+2] as i64)
155 let pil: i64 = (((filebuf[p+10] as i64) & 0x0f) << 8) | (filebuf[p+11] as i64)
156 var ep: i64 = p + 12 + pil
157 let pe: i64 = p + 3 + sl - 4
158 while ep + 5 <= pe { let st: i64 = filebuf[ep] as i64; let ed: i64 = (((filebuf[ep+1] as i64) & 0x1f) << 8) | (filebuf[ep+2] as i64); let el: i64 = (((filebuf[ep+3] as i64) & 0x0f) << 8) | (filebuf[ep+4] as i64); if st == 0x1b { if vidpid < 0 { vidpid = ed } } ep = ep + 5 + el }
159 } } }
160 if pid == vidpid { if vidpid >= 0 {
161 var p: i64 = payoff
162 if pusi == 1 { if filebuf[p] == (0 as u8) { if filebuf[p+1] == (0 as u8) { if filebuf[p+2] == (1 as u8) { p = p + 9 + (filebuf[p+8] as i64) } } } }
163 var qv: i64 = p
164 while qv < i + 188 { if eslen < MAXSCAN { es[eslen] = filebuf[qv]; eslen = eslen + 1 } qv = qv + 1 }
165 } }
166 }
167 i = i + 188
168 }
169 }
170 pidout[0] = vidpid
171 pidout[1] = pmtpid
172 return eslen
173}
174
175// Find the FIRST Annex-B NAL of type `want` in es[0..eslen); copy its bytes
176// (incl the 1-byte NAL header) into dst[0..cap); return the NAL length (0 if none).
177func find_first_nal(es: *u8, eslen: i64, want: i64, dst: *u8, cap: i64) -> i64 {
178 var k: i64 = 0
179 while k + 3 < eslen {
180 if es[k] == (0 as u8) { if es[k+1] == (0 as u8) { if es[k+2] == (1 as u8) {
181 let ns: i64 = k + 3
182 let nt: i64 = (es[ns] as i64) & 0x1f
183 if nt == want {
184 var m: i64 = ns
185 var ne: i64 = eslen
186 var fnd: i64 = 0
187 while m + 3 < eslen { if fnd == 0 { if es[m] == (0 as u8) { if es[m+1] == (0 as u8) { if es[m+2] == (1 as u8) { ne = m; fnd = 1 } } } if fnd == 0 { m = m + 1 } } else { m = eslen } }
188 if fnd == 1 { if es[ne-1] == (0 as u8) { ne = ne - 1 } }
189 let nl: i64 = ne - ns
190 var c: i64 = 0
191 while c < nl { if c < cap { dst[c] = es[ns+c] } c = c + 1 }
192 return nl
193 }
194 k = ns
195 } else { k = k + 1 } } else { k = k + 1 } } else { k = k + 1 }
196 }
197 return 0
198}
199
200// de-emulate a NAL payload (skip the 1-byte NAL header) into rbsp; return rbsp len.
201func nal_to_rbsp(nal: *u8, nallen: i64, rbsp: *u8) -> i64 {
202 return nx_h264_unescape_rbsp(((nal as i64) + 1) as *u8, nallen - 1, rbsp)
203}
204
205func report_sps(nal: *u8, nallen: i64) -> i64 {
206 let rbsp: *u8 = sys_mmap(K_MAGIC_1024)
207 let rlen: i64 = nal_to_rbsp(nal, nallen, rbsp)
208 let sout: *i64 = sys_mmap(8 * 16) as *i64
209 nx_h264_parse_sps(rbsp, rlen, sout)
210 pp("SPS: profile_idc=\x00" as *u8); pn(sout[0]); pp(" level_idc=\x00" as *u8); pn(sout[1])
211 pp(" width=\x00" as *u8); pn(sout[2]); pp(" height=\x00" as *u8); pn(sout[3])
212 pp(" chroma_format_idc=\x00" as *u8); pn(sout[7]); pp(" is_high=\x00" as *u8); pn(nx_h264_is_high(sout[0]))
213 pp(" scaling_unparsed=\x00" as *u8); pn(sout[8]); pp(" nal_len=\x00" as *u8); pn(nallen); pp("\n\x00" as *u8)
214 return 0
215}
216
217func report_pps(nal: *u8, nallen: i64) -> i64 {
218 let rbsp: *u8 = sys_mmap(K_MAGIC_1024)
219 let rlen: i64 = nal_to_rbsp(nal, nallen, rbsp)
220 let pout: *i64 = sys_mmap(8 * 16) as *i64
221 nx_h264_parse_pps(rbsp, rlen, pout)
222 let pf: *i64 = sys_mmap(8 * 16) as *i64
223 probe_parse_pps_full(rbsp, rlen, pf)
224 pp("PPS: entropy_coding_mode=\x00" as *u8); pn(pout[2]); pp(" pic_init_qp=\x00" as *u8); pn(pout[3])
225 pp(" nsg=\x00" as *u8); pn(pout[4]); pp(" nal_len=\x00" as *u8); pn(nallen); pp("\n\x00" as *u8)
226 pp("PPS-FULL: entropy_xcheck=\x00" as *u8); pn(pf[2]); pp(" more_rbsp=\x00" as *u8); pn(pf[6])
227 pp(" transform_8x8_mode=\x00" as *u8); pn(pf[5]); pp(" slice_group_unparsed=\x00" as *u8); pn(pf[7]); pp("\n\x00" as *u8)
228 if pout[2] == 1 { pp("VERDICT: CABAC entropy (High-profile CABAC decoder REQUIRED)\n\x00" as *u8) }
229 if pout[2] == 0 { pp("VERDICT: CAVLC entropy (existing bit-exact CAVLC path applies)\n\x00" as *u8) }
230 return 0
231}
232
233func main(argc: i64, argv: *i64) -> i64 {
234 if argc < 2 { pp("usage: nx_h264_ts_probe <in.ts>\n\x00" as *u8); return 2 }
235 let inpath: *u8 = argv[1] as *u8
236 let filebuf: *u8 = sys_mmap(MAXSCAN + 512)
237 let total: i64 = read_head(inpath, filebuf)
238 if total < 0 { pp("ERR: cannot open input\n\x00" as *u8); return 1 }
239 let es: *u8 = sys_mmap(MAXSCAN + 512)
240 let pidout: *i64 = sys_mmap(64) as *i64
241 let eslen: i64 = demux_video_es(filebuf, total, es, pidout)
242 pp("TS-PROBE file=\x00" as *u8); pp(inpath); pp("\n\x00" as *u8)
243 pp("scanned_bytes=\x00" as *u8); pn(total); pp(" video_pid=\x00" as *u8); pn(pidout[0])
244 pp(" pmt_pid=\x00" as *u8); pn(pidout[1]); pp(" es_bytes=\x00" as *u8); pn(eslen); pp("\n\x00" as *u8)
245 let sps: *u8 = sys_mmap(K_MAGIC_1024)
246 let pps: *u8 = sys_mmap(K_MAGIC_1024)
247 let spslen: i64 = find_first_nal(es, eslen, 7, sps, K_MAGIC_1024)
248 let ppslen: i64 = find_first_nal(es, eslen, 8, pps, K_MAGIC_1024)
249 if spslen == 0 { pp("SPS: NOT-FOUND\n\x00" as *u8) }
250 if spslen > 0 { report_sps(sps, spslen) }
251 if ppslen == 0 { pp("PPS: NOT-FOUND\n\x00" as *u8) }
252 if ppslen > 0 { report_pps(pps, ppslen) }
253 return 0
254}