From b4b4dbd49f0270786db0f0f8cff01c87155e280c Mon Sep 17 00:00:00 2001 From: Alf Date: Tue, 1 Sep 2020 20:41:34 -0700 Subject: [PATCH] add support for hev1 box --- src/mp4box/hev1.rs | 219 +++++++++++++++++++++++++++++++++++++++++++++ src/mp4box/mod.rs | 3 + src/mp4box/stsd.rs | 8 +- src/track.rs | 39 +++++++- src/types.rs | 12 +++ 5 files changed, 278 insertions(+), 3 deletions(-) create mode 100644 src/mp4box/hev1.rs diff --git a/src/mp4box/hev1.rs b/src/mp4box/hev1.rs new file mode 100644 index 0000000..3fbf6a8 --- /dev/null +++ b/src/mp4box/hev1.rs @@ -0,0 +1,219 @@ +use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; +use std::io::{Read, Seek, Write}; + +use crate::mp4box::*; + +#[derive(Debug, Clone, PartialEq)] +pub struct Hev1Box { + pub data_reference_index: u16, + pub width: u16, + pub height: u16, + pub horizresolution: FixedPointU16, + pub vertresolution: FixedPointU16, + pub frame_count: u16, + pub depth: u16, + pub hvcc: HvcCBox, +} + +impl Default for Hev1Box { + fn default() -> Self { + Hev1Box { + 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 Hev1Box { + pub fn new(config: &HevcConfig) -> Self { + Hev1Box { + 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::Hev1Box + } + + pub fn get_size(&self) -> u64 { + HEADER_SIZE + 8 + 70 + self.hvcc.box_size() + } +} + +impl Mp4Box for Hev1Box { + fn box_type(&self) -> BoxType { + return self.get_type(); + } + + fn box_size(&self) -> u64 { + return self.get_size(); + } +} + +impl ReadBox<&mut R> for Hev1Box { + fn read_box(reader: &mut R, size: u64) -> Result { + let start = box_start(reader)?; + + reader.read_u32::()?; // reserved + reader.read_u16::()?; // reserved + let data_reference_index = reader.read_u16::()?; + + reader.read_u32::()?; // pre-defined, reserved + reader.read_u64::()?; // pre-defined + reader.read_u32::()?; // pre-defined + let width = reader.read_u16::()?; + let height = reader.read_u16::()?; + let horizresolution = FixedPointU16::new_raw(reader.read_u32::()?); + let vertresolution = FixedPointU16::new_raw(reader.read_u32::()?); + reader.read_u32::()?; // reserved + let frame_count = reader.read_u16::()?; + skip_bytes(reader, 32)?; // compressorname + let depth = reader.read_u16::()?; + reader.read_i16::()?; // pre-defined + + let header = BoxHeader::read(reader)?; + let BoxHeader { name, size: s } = header; + if name == BoxType::HvcCBox { + let hvcc = HvcCBox::read_box(reader, s)?; + + skip_bytes_to(reader, start + size)?; + + Ok(Hev1Box { + data_reference_index, + width, + height, + horizresolution, + vertresolution, + frame_count, + depth, + hvcc, + }) + } else { + Err(Error::InvalidData("hvcc not found")) + } + } +} + +impl WriteBox<&mut W> for Hev1Box { + 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) + } +} + +#[derive(Debug, Clone, PartialEq, Default)] +pub struct HvcCBox { + pub configuration_version: u8, +} + +impl HvcCBox { + pub fn new() -> Self { + Self { + configuration_version: 1, + } + } +} + +impl Mp4Box for HvcCBox { + fn box_type(&self) -> BoxType { + BoxType::HvcCBox + } + + fn box_size(&self) -> u64 { + let size = HEADER_SIZE + 7; + size + } +} + +impl ReadBox<&mut R> for HvcCBox { + fn read_box(reader: &mut R, size: u64) -> Result { + let start = box_start(reader)?; + + let configuration_version = reader.read_u8()?; + + skip_bytes_to(reader, start + size)?; + + Ok(HvcCBox { + configuration_version, + }) + } +} + +impl WriteBox<&mut W> for HvcCBox { + 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.configuration_version)?; + Ok(size) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::mp4box::BoxHeader; + use std::io::Cursor; + + #[test] + fn test_hev1() { + let src_box = Hev1Box { + 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, + }, + }; + 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::Hev1Box); + assert_eq!(src_box.box_size(), header.size); + + let dst_box = Hev1Box::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 180a96c..a82aa5f 100644 --- a/src/mp4box/mod.rs +++ b/src/mp4box/mod.rs @@ -5,6 +5,7 @@ use std::io::{Read, Seek, SeekFrom, Write}; use crate::*; pub(crate) mod avc1; +pub(crate) mod hev1; pub(crate) mod co64; pub(crate) mod ctts; pub(crate) mod edts; @@ -94,6 +95,8 @@ boxtype! { SmhdBox => 0x736d6864, Avc1Box => 0x61766331, AvcCBox => 0x61766343, + Hev1Box => 0x68657631, + HvcCBox => 0x68766343, Mp4aBox => 0x6d703461, EsdsBox => 0x65736473 } diff --git a/src/mp4box/stsd.rs b/src/mp4box/stsd.rs index d09da49..2886965 100644 --- a/src/mp4box/stsd.rs +++ b/src/mp4box/stsd.rs @@ -2,13 +2,14 @@ use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use std::io::{Read, Seek, Write}; use crate::mp4box::*; -use crate::mp4box::{avc1::Avc1Box, mp4a::Mp4aBox}; +use crate::mp4box::{avc1::Avc1Box, hev1::Hev1Box, mp4a::Mp4aBox}; #[derive(Debug, Clone, PartialEq, Default)] pub struct StsdBox { pub version: u8, pub flags: u32, pub avc1: Option, + pub hev1: Option, pub mp4a: Option, } @@ -47,6 +48,7 @@ impl ReadBox<&mut R> for StsdBox { reader.read_u32::()?; // XXX entry_count let mut avc1 = None; + let mut hev1 = None; let mut mp4a = None; // Get box header. @@ -57,6 +59,9 @@ impl ReadBox<&mut R> for StsdBox { BoxType::Avc1Box => { avc1 = Some(Avc1Box::read_box(reader, s)?); } + BoxType::Hev1Box => { + hev1 = Some(Hev1Box::read_box(reader, s)?); + } BoxType::Mp4aBox => { mp4a = Some(Mp4aBox::read_box(reader, s)?); } @@ -69,6 +74,7 @@ impl ReadBox<&mut R> for StsdBox { version, flags, avc1, + hev1, mp4a, }) } diff --git a/src/track.rs b/src/track.rs index 08c6550..bee3854 100644 --- a/src/track.rs +++ b/src/track.rs @@ -7,8 +7,17 @@ use std::time::Duration; use crate::mp4box::trak::TrakBox; use crate::mp4box::*; use crate::mp4box::{ - avc1::Avc1Box, ctts::CttsBox, ctts::CttsEntry, mp4a::Mp4aBox, smhd::SmhdBox, stco::StcoBox, - stsc::StscEntry, stss::StssBox, stts::SttsEntry, vmhd::VmhdBox, + avc1::Avc1Box, + hev1::Hev1Box, + ctts::CttsBox, + ctts::CttsEntry, + mp4a::Mp4aBox, + smhd::SmhdBox, + stco::StcoBox, + stsc::StscEntry, + stss::StssBox, + stts::SttsEntry, + vmhd::VmhdBox, }; use crate::*; @@ -24,6 +33,7 @@ impl From for TrackConfig { fn from(media_conf: MediaConfig) -> Self { match media_conf { MediaConfig::AvcConfig(avc_conf) => Self::from(avc_conf), + MediaConfig::HevcConfig(hevc_conf) => Self::from(hevc_conf), MediaConfig::AacConfig(aac_conf) => Self::from(aac_conf), } } @@ -40,6 +50,17 @@ impl From for TrackConfig { } } +impl From for TrackConfig { + fn from(hevc_conf: HevcConfig) -> Self { + Self { + track_type: TrackType::Video, + timescale: 1000, // XXX + language: String::from("und"), // XXX + media_conf: MediaConfig::HevcConfig(hevc_conf), + } + } +} + impl From for TrackConfig { fn from(aac_conf: AacConfig) -> Self { Self { @@ -73,6 +94,8 @@ 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.mp4a.is_some() { Ok(MediaType::AAC) } else { @@ -83,6 +106,8 @@ impl Mp4Track { pub fn box_type(&self) -> Result { if self.trak.mdia.minf.stbl.stsd.avc1.is_some() { 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.mp4a.is_some() { Ok(FourCC::from(BoxType::Mp4aBox)) } else { @@ -460,6 +485,16 @@ impl Mp4TrackWriter { let avc1 = Avc1Box::new(avc_config); 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); + + let vmhd = VmhdBox::default(); + trak.mdia.minf.vmhd = Some(vmhd); + + let hev1 = Hev1Box::new(hevc_config); + trak.mdia.minf.stbl.stsd.hev1 = Some(hev1); + } MediaConfig::AacConfig(ref aac_config) => { let smhd = SmhdBox::default(); trak.mdia.minf.smhd = Some(smhd); diff --git a/src/types.rs b/src/types.rs index 320ada4..94810d0 100644 --- a/src/types.rs +++ b/src/types.rs @@ -229,11 +229,13 @@ impl Into for TrackType { } const MEDIA_TYPE_H264: &str = "h264"; +const MEDIA_TYPE_H265: &str = "h265"; const MEDIA_TYPE_AAC: &str = "aac"; #[derive(Debug, Clone, Copy, PartialEq)] pub enum MediaType { H264, + H265, AAC, } @@ -249,6 +251,7 @@ impl TryFrom<&str> for MediaType { fn try_from(media: &str) -> Result { match media { MEDIA_TYPE_H264 => Ok(MediaType::H264), + MEDIA_TYPE_H265 => Ok(MediaType::H265), MEDIA_TYPE_AAC => Ok(MediaType::AAC), _ => Err(Error::InvalidData("unsupported media type")), } @@ -259,6 +262,7 @@ impl Into<&str> for MediaType { fn into(self) -> &'static str { match self { MediaType::H264 => MEDIA_TYPE_H264, + MediaType::H265 => MEDIA_TYPE_H265, MediaType::AAC => MEDIA_TYPE_AAC, } } @@ -268,6 +272,7 @@ impl Into<&str> for &MediaType { fn into(self) -> &'static str { match self { MediaType::H264 => MEDIA_TYPE_H264, + MediaType::H265 => MEDIA_TYPE_H265, MediaType::AAC => MEDIA_TYPE_AAC, } } @@ -451,6 +456,12 @@ pub struct AvcConfig { pub pic_param_set: Vec, } +#[derive(Debug, PartialEq, Clone, Default)] +pub struct HevcConfig { + pub width: u16, + pub height: u16, +} + #[derive(Debug, PartialEq, Clone)] pub struct AacConfig { pub bitrate: u32, @@ -473,6 +484,7 @@ impl Default for AacConfig { #[derive(Debug, PartialEq, Clone)] pub enum MediaConfig { AvcConfig(AvcConfig), + HevcConfig(HevcConfig), AacConfig(AacConfig), }