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/examples/mp4copy.rs b/examples/mp4copy.rs index 98d1ba80..16df2831 100644 --- a/examples/mp4copy.rs +++ b/examples/mp4copy.rs @@ -5,8 +5,7 @@ use std::io::{self, BufReader, BufWriter}; use std::path::Path; use mp4::{ - AacConfig, AvcConfig, HevcConfig, MediaConfig, MediaType, Mp4Config, Result, TrackConfig, - TtxtConfig, Vp9Config, + AacConfig, AvcConfig, HevcBoxType, HevcConfig, MediaConfig, MediaType, Mp4Config, OpusConfig, Result, TrackConfig, TtxtConfig, Vp9Config }; fn main() { @@ -51,18 +50,59 @@ fn copy>(src_filename: &P, dst_filename: &P) -> Result<()> { pic_param_set: track.picture_parameter_set()?.to_vec(), }), MediaType::H265 => MediaConfig::HevcConfig(HevcConfig { - width: track.width(), - height: track.height(), + width: Some(track.width()), + height: Some(track.height()), + configuration_version: Some(1), + general_profile_space: Some(0), + general_tier_flag: Some(false), + general_profile_idc: Some(1), + general_profile_compatibility_flags: Some(0), + general_constraint_indicator_flag: Some(0), + general_level_idc: Some(93), + min_spatial_segmentation_idc: Some(0), + parallelism_type: Some(0), + chroma_format_idc: Some(1), + bit_depth_luma_minus8: Some(0), + bit_depth_chroma_minus8: Some(0), + avg_frame_rate: Some(0), + constant_frame_rate: Some(0), + num_temporal_layers: Some(1), + temporal_id_nested: Some(false), + length_size_minus_one: Some(3), + arrays: Some(vec![]), + use_hvc1: true, }), MediaType::VP9 => MediaConfig::Vp9Config(Vp9Config { width: track.width(), height: track.height(), }), - MediaType::AAC => MediaConfig::AacConfig(AacConfig { + MediaType::AAC => { + let default_aac_config = AacConfig::default(); + MediaConfig::AacConfig(AacConfig { + bitrate: track.bitrate(), + profile: track.audio_profile()?, + freq_index: track.sample_freq_index()?, + chan_conf: track.channel_config()?, + samplesize: 16, + data_reference_index: 1, + sound_version: 0, + esds_version: default_aac_config.esds_version, + esds_flags: default_aac_config.esds_flags, + es_id: default_aac_config.es_id, + object_type_indication: default_aac_config.object_type_indication, + stream_type: default_aac_config.stream_type, + up_stream: default_aac_config.up_stream, + buffer_size_db: default_aac_config.buffer_size_db, + max_bitrate: default_aac_config.max_bitrate, + avg_bitrate: default_aac_config.avg_bitrate, + qt_bytes: default_aac_config.qt_bytes, + }) + } + MediaType::OPUS => MediaConfig::OpusConfig(OpusConfig { bitrate: track.bitrate(), - profile: track.audio_profile()?, freq_index: track.sample_freq_index()?, chan_conf: track.channel_config()?, + pre_skip: 0, }), MediaType::TTXT => MediaConfig::TtxtConfig(TtxtConfig {}), }; diff --git a/examples/mp4dump.rs b/examples/mp4dump.rs index 6a97d9a0..3e56718c 100644 --- a/examples/mp4dump.rs +++ b/examples/mp4dump.rs @@ -96,6 +96,9 @@ fn get_boxes(file: File) -> Result> { if let Some(ref hev1) = &stbl.stsd.hev1 { boxes.push(build_box(hev1)); } + if let Some(ref hvc1) = &stbl.stsd.hvc1 { + boxes.push(build_box(hvc1)); + } if let Some(ref mp4a) = &stbl.stsd.mp4a { boxes.push(build_box(mp4a)); } diff --git a/src/error.rs b/src/error.rs index 11690f0c..4f72a76f 100644 --- a/src/error.rs +++ b/src/error.rs @@ -18,8 +18,8 @@ pub enum Error { BoxInTrakNotFound(u32, BoxType), #[error("traf[{0}].{1} not found")] BoxInTrafNotFound(u32, BoxType), - #[error("trak[{0}].stbl.{1} not found")] - BoxInStblNotFound(u32, BoxType), + #[error("trak[{0}].stbl.{1:?} not found")] + BoxInStblNotFound(u32, Vec), #[error("trak[{0}].stbl.{1}.entry[{2}] not found")] EntryInStblNotFound(u32, BoxType, u32), #[error("traf[{0}].trun.{1}.entry[{2}] not found")] diff --git a/src/mp4box/hev1.rs b/src/mp4box/hev1.rs index 3070fb81..008f727f 100644 --- a/src/mp4box/hev1.rs +++ b/src/mp4box/hev1.rs @@ -37,15 +37,34 @@ impl Default for Hev1Box { impl Hev1Box { pub fn new(config: &HevcConfig) -> Self { - Hev1Box { + Self { data_reference_index: 1, - width: config.width, - height: config.height, + width: config.width.unwrap_or(0), + height: config.height.unwrap_or(0), horizresolution: FixedPointU16::new(0x48), vertresolution: FixedPointU16::new(0x48), frame_count: 1, depth: 0x0018, - hvcc: HvcCBox::new(), + hvcc: HvcCBox { + configuration_version: config.configuration_version.unwrap_or(1), + general_profile_space: config.general_profile_space.unwrap_or(0), + general_tier_flag: config.general_tier_flag.unwrap_or(false), + general_profile_idc: config.general_profile_idc.unwrap_or(1), + general_profile_compatibility_flags: config.general_profile_compatibility_flags.unwrap_or(0), + general_constraint_indicator_flag: config.general_constraint_indicator_flag.unwrap_or(0), + general_level_idc: config.general_level_idc.unwrap_or(93), + min_spatial_segmentation_idc: config.min_spatial_segmentation_idc.unwrap_or(0), + parallelism_type: config.parallelism_type.unwrap_or(0), + chroma_format_idc: config.chroma_format_idc.unwrap_or(1), + bit_depth_luma_minus8: config.bit_depth_luma_minus8.unwrap_or(0), + bit_depth_chroma_minus8: config.bit_depth_chroma_minus8.unwrap_or(0), + avg_frame_rate: config.avg_frame_rate.unwrap_or(0), + constant_frame_rate: config.constant_frame_rate.unwrap_or(0), + num_temporal_layers: config.num_temporal_layers.unwrap_or(1), + temporal_id_nested: config.temporal_id_nested.unwrap_or(false), + length_size_minus_one: config.length_size_minus_one.unwrap_or(3), + arrays: config.arrays.clone().unwrap_or_default(), + }, } } @@ -180,6 +199,10 @@ pub struct HvcCBox { pub arrays: Vec, } +const VPS: u8 = 32; +const SPS: u8 = 33; +const PPS: u8 = 34; + impl HvcCBox { pub fn new() -> Self { Self { @@ -187,6 +210,27 @@ impl HvcCBox { ..Default::default() } } + + fn parameter_set(&self, track_id: u32, nal_type: u8) -> Result<&[u8]> { + for array in &self.arrays { + if array.nal_unit_type == nal_type { + return Ok(&array.nalus[0].data); + } + } + Err(Error::EntryInStblNotFound(track_id, BoxType::HvcCBox, 0)) + } + + pub fn sequence_parameter_set(&self, track_id: u32) -> Result<&[u8]> { + self.parameter_set(track_id, SPS) + } + + pub fn picture_parameter_set(&self, track_id: u32) -> Result<&[u8]> { + self.parameter_set(track_id, PPS) + } + + pub fn video_parameter_set(&self, track_id: u32) -> Result<&[u8]> { + self.parameter_set(track_id, VPS) + } } impl Mp4Box for HvcCBox { diff --git a/src/mp4box/hvc1.rs b/src/mp4box/hvc1.rs new file mode 100644 index 00000000..8f79f8d7 --- /dev/null +++ b/src/mp4box/hvc1.rs @@ -0,0 +1,241 @@ +use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; +use serde::Serialize; +use std::io::{Read, Seek, Write}; + +use crate::{hev1::HvcCBox, mp4box::*}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +pub struct Hvc1Box { + pub data_reference_index: u16, + pub width: u16, + pub height: u16, + + #[serde(with = "value_u32")] + pub horizresolution: FixedPointU16, + + #[serde(with = "value_u32")] + pub vertresolution: FixedPointU16, + pub frame_count: u16, + pub depth: u16, + pub hvcc: HvcCBox, +} + +impl Default for Hvc1Box { + fn default() -> Self { + Hvc1Box { + data_reference_index: 0, + width: 0, + height: 0, + horizresolution: FixedPointU16::new(0x48), + vertresolution: FixedPointU16::new(0x48), + frame_count: 1, + depth: 0x0018, + hvcc: HvcCBox::default(), + } + } +} + +impl Hvc1Box { + pub fn new(config: &HevcConfig) -> Self { + Self { + data_reference_index: 1, + width: config.width.unwrap_or(0), + height: config.height.unwrap_or(0), + horizresolution: FixedPointU16::new(0x48), + vertresolution: FixedPointU16::new(0x48), + frame_count: 1, + depth: 0x0018, + hvcc: HvcCBox { + configuration_version: config.configuration_version.unwrap_or(1), + general_profile_space: config.general_profile_space.unwrap_or(0), + general_tier_flag: config.general_tier_flag.unwrap_or(false), + general_profile_idc: config.general_profile_idc.unwrap_or(1), + general_profile_compatibility_flags: config.general_profile_compatibility_flags.unwrap_or(0), + general_constraint_indicator_flag: config.general_constraint_indicator_flag.unwrap_or(0), + general_level_idc: config.general_level_idc.unwrap_or(93), + min_spatial_segmentation_idc: config.min_spatial_segmentation_idc.unwrap_or(0), + parallelism_type: config.parallelism_type.unwrap_or(0), + chroma_format_idc: config.chroma_format_idc.unwrap_or(1), + bit_depth_luma_minus8: config.bit_depth_luma_minus8.unwrap_or(0), + bit_depth_chroma_minus8: config.bit_depth_chroma_minus8.unwrap_or(0), + avg_frame_rate: config.avg_frame_rate.unwrap_or(0), + constant_frame_rate: config.constant_frame_rate.unwrap_or(0), + num_temporal_layers: config.num_temporal_layers.unwrap_or(1), + temporal_id_nested: config.temporal_id_nested.unwrap_or(false), + length_size_minus_one: config.length_size_minus_one.unwrap_or(3), + arrays: config.arrays.clone().unwrap_or_default(), + }, + } + } + + pub fn get_type(&self) -> BoxType { + BoxType::Hvc1Box + } + + pub fn get_size(&self) -> u64 { + HEADER_SIZE + 8 + 70 + self.hvcc.box_size() + } +} + +impl Mp4Box for Hvc1Box { + 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!( + "data_reference_index={} width={} height={} frame_count={}", + self.data_reference_index, self.width, self.height, self.frame_count + ); + Ok(s) + } +} + +impl ReadBox<&mut R> for Hvc1Box { + 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_u32::()?; // pre-defined, reserved + reader.read_u64::()?; // pre-defined + reader.read_u32::()?; // pre-defined + let width = reader.read_u16::()?; + let height = reader.read_u16::()?; + let horizresolution = FixedPointU16::new_raw(reader.read_u32::()?); + let vertresolution = FixedPointU16::new_raw(reader.read_u32::()?); + reader.read_u32::()?; // reserved + let frame_count = reader.read_u16::()?; + skip_bytes(reader, 32)?; // compressorname + let depth = reader.read_u16::()?; + reader.read_i16::()?; // pre-defined + + let header = BoxHeader::read(reader)?; + let BoxHeader { name, size: s } = header; + if s > size { + return Err(Error::InvalidData( + "hev1 box contains a box with a larger size than it", + )); + } + if name == BoxType::HvcCBox { + let hvcc = HvcCBox::read_box(reader, s)?; + + skip_bytes_to(reader, start + size)?; + + Ok(Hvc1Box { + data_reference_index, + width, + height, + horizresolution, + vertresolution, + frame_count, + depth, + hvcc, + }) + } else { + Err(Error::InvalidData("hvcc not found")) + } + } +} + +impl WriteBox<&mut W> for Hvc1Box { + 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_u32::(0)?; // pre-defined, reserved + writer.write_u64::(0)?; // pre-defined + writer.write_u32::(0)?; // pre-defined + writer.write_u16::(self.width)?; + writer.write_u16::(self.height)?; + writer.write_u32::(self.horizresolution.raw_value())?; + writer.write_u32::(self.vertresolution.raw_value())?; + writer.write_u32::(0)?; // reserved + writer.write_u16::(self.frame_count)?; + // skip compressorname + write_zeros(writer, 32)?; + writer.write_u16::(self.depth)?; + writer.write_i16::(-1)?; // pre-defined + + // Write the hvcc configuration data without its header + writer.write_u8(self.hvcc.configuration_version)?; + let general_profile_space = (self.hvcc.general_profile_space & 0b11) << 6; + let general_tier_flag = u8::from(self.hvcc.general_tier_flag) << 5; + let general_profile_idc = self.hvcc.general_profile_idc & 0b11111; + writer.write_u8(general_profile_space | general_tier_flag | general_profile_idc)?; + writer.write_u32::(self.hvcc.general_profile_compatibility_flags)?; + writer.write_u48::(self.hvcc.general_constraint_indicator_flag)?; + writer.write_u8(self.hvcc.general_level_idc)?; + writer.write_u16::(self.hvcc.min_spatial_segmentation_idc & 0x0FFF)?; + writer.write_u8(self.hvcc.parallelism_type & 0b11)?; + writer.write_u8(self.hvcc.chroma_format_idc & 0b11)?; + writer.write_u8(self.hvcc.bit_depth_luma_minus8 & 0b111)?; + writer.write_u8(self.hvcc.bit_depth_chroma_minus8 & 0b111)?; + writer.write_u16::(self.hvcc.avg_frame_rate)?; + let constant_frame_rate = (self.hvcc.constant_frame_rate & 0b11) << 6; + let num_temporal_layers = (self.hvcc.num_temporal_layers & 0b111) << 3; + let temporal_id_nested = u8::from(self.hvcc.temporal_id_nested) << 2; + let length_size_minus_one = self.hvcc.length_size_minus_one & 0b11; + writer.write_u8(constant_frame_rate | num_temporal_layers | temporal_id_nested | length_size_minus_one)?; + writer.write_u8(self.hvcc.arrays.len() as u8)?; + for arr in &self.hvcc.arrays { + writer.write_u8((arr.nal_unit_type & 0b111111) | u8::from(arr.completeness) << 7)?; + writer.write_u16::(arr.nalus.len() as _)?; + for nalu in &arr.nalus { + writer.write_u16::(nalu.size)?; + writer.write_all(&nalu.data)?; + } + } + + Ok(size) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::mp4box::BoxHeader; + use std::io::Cursor; + + #[test] + fn test_hvc1() { + let src_box = Hvc1Box { + data_reference_index: 1, + width: 320, + height: 240, + horizresolution: FixedPointU16::new(0x48), + vertresolution: FixedPointU16::new(0x48), + frame_count: 1, + depth: 24, + hvcc: HvcCBox { + configuration_version: 1, + ..Default::default() + }, + }; + 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::Hvc1Box); + assert_eq!(src_box.box_size(), header.size); + + let dst_box = Hvc1Box::read_box(&mut reader, header.size).unwrap(); + assert_eq!(src_box, dst_box); + } +} diff --git a/src/mp4box/mod.rs b/src/mp4box/mod.rs index 4bbdd410..248b8cc7 100644 --- a/src/mp4box/mod.rs +++ b/src/mp4box/mod.rs @@ -73,6 +73,7 @@ pub(crate) mod emsg; pub(crate) mod ftyp; pub(crate) mod hdlr; pub(crate) mod hev1; +pub(crate) mod hvc1; pub(crate) mod ilst; pub(crate) mod mdhd; pub(crate) mod mdia; @@ -85,6 +86,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 +132,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 +188,7 @@ macro_rules! boxtype { } boxtype! { + DopsBox => 0x644f7073, FtypBox => 0x66747970, MvhdBox => 0x6d766864, MfhdBox => 0x6d666864, @@ -191,6 +197,7 @@ boxtype! { MoovBox => 0x6d6f6f76, MvexBox => 0x6d766578, MehdBox => 0x6d656864, + OpusBox => 0x4f707573, TrexBox => 0x74726578, EmsgBox => 0x656d7367, MoofBox => 0x6d6f6f66, @@ -204,6 +211,7 @@ boxtype! { HdlrBox => 0x68646c72, MinfBox => 0x6d696e66, VmhdBox => 0x766d6864, + SidxBox => 0x73696478, StblBox => 0x7374626c, StsdBox => 0x73747364, SttsBox => 0x73747473, @@ -225,6 +233,7 @@ boxtype! { Avc1Box => 0x61766331, AvcCBox => 0x61766343, Hev1Box => 0x68657631, + Hvc1Box => 0x68766331, HvcCBox => 0x68766343, Mp4aBox => 0x6d703461, EsdsBox => 0x65736473, diff --git a/src/mp4box/mp4a.rs b/src/mp4box/mp4a.rs index a80c6c46..06fdb347 100644 --- a/src/mp4box/mp4a.rs +++ b/src/mp4box/mp4a.rs @@ -7,8 +7,10 @@ use crate::mp4box::*; #[derive(Debug, Clone, PartialEq, Eq, Serialize)] pub struct Mp4aBox { pub data_reference_index: u16, + pub sound_version: u16, pub channelcount: u16, pub samplesize: u16, + pub qt_bytes: Option>, #[serde(with = "value_u32")] pub samplerate: FixedPointU16, @@ -19,9 +21,11 @@ impl Default for Mp4aBox { fn default() -> Self { Self { data_reference_index: 0, + sound_version: 0, channelcount: 2, samplesize: 16, samplerate: FixedPointU16::new(48000), + qt_bytes: None, esds: Some(EsdsBox::default()), } } @@ -30,10 +34,12 @@ impl Default for Mp4aBox { impl Mp4aBox { pub fn new(config: &AacConfig) -> Self { Self { - data_reference_index: 1, + data_reference_index: config.data_reference_index, + sound_version: config.sound_version, channelcount: config.chan_conf as u16, - samplesize: 16, + samplesize: config.samplesize, samplerate: FixedPointU16::new(config.freq_index.freq() as u16), + qt_bytes: config.qt_bytes.clone(), esds: Some(EsdsBox::new(config)), } } @@ -44,6 +50,11 @@ impl Mp4aBox { pub fn get_size(&self) -> u64 { let mut size = HEADER_SIZE + 8 + 20; + if self.sound_version != 0 { + if let Some(ref qt_bytes) = self.qt_bytes { + size += qt_bytes.len() as u64; + } + } if let Some(ref esds) = self.esds { size += esds.box_size(); } @@ -82,20 +93,23 @@ impl ReadBox<&mut R> for Mp4aBox { reader.read_u32::()?; // reserved reader.read_u16::()?; // reserved let data_reference_index = reader.read_u16::()?; - let version = reader.read_u16::()?; + let sound_version: u16 = reader.read_u16::()?; reader.read_u16::()?; // reserved reader.read_u32::()?; // reserved let channelcount = reader.read_u16::()?; let samplesize = reader.read_u16::()?; reader.read_u32::()?; // pre-defined, reserved let samplerate = FixedPointU16::new_raw(reader.read_u32::()?); - - if version == 1 { - // Skip QTFF - reader.read_u64::()?; - reader.read_u64::()?; + let mut qt_bytes = None; + if sound_version == 1 { + let mut buffer = [0u8; 16]; + reader.read_exact(&mut buffer)?; + qt_bytes = Some(buffer.to_vec()); + } else if sound_version == 2 { + let mut buffer = [0u8; 36]; + reader.read_exact(&mut buffer)?; + qt_bytes = Some(buffer.to_vec()); } - // Find esds in mp4a or wave let mut esds = None; let end = start + size; @@ -127,9 +141,11 @@ impl ReadBox<&mut R> for Mp4aBox { Ok(Mp4aBox { data_reference_index, + sound_version: sound_version, channelcount, samplesize, samplerate, + qt_bytes, esds, }) } @@ -144,12 +160,22 @@ impl WriteBox<&mut W> for Mp4aBox { writer.write_u16::(0)?; // reserved writer.write_u16::(self.data_reference_index)?; - writer.write_u64::(0)?; // reserved + writer.write_u16::(self.sound_version)?; // version = 1 + + writer.write_u16::(0)?; // reserved + writer.write_u32::(0)?; // reserved + writer.write_u16::(self.channelcount)?; writer.write_u16::(self.samplesize)?; writer.write_u32::(0)?; // reserved writer.write_u32::(self.samplerate.raw_value())?; + if self.sound_version != 0 { + if let Some(ref qt_bytes) = self.qt_bytes { + writer.write_all(&qt_bytes)?; + } + } + if let Some(ref esds) = self.esds { esds.write_box(writer)?; } @@ -181,11 +207,12 @@ impl Mp4Box for EsdsBox { } fn box_size(&self) -> u64 { + let es_desc_size = self.es_desc.desc_size(); HEADER_SIZE + HEADER_EXT_SIZE + 1 - + size_of_length(ESDescriptor::desc_size()) as u64 - + ESDescriptor::desc_size() as u64 + + size_of_length(es_desc_size) as u64 + + es_desc_size as u64 } fn to_json(&self) -> Result { @@ -247,7 +274,7 @@ impl WriteBox<&mut W> for EsdsBox { trait Descriptor: Sized { fn desc_tag() -> u8; - fn desc_size() -> u32; + fn desc_size(&self) -> u32; } trait ReadDesc: Sized { @@ -273,13 +300,8 @@ fn read_desc(reader: &mut R) -> Result<(u8, u32)> { Ok((tag, size)) } -fn size_of_length(size: u32) -> u32 { - match size { - 0x0..=0x7F => 1, - 0x80..=0x3FFF => 2, - 0x4000..=0x1FFFFF => 3, - _ => 4, - } +fn size_of_length(_: u32) -> u32 { + return 4; } fn write_desc(writer: &mut W, tag: u8, size: u32) -> Result { @@ -312,10 +334,12 @@ pub struct ESDescriptor { impl ESDescriptor { pub fn new(config: &AacConfig) -> Self { + let dec_config = DecoderConfigDescriptor::new(config); + let sl_config = SLConfigDescriptor::new(); Self { - es_id: 1, - dec_config: DecoderConfigDescriptor::new(config), - sl_config: SLConfigDescriptor::new(), + es_id: config.es_id.unwrap_or(0), + dec_config: dec_config, + sl_config: sl_config, } } } @@ -325,13 +349,15 @@ impl Descriptor for ESDescriptor { 0x03 } - fn desc_size() -> u32 { + fn desc_size(&self) -> u32 { + let dec_config_size = self.dec_config.desc_size(); + let sl_config_size = self.sl_config.desc_size(); 3 + 1 - + size_of_length(DecoderConfigDescriptor::desc_size()) - + DecoderConfigDescriptor::desc_size() + + size_of_length(dec_config_size) + + dec_config_size + 1 - + size_of_length(SLConfigDescriptor::desc_size()) - + SLConfigDescriptor::desc_size() + + size_of_length(sl_config_size) + + sl_config_size } } @@ -373,7 +399,7 @@ impl ReadDesc<&mut R> for ESDescriptor { impl WriteDesc<&mut W> for ESDescriptor { fn write_desc(&self, writer: &mut W) -> Result { - let size = Self::desc_size(); + let size = self.desc_size(); write_desc(writer, Self::desc_tag(), size)?; writer.write_u16::(self.es_id)?; @@ -401,12 +427,12 @@ pub struct DecoderConfigDescriptor { impl DecoderConfigDescriptor { pub fn new(config: &AacConfig) -> Self { Self { - object_type_indication: 0x40, // XXX AAC - stream_type: 0x05, // XXX Audio - up_stream: 0, - buffer_size_db: 0, - max_bitrate: config.bitrate, // XXX - avg_bitrate: config.bitrate, + object_type_indication: config.object_type_indication.unwrap_or(0x40), // XXX AAC + stream_type: config.stream_type.unwrap_or(0x05), // XXX Audio + up_stream: config.up_stream.unwrap_or(0), + buffer_size_db: config.buffer_size_db.unwrap_or(0), + max_bitrate: config.max_bitrate.unwrap_or(config.bitrate), // XXX + avg_bitrate: config.max_bitrate.unwrap_or(config.bitrate), dec_specific: DecoderSpecificDescriptor::new(config), } } @@ -417,10 +443,9 @@ impl Descriptor for DecoderConfigDescriptor { 0x04 } - fn desc_size() -> u32 { - 13 + 1 - + size_of_length(DecoderSpecificDescriptor::desc_size()) - + DecoderSpecificDescriptor::desc_size() + fn desc_size(&self) -> u32 { + let dec_specific_size = self.dec_specific.desc_size(); + 13 + 1 + size_of_length(dec_specific_size) + dec_specific_size } } @@ -467,11 +492,11 @@ impl ReadDesc<&mut R> for DecoderConfigDescriptor { impl WriteDesc<&mut W> for DecoderConfigDescriptor { fn write_desc(&self, writer: &mut W) -> Result { - let size = Self::desc_size(); + let size = self.desc_size(); write_desc(writer, Self::desc_tag(), size)?; writer.write_u8(self.object_type_indication)?; - writer.write_u8((self.stream_type << 2) + (self.up_stream & 0x02) + 1)?; // 1 reserved + writer.write_u8((self.stream_type << 2) | ((self.up_stream & 0x01) << 1) | 0)?; writer.write_u24::(self.buffer_size_db)?; writer.write_u32::(self.max_bitrate)?; writer.write_u32::(self.avg_bitrate)?; @@ -504,15 +529,19 @@ impl Descriptor for DecoderSpecificDescriptor { 0x05 } - fn desc_size() -> u32 { - 2 + fn desc_size(&self) -> u32 { + if self.profile < 31 { + 2 + } else { + 3 + } } } fn get_audio_object_type(byte_a: u8, byte_b: u8) -> u8 { let mut profile = byte_a >> 3; if profile == 31 { - profile = 32 + ((byte_a & 7) | (byte_b >> 5)); + profile = 32 + ((byte_a & 7) << 3 | (byte_b >> 5)); } profile @@ -531,7 +560,7 @@ fn get_chan_conf( chan_conf = ((sample_rate >> 4) & 0x0F) as u8; } else if extended_profile { let byte_c = reader.read_u8()?; - chan_conf = (byte_b & 1) | (byte_c & 0xE0); + chan_conf = (byte_b & 1) << 3 | (byte_c >> 5); } else { chan_conf = (byte_b >> 3) & 0x0F; } @@ -564,11 +593,18 @@ impl ReadDesc<&mut R> for DecoderSpecificDescriptor { impl WriteDesc<&mut W> for DecoderSpecificDescriptor { fn write_desc(&self, writer: &mut W) -> Result { - let size = Self::desc_size(); + let size = self.desc_size(); write_desc(writer, Self::desc_tag(), size)?; - writer.write_u8((self.profile << 3) + (self.freq_index >> 1))?; - writer.write_u8((self.freq_index << 7) + (self.chan_conf << 3))?; + if self.profile < 31 { + writer.write_u8((self.profile << 3) | (self.freq_index >> 1))?; + writer.write_u8((self.freq_index << 7) | (self.chan_conf << 3))?; + } else { + let profile_minus_32 = self.profile - 32; + writer.write_u8(31u8 << 3 | profile_minus_32 >> 3)?; + writer.write_u8(profile_minus_32 << 5 | self.freq_index << 1 | self.chan_conf >> 7)?; + writer.write_u8((self.chan_conf & 7) << 5)?; + } Ok(size) } @@ -588,7 +624,7 @@ impl Descriptor for SLConfigDescriptor { 0x06 } - fn desc_size() -> u32 { + fn desc_size(&self) -> u32 { 1 } } @@ -603,7 +639,7 @@ impl ReadDesc<&mut R> for SLConfigDescriptor { impl WriteDesc<&mut W> for SLConfigDescriptor { fn write_desc(&self, writer: &mut W) -> Result { - let size = Self::desc_size(); + let size = self.desc_size(); write_desc(writer, Self::desc_tag(), size)?; writer.write_u8(2)?; // pre-defined @@ -621,8 +657,10 @@ mod tests { fn test_mp4a() { let src_box = Mp4aBox { data_reference_index: 1, + sound_version: 0, channelcount: 2, samplesize: 16, + qt_bytes: None, samplerate: FixedPointU16::new(48000), esds: Some(EsdsBox { version: 0, @@ -659,13 +697,58 @@ mod tests { assert_eq!(src_box, dst_box); } + #[test] + fn test_mp4a_with_qt_bytes() { + let src_box = Mp4aBox { + data_reference_index: 1, + sound_version: 1, + channelcount: 2, + samplesize: 16, + qt_bytes: Some([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1].to_vec()), + samplerate: FixedPointU16::new(48000), + esds: Some(EsdsBox { + version: 0, + flags: 0, + es_desc: ESDescriptor { + es_id: 2, + dec_config: DecoderConfigDescriptor { + object_type_indication: 0x40, + stream_type: 0x05, + up_stream: 0, + buffer_size_db: 0, + max_bitrate: 67695, + avg_bitrate: 67695, + dec_specific: DecoderSpecificDescriptor { + profile: 2, + freq_index: 3, + chan_conf: 1, + }, + }, + sl_config: SLConfigDescriptor::default(), + }, + }), + }; + 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::Mp4aBox); + assert_eq!(src_box.box_size(), header.size); + let dst_box = Mp4aBox::read_box(&mut reader, header.size).unwrap(); + assert_eq!(src_box, dst_box); + } + #[test] fn test_mp4a_no_esds() { let src_box = Mp4aBox { data_reference_index: 1, + sound_version: 0, channelcount: 2, samplesize: 16, samplerate: FixedPointU16::new(48000), + qt_bytes: None, esds: None, }; let mut buf = Vec::new(); @@ -680,4 +763,48 @@ mod tests { let dst_box = Mp4aBox::read_box(&mut reader, header.size).unwrap(); assert_eq!(src_box, dst_box); } + + #[test] + fn test_decoder_specific_descriptor() { + let test_dec_spec = |src_dec_spec: DecoderSpecificDescriptor| { + let mut buf = Vec::new(); + let written = src_dec_spec.write_desc(&mut buf).unwrap(); + // expect two extra bytes for the tag and size fields + assert_eq!(buf.len(), written as usize + 5); + + let mut reader = Cursor::new(&buf); + let (tag, size) = read_desc(&mut reader).unwrap(); + assert_eq!(5, tag); + assert_eq!(size, written); + + let dst_dec_spec = DecoderSpecificDescriptor::read_desc(&mut reader, written).unwrap(); + assert_eq!(src_dec_spec, dst_dec_spec); + }; + + test_dec_spec(DecoderSpecificDescriptor { + profile: 2, // LC + freq_index: 4, // 44100 + chan_conf: 2, // Stereo + }); + test_dec_spec(DecoderSpecificDescriptor { + profile: 5, // SpectralBandReplication (HEv1) + freq_index: 3, // 48000 + chan_conf: 1, // Mono + }); + test_dec_spec(DecoderSpecificDescriptor { + profile: 29, // ParametricStereo (HEv2) + freq_index: 2, // 64000 + chan_conf: 7, // SevenOne + }); + test_dec_spec(DecoderSpecificDescriptor { + profile: 34, // MpegLayer3 + freq_index: 4, // 44100 + chan_conf: 2, // Stereo + }); + test_dec_spec(DecoderSpecificDescriptor { + profile: 42, // UnifiedSpeechAudioCoding (xHE) + freq_index: 11, // 8000 + chan_conf: 6, // FiveOne + }); + } } diff --git a/src/mp4box/opus.rs b/src/mp4box/opus.rs new file mode 100644 index 00000000..7a778b6d --- /dev/null +++ b/src/mp4box/opus.rs @@ -0,0 +1,346 @@ +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 new(config: &OpusConfig) -> Self { + Self { + channel_count: config.chan_conf as u16, + sample_size: 16, + sample_rate: FixedPointU16::new(config.freq_index.freq() as u16), + dops_box: Some(DopsBox::new(config)), + data_reference_index: 1, + } + } + + 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 DopsBox { + pub fn new(config: &OpusConfig) -> Self { + Self { + version: 0, + output_channel_count: config.chan_conf as u8, + pre_skip: config.pre_skip, + input_sample_rate: config.freq_index.freq(), + output_gain: 0, + channel_mapping_family: 0, + channel_mapping_table: None, + } + } +} + +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..d8d61276 100644 --- a/src/mp4box/stsd.rs +++ b/src/mp4box/stsd.rs @@ -4,7 +4,9 @@ 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, hvc1::Hvc1Box, mp4a::Mp4aBox, opus::OpusBox, tx3g::Tx3gBox, +}; #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)] pub struct StsdBox { @@ -17,12 +19,18 @@ pub struct StsdBox { #[serde(skip_serializing_if = "Option::is_none")] pub hev1: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub hvc1: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub vp09: Option, #[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,6 +50,8 @@ impl StsdBox { size += vp09.box_size(); } else if let Some(ref mp4a) = self.mp4a { size += mp4a.box_size(); + } else if let Some(ref opus) = self.opus { + size += opus.box_size(); } else if let Some(ref tx3g) = self.tx3g { size += tx3g.box_size(); } @@ -78,8 +88,10 @@ impl ReadBox<&mut R> for StsdBox { let mut avc1 = None; let mut hev1 = None; + let mut hvc1 = None; let mut vp09 = None; let mut mp4a = None; + let mut opus = None; let mut tx3g = None; // Get box header. @@ -98,12 +110,18 @@ impl ReadBox<&mut R> for StsdBox { BoxType::Hev1Box => { hev1 = Some(Hev1Box::read_box(reader, s)?); } + BoxType::Hvc1Box => { + hvc1 = Some(Hvc1Box::read_box(reader, s)?); + } BoxType::Vp09Box => { vp09 = Some(Vp09Box::read_box(reader, s)?); } 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)?); } @@ -117,7 +135,9 @@ impl ReadBox<&mut R> for StsdBox { flags, avc1, hev1, + hvc1, vp09, + opus, mp4a, tx3g, }) @@ -143,6 +163,8 @@ impl WriteBox<&mut W> for StsdBox { mp4a.write_box(writer)?; } else if let Some(ref tx3g) = self.tx3g { tx3g.write_box(writer)?; + } else if let Some(ref opus) = self.opus { + opus.write_box(writer)?; } Ok(size) diff --git a/src/reader.rs b/src/reader.rs index e5ac2964..530a477a 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, @@ -254,7 +261,7 @@ impl Mp4Reader { Err(Error::TrakNotFound(track_id)) } } - + //Mp4SampleMetadata pub fn read_sample(&mut self, track_id: u32, sample_id: u32) -> Result> { if let Some(track) = self.tracks.get(&track_id) { track.read_sample(&mut self.reader, sample_id) @@ -263,6 +270,18 @@ impl Mp4Reader { } } + pub fn read_sample_metadata( + &mut self, + track_id: u32, + sample_id: u32, + ) -> Result> { + if let Some(track) = self.tracks.get(&track_id) { + track.read_sample_metadata(sample_id) + } else { + Err(Error::TrakNotFound(track_id)) + } + } + pub fn sample_offset(&mut self, track_id: u32, sample_id: u32) -> Result { if let Some(track) = self.tracks.get(&track_id) { track.sample_offset(sample_id) diff --git a/src/track.rs b/src/track.rs index 7eada834..b760b470 100644 --- a/src/track.rs +++ b/src/track.rs @@ -4,13 +4,16 @@ use std::convert::TryFrom; use std::io::{Read, Seek, SeekFrom, Write}; use std::time::Duration; +use crate::elst::ElstEntry; +use crate::hvc1::Hvc1Box; +use crate::mp4a::EsdsBox; use crate::mp4box::traf::TrafBox; use crate::mp4box::trak::TrakBox; use crate::mp4box::trun::TrunBox; use crate::mp4box::{ avc1::Avc1Box, co64::Co64Box, ctts::CttsBox, ctts::CttsEntry, hev1::Hev1Box, mp4a::Mp4aBox, - smhd::SmhdBox, stco::StcoBox, stsc::StscEntry, stss::StssBox, stts::SttsEntry, tx3g::Tx3gBox, - vmhd::VmhdBox, vp09::Vp09Box, + opus::OpusBox, smhd::SmhdBox, stco::StcoBox, stsc::StscEntry, stss::StssBox, stts::SttsEntry, + tx3g::Tx3gBox, vmhd::VmhdBox, vp09::Vp09Box, }; use crate::*; @@ -30,6 +33,7 @@ impl From for TrackConfig { MediaConfig::AacConfig(aac_conf) => Self::from(aac_conf), MediaConfig::TtxtConfig(ttxt_conf) => Self::from(ttxt_conf), MediaConfig::Vp9Config(vp9_config) => Self::from(vp9_config), + MediaConfig::OpusConfig(opus_config) => Self::from(opus_config), } } } @@ -89,6 +93,17 @@ impl From for TrackConfig { } } +impl From for TrackConfig { + fn from(opus_conf: OpusConfig) -> Self { + Self { + track_type: TrackType::Audio, + timescale: 1000, // XXX + language: String::from("und"), // XXX + media_conf: MediaConfig::OpusConfig(opus_conf), + } + } +} + #[derive(Debug)] pub struct Mp4Track { pub trak: TrakBox, @@ -121,7 +136,9 @@ impl Mp4Track { pub fn media_type(&self) -> Result { if self.trak.mdia.minf.stbl.stsd.avc1.is_some() { Ok(MediaType::H264) - } else if self.trak.mdia.minf.stbl.stsd.hev1.is_some() { + } else if self.trak.mdia.minf.stbl.stsd.hev1.is_some() + || self.trak.mdia.minf.stbl.stsd.hvc1.is_some() + { Ok(MediaType::H265) } else if self.trak.mdia.minf.stbl.stsd.vp09.is_some() { Ok(MediaType::VP9) @@ -129,6 +146,8 @@ impl Mp4Track { Ok(MediaType::AAC) } else if self.trak.mdia.minf.stbl.stsd.tx3g.is_some() { Ok(MediaType::TTXT) + } else if self.trak.mdia.minf.stbl.stsd.opus.is_some() { + Ok(MediaType::OPUS) } else { Err(Error::InvalidData("unsupported media type")) } @@ -139,12 +158,16 @@ impl Mp4Track { Ok(FourCC::from(BoxType::Avc1Box)) } else if self.trak.mdia.minf.stbl.stsd.hev1.is_some() { Ok(FourCC::from(BoxType::Hev1Box)) + } else if self.trak.mdia.minf.stbl.stsd.hvc1.is_some() { + Ok(FourCC::from(BoxType::Hvc1Box)) } else if self.trak.mdia.minf.stbl.stsd.vp09.is_some() { 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.tx3g.is_some() { Ok(FourCC::from(BoxType::Tx3gBox)) + } else if self.trak.mdia.minf.stbl.stsd.opus.is_some() { + Ok(FourCC::from(BoxType::OpusBox)) } else { Err(Error::InvalidData("unsupported sample entry box")) } @@ -153,6 +176,10 @@ impl Mp4Track { pub fn width(&self) -> u16 { if let Some(ref avc1) = self.trak.mdia.minf.stbl.stsd.avc1 { avc1.width + } else if let Some(ref hev1) = self.trak.mdia.minf.stbl.stsd.hev1 { + hev1.width + } else if let Some(ref hvc1) = self.trak.mdia.minf.stbl.stsd.hvc1 { + hvc1.width } else { self.trak.tkhd.width.value() } @@ -161,6 +188,10 @@ impl Mp4Track { pub fn height(&self) -> u16 { if let Some(ref avc1) = self.trak.mdia.minf.stbl.stsd.avc1 { avc1.height + } else if let Some(ref hev1) = self.trak.mdia.minf.stbl.stsd.hev1 { + hev1.height + } else if let Some(ref hvc1) = self.trak.mdia.minf.stbl.stsd.hvc1 { + hvc1.height } else { self.trak.tkhd.height.value() } @@ -180,10 +211,25 @@ impl Mp4Track { if let Some(ref esds) = mp4a.esds { SampleFreqIndex::try_from(esds.es_desc.dec_config.dec_specific.freq_index) } else { - Err(Error::BoxInStblNotFound(self.track_id(), BoxType::EsdsBox)) + Err(Error::BoxInStblNotFound( + self.track_id(), + vec![BoxType::EsdsBox], + )) + } + } else if let Some(ref opus) = self.trak.mdia.minf.stbl.stsd.opus { + if let Some(ref dops) = opus.dops_box { + SampleFreqIndex::try_from(dops.input_sample_rate) + } else { + Err(Error::BoxInStblNotFound( + self.track_id(), + vec![BoxType::DopsBox], + )) } } else { - Err(Error::BoxInStblNotFound(self.track_id(), BoxType::Mp4aBox)) + Err(Error::BoxInStblNotFound( + self.track_id(), + vec![BoxType::Mp4aBox], + )) } } @@ -192,10 +238,25 @@ impl Mp4Track { if let Some(ref esds) = mp4a.esds { ChannelConfig::try_from(esds.es_desc.dec_config.dec_specific.chan_conf) } else { - Err(Error::BoxInStblNotFound(self.track_id(), BoxType::EsdsBox)) + Err(Error::BoxInStblNotFound( + self.track_id(), + vec![BoxType::EsdsBox], + )) + } + } else if let Some(ref opus) = self.trak.mdia.minf.stbl.stsd.opus { + if let Some(ref dops) = opus.dops_box { + ChannelConfig::try_from(dops.output_channel_count) + } else { + Err(Error::BoxInStblNotFound( + self.track_id(), + vec![BoxType::DopsBox], + )) } } else { - Err(Error::BoxInStblNotFound(self.track_id(), BoxType::Mp4aBox)) + Err(Error::BoxInStblNotFound( + self.track_id(), + vec![BoxType::Mp4aBox], + )) } } @@ -255,13 +316,16 @@ impl Mp4Track { avc1.avcc.profile_compatibility, )) } else { - Err(Error::BoxInStblNotFound(self.track_id(), BoxType::Avc1Box)) + Err(Error::BoxInStblNotFound( + self.track_id(), + vec![BoxType::Avc1Box], + )) } } pub fn sequence_parameter_set(&self) -> Result<&[u8]> { if let Some(ref avc1) = self.trak.mdia.minf.stbl.stsd.avc1 { - match avc1.avcc.sequence_parameter_sets.get(0) { + match avc1.avcc.sequence_parameter_sets.first() { Some(nal) => Ok(nal.bytes.as_ref()), None => Err(Error::EntryInStblNotFound( self.track_id(), @@ -269,14 +333,33 @@ impl Mp4Track { 0, )), } + } else if let Some(ref hev1) = self.trak.mdia.minf.stbl.stsd.hev1 { + hev1.hvcc.sequence_parameter_set(self.track_id()) + } else if let Some(ref hvc1) = self.trak.mdia.minf.stbl.stsd.hvc1 { + hvc1.hvcc.sequence_parameter_set(self.track_id()) } else { - Err(Error::BoxInStblNotFound(self.track_id(), BoxType::Avc1Box)) + Err(Error::BoxInStblNotFound( + self.track_id(), + vec![BoxType::Avc1Box, BoxType::Hev1Box, BoxType::Hvc1Box], + )) } } + pub fn get_esds(&self) -> Result<&EsdsBox> { + if let Some(ref mp4a) = self.trak.mdia.minf.stbl.stsd.mp4a { + if let Some(ref esds) = mp4a.esds { + return Ok(esds); + } + } + Err(Error::BoxInStblNotFound( + self.track_id(), + vec![BoxType::EsdsBox], + )) + } + pub fn picture_parameter_set(&self) -> Result<&[u8]> { if let Some(ref avc1) = self.trak.mdia.minf.stbl.stsd.avc1 { - match avc1.avcc.picture_parameter_sets.get(0) { + match avc1.avcc.picture_parameter_sets.first() { Some(nal) => Ok(nal.bytes.as_ref()), None => Err(Error::EntryInStblNotFound( self.track_id(), @@ -284,8 +367,28 @@ impl Mp4Track { 0, )), } + } else if let Some(ref hev1) = self.trak.mdia.minf.stbl.stsd.hev1 { + hev1.hvcc.picture_parameter_set(self.track_id()) + } else if let Some(ref hvc1) = self.trak.mdia.minf.stbl.stsd.hvc1 { + hvc1.hvcc.picture_parameter_set(self.track_id()) + } else { + Err(Error::BoxInStblNotFound( + self.track_id(), + vec![BoxType::Avc1Box, BoxType::Hev1Box, BoxType::Hvc1Box], + )) + } + } + + pub fn video_parameter_set(&self) -> Result<&[u8]> { + if let Some(ref hev1) = self.trak.mdia.minf.stbl.stsd.hev1 { + hev1.hvcc.video_parameter_set(self.track_id()) + } else if let Some(ref hvc1) = self.trak.mdia.minf.stbl.stsd.hvc1 { + hvc1.hvcc.video_parameter_set(self.track_id()) } else { - Err(Error::BoxInStblNotFound(self.track_id(), BoxType::Avc1Box)) + Err(Error::BoxInStblNotFound( + self.track_id(), + vec![BoxType::Hev1Box, BoxType::Hvc1Box], + )) } } @@ -294,10 +397,16 @@ impl Mp4Track { if let Some(ref esds) = mp4a.esds { AudioObjectType::try_from(esds.es_desc.dec_config.dec_specific.profile) } else { - Err(Error::BoxInStblNotFound(self.track_id(), BoxType::EsdsBox)) + Err(Error::BoxInStblNotFound( + self.track_id(), + vec![BoxType::EsdsBox], + )) } } else { - Err(Error::BoxInStblNotFound(self.track_id(), BoxType::Mp4aBox)) + Err(Error::BoxInStblNotFound( + self.track_id(), + vec![BoxType::Mp4aBox], + )) } } @@ -590,6 +699,19 @@ impl Mp4Track { } } + pub(crate) fn read_sample_metadata(&self, sample_id: u32) -> Result> { + let (start_time, duration) = self.sample_time(sample_id).unwrap(); + let rendering_offset = self.sample_rendering_offset(sample_id); + let is_sync = self.is_sync_sample(sample_id); + + Ok(Some(Mp4SampleMetadata { + start_time, + duration, + rendering_offset, + is_sync, + })) + } + pub(crate) fn read_sample( &self, reader: &mut R, @@ -638,6 +760,8 @@ pub(crate) struct Mp4TrackWriter { samples_per_chunk: u32, duration_per_chunk: u32, + offset: u64, + duration: u64, } impl Mp4TrackWriter { @@ -645,8 +769,9 @@ impl Mp4TrackWriter { let mut trak = TrakBox::default(); trak.tkhd.track_id = track_id; trak.mdia.mdhd.timescale = config.timescale; - trak.mdia.mdhd.language = config.language.to_owned(); + config.language.clone_into(&mut trak.mdia.mdhd.language); trak.mdia.hdlr.handler_type = config.track_type.into(); + trak.mdia.hdlr.name = config.track_type.into(); trak.mdia.minf.stbl.co64 = Some(Co64Box::default()); match config.media_conf { MediaConfig::AvcConfig(ref avc_config) => { @@ -660,14 +785,19 @@ impl Mp4TrackWriter { trak.mdia.minf.stbl.stsd.avc1 = Some(avc1); } MediaConfig::HevcConfig(ref hevc_config) => { - trak.tkhd.set_width(hevc_config.width); - trak.tkhd.set_height(hevc_config.height); + trak.tkhd.set_width(hevc_config.width.unwrap_or(0)); + trak.tkhd.set_height(hevc_config.height.unwrap_or(0)); let vmhd = VmhdBox::default(); trak.mdia.minf.vmhd = Some(vmhd); - let hev1 = Hev1Box::new(hevc_config); - trak.mdia.minf.stbl.stsd.hev1 = Some(hev1); + if hevc_config.use_hvc1 { + let hvc1 = Hvc1Box::new(hevc_config); + trak.mdia.minf.stbl.stsd.hvc1 = Some(hvc1); + } else { + let hev1 = Hev1Box::new(hevc_config); + trak.mdia.minf.stbl.stsd.hev1 = Some(hev1); + } } MediaConfig::Vp9Config(ref config) => { trak.tkhd.set_width(config.width); @@ -686,6 +816,10 @@ impl Mp4TrackWriter { let tx3g = Tx3gBox::default(); trak.mdia.minf.stbl.stsd.tx3g = Some(tx3g); } + MediaConfig::OpusConfig(ref opus_config) => { + let opus = OpusBox::new(opus_config); + trak.mdia.minf.stbl.stsd.opus = Some(opus); + } } Ok(Mp4TrackWriter { trak, @@ -896,10 +1030,28 @@ impl Mp4TrackWriter { } } + pub(crate) fn update_edit_list(&mut self, offset: u64, duration: u64) -> Result<()> { + self.offset = offset; + self.duration = duration; + Ok(()) + } + pub(crate) fn write_end(&mut self, writer: &mut W) -> Result { self.write_chunk(writer)?; let max_sample_size = self.max_sample_size(); + self.trak.edts = Some(EdtsBox { + elst: Some(ElstBox { + version: 1, + flags: 0, + entries: vec![ElstEntry { + segment_duration: self.duration, + media_time: self.offset, + media_rate: 1, + media_rate_fraction: 0, + }], + }), + }); if let Some(ref mut mp4a) = self.trak.mdia.minf.stbl.stsd.mp4a { if let Some(ref mut esds) = mp4a.esds { esds.es_desc.dec_config.buffer_size_db = max_sample_size; diff --git a/src/types.rs b/src/types.rs index 540f7fb0..1bfad67c 100644 --- a/src/types.rs +++ b/src/types.rs @@ -3,6 +3,8 @@ use std::borrow::Cow; use std::convert::TryFrom; use std::fmt; +use crate::hev1::HvcCArray; +use crate::mp4a::EsdsBox; use crate::mp4box::*; use crate::*; @@ -217,11 +219,22 @@ impl From for FourCC { } } +impl From for String { + fn from(t: TrackType) -> String { + match t { + TrackType::Video => "VideoHandler".to_string(), + TrackType::Audio => "SoundHandler".to_string(), + TrackType::Subtitle => "ClosedCaptionHandler".to_string(), + } + } +} + const MEDIA_TYPE_H264: &str = "h264"; const MEDIA_TYPE_H265: &str = "h265"; const MEDIA_TYPE_VP9: &str = "vp9"; const MEDIA_TYPE_AAC: &str = "aac"; const MEDIA_TYPE_TTXT: &str = "ttxt"; +const MEDIA_TYPE_OPUS: &str = "opus"; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum MediaType { @@ -229,6 +242,7 @@ pub enum MediaType { H265, VP9, AAC, + OPUS, TTXT, } @@ -248,6 +262,7 @@ impl TryFrom<&str> for MediaType { MEDIA_TYPE_VP9 => Ok(MediaType::VP9), MEDIA_TYPE_AAC => Ok(MediaType::AAC), MEDIA_TYPE_TTXT => Ok(MediaType::TTXT), + MEDIA_TYPE_OPUS => Ok(MediaType::OPUS), _ => Err(Error::InvalidData("unsupported media type")), } } @@ -261,6 +276,7 @@ impl From for &str { MediaType::VP9 => MEDIA_TYPE_VP9, MediaType::AAC => MEDIA_TYPE_AAC, MediaType::TTXT => MEDIA_TYPE_TTXT, + MediaType::OPUS => MEDIA_TYPE_OPUS, } } } @@ -273,6 +289,7 @@ impl From<&MediaType> for &str { MediaType::VP9 => MEDIA_TYPE_VP9, MediaType::AAC => MEDIA_TYPE_AAC, MediaType::TTXT => MEDIA_TYPE_TTXT, + MediaType::OPUS => MEDIA_TYPE_OPUS, } } } @@ -502,6 +519,28 @@ impl TryFrom for SampleFreqIndex { } } +impl TryFrom for SampleFreqIndex { + type Error = Error; + fn try_from(value: u32) -> Result { + match value { + 9600 => Ok(SampleFreqIndex::Freq96000), + 88200 => Ok(SampleFreqIndex::Freq88200), + 64000 => Ok(SampleFreqIndex::Freq64000), + 48000 => Ok(SampleFreqIndex::Freq48000), + 44100 => Ok(SampleFreqIndex::Freq44100), + 32000 => Ok(SampleFreqIndex::Freq32000), + 24000 => Ok(SampleFreqIndex::Freq24000), + 22050 => Ok(SampleFreqIndex::Freq22050), + 16000 => Ok(SampleFreqIndex::Freq16000), + 12000 => Ok(SampleFreqIndex::Freq12000), + 11025 => Ok(SampleFreqIndex::Freq11025), + 8000 => Ok(SampleFreqIndex::Freq8000), + 7350 => Ok(SampleFreqIndex::Freq7350), + _ => Err(Error::InvalidData("invalid sampling frequency index")), + } + } +} + impl SampleFreqIndex { pub fn freq(&self) -> u32 { match *self { @@ -572,10 +611,92 @@ pub struct AvcConfig { pub pic_param_set: Vec, } -#[derive(Debug, PartialEq, Eq, Clone, Default)] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum HevcBoxType { + Hev1, + Hvc1, +} + +#[derive(Debug, PartialEq, Eq, Clone)] pub struct HevcConfig { - pub width: u16, - pub height: u16, + pub width: Option, + pub height: Option, + pub configuration_version: Option, + pub general_profile_space: Option, + pub general_tier_flag: Option, + pub general_profile_idc: Option, + pub general_profile_compatibility_flags: Option, + pub general_constraint_indicator_flag: Option, + pub general_level_idc: Option, + pub min_spatial_segmentation_idc: Option, + pub parallelism_type: Option, + pub chroma_format_idc: Option, + pub bit_depth_luma_minus8: Option, + pub bit_depth_chroma_minus8: Option, + pub avg_frame_rate: Option, + pub constant_frame_rate: Option, + pub num_temporal_layers: Option, + pub temporal_id_nested: Option, + pub length_size_minus_one: Option, + pub arrays: Option>, + pub use_hvc1: bool, +} + +impl Default for HevcConfig { + fn default() -> Self { + Self { + width: None, + height: None, + configuration_version: None, + general_profile_space: None, + general_tier_flag: None, + general_profile_idc: None, + general_profile_compatibility_flags: None, + general_constraint_indicator_flag: None, + general_level_idc: None, + min_spatial_segmentation_idc: None, + parallelism_type: None, + chroma_format_idc: None, + bit_depth_luma_minus8: None, + bit_depth_chroma_minus8: None, + avg_frame_rate: None, + constant_frame_rate: None, + num_temporal_layers: None, + temporal_id_nested: None, + length_size_minus_one: None, + arrays: None, + use_hvc1: false, + } + } +} + +impl HevcConfig { + pub fn new() -> Self { + Self::default() + } + + // Builder methods for each field + pub fn with_width(mut self, width: u16) -> Self { + self.width = Some(width); + self + } + + pub fn with_height(mut self, height: u16) -> Self { + self.height = Some(height); + self + } + + pub fn with_configuration_version(mut self, version: u8) -> Self { + self.configuration_version = Some(version); + self + } + + pub fn with_use_hvc1(mut self, use_hvc1: bool) -> Self { + self.use_hvc1 = use_hvc1; + self + } + + // ... add similar methods for all other fields ... } #[derive(Debug, PartialEq, Eq, Clone, Default)] @@ -590,15 +711,143 @@ pub struct AacConfig { pub profile: AudioObjectType, pub freq_index: SampleFreqIndex, pub chan_conf: ChannelConfig, + + // New fields from Mp4aBox and child structs + pub data_reference_index: u16, + pub sound_version: u16, + pub samplesize: u16, + + // From EsdsBox + pub esds_version: Option, + pub esds_flags: Option, + + // From ESDescriptor + pub es_id: Option, + + // From DecoderConfigDescriptor + pub object_type_indication: Option, + pub stream_type: Option, + pub up_stream: Option, + pub buffer_size_db: Option, + pub max_bitrate: Option, + pub avg_bitrate: Option, + + pub qt_bytes: Option>, +} + +impl AacConfig { + pub fn from_mp4a_box(mp4a: &Mp4aBox) -> Self { + let mut config = AacConfig { + // Set your existing required fields with appropriate defaults or mappings + bitrate: mp4a + .esds + .as_ref() + .map(|esds| esds.es_desc.dec_config.avg_bitrate) + .unwrap_or(0), + + profile: mp4a + .esds + .as_ref() + .map(|esds| { + AudioObjectType::try_from(esds.es_desc.dec_config.dec_specific.profile) + .unwrap_or(AudioObjectType::AacLowComplexity) + }) + .unwrap_or(AudioObjectType::AacLowComplexity), + + freq_index: mp4a + .esds + .as_ref() + .map(|esds| { + SampleFreqIndex::try_from(esds.es_desc.dec_config.dec_specific.freq_index) + .unwrap_or(SampleFreqIndex::Freq48000) + }) + .unwrap_or(SampleFreqIndex::Freq48000), + + chan_conf: mp4a + .esds + .as_ref() + .map(|esds| { + ChannelConfig::try_from(esds.es_desc.dec_config.dec_specific.chan_conf) + .unwrap_or(ChannelConfig::Stereo) + }) + .unwrap_or(ChannelConfig::Stereo), + + // Set new fields from mp4a + data_reference_index: mp4a.data_reference_index, + sound_version: 0, + + samplesize: mp4a.samplesize, + qt_bytes: mp4a.qt_bytes.clone(), + + // Set remaining fields to None initially + esds_version: mp4a.esds.as_ref().map(|esds| esds.version), + esds_flags: mp4a.esds.as_ref().map(|esds| esds.flags), + es_id: mp4a.esds.as_ref().map(|esds| esds.es_desc.es_id), + object_type_indication: mp4a + .esds + .as_ref() + .map(|esds| esds.es_desc.dec_config.object_type_indication), + stream_type: mp4a + .esds + .as_ref() + .map(|esds| esds.es_desc.dec_config.stream_type), + up_stream: mp4a + .esds + .as_ref() + .map(|esds| esds.es_desc.dec_config.up_stream), + buffer_size_db: mp4a + .esds + .as_ref() + .map(|esds| esds.es_desc.dec_config.buffer_size_db), + max_bitrate: mp4a + .esds + .as_ref() + .map(|esds| esds.es_desc.dec_config.max_bitrate), + avg_bitrate: mp4a + .esds + .as_ref() + .map(|esds| esds.es_desc.dec_config.avg_bitrate), + }; + + // Fill in ESDS-related fields if present + if let Some(esds) = &mp4a.esds { + config.esds_version = Some(esds.version); + config.esds_flags = Some(esds.flags); + config.es_id = Some(esds.es_desc.es_id); + + let dec_config = &esds.es_desc.dec_config; + config.object_type_indication = Some(dec_config.object_type_indication); + config.stream_type = Some(dec_config.stream_type); + config.up_stream = Some(dec_config.up_stream); + config.buffer_size_db = Some(dec_config.buffer_size_db); + config.max_bitrate = Some(dec_config.max_bitrate); + config.avg_bitrate = Some(dec_config.avg_bitrate); + } + config + } } impl Default for AacConfig { fn default() -> Self { + let esds = EsdsBox::default(); Self { bitrate: 0, profile: AudioObjectType::AacLowComplexity, freq_index: SampleFreqIndex::Freq48000, chan_conf: ChannelConfig::Stereo, + samplesize: 16, + data_reference_index: 1, + sound_version: 0, + esds_version: Some(esds.version), + esds_flags: Some(esds.flags), + es_id: Some(esds.es_desc.es_id), + object_type_indication: Some(esds.es_desc.dec_config.object_type_indication), + stream_type: Some(esds.es_desc.dec_config.stream_type), + up_stream: Some(esds.es_desc.dec_config.up_stream), + buffer_size_db: Some(esds.es_desc.dec_config.buffer_size_db), + max_bitrate: Some(esds.es_desc.dec_config.max_bitrate), + avg_bitrate: Some(esds.es_desc.dec_config.avg_bitrate), + qt_bytes: None, } } } @@ -606,6 +855,25 @@ impl Default for AacConfig { #[derive(Debug, PartialEq, Eq, Clone, Default)] pub struct TtxtConfig {} +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct OpusConfig { + pub bitrate: u32, + pub freq_index: SampleFreqIndex, + pub chan_conf: ChannelConfig, + pub pre_skip: u16, +} + +impl Default for OpusConfig { + fn default() -> Self { + Self { + bitrate: 0, + freq_index: SampleFreqIndex::Freq48000, + chan_conf: ChannelConfig::Stereo, + pre_skip: 0, + } + } +} + #[derive(Debug, PartialEq, Eq, Clone)] pub enum MediaConfig { AvcConfig(AvcConfig), @@ -613,6 +881,7 @@ pub enum MediaConfig { Vp9Config(Vp9Config), AacConfig(AacConfig), TtxtConfig(TtxtConfig), + OpusConfig(OpusConfig), } #[derive(Debug)] @@ -624,6 +893,14 @@ pub struct Mp4Sample { pub bytes: Bytes, } +#[derive(Debug)] +pub struct Mp4SampleMetadata { + pub start_time: u64, + pub duration: u32, + pub rendering_offset: i32, + pub is_sync: bool, +} + impl PartialEq for Mp4Sample { fn eq(&self, other: &Self) -> bool { self.start_time == other.start_time diff --git a/src/writer.rs b/src/writer.rs index a83a888c..a658184b 100644 --- a/src/writer.rs +++ b/src/writer.rs @@ -1,4 +1,5 @@ use byteorder::{BigEndian, WriteBytesExt}; +use std::cmp; use std::io::{Seek, SeekFrom, Write}; use crate::mp4box::*; @@ -85,10 +86,22 @@ impl Mp4Writer { }) } - pub fn add_track(&mut self, config: &TrackConfig) -> Result<()> { + pub fn add_track(&mut self, config: &TrackConfig) -> Result { let track_id = self.tracks.len() as u32 + 1; let track = Mp4TrackWriter::new(track_id, config)?; self.tracks.push(track); + Ok(track_id) + } + + pub fn update_offset(&mut self, track_index: u32, offset: u64, duration_us: u64) -> Result<()> { + if let Some(track) = self.tracks.get_mut(track_index as usize) { + //convert duration to mvhd timescale + let duration = duration_us * self.timescale as u64 / 1_000_000; + + track.update_edit_list(offset, cmp::min(duration, self.duration))? + } else { + return Err(Error::TrakNotFound(track_index)); + } Ok(()) } @@ -117,7 +130,7 @@ impl Mp4Writer { fn update_mdat_size(&mut self) -> Result<()> { let mdat_end = self.writer.stream_position()?; let mdat_size = mdat_end - self.mdat_pos; - if mdat_size > std::u32::MAX as u64 { + if mdat_size > u32::MAX as u64 { self.writer.seek(SeekFrom::Start(self.mdat_pos))?; self.writer.write_u32::(1)?; self.writer.seek(SeekFrom::Start(self.mdat_pos + 8))?;