use anyhow::{Context as _, Result};
#[cfg(feature = "file-decode")]
const OPUS_MAX_PACKET_SAMPLES: usize = 5760;
#[cfg(feature = "file-decode")]
const OPUS_MAX_FRAMES: usize = 48;
#[cfg(feature = "file-decode")]
pub(crate) fn opus_samples_per_frame(toc: u8) -> usize {
#[rustfmt::skip]
const FRAME_DURATION_48K: [usize; 32] = [
480, 960, 1920, 2880, 480, 960, 1920, 2880, 480, 960, 1920, 2880, 480, 960, 480, 960, 120, 240, 480, 960, 120, 240, 480, 960, 120, 240, 480, 960, 120, 240, 480, 960, ];
FRAME_DURATION_48K[(toc >> 3) as usize]
}
#[cfg(feature = "file-decode")]
pub(crate) struct OpusFraming<'a> {
pub(super) toc: u8,
pub(super) samples_per_frame: usize,
frames: [&'a [u8]; OPUS_MAX_FRAMES],
count: usize,
}
#[cfg(feature = "file-decode")]
impl<'a> OpusFraming<'a> {
pub(super) fn frames(&self) -> &[&'a [u8]] {
&self.frames[..self.count]
}
pub(super) fn packet_samples(&self) -> usize {
self.samples_per_frame * self.count
}
}
#[cfg(feature = "file-decode")]
fn opus_frame_len(data: &[u8]) -> Result<(usize, usize)> {
let first = usize::from(
*data
.first()
.context("Opus packet: truncated frame length")?,
);
if first < 252 {
return Ok((first, 1));
}
let second = usize::from(*data.get(1).context("Opus packet: truncated frame length")?);
Ok((second * 4 + first, 2))
}
#[cfg(feature = "file-decode")]
fn opus_padding_len(data: &[u8]) -> Result<(usize, usize)> {
let mut padding = 0usize;
let mut used = 0usize;
loop {
let byte = usize::from(
*data
.get(used)
.context("Opus packet: truncated padding count")?,
);
used += 1;
if byte == 255 {
padding += 254;
} else {
padding += byte;
return Ok((padding, used));
}
}
}
#[cfg(feature = "file-decode")]
pub(crate) fn split_opus_packet(packet: &[u8]) -> Result<OpusFraming<'_>> {
let toc = *packet.first().context("Opus packet is empty")?;
let payload = &packet[1..];
let mut frames = [&[][..]; OPUS_MAX_FRAMES];
let count = match toc & 0b11 {
0 => {
frames[0] = payload;
1
}
1 => {
if !payload.len().is_multiple_of(2) {
anyhow::bail!("Opus packet: code 1 payload is not evenly divisible");
}
let half = payload.len() / 2;
frames[0] = &payload[..half];
frames[1] = &payload[half..];
2
}
2 => {
let (first_len, used) = opus_frame_len(payload)?;
let rest = payload
.get(used..)
.context("Opus packet: code 2 length overruns packet")?;
if first_len > rest.len() {
anyhow::bail!("Opus packet: code 2 first frame overruns packet");
}
frames[0] = &rest[..first_len];
frames[1] = &rest[first_len..];
2
}
_ => {
let count_byte = *payload
.first()
.context("Opus packet: code 3 is missing its frame count")?;
let count = usize::from(count_byte & 0x3F);
if !(1..=OPUS_MAX_FRAMES).contains(&count) {
anyhow::bail!("Opus packet: code 3 frame count {count} out of range");
}
let vbr = count_byte & 0x80 != 0;
let padded = count_byte & 0x40 != 0;
let mut at = 1usize;
let mut end = payload.len();
if padded {
let (padding, used) = opus_padding_len(
payload
.get(at..)
.context("Opus packet: truncated padding count")?,
)?;
at += used;
end = end
.checked_sub(padding)
.context("Opus packet: padding overruns packet")?;
if at > end {
anyhow::bail!("Opus packet: padding overlaps the frame data");
}
}
let mut sizes = [0usize; OPUS_MAX_FRAMES];
if vbr {
let mut claimed = 0usize;
for size in sizes[..count - 1].iter_mut() {
let (len, used) = opus_frame_len(
payload
.get(at..end)
.context("Opus packet: code 3 lengths overrun packet")?,
)?;
at += used;
*size = len;
claimed += len;
}
let remaining = end
.checked_sub(at)
.context("Opus packet: code 3 lengths overrun packet")?;
if claimed > remaining {
anyhow::bail!("Opus packet: code 3 frame lengths overrun packet");
}
sizes[count - 1] = remaining - claimed;
} else {
let remaining = end
.checked_sub(at)
.context("Opus packet: code 3 header overruns packet")?;
if !remaining.is_multiple_of(count) {
anyhow::bail!(
"Opus packet: code 3 CBR payload of {remaining} does not divide into {count} frames"
);
}
sizes[..count].fill(remaining / count);
}
for (frame, size) in frames[..count].iter_mut().zip(&sizes[..count]) {
let next = at
.checked_add(*size)
.filter(|next| *next <= end)
.context("Opus packet: code 3 frame overruns packet")?;
*frame = &payload[at..next];
at = next;
}
if at != end {
anyhow::bail!("Opus packet: code 3 frames do not fill the packet");
}
count
}
};
let framing = OpusFraming {
toc,
samples_per_frame: opus_samples_per_frame(toc),
frames,
count,
};
if framing.packet_samples() > OPUS_MAX_PACKET_SAMPLES {
anyhow::bail!(
"Opus packet: {} samples exceeds 120 ms",
framing.packet_samples()
);
}
Ok(framing)
}
#[cfg(all(test, feature = "file-decode"))]
mod tests {
use super::*;
fn celt_toc(code: u8) -> u8 {
(31 << 3) | code
}
fn split(packet: &[u8]) -> Result<Vec<Vec<u8>>> {
let framing = split_opus_packet(packet)?;
Ok(framing.frames().iter().map(|f| f.to_vec()).collect())
}
#[test]
fn test_samples_per_frame_covers_the_toc_table() {
assert_eq!(opus_samples_per_frame(0b0000_0000), 480);
assert_eq!(opus_samples_per_frame(0b0001_1000), 2880);
assert_eq!(opus_samples_per_frame(0b0110_0000), 480);
assert_eq!(opus_samples_per_frame(0b1000_0000), 120);
assert_eq!(opus_samples_per_frame(celt_toc(0)), 960);
}
#[test]
fn test_split_code0_is_one_frame() {
let packet = [celt_toc(0), 1, 2, 3];
let framing = split_opus_packet(&packet).expect("code 0");
assert_eq!(framing.frames(), [&[1u8, 2, 3][..]]);
assert_eq!(framing.packet_samples(), 960);
}
#[test]
fn test_split_code1_halves_the_payload() {
assert_eq!(
split(&[celt_toc(1), 1, 2, 3, 4]).expect("code 1"),
vec![vec![1, 2], vec![3, 4]]
);
let packet = [celt_toc(1), 1, 2];
let framing = split_opus_packet(&packet).expect("code 1");
assert_eq!(framing.packet_samples(), 1920);
}
#[test]
fn test_split_code1_odd_payload_is_rejected() {
assert!(split_opus_packet(&[celt_toc(1), 1, 2, 3]).is_err());
}
#[test]
fn test_split_code2_reads_a_one_byte_length() {
assert_eq!(
split(&[celt_toc(2), 2, 1, 2, 3, 4]).expect("code 2"),
vec![vec![1, 2], vec![3, 4]]
);
}
#[test]
fn test_split_code2_reads_a_two_byte_length() {
let first_len = 253 + 4; let mut packet = vec![celt_toc(2), 253, 1];
packet.extend(std::iter::repeat_n(0xAA, first_len));
packet.extend([1, 2, 3]);
let frames = split(&packet).expect("code 2 with a long first frame");
assert_eq!(frames[0].len(), first_len);
assert_eq!(frames[1], vec![1, 2, 3]);
}
#[test]
fn test_split_code2_length_overrunning_the_packet_is_rejected() {
assert!(split_opus_packet(&[celt_toc(2), 9, 1, 2]).is_err());
}
#[test]
fn test_split_code3_cbr_divides_the_payload_evenly() {
let mut packet = vec![celt_toc(3), 3];
packet.extend([1, 2, 3, 4, 5, 6]);
assert_eq!(
split(&packet).expect("code 3 CBR"),
vec![vec![1, 2], vec![3, 4], vec![5, 6]]
);
}
#[test]
fn test_split_code3_cbr_uneven_payload_is_rejected() {
let mut packet = vec![celt_toc(3), 3];
packet.extend([1, 2, 3, 4, 5]);
assert!(split_opus_packet(&packet).is_err());
}
#[test]
fn test_split_code3_vbr_reads_lengths_for_all_but_the_last_frame() {
let mut packet = vec![celt_toc(3), 0x80 | 3, 1, 2];
packet.extend([1, 2, 2, 3, 4, 5]);
assert_eq!(
split(&packet).expect("code 3 VBR"),
vec![vec![1], vec![2, 2], vec![3, 4, 5]]
);
}
#[test]
fn test_split_code3_cbr_strips_padding() {
let mut packet = vec![celt_toc(3), 0x40 | 2, 2];
packet.extend([1, 2, 3, 4]);
packet.extend([0, 0]);
assert_eq!(
split(&packet).expect("code 3 CBR with padding"),
vec![vec![1, 2], vec![3, 4]]
);
}
#[test]
fn test_split_code3_padding_count_of_255_continues() {
let mut packet = vec![celt_toc(3), 0x40 | 1, 255, 1];
packet.extend([7, 7, 7]);
packet.extend(std::iter::repeat_n(0, 255));
assert_eq!(
split(&packet).expect("code 3 with a long padding run"),
vec![vec![7, 7, 7]]
);
}
#[test]
fn test_split_code3_vbr_strips_padding() {
let mut packet = vec![celt_toc(3), 0xC0 | 2, 2, 1];
packet.extend([9, 8, 7]);
packet.extend([0, 0]);
assert_eq!(
split(&packet).expect("code 3 VBR with padding"),
vec![vec![9], vec![8, 7]]
);
}
#[test]
fn test_split_code3_zero_frame_count_is_rejected() {
assert!(split_opus_packet(&[celt_toc(3), 0]).is_err());
}
#[test]
fn test_split_code3_over_120ms_is_rejected() {
let mut packet = vec![celt_toc(3), 7];
packet.extend(std::iter::repeat_n(1, 7));
assert!(split_opus_packet(&packet).is_err());
}
#[test]
fn test_split_code3_truncated_headers_are_rejected() {
assert!(split_opus_packet(&[celt_toc(3)]).is_err());
assert!(split_opus_packet(&[celt_toc(3), 0x40 | 1]).is_err());
assert!(split_opus_packet(&[celt_toc(3), 0x80 | 3, 200, 200, 1]).is_err());
}
#[test]
fn test_split_empty_packet_is_rejected() {
assert!(split_opus_packet(&[]).is_err());
}
}