Skip to main content

oxideav_opus/
toc.rs

1//! Opus packet TOC byte parser.
2//!
3//! Implements the Table-of-Contents header described in RFC 6716 §3.1.
4//! The first byte of every well-formed Opus packet contains:
5//!
6//! ```text
7//!                              0
8//!                              0 1 2 3 4 5 6 7
9//!                             +-+-+-+-+-+-+-+-+
10//!                             | config  |s| c |
11//!                             +-+-+-+-+-+-+-+-+
12//! ```
13//!
14//! * `config` — five bits (bit 0 is the MSB per RFC 6716 numbering),
15//!   selecting one of 32 (mode, bandwidth, frame-size) tuples per
16//!   Table 2.
17//! * `s` — one bit: 0 = mono, 1 = stereo per Table 3 (informal —
18//!   described in prose immediately after Table 2 in the RFC).
19//! * `c` — two bits: code 0..3, the frame-packing code per Table 4
20//!   (also described in the prose at the end of §3.1).
21//!
22//! Only the TOC byte interpretation is implemented here. Frame
23//! packing per §3.2 and the SILK / CELT inner decoders are out of
24//! scope for round 1.
25
26use core::fmt;
27
28use crate::Error;
29
30/// Operating mode selected by the `config` field of the TOC byte.
31///
32/// Per RFC 6716 §3.1 Table 2 the 32 configurations cluster into
33/// three operating modes: a SILK-only LP mode, a Hybrid SILK+CELT
34/// mode, and a CELT-only MDCT mode.
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub enum Mode {
37    /// LP (SILK) only — used at low bitrates up to WB.
38    SilkOnly,
39    /// Hybrid SILK + CELT — used for SWB / FB speech at medium
40    /// bitrates.
41    Hybrid,
42    /// CELT (MDCT) only — used for very low delay and music
43    /// transmission.
44    CeltOnly,
45}
46
47/// Audio bandwidth selected by the `config` field of the TOC byte.
48///
49/// The five bandwidths per RFC 6716 §2:
50///
51/// * NB — narrowband, 4 kHz effective, 8 kHz sample rate equivalent.
52/// * MB — medium band, 6 kHz effective.
53/// * WB — wideband, 8 kHz effective.
54/// * SWB — super-wideband, 12 kHz effective.
55/// * FB — fullband, 20 kHz effective.
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
57pub enum Bandwidth {
58    Nb,
59    Mb,
60    Wb,
61    Swb,
62    Fb,
63}
64
65/// Channel mapping signalled by the `s` bit of the TOC byte.
66#[derive(Debug, Clone, Copy, PartialEq, Eq)]
67pub enum ChannelMapping {
68    /// `s = 0` — one channel.
69    Mono,
70    /// `s = 1` — two channels.
71    Stereo,
72}
73
74/// Frame-packing code signalled by the `c` field of the TOC byte
75/// (RFC 6716 §3.1, immediately after Table 2).
76#[derive(Debug, Clone, Copy, PartialEq, Eq)]
77pub enum FrameCountCode {
78    /// Code 0 — exactly one frame in the packet.
79    One,
80    /// Code 1 — exactly two frames, both compressed to the same size.
81    TwoEqual,
82    /// Code 2 — exactly two frames with independent compressed sizes.
83    TwoUnequal,
84    /// Code 3 — arbitrary frame count, encoded in a following byte.
85    Arbitrary,
86}
87
88/// Decoded interpretation of a single Opus packet TOC byte.
89///
90/// This does not consume any bytes beyond the TOC byte itself — frame
91/// packing (the second byte for code 3, the length sequence for
92/// code 2, etc.) is the §3.2 layer and lives elsewhere.
93#[derive(Debug, Clone, Copy, PartialEq, Eq)]
94pub struct OpusTocByte {
95    /// Raw `config` value (0..31).
96    pub config: u8,
97    /// Operating mode selected by `config`.
98    pub mode: Mode,
99    /// Audio bandwidth selected by `config`.
100    pub bandwidth: Bandwidth,
101    /// Per-frame duration in tenths of a millisecond.
102    ///
103    /// Encoded in tenths because RFC 6716 allows 2.5 ms (= 25) which
104    /// is not representable in whole milliseconds. The legal values
105    /// per Table 2 are 25, 50, 100, 200, 400, 600.
106    pub frame_size_tenths_ms: u16,
107    /// Channel mapping signalled by `s`.
108    pub channels: ChannelMapping,
109    /// Frame-packing code signalled by `c`.
110    pub frame_count_code: FrameCountCode,
111}
112
113impl OpusTocByte {
114    /// Parse the TOC byte from the front of an Opus packet.
115    ///
116    /// Returns [`Error::EmptyPacket`] if `packet` is empty (RFC 6716
117    /// §3.1 requirement R1). Any non-empty packet has a syntactically
118    /// valid TOC byte by construction (every five-bit `config` value
119    /// is assigned, every `s` and `c` value is assigned), so the only
120    /// failure mode is the empty-packet case.
121    pub fn parse(packet: &[u8]) -> Result<Self, Error> {
122        let first = *packet.first().ok_or(Error::EmptyPacket)?;
123        Ok(Self::from_byte(first))
124    }
125
126    /// Decode an isolated TOC byte. Total function — every `u8` is a
127    /// valid TOC byte.
128    pub fn from_byte(byte: u8) -> Self {
129        // RFC 6716 §3.1 numbers bit 0 as the most significant bit.
130        // In u8 terms (bit 7 = MSB): config is the top 5 bits,
131        // s is bit 2 (counted from LSB), c is the low 2 bits.
132        let config = byte >> 3;
133        let s = (byte >> 2) & 0x01;
134        let c = byte & 0x03;
135
136        let (mode, bandwidth, frame_size_tenths_ms) = decode_config(config);
137        let channels = if s == 0 {
138            ChannelMapping::Mono
139        } else {
140            ChannelMapping::Stereo
141        };
142        let frame_count_code = match c {
143            0 => FrameCountCode::One,
144            1 => FrameCountCode::TwoEqual,
145            2 => FrameCountCode::TwoUnequal,
146            3 => FrameCountCode::Arbitrary,
147            _ => unreachable!("c is masked to 2 bits"),
148        };
149
150        Self {
151            config,
152            mode,
153            bandwidth,
154            frame_size_tenths_ms,
155            channels,
156            frame_count_code,
157        }
158    }
159
160    /// Minimum and maximum frame count the TOC byte implies *without*
161    /// consulting subsequent bytes. Codes 0/1/2 have a known frame
162    /// count; code 3 needs the §3.2.5 frame-count byte to resolve and
163    /// returns the legal `(1, 48)` range here.
164    pub fn frame_count_range(self) -> (u8, u8) {
165        match self.frame_count_code {
166            FrameCountCode::One => (1, 1),
167            FrameCountCode::TwoEqual | FrameCountCode::TwoUnequal => (2, 2),
168            // RFC 6716 §3.2.5: the M field is 1..48 inclusive (R5).
169            FrameCountCode::Arbitrary => (1, 48),
170        }
171    }
172}
173
174impl fmt::Display for Mode {
175    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
176        f.write_str(match self {
177            Mode::SilkOnly => "SILK-only",
178            Mode::Hybrid => "Hybrid",
179            Mode::CeltOnly => "CELT-only",
180        })
181    }
182}
183
184impl fmt::Display for Bandwidth {
185    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
186        f.write_str(match self {
187            Bandwidth::Nb => "NB",
188            Bandwidth::Mb => "MB",
189            Bandwidth::Wb => "WB",
190            Bandwidth::Swb => "SWB",
191            Bandwidth::Fb => "FB",
192        })
193    }
194}
195
196/// Map a five-bit `config` value to the `(mode, bandwidth,
197/// frame-size)` triple per RFC 6716 §3.1 Table 2.
198///
199/// The configuration numbers in each range correspond to the choices
200/// of frame size in the same order — e.g. for SILK-only NB
201/// (configs 0..=3) the frame sizes 10, 20, 40, 60 ms correspond to
202/// configs 0, 1, 2, 3 respectively.
203fn decode_config(config: u8) -> (Mode, Bandwidth, u16) {
204    debug_assert!(config < 32);
205    match config {
206        // SILK-only — 10, 20, 40, 60 ms.
207        0 => (Mode::SilkOnly, Bandwidth::Nb, 100),
208        1 => (Mode::SilkOnly, Bandwidth::Nb, 200),
209        2 => (Mode::SilkOnly, Bandwidth::Nb, 400),
210        3 => (Mode::SilkOnly, Bandwidth::Nb, 600),
211        4 => (Mode::SilkOnly, Bandwidth::Mb, 100),
212        5 => (Mode::SilkOnly, Bandwidth::Mb, 200),
213        6 => (Mode::SilkOnly, Bandwidth::Mb, 400),
214        7 => (Mode::SilkOnly, Bandwidth::Mb, 600),
215        8 => (Mode::SilkOnly, Bandwidth::Wb, 100),
216        9 => (Mode::SilkOnly, Bandwidth::Wb, 200),
217        10 => (Mode::SilkOnly, Bandwidth::Wb, 400),
218        11 => (Mode::SilkOnly, Bandwidth::Wb, 600),
219        // Hybrid — 10, 20 ms.
220        12 => (Mode::Hybrid, Bandwidth::Swb, 100),
221        13 => (Mode::Hybrid, Bandwidth::Swb, 200),
222        14 => (Mode::Hybrid, Bandwidth::Fb, 100),
223        15 => (Mode::Hybrid, Bandwidth::Fb, 200),
224        // CELT-only — 2.5, 5, 10, 20 ms.
225        16 => (Mode::CeltOnly, Bandwidth::Nb, 25),
226        17 => (Mode::CeltOnly, Bandwidth::Nb, 50),
227        18 => (Mode::CeltOnly, Bandwidth::Nb, 100),
228        19 => (Mode::CeltOnly, Bandwidth::Nb, 200),
229        20 => (Mode::CeltOnly, Bandwidth::Wb, 25),
230        21 => (Mode::CeltOnly, Bandwidth::Wb, 50),
231        22 => (Mode::CeltOnly, Bandwidth::Wb, 100),
232        23 => (Mode::CeltOnly, Bandwidth::Wb, 200),
233        24 => (Mode::CeltOnly, Bandwidth::Swb, 25),
234        25 => (Mode::CeltOnly, Bandwidth::Swb, 50),
235        26 => (Mode::CeltOnly, Bandwidth::Swb, 100),
236        27 => (Mode::CeltOnly, Bandwidth::Swb, 200),
237        28 => (Mode::CeltOnly, Bandwidth::Fb, 25),
238        29 => (Mode::CeltOnly, Bandwidth::Fb, 50),
239        30 => (Mode::CeltOnly, Bandwidth::Fb, 100),
240        31 => (Mode::CeltOnly, Bandwidth::Fb, 200),
241        _ => unreachable!("config is masked to 5 bits"),
242    }
243}
244
245#[cfg(test)]
246mod tests {
247    use super::*;
248
249    /// Every five-bit `config` produces the correct (mode, bandwidth,
250    /// frame-size) triple per RFC 6716 §3.1 Table 2.
251    ///
252    /// We assemble the TOC byte as `config<<3 | 0<<2 | 0` (mono,
253    /// code 0) so the parser sees exactly the `config` we expect.
254    #[test]
255    fn table2_all_32_configs() {
256        // Independently encoded expected triples mirroring Table 2.
257        let expected: [(Mode, Bandwidth, u16); 32] = [
258            (Mode::SilkOnly, Bandwidth::Nb, 100),
259            (Mode::SilkOnly, Bandwidth::Nb, 200),
260            (Mode::SilkOnly, Bandwidth::Nb, 400),
261            (Mode::SilkOnly, Bandwidth::Nb, 600),
262            (Mode::SilkOnly, Bandwidth::Mb, 100),
263            (Mode::SilkOnly, Bandwidth::Mb, 200),
264            (Mode::SilkOnly, Bandwidth::Mb, 400),
265            (Mode::SilkOnly, Bandwidth::Mb, 600),
266            (Mode::SilkOnly, Bandwidth::Wb, 100),
267            (Mode::SilkOnly, Bandwidth::Wb, 200),
268            (Mode::SilkOnly, Bandwidth::Wb, 400),
269            (Mode::SilkOnly, Bandwidth::Wb, 600),
270            (Mode::Hybrid, Bandwidth::Swb, 100),
271            (Mode::Hybrid, Bandwidth::Swb, 200),
272            (Mode::Hybrid, Bandwidth::Fb, 100),
273            (Mode::Hybrid, Bandwidth::Fb, 200),
274            (Mode::CeltOnly, Bandwidth::Nb, 25),
275            (Mode::CeltOnly, Bandwidth::Nb, 50),
276            (Mode::CeltOnly, Bandwidth::Nb, 100),
277            (Mode::CeltOnly, Bandwidth::Nb, 200),
278            (Mode::CeltOnly, Bandwidth::Wb, 25),
279            (Mode::CeltOnly, Bandwidth::Wb, 50),
280            (Mode::CeltOnly, Bandwidth::Wb, 100),
281            (Mode::CeltOnly, Bandwidth::Wb, 200),
282            (Mode::CeltOnly, Bandwidth::Swb, 25),
283            (Mode::CeltOnly, Bandwidth::Swb, 50),
284            (Mode::CeltOnly, Bandwidth::Swb, 100),
285            (Mode::CeltOnly, Bandwidth::Swb, 200),
286            (Mode::CeltOnly, Bandwidth::Fb, 25),
287            (Mode::CeltOnly, Bandwidth::Fb, 50),
288            (Mode::CeltOnly, Bandwidth::Fb, 100),
289            (Mode::CeltOnly, Bandwidth::Fb, 200),
290        ];
291        for (config, &(mode, bw, dur)) in expected.iter().enumerate() {
292            let toc = OpusTocByte::from_byte((config as u8) << 3);
293            assert_eq!(toc.config, config as u8, "config field");
294            assert_eq!(toc.mode, mode, "config {config}: mode");
295            assert_eq!(toc.bandwidth, bw, "config {config}: bandwidth");
296            assert_eq!(
297                toc.frame_size_tenths_ms, dur,
298                "config {config}: frame-size (tenths of ms)"
299            );
300        }
301    }
302
303    /// The `s` bit (bit position 5 from the MSB / bit 2 from the
304    /// LSB) toggles mono vs. stereo independently of `config` and
305    /// `c`. We sweep `config` and `c` and verify each `s` polarity.
306    #[test]
307    fn stereo_bit_independent_of_config_and_code() {
308        for config in 0u8..32 {
309            for code in 0u8..4 {
310                let mono = OpusTocByte::from_byte((config << 3) | code);
311                let stereo = OpusTocByte::from_byte((config << 3) | (1 << 2) | code);
312                assert_eq!(mono.channels, ChannelMapping::Mono);
313                assert_eq!(stereo.channels, ChannelMapping::Stereo);
314                // Toggling `s` must not bleed into the other fields.
315                assert_eq!(mono.config, stereo.config);
316                assert_eq!(mono.mode, stereo.mode);
317                assert_eq!(mono.bandwidth, stereo.bandwidth);
318                assert_eq!(mono.frame_size_tenths_ms, stereo.frame_size_tenths_ms);
319                assert_eq!(mono.frame_count_code, stereo.frame_count_code);
320            }
321        }
322    }
323
324    /// The `c` two-bit field selects the frame-packing code per the
325    /// four cases enumerated immediately after Table 2.
326    #[test]
327    fn frame_count_codes() {
328        let cases = [
329            (0u8, FrameCountCode::One, (1u8, 1u8)),
330            (1, FrameCountCode::TwoEqual, (2, 2)),
331            (2, FrameCountCode::TwoUnequal, (2, 2)),
332            (3, FrameCountCode::Arbitrary, (1, 48)),
333        ];
334        for (c, expected_code, range) in cases {
335            let toc = OpusTocByte::from_byte(c);
336            assert_eq!(toc.frame_count_code, expected_code, "c={c}");
337            assert_eq!(toc.frame_count_range(), range, "c={c} frame-count range");
338        }
339    }
340
341    /// Empty packet rejection per RFC 6716 §3.1 R1.
342    #[test]
343    fn parse_empty_rejects() {
344        assert_eq!(OpusTocByte::parse(&[]), Err(Error::EmptyPacket));
345    }
346
347    /// A spot-check parse against a hand-assembled packet:
348    /// config=13 (Hybrid SWB 20 ms), s=1 (stereo), c=2 (two
349    /// unequal frames). Bit layout: `01101 1 10` = 0x6E.
350    #[test]
351    fn parse_known_byte() {
352        let toc = OpusTocByte::parse(&[0x6E, 0x00, 0x00]).unwrap();
353        assert_eq!(toc.config, 13);
354        assert_eq!(toc.mode, Mode::Hybrid);
355        assert_eq!(toc.bandwidth, Bandwidth::Swb);
356        assert_eq!(toc.frame_size_tenths_ms, 200);
357        assert_eq!(toc.channels, ChannelMapping::Stereo);
358        assert_eq!(toc.frame_count_code, FrameCountCode::TwoUnequal);
359        // And a second spot-check at the opposite corner: config=31
360        // (CELT FB 20 ms), mono, code 0.
361        let toc2 = OpusTocByte::from_byte(0xF8);
362        assert_eq!(toc2.config, 31);
363        assert_eq!(toc2.mode, Mode::CeltOnly);
364        assert_eq!(toc2.bandwidth, Bandwidth::Fb);
365        assert_eq!(toc2.frame_size_tenths_ms, 200);
366        assert_eq!(toc2.channels, ChannelMapping::Mono);
367        assert_eq!(toc2.frame_count_code, FrameCountCode::One);
368    }
369}