code wiki / (root) / nx_vsubpel.nx

nx_vsubpel.nx source

↩ module page · 156 lines · 6779 B

1// nx_vsubpel.nx -- Performs half-pel and quarter-pel sub-pixel interpolation for motion compensation in video encoding. 2const K_MAGIC_2147483647: i64 = 2147483647 3// nx_vsubpel.nx -- HALF-PEL sub-pixel motion compensation: the single biggest video quality-per-bit lever past integer 4// motion (H.264/VP9 go to quarter-pel). Real pans/faces move by NON-integer amounts; an integer-pel reference can't 5// land on a 0.5px shift, leaving a large residual = many bits. Interpolating the reference at half-pixel positions 6// (bilinear here; a 6-tap filter is the later refinement) lets the predictor hit the true sub-pixel position -> near- 7// zero residual -> far fewer bits at IDENTICAL quality. The MV is carried in HALF-pel units (2*integer + half). Pure 8// buffer math, no syscalls (wasm-friendly); caller owns the TxT scratch block. license_tier: ORIGINAL 9 10// bilinear half-pel: build a TxT block sampled from prev at base (px,py) with half-pel offsets hx,hy in {0,1}. 11// hx=1 averages with the +1 column, hy=1 with the +1 row, both = the 2x2 average (the (0.5,0.5) diagonal sample). 12func sp_interp_block(prev: *u8, W: i64, px: i64, py: i64, hx: i64, hy: i64, T: i64, out: *i64) -> i64 { 13 var yy: i64 = 0 14 while yy < T { 15 var xx: i64 = 0 16 while xx < T { 17 let x0: i64 = px + xx 18 let y0: i64 = py + yy 19 let a: i64 = prev[y0*W + x0] as i64 20 var v: i64 = a 21 if hx == 1 { 22 if hy == 1 { 23 let b: i64 = prev[y0*W + (x0+1)] as i64 24 let c: i64 = prev[(y0+1)*W + x0] as i64 25 let d: i64 = prev[(y0+1)*W + (x0+1)] as i64 26 v = (a + b + c + d + 2) / 4 27 } else { 28 let b: i64 = prev[y0*W + (x0+1)] as i64 29 v = (a + b + 1) / 2 30 } 31 } else { 32 if hy == 1 { 33 let c: i64 = prev[(y0+1)*W + x0] as i64 34 v = (a + c + 1) / 2 35 } 36 } 37 out[yy*T + xx] = v 38 xx = xx + 1 39 } 40 yy = yy + 1 41 } 42 return 0 43} 44// per-PIXEL half-pel sample at reference (x,y) with half offsets hx,hy in {0,1}. IDENTICAL bilinear math to 45// sp_interp_block, so an encoder that SCORES candidates with sp_interp_block and RECONSTRUCTS per-pixel with hp_pixel 46// gets matching values -- and a decoder using hp_pixel reconstructs bit-exact. (x+1,y+1) must be in-bounds when hx/hy=1. 47func hp_pixel(prev: *u8, W: i64, x: i64, y: i64, hx: i64, hy: i64) -> i64 { 48 let a: i64 = prev[y*W + x] as i64 49 if hx == 1 { 50 if hy == 1 { 51 let b: i64 = prev[y*W + (x+1)] as i64 52 let c: i64 = prev[(y+1)*W + x] as i64 53 let d: i64 = prev[(y+1)*W + (x+1)] as i64 54 return (a + b + c + d + 2) / 4 55 } 56 return (a + (prev[y*W + (x+1)] as i64) + 1) / 2 57 } 58 if hy == 1 { return (a + (prev[(y+1)*W + x] as i64) + 1) / 2 } 59 return a 60} 61// general QUARTER-pel bilinear sample at (x + qx/4, y + qy/4), qx,qy in {0,1,2,3} -- the H.264/VP9 motion precision. 62// Subsumes integer (qx=qy=0) and half-pel (qx/qy=2) EXACTLY (matches hp_pixel). Only reads (x+1)/(y+1) when that axis 63// has fractional weight, so the caller's bounds guard is the same as half-pel's. 64func qp_pixel(prev: *u8, W: i64, x: i64, y: i64, qx: i64, qy: i64) -> i64 { 65 let a: i64 = prev[y*W + x] as i64 66 if qx == 0 { if qy == 0 { return a } } 67 let wx: i64 = 4 - qx 68 let wy: i64 = 4 - qy 69 var acc: i64 = wx * wy * a 70 if qx > 0 { acc = acc + qx * wy * (prev[y*W + (x+1)] as i64) } 71 if qy > 0 { acc = acc + wx * qy * (prev[(y+1)*W + x] as i64) } 72 if qx > 0 { if qy > 0 { acc = acc + qx * qy * (prev[(y+1)*W + (x+1)] as i64) } } 73 return (acc + 8) / 16 74} 75// refine an integer MV (idx,idy) to QUARTER-pel: test the 16 (qx,qy) offsets, write best quarter MV (mvq[0]=4*idx+qx, 76// mvq[1]=4*idy+qy) and return its SAD over the TxT block (streaming, no scratch). Caller ensures a 1px edge guard. 77func qp_refine(cur: *u8, prev: *u8, W: i64, H: i64, cx: i64, cy: i64, idx: i64, idy: i64, T: i64, mvq: *i64) -> i64 { 78 let px: i64 = cx + idx 79 let py: i64 = cy + idy 80 var best: i64 = K_MAGIC_2147483647 81 var bqx: i64 = 0 82 var bqy: i64 = 0 83 var qy: i64 = 0 84 while qy <= 3 { 85 var qx: i64 = 0 86 while qx <= 3 { 87 var ok: i64 = 1 88 if qx > 0 { if px + T >= W { ok = 0 } } 89 if qy > 0 { if py + T >= H { ok = 0 } } 90 if ok == 1 { 91 var sad: i64 = 0 92 var ry: i64 = 0 93 while ry < T { 94 var rx: i64 = 0 95 while rx < T { 96 let dd: i64 = (cur[(cy+ry)*W + (cx+rx)] as i64) - qp_pixel(prev, W, px+rx, py+ry, qx, qy) 97 if dd < 0 { sad = sad - dd } else { sad = sad + dd } 98 rx = rx + 1 99 } 100 ry = ry + 1 101 } 102 if sad < best { best = sad; bqx = qx; bqy = qy } 103 } 104 qx = qx + 1 105 } 106 qy = qy + 1 107 } 108 mvq[0] = 4*idx + bqx 109 mvq[1] = 4*idy + bqy 110 return best 111} 112// SAD between cur's block at (cx,cy) and an interpolated i64 block 113func sp_sad(cur: *u8, W: i64, cx: i64, cy: i64, blk: *i64, T: i64) -> i64 { 114 var s: i64 = 0 115 var yy: i64 = 0 116 while yy < T { 117 var xx: i64 = 0 118 while xx < T { 119 let d: i64 = (cur[(cy+yy)*W + (cx+xx)] as i64) - blk[yy*T + xx] 120 if d < 0 { s = s - d } else { s = s + d } 121 xx = xx + 1 122 } 123 yy = yy + 1 124 } 125 return s 126} 127// refine an integer MV (idx,idy) to HALF-pel: test the 4 (hx,hy) offsets at that integer position, write the best 128// half-pel MV (mvh[0]=2*idx+hx, mvh[1]=2*idy+hy) and return its SAD. Caller guarantees a 1px guard at the frame edge. 129func sp_refine(cur: *u8, prev: *u8, W: i64, H: i64, cx: i64, cy: i64, idx: i64, idy: i64, T: i64, tmp: *i64, mvh: *i64) -> i64 { 130 let px: i64 = cx + idx 131 let py: i64 = cy + idy 132 var best: i64 = K_MAGIC_2147483647 133 var bhx: i64 = 0 134 var bhy: i64 = 0 135 var hy: i64 = 0 136 while hy <= 1 { 137 var hx: i64 = 0 138 while hx <= 1 { 139 var ok: i64 = 1 140 if px < 0 { ok = 0 } 141 if py < 0 { ok = 0 } 142 if px + T - 1 + hx >= W { ok = 0 } 143 if py + T - 1 + hy >= H { ok = 0 } 144 if ok == 1 { 145 sp_interp_block(prev, W, px, py, hx, hy, T, tmp) 146 let s: i64 = sp_sad(cur, W, cx, cy, tmp, T) 147 if s < best { best = s; bhx = hx; bhy = hy } 148 } 149 hx = hx + 1 150 } 151 hy = hy + 1 152 } 153 mvh[0] = 2*idx + bhx 154 mvh[1] = 2*idy + bhy 155 return best 156}