nx_av1_seq.nx source
↩ module page · 287 lines · 10432 B
1// nx_av1_seq.nx -- AV1/AV2 sequence header, writer and reader.
2//
3// Layer three, on top of nx_av1_ec.nx (symbol coder) and nx_av1_obu.nx (OBU
4// framing). The sequence header is what tells a decoder the frame dimensions,
5// bit depth, chroma subsampling and which coding tools are enabled -- nothing
6// downstream can be parsed without it.
7//
8// THE REDUCED PATH IS THE AVIF PATH. A still image sets
9// reduced_still_picture_header, which collapses the operating-point list, the
10// timing model and every inter-frame tool flag out of the syntax. AVIF files
11// take this branch, so implementing it correctly is what makes AVIF reachable
12// once the tile decoder lands. The full path is written too, but the reduced
13// one is the one round-tripped here.
14//
15// FRAME SIZE IS SELF-DESCRIBING. max_frame_width_minus_1 is written in
16// frame_width_bits_minus_1 + 1 bits -- the WIDTH OF THE WIDTH FIELD is itself
17// coded in the header, immediately before it. Reading the dimension with a
18// fixed width works on 16-bit-ish sizes and silently mis-parses everything
19// after it on any other, because the bit cursor is then wrong for the entire
20// remainder of the header.
21//
22// THE sRGB SHORTCUT. When colour primaries are BT.709, transfer is sRGB and
23// the matrix is identity, the spec SKIPS color_range and forces 4:4:4 full
24// range. A parser that reads color_range anyway consumes one bit too many and
25// desynchronises the rest of colour config.
26//
27// genealogy_id: av1_spec_5_5_sequence_header
28// lineage_id: nx_av1_seq_v1
29// license_tier: ORIGINAL
30
31import "nx_syscalls.nx"
32import "nx_bitstream.nx"
33
34const NX_SEQ_BW_BYTES: i64 = 32
35
36// parsed field slots
37const NX_SEQ_PROFILE: i64 = 0
38const NX_SEQ_STILL: i64 = 1
39const NX_SEQ_REDUCED: i64 = 2
40const NX_SEQ_LEVEL: i64 = 3
41const NX_SEQ_MAXW: i64 = 4
42const NX_SEQ_MAXH: i64 = 5
43const NX_SEQ_SB128: i64 = 6
44const NX_SEQ_FILTER_INTRA: i64 = 7
45const NX_SEQ_EDGE_FILTER: i64 = 8
46const NX_SEQ_SUPERRES: i64 = 9
47const NX_SEQ_CDEF: i64 = 10
48const NX_SEQ_RESTORATION: i64 = 11
49const NX_SEQ_BITDEPTH: i64 = 12
50const NX_SEQ_MONO: i64 = 13
51const NX_SEQ_SUBX: i64 = 14
52const NX_SEQ_SUBY: i64 = 15
53const NX_SEQ_RANGE: i64 = 16
54const NX_SEQ_FILMGRAIN: i64 = 17
55const NX_SEQ_BITS: i64 = 18
56
57// ===== a minimal MSB-first bit writer =============================
58//
59// nx_bitstream reads but does not write. A writer is what makes this layer
60// round-trip-provable rather than assertion-checked.
61
62struct NxAv1Bw {
63 buf: i64,
64 cap: i64,
65 bitpos: i64,
66}
67
68func nx_av1_bw_new(buf: *u8, cap: i64) -> *NxAv1Bw {
69 let w: *NxAv1Bw = sys_mmap(NX_SEQ_BW_BYTES) as *NxAv1Bw
70 w.buf = buf as i64
71 w.cap = cap
72 w.bitpos = 0
73 var i: i64 = 0
74 while i < cap { buf[i] = 0 as u8; i = i + 1 }
75 return w
76}
77
78func nx_av1_bw_put(w: *NxAv1Bw, v: i64, n: i64) -> i64 {
79 if n <= 0 { return 0 }
80 if n > 32 { return 0 }
81 let buf: *u8 = w.buf as *u8
82 var i: i64 = n - 1
83 while i >= 0 {
84 let byte_i: i64 = w.bitpos >> 3
85 if byte_i >= w.cap { return 0 }
86 let bit: i64 = (v >> i) & 1
87 if bit == 1 {
88 let shift: i64 = 7 - (w.bitpos & 7)
89 buf[byte_i] = ((buf[byte_i] as i64) | (1 << shift)) as u8
90 }
91 w.bitpos = w.bitpos + 1
92 i = i - 1
93 }
94 return n
95}
96
97func nx_av1_bw_bytes(w: *NxAv1Bw) -> i64 {
98 return (w.bitpos + 7) >> 3
99}
100
101// ===== how many bits a dimension needs ============================
102
103func nx_av1_bits_for(v: i64) -> i64 {
104 if v <= 0 { return 1 }
105 var x: i64 = v
106 var n: i64 = 0
107 while x > 0 { x = x >> 1; n = n + 1 }
108 return n
109}
110
111// ===== write a reduced still-picture sequence header ==============
112//
113// The AVIF shape. Returns bytes written, or 0.
114
115func nx_av1_seq_write_still(out: *u8, cap: i64, profile: i64, level: i64,
116 width: i64, height: i64, bitdepth: i64,
117 mono: i64, subx: i64, suby: i64) -> i64 {
118 if width <= 0 { return 0 }
119 if height <= 0 { return 0 }
120 if profile < 0 { return 0 }
121 if profile > 2 { return 0 }
122 if level < 0 { return 0 }
123 if level > 31 { return 0 }
124 if bitdepth != 8 { if bitdepth != 10 { if bitdepth != 12 { return 0 } } }
125 if bitdepth == 12 { if profile != 2 { return 0 } }
126
127 let w: *NxAv1Bw = nx_av1_bw_new(out, cap)
128 nx_av1_bw_put(w, profile, 3)
129 nx_av1_bw_put(w, 1, 1) // still_picture
130 nx_av1_bw_put(w, 1, 1) // reduced_still_picture_header
131 nx_av1_bw_put(w, level, 5) // seq_level_idx[0]
132
133 let wb: i64 = nx_av1_bits_for(width - 1)
134 let hb: i64 = nx_av1_bits_for(height - 1)
135 if wb > 16 { return 0 }
136 if hb > 16 { return 0 }
137 nx_av1_bw_put(w, wb - 1, 4) // frame_width_bits_minus_1
138 nx_av1_bw_put(w, hb - 1, 4) // frame_height_bits_minus_1
139 nx_av1_bw_put(w, width - 1, wb) // max_frame_width_minus_1
140 nx_av1_bw_put(w, height - 1, hb) // max_frame_height_minus_1
141
142 nx_av1_bw_put(w, 0, 1) // use_128x128_superblock
143 nx_av1_bw_put(w, 0, 1) // enable_filter_intra
144 nx_av1_bw_put(w, 0, 1) // enable_intra_edge_filter
145 nx_av1_bw_put(w, 0, 1) // enable_superres
146 nx_av1_bw_put(w, 1, 1) // enable_cdef
147 nx_av1_bw_put(w, 1, 1) // enable_restoration
148
149 // ---- colour config ----
150 var high: i64 = 0
151 if bitdepth > 8 { high = 1 }
152 nx_av1_bw_put(w, high, 1) // high_bitdepth
153 if profile == 2 {
154 if high == 1 {
155 var twelve: i64 = 0
156 if bitdepth == 12 { twelve = 1 }
157 nx_av1_bw_put(w, twelve, 1) // twelve_bit
158 }
159 }
160 if profile != 1 { nx_av1_bw_put(w, mono, 1) }
161 nx_av1_bw_put(w, 0, 1) // color_description_present_flag
162 if mono == 1 {
163 nx_av1_bw_put(w, 0, 1) // color_range
164 } else {
165 nx_av1_bw_put(w, 0, 1) // color_range
166 if profile == 0 {
167 // 4:2:0 by definition
168 } else {
169 if profile == 2 {
170 if bitdepth == 12 {
171 nx_av1_bw_put(w, subx, 1)
172 if subx == 1 { nx_av1_bw_put(w, suby, 1) }
173 }
174 } }
175 if subx == 1 { if suby == 1 { nx_av1_bw_put(w, 0, 2) } } // chroma_sample_position
176 nx_av1_bw_put(w, 0, 1) // separate_uv_delta_q
177 }
178 nx_av1_bw_put(w, 0, 1) // film_grain_params_present
179 return nx_av1_bw_bytes(w)
180}
181
182// ===== parse a sequence header ====================================
183//
184// Handles the reduced still-picture path fully. Returns 1 and fills fld,
185// or 0 on a malformed or unsupported header -- an unsupported header is
186// REFUSED rather than half-parsed, because a wrong frame size poisons every
187// tile that follows.
188
189func nx_av1_seq_parse(d: *u8, n: i64, fld: *i64) -> i64 {
190 if n < 2 { return 0 }
191 let bs: *NxBitStream = nx_bitstream_alloc(d, n)
192 let profile: i64 = nx_bitstream_read_msb(bs, 3)
193 if profile > 2 { return 0 }
194 let still: i64 = nx_bitstream_read_msb(bs, 1)
195 let reduced: i64 = nx_bitstream_read_msb(bs, 1)
196 if reduced != 1 { return 0 }
197
198 let level: i64 = nx_bitstream_read_msb(bs, 5)
199
200 // the WIDTH OF THE WIDTH FIELD, read before the field itself
201 let wb: i64 = nx_bitstream_read_msb(bs, 4) + 1
202 let hb: i64 = nx_bitstream_read_msb(bs, 4) + 1
203 let maxw: i64 = nx_bitstream_read_msb(bs, wb) + 1
204 let maxh: i64 = nx_bitstream_read_msb(bs, hb) + 1
205
206 let sb128: i64 = nx_bitstream_read_msb(bs, 1)
207 let fintra: i64 = nx_bitstream_read_msb(bs, 1)
208 let edgef: i64 = nx_bitstream_read_msb(bs, 1)
209 let superres: i64 = nx_bitstream_read_msb(bs, 1)
210 let cdef: i64 = nx_bitstream_read_msb(bs, 1)
211 let restoration: i64 = nx_bitstream_read_msb(bs, 1)
212
213 // ---- colour config ----
214 let high: i64 = nx_bitstream_read_msb(bs, 1)
215 var bitdepth: i64 = 8
216 if high == 1 { bitdepth = 10 }
217 if profile == 2 {
218 if high == 1 {
219 if nx_bitstream_read_msb(bs, 1) == 1 { bitdepth = 12 }
220 }
221 }
222 var mono: i64 = 0
223 if profile != 1 { mono = nx_bitstream_read_msb(bs, 1) }
224
225 let desc: i64 = nx_bitstream_read_msb(bs, 1)
226 var prim: i64 = 2
227 var trc: i64 = 2
228 var mtx: i64 = 2
229 if desc == 1 {
230 prim = nx_bitstream_read_msb(bs, 8)
231 trc = nx_bitstream_read_msb(bs, 8)
232 mtx = nx_bitstream_read_msb(bs, 8)
233 }
234
235 var subx: i64 = 1
236 var suby: i64 = 1
237 var range: i64 = 0
238 if mono == 1 {
239 range = nx_bitstream_read_msb(bs, 1)
240 subx = 1
241 suby = 1
242 } else {
243 // the sRGB shortcut: colour_range and subsampling are IMPLIED, and
244 // reading colour_range here would consume a bit that is not present
245 var srgb: i64 = 0
246 if prim == 1 { if trc == 13 { if mtx == 0 { srgb = 1 } } }
247 if srgb == 1 {
248 range = 1
249 subx = 0
250 suby = 0
251 } else {
252 range = nx_bitstream_read_msb(bs, 1)
253 if profile == 0 { subx = 1; suby = 1 } else {
254 if profile == 1 { subx = 0; suby = 0 } else {
255 if bitdepth == 12 {
256 subx = nx_bitstream_read_msb(bs, 1)
257 if subx == 1 { suby = nx_bitstream_read_msb(bs, 1) } else { suby = 0 }
258 } else { subx = 1; suby = 0 }
259 } }
260 if subx == 1 { if suby == 1 { nx_bitstream_read_msb(bs, 2) } }
261 nx_bitstream_read_msb(bs, 1) // separate_uv_delta_q
262 }
263 }
264 let grain: i64 = nx_bitstream_read_msb(bs, 1)
265 if bs.overflow == 1 { return 0 }
266
267 fld[NX_SEQ_PROFILE] = profile
268 fld[NX_SEQ_STILL] = still
269 fld[NX_SEQ_REDUCED] = reduced
270 fld[NX_SEQ_LEVEL] = level
271 fld[NX_SEQ_MAXW] = maxw
272 fld[NX_SEQ_MAXH] = maxh
273 fld[NX_SEQ_SB128] = sb128
274 fld[NX_SEQ_FILTER_INTRA] = fintra
275 fld[NX_SEQ_EDGE_FILTER] = edgef
276 fld[NX_SEQ_SUPERRES] = superres
277 fld[NX_SEQ_CDEF] = cdef
278 fld[NX_SEQ_RESTORATION] = restoration
279 fld[NX_SEQ_BITDEPTH] = bitdepth
280 fld[NX_SEQ_MONO] = mono
281 fld[NX_SEQ_SUBX] = subx
282 fld[NX_SEQ_SUBY] = suby
283 fld[NX_SEQ_RANGE] = range
284 fld[NX_SEQ_FILMGRAIN] = grain
285 fld[NX_SEQ_BITS] = bs.byte_pos * 8 + bs.bit_pos
286 return 1
287}