pub mod build;
pub mod demux;
pub mod mux;
use std::fmt;
#[derive(Debug, Clone)]
pub enum Mp4Error {
UnexpectedEof,
InvalidBox(String),
NoVideoTrack,
UnsupportedCodec,
TruncatedFile,
FragmentedMp4,
}
impl fmt::Display for Mp4Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::UnexpectedEof => write!(f, "unexpected end of MP4 data"),
Self::InvalidBox(s) => write!(f, "invalid MP4 box: {s}"),
Self::NoVideoTrack => write!(f, "no video track in MP4 file"),
Self::UnsupportedCodec => write!(f, "video track codec not supported (need HEVC or H.264)"),
Self::TruncatedFile => write!(f, "MP4 file appears truncated (incomplete recording?)"),
Self::FragmentedMp4 => write!(f, "fragmented MP4 (fMP4/DASH) is not supported"),
}
}
}
impl std::error::Error for Mp4Error {}
pub fn is_mp4(data: &[u8]) -> bool {
data.len() >= 8 && &data[4..8] == b"ftyp"
}
#[derive(Debug, Clone)]
pub struct Mp4File {
pub ftyp: Vec<u8>,
pub tracks: Vec<Track>,
pub video_track_idx: Option<usize>,
}
#[derive(Debug, Clone)]
pub struct Track {
pub track_id: u32,
pub handler_type: [u8; 4],
pub codec: [u8; 4],
pub width: u32,
pub height: u32,
pub timescale: u32,
pub duration: u64,
pub samples: Vec<Sample>,
pub hvcc_data: Option<HvccData>,
pub avcc_data: Option<AvccData>,
pub stsd_raw: Vec<u8>,
pub trak_raw: Vec<u8>,
}
#[derive(Debug, Clone)]
pub struct Sample {
pub offset: u64,
pub size: u32,
pub is_sync: bool,
pub data: Vec<u8>,
}
#[derive(Debug, Clone)]
pub struct HvccData {
pub configuration_version: u8,
pub length_size_minus1: u8,
pub vps_nalus: Vec<Vec<u8>>,
pub sps_nalus: Vec<Vec<u8>>,
pub pps_nalus: Vec<Vec<u8>>,
}
#[derive(Debug, Clone)]
pub struct AvccData {
pub configuration_version: u8,
pub profile: u8,
pub profile_compat: u8,
pub level: u8,
pub length_size_minus1: u8,
pub sps_nalus: Vec<Vec<u8>>,
pub pps_nalus: Vec<Vec<u8>>,
}
impl AvccData {
pub fn from_annexb(bytes: &[u8]) -> Option<Self> {
let mut sps: Option<Vec<u8>> = None;
let mut pps: Option<Vec<u8>> = None;
for nal in iter_annexb_nals(bytes) {
if nal.is_empty() {
continue;
}
let nal_type = nal[0] & 0x1F;
if nal_type == 7 && sps.is_none() {
sps = Some(nal.to_vec());
} else if nal_type == 8 && pps.is_none() {
pps = Some(nal.to_vec());
}
if sps.is_some() && pps.is_some() {
break;
}
}
let sps = sps?;
let pps = pps?;
if sps.len() < 4 {
return None;
}
Some(AvccData {
configuration_version: 1,
profile: sps[1],
profile_compat: sps[2],
level: sps[3],
length_size_minus1: 3,
sps_nalus: vec![sps],
pps_nalus: vec![pps],
})
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut out = Vec::with_capacity(32);
out.push(self.configuration_version);
out.push(self.profile);
out.push(self.profile_compat);
out.push(self.level);
out.push(0xFC | (self.length_size_minus1 & 0x03));
out.push(0xE0 | (self.sps_nalus.len() as u8 & 0x1F));
for sps in &self.sps_nalus {
out.push((sps.len() >> 8) as u8);
out.push((sps.len() & 0xFF) as u8);
out.extend_from_slice(sps);
}
out.push(self.pps_nalus.len() as u8);
for pps in &self.pps_nalus {
out.push((pps.len() >> 8) as u8);
out.push((pps.len() & 0xFF) as u8);
out.extend_from_slice(pps);
}
out
}
}
fn iter_annexb_nals(bytes: &[u8]) -> impl Iterator<Item = &[u8]> {
let mut starts: Vec<usize> = Vec::new();
let mut i = 0;
while i + 3 <= bytes.len() {
if bytes[i] == 0 && bytes[i + 1] == 0 {
if i + 4 <= bytes.len() && bytes[i + 2] == 0 && bytes[i + 3] == 1 {
starts.push(i + 4);
i += 4;
continue;
} else if bytes[i + 2] == 1 {
starts.push(i + 3);
i += 3;
continue;
}
}
i += 1;
}
let mut pairs = Vec::new();
for (k, &s) in starts.iter().enumerate() {
let end = if k + 1 < starts.len() {
let next = starts[k + 1];
next - (if next >= 4 && bytes[next - 4..next] == [0, 0, 0, 1] { 4 } else { 3 })
} else {
bytes.len()
};
pairs.push((s, end));
}
pairs.into_iter().map(move |(s, e)| &bytes[s..e])
}
impl Track {
pub fn is_h264(&self) -> bool {
self.codec == *b"avc1" || self.codec == *b"avc3"
}
pub fn is_hevc(&self) -> bool {
self.codec == *b"hev1" || self.codec == *b"hvc1"
}
}
pub(crate) fn fourcc(data: &[u8], offset: usize) -> Result<[u8; 4], Mp4Error> {
if offset + 4 > data.len() {
return Err(Mp4Error::UnexpectedEof);
}
Ok([data[offset], data[offset + 1], data[offset + 2], data[offset + 3]])
}
pub(crate) fn read_u16(data: &[u8], offset: usize) -> Result<u16, Mp4Error> {
if offset + 2 > data.len() {
return Err(Mp4Error::UnexpectedEof);
}
Ok(u16::from_be_bytes([data[offset], data[offset + 1]]))
}
pub(crate) fn read_u32(data: &[u8], offset: usize) -> Result<u32, Mp4Error> {
if offset + 4 > data.len() {
return Err(Mp4Error::UnexpectedEof);
}
Ok(u32::from_be_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
]))
}
pub(crate) fn read_u64(data: &[u8], offset: usize) -> Result<u64, Mp4Error> {
if offset + 8 > data.len() {
return Err(Mp4Error::UnexpectedEof);
}
Ok(u64::from_be_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
data[offset + 4],
data[offset + 5],
data[offset + 6],
data[offset + 7],
]))
}
pub(crate) fn write_u32(buf: &mut Vec<u8>, val: u32) {
buf.extend_from_slice(&val.to_be_bytes());
}
pub(crate) fn write_u64(buf: &mut Vec<u8>, val: u64) {
buf.extend_from_slice(&val.to_be_bytes());
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct BoxHeader {
pub box_type: [u8; 4],
pub size: u64,
pub header_len: u8,
}
pub(crate) fn parse_box_header(data: &[u8], offset: usize) -> Result<BoxHeader, Mp4Error> {
if offset + 8 > data.len() {
return Err(Mp4Error::UnexpectedEof);
}
let size32 = read_u32(data, offset)?;
let box_type = fourcc(data, offset + 4)?;
if size32 == 1 {
if offset + 16 > data.len() {
return Err(Mp4Error::UnexpectedEof);
}
let size64 = read_u64(data, offset + 8)?;
Ok(BoxHeader {
box_type,
size: size64,
header_len: 16,
})
} else if size32 == 0 {
let size = (data.len() - offset) as u64;
Ok(BoxHeader {
box_type,
size,
header_len: 8,
})
} else {
Ok(BoxHeader {
box_type,
size: size32 as u64,
header_len: 8,
})
}
}
pub(crate) fn iterate_boxes<F>(
data: &[u8],
start: usize,
end: usize,
mut visitor: F,
) -> Result<(), Mp4Error>
where
F: FnMut(&BoxHeader, usize, &[u8]) -> Result<(), Mp4Error>,
{
let mut pos = start;
while pos < end {
if pos + 8 > end {
break; }
let header = parse_box_header(data, pos)?;
if header.size < 8 {
return Err(Mp4Error::InvalidBox(format!(
"box {:?} has invalid size {}",
std::str::from_utf8(&header.box_type).unwrap_or("????"),
header.size
)));
}
let box_end = pos + header.size as usize;
if box_end > end {
return Err(Mp4Error::UnexpectedEof);
}
let content_start = pos + header.header_len as usize;
visitor(&header, content_start, &data[pos..box_end])?;
pos = box_end;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn avcc_from_annexb_extracts_profile_level() {
let sps = vec![0x67, 0x42, 0x00, 0x1E, 0xAC, 0xD9];
let pps = vec![0x68, 0xEB, 0xE3, 0xCB];
let mut bytes = vec![0, 0, 0, 1];
bytes.extend(&sps);
bytes.extend([0, 0, 0, 1]);
bytes.extend(&pps);
let avcc = AvccData::from_annexb(&bytes).expect("should find SPS + PPS");
assert_eq!(avcc.profile, 0x42);
assert_eq!(avcc.profile_compat, 0x00);
assert_eq!(avcc.level, 0x1E);
assert_eq!(avcc.length_size_minus1, 3);
assert_eq!(avcc.sps_nalus.len(), 1);
assert_eq!(avcc.pps_nalus.len(), 1);
assert_eq!(avcc.sps_nalus[0], sps);
assert_eq!(avcc.pps_nalus[0], pps);
}
#[test]
fn avcc_from_annexb_missing_sps_or_pps_returns_none() {
let bytes = vec![0, 0, 0, 1, 0x67, 0x42, 0x00, 0x1E];
assert!(AvccData::from_annexb(&bytes).is_none());
}
#[test]
fn avcc_to_bytes_roundtrip() {
let avcc = AvccData {
configuration_version: 1,
profile: 0x42,
profile_compat: 0x40,
level: 0x1E,
length_size_minus1: 3,
sps_nalus: vec![vec![0x67, 0x42, 0x40, 0x1E]],
pps_nalus: vec![vec![0x68, 0xCE, 0x38, 0x80]],
};
let bytes = avcc.to_bytes();
assert_eq!(bytes[0], 1); assert_eq!(bytes[1], 0x42); assert_eq!(bytes[2], 0x40); assert_eq!(bytes[3], 0x1E); assert_eq!(bytes[4], 0xFF); assert_eq!(bytes[5], 0xE1); assert_eq!(bytes[6], 0); assert_eq!(bytes[7], 4); assert_eq!(&bytes[8..12], &[0x67, 0x42, 0x40, 0x1E]);
assert_eq!(bytes[12], 1); assert_eq!(bytes[13], 0); assert_eq!(bytes[14], 4); assert_eq!(&bytes[15..19], &[0x68, 0xCE, 0x38, 0x80]);
}
#[test]
fn test_parse_box_header_32bit() {
let data = [
0x00, 0x00, 0x00, 0x14, b'f', b't', b'y', b'p', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
];
let h = parse_box_header(&data, 0).unwrap();
assert_eq!(h.box_type, *b"ftyp");
assert_eq!(h.size, 20);
assert_eq!(h.header_len, 8);
}
#[test]
fn test_parse_box_header_64bit() {
let data = [
0x00, 0x00, 0x00, 0x01, b'm', b'd', b'a', b't', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, ];
let h = parse_box_header(&data, 0).unwrap();
assert_eq!(h.box_type, *b"mdat");
assert_eq!(h.size, 256);
assert_eq!(h.header_len, 16);
}
#[test]
fn test_parse_box_header_to_eof() {
let data = [
0x00, 0x00, 0x00, 0x00, b'm', b'd', b'a', b't', 0xAA, 0xBB, 0xCC, 0xDD, ];
let h = parse_box_header(&data, 0).unwrap();
assert_eq!(h.box_type, *b"mdat");
assert_eq!(h.size, 12); assert_eq!(h.header_len, 8);
}
#[test]
fn test_iterate_boxes() {
let mut data = Vec::new();
data.extend_from_slice(&[0x00, 0x00, 0x00, 0x10]);
data.extend_from_slice(b"ftyp");
data.extend_from_slice(&[0x69, 0x73, 0x6F, 0x6D]); data.extend_from_slice(&[0x00, 0x00, 0x02, 0x00]); data.extend_from_slice(&[0x00, 0x00, 0x00, 0x0C]);
data.extend_from_slice(b"free");
data.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]);
let mut types = Vec::new();
iterate_boxes(&data, 0, data.len(), |header, _content_start, _box_data| {
types.push(header.box_type);
Ok(())
})
.unwrap();
assert_eq!(types, vec![*b"ftyp", *b"free"]);
}
#[test]
fn test_read_helpers() {
let data = [0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03];
assert_eq!(read_u16(&data, 0).unwrap(), 1);
assert_eq!(read_u32(&data, 2).unwrap(), 2);
}
}