VP8 WebM showed black video: there was no VP8 decoder. New L0 crate
filmcraft-vp8, implemented from RFC 6386 sections 1-19 (tables extracted
from the RFC text, nothing from the attached reference decoder): every
coding tool, versions 0-3, hidden altref frames, size changes. Bit-exact
with libvpx on 62 libvpx VP8 test vectors, real-world FATE samples and
generated fixtures; never panics on corrupted input.
Wired into codecs for Matroska / WebM (V_VP8, including the alpha layer
of transparent WebM) and MP4 (vp08). VP8 colour is BT.601 (RFC 6386 9.2).
A hidden altref frame is a WebM block of its own with the time of the
frame that follows it: the VP8 time lookup now moves on to that shown
frame instead of repeating the previous one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A GopCache decodes with its lock held, and that decoding starts rayon work
(rotating iPhone frames, cropping, slice-threaded decoders). On the global pool
a worker that took part of it could, waiting inside it, pick up another render
job asking the same cache for a frame and block on the lock, while the lock
holder waited for the work on that worker's stack: exports of rotated iPhone
HEVC clips stopped making frames in about half the runs.
The locked decoding now runs on a separate decode pool, whose threads never wait
for a cache's lock; a caller that is a rayon worker waits on a plain thread so it
does no other rayon work while holding the lock. The cancel/catch-up/draft/
background hints travel with it (filmcraft_media::cancel::Context). Intra-only
streams crop and rotate before taking the lock again.
Second version of #662 (reverted): the regression test's stand-in decoder summed
with overflow, which panics in debug builds; it now wraps explicitly.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Problem: symphonia rejects every Layer II frame whose bit rate / mode
combination ISO/IEC 11172-3 does not allow (mono above 192 kbit/s, stereo
below 64 kbit/s). ffmpeg writes mono MP2 at 384 kbit/s by default, so such
tracks played as silence in MPEG-TS / PS, Matroska and raw .mp2 files.
Fix: new L0 crate filmcraft-mpa, a clean-room Layer I / II decoder written
from ISO/IEC 11172-3 and 13818-3 (all modes incl. joint stereo, MPEG-1/2/2.5
rates, the disallowed combinations decoded with the allocation table their
bit rate per channel selects). PacketDecoder::mpeg_audio uses it for layers
I and II (packets of several frames too); raw .mp2 files decode with it,
resyncing after damage. Oracle tests vs ffmpeg: 73-84 dB SNR on ffmpeg /
twolame streams and synthetic Layer I / joint stereo frames; fuzzed damaged
frames never panic.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A GopCache decodes with its lock held, and that decoding starts rayon work
(rotating iPhone frames, cropping, slice-threaded decoders). On the global pool
a worker that took part of it could, waiting inside it, pick up another render
job asking the same cache for a frame and block on the lock, while the lock
holder waited for the work on that worker's stack: exports of rotated iPhone
HEVC clips stopped making frames in about half the runs.
The locked decoding now runs on a separate decode pool, whose threads never wait
for a cache's lock; a caller that is a rayon worker waits on a plain thread so it
does no other rayon work while holding the lock. The cancel/catch-up/draft/
background hints travel with it (filmcraft_media::cancel::Context). Intra-only
streams crop and rotate before taking the lock again.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AviSource maps AVI streams onto the decoders, opened from bytes or a
streaming reader (Media Browser probe):
- Video: Motion JPEG, H.264 and HEVC (Annex B, our decoders) and
uncompressed BGR24 / BGRX32 / YUY2 / UYVY / I420 / YV12 / NV12 / grey
(new RawVideoDecoder). Empty "drop frame" chunks hold the picture
before. AVI has no timestamps: H.264 with B-frames is stored in decode
order, so the presentation order comes from the pictures' order counts
(mxf::avc_presentation_order), keeping the slots the pictures occupy.
A writer that kept a fine time base (ffmpeg copying an MP4's 1/600)
fills the slots between pictures with empty chunks; the reported frame
rate is the one the pictures play at. DivX / Xvid and DV are named and
report that they have no decoder; their sound plays.
- Audio: PCM (integer and float) from the chunks; MPEG audio and AC-3
frames, which AVI chunks need not follow, found again across chunk
boundaries with the new filmcraft_mpegts::frame_bytes.
- Motion JPEG frames without Huffman tables (the AVI1 convention) decode
with the standard ITU-T T.81 Annex K tables (also for MOV).
Tests against ffmpeg's decode of the same files: H.264 with B-frames
bit-exact under random access, also copied from a 1/600 MP4;
uncompressed video and PCM bit-exact; JPEG and MP3 / MP2 / AC-3 within
decoder tolerance; drop frames; DivX; JPEG without tables equal to the
same frame with them. Engine: an AVI imports, makes a sequence, plays
picture and sound, and probes without reading it whole.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Resolve inherited physical key collisions when loading built-in presets,
while preserving established explicit winners and deliberate custom conflicts.
Read every ffprobe display-frame timestamp and register the requested credit.
Verify exact frame-plane reuse without depending on concurrent global metrics.
Co-authored-by: Codex <codex@openai.com>
AIFF is listed as an importable audio format and FilmCraft exports AIFF,
but the symphonia build had only the wav reader enabled, so every .aif /
.aiff import (including FilmCraft's own AIFF exports) failed with
"unsupported media: end of stream". Enable symphonia's aiff reader (the
same MPL-2.0 symphonia-format-riff crate already used for WAV; Cargo.lock
is unchanged). The new test decodes a synthetic 16-bit stereo AIFF.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- HEVC inverse transform: sum basis rows of non-zero coefficients with a
const-generic size (bit-exact, tested against the direct product);
SAO edge offset gets a check-free interior loop. HEVC CPU/frame -60%.
- H.264/HEVC/VP9 planes are moved into VideoFrames, not copied.
- Catch-up decoding: frame jobs tell sources which frames are late
(filmcraft_media::cancel::with_catch_up); the GOP cache skips
non-reference samples of late frames (H.264 nal_ref_idc 0, HEVC
sub-layer non-reference pictures). Scrubs keep the last 2 s decoded.
- perf.stats: engine query + UI/control method (decode counters,
playback shown/dropped, frame-worker decode/render ms, cache use).
- Timeline waveform source peak cached; bench seek section and --only.
- docs/performance.md with before/after results.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Av1Decoder in filmcraft-codecs: feeds the av1C configuration OBUs before the
first sample (and after every reset), converts pictures to 8/16-bit planar
YUV with the stream's colour description, and reports random-access points
from the first frame header of each temporal unit (shown key frame) so GOP
seeking ignores bogus container sync flags. Matroska V_AV1 tracks map to an
av01 sample entry; MP4 labels show the AV1 profile.
All-intra 8-bit MP4 and 10-bit WebM files seek sample-exactly against
libdav1d. Inter frames still return "unsupported" until inter prediction
lands.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
New L0 crate filmcraft-dnx, written from SMPTE ST 2019-1:2016 + Amd 1:2023
(tables extracted from the standard's text by tools/extract_tables.py).
Decoder: every compression ID (DNxHD 1235-1260 incl. field-coded and
macroblock-adaptive interlace, thin rasters, RGB 4:4:4; DNxHR 1270-1274
LB/SQ/HQ/HQX/444), 8/10/12-bit, 4:2:0/4:2:2/4:4:4, DCT and lossless alpha,
macroblock rows in parallel. Matches ffmpeg within IDCT precision (max 2 LSB,
<=50 per million beyond 1, 87-99% identical) on 22 ffmpeg fixtures; follows
ffmpeg where it departs from the standard's text (reconstruction offset,
CID 1271 weights, 4:4:4 signalling) - documented in the README.
Encoder: DNxHR LB/SQ/HQ/HQX(10/12-bit)/444, CBR at the eq. 7.1 frame sizes,
uniform-qsf binary search + per-macroblock refinement; ffmpeg decodes every
profile with -xerror (HQ luma PSNR 58.8-66.3 dB).
Wiring: MOV AVdn/AVdh import (codec label from the CID), export
Format::DnxHr (MOV AVdh, dnxProfile lb|sq|hq|hqx) in the export dialog and
file.exportMedia.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
crates/codecs decodes Opus with filmcraft-opus (48 kHz output):
- Matroska/WebM A_OPUS: CodecPrivate = OpusHead. CodecDelay removes the
pre-skip exactly (header pre-skip when CodecDelay is absent); packet starts
are accumulated from TOC durations because block timestamps are only
tick-accurate. Random access decodes max(SeekPreRoll, 320 ms) first.
- MP4/MOV Opus + dOps: edit list removes pre-skip (dOps pre-skip without
one); same pre-roll on random access.
- Family-1 surround reordered from Vorbis to WAV/SMPTE channel order;
corrupt packets are concealed instead of failing.
Tests (ffmpeg/libopus fixtures, skipped when absent): WebM stereo, MKV mono
VoIP (SILK/hybrid), MKV 5.1 multistream, MP4 stereo; sample-aligned against
libopus (52-89 dB sequential, 45-89 dB after random access).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>