Resolve a frame clip by binary search, not a linear scan
The provenance lookup runs once per frame per track. A linear scan over the clip list is fine for the 11-PlayItem fixture and is not fine for the real hoard: two discs there carry 900 PlayItems in one title, and several carry 250-450, so a scan would be hundreds of comparisons on every one of millions of frames. spans_trusted already guarantees the spans tile the feed contiguously and in order, which makes this a partition point. A repeated clip reuses its first reference span, so the search walks back to the FIRST entry sharing it — the bytes are read once, so the material is emitted once, at that entry offset, and the answer is stable regardless of which duplicate the partition lands on. Also adds a test at the real 900-clip scale: every clip places, output stays monotonic across all 900, an arbitrary byte resolves to the right clip out of ascending order, and a byte past the end belongs to no clip.
This commit is contained in:
+183
-4
@@ -259,6 +259,35 @@ impl SeamPlan {
|
|||||||
|
|
||||||
/// Place a raw PTS for `track`, advancing that track's own clip cursor.
|
/// Place a raw PTS for `track`, advancing that track's own clip cursor.
|
||||||
///
|
///
|
||||||
|
/// Which clip owns feed byte `b`, by BINARY SEARCH.
|
||||||
|
///
|
||||||
|
/// `spans_trusted` guarantees the spans tile the feed contiguously in
|
||||||
|
/// order, so this is a partition point rather than a scan. That matters:
|
||||||
|
/// discs in the test hoard reach 900 clips, and this runs once per frame
|
||||||
|
/// per track — a linear scan would be ~900 comparisons on every one of
|
||||||
|
/// millions of frames, which is real time spent for no reason.
|
||||||
|
///
|
||||||
|
/// A repeated clip reuses its first reference's span, so the search lands
|
||||||
|
/// on the FIRST entry with that span. The bytes are only read once, so the
|
||||||
|
/// material is emitted once, at that entry's offset.
|
||||||
|
fn clip_at_byte(&self, b: u64) -> Option<usize> {
|
||||||
|
let i = self
|
||||||
|
.clips
|
||||||
|
.partition_point(|c| c.feed_span.is_some_and(|(s, _)| s <= b));
|
||||||
|
let cand = i.checked_sub(1)?;
|
||||||
|
// Walk back over any earlier entries sharing this span (a repeated
|
||||||
|
// clip) so the answer is stable and is always the first of them.
|
||||||
|
let (s, e) = self.clips[cand].feed_span?;
|
||||||
|
if b < s || b >= e {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let mut first = cand;
|
||||||
|
while first > 0 && self.clips[first - 1].feed_span == Some((s, e)) {
|
||||||
|
first -= 1;
|
||||||
|
}
|
||||||
|
Some(first)
|
||||||
|
}
|
||||||
|
|
||||||
/// `None` means DROP: the frame lies outside every clip's marks, so the
|
/// `None` means DROP: the frame lies outside every clip's marks, so the
|
||||||
/// playlist does not include it.
|
/// playlist does not include it.
|
||||||
fn place(
|
fn place(
|
||||||
@@ -316,10 +345,7 @@ impl SeamPlan {
|
|||||||
// heuristics, which are at least approximately right. Degrade instead.
|
// heuristics, which are at least approximately right. Degrade instead.
|
||||||
if self.spans_trusted
|
if self.spans_trusted
|
||||||
&& let Some(b) = src_byte
|
&& let Some(b) = src_byte
|
||||||
&& let Some(found) = self
|
&& let Some(found) = self.clip_at_byte(b)
|
||||||
.clips
|
|
||||||
.iter()
|
|
||||||
.position(|c| c.feed_span.is_some_and(|(s, e)| b >= s && b < e))
|
|
||||||
{
|
{
|
||||||
let c = self.clips[found];
|
let c = self.clips[found];
|
||||||
let placed = raw_ns >= c.in_ns && raw_ns <= c.out_ns;
|
let placed = raw_ns >= c.in_ns && raw_ns <= c.out_ns;
|
||||||
@@ -1063,6 +1089,159 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// On frames that are NOT ambiguous, provenance and the mark heuristics
|
||||||
|
/// must give the SAME answer.
|
||||||
|
///
|
||||||
|
/// This is the strongest available cross-check. The heuristics are wrong
|
||||||
|
/// only inside an overlap, where two clips' mark ranges both contain the
|
||||||
|
/// timestamp; everywhere else they are the behaviour that shipped and was
|
||||||
|
/// verified on real discs. So for a frame that falls inside exactly one
|
||||||
|
/// clip, the two methods disagreeing means the NEW path is wrong.
|
||||||
|
#[test]
|
||||||
|
fn provenance_agrees_with_marks_wherever_marks_are_unambiguous() {
|
||||||
|
let clips = clips_with_spans();
|
||||||
|
let plain = seamless_branching_clips();
|
||||||
|
let mut disagreements = Vec::new();
|
||||||
|
|
||||||
|
for (i, c) in clips.iter().enumerate() {
|
||||||
|
let in_ns = mpls_ticks_to_ns(c.in_time);
|
||||||
|
let out_ns = mpls_ticks_to_ns(c.out_time);
|
||||||
|
// Sample across the clip.
|
||||||
|
for f in [1u32, 2, 4, 8] {
|
||||||
|
let raw = in_ns + (out_ns - in_ns) / f as i64;
|
||||||
|
// Unambiguous = inside THIS clip's marks and no other's.
|
||||||
|
let owners = clips
|
||||||
|
.iter()
|
||||||
|
.filter(|o| {
|
||||||
|
raw >= mpls_ticks_to_ns(o.in_time) && raw <= mpls_ticks_to_ns(o.out_time)
|
||||||
|
})
|
||||||
|
.count();
|
||||||
|
if owners != 1 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let (s, _) = c.feed_span.expect("span");
|
||||||
|
let mut a = SeamPlan::from_clips(&clips).expect("plan");
|
||||||
|
let mut b = SeamPlan::from_clips(&plain).expect("plan");
|
||||||
|
let by_bytes = a.place(raw, 0, true, Some(s + 10));
|
||||||
|
let by_marks = b.place(raw, 0, true, None);
|
||||||
|
if by_bytes != by_marks {
|
||||||
|
disagreements.push((i, raw, by_bytes, by_marks));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert!(
|
||||||
|
disagreements.is_empty(),
|
||||||
|
"provenance and marks disagreed on unambiguous frames: {disagreements:?}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Walk a whole title through the plan with provenance and assert the two
|
||||||
|
/// properties that decide whether a rip is watchable: output never moves
|
||||||
|
/// backwards for a track, and the total span matches the title's declared
|
||||||
|
/// duration.
|
||||||
|
///
|
||||||
|
/// A title can have the right TOTAL duration while being wrong in the
|
||||||
|
/// middle, which is why monotonicity is checked per frame rather than only
|
||||||
|
/// at the ends.
|
||||||
|
#[test]
|
||||||
|
fn a_full_pass_over_the_real_table_is_monotonic_and_totals_correctly() {
|
||||||
|
let clips = clips_with_spans();
|
||||||
|
let mut plan = SeamPlan::from_clips(&clips).expect("plan");
|
||||||
|
let declared: i64 = clips
|
||||||
|
.iter()
|
||||||
|
.map(|c| mpls_ticks_to_ns(c.out_time) - mpls_ticks_to_ns(c.in_time))
|
||||||
|
.sum();
|
||||||
|
|
||||||
|
let mut last: Option<i64> = None;
|
||||||
|
let mut first: Option<i64> = None;
|
||||||
|
let mut placed = 0u64;
|
||||||
|
for c in &clips {
|
||||||
|
let (s, e) = c.feed_span.expect("span");
|
||||||
|
let in_ns = mpls_ticks_to_ns(c.in_time);
|
||||||
|
let out_ns = mpls_ticks_to_ns(c.out_time);
|
||||||
|
// 40 frames per clip, evenly spaced, each stamped from its own span.
|
||||||
|
for k in 0..40i64 {
|
||||||
|
let raw = in_ns + (out_ns - in_ns) * k / 40;
|
||||||
|
let byte = s + (e - s) * k as u64 / 40;
|
||||||
|
if let Some(out) = plan.place(raw, 0, true, Some(byte)) {
|
||||||
|
if let Some(l) = last {
|
||||||
|
assert!(
|
||||||
|
out >= l,
|
||||||
|
"output moved backwards: {l} -> {out} (clip {}, raw {raw})",
|
||||||
|
c.clip_id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
first.get_or_insert(out);
|
||||||
|
last = Some(out);
|
||||||
|
placed += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert!(placed > 400, "most frames must be placed, got {placed}");
|
||||||
|
let span = last.expect("last") - first.expect("first");
|
||||||
|
// Within one clip's frame spacing of the declared duration.
|
||||||
|
let slack = declared / 40;
|
||||||
|
assert!(
|
||||||
|
(span - declared).abs() <= slack,
|
||||||
|
"output span {span} differs from declared {declared} by more than {slack}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A heavily branched title — the hoard has discs with 900 PlayItems —
|
||||||
|
/// must resolve every frame to the right clip, and must do it by binary
|
||||||
|
/// search rather than by scanning 900 entries per frame per track.
|
||||||
|
#[test]
|
||||||
|
fn a_nine_hundred_clip_title_resolves_every_frame_correctly() {
|
||||||
|
const N: u32 = 900;
|
||||||
|
const CLIP_TICKS: u32 = 10 * 45_000; // 10s each
|
||||||
|
const CLIP_BYTES: u64 = 5_000_000;
|
||||||
|
let clips: Vec<crate::disc::Clip> = (0..N)
|
||||||
|
.map(|i| crate::disc::Clip {
|
||||||
|
clip_id: format!("{i:05}"),
|
||||||
|
in_time: i * CLIP_TICKS,
|
||||||
|
out_time: (i + 1) * CLIP_TICKS,
|
||||||
|
duration_secs: 10.0,
|
||||||
|
source_packets: 0,
|
||||||
|
feed_span: Some((i as u64 * CLIP_BYTES, (i as u64 + 1) * CLIP_BYTES)),
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let mut plan = SeamPlan::from_clips(&clips).expect("plan");
|
||||||
|
assert!(
|
||||||
|
plan.spans_trusted,
|
||||||
|
"a contiguous 900-clip table must be trusted"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Every clip, sampled at its middle, must place — and monotonically.
|
||||||
|
let mut last: Option<i64> = None;
|
||||||
|
for i in 0..N {
|
||||||
|
let raw = mpls_ticks_to_ns(i * CLIP_TICKS + CLIP_TICKS / 2);
|
||||||
|
let byte = i as u64 * CLIP_BYTES + CLIP_BYTES / 2;
|
||||||
|
let out = plan
|
||||||
|
.place(raw, 0, true, Some(byte))
|
||||||
|
.unwrap_or_else(|| panic!("clip {i} did not place"));
|
||||||
|
if let Some(l) = last {
|
||||||
|
assert!(out >= l, "output moved backwards at clip {i}: {l} -> {out}");
|
||||||
|
}
|
||||||
|
last = Some(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The lookup must find the right clip from an arbitrary byte, not just
|
||||||
|
// in ascending order — a track can arrive out of step with another.
|
||||||
|
for i in [0u32, 1, 449, 898, 899] {
|
||||||
|
let byte = i as u64 * CLIP_BYTES + 7;
|
||||||
|
assert_eq!(
|
||||||
|
plan.clip_at_byte(byte),
|
||||||
|
Some(i as usize),
|
||||||
|
"byte {byte} must resolve to clip {i}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
assert_eq!(
|
||||||
|
plan.clip_at_byte(N as u64 * CLIP_BYTES + 1),
|
||||||
|
None,
|
||||||
|
"a byte past the last clip belongs to no clip"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// Build a real seamless-branching clip table (`00801.mpls`, 11 PlayItems, marks
|
/// Build a real seamless-branching clip table (`00801.mpls`, 11 PlayItems, marks
|
||||||
/// read off the disc) so the arithmetic is pinned to measured ground truth
|
/// read off the disc) so the arithmetic is pinned to measured ground truth
|
||||||
/// rather than to invented numbers.
|
/// rather than to invented numbers.
|
||||||
|
|||||||
Reference in New Issue
Block a user