Coverage Report

Created: 2026-07-16 06:13

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/symphonia/symphonia-format-isomp4/src/atoms/opus.rs
Line
Count
Source
1
// Symphonia
2
// Copyright (c) 2019-2026 The Project Symphonia Developers.
3
//
4
// This Source Code Form is subject to the terms of the Mozilla Public
5
// License, v. 2.0. If a copy of the MPL was not distributed with this
6
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
7
8
use symphonia_common::xiph::audio::opus;
9
use symphonia_core::codecs::audio::well_known::CODEC_ID_OPUS;
10
use symphonia_core::errors::Error;
11
use symphonia_core::io::BufReader;
12
13
use crate::atoms::stsd::AudioSampleEntry;
14
use crate::atoms::{
15
    Atom, AtomHeader, AtomIterator, ReadAtom, Result, decode_error, unsupported_error,
16
};
17
18
const OPUS_MAGIC: &[u8] = b"OpusHead";
19
const OPUS_MAGIC_LEN: usize = OPUS_MAGIC.len();
20
21
/// Opus atom.
22
#[allow(dead_code)]
23
#[derive(Debug)]
24
pub struct OpusAtom {
25
    /// Opus extra data (identification header).
26
    extra_data: Box<[u8]>,
27
}
28
29
impl Atom for OpusAtom {
30
0
    fn read<R: ReadAtom>(reader: &mut AtomIterator<R>, header: &AtomHeader) -> Result<Self> {
31
        const MIN_OPUS_EXTRA_DATA_SIZE: usize = OPUS_MAGIC_LEN + 11;
32
        const MAX_OPUS_EXTRA_DATA_SIZE: usize = MIN_OPUS_EXTRA_DATA_SIZE + 257;
33
34
        // Offset of the Opus version number in the extra data.
35
        const OPUS_EXTRADATA_VERSION_OFFSET: usize = OPUS_MAGIC_LEN;
36
37
        // The dops atom contains an Opus identification header excluding the OpusHead magic
38
        // signature. Therefore, the atom data length should be atleast as long as the shortest
39
        // Opus identification header.
40
0
        let data_len = header
41
0
            .data_size()
42
0
            .ok_or(Error::DecodeError("isomp4 (opus): expected atom size to be known"))?
43
            as usize;
44
45
0
        if data_len < MIN_OPUS_EXTRA_DATA_SIZE - OPUS_MAGIC_LEN {
46
0
            return decode_error("isomp4 (opus): opus identification header too short");
47
0
        }
48
49
0
        if data_len > MAX_OPUS_EXTRA_DATA_SIZE - OPUS_MAGIC_LEN {
50
0
            return decode_error("isomp4 (opus): opus identification header too large");
51
0
        }
52
53
0
        let mut extra_data = vec![0; OPUS_MAGIC_LEN + data_len].into_boxed_slice();
54
55
        // The Opus magic is excluded in the atom, but the extra data must start with it.
56
0
        extra_data[..OPUS_MAGIC_LEN].copy_from_slice(OPUS_MAGIC);
57
58
        // Read the extra data from the atom.
59
0
        reader.read_buf_exact(&mut extra_data[OPUS_MAGIC_LEN..])?;
60
61
        // Verify the version number is 0.
62
0
        if extra_data[OPUS_EXTRADATA_VERSION_OFFSET] != 0 {
63
0
            return unsupported_error("isomp4 (opus): unsupported opus version");
64
0
        }
65
66
0
        Ok(OpusAtom { extra_data })
67
0
    }
68
}
69
70
impl OpusAtom {
71
0
    pub fn fill_audio_sample_entry(self, entry: &mut AudioSampleEntry) {
72
0
        let mut reader = BufReader::new(&self.extra_data);
73
74
0
        if let Ok(opus_head) = opus::OpusHead::read(&mut reader, 0) {
75
0
            entry.channels = Some(opus_head.channels);
76
0
            entry.sample_rate = 48_000.0;
77
0
        }
78
79
0
        entry.codec_id = CODEC_ID_OPUS;
80
0
        entry.extra_data = Some(self.extra_data);
81
0
    }
82
}