Files
Romain PINSOLLE 85448940ef render: turn display-rotated video on the GPU (#3467) (#3484)
* render: turn display-rotated video on the GPU (#3467)

A portrait phone clip (4K60 HEVC with a 90° display matrix) dropped ~97 %
of its frames: every decoded 4K frame was turned on the CPU, which is too
slow at 60 fps, and with the GOP cache's lock held.

- GopCache caches decoded frames raw when they need a crop or a rotation;
  `frame_late` crops and turns them off the lock, and the new
  `frame_late_unturned` hands them over as decoded with the quarter turns
  still to apply.
- MediaSource gains `video_frame_unturned` (default: the upright frame, 0
  turns). MP4 and Matroska implement it; the pool's wrapping sources and
  the frame server's timed source pass it on.
- The render plan folds the turns into the layer matrix of plain layers,
  and into the GPU effect stage (`LayerFx::turns`): `fs_fx_source` reads
  the source on its decimated grid through the turn, and the CPU reference
  decimates then turns, so effects still see the upright picture.

Other paths (CPU-only effects, log media, a clean aperture, export) still
turn on the CPU as before.

* codecs: cache intra-only frames raw too (#3467)

Intra-only decoders (ProRes, DNx) cropped and turned every frame before
caching it, so frame_late_unturned found it already turned and the GPU
had nothing left to do. Their frames are now cached raw like long-GOP
output: frame_late still crops and turns them off the lock, and
frame_late_unturned hands them over as decoded.

* docs: document the unturned-frame entry points (#3467)

* codecs: follow main's frame lookup in the unturned paths (#3467)

main changed `from_tick` (Matroska) to take the frame rate and `snap`, and
MP4 looks frames up with `lookup_time(.., rate, snap)`. `video_frame_unturned`
still used the old calls: the Matroska one no longer compiled, and the MP4 one
could pick another sample than `video_frame` when `snap` is on. Both now use
the same lookup as their `video_frame`.

Also repairs the merge of main into this branch: a closing brace was lost in
the #3467 test (the test module did not compile) and a blank line was doubled
(cargo fmt --check failed).

* codecs: test every display turn, crop with turn, drafts and eviction of raw frames

* gpu: give the neutral-corrector test LayerFx its turns field

Merging main added a LayerFx initializer without the turns field, which
breaks compilation of the gpu tests (E0063).
2026-11-05 03:19:24 +01:00
..

filmcraft-codecs

The L2 container and codec hub connects media sources to FilmCraft's software and registered hardware decoders. It provides container openers, GOP-aware video seeking, packet audio decoding and standalone audio imports. See the crate-level API documentation in src/lib.rs and docs/architecture.md for the source and decoder interfaces.

Symphonia audio failure handling (#2798)

Symphonia remains an unmodified MPL-2.0 dependency. Its decoder construction, packet decoding, sample conversion and reset calls are guarded against unwinding. On targets built with panic = "unwind", a panic becomes a CodecError::Decode asking the caller to reopen the media. A packet decoder that panicked is quarantined: later decode calls fail, and reset never calls into its potentially damaged state. Because PacketDecoder::reset has no error return, a panic during reset is logged and reported by the next decode. A newly constructed decoder can be used to retry.

Standalone imports guard the whole attempt, including format probing, packet reading and decoder construction. A decoder panic aborts the import even if earlier packets decoded; partially decoded samples are not returned as a successful source. Ordinary returned errors retain the existing behavior: packet decoding returns an error without quarantining the decoder, and standalone imports skip corrupt packets. This change does not change that pre-existing damaged-file policy or harden the separate H.264/HEVC worker paths in #2798.

The default wasm32-unknown-unknown target uses panic = "abort", so decoder panics cannot be caught or converted to CodecError::Decode there. The guard handles Rust unwinding; it cannot recover from a process abort or an allocation failure that aborts. It does not replace input validation or repair bugs inside a dependency.

Tests

src/audio_panic_tests.rs wraps a real PCM decoder through Symphonia's public Decoder API with per-instance synthetic decode/reset failures. No dependency implementation is copied. The tests check errors after a successful packet, no reuse after panic (including reset), fresh-decoder recovery, ordinary-error recovery, constructor failure, and rejection of partial standalone imports. WAV truncations and header mutations run under catch_unwind; all fixtures are generated in memory. Existing MP3, AIFF, FLAC and Vorbis tests cover the normal audio paths with FFmpeg used only as an external fixture generator/oracle.

cargo test -p filmcraft-codecs --lib audio::panic_tests
FILMCRAFT_REQUIRE_ORACLES=1 cargo test -p filmcraft-codecs
cargo xtask ci