diff --git a/src/mp4box/mod.rs b/src/mp4box/mod.rs index feea896..2bc749e 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 smhd; pub(crate) mod stbl; pub(crate) mod stco; @@ -206,7 +207,10 @@ boxtype! { DayBox => 0xa9646179, CovrBox => 0x636f7672, DescBox => 0x64657363, - WideBox => 0x77696465 + WideBox => 0x77696465, + DopsBox => 0x644F7073, + OpusBox => 0x4F707573 + } pub trait Mp4Box: Sized { diff --git a/src/mp4box/opus.rs b/src/mp4box/opus.rs new file mode 100644 index 0000000..7889fb9 --- /dev/null +++ b/src/mp4box/opus.rs @@ -0,0 +1,170 @@ +use crate::mp4box::*; +use crate::Mp4Box; +use serde::Serialize; + +// taken from the following sources +// - https://opus-codec.org/docs/opus_in_isobmff.html +// - chromium source code: box_definitions.h - OpusSpecificBox +// - async-mp4 crate: https://github.com/Wicpar/async-mp4/blob/master/src/mp4box/dops.rs + +// this OpusBox is a combination of the AudioSampleEntry box and OpusSpecificBox +#[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 Mp4Box for OpusBox { + fn box_type(&self) -> BoxType { + BoxType::OpusBox + } + + fn box_size(&self) -> u64 { + // the +19 is for DopsBox + 36 + 19 + } + + fn to_json(&self) -> Result { + Ok(serde_json::to_string(&self).unwrap()) + } + + fn summary(&self) -> Result { + Ok(format!("{self:?}")) + } +} + +impl WriteBox<&mut W> for OpusBox { + fn write_box(&self, writer: &mut W) -> Result { + let mut written = 0; + written += BoxHeader::new(self.box_type(), self.box_size()).write(writer)?; + + writer.write_u32::(0)?; // reserved + written += 4; + writer.write_u16::(0)?; // reserved + written += 2; + writer.write_u16::(self.data_reference_index)?; + written += 2; + + writer.write_u16::(0)?; // reserved + written += 2; + writer.write_u16::(0)?; // reserved + written += 2; + writer.write_u32::(0)?; // reserved + written += 4; + writer.write_u16::(self.channelcount)?; + written += 2; + writer.write_u16::(self.samplesize)?; + written += 2; + writer.write_u32::(0)?; // reserved + written += 4; + writer.write_u32::(self.samplerate.raw_value())?; + written += 4; + + written += self.dops.write_box(writer)?; + + assert_eq!(written, self.box_size()); + Ok(written) + } +} + +// https://github.com/Wicpar/async-mp4/blob/master/src/mp4box/dops.rs +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +pub struct DopsBox { + pub version: u8, + pub pre_skip: u16, + pub input_sample_rate: u32, + pub output_gain: i16, + pub channel_mapping_family: ChannelMappingFamily, +} + +impl Mp4Box for DopsBox { + fn box_type(&self) -> BoxType { + BoxType::DopsBox + } + + fn box_size(&self) -> u64 { + // if channel_mapping_family is updates to support more than 2 channels, + // box_size could change, depending on the channel mapping. + 19 + } + + fn to_json(&self) -> Result { + Ok(serde_json::to_string(&self).unwrap()) + } + + fn summary(&self) -> Result { + Ok(format!("{self:?}")) + } +} + +// https://github.com/Wicpar/async-mp4/blob/master/src/mp4box/dops.rs +impl WriteBox<&mut W> for DopsBox { + fn write_box(&self, writer: &mut W) -> Result { + let mut written = 0; + written += BoxHeader::new(self.box_type(), self.box_size()).write(writer)?; + writer.write_u8(self.version)?; + written += 1; + + let num_channels = match self.channel_mapping_family { + ChannelMappingFamily::Family0 { stereo } => match stereo { + true => 2, + false => 1, + }, + }; + writer.write_u8(num_channels)?; + written += 1; + writer.write_u16::(self.pre_skip)?; + written += 2; + writer.write_u32::(self.input_sample_rate)?; + written += 4; + writer.write_i16::(self.output_gain)?; + written += 2; + + // channel mapping family 0 + writer.write_u8(0)?; + written += 1; + + // todo: StreamCount? CoupledCount? ChannelMapping? + + assert_eq!(written, self.box_size()); + Ok(written) + } +} + +#[allow(dead_code)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +pub enum ChannelMappingFamily { + Family0 { stereo: bool }, +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_opus_writer() { + let dops = DopsBox { + version: 0, + pre_skip: 1, + input_sample_rate: 2, + output_gain: 3, + channel_mapping_family: ChannelMappingFamily::Family0 { stereo: false }, + }; + + let opus = OpusBox { + data_reference_index: 1, + channelcount: 1, + samplesize: 2, + samplerate: FixedPointU16::new(48000), + dops, + }; + + let mut buffer = Vec::::new(); + opus.write_box(&mut buffer).expect("write_box failed"); + } +}