nx_ts_es.nx source
↩ module page · 100 lines · 5452 B
1// nx_ts_es.nx -- MPEG-TS -> H.264 elementary-stream helpers, LIFTED VERBATIM from
2// nx_cam_poster.nx (lines 68-152) 2026-07-31 so a second consumer does not copy-paste
3// them (rule 15). Behaviour must stay byte-identical: nx_cam_poster_gate is the oracle.
4// demux_video_es -- PAT/PMT walk -> video PID -> ES bytes; codec via nx_ts_nal
5// find_first_nal -- first NAL of a given type out of an ES
6// nal_to_rbsp -- emulation-prevention-byte removal
7// license_tier: ORIGINAL
8import "nx_syscalls.nx"
9import "nx_ts_nal.nx"
10
11// scan window for the TS->ES pass. Owned HERE because demux_video_es reads it;
12// the compiler rejects a module const used above its declaration (it would
13// silently read 0), so a lib cannot borrow this from its caller.
14const MAXSCAN: i64 = 8388608
15
16func demux_video_es(filebuf: *u8, total: i64, es: *u8, pidout: *i64) -> i64 {
17 var eslen: i64 = 0
18 var pmtpid: i64 = 0 - 1
19 var vidpid: i64 = 0 - 1
20 // HEVC seen in the PMT but deliberately NOT claimed as vidpid (this organ
21 // decodes H.264). Surfaced so the caller can refuse BY NAME instead of
22 // reporting the generic "no video PID", which reads as a corrupt file.
23 var cphevc: i64 = 0
24 var i: i64 = 0
25 while i + 188 <= total {
26 if filebuf[i] != (0x47 as u8) { i = i + 1 } else {
27 let b1: i64 = filebuf[i+1] as i64
28 let pusi: i64 = (b1 >> 6) & 1
29 let pid: i64 = ((b1 & 0x1f) << 8) | (filebuf[i+2] as i64)
30 let afc: i64 = ((filebuf[i+3] as i64) >> 4) & 3
31 var payoff: i64 = i + 4
32 if afc == 2 { payoff = i + 188 } else { if afc == 3 { payoff = i + 5 + (filebuf[i+4] as i64) } }
33 if payoff < i + 188 {
34 if pid == 0 { if pmtpid < 0 {
35 var p: i64 = payoff
36 if pusi == 1 { p = p + 1 + (filebuf[p] as i64) }
37 let sl: i64 = (((filebuf[p+1] as i64) & 0x0f) << 8) | (filebuf[p+2] as i64)
38 var pp2: i64 = p + 8
39 let pe: i64 = p + 3 + sl - 4
40 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 }
41 } }
42 if pid == pmtpid { if pmtpid >= 0 { if vidpid < 0 {
43 var p: i64 = payoff
44 if pusi == 1 { p = p + 1 + (filebuf[p] as i64) }
45 let sl: i64 = (((filebuf[p+1] as i64) & 0x0f) << 8) | (filebuf[p+2] as i64)
46 let pil: i64 = (((filebuf[p+10] as i64) & 0x0f) << 8) | (filebuf[p+11] as i64)
47 var ep: i64 = p + 12 + pil
48 let pe: i64 = p + 3 + sl - 4
49 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); let vc: i64 = tsn_codec_of_stream_type(st); if vc == TSN_H264 { if vidpid < 0 { vidpid = ed } } if vc == TSN_HEVC { cphevc = 1 } ep = ep + 5 + el }
50 } } }
51 if pid == vidpid { if vidpid >= 0 {
52 var p: i64 = payoff
53 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) } } } }
54 var qv: i64 = p
55 while qv < i + 188 { if eslen < MAXSCAN { es[eslen] = filebuf[qv]; eslen = eslen + 1 } qv = qv + 1 }
56 } }
57 }
58 i = i + 188
59 }
60 }
61 pidout[0] = vidpid
62 pidout[1] = pmtpid
63 pidout[2] = cphevc
64 return eslen
65}
66
67// H.264 ONLY, DELIBERATELY. This organ DECODES the bitstream (nx_h264_parse_pps,
68// CAVLC/CABAC) to render the poster image -- it does not merely demux. Widening
69// the PMT to accept HEVC here would hand H.265 NALs to an H.264 decoder and turn
70// a clean "no video PID" refusal into a MISDECODE. An HEVC poster needs an H.265
71// decoder, which is a separate arc; until then HEVC is REFUSED BY NAME (see the
72// cphevc path) rather than silently producing nothing.
73func find_first_nal(es: *u8, eslen: i64, want: i64, dst: *u8, cap: i64) -> i64 {
74 var k: i64 = 0
75 while k + 3 < eslen {
76 if es[k] == (0 as u8) { if es[k+1] == (0 as u8) { if es[k+2] == (1 as u8) {
77 let ns: i64 = k + 3
78 let nt: i64 = (es[ns] as i64) & 0x1f
79 if nt == want {
80 var m: i64 = ns
81 var ne: i64 = eslen
82 var fnd: i64 = 0
83 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 } }
84 if fnd == 1 { if es[ne-1] == (0 as u8) { ne = ne - 1 } }
85 let nl: i64 = ne - ns
86 var c: i64 = 0
87 while c < nl { if c < cap { dst[c] = es[ns+c] } c = c + 1 }
88 return nl
89 }
90 k = ns
91 } else { k = k + 1 } } else { k = k + 1 } } else { k = k + 1 }
92 }
93 return 0
94}
95
96func nal_to_rbsp(nal: *u8, nallen: i64, rbsp: *u8) -> i64 {
97 return nx_h264_unescape_rbsp(((nal as i64) + 1) as *u8, nallen - 1, rbsp)
98}
99
100// YUV420 (cropped region) -> RGB, integer box-average downscale into dst[dstW*dstH*3].