code wiki / (root) / nx_av1_seq.nx

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}