use crate::{CodecError, CodecResult};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OpusMode {
Silk,
Celt,
Hybrid,
}
impl OpusMode {
#[must_use]
pub const fn as_str(&self) -> &'static str {
match self {
Self::Silk => "SILK",
Self::Celt => "CELT",
Self::Hybrid => "Hybrid",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OpusBandwidth {
Narrowband,
Mediumband,
Wideband,
SuperWideband,
Fullband,
}
impl OpusBandwidth {
#[must_use]
pub const fn max_frequency(&self) -> u32 {
match self {
Self::Narrowband => 4000,
Self::Mediumband => 6000,
Self::Wideband => 8000,
Self::SuperWideband => 12000,
Self::Fullband => 20000,
}
}
#[must_use]
pub const fn from_config(config: u8) -> Self {
match config {
0 => Self::Narrowband,
1 => Self::Mediumband,
2 => Self::Wideband,
3 => Self::SuperWideband,
_ => Self::Fullband,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FrameConfig {
Single,
Double,
DoubleDifferent,
Multiple {
count: u8,
},
Arbitrary {
count: u8,
},
}
#[derive(Debug, Clone)]
pub struct TocInfo {
pub mode: OpusMode,
pub bandwidth: OpusBandwidth,
pub frame_size: u16,
pub config: FrameConfig,
pub is_stereo: bool,
}
impl TocInfo {
pub fn parse(toc: u8) -> CodecResult<Self> {
let config = toc >> 3;
let frame_type = (toc >> 3) & 0x1F;
let is_stereo = (toc & 0x04) != 0;
let frame_code = toc & 0x03;
let (mode, bandwidth) = Self::decode_config(config)?;
let frame_size = Self::decode_frame_size(config)?;
let config = match frame_code {
0 => FrameConfig::Single,
1 => FrameConfig::Double,
2 => FrameConfig::DoubleDifferent,
3 => FrameConfig::Arbitrary { count: 0 }, _ => {
return Err(CodecError::InvalidData(format!(
"Invalid frame code: {frame_code}"
)))
}
};
Ok(Self {
mode,
bandwidth,
frame_size,
config,
is_stereo,
})
}
fn decode_config(config: u8) -> CodecResult<(OpusMode, OpusBandwidth)> {
let mode = if config < 12 {
OpusMode::Silk
} else if config < 16 {
OpusMode::Hybrid
} else {
OpusMode::Celt
};
let bandwidth = match config {
0..=11 => {
let bw_config = config >> 2;
OpusBandwidth::from_config(bw_config)
}
12..=15 => {
let bw_config = config - 12;
OpusBandwidth::from_config(bw_config + 3) }
16..=31 => {
let bw_config = (config - 16) >> 2;
OpusBandwidth::from_config(bw_config)
}
_ => {
return Err(CodecError::InvalidData(format!(
"Invalid configuration: {config}"
)))
}
};
Ok((mode, bandwidth))
}
fn decode_frame_size(config: u8) -> CodecResult<u16> {
let size = match config {
0..=15 => {
let size_code = config & 0x03;
match size_code {
0 => 480, 1 => 960, 2 => 1920, 3 => 2880, _ => unreachable!(),
}
}
16..=31 => {
let size_code = config & 0x03;
match size_code {
0 => 120, 1 => 240, 2 => 480, 3 => 960, _ => unreachable!(),
}
}
_ => {
return Err(CodecError::InvalidData(format!(
"Invalid configuration: {config}"
)))
}
};
Ok(size)
}
}
#[derive(Debug, Clone)]
pub struct OpusPacket<'a> {
pub toc: TocInfo,
pub frames: Vec<&'a [u8]>,
pub padding: usize,
}
impl<'a> OpusPacket<'a> {
pub fn parse(data: &'a [u8]) -> CodecResult<Self> {
if data.is_empty() {
return Err(CodecError::InvalidData("Empty packet".to_string()));
}
let toc = TocInfo::parse(data[0])?;
let mut pos = 1;
let (frames, padding) = match toc.config {
FrameConfig::Single => {
let frame_data = &data[pos..];
(vec![frame_data], 0)
}
FrameConfig::Double => {
let remaining = data.len() - pos;
if remaining % 2 != 0 {
return Err(CodecError::InvalidData(
"Invalid double frame size".to_string(),
));
}
let frame_size = remaining / 2;
let frame1 = &data[pos..pos + frame_size];
let frame2 = &data[pos + frame_size..pos + 2 * frame_size];
(vec![frame1, frame2], 0)
}
FrameConfig::DoubleDifferent => {
if pos >= data.len() {
return Err(CodecError::InvalidData("Truncated packet".to_string()));
}
let size1 = data[pos] as usize;
pos += 1;
if pos + size1 >= data.len() {
return Err(CodecError::InvalidData("Invalid frame size".to_string()));
}
let frame1 = &data[pos..pos + size1];
pos += size1;
let frame2 = &data[pos..];
(vec![frame1, frame2], 0)
}
FrameConfig::Multiple { count } | FrameConfig::Arbitrary { count } => {
let frame_data = &data[pos..];
(vec![frame_data], 0)
}
};
Ok(Self {
toc,
frames,
padding,
})
}
#[must_use]
pub fn frame_count(&self) -> usize {
self.frames.len()
}
#[must_use]
pub fn total_size(&self) -> usize {
self.frames.iter().map(|f| f.len()).sum()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_opus_bandwidth() {
assert_eq!(OpusBandwidth::Narrowband.max_frequency(), 4000);
assert_eq!(OpusBandwidth::Wideband.max_frequency(), 8000);
assert_eq!(OpusBandwidth::Fullband.max_frequency(), 20000);
}
#[test]
fn test_toc_parse() {
let toc = TocInfo::parse(0x00).expect("should succeed");
assert_eq!(toc.mode, OpusMode::Silk);
assert_eq!(toc.bandwidth, OpusBandwidth::Narrowband);
assert_eq!(toc.frame_size, 480);
assert!(!toc.is_stereo);
}
#[test]
fn test_packet_parse_single() {
let data = vec![0x00, 0x01, 0x02, 0x03];
let packet = OpusPacket::parse(&data).expect("should succeed");
assert_eq!(packet.frame_count(), 1);
assert_eq!(packet.frames[0].len(), 3);
}
#[test]
fn test_packet_parse_double() {
let data = vec![0x01, 0xAA, 0xBB, 0xCC, 0xDD];
let packet = OpusPacket::parse(&data).expect("should succeed");
assert_eq!(packet.frame_count(), 2);
assert_eq!(packet.frames[0].len(), 2);
assert_eq!(packet.frames[1].len(), 2);
}
#[test]
fn test_empty_packet() {
let data: Vec<u8> = vec![];
let result = OpusPacket::parse(&data);
assert!(result.is_err());
}
}