use crate::media::{Media, Track};
pub fn rebase_to_zero(media: &mut Media) {
for track in &mut media.tracks {
let anchor = track.start_decode_time as i64;
if anchor == 0 {
continue;
}
for s in &mut track.samples {
if let Some(d) = s.dts {
s.dts = Some(d - anchor);
}
if let Some(p) = s.pts {
s.pts = Some(p - anchor);
}
}
track.start_decode_time = 0;
}
}
pub fn apply_offset(media: &mut Media, delta_ticks: i64) {
for track in &mut media.tracks {
let new_anchor = track.start_decode_time.saturating_add_signed(delta_ticks);
let effective_delta = new_anchor as i64 - track.start_decode_time as i64;
for s in &mut track.samples {
if let Some(d) = s.dts {
s.dts = Some(d + effective_delta);
}
if let Some(p) = s.pts {
s.pts = Some(p + effective_delta);
}
}
track.start_decode_time = new_anchor;
}
}
pub fn insert_discontinuity_gap(track: &mut Track, at_sample_index: usize, gap_ticks: u32) {
let gap = gap_ticks as i64;
if at_sample_index == 0 {
track.start_decode_time = track.start_decode_time.saturating_add(gap_ticks as u64);
}
let idx = at_sample_index.min(track.samples.len());
for s in &mut track.samples[idx..] {
if let Some(d) = s.dts {
s.dts = Some(d + gap);
}
if let Some(p) = s.pts {
s.pts = Some(p + gap);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ir::SampleFlags;
use crate::media::Track;
use crate::pipeline::{Sample, TrackSpec};
use alloc::vec;
fn sample(dts: i64, duration: u32) -> Sample {
Sample {
data: vec![0u8; 4].into(),
dts: Some(dts),
pts: Some(dts),
duration: Some(duration),
flags: SampleFlags::SYNC,
provenance: None,
}
}
fn track_at(start: i64, durs: &[u32]) -> Track {
let mut dts = start;
let mut samples = alloc::vec::Vec::with_capacity(durs.len());
for &d in durs {
samples.push(sample(dts, d));
dts += d as i64;
}
Track::new_at(spec(), samples, start as u64)
}
fn spec() -> TrackSpec {
use crate::avc_config::{AVCConfigurationBox, AVCDecoderConfigurationRecord};
use crate::nalu_types::{AvcPps, AvcSps};
use crate::pipeline::CodecConfig;
let record = AVCDecoderConfigurationRecord {
configuration_version: 1,
profile_indication: 66,
profile_compatibility: 0,
level_indication: 30,
length_size_minus_one: 3,
sps: vec![AvcSps(vec![0x67, 0x42, 0x00, 0x1e])],
pps: vec![AvcPps(vec![0x68, 0xce, 0x3c, 0x80])],
chroma_format: None,
bit_depth_luma_minus8: None,
bit_depth_chroma_minus8: None,
sps_ext: vec![],
};
TrackSpec::new(
1,
90_000,
CodecConfig::Avc {
config: AVCConfigurationBox::new(record),
width: 16,
height: 16,
},
)
}
#[test]
fn rebase_to_zero_clears_anchor_and_samples() {
let mut m = Media::new(vec![track_at(90_000, &[3000, 3000])], 1000);
rebase_to_zero(&mut m);
assert_eq!(m.tracks[0].start_decode_time, 0);
assert_eq!(m.tracks[0].samples[0].dts, Some(0));
assert_eq!(m.tracks[0].samples[1].dts, Some(3000));
assert_eq!(m.tracks[0].samples[1].pts, Some(3000));
rebase_to_zero(&mut m);
assert_eq!(m.tracks[0].start_decode_time, 0);
assert_eq!(m.tracks[0].samples[0].dts, Some(0));
}
#[test]
fn rebase_to_zero_leaves_none_untouched() {
let mut m = Media::new(vec![track_at(1000, &[10])], 1000);
m.tracks[0].samples[0].dts = None;
m.tracks[0].samples[0].pts = None;
rebase_to_zero(&mut m);
assert_eq!(m.tracks[0].samples[0].dts, None, "never fabricate a dts");
assert_eq!(m.tracks[0].samples[0].pts, None, "never fabricate a pts");
}
#[test]
fn apply_offset_shifts_anchor_and_samples() {
let mut m = Media::new(vec![track_at(100, &[10])], 1000);
apply_offset(&mut m, 50);
assert_eq!(m.tracks[0].start_decode_time, 150);
assert_eq!(m.tracks[0].samples[0].dts, Some(150));
apply_offset(&mut m, -1000);
assert_eq!(m.tracks[0].start_decode_time, 0, "underflow saturates at 0");
assert_eq!(
m.tracks[0].samples[0].dts,
Some(0),
"sample shift matches the saturated (clamped) anchor delta, not the raw delta"
);
}
#[test]
fn gap_extends_timeline_and_shifts_later_samples() {
let mut t = track_at(0, &[100, 100, 100]);
insert_discontinuity_gap(&mut t, 2, 500);
assert_eq!(t.samples[0].dts, Some(0), "earlier sample unchanged");
assert_eq!(t.samples[1].dts, Some(100), "earlier sample unchanged");
assert_eq!(
t.samples[2].dts,
Some(700),
"sample at the insertion point pushed out by the gap (200 + 500)"
);
assert_eq!(t.start_decode_time, 0, "anchor unaffected mid-track");
}
#[test]
fn gap_at_index_zero_shifts_anchor_and_every_sample() {
let mut t = track_at(1000, &[100]);
insert_discontinuity_gap(&mut t, 0, 250);
assert_eq!(t.start_decode_time, 1250);
assert_eq!(t.samples[0].dts, Some(1250));
}
}