Skip to main content

snapcast_client/decoder/
flac.rs

1//! FLAC decoder using symphonia.
2//!
3//! The snapserver sends a FLAC stream header (fLaC + STREAMINFO) as the CodecHeader,
4//! then individual FLAC frames as WireChunk payloads.
5
6use anyhow::{Result, bail};
7use snapcast_proto::SampleFormat;
8use snapcast_proto::message::codec_header::CodecHeader;
9use symphonia::core::audio::SampleBuffer;
10use symphonia::core::codecs::{CODEC_TYPE_FLAC, CodecParameters, DecoderOptions};
11use symphonia::core::formats::Packet;
12
13use crate::decoder::Decoder;
14use crate::stream::SampleEncoding;
15
16/// Parse FLAC STREAMINFO from a codec header payload.
17///
18/// Layout: "fLaC" (4) + block_header (4) + STREAMINFO (34)
19/// STREAMINFO bytes 10-13 contain: sample_rate (20 bits) | channels-1 (3 bits) | bps-1 (5 bits)
20fn parse_streaminfo(payload: &[u8]) -> Result<(SampleFormat, CodecParameters)> {
21    if payload.len() < 42 {
22        bail!(
23            "FLAC header too small ({} bytes, need >= 42)",
24            payload.len()
25        );
26    }
27    if &payload[0..4] != b"fLaC" {
28        bail!("not a FLAC header (missing fLaC magic)");
29    }
30
31    // STREAMINFO starts at offset 8 (after 4-byte magic + 4-byte block header)
32    let si = &payload[8..];
33
34    // Bytes 10-13 of STREAMINFO (offsets 10..14 from si start):
35    // 20 bits sample rate | 3 bits (channels-1) | 5 bits (bps-1) | 36 bits total samples
36    let b10 = si[10] as u32;
37    let b11 = si[11] as u32;
38    let b12 = si[12] as u32;
39
40    let sample_rate = (b10 << 12) | (b11 << 4) | (b12 >> 4);
41    let channels = ((b12 >> 1) & 0x07) + 1;
42    let bits_per_sample = (((b12 & 0x01) << 4) | (si[13] as u32 >> 4)) + 1;
43
44    let sf = SampleFormat::new(sample_rate, bits_per_sample as u16, channels as u16);
45
46    let mut params = CodecParameters::new();
47    params
48        .for_codec(CODEC_TYPE_FLAC)
49        .with_sample_rate(sample_rate)
50        .with_bits_per_sample(bits_per_sample)
51        .with_channels(
52            symphonia::core::audio::Channels::from_bits(((1u64 << channels) - 1) as u32)
53                .unwrap_or(symphonia::core::audio::Channels::FRONT_LEFT),
54        )
55        .with_extra_data(payload[8..42].to_vec().into_boxed_slice());
56
57    Ok((sf, params))
58}
59
60/// FLAC audio decoder using symphonia.
61pub struct FlacDecoder {
62    decoder: Box<dyn symphonia::core::codecs::Decoder>,
63    sample_format: SampleFormat,
64    packet_id: u64,
65}
66
67impl FlacDecoder {
68    fn new_from_params(sf: SampleFormat, params: &CodecParameters) -> Result<Self> {
69        let decoder = symphonia::default::get_codecs()
70            .make(params, &DecoderOptions::default())
71            .map_err(|e| anyhow::anyhow!("failed to create FLAC decoder: {e}"))?;
72        // Report 32-bit because we output f32 samples regardless of source bit depth.
73        let output_format = SampleFormat::new(sf.rate(), 32, sf.channels());
74        Ok(Self {
75            decoder,
76            sample_format: output_format,
77            packet_id: 0,
78        })
79    }
80}
81
82impl Decoder for FlacDecoder {
83    fn set_header(&mut self, header: &CodecHeader) -> Result<SampleFormat> {
84        tracing::trace!(
85            codec = "flac",
86            payload_len = header.payload.len(),
87            "set_header"
88        );
89        let (sf, params) = parse_streaminfo(&header.payload)?;
90        *self = Self::new_from_params(sf, &params)?;
91        Ok(self.sample_format)
92    }
93
94    fn decode(&mut self, data: &mut Vec<u8>) -> Result<bool> {
95        if data.is_empty() {
96            return Ok(false);
97        }
98
99        tracing::trace!(
100            codec = "flac",
101            input_bytes = data.len(),
102            packet_id = self.packet_id,
103            "decode"
104        );
105
106        let packet = Packet::new_from_slice(0, self.packet_id, 0, data);
107        self.packet_id += 1;
108
109        let decoded = match self.decoder.decode(&packet) {
110            Ok(buf) => buf,
111            Err(e) => {
112                tracing::warn!(codec = "flac", error = %e, "decode failed");
113                return Ok(false);
114            }
115        };
116
117        let spec = *decoded.spec();
118        let frames = decoded.frames() as u64;
119
120        let mut sample_buf = SampleBuffer::<f32>::new(frames, spec);
121        sample_buf.copy_interleaved_ref(decoded);
122        let samples = sample_buf.samples();
123
124        let mut out = Vec::with_capacity(samples.len() * 4);
125        for &s in samples {
126            out.extend_from_slice(&s.to_le_bytes());
127        }
128
129        *data = out;
130        Ok(true)
131    }
132
133    fn output_encoding(&self) -> SampleEncoding {
134        SampleEncoding::Float32
135    }
136}
137
138/// Create a FlacDecoder from a CodecHeader (convenience for factory use).
139pub fn create(header: &CodecHeader) -> Result<FlacDecoder> {
140    let (sf, params) = parse_streaminfo(&header.payload)?;
141    FlacDecoder::new_from_params(sf, &params)
142}
143
144#[cfg(test)]
145mod tests {
146    use super::*;
147
148    /// Build a minimal valid FLAC header: fLaC + STREAMINFO block
149    /// for 44100 Hz, 16-bit, 2 channels
150    fn flac_header_44100_16_2() -> Vec<u8> {
151        let mut h = Vec::new();
152        h.extend_from_slice(b"fLaC");
153        // Block header: type=0 (STREAMINFO), last=1 → 0x80, length=34
154        h.push(0x80);
155        h.extend_from_slice(&[0x00, 0x00, 0x22]); // length = 34
156
157        // STREAMINFO (34 bytes), all multi-byte fields are BIG-endian:
158        h.extend_from_slice(&[0x00, 0x10]); // min block size = 16
159        h.extend_from_slice(&[0x10, 0x00]); // max block size = 4096
160        h.extend_from_slice(&[0x00, 0x00, 0x00]); // min frame size
161        h.extend_from_slice(&[0x00, 0x00, 0x00]); // max frame size
162
163        // Bytes 10-13: sample_rate(20) | channels-1(3) | bps-1(5) | total_samples high 4 bits
164        // 44100 = 0xAC44
165        // byte10 = 0xAC44 >> 12 = 0x0A
166        // byte11 = (0xAC44 >> 4) & 0xFF = 0xC4
167        // byte12 = ((0xAC44 & 0x0F) << 4) | ((2-1) << 1) | ((16-1) >> 4)
168        //        = (0x04 << 4) | (1 << 1) | (15 >> 4) = 0x40 | 0x02 | 0x00 = 0x42
169        // byte13 = ((16-1) & 0x0F) << 4 = 0xF0
170        h.push(0x0A);
171        h.push(0xC4);
172        h.push(0x42);
173        h.push(0xF0);
174
175        // Remaining 4 bytes of total_samples + 16 bytes MD5
176        h.extend_from_slice(&[0; 20]);
177
178        assert_eq!(h.len(), 42);
179        h
180    }
181
182    #[test]
183    fn parse_streaminfo_44100_16_2() {
184        let payload = flac_header_44100_16_2();
185        let (sf, _params) = parse_streaminfo(&payload).unwrap();
186        assert_eq!(sf.rate(), 44100);
187        assert_eq!(sf.bits(), 16);
188        assert_eq!(sf.channels(), 2);
189    }
190
191    #[test]
192    fn parse_streaminfo_48000_24_2() {
193        let mut h = Vec::new();
194        h.extend_from_slice(b"fLaC");
195        h.push(0x80);
196        h.extend_from_slice(&[0x00, 0x00, 0x22]);
197        h.extend_from_slice(&[0x10, 0x00]); // min block
198        h.extend_from_slice(&[0x10, 0x00]); // max block
199        h.extend_from_slice(&[0; 6]); // frame sizes
200
201        // 48000 = 0xBB80
202        // byte10 = 0x0B, byte11 = 0xB8, byte12 = 0x02 | (1<<1) | (23>>4) = 0x02|0x02|0x01 = 0x03
203        // Wait: channels-1=1, bps-1=23
204        // byte12 = (0x00 << 4) | (1 << 1) | (23 >> 4) = 0x00 | 0x02 | 0x01 = 0x03
205        // byte13 = (23 & 0x0F) << 4 = 0x70
206        h.push(0x0B);
207        h.push(0xB8);
208        h.push(0x03);
209        h.push(0x70);
210        h.extend_from_slice(&[0; 20]);
211
212        let (sf, _) = parse_streaminfo(&h).unwrap();
213        assert_eq!(sf.rate(), 48000);
214        assert_eq!(sf.bits(), 24);
215        assert_eq!(sf.channels(), 2);
216    }
217
218    #[test]
219    fn parse_streaminfo_too_small() {
220        assert!(parse_streaminfo(&[0; 10]).is_err());
221    }
222
223    #[test]
224    fn parse_streaminfo_bad_magic() {
225        let mut h = vec![0u8; 42];
226        h[0..4].copy_from_slice(b"NOPE");
227        assert!(parse_streaminfo(&h).is_err());
228    }
229
230    #[test]
231    fn create_decoder_from_header() {
232        let header = CodecHeader {
233            codec: "flac".into(),
234            payload: flac_header_44100_16_2(),
235        };
236        let dec = create(&header);
237        if let Err(e) = &dec {
238            eprintln!("Error: {e:?}");
239        }
240        let dec = dec.unwrap();
241        assert_eq!(dec.sample_format.rate(), 44100);
242    }
243}