snapcast_client/decoder/
f32lz4.rs1use 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
19pub 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 #[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 #[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#[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#[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 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 #[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}