Skip to main content

snapcast_client/decoder/
f32lz4.rs

1//! F32 LZ4 decoder — lossless compressed f32 audio.
2//!
3//! Wire format:
4//! - Header: "F32L" + sample_rate(u32) + channels(u16) + bits(u16)
5//! - Chunks: LZ4-compressed f32 samples
6//!
7//! Output: raw f32 bytes (4 bytes per sample, little-endian).
8//! The audio_pump converts these to AudioFrame without i16 quantization.
9
10use anyhow::{Result, bail};
11use snapcast_proto::SampleFormat;
12use snapcast_proto::message::codec_header::CodecHeader;
13
14use crate::decoder::Decoder;
15use crate::stream::SampleEncoding;
16
17const MAGIC: &[u8; 4] = b"F32L";
18
19/// F32 LZ4 decoder.
20pub struct F32Lz4Decoder {
21    sample_format: SampleFormat,
22    #[cfg(feature = "encryption")]
23    decryptor: Option<crate::crypto::ChunkDecryptor>,
24    #[cfg(feature = "encryption")]
25    encryption_psk: Option<String>,
26}
27
28impl Decoder for F32Lz4Decoder {
29    fn set_header(&mut self, header: &CodecHeader) -> Result<SampleFormat> {
30        if header.payload.len() < 12 {
31            bail!("F32LZ4 header too small");
32        }
33        if &header.payload[..4] != MAGIC {
34            bail!("not an F32LZ4 header");
35        }
36        let rate = u32::from_le_bytes(header.payload[4..8].try_into().unwrap());
37        let channels = u16::from_le_bytes(header.payload[8..10].try_into().unwrap());
38        let bits = u16::from_le_bytes(header.payload[10..12].try_into().unwrap());
39        self.sample_format = SampleFormat::new(rate, bits, channels);
40
41        // Check for encryption marker after the 12-byte base header
42        #[cfg(feature = "encryption")]
43        if header.payload.len() >= 32 && &header.payload[12..16] == b"ENC\0" {
44            let salt = &header.payload[16..32];
45            if let Some(ref psk) = self.encryption_psk {
46                self.decryptor = Some(crate::crypto::ChunkDecryptor::new(psk, salt));
47                tracing::info!("F32LZ4 decryption enabled");
48            } else {
49                bail!("Server requires encryption but no encryption_psk configured");
50            }
51        }
52
53        tracing::info!(rate, channels, bits, "F32LZ4 decoder initialized");
54        Ok(self.sample_format)
55    }
56
57    fn decode(&mut self, data: &mut Vec<u8>) -> Result<bool> {
58        if data.is_empty() {
59            return Ok(false);
60        }
61
62        // Decrypt if encryption is active
63        #[cfg(feature = "encryption")]
64        if let Some(ref dec) = self.decryptor {
65            match dec.decrypt(data) {
66                Ok(decrypted) => *data = decrypted,
67                Err(e) => {
68                    tracing::warn!(error = %e, "F32LZ4 decryption failed");
69                    return Ok(false);
70                }
71            }
72        }
73
74        match lz4_flex::decompress_size_prepended(data) {
75            Ok(decompressed) => {
76                tracing::trace!(
77                    compressed = data.len(),
78                    decompressed = decompressed.len(),
79                    "F32LZ4 decoded"
80                );
81                *data = decompressed;
82                Ok(true)
83            }
84            Err(e) => {
85                tracing::warn!(error = %e, "F32LZ4 decompress failed");
86                Ok(false)
87            }
88        }
89    }
90
91    fn output_encoding(&self) -> SampleEncoding {
92        SampleEncoding::Float32
93    }
94}
95
96/// Create an F32Lz4Decoder.
97#[cfg(feature = "encryption")]
98pub fn create(encryption_psk: Option<&str>) -> F32Lz4Decoder {
99    F32Lz4Decoder {
100        sample_format: SampleFormat::default(),
101        decryptor: None,
102        encryption_psk: encryption_psk.map(String::from),
103    }
104}
105
106/// Create an F32Lz4Decoder.
107#[cfg(not(feature = "encryption"))]
108pub fn create() -> F32Lz4Decoder {
109    F32Lz4Decoder {
110        sample_format: SampleFormat::default(),
111    }
112}
113
114#[cfg(test)]
115mod tests {
116    use super::*;
117
118    #[test]
119    fn roundtrip() {
120        let _fmt = SampleFormat::new(48000, 16, 2);
121
122        // Encode
123        let f32_samples: Vec<f32> = (0..960).map(|i| (i as f32 / 960.0) * 2.0 - 1.0).collect();
124        let f32_bytes: Vec<u8> = f32_samples.iter().flat_map(|s| s.to_le_bytes()).collect();
125        let compressed = lz4_flex::compress_prepend_size(&f32_bytes);
126
127        // Decode
128        #[cfg(feature = "encryption")]
129        let mut dec = create(None);
130        #[cfg(not(feature = "encryption"))]
131        let mut dec = create();
132        let header = CodecHeader {
133            codec: "f32lz4".into(),
134            payload: {
135                let mut h = Vec::new();
136                h.extend_from_slice(MAGIC);
137                h.extend_from_slice(&48000u32.to_le_bytes());
138                h.extend_from_slice(&2u16.to_le_bytes());
139                h.extend_from_slice(&32u16.to_le_bytes());
140                h
141            },
142        };
143        let sf = dec.set_header(&header).unwrap();
144        assert_eq!(sf.rate(), 48000);
145
146        let mut data = compressed;
147        assert!(dec.decode(&mut data).unwrap());
148        assert_eq!(data, f32_bytes);
149    }
150}