1
0
Fork 0
mirror of https://github.com/alfg/mp4-rust.git synced 2024-06-02 13:39:54 +00:00
mp4-rust/src/mp4box/hvc1.rs
james.baker@helsing.ai 773e9656fc
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.
2023-10-22 16:15:31 +02:00

202 lines
6 KiB
Rust

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<String> {
Ok(serde_json::to_string(&self).unwrap())
}
fn summary(&self) -> Result<String> {
let s = format!(
"data_reference_index={} width={} height={} frame_count={}",
self.data_reference_index, self.width, self.height, self.frame_count
);
Ok(s)
}
}
impl<R: Read + Seek> ReadBox<&mut R> for Hvc1Box {
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 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<W: Write> WriteBox<&mut W> for Hvc1Box {
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)
}
}
#[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);
}
}