diff --git a/Cargo.toml b/Cargo.toml index ec35a0d4..171ea20d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mp4" -version = "0.14.0" +version = "0.15.0" authors = ["Alf "] edition = "2018" description = "MP4 reader and writer library in Rust." diff --git a/src/mp4box/mod.rs b/src/mp4box/mod.rs index 4bbdd410..7c54d006 100644 --- a/src/mp4box/mod.rs +++ b/src/mp4box/mod.rs @@ -85,6 +85,8 @@ 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; pub(crate) mod stco; @@ -129,6 +131,8 @@ 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; pub use stco::StcoBox; @@ -183,6 +187,7 @@ macro_rules! boxtype { } boxtype! { + DopsBox => 0x644f7073, FtypBox => 0x66747970, MvhdBox => 0x6d766864, MfhdBox => 0x6d666864, @@ -191,6 +196,7 @@ boxtype! { MoovBox => 0x6d6f6f76, MvexBox => 0x6d766578, MehdBox => 0x6d656864, + OpusBox => 0x4f707573, TrexBox => 0x74726578, EmsgBox => 0x656d7367, MoofBox => 0x6d6f6f66, @@ -204,6 +210,7 @@ boxtype! { HdlrBox => 0x68646c72, MinfBox => 0x6d696e66, VmhdBox => 0x766d6864, + SidxBox => 0x73696478, StblBox => 0x7374626c, StsdBox => 0x73747364, SttsBox => 0x73747473, 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/sidx.rs b/src/mp4box/sidx.rs new file mode 100644 index 00000000..cd6223c7 --- /dev/null +++ b/src/mp4box/sidx.rs @@ -0,0 +1,279 @@ +use serde::Serialize; + +use crate::mp4box::*; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +pub struct SidxBox { + pub version: u8, + pub flags: u32, + + pub reference_id: u32, + pub timescale: u32, + pub earliest_presentation_time: u64, + pub first_offset: u64, + pub reserved: u16, + pub reference_count: u16, + pub segments: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +pub struct Segment { + pub reference_type: bool, + pub reference_size: u32, + pub subsegment_duration: u32, + pub starts_with_sap: bool, + pub sap_type: u8, + pub sap_delta_time: u32, +} + +impl SidxBox { + pub fn get_type(&self) -> BoxType { + BoxType::SidxBox + } + + pub fn get_size(&self) -> u64 { + let mut size = HEADER_SIZE + HEADER_EXT_SIZE; + if self.version == 1 { + size += 24; + } else if self.version == 0 { + size += 16; + } + size += 4; + size += self.reference_count as u64 * 12; + size + } +} + +impl Default for SidxBox { + fn default() -> Self { + SidxBox { + version: 0, + flags: 0, + timescale: 1000, + first_offset: 0, + earliest_presentation_time: 0, + reference_id: 0, + reserved: 0, + reference_count: 0, + segments: Vec::new(), + } + } +} + +impl Mp4Box for SidxBox { + 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!( + "timescale={} first_offset={} earliest_presentation_time={} reference_id={}, reserved={}, reference_count={}", + self.timescale, + self.first_offset, + self.earliest_presentation_time, + self.reference_id, + self.reserved, + self.reference_count, + ); + Ok(s) + } +} + +impl ReadBox<&mut R> for SidxBox { + fn read_box(reader: &mut R, size: u64) -> Result { + let start = box_start(reader)?; + + let (version, flags) = read_box_header_ext(reader)?; + + let (reference_id, timescale, earliest_presentation_time, first_offset) = if version == 0 { + ( + reader.read_u32::()?, + reader.read_u32::()?, + reader.read_u32::()? as u64, + reader.read_u32::()? as u64, + ) + } else if version == 1 { + ( + reader.read_u32::()?, + reader.read_u32::()?, + reader.read_u64::()?, + reader.read_u64::()?, + ) + } else { + return Err(Error::InvalidData("version must be 0 or 1")); + }; + let reserved = reader.read_u16::()?; + let reference_count = reader.read_u16::()?; + let mut segments = Vec::with_capacity(reference_count as usize); + + for _ in 0..reference_count { + let segment = Segment::read(reader)?; + segments.push(segment); + } + + skip_bytes_to(reader, start + size)?; + + Ok(SidxBox { + version, + flags, + reference_id, + timescale, + earliest_presentation_time, + first_offset, + reserved, + reference_count, + segments, + }) + } +} + +impl WriteBox<&mut W> for SidxBox { + fn write_box(&self, writer: &mut W) -> Result { + let size = self.box_size(); + BoxHeader::new(self.box_type(), size).write(writer)?; + + write_box_header_ext(writer, self.version, self.flags)?; + + if self.version == 1 { + writer.write_u32::(self.reference_id)?; + writer.write_u32::(self.timescale)?; + writer.write_u64::(self.earliest_presentation_time)?; + writer.write_u64::(self.first_offset)?; + } else if self.version == 0 { + writer.write_u32::(self.reference_id)?; + writer.write_u32::(self.timescale)?; + writer.write_u32::(self.earliest_presentation_time as u32)?; + writer.write_u32::(self.first_offset as u32)?; + } else { + return Err(Error::InvalidData("version must be 0 or 1")); + } + writer.write_u16::(self.reserved)?; // reserved = 0 + writer.write_u16::(self.reference_count)?; // reserved = 0 + + for segment in self.segments.iter() { + segment.write(writer)?; + } + + Ok(size) + } +} + +impl Segment { + fn size(&self) -> usize { + 12 as usize + } + + fn read(reader: &mut R) -> Result { + let reference = reader.read_u32::()?; + let reference_type = reference >> 31 == 1; + let reference_size = reference & 0x7FFFFFFF; + let subsegment_duration = reader.read_u32::()?; + let sap = reader.read_u32::()?; + let starts_with_sap = sap >> 31 == 1; + let sap_type = (sap & 0x70000000 >> 28) as u8; + let sap_delta_time = sap & 0x0FFFFFFF; + Ok(Segment { + reference_type, + reference_size, + subsegment_duration, + starts_with_sap, + sap_type, + sap_delta_time, + }) + } + + fn write(&self, writer: &mut W) -> Result { + let reference_type_flag = u32::from(self.reference_type) << 31; + writer.write_u32::(reference_type_flag | self.reference_size)?; + writer.write_u32::(self.subsegment_duration)?; + let starts_with_sap_flag = u32::from(self.starts_with_sap) << 31; + let sap_type = (self.sap_type as u32) << 28; + writer.write_u32::(starts_with_sap_flag | sap_type | self.sap_delta_time)?; + Ok(self.size() as u64) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::mp4box::BoxHeader; + use std::io::Cursor; + + #[test] + fn test_sidx32() { + let segment = Segment { + reference_type: false, + reference_size: 0, + subsegment_duration: 123000, + starts_with_sap: false, + sap_type: 0, + sap_delta_time: 0, + }; + + let src_box = SidxBox { + version: 0, + flags: 0, + reference_id: 0, + timescale: 0, + earliest_presentation_time: 1344, + first_offset: 212, + reserved: 0, + reference_count: 1, + segments: vec![segment], + }; + 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::SidxBox); + assert_eq!(src_box.box_size(), header.size); + + let dst_box = SidxBox::read_box(&mut reader, header.size).unwrap(); + assert_eq!(src_box, dst_box); + } + + #[test] + fn test_sidx64() { + let segment = Segment { + reference_type: false, + reference_size: 0, + subsegment_duration: 123000, + starts_with_sap: false, + sap_type: 0, + sap_delta_time: 0, + }; + + let src_box = SidxBox { + version: 1, + flags: 0, + reference_id: 0, + timescale: 0, + earliest_presentation_time: 1344, + first_offset: 212, + reserved: 0, + reference_count: 1, + segments: vec![segment], + }; + 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::SidxBox); + assert_eq!(src_box.box_size(), header.size); + + let dst_box = SidxBox::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/reader.rs b/src/reader.rs index e5ac2964..2b9cd2a1 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -10,6 +10,7 @@ pub struct Mp4Reader { reader: R, pub ftyp: FtypBox, pub moov: MoovBox, + pub sidx: Option, pub moofs: Vec, pub emsgs: Vec, @@ -23,6 +24,7 @@ impl Mp4Reader { let mut ftyp = None; let mut moov = None; + let mut sidx = None; let mut moofs = Vec::new(); let mut moof_offsets = Vec::new(); let mut emsgs = Vec::new(); @@ -57,6 +59,9 @@ impl Mp4Reader { BoxType::MoovBox => { moov = Some(MoovBox::read_box(&mut reader, s)?); } + BoxType::SidxBox => { + sidx = Some(SidxBox::read_box(&mut reader, s)?); + } BoxType::MoofBox => { let moof_offset = reader.stream_position()? - 8; let moof = MoofBox::read_box(&mut reader, s)?; @@ -122,6 +127,7 @@ impl Mp4Reader { reader, ftyp: ftyp.unwrap(), moov: moov.unwrap(), + sidx: sidx, moofs, emsgs, size, @@ -208,6 +214,7 @@ impl Mp4Reader { reader, ftyp: self.ftyp.clone(), moov: self.moov.clone(), + sidx: self.sidx.clone(), moofs, emsgs: Vec::new(), tracks, 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 {