diff --git a/src/mp4box/mod.rs b/src/mp4box/mod.rs index 264929b2..7c54d006 100644 --- a/src/mp4box/mod.rs +++ b/src/mp4box/mod.rs @@ -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 sidx; pub(crate) mod smhd; pub(crate) mod stbl; @@ -130,6 +131,7 @@ pub use moov::MoovBox; pub use mp4a::Mp4aBox; pub use mvex::MvexBox; pub use mvhd::MvhdBox; +pub use opus::OpusBox; pub use sidx::SidxBox; pub use smhd::SmhdBox; pub use stbl::StblBox; @@ -185,6 +187,7 @@ macro_rules! boxtype { } boxtype! { + DopsBox => 0x644f7073, FtypBox => 0x66747970, MvhdBox => 0x6d766864, MfhdBox => 0x6d666864, @@ -193,6 +196,7 @@ boxtype! { MoovBox => 0x6d6f6f76, MvexBox => 0x6d766578, MehdBox => 0x6d656864, + OpusBox => 0x4f707573, TrexBox => 0x74726578, EmsgBox => 0x656d7367, MoofBox => 0x6d6f6f66, diff --git a/src/mp4box/opus.rs b/src/mp4box/opus.rs new file mode 100644 index 00000000..db7dc024 --- /dev/null +++ b/src/mp4box/opus.rs @@ -0,0 +1,322 @@ +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 channel_count: u16, + pub sample_size: u16, + + #[serde(with = "value_u32")] + pub sample_rate: FixedPointU16, + pub dops_box: Option, +} + +impl Default for OpusBox { + fn default() -> Self { + Self { + data_reference_index: 0, + channel_count: 2, + sample_size: 16, + sample_rate: FixedPointU16::new(48000), + dops_box: Some(DopsBox::default()), + } + } +} + +impl OpusBox { + pub fn get_type(&self) -> BoxType { + BoxType::OpusBox + } + + pub fn get_size(&self) -> u64 { + let mut size = HEADER_SIZE + 28; + if let Some(ref dops_box) = self.dops_box { + size += dops_box.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 { + Ok(serde_json::to_string(&self).unwrap()) + } + + fn summary(&self) -> Result { + let s = format!( + "channel_count={} sample_size={} sample_rate={}", + self.channel_count, + self.sample_size, + self.sample_rate.value() + ); + Ok(s) + } +} + +impl ReadBox<&mut R> for OpusBox { + fn read_box(reader: &mut R, size: u64) -> Result { + let start = box_start(reader)?; + + reader.read_u32::()?; // reserved + reader.read_u16::()?; // reserved + let data_reference_index = reader.read_u16::()?; + reader.read_u16::()?; // reserved + reader.read_u16::()?; // reserved + reader.read_u32::()?; // reserved + let channel_count = reader.read_u16::()?; + let sample_size = reader.read_u16::()?; + reader.read_u32::()?; // pre-defined, reserved + let sample_rate = FixedPointU16::new_raw(reader.read_u32::()?); + + let header = BoxHeader::read(reader)?; + let BoxHeader { name, size: s } = header; + + if s > size { + return Err(Error::InvalidData( + "opus box contains a box with a larger size than it", + )); + } + let mut dops_box = None; + if name == BoxType::DopsBox { + dops_box = Some(DopsBox::read_box(reader, s)?); + } + skip_bytes_to(reader, start + size)?; + Ok(OpusBox { + data_reference_index, + channel_count, + sample_size, + sample_rate, + dops_box, + }) + } +} + +impl WriteBox<&mut W> for OpusBox { + fn write_box(&self, writer: &mut W) -> Result { + let size = self.box_size(); + BoxHeader::new(self.box_type(), size).write(writer)?; + + writer.write_u32::(0)?; // reserved + writer.write_u16::(0)?; // reserved + writer.write_u16::(self.data_reference_index)?; + + writer.write_u64::(0)?; // reserved + writer.write_u16::(self.channel_count)?; + writer.write_u16::(self.sample_size)?; + writer.write_u32::(0)?; // reserved + writer.write_u32::(self.sample_rate.raw_value())?; + + if let Some(ref dops_box) = self.dops_box { + dops_box.write_box(writer)?; + } + Ok(size) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +pub struct DopsBox { + pub version: u8, + pub output_channel_count: u8, + pub pre_skip: u16, + pub input_sample_rate: u32, + pub output_gain: i16, + pub channel_mapping_family: u8, + pub channel_mapping_table: Option, +} + +impl Default for DopsBox { + fn default() -> Self { + Self { + version: 0, + output_channel_count: 2, + pre_skip: 16, + input_sample_rate: 0, + output_gain: -1, + channel_mapping_family: 0, + channel_mapping_table: None, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +pub struct ChannelMappingTable { + pub stream_count: u8, + pub coupled_count: u8, + pub channel_mapping: Vec, +} + +impl Default for ChannelMappingTable { + fn default() -> Self { + Self { + stream_count: 0, + coupled_count: 2, + channel_mapping: Vec::new(), + } + } +} + +impl Mp4Box for DopsBox { + fn box_type(&self) -> BoxType { + BoxType::DopsBox + } + + fn box_size(&self) -> u64 { + let mut channel_table_size = 0; + if self.channel_mapping_family != 0 { + channel_table_size = self.output_channel_count as u64 + 2; + } + HEADER_SIZE + 11 + channel_table_size + } + + fn to_json(&self) -> Result { + Ok(serde_json::to_string(&self).unwrap()) + } + + fn summary(&self) -> Result { + Ok(String::new()) + } +} + +impl ReadBox<&mut R> for DopsBox { + fn read_box(reader: &mut R, size: u64) -> Result { + let start = box_start(reader)?; + let version = reader.read_u8()?; + let output_channel_count = reader.read_u8()?; + let pre_skip = reader.read_u16::()?; + let input_sample_rate = reader.read_u32::()?; + let output_gain = reader.read_i16::()?; + let channel_mapping_family = reader.read_u8()?; + let mut channel_mapping_table = None; + if channel_mapping_family != 0 { + let stream_count = reader.read_u8()?; + let coupled_count = reader.read_u8()?; + let mut channel_mapping = Vec::new(); + for _ in 0..output_channel_count { + channel_mapping.push(reader.read_u8()?); + } + channel_mapping_table = Some(ChannelMappingTable { + stream_count, + coupled_count, + channel_mapping, + }); + } + + skip_bytes_to(reader, start + size)?; + Ok(DopsBox { + version, + output_channel_count, + pre_skip, + input_sample_rate, + output_gain, + channel_mapping_family, + channel_mapping_table, + }) + } +} + +impl WriteBox<&mut W> for DopsBox { + fn write_box(&self, writer: &mut W) -> Result { + let size = self.box_size(); + BoxHeader::new(self.box_type(), size).write(writer)?; + + writer.write_u8(self.version)?; + writer.write_u8(self.output_channel_count)?; + writer.write_u16::(self.pre_skip)?; + writer.write_u32::(self.input_sample_rate)?; + writer.write_i16::(self.output_gain)?; + writer.write_u8(self.channel_mapping_family)?; + + if self.channel_mapping_family != 0 { + let channel_mapping_table = self.channel_mapping_table.clone().unwrap(); + writer.write_u8(channel_mapping_table.stream_count)?; + writer.write_u8(channel_mapping_table.coupled_count)?; + for b in channel_mapping_table.channel_mapping.iter() { + writer.write_u8(*b)?; + } + } + 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, + channel_count: 6, + sample_size: 16, + sample_rate: FixedPointU16::new(48000), + dops_box: Some(DopsBox { + version: 0, + output_channel_count: 6, + pre_skip: 312, + input_sample_rate: 48000, + output_gain: 0, + channel_mapping_family: 1, + channel_mapping_table: Some(ChannelMappingTable { + stream_count: 4, + coupled_count: 2, + channel_mapping: [0, 4, 1, 2, 3, 5].to_vec(), + }), + }), + }; + 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); + } + + #[test] + fn test_opus_witout_channel_mapping_table() { + let src_box = OpusBox { + data_reference_index: 1, + channel_count: 6, + sample_size: 16, + sample_rate: FixedPointU16::new(48000), + dops_box: Some(DopsBox { + version: 0, + output_channel_count: 6, + pre_skip: 312, + input_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); + } +} diff --git a/src/mp4box/stsd.rs b/src/mp4box/stsd.rs index af947c6c..111750d4 100644 --- a/src/mp4box/stsd.rs +++ b/src/mp4box/stsd.rs @@ -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 { @@ -23,6 +23,9 @@ pub struct StsdBox { #[serde(skip_serializing_if = "Option::is_none")] pub mp4a: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub opus: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub tx3g: Option, } @@ -42,7 +45,10 @@ impl StsdBox { size += vp09.box_size(); } else if let Some(ref mp4a) = self.mp4a { size += mp4a.box_size(); - } else if let Some(ref tx3g) = self.tx3g { + } else if let Some(ref opus) = self.opus { + size += opus.box_size(); + } + else if let Some(ref tx3g) = self.tx3g { size += tx3g.box_size(); } size @@ -80,6 +86,7 @@ impl ReadBox<&mut R> for StsdBox { let mut hev1 = None; let mut vp09 = None; let mut mp4a = None; + let mut opus = None; let mut tx3g = None; // Get box header. @@ -104,6 +111,9 @@ impl ReadBox<&mut R> for StsdBox { BoxType::Mp4aBox => { mp4a = Some(Mp4aBox::read_box(reader, s)?); } + BoxType::OpusBox => { + opus = Some(OpusBox::read_box(reader, s)?); + } BoxType::Tx3gBox => { tx3g = Some(Tx3gBox::read_box(reader, s)?); } @@ -118,6 +128,7 @@ impl ReadBox<&mut R> for StsdBox { avc1, hev1, vp09, + opus, mp4a, tx3g, }) diff --git a/src/track.rs b/src/track.rs index 7eada834..00eb38f2 100644 --- a/src/track.rs +++ b/src/track.rs @@ -143,6 +143,8 @@ impl Mp4Track { Ok(FourCC::from(BoxType::Vp09Box)) } else if self.trak.mdia.minf.stbl.stsd.mp4a.is_some() { Ok(FourCC::from(BoxType::Mp4aBox)) + } else if self.trak.mdia.minf.stbl.stsd.opus.is_some() { + Ok(FourCC::from(BoxType::OpusBox)) } else if self.trak.mdia.minf.stbl.stsd.tx3g.is_some() { Ok(FourCC::from(BoxType::Tx3gBox)) } else {