use core::fmt;
use crate::Error;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Mode {
SilkOnly,
Hybrid,
CeltOnly,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Bandwidth {
Nb,
Mb,
Wb,
Swb,
Fb,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ChannelMapping {
Mono,
Stereo,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FrameCountCode {
One,
TwoEqual,
TwoUnequal,
Arbitrary,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct OpusTocByte {
pub config: u8,
pub mode: Mode,
pub bandwidth: Bandwidth,
pub frame_size_tenths_ms: u16,
pub channels: ChannelMapping,
pub frame_count_code: FrameCountCode,
}
impl OpusTocByte {
pub fn parse(packet: &[u8]) -> Result<Self, Error> {
let first = *packet.first().ok_or(Error::EmptyPacket)?;
Ok(Self::from_byte(first))
}
pub fn from_byte(byte: u8) -> Self {
let config = byte >> 3;
let s = (byte >> 2) & 0x01;
let c = byte & 0x03;
let (mode, bandwidth, frame_size_tenths_ms) = decode_config(config);
let channels = if s == 0 {
ChannelMapping::Mono
} else {
ChannelMapping::Stereo
};
let frame_count_code = match c {
0 => FrameCountCode::One,
1 => FrameCountCode::TwoEqual,
2 => FrameCountCode::TwoUnequal,
3 => FrameCountCode::Arbitrary,
_ => unreachable!("c is masked to 2 bits"),
};
Self {
config,
mode,
bandwidth,
frame_size_tenths_ms,
channels,
frame_count_code,
}
}
pub fn frame_count_range(self) -> (u8, u8) {
match self.frame_count_code {
FrameCountCode::One => (1, 1),
FrameCountCode::TwoEqual | FrameCountCode::TwoUnequal => (2, 2),
FrameCountCode::Arbitrary => (1, 48),
}
}
}
impl fmt::Display for Mode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Mode::SilkOnly => "SILK-only",
Mode::Hybrid => "Hybrid",
Mode::CeltOnly => "CELT-only",
})
}
}
impl fmt::Display for Bandwidth {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Bandwidth::Nb => "NB",
Bandwidth::Mb => "MB",
Bandwidth::Wb => "WB",
Bandwidth::Swb => "SWB",
Bandwidth::Fb => "FB",
})
}
}
fn decode_config(config: u8) -> (Mode, Bandwidth, u16) {
debug_assert!(config < 32);
match config {
0 => (Mode::SilkOnly, Bandwidth::Nb, 100),
1 => (Mode::SilkOnly, Bandwidth::Nb, 200),
2 => (Mode::SilkOnly, Bandwidth::Nb, 400),
3 => (Mode::SilkOnly, Bandwidth::Nb, 600),
4 => (Mode::SilkOnly, Bandwidth::Mb, 100),
5 => (Mode::SilkOnly, Bandwidth::Mb, 200),
6 => (Mode::SilkOnly, Bandwidth::Mb, 400),
7 => (Mode::SilkOnly, Bandwidth::Mb, 600),
8 => (Mode::SilkOnly, Bandwidth::Wb, 100),
9 => (Mode::SilkOnly, Bandwidth::Wb, 200),
10 => (Mode::SilkOnly, Bandwidth::Wb, 400),
11 => (Mode::SilkOnly, Bandwidth::Wb, 600),
12 => (Mode::Hybrid, Bandwidth::Swb, 100),
13 => (Mode::Hybrid, Bandwidth::Swb, 200),
14 => (Mode::Hybrid, Bandwidth::Fb, 100),
15 => (Mode::Hybrid, Bandwidth::Fb, 200),
16 => (Mode::CeltOnly, Bandwidth::Nb, 25),
17 => (Mode::CeltOnly, Bandwidth::Nb, 50),
18 => (Mode::CeltOnly, Bandwidth::Nb, 100),
19 => (Mode::CeltOnly, Bandwidth::Nb, 200),
20 => (Mode::CeltOnly, Bandwidth::Wb, 25),
21 => (Mode::CeltOnly, Bandwidth::Wb, 50),
22 => (Mode::CeltOnly, Bandwidth::Wb, 100),
23 => (Mode::CeltOnly, Bandwidth::Wb, 200),
24 => (Mode::CeltOnly, Bandwidth::Swb, 25),
25 => (Mode::CeltOnly, Bandwidth::Swb, 50),
26 => (Mode::CeltOnly, Bandwidth::Swb, 100),
27 => (Mode::CeltOnly, Bandwidth::Swb, 200),
28 => (Mode::CeltOnly, Bandwidth::Fb, 25),
29 => (Mode::CeltOnly, Bandwidth::Fb, 50),
30 => (Mode::CeltOnly, Bandwidth::Fb, 100),
31 => (Mode::CeltOnly, Bandwidth::Fb, 200),
_ => unreachable!("config is masked to 5 bits"),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn table2_all_32_configs() {
let expected: [(Mode, Bandwidth, u16); 32] = [
(Mode::SilkOnly, Bandwidth::Nb, 100),
(Mode::SilkOnly, Bandwidth::Nb, 200),
(Mode::SilkOnly, Bandwidth::Nb, 400),
(Mode::SilkOnly, Bandwidth::Nb, 600),
(Mode::SilkOnly, Bandwidth::Mb, 100),
(Mode::SilkOnly, Bandwidth::Mb, 200),
(Mode::SilkOnly, Bandwidth::Mb, 400),
(Mode::SilkOnly, Bandwidth::Mb, 600),
(Mode::SilkOnly, Bandwidth::Wb, 100),
(Mode::SilkOnly, Bandwidth::Wb, 200),
(Mode::SilkOnly, Bandwidth::Wb, 400),
(Mode::SilkOnly, Bandwidth::Wb, 600),
(Mode::Hybrid, Bandwidth::Swb, 100),
(Mode::Hybrid, Bandwidth::Swb, 200),
(Mode::Hybrid, Bandwidth::Fb, 100),
(Mode::Hybrid, Bandwidth::Fb, 200),
(Mode::CeltOnly, Bandwidth::Nb, 25),
(Mode::CeltOnly, Bandwidth::Nb, 50),
(Mode::CeltOnly, Bandwidth::Nb, 100),
(Mode::CeltOnly, Bandwidth::Nb, 200),
(Mode::CeltOnly, Bandwidth::Wb, 25),
(Mode::CeltOnly, Bandwidth::Wb, 50),
(Mode::CeltOnly, Bandwidth::Wb, 100),
(Mode::CeltOnly, Bandwidth::Wb, 200),
(Mode::CeltOnly, Bandwidth::Swb, 25),
(Mode::CeltOnly, Bandwidth::Swb, 50),
(Mode::CeltOnly, Bandwidth::Swb, 100),
(Mode::CeltOnly, Bandwidth::Swb, 200),
(Mode::CeltOnly, Bandwidth::Fb, 25),
(Mode::CeltOnly, Bandwidth::Fb, 50),
(Mode::CeltOnly, Bandwidth::Fb, 100),
(Mode::CeltOnly, Bandwidth::Fb, 200),
];
for (config, &(mode, bw, dur)) in expected.iter().enumerate() {
let toc = OpusTocByte::from_byte((config as u8) << 3);
assert_eq!(toc.config, config as u8, "config field");
assert_eq!(toc.mode, mode, "config {config}: mode");
assert_eq!(toc.bandwidth, bw, "config {config}: bandwidth");
assert_eq!(
toc.frame_size_tenths_ms, dur,
"config {config}: frame-size (tenths of ms)"
);
}
}
#[test]
fn stereo_bit_independent_of_config_and_code() {
for config in 0u8..32 {
for code in 0u8..4 {
let mono = OpusTocByte::from_byte((config << 3) | code);
let stereo = OpusTocByte::from_byte((config << 3) | (1 << 2) | code);
assert_eq!(mono.channels, ChannelMapping::Mono);
assert_eq!(stereo.channels, ChannelMapping::Stereo);
assert_eq!(mono.config, stereo.config);
assert_eq!(mono.mode, stereo.mode);
assert_eq!(mono.bandwidth, stereo.bandwidth);
assert_eq!(mono.frame_size_tenths_ms, stereo.frame_size_tenths_ms);
assert_eq!(mono.frame_count_code, stereo.frame_count_code);
}
}
}
#[test]
fn frame_count_codes() {
let cases = [
(0u8, FrameCountCode::One, (1u8, 1u8)),
(1, FrameCountCode::TwoEqual, (2, 2)),
(2, FrameCountCode::TwoUnequal, (2, 2)),
(3, FrameCountCode::Arbitrary, (1, 48)),
];
for (c, expected_code, range) in cases {
let toc = OpusTocByte::from_byte(c);
assert_eq!(toc.frame_count_code, expected_code, "c={c}");
assert_eq!(toc.frame_count_range(), range, "c={c} frame-count range");
}
}
#[test]
fn parse_empty_rejects() {
assert_eq!(OpusTocByte::parse(&[]), Err(Error::EmptyPacket));
}
#[test]
fn parse_known_byte() {
let toc = OpusTocByte::parse(&[0x6E, 0x00, 0x00]).unwrap();
assert_eq!(toc.config, 13);
assert_eq!(toc.mode, Mode::Hybrid);
assert_eq!(toc.bandwidth, Bandwidth::Swb);
assert_eq!(toc.frame_size_tenths_ms, 200);
assert_eq!(toc.channels, ChannelMapping::Stereo);
assert_eq!(toc.frame_count_code, FrameCountCode::TwoUnequal);
let toc2 = OpusTocByte::from_byte(0xF8);
assert_eq!(toc2.config, 31);
assert_eq!(toc2.mode, Mode::CeltOnly);
assert_eq!(toc2.bandwidth, Bandwidth::Fb);
assert_eq!(toc2.frame_size_tenths_ms, 200);
assert_eq!(toc2.channels, ChannelMapping::Mono);
assert_eq!(toc2.frame_count_code, FrameCountCode::One);
}
}