Skip to content

Commit ed1c47d

Browse files
committed
Add Opus and dOps box parsing
Channel mapping table is still TBD
1 parent 35560e9 commit ed1c47d

File tree

5 files changed

+357
-4
lines changed

5 files changed

+357
-4
lines changed

src/mp4box/mod.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ pub(crate) mod moov;
8585
pub(crate) mod mp4a;
8686
pub(crate) mod mvex;
8787
pub(crate) mod mvhd;
88+
pub(crate) mod opus;
8889
pub(crate) mod smhd;
8990
pub(crate) mod stbl;
9091
pub(crate) mod stco;
@@ -238,7 +239,9 @@ boxtype! {
238239
CovrBox => 0x636f7672,
239240
DescBox => 0x64657363,
240241
WideBox => 0x77696465,
241-
WaveBox => 0x77617665
242+
WaveBox => 0x77617665,
243+
OpusBox => 0x4F707573,
244+
DopsBox => 0x644F7073
242245
}
243246

244247
pub trait Mp4Box: Sized {

src/mp4box/opus.rs

Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
2+
use serde::Serialize;
3+
use std::io::{Read, Seek, Write};
4+
5+
use crate::mp4box::*;
6+
7+
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
8+
pub struct OpusBox {
9+
pub data_reference_index: u16,
10+
pub channelcount: u16,
11+
pub samplesize: u16,
12+
13+
#[serde(with = "value_u32")]
14+
pub samplerate: FixedPointU16,
15+
pub dops: DopsBox,
16+
}
17+
18+
impl Default for OpusBox {
19+
fn default() -> Self {
20+
Self {
21+
data_reference_index: 0,
22+
channelcount: 2,
23+
samplesize: 16,
24+
samplerate: FixedPointU16::new(48000),
25+
dops: DopsBox::default(),
26+
}
27+
}
28+
}
29+
30+
impl OpusBox {
31+
pub fn new(config: &OpusConfig) -> Self {
32+
Self {
33+
data_reference_index: 1,
34+
channelcount: config.chan_conf as u16,
35+
samplesize: 16,
36+
samplerate: FixedPointU16::new(config.freq_index.freq() as u16),
37+
dops: DopsBox::new(config),
38+
}
39+
}
40+
41+
pub fn get_type(&self) -> BoxType {
42+
BoxType::OpusBox
43+
}
44+
45+
pub fn get_size(&self) -> u64 {
46+
let mut size = HEADER_SIZE + 8 + 20;
47+
size += self.dops.box_size();
48+
size
49+
}
50+
}
51+
52+
impl Mp4Box for OpusBox {
53+
fn box_type(&self) -> BoxType {
54+
self.get_type()
55+
}
56+
57+
fn box_size(&self) -> u64 {
58+
self.get_size()
59+
}
60+
61+
fn to_json(&self) -> Result<String> {
62+
Ok(serde_json::to_string(&self).unwrap())
63+
}
64+
65+
fn summary(&self) -> Result<String> {
66+
let s = format!(
67+
"channel_count={} sample_size={} sample_rate={}",
68+
self.channelcount,
69+
self.samplesize,
70+
self.samplerate.value()
71+
);
72+
Ok(s)
73+
}
74+
}
75+
76+
impl<R: Read + Seek> ReadBox<&mut R> for OpusBox {
77+
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
78+
let start = box_start(reader)?;
79+
80+
reader.read_u32::<BigEndian>()?; // reserved
81+
reader.read_u16::<BigEndian>()?; // reserved
82+
let data_reference_index = reader.read_u16::<BigEndian>()?;
83+
let _version = reader.read_u16::<BigEndian>()?;
84+
reader.read_u16::<BigEndian>()?; // reserved
85+
reader.read_u32::<BigEndian>()?; // reserved
86+
let channelcount = reader.read_u16::<BigEndian>()?;
87+
let samplesize = reader.read_u16::<BigEndian>()?;
88+
reader.read_u32::<BigEndian>()?; // pre-defined, reserved
89+
let samplerate = FixedPointU16::new_raw(reader.read_u32::<BigEndian>()?);
90+
91+
// read dOps box
92+
let header = BoxHeader::read(reader)?;
93+
let BoxHeader { name: _name, size: s } = header;
94+
let dops = DopsBox::read_box(reader, s)?;
95+
96+
// This shouldn't happen:
97+
let end = start + size;
98+
skip_bytes_to(reader, end)?;
99+
100+
Ok(OpusBox {
101+
data_reference_index,
102+
channelcount,
103+
samplesize,
104+
samplerate,
105+
dops,
106+
})
107+
}
108+
}
109+
110+
impl<W: Write> WriteBox<&mut W> for OpusBox {
111+
fn write_box(&self, writer: &mut W) -> Result<u64> {
112+
let size = self.box_size();
113+
BoxHeader::new(self.box_type(), size).write(writer)?;
114+
115+
writer.write_u32::<BigEndian>(0)?; // reserved
116+
writer.write_u16::<BigEndian>(0)?; // reserved
117+
writer.write_u16::<BigEndian>(self.data_reference_index)?;
118+
119+
writer.write_u64::<BigEndian>(0)?; // reserved
120+
writer.write_u16::<BigEndian>(self.channelcount)?;
121+
writer.write_u16::<BigEndian>(self.samplesize)?;
122+
writer.write_u32::<BigEndian>(0)?; // reserved
123+
writer.write_u32::<BigEndian>(self.samplerate.raw_value())?;
124+
125+
self.dops.write_box(writer)?;
126+
127+
Ok(size)
128+
}
129+
}
130+
131+
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
132+
pub struct ChannelMappingTable {
133+
pub stream_count: u8,
134+
pub coupled_count: u8,
135+
pub channel_mapping: Vec<u8>, // len == channel_count
136+
}
137+
138+
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
139+
pub struct DopsBox {
140+
pub version: u8,
141+
pub channel_count: u8,
142+
pub pre_skip: u16,
143+
// Input sample rate (32 bits unsigned, little endian): informational only
144+
pub sample_rate: u32,
145+
// Output gain (16 bits, little endian, signed Q7.8 in dB) to apply when decoding
146+
pub output_gain: i16,
147+
// Channel mapping family (8 bits unsigned)
148+
// - 0 = one stream: mono or L,R stereo
149+
// - 1 = channels in vorbis spec order: mono or L,R stereo or ... or FL,C,FR,RL,RR,LFE, ...
150+
// - 2..254 = reserved (treat as 255)
151+
// - 255 = no defined channel meaning
152+
pub channel_mapping_family: u8,
153+
// The ChannelMapping field shall be set to the same octet string as
154+
// *Channel Mapping* field in the identification header defined in Ogg Opus
155+
pub channel_mapping_table: Option<ChannelMappingTable>,
156+
}
157+
158+
impl DopsBox {
159+
pub fn new(config: &OpusConfig) -> Self {
160+
Self {
161+
version: 0,
162+
channel_count: config.chan_conf as u8,
163+
pre_skip: config.pre_skip,
164+
sample_rate: config.freq_index.freq(),
165+
output_gain: 0,
166+
channel_mapping_family: 0,
167+
channel_mapping_table: None,
168+
}
169+
}
170+
}
171+
172+
impl Mp4Box for DopsBox {
173+
fn box_type(&self) -> BoxType {
174+
BoxType::DopsBox
175+
}
176+
177+
fn box_size(&self) -> u64 {
178+
HEADER_SIZE + 11 // TODO add channel mapping table size
179+
}
180+
181+
fn to_json(&self) -> Result<String> {
182+
Ok(serde_json::to_string(&self).unwrap())
183+
}
184+
185+
fn summary(&self) -> Result<String> {
186+
Ok(String::new())
187+
}
188+
}
189+
190+
impl<R: Read + Seek> ReadBox<&mut R> for DopsBox {
191+
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
192+
let start = box_start(reader)?;
193+
let end = start + size;
194+
195+
let version = reader.read_u8()?;
196+
let channel_count = reader.read_u8()?;
197+
let pre_skip = reader.read_u16::<BigEndian>()?;
198+
let sample_rate = reader.read_u32::<BigEndian>()?;
199+
let output_gain = reader.read_i16::<BigEndian>()?;
200+
let channel_mapping_family = reader.read_u8()?;
201+
202+
// TODO parse channel_mapping_table.
203+
skip_bytes_to(reader, end)?;
204+
205+
Ok(DopsBox {
206+
channel_count,
207+
version,
208+
pre_skip,
209+
sample_rate,
210+
output_gain,
211+
channel_mapping_family,
212+
channel_mapping_table: None,
213+
})
214+
}
215+
}
216+
217+
impl<W: Write> WriteBox<&mut W> for DopsBox {
218+
fn write_box(&self, writer: &mut W) -> Result<u64> {
219+
let size = self.box_size();
220+
BoxHeader::new(self.box_type(), size).write(writer)?;
221+
222+
writer.write_u8(self.version)?;
223+
writer.write_u8(self.channel_count)?;
224+
writer.write_u16::<BigEndian>(self.pre_skip)?;
225+
writer.write_u32::<BigEndian>(self.sample_rate)?;
226+
227+
writer.write_i16::<BigEndian>(self.output_gain)?;
228+
writer.write_u8(self.channel_mapping_family)?;
229+
230+
// TODO write channel_mapping_table
231+
232+
Ok(size)
233+
}
234+
}
235+
236+
#[cfg(test)]
237+
mod tests {
238+
use super::*;
239+
use crate::mp4box::BoxHeader;
240+
use std::io::Cursor;
241+
242+
#[test]
243+
fn test_opus() {
244+
let src_box = OpusBox {
245+
data_reference_index: 1,
246+
channelcount: 2,
247+
samplesize: 16,
248+
samplerate: FixedPointU16::new(48000),
249+
dops: DopsBox {
250+
version: 0,
251+
channel_count: 2,
252+
pre_skip: 0,
253+
sample_rate: 48000,
254+
output_gain: 0,
255+
channel_mapping_family: 0,
256+
channel_mapping_table: None,
257+
},
258+
};
259+
let mut buf = Vec::new();
260+
src_box.write_box(&mut buf).unwrap();
261+
assert_eq!(buf.len(), src_box.box_size() as usize);
262+
263+
let mut reader = Cursor::new(&buf);
264+
let header = BoxHeader::read(&mut reader).unwrap();
265+
assert_eq!(header.name, BoxType::OpusBox);
266+
assert_eq!(src_box.box_size(), header.size);
267+
268+
let dst_box = OpusBox::read_box(&mut reader, header.size).unwrap();
269+
assert_eq!(src_box, dst_box);
270+
}
271+
}

src/mp4box/stsd.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::io::{Read, Seek, Write};
44

55
use crate::mp4box::vp09::Vp09Box;
66
use crate::mp4box::*;
7-
use crate::mp4box::{avc1::Avc1Box, hev1::Hev1Box, mp4a::Mp4aBox, tx3g::Tx3gBox};
7+
use crate::mp4box::{avc1::Avc1Box, hev1::Hev1Box, mp4a::Mp4aBox, opus::OpusBox, tx3g::Tx3gBox};
88

99
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
1010
pub struct StsdBox {
@@ -25,6 +25,9 @@ pub struct StsdBox {
2525

2626
#[serde(skip_serializing_if = "Option::is_none")]
2727
pub tx3g: Option<Tx3gBox>,
28+
29+
#[serde(skip_serializing_if = "Option::is_none")]
30+
pub opus: Option<OpusBox>,
2831
}
2932

3033
impl StsdBox {
@@ -81,6 +84,7 @@ impl<R: Read + Seek> ReadBox<&mut R> for StsdBox {
8184
let mut vp09 = None;
8285
let mut mp4a = None;
8386
let mut tx3g = None;
87+
let mut opus = None;
8488

8589
// Get box header.
8690
let header = BoxHeader::read(reader)?;
@@ -107,6 +111,9 @@ impl<R: Read + Seek> ReadBox<&mut R> for StsdBox {
107111
BoxType::Tx3gBox => {
108112
tx3g = Some(Tx3gBox::read_box(reader, s)?);
109113
}
114+
BoxType::OpusBox => {
115+
opus = Some(OpusBox::read_box(reader, s)?);
116+
}
110117
_ => {}
111118
}
112119

@@ -120,6 +127,7 @@ impl<R: Read + Seek> ReadBox<&mut R> for StsdBox {
120127
vp09,
121128
mp4a,
122129
tx3g,
130+
opus,
123131
})
124132
}
125133
}

0 commit comments

Comments
 (0)