code wiki / _hdl_build / nx_h264_emit.nx
nx_h264_emit.nx source
↩ module page · 249 lines · 9940 B
1// nx_h264_emit.nx -- emit a VALID H.264 Annex-B bitstream: SPS + PPS + IDR slice of I_PCM macroblocks.
2//
3// argv: <outfile> [mbw] [mbh]
4//
5// WHY I_PCM: it carries raw samples, so a decoder that accepts this stream has exercised our NAL
6// framing, emulation-prevention, exp-golomb writer, SPS, PPS and slice header WITHOUT us needing a
7// transform, prediction or entropy coder. It isolates BITSTREAM CONFORMANCE from compression, which
8// is exactly the axis `video|codec-conformance` declares. The pixels come back bit-exact or the
9// stream was wrong -- there is no "close enough" to hide behind.
10//
11// The whole point is that the judge is EXTERNAL: ffmpeg is the reference decoder and it is installed
12// on this host. Our own gates cannot grade this; a decoder we did not write either accepts the
13// stream or does not.
14// license_tier: ORIGINAL No hw writes (Rule 26).
15import "nx_seg_store.nx"
16
17// Local print/parse helpers: _p/_fn live in the ecomat lib, which this organ has no reason to pull
18// in, and nx_seg_store exports no atoi. Defining them here keeps the emitter dependency-light --
19// it should need a bit writer and a file descriptor, nothing else.
20func _p(s: *u8) -> i64 {
21 var n: i64 = 0
22 while s[n] != (0 as u8) { n = n + 1 }
23 sys_write(1, s, n)
24 return 0
25}
26
27func _fn(fd: i64, v: i64) -> i64 {
28 let t: *u8 = sys_mmap(32)
29 var m: i64 = v
30 var neg: i64 = 0
31 if m < 0 { neg = 1; m = 0 - m }
32 var k: i64 = 0
33 if m == 0 { t[0] = 48 as u8; k = 1 }
34 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
35 let o: *u8 = sys_mmap(40)
36 var p: i64 = 0
37 if neg == 1 { o[p] = 45 as u8; p = p + 1 }
38 var i: i64 = 0
39 while i < k { o[p] = t[k - 1 - i]; p = p + 1; i = i + 1 }
40 sys_write(fd, o, p)
41 return 0
42}
43
44func h_atoi(s: *u8) -> i64 {
45 var v: i64 = 0
46 var i: i64 = 0
47 while s[i] != (0 as u8) {
48 let c: i64 = s[i] as i64
49 if c >= 48 { if c <= 57 { v = (v * 10) + (c - 48) } }
50 i = i + 1
51 }
52 return v
53}
54
55const H_MODE: i64 = 0x1a4
56const H_MAXBUF: i64 = 4194304
57
58// ---- bit writer: MSB-first into a byte buffer, pos measured in BITS ----
59func bw_put(buf: *u8, posbox: *i64, val: i64, nbits: i64) -> i64 {
60 var i: i64 = nbits - 1
61 while i >= 0 {
62 let bit: i64 = (val >> i) & 1
63 let p: i64 = posbox[0]
64 let byi: i64 = p >> 3
65 let bii: i64 = 7 - (p & 7)
66 if bit == 1 { buf[byi] = ((buf[byi] as i64) | (1 << bii)) as u8 } else { buf[byi] = ((buf[byi] as i64) & (255 - (1 << bii))) as u8 }
67 posbox[0] = p + 1
68 i = i - 1
69 }
70 return 0
71}
72
73func bw_bitlen(v: i64) -> i64 {
74 var n: i64 = 0
75 var x: i64 = v
76 while x > 0 { n = n + 1; x = x >> 1 }
77 return n
78}
79
80// unsigned exp-golomb: codeNum v -> (n-1) zeros, then (v+1) in n bits, n = bitlen(v+1)
81func bw_ue(buf: *u8, posbox: *i64, v: i64) -> i64 {
82 let c: i64 = v + 1
83 let n: i64 = bw_bitlen(c)
84 bw_put(buf, posbox, 0, n - 1)
85 bw_put(buf, posbox, c, n)
86 return 0
87}
88
89// signed exp-golomb: k>0 -> 2k-1, k<=0 -> -2k
90func bw_se(buf: *u8, posbox: *i64, v: i64) -> i64 {
91 if v > 0 { return bw_ue(buf, posbox, (2 * v) - 1) }
92 return bw_ue(buf, posbox, 0 - (2 * v))
93}
94
95// rbsp_trailing_bits: a single 1 then zeros to the byte boundary
96func bw_trailing(buf: *u8, posbox: *i64) -> i64 {
97 bw_put(buf, posbox, 1, 1)
98 while (posbox[0] & 7) != 0 { bw_put(buf, posbox, 0, 1) }
99 return 0
100}
101
102// pcm_alignment_zero_bits: zeros to the byte boundary (no leading 1)
103func bw_align0(buf: *u8, posbox: *i64) -> i64 {
104 while (posbox[0] & 7) != 0 { bw_put(buf, posbox, 0, 1) }
105 return 0
106}
107
108// ---- NAL assembly: start code + header byte + RBSP with emulation prevention ----
109// A raw 00 00 00/01/02/03 inside the payload would be mistaken for a start code, so the spec
110// requires an escape byte 0x03 after any two consecutive zero bytes. Omitting this is the classic
111// way a stream decodes fine on short test data and corrupts on real data.
112func nal_emit(out: *u8, obox: *i64, nal_ref_idc: i64, nal_type: i64, rbsp: *u8, rlen: i64) -> i64 {
113 var o: i64 = obox[0]
114 out[o] = 0 as u8; o = o + 1
115 out[o] = 0 as u8; o = o + 1
116 out[o] = 0 as u8; o = o + 1
117 out[o] = 1 as u8; o = o + 1
118 out[o] = (((nal_ref_idc & 3) << 5) | (nal_type & 31)) as u8
119 o = o + 1
120 var zeros: i64 = 0
121 var i: i64 = 0
122 while i < rlen {
123 let b: i64 = rbsp[i] as i64
124 if zeros >= 2 { if b <= 3 {
125 out[o] = 3 as u8
126 o = o + 1
127 zeros = 0
128 } }
129 out[o] = b as u8
130 o = o + 1
131 if b == 0 { zeros = zeros + 1 } else { zeros = 0 }
132 i = i + 1
133 }
134 obox[0] = o
135 return 0
136}
137
138func main(argc: i64, argv: *i64) -> i64 {
139 if argc < 2 {
140 _p("usage: nx_h264_emit <outfile> [mb_width] [mb_height]\n" as *u8)
141 _p(" emits SPS+PPS+IDR(I_PCM) Annex-B; verify with the ffmpeg reference decoder.\n" as *u8)
142 sys_exit(1)
143 return 1
144 }
145 let outpath: *u8 = argv[1] as *u8
146 var mbw: i64 = 1
147 var mbh: i64 = 1
148 if argc >= 3 { mbw = h_atoi(argv[2] as *u8) }
149 if argc >= 4 { mbh = h_atoi(argv[3] as *u8) }
150 if mbw < 1 { mbw = 1 }
151 if mbh < 1 { mbh = 1 }
152
153 let rbsp: *u8 = sys_mmap(H_MAXBUF)
154 let out: *u8 = sys_mmap(H_MAXBUF)
155 let pos: *i64 = sys_mmap(16) as *i64
156 let obox: *i64 = sys_mmap(16) as *i64
157 obox[0] = 0
158
159 // ---------------- SPS (nal_type 7) ----------------
160 pos[0] = 0
161 bw_put(rbsp, pos, 66, 8) // profile_idc = 66 (Baseline)
162 bw_put(rbsp, pos, 0, 8) // constraint_set flags + reserved_zero_2bits
163 bw_put(rbsp, pos, 10, 8) // level_idc = 1.0
164 bw_ue(rbsp, pos, 0) // seq_parameter_set_id
165 bw_ue(rbsp, pos, 0) // log2_max_frame_num_minus4 -> 4
166 bw_ue(rbsp, pos, 2) // pic_order_cnt_type = 2 (decode order == display order)
167 bw_ue(rbsp, pos, 0) // max_num_ref_frames
168 bw_put(rbsp, pos, 0, 1) // gaps_in_frame_num_value_allowed_flag
169 bw_ue(rbsp, pos, mbw - 1) // pic_width_in_mbs_minus1
170 bw_ue(rbsp, pos, mbh - 1) // pic_height_in_map_units_minus1
171 bw_put(rbsp, pos, 1, 1) // frame_mbs_only_flag
172 bw_put(rbsp, pos, 0, 1) // direct_8x8_inference_flag
173 bw_put(rbsp, pos, 0, 1) // frame_cropping_flag
174 bw_put(rbsp, pos, 0, 1) // vui_parameters_present_flag
175 bw_trailing(rbsp, pos)
176 nal_emit(out, obox, 3, 7, rbsp, pos[0] >> 3)
177
178 // ---------------- PPS (nal_type 8) ----------------
179 pos[0] = 0
180 bw_ue(rbsp, pos, 0) // pic_parameter_set_id
181 bw_ue(rbsp, pos, 0) // seq_parameter_set_id
182 bw_put(rbsp, pos, 0, 1) // entropy_coding_mode_flag = 0 (CAVLC)
183 bw_put(rbsp, pos, 0, 1) // bottom_field_pic_order_in_frame_present_flag
184 bw_ue(rbsp, pos, 0) // num_slice_groups_minus1
185 bw_ue(rbsp, pos, 0) // num_ref_idx_l0_default_active_minus1
186 bw_ue(rbsp, pos, 0) // num_ref_idx_l1_default_active_minus1
187 bw_put(rbsp, pos, 0, 1) // weighted_pred_flag
188 bw_put(rbsp, pos, 0, 2) // weighted_bipred_idc
189 bw_se(rbsp, pos, 0) // pic_init_qp_minus26
190 bw_se(rbsp, pos, 0) // pic_init_qs_minus26
191 bw_se(rbsp, pos, 0) // chroma_qp_index_offset
192 bw_put(rbsp, pos, 0, 1) // deblocking_filter_control_present_flag
193 bw_put(rbsp, pos, 0, 1) // constrained_intra_pred_flag
194 bw_put(rbsp, pos, 0, 1) // redundant_pic_cnt_present_flag
195 bw_trailing(rbsp, pos)
196 nal_emit(out, obox, 3, 8, rbsp, pos[0] >> 3)
197
198 // ---------------- IDR slice (nal_type 5) ----------------
199 pos[0] = 0
200 bw_ue(rbsp, pos, 0) // first_mb_in_slice
201 bw_ue(rbsp, pos, 7) // slice_type 7 = I (all slices in pic are I)
202 bw_ue(rbsp, pos, 0) // pic_parameter_set_id
203 bw_put(rbsp, pos, 0, 4) // frame_num, u(log2_max_frame_num = 4)
204 bw_ue(rbsp, pos, 0) // idr_pic_id (IdrPicFlag)
205 bw_put(rbsp, pos, 0, 1) // dec_ref_pic_marking: no_output_of_prior_pics_flag
206 bw_put(rbsp, pos, 0, 1) // dec_ref_pic_marking: long_term_reference_flag
207 bw_se(rbsp, pos, 0) // slice_qp_delta
208
209 // slice_data: every macroblock is I_PCM (mb_type 25 in an I slice)
210 let nmb: i64 = mbw * mbh
211 var m: i64 = 0
212 while m < nmb {
213 bw_ue(rbsp, pos, 25) // mb_type = I_PCM
214 bw_align0(rbsp, pos) // pcm_alignment_zero_bits
215 let mbx: i64 = m % mbw
216 let mby: i64 = m / mbw
217 // 256 luma samples: a deterministic ramp so a wrong stride shows up as a visible shear
218 var y: i64 = 0
219 while y < 16 {
220 var x: i64 = 0
221 while x < 16 {
222 bw_put(rbsp, pos, ((y * 16) + x) & 255, 8)
223 x = x + 1
224 }
225 y = y + 1
226 }
227 // 64 Cb then 64 Cr, varied per macroblock so MB ordering is checkable too
228 var c: i64 = 0
229 while c < 64 { bw_put(rbsp, pos, (64 + mbx) & 255, 8); c = c + 1 }
230 c = 0
231 while c < 64 { bw_put(rbsp, pos, (192 - mby) & 255, 8); c = c + 1 }
232 m = m + 1
233 }
234 bw_trailing(rbsp, pos)
235 nal_emit(out, obox, 3, 5, rbsp, pos[0] >> 3)
236
237 let fd: i64 = sys_openat_wr(outpath, H_MODE)
238 if fd < 0 { _p("NX-H264-EMIT RED reason=cannot-open-output\n" as *u8); sys_exit(2); return 2 }
239 sys_write(fd, out, obox[0])
240 sys_close(fd)
241
242 _p("NX-H264-EMIT ok " as *u8); _p(outpath)
243 _p(" mbs=" as *u8); _fn(1, nmb)
244 _p(" res=" as *u8); _fn(1, mbw * 16); _p("x" as *u8); _fn(1, mbh * 16)
245 _p(" bytes=" as *u8); _fn(1, obox[0]); _p("\n" as *u8)
246 _p(" verify EXTERNALLY: ffmpeg -i " as *u8); _p(outpath); _p(" -f rawvideo -pix_fmt yuv420p out.yuv\n" as *u8)
247 sys_exit(0)
248 return 0
249}