mirror of
https://github.com/alfg/mp4-rust.git
synced 2025-02-01 22:22:20 +00:00
add support for hev1 box
This commit is contained in:
parent
65b3408625
commit
b4b4dbd49f
5 changed files with 278 additions and 3 deletions
219
src/mp4box/hev1.rs
Normal file
219
src/mp4box/hev1.rs
Normal file
|
@ -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<R: Read + Seek> ReadBox<&mut R> for Hev1Box {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = box_start(reader)?;
|
||||
|
||||
reader.read_u32::<BigEndian>()?; // reserved
|
||||
reader.read_u16::<BigEndian>()?; // reserved
|
||||
let data_reference_index = reader.read_u16::<BigEndian>()?;
|
||||
|
||||
reader.read_u32::<BigEndian>()?; // pre-defined, reserved
|
||||
reader.read_u64::<BigEndian>()?; // pre-defined
|
||||
reader.read_u32::<BigEndian>()?; // pre-defined
|
||||
let width = reader.read_u16::<BigEndian>()?;
|
||||
let height = reader.read_u16::<BigEndian>()?;
|
||||
let horizresolution = FixedPointU16::new_raw(reader.read_u32::<BigEndian>()?);
|
||||
let vertresolution = FixedPointU16::new_raw(reader.read_u32::<BigEndian>()?);
|
||||
reader.read_u32::<BigEndian>()?; // reserved
|
||||
let frame_count = reader.read_u16::<BigEndian>()?;
|
||||
skip_bytes(reader, 32)?; // compressorname
|
||||
let depth = reader.read_u16::<BigEndian>()?;
|
||||
reader.read_i16::<BigEndian>()?; // 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<W: Write> WriteBox<&mut W> for Hev1Box {
|
||||
fn write_box(&self, writer: &mut W) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write(writer)?;
|
||||
|
||||
writer.write_u32::<BigEndian>(0)?; // reserved
|
||||
writer.write_u16::<BigEndian>(0)?; // reserved
|
||||
writer.write_u16::<BigEndian>(self.data_reference_index)?;
|
||||
|
||||
writer.write_u32::<BigEndian>(0)?; // pre-defined, reserved
|
||||
writer.write_u64::<BigEndian>(0)?; // pre-defined
|
||||
writer.write_u32::<BigEndian>(0)?; // pre-defined
|
||||
writer.write_u16::<BigEndian>(self.width)?;
|
||||
writer.write_u16::<BigEndian>(self.height)?;
|
||||
writer.write_u32::<BigEndian>(self.horizresolution.raw_value())?;
|
||||
writer.write_u32::<BigEndian>(self.vertresolution.raw_value())?;
|
||||
writer.write_u32::<BigEndian>(0)?; // reserved
|
||||
writer.write_u16::<BigEndian>(self.frame_count)?;
|
||||
// skip compressorname
|
||||
write_zeros(writer, 32)?;
|
||||
writer.write_u16::<BigEndian>(self.depth)?;
|
||||
writer.write_i16::<BigEndian>(-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<R: Read + Seek> ReadBox<&mut R> for HvcCBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = box_start(reader)?;
|
||||
|
||||
let configuration_version = reader.read_u8()?;
|
||||
|
||||
skip_bytes_to(reader, start + size)?;
|
||||
|
||||
Ok(HvcCBox {
|
||||
configuration_version,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut W> for HvcCBox {
|
||||
fn write_box(&self, writer: &mut W) -> Result<u64> {
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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<Avc1Box>,
|
||||
pub hev1: Option<Hev1Box>,
|
||||
pub mp4a: Option<Mp4aBox>,
|
||||
}
|
||||
|
||||
|
@ -47,6 +48,7 @@ impl<R: Read + Seek> ReadBox<&mut R> for StsdBox {
|
|||
reader.read_u32::<BigEndian>()?; // XXX entry_count
|
||||
|
||||
let mut avc1 = None;
|
||||
let mut hev1 = None;
|
||||
let mut mp4a = None;
|
||||
|
||||
// Get box header.
|
||||
|
@ -57,6 +59,9 @@ impl<R: Read + Seek> 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<R: Read + Seek> ReadBox<&mut R> for StsdBox {
|
|||
version,
|
||||
flags,
|
||||
avc1,
|
||||
hev1,
|
||||
mp4a,
|
||||
})
|
||||
}
|
||||
|
|
39
src/track.rs
39
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<MediaConfig> 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<AvcConfig> for TrackConfig {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<HevcConfig> 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<AacConfig> for TrackConfig {
|
||||
fn from(aac_conf: AacConfig) -> Self {
|
||||
Self {
|
||||
|
@ -73,6 +94,8 @@ impl Mp4Track {
|
|||
pub fn media_type(&self) -> Result<MediaType> {
|
||||
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<FourCC> {
|
||||
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);
|
||||
|
|
12
src/types.rs
12
src/types.rs
|
@ -229,11 +229,13 @@ impl Into<FourCC> 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<MediaType> {
|
||||
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<u8>,
|
||||
}
|
||||
|
||||
#[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),
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue