use crate::repacketizer::samples_per_frame as toc_samples_per_frame;
use crate::toc::{bandwidth_from_toc, channels_from_toc, mode_from_toc};
use crate::{Bandwidth, Error, OpusMode, Result};
pub const MAX_PACKET_SAMPLES: usize = 5760;
fn max_samples(sample_rate: i32) -> usize {
MAX_PACKET_SAMPLES * sample_rate as usize / 48_000
}
pub(crate) fn check_rate(sample_rate: i32) -> Result<()> {
if ![8_000, 12_000, 16_000, 24_000, 48_000].contains(&sample_rate) {
return Err(Error::InvalidArgument(
"sample rate must be 8, 12, 16, 24 or 48 kHz",
));
}
Ok(())
}
pub fn frame_count(packet: &[u8]) -> Result<usize> {
let toc = *packet
.first()
.ok_or(Error::InvalidPacket("packet is empty"))?;
match toc & 0x03 {
0 => Ok(1),
1 | 2 => Ok(2),
_ => {
let n = *packet
.get(1)
.ok_or(Error::InvalidPacket("code 3 packet has no frame count"))?
& 0x3F;
if n == 0 {
return Err(Error::InvalidPacket("code 3 packet declares zero frames"));
}
Ok(n as usize)
}
}
}
pub fn samples(packet: &[u8], sample_rate: i32) -> Result<usize> {
check_rate(sample_rate)?;
let toc = *packet
.first()
.ok_or(Error::InvalidPacket("packet is empty"))?;
let n = frame_count(packet)? * toc_samples_per_frame(toc, sample_rate) as usize;
if n > max_samples(sample_rate) {
return Err(Error::InvalidPacket("packet claims more than 120 ms"));
}
Ok(n)
}
pub fn samples_per_frame(packet: &[u8], sample_rate: i32) -> Result<usize> {
check_rate(sample_rate)?;
let toc = *packet
.first()
.ok_or(Error::InvalidPacket("packet is empty"))?;
Ok(toc_samples_per_frame(toc, sample_rate) as usize)
}
pub fn samples_48k(packet: &[u8]) -> Result<usize> {
samples(packet, 48_000)
}
pub fn mode(packet: &[u8]) -> Result<OpusMode> {
let toc = *packet
.first()
.ok_or(Error::InvalidPacket("packet is empty"))?;
Ok(mode_from_toc(toc))
}
pub fn bandwidth(packet: &[u8]) -> Result<Bandwidth> {
let toc = *packet
.first()
.ok_or(Error::InvalidPacket("packet is empty"))?;
Ok(bandwidth_from_toc(toc))
}
pub fn channels(packet: &[u8]) -> Result<usize> {
let toc = *packet
.first()
.ok_or(Error::InvalidPacket("packet is empty"))?;
Ok(channels_from_toc(toc))
}
#[cfg(test)]
mod tests {
use super::*;
fn toc(config: u8, stereo: bool, code: u8) -> u8 {
(config << 3) | ((stereo as u8) << 2) | code
}
#[test]
fn durations_match_the_toc() {
for (config, ms10) in [(16u8, 25i32), (17, 50), (18, 100), (19, 200)] {
let p = [toc(config, false, 0)];
assert_eq!(
samples_48k(&p).unwrap(),
(48_000 * ms10 / 10_000) as usize,
"config {config}"
);
}
for (config, ms) in [(0u8, 10i32), (1, 20), (2, 40), (3, 60)] {
let p = [toc(config, false, 0)];
assert_eq!(
samples_48k(&p).unwrap(),
(48 * ms) as usize,
"config {config}"
);
}
}
#[test]
fn frame_packing_codes_multiply_the_duration() {
let one = [toc(19, false, 0)]; assert_eq!(samples_48k(&one).unwrap(), 960);
for code in [1u8, 2] {
let two = [toc(19, false, code)];
assert_eq!(samples_48k(&two).unwrap(), 1920, "code {code}");
}
let three = [toc(19, false, 3), 3];
assert_eq!(samples_48k(&three).unwrap(), 2880);
}
#[test]
fn rates_scale_the_answer() {
let p = [toc(19, false, 0)]; for (rate, expect) in [
(8_000, 160),
(12_000, 240),
(16_000, 320),
(24_000, 480),
(48_000, 960),
] {
assert_eq!(samples(&p, rate).unwrap(), expect, "{rate} Hz");
}
assert!(samples(&p, 44_100).is_err(), "44.1 kHz is not an Opus rate");
}
#[test]
fn channels_come_from_the_toc() {
assert_eq!(channels(&[toc(19, false, 0)]).unwrap(), 1);
assert_eq!(channels(&[toc(19, true, 0)]).unwrap(), 2);
}
#[test]
fn malformed_packets_are_rejected() {
assert!(samples_48k(&[]).is_err(), "empty packet");
assert!(frame_count(&[]).is_err(), "empty packet");
assert!(samples_48k(&[toc(19, false, 3)]).is_err());
assert!(samples_48k(&[toc(19, false, 3), 0]).is_err());
assert!(samples_48k(&[toc(19, false, 3), 48]).is_err());
assert_eq!(samples_48k(&[toc(19, false, 3), 6]).unwrap(), 5760);
}
}