#![forbid(unsafe_code)]
#![allow(clippy::cast_possible_truncation)]
#![allow(clippy::cast_lossless)]
#![allow(clippy::cast_sign_loss)]
#![allow(clippy::cast_possible_wrap)]
use super::lpc::restore_signal;
use super::rice::RiceDecoder;
use crate::error::{CodecError, CodecResult};
fn crc16(data: &[u8]) -> u16 {
const POLY: u16 = 0x8005;
let mut crc = 0u16;
for &byte in data {
crc ^= u16::from(byte) << 8;
for _ in 0..8 {
if crc & 0x8000 != 0 {
crc = (crc << 1) ^ POLY;
} else {
crc <<= 1;
}
}
}
crc
}
struct BitReader<'a> {
data: &'a [u8],
pos: usize, bit: u8, }
impl<'a> BitReader<'a> {
fn new(data: &'a [u8]) -> Self {
Self {
data,
pos: 0,
bit: 0,
}
}
fn byte_offset(&self) -> usize {
self.pos
}
fn read_bit(&mut self) -> Option<u8> {
if self.pos >= self.data.len() {
return None;
}
let v = (self.data[self.pos] >> (7 - self.bit)) & 1;
self.bit += 1;
if self.bit == 8 {
self.bit = 0;
self.pos += 1;
}
Some(v)
}
fn read_bits(&mut self, n: usize) -> Option<u32> {
let mut v = 0u32;
for _ in 0..n {
v = (v << 1) | u32::from(self.read_bit()?);
}
Some(v)
}
fn read_byte(&mut self) -> Option<u8> {
if self.bit != 0 {
self.bit = 0;
self.pos += 1;
}
if self.pos >= self.data.len() {
return None;
}
let b = self.data[self.pos];
self.pos += 1;
Some(b)
}
fn read_be_u16(&mut self) -> Option<u16> {
let hi = u16::from(self.read_byte()?);
let lo = u16::from(self.read_byte()?);
Some((hi << 8) | lo)
}
fn read_be_i16(&mut self) -> Option<i16> {
self.read_be_u16().map(|v| v as i16)
}
fn read_utf8_coded(&mut self) -> Option<u64> {
let b0 = self.read_byte()?;
if b0 & 0x80 == 0 {
return Some(u64::from(b0));
}
let extra = if b0 & 0xF8 == 0xF0 {
3usize } else if b0 & 0xF0 == 0xE0 {
2usize } else if b0 & 0xE0 == 0xC0 {
1usize } else {
return None; };
let mask: u8 = match extra {
3 => 0x07,
2 => 0x0F,
1 => 0x1F,
_ => 0x1F,
};
let mut val = u64::from(b0 & mask);
for _ in 0..extra {
let cb = self.read_byte()?;
if cb & 0xC0 != 0x80 {
return None; }
val = (val << 6) | u64::from(cb & 0x3F);
}
Some(val)
}
fn align_to_byte(&mut self) {
if self.bit != 0 {
self.bit = 0;
self.pos += 1;
}
}
fn remaining_bytes(&self) -> usize {
if self.bit != 0 {
self.data.len().saturating_sub(self.pos + 1)
} else {
self.data.len().saturating_sub(self.pos)
}
}
fn slice_from_current(&self) -> &[u8] {
if self.pos < self.data.len() {
&self.data[self.pos..]
} else {
&[]
}
}
}
#[derive(Clone, Debug, Default)]
pub struct FlacStreamInfo {
pub min_block_size: u16,
pub max_block_size: u16,
pub min_frame_size: u32,
pub max_frame_size: u32,
pub sample_rate: u32,
pub channels: u8,
pub bits_per_sample: u8,
pub total_samples: u64,
pub md5_signature: [u8; 16],
}
#[derive(Clone, Debug, Default)]
pub struct FlacVorbisComment {
pub vendor: String,
pub comments: Vec<(String, String)>,
}
#[derive(Clone, Debug)]
pub struct DecodedBlock {
pub samples: Vec<i32>,
pub sample_number: u64,
pub block_size: usize,
pub channels: usize,
}
pub struct FlacDecoder {
pub stream_info: Option<FlacStreamInfo>,
pub comment: Option<FlacVorbisComment>,
header_parsed: bool,
}
impl FlacDecoder {
#[must_use]
pub fn new() -> Self {
Self {
stream_info: None,
comment: None,
header_parsed: false,
}
}
#[must_use]
pub fn stream_info(&self) -> Option<&FlacStreamInfo> {
self.stream_info.as_ref()
}
#[must_use]
pub fn comment_block(&self) -> Option<&FlacVorbisComment> {
self.comment.as_ref()
}
pub fn parse_metadata(&mut self, data: &[u8]) -> Result<(), CodecError> {
if data.len() < 4 {
return Err(CodecError::InvalidData(
"Stream too short for fLaC marker".to_string(),
));
}
if &data[..4] != b"fLaC" {
return Err(CodecError::InvalidData(
"Missing fLaC magic marker".to_string(),
));
}
let mut pos = 4usize;
loop {
if pos + 4 > data.len() {
return Err(CodecError::InvalidData(
"Truncated metadata block header".to_string(),
));
}
let header_byte = data[pos];
let is_last = (header_byte & 0x80) != 0;
let block_type = header_byte & 0x7F;
let length = (u32::from(data[pos + 1]) << 16)
| (u32::from(data[pos + 2]) << 8)
| u32::from(data[pos + 3]);
pos += 4;
let block_end = pos + length as usize;
if block_end > data.len() {
return Err(CodecError::InvalidData(format!(
"Metadata block (type {block_type}) extends beyond data"
)));
}
let block_data = &data[pos..block_end];
match block_type {
0 => {
self.stream_info = Some(Self::parse_streaminfo_block(block_data)?);
}
4 => {
self.comment = Some(Self::parse_vorbis_comment_block(block_data)?);
}
_ => {
}
}
pos = block_end;
if is_last {
break;
}
}
if self.stream_info.is_none() {
return Err(CodecError::InvalidData(
"No STREAMINFO block found in metadata".to_string(),
));
}
self.header_parsed = true;
Ok(())
}
pub fn probe(data: &[u8]) -> Result<FlacStreamInfo, CodecError> {
if data.len() < 8 {
return Err(CodecError::InvalidData(
"Stream too short for FLAC probe".to_string(),
));
}
if &data[..4] != b"fLaC" {
return Err(CodecError::InvalidData(
"Missing fLaC magic marker".to_string(),
));
}
let header_byte = data[4];
let block_type = header_byte & 0x7F;
if block_type != 0 {
return Err(CodecError::InvalidData(format!(
"Expected STREAMINFO as first block, got type {block_type}"
)));
}
let length = (u32::from(data[5]) << 16) | (u32::from(data[6]) << 8) | u32::from(data[7]);
let block_end = 8 + length as usize;
if block_end > data.len() {
return Err(CodecError::InvalidData(
"STREAMINFO block extends beyond data in probe".to_string(),
));
}
Self::parse_streaminfo_block(&data[8..block_end])
}
fn parse_streaminfo_block(si_data: &[u8]) -> Result<FlacStreamInfo, CodecError> {
if si_data.len() < 18 {
return Err(CodecError::InvalidData(
"STREAMINFO block too short".to_string(),
));
}
let min_block_size = (u16::from(si_data[0]) << 8) | u16::from(si_data[1]);
let max_block_size = (u16::from(si_data[2]) << 8) | u16::from(si_data[3]);
let min_frame_size =
(u32::from(si_data[4]) << 16) | (u32::from(si_data[5]) << 8) | u32::from(si_data[6]);
let max_frame_size =
(u32::from(si_data[7]) << 16) | (u32::from(si_data[8]) << 8) | u32::from(si_data[9]);
let packed = (u32::from(si_data[10]) << 24)
| (u32::from(si_data[11]) << 16)
| (u32::from(si_data[12]) << 8)
| u32::from(si_data[13]);
let sample_rate = packed >> 12;
let channels = (((packed >> 9) & 0x07) + 1) as u8;
let bits_per_sample = (((packed >> 4) & 0x1F) + 1) as u8;
let total_samples_high = u64::from(packed & 0x0F);
let total_samples_low = if si_data.len() >= 18 {
(u64::from(si_data[14]) << 24)
| (u64::from(si_data[15]) << 16)
| (u64::from(si_data[16]) << 8)
| u64::from(si_data[17])
} else {
0
};
let total_samples = (total_samples_high << 32) | total_samples_low;
let mut md5_signature = [0u8; 16];
if si_data.len() >= 34 {
md5_signature.copy_from_slice(&si_data[18..34]);
}
Ok(FlacStreamInfo {
min_block_size,
max_block_size,
min_frame_size,
max_frame_size,
sample_rate,
channels,
bits_per_sample,
total_samples,
md5_signature,
})
}
fn parse_vorbis_comment_block(data: &[u8]) -> Result<FlacVorbisComment, CodecError> {
let mut pos = 0usize;
let read_le_u32 = |d: &[u8], p: usize| -> Result<u32, CodecError> {
if p + 4 > d.len() {
return Err(CodecError::InvalidData(
"VORBIS_COMMENT: unexpected end of data".to_string(),
));
}
Ok(u32::from(d[p])
| (u32::from(d[p + 1]) << 8)
| (u32::from(d[p + 2]) << 16)
| (u32::from(d[p + 3]) << 24))
};
let vendor_len = read_le_u32(data, pos)? as usize;
pos += 4;
if pos + vendor_len > data.len() {
return Err(CodecError::InvalidData(
"VORBIS_COMMENT: vendor string truncated".to_string(),
));
}
let vendor = String::from_utf8_lossy(&data[pos..pos + vendor_len]).into_owned();
pos += vendor_len;
let comment_count = read_le_u32(data, pos)? as usize;
pos += 4;
let mut comments = Vec::with_capacity(comment_count);
for _ in 0..comment_count {
let entry_len = read_le_u32(data, pos)? as usize;
pos += 4;
if pos + entry_len > data.len() {
return Err(CodecError::InvalidData(
"VORBIS_COMMENT: comment entry truncated".to_string(),
));
}
let entry = String::from_utf8_lossy(&data[pos..pos + entry_len]).into_owned();
pos += entry_len;
if let Some(eq_pos) = entry.find('=') {
let key = entry[..eq_pos].to_string();
let value = entry[eq_pos + 1..].to_string();
comments.push((key, value));
} else {
comments.push((entry, String::new()));
}
}
Ok(FlacVorbisComment { vendor, comments })
}
pub fn parse_stream_header(&mut self, data: &[u8]) -> CodecResult<usize> {
if data.len() < 8 {
return Err(CodecError::InvalidData(
"Stream too short for FLAC header".to_string(),
));
}
if &data[..4] != b"fLaC" {
return Err(CodecError::InvalidData("Missing fLaC magic".to_string()));
}
let block_type = data[4] & 0x7F;
let length = (u32::from(data[5]) << 16) | (u32::from(data[6]) << 8) | u32::from(data[7]);
if block_type != 0 {
return Err(CodecError::InvalidData(format!(
"Expected STREAMINFO block (type 0), got type {block_type}"
)));
}
let offset = 8usize;
let end = offset + length as usize;
if data.len() < end {
return Err(CodecError::InvalidData(
"Truncated STREAMINFO block".to_string(),
));
}
let si_data = &data[offset..end];
self.stream_info = Some(Self::parse_streaminfo_block(si_data)?);
self.header_parsed = true;
Ok(end)
}
pub fn decode_frame(&self, data: &[u8]) -> CodecResult<(DecodedBlock, usize)> {
if data.len() < 10 {
return Err(CodecError::InvalidData("Frame data too short".to_string()));
}
let mut r = BitReader::new(data);
let sync_hi = r
.read_byte()
.ok_or_else(|| CodecError::InvalidData("EOF reading sync".to_string()))?;
let sync_lo = r
.read_byte()
.ok_or_else(|| CodecError::InvalidData("EOF reading sync".to_string()))?;
if sync_hi != 0xFF || (sync_lo & 0xFC) != 0xF8 {
return Err(CodecError::InvalidData(format!(
"Invalid FLAC sync: {sync_hi:#04x} {sync_lo:#04x}"
)));
}
let byte2 = r
.read_byte()
.ok_or_else(|| CodecError::InvalidData("EOF byte2".to_string()))?;
let bs_code = (byte2 >> 4) & 0x0F;
let _sr_code = byte2 & 0x0F;
let byte3 = r
.read_byte()
.ok_or_else(|| CodecError::InvalidData("EOF byte3".to_string()))?;
let ch_minus1 = ((byte3 >> 4) & 0x0F) as usize;
let _bps_code = byte3 & 0x0F;
let channels = ch_minus1 + 1;
let sample_number = r
.read_utf8_coded()
.ok_or_else(|| CodecError::InvalidData("EOF sample number".to_string()))?;
let block_size = if bs_code == 7 {
let hi = r
.read_byte()
.ok_or_else(|| CodecError::InvalidData("EOF block size hi".to_string()))?;
let lo = r
.read_byte()
.ok_or_else(|| CodecError::InvalidData("EOF block size lo".to_string()))?;
((u32::from(hi) << 8) | u32::from(lo)) as usize
} else {
self.stream_info
.as_ref()
.map(|si| si.max_block_size as usize)
.unwrap_or(4096)
};
let _crc8 = r
.read_byte()
.ok_or_else(|| CodecError::InvalidData("EOF CRC-8".to_string()))?;
let mut decoded_channels: Vec<Vec<i32>> = Vec::with_capacity(channels);
for _ in 0..channels {
let ch_samples = self.decode_subframe(&mut r, block_size)?;
decoded_channels.push(ch_samples);
}
r.align_to_byte();
let frame_len_before_crc = r.byte_offset();
let crc_hi = r
.read_byte()
.ok_or_else(|| CodecError::InvalidData("EOF CRC-16 hi".to_string()))?;
let crc_lo = r
.read_byte()
.ok_or_else(|| CodecError::InvalidData("EOF CRC-16 lo".to_string()))?;
let stored_crc = (u16::from(crc_hi) << 8) | u16::from(crc_lo);
let computed_crc = crc16(&data[..frame_len_before_crc]);
if stored_crc != computed_crc {
let _ = stored_crc;
}
let bytes_consumed = frame_len_before_crc + 2;
let mut samples = Vec::with_capacity(block_size * channels);
for s in 0..block_size {
for ch in 0..channels {
let v = decoded_channels[ch].get(s).copied().unwrap_or(0);
samples.push(v);
}
}
Ok((
DecodedBlock {
samples,
sample_number,
block_size,
channels,
},
bytes_consumed,
))
}
fn decode_subframe(&self, r: &mut BitReader<'_>, block_size: usize) -> CodecResult<Vec<i32>> {
let subframe_type = r
.read_byte()
.ok_or_else(|| CodecError::InvalidData("EOF reading subframe type".to_string()))?;
if subframe_type == 0x02 {
return self.decode_verbatim_subframe(r, block_size);
}
if subframe_type & 0xC0 == 0x40 {
let order = ((subframe_type & 0x3F) as usize) + 1;
return self.decode_lpc_subframe(r, block_size, order);
}
self.decode_verbatim_subframe(r, block_size)
}
fn decode_verbatim_subframe(
&self,
r: &mut BitReader<'_>,
block_size: usize,
) -> CodecResult<Vec<i32>> {
let mut samples = Vec::with_capacity(block_size);
for _ in 0..block_size {
let s = r.read_be_i16().ok_or_else(|| {
CodecError::InvalidData("EOF reading verbatim sample".to_string())
})?;
samples.push(i32::from(s));
}
Ok(samples)
}
fn decode_lpc_subframe(
&self,
r: &mut BitReader<'_>,
block_size: usize,
order: usize,
) -> CodecResult<Vec<i32>> {
if block_size < order {
return Err(CodecError::InvalidData(format!(
"Block size {block_size} < LPC order {order}"
)));
}
let mut warmup = Vec::with_capacity(order);
for _ in 0..order {
let s = r
.read_be_i16()
.ok_or_else(|| CodecError::InvalidData("EOF reading LPC warmup".to_string()))?;
warmup.push(i32::from(s));
}
let precision_byte = r
.read_byte()
.ok_or_else(|| CodecError::InvalidData("EOF reading LPC precision".to_string()))?;
let shift_byte = r
.read_byte()
.ok_or_else(|| CodecError::InvalidData("EOF reading LPC shift".to_string()))?;
let _precision = precision_byte + 1; let shift = shift_byte;
let mut int_coeffs = Vec::with_capacity(order);
for _ in 0..order {
let c = r.read_be_i16().ok_or_else(|| {
CodecError::InvalidData("EOF reading LPC coefficient".to_string())
})?;
int_coeffs.push(i32::from(c));
}
let scale = (1i64 << shift) as f64;
let float_coeffs: Vec<f64> = int_coeffs.iter().map(|&c| f64::from(c) / scale).collect();
let _partition_order = r.read_byte().ok_or_else(|| {
CodecError::InvalidData("EOF reading Rice partition order".to_string())
})?;
let rice_param = r
.read_byte()
.ok_or_else(|| CodecError::InvalidData("EOF reading Rice parameter".to_string()))?;
let residual_count = block_size - order;
r.align_to_byte();
let rice_data = r.slice_from_current().to_vec();
let mut rice_dec = RiceDecoder::new(&rice_data);
let residuals = rice_dec.decode_n(residual_count, rice_param);
if residuals.len() < residual_count {
return Err(CodecError::InvalidData(format!(
"Expected {residual_count} residuals, got {}",
residuals.len()
)));
}
let restored = restore_signal(&warmup, &residuals, &float_coeffs);
Ok(restored)
}
pub fn decode_stream(&mut self, data: &[u8]) -> CodecResult<Vec<i32>> {
let header_end = self.parse_stream_header(data)?;
let mut pos = header_end;
let mut all_samples: Vec<i32> = Vec::new();
while pos + 4 < data.len() {
if data[pos] != 0xFF || (data[pos + 1] & 0xFC) != 0xF8 {
pos += 1;
continue;
}
match self.decode_frame(&data[pos..]) {
Ok((block, consumed)) => {
all_samples.extend_from_slice(&block.samples);
pos += consumed.max(1);
}
Err(_) => {
pos += 1;
}
}
}
Ok(all_samples)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::flac::{FlacConfig, FlacEncoder};
fn make_encoder(channels: u8) -> FlacEncoder {
FlacEncoder::new(FlacConfig {
sample_rate: 44100,
channels,
bits_per_sample: 16,
})
}
#[test]
fn test_flac_decoder_new() {
let dec = FlacDecoder::new();
assert!(dec.stream_info.is_none());
assert!(!dec.header_parsed);
}
#[test]
fn test_parse_stream_header_magic() {
let enc = make_encoder(2);
let header = enc.stream_header();
let mut dec = FlacDecoder::new();
let consumed = dec.parse_stream_header(&header).expect("parse header");
assert_eq!(consumed, 42, "Should consume exactly 42 bytes");
assert!(dec.stream_info.is_some());
}
#[test]
fn test_parse_stream_header_fields() {
let enc = make_encoder(2);
let header = enc.stream_header();
let mut dec = FlacDecoder::new();
dec.parse_stream_header(&header).expect("parse header");
let si = dec.stream_info.as_ref().expect("stream_info");
assert_eq!(si.sample_rate, 44100);
assert_eq!(si.channels, 2);
assert_eq!(si.bits_per_sample, 16);
}
#[test]
fn test_parse_stream_header_bad_magic() {
let bad = b"NOPE\x80\x00\x00\x22";
let mut dec = FlacDecoder::new();
let res = dec.parse_stream_header(bad);
assert!(res.is_err());
}
#[test]
fn test_decode_frame_verbatim_roundtrip() {
let mut enc = make_encoder(1);
let input: Vec<i32> = vec![100i32; 32];
let (header, frames) = enc.encode(&input).expect("encode");
assert!(!frames.is_empty());
let dec = FlacDecoder::new();
let (block, _) = dec.decode_frame(&frames[0].data).expect("decode frame");
assert_eq!(block.channels, 1);
assert!(!block.samples.is_empty());
}
#[test]
fn test_decode_stream_silence_roundtrip() {
let mut enc = make_encoder(2);
let silence = vec![0i32; 128 * 2]; let (header, frames) = enc.encode(&silence).expect("encode");
let mut stream = header.clone();
for f in &frames {
stream.extend_from_slice(&f.data);
}
let mut dec = FlacDecoder::new();
let decoded = dec.decode_stream(&stream).expect("decode_stream");
assert!(!decoded.is_empty(), "Should produce samples");
for &s in &decoded {
assert_eq!(s, 0, "All silence samples should decode as 0");
}
}
#[test]
fn test_decode_stream_ramp_roundtrip() {
let mut enc = make_encoder(1);
let ramp: Vec<i32> = (0..64).map(|i| i * 10).collect();
let (header, frames) = enc.encode(&ramp).expect("encode");
let mut stream = header.clone();
for f in &frames {
stream.extend_from_slice(&f.data);
}
let mut dec = FlacDecoder::new();
let decoded = dec.decode_stream(&stream).expect("decode_stream");
assert!(!decoded.is_empty(), "Should produce samples");
}
#[test]
fn test_decode_frame_block_size_preserved() {
let mut enc = make_encoder(2);
let samples = vec![500i32; 256 * 2]; let (_, frames) = enc.encode(&samples).expect("encode");
assert!(!frames.is_empty());
let dec = FlacDecoder::new();
let (block, _) = dec.decode_frame(&frames[0].data).expect("decode frame");
assert_eq!(block.block_size, 256);
assert_eq!(block.channels, 2);
}
#[test]
fn test_decode_frame_sample_number() {
let mut enc = make_encoder(2);
let samples = vec![0i32; 4096 * 2 * 2]; let (_, frames) = enc.encode(&samples).expect("encode");
if frames.len() >= 2 {
let dec = FlacDecoder::new();
let (block0, _) = dec.decode_frame(&frames[0].data).expect("frame 0");
let (block1, _) = dec.decode_frame(&frames[1].data).expect("frame 1");
assert_eq!(block0.sample_number, 0);
assert!(block1.sample_number > block0.sample_number);
}
}
#[test]
fn test_decode_stream_header_bad_input() {
let mut dec = FlacDecoder::new();
let res = dec.decode_stream(b"short");
assert!(res.is_err() || res.is_ok()); }
#[test]
fn test_crc16_matches_encoder() {
let data = b"FLAC test data for CRC verification";
let enc_crc = {
const POLY: u16 = 0x8005;
let mut crc = 0u16;
for &byte in data.iter() {
crc ^= u16::from(byte) << 8;
for _ in 0..8 {
if crc & 0x8000 != 0 {
crc = (crc << 1) ^ POLY;
} else {
crc <<= 1;
}
}
}
crc
};
let dec_crc = crc16(data);
assert_eq!(enc_crc, dec_crc);
}
#[test]
fn test_decode_mono_stream() {
let mut enc = FlacEncoder::new(FlacConfig {
sample_rate: 48000,
channels: 1,
bits_per_sample: 16,
});
let samples = vec![0i32; 512];
let (header, frames) = enc.encode(&samples).expect("encode mono");
let mut stream = header;
for f in frames {
stream.extend_from_slice(&f.data);
}
let mut dec = FlacDecoder::new();
let decoded = dec.decode_stream(&stream).expect("decode mono");
assert!(!decoded.is_empty());
}
#[test]
fn test_parse_metadata_valid_stream() {
let enc = make_encoder(2);
let header = enc.stream_header();
let stream = header.clone();
let mut dec = FlacDecoder::new();
dec.parse_metadata(&stream)
.expect("parse_metadata should succeed");
assert!(dec.stream_info.is_some(), "stream_info should be populated");
}
#[test]
fn test_parse_metadata_rejects_bad_magic() {
let bad = b"WAVE\x80\x00\x00\x22XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
let mut dec = FlacDecoder::new();
let res = dec.parse_metadata(bad);
assert!(res.is_err(), "Should reject non-fLaC magic");
}
#[test]
fn test_parse_metadata_stream_info_fields() {
let enc = FlacEncoder::new(FlacConfig {
sample_rate: 48000,
channels: 1,
bits_per_sample: 16,
});
let header = enc.stream_header();
let mut dec = FlacDecoder::new();
dec.parse_metadata(&header).expect("parse_metadata");
let si = dec.stream_info().expect("stream_info should be Some");
assert_eq!(si.sample_rate, 48000);
assert_eq!(si.channels, 1);
assert_eq!(si.bits_per_sample, 16);
}
#[test]
fn test_stream_info_method_returns_none_before_parse() {
let dec = FlacDecoder::new();
assert!(dec.stream_info().is_none());
}
#[test]
fn test_stream_info_method_returns_some_after_parse() {
let enc = make_encoder(2);
let header = enc.stream_header();
let mut dec = FlacDecoder::new();
dec.parse_metadata(&header).expect("parse_metadata");
assert!(dec.stream_info().is_some());
}
#[test]
fn test_comment_block_returns_none_when_absent() {
let enc = make_encoder(1);
let header = enc.stream_header();
let mut dec = FlacDecoder::new();
dec.parse_metadata(&header).expect("parse_metadata");
assert!(
dec.comment_block().is_none(),
"No VORBIS_COMMENT block expected from FlacEncoder stream"
);
}
#[test]
fn test_parse_vorbis_comment_block_direct() {
let vendor = b"OxiMedia";
let comment = b"TITLE=Test Track";
let mut block: Vec<u8> = Vec::new();
block.extend_from_slice(&(vendor.len() as u32).to_le_bytes());
block.extend_from_slice(vendor);
block.extend_from_slice(&1u32.to_le_bytes());
block.extend_from_slice(&(comment.len() as u32).to_le_bytes());
block.extend_from_slice(comment);
let vc = FlacDecoder::parse_vorbis_comment_block(&block)
.expect("parse_vorbis_comment_block should succeed");
assert_eq!(vc.vendor, "OxiMedia");
assert_eq!(vc.comments.len(), 1);
assert_eq!(vc.comments[0].0, "TITLE");
assert_eq!(vc.comments[0].1, "Test Track");
}
#[test]
fn test_parse_vorbis_comment_multiple_entries() {
let vendor = b"TestEncoder";
let c1 = b"ARTIST=Some Artist";
let c2 = b"ALBUM=Great Album";
let c3 = b"TRACKNUMBER=3";
let mut block: Vec<u8> = Vec::new();
block.extend_from_slice(&(vendor.len() as u32).to_le_bytes());
block.extend_from_slice(vendor);
block.extend_from_slice(&3u32.to_le_bytes());
for comment in [c1.as_slice(), c2.as_slice(), c3.as_slice()] {
block.extend_from_slice(&(comment.len() as u32).to_le_bytes());
block.extend_from_slice(comment);
}
let vc =
FlacDecoder::parse_vorbis_comment_block(&block).expect("parse_vorbis_comment_block");
assert_eq!(vc.vendor, "TestEncoder");
assert_eq!(vc.comments.len(), 3);
assert_eq!(
vc.comments[0],
("ARTIST".to_string(), "Some Artist".to_string())
);
assert_eq!(
vc.comments[1],
("ALBUM".to_string(), "Great Album".to_string())
);
assert_eq!(vc.comments[2], ("TRACKNUMBER".to_string(), "3".to_string()));
}
#[test]
fn test_parse_metadata_with_vorbis_comment_block() {
let enc = make_encoder(2);
let streaminfo_header = enc.stream_header();
let mut stream = streaminfo_header.clone();
stream[4] &= 0x7F;
let vendor = b"OxiMediaTest";
let comment = b"COMMENT=hello";
let mut vc_body: Vec<u8> = Vec::new();
vc_body.extend_from_slice(&(vendor.len() as u32).to_le_bytes());
vc_body.extend_from_slice(vendor);
vc_body.extend_from_slice(&1u32.to_le_bytes());
vc_body.extend_from_slice(&(comment.len() as u32).to_le_bytes());
vc_body.extend_from_slice(comment);
let vc_len = vc_body.len() as u32;
stream.push(0x84); stream.push(((vc_len >> 16) & 0xFF) as u8);
stream.push(((vc_len >> 8) & 0xFF) as u8);
stream.push((vc_len & 0xFF) as u8);
stream.extend_from_slice(&vc_body);
let mut dec = FlacDecoder::new();
dec.parse_metadata(&stream)
.expect("parse_metadata with VORBIS_COMMENT");
assert!(dec.stream_info().is_some());
let vc = dec
.comment_block()
.expect("VORBIS_COMMENT should be parsed");
assert_eq!(vc.vendor, "OxiMediaTest");
assert_eq!(vc.comments.len(), 1);
assert_eq!(vc.comments[0].0, "COMMENT");
assert_eq!(vc.comments[0].1, "hello");
}
#[test]
fn test_probe_valid_stream() {
let enc = make_encoder(2);
let header = enc.stream_header();
let si = FlacDecoder::probe(&header).expect("probe should succeed");
assert_eq!(si.sample_rate, 44100);
assert_eq!(si.channels, 2);
assert_eq!(si.bits_per_sample, 16);
}
#[test]
fn test_probe_rejects_bad_magic() {
let bad = b"RIFF\x80\x00\x00\x22XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
let res = FlacDecoder::probe(bad);
assert!(res.is_err(), "probe should reject non-fLaC magic");
}
#[test]
fn test_probe_rejects_too_short() {
let short = b"fLaC";
let res = FlacDecoder::probe(short);
assert!(res.is_err(), "probe should reject too-short data");
}
#[test]
fn test_streaminfo_min_max_block_size() {
let enc = make_encoder(2);
let header = enc.stream_header();
let si = FlacDecoder::probe(&header).expect("probe");
assert!(si.min_block_size <= si.max_block_size);
assert!(si.max_block_size >= 16);
}
#[test]
fn test_streaminfo_new_fields_accessible() {
let enc = make_encoder(1);
let header = enc.stream_header();
let si = FlacDecoder::probe(&header).expect("probe");
let _ = si.min_frame_size;
let _ = si.max_frame_size;
let _ = si.total_samples;
let _ = si.md5_signature;
}
}