/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 | | } |