use lewton::audio::{PreviousWindowRight, read_audio_packet_generic};
use lewton::header::{
HeaderReadError, IdentHeader, SetupHeader, read_header_ident, read_header_setup,
};
use crate::audio::{AudioDecoder, AudioError, AudioFrame};
pub struct VorbisDecoder {
ident: IdentHeader,
setup: SetupHeader,
pwr: PreviousWindowRight,
declared_sample_rate: u32,
#[allow(dead_code)]
declared_channels: u8,
next_pts_us: Option<i64>,
}
impl VorbisDecoder {
pub fn new(
extra_data: Option<&[u8]>,
sample_rate: u32,
channels: u8,
) -> Result<Self, AudioError> {
let extra = extra_data.ok_or_else(|| {
AudioError::Decode(
"vorbis decoder needs CodecPrivate-style setup headers as extra_data".to_string(),
)
})?;
let (ident_bytes, _comment_bytes, setup_bytes) = parse_xiph_lacing(extra)?;
let ident = read_header_ident(ident_bytes)
.map_err(|e| AudioError::Decode(format!("vorbis ident header: {}", header_err(&e))))?;
let cs = if sample_rate == 0 {
ident.audio_sample_rate
} else {
sample_rate
};
let cc = if channels == 0 {
ident.audio_channels
} else {
channels
};
if ident.audio_channels == 0 || ident.audio_channels > 2 {
return Err(AudioError::Unsupported(format!(
"vorbis channel count {} (this decoder routes >2 channels through resampler/encoder which only supports mono/stereo)",
ident.audio_channels
)));
}
let setup = read_header_setup(
setup_bytes,
ident.audio_channels,
(ident.blocksize_0, ident.blocksize_1),
)
.map_err(|e| AudioError::Decode(format!("vorbis setup header: {}", header_err(&e))))?;
Ok(Self {
ident,
setup,
pwr: PreviousWindowRight::new(),
declared_sample_rate: cs,
declared_channels: cc,
next_pts_us: None,
})
}
}
impl AudioDecoder for VorbisDecoder {
fn decode(&mut self, packet: &[u8], pts: i64) -> Result<Vec<AudioFrame>, AudioError> {
if self.next_pts_us.is_none() {
self.next_pts_us = Some(pts);
}
if packet.is_empty() {
return Ok(Vec::new());
}
let decoded: Vec<Vec<f32>> = read_audio_packet_generic::<Vec<Vec<f32>>>(
&self.ident,
&self.setup,
packet,
&mut self.pwr,
)
.map_err(|e| AudioError::Decode(format!("vorbis audio packet: {e:?}")))?;
if decoded.is_empty() {
return Ok(Vec::new());
}
let channels = decoded.len() as u8;
if channels == 0 {
return Ok(Vec::new());
}
let frames_per_channel = decoded[0].len();
if frames_per_channel == 0 {
return Ok(Vec::new());
}
let mut interleaved = Vec::with_capacity(frames_per_channel * channels as usize);
for i in 0..frames_per_channel {
for ch in 0..channels as usize {
let s = decoded[ch][i];
interleaved.push(s.clamp(-1.0, 1.0));
}
}
let pts_us = self.next_pts_us.unwrap_or(pts);
let frame_us = (frames_per_channel as i64 * 1_000_000) / self.declared_sample_rate as i64;
self.next_pts_us = Some(pts_us + frame_us);
Ok(vec![AudioFrame {
samples: interleaved,
sample_rate: self.declared_sample_rate,
channels,
pts: pts_us,
}])
}
fn flush(&mut self) -> Result<Vec<AudioFrame>, AudioError> {
Ok(Vec::new())
}
}
fn parse_xiph_lacing(bytes: &[u8]) -> Result<(&[u8], &[u8], &[u8]), AudioError> {
if bytes.is_empty() {
return Err(AudioError::Decode("vorbis extra_data is empty".to_string()));
}
let n_minus_1 = bytes[0] as usize;
if n_minus_1 != 2 {
return Err(AudioError::Decode(format!(
"vorbis extra_data lacing prefix says n-1={n_minus_1}, expected 2 (3 headers)"
)));
}
let mut cursor = 1usize;
let mut lengths = [0usize; 2];
for slot in lengths.iter_mut() {
let mut total = 0usize;
loop {
if cursor >= bytes.len() {
return Err(AudioError::Decode(
"vorbis extra_data ended inside Xiph lacing length".to_string(),
));
}
let v = bytes[cursor] as usize;
cursor += 1;
total += v;
if v != 0xFF {
break;
}
}
*slot = total;
}
let len0 = lengths[0];
let len1 = lengths[1];
let header_bytes_start = cursor;
if header_bytes_start + len0 + len1 > bytes.len() {
return Err(AudioError::Decode(format!(
"vorbis extra_data: lacing lengths {} + {} + tail exceed buffer ({} bytes after prefix, total {})",
len0,
len1,
bytes.len() - header_bytes_start,
bytes.len()
)));
}
let len2 = bytes.len() - header_bytes_start - len0 - len1;
let h0 = &bytes[header_bytes_start..header_bytes_start + len0];
let h1 = &bytes[header_bytes_start + len0..header_bytes_start + len0 + len1];
let h2 = &bytes[header_bytes_start + len0 + len1..header_bytes_start + len0 + len1 + len2];
Ok((h0, h1, h2))
}
fn header_err(e: &HeaderReadError) -> String {
format!("{e:?}")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn xiph_lacing_parses_simple_three_segment_buffer() {
let mut buf = vec![2u8, 30, 19];
buf.extend(std::iter::repeat(0xAAu8).take(30));
buf.extend(std::iter::repeat(0xBBu8).take(19));
buf.extend(std::iter::repeat(0xCCu8).take(5));
let (a, b, c) = parse_xiph_lacing(&buf).expect("parses");
assert_eq!(a.len(), 30);
assert_eq!(b.len(), 19);
assert_eq!(c.len(), 5);
assert!(a.iter().all(|x| *x == 0xAA));
assert!(b.iter().all(|x| *x == 0xBB));
assert!(c.iter().all(|x| *x == 0xCC));
}
#[test]
fn xiph_lacing_handles_long_runs() {
let mut buf = vec![2u8, 0xFF, 0x05, 0x10];
buf.extend(std::iter::repeat(0u8).take(260));
buf.extend(std::iter::repeat(1u8).take(16));
buf.extend(std::iter::repeat(2u8).take(8));
let (a, b, c) = parse_xiph_lacing(&buf).expect("parses");
assert_eq!(a.len(), 260);
assert_eq!(b.len(), 16);
assert_eq!(c.len(), 8);
}
#[test]
fn xiph_lacing_rejects_wrong_header_count() {
let buf = vec![1u8, 5, 5];
assert!(parse_xiph_lacing(&buf).is_err());
}
#[test]
fn xiph_lacing_rejects_truncated_buffer() {
let buf = vec![2u8, 30, 19, 0, 0]; assert!(parse_xiph_lacing(&buf).is_err());
}
#[test]
fn vorbis_decoder_rejects_missing_extra_data() {
let r = VorbisDecoder::new(None, 44100, 2);
assert!(matches!(r, Err(AudioError::Decode(_))));
}
#[test]
fn vorbis_decoder_rejects_garbage_extra_data() {
let extra = vec![2u8, 30, 19, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF];
let r = VorbisDecoder::new(Some(&extra), 44100, 2);
assert!(matches!(r, Err(AudioError::Decode(_))));
}
}