Skip to main content

zvidlib_opus_syntax/
lib.rs

1//! Opus configuration records and packet timing, shared by zvidlib's
2//! containers and its Opus codec.
3//!
4//! Opus always decodes at 48 kHz, which is therefore the sample clock of every
5//! Opus track zvidlib reads or writes. [`OpusHead`] is the stream's decoder
6//! configuration in both of the forms containers carry it, and
7//! [`opus_packet_samples`] reads a packet's duration from its
8//! table-of-contents byte, which is all a container needs to time a packet
9//! without decoding it.
10//!
11//! This is an internal crate of [zvidlib](https://crates.io/crates/zvidlib),
12//! which re-exports its public items under the paths documented there. Depend
13//! on `zvidlib` rather than on this crate directly.
14
15#[allow(unused_imports)]
16use zvidlib_core::*;
17
18use crate::{EncodedAudioSample, Error, ErrorKind, Result};
19
20/// The sample rate Opus decodes at, and the sample clock of an Opus track.
21pub const OPUS_SAMPLE_RATE: u32 = 48_000;
22
23/// How much audio to decode ahead of a seek target before its samples are
24/// trusted, 320 ms.
25///
26/// RFC 7845 section 4.6 asks for at least 80 ms, by which point the decoder's
27/// prediction state has converged far enough to play. Its output there still
28/// differs from a continuous decode's by 12 to 30 dB, though, and each further
29/// 80 ms takes a good deal more off: by 320 ms a read after a seek matches a
30/// read that walked there to within `f32` rounding in CELT and within a few
31/// steps of 16-bit PCM in SILK, whose filters run on integers. Opus decodes so
32/// much faster than real time that the extra preroll costs a few milliseconds
33/// a seek.
34pub const OPUS_PREROLL_SAMPLES: u32 = 15_360;
35
36/// The Opus identification header: the decoder configuration of an Opus
37/// stream (RFC 7845 section 5.1).
38///
39/// MP4 carries it as the fields of a `dOps` box ([`Self::from_dops`],
40/// [`Self::to_dops`]); Ogg, Matroska and WebM `CodecPrivate`, and a `WebCodecs`
41/// `AudioDecoderConfig.description` carry the `OpusHead` packet itself
42/// ([`Self::from_identification_header`],
43/// [`Self::to_identification_header`]).
44#[derive(Clone, Debug, Eq, PartialEq)]
45pub struct OpusHead {
46    /// The number of output channels.
47    pub channels: u8,
48    /// Samples at 48 kHz to discard from the start of the decoded stream: the
49    /// encoder's delay.
50    pub pre_skip: u16,
51    /// The sample rate of the audio before it was encoded, for information
52    /// only; Opus decodes at [`OPUS_SAMPLE_RATE`] regardless. Zero when
53    /// unknown.
54    pub input_sample_rate: u32,
55    /// Gain to apply to the decoded output, in Q7.8 decibels.
56    pub output_gain: i16,
57    /// The channel mapping family: 0 for mono or stereo in one stream, 1 for
58    /// the Vorbis channel orders of up to eight channels in several streams.
59    pub channel_mapping_family: u8,
60    /// The number of Opus streams in each packet.
61    pub stream_count: u8,
62    /// How many of those streams are coupled stereo pairs.
63    pub coupled_count: u8,
64    /// For each output channel, the decoded stream channel that feeds it, or
65    /// 255 for silence.
66    pub channel_mapping: Vec<u8>,
67}
68
69impl OpusHead {
70    /// A mapping-family-0 header for a mono or stereo stream.
71    pub fn new(channels: u8, pre_skip: u16, input_sample_rate: u32) -> Result<Self> {
72        if channels != 1 && channels != 2 {
73            return Err(invalid("an Opus mapping family 0 stream is mono or stereo"));
74        }
75        Ok(Self {
76            channels,
77            pre_skip,
78            input_sample_rate,
79            output_gain: 0,
80            channel_mapping_family: 0,
81            stream_count: 1,
82            coupled_count: channels - 1,
83            channel_mapping: (0..channels).collect(),
84        })
85    }
86
87    /// Parses a complete `dOps` box, size and type included, as the Opus
88    /// sample entry of an MP4 carries it ("Encapsulation of Opus in ISO Base
89    /// Media File Format", section 4.3.2).
90    pub fn from_dops(dops: &[u8]) -> Result<Self> {
91        let payload = crate::codec_config::box_payload(dops, b"dOps")?;
92        let declared = u32::from_be_bytes(dops[..4].try_into().expect("checked by box_payload"));
93        if usize::try_from(declared).ok() != Some(dops.len()) {
94            return Err(malformed("Opus dOps box size is inconsistent"));
95        }
96        let header = payload
97            .get(..11)
98            .ok_or_else(|| malformed("Opus dOps box is truncated"))?;
99        if header[0] != 0 {
100            return Err(unsupported(format!(
101                "Opus dOps version {} is unsupported",
102                header[0]
103            )));
104        }
105        let head = Self::from_fields(
106            header[1],
107            u16::from_be_bytes([header[2], header[3]]),
108            u32::from_be_bytes([header[4], header[5], header[6], header[7]]),
109            i16::from_be_bytes([header[8], header[9]]),
110            header[10],
111            &payload[11..],
112        )?;
113        Ok(head)
114    }
115
116    /// Writes this header as a complete `dOps` box, size and type included.
117    pub fn to_dops(&self) -> Vec<u8> {
118        let mut payload = vec![0, self.channels];
119        payload.extend_from_slice(&self.pre_skip.to_be_bytes());
120        payload.extend_from_slice(&self.input_sample_rate.to_be_bytes());
121        payload.extend_from_slice(&self.output_gain.to_be_bytes());
122        self.push_mapping(&mut payload);
123        let mut whole = u32::try_from(payload.len() + 8)
124            .expect("a dOps box is at most a few hundred bytes")
125            .to_be_bytes()
126            .to_vec();
127        whole.extend_from_slice(b"dOps");
128        whole.extend_from_slice(&payload);
129        whole
130    }
131
132    /// Parses an RFC 7845 `OpusHead` identification header packet, the
133    /// little-endian form Ogg pages and Matroska or WebM `CodecPrivate` carry.
134    pub fn from_identification_header(packet: &[u8]) -> Result<Self> {
135        let header = packet
136            .get(..19)
137            .ok_or_else(|| malformed("Opus identification header is truncated"))?;
138        if &header[..8] != b"OpusHead" {
139            return Err(malformed(
140                "Opus identification header does not start with OpusHead",
141            ));
142        }
143        // The major version is the upper four bits; RFC 7845 asks decoders to
144        // accept every minor version of the major version they implement.
145        if header[8] >> 4 != 0 {
146            return Err(unsupported(format!(
147                "Opus identification header version {} is unsupported",
148                header[8]
149            )));
150        }
151        Self::from_fields(
152            header[9],
153            u16::from_le_bytes([header[10], header[11]]),
154            u32::from_le_bytes([header[12], header[13], header[14], header[15]]),
155            i16::from_le_bytes([header[16], header[17]]),
156            header[18],
157            &packet[19..],
158        )
159    }
160
161    /// Writes this header as an RFC 7845 `OpusHead` identification header
162    /// packet (version 1).
163    pub fn to_identification_header(&self) -> Vec<u8> {
164        let mut packet = b"OpusHead".to_vec();
165        packet.push(1);
166        packet.push(self.channels);
167        packet.extend_from_slice(&self.pre_skip.to_le_bytes());
168        packet.extend_from_slice(&self.input_sample_rate.to_le_bytes());
169        packet.extend_from_slice(&self.output_gain.to_le_bytes());
170        self.push_mapping(&mut packet);
171        packet
172    }
173
174    /// The linear factor [`Self::output_gain`] scales decoded samples by.
175    pub fn output_gain_factor(&self) -> f32 {
176        10_f32.powf(f32::from(self.output_gain) / (20.0 * 256.0))
177    }
178
179    fn from_fields(
180        channels: u8,
181        pre_skip: u16,
182        input_sample_rate: u32,
183        output_gain: i16,
184        channel_mapping_family: u8,
185        mapping_table: &[u8],
186    ) -> Result<Self> {
187        if channels == 0 {
188            return Err(malformed("an Opus stream has at least one channel"));
189        }
190        if channel_mapping_family == 0 {
191            if channels > 2 {
192                return Err(malformed(
193                    "an Opus mapping family 0 stream is mono or stereo",
194                ));
195            }
196            return Ok(Self {
197                output_gain,
198                ..Self::new(channels, pre_skip, input_sample_rate)?
199            });
200        }
201        let table = mapping_table
202            .get(..2 + usize::from(channels))
203            .ok_or_else(|| malformed("Opus channel mapping table is truncated"))?;
204        let (stream_count, coupled_count) = (table[0], table[1]);
205        if stream_count == 0 || coupled_count > stream_count {
206            return Err(malformed("Opus channel mapping has invalid stream counts"));
207        }
208        let decoded_channels = u16::from(stream_count) + u16::from(coupled_count);
209        let channel_mapping = table[2..].to_vec();
210        if channel_mapping
211            .iter()
212            .any(|&index| index != 255 && u16::from(index) >= decoded_channels)
213        {
214            return Err(malformed(
215                "Opus channel mapping names a channel no stream decodes",
216            ));
217        }
218        Ok(Self {
219            channels,
220            pre_skip,
221            input_sample_rate,
222            output_gain,
223            channel_mapping_family,
224            stream_count,
225            coupled_count,
226            channel_mapping,
227        })
228    }
229
230    fn push_mapping(&self, out: &mut Vec<u8>) {
231        out.push(self.channel_mapping_family);
232        if self.channel_mapping_family != 0 {
233            out.push(self.stream_count);
234            out.push(self.coupled_count);
235            out.extend_from_slice(&self.channel_mapping);
236        }
237    }
238}
239
240/// The number of 48 kHz samples one Opus packet decodes to, read from its
241/// table-of-contents byte and frame count (RFC 6716 section 3.1).
242pub fn opus_packet_samples(packet: &[u8]) -> Result<u32> {
243    let &toc = packet
244        .first()
245        .ok_or_else(|| invalid_packet("packet is empty"))?;
246    let frames = match toc & 0x03 {
247        0 => 1,
248        1 | 2 => 2,
249        _ => {
250            let count = packet
251                .get(1)
252                .ok_or_else(|| invalid_packet("code 3 packet has no frame count"))?
253                & 0x3F;
254            if count == 0 {
255                return Err(invalid_packet("code 3 packet declares zero frames"));
256            }
257            u32::from(count)
258        }
259    };
260    let samples = frames * frame_samples(toc);
261    // A packet cannot describe more than 120 ms, so a frame count that implies
262    // more is a malformed packet rather than a very long one.
263    if samples > MAX_PACKET_SAMPLES {
264        return Err(invalid_packet("packet claims more than 120 ms"));
265    }
266    Ok(samples)
267}
268
269/// The 48 kHz samples in each frame of a packet whose table-of-contents byte
270/// is `toc`: 2.5 to 20 ms for CELT, 10 or 20 ms for hybrid, and 10 to 60 ms for
271/// SILK (RFC 6716 section 3.1).
272fn frame_samples(toc: u8) -> u32 {
273    let size = u32::from((toc >> 3) & 0x03);
274    if toc & 0x80 != 0 {
275        (OPUS_SAMPLE_RATE << size) / 400
276    } else if toc & 0x60 == 0x60 {
277        if toc & 0x08 != 0 {
278            OPUS_SAMPLE_RATE / 50
279        } else {
280            OPUS_SAMPLE_RATE / 100
281        }
282    } else if size == 3 {
283        OPUS_SAMPLE_RATE * 60 / 1000
284    } else {
285        (OPUS_SAMPLE_RATE << size) / 100
286    }
287}
288
289/// The number of packets an [`crate::AudioSampleReader`] should decode ahead
290/// of a seek target in an Opus track whose packets are `packets`, enough to
291/// cover [`OPUS_PREROLL_SAMPLES`] even at the track's shortest packet.
292pub fn opus_preroll_packets(packets: &[EncodedAudioSample]) -> usize {
293    let shortest = packets
294        .iter()
295        .map(|packet| packet.decoded_range.len())
296        .filter(|&length| length > 0)
297        .min()
298        .unwrap_or(u64::from(OPUS_PREROLL_SAMPLES));
299    usize::try_from(u64::from(OPUS_PREROLL_SAMPLES).div_ceil(shortest)).unwrap_or(usize::MAX)
300}
301
302/// The longest an Opus packet can last, 120 ms at 48 kHz.
303const MAX_PACKET_SAMPLES: u32 = 5_760;
304
305/// A packet the table of contents says is malformed, reported as the native
306/// decoder reports the same packet.
307fn invalid_packet(what: &str) -> Error {
308    codec(format!("Opus: invalid packet: {what}"))
309}
310
311fn codec(message: impl Into<String>) -> Error {
312    Error::new(ErrorKind::Codec, message)
313}
314
315fn invalid(message: impl Into<String>) -> Error {
316    Error::new(ErrorKind::InvalidInput, message)
317}
318
319fn malformed(message: impl Into<String>) -> Error {
320    Error::new(ErrorKind::MalformedMedia, message)
321}
322
323fn unsupported(message: impl Into<String>) -> Error {
324    Error::new(ErrorKind::Unsupported, message)
325}