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