Skip to content

Commit 1e9ad39

Browse files
committed
MP4 seek by metadata
1 parent 8c9dec6 commit 1e9ad39

File tree

1 file changed

+43
-15
lines changed

1 file changed

+43
-15
lines changed

smelter-core/src/pipeline/mp4/reader.rs

Lines changed: 43 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -190,24 +190,52 @@ impl<Reader: Read + Seek + Send + 'static> Track<Reader> {
190190
/// `start_index` is the last sync sample before seek (for decoder warmup).
191191
/// `present_from_index` is the first sample at or after seek.
192192
/// Returns `None` if seek is past the end.
193-
fn find_seek_start_sample(&mut self, seek: Duration) -> Option<(u32, u32)> {
193+
fn find_seek_start_sample(&self, seek: Duration) -> Option<(u32, u32)> {
194194
let seek_timescale = ((seek + self.offset).as_secs_f64() * self.timescale as f64) as u64;
195-
let mut best_sync_index = 1u32;
196-
// TODO: improve performance
197-
for i in 1..=self.sample_count {
198-
match self.reader.read_sample(self.track_id, i) {
199-
Ok(Some(sample)) => {
200-
if sample.is_sync {
201-
best_sync_index = i
202-
}
203-
if sample.start_time >= seek_timescale {
204-
return Some((best_sync_index, i));
205-
}
206-
}
207-
_ => return None,
195+
let track = &self.reader.tracks()[&self.track_id];
196+
197+
// The STTS box maps samples to batches of the same sample length
198+
let stts = &track.trak.mdia.minf.stbl.stts;
199+
200+
let mut first_batch_sample_id = 1u32;
201+
let mut elapsed = 0u64;
202+
let mut present_from_index = None;
203+
204+
// Finds the first sample after the provided seek point.
205+
for entry in &stts.entries {
206+
let batch_end_time = elapsed + entry.sample_count as u64 * entry.sample_delta as u64;
207+
208+
if seek_timescale < batch_end_time {
209+
let offset_in_batch = {
210+
let time_into_batch = seek_timescale - elapsed;
211+
time_into_batch.div_ceil(entry.sample_delta as u64) as u32
212+
};
213+
present_from_index = Some(first_batch_sample_id + offset_in_batch);
214+
break;
208215
}
216+
217+
elapsed = batch_end_time;
218+
first_batch_sample_id += entry.sample_count;
209219
}
210-
None
220+
221+
let present_from_index = present_from_index?;
222+
223+
// The STSS box contains indices of sync samples (e.g. key frames).
224+
// `None` means all samples are sync samples.
225+
let stss = &track.trak.mdia.minf.stbl.stss;
226+
227+
let sync_index = match &stss {
228+
Some(stss) => {
229+
let pos = stss.entries.partition_point(|&s| s <= present_from_index);
230+
231+
// `pos == 0` means no sync sample was found before the seek time.
232+
// Fall back to the first sample.
233+
if pos == 0 { 1 } else { stss.entries[pos - 1] }
234+
}
235+
None => present_from_index,
236+
};
237+
238+
Some((sync_index, present_from_index))
211239
}
212240
}
213241

0 commit comments

Comments
 (0)