code wiki / (root) / nx_av1_ec.nx

nx_av1_ec.nx source

↩ module page · 183 lines · 6888 B

1// nx_av1_ec.nx -- the AV1/AV2 multi-symbol CDF arithmetic decoder. 2// 3// THE MOAT FOUNDATION. AV1, AVIF and AV2 all rest on this one primitive: a 4// multi-symbol (not binary) arithmetic coder whose probability tables ADAPT 5// as they decode. Nothing in this tree had it. nx_rangecoder.nx is the older 6// BINARY range coder (CABAC/VP8 class) -- it cannot decode AV1, because AV1 7// draws an N-ary symbol per call against a cumulative distribution rather 8// than a sequence of bits against a single probability. 9// 10// AV2 v1.0.0 (spec frozen 2026-05-28) inherits this coder essentially intact, 11// so this module is the shared substrate for both. Build it once, correctly. 12// 13// CDF CONVENTION -- FORWARD, PER THE SPEC. cdf[i] is the cumulative 14// probability of symbols <= i scaled to 32768, so the array INCREASES and 15// cdf[N-1] == 32768. libaom stores the INVERSE internally (32768 minus this, 16// decreasing to 0) and its update formula is written for that form. Mixing 17// the two is the classic porting error and it is nearly invisible: the 18// terminating entry agrees under BOTH conventions, so the first symbol of a 19// stream often decodes correctly and everything after it diverges. This file 20// is spec-form throughout; do not paste libaom arithmetic into it. 21// 22// THE TRAILING SLOT. cdf has N+1 entries: N probabilities plus a COUNTER at 23// cdf[N] that tracks how many times this context has been used. The counter 24// drives the adaptation rate -- a fresh context adapts fast, a mature one 25// slowly. Dropping it (sizing the array at N) both breaks adaptation and 26// writes one past the end. 27// 28// genealogy_id: av1_spec_8_3_symbol_decoder 29// lineage_id: nx_av1_ec_v1 30// license_tier: ORIGINAL 31 32import "nx_syscalls.nx" 33import "nx_bitstream.nx" 34 35const NX_AV1_CDF_TOTAL: i64 = 32768 36const NX_AV1_EC_BYTES: i64 = 40 37 38struct NxAv1Ec { 39 bs: i64, 40 value: i64, 41 range: i64, 42 max_bits: i64, 43} 44 45// ===== FloorLog2 ================================================== 46// 47// Returns -1 for 0 so a caller can distinguish "no bits" from "bit 0". 48 49func nx_av1_floor_log2(v: i64) -> i64 { 50 if v <= 0 { return 0 - 1 } 51 var x: i64 = v 52 var n: i64 = 0 53 while x > 1 { x = x >> 1; n = n + 1 } 54 return n 55} 56 57func nx_av1_min(a: i64, b: i64) -> i64 { if a < b { return a } return b } 58func nx_av1_max(a: i64, b: i64) -> i64 { if a > b { return a } return b } 59 60// ===== init ======================================================= 61// 62// sz is the size in BYTES of the tile/partition this coder will read. The 63// first 15 bits prime the value register; max_bits tracks how many real bits 64// remain so renormalization can pad with zeros past the end instead of 65// reading off the buffer. 66 67func nx_av1_ec_init(bs: *NxBitStream, sz: i64) -> *NxAv1Ec { 68 if bs == (0 as *NxBitStream) { return 0 as *NxAv1Ec } 69 if sz <= 0 { return 0 as *NxAv1Ec } 70 let num_bits: i64 = nx_av1_min(sz * 8, 15) 71 let buf: i64 = nx_bitstream_read_msb(bs, num_bits) 72 let padded: i64 = buf << (15 - num_bits) 73 let ec: *NxAv1Ec = sys_mmap(NX_AV1_EC_BYTES) as *NxAv1Ec 74 ec.bs = bs as i64 75 ec.value = (NX_AV1_CDF_TOTAL - 1) ^ padded 76 ec.range = NX_AV1_CDF_TOTAL 77 ec.max_bits = sz * 8 - 15 78 return ec 79} 80 81// ===== renormalization ============================================ 82// 83// Rescales range back up into its working window and refills value. Past the 84// end of the real data it pads with zeros rather than failing -- AV1 streams 85// legitimately run the coder past the final byte. 86 87func nx_av1_ec_renorm(ec: *NxAv1Ec) -> i64 { 88 let bits: i64 = 15 - nx_av1_floor_log2(ec.range) 89 if bits <= 0 { return 1 } 90 let bs: *NxBitStream = ec.bs as *NxBitStream 91 let num_bits: i64 = nx_av1_min(bits, nx_av1_max(0, ec.max_bits)) 92 var new_data: i64 = 0 93 if num_bits > 0 { new_data = nx_bitstream_read_msb(bs, num_bits) } 94 let padded: i64 = new_data << (bits - num_bits) 95 ec.range = ec.range << bits 96 ec.value = padded ^ (((ec.value + 1) << bits) - 1) 97 ec.max_bits = ec.max_bits - bits 98 return 1 99} 100 101// ===== decode one symbol ========================================== 102// 103// cdf holds N inverted cumulative probabilities plus the usage counter at 104// cdf[N]. Returns the symbol in [0, N-1], or -1 on a malformed call. 105// cdf[N-1] == 32768 makes f zero on the last symbol, so cur reaches 0 and the 106// walk always terminates. 107 108func nx_av1_decode_symbol(ec: *NxAv1Ec, cdf: *i64, n: i64) -> i64 { 109 if ec == (0 as *NxAv1Ec) { return 0 - 1 } 110 if n < 2 { return 0 - 1 } 111 var symbol: i64 = 0 - 1 112 var cur: i64 = ec.range 113 var prev: i64 = cur 114 var go: i64 = 1 115 while go == 1 { 116 symbol = symbol + 1 117 if symbol >= n { return 0 - 1 } 118 prev = cur 119 let f: i64 = NX_AV1_CDF_TOTAL - cdf[symbol] 120 cur = ((ec.range >> 8) * f) >> 7 121 if ec.value >= cur { go = 0 } 122 } 123 ec.range = prev - cur 124 ec.value = ec.value - cur 125 nx_av1_ec_renorm(ec) 126 return symbol 127} 128 129// ===== adaptation ================================================= 130// 131// Moves the chosen symbol's probability up and every other down, at a rate 132// that slows as the context matures. The counter at cdf[N] saturates at 32. 133// 134// `tmp` is a RUNNING THRESHOLD, not a per-entry equality test: it is 0 for 135// entries below the decoded symbol and 32768 from the symbol onward. Every 136// entry below the symbol is pulled down and every entry from the symbol up is 137// pushed toward full, which widens exactly that symbol's interval. Writing it 138// as a bare `if i == symbol` touches ONE entry and leaves the distribution 139// un-normalised -- it still decodes, it just drifts away from the encoder 140// until the stream desyncs. 141 142func nx_av1_update_cdf(cdf: *i64, symbol: i64, n: i64) -> i64 { 143 if n < 2 { return 0 } 144 if symbol < 0 { return 0 } 145 if symbol >= n { return 0 } 146 var rate: i64 = 3 147 if cdf[n] > 15 { rate = rate + 1 } 148 if cdf[n] > 31 { rate = rate + 1 } 149 let lg: i64 = nx_av1_floor_log2(n) 150 rate = rate + nx_av1_min(lg, 2) 151 152 var tmp: i64 = 0 153 var i: i64 = 0 154 while i < n - 1 { 155 if i == symbol { tmp = NX_AV1_CDF_TOTAL } 156 if tmp < cdf[i] { 157 cdf[i] = cdf[i] - ((cdf[i] - tmp) >> rate) 158 } else { 159 cdf[i] = cdf[i] + ((tmp - cdf[i]) >> rate) 160 } 161 i = i + 1 162 } 163 if cdf[n] < 32 { cdf[n] = cdf[n] + 1 } 164 return 1 165} 166 167// ===== a uniform starting CDF ===================================== 168// 169// Allocates N+1 slots and fills the first N with an equiprobable inverted 170// distribution, terminator included, and zeroes the usage counter. 171 172func nx_av1_cdf_uniform(n: i64) -> *i64 { 173 if n < 2 { return 0 as *i64 } 174 let cdf: *i64 = sys_mmap((n + 2) * 8 + 64) as *i64 175 var i: i64 = 0 176 while i < n { 177 cdf[i] = ((i + 1) * NX_AV1_CDF_TOTAL) / n 178 i = i + 1 179 } 180 cdf[n - 1] = NX_AV1_CDF_TOTAL 181 cdf[n] = 0 182 return cdf 183}