From 773e9656fc35850f7d057ab9fc9d5c9dcf0bdcc3 Mon Sep 17 00:00:00 2001 From: "james.baker@helsing.ai" Date: Sun, 22 Oct 2023 16:12:00 +0200 Subject: [PATCH 01/46] Support hvc1 box & enable simple access of hevc parameter sets I have an HEVC mp4 file of the `hvc1` kind. In order to construct the decoder (using Apple VideoToolbox) I need the PPS, SPS, VPS parameter sets. This PR does two things: 1. It adds parsing for the `hvc1` box. This appears similar to `hev1` at least at the 'trak' level. In my files, there are additional inner boxes, for example a `colr` box. I therefore skip nested boxes. 1. Enable hevc accessors for the vps, pps, sps properties that are required to initialise many decoders. I should note here - I have no _particular_ video decoding knowledge, but do have some experience with binary formats and software engineering in general - have reverse engineered what I need in order to solve my present problem by looking at output from the `MP4Box` tool. --- examples/mp4dump.rs | 3 + src/error.rs | 4 +- src/mp4box/hev1.rs | 25 ++++++ src/mp4box/hvc1.rs | 201 ++++++++++++++++++++++++++++++++++++++++++++ src/mp4box/mod.rs | 2 + src/mp4box/stsd.rs | 12 ++- src/track.rs | 78 +++++++++++++++-- 7 files changed, 313 insertions(+), 12 deletions(-) create mode 100644 src/mp4box/hvc1.rs 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..ae37d84f 100644 --- a/src/mp4box/hev1.rs +++ b/src/mp4box/hev1.rs @@ -180,6 +180,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 +191,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..418ce66f --- /dev/null +++ b/src/mp4box/hvc1.rs @@ -0,0 +1,201 @@ +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, + height: config.height, + horizresolution: FixedPointU16::new(0x48), + vertresolution: FixedPointU16::new(0x48), + frame_count: 1, + depth: 0x0018, + hvcc: HvcCBox::new(), + } + } + + 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 mut hvcc = None; + + while reader.stream_position()? < start + size { + let header = BoxHeader::read(reader)?; + let BoxHeader { name, size: s } = header; + if s > size { + return Err(Error::InvalidData( + "hvc1 box contains a box with a larger size than it", + )); + } + if name == BoxType::HvcCBox { + hvcc = Some(HvcCBox::read_box(reader, s)?); + } else { + skip_box(reader, s)?; + } + } + let Some(hvcc) = hvcc else { + return Err(Error::InvalidData("hvcc not found")); + }; + + skip_bytes_to(reader, start + size)?; + + Ok(Hvc1Box { + data_reference_index, + width, + height, + horizresolution, + vertresolution, + frame_count, + depth, + hvcc, + }) + } +} + +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 + + self.hvcc.write_box(writer)?; + + 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..147a901b 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; @@ -225,6 +226,7 @@ boxtype! { Avc1Box => 0x61766331, AvcCBox => 0x61766343, Hev1Box => 0x68657631, + Hvc1Box => 0x68766331, HvcCBox => 0x68766343, Mp4aBox => 0x6d703461, EsdsBox => 0x65736473, diff --git a/src/mp4box/stsd.rs b/src/mp4box/stsd.rs index af947c6c..96014946 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, hvc1::Hvc1Box, mp4a::Mp4aBox, tx3g::Tx3gBox}; #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)] pub struct StsdBox { @@ -17,6 +17,9 @@ 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, @@ -78,6 +81,7 @@ 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 tx3g = None; @@ -91,6 +95,8 @@ impl ReadBox<&mut R> for StsdBox { )); } + println!("test {name}"); + match name { BoxType::Avc1Box => { avc1 = Some(Avc1Box::read_box(reader, s)?); @@ -98,6 +104,9 @@ 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)?); } @@ -117,6 +126,7 @@ impl ReadBox<&mut R> for StsdBox { flags, avc1, hev1, + hvc1, vp09, mp4a, tx3g, diff --git a/src/track.rs b/src/track.rs index 7eada834..dad63810 100644 --- a/src/track.rs +++ b/src/track.rs @@ -123,6 +123,8 @@ impl Mp4Track { Ok(MediaType::H264) } else if self.trak.mdia.minf.stbl.stsd.hev1.is_some() { Ok(MediaType::H265) + } else if 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) } else if self.trak.mdia.minf.stbl.stsd.mp4a.is_some() { @@ -139,6 +141,8 @@ 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() { @@ -153,6 +157,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 +169,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 +192,16 @@ 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 { - Err(Error::BoxInStblNotFound(self.track_id(), BoxType::Mp4aBox)) + Err(Error::BoxInStblNotFound( + self.track_id(), + vec![BoxType::Mp4aBox], + )) } } @@ -192,10 +210,16 @@ 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 { - Err(Error::BoxInStblNotFound(self.track_id(), BoxType::Mp4aBox)) + Err(Error::BoxInStblNotFound( + self.track_id(), + vec![BoxType::Mp4aBox], + )) } } @@ -255,7 +279,10 @@ impl Mp4Track { avc1.avcc.profile_compatibility, )) } else { - Err(Error::BoxInStblNotFound(self.track_id(), BoxType::Avc1Box)) + Err(Error::BoxInStblNotFound( + self.track_id(), + vec![BoxType::Avc1Box], + )) } } @@ -269,8 +296,15 @@ 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], + )) } } @@ -284,8 +318,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(), BoxType::Avc1Box)) + 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(), + vec![BoxType::Hev1Box, BoxType::Hvc1Box], + )) } } @@ -294,10 +348,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], + )) } } From bfc1626a120135502e227078f666706dcaca5b02 Mon Sep 17 00:00:00 2001 From: "james.baker@helsing.ai" Date: Sun, 22 Oct 2023 16:18:07 +0200 Subject: [PATCH 02/46] remove println --- src/mp4box/stsd.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/mp4box/stsd.rs b/src/mp4box/stsd.rs index 96014946..eb00cad8 100644 --- a/src/mp4box/stsd.rs +++ b/src/mp4box/stsd.rs @@ -95,8 +95,6 @@ impl ReadBox<&mut R> for StsdBox { )); } - println!("test {name}"); - match name { BoxType::Avc1Box => { avc1 = Some(Avc1Box::read_box(reader, s)?); From 9c44199fd753a8fa73d6e1813226d952285f2fef Mon Sep 17 00:00:00 2001 From: "james.baker@helsing.ai" Date: Sun, 22 Oct 2023 16:19:10 +0200 Subject: [PATCH 03/46] clippy --- src/track.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/track.rs b/src/track.rs index dad63810..f080ca68 100644 --- a/src/track.rs +++ b/src/track.rs @@ -121,9 +121,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() { - Ok(MediaType::H265) - } else if self.trak.mdia.minf.stbl.stsd.hvc1.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) From 7360c5ccb1dde1b2c84f8e309a53d0f7c7cc5cfb Mon Sep 17 00:00:00 2001 From: Damitha Gunwardena Date: Mon, 23 Dec 2024 15:59:56 +1100 Subject: [PATCH 04/46] support sidx --- src/mp4box/mod.rs | 3 + src/mp4box/sidx.rs | 279 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 282 insertions(+) create mode 100644 src/mp4box/sidx.rs diff --git a/src/mp4box/mod.rs b/src/mp4box/mod.rs index 4bbdd410..264929b2 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 sidx; pub(crate) mod smhd; pub(crate) mod stbl; pub(crate) mod stco; @@ -129,6 +130,7 @@ pub use moov::MoovBox; pub use mp4a::Mp4aBox; pub use mvex::MvexBox; pub use mvhd::MvhdBox; +pub use sidx::SidxBox; pub use smhd::SmhdBox; pub use stbl::StblBox; pub use stco::StcoBox; @@ -204,6 +206,7 @@ boxtype! { HdlrBox => 0x68646c72, MinfBox => 0x6d696e66, VmhdBox => 0x766d6864, + SidxBox => 0x73696478, StblBox => 0x7374626c, StsdBox => 0x73747364, SttsBox => 0x73747473, 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); + } +} From da7debf4343291427b864fcff6ba6d3b4c80da56 Mon Sep 17 00:00:00 2001 From: Damitha Gunwardena Date: Mon, 23 Dec 2024 16:11:26 +1100 Subject: [PATCH 05/46] reader update --- src/reader.rs | 7 +++++++ 1 file changed, 7 insertions(+) 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, From 58c8dc1af453862b252b4aeb0592b95a212d2245 Mon Sep 17 00:00:00 2001 From: damitha Date: Mon, 17 Mar 2025 17:21:38 +1100 Subject: [PATCH 06/46] . --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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." From b2caeaf43ce83fd73b86a14f33abda0674b9a2a5 Mon Sep 17 00:00:00 2001 From: damitha Date: Tue, 18 Mar 2025 21:59:22 +1100 Subject: [PATCH 07/46] opus support --- src/mp4box/mod.rs | 4 + src/mp4box/opus.rs | 322 +++++++++++++++++++++++++++++++++++++++++++++ src/mp4box/stsd.rs | 15 ++- 3 files changed, 339 insertions(+), 2 deletions(-) create mode 100644 src/mp4box/opus.rs diff --git a/src/mp4box/mod.rs b/src/mp4box/mod.rs index 264929b2..7c54d006 100644 --- a/src/mp4box/mod.rs +++ b/src/mp4box/mod.rs @@ -85,6 +85,7 @@ pub(crate) mod moov; pub(crate) mod mp4a; pub(crate) mod mvex; pub(crate) mod mvhd; +pub(crate) mod opus; pub(crate) mod sidx; pub(crate) mod smhd; pub(crate) mod stbl; @@ -130,6 +131,7 @@ pub use moov::MoovBox; pub use mp4a::Mp4aBox; pub use mvex::MvexBox; pub use mvhd::MvhdBox; +pub use opus::OpusBox; pub use sidx::SidxBox; pub use smhd::SmhdBox; pub use stbl::StblBox; @@ -185,6 +187,7 @@ macro_rules! boxtype { } boxtype! { + DopsBox => 0x644f7073, FtypBox => 0x66747970, MvhdBox => 0x6d766864, MfhdBox => 0x6d666864, @@ -193,6 +196,7 @@ boxtype! { MoovBox => 0x6d6f6f76, MvexBox => 0x6d766578, MehdBox => 0x6d656864, + OpusBox => 0x4f707573, TrexBox => 0x74726578, EmsgBox => 0x656d7367, MoofBox => 0x6d6f6f66, diff --git a/src/mp4box/opus.rs b/src/mp4box/opus.rs new file mode 100644 index 00000000..db7dc024 --- /dev/null +++ b/src/mp4box/opus.rs @@ -0,0 +1,322 @@ +use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; +use serde::Serialize; +use std::io::{Read, Seek, Write}; + +use crate::mp4box::*; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +pub struct OpusBox { + pub data_reference_index: u16, + pub channel_count: u16, + pub sample_size: u16, + + #[serde(with = "value_u32")] + pub sample_rate: FixedPointU16, + pub dops_box: Option, +} + +impl Default for OpusBox { + fn default() -> Self { + Self { + data_reference_index: 0, + channel_count: 2, + sample_size: 16, + sample_rate: FixedPointU16::new(48000), + dops_box: Some(DopsBox::default()), + } + } +} + +impl OpusBox { + pub fn get_type(&self) -> BoxType { + BoxType::OpusBox + } + + pub fn get_size(&self) -> u64 { + let mut size = HEADER_SIZE + 28; + if let Some(ref dops_box) = self.dops_box { + size += dops_box.box_size(); + } + size + } +} + +impl Mp4Box for OpusBox { + fn box_type(&self) -> BoxType { + self.get_type() + } + + fn box_size(&self) -> u64 { + self.get_size() + } + + fn to_json(&self) -> Result { + Ok(serde_json::to_string(&self).unwrap()) + } + + fn summary(&self) -> Result { + let s = format!( + "channel_count={} sample_size={} sample_rate={}", + self.channel_count, + self.sample_size, + self.sample_rate.value() + ); + Ok(s) + } +} + +impl ReadBox<&mut R> for OpusBox { + fn read_box(reader: &mut R, size: u64) -> Result { + let start = box_start(reader)?; + + reader.read_u32::()?; // reserved + reader.read_u16::()?; // reserved + let data_reference_index = reader.read_u16::()?; + reader.read_u16::()?; // reserved + reader.read_u16::()?; // reserved + reader.read_u32::()?; // reserved + let channel_count = reader.read_u16::()?; + let sample_size = reader.read_u16::()?; + reader.read_u32::()?; // pre-defined, reserved + let sample_rate = FixedPointU16::new_raw(reader.read_u32::()?); + + let header = BoxHeader::read(reader)?; + let BoxHeader { name, size: s } = header; + + if s > size { + return Err(Error::InvalidData( + "opus box contains a box with a larger size than it", + )); + } + let mut dops_box = None; + if name == BoxType::DopsBox { + dops_box = Some(DopsBox::read_box(reader, s)?); + } + skip_bytes_to(reader, start + size)?; + Ok(OpusBox { + data_reference_index, + channel_count, + sample_size, + sample_rate, + dops_box, + }) + } +} + +impl WriteBox<&mut W> for OpusBox { + fn write_box(&self, writer: &mut W) -> Result { + let size = self.box_size(); + BoxHeader::new(self.box_type(), size).write(writer)?; + + writer.write_u32::(0)?; // reserved + writer.write_u16::(0)?; // reserved + writer.write_u16::(self.data_reference_index)?; + + writer.write_u64::(0)?; // reserved + writer.write_u16::(self.channel_count)?; + writer.write_u16::(self.sample_size)?; + writer.write_u32::(0)?; // reserved + writer.write_u32::(self.sample_rate.raw_value())?; + + if let Some(ref dops_box) = self.dops_box { + dops_box.write_box(writer)?; + } + Ok(size) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +pub struct DopsBox { + pub version: u8, + pub output_channel_count: u8, + pub pre_skip: u16, + pub input_sample_rate: u32, + pub output_gain: i16, + pub channel_mapping_family: u8, + pub channel_mapping_table: Option, +} + +impl Default for DopsBox { + fn default() -> Self { + Self { + version: 0, + output_channel_count: 2, + pre_skip: 16, + input_sample_rate: 0, + output_gain: -1, + channel_mapping_family: 0, + channel_mapping_table: None, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +pub struct ChannelMappingTable { + pub stream_count: u8, + pub coupled_count: u8, + pub channel_mapping: Vec, +} + +impl Default for ChannelMappingTable { + fn default() -> Self { + Self { + stream_count: 0, + coupled_count: 2, + channel_mapping: Vec::new(), + } + } +} + +impl Mp4Box for DopsBox { + fn box_type(&self) -> BoxType { + BoxType::DopsBox + } + + fn box_size(&self) -> u64 { + let mut channel_table_size = 0; + if self.channel_mapping_family != 0 { + channel_table_size = self.output_channel_count as u64 + 2; + } + HEADER_SIZE + 11 + channel_table_size + } + + fn to_json(&self) -> Result { + Ok(serde_json::to_string(&self).unwrap()) + } + + fn summary(&self) -> Result { + Ok(String::new()) + } +} + +impl ReadBox<&mut R> for DopsBox { + fn read_box(reader: &mut R, size: u64) -> Result { + let start = box_start(reader)?; + let version = reader.read_u8()?; + let output_channel_count = reader.read_u8()?; + let pre_skip = reader.read_u16::()?; + let input_sample_rate = reader.read_u32::()?; + let output_gain = reader.read_i16::()?; + let channel_mapping_family = reader.read_u8()?; + let mut channel_mapping_table = None; + if channel_mapping_family != 0 { + let stream_count = reader.read_u8()?; + let coupled_count = reader.read_u8()?; + let mut channel_mapping = Vec::new(); + for _ in 0..output_channel_count { + channel_mapping.push(reader.read_u8()?); + } + channel_mapping_table = Some(ChannelMappingTable { + stream_count, + coupled_count, + channel_mapping, + }); + } + + skip_bytes_to(reader, start + size)?; + Ok(DopsBox { + version, + output_channel_count, + pre_skip, + input_sample_rate, + output_gain, + channel_mapping_family, + channel_mapping_table, + }) + } +} + +impl WriteBox<&mut W> for DopsBox { + fn write_box(&self, writer: &mut W) -> Result { + let size = self.box_size(); + BoxHeader::new(self.box_type(), size).write(writer)?; + + writer.write_u8(self.version)?; + writer.write_u8(self.output_channel_count)?; + writer.write_u16::(self.pre_skip)?; + writer.write_u32::(self.input_sample_rate)?; + writer.write_i16::(self.output_gain)?; + writer.write_u8(self.channel_mapping_family)?; + + if self.channel_mapping_family != 0 { + let channel_mapping_table = self.channel_mapping_table.clone().unwrap(); + writer.write_u8(channel_mapping_table.stream_count)?; + writer.write_u8(channel_mapping_table.coupled_count)?; + for b in channel_mapping_table.channel_mapping.iter() { + writer.write_u8(*b)?; + } + } + Ok(size) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::mp4box::BoxHeader; + use std::io::Cursor; + + #[test] + fn test_opus() { + let src_box = OpusBox { + data_reference_index: 1, + channel_count: 6, + sample_size: 16, + sample_rate: FixedPointU16::new(48000), + dops_box: Some(DopsBox { + version: 0, + output_channel_count: 6, + pre_skip: 312, + input_sample_rate: 48000, + output_gain: 0, + channel_mapping_family: 1, + channel_mapping_table: Some(ChannelMappingTable { + stream_count: 4, + coupled_count: 2, + channel_mapping: [0, 4, 1, 2, 3, 5].to_vec(), + }), + }), + }; + let mut buf = Vec::new(); + src_box.write_box(&mut buf).unwrap(); + assert_eq!(buf.len(), src_box.box_size() as usize); + + let mut reader = Cursor::new(&buf); + let header = BoxHeader::read(&mut reader).unwrap(); + assert_eq!(header.name, BoxType::OpusBox); + assert_eq!(src_box.box_size(), header.size); + + let dst_box = OpusBox::read_box(&mut reader, header.size).unwrap(); + assert_eq!(src_box, dst_box); + } + + #[test] + fn test_opus_witout_channel_mapping_table() { + let src_box = OpusBox { + data_reference_index: 1, + channel_count: 6, + sample_size: 16, + sample_rate: FixedPointU16::new(48000), + dops_box: Some(DopsBox { + version: 0, + output_channel_count: 6, + pre_skip: 312, + input_sample_rate: 48000, + output_gain: 0, + channel_mapping_family: 0, + channel_mapping_table: None, + }), + }; + let mut buf = Vec::new(); + src_box.write_box(&mut buf).unwrap(); + assert_eq!(buf.len(), src_box.box_size() as usize); + + let mut reader = Cursor::new(&buf); + let header = BoxHeader::read(&mut reader).unwrap(); + assert_eq!(header.name, BoxType::OpusBox); + assert_eq!(src_box.box_size(), header.size); + + let dst_box = OpusBox::read_box(&mut reader, header.size).unwrap(); + assert_eq!(src_box, dst_box); + } +} diff --git a/src/mp4box/stsd.rs b/src/mp4box/stsd.rs index af947c6c..111750d4 100644 --- a/src/mp4box/stsd.rs +++ b/src/mp4box/stsd.rs @@ -4,7 +4,7 @@ use std::io::{Read, Seek, Write}; use crate::mp4box::vp09::Vp09Box; use crate::mp4box::*; -use crate::mp4box::{avc1::Avc1Box, hev1::Hev1Box, mp4a::Mp4aBox, tx3g::Tx3gBox}; +use crate::mp4box::{avc1::Avc1Box, hev1::Hev1Box, mp4a::Mp4aBox, opus::OpusBox, tx3g::Tx3gBox}; #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)] pub struct StsdBox { @@ -23,6 +23,9 @@ pub struct StsdBox { #[serde(skip_serializing_if = "Option::is_none")] pub mp4a: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub opus: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub tx3g: Option, } @@ -42,7 +45,10 @@ impl StsdBox { size += vp09.box_size(); } else if let Some(ref mp4a) = self.mp4a { size += mp4a.box_size(); - } else if let Some(ref tx3g) = self.tx3g { + } else if let Some(ref opus) = self.opus { + size += opus.box_size(); + } + else if let Some(ref tx3g) = self.tx3g { size += tx3g.box_size(); } size @@ -80,6 +86,7 @@ impl ReadBox<&mut R> for StsdBox { let mut hev1 = None; let mut vp09 = None; let mut mp4a = None; + let mut opus = None; let mut tx3g = None; // Get box header. @@ -104,6 +111,9 @@ impl ReadBox<&mut R> for StsdBox { BoxType::Mp4aBox => { mp4a = Some(Mp4aBox::read_box(reader, s)?); } + BoxType::OpusBox => { + opus = Some(OpusBox::read_box(reader, s)?); + } BoxType::Tx3gBox => { tx3g = Some(Tx3gBox::read_box(reader, s)?); } @@ -118,6 +128,7 @@ impl ReadBox<&mut R> for StsdBox { avc1, hev1, vp09, + opus, mp4a, tx3g, }) From 7889981d33c09adb6dee4c0329ab68ce930a4a71 Mon Sep 17 00:00:00 2001 From: damitha Date: Tue, 18 Mar 2025 22:04:51 +1100 Subject: [PATCH 08/46] . --- src/track.rs | 2 ++ 1 file changed, 2 insertions(+) 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 { From f483d55966cde578f224e0c4a8e2acc66a171cd4 Mon Sep 17 00:00:00 2001 From: damitha Date: Tue, 18 Mar 2025 22:46:39 +1100 Subject: [PATCH 09/46] . --- examples/mp4copy.rs | 9 ++++++-- src/track.rs | 46 ++++++++++++++++++++++++++++++++------- src/types.rs | 52 ++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 94 insertions(+), 13 deletions(-) diff --git a/examples/mp4copy.rs b/examples/mp4copy.rs index 98d1ba80..ac8a46a1 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, HevcConfig, MediaConfig, MediaType, Mp4Config, OpusConfig, Result, TrackConfig, TtxtConfig, Vp9Config }; fn main() { @@ -64,6 +63,12 @@ fn copy>(src_filename: &P, dst_filename: &P) -> Result<()> { freq_index: track.sample_freq_index()?, chan_conf: track.channel_config()?, }), + MediaType::OPUS => MediaConfig::OpusConfig(OpusConfig { + bitrate: track.bitrate(), + freq_index: track.sample_freq_index()?, + chan_conf: track.channel_config()?, + pre_skip: 0, + }), MediaType::TTXT => MediaConfig::TtxtConfig(TtxtConfig {}), }; diff --git a/src/track.rs b/src/track.rs index 00eb38f2..1b15b754 100644 --- a/src/track.rs +++ b/src/track.rs @@ -9,8 +9,8 @@ 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 +30,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 +90,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, @@ -129,6 +141,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")) } @@ -143,10 +157,10 @@ 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 if self.trak.mdia.minf.stbl.stsd.opus.is_some() { + Ok(FourCC::from(BoxType::OpusBox)) } else { Err(Error::InvalidData("unsupported sample entry box")) } @@ -184,6 +198,12 @@ impl Mp4Track { } else { Err(Error::BoxInStblNotFound(self.track_id(), 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(), BoxType::DopsBox)) + } } else { Err(Error::BoxInStblNotFound(self.track_id(), BoxType::Mp4aBox)) } @@ -196,6 +216,12 @@ impl Mp4Track { } else { Err(Error::BoxInStblNotFound(self.track_id(), 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(), BoxType::DopsBox)) + } } else { Err(Error::BoxInStblNotFound(self.track_id(), BoxType::Mp4aBox)) } @@ -263,7 +289,7 @@ impl Mp4Track { 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(), @@ -278,7 +304,7 @@ impl Mp4Track { 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(), @@ -647,7 +673,7 @@ 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.minf.stbl.co64 = Some(Co64Box::default()); match config.media_conf { @@ -688,6 +714,10 @@ impl Mp4TrackWriter { let tx3g = Tx3gBox::default(); trak.mdia.minf.stbl.stsd.tx3g = Some(tx3g); } + MediaConfig::OpusConfig(ref _opus_config) => { + let opus = OpusBox::default(); + trak.mdia.minf.stbl.stsd.opus = Some(opus); + } } Ok(Mp4TrackWriter { trak, @@ -917,4 +947,4 @@ impl Mp4TrackWriter { Ok(self.trak.clone()) } -} +} \ No newline at end of file diff --git a/src/types.rs b/src/types.rs index 540f7fb0..13a24b1a 100644 --- a/src/types.rs +++ b/src/types.rs @@ -222,6 +222,7 @@ 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,8 +230,8 @@ pub enum MediaType { H265, VP9, AAC, - TTXT, -} + OPUS, + TTXT} impl fmt::Display for MediaType { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { @@ -248,6 +249,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 +263,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 +276,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 +506,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 { @@ -606,6 +632,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 +658,7 @@ pub enum MediaConfig { Vp9Config(Vp9Config), AacConfig(AacConfig), TtxtConfig(TtxtConfig), + OpusConfig(OpusConfig), } #[derive(Debug)] @@ -738,4 +784,4 @@ impl<'a, T: Metadata<'a>> Metadata<'a> for Option { fn summary(&self) -> Option> { self.as_ref().and_then(|t| t.summary()) } -} +} \ No newline at end of file From 8e0b548f61684c1166c5089e511c1d1f5281388f Mon Sep 17 00:00:00 2001 From: damitha Date: Tue, 18 Mar 2025 23:03:26 +1100 Subject: [PATCH 10/46] . --- src/writer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/writer.rs b/src/writer.rs index a83a888c..eac3a247 100644 --- a/src/writer.rs +++ b/src/writer.rs @@ -117,7 +117,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))?; From 37bc464dece42e71a90e03a2045f3be3f6f572ee Mon Sep 17 00:00:00 2001 From: damitha Date: Tue, 18 Mar 2025 23:07:02 +1100 Subject: [PATCH 11/46] . --- src/mp4box/stsd.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/mp4box/stsd.rs b/src/mp4box/stsd.rs index 111750d4..b2e79f49 100644 --- a/src/mp4box/stsd.rs +++ b/src/mp4box/stsd.rs @@ -154,6 +154,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) From 6232617277f80d26bc9cdf5faeea265ee37deb77 Mon Sep 17 00:00:00 2001 From: damitha Date: Tue, 18 Mar 2025 23:22:28 +1100 Subject: [PATCH 12/46] . --- src/mp4box/opus.rs | 25 +++++++++++++++++++++++++ src/track.rs | 4 ++-- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/mp4box/opus.rs b/src/mp4box/opus.rs index db7dc024..811a6ba5 100644 --- a/src/mp4box/opus.rs +++ b/src/mp4box/opus.rs @@ -28,6 +28,17 @@ impl Default for OpusBox { } 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 } @@ -189,6 +200,20 @@ impl Mp4Box for DopsBox { } } +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)?; diff --git a/src/track.rs b/src/track.rs index 1b15b754..c345bbfe 100644 --- a/src/track.rs +++ b/src/track.rs @@ -714,8 +714,8 @@ impl Mp4TrackWriter { let tx3g = Tx3gBox::default(); trak.mdia.minf.stbl.stsd.tx3g = Some(tx3g); } - MediaConfig::OpusConfig(ref _opus_config) => { - let opus = OpusBox::default(); + MediaConfig::OpusConfig(ref opus_config) => { + let opus = OpusBox::new(opus_config); trak.mdia.minf.stbl.stsd.opus = Some(opus); } } From d33ea6eb09f5c580f8c1341e3f75303a4d70e34a Mon Sep 17 00:00:00 2001 From: damitha Date: Wed, 19 Mar 2025 18:17:36 +1100 Subject: [PATCH 13/46] . --- examples/mp4copy.rs | 3 +++ src/mp4box/mp4a.rs | 1 - src/types.rs | 6 ++++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/examples/mp4copy.rs b/examples/mp4copy.rs index ac8a46a1..ed953494 100644 --- a/examples/mp4copy.rs +++ b/examples/mp4copy.rs @@ -62,6 +62,9 @@ fn copy>(src_filename: &P, dst_filename: &P) -> Result<()> { profile: track.audio_profile()?, freq_index: track.sample_freq_index()?, chan_conf: track.channel_config()?, + es_id: 1, + object_type_indication: 0x40, + stream_type: 5, }), MediaType::OPUS => MediaConfig::OpusConfig(OpusConfig { bitrate: track.bitrate(), diff --git a/src/mp4box/mp4a.rs b/src/mp4box/mp4a.rs index a80c6c46..5fda76b7 100644 --- a/src/mp4box/mp4a.rs +++ b/src/mp4box/mp4a.rs @@ -394,7 +394,6 @@ pub struct DecoderConfigDescriptor { pub buffer_size_db: u32, pub max_bitrate: u32, pub avg_bitrate: u32, - pub dec_specific: DecoderSpecificDescriptor, } diff --git a/src/types.rs b/src/types.rs index 13a24b1a..cfcb58f4 100644 --- a/src/types.rs +++ b/src/types.rs @@ -616,6 +616,9 @@ pub struct AacConfig { pub profile: AudioObjectType, pub freq_index: SampleFreqIndex, pub chan_conf: ChannelConfig, + pub es_id: u16, + pub object_type_indication: u8, + pub stream_type: u8, } impl Default for AacConfig { @@ -625,6 +628,9 @@ impl Default for AacConfig { profile: AudioObjectType::AacLowComplexity, freq_index: SampleFreqIndex::Freq48000, chan_conf: ChannelConfig::Stereo, + es_id: 1, + object_type_indication: 0x40, + stream_type: 5, } } } From e850368b2354d5c4451797049320c2dfa5df7954 Mon Sep 17 00:00:00 2001 From: damitha Date: Wed, 19 Mar 2025 18:37:44 +1100 Subject: [PATCH 14/46] . --- src/mp4box/mp4a.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mp4box/mp4a.rs b/src/mp4box/mp4a.rs index 5fda76b7..168a3dc8 100644 --- a/src/mp4box/mp4a.rs +++ b/src/mp4box/mp4a.rs @@ -400,8 +400,8 @@ pub struct DecoderConfigDescriptor { impl DecoderConfigDescriptor { pub fn new(config: &AacConfig) -> Self { Self { - object_type_indication: 0x40, // XXX AAC - stream_type: 0x05, // XXX Audio + object_type_indication: config.object_type_indication, // XXX AAC + stream_type: config.stream_type, // XXX Audio up_stream: 0, buffer_size_db: 0, max_bitrate: config.bitrate, // XXX From 1199aa88f9a9f28392ca2b195e771c31b5bfbabb Mon Sep 17 00:00:00 2001 From: damitha Date: Thu, 20 Mar 2025 07:07:27 +1100 Subject: [PATCH 15/46] . --- examples/mp4copy.rs | 1 + src/mp4box/mp4a.rs | 11 +++++------ src/types.rs | 2 ++ 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/examples/mp4copy.rs b/examples/mp4copy.rs index ed953494..7567303c 100644 --- a/examples/mp4copy.rs +++ b/examples/mp4copy.rs @@ -65,6 +65,7 @@ fn copy>(src_filename: &P, dst_filename: &P) -> Result<()> { es_id: 1, object_type_indication: 0x40, stream_type: 5, + buffer_size_db: 0, }), MediaType::OPUS => MediaConfig::OpusConfig(OpusConfig { bitrate: track.bitrate(), diff --git a/src/mp4box/mp4a.rs b/src/mp4box/mp4a.rs index 168a3dc8..fc6641c4 100644 --- a/src/mp4box/mp4a.rs +++ b/src/mp4box/mp4a.rs @@ -305,7 +305,6 @@ fn write_desc(writer: &mut W, tag: u8, size: u32) -> Result { #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)] pub struct ESDescriptor { pub es_id: u16, - pub dec_config: DecoderConfigDescriptor, pub sl_config: SLConfigDescriptor, } @@ -313,7 +312,7 @@ pub struct ESDescriptor { impl ESDescriptor { pub fn new(config: &AacConfig) -> Self { Self { - es_id: 1, + es_id: config.es_id, dec_config: DecoderConfigDescriptor::new(config), sl_config: SLConfigDescriptor::new(), } @@ -400,11 +399,11 @@ pub struct DecoderConfigDescriptor { impl DecoderConfigDescriptor { pub fn new(config: &AacConfig) -> Self { Self { - object_type_indication: config.object_type_indication, // XXX AAC - stream_type: config.stream_type, // XXX Audio + object_type_indication: config.object_type_indication, + stream_type: config.stream_type, up_stream: 0, - buffer_size_db: 0, - max_bitrate: config.bitrate, // XXX + buffer_size_db: config.buffer_size_db, + max_bitrate: config.bitrate, avg_bitrate: config.bitrate, dec_specific: DecoderSpecificDescriptor::new(config), } diff --git a/src/types.rs b/src/types.rs index cfcb58f4..73f5f41e 100644 --- a/src/types.rs +++ b/src/types.rs @@ -619,6 +619,7 @@ pub struct AacConfig { pub es_id: u16, pub object_type_indication: u8, pub stream_type: u8, + pub buffer_size_db: u8, } impl Default for AacConfig { @@ -631,6 +632,7 @@ impl Default for AacConfig { es_id: 1, object_type_indication: 0x40, stream_type: 5, + buffer_size_db: 0, } } } From 284e01dac21bf5352dad190374264484cf20b5b8 Mon Sep 17 00:00:00 2001 From: damitha Date: Thu, 20 Mar 2025 07:07:46 +1100 Subject: [PATCH 16/46] . --- src/types.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types.rs b/src/types.rs index 73f5f41e..6b36e108 100644 --- a/src/types.rs +++ b/src/types.rs @@ -619,7 +619,7 @@ pub struct AacConfig { pub es_id: u16, pub object_type_indication: u8, pub stream_type: u8, - pub buffer_size_db: u8, + pub buffer_size_db: u32, } impl Default for AacConfig { From 1eda16273287ee47b3d733d21768ffb87ddd0126 Mon Sep 17 00:00:00 2001 From: damitha Date: Thu, 20 Mar 2025 19:36:27 +1100 Subject: [PATCH 17/46] . --- examples/mp4copy.rs | 6 +-- src/lib.rs | 2 +- src/mp4box/mp4a.rs | 117 +++++++++++++++++++++++++++++++++----------- src/types.rs | 8 --- 4 files changed, 90 insertions(+), 43 deletions(-) diff --git a/examples/mp4copy.rs b/examples/mp4copy.rs index 7567303c..835141e0 100644 --- a/examples/mp4copy.rs +++ b/examples/mp4copy.rs @@ -61,11 +61,7 @@ fn copy>(src_filename: &P, dst_filename: &P) -> Result<()> { bitrate: track.bitrate(), profile: track.audio_profile()?, freq_index: track.sample_freq_index()?, - chan_conf: track.channel_config()?, - es_id: 1, - object_type_indication: 0x40, - stream_type: 5, - buffer_size_db: 0, + chan_conf: track.channel_config()? }), MediaType::OPUS => MediaConfig::OpusConfig(OpusConfig { bitrate: track.bitrate(), diff --git a/src/lib.rs b/src/lib.rs index 92319e18..6a3ab251 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,7 +15,7 @@ //! use mp4::{Result}; //! //! fn main() -> Result<()> { -//! let f = File::open("tests/samples/minimal.mp4").unwrap(); +//! let f = File::open("tests/samples/he-aac.mp4").unwrap(); //! let size = f.metadata()?.len(); //! let reader = BufReader::new(f); //! diff --git a/src/mp4box/mp4a.rs b/src/mp4box/mp4a.rs index fc6641c4..f131b42d 100644 --- a/src/mp4box/mp4a.rs +++ b/src/mp4box/mp4a.rs @@ -181,11 +181,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 +248,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 { @@ -305,6 +306,7 @@ fn write_desc(writer: &mut W, tag: u8, size: u32) -> Result { #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)] pub struct ESDescriptor { pub es_id: u16, + pub dec_config: DecoderConfigDescriptor, pub sl_config: SLConfigDescriptor, } @@ -312,7 +314,7 @@ pub struct ESDescriptor { impl ESDescriptor { pub fn new(config: &AacConfig) -> Self { Self { - es_id: config.es_id, + es_id: 1, dec_config: DecoderConfigDescriptor::new(config), sl_config: SLConfigDescriptor::new(), } @@ -324,13 +326,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 } } @@ -372,7 +376,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)?; @@ -393,17 +397,18 @@ pub struct DecoderConfigDescriptor { pub buffer_size_db: u32, pub max_bitrate: u32, pub avg_bitrate: u32, + pub dec_specific: DecoderSpecificDescriptor, } impl DecoderConfigDescriptor { pub fn new(config: &AacConfig) -> Self { Self { - object_type_indication: config.object_type_indication, - stream_type: config.stream_type, + object_type_indication: 0x40, // XXX AAC + stream_type: 0x05, // XXX Audio up_stream: 0, - buffer_size_db: config.buffer_size_db, - max_bitrate: config.bitrate, + buffer_size_db: 0, + max_bitrate: config.bitrate, // XXX avg_bitrate: config.bitrate, dec_specific: DecoderSpecificDescriptor::new(config), } @@ -415,10 +420,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 } } @@ -465,7 +469,7 @@ 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)?; @@ -502,15 +506,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 @@ -529,7 +537,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; } @@ -562,11 +570,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) } @@ -586,7 +601,7 @@ impl Descriptor for SLConfigDescriptor { 0x06 } - fn desc_size() -> u32 { + fn desc_size(&self) -> u32 { 1 } } @@ -601,7 +616,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 @@ -678,4 +693,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 + 2); + + 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 + }); + } +} \ No newline at end of file diff --git a/src/types.rs b/src/types.rs index 6b36e108..13a24b1a 100644 --- a/src/types.rs +++ b/src/types.rs @@ -616,10 +616,6 @@ pub struct AacConfig { pub profile: AudioObjectType, pub freq_index: SampleFreqIndex, pub chan_conf: ChannelConfig, - pub es_id: u16, - pub object_type_indication: u8, - pub stream_type: u8, - pub buffer_size_db: u32, } impl Default for AacConfig { @@ -629,10 +625,6 @@ impl Default for AacConfig { profile: AudioObjectType::AacLowComplexity, freq_index: SampleFreqIndex::Freq48000, chan_conf: ChannelConfig::Stereo, - es_id: 1, - object_type_indication: 0x40, - stream_type: 5, - buffer_size_db: 0, } } } From 792a92222aa43cda8df7439a8833711c3e84d3d6 Mon Sep 17 00:00:00 2001 From: damitha Date: Fri, 21 Mar 2025 12:13:09 +1100 Subject: [PATCH 18/46] . --- src/lib.rs | 2 +- src/writer.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 6a3ab251..92319e18 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,7 +15,7 @@ //! use mp4::{Result}; //! //! fn main() -> Result<()> { -//! let f = File::open("tests/samples/he-aac.mp4").unwrap(); +//! let f = File::open("tests/samples/minimal.mp4").unwrap(); //! let size = f.metadata()?.len(); //! let reader = BufReader::new(f); //! diff --git a/src/writer.rs b/src/writer.rs index eac3a247..6ab559d0 100644 --- a/src/writer.rs +++ b/src/writer.rs @@ -85,11 +85,11 @@ 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(()) + Ok(track_id) } fn update_durations(&mut self, track_dur: u64) { From b3882388379e8854f0f9ee4a827d8d67eddfae97 Mon Sep 17 00:00:00 2001 From: damitha Date: Fri, 21 Mar 2025 12:53:39 +1100 Subject: [PATCH 19/46] . --- src/track.rs | 16 ++++++++++++++++ src/writer.rs | 17 +++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/src/track.rs b/src/track.rs index c345bbfe..509f6654 100644 --- a/src/track.rs +++ b/src/track.rs @@ -4,6 +4,7 @@ use std::convert::TryFrom; use std::io::{Read, Seek, SeekFrom, Write}; use std::time::Duration; +use crate::elst::ElstEntry; use crate::mp4box::traf::TrafBox; use crate::mp4box::trak::TrakBox; use crate::mp4box::trun::TrunBox; @@ -929,9 +930,24 @@ impl Mp4TrackWriter { } pub(crate) fn write_end(&mut self, writer: &mut W) -> Result { + return self.write_end_with_offset(writer, 0); + } + + + pub(crate) fn write_end_with_offset(&mut self, writer: &mut W, offset: u64) -> 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.trak.mdia.mdhd.duration, + media_time: 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/writer.rs b/src/writer.rs index 6ab559d0..f91ed5df 100644 --- a/src/writer.rs +++ b/src/writer.rs @@ -146,4 +146,21 @@ impl Mp4Writer { moov.write_box(&mut self.writer)?; Ok(()) } + + pub fn write_end_with_offset(&mut self, offset: u64) -> Result<()> { + let mut moov = MoovBox::default(); + + for track in self.tracks.iter_mut() { + moov.traks.push(track.write_end_with_offset(&mut self.writer,offset)?); + } + self.update_mdat_size()?; + + moov.mvhd.timescale = self.timescale; + moov.mvhd.duration = self.duration; + if moov.mvhd.duration > (u32::MAX as u64) { + moov.mvhd.version = 1 + } + moov.write_box(&mut self.writer)?; + Ok(()) + } } From c032eef329a8369cbecd2bf1cff55f29f3fefc8a Mon Sep 17 00:00:00 2001 From: damitha Date: Fri, 21 Mar 2025 13:17:00 +1100 Subject: [PATCH 20/46] . --- examples/mp4copy.rs | 5 +++-- src/mp4box/mp4a.rs | 2 +- src/mp4box/opus.rs | 1 - src/mp4box/stsd.rs | 5 ++--- src/track.rs | 39 +++++++++++++++++++++++++-------------- src/types.rs | 5 +++-- src/writer.rs | 29 ++++++++++++----------------- 7 files changed, 46 insertions(+), 40 deletions(-) diff --git a/examples/mp4copy.rs b/examples/mp4copy.rs index 835141e0..f6f2cd5b 100644 --- a/examples/mp4copy.rs +++ b/examples/mp4copy.rs @@ -5,7 +5,8 @@ use std::io::{self, BufReader, BufWriter}; use std::path::Path; use mp4::{ - AacConfig, AvcConfig, HevcConfig, MediaConfig, MediaType, Mp4Config, OpusConfig, Result, TrackConfig, TtxtConfig, Vp9Config + AacConfig, AvcConfig, HevcConfig, MediaConfig, MediaType, Mp4Config, OpusConfig, Result, + TrackConfig, TtxtConfig, Vp9Config, }; fn main() { @@ -61,7 +62,7 @@ fn copy>(src_filename: &P, dst_filename: &P) -> Result<()> { bitrate: track.bitrate(), profile: track.audio_profile()?, freq_index: track.sample_freq_index()?, - chan_conf: track.channel_config()? + chan_conf: track.channel_config()?, }), MediaType::OPUS => MediaConfig::OpusConfig(OpusConfig { bitrate: track.bitrate(), diff --git a/src/mp4box/mp4a.rs b/src/mp4box/mp4a.rs index f131b42d..f83573ba 100644 --- a/src/mp4box/mp4a.rs +++ b/src/mp4box/mp4a.rs @@ -737,4 +737,4 @@ mod tests { chan_conf: 6, // FiveOne }); } -} \ No newline at end of file +} diff --git a/src/mp4box/opus.rs b/src/mp4box/opus.rs index 811a6ba5..7a778b6d 100644 --- a/src/mp4box/opus.rs +++ b/src/mp4box/opus.rs @@ -28,7 +28,6 @@ impl Default for OpusBox { } impl OpusBox { - pub fn new(config: &OpusConfig) -> Self { Self { channel_count: config.chan_conf as u16, diff --git a/src/mp4box/stsd.rs b/src/mp4box/stsd.rs index b2e79f49..997552b3 100644 --- a/src/mp4box/stsd.rs +++ b/src/mp4box/stsd.rs @@ -47,8 +47,7 @@ impl StsdBox { size += mp4a.box_size(); } else if let Some(ref opus) = self.opus { size += opus.box_size(); - } - else if let Some(ref tx3g) = self.tx3g { + } else if let Some(ref tx3g) = self.tx3g { size += tx3g.box_size(); } size @@ -154,7 +153,7 @@ 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 { + } else if let Some(ref opus) = self.opus { opus.write_box(writer)?; } diff --git a/src/track.rs b/src/track.rs index 509f6654..8cca1cee 100644 --- a/src/track.rs +++ b/src/track.rs @@ -218,7 +218,7 @@ impl Mp4Track { Err(Error::BoxInStblNotFound(self.track_id(), BoxType::EsdsBox)) } } else if let Some(ref opus) = self.trak.mdia.minf.stbl.stsd.opus { - if let Some(ref dops ) = opus.dops_box { + if let Some(ref dops) = opus.dops_box { ChannelConfig::try_from(dops.output_channel_count) } else { Err(Error::BoxInStblNotFound(self.track_id(), BoxType::DopsBox)) @@ -667,6 +667,7 @@ pub(crate) struct Mp4TrackWriter { samples_per_chunk: u32, duration_per_chunk: u32, + offset: u64, } impl Mp4TrackWriter { @@ -929,25 +930,35 @@ impl Mp4TrackWriter { } } + pub(crate) fn update_offset(&mut self, offset: u64) -> Result<()> { + self.offset = offset; + Ok(()) + } + pub(crate) fn write_end(&mut self, writer: &mut W) -> Result { return self.write_end_with_offset(writer, 0); } - - pub(crate) fn write_end_with_offset(&mut self, writer: &mut W, offset: u64) -> Result { + pub(crate) fn write_end_with_offset( + &mut self, + writer: &mut W, + offset: u64, + ) -> 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.trak.mdia.mdhd.duration, - media_time: offset, - media_rate: 1, - media_rate_fraction: 0, - }], - })}); + self.trak.edts = Some(EdtsBox { + elst: Some(ElstBox { + version: 1, + flags: 0, + entries: vec![ElstEntry { + segment_duration: self.trak.mdia.mdhd.duration, + media_time: (offset * self.trak.mdia.mdhd.timescale as u64) / 1_000_000, + 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; @@ -963,4 +974,4 @@ impl Mp4TrackWriter { Ok(self.trak.clone()) } -} \ No newline at end of file +} diff --git a/src/types.rs b/src/types.rs index 13a24b1a..c0005849 100644 --- a/src/types.rs +++ b/src/types.rs @@ -231,7 +231,8 @@ pub enum MediaType { VP9, AAC, OPUS, - TTXT} + TTXT, +} impl fmt::Display for MediaType { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { @@ -784,4 +785,4 @@ impl<'a, T: Metadata<'a>> Metadata<'a> for Option { fn summary(&self) -> Option> { self.as_ref().and_then(|t| t.summary()) } -} \ No newline at end of file +} diff --git a/src/writer.rs b/src/writer.rs index f91ed5df..f02db6ac 100644 --- a/src/writer.rs +++ b/src/writer.rs @@ -20,6 +20,7 @@ pub struct Mp4Writer { mdat_pos: u64, timescale: u32, duration: u64, + offsets: Vec, } impl Mp4Writer { @@ -74,6 +75,7 @@ impl Mp4Writer { BoxHeader::new(BoxType::WideBox, HEADER_SIZE).write(&mut writer)?; let tracks = Vec::new(); + let offsets = Vec::new(); let timescale = config.timescale; let duration = 0; Ok(Self { @@ -82,6 +84,7 @@ impl Mp4Writer { mdat_pos, timescale, duration, + offsets, }) } @@ -92,6 +95,15 @@ impl Mp4Writer { Ok(track_id) } + pub fn update_offset(&mut self, track_index: u32, offset_us: u64) -> Result<()> { + if let Some(track) = self.tracks.get_mut(track_index as usize) { + track.update_offset(offset_us)? + } else { + return Err(Error::TrakNotFound(track_index)); + } + Ok(()) + } + fn update_durations(&mut self, track_dur: u64) { if track_dur > self.duration { self.duration = track_dur; @@ -146,21 +158,4 @@ impl Mp4Writer { moov.write_box(&mut self.writer)?; Ok(()) } - - pub fn write_end_with_offset(&mut self, offset: u64) -> Result<()> { - let mut moov = MoovBox::default(); - - for track in self.tracks.iter_mut() { - moov.traks.push(track.write_end_with_offset(&mut self.writer,offset)?); - } - self.update_mdat_size()?; - - moov.mvhd.timescale = self.timescale; - moov.mvhd.duration = self.duration; - if moov.mvhd.duration > (u32::MAX as u64) { - moov.mvhd.version = 1 - } - moov.write_box(&mut self.writer)?; - Ok(()) - } } From c7cf24ecb342bae76ec19918ade018ae0f4e9b68 Mon Sep 17 00:00:00 2001 From: damitha Date: Fri, 21 Mar 2025 13:49:10 +1100 Subject: [PATCH 21/46] . --- src/track.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/track.rs b/src/track.rs index 8cca1cee..77b88bf7 100644 --- a/src/track.rs +++ b/src/track.rs @@ -952,7 +952,7 @@ impl Mp4TrackWriter { version: 1, flags: 0, entries: vec![ElstEntry { - segment_duration: self.trak.mdia.mdhd.duration, + segment_duration: (offset * 1_000) / 1_000_000, media_time: (offset * self.trak.mdia.mdhd.timescale as u64) / 1_000_000, media_rate: 1, media_rate_fraction: 0, From 2d8a14fed8951bf6c506f09b542b9897a3e212dd Mon Sep 17 00:00:00 2001 From: damitha Date: Fri, 21 Mar 2025 13:58:37 +1100 Subject: [PATCH 22/46] . --- src/track.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/track.rs b/src/track.rs index 77b88bf7..5ab8f19b 100644 --- a/src/track.rs +++ b/src/track.rs @@ -959,6 +959,7 @@ impl Mp4TrackWriter { }], }), }); + print!("{:?}",self.trak); 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; From b7ca80278eb1a4c8ad536dbacc39828a9732ec25 Mon Sep 17 00:00:00 2001 From: damitha Date: Fri, 21 Mar 2025 14:11:54 +1100 Subject: [PATCH 23/46] . --- src/track.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/track.rs b/src/track.rs index 5ab8f19b..b70094b8 100644 --- a/src/track.rs +++ b/src/track.rs @@ -952,8 +952,8 @@ impl Mp4TrackWriter { version: 1, flags: 0, entries: vec![ElstEntry { - segment_duration: (offset * 1_000) / 1_000_000, - media_time: (offset * self.trak.mdia.mdhd.timescale as u64) / 1_000_000, + segment_duration: self.trak.tkhd.duration, + media_time: offset, media_rate: 1, media_rate_fraction: 0, }], From b9d5efa01ae5055e11de35f436becea44e8dba65 Mon Sep 17 00:00:00 2001 From: damitha Date: Fri, 21 Mar 2025 14:17:36 +1100 Subject: [PATCH 24/46] . --- src/track.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/track.rs b/src/track.rs index b70094b8..c98fb6ab 100644 --- a/src/track.rs +++ b/src/track.rs @@ -953,7 +953,7 @@ impl Mp4TrackWriter { flags: 0, entries: vec![ElstEntry { segment_duration: self.trak.tkhd.duration, - media_time: offset, + media_time: self.offset, media_rate: 1, media_rate_fraction: 0, }], From 3236012cb0bcc6ba6314823ff6851177e81be114 Mon Sep 17 00:00:00 2001 From: damitha Date: Fri, 21 Mar 2025 16:09:06 +1100 Subject: [PATCH 25/46] . --- src/track.rs | 16 ++++++---------- src/writer.rs | 6 ++++-- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/track.rs b/src/track.rs index c98fb6ab..89ffd858 100644 --- a/src/track.rs +++ b/src/track.rs @@ -668,6 +668,7 @@ pub(crate) struct Mp4TrackWriter { samples_per_chunk: u32, duration_per_chunk: u32, offset: u64, + duration: u64, } impl Mp4TrackWriter { @@ -930,19 +931,15 @@ impl Mp4TrackWriter { } } - pub(crate) fn update_offset(&mut self, offset: u64) -> Result<()> { - self.offset = offset; + 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 { - return self.write_end_with_offset(writer, 0); - } - - pub(crate) fn write_end_with_offset( + pub(crate) fn write_end( &mut self, writer: &mut W, - offset: u64, ) -> Result { self.write_chunk(writer)?; @@ -952,14 +949,13 @@ impl Mp4TrackWriter { version: 1, flags: 0, entries: vec![ElstEntry { - segment_duration: self.trak.tkhd.duration, + segment_duration: self.duration, media_time: self.offset, media_rate: 1, media_rate_fraction: 0, }], }), }); - print!("{:?}",self.trak); 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/writer.rs b/src/writer.rs index f02db6ac..2ca39c41 100644 --- a/src/writer.rs +++ b/src/writer.rs @@ -95,9 +95,11 @@ impl Mp4Writer { Ok(track_id) } - pub fn update_offset(&mut self, track_index: u32, offset_us: u64) -> Result<()> { + 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) { - track.update_offset(offset_us)? + //convert duration to mvhd timescale + let duration = duration_us * self.timescale as u64 / 1_000_000; + track.update_edit_list(offset,duration)? } else { return Err(Error::TrakNotFound(track_index)); } From 8b47da93c6b82190bf787b8747c56562a6628646 Mon Sep 17 00:00:00 2001 From: damitha Date: Fri, 21 Mar 2025 16:11:15 +1100 Subject: [PATCH 26/46] . --- src/track.rs | 7 ++----- src/writer.rs | 7 +++---- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/track.rs b/src/track.rs index 89ffd858..a6c066de 100644 --- a/src/track.rs +++ b/src/track.rs @@ -932,15 +932,12 @@ impl Mp4TrackWriter { } pub(crate) fn update_edit_list(&mut self, offset: u64, duration: u64) -> Result<()> { - self.offset = offset ; + self.offset = offset; self.duration = duration; Ok(()) } - pub(crate) fn write_end( - &mut self, - writer: &mut W, - ) -> Result { + pub(crate) fn write_end(&mut self, writer: &mut W) -> Result { self.write_chunk(writer)?; let max_sample_size = self.max_sample_size(); diff --git a/src/writer.rs b/src/writer.rs index 2ca39c41..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::*; @@ -20,7 +21,6 @@ pub struct Mp4Writer { mdat_pos: u64, timescale: u32, duration: u64, - offsets: Vec, } impl Mp4Writer { @@ -75,7 +75,6 @@ impl Mp4Writer { BoxHeader::new(BoxType::WideBox, HEADER_SIZE).write(&mut writer)?; let tracks = Vec::new(); - let offsets = Vec::new(); let timescale = config.timescale; let duration = 0; Ok(Self { @@ -84,7 +83,6 @@ impl Mp4Writer { mdat_pos, timescale, duration, - offsets, }) } @@ -99,7 +97,8 @@ impl Mp4Writer { 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,duration)? + + track.update_edit_list(offset, cmp::min(duration, self.duration))? } else { return Err(Error::TrakNotFound(track_index)); } From f085d794f1cc0c135051e2476901b235a5ebaefe Mon Sep 17 00:00:00 2001 From: damitha Date: Sat, 22 Mar 2025 14:59:56 +1100 Subject: [PATCH 27/46] . --- src/reader.rs | 11 ++++++++++- src/track.rs | 17 +++++++++++++++++ src/types.rs | 8 ++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/reader.rs b/src/reader.rs index 2b9cd2a1..dff0a83f 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -261,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) @@ -270,6 +270,15 @@ 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(&mut self.reader, 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 a6c066de..ec334f86 100644 --- a/src/track.rs +++ b/src/track.rs @@ -619,6 +619,23 @@ impl Mp4Track { } } + pub(crate) fn read_sample_metadata( + &self, + reader: &mut R, + 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, diff --git a/src/types.rs b/src/types.rs index c0005849..18f2fb40 100644 --- a/src/types.rs +++ b/src/types.rs @@ -671,6 +671,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 From 4e0db2a3a3a6ee47718e28f359e906f70bd489b6 Mon Sep 17 00:00:00 2001 From: damitha Date: Sat, 22 Mar 2025 18:06:07 +1100 Subject: [PATCH 28/46] . --- examples/mp4copy.rs | 1 + src/mp4box/mp4a.rs | 14 ++++++++++---- src/reader.rs | 2 +- src/track.rs | 3 +-- src/types.rs | 3 +++ 5 files changed, 16 insertions(+), 7 deletions(-) diff --git a/examples/mp4copy.rs b/examples/mp4copy.rs index f6f2cd5b..583dc63b 100644 --- a/examples/mp4copy.rs +++ b/examples/mp4copy.rs @@ -63,6 +63,7 @@ fn copy>(src_filename: &P, dst_filename: &P) -> Result<()> { profile: track.audio_profile()?, freq_index: track.sample_freq_index()?, chan_conf: track.channel_config()?, + esds: None }), MediaType::OPUS => MediaConfig::OpusConfig(OpusConfig { bitrate: track.bitrate(), diff --git a/src/mp4box/mp4a.rs b/src/mp4box/mp4a.rs index f83573ba..264fdc3b 100644 --- a/src/mp4box/mp4a.rs +++ b/src/mp4box/mp4a.rs @@ -28,13 +28,13 @@ impl Default for Mp4aBox { } impl Mp4aBox { - pub fn new(config: &AacConfig) -> Self { + pub fn new(config: &AacConfig ) -> Self { Self { data_reference_index: 1, channelcount: config.chan_conf as u16, samplesize: 16, samplerate: FixedPointU16::new(config.freq_index.freq() as u16), - esds: Some(EsdsBox::new(config)), + esds: config.esds.clone(), } } @@ -313,10 +313,16 @@ pub struct ESDescriptor { impl ESDescriptor { pub fn new(config: &AacConfig) -> Self { + let mut dec_config = DecoderConfigDescriptor::new(config); + let mut sl_config = SLConfigDescriptor::new(); + if config.esds.is_some() { + dec_config = config.esds.clone().unwrap().es_desc.dec_config; + sl_config = config.esds.clone().unwrap().es_desc.sl_config; + } Self { es_id: 1, - dec_config: DecoderConfigDescriptor::new(config), - sl_config: SLConfigDescriptor::new(), + dec_config: dec_config, + sl_config: sl_config, } } } diff --git a/src/reader.rs b/src/reader.rs index dff0a83f..a164e750 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -272,7 +272,7 @@ 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(&mut self.reader, sample_id) + track.read_sample_metadata(sample_id) } else { Err(Error::TrakNotFound(track_id)) } diff --git a/src/track.rs b/src/track.rs index ec334f86..677fbd18 100644 --- a/src/track.rs +++ b/src/track.rs @@ -619,9 +619,8 @@ impl Mp4Track { } } - pub(crate) fn read_sample_metadata( + pub(crate) fn read_sample_metadata( &self, - reader: &mut R, sample_id: u32, ) -> Result> { let (start_time, duration) = self.sample_time(sample_id).unwrap(); diff --git a/src/types.rs b/src/types.rs index 18f2fb40..27e90cc9 100644 --- a/src/types.rs +++ b/src/types.rs @@ -3,6 +3,7 @@ use std::borrow::Cow; use std::convert::TryFrom; use std::fmt; +use crate::mp4a::EsdsBox; use crate::mp4box::*; use crate::*; @@ -617,6 +618,7 @@ pub struct AacConfig { pub profile: AudioObjectType, pub freq_index: SampleFreqIndex, pub chan_conf: ChannelConfig, + pub esds: Option, } impl Default for AacConfig { @@ -626,6 +628,7 @@ impl Default for AacConfig { profile: AudioObjectType::AacLowComplexity, freq_index: SampleFreqIndex::Freq48000, chan_conf: ChannelConfig::Stereo, + esds: None, } } } From 231d70d29740cb228c6d03a94680f333e606373a Mon Sep 17 00:00:00 2001 From: damitha Date: Sun, 23 Mar 2025 20:42:15 +1100 Subject: [PATCH 29/46] . --- examples/mp4copy.rs | 2 +- src/mp4box/mp4a.rs | 4 ++-- src/reader.rs | 7 +++++-- src/track.rs | 17 ++++++++++++----- 4 files changed, 20 insertions(+), 10 deletions(-) diff --git a/examples/mp4copy.rs b/examples/mp4copy.rs index 583dc63b..7bbfd098 100644 --- a/examples/mp4copy.rs +++ b/examples/mp4copy.rs @@ -63,7 +63,7 @@ fn copy>(src_filename: &P, dst_filename: &P) -> Result<()> { profile: track.audio_profile()?, freq_index: track.sample_freq_index()?, chan_conf: track.channel_config()?, - esds: None + esds: None, }), MediaType::OPUS => MediaConfig::OpusConfig(OpusConfig { bitrate: track.bitrate(), diff --git a/src/mp4box/mp4a.rs b/src/mp4box/mp4a.rs index 264fdc3b..bf11389a 100644 --- a/src/mp4box/mp4a.rs +++ b/src/mp4box/mp4a.rs @@ -28,7 +28,7 @@ impl Default for Mp4aBox { } impl Mp4aBox { - pub fn new(config: &AacConfig ) -> Self { + pub fn new(config: &AacConfig) -> Self { Self { data_reference_index: 1, channelcount: config.chan_conf as u16, @@ -314,7 +314,7 @@ pub struct ESDescriptor { impl ESDescriptor { pub fn new(config: &AacConfig) -> Self { let mut dec_config = DecoderConfigDescriptor::new(config); - let mut sl_config = SLConfigDescriptor::new(); + let mut sl_config = SLConfigDescriptor::new(); if config.esds.is_some() { dec_config = config.esds.clone().unwrap().es_desc.dec_config; sl_config = config.esds.clone().unwrap().es_desc.sl_config; diff --git a/src/reader.rs b/src/reader.rs index a164e750..530a477a 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -270,7 +270,11 @@ impl Mp4Reader { } } - pub fn read_sample_metadata(&mut self, track_id: u32, sample_id: u32) -> Result> { + 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 { @@ -278,7 +282,6 @@ impl Mp4Reader { } } - 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 677fbd18..2ab1ecf0 100644 --- a/src/track.rs +++ b/src/track.rs @@ -5,6 +5,7 @@ use std::io::{Read, Seek, SeekFrom, Write}; use std::time::Duration; use crate::elst::ElstEntry; +use crate::mp4a::EsdsBox; use crate::mp4box::traf::TrafBox; use crate::mp4box::trak::TrakBox; use crate::mp4box::trun::TrunBox; @@ -303,6 +304,15 @@ impl Mp4Track { } } + 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(), 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.first() { @@ -619,11 +629,8 @@ impl Mp4Track { } } - pub(crate) fn read_sample_metadata( - &self, - sample_id: u32, - ) -> Result> { - let (start_time, duration) = self.sample_time(sample_id).unwrap(); + 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); From 666939ed6487b90805f639f17d316e996a168fcb Mon Sep 17 00:00:00 2001 From: damitha Date: Mon, 24 Mar 2025 19:17:19 +1100 Subject: [PATCH 30/46] . --- examples/mp4copy.rs | 28 +++++++--- src/mp4box/mp4a.rs | 95 +++++++++++++++++++++++++++----- src/types.rs | 130 +++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 229 insertions(+), 24 deletions(-) diff --git a/examples/mp4copy.rs b/examples/mp4copy.rs index 7bbfd098..fe188ed6 100644 --- a/examples/mp4copy.rs +++ b/examples/mp4copy.rs @@ -58,13 +58,27 @@ fn copy>(src_filename: &P, dst_filename: &P) -> Result<()> { width: track.width(), height: track.height(), }), - MediaType::AAC => MediaConfig::AacConfig(AacConfig { - bitrate: track.bitrate(), - profile: track.audio_profile()?, - freq_index: track.sample_freq_index()?, - chan_conf: track.channel_config()?, - esds: None, - }), + 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()?, + 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(), freq_index: track.sample_freq_index()?, diff --git a/src/mp4box/mp4a.rs b/src/mp4box/mp4a.rs index bf11389a..c5ce066b 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()), } } @@ -31,10 +35,12 @@ impl Mp4aBox { pub fn new(config: &AacConfig) -> Self { Self { data_reference_index: 1, + sound_version: 0, channelcount: config.chan_conf as u16, samplesize: 16, samplerate: FixedPointU16::new(config.freq_index.freq() as u16), - esds: config.esds.clone(), + qt_bytes: None, + esds: Some(EsdsBox::default()), } } @@ -44,6 +50,9 @@ impl Mp4aBox { pub fn get_size(&self) -> u64 { let mut size = HEADER_SIZE + 8 + 20; + 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 +91,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 +139,11 @@ impl ReadBox<&mut R> for Mp4aBox { Ok(Mp4aBox { data_reference_index, + sound_version: sound_version, channelcount, samplesize, samplerate, + qt_bytes, esds, }) } @@ -144,12 +158,20 @@ 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 let Some(ref qt_bytes) = self.qt_bytes { + writer.write_all(&qt_bytes)?; + } + if let Some(ref esds) = self.esds { esds.write_box(writer)?; } @@ -313,12 +335,8 @@ pub struct ESDescriptor { impl ESDescriptor { pub fn new(config: &AacConfig) -> Self { - let mut dec_config = DecoderConfigDescriptor::new(config); - let mut sl_config = SLConfigDescriptor::new(); - if config.esds.is_some() { - dec_config = config.esds.clone().unwrap().es_desc.dec_config; - sl_config = config.esds.clone().unwrap().es_desc.sl_config; - } + let dec_config = DecoderConfigDescriptor::new(config); + let sl_config = SLConfigDescriptor::new(); Self { es_id: 1, dec_config: dec_config, @@ -640,8 +658,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, @@ -678,13 +698,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(); diff --git a/src/types.rs b/src/types.rs index 27e90cc9..0542494a 100644 --- a/src/types.rs +++ b/src/types.rs @@ -618,17 +618,143 @@ pub struct AacConfig { pub profile: AudioObjectType, pub freq_index: SampleFreqIndex, pub chan_conf: ChannelConfig, - pub esds: Option, + + // New fields from Mp4aBox and child structs + pub data_reference_index: u16, + pub sound_version: 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: mp4a.sound_version, + + 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); + + // Assuming decoder_specific has a getter for its binary data + // config.decoder_specific_data = Some(dec_config.dec_specific.get_data().clone()); + } + 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, - esds: None, + 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, } } } From b4dbe3fe0893e70539d199d07e1c42129284a575 Mon Sep 17 00:00:00 2001 From: damitha Date: Mon, 24 Mar 2025 19:45:57 +1100 Subject: [PATCH 31/46] . --- examples/mp4copy.rs | 1 + src/mp4box/mp4a.rs | 22 +++++++++++----------- src/types.rs | 6 +++--- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/examples/mp4copy.rs b/examples/mp4copy.rs index fe188ed6..40c73e9b 100644 --- a/examples/mp4copy.rs +++ b/examples/mp4copy.rs @@ -65,6 +65,7 @@ fn copy>(src_filename: &P, dst_filename: &P) -> Result<()> { 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, diff --git a/src/mp4box/mp4a.rs b/src/mp4box/mp4a.rs index c5ce066b..1119322c 100644 --- a/src/mp4box/mp4a.rs +++ b/src/mp4box/mp4a.rs @@ -34,12 +34,12 @@ impl Default for Mp4aBox { impl Mp4aBox { pub fn new(config: &AacConfig) -> Self { Self { - data_reference_index: 1, - sound_version: 0, + 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: None, + qt_bytes: config.qt_bytes.clone(), esds: Some(EsdsBox::default()), } } @@ -338,7 +338,7 @@ impl ESDescriptor { let dec_config = DecoderConfigDescriptor::new(config); let sl_config = SLConfigDescriptor::new(); Self { - es_id: 1, + es_id: config.es_id.unwrap_or(0), dec_config: dec_config, sl_config: sl_config, } @@ -428,12 +428,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), } } diff --git a/src/types.rs b/src/types.rs index 0542494a..88b397aa 100644 --- a/src/types.rs +++ b/src/types.rs @@ -622,6 +622,7 @@ pub struct AacConfig { // 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, @@ -682,6 +683,7 @@ impl AacConfig { data_reference_index: mp4a.data_reference_index, sound_version: mp4a.sound_version, + samplesize: mp4a.samplesize, qt_bytes: mp4a.qt_bytes.clone(), // Set remaining fields to None initially @@ -727,9 +729,6 @@ impl AacConfig { 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); - - // Assuming decoder_specific has a getter for its binary data - // config.decoder_specific_data = Some(dec_config.dec_specific.get_data().clone()); } config } @@ -743,6 +742,7 @@ impl Default for AacConfig { 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), From 2422925285830790670aac1777d7f9e988a5a6f1 Mon Sep 17 00:00:00 2001 From: damitha Date: Mon, 24 Mar 2025 20:33:34 +1100 Subject: [PATCH 32/46] . --- src/mp4box/mp4a.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mp4box/mp4a.rs b/src/mp4box/mp4a.rs index 1119322c..a8fb56da 100644 --- a/src/mp4box/mp4a.rs +++ b/src/mp4box/mp4a.rs @@ -40,7 +40,7 @@ impl Mp4aBox { samplesize: config.samplesize, samplerate: FixedPointU16::new(config.freq_index.freq() as u16), qt_bytes: config.qt_bytes.clone(), - esds: Some(EsdsBox::default()), + esds: Some(EsdsBox::new(config)), } } From 263d205639c664ce600ccf5e806e365773feefe4 Mon Sep 17 00:00:00 2001 From: damitha Date: Mon, 24 Mar 2025 22:42:01 +1100 Subject: [PATCH 33/46] . --- src/mp4box/mp4a.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/mp4box/mp4a.rs b/src/mp4box/mp4a.rs index a8fb56da..cfcb7ec5 100644 --- a/src/mp4box/mp4a.rs +++ b/src/mp4box/mp4a.rs @@ -297,12 +297,7 @@ fn read_desc(reader: &mut R) -> Result<(u8, u32)> { } fn size_of_length(size: u32) -> u32 { - match size { - 0x0..=0x7F => 1, - 0x80..=0x3FFF => 2, - 0x4000..=0x1FFFFF => 3, - _ => 4, - } + return 4; } fn write_desc(writer: &mut W, tag: u8, size: u32) -> Result { @@ -771,7 +766,7 @@ mod tests { 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 + 2); + assert_eq!(buf.len(), written as usize + 5); let mut reader = Cursor::new(&buf); let (tag, size) = read_desc(&mut reader).unwrap(); From 14934c00d00309aef307f6dc3b8acf06662594ed Mon Sep 17 00:00:00 2001 From: damitha Date: Mon, 24 Mar 2025 23:10:38 +1100 Subject: [PATCH 34/46] . --- src/mp4box/mp4a.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mp4box/mp4a.rs b/src/mp4box/mp4a.rs index cfcb7ec5..7349661f 100644 --- a/src/mp4box/mp4a.rs +++ b/src/mp4box/mp4a.rs @@ -492,7 +492,7 @@ impl WriteDesc<&mut W> for DecoderConfigDescriptor { 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) | 1)?; writer.write_u24::(self.buffer_size_db)?; writer.write_u32::(self.max_bitrate)?; writer.write_u32::(self.avg_bitrate)?; From 79c0ccf283601d113159c42c3461fd0c113dd3a1 Mon Sep 17 00:00:00 2001 From: damitha Date: Mon, 24 Mar 2025 23:16:19 +1100 Subject: [PATCH 35/46] . --- src/mp4box/mp4a.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mp4box/mp4a.rs b/src/mp4box/mp4a.rs index 7349661f..5688f3e3 100644 --- a/src/mp4box/mp4a.rs +++ b/src/mp4box/mp4a.rs @@ -492,7 +492,7 @@ impl WriteDesc<&mut W> for DecoderConfigDescriptor { write_desc(writer, Self::desc_tag(), size)?; writer.write_u8(self.object_type_indication)?; - writer.write_u8((self.stream_type << 2) | ((self.up_stream & 0x01) << 1) | 1)?; + 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)?; From 8747b9af4f005b9ba795941bfa979cd312ea8383 Mon Sep 17 00:00:00 2001 From: damitha Date: Mon, 24 Mar 2025 23:40:22 +1100 Subject: [PATCH 36/46] . --- src/types.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types.rs b/src/types.rs index 88b397aa..2dcec347 100644 --- a/src/types.rs +++ b/src/types.rs @@ -681,7 +681,7 @@ impl AacConfig { // Set new fields from mp4a data_reference_index: mp4a.data_reference_index, - sound_version: mp4a.sound_version, + sound_version: 0, samplesize: mp4a.samplesize, qt_bytes: mp4a.qt_bytes.clone(), From 21ce6a9368b4408fd338628f940a16b6ed6a1033 Mon Sep 17 00:00:00 2001 From: damitha Date: Mon, 24 Mar 2025 23:47:01 +1100 Subject: [PATCH 37/46] . --- src/mp4box/mp4a.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/mp4box/mp4a.rs b/src/mp4box/mp4a.rs index 5688f3e3..18389e2b 100644 --- a/src/mp4box/mp4a.rs +++ b/src/mp4box/mp4a.rs @@ -168,9 +168,11 @@ impl WriteBox<&mut W> for Mp4aBox { writer.write_u32::(0)?; // reserved writer.write_u32::(self.samplerate.raw_value())?; - if let Some(ref qt_bytes) = self.qt_bytes { + 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)?; From b780ef092a9c8d8821b68ce7ed236cfb86f42273 Mon Sep 17 00:00:00 2001 From: damitha Date: Mon, 24 Mar 2025 23:50:14 +1100 Subject: [PATCH 38/46] . --- src/mp4box/mp4a.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/mp4box/mp4a.rs b/src/mp4box/mp4a.rs index 18389e2b..36eeabb7 100644 --- a/src/mp4box/mp4a.rs +++ b/src/mp4box/mp4a.rs @@ -50,8 +50,10 @@ impl Mp4aBox { pub fn get_size(&self) -> u64 { let mut size = HEADER_SIZE + 8 + 20; - if let Some(ref qt_bytes) = self.qt_bytes { - size += qt_bytes.len() as u64; + 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(); From d2e69b9a7c5978ff7920ce3d41d433ffa9de356c Mon Sep 17 00:00:00 2001 From: damitha Date: Tue, 25 Mar 2025 09:59:05 +1100 Subject: [PATCH 39/46] . --- src/mp4box/mp4a.rs | 2 +- src/track.rs | 1 + src/types.rs | 10 ++++++++++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/mp4box/mp4a.rs b/src/mp4box/mp4a.rs index 36eeabb7..b979555a 100644 --- a/src/mp4box/mp4a.rs +++ b/src/mp4box/mp4a.rs @@ -300,7 +300,7 @@ fn read_desc(reader: &mut R) -> Result<(u8, u32)> { Ok((tag, size)) } -fn size_of_length(size: u32) -> u32 { +fn size_of_length(_: u32) -> u32 { return 4; } diff --git a/src/track.rs b/src/track.rs index 2ab1ecf0..8a97e1de 100644 --- a/src/track.rs +++ b/src/track.rs @@ -701,6 +701,7 @@ impl Mp4TrackWriter { trak.mdia.mdhd.timescale = config.timescale; 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) => { diff --git a/src/types.rs b/src/types.rs index 2dcec347..3d8c5c1d 100644 --- a/src/types.rs +++ b/src/types.rs @@ -218,6 +218,16 @@ 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"; From 90d089bf831eacdc937e4a804cbab16132d0a378 Mon Sep 17 00:00:00 2001 From: damitha Date: Tue, 25 Mar 2025 20:30:57 +1100 Subject: [PATCH 40/46] . --- src/track.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/track.rs b/src/track.rs index d55a4be6..2055a7ca 100644 --- a/src/track.rs +++ b/src/track.rs @@ -219,7 +219,7 @@ impl Mp4Track { if let Some(ref dops) = opus.dops_box { SampleFreqIndex::try_from(dops.input_sample_rate) } else { - Err(Error::BoxInStblNotFound(self.track_id(), BoxType::DopsBox)) + Err(Error::BoxInStblNotFound(self.track_id(), vec![BoxType::DopsBox])) } } else { Err(Error::BoxInStblNotFound( @@ -243,7 +243,7 @@ impl Mp4Track { if let Some(ref dops) = opus.dops_box { ChannelConfig::try_from(dops.output_channel_count) } else { - Err(Error::BoxInStblNotFound(self.track_id(), BoxType::DopsBox)) + Err(Error::BoxInStblNotFound(self.track_id(), vec![BoxType::DopsBox])) } } else { Err(Error::BoxInStblNotFound( @@ -344,7 +344,7 @@ impl Mp4Track { return Ok(esds); } } - Err(Error::BoxInStblNotFound(self.track_id(), BoxType::EsdsBox)) + Err(Error::BoxInStblNotFound(self.track_id(), vec![BoxType::EsdsBox])) } pub fn picture_parameter_set(&self) -> Result<&[u8]> { From 3f9084df05b6e087697e0719d85b508f2942cbb6 Mon Sep 17 00:00:00 2001 From: damitha Date: Tue, 25 Mar 2025 21:00:23 +1100 Subject: [PATCH 41/46] . --- src/mp4box/hvc1.rs | 55 ++++++++++++++++++++-------------------------- src/mp4box/mp4a.rs | 8 +++---- src/mp4box/stsd.rs | 4 +++- src/track.rs | 15 ++++++++++--- 4 files changed, 43 insertions(+), 39 deletions(-) diff --git a/src/mp4box/hvc1.rs b/src/mp4box/hvc1.rs index 418ce66f..bde79d2a 100644 --- a/src/mp4box/hvc1.rs +++ b/src/mp4box/hvc1.rs @@ -101,38 +101,31 @@ impl ReadBox<&mut R> for Hvc1Box { let depth = reader.read_u16::()?; reader.read_i16::()?; // pre-defined - let mut hvcc = None; - - while reader.stream_position()? < start + size { - let header = BoxHeader::read(reader)?; - let BoxHeader { name, size: s } = header; - if s > size { - return Err(Error::InvalidData( - "hvc1 box contains a box with a larger size than it", - )); - } - if name == BoxType::HvcCBox { - hvcc = Some(HvcCBox::read_box(reader, s)?); - } else { - skip_box(reader, s)?; - } + 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")) } - let Some(hvcc) = hvcc else { - return Err(Error::InvalidData("hvcc not found")); - }; - - skip_bytes_to(reader, start + size)?; - - Ok(Hvc1Box { - data_reference_index, - width, - height, - horizresolution, - vertresolution, - frame_count, - depth, - hvcc, - }) } } diff --git a/src/mp4box/mp4a.rs b/src/mp4box/mp4a.rs index b979555a..06fdb347 100644 --- a/src/mp4box/mp4a.rs +++ b/src/mp4box/mp4a.rs @@ -170,11 +170,11 @@ impl WriteBox<&mut W> for Mp4aBox { 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 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)?; diff --git a/src/mp4box/stsd.rs b/src/mp4box/stsd.rs index 4006747b..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, hvc1::Hvc1Box, mp4a::Mp4aBox, opus::OpusBox, 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 { diff --git a/src/track.rs b/src/track.rs index 2055a7ca..021b8881 100644 --- a/src/track.rs +++ b/src/track.rs @@ -219,7 +219,10 @@ impl Mp4Track { 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])) + Err(Error::BoxInStblNotFound( + self.track_id(), + vec![BoxType::DopsBox], + )) } } else { Err(Error::BoxInStblNotFound( @@ -243,7 +246,10 @@ impl Mp4Track { 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])) + Err(Error::BoxInStblNotFound( + self.track_id(), + vec![BoxType::DopsBox], + )) } } else { Err(Error::BoxInStblNotFound( @@ -344,7 +350,10 @@ impl Mp4Track { return Ok(esds); } } - Err(Error::BoxInStblNotFound(self.track_id(), vec![BoxType::EsdsBox])) + Err(Error::BoxInStblNotFound( + self.track_id(), + vec![BoxType::EsdsBox], + )) } pub fn picture_parameter_set(&self) -> Result<&[u8]> { From b06846e132eb201ae34f308d96f7c56dcfc6853e Mon Sep 17 00:00:00 2001 From: damitha Date: Wed, 26 Mar 2025 14:52:32 +1100 Subject: [PATCH 42/46] . --- examples/mp4copy.rs | 22 +++++++++++-- src/mp4box/hev1.rs | 4 +-- src/mp4box/hvc1.rs | 4 +-- src/track.rs | 4 +-- src/types.rs | 76 +++++++++++++++++++++++++++++++++++++++++++-- 5 files changed, 99 insertions(+), 11 deletions(-) diff --git a/examples/mp4copy.rs b/examples/mp4copy.rs index 40c73e9b..d98930b0 100644 --- a/examples/mp4copy.rs +++ b/examples/mp4copy.rs @@ -51,8 +51,26 @@ 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: 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, }), MediaType::VP9 => MediaConfig::Vp9Config(Vp9Config { width: track.width(), diff --git a/src/mp4box/hev1.rs b/src/mp4box/hev1.rs index ae37d84f..c955b3fa 100644 --- a/src/mp4box/hev1.rs +++ b/src/mp4box/hev1.rs @@ -39,8 +39,8 @@ impl Hev1Box { pub fn new(config: &HevcConfig) -> Self { Hev1Box { 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, diff --git a/src/mp4box/hvc1.rs b/src/mp4box/hvc1.rs index bde79d2a..48a2886a 100644 --- a/src/mp4box/hvc1.rs +++ b/src/mp4box/hvc1.rs @@ -39,8 +39,8 @@ impl Hvc1Box { pub fn new(config: &HevcConfig) -> Self { 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, diff --git a/src/track.rs b/src/track.rs index 021b8881..5d0425f5 100644 --- a/src/track.rs +++ b/src/track.rs @@ -784,8 +784,8 @@ 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); diff --git a/src/types.rs b/src/types.rs index 3d8c5c1d..4dc03241 100644 --- a/src/types.rs +++ b/src/types.rs @@ -3,6 +3,7 @@ use std::borrow::Cow; use std::convert::TryFrom; use std::fmt; +use crate::hev1::HvcCArray; use crate::mp4a::EsdsBox; use crate::mp4box::*; use crate::*; @@ -610,10 +611,79 @@ pub struct AvcConfig { pub pic_param_set: Vec, } -#[derive(Debug, PartialEq, Eq, Clone, Default)] +#[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>, +} + +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, + } + } +} + +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 + } + + // ... add similar methods for all other fields ... } #[derive(Debug, PartialEq, Eq, Clone, Default)] From 4807dc26e151ff9e9ed152d76fe499669d0368bc Mon Sep 17 00:00:00 2001 From: damitha Date: Wed, 26 Mar 2025 14:56:20 +1100 Subject: [PATCH 43/46] . --- examples/mp4copy.rs | 36 ++++++++++++++++++------------------ src/mp4box/hev1.rs | 23 +++++++++++++++++++++-- src/mp4box/hvc1.rs | 21 ++++++++++++++++++++- 3 files changed, 59 insertions(+), 21 deletions(-) diff --git a/examples/mp4copy.rs b/examples/mp4copy.rs index d98930b0..a6d164ee 100644 --- a/examples/mp4copy.rs +++ b/examples/mp4copy.rs @@ -53,24 +53,24 @@ fn copy>(src_filename: &P, dst_filename: &P) -> Result<()> { MediaType::H265 => MediaConfig::HevcConfig(HevcConfig { width: Some(track.width()), height: Some(track.height()), - 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, + 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![]), }), MediaType::VP9 => MediaConfig::Vp9Config(Vp9Config { width: track.width(), diff --git a/src/mp4box/hev1.rs b/src/mp4box/hev1.rs index c955b3fa..008f727f 100644 --- a/src/mp4box/hev1.rs +++ b/src/mp4box/hev1.rs @@ -37,7 +37,7 @@ impl Default for Hev1Box { impl Hev1Box { pub fn new(config: &HevcConfig) -> Self { - Hev1Box { + Self { data_reference_index: 1, width: config.width.unwrap_or(0), height: config.height.unwrap_or(0), @@ -45,7 +45,26 @@ impl Hev1Box { 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(), + }, } } diff --git a/src/mp4box/hvc1.rs b/src/mp4box/hvc1.rs index 48a2886a..45e76fd7 100644 --- a/src/mp4box/hvc1.rs +++ b/src/mp4box/hvc1.rs @@ -45,7 +45,26 @@ impl Hvc1Box { 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(), + }, } } From 0e70b588921f00590b471732a17bcbe238c3c992 Mon Sep 17 00:00:00 2001 From: damitha Date: Wed, 26 Mar 2025 15:14:44 +1100 Subject: [PATCH 44/46] . --- examples/mp4copy.rs | 4 ++-- src/track.rs | 13 +++++++++++-- src/types.rs | 13 +++++++++++++ 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/examples/mp4copy.rs b/examples/mp4copy.rs index a6d164ee..ddfb2574 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, OpusConfig, Result, - TrackConfig, TtxtConfig, Vp9Config, + AacConfig, AvcConfig, HevcBoxType, HevcConfig, MediaConfig, MediaType, Mp4Config, OpusConfig, Result, TrackConfig, TtxtConfig, Vp9Config }; fn main() { @@ -71,6 +70,7 @@ fn copy>(src_filename: &P, dst_filename: &P) -> Result<()> { temporal_id_nested: Some(false), length_size_minus_one: Some(3), arrays: Some(vec![]), + box_type: Some(HevcBoxType::Hev1), }), MediaType::VP9 => MediaConfig::Vp9Config(Vp9Config { width: track.width(), diff --git a/src/track.rs b/src/track.rs index 5d0425f5..0a2004a2 100644 --- a/src/track.rs +++ b/src/track.rs @@ -5,6 +5,7 @@ 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; @@ -790,8 +791,16 @@ impl Mp4TrackWriter { let vmhd = VmhdBox::default(); trak.mdia.minf.vmhd = Some(vmhd); - let hev1 = Hev1Box::new(hevc_config); - trak.mdia.minf.stbl.stsd.hev1 = Some(hev1); + match hevc_config.box_type.unwrap_or(HevcBoxType::Hev1) { + HevcBoxType::Hev1 => { + let hev1 = Hev1Box::new(hevc_config); + trak.mdia.minf.stbl.stsd.hev1 = Some(hev1); + } + HevcBoxType::Hvc1 => { + let hvc1 = Hvc1Box::new(hevc_config); + trak.mdia.minf.stbl.stsd.hvc1 = Some(hvc1); + } + } } MediaConfig::Vp9Config(ref config) => { trak.tkhd.set_width(config.width); diff --git a/src/types.rs b/src/types.rs index 4dc03241..2fada372 100644 --- a/src/types.rs +++ b/src/types.rs @@ -611,6 +611,12 @@ pub struct AvcConfig { pub pic_param_set: Vec, } +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum HevcBoxType { + Hev1, + Hvc1, +} + #[derive(Debug, PartialEq, Eq, Clone)] pub struct HevcConfig { pub width: Option, @@ -633,6 +639,7 @@ pub struct HevcConfig { pub temporal_id_nested: Option, pub length_size_minus_one: Option, pub arrays: Option>, + pub box_type: Option, } impl Default for HevcConfig { @@ -658,6 +665,7 @@ impl Default for HevcConfig { temporal_id_nested: None, length_size_minus_one: None, arrays: None, + box_type: None, } } } @@ -683,6 +691,11 @@ impl HevcConfig { self } + pub fn with_box_type(mut self, box_type: HevcBoxType) -> Self { + self.box_type = Some(box_type); + self + } + // ... add similar methods for all other fields ... } From caf699fce81bb7d97938291b7194ace47f3f928b Mon Sep 17 00:00:00 2001 From: damitha Date: Wed, 26 Mar 2025 15:23:25 +1100 Subject: [PATCH 45/46] . --- examples/mp4copy.rs | 2 +- src/track.rs | 15 ++++++--------- src/types.rs | 8 ++++---- 3 files changed, 11 insertions(+), 14 deletions(-) diff --git a/examples/mp4copy.rs b/examples/mp4copy.rs index ddfb2574..16df2831 100644 --- a/examples/mp4copy.rs +++ b/examples/mp4copy.rs @@ -70,7 +70,7 @@ fn copy>(src_filename: &P, dst_filename: &P) -> Result<()> { temporal_id_nested: Some(false), length_size_minus_one: Some(3), arrays: Some(vec![]), - box_type: Some(HevcBoxType::Hev1), + use_hvc1: true, }), MediaType::VP9 => MediaConfig::Vp9Config(Vp9Config { width: track.width(), diff --git a/src/track.rs b/src/track.rs index 0a2004a2..b760b470 100644 --- a/src/track.rs +++ b/src/track.rs @@ -791,15 +791,12 @@ impl Mp4TrackWriter { let vmhd = VmhdBox::default(); trak.mdia.minf.vmhd = Some(vmhd); - match hevc_config.box_type.unwrap_or(HevcBoxType::Hev1) { - HevcBoxType::Hev1 => { - let hev1 = Hev1Box::new(hevc_config); - trak.mdia.minf.stbl.stsd.hev1 = Some(hev1); - } - HevcBoxType::Hvc1 => { - let hvc1 = Hvc1Box::new(hevc_config); - trak.mdia.minf.stbl.stsd.hvc1 = Some(hvc1); - } + 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) => { diff --git a/src/types.rs b/src/types.rs index 2fada372..1bfad67c 100644 --- a/src/types.rs +++ b/src/types.rs @@ -639,7 +639,7 @@ pub struct HevcConfig { pub temporal_id_nested: Option, pub length_size_minus_one: Option, pub arrays: Option>, - pub box_type: Option, + pub use_hvc1: bool, } impl Default for HevcConfig { @@ -665,7 +665,7 @@ impl Default for HevcConfig { temporal_id_nested: None, length_size_minus_one: None, arrays: None, - box_type: None, + use_hvc1: false, } } } @@ -691,8 +691,8 @@ impl HevcConfig { self } - pub fn with_box_type(mut self, box_type: HevcBoxType) -> Self { - self.box_type = Some(box_type); + pub fn with_use_hvc1(mut self, use_hvc1: bool) -> Self { + self.use_hvc1 = use_hvc1; self } From 485d9c9a96233e440021a769e756376e43a35a59 Mon Sep 17 00:00:00 2001 From: damitha Date: Wed, 26 Mar 2025 18:26:32 +1100 Subject: [PATCH 46/46] Implement detailed writing of HVCC configuration data in Hvc1Box, including various profile and constraint parameters. --- src/mp4box/hvc1.rs | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/mp4box/hvc1.rs b/src/mp4box/hvc1.rs index 45e76fd7..8f79f8d7 100644 --- a/src/mp4box/hvc1.rs +++ b/src/mp4box/hvc1.rs @@ -171,7 +171,35 @@ impl WriteBox<&mut W> for Hvc1Box { writer.write_u16::(self.depth)?; writer.write_i16::(-1)?; // pre-defined - self.hvcc.write_box(writer)?; + // 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) }