use std::{io,fs,time}; use io::Read; use mp4; use anyhow; use mp4::ReadBox; pub struct Source { reader: io::BufReader, start: time::Instant, pending: Option, } pub struct Fragment { pub data: Vec, pub keyframe: bool, pub timestamp: u64, // only used to simulate a live stream } impl Source { pub fn new(path: &str) -> io::Result { let f = fs::File::open(path)?; let reader = io::BufReader::new(f); let start = time::Instant::now(); Ok(Self{ reader, start, pending: None, }) } pub fn next(&mut self) -> anyhow::Result> { let pending = match self.pending.take() { Some(f) => f, None => self.next_inner()?, }; if pending.timestamp > 0 && pending.timestamp < self.start.elapsed().as_millis() as u64 { self.pending = Some(pending); return Ok(None) } Ok(Some(pending)) } fn next_inner(&mut self) -> anyhow::Result { // Read the next full atom. let atom = read_box(&mut self.reader)?; let mut timestamp = 0; let mut keyframe = false; // Before we return it, let's do some simple parsing. let mut reader = io::Cursor::new(&atom); let header = mp4::BoxHeader::read(&mut reader)?; if header.name == mp4::BoxType::MoofBox { let moof = mp4::MoofBox::read_box(&mut reader, header.size)?; keyframe = has_keyframe(&moof); timestamp = first_timestamp(&moof); } Ok(Fragment { data: atom, keyframe, timestamp, }) } } // Read a full MP4 atom into a vector. fn read_box(reader: &mut R) -> anyhow::Result> { // Read the 8 bytes for the size + type let mut buf = [0u8; 8]; reader.read_exact(&mut buf)?; // Convert the first 4 bytes into the size. let size = u32::from_be_bytes(buf[0..4].try_into()?) as u64; let mut out = buf.to_vec(); let mut limit = match size { // Runs until the end of the file. 0 => reader.take(u64::MAX), // The next 8 bytes are the extended size to be used instead. 1 => { reader.read_exact(&mut buf)?; let size_large = u64::from_be_bytes(buf); anyhow::ensure!(size_large >= 16, "impossible extended box size: {}", size_large); reader.take(size_large - 16) }, 2..=7 => { anyhow::bail!("impossible box size: {}", size) } // Otherwise read based on the size. size => reader.take(size - 8) }; // Append to the vector and return it. limit.read_to_end(&mut out)?; Ok(out) } fn has_keyframe(moof: &mp4::MoofBox) -> bool { for traf in &moof.trafs { // TODO trak default flags if this is None let default_flags = traf.tfhd.default_sample_flags.unwrap_or_default(); let trun = match &traf.trun { Some(t) => t, None => return false, }; for i in 0..trun.sample_count { let mut flags = match trun.sample_flags.get(i as usize) { Some(f) => *f, None => default_flags, }; if i == 0 && trun.first_sample_flags.is_some() { flags = trun.first_sample_flags.unwrap(); } // https://chromium.googlesource.com/chromium/src/media/+/master/formats/mp4/track_run_iterator.cc#177 let keyframe = (flags >> 24) & 0x3 == 0x2; // kSampleDependsOnNoOther let non_sync = (flags >> 16) & 0x1 == 0x1; // kSampleIsNonSyncSample if keyframe && !non_sync { return true } } } false } fn first_timestamp(moof: &mp4::MoofBox) -> u64 { let traf = match moof.trafs.first() { Some(t) => t, None => return 0, }; let tfdt = match &traf.tfdt { Some(t) => t, None => return 0, }; tfdt.base_media_decode_time }