use crate::silk_header::silk_frame_count;
use crate::toc::{Bandwidth, ChannelMapping, Mode, OpusTocByte};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OperatingMode {
SilkOnly,
Hybrid,
CeltOnly,
}
impl From<Mode> for OperatingMode {
fn from(mode: Mode) -> Self {
match mode {
Mode::SilkOnly => OperatingMode::SilkOnly,
Mode::Hybrid => OperatingMode::Hybrid,
Mode::CeltOnly => OperatingMode::CeltOnly,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SilkBandwidth {
Nb,
Mb,
Wb,
}
impl SilkBandwidth {
pub fn to_bandwidth(self) -> Bandwidth {
match self {
SilkBandwidth::Nb => Bandwidth::Nb,
SilkBandwidth::Mb => Bandwidth::Mb,
SilkBandwidth::Wb => Bandwidth::Wb,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct OpusFrameRouting {
pub operating_mode: OperatingMode,
pub toc_bandwidth: Bandwidth,
pub frame_size_tenths_ms: u16,
pub channels: ChannelMapping,
pub silk_layer: bool,
pub celt_layer: bool,
pub silk_bandwidth: Option<SilkBandwidth>,
pub silk_frames_per_channel: Option<u8>,
}
impl OpusFrameRouting {
pub fn from_toc(toc: OpusTocByte) -> Self {
let operating_mode = OperatingMode::from(toc.mode);
let silk_layer = matches!(
operating_mode,
OperatingMode::SilkOnly | OperatingMode::Hybrid
);
let celt_layer = matches!(
operating_mode,
OperatingMode::Hybrid | OperatingMode::CeltOnly
);
let silk_bandwidth = if silk_layer {
match operating_mode {
OperatingMode::Hybrid => Some(SilkBandwidth::Wb),
OperatingMode::SilkOnly => Some(match toc.bandwidth {
Bandwidth::Nb => SilkBandwidth::Nb,
Bandwidth::Mb => SilkBandwidth::Mb,
Bandwidth::Wb => SilkBandwidth::Wb,
Bandwidth::Swb | Bandwidth::Fb => SilkBandwidth::Wb,
}),
OperatingMode::CeltOnly => None,
}
} else {
None
};
let silk_frames_per_channel = if silk_layer {
Some(silk_frame_count(toc.frame_size_tenths_ms).unwrap_or(1))
} else {
None
};
Self {
operating_mode,
toc_bandwidth: toc.bandwidth,
frame_size_tenths_ms: toc.frame_size_tenths_ms,
channels: toc.channels,
silk_layer,
celt_layer,
silk_bandwidth,
silk_frames_per_channel,
}
}
pub fn channel_count(&self) -> u8 {
match self.channels {
ChannelMapping::Mono => 1,
ChannelMapping::Stereo => 2,
}
}
pub fn total_silk_frames(&self) -> u8 {
match self.silk_frames_per_channel {
Some(n) => self.channel_count() * n,
None => 0,
}
}
pub fn has_per_frame_lbrr_bits(&self) -> bool {
self.silk_layer && matches!(self.frame_size_tenths_ms, 400 | 600)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::toc::{Bandwidth, ChannelMapping, FrameCountCode, Mode};
fn route(config: u8, stereo: bool) -> OpusFrameRouting {
let byte = (config << 3) | (if stereo { 1 << 2 } else { 0 });
OpusFrameRouting::from_toc(OpusTocByte::from_byte(byte))
}
#[test]
fn operating_mode_from_mode_total() {
assert_eq!(OperatingMode::from(Mode::SilkOnly), OperatingMode::SilkOnly);
assert_eq!(OperatingMode::from(Mode::Hybrid), OperatingMode::Hybrid);
assert_eq!(OperatingMode::from(Mode::CeltOnly), OperatingMode::CeltOnly);
}
#[test]
fn silk_bandwidth_to_bandwidth() {
assert_eq!(SilkBandwidth::Nb.to_bandwidth(), Bandwidth::Nb);
assert_eq!(SilkBandwidth::Mb.to_bandwidth(), Bandwidth::Mb);
assert_eq!(SilkBandwidth::Wb.to_bandwidth(), Bandwidth::Wb);
}
#[test]
fn silk_only_routing_matches_table2() {
let expected: [(Bandwidth, u16, SilkBandwidth, u8); 12] = [
(Bandwidth::Nb, 100, SilkBandwidth::Nb, 1),
(Bandwidth::Nb, 200, SilkBandwidth::Nb, 1),
(Bandwidth::Nb, 400, SilkBandwidth::Nb, 2),
(Bandwidth::Nb, 600, SilkBandwidth::Nb, 3),
(Bandwidth::Mb, 100, SilkBandwidth::Mb, 1),
(Bandwidth::Mb, 200, SilkBandwidth::Mb, 1),
(Bandwidth::Mb, 400, SilkBandwidth::Mb, 2),
(Bandwidth::Mb, 600, SilkBandwidth::Mb, 3),
(Bandwidth::Wb, 100, SilkBandwidth::Wb, 1),
(Bandwidth::Wb, 200, SilkBandwidth::Wb, 1),
(Bandwidth::Wb, 400, SilkBandwidth::Wb, 2),
(Bandwidth::Wb, 600, SilkBandwidth::Wb, 3),
];
for (config, &(bw, dur, silk_bw, n)) in expected.iter().enumerate() {
let r = route(config as u8, false);
assert_eq!(r.operating_mode, OperatingMode::SilkOnly, "config {config}");
assert_eq!(r.toc_bandwidth, bw, "config {config}");
assert_eq!(r.frame_size_tenths_ms, dur, "config {config}");
assert!(r.silk_layer, "config {config}");
assert!(!r.celt_layer, "config {config}");
assert_eq!(r.silk_bandwidth, Some(silk_bw), "config {config}");
assert_eq!(r.silk_frames_per_channel, Some(n), "config {config}");
}
}
#[test]
fn hybrid_routing_pins_silk_to_wb() {
let expected: [(Bandwidth, u16, u8); 4] = [
(Bandwidth::Swb, 100, 1),
(Bandwidth::Swb, 200, 1),
(Bandwidth::Fb, 100, 1),
(Bandwidth::Fb, 200, 1),
];
for (i, &(bw, dur, n)) in expected.iter().enumerate() {
let config = 12 + i as u8;
let r = route(config, false);
assert_eq!(r.operating_mode, OperatingMode::Hybrid, "config {config}");
assert_eq!(r.toc_bandwidth, bw, "config {config}");
assert_eq!(r.frame_size_tenths_ms, dur, "config {config}");
assert!(r.silk_layer, "config {config}");
assert!(r.celt_layer, "config {config}");
assert_eq!(r.silk_bandwidth, Some(SilkBandwidth::Wb), "config {config}");
assert_eq!(r.silk_frames_per_channel, Some(n), "config {config}");
}
}
#[test]
fn celt_only_routing_has_no_silk() {
for config in 16u8..32 {
for stereo in [false, true] {
let r = route(config, stereo);
assert_eq!(
r.operating_mode,
OperatingMode::CeltOnly,
"c={config} s={stereo}"
);
assert!(!r.silk_layer, "c={config} s={stereo}");
assert!(r.celt_layer, "c={config} s={stereo}");
assert_eq!(r.silk_bandwidth, None);
assert_eq!(r.silk_frames_per_channel, None);
assert_eq!(r.total_silk_frames(), 0);
assert!(!r.has_per_frame_lbrr_bits());
}
}
}
#[test]
fn celt_only_frame_sizes_cover_25_to_200() {
for (k, &dur) in [25u16, 50, 100, 200].iter().enumerate() {
let r = route(16 + k as u8, false);
assert_eq!(r.frame_size_tenths_ms, dur);
assert_eq!(r.silk_frames_per_channel, None);
}
}
#[test]
fn channel_count_doubles_total_silk_frames_in_stereo() {
for config in 0u8..12 {
let mono = route(config, false);
let stereo = route(config, true);
assert_eq!(mono.channel_count(), 1);
assert_eq!(stereo.channel_count(), 2);
assert_eq!(mono.silk_frames_per_channel, stereo.silk_frames_per_channel);
assert_eq!(stereo.total_silk_frames(), 2 * mono.total_silk_frames());
}
}
#[test]
fn per_frame_lbrr_gate_matches_section_4_2_4() {
for config in 0u8..32 {
let r = route(config, false);
let expected = r.silk_layer && matches!(r.frame_size_tenths_ms, 400 | 600);
assert_eq!(r.has_per_frame_lbrr_bits(), expected, "config {config}");
}
}
#[test]
fn total_silk_frames_formula() {
for config in 0u8..32 {
for stereo in [false, true] {
let r = route(config, stereo);
match r.silk_frames_per_channel {
Some(n) => assert_eq!(
r.total_silk_frames(),
r.channel_count() * n,
"config {config} stereo {stereo}"
),
None => assert_eq!(r.total_silk_frames(), 0),
}
}
}
}
#[test]
fn worked_example_60ms_stereo_hybrid() {
let r = route(13, true);
assert_eq!(r.operating_mode, OperatingMode::Hybrid);
assert_eq!(r.toc_bandwidth, Bandwidth::Swb);
assert_eq!(r.frame_size_tenths_ms, 200);
assert_eq!(r.silk_bandwidth, Some(SilkBandwidth::Wb));
assert_eq!(r.channel_count(), 2);
assert_eq!(r.silk_frames_per_channel, Some(1));
assert_eq!(r.total_silk_frames(), 2);
assert!(!r.has_per_frame_lbrr_bits());
let r60 = route(11, true);
assert_eq!(r60.operating_mode, OperatingMode::SilkOnly);
assert_eq!(r60.frame_size_tenths_ms, 600);
assert_eq!(r60.silk_bandwidth, Some(SilkBandwidth::Wb));
assert_eq!(r60.silk_frames_per_channel, Some(3));
assert_eq!(r60.channel_count(), 2);
assert_eq!(r60.total_silk_frames(), 6);
assert!(r60.has_per_frame_lbrr_bits());
}
#[test]
fn fields_passed_through_from_toc() {
for config in 0u8..32 {
let base = OpusFrameRouting::from_toc(OpusTocByte::from_byte(config << 3));
for c in 1u8..=3 {
let r = OpusFrameRouting::from_toc(OpusTocByte::from_byte((config << 3) | c));
assert_eq!(r.operating_mode, base.operating_mode);
assert_eq!(r.toc_bandwidth, base.toc_bandwidth);
assert_eq!(r.frame_size_tenths_ms, base.frame_size_tenths_ms);
assert_eq!(r.silk_layer, base.silk_layer);
assert_eq!(r.celt_layer, base.celt_layer);
assert_eq!(r.silk_bandwidth, base.silk_bandwidth);
assert_eq!(r.silk_frames_per_channel, base.silk_frames_per_channel);
}
}
let toc_c3 = OpusTocByte::from_byte(0b11);
assert_eq!(toc_c3.frame_count_code, FrameCountCode::Arbitrary);
}
#[test]
fn channel_mapping_preserved_for_celt_only() {
let mono = route(20, false);
let stereo = route(20, true);
assert_eq!(mono.channels, ChannelMapping::Mono);
assert_eq!(stereo.channels, ChannelMapping::Stereo);
assert_eq!(mono.channel_count(), 1);
assert_eq!(stereo.channel_count(), 2);
}
#[test]
fn invariants_hold_across_all_32_configs() {
for config in 0u8..32 {
for stereo in [false, true] {
let r = route(config, stereo);
assert!(r.silk_layer || r.celt_layer);
assert_eq!(r.silk_layer, r.silk_bandwidth.is_some());
assert_eq!(r.silk_layer, r.silk_frames_per_channel.is_some());
assert_eq!(
matches!(r.operating_mode, OperatingMode::CeltOnly),
!r.silk_layer
);
assert_eq!(
matches!(r.operating_mode, OperatingMode::SilkOnly),
!r.celt_layer
);
assert_eq!(
matches!(r.operating_mode, OperatingMode::Hybrid),
r.silk_layer && r.celt_layer
);
if matches!(r.operating_mode, OperatingMode::Hybrid) {
assert_eq!(r.silk_bandwidth, Some(SilkBandwidth::Wb));
}
}
}
}
}