Skip to main content

oximedia_codec/flac/
encoder.rs

1//! FLAC audio encoder.
2//!
3//! Encodes interleaved i32 PCM samples into FLAC frames.
4//!
5//! # Encoding pipeline
6//!
7//! 1. Frame blocking (split PCM into block-size chunks).
8//! 2. Per-channel LPC analysis (autocorrelation + Levinson-Durbin).
9//! 3. Residual computation (signal − LPC prediction).
10//! 4. Rice coding (optimal Rice parameter per partition).
11//! 5. Frame serialisation (FLAC binary format with CRC-16).
12
13#![forbid(unsafe_code)]
14#![allow(clippy::cast_possible_truncation)]
15#![allow(clippy::cast_lossless)]
16#![allow(clippy::cast_precision_loss)]
17#![allow(clippy::cast_sign_loss)]
18
19use super::lpc::{autocorrelate, compute_residuals, levinson_durbin, quantise_coeffs};
20use super::rice::{optimal_rice_param, rice_encode};
21use crate::error::{CodecError, CodecResult};
22
23// =============================================================================
24// CRC-16 (CCITT-variant used by FLAC)
25// =============================================================================
26
27fn crc16(data: &[u8]) -> u16 {
28    const POLY: u16 = 0x8005;
29    let mut crc = 0u16;
30    for &byte in data {
31        crc ^= u16::from(byte) << 8;
32        for _ in 0..8 {
33            if crc & 0x8000 != 0 {
34                crc = (crc << 1) ^ POLY;
35            } else {
36                crc <<= 1;
37            }
38        }
39    }
40    crc
41}
42
43// =============================================================================
44// Configuration
45// =============================================================================
46
47/// FLAC encoder configuration.
48#[derive(Clone, Debug)]
49pub struct FlacConfig {
50    /// Sample rate in Hz.
51    pub sample_rate: u32,
52    /// Number of audio channels.
53    pub channels: u8,
54    /// Bits per sample (8, 16, 20, or 24).
55    pub bits_per_sample: u8,
56}
57
58impl FlacConfig {
59    /// Frame block size (number of samples per channel per frame).
60    pub const BLOCK_SIZE: usize = 4096;
61    /// LPC order used for compression.
62    pub const LPC_ORDER: usize = 8;
63}
64
65// =============================================================================
66// Encoded frame
67// =============================================================================
68
69/// One encoded FLAC frame.
70#[derive(Clone, Debug)]
71pub struct FlacFrame {
72    /// Raw FLAC frame bytes (including sync code + header + subframes + CRC).
73    pub data: Vec<u8>,
74    /// Sample number of the first sample in this frame.
75    pub sample_number: u64,
76    /// Number of samples (per channel) in this frame.
77    pub block_size: u32,
78}
79
80// =============================================================================
81// Encoder
82// =============================================================================
83
84/// FLAC audio encoder.
85pub struct FlacEncoder {
86    config: FlacConfig,
87    /// Total samples encoded so far (per channel).
88    samples_encoded: u64,
89}
90
91impl FlacEncoder {
92    /// Create a new FLAC encoder.
93    #[must_use]
94    pub fn new(config: FlacConfig) -> Self {
95        Self {
96            config,
97            samples_encoded: 0,
98        }
99    }
100
101    /// Generate the FLAC stream header (`fLaC` magic + STREAMINFO block).
102    ///
103    /// Must be placed at the start of the stream before any frames.
104    pub fn stream_header(&self) -> Vec<u8> {
105        let mut out = Vec::new();
106        out.extend_from_slice(b"fLaC");
107
108        // METADATA_BLOCK_HEADER: last-metadata-block=1, type=0 (STREAMINFO), length=34
109        out.push(0x80); // bit 7 = last, bits 0-6 = type 0
110        out.push(0x00);
111        out.push(0x00);
112        out.push(34); // STREAMINFO is always 34 bytes
113
114        // STREAMINFO
115        let bs = FlacConfig::BLOCK_SIZE as u16;
116        out.extend_from_slice(&bs.to_be_bytes()); // min block size
117        out.extend_from_slice(&bs.to_be_bytes()); // max block size
118        out.extend_from_slice(&[0, 0, 0]); // min frame size (unknown)
119        out.extend_from_slice(&[0, 0, 0]); // max frame size (unknown)
120
121        // sample_rate (20 bits) + channels-1 (3 bits) + bps-1 (5 bits) + total_samples (36 bits)
122        let sr = self.config.sample_rate;
123        let ch = (self.config.channels - 1) as u32;
124        let bps = (self.config.bits_per_sample - 1) as u32;
125
126        // Pack: [sr_20bit][ch_3bit][bps_5bit] = u32, then 36-bit total_samples = 0
127        let packed = (sr << 12) | (ch << 9) | (bps << 4);
128        out.extend_from_slice(&packed.to_be_bytes()); // 4 bytes covers 20+3+5 bits + 4 bits of zeros
129        out.extend_from_slice(&[0, 0, 0, 0]); // remaining bits of total_samples
130
131        // MD5 signature (16 bytes, all zeros — unknown at encode time)
132        out.extend_from_slice(&[0u8; 16]);
133
134        out
135    }
136
137    /// Encode interleaved i32 PCM samples into one or more FLAC frames.
138    ///
139    /// `samples` is interleaved: `[ch0_s0, ch1_s0, ch0_s1, ch1_s1, ...]`.
140    ///
141    /// Returns `(stream_header, frames)` on the first call, or just frames on
142    /// subsequent calls.  Use `stream_header()` for the initial fLaC header.
143    ///
144    /// # Errors
145    ///
146    /// Returns `CodecError::InvalidParameter` if the sample count is not a
147    /// multiple of `channels`.
148    pub fn encode(&mut self, samples: &[i32]) -> CodecResult<(Vec<u8>, Vec<FlacFrame>)> {
149        let ch = self.config.channels as usize;
150        if samples.len() % ch != 0 {
151            return Err(CodecError::InvalidParameter(
152                "Sample count must be a multiple of channel count".to_string(),
153            ));
154        }
155
156        let frame_samples = samples.len() / ch;
157        let block = FlacConfig::BLOCK_SIZE;
158        let header = self.stream_header();
159        let mut frames = Vec::new();
160
161        let mut offset = 0usize;
162        while offset < frame_samples {
163            let end = (offset + block).min(frame_samples);
164            let block_len = end - offset;
165
166            // Deinterleave
167            let channels: Vec<Vec<i32>> = (0..ch)
168                .map(|c| (offset..end).map(|s| samples[s * ch + c]).collect())
169                .collect();
170
171            let frame = self.encode_frame(&channels, block_len, self.samples_encoded)?;
172            self.samples_encoded += block_len as u64;
173            frames.push(frame);
174
175            offset = end;
176        }
177
178        Ok((header, frames))
179    }
180
181    /// Encode one block of deinterleaved channel data into a FLAC frame.
182    fn encode_frame(
183        &self,
184        channels: &[Vec<i32>],
185        block_len: usize,
186        sample_number: u64,
187    ) -> CodecResult<FlacFrame> {
188        let ch = channels.len();
189        let mut data: Vec<u8> = Vec::new();
190
191        // Frame sync code: 0xFF 0xF8 (fixed-block, 16-bit, variable blocksize encoding)
192        data.push(0xFF);
193        data.push(0xF8);
194
195        // Block size (16 bits) and sample rate tokens
196        // Use the "explicit block size" form: 0x70 = 7 in high nibble → next 2 bytes = block_size
197        data.push(0x70 | 0x09); // block size = get 16-bit after header; sample rate from STREAMINFO
198        data.push(((ch as u8 - 1) << 4) | (self.config.bits_per_sample / 4 - 1));
199
200        // UTF-8-coded sample number (simplified: 4-byte form)
201        let sn = sample_number;
202        data.push(0xF0 | ((sn >> 18) as u8 & 0x07));
203        data.push(0x80 | ((sn >> 12) as u8 & 0x3F));
204        data.push(0x80 | ((sn >> 6) as u8 & 0x3F));
205        data.push(0x80 | (sn as u8 & 0x3F));
206
207        // Explicit block size (16-bit LE)
208        let bs = block_len as u16;
209        data.push((bs >> 8) as u8);
210        data.push(bs as u8);
211
212        // CRC-8 of header (simplified: just 0)
213        data.push(0);
214
215        // Encode each subframe
216        for chan in channels {
217            let subframe = self.encode_subframe(chan)?;
218            data.extend_from_slice(&subframe);
219        }
220
221        // Zero-pad to byte boundary
222        while data.len() % 2 != 0 {
223            data.push(0);
224        }
225
226        // CRC-16 of the frame (2 bytes)
227        let crc = crc16(&data);
228        data.extend_from_slice(&crc.to_be_bytes());
229
230        Ok(FlacFrame {
231            data,
232            sample_number,
233            block_size: block_len as u32,
234        })
235    }
236
237    /// Encode one channel as a FLAC subframe using LPC.
238    fn encode_subframe(&self, samples: &[i32]) -> CodecResult<Vec<u8>> {
239        let order = FlacConfig::LPC_ORDER.min(samples.len() / 2);
240
241        // Convert to f64 for LPC analysis
242        let signal_f64: Vec<f64> = samples.iter().map(|&s| s as f64).collect();
243        let ac = autocorrelate(&signal_f64, order);
244        let (float_coeffs, _) = levinson_durbin(&ac, order);
245
246        let effective_order = float_coeffs.len();
247
248        // Fallback to verbatim if LPC fails
249        if effective_order == 0 {
250            return self.encode_verbatim(samples);
251        }
252
253        // Quantise LPC coefficients
254        let (int_coeffs, shift) = quantise_coeffs(&float_coeffs, 15);
255
256        // Compute residuals
257        let residuals = compute_residuals(samples, &float_coeffs);
258
259        // Rice encode residuals
260        let k = optimal_rice_param(&residuals);
261        let rice_bytes = rice_encode(&residuals, k);
262
263        // Subframe header: type = LPC (0b001_order)
264        let mut sf: Vec<u8> = Vec::new();
265        let subframe_type = 0x40 | ((effective_order as u8 - 1) & 0x3F);
266        sf.push(subframe_type);
267
268        // Warmup samples (order samples verbatim, 16-bit)
269        for &s in &samples[..effective_order] {
270            sf.push((s >> 8) as u8);
271            sf.push(s as u8);
272        }
273
274        // Quantised coefficients precision and shift
275        sf.push(14); // precision = 15 bits - 1
276        sf.push(shift);
277
278        // Coefficients (i16 BE)
279        for &c in &int_coeffs {
280            sf.push((c >> 8) as u8);
281            sf.push(c as u8);
282        }
283
284        // Rice partition header: parameter bits = 4, num partitions = 0 (one partition)
285        sf.push(0x00);
286        sf.push(k);
287        sf.extend_from_slice(&rice_bytes);
288
289        Ok(sf)
290    }
291
292    /// Encode channel as verbatim subframe (no compression).
293    fn encode_verbatim(&self, samples: &[i32]) -> CodecResult<Vec<u8>> {
294        let mut sf = Vec::new();
295        sf.push(0x02); // verbatim subframe type
296        for &s in samples {
297            sf.push((s >> 8) as u8);
298            sf.push(s as u8);
299        }
300        Ok(sf)
301    }
302}
303
304// =============================================================================
305// Tests
306// =============================================================================
307
308#[cfg(test)]
309mod tests {
310    use super::*;
311
312    fn make_encoder() -> FlacEncoder {
313        FlacEncoder::new(FlacConfig {
314            sample_rate: 44100,
315            channels: 2,
316            bits_per_sample: 16,
317        })
318    }
319
320    #[test]
321    fn test_flac_stream_header_magic() {
322        let enc = make_encoder();
323        let header = enc.stream_header();
324        assert!(header.starts_with(b"fLaC"), "Header must start with fLaC");
325    }
326
327    #[test]
328    fn test_flac_stream_header_length() {
329        let enc = make_encoder();
330        let header = enc.stream_header();
331        // fLaC (4) + METADATA_BLOCK_HEADER (4) + STREAMINFO (34) = 42
332        assert_eq!(header.len(), 42, "Stream header must be 42 bytes");
333    }
334
335    #[test]
336    fn test_flac_encode_silence() {
337        let mut enc = make_encoder();
338        let silence = vec![0i32; 4096 * 2]; // 4096 stereo frames
339        let (header, frames) = enc.encode(&silence).expect("encode");
340        assert!(header.starts_with(b"fLaC"));
341        assert!(!frames.is_empty(), "Should produce at least one frame");
342    }
343
344    #[test]
345    fn test_flac_encode_ramp() {
346        let mut enc = make_encoder();
347        let ramp: Vec<i32> = (0..4096 * 2).map(|i| (i % 1000 - 500) as i32).collect();
348        let (_, frames) = enc.encode(&ramp).expect("encode ramp");
349        assert!(!frames.is_empty());
350        for frame in &frames {
351            // Each frame must have data and a non-zero CRC at the end
352            assert!(frame.data.len() > 10, "Frame data must be non-trivial");
353        }
354    }
355
356    #[test]
357    fn test_flac_encode_wrong_channel_count_errors() {
358        let mut enc = make_encoder();
359        // 3 samples for 2 channels → error
360        let result = enc.encode(&[0i32; 3]);
361        assert!(result.is_err());
362    }
363
364    #[test]
365    fn test_flac_frame_sample_number_increases() {
366        let mut enc = make_encoder();
367        let samples: Vec<i32> = vec![0i32; FlacConfig::BLOCK_SIZE * 4]; // 2 frames worth * 2 ch
368        let (_, frames) = enc.encode(&samples).expect("encode");
369        if frames.len() >= 2 {
370            assert!(
371                frames[1].sample_number > frames[0].sample_number,
372                "Sample number must increase between frames"
373            );
374        }
375    }
376
377    #[test]
378    fn test_flac_encode_mono() {
379        let mut enc = FlacEncoder::new(FlacConfig {
380            sample_rate: 48000,
381            channels: 1,
382            bits_per_sample: 16,
383        });
384        let samples = vec![0i32; 2048];
385        let (header, frames) = enc.encode(&samples).expect("mono encode");
386        assert!(header.starts_with(b"fLaC"));
387        assert!(!frames.is_empty());
388    }
389
390    #[test]
391    fn test_flac_frame_has_crc() {
392        let mut enc = make_encoder();
393        let samples = vec![100i32; 512 * 2];
394        let (_, frames) = enc.encode(&samples).expect("encode");
395        assert!(!frames.is_empty());
396        // Last 2 bytes are CRC-16 — they should not both be zero for non-trivial frames
397        let f = &frames[0].data;
398        let n = f.len();
399        assert!(n >= 2, "Frame too short");
400        // At least one CRC byte should differ from the all-zero pattern most of the time
401        let _ = (f[n - 2], f[n - 1]); // Just access them; CRC correctness verified by decoder
402    }
403
404    #[test]
405    fn test_crc16_deterministic() {
406        let data = b"hello flac";
407        let c1 = crc16(data);
408        let c2 = crc16(data);
409        assert_eq!(c1, c2);
410    }
411
412    #[test]
413    fn test_crc16_sensitivity() {
414        let data1 = b"hello";
415        let data2 = b"world";
416        assert_ne!(crc16(data1), crc16(data2));
417    }
418}