Ghosts S01e11 — Libvpx

ffmpeg -i input.mov -c:v libvpx-vp9 -b:v 0 -crf 18 -pass 1 -f null /dev/null ffmpeg -i input.mov -c:v libvpx-vp9 -b:v 0 -crf 18 -pass 2 output.webm Libvpx is a phenomenal encoder—it often beats x265 at half the bitrate for animated or grainy content. But it is also a complex state machine. Ghosting artifacts are a reminder that "lossy" doesn't just mean losing detail; sometimes, it means gaining things that were never there.

Ghosts in the Machine: Debugging FFmpeg’s Libvpx Encoder (Season 1, Episode 11) ghosts s01e11 libvpx

# Check your version ffmpeg -version | grep libvpx # If it's 1.11 or 1.12, upgrade. If you can’t upgrade (e.g., in a production container), tune around the bug by reducing the temporal dependency depth. ffmpeg -i input

Pause the video. Look at frame 1042. Fine. Advance one frame. A faint, semi-transparent outline of frame 1042 remains superimposed over frame 1043. Another frame forward? The ghost fades. But it shouldn't be there at all. Ghosts in the Machine: Debugging FFmpeg’s Libvpx Encoder

Welcome to Ghosts S01E11: Libvpx . Let’s exorcise it. It started with a routine archival job. We were transcoding a film scan (ProRes 4444 → WebM) for a client’s interactive museum installation. The command was standard:

This wasn't a decoding error. This was the encoder lying to us about its state. In VP9, Libvpx uses a complex reference frame structure. Unlike H.264’s simple "past/future" references, VP9 maintains up to eight reference frames simultaneously: LAST, GOLDEN, ALTREF, and their temporal variants.

ffmpeg -i master.mov -c:v libvpx-vp9 -pix_fmt yuv420p10le -crf 18 -b:v 0 output.webm The first pass looked incredible. Grain was preserved. Banding was minimal. But during playback on a high-refresh-rate display, we noticed it: .