code wiki / _hdl_build / nx_vcodec_quality_probe.nx
nx_vcodec_quality_probe.nx source
↩ module page · 93 lines · 4647 B
1import "nx_gate_base.nx"
2// nx_vcodec_quality_probe.nx -- ground the quantizer choice with REAL PSNR. The live client clamped qp to
3// 24..48 (vt_quant does blk[i]/q -> q=24 is a very coarse deadzone -> the "garbage blocky" the operator saw).
4// This sweeps q on a DETAILED frame (gradient + high-freq texture + edges) at the production geometry and
5// prints keyframe PSNR + bytes, so the new qp floor is measured, not guessed. license_tier: ORIGINAL
6import "nx_syscalls.nx"
7import "nx_video_codec_wasm.nx"
8const K_MAGIC_9900: i64 = 9900
9const K_MAGIC_65025: i64 = 65025
10const K_MAGIC_262144: i64 = 262144
11const K_MAGIC_6016: i64 = 6016
12
13func grow(name: *u8, ok: i64) -> i64 { if ok==1 { gw(" PASS " as *u8) } else { gw(" FAIL " as *u8) } gw(name); gw("
14" as *u8); return ok }
15func gn(v: i64) -> i64 {
16 let b: *u8=sys_mmap(28); var m: i64=v; if m<0{sys_write(1,"-" as *u8,1);m=0-m}
17 let t: *u8=sys_mmap(28); var k: i64=0; if m==0{t[0]=48 as u8;k=1} while m>0{t[k]=(48+(m%10)) as u8;m=m/10;k=k+1}
18 var i: i64=0; while i<k{b[i]=t[k-1-i];i=i+1} sys_write(1,b,k); return 0 }
19
20func absi(x: i64) -> i64 { if x < 0 { return 0 - x } return x }
21
22// blockiness = 100 * (avg |step| at 4-pixel block boundaries) / (avg |step| in the interior). A blocky
23// frame has big steps exactly at boundaries -> >>100. A deblocked/smooth frame -> near 100.
24func blockiness(p: *u8, W: i64, H: i64) -> i64 {
25 var bsum: i64 = 0; var bn: i64 = 0; var isum: i64 = 0; var iN: i64 = 0
26 var y: i64 = 0
27 while y < H {
28 var x: i64 = 1
29 while x < W {
30 let d: i64 = absi((p[y*W+x]&0xff) - (p[y*W+x-1]&0xff))
31 if (x & 3) == 0 { bsum = bsum + d; bn = bn + 1 } else { isum = isum + d; iN = iN + 1 }
32 x = x + 1
33 }
34 y = y + 1
35 }
36 if iN == 0 { return 0 } if isum == 0 { return 999 }
37 return (bsum * iN * 100) / (isum * bn) }
38
39// PSNR*100 (dB) of Y plane, N pixels
40func psnr_y(a: *u8, b: *u8, N: i64) -> i64 {
41 var se: i64 = 0; var i: i64 = 0
42 while i < N { let d: i64 = (a[i]&0xff) - (b[i]&0xff); se = se + d*d; i = i + 1 }
43 if se == 0 { return K_MAGIC_9900 }
44 let mse_x100: i64 = (se * 100) / N
45 // 10*log10(255^2*100/mse_x100); crude integer log10 via repeated /10 on the ratio
46 let num: i64 = K_MAGIC_65025 * 100
47 var ratio: i64 = num / mse_x100
48 if ratio < 1 { ratio = 1 }
49 var db10: i64 = 0 // 10*log10(ratio) approx, in tenths
50 var r: i64 = ratio
51 while r >= 10 { db10 = db10 + 10; r = r / 10 }
52 // fractional part via linear interp between decades (r in [1,10))
53 db10 = db10 + (r - 1) // ~ +0..9 tenths
54 return db10 * 100 } // -> dB*100-ish (coarse but monotonic for comparison)
55
56func probe(W: i64, H: i64, q: i64) -> i64 {
57 let N: i64 = W*H; let C: i64 = (W/2)*(H/2); let sz: i64 = N + 2*C
58 let yuv: *u8 = sys_mmap(sz)
59 var y: i64 = 0
60 while y < H { var x: i64 = 0
61 while x < W {
62 // gradient + high-frequency texture + a hard edge column = a stress pattern for blockiness
63 var v: i64 = (x*255)/W
64 v = v + (((x/2 + y/3) % 7) * 18) - 54 // texture
65 if x > W/2 { v = v + 40 } // edge
66 if v < 0 { v = 0 } if v > 255 { v = 255 }
67 yuv[y*W + x] = v as u8
68 x = x + 1 } y = y + 1 }
69 var i: i64 = N; while i < N+C { yuv[i]=110 as u8; i=i+1 }
70 while i < sz { yuv[i]=140 as u8; i=i+1 }
71 let prevE: *u8 = sys_mmap(sz); let reconE: *u8 = sys_mmap(sz)
72 let prevD: *u8 = sys_mmap(sz); let reconD: *u8 = sys_mmap(sz)
73 let stream: *u8 = sys_mmap(K_MAGIC_262144)
74 let blk: *i64 = sys_mmap(512) as *i64; let mv: *i64 = sys_mmap(128) as *i64
75 let kb: i64 = vv_enc(yuv, prevE, reconE, W, H, q, 1, K_MAGIC_6016, stream, K_MAGIC_262144, blk, mv)
76 vv_dec(prevD, reconD, W, H, q, stream, kb, blk, mv)
77 let db: i64 = psnr_y(reconD, yuv, N)
78 let bkIn: i64 = blockiness(yuv, W, H)
79 let bkOut: i64 = blockiness(reconD, W, H)
80 gw(" q=" as *u8); gn(q); gw(" PSNR~" as *u8); gn(db/100); gw("dB blockiness in=" as *u8); gn(bkIn)
81 gw(" out=" as *u8); gn(bkOut); gw(" key=" as *u8); gn(kb); gw("B\n" as *u8)
82 return 0 }
83
84func main() -> i64 {
85 gw("=== nx_vcodec_quality_probe: qp sweep @256x192 (detailed frame) ===\n" as *u8)
86 probe(256, 192, 6)
87 probe(256, 192, 10)
88 probe(256, 192, 16)
89 probe(256, 192, 24) // current live FLOOR (best case) -- the operator's 'garbage'
90 probe(256, 192, 32)
91 probe(256, 192, 48) // current live ceiling (worst case)
92 gw("(higher dB = sharper; the live floor was q=24 -> we can go much finer for good pipes)\n" as *u8)
93 return 0 }