mirror of
https://github.com/storytold/filmcraft.git
synced 2026-10-29 04:27:47 -06:00
- New crates/platform (filmcraft-platform, L5): OS media FFI only. The one crate allowed `unsafe` (own [lints] with unsafe_code = "deny", #[allow(unsafe_code)] only on the videotoolbox module, SAFETY comment on every block, safe Result-returning public API). AGENTS.md §0.3 names the exception; docs/adr/0001-platform-ffi.md records why and the rules. - VideoToolbox decoder for avcC / hvcC, 8/10-bit 4:2:0 and 4:2:2: session from the sample entry's parameter sets with hardware required, asynchronous decode (2 in flight), biplanar CVPixelBuffer copied to planar Yuv8/Yuv16, reorder buffer of the stream's own depth, RASL skipped after a starting CRA. Output callback under catch_unwind. - HybridDecoder: transparent mid-stream fallback to the software decoder (replays samples since the last restart point, drops already-returned pictures). New codecs::software_video_decoder (built-in factories only) and codecs::hw (switch, counters, NalStreamInfo shared with software decoders; vui_color / sar_par helpers now used by both). - Settings ▸ Playback ▸ Hardware decoding: Auto / Off (engine applies it to the codecs switch); desktop app, CLI/MCP and bench call register(). - perf.stats decode.framesDecoded and decode.hardware counters. - Tests: bit-exact parity with the software decoders (H.264 High, HEVC Main open GOP, Main 10; resets, reseeks, flushes), forced-failure fallback, mutation fuzz of samples and parameter sets, cross-platform fallback with a stand-in decoder, setting Off, HEVC 4:2:2 vs ffmpeg. - Bench --hw auto|off; results in docs/performance.md (4K CPU/frame ~86-119 ms -> ~4 ms). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
48 lines
2.1 KiB
Rust
48 lines
2.1 KiB
Rust
//! Settings ▸ Playback ▸ Hardware decoding through the decoder registry and the media stack:
|
|
//! Off gives the software decoder (and no hardware frames), Auto the hardware one where the
|
|
//! system has it. Its own test binary: the switch is process-wide.
|
|
|
|
mod common;
|
|
|
|
use common::*;
|
|
use filmcraft_media::FrameRequest;
|
|
|
|
#[test]
|
|
fn off_uses_the_software_decoder() {
|
|
let ff = filmcraft_testkit::require_ffmpeg!();
|
|
let Some(path) = named(&ff, "hevc_main.mp4") else { return };
|
|
let s = read_stream(&path);
|
|
let available = filmcraft_platform::register();
|
|
assert_eq!(filmcraft_platform::register(), available, "registering twice is harmless");
|
|
|
|
filmcraft_codecs::hw::set_hardware_decoding(false);
|
|
let d = filmcraft_codecs::make_video_decoder(&s.entry).unwrap();
|
|
assert_eq!(d.name(), "FilmCraft HEVC");
|
|
let bytes: std::sync::Arc<[u8]> = std::fs::read(&path).unwrap().into();
|
|
let (g0, h0) = (filmcraft_codecs::gop_stats(), filmcraft_codecs::hw::hw_stats());
|
|
let src = filmcraft_codecs::open_bytes("hevc_main.mp4", bytes.clone()).unwrap();
|
|
let rate = src.info().frame_rate();
|
|
for i in 0..30 {
|
|
src.video_frame(FrameRequest::full(rate.tick_of(i))).unwrap();
|
|
}
|
|
assert!(filmcraft_codecs::gop_stats().frames > g0.frames, "frames counted");
|
|
assert_eq!(filmcraft_codecs::hw::hw_stats().frames, h0.frames, "Off: no hardware frames");
|
|
|
|
filmcraft_codecs::hw::set_hardware_decoding(true);
|
|
let d = filmcraft_codecs::make_video_decoder(&s.entry).unwrap();
|
|
let hw = filmcraft_platform::hardware_decoder_for(&s.entry);
|
|
if !hw {
|
|
eprintln!("SKIPPED (Auto half): no hardware decoder ({available:?})");
|
|
assert_eq!(d.name(), "FilmCraft HEVC");
|
|
return;
|
|
}
|
|
assert_eq!(d.name(), "VideoToolbox HEVC");
|
|
let h1 = filmcraft_codecs::hw::hw_stats();
|
|
let src = filmcraft_codecs::open_bytes("hevc_main.mp4", bytes).unwrap();
|
|
for i in 0..30 {
|
|
src.video_frame(FrameRequest::full(rate.tick_of(i))).unwrap();
|
|
}
|
|
let h2 = filmcraft_codecs::hw::hw_stats();
|
|
assert!(h2.frames > h1.frames && h2.sessions > h1.sessions, "Auto: hardware frames {h1:?} → {h2:?}");
|
|
}
|