Say which frame and which marks caused a provenance drop
The heuristic path has always logged its first drop per track with the frame's timestamp and the clip marks it was judged against. The provenance path did not log at all, so a title that dropped MOST of its frames there failed the volume gate without a single line saying which frame, which clip, or which marks — a full-length rip produced zero drop events and an error, which is not a diagnosable failure. Also pins the all-identical-spans hazard as a test. Every PlayItem referencing one clip file gives every clip the same span, which passes the tiling check and is therefore "trusted" while carrying no information at all about which PlayItem a byte belongs to. That is not what the discs on hand do — theirs have one distinct span per clip — but the check conflates "the spans tile" with "the spans distinguish", and only the second justifies placing a frame by its byte offset.
This commit is contained in:
@@ -430,6 +430,25 @@ impl SeamPlan {
|
|||||||
// (a clip's file is not trimmed to its marks). Counted, so the
|
// (a clip's file is not trimmed to its marks). Counted, so the
|
||||||
// volume gates in the sinks can see it.
|
// volume gates in the sinks can see it.
|
||||||
self.dropped[track] = self.dropped[track].saturating_add(1);
|
self.dropped[track] = self.dropped[track].saturating_add(1);
|
||||||
|
// Once per track, on the FIRST drop only — a join legitimately
|
||||||
|
// drops a handful of frames and this must not become per-frame
|
||||||
|
// noise. The heuristic path below has always logged this; the
|
||||||
|
// provenance path did not, so a title that dropped MOST of its
|
||||||
|
// frames here produced a volume-gate failure and not one line
|
||||||
|
// saying which frame, which clip, or which marks it was judged
|
||||||
|
// against. That is not a diagnosable failure.
|
||||||
|
if self.dropped[track] == 1 {
|
||||||
|
tracing::info!(
|
||||||
|
target: "freemkv::mux",
|
||||||
|
track,
|
||||||
|
clip = found,
|
||||||
|
byte = b,
|
||||||
|
raw_ns,
|
||||||
|
in_ns = c.in_ns,
|
||||||
|
out_ns = c.out_ns,
|
||||||
|
"frame outside its clip's marks (by provenance); dropping"
|
||||||
|
);
|
||||||
|
}
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
return Some(raw_ns.saturating_add(c.offset_ns));
|
return Some(raw_ns.saturating_add(c.offset_ns));
|
||||||
@@ -451,6 +470,17 @@ impl SeamPlan {
|
|||||||
let c = self.clips[clip];
|
let c = self.clips[clip];
|
||||||
if raw_ns < c.in_ns || raw_ns > c.out_ns {
|
if raw_ns < c.in_ns || raw_ns > c.out_ns {
|
||||||
self.dropped[track] = self.dropped[track].saturating_add(1);
|
self.dropped[track] = self.dropped[track].saturating_add(1);
|
||||||
|
if self.dropped[track] == 1 {
|
||||||
|
tracing::info!(
|
||||||
|
target: "freemkv::mux",
|
||||||
|
track,
|
||||||
|
clip,
|
||||||
|
raw_ns,
|
||||||
|
in_ns = c.in_ns,
|
||||||
|
out_ns = c.out_ns,
|
||||||
|
"frame with no provenance outside its track's clip; dropping"
|
||||||
|
);
|
||||||
|
}
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let out = raw_ns.saturating_add(c.offset_ns);
|
let out = raw_ns.saturating_add(c.offset_ns);
|
||||||
@@ -1336,6 +1366,45 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A title whose PlayItems all reference ONE clip file: every span is
|
||||||
|
/// identical, so the tiling check's "equal to previous is allowed" arm
|
||||||
|
/// matches every entry and the spans are TRUSTED — while carrying no
|
||||||
|
/// information at all about which PlayItem a byte belongs to.
|
||||||
|
///
|
||||||
|
/// That combination is the dangerous one: provenance looks authoritative
|
||||||
|
/// and is actually blind, so every frame resolves to the FIRST PlayItem and
|
||||||
|
/// everything past its mark range is dropped.
|
||||||
|
#[test]
|
||||||
|
fn one_clip_file_behind_every_play_item_is_not_distinguishable_by_byte() {
|
||||||
|
const N: u32 = 8;
|
||||||
|
const SPAN: (u64, u64) = (0, 40_000_000_000);
|
||||||
|
const SEG: u32 = 600 * 45_000; // 10 min per sub-range
|
||||||
|
let clips: Vec<crate::disc::Clip> = (0..N)
|
||||||
|
.map(|i| crate::disc::Clip {
|
||||||
|
clip_id: "00001".to_string(), // the SAME file every time
|
||||||
|
in_time: i * SEG,
|
||||||
|
out_time: (i + 1) * SEG,
|
||||||
|
duration_secs: 600.0,
|
||||||
|
source_packets: 0,
|
||||||
|
feed_span: Some(SPAN),
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let plan = SeamPlan::from_clips(&clips).expect("plan");
|
||||||
|
assert!(
|
||||||
|
plan.spans_trusted,
|
||||||
|
"identical spans pass the tiling check -- this is the hazard"
|
||||||
|
);
|
||||||
|
// Every byte in the file resolves to the FIRST play item, so a byte
|
||||||
|
// offset cannot say which of the 8 ranges a frame belongs to.
|
||||||
|
for b in [0u64, 1_000_000, 20_000_000_000, 39_999_999_999] {
|
||||||
|
assert_eq!(
|
||||||
|
plan.clip_at_byte(b),
|
||||||
|
Some(0),
|
||||||
|
"byte {b} resolves to the first play item, always"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A playlist whose clips each restart their own STC is the common case on
|
/// A playlist whose clips each restart their own STC is the common case on
|
||||||
/// the branched discs in the hoard: every clip's marks cover the same low
|
/// the branched discs in the hoard: every clip's marks cover the same low
|
||||||
/// values, so the marks are NOT points on one title-wide clock.
|
/// values, so the marks are NOT points on one title-wide clock.
|
||||||
|
|||||||
Reference in New Issue
Block a user