use crate::error::PackagerResult;
use bytes::{BufMut, BytesMut};
use std::time::Duration;
use tracing::debug;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TrackType {
Video,
Audio,
Text,
}
#[derive(Debug, Clone)]
pub struct CmafTrack {
pub track_id: u32,
pub track_type: TrackType,
pub timescale: u32,
pub codec: String,
pub duration: Duration,
}
impl CmafTrack {
#[must_use]
pub fn new(track_id: u32, track_type: TrackType, timescale: u32, codec: String) -> Self {
Self {
track_id,
track_type,
timescale,
codec,
duration: Duration::ZERO,
}
}
#[must_use]
pub fn with_duration(mut self, duration: Duration) -> Self {
self.duration = duration;
self
}
}
pub struct CmafHeader {
track: CmafTrack,
}
impl CmafHeader {
#[must_use]
pub fn new(track: CmafTrack) -> Self {
Self { track }
}
pub fn generate_init_segment(&self) -> PackagerResult<Vec<u8>> {
let mut output = BytesMut::new();
self.write_ftyp(&mut output)?;
self.write_moov(&mut output)?;
Ok(output.to_vec())
}
fn write_ftyp(&self, output: &mut BytesMut) -> PackagerResult<()> {
let ftyp_start = output.len();
output.put_u32(0); output.put_slice(b"ftyp");
output.put_slice(b"cmfc");
output.put_u32(0);
output.put_slice(b"iso6"); output.put_slice(b"cmfc"); output.put_slice(b"dash");
let size = output.len() - ftyp_start;
let size_bytes = (size as u32).to_be_bytes();
output[ftyp_start..ftyp_start + 4].copy_from_slice(&size_bytes);
debug!("Wrote ftyp box: {} bytes", size);
Ok(())
}
fn write_moov(&self, output: &mut BytesMut) -> PackagerResult<()> {
let moov_start = output.len();
output.put_u32(0); output.put_slice(b"moov");
self.write_mvhd(output)?;
self.write_trak(output)?;
self.write_mvex(output)?;
let size = output.len() - moov_start;
let size_bytes = (size as u32).to_be_bytes();
output[moov_start..moov_start + 4].copy_from_slice(&size_bytes);
debug!("Wrote moov box: {} bytes", size);
Ok(())
}
fn write_mvhd(&self, output: &mut BytesMut) -> PackagerResult<()> {
output.put_u32(108); output.put_slice(b"mvhd");
output.put_u32(0);
output.put_u32(0);
output.put_u32(0);
output.put_u32(self.track.timescale);
#[allow(clippy::cast_possible_truncation)]
let duration = (self.track.duration.as_secs_f64() * f64::from(self.track.timescale)) as u32;
output.put_u32(duration);
output.put_u32(0x00010000);
output.put_u16(0x0100);
output.put_u16(0);
output.put_u64(0);
output.put_u32(0x00010000);
output.put_u32(0);
output.put_u32(0);
output.put_u32(0);
output.put_u32(0x00010000);
output.put_u32(0);
output.put_u32(0);
output.put_u32(0);
output.put_u32(0x40000000);
output.put_bytes(0, 24);
output.put_u32(2);
Ok(())
}
fn write_trak(&self, output: &mut BytesMut) -> PackagerResult<()> {
let trak_start = output.len();
output.put_u32(0); output.put_slice(b"trak");
self.write_tkhd(output)?;
self.write_mdia(output)?;
let size = output.len() - trak_start;
let size_bytes = (size as u32).to_be_bytes();
output[trak_start..trak_start + 4].copy_from_slice(&size_bytes);
Ok(())
}
fn write_tkhd(&self, output: &mut BytesMut) -> PackagerResult<()> {
output.put_u32(92); output.put_slice(b"tkhd");
output.put_u32(0x000007);
output.put_u32(0);
output.put_u32(0);
output.put_u32(self.track.track_id);
output.put_u32(0);
#[allow(clippy::cast_possible_truncation)]
let duration = (self.track.duration.as_secs_f64() * f64::from(self.track.timescale)) as u32;
output.put_u32(duration);
output.put_u64(0);
output.put_u16(0);
output.put_u16(0);
let volume = match self.track.track_type {
TrackType::Audio => 0x0100u16,
_ => 0u16,
};
output.put_u16(volume);
output.put_u16(0);
output.put_u32(0x00010000);
output.put_u32(0);
output.put_u32(0);
output.put_u32(0);
output.put_u32(0x00010000);
output.put_u32(0);
output.put_u32(0);
output.put_u32(0);
output.put_u32(0x40000000);
output.put_u32(0);
output.put_u32(0);
Ok(())
}
fn write_mdia(&self, output: &mut BytesMut) -> PackagerResult<()> {
let mdia_start = output.len();
output.put_u32(0); output.put_slice(b"mdia");
self.write_mdhd(output)?;
self.write_hdlr(output)?;
self.write_minf(output)?;
let size = output.len() - mdia_start;
let size_bytes = (size as u32).to_be_bytes();
output[mdia_start..mdia_start + 4].copy_from_slice(&size_bytes);
Ok(())
}
fn write_mdhd(&self, output: &mut BytesMut) -> PackagerResult<()> {
output.put_u32(32); output.put_slice(b"mdhd");
output.put_u32(0);
output.put_u32(0);
output.put_u32(0);
output.put_u32(self.track.timescale);
#[allow(clippy::cast_possible_truncation)]
let duration = (self.track.duration.as_secs_f64() * f64::from(self.track.timescale)) as u32;
output.put_u32(duration);
output.put_u16(0x55C4);
output.put_u16(0);
Ok(())
}
fn write_hdlr(&self, output: &mut BytesMut) -> PackagerResult<()> {
let handler_type = match self.track.track_type {
TrackType::Video => b"vide",
TrackType::Audio => b"soun",
TrackType::Text => b"text",
};
let name = match self.track.track_type {
TrackType::Video => "VideoHandler\0",
TrackType::Audio => "SoundHandler\0",
TrackType::Text => "TextHandler\0",
};
let box_size = 32 + name.len();
output.put_u32(box_size as u32);
output.put_slice(b"hdlr");
output.put_u32(0);
output.put_u32(0);
output.put_slice(handler_type);
output.put_u32(0);
output.put_u32(0);
output.put_u32(0);
output.put_slice(name.as_bytes());
Ok(())
}
fn write_minf(&self, output: &mut BytesMut) -> PackagerResult<()> {
let minf_start = output.len();
output.put_u32(0); output.put_slice(b"minf");
match self.track.track_type {
TrackType::Video => self.write_vmhd(output)?,
TrackType::Audio => self.write_smhd(output)?,
TrackType::Text => self.write_nmhd(output)?,
}
self.write_dinf(output)?;
self.write_stbl(output)?;
let size = output.len() - minf_start;
let size_bytes = (size as u32).to_be_bytes();
output[minf_start..minf_start + 4].copy_from_slice(&size_bytes);
Ok(())
}
fn write_vmhd(&self, output: &mut BytesMut) -> PackagerResult<()> {
output.put_u32(20); output.put_slice(b"vmhd");
output.put_u32(1);
output.put_u16(0);
output.put_u16(0);
output.put_u16(0);
output.put_u16(0);
Ok(())
}
fn write_smhd(&self, output: &mut BytesMut) -> PackagerResult<()> {
output.put_u32(16); output.put_slice(b"smhd");
output.put_u32(0);
output.put_u16(0);
output.put_u16(0);
Ok(())
}
fn write_nmhd(&self, output: &mut BytesMut) -> PackagerResult<()> {
output.put_u32(12); output.put_slice(b"nmhd");
output.put_u32(0);
Ok(())
}
fn write_dinf(&self, output: &mut BytesMut) -> PackagerResult<()> {
output.put_u32(36); output.put_slice(b"dinf");
output.put_u32(28); output.put_slice(b"dref");
output.put_u32(0);
output.put_u32(1);
output.put_u32(12);
output.put_slice(b"url ");
output.put_u32(1);
Ok(())
}
fn write_stbl(&self, output: &mut BytesMut) -> PackagerResult<()> {
let stbl_start = output.len();
output.put_u32(0); output.put_slice(b"stbl");
output.put_u32(16);
output.put_slice(b"stsd");
output.put_u32(0); output.put_u32(0);
output.put_u32(16);
output.put_slice(b"stts");
output.put_u32(0);
output.put_u32(0);
output.put_u32(16);
output.put_slice(b"stsc");
output.put_u32(0);
output.put_u32(0);
output.put_u32(20);
output.put_slice(b"stsz");
output.put_u32(0);
output.put_u32(0);
output.put_u32(0);
output.put_u32(16);
output.put_slice(b"stco");
output.put_u32(0);
output.put_u32(0);
let size = output.len() - stbl_start;
let size_bytes = (size as u32).to_be_bytes();
output[stbl_start..stbl_start + 4].copy_from_slice(&size_bytes);
Ok(())
}
fn write_mvex(&self, output: &mut BytesMut) -> PackagerResult<()> {
output.put_u32(40); output.put_slice(b"mvex");
output.put_u32(32);
output.put_slice(b"trex");
output.put_u32(0);
output.put_u32(self.track.track_id);
output.put_u32(1);
output.put_u32(0);
output.put_u32(0);
output.put_u32(0);
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cmaf_track_creation() {
let track = CmafTrack::new(1, TrackType::Video, 90000, "av01.0.04M.08".to_string());
assert_eq!(track.track_id, 1);
assert_eq!(track.track_type, TrackType::Video);
assert_eq!(track.timescale, 90000);
}
#[test]
fn test_cmaf_header_generation() {
let track = CmafTrack::new(1, TrackType::Video, 90000, "av01.0.04M.08".to_string())
.with_duration(Duration::from_secs(60));
let header = CmafHeader::new(track);
let init_segment = header
.generate_init_segment()
.expect("should succeed in test");
assert!(!init_segment.is_empty());
assert!(init_segment.len() > 8); }
}