Skip to main content

rff_codec_flac/
lib.rs

1//! FLAC codec adapter over our in-house [`rusty_flac`] (pure Rust, no FFI):
2//! lossless encoder + decoder, both ours end to end.
3//!
4//! FLAC is self-describing (its `STREAMINFO` carries sample rate / channels /
5//! bit depth), so — like the image codecs — a packet is the whole `.flac`
6//! stream and no [`configure`](rff_codec::Decoder::configure) is needed. The
7//! whole stream decodes to one interleaved `f32` [`AudioFrame`] (samples
8//! normalized from FLAC's native bit depth).
9
10use rff_codec::{Codec, CodecRegistry, Decoder, Encoder};
11use rff_core::{
12    AudioFrame, Dictionary, Error, Frame, MediaType, Packet, Result, SampleFormat,
13};
14
15/// Register the FLAC codec into a [`CodecRegistry`].
16pub fn register(registry: &mut CodecRegistry) {
17    registry.register(Codec {
18        id: rff_core::CodecId::Flac,
19        name: "flac",
20        long_name: "FLAC (Free Lossless Audio Codec)",
21        media_type: MediaType::Audio,
22        decoder: Some(|| Box::new(FlacDecoder::default())),
23        encoder: Some(|| Box::new(FlacEncoder::new())),
24    });
25}
26
27// ---------------------------------------------------------------------------
28// Decoder adapter
29// ---------------------------------------------------------------------------
30
31#[derive(Default)]
32struct FlacDecoder {
33    frame: Option<Frame>,
34    eof: bool,
35}
36
37impl Decoder for FlacDecoder {
38    fn send_packet(&mut self, packet: &Packet) -> Result<()> {
39        let (info, chans) = rusty_flac::decode(&packet.data)
40            .map_err(|e| Error::invalid(format!("flac decode: {e}")))?;
41        let channels = info.channels.max(1) as usize;
42        let total = chans.first().map_or(0, |c| c.len());
43
44        // ≤16-bit streams interleave straight to native s16 (no float detour);
45        // wider depths go to f32 (exact — 24-bit fits the mantissa).
46        let frame = if info.bits_per_sample <= 16 {
47            let shift = 16 - info.bits_per_sample; // sub-16-bit up-scales to the s16 grid
48            let mut bytes: Vec<u8> = vec![0u8; total * channels * 2];
49            match channels {
50                1 => {
51                    for (out, &v) in bytes.chunks_exact_mut(2).zip(&chans[0]) {
52                        out.copy_from_slice(&((v << shift) as i16).to_le_bytes());
53                    }
54                }
55                2 => {
56                    let (l, r) = (&chans[0], &chans[1]);
57                    for (i, out) in bytes.chunks_exact_mut(4).enumerate() {
58                        // One packed 4-byte store per frame.
59                        let packed = ((l[i] << shift) as i16 as u16 as u32)
60                            | (((r[i] << shift) as i16 as u16 as u32) << 16);
61                        out.copy_from_slice(&packed.to_le_bytes());
62                    }
63                }
64                _ => {
65                    for (i, out) in bytes.chunks_exact_mut(channels * 2).enumerate() {
66                        for (c, chan) in chans.iter().enumerate() {
67                            out[c * 2..c * 2 + 2]
68                                .copy_from_slice(&((chan[i] << shift) as i16).to_le_bytes());
69                        }
70                    }
71                }
72            }
73            AudioFrame {
74                sample_rate: info.sample_rate,
75                channels: channels as u16,
76                format: SampleFormat::S16,
77                planes: vec![bytes],
78                samples: total,
79                pts: packet.pts,
80            }
81        } else {
82            // Normalize native integer samples to f32 in [-1, 1).
83            let scale = (1u64 << (info.bits_per_sample - 1)) as f32;
84            let inv = 1.0 / scale;
85            let mut bytes: Vec<u8> = Vec::with_capacity(total * channels * 4);
86            for i in 0..total {
87                for chan in &chans {
88                    bytes.extend_from_slice(&(chan[i] as f32 * inv).to_le_bytes());
89                }
90            }
91            AudioFrame {
92                sample_rate: info.sample_rate,
93                channels: channels as u16,
94                format: SampleFormat::F32,
95                planes: vec![bytes],
96                samples: total,
97                pts: packet.pts,
98            }
99        };
100        self.frame = Some(Frame::Audio(frame));
101        Ok(())
102    }
103
104    fn receive_frame(&mut self) -> Result<Frame> {
105        if let Some(frame) = self.frame.take() {
106            return Ok(frame);
107        }
108        if self.eof {
109            Err(Error::Eof)
110        } else {
111            Err(Error::Again)
112        }
113    }
114
115    fn flush(&mut self) {
116        self.eof = true;
117    }
118}
119
120// ---------------------------------------------------------------------------
121// Encoder adapter
122// ---------------------------------------------------------------------------
123
124struct FlacEncoder {
125    enc: Option<rusty_flac::Encoder>,
126    /// `-compression_level`, stored until the first frame creates the encoder.
127    level: Option<u32>,
128    channels: usize,
129    /// Interleave / quantize scratch, reused between frames.
130    scratch: Vec<i32>,
131    out: Option<Vec<u8>>,
132    flushed: bool,
133}
134
135impl FlacEncoder {
136    fn new() -> Self {
137        FlacEncoder {
138            enc: None,
139            level: None,
140            channels: 0,
141            scratch: Vec::new(),
142            out: None,
143            flushed: false,
144        }
145    }
146}
147
148/// Round a float sample in [-1, 1) onto the encoder's integer grid.
149#[inline]
150fn quantize(s: f32, scale: f32) -> i32 {
151    (s * scale).round().clamp(-scale, scale - 1.0) as i32
152}
153
154impl Encoder for FlacEncoder {
155    fn configure(&mut self, options: &Dictionary) -> Result<()> {
156        if let Some(level) = options.get_int("compression_level") {
157            self.level = Some(level.clamp(0, 8) as u32);
158        }
159        Ok(())
160    }
161
162    fn send_frame(&mut self, frame: &Frame) -> Result<()> {
163        let Frame::Audio(a) = frame else {
164            return Err(Error::invalid("flac encode: expected an audio frame"));
165        };
166        if self.enc.is_none() {
167            // S16 is a native 16-bit grid; float carries ~24 bits of mantissa,
168            // so map it to a 24-bit grid (lossless for int-derived floats).
169            let bps = match a.format {
170                SampleFormat::S16 => 16,
171                _ => 24,
172            };
173            let channels = a.channels.max(1) as u32;
174            let mut enc = rusty_flac::Encoder::new(a.sample_rate, channels, bps)
175                .map_err(|e| Error::invalid(format!("flac encode: {e}")))?;
176            if let Some(level) = self.level {
177                enc.set_compression_level(level);
178            }
179            self.enc = Some(enc);
180            self.channels = channels as usize;
181        } else if a.channels.max(1) as usize != self.channels {
182            return Err(Error::invalid(
183                "flac encode: channel count changed mid-stream",
184            ));
185        }
186
187        let ch = self.channels;
188        let n = a.samples;
189        let enc = self.enc.as_mut().expect("encoder initialized above");
190        let map_err = |e: rusty_flac::EncodeError| Error::invalid(format!("flac encode: {e}"));
191        match a.format {
192            // The hot layouts stream straight from the packet bytes.
193            SampleFormat::S16 => enc
194                .push_s16le_bytes(&a.planes[0][..n * ch * 2])
195                .map_err(map_err),
196            SampleFormat::F32 => enc
197                .push_f32le_bytes(&a.planes[0][..n * ch * 4])
198                .map_err(map_err),
199            SampleFormat::F32Planar => {
200                let scale = (1i64 << 23) as f32;
201                let scratch = &mut self.scratch;
202                scratch.clear();
203                scratch.reserve(n * ch);
204                for i in 0..n {
205                    for c in 0..ch {
206                        let d = &a.planes[c];
207                        let o = i * 4;
208                        let s = f32::from_le_bytes([d[o], d[o + 1], d[o + 2], d[o + 3]]);
209                        scratch.push(quantize(s, scale));
210                    }
211                }
212                enc.push_interleaved(scratch).map_err(map_err)
213            }
214            _ => Err(Error::invalid(
215                "flac encode: unsupported sample format (need S16/F32/F32Planar)",
216            )),
217        }
218    }
219
220    fn receive_packet(&mut self) -> Result<Packet> {
221        if let Some(data) = self.out.take() {
222            let mut p = Packet::from_data(0, data);
223            p.pts = Some(0);
224            return Ok(p);
225        }
226        if self.flushed {
227            Err(Error::Eof)
228        } else {
229            Err(Error::Again)
230        }
231    }
232
233    fn flush(&mut self) {
234        if self.flushed {
235            return;
236        }
237        self.flushed = true;
238        if let Some(enc) = self.enc.take() {
239            self.out = Some(enc.finish());
240        }
241    }
242}
243
244#[cfg(test)]
245mod tests {
246    use super::*;
247
248    #[test]
249    fn decode_rejects_non_flac() {
250        let mut dec = FlacDecoder::default();
251        assert!(dec
252            .send_packet(&Packet::from_data(0, b"not flac".to_vec()))
253            .is_err());
254    }
255
256    /// Adapter end-to-end: S16 frame in → FLAC → decode → same samples out.
257    #[test]
258    fn adapter_roundtrip_s16() {
259        let n = 6000usize;
260        let mut interleaved = Vec::with_capacity(n * 2 * 2);
261        for i in 0..n {
262            let l = ((i as f64 * 0.07).sin() * 15000.0) as i16;
263            let r = (i as i16).wrapping_mul(31);
264            interleaved.extend_from_slice(&l.to_le_bytes());
265            interleaved.extend_from_slice(&r.to_le_bytes());
266        }
267        let frame = Frame::Audio(AudioFrame {
268            sample_rate: 44100,
269            channels: 2,
270            format: SampleFormat::S16,
271            planes: vec![interleaved.clone()],
272            samples: n,
273            pts: Some(0),
274        });
275
276        let mut enc = FlacEncoder::new();
277        enc.send_frame(&frame).unwrap();
278        enc.flush();
279        let packet = enc.receive_packet().unwrap();
280
281        let (info, chans) = rusty_flac::decode(&packet.data).unwrap();
282        assert_eq!(info.bits_per_sample, 16);
283        assert_eq!(info.channels, 2);
284        for i in 0..n {
285            let l = i16::from_le_bytes([interleaved[i * 4], interleaved[i * 4 + 1]]) as i32;
286            let r = i16::from_le_bytes([interleaved[i * 4 + 2], interleaved[i * 4 + 3]]) as i32;
287            assert_eq!(chans[0][i], l);
288            assert_eq!(chans[1][i], r);
289        }
290    }
291}