#[derive(Debug, Clone)]
pub struct FragmentConfig {
pub width: u32,
pub height: u32,
pub timescale: u32,
pub fragment_duration_ms: u32,
pub sps: Vec<u8>,
pub pps: Vec<u8>,
}
impl Default for FragmentConfig {
fn default() -> Self {
Self {
width: 1920,
height: 1080,
timescale: 90000,
fragment_duration_ms: 2000,
sps: vec![0x67, 0x42, 0x00, 0x1e, 0xda, 0x02, 0x80, 0x2d, 0x8b, 0x11],
pps: vec![0x68, 0xce, 0x38, 0x80],
}
}
}
#[derive(Debug, Clone)]
struct FragmentSample {
pts: u64,
dts: u64,
data: Vec<u8>,
is_sync: bool,
}
pub struct FragmentedMuxer {
config: FragmentConfig,
samples: Vec<FragmentSample>,
sequence_number: u32,
base_media_decode_time: u64,
init_segment: Option<Vec<u8>>,
}
impl FragmentedMuxer {
pub fn new(config: FragmentConfig) -> Self {
Self {
config,
samples: Vec::new(),
sequence_number: 1,
base_media_decode_time: 0,
init_segment: None,
}
}
pub fn init_segment(&mut self) -> Vec<u8> {
if let Some(ref init) = self.init_segment {
return init.clone();
}
let mut buf = Vec::new();
let ftyp = build_ftyp_fmp4();
buf.extend_from_slice(&ftyp);
let moov = build_moov_fmp4(&self.config);
buf.extend_from_slice(&moov);
self.init_segment = Some(buf.clone());
buf
}
pub fn write_video(&mut self, pts: u64, dts: u64, data: &[u8], is_sync: bool) {
self.samples.push(FragmentSample {
pts,
dts,
data: data.to_vec(),
is_sync,
});
}
pub fn flush_segment(&mut self) -> Option<Vec<u8>> {
if self.samples.is_empty() {
return None;
}
let samples = std::mem::take(&mut self.samples);
let segment = build_media_segment(
&samples,
self.sequence_number,
self.base_media_decode_time,
self.config.timescale,
);
self.sequence_number += 1;
if let Some(last) = samples.last() {
if samples.len() >= 2 {
let avg_duration = (last.dts - samples[0].dts) / (samples.len() as u64 - 1);
self.base_media_decode_time = last.dts + avg_duration;
} else {
self.base_media_decode_time = last.dts + 3000; }
}
Some(segment)
}
pub fn ready_to_flush(&self) -> bool {
if self.samples.is_empty() {
return false;
}
if self.samples.len() < 2 {
return false;
}
let first_dts = self.samples[0].dts;
let last_dts = self.samples.last().unwrap().dts;
let duration_ticks = last_dts - first_dts;
let duration_ms = duration_ticks * 1000 / self.config.timescale as u64;
duration_ms >= self.config.fragment_duration_ms as u64
}
pub fn current_fragment_duration_ms(&self) -> u64 {
if self.samples.len() < 2 {
return 0;
}
let first_dts = self.samples[0].dts;
let last_dts = self.samples.last().unwrap().dts;
let duration_ticks = last_dts - first_dts;
duration_ticks * 1000 / self.config.timescale as u64
}
}
fn build_box(typ: &[u8; 4], payload: &[u8]) -> Vec<u8> {
let size = (8 + payload.len()) as u32;
let mut buf = Vec::with_capacity(size as usize);
buf.extend_from_slice(&size.to_be_bytes());
buf.extend_from_slice(typ);
buf.extend_from_slice(payload);
buf
}
fn build_ftyp_fmp4() -> Vec<u8> {
let mut payload = Vec::new();
payload.extend_from_slice(b"iso5"); payload.extend_from_slice(&0u32.to_be_bytes()); payload.extend_from_slice(b"iso5"); payload.extend_from_slice(b"iso6");
payload.extend_from_slice(b"mp41");
build_box(b"ftyp", &payload)
}
fn build_moov_fmp4(config: &FragmentConfig) -> Vec<u8> {
let mut payload = Vec::new();
let mvhd = build_mvhd_fmp4(config.timescale);
payload.extend_from_slice(&mvhd);
let mvex = build_mvex();
payload.extend_from_slice(&mvex);
let trak = build_trak_fmp4(config);
payload.extend_from_slice(&trak);
build_box(b"moov", &payload)
}
fn build_mvhd_fmp4(timescale: u32) -> Vec<u8> {
let mut payload = Vec::new();
payload.extend_from_slice(&0u32.to_be_bytes()); payload.extend_from_slice(&0u32.to_be_bytes()); payload.extend_from_slice(&0u32.to_be_bytes()); payload.extend_from_slice(×cale.to_be_bytes()); payload.extend_from_slice(&0u32.to_be_bytes()); payload.extend_from_slice(&0x0001_0000_u32.to_be_bytes()); payload.extend_from_slice(&0x0100_u16.to_be_bytes()); payload.extend_from_slice(&[0u8; 10]); payload.extend_from_slice(&0x0001_0000_u32.to_be_bytes());
payload.extend_from_slice(&[0u8; 12]);
payload.extend_from_slice(&0x0001_0000_u32.to_be_bytes());
payload.extend_from_slice(&[0u8; 12]);
payload.extend_from_slice(&0x4000_0000_u32.to_be_bytes());
payload.extend_from_slice(&[0u8; 24]); payload.extend_from_slice(&2u32.to_be_bytes()); build_box(b"mvhd", &payload)
}
fn build_mvex() -> Vec<u8> {
let mut trex_payload = Vec::new();
trex_payload.extend_from_slice(&0u32.to_be_bytes()); trex_payload.extend_from_slice(&1u32.to_be_bytes()); trex_payload.extend_from_slice(&1u32.to_be_bytes()); trex_payload.extend_from_slice(&0u32.to_be_bytes()); trex_payload.extend_from_slice(&0u32.to_be_bytes()); trex_payload.extend_from_slice(&0u32.to_be_bytes()); let trex = build_box(b"trex", &trex_payload);
build_box(b"mvex", &trex)
}
fn build_trak_fmp4(config: &FragmentConfig) -> Vec<u8> {
let mut payload = Vec::new();
let tkhd = build_tkhd_fmp4(config);
payload.extend_from_slice(&tkhd);
let mdia = build_mdia_fmp4(config);
payload.extend_from_slice(&mdia);
build_box(b"trak", &payload)
}
fn build_tkhd_fmp4(config: &FragmentConfig) -> Vec<u8> {
let mut payload = Vec::new();
payload.extend_from_slice(&0x0000_0003_u32.to_be_bytes()); payload.extend_from_slice(&0u32.to_be_bytes()); payload.extend_from_slice(&0u32.to_be_bytes()); payload.extend_from_slice(&1u32.to_be_bytes()); payload.extend_from_slice(&0u32.to_be_bytes()); payload.extend_from_slice(&0u32.to_be_bytes()); payload.extend_from_slice(&[0u8; 8]); payload.extend_from_slice(&0u16.to_be_bytes()); payload.extend_from_slice(&0u16.to_be_bytes()); payload.extend_from_slice(&0u16.to_be_bytes()); payload.extend_from_slice(&0u16.to_be_bytes()); payload.extend_from_slice(&0x0001_0000_u32.to_be_bytes());
payload.extend_from_slice(&[0u8; 12]);
payload.extend_from_slice(&0x0001_0000_u32.to_be_bytes());
payload.extend_from_slice(&[0u8; 12]);
payload.extend_from_slice(&0x4000_0000_u32.to_be_bytes());
payload.extend_from_slice(&((config.width) << 16).to_be_bytes());
payload.extend_from_slice(&((config.height) << 16).to_be_bytes());
build_box(b"tkhd", &payload)
}
fn build_mdia_fmp4(config: &FragmentConfig) -> Vec<u8> {
let mut payload = Vec::new();
let mdhd = build_mdhd_fmp4(config.timescale, None);
payload.extend_from_slice(&mdhd);
let hdlr = build_hdlr_video();
payload.extend_from_slice(&hdlr);
let minf = build_minf_fmp4(config);
payload.extend_from_slice(&minf);
build_box(b"mdia", &payload)
}
fn encode_language_code(language: &str) -> [u8; 2] {
let chars: Vec<char> = language.chars().take(3).collect();
let c1 = chars.first().copied().unwrap_or('u') as u16;
let c2 = chars.get(1).copied().unwrap_or('n') as u16;
let c3 = chars.get(2).copied().unwrap_or('d') as u16;
let packed = ((c1.saturating_sub(0x60) & 0x1F) << 10) |
((c2.saturating_sub(0x60) & 0x1F) << 5) |
(c3.saturating_sub(0x60) & 0x1F);
packed.to_be_bytes()
}
fn build_mdhd_fmp4(timescale: u32, language: Option<&str>) -> Vec<u8> {
let mut payload = Vec::new();
payload.extend_from_slice(&0u32.to_be_bytes()); payload.extend_from_slice(&0u32.to_be_bytes()); payload.extend_from_slice(&0u32.to_be_bytes()); payload.extend_from_slice(×cale.to_be_bytes()); payload.extend_from_slice(&0u32.to_be_bytes()); payload.extend_from_slice(&encode_language_code(language.unwrap_or("und"))); payload.extend_from_slice(&0u16.to_be_bytes()); build_box(b"mdhd", &payload)
}
fn build_hdlr_video() -> Vec<u8> {
let mut payload = Vec::new();
payload.extend_from_slice(&0u32.to_be_bytes()); payload.extend_from_slice(&0u32.to_be_bytes()); payload.extend_from_slice(b"vide"); payload.extend_from_slice(&[0u8; 12]); payload.extend_from_slice(b"VideoHandler\0"); build_box(b"hdlr", &payload)
}
fn build_minf_fmp4(config: &FragmentConfig) -> Vec<u8> {
let mut payload = Vec::new();
let vmhd = build_vmhd();
payload.extend_from_slice(&vmhd);
let dinf = build_dinf();
payload.extend_from_slice(&dinf);
let stbl = build_stbl_fmp4(config);
payload.extend_from_slice(&stbl);
build_box(b"minf", &payload)
}
fn build_vmhd() -> Vec<u8> {
let mut payload = Vec::new();
payload.extend_from_slice(&0x0000_0001_u32.to_be_bytes()); payload.extend_from_slice(&[0u8; 8]); build_box(b"vmhd", &payload)
}
fn build_dinf() -> Vec<u8> {
let mut dref_payload = Vec::new();
dref_payload.extend_from_slice(&0u32.to_be_bytes()); dref_payload.extend_from_slice(&1u32.to_be_bytes()); let url_payload = [0x00, 0x00, 0x00, 0x01]; let url_box = build_box(b"url ", &url_payload);
dref_payload.extend_from_slice(&url_box);
let dref = build_box(b"dref", &dref_payload);
build_box(b"dinf", &dref)
}
fn build_stbl_fmp4(config: &FragmentConfig) -> Vec<u8> {
let mut payload = Vec::new();
let stsd = build_stsd_fmp4(config);
payload.extend_from_slice(&stsd);
let stts = build_empty_stts();
payload.extend_from_slice(&stts);
let stsc = build_empty_stsc();
payload.extend_from_slice(&stsc);
let stsz = build_empty_stsz();
payload.extend_from_slice(&stsz);
let stco = build_empty_stco();
payload.extend_from_slice(&stco);
build_box(b"stbl", &payload)
}
fn build_stsd_fmp4(config: &FragmentConfig) -> Vec<u8> {
let avc1 = build_avc1_fmp4(config);
let mut payload = Vec::new();
payload.extend_from_slice(&0u32.to_be_bytes()); payload.extend_from_slice(&1u32.to_be_bytes()); payload.extend_from_slice(&avc1);
build_box(b"stsd", &payload)
}
fn build_avc1_fmp4(config: &FragmentConfig) -> Vec<u8> {
let mut payload = Vec::new();
payload.extend_from_slice(&[0u8; 6]); payload.extend_from_slice(&1u16.to_be_bytes()); payload.extend_from_slice(&0u16.to_be_bytes()); payload.extend_from_slice(&0u16.to_be_bytes()); payload.extend_from_slice(&[0u8; 12]); payload.extend_from_slice(&(config.width as u16).to_be_bytes());
payload.extend_from_slice(&(config.height as u16).to_be_bytes());
payload.extend_from_slice(&0x0048_0000_u32.to_be_bytes()); payload.extend_from_slice(&0x0048_0000_u32.to_be_bytes()); payload.extend_from_slice(&0u32.to_be_bytes()); payload.extend_from_slice(&1u16.to_be_bytes()); payload.extend_from_slice(&[0u8; 32]); payload.extend_from_slice(&0x0018_u16.to_be_bytes()); payload.extend_from_slice(&0xffff_u16.to_be_bytes());
let avcc = build_avcc_fmp4(config);
payload.extend_from_slice(&avcc);
build_box(b"avc1", &payload)
}
fn build_avcc_fmp4(config: &FragmentConfig) -> Vec<u8> {
let mut payload = vec![
1, config.sps.get(1).copied().unwrap_or(0x42), config.sps.get(2).copied().unwrap_or(0x00), config.sps.get(3).copied().unwrap_or(0x1e), 0xff, 0xe1, ];
payload.extend_from_slice(&(config.sps.len() as u16).to_be_bytes());
payload.extend_from_slice(&config.sps);
payload.push(1); payload.extend_from_slice(&(config.pps.len() as u16).to_be_bytes());
payload.extend_from_slice(&config.pps);
build_box(b"avcC", &payload)
}
fn build_empty_stts() -> Vec<u8> {
let mut payload = Vec::new();
payload.extend_from_slice(&0u32.to_be_bytes()); payload.extend_from_slice(&0u32.to_be_bytes()); build_box(b"stts", &payload)
}
fn build_empty_stsc() -> Vec<u8> {
let mut payload = Vec::new();
payload.extend_from_slice(&0u32.to_be_bytes()); payload.extend_from_slice(&0u32.to_be_bytes()); build_box(b"stsc", &payload)
}
fn build_empty_stsz() -> Vec<u8> {
let mut payload = Vec::new();
payload.extend_from_slice(&0u32.to_be_bytes()); payload.extend_from_slice(&0u32.to_be_bytes()); payload.extend_from_slice(&0u32.to_be_bytes()); build_box(b"stsz", &payload)
}
fn build_empty_stco() -> Vec<u8> {
let mut payload = Vec::new();
payload.extend_from_slice(&0u32.to_be_bytes()); payload.extend_from_slice(&0u32.to_be_bytes()); build_box(b"stco", &payload)
}
fn build_media_segment(
samples: &[FragmentSample],
sequence_number: u32,
base_media_decode_time: u64,
_timescale: u32, ) -> Vec<u8> {
let mdat_payload_size: usize = samples.iter().map(|s| s.data.len()).sum();
let moof = build_moof(samples, sequence_number, base_media_decode_time);
let moof_size = moof.len() as u32;
let data_offset = moof_size + 8;
let moof = build_moof_with_offset(
samples,
sequence_number,
base_media_decode_time,
data_offset,
);
let mut segment = Vec::with_capacity(moof.len() + 8 + mdat_payload_size);
segment.extend_from_slice(&moof);
let mdat_size = (8 + mdat_payload_size) as u32;
segment.extend_from_slice(&mdat_size.to_be_bytes());
segment.extend_from_slice(b"mdat");
for sample in samples {
segment.extend_from_slice(&sample.data);
}
segment
}
fn build_moof(
samples: &[FragmentSample],
sequence_number: u32,
base_media_decode_time: u64,
) -> Vec<u8> {
build_moof_with_offset(samples, sequence_number, base_media_decode_time, 0)
}
fn build_moof_with_offset(
samples: &[FragmentSample],
sequence_number: u32,
base_media_decode_time: u64,
data_offset: u32,
) -> Vec<u8> {
let mut payload = Vec::new();
let mfhd = build_mfhd(sequence_number);
payload.extend_from_slice(&mfhd);
let traf = build_traf(samples, base_media_decode_time, data_offset);
payload.extend_from_slice(&traf);
build_box(b"moof", &payload)
}
fn build_mfhd(sequence_number: u32) -> Vec<u8> {
let mut payload = Vec::new();
payload.extend_from_slice(&0u32.to_be_bytes()); payload.extend_from_slice(&sequence_number.to_be_bytes());
build_box(b"mfhd", &payload)
}
fn build_traf(
samples: &[FragmentSample],
base_media_decode_time: u64,
data_offset: u32,
) -> Vec<u8> {
let mut payload = Vec::new();
let tfhd = build_tfhd();
payload.extend_from_slice(&tfhd);
let tfdt = build_tfdt(base_media_decode_time);
payload.extend_from_slice(&tfdt);
let trun = build_trun(samples, data_offset);
payload.extend_from_slice(&trun);
build_box(b"traf", &payload)
}
fn build_tfhd() -> Vec<u8> {
let mut payload = Vec::new();
payload.extend_from_slice(&0x0002_0000_u32.to_be_bytes()); payload.extend_from_slice(&1u32.to_be_bytes()); build_box(b"tfhd", &payload)
}
fn build_tfdt(base_media_decode_time: u64) -> Vec<u8> {
let mut payload = Vec::new();
payload.extend_from_slice(&0x0100_0000_u32.to_be_bytes()); payload.extend_from_slice(&base_media_decode_time.to_be_bytes());
build_box(b"tfdt", &payload)
}
fn build_trun(samples: &[FragmentSample], data_offset: u32) -> Vec<u8> {
let flags: u32 = 0x000001 | 0x000100 | 0x000200 | 0x000400 | 0x000800;
let mut payload = Vec::new();
payload.extend_from_slice(&(0x0100_0000 | flags).to_be_bytes());
payload.extend_from_slice(&(samples.len() as u32).to_be_bytes()); payload.extend_from_slice(&data_offset.to_be_bytes());
for (i, sample) in samples.iter().enumerate() {
let duration = if i + 1 < samples.len() {
(samples[i + 1].dts - sample.dts) as u32
} else if i > 0 {
(sample.dts - samples[i - 1].dts) as u32
} else {
3000 };
payload.extend_from_slice(&duration.to_be_bytes());
payload.extend_from_slice(&(sample.data.len() as u32).to_be_bytes());
let flags = if sample.is_sync {
0x0200_0000_u32 } else {
0x0101_0000_u32 };
payload.extend_from_slice(&flags.to_be_bytes());
let cts = (sample.pts as i64 - sample.dts as i64) as i32;
payload.extend_from_slice(&cts.to_be_bytes());
}
build_box(b"trun", &payload)
}
#[cfg(test)]
mod tests {
use super::*;
fn find_box_offset(data: &[u8], typ: &[u8; 4]) -> Option<usize> {
data.windows(4)
.position(|w| w == typ)
.and_then(|pos| pos.checked_sub(4))
}
fn read_u32_be(data: &[u8], offset: usize) -> u32 {
u32::from_be_bytes(data[offset..offset + 4].try_into().unwrap())
}
fn read_u64_be(data: &[u8], offset: usize) -> u64 {
u64::from_be_bytes(data[offset..offset + 8].try_into().unwrap())
}
#[test]
fn init_segment_contains_ftyp_moov() {
let config = FragmentConfig::default();
let mut muxer = FragmentedMuxer::new(config);
let init = muxer.init_segment();
assert_eq!(&init[4..8], b"ftyp");
let ftyp_size = u32::from_be_bytes(init[0..4].try_into().unwrap()) as usize;
assert_eq!(&init[ftyp_size + 4..ftyp_size + 8], b"moov");
}
#[test]
fn init_segment_is_cached_after_first_call() {
let config = FragmentConfig::default();
let mut muxer = FragmentedMuxer::new(config);
let init1 = muxer.init_segment();
let init2 = muxer.init_segment();
assert_eq!(init1, init2);
}
#[test]
fn media_segment_contains_moof_mdat() {
let config = FragmentConfig::default();
let mut muxer = FragmentedMuxer::new(config);
let sample_data = vec![0x00, 0x00, 0x00, 0x05, 0x65, 0xaa, 0xbb, 0xcc, 0xdd];
muxer.write_video(0, 0, &sample_data, true);
muxer.write_video(3000, 3000, &sample_data, false);
let segment = muxer.flush_segment().unwrap();
assert_eq!(&segment[4..8], b"moof");
let moof_size = u32::from_be_bytes(segment[0..4].try_into().unwrap()) as usize;
assert_eq!(&segment[moof_size + 4..moof_size + 8], b"mdat");
}
#[test]
fn ready_to_flush_tracks_sample_count_and_duration() {
let config = FragmentConfig {
fragment_duration_ms: 1,
..Default::default()
};
let mut muxer = FragmentedMuxer::new(config);
assert!(!muxer.ready_to_flush(), "empty should not be ready");
let sample_data = vec![0, 0, 0, 5, 0x65, 1, 2, 3, 4];
muxer.write_video(0, 0, &sample_data, true);
assert!(!muxer.ready_to_flush(), "single sample should not be ready");
muxer.write_video(90, 90, &sample_data, false);
assert!(
muxer.ready_to_flush(),
"two samples reaching duration should be ready"
);
}
#[test]
fn tfdt_base_decode_time_advances_between_segments() {
let config = FragmentConfig::default();
let mut muxer = FragmentedMuxer::new(config);
let sample_data = vec![0, 0, 0, 5, 0x65, 1, 2, 3, 4];
muxer.write_video(0, 0, &sample_data, true);
muxer.write_video(3000, 3000, &sample_data, false);
let _seg1 = muxer.flush_segment().unwrap();
muxer.write_video(6000, 6000, &sample_data, true);
let seg2 = muxer.flush_segment().unwrap();
let tfdt_off = find_box_offset(&seg2, b"tfdt").expect("tfdt box");
let tfdt_size = read_u32_be(&seg2, tfdt_off) as usize;
assert!(tfdt_size >= 8 + 12);
let base = read_u64_be(&seg2, tfdt_off + 8 + 4);
assert_eq!(base, 6000);
}
#[test]
fn trun_single_sample_uses_default_duration_3000() {
let config = FragmentConfig::default();
let mut muxer = FragmentedMuxer::new(config);
let sample_data = vec![0, 0, 0, 5, 0x65, 1, 2, 3, 4];
muxer.write_video(0, 0, &sample_data, true);
let seg = muxer.flush_segment().unwrap();
let trun_off = find_box_offset(&seg, b"trun").expect("trun box");
let duration = read_u32_be(&seg, trun_off + 8 + 12);
assert_eq!(duration, 3000);
}
#[test]
fn flush_returns_none_when_empty() {
let config = FragmentConfig::default();
let mut muxer = FragmentedMuxer::new(config);
assert!(muxer.flush_segment().is_none());
}
}