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