Skip to content
NuclearMeltdown edited this page Aug 25, 2026 · 2 revisions

ffmpeg

src/record/ffmpeg_locator.cpp, src/record/ffmpeg_download.cpp

Two things require ffmpeg.exe: recording, and HDR screenshots in AVIF, which go through libaom.

Nothing else does. SDR screenshots go through Windows Imaging Component precisely so they still work on a fresh machine where nobody has pressed the download button yet, and so does the other HDR format — Windows ships the JPEG XR encoder — so an HDR still can be saved without ffmpeg by choosing that one instead. The preview, the composite filters, deinterlacing and the virtual camera never touch it.

ffmpeg lives at the top of the Encoder tab rather than buried in it, so the option to install it is the first thing visible when it is missing. That placement is deliberate and was restored after being accidentally removed once.

Finding it

LocateFfmpeg() looks in this order:

  1. The configured path, if one is set
  2. ffmpeg\bin next to CapView.exe
  3. ffmpeg next to CapView.exe
  4. Next to CapView.exe itself
  5. PATH

Only the path and version are filled in here. The encoder probe is separate, because it takes seconds.

Downloading it

The button in the Encoder tab, or from the command line:

CapView.exe --fetch-ffmpeg

FfmpegDownloader::Start() runs on a worker thread and does four things:

  1. Fetches the checksum first, from https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-essentials.zip.sha256. First, so that a mismatch is detectable at all — a checksum fetched after the archive from the same compromised source proves nothing.
  2. Downloads the archive, reporting progress where the server supplies a content length and a negative progress where it does not.
  3. Verifies SHA-256 with BCrypt, streaming the file rather than loading it.
  4. Extracts only ffmpeg.exe and deletes the archive.

StartVersionCheck() asks the server which version it would deliver without downloading anything, on the same worker.

Why downloading rather than bundling

The usual Windows builds contain x264 and x265 and are therefore GPL licensed, and whoever hands them on owes the source with them.

Pressing a button that fetches from gyan.dev is not distribution, which keeps CapView's own MIT licence uncomplicated. Calling ffmpeg as a child process is not linking either, so nothing about the GPL reaches this program. Redistributing CapView together with an ffmpeg build is a different matter, and the GPL then applies to what is being distributed.

Working out what the hardware can do

ffmpeg -encoders is not enough. It lists what the build was compiled with, not what the hardware can do: a build with h264_nvenc on a machine with an AMD card lists it happily and then fails at record time.

So each candidate gets a one frame test encode to NUL, which takes a moment and tells the truth.

Two entry points:

  • ProbeEncoders() — the full list, several seconds. This is the "Test encoders" button, run on demand and never at startup.
  • ResolveUsable() — tests only as much as is needed to answer "can I record right now": the requested encoder, or for automatic the preference order until one works. Results are remembered, so a second call costs nothing.

The result is stored in the configuration and reused on subsequent starts. ApplyProbeResults() fills the list back in from the stored values, so the tab shows what this machine can actually do without re-running anything.

It is discarded when the graphics adapter or the ffmpeg build changes — both of those invalidate every answer in it.

The two automatic modes

There is no scoring. Which order applies is the user's choice, because the two goals genuinely conflict: AV1 is the better codec and the one an older television cannot play.

Mode Order Suited to
Plays anywhere H.264, H.265, AV1 Files that open on any phone, television or editor
Smaller files AV1, H.265, H.264 The same quality at a fraction of the size

Hardware encoders precede the CPU encoders in both orders. Each mode takes the first entry of its order that passed the test.

Running it

RunFfmpeg() captures stdout and stderr together and returns false only when the process could not be started at all — a non-zero exit is a result, not a failure to run, and the caller decides what it means.

Clone this wiki locally