code wiki / _hdl_build / nx_nv1.nx

nx_nv1.nx source

↩ module page · 130 lines · 4946 B

1// nx_nv1.nx -- NishiLossless v1 (NLC1) circle container: writer + validator. 2// 3// The named exceed rung from the video session: circles recorded LOSSLESSLY 4// (Int16 PCM at the device's NATIVE rate -- the same no-resample doctrine as 5// live call audio) + JPEG frames, in OUR OWN container. MediaRecorder/webm/ 6// opus is gone from the record path. The browser client (app.js nv1Pack/ 7// nv1Parse) implements this same format; THIS module is the team-side 8// authority: the gate proves the format, and the daemon can later validate 9// uploads server-side with nv1_validate. 10// 11// Format (all little-endian): 12// header (24B): "NLC1" | u32 version=1 | u32 sample_rate | u32 channels=1 13// | u32 reserved | u32 reserved 14// chunk (9B + payload): u8 kind | u32 len | u32 t_ms | payload 15// kind 0x41 'A': Int16 PCM mono (len bytes = 2*samples), t_ms derived 16// from the running SAMPLE COUNT (sample-exact, not clock) 17// kind 0x56 'V': one JPEG frame, t_ms = capture time 18// kind 0x4C 'L': LPC-compressed Int16 PCM (see nx_nv1_lpc.nx; payload 19// starts u8 mode | u8 k | u32 nsamples), counts toward 20// audio chunks + total samples like 'A' 21// kind 0x45 'E': terminator, len MUST be 0, t_ms = total duration 22// The terminator must be the FINAL bytes of the container (no trailing). 23// 24// FLAC-class LPC compression of the PCM (the named next rung) now lives in 25// nx_nv1_lpc.nx as chunk kind 'L'; 'A' remains valid (raw path + back-compat). 26// license_tier: ORIGINAL 27 28import "nx_syscalls.nx" 29const NV1_MAGIC_65536: i64 = 65536 30const NV1_MAGIC_16777216: i64 = 16777216 31 32const NV1_HDR: i64 = 24 33const NV1_CHDR: i64 = 9 34 35func nv1_rd_u32(b: *u8, off: i64) -> i64 { 36 var v: i64 = b[off] & 0xff 37 v = v + ((b[off + 1] & 0xff) * 256) 38 v = v + ((b[off + 2] & 0xff) * NV1_MAGIC_65536) 39 v = v + ((b[off + 3] & 0xff) * NV1_MAGIC_16777216) 40 return v 41} 42 43func nv1_wr_u32(b: *u8, off: i64, v: i64) -> i64 { 44 b[off] = (v & 255) as u8 45 b[off + 1] = ((v / 256) & 255) as u8 46 b[off + 2] = ((v / NV1_MAGIC_65536) & 255) as u8 47 b[off + 3] = ((v / NV1_MAGIC_16777216) & 255) as u8 48 return 4 49} 50 51func nv1_write_header(out: *u8, rate: i64) -> i64 { 52 out[0] = 78; out[1] = 76; out[2] = 67; out[3] = 49 // "NLC1" 53 nv1_wr_u32(out, 4, 1) 54 nv1_wr_u32(out, 8, rate) 55 nv1_wr_u32(out, 12, 1) 56 nv1_wr_u32(out, 16, 0) 57 nv1_wr_u32(out, 20, 0) 58 return NV1_HDR 59} 60 61// append one chunk at write cursor w; returns the new cursor 62func nv1_write_chunk(out: *u8, w: i64, kind: i64, t_ms: i64, payload: *u8, n: i64) -> i64 { 63 out[w] = kind as u8 64 nv1_wr_u32(out, w + 1, n) 65 nv1_wr_u32(out, w + 5, t_ms) 66 var i: i64 = 0 67 while i < n { out[w + NV1_CHDR + i] = payload[i]; i = i + 1 } 68 return w + NV1_CHDR + n 69} 70 71func nv1_write_end(out: *u8, w: i64, dur_ms: i64) -> i64 { 72 out[w] = 69 // 'E' 73 nv1_wr_u32(out, w + 1, 0) 74 nv1_wr_u32(out, w + 5, dur_ms) 75 return w + NV1_CHDR 76} 77 78// Validate a complete container. info[0]=rate info[1]=audio_chunks 79// info[2]=total_samples info[3]=video_frames info[4]=duration_ms. 80// 0 = OK; negatives name the defect: 81// -1 too short -2 bad magic -3 bad version -4 bad chunk kind / nonzero-E 82// -5 chunk overruns buffer -6 missing terminator -7 channels != 1 83// -8 trailing bytes after terminator -9 L chunk payload shorter than its 84// 6-byte sub-header (mode/k/nsamples) 85func nv1_validate(buf: *u8, n: i64, info: *i64) -> i64 { 86 if n < NV1_HDR + NV1_CHDR { return 0 - 1 } 87 var m: i64 = 1 88 if (buf[0] & 0xff) != 78 { m = 0 } 89 if (buf[1] & 0xff) != 76 { m = 0 } 90 if (buf[2] & 0xff) != 67 { m = 0 } 91 if (buf[3] & 0xff) != 49 { m = 0 } 92 if m == 0 { return 0 - 2 } 93 if nv1_rd_u32(buf, 4) != 1 { return 0 - 3 } 94 if nv1_rd_u32(buf, 12) != 1 { return 0 - 7 } 95 info[0] = nv1_rd_u32(buf, 8) 96 var off: i64 = NV1_HDR 97 var ac: i64 = 0 98 var vc: i64 = 0 99 var samp: i64 = 0 100 var dur: i64 = 0 101 var ended: i64 = 0 102 while ended == 0 { 103 if off + NV1_CHDR > n { return 0 - 6 } 104 let k: i64 = buf[off] & 0xff 105 let ln: i64 = nv1_rd_u32(buf, off + 1) 106 let tm: i64 = nv1_rd_u32(buf, off + 5) 107 if off + NV1_CHDR + ln > n { return 0 - 5 } 108 var known: i64 = 0 109 if k == 65 { ac = ac + 1; samp = samp + ln / 2; known = 1 } 110 if k == 76 { 111 if ln < 6 { return 0 - 9 } 112 ac = ac + 1 113 samp = samp + nv1_rd_u32(buf, off + NV1_CHDR + 2) 114 known = 1 115 } 116 if k == 86 { vc = vc + 1; known = 1 } 117 if k == 69 { 118 if ln != 0 { return 0 - 4 } 119 ended = 1; dur = tm; known = 1 120 } 121 if known == 0 { return 0 - 4 } 122 off = off + NV1_CHDR + ln 123 } 124 if off != n { return 0 - 8 } 125 info[1] = ac 126 info[2] = samp 127 info[3] = vc 128 info[4] = dur 129 return 0 130}