mirror of
https://github.com/storytold/filmcraft.git
synced 2026-10-25 18:48:09 -06:00
Settings > Playback > Draft decoding (off by default): the Program monitor asks for draft frames only while playing at 1/2 resolution or lower (FrameKey::draft, filmcraft_media::cancel::with_draft). The GOP cache runs the decoder in draft mode for those requests and serves draft frames to draft requests only, so paused frames, renders and exports always get exact pictures (codecs test: exports after draft playback match ffmpeg). Draft plans hand the GPU box-decimated YUV planes at the drawn size. perf.stats reports draft frames, H.264 threads and the preference; the bench gains 2160p quarter and draft playback cases. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
69 lines
2.8 KiB
Rust
69 lines
2.8 KiB
Rust
//! H.264 draft decoding (opt-in, reduced-resolution playback) through the media stack: it is off
|
|
//! by default, its approximate frames only ever reach draft requests, and decoding for an export
|
|
//! after draft playback (same source, same GOP cache) still matches ffmpeg bit for bit.
|
|
|
|
mod common;
|
|
|
|
use common::*;
|
|
use filmcraft_media::FrameRequest;
|
|
use filmcraft_media::cancel::with_draft;
|
|
|
|
#[test]
|
|
fn draft_playback_never_reaches_exports() {
|
|
let ff = filmcraft_testkit::require_ffmpeg!();
|
|
// B-pyramid: references and non-reference B pictures (the ones draft mode leaves unfiltered)
|
|
let Some(file) = fixture(
|
|
&ff,
|
|
"draft_h264.mp4",
|
|
&[
|
|
"-f",
|
|
"lavfi",
|
|
"-i",
|
|
"testsrc2=s=320x240:r=24:d=2,noise=alls=10:allf=t",
|
|
"-c:v",
|
|
"libx264",
|
|
"-preset",
|
|
"fast",
|
|
"-x264-params",
|
|
"bframes=3:b-pyramid=normal:keyint=48",
|
|
"-pix_fmt",
|
|
"yuv420p",
|
|
],
|
|
) else {
|
|
return;
|
|
};
|
|
let raw = ffmpeg_frames(&ff, &file, "yuv420p");
|
|
let (w, h) = (320usize, 240usize);
|
|
let fsize = w * h * 3 / 2;
|
|
let n = raw.len() / fsize;
|
|
assert!(n >= 40, "{n} frames");
|
|
let reference = |i: usize| raw_planes(&raw[i * fsize..(i + 1) * fsize], w, h, w / 2, h / 2, 1);
|
|
let src = filmcraft_codecs::open_bytes("draft_h264.mp4", bytes(&file)).expect("open");
|
|
let rate = src.info().frame_rate();
|
|
|
|
// Default: nothing is a draft, every frame exact.
|
|
let g0 = filmcraft_codecs::gop_stats();
|
|
for i in 0..n {
|
|
let f = src.video_frame(FrameRequest { time: rate.tick_of(i as i64), scale: 0.5 }).expect("frame");
|
|
assert_eq!(max_diff(&planes(&f), &reference(i)), 0, "frame {i} without draft mode");
|
|
}
|
|
assert_eq!(filmcraft_codecs::gop_stats().draft, g0.draft, "draft decoding is off by default");
|
|
|
|
// Draft playback at 1/2 resolution on a fresh source: non-reference pictures are drafts.
|
|
let src = filmcraft_codecs::open_bytes("draft_h264.mp4", bytes(&file)).expect("open");
|
|
let g1 = filmcraft_codecs::gop_stats();
|
|
let mut approximate = 0;
|
|
for i in 0..n {
|
|
let f = with_draft(true, || src.video_frame(FrameRequest { time: rate.tick_of(i as i64), scale: 0.5 })).expect("frame");
|
|
approximate += (max_diff(&planes(&f), &reference(i)) > 0) as usize;
|
|
}
|
|
let drafts = filmcraft_codecs::gop_stats().draft - g1.draft;
|
|
assert!(drafts > 0 && approximate > 0 && approximate as u64 <= drafts, "{drafts} draft frames, {approximate} differ");
|
|
|
|
// Then an export from the same source (no draft hint): every frame is exact again.
|
|
for i in 0..n {
|
|
let f = src.video_frame(FrameRequest::full(rate.tick_of(i as i64))).expect("frame");
|
|
assert_eq!(max_diff(&planes(&f), &reference(i)), 0, "export frame {i} after draft playback");
|
|
}
|
|
}
|