From 8f4e156fcb096deb798b4b6809228e1ca2180095 Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Fri, 16 Oct 2026 11:01:16 +0900 Subject: [PATCH 1/8] fix(rendering): keep every pts hole of a realistic recording in the decoder hole map --- crates/rendering/src/decoder/mod.rs | 69 +++++++++++++++++++++++++---- 1 file changed, 60 insertions(+), 9 deletions(-) diff --git a/crates/rendering/src/decoder/mod.rs b/crates/rendering/src/decoder/mod.rs index fdabc7e2d5f..8d383a3b627 100644 --- a/crates/rendering/src/decoder/mod.rs +++ b/crates/rendering/src/decoder/mod.rs @@ -712,24 +712,30 @@ pub fn pts_to_frame(pts: i64, time_base: Rational, fps: u32) -> u32 { pub const FRAME_CACHE_SIZE: usize = 90; const DEFAULT_MAX_FALLBACK_DISTANCE: u32 = 90; +/// Bounds the pts-hole map. Holes are facts about the file, and a pooled +/// reader parked past a hole never re-vends it, so the map keeps every hole a +/// realistic recording has and only evicts on pathological inputs. +const MAX_TRACKED_HOLES: usize = 1 << 14; + /// Records a pts hole discovered from a decode-order vend jump (frames vend -/// in pts order, so a jump means no samples exist in between). The map stays -/// bounded by dropping the narrowest hole — wide static-screen holds matter -/// most. +/// in pts order, so a jump means no samples exist in between). When the map +/// is full it drops the hole farthest from the one just recorded: requests +/// trail the reader, so the newest holes are the ones about to be asked for. pub(super) fn record_pts_hole( holes: &mut std::collections::BTreeMap, start: u32, end: u32, ) { - const MAX_TRACKED_HOLES: usize = 64; holes.insert(start, end); if holes.len() > MAX_TRACKED_HOLES - && let Some(narrowest) = holes - .iter() - .min_by_key(|&(&s, &e)| e.saturating_sub(s)) - .map(|(&s, _)| s) + && let (Some(&first), Some(&last)) = (holes.keys().next(), holes.keys().next_back()) { - holes.remove(&narrowest); + let farthest = if start.abs_diff(first) >= start.abs_diff(last) { + first + } else { + last + }; + holes.remove(&farthest); } } @@ -1790,6 +1796,51 @@ pub async fn spawn_decoder( mod tests { use super::*; + #[test] + fn pts_hole_tracking_keeps_the_newest_holes_when_full() { + let mut holes = std::collections::BTreeMap::new(); + let mut frame = 0u32; + for i in 0..(MAX_TRACKED_HOLES as u32 + 1000) { + let width = if i % 4 == 0 { 3 } else { 2 }; + record_pts_hole(&mut holes, frame, frame + width); + assert_eq!(holes.get(&frame), Some(&(frame + width))); + frame += width; + } + assert_eq!(holes.len(), MAX_TRACKED_HOLES); + assert!( + holes + .keys() + .all(|&start| start as usize + 3 * MAX_TRACKED_HOLES >= frame as usize) + ); + } + + #[test] + fn pts_hole_tracking_drops_the_farthest_hole_after_a_backward_seek() { + let mut holes = std::collections::BTreeMap::new(); + let last = MAX_TRACKED_HOLES as u32 - 1; + for i in 0..=last { + record_pts_hole(&mut holes, 1000 + i * 2, 1000 + i * 2 + 2); + } + record_pts_hole(&mut holes, 10, 40); + assert_eq!(holes.len(), MAX_TRACKED_HOLES); + assert_eq!(holes.get(&10), Some(&40)); + assert!(holes.contains_key(&1000)); + assert!(!holes.contains_key(&(1000 + last * 2))); + } + + #[test] + fn pts_hole_tracking_keeps_an_old_static_hold_while_later_holes_arrive() { + let mut holes = std::collections::BTreeMap::new(); + record_pts_hole(&mut holes, 1797, 2402); + let mut frame = 2402u32; + for i in 0..2000u32 { + let width = 2 + i % 7; + record_pts_hole(&mut holes, frame, frame + width); + frame += width; + } + assert_eq!(holes.get(&1797), Some(&2402)); + } + #[test] fn decoded_frame_storage_identity_matches_clones() { let frame = DecodedFrame::new_nv12(vec![0; 6], 2, 2, 2, 2); From e4eb059391f798871cb443e2fe8bfe394eba9caa Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Fri, 16 Oct 2026 11:01:16 +0900 Subject: [PATCH 2/8] fix(rendering): reset a pooled reader that has already passed the requested frame --- crates/rendering/src/decoder/avassetreader.rs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/crates/rendering/src/decoder/avassetreader.rs b/crates/rendering/src/decoder/avassetreader.rs index d666771eaff..6decb4b7c5c 100644 --- a/crates/rendering/src/decoder/avassetreader.rs +++ b/crates/rendering/src/decoder/avassetreader.rs @@ -542,7 +542,12 @@ impl AVAssetReaderDecoder { }) } - fn select_best_decoder(&mut self, requested_time: f32, is_scrubbing: bool) -> (usize, bool) { + fn select_best_decoder( + &mut self, + requested_frame: u32, + requested_time: f32, + is_scrubbing: bool, + ) -> (usize, bool) { let decoder_count = self.decoders.len(); let (best_id, _distance, needs_reset) = if is_scrubbing { self.pool_manager @@ -557,6 +562,16 @@ impl AVAssetReaderDecoder { }; let decoder_idx = best_id.min(decoder_count.saturating_sub(1)); + // Pool positions are capped to the newest request, so a reader that + // decoded ahead can look usable for a request it has already passed. + // It can never vend that frame again, and reading on would answer + // with a stale or later frame, so it has to seek back first. + let needs_reset = needs_reset + || self + .decoders + .get(decoder_idx) + .and_then(|decoder| decoder.prev_vended) + .is_some_and(|prev| prev >= requested_frame); let max_decoder_count = self .pool_manager @@ -903,7 +918,7 @@ impl AVAssetReaderDecoder { } let this = this.as_mut().expect("Video reader initialized above"); - let (decoder_idx, was_reset) = this.select_best_decoder(requested_time, is_scrubbing); + let (decoder_idx, was_reset) = this.select_best_decoder(requested_frame, requested_time, is_scrubbing); let cache_min = if was_reset { min_requested_frame.saturating_sub(FRAME_CACHE_SIZE as u32 * 2) From b710ca9034d7b633d42113ec867a411843ba792f Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Fri, 16 Oct 2026 11:01:16 +0900 Subject: [PATCH 3/8] fix(rendering): keep each pooled reader's last vended frame, and retry from a fresh seek when it is gone --- crates/rendering/src/decoder/avassetreader.rs | 152 ++++++++++++++++-- 1 file changed, 140 insertions(+), 12 deletions(-) diff --git a/crates/rendering/src/decoder/avassetreader.rs b/crates/rendering/src/decoder/avassetreader.rs index 6decb4b7c5c..f8acb4d56d5 100644 --- a/crates/rendering/src/decoder/avassetreader.rs +++ b/crates/rendering/src/decoder/avassetreader.rs @@ -547,6 +547,7 @@ impl AVAssetReaderDecoder { requested_frame: u32, requested_time: f32, is_scrubbing: bool, + must_reset: bool, ) -> (usize, bool) { let decoder_count = self.decoders.len(); let (best_id, _distance, needs_reset) = if is_scrubbing { @@ -567,11 +568,11 @@ impl AVAssetReaderDecoder { // It can never vend that frame again, and reading on would answer // with a stale or later frame, so it has to seek back first. let needs_reset = needs_reset + || must_reset || self .decoders .get(decoder_idx) - .and_then(|decoder| decoder.prev_vended) - .is_some_and(|prev| prev >= requested_frame); + .is_some_and(|decoder| reader_passed_request(decoder.prev_vended, requested_frame)); let max_decoder_count = self .pool_manager @@ -722,6 +723,7 @@ impl AVAssetReaderDecoder { frame: u32, max_fallback_distance: u32, sender: oneshot::Sender, + retried: bool, } let mut deferred_requests = VecDeque::::new(); @@ -779,6 +781,7 @@ impl AVAssetReaderDecoder { frame, max_fallback_distance, sender, + retried: false, }); } } @@ -797,6 +800,7 @@ impl AVAssetReaderDecoder { frame, max_fallback_distance, sender, + retried: false, }); } } @@ -918,7 +922,12 @@ impl AVAssetReaderDecoder { } let this = this.as_mut().expect("Video reader initialized above"); - let (decoder_idx, was_reset) = this.select_best_decoder(requested_frame, requested_time, is_scrubbing); + let (decoder_idx, was_reset) = this.select_best_decoder( + requested_frame, + requested_time, + is_scrubbing, + pending_requests.iter().any(|request| request.retried), + ); let cache_min = if was_reset { min_requested_frame.saturating_sub(FRAME_CACHE_SIZE as u32 * 2) @@ -931,9 +940,23 @@ impl AVAssetReaderDecoder { max_requested_frame + FRAME_CACHE_SIZE as u32 }; + // The last frame each other pooled reader vended is the answer for a + // request that lands in the gap after it, and the reader can never + // vend it again; keep it cached while requests alternate between + // readers. + let pinned_frames: Vec = this + .decoders + .iter() + .enumerate() + .filter(|&(index, _)| index != decoder_idx) + .filter_map(|(_, decoder)| decoder.prev_vended) + .collect(); + if was_reset { *last_sent_frame.borrow_mut() = None; - cache.retain(|&f, _| f >= cache_min && f <= cache_max); + cache.retain(|f, _| { + (*f >= cache_min && *f <= cache_max) || pinned_frames.contains(f) + }); cache_bytes = cache .values() .map(CachedFrame::byte_len) @@ -951,6 +974,7 @@ impl AVAssetReaderDecoder { // reader re-vends from the keyframe at-or-before the request, and // that frame is the true VFR hold for requests inside a pts hole. let mut hold_candidate: Option<(u32, R)> = None; + let mut retry_requests = Vec::new(); { let decoder = &mut this.decoders[decoder_idx]; @@ -978,7 +1002,8 @@ impl AVAssetReaderDecoder { let current_frame = pts_to_frame(frame.pts().value, Rational::new(1, frame.pts().scale), fps); - if let Some(prev) = decoder.prev_vended + let vended_before = decoder.prev_vended; + if let Some(prev) = vended_before && current_frame > prev + 1 { record_pts_hole(&mut pts_holes, prev, current_frame); @@ -1030,16 +1055,18 @@ impl AVAssetReaderDecoder { ) { if let Some(last_active) = &last_active_frame { - let frame_to_remove = if requested_frame > *last_active { - *cache.keys().next().unwrap() + let from_oldest = if requested_frame > *last_active { + true } else if requested_frame < *last_active { - *cache.keys().next_back().unwrap() + false } else { - let min = *cache.keys().min().unwrap(); - let max = *cache.keys().max().unwrap(); - if current_frame > max { min } else { max } + current_frame > *cache.keys().next_back().unwrap() }; - if let Some(removed) = cache.remove(&frame_to_remove) { + let victim = cache_eviction_victim(&cache, from_oldest, |frame| { + Some(frame) == vended_before || pinned_frames.contains(&frame) + }); + if let Some(removed) = victim.and_then(|frame| cache.remove(&frame)) + { cache_bytes = cache_bytes.saturating_sub(removed.byte_len()); } } else { @@ -1093,6 +1120,26 @@ impl AVAssetReaderDecoder { .flatten() .max_by_key(|d| d.number); + // The reader vends every sample in pts order, so the + // frame it vended just before this one is the newest + // at or before the request. When that frame is no + // longer held anywhere, any other answer is stale or + // from the future: retry once from a fresh seek, which + // re-vends and keeps it. + if !req.retried + && answer_misses_previous_vend( + vended_before, + req.frame, + best_before.as_ref().map(|d| d.number), + ) + { + retry_requests.push(PendingRequest { + retried: true, + ..req + }); + continue; + } + if let Some(data) = best_before { gap_hold = Some(data.clone()); *last_sent_frame.borrow_mut() = Some(data.clone()); @@ -1133,6 +1180,10 @@ impl AVAssetReaderDecoder { decoder.is_done = true; } + for request in retry_requests.into_iter().rev() { + deferred_requests.push_front(request); + } + let last_frame_in_cache = cache.keys().max().copied().unwrap_or(0); if frames_iterated > 0 { this.decoders[decoder_idx] @@ -1306,6 +1357,45 @@ impl AVAssetReaderDecoder { } } +fn reader_passed_request(prev_vended: Option, requested_frame: u32) -> bool { + prev_vended.is_some_and(|prev| prev >= requested_frame) +} + +// A request the reader steps over is answered by the frame it vended just +// before, the newest one at or before the request. Any other answer is stale. +fn answer_misses_previous_vend( + vended_before: Option, + requested_frame: u32, + answer: Option, +) -> bool { + vended_before + .filter(|&number| number <= requested_frame) + .is_some_and(|expected| answer != Some(expected)) +} + +// Drops from the end the cache is trimmed from, skipping pinned frames unless +// every frame is pinned. +fn cache_eviction_victim( + cache: &BTreeMap, + from_oldest: bool, + is_pinned: impl Fn(u32) -> bool, +) -> Option { + if from_oldest { + cache + .keys() + .copied() + .find(|&frame| !is_pinned(frame)) + .or_else(|| cache.keys().next().copied()) + } else { + cache + .keys() + .rev() + .copied() + .find(|&frame| !is_pinned(frame)) + .or_else(|| cache.keys().next_back().copied()) + } +} + fn should_grow_decoder_pool( is_scrubbing: bool, needs_reset: bool, @@ -1385,6 +1475,44 @@ mod tests { )); } + #[test] + fn a_reader_that_vended_the_request_cannot_serve_it_again() { + assert!(!reader_passed_request(None, 10)); + assert!(!reader_passed_request(Some(9), 10)); + assert!(reader_passed_request(Some(10), 10)); + assert!(reader_passed_request(Some(57), 54)); + } + + #[test] + fn only_the_frame_vended_before_a_gap_answers_requests_inside_it() { + assert!(!answer_misses_previous_vend(Some(1054), 1055, Some(1054))); + assert!(answer_misses_previous_vend(Some(1054), 1055, Some(1052))); + assert!(answer_misses_previous_vend(Some(751), 752, Some(421))); + assert!(answer_misses_previous_vend(Some(751), 752, None)); + assert!(!answer_misses_previous_vend(None, 752, Some(421))); + assert!(!answer_misses_previous_vend(Some(760), 752, Some(751))); + } + + #[test] + fn cache_eviction_skips_pinned_frames() { + let cache: BTreeMap = [415, 419, 421, 749, 751] + .into_iter() + .map(|f| (f, ())) + .collect(); + assert_eq!(cache_eviction_victim(&cache, true, |_| false), Some(415)); + assert_eq!(cache_eviction_victim(&cache, false, |_| false), Some(751)); + assert_eq!( + cache_eviction_victim(&cache, false, |f| f == 751), + Some(749) + ); + assert_eq!(cache_eviction_victim(&cache, true, |f| f < 420), Some(421)); + assert_eq!(cache_eviction_victim(&cache, true, |_| true), Some(415)); + assert_eq!( + cache_eviction_victim(&BTreeMap::::new(), true, |_| false), + None + ); + } + #[test] fn decoder_pool_grows_only_for_unserved_non_sequential_access() { assert!(should_grow_decoder_pool(true, true, 1, 5)); From 990d1d35280e29fad6d4e2b0b42501b74f6a9794 Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Fri, 16 Oct 2026 11:01:17 +0900 Subject: [PATCH 4/8] fix(rendering): answer requests before a track's first frame with that frame --- crates/rendering/src/decoder/avassetreader.rs | 58 +++++++++++++------ 1 file changed, 39 insertions(+), 19 deletions(-) diff --git a/crates/rendering/src/decoder/avassetreader.rs b/crates/rendering/src/decoder/avassetreader.rs index f8acb4d56d5..730d7fb37ad 100644 --- a/crates/rendering/src/decoder/avassetreader.rs +++ b/crates/rendering/src/decoder/avassetreader.rs @@ -544,10 +544,9 @@ impl AVAssetReaderDecoder { fn select_best_decoder( &mut self, - requested_frame: u32, requested_time: f32, is_scrubbing: bool, - must_reset: bool, + must_reset: impl Fn(&DecoderInstance) -> bool, ) -> (usize, bool) { let decoder_count = self.decoders.len(); let (best_id, _distance, needs_reset) = if is_scrubbing { @@ -563,16 +562,7 @@ impl AVAssetReaderDecoder { }; let decoder_idx = best_id.min(decoder_count.saturating_sub(1)); - // Pool positions are capped to the newest request, so a reader that - // decoded ahead can look usable for a request it has already passed. - // It can never vend that frame again, and reading on would answer - // with a stale or later frame, so it has to seek back first. - let needs_reset = needs_reset - || must_reset - || self - .decoders - .get(decoder_idx) - .is_some_and(|decoder| reader_passed_request(decoder.prev_vended, requested_frame)); + let needs_reset = needs_reset || self.decoders.get(decoder_idx).is_some_and(must_reset); let max_decoder_count = self .pool_manager @@ -689,7 +679,7 @@ impl AVAssetReaderDecoder { } }; let mut this = None::; - let mut keyframe_index = None; + let mut keyframe_index: Option> = None; let init_result = DecoderInitResult { width, @@ -883,6 +873,28 @@ impl AVAssetReaderDecoder { } pending_requests = still_unfulfilled; + // A track can start after the recording does. Before its first + // frame, that frame is the answer; serving it from the cache keeps + // the reader from stepping ahead on every such request. + let stream_start_frame = keyframe_index + .as_ref() + .and_then(|index| index.keyframes().first()) + .map(|&(_, secs)| (secs * f64::from(fps)).round() as u32); + if let Some(start) = stream_start_frame + && let Some(first) = cache.get(&start).map(|cached| cached.data().clone()) + { + let mut after_start = Vec::with_capacity(pending_requests.len()); + for req in pending_requests.drain(..) { + if req.frame < start { + *last_sent_frame.borrow_mut() = Some(first.clone()); + let _ = req.sender.send(first.to_decoded_frame()); + } else { + after_start.push(req); + } + } + pending_requests = after_start; + } + if pending_requests.is_empty() { continue; } @@ -922,12 +934,20 @@ impl AVAssetReaderDecoder { } let this = this.as_mut().expect("Video reader initialized above"); - let (decoder_idx, was_reset) = this.select_best_decoder( - requested_frame, - requested_time, - is_scrubbing, - pending_requests.iter().any(|request| request.retried), - ); + // Pool positions are capped to the newest request, so a reader that + // decoded ahead can look usable for a request it has already passed. + // It can never vend that frame again, and reading on would answer + // with a stale or later frame, so it has to seek back first. Before + // the stream's first frame nothing better exists, so a seek is wasted. + let retrying = pending_requests.iter().any(|request| request.retried); + let before_stream_start = + stream_start_frame.is_some_and(|start| requested_frame < start); + let (decoder_idx, was_reset) = + this.select_best_decoder(requested_time, is_scrubbing, |decoder| { + retrying + || (!before_stream_start + && reader_passed_request(decoder.prev_vended, requested_frame)) + }); let cache_min = if was_reset { min_requested_frame.saturating_sub(FRAME_CACHE_SIZE as u32 * 2) From 5ca972e170a7ff5ea6123123b57956ab8720db26 Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Fri, 16 Oct 2026 11:01:17 +0900 Subject: [PATCH 5/8] fix(video-decode): start AVAssetReader seeks on the exact keyframe tick --- crates/video-decode/src/avassetreader.rs | 68 ++++++++++++++++++------ 1 file changed, 53 insertions(+), 15 deletions(-) diff --git a/crates/video-decode/src/avassetreader.rs b/crates/video-decode/src/avassetreader.rs index 14f1c12051f..bc29aba9ea8 100644 --- a/crates/video-decode/src/avassetreader.rs +++ b/crates/video-decode/src/avassetreader.rs @@ -14,6 +14,7 @@ use tokio::runtime::Handle as TokioHandle; #[derive(Clone)] pub struct KeyframeIndex { keyframes: Vec<(u32, f64)>, + time_base: ffmpeg::Rational, fps: f64, duration_secs: f64, pixel_format: Option, @@ -276,6 +277,7 @@ impl KeyframeIndex { Ok(Self { keyframes, + time_base, fps, duration_secs, pixel_format, @@ -401,20 +403,44 @@ impl KeyframeIndex { pub fn cached_video_info(&self) -> Option<(cv::PixelFormat, u32, u32)> { self.pixel_format.map(|pf| (pf, self.width, self.height)) } -} -fn compute_seek_time(keyframe_index: Option<&Arc>, requested_time: f32) -> f32 { - if let Some(kf_index) = keyframe_index { - let keyframes = kf_index.keyframes(); - // Rounded frame numbers can alias keyframes across a VFR gap and seek - // past the image that should still be held at the requested time. - let after_request = - keyframes.partition_point(|(_, time)| *time <= f64::from(requested_time)); - if let Some((_, keyframe_time)) = keyframes.get(after_request.saturating_sub(1)) { - return *keyframe_time as f32; + // The reader decodes from the sync sample at or before its start time, so + // a start rounded to just below a keyframe decodes the whole previous GOP + // and vends its last frame retimed to the start. Starting on the exact + // stream tick keeps the decode on the requested keyframe. + fn exact_time(&self, secs: f64) -> cm::Time { + let numerator = i64::from(self.time_base.numerator()); + let denominator = self.time_base.denominator(); + if numerator <= 0 || denominator <= 0 { + return cm::Time::with_secs(secs, 100); } + let ticks = (secs * f64::from(denominator) / numerator as f64).round() as i64; + cm::Time::new(ticks * numerator, denominator) } - requested_time +} + +fn seek_keyframe_time(keyframe_index: &KeyframeIndex, requested_time: f32) -> Option { + let keyframes = keyframe_index.keyframes(); + // Rounded frame numbers can alias keyframes across a VFR gap and seek + // past the image that should still be held at the requested time. + let after_request = keyframes.partition_point(|(_, time)| *time <= f64::from(requested_time)); + keyframes + .get(after_request.saturating_sub(1)) + .map(|(_, time)| *time) +} + +fn compute_seek_time(keyframe_index: Option<&Arc>, requested_time: f32) -> f32 { + keyframe_index + .and_then(|index| seek_keyframe_time(index, requested_time)) + .map_or(requested_time, |time| time as f32) +} + +fn reader_start_time(keyframe_index: Option<&Arc>, requested_time: f32) -> cm::Time { + keyframe_index + .and_then(|index| { + seek_keyframe_time(index, requested_time).map(|time| index.exact_time(time)) + }) + .unwrap_or_else(|| cm::Time::with_secs(requested_time as f64, 100)) } pub struct AVAssetReaderDecoder { @@ -490,7 +516,7 @@ impl AVAssetReaderDecoder { let source = Self::get_reader_track_output( &path, - seek_time, + reader_start_time(keyframe_index.as_ref(), start_time), &tokio_handle, pixel_format, width, @@ -516,7 +542,7 @@ impl AVAssetReaderDecoder { self.source = Self::get_reader_track_output( &self.path, - seek_time, + reader_start_time(self.keyframe_index.as_ref(), requested_time), &self.tokio_handle, self.pixel_format, self.width, @@ -558,14 +584,14 @@ impl AVAssetReaderDecoder { fn get_reader_track_output( path: &Path, - time: f32, + start: cm::Time, handle: &TokioHandle, pixel_format: cv::PixelFormat, width: u32, height: u32, ) -> Result { let time_range = cm::TimeRange { - start: cm::Time::with_secs(time as f64, 100), + start, duration: cm::Time::infinity(), }; @@ -723,6 +749,7 @@ mod tests { fn seek_index(keyframes: Keyframes) -> Arc { Arc::new(KeyframeIndex { keyframes, + time_base: ffmpeg::Rational::new(1, 600), fps: 2.0, duration_secs: 8.0, pixel_format: None, @@ -755,6 +782,17 @@ mod tests { assert_eq!(compute_seek_time(None, 3.0), 3.0); } + #[test] + fn reader_starts_on_the_exact_keyframe_tick() { + let index = seek_index(vec![(0, 0.0), (1, 577.0 / 600.0), (3, 1147.0 / 600.0)]); + let start = reader_start_time(Some(&index), 1.5); + assert_eq!((start.value, start.scale), (577, 600)); + let start = reader_start_time(Some(&index), 1.92); + assert_eq!((start.value, start.scale), (1147, 600)); + let start = reader_start_time(None, 1.5); + assert_eq!((start.value, start.scale), (150, 100)); + } + fn both_indexes(path: &Path) -> Option<(Option, Keyframes)> { ffmpeg::init().unwrap(); let mut input = avformat::input(path).ok()?; From b23754a6fa11a3b18558f32583a698502d6380c6 Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Fri, 16 Oct 2026 11:01:17 +0900 Subject: [PATCH 6/8] fix(rendering): reset a reused decoder instead of answering with a frame from an older seek --- crates/rendering/src/decoder/avassetreader.rs | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/crates/rendering/src/decoder/avassetreader.rs b/crates/rendering/src/decoder/avassetreader.rs index 730d7fb37ad..c46b6368aea 100644 --- a/crates/rendering/src/decoder/avassetreader.rs +++ b/crates/rendering/src/decoder/avassetreader.rs @@ -385,6 +385,8 @@ struct DecoderInstance { /// Lives on the instance because the reader vends in pts order between /// resets, but request batches may be served by different pool decoders. prev_vended: Option, + /// First frame of the uninterrupted vend run that ends at `prev_vended`. + run_start: Option, _permit: OwnedSemaphorePermit, } @@ -410,6 +412,7 @@ impl DecoderInstance { tokio_handle, keyframe_index, prev_vended: None, + run_start: None, _permit: permit, }) } @@ -421,6 +424,7 @@ impl DecoderInstance { self.frames_iter_valid = true; self.health.reset_counters(); self.prev_vended = None; + self.run_start = None; } Err(e) => { tracing::error!( @@ -453,12 +457,21 @@ impl DecoderInstance { self.frames_iter_valid = true; self.health = DecoderHealth::new(); self.prev_vended = None; + self.run_start = None; Ok(()) } fn current_position(&self) -> f32 { self.inner.current_position_secs() } + + fn would_serve_stale_frame(&self, frame: u32, cache: &BTreeMap) -> bool { + reuse_would_serve_stale_frame( + self.run_start, + self.prev_vended, + cache.range(..=frame).next_back().map(|(&cached, _)| cached), + ) + } } pub struct AVAssetReaderDecoder { @@ -945,6 +958,7 @@ impl AVAssetReaderDecoder { let (decoder_idx, was_reset) = this.select_best_decoder(requested_time, is_scrubbing, |decoder| { retrying + || decoder.would_serve_stale_frame(requested_frame, &cache) || (!before_stream_start && reader_passed_request(decoder.prev_vended, requested_frame)) }); @@ -1028,6 +1042,9 @@ impl AVAssetReaderDecoder { { record_pts_hole(&mut pts_holes, prev, current_frame); } + if decoder.prev_vended.is_none() { + decoder.run_start = Some(current_frame); + } decoder.prev_vended = Some(current_frame); let position_secs = current_frame as f32 / fps as f32; @@ -1416,6 +1433,24 @@ fn cache_eviction_victim( } } +// Requests the reader has already passed, or that fall in a gap before its +// next frame, are answered with the newest cached frame at or before them. +// That frame is only trustworthy when this reader vended it in its current +// run; an older one comes from an unrelated seek and shows stale content. +// With nothing cached at or before the request, whether the reader has +// passed it or a gap follows its evicted last frame, the only answer left is +// a later frame instead of the one on screen. +fn reuse_would_serve_stale_frame( + run_start: Option, + prev_vended: Option, + newest_cached_at_or_before: Option, +) -> bool { + let (Some(run_start), Some(_)) = (run_start, prev_vended) else { + return false; + }; + newest_cached_at_or_before.is_none_or(|cached| cached < run_start) +} + fn should_grow_decoder_pool( is_scrubbing: bool, needs_reset: bool, @@ -1533,6 +1568,31 @@ mod tests { ); } + #[test] + fn reused_readers_never_answer_with_frames_from_an_unrelated_seek() { + assert!(!reuse_would_serve_stale_frame(None, None, Some(10))); + assert!(!reuse_would_serve_stale_frame(Some(40), Some(55), Some(48))); + assert!(!reuse_would_serve_stale_frame(Some(40), Some(55), Some(40))); + assert!(reuse_would_serve_stale_frame(Some(40), Some(55), Some(39))); + assert!(reuse_would_serve_stale_frame(Some(40), Some(55), Some(10))); + assert!(reuse_would_serve_stale_frame(Some(40), Some(48), None)); + } + + #[test] + fn returning_to_a_passed_hold_after_its_frame_is_evicted_resets_the_reader() { + let (run_start, held, vended) = (40, 48, 55); + assert!(!reuse_would_serve_stale_frame( + Some(run_start), + Some(vended), + Some(held) + )); + assert!(reuse_would_serve_stale_frame( + Some(run_start), + Some(vended), + None + )); + } + #[test] fn decoder_pool_grows_only_for_unserved_non_sequential_access() { assert!(should_grow_decoder_pool(true, true, 1, 5)); From 6188bc50fcff6c74c6ad4aedda852fb20ac9660d Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Fri, 16 Oct 2026 21:19:01 +0900 Subject: [PATCH 7/8] test: remove the decoder model entries the AVAssetReader fixes resolve --- .../src/decoder/model/expectations.toml | 234 ++---------------- crates/video-decode/src/avassetreader.rs | 1 + 2 files changed, 27 insertions(+), 208 deletions(-) diff --git a/crates/rendering/src/decoder/model/expectations.toml b/crates/rendering/src/decoder/model/expectations.toml index 80a6f350a0f..9dd4ccd5245 100644 --- a/crates/rendering/src/decoder/model/expectations.toml +++ b/crates/rendering/src/decoder/model/expectations.toml @@ -1034,14 +1034,7 @@ link = "" [[xfail]] case = "decoder-model/ffmpeg/camera-fast/random-seeks/playback" class = "overread" -seeds = "7" -reason = "Camera runaway: when the pts-hole map is full it evicts the narrowest hole, so with two requests per source frame the reader reads one frame further ahead per hole until answers come from the future" -link = "https://github.com/CapSoftware/Cap/pull/2595" - -[[xfail]] -case = "decoder-model/ffmpeg/camera-fast/random-seeks/playback" -class = "overread" -seeds = "0-4 6 8-12 14-16 18 19 22-32 34-51 54-59" +seeds = "0-4 6-12 14-16 18 19 22-32 34-51 54-59" reason = "Camera runaway: when the pts-hole map is full it evicts the narrowest hole, so with two requests per source frame the reader reads one frame further ahead per hole until answers come from the future" link = "https://github.com/CapSoftware/Cap/pull/2595" @@ -1055,14 +1048,7 @@ link = "" [[xfail]] case = "decoder-model/ffmpeg/camera-fast/sequential/export" class = "grid-rounding" -seeds = "4 11 22 27 29" -reason = "Camera runaway: when the pts-hole map is full it evicts the narrowest hole, so with two requests per source frame the reader reads one frame further ahead per hole until answers come from the future" -link = "https://github.com/CapSoftware/Cap/pull/2595" - -[[xfail]] -case = "decoder-model/ffmpeg/camera-fast/sequential/export" -class = "grid-rounding" -seeds = "3 6 28" +seeds = "3 4 6 11 22 27-29" reason = "Camera runaway: when the pts-hole map is full it evicts the narrowest hole, so with two requests per source frame the reader reads one frame further ahead per hole until answers come from the future" link = "https://github.com/CapSoftware/Cap/pull/2595" @@ -1104,14 +1090,7 @@ link = "https://github.com/CapSoftware/Cap/pull/2595" [[xfail]] case = "decoder-model/ffmpeg/camera/sequential/export" class = "grid-rounding" -seeds = "0 2 3" -reason = "Camera runaway: when the pts-hole map is full it evicts the narrowest hole, so with two requests per source frame the reader reads one frame further ahead per hole until answers come from the future" -link = "https://github.com/CapSoftware/Cap/pull/2595" - -[[xfail]] -case = "decoder-model/ffmpeg/camera/sequential/export" -class = "grid-rounding" -seeds = "1" +seeds = "0-3" reason = "Camera runaway: when the pts-hole map is full it evicts the narrowest hole, so with two requests per source frame the reader reads one frame further ahead per hole until answers come from the future" link = "https://github.com/CapSoftware/Cap/pull/2595" @@ -1167,56 +1146,28 @@ link = "https://github.com/CapSoftware/Cap/pull/2595" [[xfail]] case = "decoder-model/ffmpeg/cfr/keyframe-seeks/playback" class = "future" -seeds = "0 38 46 56 62 69 83 90 93 104 110 133 145 169 181 183 186 205 207 239 253 256 260 299" -reason = "NEW: the FFmpeg decoder seeks with the request time rounded to the stream tick, so a request a fraction of a tick before a keyframe lands on that keyframe and is answered with a later frame" -link = "" - -[[xfail]] -case = "decoder-model/ffmpeg/cfr/keyframe-seeks/playback" -class = "future" -seeds = "1 4 6 17 18 20 23 41 42 48 50 53 63 65 101 108 115 118 119 122 124 130 136 140 142 147 149-151 154 161 167 188 192 199-201 203 209 212 216 219 223 235 237 245 248 250 261 263 268 270 271 275 285 288 290 291" +seeds = "0 1 4 6 17 18 20 23 38 41 42 46 48 50 53 56 62 63 65 69 83 90 93 101 104 108 110 115 118 119 122 124 130 133 136 140 142 145 147 149-151 154 161 167 169 181 183 186 188 192 199-201 203 205 207 209 212 216 219 223 235 237 239 245 248 250 253 256 260 261 263 268 270 271 275 285 288 290 291 299" reason = "NEW: the FFmpeg decoder seeks with the request time rounded to the stream tick, so a request a fraction of a tick before a keyframe lands on that keyframe and is answered with a later frame" link = "" [[xfail]] case = "decoder-model/ffmpeg/cfr/keyframe-seeks/playback" class = "grid-rounding" -seeds = "0 4 5 8 20 29 48 53 62 63 69 115 121 130 136 142 147 151 154 161 167 173 188 200 201 207 212 216 219 223 225 226 234 235 237-239 245 248 254 255 263 268 270 275 278 285 291 299" -reason = "NEW: the FFmpeg decoder seeks to the exact request time, so after a reset it answers with the frame on screen at that time even when its rounded grid label is after the request; sequential playback shows the previous frame for the same request" -link = "" - -[[xfail]] -case = "decoder-model/ffmpeg/cfr/keyframe-seeks/playback" -class = "grid-rounding" -seeds = "2 6 9 12 17 19 23 27 28 30 31 33 38 39 41 42 46 50 52 54 56 60 61 65 68 71 72 74 75 79 82-84 86 90 92 93 96 98 99 101-104 106-108 110 111 119 122 124 125 129 131 133 135 137 138 140 141 143 145 148-150 152 156 164 165 168-172 177-179 181-183 186 189-192 194 195 197-199 203 205 206 209-211 222 227 236 241 243 250 253 256 258 265 266 273 274 283 284 287 290 294 295 297" +seeds = "0 2 4-6 8 9 12 17 19 20 23 27-31 33 38 39 41 42 46 48 50 52-54 56 60-63 65 68 69 71 72 74 75 79 82-84 86 90 92 93 96 98 99 101-104 106-108 110 111 115 119 121 122 124 125 129-131 133 135-138 140-143 145 147-152 154 156 161 164 165 167-173 177-179 181-183 186 188-192 194 195 197-201 203 205-207 209-212 216 219 222 223 225-227 234-239 241 243 245 248 250 253-256 258 263 265 266 268 270 273-275 278 283-285 287 290 291 294 295 297 299" reason = "NEW: the FFmpeg decoder seeks to the exact request time, so after a reset it answers with the frame on screen at that time even when its rounded grid label is after the request; sequential playback shows the previous frame for the same request" link = "" [[xfail]] case = "decoder-model/ffmpeg/cfr/keyframe-seeks/playback" class = "overread" -seeds = "0 4-8 12 15 16 19 20 26 31 37 46 47 51 53 62 63 69 70 72 80 88 91 95 100 101 111 112 115 117 121 125 127 130 136 138 142 147 153 155 161 167 170 172 173 175 183 196 200 202-204 207-212 216 219 225-227 230 234 235 237-241 244 245 253-256 263 265 275 278 285 286 291 292 294 297 299" -reason = "NEW: after a backward request served from the FFmpeg decoder's cache, the reader is left far ahead and the next request reads past the frame it needs" -link = "" - -[[xfail]] -case = "decoder-model/ffmpeg/cfr/keyframe-seeks/playback" -class = "overread" -seeds = "2 11 14 27 29 30 33 38 39 50 52 54 56 59-61 67 68 71 74 82 83 86 90 92 93 96 98 99 102-104 106 108 110 113 119 126 129 131 133 135 137 139 144 145 148 152 156 162 164 165 168 169 171 177-179 181 182 185 186 189 190 197 198 205 222 229 236 243 247 258 260 266 274 287 295" +seeds = "0 2 4-8 11 12 14-16 19 20 26 27 29-31 33 37-39 46 47 50-54 56 59-63 67-72 74 80 82 83 86 88 90-93 95 96 98-104 106 108 110-113 115 117 119 121 125-127 129-131 133 135-139 142 144 145 147 148 152 153 155 156 161 162 164 165 167-173 175 177-179 181-183 185 186 189 190 196-198 200 202-205 207-212 216 219 222 225-227 229 230 234-241 243-245 247 253-256 258 260 263 265 266 274 275 278 285-287 291 292 294 295 297 299" reason = "NEW: after a backward request served from the FFmpeg decoder's cache, the reader is left far ahead and the next request reads past the frame it needs" link = "" [[xfail]] case = "decoder-model/ffmpeg/cfr/keyframe-seeks/playback" class = "stale" -seeds = "4 6 20 53 63 101 108 115 119 130 136 147 154 161 200 203 209 212 216 219 235 237 245 275 291" -reason = "NEW: the FFmpeg decoder answers backward requests with a cached frame up to 2 grid frames older than the requested one, answers grid frames 0 to 2 with the first frame, and after a backward request served from the cache it reads forward from a reader that is still far ahead, answering with an older cached frame" -link = "" - -[[xfail]] -case = "decoder-model/ffmpeg/cfr/keyframe-seeks/playback" -class = "stale" -seeds = "0 2 5 7 8 12 14-16 19 26 27 29-31 33 37-39 46 47 51 52 54 56 59-62 67-72 74 80 82 83 86 88 90-93 95 96 98-100 102-104 106 110-113 117 121 125-127 129 131 133 135 137-139 144 145 148 152 153 155 156 162 164 165 168-173 175 177-179 181-183 186 189 190 196-198 202 204 205 207 208 210 211 215 222 225-227 229 230 234 236 238-241 243 244 247 253-256 258-260 265 269 274 278 286 287 292 294 295 297 299" +seeds = "0 2 4-8 12 14-16 19 20 26 27 29-31 33 37-39 46 47 51-54 56 59-63 67-72 74 80 82 83 86 88 90-93 95 96 98-104 106 108 110-113 115 117 119 121 125-127 129-131 133 135-139 144 145 147 148 152-156 161 162 164 165 168-173 175 177-179 181-183 186 189 190 196-198 200 202-205 207-212 215 216 219 222 225-227 229 230 234-241 243-245 247 253-256 258-260 265 269 274 275 278 286 287 291 292 294 295 297 299" reason = "NEW: the FFmpeg decoder answers backward requests with a cached frame up to 2 grid frames older than the requested one, answers grid frames 0 to 2 with the first frame, and after a backward request served from the cache it reads forward from a reader that is still far ahead, answering with an older cached frame" link = "" @@ -1230,28 +1181,14 @@ link = "" [[xfail]] case = "decoder-model/ffmpeg/cfr/random-seeks/playback" class = "grid-rounding" -seeds = "67 100 254 279" -reason = "NEW: the FFmpeg decoder seeks to the exact request time, so after a reset it answers with the frame on screen at that time even when its rounded grid label is after the request; sequential playback shows the previous frame for the same request" -link = "" - -[[xfail]] -case = "decoder-model/ffmpeg/cfr/random-seeks/playback" -class = "grid-rounding" -seeds = "13 31 40 52 57 61 63 69 74 80 93 117 120 126 140 153 166 180 182 188 196 251 258 276 299" +seeds = "13 31 40 52 57 61 63 67 69 74 80 93 100 117 120 126 140 153 166 180 182 188 196 251 254 258 276 279 299" reason = "NEW: the FFmpeg decoder seeks to the exact request time, so after a reset it answers with the frame on screen at that time even when its rounded grid label is after the request; sequential playback shows the previous frame for the same request" link = "" [[xfail]] case = "decoder-model/ffmpeg/cfr/random-seeks/playback" class = "overread" -seeds = "0 4 10-12 16 18 25 27 30 32 42 50 55 67 73 82 89 100 108 126 128 129 134 137-139 146 147 151 164 166 171 174 178-181 207 217 220 228 232 246 247 250 254 262 273 279 281 289 290 297" -reason = "NEW: after a backward request served from the FFmpeg decoder's cache, the reader is left far ahead and the next request reads past the frame it needs" -link = "" - -[[xfail]] -case = "decoder-model/ffmpeg/cfr/random-seeks/playback" -class = "overread" -seeds = "3 9 13 64 95 101 106 140 148 163 187 204 227 287 299" +seeds = "0 3 4 9-13 16 18 25 27 30 32 42 50 55 64 67 73 82 89 95 100 101 106 108 126 128 129 134 137-140 146-148 151 163 164 166 171 174 178-181 187 204 207 217 220 227 228 232 246 247 250 254 262 273 279 281 287 289 290 297 299" reason = "NEW: after a backward request served from the FFmpeg decoder's cache, the reader is left far ahead and the next request reads past the frame it needs" link = "" @@ -1272,28 +1209,14 @@ link = "" [[xfail]] case = "decoder-model/ffmpeg/cfr/scrub/playback" class = "grid-rounding" -seeds = "27 33 39 41 50 68 86 90 93 101 104 109 121 123 124 133 144 149 155 160 165 170 173 179 180 183 186 192 199" -reason = "NEW: the FFmpeg decoder seeks to the exact request time, so after a reset it answers with the frame on screen at that time even when its rounded grid label is after the request; sequential playback shows the previous frame for the same request" -link = "" - -[[xfail]] -case = "decoder-model/ffmpeg/cfr/scrub/playback" -class = "grid-rounding" -seeds = "30 62 63 84 106 122 126 143 176 187" +seeds = "27 30 33 39 41 50 62 63 68 84 86 90 93 101 104 106 109 121-124 126 133 143 144 149 155 160 165 170 173 176 179 180 183 186 187 192 199" reason = "NEW: the FFmpeg decoder seeks to the exact request time, so after a reset it answers with the frame on screen at that time even when its rounded grid label is after the request; sequential playback shows the previous frame for the same request" link = "" [[xfail]] case = "decoder-model/ffmpeg/cfr/scrub/playback" class = "overread" -seeds = "1 4 7 8 11 14 16-20 26 27 36-38 40-43 46 52 53 55 56 58 64 68 70 71 73 74 77 78 80 82 83 85 88 90-94 98-101 105 107 110 113 114 117 121 124 127 128 131-133 135 137 141 142 145 148 149 151 152 154 155 157 162 163 167 173-175 177 179 181 183 187 189 192 193 198" -reason = "NEW: after a backward request served from the FFmpeg decoder's cache, the reader is left far ahead and the next request reads past the frame it needs" -link = "" - -[[xfail]] -case = "decoder-model/ffmpeg/cfr/scrub/playback" -class = "overread" -seeds = "0 3 13 21 31 33-35 39 44 50 51 54 67 75 84 86 97 103 104 106 108 109 111 115 118 119 122 123 134 138-140 144 147 158-161 165 168 170 176 180 185 186 197" +seeds = "0 1 3 4 7 8 11 13 14 16-21 26 27 31 33-44 46 50-56 58 64 67 68 70 71 73-75 77 78 80 82-86 88 90-94 97-101 103-111 113-115 117-119 121-124 127 128 131-135 137-142 144 145 147-149 151 152 154 155 157-163 165 167 168 170 173-177 179-181 183 185-187 189 192 193 197 198" reason = "NEW: after a backward request served from the FFmpeg decoder's cache, the reader is left far ahead and the next request reads past the frame it needs" link = "" @@ -1328,14 +1251,7 @@ link = "" [[xfail]] case = "decoder-model/ffmpeg/cfr/timeline/export" class = "overread" -seeds = "12 42 43 64 133" -reason = "NEW: after a backward request served from the FFmpeg decoder's cache, the reader is left far ahead and the next request reads past the frame it needs" -link = "" - -[[xfail]] -case = "decoder-model/ffmpeg/cfr/timeline/export" -class = "overread" -seeds = "53 66 103 107 132" +seeds = "12 42 43 53 64 66 103 107 132 133" reason = "NEW: after a backward request served from the FFmpeg decoder's cache, the reader is left far ahead and the next request reads past the frame it needs" link = "" @@ -1349,28 +1265,14 @@ link = "" [[xfail]] case = "decoder-model/ffmpeg/cfr/transition/export" class = "future" -seeds = "73" -reason = "NEW: the FFmpeg decoder seeks with the request time rounded to the stream tick, so a request a fraction of a tick before a keyframe lands on that keyframe and is answered with a later frame" -link = "" - -[[xfail]] -case = "decoder-model/ffmpeg/cfr/transition/export" -class = "future" -seeds = "6 66" +seeds = "6 66 73" reason = "NEW: the FFmpeg decoder seeks with the request time rounded to the stream tick, so a request a fraction of a tick before a keyframe lands on that keyframe and is answered with a later frame" link = "" [[xfail]] case = "decoder-model/ffmpeg/cfr/transition/export" class = "grid-rounding" -seeds = "0 15 22 23 25 29 66 73" -reason = "NEW: the FFmpeg decoder seeks to the exact request time, so after a reset it answers with the frame on screen at that time even when its rounded grid label is after the request; sequential playback shows the previous frame for the same request" -link = "" - -[[xfail]] -case = "decoder-model/ffmpeg/cfr/transition/export" -class = "grid-rounding" -seeds = "5-7 38 41 45 53 56 61 62 72" +seeds = "0 5-7 15 22 23 25 29 38 41 45 53 56 61 62 66 72 73" reason = "NEW: the FFmpeg decoder seeks to the exact request time, so after a reset it answers with the frame on screen at that time even when its rounded grid label is after the request; sequential playback shows the previous frame for the same request" link = "" @@ -1398,14 +1300,7 @@ link = "" [[xfail]] case = "decoder-model/ffmpeg/cfr/transition/playback" class = "grid-rounding" -seeds = "14 20 21 25 31 53 54 57 61 66" -reason = "NEW: the FFmpeg decoder seeks to the exact request time, so after a reset it answers with the frame on screen at that time even when its rounded grid label is after the request; sequential playback shows the previous frame for the same request" -link = "" - -[[xfail]] -case = "decoder-model/ffmpeg/cfr/transition/playback" -class = "grid-rounding" -seeds = "9 19 29 42 51 58 64 65" +seeds = "9 14 19-21 25 29 31 42 51 53 54 57 58 61 64-66" reason = "NEW: the FFmpeg decoder seeks to the exact request time, so after a reset it answers with the frame on screen at that time even when its rounded grid label is after the request; sequential playback shows the previous frame for the same request" link = "" @@ -1433,28 +1328,14 @@ link = "" [[xfail]] case = "decoder-model/ffmpeg/dense/random-seeks/playback" class = "grid-rounding" -seeds = "17 22 33" -reason = "NEW: the FFmpeg decoder seeks to the exact request time, so after a reset it answers with the frame on screen at that time even when its rounded grid label is after the request; sequential playback shows the previous frame for the same request" -link = "" - -[[xfail]] -case = "decoder-model/ffmpeg/dense/random-seeks/playback" -class = "grid-rounding" -seeds = "1 7 15 21 25 32 35 36 45 47 48 55 64 70 82 86 90 91 100 103 114 115 121 126 147" +seeds = "1 7 15 17 21 22 25 32 33 35 36 45 47 48 55 64 70 82 86 90 91 100 103 114 115 121 126 147" reason = "NEW: the FFmpeg decoder seeks to the exact request time, so after a reset it answers with the frame on screen at that time even when its rounded grid label is after the request; sequential playback shows the previous frame for the same request" link = "" [[xfail]] case = "decoder-model/ffmpeg/dense/random-seeks/playback" class = "overread" -seeds = "0 4 14 15 17 20 22 25 27 29 31 32 38 43 50 54 56 57 61 65 67 71 84 91 92 97 99 102 114 116 123 126 128 130 135 137 140 144" -reason = "NEW: after a backward request served from the FFmpeg decoder's cache, the reader is left far ahead and the next request reads past the frame it needs" -link = "" - -[[xfail]] -case = "decoder-model/ffmpeg/dense/random-seeks/playback" -class = "overread" -seeds = "33 86" +seeds = "0 4 14 15 17 20 22 25 27 29 31-33 38 43 50 54 56 57 61 65 67 71 84 86 91 92 97 99 102 114 116 123 126 128 130 135 137 140 144" reason = "NEW: after a backward request served from the FFmpeg decoder's cache, the reader is left far ahead and the next request reads past the frame it needs" link = "" @@ -1496,56 +1377,28 @@ link = "https://github.com/CapSoftware/Cap/pull/2595" [[xfail]] case = "decoder-model/ffmpeg/vfr/keyframe-seeks/playback" class = "future" -seeds = "3 8 10 46 61 68 69 86 89 99" -reason = "NEW: the FFmpeg decoder seeks with the request time rounded to the stream tick, so a request a fraction of a tick before a keyframe lands on that keyframe and is answered with a later frame" -link = "" - -[[xfail]] -case = "decoder-model/ffmpeg/vfr/keyframe-seeks/playback" -class = "future" -seeds = "2 4 5 9 11 14 20-22 24-27 29 31 32 34 36 37 39-41 48 50 53-56 58 59 62-65 67 70 71 73 76 79 82 84 88 90 91 93-98" +seeds = "2-5 8-11 14 20-22 24-27 29 31 32 34 36 37 39-41 46 48 50 53-56 58 59 61-65 67-71 73 76 79 82 84 86 88-91 93-99" reason = "NEW: the FFmpeg decoder seeks with the request time rounded to the stream tick, so a request a fraction of a tick before a keyframe lands on that keyframe and is answered with a later frame" link = "" [[xfail]] case = "decoder-model/ffmpeg/vfr/keyframe-seeks/playback" class = "grid-rounding" -seeds = "2 10 13 21 22 24 28 32 34 36 37 40 42 46 48 53-55 63-65 68 69 73 76 88 90 91 93-95" -reason = "NEW: the FFmpeg decoder seeks to the exact request time, so after a reset it answers with the frame on screen at that time even when its rounded grid label is after the request; sequential playback shows the previous frame for the same request" -link = "" - -[[xfail]] -case = "decoder-model/ffmpeg/vfr/keyframe-seeks/playback" -class = "grid-rounding" -seeds = "1 3-5 8 9 11 15 16 18 23 25 27 29-31 41 43 44 47 50 51 56-62 66 70-72 74 75 79-87 89 92 96 97 99" +seeds = "1-5 8-11 13 15 16 18 21-25 27-32 34 36 37 40-44 46-48 50 51 53-66 68-76 79-97 99" reason = "NEW: the FFmpeg decoder seeks to the exact request time, so after a reset it answers with the frame on screen at that time even when its rounded grid label is after the request; sequential playback shows the previous frame for the same request" link = "" [[xfail]] case = "decoder-model/ffmpeg/vfr/keyframe-seeks/playback" class = "overread" -seeds = "1-5 7 8 10-14 19-21 24 25 29 34 36 40-43 48 51 53 55 59 62 64 68 73 79 81 86 91 94 96" -reason = "NEW: after a backward request served from the FFmpeg decoder's cache, the reader is left far ahead and the next request reads past the frame it needs" -link = "" - -[[xfail]] -case = "decoder-model/ffmpeg/vfr/keyframe-seeks/playback" -class = "overread" -seeds = "6 9 15 17 18 23 28 30 31 44 46 47 50 52 57 58 60 61 66 69 72 75 76 78 83 84 87 89 97 99" +seeds = "1-15 17-21 23-25 28-31 34 36 40-44 46-48 50-53 55 57-62 64 66 68 69 72 73 75 76 78 79 81 83 84 86 87 89 91 94 96 97 99" reason = "NEW: after a backward request served from the FFmpeg decoder's cache, the reader is left far ahead and the next request reads past the frame it needs" link = "" [[xfail]] case = "decoder-model/ffmpeg/vfr/keyframe-seeks/playback" class = "stale" -seeds = "2 4 9 11 14 20 21 24 25 29 31 34 40 41 48 55 59 62 64 73 76 79 84 91 93 94 96" -reason = "NEW: the FFmpeg decoder answers backward requests with a cached frame up to 2 grid frames older than the requested one, answers grid frames 0 to 2 with the first frame, and after a backward request served from the cache it reads forward from a reader that is still far ahead, answering with an older cached frame" -link = "" - -[[xfail]] -case = "decoder-model/ffmpeg/vfr/keyframe-seeks/playback" -class = "stale" -seeds = "1 3 7 8 10 12 13 15 17-19 23 28 30 42-44 46 47 51 52 57 60 61 66 68 69 72 75 78 81 83 86 87 89 99" +seeds = "1-4 7-15 17-21 23-25 28-31 34 40-44 46-48 51 52 55 57 59-62 64 66 68 69 72 73 75 76 78 79 81 83 84 86 87 89 91 93 94 96 99" reason = "NEW: the FFmpeg decoder answers backward requests with a cached frame up to 2 grid frames older than the requested one, answers grid frames 0 to 2 with the first frame, and after a backward request served from the cache it reads forward from a reader that is still far ahead, answering with an older cached frame" link = "" @@ -1566,14 +1419,7 @@ link = "" [[xfail]] case = "decoder-model/ffmpeg/vfr/random-seeks/playback" class = "overread" -seeds = "3 12 18 20 29 53 55 67 79 84 93 120 148 167 177" -reason = "NEW: after a backward request served from the FFmpeg decoder's cache, the reader is left far ahead and the next request reads past the frame it needs" -link = "" - -[[xfail]] -case = "decoder-model/ffmpeg/vfr/random-seeks/playback" -class = "overread" -seeds = "11 80 101 102 104 110 130 157 171 180" +seeds = "3 11 12 18 20 29 53 55 67 79 80 84 93 101 102 104 110 120 130 148 157 167 171 177 180" reason = "NEW: after a backward request served from the FFmpeg decoder's cache, the reader is left far ahead and the next request reads past the frame it needs" link = "" @@ -1594,28 +1440,14 @@ link = "" [[xfail]] case = "decoder-model/ffmpeg/vfr/scrub/playback" class = "grid-rounding" -seeds = "2 37 59 67 81 111" -reason = "NEW: the FFmpeg decoder seeks to the exact request time, so after a reset it answers with the frame on screen at that time even when its rounded grid label is after the request; sequential playback shows the previous frame for the same request" -link = "" - -[[xfail]] -case = "decoder-model/ffmpeg/vfr/scrub/playback" -class = "grid-rounding" -seeds = "23 30 46 48 78 87 114" +seeds = "2 23 30 37 46 48 59 67 78 81 87 111 114" reason = "NEW: the FFmpeg decoder seeks to the exact request time, so after a reset it answers with the frame on screen at that time even when its rounded grid label is after the request; sequential playback shows the previous frame for the same request" link = "" [[xfail]] case = "decoder-model/ffmpeg/vfr/scrub/playback" class = "overread" -seeds = "1 7 12 18 33 34 36 37 43 45 46 49 56 67 70-72 81 101 103 105 111 112" -reason = "NEW: after a backward request served from the FFmpeg decoder's cache, the reader is left far ahead and the next request reads past the frame it needs" -link = "" - -[[xfail]] -case = "decoder-model/ffmpeg/vfr/scrub/playback" -class = "overread" -seeds = "2 4 10 15 19 23 25 28 38 53 54 59 69 75 80 88 91 95 98 107 110 114 119" +seeds = "1 2 4 7 10 12 15 18 19 23 25 28 33 34 36-38 43 45 46 49 53 54 56 59 67 69-72 75 80 81 88 91 95 98 101 103 105 107 110-112 114 119" reason = "NEW: after a backward request served from the FFmpeg decoder's cache, the reader is left far ahead and the next request reads past the frame it needs" link = "" @@ -1643,14 +1475,7 @@ link = "" [[xfail]] case = "decoder-model/ffmpeg/vfr/timeline/export" class = "grid-rounding" -seeds = "20" -reason = "NEW: the FFmpeg decoder seeks to the exact request time, so after a reset it answers with the frame on screen at that time even when its rounded grid label is after the request; sequential playback shows the previous frame for the same request" -link = "" - -[[xfail]] -case = "decoder-model/ffmpeg/vfr/timeline/export" -class = "grid-rounding" -seeds = "77 110" +seeds = "20 77 110" reason = "NEW: the FFmpeg decoder seeks to the exact request time, so after a reset it answers with the frame on screen at that time even when its rounded grid label is after the request; sequential playback shows the previous frame for the same request" link = "" @@ -1678,14 +1503,7 @@ link = "" [[xfail]] case = "decoder-model/ffmpeg/vfr/transition/playback" class = "grid-rounding" -seeds = "10 12 13 66 71 75" -reason = "NEW: the FFmpeg decoder seeks to the exact request time, so after a reset it answers with the frame on screen at that time even when its rounded grid label is after the request; sequential playback shows the previous frame for the same request" -link = "" - -[[xfail]] -case = "decoder-model/ffmpeg/vfr/transition/playback" -class = "grid-rounding" -seeds = "15 16 19 20 28 33 34 43 47 48 56 59 62 68 70 77" +seeds = "10 12 13 15 16 19 20 28 33 34 43 47 48 56 59 62 66 68 70 71 75 77" reason = "NEW: the FFmpeg decoder seeks to the exact request time, so after a reset it answers with the frame on screen at that time even when its rounded grid label is after the request; sequential playback shows the previous frame for the same request" link = "" diff --git a/crates/video-decode/src/avassetreader.rs b/crates/video-decode/src/avassetreader.rs index bc29aba9ea8..c1dc1f7effe 100644 --- a/crates/video-decode/src/avassetreader.rs +++ b/crates/video-decode/src/avassetreader.rs @@ -300,6 +300,7 @@ impl KeyframeIndex { .collect(); Self { keyframes, + time_base: ffmpeg::Rational::new(script.time_base.0, script.time_base.1), fps, duration_secs: script.duration_secs(), pixel_format: Some(cv::PixelFormat::_420V), From 434db16acc814173a2cd176d3b014b60a70083e8 Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Sat, 17 Oct 2026 00:28:41 +0900 Subject: [PATCH 8/8] test: remove the expectations the AVAssetReader fixes resolve CI on this branch reports the camera runaway, keyframe-seek, seek-storm, VFR-hold and late-camera cases passing on macOS and the jitter-camera FFmpeg cases passing on Linux; the FFmpeg and AVAssetReader decoder models no longer produce 159 seed failures. Flaky entries for the same mechanisms are removed where the seek and jitter benchmarks (4 pairs each) and the VFR-hold run no longer show them. --- .../src/decoder/model/expectations.toml | 1134 +---------------- crates/verify/expectations.toml | 140 -- 2 files changed, 10 insertions(+), 1264 deletions(-) diff --git a/crates/rendering/src/decoder/model/expectations.toml b/crates/rendering/src/decoder/model/expectations.toml index 9dd4ccd5245..f2fb6d9a13c 100644 --- a/crates/rendering/src/decoder/model/expectations.toml +++ b/crates/rendering/src/decoder/model/expectations.toml @@ -21,715 +21,10 @@ # model found that no open PR fixes. [[xfail]] -case = "decoder-model/avf/camera-fast/random-seeks/playback" -class = "future" -seeds = "0 7 16 49" -reason = "Camera runaway: when the pts-hole map is full it evicts the narrowest hole, so with two requests per source frame the reader reads one frame further ahead per hole until answers come from the future; aVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2595 https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/camera-fast/random-seeks/playback" -class = "future" -platforms = ["macos"] -seeds = "15 19" -reason = "Camera runaway: when the pts-hole map is full it evicts the narrowest hole, so with two requests per source frame the reader reads one frame further ahead per hole until answers come from the future; aVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2595 https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/camera-fast/random-seeks/playback" -class = "grid-rounding" -seeds = "8 50" -reason = "Camera runaway: when the pts-hole map is full it evicts the narrowest hole, so with two requests per source frame the reader reads one frame further ahead per hole until answers come from the future; aVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2595 https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/camera-fast/random-seeks/playback" -class = "grid-rounding" -platforms = ["macos"] -seeds = "29" -reason = "Camera runaway: when the pts-hole map is full it evicts the narrowest hole, so with two requests per source frame the reader reads one frame further ahead per hole until answers come from the future; aVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2595 https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/camera-fast/random-seeks/playback" -class = "overread" -seeds = "1 2 4 5 7-17 19-25 27-29 31-40 42-44 46-52 54 56 58 59" -reason = "Camera runaway: when the pts-hole map is full it evicts the narrowest hole, so with two requests per source frame the reader reads one frame further ahead per hole until answers come from the future; aVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2595 https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/camera-fast/random-seeks/playback" -class = "overread" -platforms = ["macos"] -seeds = "0 3 30 57" -reason = "Camera runaway: when the pts-hole map is full it evicts the narrowest hole, so with two requests per source frame the reader reads one frame further ahead per hole until answers come from the future; aVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2595 https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/camera-fast/random-seeks/playback" -class = "stale" -seeds = "1 3 6 8 10 15 18-20 24 26 28-30 47 50 52-54 57-59" -reason = "Camera runaway: when the pts-hole map is full it evicts the narrowest hole, so with two requests per source frame the reader reads one frame further ahead per hole until answers come from the future; aVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2595 https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/camera-fast/random-seeks/playback" -class = "stale" -platforms = ["macos"] -seeds = "7 49" -reason = "Camera runaway: when the pts-hole map is full it evicts the narrowest hole, so with two requests per source frame the reader reads one frame further ahead per hole until answers come from the future; aVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2595 https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/camera-fast/sequential/export" -class = "future" -seeds = "0 1 4 6 8 9 11-13 15-18 20 22 23 25-28" -reason = "Camera runaway: when the pts-hole map is full it evicts the narrowest hole, so with two requests per source frame the reader reads one frame further ahead per hole until answers come from the future" -link = "https://github.com/CapSoftware/Cap/pull/2595" - -[[xfail]] -case = "decoder-model/avf/camera-fast/sequential/export" -class = "grid-rounding" -seeds = "0" -reason = "Camera runaway: when the pts-hole map is full it evicts the narrowest hole, so with two requests per source frame the reader reads one frame further ahead per hole until answers come from the future" -link = "https://github.com/CapSoftware/Cap/pull/2595" - -[[xfail]] -case = "decoder-model/avf/camera-fast/sequential/export" -class = "grid-rounding" -platforms = ["macos"] -seeds = "23" -reason = "Camera runaway: when the pts-hole map is full it evicts the narrowest hole, so with two requests per source frame the reader reads one frame further ahead per hole until answers come from the future" -link = "https://github.com/CapSoftware/Cap/pull/2595" - -[[xfail]] -case = "decoder-model/avf/camera-fast/sequential/export" -class = "overread" -seeds = "0-29" -reason = "Camera runaway: when the pts-hole map is full it evicts the narrowest hole, so with two requests per source frame the reader reads one frame further ahead per hole until answers come from the future" -link = "https://github.com/CapSoftware/Cap/pull/2595" - -[[xfail]] -case = "decoder-model/avf/camera-fast/sequential/export" -class = "resets" -platforms = ["macos"] -seeds = "0 1 4 6 8 11-13 15-18 20 22 23 25-27" -reason = "Found once each sequence keeps running after its first wrong answer (full failure sets, runs 38851482697 and 38851482675): reader starts and seeks exceed the budget of two per jump in the request stream later in the sequence (seed index 0 request 1815). AVAssetReader decoder request handling, the mechanisms of this decoder's other entries (pool position capping, reader reuse after a reset, pts holes), which #2638 targets" -link = "https://github.com/CapSoftware/Cap/pull/2638" - -[[xfail]] -case = "decoder-model/avf/camera-fast/sequential/export" -class = "stale" -platforms = ["macos"] -seeds = "15" -reason = "Found once each sequence keeps running after its first wrong answer (full failure sets, runs 38851482697 and 38851482675): a later request is answered with an older sample than the right one (seed index 15 request 3029). AVAssetReader decoder request handling, the mechanisms of this decoder's other entries (pool position capping, reader reuse after a reset, pts holes), which #2638 targets" -link = "https://github.com/CapSoftware/Cap/pull/2638" - -[[xfail]] -case = "decoder-model/avf/camera-fast/sequential/playback" -class = "future" -seeds = "1 4 10 11 14 17 19 24 25 27" -reason = "Camera runaway: when the pts-hole map is full it evicts the narrowest hole, so with two requests per source frame the reader reads one frame further ahead per hole until answers come from the future" -link = "https://github.com/CapSoftware/Cap/pull/2595" - -[[xfail]] -case = "decoder-model/avf/camera-fast/sequential/playback" -class = "grid-rounding" -platforms = ["macos"] -seeds = "19" -reason = "Found once each sequence keeps running after its first wrong answer (full failure sets, runs 38851482697 and 38851482675): an answer is the sample on screen at the exact request time instead of the grid frame (seed index 19 request 3265). AVAssetReader decoder request handling, the mechanisms of this decoder's other entries (pool position capping, reader reuse after a reset, pts holes), which #2638 targets" -link = "https://github.com/CapSoftware/Cap/pull/2638" - -[[xfail]] -case = "decoder-model/avf/camera-fast/sequential/playback" -class = "overread" -seeds = "0-4 6-29" -reason = "Camera runaway: when the pts-hole map is full it evicts the narrowest hole, so with two requests per source frame the reader reads one frame further ahead per hole until answers come from the future" -link = "https://github.com/CapSoftware/Cap/pull/2595" - -[[xfail]] -case = "decoder-model/avf/camera-fast/sequential/playback" -class = "resets" -seeds = "8 28" -reason = "Camera runaway: when the pts-hole map is full it evicts the narrowest hole, so with two requests per source frame the reader reads one frame further ahead per hole until answers come from the future" -link = "https://github.com/CapSoftware/Cap/pull/2595" - -[[xfail]] -case = "decoder-model/avf/camera-fast/sequential/playback" -class = "resets" -platforms = ["macos"] -seeds = "1 4 10 11 14 17 19 24 25 27" -reason = "Camera runaway: when the pts-hole map is full it evicts the narrowest hole, so with two requests per source frame the reader reads one frame further ahead per hole until answers come from the future" -link = "https://github.com/CapSoftware/Cap/pull/2595" - -[[xfail]] -case = "decoder-model/avf/camera/sequential/export" -class = "future" -seeds = "0-2" -reason = "Camera runaway: when the pts-hole map is full it evicts the narrowest hole, so with two requests per source frame the reader reads one frame further ahead per hole until answers come from the future" -link = "https://github.com/CapSoftware/Cap/pull/2595" - -[[xfail]] -case = "decoder-model/avf/camera/sequential/export" -class = "grid-rounding" -platforms = ["macos"] -seeds = "0" -reason = "Found once each sequence keeps running after its first wrong answer (full failure sets, runs 38851482697 and 38851482675): an answer is the sample on screen at the exact request time instead of the grid frame (seed index 0 request 8094). AVAssetReader decoder request handling, the mechanisms of this decoder's other entries (pool position capping, reader reuse after a reset, pts holes), which #2638 targets" -link = "https://github.com/CapSoftware/Cap/pull/2638" - -[[xfail]] -case = "decoder-model/avf/camera/sequential/export" -class = "overread" -seeds = "0-3" -reason = "Camera runaway: when the pts-hole map is full it evicts the narrowest hole, so with two requests per source frame the reader reads one frame further ahead per hole until answers come from the future" -link = "https://github.com/CapSoftware/Cap/pull/2595" - -[[xfail]] -case = "decoder-model/avf/camera/sequential/export" -class = "resets" -platforms = ["macos"] -seeds = "0-2" -reason = "Found once each sequence keeps running after its first wrong answer (full failure sets, runs 38851482697 and 38851482675): reader starts and seeks exceed the budget of two per jump in the request stream later in the sequence (seed index 0 request 4610). AVAssetReader decoder request handling, the mechanisms of this decoder's other entries (pool position capping, reader reuse after a reset, pts holes), which #2638 targets" -link = "https://github.com/CapSoftware/Cap/pull/2638" - -[[xfail]] -case = "decoder-model/avf/camera/sequential/playback" -class = "future" -seeds = "2 3" -reason = "Camera runaway: when the pts-hole map is full it evicts the narrowest hole, so with two requests per source frame the reader reads one frame further ahead per hole until answers come from the future" -link = "https://github.com/CapSoftware/Cap/pull/2595" - -[[xfail]] -case = "decoder-model/avf/camera/sequential/playback" -class = "overread" -seeds = "0 2 3" -reason = "Camera runaway: when the pts-hole map is full it evicts the narrowest hole, so with two requests per source frame the reader reads one frame further ahead per hole until answers come from the future" -link = "https://github.com/CapSoftware/Cap/pull/2595" - -[[xfail]] -case = "decoder-model/avf/camera/sequential/playback" -class = "resets" -platforms = ["macos"] -seeds = "2 3" -reason = "Found once each sequence keeps running after its first wrong answer (full failure sets, runs 38851482697 and 38851482675): reader starts and seeks exceed the budget of two per jump in the request stream later in the sequence (seed index 2 request 7107). AVAssetReader decoder request handling, the mechanisms of this decoder's other entries (pool position capping, reader reuse after a reset, pts holes), which #2638 targets" -link = "https://github.com/CapSoftware/Cap/pull/2638" - -[[xfail]] -case = "decoder-model/avf/cfr/keyframe-seeks/playback" -class = "future" -seeds = "10 42 59 85 87 205 214 219 232 234 240 250 258 260" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/cfr/keyframe-seeks/playback" -class = "future" -platforms = ["macos"] -seeds = "3 13 16 34 37 65 71 72 75 81 83 92 94 100-102 109 110 119 139 146 162 171 179 197 210 212 218 221 225 228 230 239 246 274 282 283 293 297" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/cfr/keyframe-seeks/playback" -class = "grid-rounding" -seeds = "25 32 42 55 73 76 81 88 94 113 122 124 132 152 160 185 228 229 243 280 290" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/cfr/keyframe-seeks/playback" -class = "grid-rounding" -platforms = ["macos"] -seeds = "1-3 16 20 37 38 43 46 48 71 87 90 100 101 107 110 116 119 125 133 137 139 142 154 169 178 181 196 206 209 210 212 214 218 225 231-234 240 246 249 251 256 262 267 271 274 282 284 293 296 297" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/cfr/keyframe-seeks/playback" -class = "overread" -platforms = ["macos"] -seeds = "3 6 9-11 16 18 20-22 25 32 33 37 40 42 43 46 48 55 58 65 71 75 83 87-90 94 95 100 101 106-108 110-114 116 119 120 122 125 127 132 138-140 142 146 150 160 170 178 180 181 185 188 197 201 205 207-210 214 218 219 221 226 228-231 234 236 238 241 243 246 253 256 260 262 267 270 271 274 280 282 287 290 293 296 297" -reason = "Found once each sequence keeps running after its first wrong answer (full failure sets, runs 38851482697 and 38851482675): the reader decodes past the sample after the answer, running ahead of the requests (seed index 3 request 8). AVAssetReader decoder request handling, the mechanisms of this decoder's other entries (pool position capping, reader reuse after a reset, pts holes), which #2638 targets" -link = "https://github.com/CapSoftware/Cap/pull/2638" - -[[xfail]] -case = "decoder-model/avf/cfr/keyframe-seeks/playback" -class = "stale" -seeds = "1-3 6-9 11 13 15-22 25 27 28 32-35 37 38 40 41 43-51 53-55 58 65 66 68-73 75 76 79 81 83 84 86 88-92 94-96 100-103 106-114 116-122 124-129 131-133 136-140 142 145-147 149 150 153 154 156 157 160 162 164 169-173 175 176 178-181 185-188 191 196 197 201 204 206-210 212 218 221-231 233 235-239 241-244 246-249 251-257 262 264 265 267 268 270-275 277 278 280-284 287 289-291 293 295-299" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/cfr/keyframe-seeks/playback" -class = "stale" -platforms = ["macos"] -seeds = "42 87 214 232 234 240 258" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/cfr/random-seeks/playback" -class = "future" -seeds = "52 221" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/cfr/random-seeks/playback" -class = "future" -platforms = ["macos"] -seeds = "0 33 224 238" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/cfr/random-seeks/playback" -class = "grid-rounding" -seeds = "10 26 190 208 212 218 238 271 274" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/cfr/random-seeks/playback" -class = "grid-rounding" -platforms = ["macos"] -seeds = "6 11 19 29 33 46 50 86 111 112 124 131 132 150 161 169 188 191 203 241 261 299" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/cfr/random-seeks/playback" -class = "overread" -seeds = "54 79 146 184 190 197" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/cfr/random-seeks/playback" -class = "overread" -platforms = ["macos"] -seeds = "6 8 10 11 15 19 20 26 29 33 35 36 38 41 42 46-48 55 58 62 64 70 71 75 76 83-86 92 93 96 97 102 110-112 118-120 122 124 126 131 132 138 143-145 150 151 154 155 160 161 167 169 170 174 181 188 189 191 193 196 202 203 216 218 226 230 232 238 241 261 262 271 272 274 275 281 283 299" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/cfr/random-seeks/playback" -class = "stale" -seeds = "0 2 6 8 10 11 14 15 19-27 29-36 38 41-43 45-48 50 55 57 58 62 64 67 69-71 74-77 80 82-87 91-93 95-97 99 100 102 107 110-112 114 115 118-124 126 127 129 131-133 136 138 140 141 143-148 150 151 154 155 157-161 163 165-167 169 170 172 174-176 179-181 183 185 188-193 196 201-203 205 207 212 214-216 218 220 224-226 229-236 238 240-243 245 248-251 254-259 261 262 264 269 271 272 274 275 277 281-283 286 291 296 299" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/cfr/scrub/playback" -class = "grid-rounding" -platforms = ["macos"] -seeds = "83 177" -reason = "Found once each sequence keeps running after its first wrong answer (full failure sets, runs 38851482697 and 38851482675): an answer is the sample on screen at the exact request time instead of the grid frame (seed index 83 request 167). AVAssetReader decoder request handling, the mechanisms of this decoder's other entries (pool position capping, reader reuse after a reset, pts holes), which #2638 targets" -link = "https://github.com/CapSoftware/Cap/pull/2638" - -[[xfail]] -case = "decoder-model/avf/cfr/scrub/playback" -class = "overread" -seeds = "41 89 96" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/cfr/scrub/playback" -class = "overread" -platforms = ["macos"] -seeds = "15 17 65 70 71 76 83 154 168 169 181" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/cfr/scrub/playback" -class = "stale" -seeds = "1 2 5 7-9 13-15 17 19 22-24 27-29 34 36 37 40 43 48 53 55 56 58 59 61 62 65 66 70-73 76 78-81 83 84 86-88 91 93 99-101 105-107 109 110 113 115 120 123 128 129 131-137 142-144 148 149 151 152 154 155 157-160 162 166-169 171 173-175 177 180-182 184 185 189 190 196-199" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/cfr/sequential/playback" -class = "stale" -seeds = "23 115" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/cfr/tail/export" -class = "stale" -seeds = "1 7 54 60 69" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/cfr/tail/playback" -class = "stale" -seeds = "12 34 40 73 93" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/cfr/timeline/export" -class = "grid-rounding" -seeds = "16 28 45 57 93 128 137" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/cfr/timeline/export" -class = "grid-rounding" -platforms = ["macos"] -seeds = "36 40 52 54 58 60 70 75 117 142 148" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/cfr/timeline/export" -class = "overread" -seeds = "56 131" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/cfr/timeline/export" -class = "overread" -platforms = ["macos"] -seeds = "2 7-9 11 19 24 31 35-37 40 45 52-54 57 60 63 66 70 71 75 76 81 86 93 95 101 102 106 112 119 121 128-130 132-134 137 142 143 147 148" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/cfr/timeline/export" -class = "stale" -seeds = "2 7-12 14-17 19 23 24 26 28 30-33 35-40 42 44 45 47 49 52-55 57 58 60-63 66 69-71 75 76 81-83 86 88 90 93-95 97 98 101-103 106 108 112 113 117-119 121 122 125 126 128-130 132-134 137 142 143 147 148" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/cfr/transition/export" -class = "future" -seeds = "17 51 64" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/cfr/transition/export" -class = "future" -platforms = ["macos"] -seeds = "19" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/cfr/transition/export" -class = "grid-rounding" -seeds = "16 32" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/cfr/transition/export" -class = "overread" -platforms = ["macos"] -seeds = "6 12 16 28 38 42 48 65 75 78 79" -reason = "Found once each sequence keeps running after its first wrong answer (full failure sets, runs 38851482697 and 38851482675): the reader decodes past the sample after the answer, running ahead of the requests (seed index 6 request 225). AVAssetReader decoder request handling, the mechanisms of this decoder's other entries (pool position capping, reader reuse after a reset, pts holes), which #2638 targets" -link = "https://github.com/CapSoftware/Cap/pull/2638" - -[[xfail]] -case = "decoder-model/avf/cfr/transition/export" -class = "stale" -seeds = "0 5 6 8 10 12 16 19 22-25 28 33 38 40 42 48 50 56 59 61 65 66 68 73 75 78 79" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/cfr/transition/export" -class = "stale" -platforms = ["macos"] -seeds = "17" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/cfr/transition/playback" -class = "future" -seeds = "45" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/cfr/transition/playback" -class = "grid-rounding" -seeds = "40" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/cfr/transition/playback" -class = "overread" -platforms = ["macos"] -seeds = "4 8 11 42 66 73 76" -reason = "Found once each sequence keeps running after its first wrong answer (full failure sets, runs 38851482697 and 38851482675): the reader decodes past the sample after the answer, running ahead of the requests (seed index 4 request 189). AVAssetReader decoder request handling, the mechanisms of this decoder's other entries (pool position capping, reader reuse after a reset, pts holes), which #2638 targets" -link = "https://github.com/CapSoftware/Cap/pull/2638" - -[[xfail]] -case = "decoder-model/avf/cfr/transition/playback" -class = "stale" -seeds = "4 6 8 11 14-16 20 21 23 25 29 35 36 41 42 58 60 64 66 68-70 73 74 76" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/dense/random-seeks/playback" -class = "future" -seeds = "132" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/dense/random-seeks/playback" -class = "overread" -platforms = ["macos"] -seeds = "1 46 53 56 58 65 73 81 96 110 118 126 134 137 138 149" -reason = "Found once each sequence keeps running after its first wrong answer (full failure sets, runs 38851482697 and 38851482675): the reader decodes past the sample after the answer, running ahead of the requests (seed index 1 request 323). AVAssetReader decoder request handling, the mechanisms of this decoder's other entries (pool position capping, reader reuse after a reset, pts holes), which #2638 targets" -link = "https://github.com/CapSoftware/Cap/pull/2638" - -[[xfail]] -case = "decoder-model/avf/dense/random-seeks/playback" -class = "stale" -seeds = "0 1 3 5 9 13 15 18 25 28 30 32 34 36 37 39 43-47 49 51 53 56-59 61 62 64-66 69 73 74 76 79-82 87 89 91 96 97 100-102 105 107 109 110 114 115 117-119 122 126 127 131 134-140 143 149" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/halfgrid/back-into-old-gap/export" -class = "future" -platforms = ["macos"] -seeds = "3 15 50" -reason = "Found once each sequence keeps running after its first wrong answer (full failure sets, runs 38851482697 and 38851482675): a later request is answered with a sample ahead of the right one (seed index 3 request 1622). AVAssetReader decoder request handling, the mechanisms of this decoder's other entries (pool position capping, reader reuse after a reset, pts holes), which #2638 targets" -link = "https://github.com/CapSoftware/Cap/pull/2638" - -[[xfail]] -case = "decoder-model/avf/halfgrid/back-into-old-gap/export" -class = "overread" -seeds = "18 30 54 55" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/halfgrid/back-into-old-gap/export" -class = "overread" -platforms = ["macos"] -seeds = "2 3 8 15 17 20 22 45 50 59" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/halfgrid/back-into-old-gap/export" -class = "stale" -seeds = "2 3 8 15 17 19 20 22 30 37 39 42-45 50 54 55 59" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/halfgrid/back-into-old-gap/playback" -class = "future" -seeds = "30 33 38" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/halfgrid/back-into-old-gap/playback" -class = "future" -platforms = ["macos"] -seeds = "34 58 59" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/halfgrid/back-into-old-gap/playback" -class = "grid-rounding" -seeds = "7" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/halfgrid/back-into-old-gap/playback" -class = "grid-rounding" -platforms = ["macos"] -seeds = "25" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/halfgrid/back-into-old-gap/playback" -class = "overread" -seeds = "7 37 55" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/halfgrid/back-into-old-gap/playback" -class = "overread" -platforms = ["macos"] -seeds = "2 18 22-27 30 33 34 38 41 42 58 59" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/halfgrid/back-into-old-gap/playback" -class = "stale" -seeds = "2 4 7 18 20 22-27 29 34 36 37 39 41 42 45 47 55 58 59" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/late-start/random-seeks/playback" -class = "future" -seeds = "36 40 61 67 79" -reason = "AVAssetReader requests before a late-starting stream's first frame read the reader one frame further ahead per request, and seeks into the stream start show the wrong frame" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/late-start/random-seeks/playback" -class = "future" -platforms = ["macos"] -seeds = "28 39 74" -reason = "AVAssetReader requests before a late-starting stream's first frame read the reader one frame further ahead per request, and seeks into the stream start show the wrong frame" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/late-start/random-seeks/playback" -class = "grid-rounding" -seeds = "12 31 37 43" -reason = "AVAssetReader requests before a late-starting stream's first frame read the reader one frame further ahead per request, and seeks into the stream start show the wrong frame" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/late-start/random-seeks/playback" -class = "grid-rounding" -platforms = ["macos"] -seeds = "11 14 38 47 50 74" -reason = "AVAssetReader requests before a late-starting stream's first frame read the reader one frame further ahead per request, and seeks into the stream start show the wrong frame" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/late-start/random-seeks/playback" -class = "overread" -seeds = "0-7 10 13 16-21 23-29 32-37 39 40 42 44-49 51-60 64-75 77-79" -reason = "AVAssetReader requests before a late-starting stream's first frame read the reader one frame further ahead per request, and seeks into the stream start show the wrong frame" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/late-start/random-seeks/playback" -class = "overread" -platforms = ["macos"] -seeds = "11 12 14 15 22 38 41 43 50 61-63" -reason = "AVAssetReader requests before a late-starting stream's first frame read the reader one frame further ahead per request, and seeks into the stream start show the wrong frame" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/late-start/random-seeks/playback" -class = "stale" -seeds = "4 8 11-16 18 22 24 26-28 31 32 37-39 41-44 47 50 57 62-64 74" -reason = "AVAssetReader requests before a late-starting stream's first frame read the reader one frame further ahead per request, and seeks into the stream start show the wrong frame" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/late-start/random-seeks/playback" -class = "stale" -platforms = ["macos"] -seeds = "61 67 79" -reason = "AVAssetReader requests before a late-starting stream's first frame read the reader one frame further ahead per request, and seeks into the stream start show the wrong frame" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/late-start/sequential/playback" -class = "future" -seeds = "2 5 20 21 23 24 28 38 41-43 45 52 55 57 63 72 77 78" -reason = "AVAssetReader requests before a late-starting stream's first frame read the reader one frame further ahead per request, and seeks into the stream start show the wrong frame" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/late-start/sequential/playback" -class = "overread" -seeds = "0-2 5-10 14 15 19-29 31 32 34 37 38 40-47 49 51-59 66 70-78" -reason = "AVAssetReader requests before a late-starting stream's first frame read the reader one frame further ahead per request, and seeks into the stream start show the wrong frame" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/late-start/sequential/playback" -class = "overread" -platforms = ["macos"] -seeds = "63" -reason = "AVAssetReader requests before a late-starting stream's first frame read the reader one frame further ahead per request, and seeks into the stream start show the wrong frame" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/late-start/sequential/playback" -class = "resets" -seeds = "29 75" -reason = "AVAssetReader requests before a late-starting stream's first frame read the reader one frame further ahead per request, and seeks into the stream start show the wrong frame" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/late-start/sequential/playback" -class = "resets" -platforms = ["macos"] -seeds = "42" -reason = "AVAssetReader requests before a late-starting stream's first frame read the reader one frame further ahead per request, and seeks into the stream start show the wrong frame" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/late-start/sequential/playback" -class = "stale" -seeds = "50 60 64 79" -reason = "AVAssetReader requests before a late-starting stream's first frame read the reader one frame further ahead per request, and seeks into the stream start show the wrong frame" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/late-start/sequential/playback" -class = "stale" -platforms = ["macos"] -seeds = "24" -reason = "AVAssetReader requests before a late-starting stream's first frame read the reader one frame further ahead per request, and seeks into the stream start show the wrong frame" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/syn/edlist/export" -class = "future" -seeds = "0-7" -reason = "Camera runaway: when the pts-hole map is full it evicts the narrowest hole, so with two requests per source frame the reader reads one frame further ahead per hole until answers come from the future" -link = "https://github.com/CapSoftware/Cap/pull/2595" - -[[xfail]] -case = "decoder-model/avf/syn/edlist/export" -class = "overread" -seeds = "0-7" -reason = "Camera runaway: when the pts-hole map is full it evicts the narrowest hole, so with two requests per source frame the reader reads one frame further ahead per hole until answers come from the future" -link = "https://github.com/CapSoftware/Cap/pull/2595" - -[[xfail]] -case = "decoder-model/avf/syn/edlist/playback" -class = "future" -seeds = "2" -reason = "Camera runaway: when the pts-hole map is full it evicts the narrowest hole, so with two requests per source frame the reader reads one frame further ahead per hole until answers come from the future" -link = "https://github.com/CapSoftware/Cap/pull/2595" - -[[xfail]] -case = "decoder-model/avf/syn/edlist/playback" -class = "overread" -seeds = "0-7" -reason = "Camera runaway: when the pts-hole map is full it evicts the narrowest hole, so with two requests per source frame the reader reads one frame further ahead per hole until answers come from the future" -link = "https://github.com/CapSoftware/Cap/pull/2595" - -[[xfail]] -case = "decoder-model/avf/vfr/keyframe-seeks/playback" +case = "decoder-model/avf/late-start/random-seeks/playback" class = "future" -seeds = "51 56 60 61 63 86" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" +seeds = "61" +reason = "AVAssetReader requests before a late-starting stream's first frame read the reader one frame further ahead per request, and seeks into the stream start show the wrong frame" link = "https://github.com/CapSoftware/Cap/pull/2603" [[xfail]] @@ -740,44 +35,6 @@ flaky = true reason = "Seeds 28, 53 and 59 fail in every run; which wrong answer comes first depends on the wall clock. Without a stall the first wrong answer is a future one at a seek (seed 28 request 61, seed 53 request 8, seed 59 request 9: the next sample, e.g. sample 68 for 67): pool position capping, the reader's pool position is capped to the newest request (avassetreader.rs update_decoder_position(decoder_idx, capped)) so a reader that already decoded past the sample is reused without a reset. When the test thread stalls for 250 ms or more, the decoder's idle timeout (IDLE_READER_TIMEOUT, avassetreader.rs) clears the cache and reader state, the later requests take another path and the first wrong answer is a stale one from the wrong-GOP seek (reader started on the keyframe time rounded to 10 ms). Measured on Apple silicon: future 3 of 3 per seed without a stall, stale for all three seeds with a 300 ms stall before every request, and for seed 53 with a single 300 ms stall before request 6 (a 240 ms stall keeps future). macos-15: future in 6 of 7 runs, stale in 1 (38743345280). With #2595's decoder changes seed 28 passes and seeds 53 and 59 keep only the stale (wrong-GOP) answer" link = "https://github.com/CapSoftware/Cap/pull/2595 https://github.com/CapSoftware/Cap/pull/2603" -[[xfail]] -case = "decoder-model/avf/vfr/keyframe-seeks/playback" -class = "future" -platforms = ["macos"] -seeds = "0 3 6 7 9 14 19 24 26 29 34 36 41 45 47 49 55 62 65 70 76 83 85 88 95 97" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/vfr/keyframe-seeks/playback" -class = "grid-rounding" -seeds = "5 25 27 32 48 52 56 65 76 86 90 98" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/vfr/keyframe-seeks/playback" -class = "grid-rounding" -platforms = ["macos"] -seeds = "0 3 7 14 17 21 24 26 34 35 37 40 41 49 51 59 61 68 69 75 83 88 97 99" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/vfr/keyframe-seeks/playback" -class = "overread" -seeds = "65 76 86 98" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/vfr/keyframe-seeks/playback" -class = "overread" -platforms = ["macos"] -seeds = "0 6 7 14 17 19 21 25 28 29 32 34 37 39-41 47 48 51-53 56 60 61 63 64 68-71 75 78 83-85 87-89 94 95 97" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - [[xfail]] case = "decoder-model/avf/vfr/keyframe-seeks/playback" class = "stale" @@ -786,237 +43,6 @@ flaky = true reason = "Seeds 28, 53 and 59 fail in every run; which wrong answer comes first depends on the wall clock. Without a stall the first wrong answer is a future one at a seek (seed 28 request 61, seed 53 request 8, seed 59 request 9: the next sample, e.g. sample 68 for 67): pool position capping, the reader's pool position is capped to the newest request (avassetreader.rs update_decoder_position(decoder_idx, capped)) so a reader that already decoded past the sample is reused without a reset. When the test thread stalls for 250 ms or more, the decoder's idle timeout (IDLE_READER_TIMEOUT, avassetreader.rs) clears the cache and reader state, the later requests take another path and the first wrong answer is a stale one from the wrong-GOP seek (reader started on the keyframe time rounded to 10 ms). Measured on Apple silicon: future 3 of 3 per seed without a stall, stale for all three seeds with a 300 ms stall before every request, and for seed 53 with a single 300 ms stall before request 6 (a 240 ms stall keeps future). macos-15: future in 6 of 7 runs, stale in 1 (38743345280). With #2595's decoder changes seed 28 passes and seeds 53 and 59 keep only the stale (wrong-GOP) answer" link = "https://github.com/CapSoftware/Cap/pull/2595 https://github.com/CapSoftware/Cap/pull/2603" -[[xfail]] -case = "decoder-model/avf/vfr/keyframe-seeks/playback" -class = "stale" -seeds = "0-3 6 7 9 10 12-22 24-26 29-37 39-42 44-50 52 54 55 57 58 62 64-76 78-85 87-91 93-97 99" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/vfr/keyframe-seeks/playback" -class = "stale" -platforms = ["macos"] -seeds = "51 56 60 61 63" -reason = "Seeds 28, 53 and 59 fail in every run; which wrong answer comes first depends on the wall clock. Without a stall the first wrong answer is a future one at a seek (seed 28 request 61, seed 53 request 8, seed 59 request 9: the next sample, e.g. sample 68 for 67): pool position capping, the reader's pool position is capped to the newest request (avassetreader.rs update_decoder_position(decoder_idx, capped)) so a reader that already decoded past the sample is reused without a reset. When the test thread stalls for 250 ms or more, the decoder's idle timeout (IDLE_READER_TIMEOUT, avassetreader.rs) clears the cache and reader state, the later requests take another path and the first wrong answer is a stale one from the wrong-GOP seek (reader started on the keyframe time rounded to 10 ms). Measured on Apple silicon: future 3 of 3 per seed without a stall, stale for all three seeds with a 300 ms stall before every request, and for seed 53 with a single 300 ms stall before request 6 (a 240 ms stall keeps future). macos-15: future in 6 of 7 runs, stale in 1 (38743345280). With #2595's decoder changes seed 28 passes and seeds 53 and 59 keep only the stale (wrong-GOP) answer" -link = "https://github.com/CapSoftware/Cap/pull/2595 https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/vfr/random-seeks/playback" -class = "future" -seeds = "7 17 23 76 84 97 100 105 109 114 115 119 135 136 148 174" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/vfr/random-seeks/playback" -class = "future" -platforms = ["macos"] -seeds = "5 18 24 41 44 83 103 110 126 142 153" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/vfr/random-seeks/playback" -class = "grid-rounding" -seeds = "77 87 114 133 156" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/vfr/random-seeks/playback" -class = "grid-rounding" -platforms = ["macos"] -seeds = "97" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/vfr/random-seeks/playback" -class = "overread" -seeds = "58 171 180 192" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/vfr/random-seeks/playback" -class = "overread" -platforms = ["macos"] -seeds = "5 7 11 12 17 18 23 24 31 41 43 44 63 66 73 75-77 79 83 84 86 87 90 96 97 100 102-105 107 109 110 112 114-116 120 135-137 142 146 148 153 156 162 163 165 172 174 176 181 189 191 193" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/vfr/random-seeks/playback" -class = "stale" -seeds = "5 6 10-12 18 20-22 24 25 31 35 36 41-44 48-50 58 61 63 66 69 72-75 77 79 81-83 86 87 89 90 96 99 101-104 107 110 112 116 120 122 123 125-127 131 132 137 142 143 146 151 153 156 157 159 162 163 165 171 172 176 179-181 189 191-194" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/vfr/random-seeks/playback" -class = "stale" -platforms = ["macos"] -seeds = "17 97 109 119" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/vfr/scrub/playback" -class = "future" -seeds = "6 47 68 94" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/vfr/scrub/playback" -class = "future" -platforms = ["macos"] -seeds = "16 22 24 26 34 45 57 97 101 110" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/vfr/scrub/playback" -class = "grid-rounding" -platforms = ["macos"] -seeds = "68 70" -reason = "Found once each sequence keeps running after its first wrong answer (full failure sets, runs 38851482697 and 38851482675): an answer is the sample on screen at the exact request time instead of the grid frame (seed index 68 request 147). AVAssetReader decoder request handling, the mechanisms of this decoder's other entries (pool position capping, reader reuse after a reset, pts holes), which #2638 targets" -link = "https://github.com/CapSoftware/Cap/pull/2638" - -[[xfail]] -case = "decoder-model/avf/vfr/scrub/playback" -class = "overread" -seeds = "28 36 48 66 77 114" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/vfr/scrub/playback" -class = "overread" -platforms = ["macos"] -seeds = "7 9 16 22 24-26 34 37 40 45 50 62 68 69 72 74 84 87 89 90 101 110 118" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/vfr/scrub/playback" -class = "stale" -seeds = "1 2 7 9 10 12 14 16 17 21-26 29 30 34 37 40 41 43-45 49 50 53 54 56-58 61 62 65 66 69 70 72-74 77-92 97 100-104 108-110 113 114 116 118 119" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/vfr/scrub/playback" -class = "stale" -platforms = ["macos"] -seeds = "94" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/vfr/sequential/export" -class = "stale" -seeds = "34 40 73 99" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/vfr/sequential/playback" -class = "stale" -seeds = "6 15 22 29 49 69 85" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/vfr/timeline/export" -class = "future" -seeds = "13 44 59" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/vfr/timeline/export" -class = "grid-rounding" -seeds = "43 77 91 93 96" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/vfr/timeline/export" -class = "overread" -seeds = "59" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/vfr/timeline/export" -class = "overread" -platforms = ["macos"] -seeds = "12 13 15 18 27 37 43 44 51 72 82 91 96 102" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/vfr/timeline/export" -class = "stale" -seeds = "12 15 17 18 20 23 27 31 34 37 43 50 51 54 61 72 73 76 77 82 85 86 90 91 93 96 102 103 108 112 115" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/vfr/transition/playback" -class = "future" -seeds = "5 14 15 17 20 22 23 27 28 30 31 33 36 40 43 48 51 54 63-65 76-78" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/vfr/transition/playback" -class = "future" -platforms = ["macos"] -seeds = "18 35 41 42 44 50 56 58 62 68-70 74" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/vfr/transition/playback" -class = "grid-rounding" -seeds = "8" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/vfr/transition/playback" -class = "grid-rounding" -platforms = ["macos"] -seeds = "43 44" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/vfr/transition/playback" -class = "overread" -platforms = ["macos"] -seeds = "5 10 13 14 17 18 20 22 23 27 28 30 31 33 35-37 40-44 48 50 51 54 56 58 62 63 65 67-70 74 76-79" -reason = "Found once each sequence keeps running after its first wrong answer (full failure sets, runs 38851482697 and 38851482675): the reader decodes past the sample after the answer, running ahead of the requests (seed index 5 request 157). AVAssetReader decoder request handling, the mechanisms of this decoder's other entries (pool position capping, reader reuse after a reset, pts holes), which #2638 targets" -link = "https://github.com/CapSoftware/Cap/pull/2638" - -[[xfail]] -case = "decoder-model/avf/vfr/transition/playback" -class = "stale" -seeds = "0 4 10 11 13 16 18 29 35 37 39 41 42 44 47 50 56-60 62 67-70 74 79" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - -[[xfail]] -case = "decoder-model/avf/vfr/transition/playback" -class = "stale" -platforms = ["macos"] -seeds = "17 28 31 51 63 65" -reason = "AVAssetReader seeks: the reader starts on the keyframe time rounded to 10 ms, so it can return the previous GOP's last frame under the keyframe's number, and a reused pooled reader answers from frames cached by an earlier, unrelated seek" -link = "https://github.com/CapSoftware/Cap/pull/2603" - [[xfail]] case = "decoder-model/ffmpeg/camera-fast/random-seeks/playback" class = "future" @@ -1034,7 +60,7 @@ link = "" [[xfail]] case = "decoder-model/ffmpeg/camera-fast/random-seeks/playback" class = "overread" -seeds = "0-4 6-12 14-16 18 19 22-32 34-51 54-59" +seeds = "47" reason = "Camera runaway: when the pts-hole map is full it evicts the narrowest hole, so with two requests per source frame the reader reads one frame further ahead per hole until answers come from the future" link = "https://github.com/CapSoftware/Cap/pull/2595" @@ -1045,104 +71,6 @@ seeds = "47" reason = "NEW: the FFmpeg decoder answers backward requests with a cached frame up to 2 grid frames older than the requested one, answers grid frames 0 to 2 with the first frame, and after a backward request served from the cache it reads forward from a reader that is still far ahead, answering with an older cached frame" link = "" -[[xfail]] -case = "decoder-model/ffmpeg/camera-fast/sequential/export" -class = "grid-rounding" -seeds = "3 4 6 11 22 27-29" -reason = "Camera runaway: when the pts-hole map is full it evicts the narrowest hole, so with two requests per source frame the reader reads one frame further ahead per hole until answers come from the future" -link = "https://github.com/CapSoftware/Cap/pull/2595" - -[[xfail]] -case = "decoder-model/ffmpeg/camera-fast/sequential/export" -class = "overread" -seeds = "0-29" -reason = "Camera runaway: when the pts-hole map is full it evicts the narrowest hole, so with two requests per source frame the reader reads one frame further ahead per hole until answers come from the future" -link = "https://github.com/CapSoftware/Cap/pull/2595" - -[[xfail]] -case = "decoder-model/ffmpeg/camera-fast/sequential/export" -class = "stale" -seeds = "0 3-6 11 15 22 27-29" -reason = "Camera runaway: when the pts-hole map is full it evicts the narrowest hole, so with two requests per source frame the reader reads one frame further ahead per hole until answers come from the future" -link = "https://github.com/CapSoftware/Cap/pull/2595" - -[[xfail]] -case = "decoder-model/ffmpeg/camera-fast/sequential/playback" -class = "grid-rounding" -seeds = "2 6 24" -reason = "Camera runaway: when the pts-hole map is full it evicts the narrowest hole, so with two requests per source frame the reader reads one frame further ahead per hole until answers come from the future" -link = "https://github.com/CapSoftware/Cap/pull/2595" - -[[xfail]] -case = "decoder-model/ffmpeg/camera-fast/sequential/playback" -class = "overread" -seeds = "0-8 10-14 16 17 19-21 23-29" -reason = "Camera runaway: when the pts-hole map is full it evicts the narrowest hole, so with two requests per source frame the reader reads one frame further ahead per hole until answers come from the future" -link = "https://github.com/CapSoftware/Cap/pull/2595" - -[[xfail]] -case = "decoder-model/ffmpeg/camera-fast/sequential/playback" -class = "stale" -seeds = "0 2 6 11 24" -reason = "Camera runaway: when the pts-hole map is full it evicts the narrowest hole, so with two requests per source frame the reader reads one frame further ahead per hole until answers come from the future" -link = "https://github.com/CapSoftware/Cap/pull/2595" - -[[xfail]] -case = "decoder-model/ffmpeg/camera/sequential/export" -class = "grid-rounding" -seeds = "0-3" -reason = "Camera runaway: when the pts-hole map is full it evicts the narrowest hole, so with two requests per source frame the reader reads one frame further ahead per hole until answers come from the future" -link = "https://github.com/CapSoftware/Cap/pull/2595" - -[[xfail]] -case = "decoder-model/ffmpeg/camera/sequential/export" -class = "overread" -seeds = "0-3" -reason = "Camera runaway: when the pts-hole map is full it evicts the narrowest hole, so with two requests per source frame the reader reads one frame further ahead per hole until answers come from the future" -link = "https://github.com/CapSoftware/Cap/pull/2595" - -[[xfail]] -case = "decoder-model/ffmpeg/camera/sequential/export" -class = "resets" -seeds = "0-3" -reason = "Found once each sequence keeps running after its first wrong answer (full failure sets, runs 38851482697 and 38851482675): reader starts and seeks exceed the budget of two per jump in the request stream later in the sequence (seed index 0 request 6042). FFmpeg decoder request handling, the mechanisms of this decoder's other entries (backward lookback tolerance, first-frame shortcut, reset decisions)" -link = "" - -[[xfail]] -case = "decoder-model/ffmpeg/camera/sequential/export" -class = "stale" -seeds = "0-3" -reason = "Camera runaway: when the pts-hole map is full it evicts the narrowest hole, so with two requests per source frame the reader reads one frame further ahead per hole until answers come from the future" -link = "https://github.com/CapSoftware/Cap/pull/2595" - -[[xfail]] -case = "decoder-model/ffmpeg/camera/sequential/playback" -class = "grid-rounding" -seeds = "2 3" -reason = "Camera runaway: when the pts-hole map is full it evicts the narrowest hole, so with two requests per source frame the reader reads one frame further ahead per hole until answers come from the future" -link = "https://github.com/CapSoftware/Cap/pull/2595" - -[[xfail]] -case = "decoder-model/ffmpeg/camera/sequential/playback" -class = "overread" -seeds = "2 3" -reason = "Camera runaway: when the pts-hole map is full it evicts the narrowest hole, so with two requests per source frame the reader reads one frame further ahead per hole until answers come from the future" -link = "https://github.com/CapSoftware/Cap/pull/2595" - -[[xfail]] -case = "decoder-model/ffmpeg/camera/sequential/playback" -class = "resets" -seeds = "2 3" -reason = "Found once each sequence keeps running after its first wrong answer (full failure sets, runs 38851482697 and 38851482675): reader starts and seeks exceed the budget of two per jump in the request stream later in the sequence (seed index 2 request 6210). FFmpeg decoder request handling, the mechanisms of this decoder's other entries (backward lookback tolerance, first-frame shortcut, reset decisions)" -link = "" - -[[xfail]] -case = "decoder-model/ffmpeg/camera/sequential/playback" -class = "stale" -seeds = "2 3" -reason = "Camera runaway: when the pts-hole map is full it evicts the narrowest hole, so with two requests per source frame the reader reads one frame further ahead per hole until answers come from the future" -link = "https://github.com/CapSoftware/Cap/pull/2595" - [[xfail]] case = "decoder-model/ffmpeg/cfr/keyframe-seeks/playback" class = "future" @@ -1188,7 +116,7 @@ link = "" [[xfail]] case = "decoder-model/ffmpeg/cfr/random-seeks/playback" class = "overread" -seeds = "0 3 4 9-13 16 18 25 27 30 32 42 50 55 64 67 73 82 89 95 100 101 106 108 126 128 129 134 137-140 146-148 151 163 164 166 171 174 178-181 187 204 207 217 220 227 228 232 246 247 250 254 262 273 279 281 287 289 290 297 299" +seeds = "0 4 10-13 16 18 25 27 30 32 42 50 55 67 73 82 89 100 108 126 128-129 134 137-140 146-148 151 164 166 171 174 178-181 207 217 220 228 232 246-247 250 254 262 273 279 281 289-290 297" reason = "NEW: after a backward request served from the FFmpeg decoder's cache, the reader is left far ahead and the next request reads past the frame it needs" link = "" @@ -1216,7 +144,7 @@ link = "" [[xfail]] case = "decoder-model/ffmpeg/cfr/scrub/playback" class = "overread" -seeds = "0 1 3 4 7 8 11 13 14 16-21 26 27 31 33-44 46 50-56 58 64 67 68 70 71 73-75 77 78 80 82-86 88 90-94 97-101 103-111 113-115 117-119 121-124 127 128 131-135 137-142 144 145 147-149 151 152 154 155 157-163 165 167 168 170 173-177 179-181 183 185-187 189 192 193 197 198" +seeds = "0-1 3-4 7-8 11 13-14 16-21 26-27 33-43 46 50-56 58 64 67-68 70-71 73-75 77-78 80 82-86 88 90-94 97-101 103-110 113-115 117-119 121-124 127-128 131-135 137-139 141-142 144-145 147-149 151-152 154-155 157 160-163 165 167-168 170 173-177 179-181 183 185-187 189 192 197-198" reason = "NEW: after a backward request served from the FFmpeg decoder's cache, the reader is left far ahead and the next request reads past the frame it needs" link = "" @@ -1251,7 +179,7 @@ link = "" [[xfail]] case = "decoder-model/ffmpeg/cfr/timeline/export" class = "overread" -seeds = "12 42 43 53 64 66 103 107 132 133" +seeds = "12 42-43 64 133" reason = "NEW: after a backward request served from the FFmpeg decoder's cache, the reader is left far ahead and the next request reads past the frame it needs" link = "" @@ -1346,34 +274,6 @@ seeds = "0 4 14 15 17 20 22 25 27 29 31-33 38 39 43 50 54 56 57 61 63 65 67 71 8 reason = "NEW: the FFmpeg decoder answers backward requests with a cached frame up to 2 grid frames older than the requested one, answers grid frames 0 to 2 with the first frame, and after a backward request served from the cache it reads forward from a reader that is still far ahead, answering with an older cached frame" link = "" -[[xfail]] -case = "decoder-model/ffmpeg/halfgrid/back-into-old-gap/export" -class = "overread" -seeds = "23 41" -reason = "Camera runaway: when the pts-hole map is full it evicts the narrowest hole, so with two requests per source frame the reader reads one frame further ahead per hole until answers come from the future" -link = "https://github.com/CapSoftware/Cap/pull/2595" - -[[xfail]] -case = "decoder-model/ffmpeg/halfgrid/back-into-old-gap/playback" -class = "overread" -seeds = "20" -reason = "Camera runaway: when the pts-hole map is full it evicts the narrowest hole, so with two requests per source frame the reader reads one frame further ahead per hole until answers come from the future" -link = "https://github.com/CapSoftware/Cap/pull/2595" - -[[xfail]] -case = "decoder-model/ffmpeg/syn/edlist/export" -class = "overread" -seeds = "0-7" -reason = "Camera runaway: when the pts-hole map is full it evicts the narrowest hole, so with two requests per source frame the reader reads one frame further ahead per hole until answers come from the future" -link = "https://github.com/CapSoftware/Cap/pull/2595" - -[[xfail]] -case = "decoder-model/ffmpeg/syn/edlist/playback" -class = "overread" -seeds = "0-7" -reason = "Camera runaway: when the pts-hole map is full it evicts the narrowest hole, so with two requests per source frame the reader reads one frame further ahead per hole until answers come from the future" -link = "https://github.com/CapSoftware/Cap/pull/2595" - [[xfail]] case = "decoder-model/ffmpeg/vfr/keyframe-seeks/playback" class = "future" @@ -1391,7 +291,7 @@ link = "" [[xfail]] case = "decoder-model/ffmpeg/vfr/keyframe-seeks/playback" class = "overread" -seeds = "1-15 17-21 23-25 28-31 34 36 40-44 46-48 50-53 55 57-62 64 66 68 69 72 73 75 76 78 79 81 83 84 86 87 89 91 94 96 97 99" +seeds = "1-15 17-21 23-25 28-31 34 40-44 46-48 50-53 55 57-62 64 66 68-69 72-73 75-76 78-79 81 83-84 86-87 89 91 94 96-97 99" reason = "NEW: after a backward request served from the FFmpeg decoder's cache, the reader is left far ahead and the next request reads past the frame it needs" link = "" @@ -1419,7 +319,7 @@ link = "" [[xfail]] case = "decoder-model/ffmpeg/vfr/random-seeks/playback" class = "overread" -seeds = "3 11 12 18 20 29 53 55 67 79 80 84 93 101 102 104 110 120 130 148 157 167 171 177 180" +seeds = "3 12 18 20 29 53 55 67 79-80 84 93 120 148 167 177" reason = "NEW: after a backward request served from the FFmpeg decoder's cache, the reader is left far ahead and the next request reads past the frame it needs" link = "" @@ -1447,7 +347,7 @@ link = "" [[xfail]] case = "decoder-model/ffmpeg/vfr/scrub/playback" class = "overread" -seeds = "1 2 4 7 10 12 15 18 19 23 25 28 33 34 36-38 43 45 46 49 53 54 56 59 67 69-72 75 80 81 88 91 95 98 101 103 105 107 110-112 114 119" +seeds = "1-2 7 12 15 18-19 23 28 33-34 36-37 43 45-46 49 53-54 56 59 67 69-72 75 80-81 88 91 95 101 103 105 107 110-112 119" reason = "NEW: after a backward request served from the FFmpeg decoder's cache, the reader is left far ahead and the next request reads past the frame it needs" link = "" @@ -1479,20 +379,6 @@ seeds = "20 77 110" reason = "NEW: the FFmpeg decoder seeks to the exact request time, so after a reset it answers with the frame on screen at that time even when its rounded grid label is after the request; sequential playback shows the previous frame for the same request" link = "" -[[xfail]] -case = "decoder-model/ffmpeg/vfr/timeline/export" -class = "overread" -seeds = "0 20 47 56 62 88 107 110 119" -reason = "NEW: after a backward request served from the FFmpeg decoder's cache, the reader is left far ahead and the next request reads past the frame it needs" -link = "" - -[[xfail]] -case = "decoder-model/ffmpeg/vfr/timeline/export" -class = "stale" -seeds = "20 56 119" -reason = "NEW: the FFmpeg decoder answers backward requests with a cached frame up to 2 grid frames older than the requested one, answers grid frames 0 to 2 with the first frame, and after a backward request served from the cache it reads forward from a reader that is still far ahead, answering with an older cached frame" -link = "" - [[xfail]] case = "decoder-model/ffmpeg/vfr/transition/playback" class = "future" diff --git a/crates/verify/expectations.toml b/crates/verify/expectations.toml index 09db7f24934..af77ea04fdb 100644 --- a/crates/verify/expectations.toml +++ b/crates/verify/expectations.toml @@ -47,55 +47,6 @@ require_bound = true -[[xfail]] -case = "export/jitter-camera-40s/full/mp460/avf" -class = "future" -platforms = ["macos"] -bound = { camera-frames = [500, 560], delta = 320, frame = [1580, 2399] } -reason = "Camera runaway: with a jittery ~12 fps camera at 30 fps nominal exported at 60 fps, the pts-hole map evicts the hole it just recorded once 64 wider ones are tracked, and the reader drifts ahead from output frame 1585 on (530 camera frames ahead, up to 298 source frames, every run; the screen is never affected)" -link = "https://github.com/CapSoftware/Cap/pull/2638" - -[[xfail]] -case = "export/jitter-camera-120s/full/mp460/avf" -class = "future" -platforms = ["macos"] -bound = { camera-frames = [5000, 5250], delta = 2232, frame = [1585, 7199] } -reason = "Same camera runaway on the 120 s fixture (full tier): from output frame 1585, 5146 camera frames ahead, up to 2232 source frames (74 s); 5081 to 5201 in the runs whose exports differ (see the nondeterministic entry). The screen is never affected" -link = "https://github.com/CapSoftware/Cap/pull/2638" - -[[xfail]] -case = "playback/jitter-camera-120s/full/seq60-from0/avf" -class = "future" -platforms = ["macos"] -bound = { camera-frames = [2300, 2800], delta = 1900, frame = [2480, 7199] } -reason = "Same camera runaway in offline playback of the 120 s fixture: from output frame 2490, 2485 to 2618 camera frames ahead, up to 1419 to 1726 source frames (the count varies with the decoder's request batching)" -link = "https://github.com/CapSoftware/Cap/pull/2638" - -[[xfail]] -case = "playback/jitter-camera-120s/full/seq60-from0/avf" -class = "stale" -platforms = ["macos"] -flaky = true -bound = { screen-frames = 1, camera-frames = 1, delta = 1, frame = [7199, 7199] } -reason = "The last output frame of the 120 s offline playback (7199, 119.983 s) shows the source frame before the right one in 1 of 3 full runs (38763192776). Mechanism: when the request stream pauses for 250 ms or more, the AVAssetReader decoder's idle timeout (IDLE_READER_TIMEOUT, crates/rendering/src/decoder/avassetreader.rs) clears its cache and reader state, and the final request is then answered with the newest frame before it. Reproduced on Apple silicon by pausing 300 ms before the last request (1 of 1); a 100 ms pause does not reproduce it. Bound: only the final frame, one source frame stale" -link = "https://github.com/CapSoftware/Cap/pull/2638" - -[[xfail]] -case = "export/jitter-camera-120s/full/mp460/avf" -class = "nondeterministic" -platforms = ["macos"] -flaky = true -bound = { count = 1, frames = [1, 5615], frame = [1585, 7199] } -reason = "Two exports of the 120 s jitter-camera project can differ, only inside the camera runaway of the future entry above (from output frame 1585 on; first difference measured at 1723 or later). Mechanism (scratch/jitter-probe3, runs 38847126270, 38849423910, 38849597307, instrumented decoder): export asks for camera frames one at a time (every batch held one request in 116 traced exports, so try_recv batching is not involved); when more than 250 ms pass between two camera requests the AVAssetReader decoder's idle timeout (IDLE_READER_TIMEOUT, crates/rendering/src/decoder/avassetreader.rs:48, teardown at :728-744) drops every reader, and inside the runaway the re-created reader answers that request correctly, so the runaway restarts from there and every later answer changes (a 390 ms stall before camera request 1105: answer 1105 in one export, 2345 in the other). A forced teardown at camera request 2000 gives 5094 wrong frames, identical in both runs; with the teardown disabled 0 of 12 samples differed although request gaps reached 439 ms. Under suite load the gap is wall-clock, hence flaky (3 of 8 runs beside the export suite; alone 16 of 16 identical). #2638 removes the runaway and with it this class (20 of 20 identical). Bound: same frame count, differences only at or after the runaway's first frame; the future entry bounds the wrong frames themselves (5000 to 5250 camera frames from frame 1585, at most 2232 ahead), and a camera that falls back to FFmpeg after a slow re-created reader fails as stale" -link = "https://github.com/CapSoftware/Cap/pull/2638" - -[[xfail]] -case = "*/jitter-camera-120s/full/*/ffmpeg" -class = "stale" -bound = { camera-frames = 1551, delta = 90, frame = [2493, 6767] } -reason = "Same pts-hole eviction on the FFmpeg decoder (full tier, 120 s): 1551 of 7199 camera frames stale, up to 90 source frames, output frames 2493 to 6767, identical in the MP4 export and in offline playback on every platform that runs it" -link = "https://github.com/CapSoftware/Cap/pull/2638" - [[xfail]] case = "export/backward-hold/backward-jumps/mp460/avf" class = "stale" @@ -105,14 +56,6 @@ bound = { camera-frames = 1, delta = 1, frame = [2579, 2579] } reason = "AVAssetReader serves one camera frame 1 source frame stale after the timeline jumps backward (output frame 2579: source 1048 for 1049): the reader chosen after the jump had already vended past the request and its previous vend was no longer cached, so the nearest older cached frame answered. Every run in probe 38762089521 (42 of 42) and on hosted macOS, but it passed on blacksmith-6vcpu-macos-15 in push run 38846099909: whether the previous vend is still cached depends on the decoder's wall-clock idle timeout, so it is flaky there" link = "https://github.com/CapSoftware/Cap/pull/2638" -[[xfail]] -case = "export/camera-pts-late/full/mp430/avf" -class = "future" -platforms = ["macos"] -bound = { camera-frames = 127, delta = 53, frame = [8, 134] } -reason = "A camera file whose pts start 2 s in runs ahead instead of holding its first frame: before the track's first sample every request steps the reader one sample further (127 camera frames, output frames 8 to 134, up to 53 source frames ahead)" -link = "https://github.com/CapSoftware/Cap/pull/2638" - [[xfail]] case = "export/camera-pts-late/full/mp430/ffmpeg" class = "future" @@ -120,81 +63,6 @@ bound = { camera-frames = 178, delta = 60, frame = [1, 178] } reason = "NEW: FFmpeg decoder, same as the AVAssetReader case: a camera file whose pts start 2 s in runs ahead from the first output frame (178 of 179 frames, up to 60 source frames ahead, every platform)" link = "" -[[xfail]] -case = "seek/seek-gop/keyframes60/avf" -class = "stale" -platforms = ["macos"] -bound = { screen-frames = 3, camera-frames = 3, delta = 1, frame = [351, 701] } -reason = "Wrong-GOP seek: the reader starts on the keyframe time rounded to 10 ms, decodes the previous GOP and shows its last frame under the keyframe's number (requests between the keyframe pts and the next slot show keyframe - 1): 3 seeks, one source frame stale" -link = "https://github.com/CapSoftware/Cap/pull/2638" - -[[xfail]] -case = "seek/seek-native/keyframes60/avf" -class = "stale" -platforms = ["macos"] -bound = { screen-frames = 8, camera-frames = 8, delta = 44, frame = [45, 705] } -reason = "Wrong-GOP seek on the native fixture (see seek/seek-gop/keyframes60/avf): 8 seeks show a frame of the previous GOP, up to 44 source frames stale" -link = "https://github.com/CapSoftware/Cap/pull/2638" - -[[xfail]] -case = "seek/seek-gop/storm30/avf" -class = "stale" -platforms = ["macos"] -bound = { screen-frames = 30, camera-frames = 30, delta = 450, frame = [1, 900] } -reason = "A reused AVAssetReader answers from a frame cached by an earlier, unrelated seek (seek 349 shows 237): 20 of 3000 full-tier seeks (1 in the fast tier), up to 352 source frames stale. Which earlier seek's frame answers depends on timing, so the bound is the mechanism's: any frame of the fixture (450), at most 1% of the seeks" -link = "https://github.com/CapSoftware/Cap/pull/2638" - -[[xfail]] -case = "seek/seek-native/storm30/avf" -class = "stale" -platforms = ["macos"] -bound = { screen-frames = 40, camera-frames = 40, delta = 450, frame = [1, 900] } -reason = "Same reader reuse as seek/seek-gop/storm30/avf on the native fixture: 7 to 28 of 3000 full-tier seeks (2 in the fast tier), up to 243 to 287 source frames stale between runs; bounded like seek/seek-gop/storm30/avf" -link = "https://github.com/CapSoftware/Cap/pull/2638" - -[[xfail]] -case = "seek/seek-*/storm30/avf" -class = "future" -platforms = ["macos"] -flaky = true -bound = { screen-frames = 12, camera-frames = 12, delta = 2, frame = [1, 900] } -reason = "Full-tier seek storm: a few seeks are answered one or two source frames ahead (seek-gop frame 4 shows 5 for 3; 3 seeks on seek-gop, 11 on seek-native). Pool position capping: after a read the reader's pool position is capped to the newest request (crates/rendering/src/decoder/avassetreader.rs, update_decoder_position(decoder_idx, capped)), so a reader that already decoded past a frame is reused for it without a reset and answers with its next sample (same mechanism as seek/seek-native/keyframes60/avf, minimal repro there). Whether a storm reaches that state depends on request timing: the decoder's 250 ms idle timeout (IDLE_READER_TIMEOUT) clears its cache and readers when requests pause, and with a 300 ms pause every 7 requests the seek-native storm has no ahead answer (measured on Apple silicon; 7 ahead without pauses). seek-gop: 3 of 3 full runs and locally; seek-native: 1 of 3 full runs (38763178760) and locally" -link = "https://github.com/CapSoftware/Cap/pull/2638" - -[[xfail]] -case = "seek/vfr-holds/storm60/avf" -class = "future" -platforms = ["macos"] -bound = { screen-frames = 72, camera-frames = 14, delta = 253, frame = [1, 1800] } -reason = "Seek storm over long VFR holds: AVAssetReader shows a frame from the far side of a hold (expected 60, got 217): 72 screen and 14 camera seeks of the full storm (8 and 2 in the fast tier), up to 253 source frames ahead" -link = "https://github.com/CapSoftware/Cap/pull/2638" - -[[xfail]] -case = "seek/vfr-holds/storm60/avf" -class = "stale" -platforms = ["macos"] -flaky = true -bound = { screen-frames = 60, camera-frames = 40, delta = 536, frame = [1, 1800] } -reason = "Same storm, stale answers from reused readers: 52 to 53 screen and 35 to 36 camera seeks in full runs, 0 screen and 2 camera in the fast tier, up to 536 source frames stale (timing-dependent reader reuse; 3 of 4 runs on main)" -link = "https://github.com/CapSoftware/Cap/pull/2638" - -[[xfail]] -case = "seek/vfr-holds/into-holds60/avf" -class = "stale" -platforms = ["macos"] -flaky = true -bound = { camera-frames = 1, delta = 1, frame = [149, 149] } -reason = "NEW: AVAssetReader camera answers one frame stale when a region is revisited after other seeks (see seek/vfr-holds/revisit60/avf for the deterministic sequence): output frame 149, one source frame. Fails 12 of 13 runs on main; it passed once under parallel load, probably because the decoder's wall-clock paths (250 ms idle reader drop, 100 ms scrub cooldown) change which branch answers" -link = "https://github.com/CapSoftware/Cap/pull/2638" - -[[xfail]] -case = "seek/vfr-holds/revisit60/avf" -class = "stale" -platforms = ["macos"] -bound = { camera-frames = 1, delta = 1, frame = [149, 149] } -reason = "NEW: seeks to 2.0 s, 3.5 s, 1.2 s, 2.3 s (each + 29 playback frames, 60 fps): the camera request at 2.483 s (slot 74, CFR camera) is answered with slot 73. Deterministic on runners: every run on main and with #2603; FFmpeg is correct. Repro: CAP_VERIFY_SEEK_SCRIPT='vfr-holds@60:2.0+29,3.5+29,1.2+29,2.3+29' cargo test -p cap-verify --test seek script -- --nocapture" -link = "https://github.com/CapSoftware/Cap/pull/2638" - [[xfail]] case = "seek/seek-gop/storm30/ffmpeg" class = "stale" @@ -332,14 +200,6 @@ bound = { screen-frames = 18, delta = 49, frame = [61, 115] } reason = "FFmpeg decoder, same-clip cross-fade (cuts 0.3-4.1 s and 3.9-7.8 s of one recording, 1.8 s transition): both sides read one decoder at two positions, so blend frames take a side up to 49 source frames stale (frame 61: 80 for 129; 18 frames, output frames 61 to 110, 6 of them where the stale side is the weaker one, inside the 1.8 s transition at output frames 61 to 115). Same mechanism as editing/export/same-clip-transition/forward/ffmpeg; identical on Linux and Windows and for the 30 s full-tier fixture (same cuts)" link = "" -[[xfail]] -case = "seek/seek-native/keyframes60/avf" -class = "future" -platforms = ["macos"] -bound = { screen-frames = 1, camera-frames = 1, delta = 1, frame = [1, 1] } -reason = "Pool position capping: after a read, the reader's pool position is capped to the newest request (crates/rendering/src/decoder/avassetreader.rs, update_decoder_position(decoder_idx, capped)), so a reader that already decoded past a frame still looks usable for it (find_best_decoder_for_time_with_reuse_threshold: position <= request), is reused without a reset, and answers with the next sample it decodes. Here output frame 1 (0.017 s) shows source 1 for source 0. Minimal: CAP_VERIFY_SEEK_SCRIPT='seek-native@60:0,8.8,0.016667' cargo test -p cap-verify --test seek script -- --nocapture. Deterministic on every macOS runner" -link = "https://github.com/CapSoftware/Cap/pull/2638" - [[xfail]] case = "seek/seek-native/keyframes60/ffmpeg" class = "stale"