aede_core/audit/
flac.rs

1//! Walks the FLAC frame structure, with bounded precision confirmation.
2//!
3//! Two claims a file makes about itself cannot be checked from its header
4//! alone: how many bits of resolution it really carries, and whether its two
5//! channels hold different sound. Both answers are already written in the
6//! encoded stream. The structural walk finds candidates; padding claims also
7//! need the exact integer samples after stereo decorrelation.
8//!
9//! **Resolution.** When the low bits of every sample are zero — the signature
10//! of 16-bit audio re-encoded as 24-bit — the encoder does not store them. It
11//! records a *wasted bits* count in each subframe instead. A file claiming 24
12//! bits with eight wasted bits everywhere may carry 16 bits of real music.
13//! Correlated and constant subframes can hide significant original low bits,
14//! so a lazy existing decoder confirms candidates one bounded frame at a
15//! time, from the bytes already read. An unavailable confirmation makes no
16//! padding claim. The checksum walk remains entirely structural.
17//!
18//! **Channels.** For stereo, the encoder picks the cheapest of four
19//! correlations per frame. When both channels are identical the difference
20//! channel is constant zero, which shows up as a `CONSTANT` subframe.
21//!
22//! An encoder that skipped these optimisations yields no additional evidence.
23//! Precision confirmation can withdraw a structural padding candidate; it
24//! does not certify the file's integrity or establish the recording's origin.
25//!
26//! Reference: <https://xiph.org/flac/format.html>
27
28use std::path::Path;
29
30use super::bits::BitReader;
31use crate::tags::TagError;
32use crate::tags::bytes::read_at_most;
33
34#[path = "flac_precision.rs"]
35mod precision;
36
37/// How much of a file the walk is allowed to read.
38///
39/// The structural walk and optional single-frame precision confirmation must
40/// stay bounded over a large library. Padding is a property of the whole
41/// file; the quick result describes only the frames examined.
42#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43pub struct Limits {
44    /// Bytes of encoded audio to read at most.
45    pub max_bytes: usize,
46    /// Frames to walk at most.
47    pub max_frames: usize,
48}
49
50impl Limits {
51    /// Enough to answer confidently while staying cheap over a whole library:
52    /// 512 frames is roughly 47 seconds of CD-quality audio.
53    pub fn quick() -> Limits {
54        Limits {
55            max_bytes: 8 * 1024 * 1024,
56            max_frames: 512,
57        }
58    }
59
60    /// Reads the file to its end. For inspecting one file on demand.
61    pub fn thorough() -> Limits {
62        Limits {
63            max_bytes: usize::MAX,
64            max_frames: usize::MAX,
65        }
66    }
67}
68
69impl Default for Limits {
70    fn default() -> Limits {
71        Limits::quick()
72    }
73}
74
75/// What the frame structure says about the audio, beyond what the header
76/// claims.
77#[derive(Debug, Clone, PartialEq)]
78pub struct FlacAudit {
79    /// Bit depth written in STREAMINFO.
80    pub declared_bit_depth: u16,
81    /// Conservative bit-depth estimate for the frames examined, once
82    /// confirmed wasted low bits are removed. Unavailable confirmation retains
83    /// the declared depth.
84    ///
85    /// Lower than `declared_bit_depth` means the file was padded: 24-bit
86    /// packaging around 16-bit music.
87    pub effective_bit_depth: u16,
88    /// Channel count written in STREAMINFO.
89    pub channels: u16,
90    /// What the two channels of a stereo file actually hold.
91    pub stereo: StereoContent,
92    /// Whether every subframe examined was constant zero, which means silence.
93    pub digital_silence: bool,
94    /// Number of frames the walk covered.
95    pub frames_examined: usize,
96    /// `true` when a limit stopped the walk before the end of the file.
97    ///
98    /// The verdict then describes the portion examined, not the whole file.
99    pub truncated: bool,
100}
101
102impl FlacAudit {
103    /// `true` when the file claims more resolution than it carries.
104    pub fn is_padded(&self) -> bool {
105        self.effective_bit_depth < self.declared_bit_depth
106    }
107}
108
109/// What a stereo file's two channels hold relative to one another.
110#[derive(Debug, Clone, Copy, PartialEq, Eq)]
111pub enum StereoContent {
112    /// Not a two-channel file, or the encoder gave nothing away.
113    Unknown,
114    /// The channels differ: real stereo.
115    Independent,
116    /// Both channels are identical: mono in a stereo container.
117    Duplicated,
118}
119
120/// Walks the frames of a FLAC file and reports what they reveal.
121pub fn audit(path: &Path, limits: Limits) -> Result<FlacAudit, TagError> {
122    let mut file = crate::tags::bytes::open_regular(path)?;
123    let (stream_info, audio_start) = read_stream_info(&mut file)?;
124
125    let budget = match limits.max_bytes {
126        usize::MAX => file.metadata()?.len().saturating_sub(audio_start) as usize,
127        bytes => bytes,
128    };
129    let data = read_at_most(&mut file, audio_start, budget)?;
130    let reached_end_of_file = audio_start + data.len() as u64 >= file.metadata()?.len();
131    let mut reader = BitReader::new(&data);
132
133    let mut min_wasted = u32::MAX;
134    let mut confirmed_wasted = u32::MAX;
135    let mut precision = precision::PrecisionConfirmation::new(stream_info.raw);
136    let mut frames = 0usize;
137    let mut all_constant = true;
138    let mut duplicated_frames = 0usize;
139    let mut stereo_frames = 0usize;
140
141    while let Some(frame) = read_frame(&mut reader, &stream_info) {
142        frames += 1;
143        min_wasted = min_wasted.min(frame.min_wasted);
144        // Nonzero constant frames have no structural minimum of their own,
145        // but their significant bits can refute a later padding candidate.
146        // Digital zero is neutral. Once a sample refutes padding, no more
147        // decoding is needed for this file.
148        if min_wasted > 0 && confirmed_wasted > 0 && !frame.all_constant {
149            let confirmed = data
150                .get(frame.span.start..frame.span.end)
151                .and_then(|bytes| precision.confirm(bytes, frame.block_size))
152                .unwrap_or(0);
153            confirmed_wasted = confirmed_wasted.min(confirmed);
154        }
155        all_constant &= frame.all_constant;
156        if frame.is_stereo {
157            stereo_frames += 1;
158            if frame.channels_identical {
159                duplicated_frames += 1;
160            }
161        }
162        // Frames are byte aligned; the next sync code starts on a boundary.
163        reader.align_to_byte();
164        if reader.is_exhausted() || frames >= limits.max_frames {
165            break;
166        }
167    }
168
169    let wasted = if frames == 0 || min_wasted == u32::MAX {
170        0
171    } else {
172        min_wasted.min(confirmed_wasted)
173    };
174    let effective = (stream_info.bits_per_sample as u32).saturating_sub(wasted);
175
176    let stereo = if stream_info.channels != 2 || stereo_frames == 0 {
177        StereoContent::Unknown
178    } else if duplicated_frames == stereo_frames {
179        StereoContent::Duplicated
180    } else {
181        StereoContent::Independent
182    };
183
184    Ok(FlacAudit {
185        declared_bit_depth: stream_info.bits_per_sample,
186        effective_bit_depth: effective.max(1) as u16,
187        channels: stream_info.channels,
188        stereo,
189        digital_silence: frames > 0 && all_constant,
190        frames_examined: frames,
191        truncated: !reached_end_of_file || frames >= limits.max_frames,
192    })
193}
194
195/// The few STREAMINFO fields the frame walk needs.
196pub(crate) struct StreamInfo {
197    pub(crate) bits_per_sample: u16,
198    pub(crate) channels: u16,
199    // Older structural readers accepted the first 18 bytes. Missing decoder
200    // metadata must not change their checksum behavior or support a claim.
201    raw: Option<[u8; 34]>,
202}
203
204/// Reads STREAMINFO and returns it with the offset of the first frame.
205pub(crate) fn read_stream_info(file: &mut std::fs::File) -> Result<(StreamInfo, u64), TagError> {
206    let start = crate::tags::id3::skip_id3v2(file)?;
207    let header = read_at_most(file, start, 4)?;
208    if header.len() < 4 || &header[..4] != b"fLaC" {
209        return Err(TagError::UnrecognizedFormat);
210    }
211
212    let mut offset = start + 4;
213    let mut info = None;
214    loop {
215        let head = read_at_most(file, offset, 4)?;
216        if head.len() < 4 {
217            return Err(TagError::Malformed("metadata block header truncated"));
218        }
219        let last = head[0] & 0x80 != 0;
220        let kind = head[0] & 0x7F;
221        let length = ((head[1] as u64) << 16) | ((head[2] as u64) << 8) | head[3] as u64;
222        offset += 4;
223
224        if kind == 0 {
225            let body = read_at_most(file, offset, length.min(34) as usize)?;
226            if body.len() < 18 {
227                return Err(TagError::Malformed("STREAMINFO truncated"));
228            }
229            let packed = u64::from_be_bytes([
230                body[10], body[11], body[12], body[13], body[14], body[15], body[16], body[17],
231            ]);
232            info = Some(StreamInfo {
233                channels: (((packed >> 41) & 0x07) as u16) + 1,
234                bits_per_sample: (((packed >> 36) & 0x1F) as u16) + 1,
235                raw: body.as_slice().try_into().ok(),
236            });
237        }
238        offset += length;
239        if last {
240            break;
241        }
242    }
243
244    match info {
245        Some(info) => Ok((info, offset)),
246        None => Err(TagError::Malformed("FLAC stream without STREAMINFO")),
247    }
248}
249
250/// What one frame contributes to the audit.
251pub(crate) struct FrameAudit {
252    min_wasted: u32,
253    block_size: u32,
254    all_constant: bool,
255    is_stereo: bool,
256    channels_identical: bool,
257    /// Byte range of the frame within the slice being walked, and where its
258    /// header stops. The checksum walk and precision confirmation share these
259    /// already-read bytes.
260    pub(crate) span: FrameSpan,
261}
262
263/// Where a frame sits in the stream, and the checksums it carries.
264pub(crate) struct FrameSpan {
265    /// Offset of the sync code.
266    pub(crate) start: usize,
267    /// Offset just past the frame's own CRC-16.
268    pub(crate) end: usize,
269    /// Offset of the header's CRC-8, which is also where the header stops.
270    pub(crate) header_end: usize,
271    /// The CRC-8 the header announces.
272    pub(crate) stored_crc8: u8,
273    /// The CRC-16 the frame announces.
274    pub(crate) stored_crc16: u16,
275}
276
277/// Reads one frame header and walks its subframes.
278///
279/// Returns `None` at the end of the stream or on anything unexpected, which
280/// ends the walk without failing the audit.
281pub(crate) fn read_frame(reader: &mut BitReader<'_>, info: &StreamInfo) -> Option<FrameAudit> {
282    let start = reader.bit_pos() / 8;
283    if reader.bits(14)? != 0b11_1111_1111_1110 {
284        return None;
285    }
286    reader.bits(1)?; // reserved
287    reader.bits(1)?; // blocking strategy
288
289    let block_size_bits = reader.bits(4)? as u32;
290    let sample_rate_bits = reader.bits(4)? as u32;
291    let channel_assignment = reader.bits(4)? as u32;
292    let sample_size_bits = reader.bits(3)? as u32;
293    reader.bits(1)?; // reserved
294
295    // Coded frame or sample number, in a UTF-8-like variable length encoding.
296    let first = reader.bits(8)? as u8;
297    let extra = match first {
298        0x00..=0x7F => 0,
299        0xC0..=0xDF => 1,
300        0xE0..=0xEF => 2,
301        0xF0..=0xF7 => 3,
302        0xF8..=0xFB => 4,
303        0xFC..=0xFD => 5,
304        0xFE => 6,
305        _ => return None,
306    };
307    reader.skip(extra * 8)?;
308
309    let block_size = match block_size_bits {
310        0 => return None,
311        1 => 192,
312        2..=5 => 576 << (block_size_bits - 2),
313        6 => reader.bits(8)? as u32 + 1,
314        7 => reader.bits(16)? as u32 + 1,
315        _ => 256 << (block_size_bits - 8),
316    };
317    match sample_rate_bits {
318        12 => {
319            reader.bits(8)?;
320        }
321        13 | 14 => {
322            reader.bits(16)?;
323        }
324        15 => return None,
325        _ => {}
326    }
327    let header_end = reader.bit_pos() / 8;
328    let stored_crc8 = reader.bits(8)? as u8;
329
330    let bits_per_sample = match sample_size_bits {
331        0 => info.bits_per_sample as u32,
332        1 => 8,
333        2 => 12,
334        4 => 16,
335        5 => 20,
336        6 => 24,
337        7 => 32,
338        _ => return None,
339    };
340
341    // Channel assignments 8, 9 and 10 store a difference channel, which needs
342    // one extra bit and tells us whether the channels are identical.
343    let (channel_count, side_index) = match channel_assignment {
344        0..=7 => (channel_assignment + 1, None),
345        8 => (2, Some(1)),
346        9 => (2, Some(0)),
347        10 => (2, Some(1)),
348        _ => return None,
349    };
350    // Mid/side stores (left + right) >> 1. Restoring its candidate bit retains
351    // detection of padded streams; it is not proof about the original low
352    // bits. The public precision audit confirms any positive candidate.
353    let mid_index = if channel_assignment == 10 {
354        Some(0)
355    } else {
356        None
357    };
358
359    let mut min_wasted = u32::MAX;
360    let mut all_constant = true;
361    let mut side_is_zero = false;
362
363    for channel in 0..channel_count {
364        let extra_bit = side_index == Some(channel);
365        let bps = bits_per_sample + if extra_bit { 1 } else { 0 };
366        let sub = read_subframe(reader, block_size, bps)?;
367        // Constant headers alone cannot establish precision: the encoder has
368        // no reason to record wasted bits on a value stored once. Ignore them
369        // for this candidate, then confirm their actual samples in the audit.
370        if sub.constant_value.is_none() {
371            let corrected = sub.wasted + if mid_index == Some(channel) { 1 } else { 0 };
372            min_wasted = min_wasted.min(corrected);
373        }
374        all_constant &= sub.constant_value == Some(0);
375        if extra_bit && sub.constant_value == Some(0) {
376            side_is_zero = true;
377        }
378    }
379
380    reader.align_to_byte();
381    let stored_crc16 = reader.bits(16)? as u16;
382
383    Some(FrameAudit {
384        min_wasted,
385        block_size,
386        all_constant,
387        is_stereo: side_index.is_some(),
388        channels_identical: side_is_zero,
389        span: FrameSpan {
390            start,
391            end: reader.bit_pos() / 8,
392            header_end,
393            stored_crc8,
394            stored_crc16,
395        },
396    })
397}
398
399/// What one subframe contributes.
400struct SubframeAudit {
401    wasted: u32,
402    /// The value, when the subframe is a constant one.
403    constant_value: Option<i64>,
404}
405
406/// Reads a subframe header and steps over its data.
407///
408/// The residual is parsed but not reconstructed: Rice codes are
409/// variable-length, so they must be read to be skipped, but nothing needs to
410/// be computed from them.
411fn read_subframe(
412    reader: &mut BitReader<'_>,
413    block_size: u32,
414    bits_per_sample: u32,
415) -> Option<SubframeAudit> {
416    if reader.bits(1)? != 0 {
417        return None; // padding bit must be zero
418    }
419    let kind = reader.bits(6)? as u32;
420    let has_wasted = reader.bits(1)? == 1;
421    let wasted = if has_wasted { reader.unary()? + 1 } else { 0 };
422
423    let bps = bits_per_sample.checked_sub(wasted)?;
424    if bps == 0 || bps > 33 {
425        return None;
426    }
427
428    let mut constant_value = None;
429    match kind {
430        0 => {
431            constant_value = Some(reader.signed_bits(bps)?);
432        }
433        1 => {
434            reader.skip(block_size as usize * bps as usize)?;
435        }
436        8..=12 => {
437            let order = kind - 8;
438            reader.skip(order as usize * bps as usize)?;
439            skip_residual(reader, block_size, order)?;
440        }
441        32..=63 => {
442            let order = kind - 31;
443            reader.skip(order as usize * bps as usize)?;
444            let precision = reader.bits(4)? as u32 + 1;
445            if precision > 15 {
446                return None; // 0b1111 is invalid
447            }
448            reader.bits(5)?; // quantisation shift
449            reader.skip(order as usize * precision as usize)?;
450            skip_residual(reader, block_size, order)?;
451        }
452        _ => return None, // reserved
453    }
454
455    Some(SubframeAudit {
456        wasted,
457        constant_value,
458    })
459}
460
461/// Steps over a residual, partition by partition.
462fn skip_residual(reader: &mut BitReader<'_>, block_size: u32, order: u32) -> Option<()> {
463    let method = reader.bits(2)?;
464    let param_bits = match method {
465        0 => 4,
466        1 => 5,
467        _ => return None, // reserved
468    };
469    let escape = (1u64 << param_bits) - 1;
470
471    let partition_order = reader.bits(4)? as u32;
472    let partitions = 1u32 << partition_order;
473    if !block_size.is_multiple_of(partitions) {
474        return None;
475    }
476
477    for partition in 0..partitions {
478        let mut samples = block_size >> partition_order;
479        if partition == 0 {
480            samples = samples.checked_sub(order)?;
481        }
482        let parameter = reader.bits(param_bits)?;
483        if parameter == escape {
484            let raw_bits = reader.bits(5)? as usize;
485            reader.skip(samples as usize * raw_bits)?;
486        } else {
487            for _ in 0..samples {
488                reader.unary()?;
489                reader.bits(parameter as u32)?;
490            }
491        }
492    }
493    Some(())
494}