From 00b50636b6791c376ce170a6caf2909512e88d0b Mon Sep 17 00:00:00 2001 From: ninthakeey Date: Wed, 27 Jan 2021 11:31:32 +0800 Subject: [PATCH] example/copy supports vp9-in-mp4 and example/dump supports fmp4 (#41) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: mvex box中的mehd box改为可选,支持fmp4的解析 * feat: support to copy mp4 with vp9 codec, but not support to copy fmp4 with vp9 codec * Update types.rs undo unnecessary changes. * Update types.rs undo reduce unnecessary changes. * Update types.rs * Update mp4copy.rs Add vp9 code after h265 * Update stsd.rs Add vp09 after the Hevc * Update types.rs Add after the HevcConfig. * fix: Track.rs add vp9 support * feat: mp4 writer set vp09 box into stsd box --- examples/mp4copy.rs | 5 ++ examples/mp4dump.rs | 4 +- src/mp4box/mod.rs | 6 +- src/mp4box/mvex.rs | 13 ++- src/mp4box/stsd.rs | 13 +++ src/mp4box/vp09.rs | 191 ++++++++++++++++++++++++++++++++++++++++++++ src/mp4box/vpcc.rs | 128 +++++++++++++++++++++++++++++ src/track.rs | 24 ++++++ src/types.rs | 12 +++ 9 files changed, 387 insertions(+), 9 deletions(-) create mode 100644 src/mp4box/vp09.rs create mode 100644 src/mp4box/vpcc.rs diff --git a/examples/mp4copy.rs b/examples/mp4copy.rs index e90ea6a..449e782 100644 --- a/examples/mp4copy.rs +++ b/examples/mp4copy.rs @@ -8,6 +8,7 @@ use mp4::{ AacConfig, AvcConfig, HevcConfig, + Vp9Config, TtxtConfig, MediaConfig, MediaType, @@ -61,6 +62,10 @@ fn copy>(src_filename: &P, dst_filename: &P) -> Result<()> { width: track.width(), height: track.height(), }), + MediaType::VP9 => MediaConfig::Vp9Config(Vp9Config { + width: track.width(), + height: track.height(), + }), MediaType::AAC => MediaConfig::AacConfig(AacConfig { bitrate: track.bitrate(), profile: track.audio_profile()?, diff --git a/examples/mp4dump.rs b/examples/mp4dump.rs index f8ee462..2882e32 100644 --- a/examples/mp4dump.rs +++ b/examples/mp4dump.rs @@ -54,7 +54,9 @@ fn get_boxes(file: File) -> Result> { if let Some(ref mvex) = &mp4.moov.mvex { boxes.push(build_box(mvex)); - boxes.push(build_box(&mvex.mehd)); + if let Some(mehd) = &mvex.mehd { + boxes.push(build_box(mehd)); + } boxes.push(build_box(&mvex.trex)); } diff --git a/src/mp4box/mod.rs b/src/mp4box/mod.rs index de660ab..42b0e3f 100644 --- a/src/mp4box/mod.rs +++ b/src/mp4box/mod.rs @@ -90,6 +90,8 @@ pub(crate) mod traf; pub(crate) mod trun; pub(crate) mod tx3g; pub(crate) mod vmhd; +pub(crate) mod vp09; +pub(crate) mod vpcc; pub use ftyp::FtypBox; pub use moov::MoovBox; @@ -170,7 +172,9 @@ boxtype! { HvcCBox => 0x68766343, Mp4aBox => 0x6d703461, EsdsBox => 0x65736473, - Tx3gBox => 0x74783367 + Tx3gBox => 0x74783367, + VpccBox => 0x76706343, + Vp09Box => 0x76703039 } pub trait Mp4Box: Sized { diff --git a/src/mp4box/mvex.rs b/src/mp4box/mvex.rs index c5eedf2..31dc0b7 100644 --- a/src/mp4box/mvex.rs +++ b/src/mp4box/mvex.rs @@ -6,7 +6,7 @@ use crate::mp4box::{mehd::MehdBox, trex::TrexBox}; #[derive(Debug, Clone, PartialEq, Default, Serialize)] pub struct MvexBox { - pub mehd: MehdBox, + pub mehd: Option, pub trex: TrexBox, } @@ -16,7 +16,7 @@ impl MvexBox { } pub fn get_size(&self) -> u64 { - HEADER_SIZE + self.mehd.box_size() + self.trex.box_size() + HEADER_SIZE + self.mehd.as_ref().map(|x| x.box_size()).unwrap_or(0) + self.trex.box_size() } } @@ -69,9 +69,6 @@ impl ReadBox<&mut R> for MvexBox { current = reader.seek(SeekFrom::Current(0))?; } - if mehd.is_none() { - return Err(Error::BoxNotFound(BoxType::MehdBox)); - } if trex.is_none() { return Err(Error::BoxNotFound(BoxType::TrexBox)); } @@ -79,7 +76,7 @@ impl ReadBox<&mut R> for MvexBox { skip_bytes_to(reader, start + size)?; Ok(MvexBox { - mehd: mehd.unwrap(), + mehd, trex: trex.unwrap(), }) } @@ -90,7 +87,9 @@ impl WriteBox<&mut W> for MvexBox { let size = self.box_size(); BoxHeader::new(self.box_type(), size).write(writer)?; - self.mehd.write_box(writer)?; + if let Some(mehd) = &self.mehd{ + mehd.write_box(writer)?; + } self.trex.write_box(writer)?; Ok(size) diff --git a/src/mp4box/stsd.rs b/src/mp4box/stsd.rs index c5671c6..a05c65f 100644 --- a/src/mp4box/stsd.rs +++ b/src/mp4box/stsd.rs @@ -4,6 +4,7 @@ use serde::{Serialize}; use crate::mp4box::*; use crate::mp4box::{avc1::Avc1Box, hev1::Hev1Box, mp4a::Mp4aBox, tx3g::Tx3gBox}; +use crate::mp4box::vp09::Vp09Box; #[derive(Debug, Clone, PartialEq, Default, Serialize)] pub struct StsdBox { @@ -15,6 +16,9 @@ pub struct StsdBox { #[serde(skip_serializing_if = "Option::is_none")] pub hev1: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub vp09: Option, #[serde(skip_serializing_if = "Option::is_none")] pub mp4a: Option, @@ -34,6 +38,8 @@ impl StsdBox { size += avc1.box_size(); } else if let Some(ref hev1) = self.hev1 { size += hev1.box_size(); + } else if let Some(ref vp09) = self.vp09 { + size += vp09.box_size(); } else if let Some(ref mp4a) = self.mp4a { size += mp4a.box_size(); } else if let Some(ref tx3g) = self.tx3g { @@ -72,6 +78,7 @@ impl ReadBox<&mut R> for StsdBox { let mut avc1 = None; let mut hev1 = None; + let mut vp09 = None; let mut mp4a = None; let mut tx3g = None; @@ -86,6 +93,9 @@ impl ReadBox<&mut R> for StsdBox { BoxType::Hev1Box => { hev1 = Some(Hev1Box::read_box(reader, s)?); } + BoxType::Vp09Box => { + vp09 = Some(Vp09Box::read_box(reader, s)?); + } BoxType::Mp4aBox => { mp4a = Some(Mp4aBox::read_box(reader, s)?); } @@ -102,6 +112,7 @@ impl ReadBox<&mut R> for StsdBox { flags, avc1, hev1, + vp09, mp4a, tx3g, }) @@ -121,6 +132,8 @@ impl WriteBox<&mut W> for StsdBox { avc1.write_box(writer)?; } else if let Some(ref hev1) = self.hev1 { hev1.write_box(writer)?; + } else if let Some(ref vp09) = self.vp09 { + vp09.write_box(writer)?; } else if let Some(ref mp4a) = self.mp4a { mp4a.write_box(writer)?; } else if let Some(ref tx3g) = self.tx3g { diff --git a/src/mp4box/vp09.rs b/src/mp4box/vp09.rs new file mode 100644 index 0000000..cdb49e4 --- /dev/null +++ b/src/mp4box/vp09.rs @@ -0,0 +1,191 @@ +use crate::Mp4Box; +use crate::mp4box::*; +use serde::{Serialize}; +use crate::mp4box::vpcc::VpccBox; + +#[derive(Debug, Clone, PartialEq, Default, Serialize)] +pub struct Vp09Box { + pub version: u8, + pub flags: u32, + pub start_code: u16, + pub data_reference_index: u16, + pub reserved0: [u8; 16], + pub width: u16, + pub height: u16, + pub horizresolution: (u16, u16), + pub vertresolution: (u16, u16), + pub reserved1: [u8; 4], + pub frame_count: u16, + pub compressorname: [u8; 32], + pub depth: u16, + pub end_code: u16, + pub vpcc: VpccBox, +} + +impl Vp09Box { + pub const DEFAULT_START_CODE: u16 = 0; + pub const DEFAULT_END_CODE: u16 = 0xFFFF; + pub const DEFAULT_DATA_REFERENCE_INDEX: u16 = 1; + pub const DEFAULT_HORIZRESOLUTION: (u16, u16) = (0x48, 0x00); + pub const DEFAULT_VERTRESOLUTION: (u16, u16) = (0x48, 0x00); + pub const DEFAULT_FRAME_COUNT: u16 = 1; + pub const DEFAULT_COMPRESSORNAME: [u8; 32] = [0; 32]; + pub const DEFAULT_DEPTH: u16 = 24; + + pub fn new(config: &Vp9Config) -> Self { + Vp09Box { + version: 0, + flags: 0, + start_code: Vp09Box::DEFAULT_START_CODE, + data_reference_index: Vp09Box::DEFAULT_DATA_REFERENCE_INDEX, + reserved0: Default::default(), + width: config.width, + height: config.height, + horizresolution: Vp09Box::DEFAULT_HORIZRESOLUTION, + vertresolution: Vp09Box::DEFAULT_VERTRESOLUTION, + reserved1: Default::default(), + frame_count: Vp09Box::DEFAULT_FRAME_COUNT, + compressorname: Vp09Box::DEFAULT_COMPRESSORNAME, + depth: Vp09Box::DEFAULT_DEPTH, + end_code: Vp09Box::DEFAULT_END_CODE, + vpcc: VpccBox { + version: VpccBox::DEFAULT_VERSION, + flags: 0, + profile: 0, + level: 0x1F, + bit_depth: VpccBox::DEFAULT_BIT_DEPTH, + chroma_subsampling: 0, + video_full_range_flag: false, + color_primaries: 0, + transfer_characteristics: 0, + matrix_coefficients: 0, + codec_initialization_data_size: 0, + }, + } + } +} + +impl Mp4Box for Vp09Box { + fn box_type(&self) -> BoxType { + BoxType::Vp09Box + } + + fn box_size(&self) -> u64 { + 0x6A + } + + fn to_json(&self) -> Result { + Ok(serde_json::to_string(&self).unwrap()) + } + + fn summary(&self) -> Result { + Ok(format!("{:?}", self)) + } +} + +impl ReadBox<&mut R> for Vp09Box { + fn read_box(reader: &mut R, size: u64) -> Result { + let start = box_start(reader)?; + let (version, flags) = read_box_header_ext(reader)?; + + let start_code: u16 = reader.read_u16::()?; + let data_reference_index: u16 = reader.read_u16::()?; + let reserved0: [u8; 16] = { + let mut buf = [0u8; 16]; + reader.read_exact(&mut buf)?; + buf + }; + let width: u16 = reader.read_u16::()?; + let height: u16 = reader.read_u16::()?; + let horizresolution: (u16, u16) = (reader.read_u16::()?, reader.read_u16::()?); + let vertresolution: (u16, u16) = (reader.read_u16::()?, reader.read_u16::()?); + let reserved1: [u8; 4] = { + let mut buf = [0u8; 4]; + reader.read_exact(&mut buf)?; + buf + }; + let frame_count: u16 = reader.read_u16::()?; + let compressorname: [u8; 32] = { + let mut buf = [0u8; 32]; + reader.read_exact(&mut buf)?; + buf + }; + let depth: u16 = reader.read_u16::()?; + let end_code: u16 = reader.read_u16::()?; + + let vpcc = { + let header = BoxHeader::read(reader)?; + VpccBox::read_box(reader, header.size)? + }; + + skip_bytes_to(reader, start + size)?; + + Ok(Self { + version, + flags, + start_code, + data_reference_index, + reserved0, + width, + height, + horizresolution, + vertresolution, + reserved1, + frame_count, + compressorname, + depth, + end_code, + vpcc, + }) + } +} + +impl WriteBox<&mut W> for Vp09Box { + 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)?; + + writer.write_u16::(self.start_code)?; + writer.write_u16::(self.data_reference_index)?; + writer.write_all(&self.reserved0)?; + writer.write_u16::(self.width)?; + writer.write_u16::(self.height)?; + writer.write_u16::(self.horizresolution.0)?; + writer.write_u16::(self.horizresolution.1)?; + writer.write_u16::(self.vertresolution.0)?; + writer.write_u16::(self.vertresolution.1)?; + writer.write_all(&self.reserved1)?; + writer.write_u16::(self.frame_count)?; + writer.write_all(&self.compressorname)?; + writer.write_u16::(self.depth)?; + writer.write_u16::(self.end_code)?; + VpccBox::write_box(&self.vpcc, writer)?; + + Ok(size) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::mp4box::BoxHeader; + use std::io::Cursor; + + #[test] + fn test_vpcc() { + let src_box = Vp09Box::new(&Vp9Config{ width: 1920, height: 1080 }); + 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::Vp09Box); + assert_eq!(src_box.box_size(), header.size); + + let dst_box = Vp09Box::read_box(&mut reader, header.size).unwrap(); + assert_eq!(src_box, dst_box); + } +} diff --git a/src/mp4box/vpcc.rs b/src/mp4box/vpcc.rs new file mode 100644 index 0000000..7330c33 --- /dev/null +++ b/src/mp4box/vpcc.rs @@ -0,0 +1,128 @@ +use crate::Mp4Box; +use crate::mp4box::*; +use serde::{Serialize}; + +#[derive(Debug, Clone, PartialEq, Default, Serialize)] +pub struct VpccBox { + pub version: u8, + pub flags: u32, + pub profile: u8, + pub level: u8, + pub bit_depth: u8, + pub chroma_subsampling: u8, + pub video_full_range_flag: bool, + pub color_primaries: u8, + pub transfer_characteristics: u8, + pub matrix_coefficients: u8, + pub codec_initialization_data_size: u16, +} + +impl VpccBox { + pub const DEFAULT_VERSION: u8 = 1; + pub const DEFAULT_BIT_DEPTH: u8 = 8; +} + +impl Mp4Box for VpccBox { + fn box_type(&self) -> BoxType { + BoxType::VpccBox + } + + fn box_size(&self) -> u64 { + HEADER_SIZE + HEADER_EXT_SIZE + 8 + } + + fn to_json(&self) -> Result { + Ok(serde_json::to_string(&self).unwrap()) + } + + fn summary(&self) -> Result { + Ok(format!("{:?}", self)) + } +} + +impl ReadBox<&mut R> for VpccBox { + fn read_box(reader: &mut R, size: u64) -> Result { + let start = box_start(reader)?; + let (version, flags) = read_box_header_ext(reader)?; + + let profile: u8 = reader.read_u8()?; + let level: u8 = reader.read_u8()?; + let (bit_depth, chroma_subsampling, video_full_range_flag) = { + let b = reader.read_u8()?; + (b >> 4, b << 4 >> 5, b & 0x01 == 1) + }; + let transfer_characteristics: u8 = reader.read_u8()?; + let matrix_coefficients: u8 = reader.read_u8()?; + let codec_initialization_data_size: u16 = reader.read_u16::()?; + + skip_bytes_to(reader, start + size)?; + + Ok(Self { + version, + flags, + profile, + level, + bit_depth, + chroma_subsampling, + video_full_range_flag, + color_primaries: 0, + transfer_characteristics, + matrix_coefficients, + codec_initialization_data_size, + }) + } +} + +impl WriteBox<&mut W> for VpccBox { + 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)?; + + writer.write_u8(self.profile)?; + writer.write_u8(self.level)?; + writer.write_u8((self.bit_depth << 4) | (self.chroma_subsampling << 1) | (self.video_full_range_flag as u8))?; + writer.write_u8(self.color_primaries)?; + writer.write_u8(self.transfer_characteristics)?; + writer.write_u8(self.matrix_coefficients)?; + writer.write_u16::(self.codec_initialization_data_size)?; + + Ok(size) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::mp4box::BoxHeader; + use std::io::Cursor; + + #[test] + fn test_vpcc() { + let src_box = VpccBox { + version: VpccBox::DEFAULT_VERSION, + flags: 0, + profile: 0, + level: 0x1F, + bit_depth: VpccBox::DEFAULT_BIT_DEPTH, + chroma_subsampling: 0, + video_full_range_flag: false, + color_primaries: 0, + transfer_characteristics: 0, + matrix_coefficients: 0, + codec_initialization_data_size: 0, + }; + 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::VpccBox); + assert_eq!(src_box.box_size(), header.size); + + let dst_box = VpccBox::read_box(&mut reader, header.size).unwrap(); + assert_eq!(src_box, dst_box); + } +} \ No newline at end of file diff --git a/src/track.rs b/src/track.rs index 49c4185..c4e0eef 100644 --- a/src/track.rs +++ b/src/track.rs @@ -10,6 +10,7 @@ use crate::mp4box::*; use crate::mp4box::{ avc1::Avc1Box, hev1::Hev1Box, + vp09::Vp09Box, ctts::CttsBox, ctts::CttsEntry, mp4a::Mp4aBox, @@ -38,6 +39,7 @@ impl From for TrackConfig { MediaConfig::HevcConfig(hevc_conf) => Self::from(hevc_conf), MediaConfig::AacConfig(aac_conf) => Self::from(aac_conf), MediaConfig::TtxtConfig(ttxt_conf) => Self::from(ttxt_conf), + MediaConfig::Vp9Config(vp9_config) => Self::from(vp9_config), } } } @@ -86,6 +88,17 @@ impl From for TrackConfig { } } +impl From for TrackConfig { + fn from(vp9_conf: Vp9Config) -> Self { + Self { + track_type: TrackType::Video, + timescale: 1000, // XXX + language: String::from("und"), // XXX + media_conf: MediaConfig::Vp9Config(vp9_conf), + } + } +} + #[derive(Debug)] pub struct Mp4Track { pub trak: TrakBox, @@ -114,6 +127,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.vp09.is_some() { + Ok(MediaType::VP9) } else if self.trak.mdia.minf.stbl.stsd.mp4a.is_some() { Ok(MediaType::AAC) } else if self.trak.mdia.minf.stbl.stsd.tx3g.is_some() { @@ -128,6 +143,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.vp09.is_some() { + Ok(FourCC::from(BoxType::Vp09Box)) } else if self.trak.mdia.minf.stbl.stsd.mp4a.is_some() { Ok(FourCC::from(BoxType::Mp4aBox)) } else if self.trak.mdia.minf.stbl.stsd.tx3g.is_some() { @@ -555,6 +572,12 @@ impl Mp4TrackWriter { let hev1 = Hev1Box::new(hevc_config); trak.mdia.minf.stbl.stsd.hev1 = Some(hev1); } + MediaConfig::Vp9Config(ref config) => { + trak.tkhd.set_width(config.width); + trak.tkhd.set_height(config.height); + + trak.mdia.minf.stbl.stsd.vp09 = Some(Vp09Box::new(config)); + } MediaConfig::AacConfig(ref aac_config) => { let smhd = SmhdBox::default(); trak.mdia.minf.smhd = Some(smhd); @@ -566,6 +589,7 @@ impl Mp4TrackWriter { let tx3g = Tx3gBox::default(); trak.mdia.minf.stbl.stsd.tx3g = Some(tx3g); } + } Ok(Mp4TrackWriter { trak, diff --git a/src/types.rs b/src/types.rs index eef776d..e898a71 100644 --- a/src/types.rs +++ b/src/types.rs @@ -238,6 +238,7 @@ impl Into for TrackType { const MEDIA_TYPE_H264: &str = "h264"; const MEDIA_TYPE_H265: &str = "h265"; +const MEDIA_TYPE_VP9: &str = "vp9"; const MEDIA_TYPE_AAC: &str = "aac"; const MEDIA_TYPE_TTXT: &str = "ttxt"; @@ -245,6 +246,7 @@ const MEDIA_TYPE_TTXT: &str = "ttxt"; pub enum MediaType { H264, H265, + VP9, AAC, TTXT, } @@ -262,6 +264,7 @@ impl TryFrom<&str> for MediaType { match media { MEDIA_TYPE_H264 => Ok(MediaType::H264), MEDIA_TYPE_H265 => Ok(MediaType::H265), + MEDIA_TYPE_VP9 => Ok(MediaType::VP9), MEDIA_TYPE_AAC => Ok(MediaType::AAC), MEDIA_TYPE_TTXT => Ok(MediaType::TTXT), _ => Err(Error::InvalidData("unsupported media type")), @@ -274,6 +277,7 @@ impl Into<&str> for MediaType { match self { MediaType::H264 => MEDIA_TYPE_H264, MediaType::H265 => MEDIA_TYPE_H265, + MediaType::VP9 => MEDIA_TYPE_VP9, MediaType::AAC => MEDIA_TYPE_AAC, MediaType::TTXT => MEDIA_TYPE_TTXT, } @@ -285,6 +289,7 @@ impl Into<&str> for &MediaType { match self { MediaType::H264 => MEDIA_TYPE_H264, MediaType::H265 => MEDIA_TYPE_H265, + MediaType::VP9 => MEDIA_TYPE_VP9, MediaType::AAC => MEDIA_TYPE_AAC, MediaType::TTXT => MEDIA_TYPE_TTXT, } @@ -475,6 +480,12 @@ pub struct HevcConfig { pub height: u16, } +#[derive(Debug, PartialEq, Clone, Default)] +pub struct Vp9Config { + pub width: u16, + pub height: u16, +} + #[derive(Debug, PartialEq, Clone)] pub struct AacConfig { pub bitrate: u32, @@ -501,6 +512,7 @@ pub struct TtxtConfig {} pub enum MediaConfig { AvcConfig(AvcConfig), HevcConfig(HevcConfig), + Vp9Config(Vp9Config), AacConfig(AacConfig), TtxtConfig(TtxtConfig), }