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}