Skip to content

Add Opus and dOps box parsing #142

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions examples/mp4copy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ use std::io::{self, BufReader, BufWriter};
use std::path::Path;

use mp4::{
AacConfig, AvcConfig, HevcConfig, MediaConfig, MediaType, Mp4Config, Result, TrackConfig,
TtxtConfig, Vp9Config,
AacConfig, AvcConfig, HevcConfig, MediaConfig, MediaType, Mp4Config, OpusConfig, Result,
TrackConfig, TtxtConfig, Vp9Config,
};

fn main() {
Expand Down Expand Up @@ -64,6 +64,12 @@ fn copy<P: AsRef<Path>>(src_filename: &P, dst_filename: &P) -> Result<()> {
freq_index: track.sample_freq_index()?,
chan_conf: track.channel_config()?,
}),
MediaType::OPUS => MediaConfig::OpusConfig(OpusConfig {
bitrate: track.bitrate(),
freq_index: track.sample_freq_index()?,
chan_conf: track.channel_config()?,
pre_skip: 0,
}),
MediaType::TTXT => MediaConfig::TtxtConfig(TtxtConfig {}),
};

Expand Down
5 changes: 4 additions & 1 deletion src/mp4box/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ pub(crate) mod moov;
pub(crate) mod mp4a;
pub(crate) mod mvex;
pub(crate) mod mvhd;
pub(crate) mod opus;
pub(crate) mod smhd;
pub(crate) mod stbl;
pub(crate) mod stco;
Expand Down Expand Up @@ -238,7 +239,9 @@ boxtype! {
CovrBox => 0x636f7672,
DescBox => 0x64657363,
WideBox => 0x77696465,
WaveBox => 0x77617665
WaveBox => 0x77617665,
OpusBox => 0x4F707573,
DopsBox => 0x644F7073
}

pub trait Mp4Box: Sized {
Expand Down
2 changes: 1 addition & 1 deletion src/mp4box/mp4a.rs
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ fn size_of_length(size: u32) -> u32 {
fn write_desc<W: Write>(writer: &mut W, tag: u8, size: u32) -> Result<u64> {
writer.write_u8(tag)?;

if size as u64 > std::u32::MAX as u64 {
if size as u64 > u32::MAX as u64 {
return Err(Error::InvalidData("invalid descriptor length range"));
}

Expand Down
274 changes: 274 additions & 0 deletions src/mp4box/opus.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,274 @@
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use serde::Serialize;
use std::io::{Read, Seek, Write};

use crate::mp4box::*;

#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct OpusBox {
pub data_reference_index: u16,
pub channelcount: u16,
pub samplesize: u16,

#[serde(with = "value_u32")]
pub samplerate: FixedPointU16,
pub dops: DopsBox,
}

impl Default for OpusBox {
fn default() -> Self {
Self {
data_reference_index: 0,
channelcount: 2,
samplesize: 16,
samplerate: FixedPointU16::new(48000),
dops: DopsBox::default(),
}
}
}

impl OpusBox {
pub fn new(config: &OpusConfig) -> Self {
Self {
data_reference_index: 1,
channelcount: config.chan_conf as u16,
samplesize: 16,
samplerate: FixedPointU16::new(config.freq_index.freq() as u16),
dops: DopsBox::new(config),
}
}

pub fn get_type(&self) -> BoxType {
BoxType::OpusBox
}

pub fn get_size(&self) -> u64 {
let mut size = HEADER_SIZE + 8 + 20;
size += self.dops.box_size();
size
}
}

impl Mp4Box for OpusBox {
fn box_type(&self) -> BoxType {
self.get_type()
}

fn box_size(&self) -> u64 {
self.get_size()
}

fn to_json(&self) -> Result<String> {
Ok(serde_json::to_string(&self).unwrap())
}

fn summary(&self) -> Result<String> {
let s = format!(
"channel_count={} sample_size={} sample_rate={}",
self.channelcount,
self.samplesize,
self.samplerate.value()
);
Ok(s)
}
}

impl<R: Read + Seek> ReadBox<&mut R> for OpusBox {
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
let start = box_start(reader)?;

reader.read_u32::<BigEndian>()?; // reserved
reader.read_u16::<BigEndian>()?; // reserved
let data_reference_index = reader.read_u16::<BigEndian>()?;
let _version = reader.read_u16::<BigEndian>()?;
reader.read_u16::<BigEndian>()?; // reserved
reader.read_u32::<BigEndian>()?; // reserved
let channelcount = reader.read_u16::<BigEndian>()?;
let samplesize = reader.read_u16::<BigEndian>()?;
reader.read_u32::<BigEndian>()?; // pre-defined, reserved
let samplerate = FixedPointU16::new_raw(reader.read_u32::<BigEndian>()?);

// read dOps box
let header = BoxHeader::read(reader)?;
let BoxHeader {
name: _name,
size: s,
} = header;
let dops = DopsBox::read_box(reader, s)?;

// This shouldn't happen:
let end = start + size;
skip_bytes_to(reader, end)?;

Ok(OpusBox {
data_reference_index,
channelcount,
samplesize,
samplerate,
dops,
})
}
}

impl<W: Write> WriteBox<&mut W> for OpusBox {
fn write_box(&self, writer: &mut W) -> Result<u64> {
let size = self.box_size();
BoxHeader::new(self.box_type(), size).write(writer)?;

writer.write_u32::<BigEndian>(0)?; // reserved
writer.write_u16::<BigEndian>(0)?; // reserved
writer.write_u16::<BigEndian>(self.data_reference_index)?;

writer.write_u64::<BigEndian>(0)?; // reserved
writer.write_u16::<BigEndian>(self.channelcount)?;
writer.write_u16::<BigEndian>(self.samplesize)?;
writer.write_u32::<BigEndian>(0)?; // reserved
writer.write_u32::<BigEndian>(self.samplerate.raw_value())?;

self.dops.write_box(writer)?;

Ok(size)
}
}

#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
pub struct ChannelMappingTable {
pub stream_count: u8,
pub coupled_count: u8,
pub channel_mapping: Vec<u8>, // len == channel_count
}

#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
pub struct DopsBox {
pub version: u8,
pub channel_count: u8,
pub pre_skip: u16,
// Input sample rate (32 bits unsigned, little endian): informational only
pub sample_rate: u32,
// Output gain (16 bits, little endian, signed Q7.8 in dB) to apply when decoding
pub output_gain: i16,
// Channel mapping family (8 bits unsigned)
// - 0 = one stream: mono or L,R stereo
// - 1 = channels in vorbis spec order: mono or L,R stereo or ... or FL,C,FR,RL,RR,LFE, ...
// - 2..254 = reserved (treat as 255)
// - 255 = no defined channel meaning
pub channel_mapping_family: u8,
// The ChannelMapping field shall be set to the same octet string as
// *Channel Mapping* field in the identification header defined in Ogg Opus
pub channel_mapping_table: Option<ChannelMappingTable>,
}

impl DopsBox {
pub fn new(config: &OpusConfig) -> Self {
Self {
version: 0,
channel_count: config.chan_conf as u8,
pre_skip: config.pre_skip,
sample_rate: config.freq_index.freq(),
output_gain: 0,
channel_mapping_family: 0,
channel_mapping_table: None,
}
}
}

impl Mp4Box for DopsBox {
fn box_type(&self) -> BoxType {
BoxType::DopsBox
}

fn box_size(&self) -> u64 {
HEADER_SIZE + 11 // TODO add channel mapping table size
}

fn to_json(&self) -> Result<String> {
Ok(serde_json::to_string(&self).unwrap())
}

fn summary(&self) -> Result<String> {
Ok(String::new())
}
}

impl<R: Read + Seek> ReadBox<&mut R> for DopsBox {
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
let start = box_start(reader)?;
let end = start + size;

let version = reader.read_u8()?;
let channel_count = reader.read_u8()?;
let pre_skip = reader.read_u16::<BigEndian>()?;
let sample_rate = reader.read_u32::<BigEndian>()?;
let output_gain = reader.read_i16::<BigEndian>()?;
let channel_mapping_family = reader.read_u8()?;

// TODO parse channel_mapping_table.
skip_bytes_to(reader, end)?;

Ok(DopsBox {
channel_count,
version,
pre_skip,
sample_rate,
output_gain,
channel_mapping_family,
channel_mapping_table: None,
})
}
}

impl<W: Write> WriteBox<&mut W> for DopsBox {
fn write_box(&self, writer: &mut W) -> Result<u64> {
let size = self.box_size();
BoxHeader::new(self.box_type(), size).write(writer)?;

writer.write_u8(self.version)?;
writer.write_u8(self.channel_count)?;
writer.write_u16::<BigEndian>(self.pre_skip)?;
writer.write_u32::<BigEndian>(self.sample_rate)?;

writer.write_i16::<BigEndian>(self.output_gain)?;
writer.write_u8(self.channel_mapping_family)?;

// TODO write channel_mapping_table

Ok(size)
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::mp4box::BoxHeader;
use std::io::Cursor;

#[test]
fn test_opus() {
let src_box = OpusBox {
data_reference_index: 1,
channelcount: 2,
samplesize: 16,
samplerate: FixedPointU16::new(48000),
dops: DopsBox {
version: 0,
channel_count: 2,
pre_skip: 0,
sample_rate: 48000,
output_gain: 0,
channel_mapping_family: 0,
channel_mapping_table: None,
},
};
let mut buf = Vec::new();
src_box.write_box(&mut buf).unwrap();
assert_eq!(buf.len(), src_box.box_size() as usize);

let mut reader = Cursor::new(&buf);
let header = BoxHeader::read(&mut reader).unwrap();
assert_eq!(header.name, BoxType::OpusBox);
assert_eq!(src_box.box_size(), header.size);

let dst_box = OpusBox::read_box(&mut reader, header.size).unwrap();
assert_eq!(src_box, dst_box);
}
}
10 changes: 9 additions & 1 deletion src/mp4box/stsd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::io::{Read, Seek, Write};

use crate::mp4box::vp09::Vp09Box;
use crate::mp4box::*;
use crate::mp4box::{avc1::Avc1Box, hev1::Hev1Box, mp4a::Mp4aBox, tx3g::Tx3gBox};
use crate::mp4box::{avc1::Avc1Box, hev1::Hev1Box, mp4a::Mp4aBox, opus::OpusBox, tx3g::Tx3gBox};

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

#[serde(skip_serializing_if = "Option::is_none")]
pub tx3g: Option<Tx3gBox>,

#[serde(skip_serializing_if = "Option::is_none")]
pub opus: Option<OpusBox>,
}

impl StsdBox {
Expand Down Expand Up @@ -81,6 +84,7 @@ impl<R: Read + Seek> ReadBox<&mut R> for StsdBox {
let mut vp09 = None;
let mut mp4a = None;
let mut tx3g = None;
let mut opus = None;

// Get box header.
let header = BoxHeader::read(reader)?;
Expand All @@ -107,6 +111,9 @@ impl<R: Read + Seek> ReadBox<&mut R> for StsdBox {
BoxType::Tx3gBox => {
tx3g = Some(Tx3gBox::read_box(reader, s)?);
}
BoxType::OpusBox => {
opus = Some(OpusBox::read_box(reader, s)?);
}
_ => {}
}

Expand All @@ -120,6 +127,7 @@ impl<R: Read + Seek> ReadBox<&mut R> for StsdBox {
vp09,
mp4a,
tx3g,
opus,
})
}
}
Expand Down
Loading
Loading