diff --git a/examples/mp4dump.rs b/examples/mp4dump.rs index 6a97d9a..3e56718 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 11690f0..4f72a76 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 3070fb8..ae37d84 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 0000000..418ce66 --- /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 4bbdd41..147a901 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 af947c6..eb00cad 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; @@ -98,6 +102,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 +124,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 7eada83..f080ca6 100644 --- a/src/track.rs +++ b/src/track.rs @@ -121,7 +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() { + } 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) @@ -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], + )) } }