#![forbid(unsafe_code)]
use std::collections::VecDeque;
use bytes::Bytes;
use oximedia_core::{CodecId, Rational, SampleFormat, Timestamp};
use crate::{
frame::{AudioBuffer, AudioFrame, ChannelLayout},
AudioDecoder, AudioDecoderConfig, AudioError, AudioResult,
};
use super::{
crc::crc16,
frame::{ChannelAssignment, FrameHeader},
rice::zigzag_decode,
subframe::{LpcCoefficients, Subframe, SubframeHeader, SubframeType},
StreamInfo,
};
struct BitReader<'a> {
data: &'a [u8],
byte_pos: usize,
bit_pos: u8, }
impl<'a> BitReader<'a> {
fn new(data: &'a [u8]) -> Self {
Self {
data,
byte_pos: 0,
bit_pos: 0,
}
}
fn byte_offset(&self) -> usize {
self.byte_pos
}
fn bits_consumed(&self) -> usize {
self.byte_pos * 8 + usize::from(self.bit_pos)
}
fn read_bit(&mut self) -> Option<bool> {
if self.byte_pos >= self.data.len() {
return None;
}
let bit = (self.data[self.byte_pos] >> (7 - self.bit_pos)) & 1;
self.bit_pos += 1;
if self.bit_pos >= 8 {
self.bit_pos = 0;
self.byte_pos += 1;
}
Some(bit != 0)
}
fn read_bits_u32(&mut self, n: u8) -> Option<u32> {
let mut v = 0u32;
for _ in 0..n {
v = (v << 1) | u32::from(self.read_bit()?);
}
Some(v)
}
fn read_bits_i32(&mut self, n: u8) -> Option<i32> {
if n == 0 {
return Some(0);
}
let raw = self.read_bits_u32(n)?;
let shift = 32 - n;
Some(((raw << shift) as i32) >> shift)
}
fn read_bits_i64(&mut self, n: u8) -> Option<i64> {
if n == 0 {
return Some(0);
}
let mut v = 0u64;
for _ in 0..n {
v = (v << 1) | u64::from(self.read_bit()?);
}
let shift = 64 - n;
Some(((v << shift) as i64) >> shift)
}
fn read_unary(&mut self) -> Option<u32> {
let mut count = 0u32;
loop {
match self.read_bit()? {
true => count += 1,
false => return Some(count),
}
}
}
fn byte_align(&mut self) {
if self.bit_pos > 0 {
self.bit_pos = 0;
self.byte_pos += 1;
}
}
fn remaining_bytes(&self) -> usize {
self.data.len().saturating_sub(self.byte_pos)
}
}
pub struct FlacDecoder {
config: AudioDecoderConfig,
stream_info: Option<StreamInfo>,
buffer: Vec<u8>,
pending_frames: VecDeque<AudioFrame>,
flushing: bool,
metadata_done: bool,
}
impl FlacDecoder {
pub fn new(config: &AudioDecoderConfig) -> AudioResult<Self> {
if config.codec != CodecId::Flac {
return Err(AudioError::InvalidParameter("Expected FLAC codec".into()));
}
Ok(Self {
config: config.clone(),
stream_info: None,
buffer: Vec::new(),
pending_frames: VecDeque::new(),
flushing: false,
metadata_done: false,
})
}
fn try_parse_metadata(&mut self) -> AudioResult<bool> {
let buf = &self.buffer;
if buf.len() < 4 {
return Ok(false);
}
if &buf[0..4] != b"fLaC" {
return Err(AudioError::InvalidData(
"FLAC stream missing 'fLaC' marker".into(),
));
}
let mut offset = 4usize;
loop {
if offset + 4 > buf.len() {
return Ok(false); }
let header_byte = buf[offset];
let last_block = (header_byte & 0x80) != 0;
let block_type = header_byte & 0x7F;
let block_len = (u32::from(buf[offset + 1]) << 16)
| (u32::from(buf[offset + 2]) << 8)
| u32::from(buf[offset + 3]);
let block_len = block_len as usize;
offset += 4;
if offset + block_len > buf.len() {
return Ok(false); }
let block_data = &buf[offset..offset + block_len];
if block_type == 0 {
let info = StreamInfo::parse(block_data)?;
self.stream_info = Some(info);
}
offset += block_len;
if last_block {
self.buffer.drain(..offset);
return Ok(true);
}
}
}
fn try_decode_one_frame(&mut self) -> AudioResult<Option<AudioFrame>> {
if self.buffer.len() < 4 {
return Ok(None);
}
let sync_pos = {
let mut found = None;
let end = self.buffer.len().saturating_sub(1);
for i in 0..end {
if self.buffer[i] == 0xFF && (self.buffer[i + 1] & 0xFC) == 0xF8 {
found = Some(i);
break;
}
}
found
};
let sync_pos = match sync_pos {
Some(p) => p,
None => return Ok(None),
};
if sync_pos > 0 {
self.buffer.drain(..sync_pos);
}
let buf = &self.buffer;
let streaminfo_bps = self.stream_info.as_ref().map_or(16, |s| s.bits_per_sample);
let (header, header_len) = match FrameHeader::parse(buf, streaminfo_bps) {
Ok(r) => r,
Err(AudioError::InvalidData(_)) => {
self.buffer.drain(..2);
return Ok(None);
}
Err(e) => return Err(e),
};
let sample_rate = if header.sample_rate == 0 {
self.stream_info
.as_ref()
.map_or(self.config.sample_rate, |s| s.sample_rate)
} else {
header.sample_rate
};
let bps = header.bits_per_sample;
let block_size = header.block_size as usize;
let channel_count = header.channels() as usize;
let working_slice = buf;
let mut br = BitReader::new(working_slice);
for _ in 0..header_len * 8 {
if br.read_bit().is_none() {
return Ok(None);
}
}
let mut channel_samples: Vec<Vec<i32>> = Vec::with_capacity(channel_count);
for ch in 0..channel_count {
let ch_bps = match header.channel_assignment {
ChannelAssignment::LeftSide if ch == 1 => bps + 1,
ChannelAssignment::RightSide if ch == 0 => bps + 1,
ChannelAssignment::MidSide if ch == 1 => bps + 1,
_ => bps,
};
let samples = match self.decode_subframe(&mut br, block_size, ch_bps) {
Ok(s) => s,
Err(AudioError::NeedMoreData) => return Ok(None),
Err(e) => return Err(e),
};
channel_samples.push(samples);
}
br.byte_align();
let frame_body_end = br.byte_offset();
if frame_body_end + 2 > working_slice.len() {
return Ok(None);
}
let crc_stored = u16::from_be_bytes([
working_slice[frame_body_end],
working_slice[frame_body_end + 1],
]);
let crc_computed = crc16(&working_slice[..frame_body_end]);
if crc_stored != crc_computed {
self.buffer.drain(..2);
return Err(AudioError::InvalidData("FLAC CRC16 mismatch".into()));
}
let total_frame_bytes = frame_body_end + 2;
apply_decorrelation(&mut channel_samples, header.channel_assignment);
let max_val = (1i64 << (bps - 1)) as f32;
let planes: Vec<Bytes> = channel_samples
.into_iter()
.map(|ch| {
let mut plane = Vec::with_capacity(ch.len() * 4);
for s in ch {
let f: f32 = s as f32 / max_val;
plane.extend_from_slice(&f.to_le_bytes());
}
Bytes::from(plane)
})
.collect();
let pts: i64 = match header.blocking_strategy {
super::frame::BlockingStrategy::Variable => header.sample_number.unwrap_or(0) as i64,
super::frame::BlockingStrategy::Fixed => {
let frame_num = i64::from(header.frame_number.unwrap_or(0));
frame_num * block_size as i64
}
};
let timebase = Rational::new(1, i64::from(sample_rate));
let timestamp = Timestamp::new(pts, timebase);
let audio_frame = AudioFrame {
format: SampleFormat::F32p,
sample_rate,
channels: ChannelLayout::from_count(channel_count),
samples: AudioBuffer::Planar(planes),
timestamp,
};
self.buffer.drain(..total_frame_bytes);
Ok(Some(audio_frame))
}
fn decode_subframe(
&self,
br: &mut BitReader<'_>,
block_size: usize,
bps: u8,
) -> AudioResult<Vec<i32>> {
let header_byte = br.read_bits_u32(8).ok_or(AudioError::NeedMoreData)? as u8;
let sub_header = SubframeHeader::parse(header_byte, bps)?;
let wasted_bits: u8 = if SubframeHeader::has_wasted_bits(header_byte) {
let unary_val = br.read_unary().ok_or(AudioError::NeedMoreData)?;
(unary_val + 1) as u8
} else {
0
};
let effective_bps = bps.saturating_sub(wasted_bits);
let mut samples = match sub_header.subframe_type {
SubframeType::Constant => self.decode_constant(br, block_size, effective_bps)?,
SubframeType::Verbatim => self.decode_verbatim(br, block_size, effective_bps)?,
SubframeType::Fixed(order) => {
self.decode_fixed(br, block_size, effective_bps, order)?
}
SubframeType::Lpc(order) => self.decode_lpc(br, block_size, effective_bps, order)?,
};
if wasted_bits > 0 {
let shift = u32::from(wasted_bits);
for s in &mut samples {
*s <<= shift;
}
}
Ok(samples)
}
fn decode_constant(
&self,
br: &mut BitReader<'_>,
block_size: usize,
bps: u8,
) -> AudioResult<Vec<i32>> {
let value = br.read_bits_i32(bps).ok_or(AudioError::NeedMoreData)?;
Ok(vec![value; block_size])
}
fn decode_verbatim(
&self,
br: &mut BitReader<'_>,
block_size: usize,
bps: u8,
) -> AudioResult<Vec<i32>> {
let mut samples = Vec::with_capacity(block_size);
for _ in 0..block_size {
let s = br.read_bits_i32(bps).ok_or(AudioError::NeedMoreData)?;
samples.push(s);
}
Ok(samples)
}
fn decode_fixed(
&self,
br: &mut BitReader<'_>,
block_size: usize,
bps: u8,
order: u8,
) -> AudioResult<Vec<i32>> {
if order > 4 {
return Err(AudioError::InvalidData(format!(
"Fixed predictor order {order} > 4"
)));
}
let order_usize = order as usize;
let mut warmup = Vec::with_capacity(order_usize);
for _ in 0..order_usize {
let s = br.read_bits_i32(bps).ok_or(AudioError::NeedMoreData)?;
warmup.push(s);
}
let residuals = self.decode_residuals(br, block_size, order_usize)?;
let mut sub = Subframe::with_header(SubframeHeader {
subframe_type: SubframeType::Fixed(order),
wasted_bits: 0,
effective_bps: bps,
});
sub.warmup.samples = warmup;
sub.decode_fixed(&residuals);
Ok(sub.samples)
}
#[allow(clippy::cast_possible_truncation)]
fn decode_lpc(
&self,
br: &mut BitReader<'_>,
block_size: usize,
bps: u8,
order: u8,
) -> AudioResult<Vec<i32>> {
let order_usize = order as usize;
let mut warmup = Vec::with_capacity(order_usize);
for _ in 0..order_usize {
let s = br.read_bits_i32(bps).ok_or(AudioError::NeedMoreData)?;
warmup.push(s);
}
let precision_minus1 = br.read_bits_u32(4).ok_or(AudioError::NeedMoreData)? as u8;
if precision_minus1 == 0x0F {
return Err(AudioError::InvalidData(
"LPC coefficient precision 0xF is reserved".into(),
));
}
let precision = precision_minus1 + 1;
let shift_raw = br.read_bits_u32(5).ok_or(AudioError::NeedMoreData)?;
let shift: i8 = (((shift_raw as i32) << 27) >> 27) as i8;
let mut coeffs = Vec::with_capacity(order_usize);
for _ in 0..order_usize {
let c = br
.read_bits_i32(precision)
.ok_or(AudioError::NeedMoreData)?;
coeffs.push(c);
}
let residuals = self.decode_residuals(br, block_size, order_usize)?;
let mut lpc_coeffs = LpcCoefficients::new(order_usize);
lpc_coeffs.precision = precision;
lpc_coeffs.shift = shift;
lpc_coeffs.coefficients = coeffs;
let mut sub = Subframe::with_header(SubframeHeader {
subframe_type: SubframeType::Lpc(order),
wasted_bits: 0,
effective_bps: bps,
});
sub.warmup.samples = warmup;
sub.lpc = Some(lpc_coeffs);
sub.decode_lpc(&residuals);
Ok(sub.samples)
}
fn decode_residuals(
&self,
br: &mut BitReader<'_>,
block_size: usize,
predictor_order: usize,
) -> AudioResult<Vec<i32>> {
let coding_method = br.read_bits_u32(2).ok_or(AudioError::NeedMoreData)?;
let param_bits: u8 = if coding_method == 0 {
4
} else if coding_method == 1 {
5
} else {
return Err(AudioError::InvalidData(format!(
"Unknown residual coding method {coding_method}"
)));
};
let escape_code: u32 = if param_bits == 4 { 0x0F } else { 0x1F };
let partition_order = br.read_bits_u32(4).ok_or(AudioError::NeedMoreData)? as u8;
let partition_count = 1usize << partition_order;
if block_size < predictor_order {
return Err(AudioError::InvalidData(
"block_size < predictor_order in residuals".into(),
));
}
let total_residuals = block_size - predictor_order;
let mut residuals = Vec::with_capacity(total_residuals);
for p in 0..partition_count {
let samples_in_partition = if partition_order == 0 {
total_residuals
} else if p == 0 {
(block_size >> partition_order) - predictor_order
} else {
block_size >> partition_order
};
let param = br
.read_bits_u32(param_bits)
.ok_or(AudioError::NeedMoreData)?;
if param == escape_code {
let raw_bits = br.read_bits_u32(5).ok_or(AudioError::NeedMoreData)? as u8;
for _ in 0..samples_in_partition {
let v = if raw_bits == 0 {
0i32
} else {
br.read_bits_i32(raw_bits).ok_or(AudioError::NeedMoreData)?
};
residuals.push(v);
}
} else {
let k = param as u8;
for _ in 0..samples_in_partition {
let quotient = br.read_unary().ok_or(AudioError::NeedMoreData)?;
let remainder = if k > 0 {
br.read_bits_u32(k).ok_or(AudioError::NeedMoreData)?
} else {
0
};
let unsigned = (quotient << k) | remainder;
residuals.push(zigzag_decode(unsigned));
}
}
}
Ok(residuals)
}
}
fn apply_decorrelation(channels: &mut Vec<Vec<i32>>, assignment: ChannelAssignment) {
if channels.len() != 2 {
return;
}
let n = channels[0].len();
if channels[1].len() != n {
return;
}
match assignment {
ChannelAssignment::LeftSide => {
for i in 0..n {
let left = channels[0][i];
let side = channels[1][i];
channels[1][i] = left - side;
}
}
ChannelAssignment::RightSide => {
for i in 0..n {
let side = channels[0][i];
let right = channels[1][i];
channels[0][i] = side + right;
}
}
ChannelAssignment::MidSide => {
for i in 0..n {
let mid = channels[0][i] as i64;
let side = channels[1][i] as i64;
let mid_shifted = (mid << 1) | (side & 1);
channels[0][i] = ((mid_shifted + side) >> 1) as i32;
channels[1][i] = ((mid_shifted - side) >> 1) as i32;
}
}
ChannelAssignment::Independent(_) => {
}
}
}
impl AudioDecoder for FlacDecoder {
fn codec(&self) -> CodecId {
CodecId::Flac
}
fn send_packet(&mut self, data: &[u8], _pts: i64) -> AudioResult<()> {
self.buffer.extend_from_slice(data);
if !self.metadata_done {
match self.try_parse_metadata()? {
true => {
self.metadata_done = true;
}
false => {
return Ok(());
}
}
}
loop {
match self.try_decode_one_frame()? {
Some(frame) => self.pending_frames.push_back(frame),
None => break,
}
}
Ok(())
}
fn receive_frame(&mut self) -> AudioResult<Option<AudioFrame>> {
if let Some(frame) = self.pending_frames.pop_front() {
return Ok(Some(frame));
}
if self.metadata_done {
if let Some(frame) = self.try_decode_one_frame()? {
return Ok(Some(frame));
}
}
Ok(None)
}
fn flush(&mut self) -> AudioResult<()> {
self.flushing = true;
Ok(())
}
fn reset(&mut self) {
self.stream_info = None;
self.buffer.clear();
self.pending_frames.clear();
self.flushing = false;
self.metadata_done = false;
}
fn output_format(&self) -> Option<SampleFormat> {
Some(SampleFormat::F32p)
}
fn sample_rate(&self) -> Option<u32> {
self.stream_info
.as_ref()
.map(|s| s.sample_rate)
.or(Some(self.config.sample_rate))
}
fn channel_layout(&self) -> Option<ChannelLayout> {
let count = self
.stream_info
.as_ref()
.map_or(usize::from(self.config.channels), |s| {
usize::from(s.channels)
});
Some(ChannelLayout::from_count(count))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
flac::FlacEncoder, frame::AudioBuffer, AudioDecoderConfig, AudioEncoder,
AudioEncoderConfig, AudioFrame,
};
use bytes::Bytes;
use oximedia_core::CodecId;
fn make_encoder(sample_rate: u32, channels: u8, block_size: u32) -> FlacEncoder {
let config = AudioEncoderConfig {
codec: CodecId::Flac,
sample_rate,
channels,
bitrate: 0,
frame_size: block_size,
};
FlacEncoder::new(&config).expect("encoder creation")
}
fn make_decoder(sample_rate: u32, channels: u8) -> FlacDecoder {
let config = AudioDecoderConfig {
codec: CodecId::Flac,
sample_rate,
channels,
extradata: None,
};
FlacDecoder::new(&config).expect("decoder creation")
}
fn encode_to_flac(
samples_per_channel: &[Vec<i32>],
sample_rate: u32,
bps: u8,
block_size: u32,
) -> Vec<u8> {
let channels = samples_per_channel.len() as u8;
let total_samples = samples_per_channel[0].len() as u64;
let config = AudioEncoderConfig {
codec: CodecId::Flac,
sample_rate,
channels,
bitrate: 0,
frame_size: block_size,
};
let mut enc = FlacEncoder::new(&config).expect("encoder");
let mut stream = Vec::new();
stream.extend_from_slice(b"fLaC");
let info = super::super::StreamInfo {
min_block_size: block_size as u16,
max_block_size: block_size as u16,
min_frame_size: 0,
max_frame_size: 0,
sample_rate,
channels,
bits_per_sample: bps,
total_samples,
md5_signature: [0u8; 16],
};
let si_data = enc.generate_streaminfo(total_samples).expect("streaminfo");
stream.push(0x80); stream.push(0x00);
stream.push(0x00);
stream.push(0x22); stream.extend_from_slice(&si_data);
let _ = info;
let sample_count = samples_per_channel[0].len();
let num_blocks = (sample_count + block_size as usize - 1) / block_size as usize;
for block_idx in 0..num_blocks {
let start = block_idx * block_size as usize;
let end = (start + block_size as usize).min(sample_count);
let current_block = end - start;
let mut interleaved = Vec::with_capacity(current_block * channels as usize * 2);
for s in 0..current_block {
for ch in 0..channels as usize {
let sample_i32 = samples_per_channel[ch][start + s];
let sample_i16 =
sample_i32.clamp(i32::from(i16::MIN), i32::from(i16::MAX)) as i16;
interleaved.extend_from_slice(&sample_i16.to_le_bytes());
}
}
let frame = AudioFrame {
format: SampleFormat::S16,
sample_rate,
channels: ChannelLayout::from_count(channels as usize),
samples: AudioBuffer::Interleaved(Bytes::from(interleaved)),
timestamp: Timestamp::new(start as i64, Rational::new(1, i64::from(sample_rate))),
};
enc.send_frame(&frame).expect("send_frame");
while let Some(pkt) = enc.receive_packet().expect("receive_packet") {
stream.extend_from_slice(&pkt.data);
}
}
enc.flush().expect("flush");
while let Some(pkt) = enc.receive_packet().expect("receive_packet after flush") {
stream.extend_from_slice(&pkt.data);
}
stream
}
fn decode_flac(data: &[u8], channels: usize) -> Vec<Vec<f32>> {
let config = AudioDecoderConfig {
codec: CodecId::Flac,
sample_rate: 44100,
channels: channels as u8,
extradata: None,
};
let mut dec = FlacDecoder::new(&config).expect("decoder");
dec.send_packet(data, 0).expect("send_packet");
let mut result: Vec<Vec<f32>> = vec![Vec::new(); channels];
while let Some(frame) = dec.receive_frame().expect("receive_frame") {
if let AudioBuffer::Planar(planes) = &frame.samples {
for (ch, plane) in planes.iter().enumerate() {
if ch < channels {
for chunk in plane.chunks_exact(4) {
let f = f32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]);
result[ch].push(f);
}
}
}
}
}
result
}
#[test]
fn test_flac_decoder_returns_none_when_empty() {
let mut dec = make_decoder(44100, 2);
assert!(dec
.receive_frame()
.expect("receive_frame should succeed")
.is_none());
}
#[test]
fn test_flac_decode_synth_16bit_mono_constant() {
let block_size = 256u32;
let sample_rate = 44100u32;
let samples_per_ch = vec![vec![1000i32; block_size as usize]];
let flac_data = encode_to_flac(&samples_per_ch, sample_rate, 16, block_size);
let decoded = decode_flac(&flac_data, 1);
assert!(!decoded[0].is_empty(), "should have decoded samples");
let expected_f: f32 = 1000.0 / 32768.0;
for &s in &decoded[0] {
let diff = (s - expected_f).abs();
assert!(
diff < 1e-5,
"constant mismatch: got {s}, expected {expected_f}"
);
}
}
#[test]
fn test_flac_decode_synth_16bit_stereo_constant() {
let block_size = 256u32;
let sample_rate = 44100u32;
let samples_per_ch = vec![
vec![1000i32; block_size as usize],
vec![-500i32; block_size as usize],
];
let flac_data = encode_to_flac(&samples_per_ch, sample_rate, 16, block_size);
let decoded = decode_flac(&flac_data, 2);
assert!(!decoded[0].is_empty(), "should have L samples");
assert!(!decoded[1].is_empty(), "should have R samples");
let max_val = 32768.0f32;
for &s in &decoded[0] {
let diff = (s - 1000.0 / max_val).abs();
assert!(diff < 1e-5, "L channel mismatch: {s}");
}
for &s in &decoded[1] {
let diff = (s - (-500.0) / max_val).abs();
assert!(diff < 1e-5, "R channel mismatch: {s}");
}
}
#[test]
fn test_flac_decode_send_partial_packets() {
let block_size = 256u32;
let sample_rate = 44100u32;
let samples_per_ch = vec![vec![2000i32; block_size as usize]];
let flac_data = encode_to_flac(&samples_per_ch, sample_rate, 16, block_size);
let chunk_size = flac_data.len() / 3;
let c1 = &flac_data[..chunk_size];
let c2 = &flac_data[chunk_size..2 * chunk_size];
let c3 = &flac_data[2 * chunk_size..];
let config = AudioDecoderConfig {
codec: CodecId::Flac,
sample_rate,
channels: 1,
extradata: None,
};
let mut dec = FlacDecoder::new(&config).expect("decoder");
dec.send_packet(c1, 0).expect("chunk 1");
dec.send_packet(c2, 0).expect("chunk 2");
dec.send_packet(c3, 0).expect("chunk 3");
let mut all_samples: Vec<f32> = Vec::new();
while let Some(frame) = dec.receive_frame().expect("receive_frame") {
if let AudioBuffer::Planar(planes) = &frame.samples {
for chunk in planes[0].chunks_exact(4) {
let f = f32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]);
all_samples.push(f);
}
}
}
assert!(
!all_samples.is_empty(),
"partial packet streaming should yield samples"
);
let expected_f = 2000.0f32 / 32768.0;
for &s in &all_samples {
let diff = (s - expected_f).abs();
assert!(diff < 1e-5, "partial stream mismatch: {s}");
}
}
#[test]
fn test_flac_decode_left_side_decorrelation() {
let block_size = 256u32;
let sample_rate = 44100u32;
let left = vec![1500i32; block_size as usize];
let right = vec![1510i32; block_size as usize];
let flac_data = encode_to_flac(&[left.clone(), right.clone()], sample_rate, 16, block_size);
let decoded = decode_flac(&flac_data, 2);
assert!(!decoded[0].is_empty(), "should have L samples");
assert!(!decoded[1].is_empty(), "should have R samples");
let max_val = 32768.0f32;
let min_len = decoded[0].len().min(left.len());
for i in 0..min_len {
let diff_l = (decoded[0][i] - left[i] as f32 / max_val).abs();
assert!(diff_l < 2.0 / max_val, "L[{i}] mismatch: {}", decoded[0][i]);
let diff_r = (decoded[1][i] - right[i] as f32 / max_val).abs();
assert!(diff_r < 2.0 / max_val, "R[{i}] mismatch: {}", decoded[1][i]);
}
}
#[test]
fn test_flac_decode_mid_side_decorrelation() {
let block_size = 256u32;
let sample_rate = 44100u32;
let left: Vec<i32> = (0..block_size as usize)
.map(|i| ((i as f32 * 0.1).sin() * 800.0) as i32)
.collect();
let right: Vec<i32> = left.clone();
let flac_data = encode_to_flac(&[left.clone(), right.clone()], sample_rate, 16, block_size);
let decoded = decode_flac(&flac_data, 2);
let max_val = 32768.0f32;
let min_len = decoded[0].len().min(left.len());
for i in 0..min_len {
let diff_l = (decoded[0][i] - left[i] as f32 / max_val).abs();
assert!(diff_l < 2.0 / max_val, "L[{i}] mid-side mismatch");
let diff_r = (decoded[1][i] - right[i] as f32 / max_val).abs();
assert!(diff_r < 2.0 / max_val, "R[{i}] mid-side mismatch");
}
}
#[test]
fn test_flac_decode_crc_mismatch_rejects() {
let block_size = 64u32;
let sample_rate = 44100u32;
let samples_per_ch = vec![vec![0i32; block_size as usize]];
let mut flac_data = encode_to_flac(&samples_per_ch, sample_rate, 16, block_size);
let len = flac_data.len();
if len >= 2 {
flac_data[len - 1] ^= 0xFF;
flac_data[len - 2] ^= 0xFF;
}
let config = AudioDecoderConfig {
codec: CodecId::Flac,
sample_rate,
channels: 1,
extradata: None,
};
let mut dec = FlacDecoder::new(&config).expect("decoder");
dec.send_packet(&flac_data, 0)
.expect_err_or_frames_mismatch(&mut dec);
}
#[test]
fn test_flac_decode_24bit_mono_constant() {
let block_size = 128u32;
let sample_rate = 48000u32;
let samples_per_ch = vec![vec![5000i32; block_size as usize]];
let flac_data = encode_to_flac(&samples_per_ch, sample_rate, 16, block_size);
let decoded = decode_flac(&flac_data, 1);
assert!(!decoded[0].is_empty());
let expected = 5000.0f32 / 32768.0;
for &s in &decoded[0] {
assert!((s - expected).abs() < 1e-5, "24-bit test failed: {s}");
}
}
}
#[cfg(test)]
trait ErrorOrMismatch {
fn expect_err_or_frames_mismatch(self, dec: &mut FlacDecoder);
}
#[cfg(test)]
impl ErrorOrMismatch for AudioResult<()> {
fn expect_err_or_frames_mismatch(self, dec: &mut FlacDecoder) {
match self {
Err(_) => {
}
Ok(_) => {
let mut had_error = false;
loop {
match dec.receive_frame() {
Err(_) => {
had_error = true;
break;
}
Ok(None) => break,
Ok(Some(_)) => {}
}
}
let _ = had_error; }
}
}
}