Skip to main content

libflac_rs/
decoder.rs

1//! FLAC frame decoder (`stream_decoder.c`): parse audio frames back into PCM.
2//!
3//! The decoder's contract is *losslessness* — it must reproduce the exact samples
4//! the encoder consumed — not byte-parity with libFLAC's decoder internals, so the
5//! code is idiomatic. Correctness is established by round-tripping every encoder
6//! corpus (`decode_frames(encode_frames(pcm)) == pcm`) and by decoding real
7//! libFLAC output. All prediction/restoration runs in `i64` so the 33-bit side
8//! channel and any wide predictor sum are exact; the chosen subframes always have
9//! `i32`-fitting residuals, so ≤32-bit channels round-trip exactly.
10
11use crate::bitreader::BitReader;
12use crate::crc::{crc8, crc16};
13use crate::frame::ChannelAssignment;
14use crate::metadata::{SEEKPOINT_PLACEHOLDER, SeekPoint};
15
16/// Decoded audio frames.
17pub struct DecodedFrames {
18    /// Interleaved PCM (channel-major within each sample).
19    pub interleaved: Vec<i32>,
20    pub channels: u32,
21    pub bits_per_sample: u32,
22    pub sample_rate: u32,
23}
24
25/// Decode every audio frame in `data` (the raw frame stream produced by
26/// [`Encoder::encode_frames`](crate::Encoder::encode_frames), i.e. no metadata) back
27/// to interleaved PCM. Returns `None` on any malformed/truncated input or CRC
28/// mismatch.
29pub fn decode_frames(data: &[u8]) -> Option<DecodedFrames> {
30    let mut br = BitReader::new(data);
31    let mut interleaved = Vec::new();
32    let mut channels = 0u32;
33    let mut bits_per_sample = 0u32;
34    let mut sample_rate = 0u32;
35
36    while br.byte_pos() < data.len() {
37        let frame = decode_frame(&mut br, data)?;
38        channels = frame.channels;
39        bits_per_sample = frame.bits_per_sample;
40        sample_rate = frame.sample_rate;
41        for i in 0..frame.blocksize {
42            for ch in &frame.samples {
43                interleaved.push(ch[i]);
44            }
45        }
46    }
47
48    Some(DecodedFrames {
49        interleaved,
50        channels,
51        bits_per_sample,
52        sample_rate,
53    })
54}
55
56/// A fully decoded FLAC stream (`fLaC` marker + metadata + frames).
57pub struct DecodedStream {
58    pub interleaved: Vec<i32>,
59    pub channels: u32,
60    pub bits_per_sample: u32,
61    pub sample_rate: u32,
62    pub total_samples: u64,
63    /// The STREAMINFO MD5 (all-zero if the encoder had MD5 off).
64    pub md5: [u8; 16],
65    /// Whether the decoded audio's MD5 matches STREAMINFO (trivially `true` when
66    /// STREAMINFO carries no MD5).
67    pub md5_ok: bool,
68    /// The seek points from the SEEKTABLE block, if any (empty otherwise). Trailing
69    /// placeholder points (sample number `0xFFFF…`) are retained as stored.
70    pub seek_points: Vec<SeekPoint>,
71}
72
73/// Decode a complete FLAC stream: the `fLaC` marker, the metadata blocks (only
74/// STREAMINFO is interpreted; the rest are skipped), then the audio frames.
75/// Verifies the audio MD5 against STREAMINFO when present. `None` on malformed
76/// input or any CRC mismatch.
77pub fn decode(data: &[u8]) -> Option<DecodedStream> {
78    let mut br = BitReader::new(data);
79    let (info, seek_points) = parse_header(&mut br)?;
80
81    let mut interleaved = Vec::new();
82    while br.byte_pos() < data.len() {
83        let frame = decode_frame(&mut br, data)?;
84        for i in 0..frame.blocksize {
85            for ch in &frame.samples {
86                interleaved.push(ch[i]);
87            }
88        }
89    }
90    // Trim any encoder padding beyond the declared length.
91    if info.total_samples > 0 {
92        interleaved.truncate(info.total_samples as usize * info.channels as usize);
93    }
94
95    let md5_ok = info.md5 == [0u8; 16] || {
96        let computed =
97            crate::md5::audio_md5(&interleaved, info.bits_per_sample.div_ceil(8) as usize);
98        computed == info.md5
99    };
100
101    Some(DecodedStream {
102        interleaved,
103        channels: info.channels,
104        bits_per_sample: info.bits_per_sample,
105        sample_rate: info.sample_rate,
106        total_samples: info.total_samples,
107        md5: info.md5,
108        md5_ok,
109        seek_points,
110    })
111}
112
113/// Parse the `fLaC` marker and the metadata blocks (interpreting STREAMINFO and
114/// SEEKTABLE, skipping the rest), leaving `br` positioned at the first audio frame.
115fn parse_header(br: &mut BitReader) -> Option<(StreamInfo, Vec<SeekPoint>)> {
116    if br.read_raw_u32(32)? != 0x664c_6143 {
117        return None; // "fLaC"
118    }
119    let mut info: Option<StreamInfo> = None;
120    let mut seek_points: Vec<SeekPoint> = Vec::new();
121    loop {
122        let is_last = br.read_raw_u32(1)? == 1;
123        let block_type = br.read_raw_u32(7)?;
124        let length = br.read_raw_u32(24)? as usize;
125        if block_type == 0 {
126            // STREAMINFO (34 bytes).
127            let _min_bs = br.read_raw_u32(16)?;
128            let _max_bs = br.read_raw_u32(16)?;
129            let _min_fs = br.read_raw_u32(24)?;
130            let _max_fs = br.read_raw_u32(24)?;
131            let sample_rate = br.read_raw_u32(20)?;
132            let channels = br.read_raw_u32(3)? + 1;
133            let bits_per_sample = br.read_raw_u32(5)? + 1;
134            let total_samples = br.read_raw_u64(36)?;
135            let mut md5 = [0u8; 16];
136            for b in &mut md5 {
137                *b = br.read_raw_u32(8)? as u8;
138            }
139            info = Some(StreamInfo {
140                sample_rate,
141                channels,
142                bits_per_sample,
143                total_samples,
144                md5,
145            });
146        } else if block_type == 3 {
147            // SEEKTABLE: length / 18 points of (sample u64, offset u64, samples u16).
148            let n = length / 18;
149            seek_points = Vec::with_capacity(n);
150            for _ in 0..n {
151                let sample_number = br.read_raw_u64(64)?;
152                let stream_offset = br.read_raw_u64(64)?;
153                let frame_samples = br.read_raw_u32(16)?;
154                seek_points.push(SeekPoint {
155                    sample_number,
156                    stream_offset,
157                    frame_samples,
158                });
159            }
160            // Tolerate a length not divisible by 18 (non-conforming writers).
161            br.skip_bytes(length - n * 18)?;
162        } else {
163            br.skip_bytes(length)?;
164        }
165        if is_last {
166            break;
167        }
168    }
169    Some((info?, seek_points))
170}
171
172/// The result of a [`decode_seek`]: PCM from `first_sample` to the end of the
173/// stream.
174pub struct SeekResult {
175    /// Interleaved PCM starting at `first_sample`.
176    pub interleaved: Vec<i32>,
177    /// The first sample number the PCM begins at (the requested target).
178    pub first_sample: u64,
179    pub channels: u32,
180    pub bits_per_sample: u32,
181    pub sample_rate: u32,
182}
183
184/// The seek point to start decoding from for `target`: the non-placeholder point
185/// with the largest `sample_number` `<= target`, as `(sample_number, stream_offset)`
186/// relative to the first audio frame. Falls back to `(0, 0)` (the first frame) when
187/// no usable point exists. A linear scan — seektables are small.
188fn seek_start(seek_points: &[SeekPoint], target: u64) -> (u64, u64) {
189    let mut best = (0u64, 0u64);
190    for p in seek_points {
191        if p.sample_number != SEEKPOINT_PLACEHOLDER
192            && p.sample_number <= target
193            && p.sample_number >= best.0
194        {
195            best = (p.sample_number, p.stream_offset);
196        }
197    }
198    best
199}
200
201/// Decode a stream starting at `target_sample`, using the SEEKTABLE (if any) to jump
202/// near it before decoding forward. Returns the interleaved PCM from `target_sample`
203/// to the end of the stream. `None` on malformed input, a CRC mismatch, or
204/// `target_sample >= total_samples` (when the total is known). With no seektable it
205/// still works by decoding from the first frame.
206pub fn decode_seek(data: &[u8], target_sample: u64) -> Option<SeekResult> {
207    let mut br = BitReader::new(data);
208    let (info, seek_points) = parse_header(&mut br)?;
209    if info.total_samples != 0 && target_sample >= info.total_samples {
210        return None;
211    }
212    let audio_offset = br.byte_pos();
213
214    let (start_sample, start_offset) = seek_start(&seek_points, target_sample);
215    let start_byte = audio_offset.checked_add(start_offset as usize)?;
216    let frames = data.get(start_byte..)?;
217
218    let mut fbr = BitReader::new(frames);
219    let mut current = start_sample;
220    let mut interleaved = Vec::new();
221    while fbr.byte_pos() < frames.len() {
222        let frame = decode_frame(&mut fbr, frames)?;
223        let frame_end = current + frame.blocksize as u64;
224        // Emit only the part of this frame at or after the target sample.
225        if frame_end > target_sample {
226            let skip = target_sample.saturating_sub(current) as usize;
227            for i in skip..frame.blocksize {
228                for ch in &frame.samples {
229                    interleaved.push(ch[i]);
230                }
231            }
232        }
233        current = frame_end;
234    }
235    if info.total_samples != 0 {
236        let want = (info.total_samples - target_sample) as usize * info.channels as usize;
237        interleaved.truncate(want);
238    }
239
240    Some(SeekResult {
241        interleaved,
242        first_sample: target_sample,
243        channels: info.channels,
244        bits_per_sample: info.bits_per_sample,
245        sample_rate: info.sample_rate,
246    })
247}
248
249/// Decode a complete **Ogg FLAC** stream: `OggS` pages → FLAC packets → PCM. Parses
250/// the BOS mapping packet (`0x7F"FLAC"` + version + header count + `"fLaC"` +
251/// STREAMINFO), concatenates the audio-frame packets, and decodes them, verifying the
252/// audio MD5 against STREAMINFO. `None` on malformed Ogg (a bad page CRC or missing
253/// mapping) or any frame CRC mismatch. (Ogg FLAC carries no seektable, so
254/// `seek_points` is empty.)
255pub fn decode_ogg(data: &[u8]) -> Option<DecodedStream> {
256    let packets = crate::ogg::read_packets(data)?;
257    let bos = packets.first()?;
258    // BOS layout: 0x7F | "FLAC" | maj | min | num_headers(2) | "fLaC" | STREAMINFO(38)
259    if bos.len() < 13 + 38 || bos[0] != 0x7F || &bos[1..5] != b"FLAC" || &bos[9..13] != b"fLaC" {
260        return None;
261    }
262    let mut sbr = BitReader::new(&bos[13..]);
263    let _is_last = sbr.read_raw_u32(1)?;
264    if sbr.read_raw_u32(7)? != 0 {
265        return None; // first metadata block must be STREAMINFO
266    }
267    let _len = sbr.read_raw_u32(24)?;
268    let _min_bs = sbr.read_raw_u32(16)?;
269    let _max_bs = sbr.read_raw_u32(16)?;
270    let _min_fs = sbr.read_raw_u32(24)?;
271    let _max_fs = sbr.read_raw_u32(24)?;
272    let sample_rate = sbr.read_raw_u32(20)?;
273    let channels = sbr.read_raw_u32(3)? + 1;
274    let bits_per_sample = sbr.read_raw_u32(5)? + 1;
275    let total_samples = sbr.read_raw_u64(36)?;
276    let mut md5 = [0u8; 16];
277    for b in &mut md5 {
278        *b = sbr.read_raw_u32(8)? as u8;
279    }
280
281    // Audio frames are the packets from the first one with a 0x3FFE sync (high byte
282    // 0xFF); the metadata packets (VORBIS_COMMENT, etc.) precede them.
283    let mut frame_bytes = Vec::new();
284    if let Some(idx) = packets[1..].iter().position(|p| p.first() == Some(&0xFF)) {
285        for p in &packets[1 + idx..] {
286            frame_bytes.extend_from_slice(p);
287        }
288    }
289    let decoded = decode_frames(&frame_bytes)?;
290    let mut interleaved = decoded.interleaved;
291    if total_samples > 0 {
292        interleaved.truncate(total_samples as usize * channels as usize);
293    }
294    let md5_ok = md5 == [0u8; 16] || {
295        crate::md5::audio_md5(&interleaved, bits_per_sample.div_ceil(8) as usize) == md5
296    };
297
298    Some(DecodedStream {
299        interleaved,
300        channels,
301        bits_per_sample,
302        sample_rate,
303        total_samples,
304        md5,
305        md5_ok,
306        seek_points: Vec::new(),
307    })
308}
309
310struct StreamInfo {
311    sample_rate: u32,
312    channels: u32,
313    bits_per_sample: u32,
314    total_samples: u64,
315    md5: [u8; 16],
316}
317
318struct Frame {
319    blocksize: usize,
320    channels: u32,
321    bits_per_sample: u32,
322    sample_rate: u32,
323    /// Per-channel decoded PCM (already un-decorrelated to independent channels).
324    samples: Vec<Vec<i32>>,
325}
326
327fn decode_frame(br: &mut BitReader, data: &[u8]) -> Option<Frame> {
328    let frame_start = br.byte_pos();
329
330    // --- header ---
331    if br.read_raw_u32(14)? != 0x3ffe {
332        return None; // sync
333    }
334    let _reserved = br.read_raw_u32(1)?;
335    let blocking_strategy = br.read_raw_u32(1)?; // 0 = fixed, 1 = variable block size
336    let bs_code = br.read_raw_u32(4)?;
337    let sr_code = br.read_raw_u32(4)?;
338    let ca_code = br.read_raw_u32(4)?;
339    let bps_code = br.read_raw_u32(3)?;
340    let _reserved2 = br.read_raw_u32(1)?;
341    // Fixed block size carries the frame number (UTF-8 u32); variable carries the
342    // first sample number (UTF-8 u64). We track samples by summing block sizes, so
343    // the value is discarded — but the two forms consume different byte counts, so
344    // the right one must be read for the rest of the header (and CRC-8) to align.
345    if blocking_strategy == 0 {
346        let _frame_number = br.read_utf8_u32()?;
347    } else {
348        let _sample_number = br.read_utf8_u64()?;
349    }
350
351    let blocksize = match bs_code {
352        1 => 192,
353        2 => 576,
354        3 => 1152,
355        4 => 2304,
356        5 => 4608,
357        6 => br.read_raw_u32(8)? as usize + 1,
358        7 => br.read_raw_u32(16)? as usize + 1,
359        8 => 256,
360        9 => 512,
361        10 => 1024,
362        11 => 2048,
363        12 => 4096,
364        13 => 8192,
365        14 => 16384,
366        15 => 32768,
367        _ => return None, // 0 reserved
368    };
369    let sample_rate = match sr_code {
370        1 => 88200,
371        2 => 176400,
372        3 => 192000,
373        4 => 8000,
374        5 => 16000,
375        6 => 22050,
376        7 => 24000,
377        8 => 32000,
378        9 => 44100,
379        10 => 48000,
380        11 => 96000,
381        12 => br.read_raw_u32(8)? * 1000,
382        13 => br.read_raw_u32(16)?,
383        14 => br.read_raw_u32(16)? * 10,
384        _ => 0, // 0 = from STREAMINFO, 15 invalid
385    };
386    let bits_per_sample = match bps_code {
387        1 => 8,
388        2 => 12,
389        4 => 16,
390        5 => 20,
391        6 => 24,
392        7 => 32,
393        _ => return None, // 0 from STREAMINFO, 3 reserved
394    };
395    let (assignment, channels) = match ca_code {
396        0..=7 => (ChannelAssignment::Independent, ca_code + 1),
397        8 => (ChannelAssignment::LeftSide, 2),
398        9 => (ChannelAssignment::RightSide, 2),
399        10 => (ChannelAssignment::MidSide, 2),
400        _ => return None,
401    };
402
403    // CRC-8 over the header bytes just read (the reader is byte-aligned here).
404    let computed_crc8 = crc8(br.bytes_since(frame_start));
405    if br.read_raw_u32(8)? as u8 != computed_crc8 {
406        return None;
407    }
408
409    // --- subframes (i64 channel data) ---
410    let mut chans: Vec<Vec<i64>> = Vec::with_capacity(channels as usize);
411    for ch in 0..channels {
412        let side = matches!(
413            (assignment, ch),
414            (ChannelAssignment::LeftSide, 1)
415                | (ChannelAssignment::RightSide, 0)
416                | (ChannelAssignment::MidSide, 1)
417        );
418        let channel_bps = bits_per_sample + u32::from(side);
419        chans.push(decode_subframe(br, blocksize, channel_bps)?);
420    }
421
422    // --- footer: CRC-16 over the whole frame so far ---
423    br.align_to_byte();
424    let computed_crc16 = crc16(br.bytes_since(frame_start));
425    if br.read_raw_u32(16)? as u16 != computed_crc16 {
426        return None;
427    }
428    let _ = data; // backing slice is reached through the reader
429
430    Some(Frame {
431        blocksize,
432        channels,
433        bits_per_sample,
434        sample_rate,
435        samples: undecorrelate(assignment, &chans, blocksize),
436    })
437}
438
439/// Decode one subframe to its (wasted-bits-restored) `i64` channel signal.
440fn decode_subframe(br: &mut BitReader, blocksize: usize, channel_bps: u32) -> Option<Vec<i64>> {
441    let header = br.read_raw_u32(8)?;
442    let wasted = if header & 1 == 1 {
443        br.read_unary()? + 1
444    } else {
445        0
446    };
447    let type6 = (header >> 1) & 0x3f;
448    let subframe_bps = channel_bps - wasted;
449
450    let mut data = vec![0i64; blocksize];
451    if type6 == 0 {
452        // CONSTANT
453        let v = br.read_signed(subframe_bps)?;
454        data.fill(v);
455    } else if type6 == 1 {
456        // VERBATIM
457        for d in data.iter_mut() {
458            *d = br.read_signed(subframe_bps)?;
459        }
460    } else if type6 & 0x20 != 0 {
461        // LPC, order = (type6 & 0x1f) + 1
462        let order = ((type6 & 0x1f) + 1) as usize;
463        for d in data.iter_mut().take(order) {
464            *d = br.read_signed(subframe_bps)?;
465        }
466        let precision = br.read_raw_u32(4)? + 1;
467        let shift = sign_extend5(br.read_raw_u32(5)?);
468        let mut qlp = vec![0i32; order];
469        for c in qlp.iter_mut() {
470            *c = br.read_signed(precision)? as i32;
471        }
472        let residual = decode_residual(br, blocksize, order)?;
473        for i in order..blocksize {
474            let mut sum = 0i64;
475            for (j, &c) in qlp.iter().enumerate() {
476                sum += c as i64 * data[i - 1 - j];
477            }
478            data[i] = residual[i - order] as i64 + (sum >> shift);
479        }
480    } else if type6 & 0x08 != 0 {
481        // FIXED, order = type6 & 0x07
482        let order = (type6 & 0x07) as usize;
483        for d in data.iter_mut().take(order) {
484            *d = br.read_signed(subframe_bps)?;
485        }
486        let residual = decode_residual(br, blocksize, order)?;
487        for i in order..blocksize {
488            let r = residual[i - order] as i64;
489            data[i] = match order {
490                0 => r,
491                1 => r + data[i - 1],
492                2 => r + 2 * data[i - 1] - data[i - 2],
493                3 => r + 3 * data[i - 1] - 3 * data[i - 2] + data[i - 3],
494                _ => r + 4 * data[i - 1] - 6 * data[i - 2] + 4 * data[i - 3] - data[i - 4],
495            };
496        }
497    } else {
498        return None; // reserved subframe type
499    }
500
501    if wasted > 0 {
502        for d in data.iter_mut() {
503            *d <<= wasted;
504        }
505    }
506    Some(data)
507}
508
509/// Decode the partitioned-Rice residual (`blocksize - order` values).
510fn decode_residual(br: &mut BitReader, blocksize: usize, order: usize) -> Option<Vec<i32>> {
511    let is_rice2 = br.read_raw_u32(2)? == 1;
512    let param_len = if is_rice2 { 5 } else { 4 };
513    let escape = if is_rice2 { 31 } else { 15 };
514    let partition_order = br.read_raw_u32(4)?;
515    let partitions = 1usize << partition_order;
516
517    let mut residual = vec![0i32; blocksize - order];
518    let mut idx = 0usize;
519    for p in 0..partitions {
520        let count = if p == 0 {
521            (blocksize >> partition_order) - order
522        } else {
523            blocksize >> partition_order
524        };
525        let param = br.read_raw_u32(param_len)?;
526        if param == escape {
527            // Escaped partition: raw `raw_bits`-wide samples (the encoder never
528            // emits these, but other encoders may).
529            let raw_bits = br.read_raw_u32(5)?;
530            for r in &mut residual[idx..idx + count] {
531                *r = br.read_signed(raw_bits)? as i32;
532            }
533        } else {
534            br.read_rice_signed_block(&mut residual[idx..idx + count], param)?;
535        }
536        idx += count;
537    }
538    Some(residual)
539}
540
541/// Reverse the stereo decorrelation back to independent `i32` channels.
542fn undecorrelate(
543    assignment: ChannelAssignment,
544    chans: &[Vec<i64>],
545    blocksize: usize,
546) -> Vec<Vec<i32>> {
547    match assignment {
548        ChannelAssignment::Independent => chans
549            .iter()
550            .map(|c| c.iter().map(|&s| s as i32).collect())
551            .collect(),
552        ChannelAssignment::LeftSide => {
553            // ch0 = left, ch1 = side = left - right -> right = left - side.
554            let (l, s) = (&chans[0], &chans[1]);
555            let left: Vec<i32> = l.iter().map(|&v| v as i32).collect();
556            let right: Vec<i32> = (0..blocksize).map(|i| (l[i] - s[i]) as i32).collect();
557            vec![left, right]
558        }
559        ChannelAssignment::RightSide => {
560            // ch0 = side = left - right, ch1 = right -> left = right + side.
561            let (s, r) = (&chans[0], &chans[1]);
562            let left: Vec<i32> = (0..blocksize).map(|i| (r[i] + s[i]) as i32).collect();
563            let right: Vec<i32> = r.iter().map(|&v| v as i32).collect();
564            vec![left, right]
565        }
566        ChannelAssignment::MidSide => {
567            // ch0 = mid = (L+R)>>1 (LSB dropped), ch1 = side = L-R. The dropped LSB
568            // equals side&1, so mid2 = (mid<<1)|(side&1) = L+R; L=(mid2+side)/2.
569            let (m, s) = (&chans[0], &chans[1]);
570            let mut left = vec![0i32; blocksize];
571            let mut right = vec![0i32; blocksize];
572            for i in 0..blocksize {
573                let side = s[i];
574                let mid2 = (m[i] << 1) | (side & 1);
575                left[i] = ((mid2 + side) >> 1) as i32;
576                right[i] = ((mid2 - side) >> 1) as i32;
577            }
578            vec![left, right]
579        }
580    }
581}
582
583/// Sign-extend a 5-bit quantization shift (always non-negative in practice).
584fn sign_extend5(v: u32) -> i32 {
585    ((v << 27) as i32) >> 27
586}
587
588#[cfg(test)]
589mod tests {
590    use super::{decode, decode_frames, decode_ogg, decode_seek};
591    use crate::bitwriter::BitWriter;
592    use crate::encoder::{encode, encode_frames, encode_ogg, preset};
593    use crate::metadata::{LIBFLAC_VENDOR_STRING, MetadataBlock, spaced_seek_points};
594
595    /// Multi-partial + noise PCM scaled to a `bps`-bit range, `channels` wide.
596    fn gen_pcm(seed: u32, n: usize, bps: u32, channels: u32) -> Vec<i32> {
597        let mut st = seed.wrapping_mul(2_654_435_761).wrapping_add(1);
598        let mut rng = || {
599            st ^= st << 13;
600            st ^= st >> 17;
601            st ^= st << 5;
602            st
603        };
604        let maxv = ((1i64 << (bps - 1)) - 1) as f64;
605        let minv = -(1i64 << (bps - 1)) as f64;
606        let scale = (1u64 << (bps - 1)) as f64 / 32768.0;
607        let f1 = 0.008 + (rng() >> 22) as f64 / 2.0e6;
608        let f2 = 0.05 + (rng() >> 22) as f64 / 1.0e6;
609        let a1 = (1500.0 + (rng() >> 19) as f64 / 8.0e3) * scale;
610        let a2 = (400.0 + (rng() >> 20) as f64 / 1.0e4) * scale;
611        let noise = 250.0 * scale;
612        let mut out = Vec::with_capacity(n * channels as usize);
613        for i in 0..n {
614            for c in 0..channels {
615                let p = c as f64 * 0.35;
616                let v = a1 * (f1 * i as f64 + p).sin()
617                    + a2 * (f2 * i as f64 + p).sin()
618                    + noise * ((rng() >> 16) as u16 as i16 as f64 / 32768.0);
619                out.push(v.round().clamp(minv, maxv) as i32);
620            }
621        }
622        out
623    }
624
625    #[test]
626    fn round_trip_all_depths_levels() {
627        let bs = 2048u32;
628        for &bps in &[8u32, 12, 16, 20, 24, 32] {
629            for &ch in &[1u32, 2] {
630                for level in [0u32, 2, 5, 8] {
631                    for seed in 1..=3u32 {
632                        // Non-block-multiple lengths exercise a short final frame.
633                        let n = bs as usize + (seed as usize * 173) % 1500;
634                        let pcm = gen_pcm(seed, n, bps, ch);
635                        let frames = encode_frames(&pcm, ch, bps, 44_100, bs, &preset(level));
636                        let dec = decode_frames(&frames).expect("decode failed (CRC/format)");
637                        assert_eq!(dec.channels, ch);
638                        assert_eq!(dec.bits_per_sample, bps);
639                        assert_eq!(dec.sample_rate, 44_100);
640                        assert_eq!(
641                            dec.interleaved, pcm,
642                            "round-trip mismatch [bps {bps} ch {ch} level {level} seed {seed}]"
643                        );
644                    }
645                }
646            }
647        }
648    }
649
650    #[test]
651    fn full_stream_round_trip_with_md5() {
652        use crate::encoder::encode;
653        use crate::metadata::{LIBFLAC_VENDOR_STRING, MetadataBlock};
654        let bs = 2048u32;
655        for &bps in &[8u32, 16, 24, 32] {
656            for level in [0u32, 8] {
657                let pcm = gen_pcm(9, bs as usize + 555, bps, 2);
658                // do_md5 = true, the libFLAC vendor string, no padding.
659                let stream = encode(
660                    &pcm,
661                    2,
662                    bps,
663                    44_100,
664                    bs,
665                    &preset(level),
666                    true,
667                    &[MetadataBlock::VorbisComment(LIBFLAC_VENDOR_STRING)],
668                );
669                let dec = decode(&stream).expect("decode full stream");
670                assert_eq!(dec.interleaved, pcm, "[bps {bps} level {level}] PCM");
671                assert_eq!(dec.total_samples, (pcm.len() / 2) as u64);
672                assert!(dec.md5_ok, "[bps {bps} level {level}] MD5 mismatch");
673            }
674        }
675    }
676
677    #[test]
678    fn round_trip_edge_signals() {
679        let bs = 2048u32;
680        for &bps in &[16u32, 24, 32] {
681            let maxv = ((1i64 << (bps - 1)) - 1) as i32;
682            let minv = -(1i64 << (bps - 1)) as i32;
683            let cases: &[(&str, Vec<i32>)] = &[
684                ("silence", vec![0i32; bs as usize * 2 + 50]), // CONSTANT
685                ("dc", vec![1234 << 3; bs as usize + 10]),     // CONSTANT + wasted bits
686                ("full_pos", vec![maxv; bs as usize + 7]),
687                ("full_neg", vec![minv; bs as usize + 7]),
688                ("tiny", vec![5, -5, 5]), // < MAX_FIXED_ORDER -> VERBATIM, short frame
689            ];
690            for &(name, ref mono) in cases {
691                // Duplicate into stereo so mid-side paths run too.
692                let stereo: Vec<i32> = mono.iter().flat_map(|&v| [v, v]).collect();
693                for level in [0u32, 8] {
694                    let frames = encode_frames(&stereo, 2, bps, 44_100, bs, &preset(level));
695                    let dec = decode_frames(&frames).expect("decode failed");
696                    assert_eq!(dec.interleaved, stereo, "[{name} bps {bps} level {level}]");
697                }
698            }
699        }
700    }
701
702    /// SEEKTABLE-driven `seek()`: a full stream with a spaced seektable, sought to a
703    /// range of targets (frame starts, mid-frame, the very end), must return PCM
704    /// that exactly matches the original from that sample onward. Also covers a
705    /// stream with *no* seektable (decode-from-start fallback).
706    #[test]
707    fn seek_lands_on_exact_sample() {
708        let bs = 2048u32;
709        let ch = 2u32;
710        for &bps in &[16u32, 24] {
711            let n = bs as usize * 5 + 777; // ~6 frames, short final
712            let total = n as u64;
713            let pcm = gen_pcm(5, n, bps, ch);
714            let with_seektable = spaced_seek_points(10, total);
715            let targets = [
716                0u64,
717                1,
718                bs as u64 - 1,
719                bs as u64,
720                bs as u64 + 5,
721                bs as u64 * 3 + 100,
722                total - bs as u64,
723                total - 10,
724                total - 1,
725            ];
726            // With and without a seektable (the latter exercises decode-from-start).
727            for seektable in [with_seektable.as_slice(), &[]] {
728                let blocks = [
729                    MetadataBlock::VorbisComment(LIBFLAC_VENDOR_STRING),
730                    MetadataBlock::Seektable(seektable),
731                ];
732                let nblocks = if seektable.is_empty() {
733                    &blocks[..1]
734                } else {
735                    &blocks[..]
736                };
737                let stream = encode(&pcm, ch, bps, 44_100, bs, &preset(8), true, nblocks);
738                for &target in &targets {
739                    let r = decode_seek(&stream, target).expect("seek");
740                    assert_eq!(r.first_sample, target);
741                    assert_eq!(r.channels, ch);
742                    assert_eq!(r.bits_per_sample, bps);
743                    let expected = &pcm[target as usize * ch as usize..];
744                    assert_eq!(
745                        r.interleaved,
746                        expected,
747                        "[bps {bps} seektable {}] seek to {target}",
748                        !seektable.is_empty()
749                    );
750                }
751                // Seeking at/after the end is rejected.
752                assert!(decode_seek(&stream, total).is_none());
753            }
754        }
755    }
756
757    /// The encoder only writes fixed-block-size frames, so hand-build a
758    /// variable-block-size frame (blocking-strategy bit = 1, a UTF-8 u64 *sample*
759    /// number) and confirm the decoder reads the wider header field, keeps CRC-8
760    /// alignment, and decodes the subframe. A mono 16-bit CONSTANT frame, block
761    /// size 4, sample number `0x123456` (a 4-byte UTF-8 code).
762    #[test]
763    fn decodes_variable_block_size_frame() {
764        let mut bw = BitWriter::new();
765        bw.write_raw_u32(0x3FFE, 14); // sync
766        bw.write_raw_u32(0, 1); // reserved
767        bw.write_raw_u32(1, 1); // blocking strategy = variable
768        bw.write_raw_u32(6, 4); // block-size code 6 => 8-bit (blocksize-1) follows
769        bw.write_raw_u32(9, 4); // sample-rate code 9 = 44100
770        bw.write_raw_u32(0, 4); // channel assignment 0 = mono
771        bw.write_raw_u32(4, 3); // sample-size code 4 = 16 bps
772        bw.write_raw_u32(0, 1); // reserved
773        bw.write_utf8_u64(0x123456); // sample number (4-byte UTF-8)
774        bw.write_raw_u32(4 - 1, 8); // block size hint: blocksize - 1 = 3
775        let header_crc = bw.crc8(); // CRC-8 over the (byte-aligned) header
776        bw.write_raw_u32(header_crc as u32, 8);
777        // CONSTANT subframe (header 0x00) holding the value 1000.
778        bw.write_raw_u32(0x00, 8);
779        bw.write_raw_u32(1000, 16);
780        let frame_crc = bw.crc16(); // CRC-16 over the whole frame so far
781        bw.write_raw_u32(frame_crc as u32, 16);
782        let frame = bw.as_bytes().to_vec();
783
784        let dec = decode_frames(&frame).expect("decode variable-block-size frame");
785        assert_eq!(dec.channels, 1);
786        assert_eq!(dec.bits_per_sample, 16);
787        assert_eq!(dec.sample_rate, 44_100);
788        assert_eq!(dec.interleaved, vec![1000i32; 4]);
789    }
790
791    /// Ogg FLAC round-trip: `encode_ogg` → `decode_ogg` reproduces the PCM exactly
792    /// (with the embedded MD5 verifying), across all depths incl. 32-bit. The
793    /// byte-exactness of `encode_ogg` vs libFLAC+libogg is covered by the differential
794    /// harness (see `ORACLE.md`); this exercises the pure-Rust page demux + FLAC demap.
795    #[test]
796    fn ogg_round_trip() {
797        for &bps in &[8u32, 16, 20, 24, 32] {
798            for level in [0u32, 5, 8] {
799                // Non-block-multiple length → a short final frame.
800                let pcm = gen_pcm(7, 12_000 + bps as usize * 13, bps, 2);
801                let ogg = encode_ogg(
802                    &pcm,
803                    2,
804                    bps,
805                    44_100,
806                    4096,
807                    &preset(level),
808                    true,
809                    &[MetadataBlock::VorbisComment(LIBFLAC_VENDOR_STRING)],
810                    0x55AA_1234,
811                );
812                let dec = decode_ogg(&ogg).expect("decode_ogg");
813                assert_eq!(
814                    dec.interleaved, pcm,
815                    "[bps {bps} level {level}] ogg round-trip"
816                );
817                assert!(dec.md5_ok, "[bps {bps} level {level}] ogg MD5");
818                assert_eq!(dec.total_samples, (pcm.len() / 2) as u64);
819            }
820        }
821    }
822}