Skip to main content

wechat_protocol/
silk.rs

1//! Bounded independent SILK SDK wrapper. Public source/license pins are under
2//! vendor/silk; no WeChat codec library is linked or loaded.
3use anyhow::{ensure, Result};
4unsafe extern "C" {
5    fn aw_silk_encode(
6        pcm: *const i16,
7        samples: i32,
8        out: *mut u8,
9        capacity: i32,
10        length: *mut i32,
11    ) -> i32;
12    fn aw_silk_decode(
13        input: *const u8,
14        length: i32,
15        pcm: *mut i16,
16        capacity: i32,
17        samples: *mut i32,
18    ) -> i32;
19}
20pub const SAMPLE_RATE: u32 = 16000;
21/// Target bit rate of the single bundled encoder profile (`silk_bridge.c`).
22/// SILK's wideband range tops out here; the previous 16 kbit/s profile was
23/// audibly destructive for anything richer than speech, and the rate control is
24/// soft, so the actual stream lands well under this ceiling.
25pub const BIT_RATE: u32 = 40000;
26/// Duration of one encoded SILK packet in the bundled profile.
27pub const FRAME_MS: u32 = 20;
28/// Longest single voice note: the official 60-second voice-message limit.
29pub const MAX_NOTE_MS: u32 = 60_000;
30pub const MAX_SAMPLES: usize = (MAX_NOTE_MS / 1000 * SAMPLE_RATE) as usize;
31/// Largest stream accepted by framing validation and decoding.
32pub const MAX_STREAM_BYTES: usize = 512 * 1024;
33/// Encoded-size bound for one maximum-length note from the bundled profile:
34/// the 10-byte header plus, per 20 ms packet, a 2-byte length prefix and the
35/// 40 kbit/s payload budget (100 bytes) with 1.5x headroom. SILK rate control
36/// is soft, so single packets may exceed the budget; the observed 60-second
37/// worst case (full-scale white noise) averages about 2.2 KB/s, or 131 KB.
38pub const MAX_NOTE_BYTES: usize = 10
39    + (MAX_NOTE_MS / FRAME_MS) as usize * (2 + (BIT_RATE / 8 * FRAME_MS / 1000) as usize * 3 / 2);
40const _: () = assert!(MAX_NOTE_BYTES <= MAX_STREAM_BYTES);
41pub fn encode(pcm: &[i16]) -> Result<Vec<u8>> {
42    ensure!(
43        !pcm.is_empty() && pcm.len() <= MAX_SAMPLES,
44        "voice_duration_limit"
45    );
46    let mut out = vec![0u8; MAX_STREAM_BYTES];
47    let mut length = 0i32;
48    // SAFETY: C receives immutable aligned samples and an explicitly sized output;
49    // the wrapper validates frame/output bounds before every copy.
50    let status = unsafe {
51        aw_silk_encode(
52            pcm.as_ptr(),
53            pcm.len() as i32,
54            out.as_mut_ptr(),
55            out.len() as i32,
56            &mut length,
57        )
58    };
59    ensure!(
60        status == 0 && length >= 12 && length as usize <= out.len(),
61        "silk_encode_failed"
62    );
63    out.truncate(length as usize);
64    validate_framing(&out)?;
65    Ok(out)
66}
67pub fn validate_framing(input: &[u8]) -> Result<u32> {
68    ensure!(
69        input.len() >= 12
70            && input.len() <= MAX_STREAM_BYTES
71            && input[0] <= 3
72            && input[1..10] == *b"#!SILK_V3",
73        "invalid_silk_stream"
74    );
75    let mut p = 10;
76    let mut packets = 0u32;
77    while p < input.len() {
78        ensure!(p + 2 <= input.len(), "invalid_silk_packet");
79        let n = i16::from_le_bytes(input[p..p + 2].try_into()?) as i32;
80        p += 2;
81        ensure!(
82            n > 0 && n <= 5120 && n as usize <= input.len() - p,
83            "invalid_silk_packet"
84        );
85        p += n as usize;
86        packets += 1;
87        ensure!(packets <= MAX_NOTE_MS / FRAME_MS, "voice_duration_limit");
88    }
89    ensure!(packets > 0, "empty_silk_stream");
90    Ok(packets * FRAME_MS)
91}
92pub fn decode(input: &[u8]) -> Result<Vec<i16>> {
93    validate_framing(input)?;
94    let mut pcm = vec![0i16; MAX_SAMPLES];
95    let mut samples = 0i32;
96    // SAFETY: Framing and total sizes were checked above. The C bridge bounds each
97    // decoder frame and copy to the supplied PCM capacity.
98    let status = unsafe {
99        aw_silk_decode(
100            input.as_ptr(),
101            input.len() as i32,
102            pcm.as_mut_ptr(),
103            pcm.len() as i32,
104            &mut samples,
105        )
106    };
107    ensure!(
108        status == 0 && samples > 0 && samples as usize <= pcm.len(),
109        "silk_decode_failed"
110    );
111    pcm.truncate(samples as usize);
112    Ok(pcm)
113}
114pub fn wav(pcm: &[i16]) -> Result<Vec<u8>> {
115    ensure!(pcm.len() <= MAX_SAMPLES, "voice_duration_limit");
116    let n = u32::try_from(pcm.len() * 2)?;
117    let mut out = Vec::with_capacity(44 + n as usize);
118    out.extend(b"RIFF");
119    out.extend((36 + n).to_le_bytes());
120    out.extend(b"WAVEfmt ");
121    out.extend(16u32.to_le_bytes());
122    out.extend(1u16.to_le_bytes());
123    out.extend(1u16.to_le_bytes());
124    out.extend(SAMPLE_RATE.to_le_bytes());
125    out.extend((SAMPLE_RATE * 2).to_le_bytes());
126    out.extend(2u16.to_le_bytes());
127    out.extend(16u16.to_le_bytes());
128    out.extend(b"data");
129    out.extend(n.to_le_bytes());
130    for s in pcm {
131        out.extend(s.to_le_bytes());
132    }
133    Ok(out)
134}
135#[cfg(test)]
136mod tests {
137    use super::*;
138    #[test]
139    fn independent_fresh_tones_encode_decode() {
140        let pcm = (0..16000)
141            .map(|i| ((i as f64 * 440.0 * std::f64::consts::TAU / 16000.0).sin() * 10000.0) as i16)
142            .collect::<Vec<_>>();
143        let encoded = encode(&pcm).unwrap();
144        assert_eq!(validate_framing(&encoded).unwrap(), 1000);
145        let decoded = decode(&encoded).unwrap();
146        assert_eq!(decoded.len(), pcm.len());
147        assert!(decoded.iter().any(|s| s.abs() > 1000));
148        assert_eq!(&wav(&decoded).unwrap()[..4], b"RIFF");
149    }
150    #[test]
151    fn maximum_note_stays_within_derived_byte_bound() {
152        assert_eq!(MAX_SAMPLES, 960_000);
153        assert_eq!(MAX_NOTE_BYTES, 456_010);
154        // Full-scale white noise is the least compressible input; a harmonic
155        // signal with a moving pitch exercises the voiced path.
156        let mut seed = 0x9e37_79b9_7f4a_7c15u64;
157        let noise = (0..MAX_SAMPLES)
158            .map(|_| {
159                seed = seed
160                    .wrapping_mul(6364136223846793005)
161                    .wrapping_add(1442695040888963407);
162                (seed >> 48) as u16 as i16
163            })
164            .collect::<Vec<_>>();
165        let harmonic = (0..MAX_SAMPLES)
166            .map(|i| {
167                let t = i as f64 / SAMPLE_RATE as f64;
168                let f0 = 120.0 + 40.0 * (t * 3.0).sin();
169                let v = (1..30)
170                    .map(|h| (t * f0 * h as f64 * std::f64::consts::TAU).sin() / h as f64)
171                    .sum::<f64>();
172                (v * (t * 4.0).sin().abs() * 8000.0) as i16
173            })
174            .collect::<Vec<_>>();
175        for pcm in [noise, harmonic] {
176            let encoded = encode(&pcm).unwrap();
177            assert!(encoded.len() <= MAX_NOTE_BYTES, "{}", encoded.len());
178            assert_eq!(validate_framing(&encoded).unwrap(), MAX_NOTE_MS);
179        }
180        let too_long = vec![0i16; MAX_SAMPLES + 1];
181        assert!(encode(&too_long).is_err());
182    }
183    #[test]
184    fn malformed_packet_refused_before_ffi() {
185        for input in [
186            vec![],
187            [vec![2], b"#!SILK_V3".to_vec(), vec![0, 0]].concat(),
188            [vec![2], b"#!SILK_V3".to_vec(), vec![255, 255]].concat(),
189        ] {
190            assert!(decode(&input).is_err());
191        }
192    }
193}