use oxideav_core::{Error, Result};
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum OpusMode {
SilkOnly,
Hybrid,
CeltOnly,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum OpusBandwidth {
Narrowband,
Mediumband,
Wideband,
SuperWideband,
Fullband,
}
impl OpusBandwidth {
pub fn cutoff_hz(self) -> u32 {
match self {
Self::Narrowband => 4_000,
Self::Mediumband => 6_000,
Self::Wideband => 8_000,
Self::SuperWideband => 12_000,
Self::Fullband => 20_000,
}
}
}
#[derive(Copy, Clone, Debug)]
pub struct Toc {
pub config: u8,
pub mode: OpusMode,
pub bandwidth: OpusBandwidth,
pub frame_samples_48k: u32,
pub stereo: bool,
pub code: u8,
}
impl Toc {
pub fn parse(b: u8) -> Self {
let config = b >> 3;
let stereo = (b >> 2) & 1 != 0;
let code = b & 0x3;
let (mode, bandwidth, frame_samples_48k) = decode_config(config);
Self {
config,
mode,
bandwidth,
frame_samples_48k,
stereo,
code,
}
}
pub fn channels(&self) -> u16 {
if self.stereo {
2
} else {
1
}
}
}
fn decode_config(config: u8) -> (OpusMode, OpusBandwidth, u32) {
match config {
0 => (OpusMode::SilkOnly, OpusBandwidth::Narrowband, 480),
1 => (OpusMode::SilkOnly, OpusBandwidth::Narrowband, 960),
2 => (OpusMode::SilkOnly, OpusBandwidth::Narrowband, 1920),
3 => (OpusMode::SilkOnly, OpusBandwidth::Narrowband, 2880),
4 => (OpusMode::SilkOnly, OpusBandwidth::Mediumband, 480),
5 => (OpusMode::SilkOnly, OpusBandwidth::Mediumband, 960),
6 => (OpusMode::SilkOnly, OpusBandwidth::Mediumband, 1920),
7 => (OpusMode::SilkOnly, OpusBandwidth::Mediumband, 2880),
8 => (OpusMode::SilkOnly, OpusBandwidth::Wideband, 480),
9 => (OpusMode::SilkOnly, OpusBandwidth::Wideband, 960),
10 => (OpusMode::SilkOnly, OpusBandwidth::Wideband, 1920),
11 => (OpusMode::SilkOnly, OpusBandwidth::Wideband, 2880),
12 => (OpusMode::Hybrid, OpusBandwidth::SuperWideband, 480),
13 => (OpusMode::Hybrid, OpusBandwidth::SuperWideband, 960),
14 => (OpusMode::Hybrid, OpusBandwidth::Fullband, 480),
15 => (OpusMode::Hybrid, OpusBandwidth::Fullband, 960),
16 => (OpusMode::CeltOnly, OpusBandwidth::Narrowband, 120),
17 => (OpusMode::CeltOnly, OpusBandwidth::Narrowband, 240),
18 => (OpusMode::CeltOnly, OpusBandwidth::Narrowband, 480),
19 => (OpusMode::CeltOnly, OpusBandwidth::Narrowband, 960),
20 => (OpusMode::CeltOnly, OpusBandwidth::Wideband, 120),
21 => (OpusMode::CeltOnly, OpusBandwidth::Wideband, 240),
22 => (OpusMode::CeltOnly, OpusBandwidth::Wideband, 480),
23 => (OpusMode::CeltOnly, OpusBandwidth::Wideband, 960),
24 => (OpusMode::CeltOnly, OpusBandwidth::SuperWideband, 120),
25 => (OpusMode::CeltOnly, OpusBandwidth::SuperWideband, 240),
26 => (OpusMode::CeltOnly, OpusBandwidth::SuperWideband, 480),
27 => (OpusMode::CeltOnly, OpusBandwidth::SuperWideband, 960),
28 => (OpusMode::CeltOnly, OpusBandwidth::Fullband, 120),
29 => (OpusMode::CeltOnly, OpusBandwidth::Fullband, 240),
30 => (OpusMode::CeltOnly, OpusBandwidth::Fullband, 480),
31 => (OpusMode::CeltOnly, OpusBandwidth::Fullband, 960),
_ => (OpusMode::CeltOnly, OpusBandwidth::Fullband, 960),
}
}
#[derive(Debug)]
pub struct OpusPacket<'a> {
pub toc: Toc,
pub frames: Vec<&'a [u8]>,
pub padding: &'a [u8],
}
fn read_length(data: &[u8]) -> Result<(usize, usize)> {
let b0 = *data
.first()
.ok_or_else(|| Error::invalid("Opus frame length truncated"))?;
if b0 < 252 {
Ok((b0 as usize, 1))
} else {
let b1 = *data
.get(1)
.ok_or_else(|| Error::invalid("Opus frame length truncated (2-byte)"))?;
Ok(((b1 as usize * 4) + b0 as usize, 2))
}
}
pub fn parse_packet(data: &[u8]) -> Result<OpusPacket<'_>> {
if data.is_empty() {
return Err(Error::invalid("Opus packet empty (needs ≥ 1 TOC byte)"));
}
let toc = Toc::parse(data[0]);
let body = &data[1..];
match toc.code {
0 => Ok(OpusPacket {
toc,
frames: vec![body],
padding: &[],
}),
1 => {
if body.len() % 2 != 0 {
return Err(Error::invalid(
"Opus code-1 packet: body length must be even",
));
}
let half = body.len() / 2;
Ok(OpusPacket {
toc,
frames: vec![&body[..half], &body[half..]],
padding: &[],
})
}
2 => {
let (n1, used) = read_length(body)?;
let rest = &body[used..];
if n1 > rest.len() {
return Err(Error::invalid(
"Opus code-2 packet: frame-1 length exceeds payload",
));
}
Ok(OpusPacket {
toc,
frames: vec![&rest[..n1], &rest[n1..]],
padding: &[],
})
}
3 => parse_code3(toc, body),
_ => unreachable!(),
}
}
pub fn parse_self_delimited_packet(data: &[u8]) -> Result<(OpusPacket<'_>, usize)> {
if data.is_empty() {
return Err(Error::invalid("self-delimited Opus packet empty"));
}
let toc = Toc::parse(data[0]);
let mut cursor = 1usize;
match toc.code {
0 => {
let (n, used) = read_length(&data[cursor..])?;
cursor += used;
if cursor + n > data.len() {
return Err(Error::invalid(
"self-delimited code-0: frame exceeds buffer",
));
}
let frames = vec![&data[cursor..cursor + n]];
cursor += n;
Ok((
OpusPacket {
toc,
frames,
padding: &[],
},
cursor,
))
}
1 => {
let (n, used) = read_length(&data[cursor..])?;
cursor += used;
if cursor + 2 * n > data.len() {
return Err(Error::invalid(
"self-delimited code-1: 2 frames exceed buffer",
));
}
let f1 = &data[cursor..cursor + n];
let f2 = &data[cursor + n..cursor + 2 * n];
cursor += 2 * n;
Ok((
OpusPacket {
toc,
frames: vec![f1, f2],
padding: &[],
},
cursor,
))
}
2 => {
let (n1, u1) = read_length(&data[cursor..])?;
cursor += u1;
let (n2, u2) = read_length(&data[cursor..])?;
cursor += u2;
if cursor + n1 + n2 > data.len() {
return Err(Error::invalid(
"self-delimited code-2: frames exceed buffer",
));
}
let f1 = &data[cursor..cursor + n1];
let f2 = &data[cursor + n1..cursor + n1 + n2];
cursor += n1 + n2;
Ok((
OpusPacket {
toc,
frames: vec![f1, f2],
padding: &[],
},
cursor,
))
}
3 => {
let body = &data[cursor..];
if body.is_empty() {
return Err(Error::invalid(
"self-delimited code-3: missing frame-count byte",
));
}
let fc = body[0];
let vbr = fc & 0x80 != 0;
let has_padding = fc & 0x40 != 0;
let n_frames = (fc & 0x3F) as usize;
if n_frames == 0 {
return Err(Error::invalid("self-delimited code-3: 0 frames"));
}
let mut body_cur = 1usize;
let mut pad_bytes = 0usize;
if has_padding {
loop {
if body_cur >= body.len() {
return Err(Error::invalid(
"self-delimited code-3: padding length truncated",
));
}
let p = body[body_cur];
body_cur += 1;
if p == 255 {
pad_bytes += 254;
} else {
pad_bytes += p as usize;
break;
}
}
}
let mut frame_sizes: Vec<usize> = Vec::with_capacity(n_frames);
if vbr {
for _ in 0..n_frames {
let (len, used) = read_length(&body[body_cur..])?;
frame_sizes.push(len);
body_cur += used;
}
} else {
let (len, used) = read_length(&body[body_cur..])?;
body_cur += used;
for _ in 0..n_frames {
frame_sizes.push(len);
}
}
let total_frame_bytes: usize = frame_sizes.iter().sum();
if body_cur + total_frame_bytes + pad_bytes > body.len() {
return Err(Error::invalid(
"self-delimited code-3: frame data exceeds buffer",
));
}
let data_start_in_body = body_cur;
let mut frames: Vec<&[u8]> = Vec::with_capacity(n_frames);
let mut pos = data_start_in_body;
for &sz in &frame_sizes {
frames.push(&body[pos..pos + sz]);
pos += sz;
}
let padding_start = pos;
let consumed = cursor + padding_start + pad_bytes;
Ok((
OpusPacket {
toc,
frames,
padding: &body[padding_start..padding_start + pad_bytes],
},
consumed,
))
}
_ => unreachable!(),
}
}
fn parse_code3<'a>(toc: Toc, body: &'a [u8]) -> Result<OpusPacket<'a>> {
if body.is_empty() {
return Err(Error::invalid(
"Opus code-3 packet: missing frame-count byte",
));
}
let fc = body[0];
let vbr = fc & 0x80 != 0;
let has_padding = fc & 0x40 != 0;
let n_frames = (fc & 0x3F) as usize;
if n_frames == 0 {
return Err(Error::invalid("Opus code-3 packet: frame count is 0"));
}
if n_frames > 48 {
return Err(Error::invalid(
"Opus code-3 packet: frame count exceeds 48 (>120 ms)",
));
}
let samples_per = toc.frame_samples_48k as usize;
if samples_per * n_frames > 5760 {
return Err(Error::invalid(
"Opus code-3 packet: total duration > 120 ms",
));
}
let mut cursor = 1usize;
let mut pad_bytes = 0usize;
if has_padding {
loop {
if cursor >= body.len() {
return Err(Error::invalid(
"Opus code-3 packet: padding length truncated",
));
}
let p = body[cursor];
cursor += 1;
if p == 255 {
pad_bytes += 254;
} else {
pad_bytes += p as usize;
break;
}
}
}
let mut frame_sizes = Vec::with_capacity(n_frames);
if vbr {
for _ in 0..n_frames - 1 {
let (len, used) = read_length(&body[cursor..])?;
frame_sizes.push(len);
cursor += used;
}
}
let data_end = body
.len()
.checked_sub(pad_bytes)
.ok_or_else(|| Error::invalid("Opus code-3 packet: padding exceeds body"))?;
if cursor > data_end {
return Err(Error::invalid(
"Opus code-3 packet: headers overflow past data region",
));
}
let data_region = &body[cursor..data_end];
if !vbr {
if data_region.len() % n_frames != 0 {
return Err(Error::invalid(
"Opus code-3 CBR packet: body not divisible by frame count",
));
}
let each = data_region.len() / n_frames;
for _ in 0..n_frames {
frame_sizes.push(each);
}
} else {
let explicit_total: usize = frame_sizes.iter().sum();
if explicit_total > data_region.len() {
return Err(Error::invalid(
"Opus code-3 VBR packet: coded lengths exceed body size",
));
}
frame_sizes.push(data_region.len() - explicit_total);
}
let mut frames = Vec::with_capacity(n_frames);
let mut pos = 0usize;
for &sz in &frame_sizes {
if pos + sz > data_region.len() {
return Err(Error::invalid(
"Opus code-3 packet: frame slice runs past body",
));
}
frames.push(&data_region[pos..pos + sz]);
pos += sz;
}
Ok(OpusPacket {
toc,
frames,
padding: &body[data_end..],
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn toc_config_celt_20ms_fb_stereo() {
let b = (31 << 3) | (1 << 2);
let t = Toc::parse(b);
assert_eq!(t.config, 31);
assert_eq!(t.mode, OpusMode::CeltOnly);
assert_eq!(t.bandwidth, OpusBandwidth::Fullband);
assert_eq!(t.frame_samples_48k, 960);
assert!(t.stereo);
assert_eq!(t.code, 0);
}
#[test]
fn toc_silk_nb_mono() {
let t = Toc::parse(0);
assert_eq!(t.mode, OpusMode::SilkOnly);
assert_eq!(t.bandwidth, OpusBandwidth::Narrowband);
assert!(!t.stereo);
}
#[test]
fn toc_hybrid_fb() {
let t = Toc::parse(15 << 3);
assert_eq!(t.mode, OpusMode::Hybrid);
assert_eq!(t.bandwidth, OpusBandwidth::Fullband);
assert_eq!(t.frame_samples_48k, 960);
}
#[test]
fn parse_code0_single_frame() {
let mut p = vec![31u8 << 3]; p.extend_from_slice(&[0xAA, 0xBB, 0xCC]);
let pkt = parse_packet(&p).unwrap();
assert_eq!(pkt.frames.len(), 1);
assert_eq!(pkt.frames[0], &[0xAA, 0xBB, 0xCC]);
}
#[test]
fn parse_code1_two_equal_frames() {
let mut p = vec![(31u8 << 3) | 1];
p.extend_from_slice(&[1, 2, 3, 4]);
let pkt = parse_packet(&p).unwrap();
assert_eq!(pkt.frames.len(), 2);
assert_eq!(pkt.frames[0], &[1, 2]);
assert_eq!(pkt.frames[1], &[3, 4]);
}
#[test]
fn parse_code1_rejects_odd_body() {
let mut p = vec![(31u8 << 3) | 1];
p.extend_from_slice(&[1, 2, 3]);
assert!(parse_packet(&p).is_err());
}
#[test]
fn parse_code2_two_frames_short_length() {
let mut p = vec![(31u8 << 3) | 2, 3];
p.extend_from_slice(&[1, 2, 3]); p.extend_from_slice(&[9, 9]); let pkt = parse_packet(&p).unwrap();
assert_eq!(pkt.frames.len(), 2);
assert_eq!(pkt.frames[0], &[1, 2, 3]);
assert_eq!(pkt.frames[1], &[9, 9]);
}
#[test]
fn parse_code2_two_frames_long_length() {
let mut p = vec![(31u8 << 3) | 2, 252, 1];
p.extend(std::iter::repeat_n(0x5A, 256));
p.extend_from_slice(&[0xAA, 0xBB]);
let pkt = parse_packet(&p).unwrap();
assert_eq!(pkt.frames.len(), 2);
assert_eq!(pkt.frames[0].len(), 256);
assert_eq!(pkt.frames[1], &[0xAA, 0xBB]);
}
#[test]
fn parse_code3_cbr_three_frames() {
let mut p = vec![(16u8 << 3) | 3, 3];
p.extend_from_slice(&[1, 2, 3, 4, 5, 6]);
let pkt = parse_packet(&p).unwrap();
assert_eq!(pkt.frames.len(), 3);
assert_eq!(pkt.frames[0], &[1, 2]);
assert_eq!(pkt.frames[1], &[3, 4]);
assert_eq!(pkt.frames[2], &[5, 6]);
}
#[test]
fn parse_code3_vbr_three_frames_with_padding() {
let mut p = vec![(16u8 << 3) | 3, 0xC3, 2, 1, 2, 0xA];
p.extend_from_slice(&[0xB, 0xC]);
p.extend_from_slice(&[0xD, 0xD, 0xD, 0xD]);
p.extend_from_slice(&[0, 0]);
let pkt = parse_packet(&p).unwrap();
assert_eq!(pkt.frames.len(), 3);
assert_eq!(pkt.frames[0], &[0xA]);
assert_eq!(pkt.frames[1], &[0xB, 0xC]);
assert_eq!(pkt.frames[2], &[0xD, 0xD, 0xD, 0xD]);
assert_eq!(pkt.padding, &[0, 0]);
}
#[test]
fn parse_code3_rejects_zero_frames() {
let p = vec![(16u8 << 3) | 3, 0x00];
assert!(parse_packet(&p).is_err());
}
#[test]
fn parse_empty_packet_errors() {
assert!(parse_packet(&[]).is_err());
}
#[test]
fn parse_self_delimited_code0() {
let mut p = vec![31u8 << 3, 3, 0xAA, 0xBB, 0xCC];
p.extend_from_slice(&[0xFF, 0xFF]);
let (pkt, consumed) = parse_self_delimited_packet(&p).unwrap();
assert_eq!(pkt.frames.len(), 1);
assert_eq!(pkt.frames[0], &[0xAA, 0xBB, 0xCC]);
assert_eq!(consumed, 5);
}
#[test]
fn parse_self_delimited_code1() {
let mut p = vec![(31u8 << 3) | 1, 2, 0x10, 0x20, 0x30, 0x40];
p.extend_from_slice(&[0x99]);
let (pkt, consumed) = parse_self_delimited_packet(&p).unwrap();
assert_eq!(pkt.frames.len(), 2);
assert_eq!(pkt.frames[0], &[0x10, 0x20]);
assert_eq!(pkt.frames[1], &[0x30, 0x40]);
assert_eq!(consumed, 6); }
#[test]
fn parse_self_delimited_code2() {
let mut p = vec![(31u8 << 3) | 2, 1, 3, 0xAA, 0xBB, 0xBB, 0xBB];
p.extend_from_slice(&[0x00, 0x00]);
let (pkt, consumed) = parse_self_delimited_packet(&p).unwrap();
assert_eq!(pkt.frames.len(), 2);
assert_eq!(pkt.frames[0], &[0xAA]);
assert_eq!(pkt.frames[1], &[0xBB, 0xBB, 0xBB]);
assert_eq!(consumed, 7);
}
#[test]
fn parse_self_delimited_code3_cbr() {
let mut p = vec![(16u8 << 3) | 3, 3, 2, 1, 2, 3, 4, 5, 6];
p.push(0xEE);
let (pkt, consumed) = parse_self_delimited_packet(&p).unwrap();
assert_eq!(pkt.frames.len(), 3);
assert_eq!(pkt.frames[0], &[1, 2]);
assert_eq!(pkt.frames[1], &[3, 4]);
assert_eq!(pkt.frames[2], &[5, 6]);
assert_eq!(consumed, 9); }
#[test]
fn parse_self_delimited_code3_vbr() {
let p = vec![(16u8 << 3) | 3, 0x83, 1, 2, 3, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF];
let (pkt, consumed) = parse_self_delimited_packet(&p).unwrap();
assert_eq!(pkt.frames.len(), 3);
assert_eq!(pkt.frames[0], &[0xA]);
assert_eq!(pkt.frames[1], &[0xB, 0xC]);
assert_eq!(pkt.frames[2], &[0xD, 0xE, 0xF]);
assert_eq!(consumed, 11);
}
}