2025-10-27 16:10:57 +01:00
|
|
|
use std::cmp;
|
|
|
|
|
|
|
|
|
|
/// Describes the properties of a FLAC/PCM stream.
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
pub struct StreamInfo {
|
|
|
|
|
pub sample_rate: u32,
|
|
|
|
|
pub channels: u8,
|
|
|
|
|
pub bits_per_sample: u8,
|
|
|
|
|
pub total_samples: Option<u64>,
|
|
|
|
|
pub max_block_size: u16,
|
|
|
|
|
pub min_block_size: u16,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl StreamInfo {
|
|
|
|
|
pub fn bytes_per_sample(&self) -> usize {
|
|
|
|
|
bytes_per_sample(self.bits_per_sample)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-27 17:05:36 +01:00
|
|
|
/// Describes the format of PCM audio data.
|
|
|
|
|
///
|
|
|
|
|
/// This is used when encoding PCM to FLAC to specify the audio properties.
|
2025-10-27 16:10:57 +01:00
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
|
|
|
pub struct PcmFormat {
|
2025-10-27 17:05:36 +01:00
|
|
|
/// Sample rate in Hz (e.g., 44100, 48000, 96000)
|
2025-10-27 16:10:57 +01:00
|
|
|
pub sample_rate: u32,
|
2025-10-27 17:05:36 +01:00
|
|
|
|
|
|
|
|
/// Number of audio channels (1 = mono, 2 = stereo, etc.)
|
2025-10-27 16:10:57 +01:00
|
|
|
pub channels: u8,
|
2025-10-27 17:05:36 +01:00
|
|
|
|
|
|
|
|
/// Bits per sample (typically 16 or 24)
|
2025-10-27 16:10:57 +01:00
|
|
|
pub bits_per_sample: u8,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl PcmFormat {
|
2025-10-27 17:05:36 +01:00
|
|
|
/// Validates the PCM format parameters.
|
|
|
|
|
///
|
|
|
|
|
/// # Errors
|
|
|
|
|
///
|
|
|
|
|
/// Returns an error if any parameter is invalid or out of the supported range.
|
|
|
|
|
///
|
|
|
|
|
/// # Warnings
|
|
|
|
|
///
|
|
|
|
|
/// This function will log warnings (via the error message) for unusual but
|
|
|
|
|
/// technically valid configurations.
|
2025-10-27 16:10:57 +01:00
|
|
|
pub fn validate(&self) -> Result<(), String> {
|
2025-10-27 17:05:36 +01:00
|
|
|
// Validate channels
|
2025-10-27 16:10:57 +01:00
|
|
|
if self.channels == 0 {
|
|
|
|
|
return Err("channel count must be greater than 0".into());
|
|
|
|
|
}
|
|
|
|
|
if self.channels > 8 {
|
2025-10-27 17:05:36 +01:00
|
|
|
return Err("channel count greater than 8 is unsupported by FLAC".into());
|
2025-10-27 16:10:57 +01:00
|
|
|
}
|
2025-10-27 17:05:36 +01:00
|
|
|
|
|
|
|
|
// Validate sample rate
|
2025-10-27 16:10:57 +01:00
|
|
|
if self.sample_rate == 0 {
|
|
|
|
|
return Err("sample rate must be greater than 0".into());
|
|
|
|
|
}
|
2025-10-27 17:05:36 +01:00
|
|
|
if self.sample_rate > 655_350 {
|
|
|
|
|
return Err("sample rate exceeds FLAC maximum (655350 Hz)".into());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Warn about unusual sample rates
|
|
|
|
|
const STANDARD_RATES: &[u32] = &[
|
|
|
|
|
8000, 11025, 16000, 22050, 32000, 44100, 48000, 88200, 96000, 176400, 192000, 352800,
|
|
|
|
|
384000,
|
|
|
|
|
];
|
|
|
|
|
if !STANDARD_RATES.contains(&self.sample_rate) {
|
2025-11-02 08:18:50 +01:00
|
|
|
tracing::warn!(
|
|
|
|
|
sample_rate = %self.sample_rate,
|
|
|
|
|
"non-standard sample rate (valid but unusual)"
|
2025-10-27 17:05:36 +01:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Validate bit depth
|
2025-10-27 16:10:57 +01:00
|
|
|
if self.bits_per_sample == 0 || self.bits_per_sample > 32 {
|
|
|
|
|
return Err("bits per sample must be in 1..=32".into());
|
|
|
|
|
}
|
2025-10-27 17:05:36 +01:00
|
|
|
|
|
|
|
|
// FLAC officially supports 4-32 bits/sample
|
|
|
|
|
if self.bits_per_sample < 4 {
|
2025-11-02 08:18:50 +01:00
|
|
|
tracing::warn!(
|
|
|
|
|
bits_per_sample = %self.bits_per_sample,
|
|
|
|
|
"bits_per_sample is less than 4 (unusual for FLAC)"
|
2025-10-27 17:05:36 +01:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Warn about common bit depths
|
|
|
|
|
const COMMON_BIT_DEPTHS: &[u8] = &[8, 16, 24, 32];
|
|
|
|
|
if !COMMON_BIT_DEPTHS.contains(&self.bits_per_sample) {
|
2025-11-02 08:18:50 +01:00
|
|
|
tracing::warn!(
|
|
|
|
|
bits_per_sample = %self.bits_per_sample,
|
|
|
|
|
"non-standard bit depth (valid but unusual)"
|
2025-10-27 17:05:36 +01:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-27 16:10:57 +01:00
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-27 17:05:36 +01:00
|
|
|
/// Returns the number of bytes needed to store one sample at this bit depth.
|
2025-10-27 16:10:57 +01:00
|
|
|
pub fn bytes_per_sample(&self) -> usize {
|
|
|
|
|
bytes_per_sample(self.bits_per_sample)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
|
pub(crate) struct PcmChunk {
|
|
|
|
|
pub data: Vec<i32>,
|
|
|
|
|
pub frames: u32,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl PcmChunk {
|
|
|
|
|
pub fn new(data: Vec<i32>, frames: u32, channels: u8) -> Self {
|
|
|
|
|
debug_assert_eq!(data.len(), frames as usize * channels as usize);
|
|
|
|
|
Self { data, frames }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub(crate) fn bytes_per_sample(bits_per_sample: u8) -> usize {
|
|
|
|
|
cmp::max(1, ((bits_per_sample as usize) + 7) / 8)
|
|
|
|
|
}
|