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//! The decoder takes two packet shapes:
5//!
6//! - **One FLAC frame per packet** (what the FLAC and Matroska demuxers emit):
7//!   [`configure`](rff_codec::Decoder::configure) must supply the stream's
8//!   STREAMINFO via `extradata` (`fLaC` + metadata blocks, or the bare
9//!   34-byte block). Each packet decodes to one [`AudioFrame`].
10//! - **A whole `.flac` stream** (starting `fLaC`, as the encoder emits it),
11//!   which decodes to one [`AudioFrame`] and needs no configuration.
12//!
13//! Either way samples come out interleaved: native s16 for <=16-bit streams,
14//! f32 normalized from the native depth for wider ones.
15
16use std::collections::VecDeque;
17
18use rff_codec::{Codec, CodecParams, CodecRegistry, Decoder, Encoder};
19use rff_core::{AudioFrame, Dictionary, Error, Frame, MediaType, Packet, Result, SampleFormat};
20
21/// Register the FLAC codec into a [`CodecRegistry`].
22pub fn register(registry: &mut CodecRegistry) {
23    registry.register(Codec {
24        id: rff_core::CodecId::Flac,
25        name: "flac",
26        long_name: "FLAC (Free Lossless Audio Codec)",
27        media_type: MediaType::Audio,
28        decoder: Some(|| Box::new(FlacDecoder::default())),
29        encoder: Some(|| Box::new(FlacEncoder::new())),
30    });
31}
32
33// ---------------------------------------------------------------------------
34// Decoder adapter
35// ---------------------------------------------------------------------------
36
37#[derive(Default)]
38struct FlacDecoder {
39    frames: VecDeque<Frame>,
40    eof: bool,
41    /// Frame decoder for the stream `configure` described (its STREAMINFO
42    /// from `extradata`); it keeps its channel scratch across packets.
43    core: Option<rusty_flac::FrameDecoder>,
44    /// Interleaved PCM scratch, reused between packets.
45    s16: Vec<i16>,
46    f32: Vec<f32>,
47}
48
49/// Find STREAMINFO in codec extradata: `fLaC` + metadata blocks (what the
50/// FLAC and Matroska demuxers hand over), or a bare 34-byte block.
51fn streaminfo_block(extradata: &[u8]) -> Option<&[u8]> {
52    if extradata.len() == 34 {
53        return Some(extradata);
54    }
55    let mut rest = extradata.strip_prefix(b"fLaC")?;
56    while rest.len() >= 4 {
57        let (hdr, body) = rest.split_at(4);
58        let len = ((hdr[1] as usize) << 16) | ((hdr[2] as usize) << 8) | hdr[3] as usize;
59        if hdr[0] & 0x7F == 0 && len >= 34 && body.len() >= 34 {
60            return Some(&body[..34]);
61        }
62        if hdr[0] & 0x80 != 0 || body.len() < len {
63            return None;
64        }
65        rest = &body[len..];
66    }
67    None
68}
69
70/// Interleaved PCM in the frame format the adapter emits: native s16 for
71/// <=16-bit streams (sub-16-bit up-scaled to the s16 grid), f32 normalised
72/// from the native depth for wider ones (exact: 24-bit fits the mantissa).
73enum Pcm<'a> {
74    S16(&'a [i16]),
75    F32(&'a [f32]),
76}
77
78/// One [`AudioFrame`] from interleaved PCM, its plane written in fixed-size
79/// chunks (one 2- or 4-byte store per sample into a pre-sized plane).
80fn audio_frame(info: &rusty_flac::StreamInfo, pcm: Pcm<'_>, pts: Option<i64>) -> AudioFrame {
81    let channels = info.channels.max(1) as usize;
82    let (format, plane, n) = match pcm {
83        Pcm::S16(v) => {
84            let mut b = vec![0u8; v.len() * 2];
85            for (d, s) in b.chunks_exact_mut(2).zip(v) {
86                d.copy_from_slice(&s.to_le_bytes());
87            }
88            (SampleFormat::S16, b, v.len())
89        }
90        Pcm::F32(v) => {
91            let mut b = vec![0u8; v.len() * 4];
92            for (d, s) in b.chunks_exact_mut(4).zip(v) {
93                d.copy_from_slice(&s.to_le_bytes());
94            }
95            (SampleFormat::F32, b, v.len())
96        }
97    };
98    AudioFrame {
99        sample_rate: info.sample_rate,
100        channels: channels as u16,
101        format,
102        planes: vec![plane],
103        samples: n / channels,
104        pts,
105    }
106}
107
108fn map_err(e: rusty_flac::DecodeError) -> Error {
109    Error::invalid(format!("flac decode: {e}"))
110}
111
112impl FlacDecoder {
113    /// A whole `.flac` stream in one packet (the encoder's output).
114    fn decode_stream(&mut self, data: &[u8], pts: Option<i64>) -> Result<()> {
115        let mut dec = rusty_flac::Decoder::new(data).map_err(map_err)?;
116        let info = dec.streaminfo().clone();
117        let frame = if info.bits_per_sample <= 16 {
118            self.s16.clear();
119            while dec
120                .next_frame_interleaved(&mut self.s16)
121                .map_err(map_err)?
122                .is_some()
123            {}
124            audio_frame(&info, Pcm::S16(&self.s16), pts)
125        } else {
126            self.f32.clear();
127            while dec
128                .next_frame_interleaved(&mut self.f32)
129                .map_err(map_err)?
130                .is_some()
131            {}
132            audio_frame(&info, Pcm::F32(&self.f32), pts)
133        };
134        self.frames.push_back(Frame::Audio(frame));
135        Ok(())
136    }
137
138    /// Frame packet(s), as the FLAC / Matroska demuxers emit them. The first
139    /// frame must decode; further frames in the same packet decode too, and
140    /// bytes after the last one that don't parse as a frame (e.g. a
141    /// trailing tag) are ignored. An empty packet yields nothing.
142    fn decode_frames(&mut self, data: &[u8], pts: Option<i64>) -> Result<()> {
143        let Some(core) = self.core.as_mut() else {
144            return Err(Error::invalid(
145                "flac decode: frame packet without STREAMINFO (extradata)",
146            ));
147        };
148        if data.is_empty() {
149            return Ok(());
150        }
151        let info = core.streaminfo().clone();
152        fn run<S: rusty_flac::Sample>(
153            core: &mut rusty_flac::FrameDecoder,
154            data: &[u8],
155            out: &mut Vec<S>,
156        ) -> Result<()> {
157            out.clear();
158            let mut at = 0;
159            while at < data.len() {
160                match core.decode_frame_interleaved(&data[at..], out) {
161                    Ok((_, used)) => at += used,
162                    Err(e) if at == 0 => return Err(map_err(e)),
163                    Err(_) => break,
164                }
165            }
166            Ok(())
167        }
168        let frame = if info.bits_per_sample <= 16 {
169            run(core, data, &mut self.s16)?;
170            audio_frame(&info, Pcm::S16(&self.s16), pts)
171        } else {
172            run(core, data, &mut self.f32)?;
173            audio_frame(&info, Pcm::F32(&self.f32), pts)
174        };
175        self.frames.push_back(Frame::Audio(frame));
176        Ok(())
177    }
178}
179
180impl Decoder for FlacDecoder {
181    fn configure(&mut self, params: &CodecParams) -> Result<()> {
182        if let Some(si) = streaminfo_block(&params.extradata) {
183            // A STREAMINFO with an impossible channel count leaves the
184            // decoder unconfigured (frame packets are then refused).
185            self.core = rusty_flac::StreamInfo::parse(si)
186                .and_then(rusty_flac::FrameDecoder::new)
187                .ok();
188        }
189        Ok(())
190    }
191
192    fn send_packet(&mut self, packet: &Packet) -> Result<()> {
193        if packet.data.starts_with(b"fLaC") {
194            self.decode_stream(&packet.data, packet.pts)
195        } else {
196            self.decode_frames(&packet.data, packet.pts)
197        }
198    }
199
200    fn receive_frame(&mut self) -> Result<Frame> {
201        if let Some(frame) = self.frames.pop_front() {
202            return Ok(frame);
203        }
204        if self.eof {
205            Err(Error::Eof)
206        } else {
207            Err(Error::Again)
208        }
209    }
210
211    fn flush(&mut self) {
212        self.eof = true;
213    }
214}
215
216// ---------------------------------------------------------------------------
217// Encoder adapter
218// ---------------------------------------------------------------------------
219
220struct FlacEncoder {
221    enc: Option<rusty_flac::Encoder>,
222    /// `-compression_level`, stored until the first frame creates the encoder.
223    level: Option<u32>,
224    channels: usize,
225    /// Interleave / quantize scratch, reused between frames.
226    scratch: Vec<i32>,
227    out: Option<Vec<u8>>,
228    flushed: bool,
229}
230
231impl FlacEncoder {
232    fn new() -> Self {
233        FlacEncoder {
234            enc: None,
235            level: None,
236            channels: 0,
237            scratch: Vec::new(),
238            out: None,
239            flushed: false,
240        }
241    }
242}
243
244/// Round a float sample in [-1, 1) onto the encoder's integer grid.
245#[inline]
246fn quantize(s: f32, scale: f32) -> i32 {
247    (s * scale).round().clamp(-scale, scale - 1.0) as i32
248}
249
250impl Encoder for FlacEncoder {
251    fn configure(&mut self, options: &Dictionary) -> Result<()> {
252        if let Some(level) = options.get_int("compression_level") {
253            self.level = Some(level.clamp(0, 8) as u32);
254        }
255        Ok(())
256    }
257
258    fn send_frame(&mut self, frame: &Frame) -> Result<()> {
259        let Frame::Audio(a) = frame else {
260            return Err(Error::invalid("flac encode: expected an audio frame"));
261        };
262        if self.enc.is_none() {
263            // S16 is a native 16-bit grid; float carries ~24 bits of mantissa,
264            // so map it to a 24-bit grid (lossless for int-derived floats).
265            let bps = match a.format {
266                SampleFormat::S16 => 16,
267                _ => 24,
268            };
269            let channels = a.channels.max(1) as u32;
270            let mut enc = rusty_flac::Encoder::new(a.sample_rate, channels, bps)
271                .map_err(|e| Error::invalid(format!("flac encode: {e}")))?;
272            if let Some(level) = self.level {
273                enc.set_compression_level(level);
274            }
275            self.enc = Some(enc);
276            self.channels = channels as usize;
277        } else if a.channels.max(1) as usize != self.channels {
278            return Err(Error::invalid(
279                "flac encode: channel count changed mid-stream",
280            ));
281        }
282
283        let ch = self.channels;
284        let n = a.samples;
285        let enc = self.enc.as_mut().expect("encoder initialized above");
286        let map_err = |e: rusty_flac::EncodeError| Error::invalid(format!("flac encode: {e}"));
287        match a.format {
288            // The hot layouts stream straight from the packet bytes.
289            SampleFormat::S16 => enc
290                .push_s16le_bytes(&a.planes[0][..n * ch * 2])
291                .map_err(map_err),
292            SampleFormat::F32 => enc
293                .push_f32le_bytes(&a.planes[0][..n * ch * 4])
294                .map_err(map_err),
295            SampleFormat::F32Planar => {
296                let scale = (1i64 << 23) as f32;
297                let scratch = &mut self.scratch;
298                scratch.clear();
299                scratch.reserve(n * ch);
300                for i in 0..n {
301                    for c in 0..ch {
302                        let d = &a.planes[c];
303                        let o = i * 4;
304                        let s = f32::from_le_bytes([d[o], d[o + 1], d[o + 2], d[o + 3]]);
305                        scratch.push(quantize(s, scale));
306                    }
307                }
308                enc.push_interleaved(scratch).map_err(map_err)
309            }
310            _ => Err(Error::invalid(
311                "flac encode: unsupported sample format (need S16/F32/F32Planar)",
312            )),
313        }
314    }
315
316    fn receive_packet(&mut self) -> Result<Packet> {
317        if let Some(data) = self.out.take() {
318            let mut p = Packet::from_data(0, data);
319            p.pts = Some(0);
320            return Ok(p);
321        }
322        if self.flushed {
323            Err(Error::Eof)
324        } else {
325            Err(Error::Again)
326        }
327    }
328
329    fn flush(&mut self) {
330        if self.flushed {
331            return;
332        }
333        self.flushed = true;
334        if let Some(enc) = self.enc.take() {
335            self.out = Some(enc.finish());
336        }
337    }
338}
339
340#[cfg(test)]
341mod tests {
342    use super::*;
343
344    #[test]
345    fn decode_rejects_non_flac() {
346        let mut dec = FlacDecoder::default();
347        assert!(dec
348            .send_packet(&Packet::from_data(0, b"not flac".to_vec()))
349            .is_err());
350    }
351
352    /// Adapter end-to-end: S16 frame in → FLAC → decode → same samples out.
353    #[test]
354    fn adapter_roundtrip_s16() {
355        let n = 6000usize;
356        let mut interleaved = Vec::with_capacity(n * 2 * 2);
357        for i in 0..n {
358            let l = ((i as f64 * 0.07).sin() * 15000.0) as i16;
359            let r = (i as i16).wrapping_mul(31);
360            interleaved.extend_from_slice(&l.to_le_bytes());
361            interleaved.extend_from_slice(&r.to_le_bytes());
362        }
363        let frame = Frame::Audio(AudioFrame {
364            sample_rate: 44100,
365            channels: 2,
366            format: SampleFormat::S16,
367            planes: vec![interleaved.clone()],
368            samples: n,
369            pts: Some(0),
370        });
371
372        let mut enc = FlacEncoder::new();
373        enc.send_frame(&frame).unwrap();
374        enc.flush();
375        let packet = enc.receive_packet().unwrap();
376
377        let (info, chans) = rusty_flac::decode(&packet.data).unwrap();
378        assert_eq!(info.bits_per_sample, 16);
379        assert_eq!(info.channels, 2);
380        for i in 0..n {
381            let l = i16::from_le_bytes([interleaved[i * 4], interleaved[i * 4 + 1]]) as i32;
382            let r = i16::from_le_bytes([interleaved[i * 4 + 2], interleaved[i * 4 + 3]]) as i32;
383            assert_eq!(chans[0][i], l);
384            assert_eq!(chans[1][i], r);
385        }
386    }
387
388    /// Split a `.flac` stream into (`fLaC` + metadata, frames) the way the
389    /// demuxer does: a frame ends at a sync code where the CRC-16 residue of
390    /// the bytes since the frame start is zero.
391    fn split_frames(stream: &[u8]) -> (Vec<u8>, Vec<Vec<u8>>) {
392        let mut pos = 4;
393        loop {
394            let last = stream[pos] & 0x80 != 0;
395            let len = u32::from_be_bytes([0, stream[pos + 1], stream[pos + 2], stream[pos + 3]]);
396            pos += 4 + len as usize;
397            if last {
398                break;
399            }
400        }
401        let header = stream[..pos].to_vec();
402        let crc16 = |data: &[u8]| {
403            data.iter().fold(0u16, |mut c, &b| {
404                c ^= (b as u16) << 8;
405                for _ in 0..8 {
406                    c = if c & 0x8000 != 0 {
407                        (c << 1) ^ 0x8005
408                    } else {
409                        c << 1
410                    };
411                }
412                c
413            })
414        };
415        let mut frames = Vec::new();
416        let mut start = pos;
417        for p in pos + 10..stream.len() {
418            if stream[p] == 0xFF
419                && stream.get(p + 1).is_some_and(|&b| b & 0xFE == 0xF8)
420                && crc16(&stream[start..p]) == 0
421            {
422                frames.push(stream[start..p].to_vec());
423                start = p;
424            }
425        }
426        frames.push(stream[start..].to_vec());
427        (header, frames)
428    }
429
430    fn params(extradata: Vec<u8>) -> CodecParams {
431        CodecParams {
432            codec_id: rff_core::CodecId::Flac,
433            width: 0,
434            height: 0,
435            pixel_format: None,
436            sample_rate: 0,
437            channels: 0,
438            sample_format: None,
439            extradata,
440        }
441    }
442
443    fn audio(frame: Frame) -> AudioFrame {
444        match frame {
445            Frame::Audio(a) => a,
446            Frame::Video(_) => panic!("expected audio"),
447        }
448    }
449
450    /// Per-frame packets decode to exactly the PCM of the whole-stream decode.
451    #[test]
452    fn frame_packets_decode_identically_to_the_whole_stream() {
453        let n = 23_456usize;
454        let sig = |i: usize, k: i64| ((i as f64 * 0.031 * k as f64).sin() * 30000.0) as i64;
455        for (rate, channels, bps) in [(44_100, 2u32, 16u32), (48_000, 1, 24), (22_050, 3, 8)] {
456            let planes: Vec<Vec<i32>> = (0..channels as i64)
457                .map(|c| {
458                    (0..n)
459                        .map(|i| {
460                            (sig(i, c + 1) >> (16 - bps.min(16))) as i32 * (1 << (bps.max(16) - 16))
461                        })
462                        .collect()
463                })
464                .collect();
465            let refs: Vec<&[i32]> = planes.iter().map(|p| p.as_slice()).collect();
466            let mut enc = rusty_flac::Encoder::new(rate, channels, bps).unwrap();
467            enc.push_planar(&refs).unwrap();
468            let stream = enc.finish();
469
470            let mut whole = FlacDecoder::default();
471            whole
472                .send_packet(&Packet::from_data(0, stream.clone()))
473                .unwrap();
474            let want = audio(whole.receive_frame().unwrap());
475
476            let (header, frames) = split_frames(&stream);
477            assert!(frames.len() > 3);
478            for extradata in [header.clone(), header[8..42].to_vec()] {
479                let mut dec = FlacDecoder::default();
480                dec.configure(&params(extradata)).unwrap();
481                let mut got = Vec::new();
482                let mut samples = 0;
483                for (i, f) in frames.iter().enumerate() {
484                    let mut p = Packet::from_data(0, f.clone());
485                    p.pts = Some(i as i64);
486                    dec.send_packet(&p).unwrap();
487                    let a = audio(dec.receive_frame().unwrap());
488                    assert_eq!(a.pts, Some(i as i64));
489                    assert_eq!(
490                        (a.format, a.sample_rate, a.channels),
491                        (want.format, want.sample_rate, want.channels)
492                    );
493                    samples += a.samples;
494                    got.extend_from_slice(&a.planes[0]);
495                    assert!(matches!(dec.receive_frame(), Err(Error::Again)));
496                }
497                assert_eq!(samples, want.samples);
498                assert_eq!(got, want.planes[0], "{rate} Hz {channels} ch {bps}-bit");
499            }
500        }
501    }
502
503    #[test]
504    fn frame_packets_need_streaminfo_and_reject_corruption() {
505        let mut enc = rusty_flac::Encoder::new(44_100, 1, 16).unwrap();
506        enc.push_planar(&[&(0..9000)
507            .map(|i| (i * 37 % 2001) - 1000)
508            .collect::<Vec<i32>>()])
509            .unwrap();
510        let stream = enc.finish();
511        let (header, frames) = split_frames(&stream);
512        let mut dec = FlacDecoder::default();
513        assert!(dec
514            .send_packet(&Packet::from_data(0, frames[0].clone()))
515            .is_err());
516        dec.configure(&params(header)).unwrap();
517        let mut bad = frames[0].clone();
518        let mid = bad.len() / 2;
519        bad[mid] ^= 0x40;
520        assert!(dec.send_packet(&Packet::from_data(0, bad)).is_err());
521        assert!(dec
522            .send_packet(&Packet::from_data(0, b"junk".to_vec()))
523            .is_err());
524        // An empty packet yields nothing; a frame with a trailing tag decodes.
525        dec.send_packet(&Packet::new(0)).unwrap();
526        assert!(matches!(dec.receive_frame(), Err(Error::Again)));
527        let mut tagged = frames.last().unwrap().clone();
528        tagged.extend_from_slice(b"TAG trailing id3v1-ish bytes");
529        dec.send_packet(&Packet::from_data(0, tagged)).unwrap();
530        assert!(dec.receive_frame().is_ok());
531        // Garbage extradata leaves the decoder unconfigured, not panicking.
532        let mut dec = FlacDecoder::default();
533        dec.configure(&params(b"fLaC\x01\xff\xff\xff".to_vec()))
534            .unwrap();
535        assert!(dec
536            .send_packet(&Packet::from_data(0, frames[0].clone()))
537            .is_err());
538    }
539}