Skip to content
NuclearMeltdown edited this page Oct 19, 2026 · 9 revisions

ffmpeg

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

Three things require ffmpeg's libraries: recording, remuxing to MP4, 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.

Loading it

Since 6.1 qBlank does not run ffmpeg.exe; it loads four of its libraries into itself and calls them (see Recording for why):

File For
avcodec-63.dll The encoders
avformat-63.dll MKV and MP4, writing and reading
avutil-61.dll Frames, buffers, options, the log
swresample-7.dll The recording sources onto the recording's clock

They are looked for in one place only: the ffmpeg folder next to qBlank.exe, where the download puts them. Not in PATH, and there is no path setting any more. A library is code running inside qBlank, and one that happens to be lying somewhere is not one to load.

LoadLibav() loads all four or none, resolves every function qBlank calls, and checks each library's major version against the one the headers in third_party/ffmpeg were written for. The file name carries the number already, but a file can be named anything. A library of another major has another layout for its structures, and reading those wrongly does not fail cleanly. It explains itself instead: avcodec-63.dll is version 62, 63 is needed.

Once loaded they stay loaded until qBlank exits. Only finding them happens at startup; the encoder probe is separate, because it takes seconds.

ffmpeg's own log goes into qBlank's: its errors as warnings, and the first error on a thread is kept, so that a failed call can say what went wrong in ffmpeg's words instead of a number. The encoder probe turns that off while it tests, because failing is what half of it is for.

Downloading it

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

qBlank.exe --fetch-ffmpeg

What it fetches is the shared build from GyanD/codexffmpeg, the GitHub releases of the gyan.dev builds. Shared means the libraries come as DLLs, which the essentials build qBlank used to fetch does not have.

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

  1. Asks GitHub which release is newest, among the tags of the major the headers belong to (git/matching-refs/tags/9.), and then that release for its file list.
  2. Takes the checksum from the same answer. GitHub publishes a SHA-256 for every file it holds (digest), so it comes from GitHub and not from the file it is checking.
  3. Downloads the archive straight to disk, ffmpeg-<version>-full_build-shared.7z, 57 MB. Where the system's tar cannot read 7z the zip instead, 95 MB.
  4. Verifies SHA-256 with BCrypt, streaming the file rather than loading it.
  5. Extracts only the four libraries into ffmpeg\new, deletes the archive, and writes a complete file with the version into the folder. Unpacked they are about 120 MB, most of it avcodec.

Then they move in, all four or none: each installed one steps aside as .old, the new one takes its name, and if any rename fails every one is put back. A loaded library cannot be written over but can be renamed, so this works with them in use — but then the process would be running one set and holding the next, and qBlank does not mix them. So:

  • Nothing loaded yet, the first download: in at once. ffmpeg is located again as soon as no recording or encoder test is running, and the tab shows it.
  • Already loaded, an update: it waits in ffmpeg\new, and the next start moves it in before anything is loaded. The tab says takes over at the next start.

A new folder without complete is a download that did not finish, and it is deleted. The .old files go once nothing has them open; and the ffmpeg.exe earlier versions downloaded goes once the libraries have taken its place.

StartVersionCheck() does only the first step: which version the download would fetch, without downloading anything, on the same worker.

Updating it

The check compares that version with the one the libraries report (av_version_info(): 9.0.2-full_build-www.gyan.dev, the release number at the front). A git build writes a date there instead and is never offered an update, because there is no release to compare it with.

  • Settings: when a newer release is out, the version shows in amber and the button reads Update to X.
  • At startup: with "Check for updates at startup" on, the check runs next to the one for qBlank. A newer release raises a popup once per session: Update or Later. Hidden while the download runs, the result comes as a toast.

Only the major the headers were written for is ever offered: 9.0.3, 9.1, never 10.0. A new major can change the structures qBlank reads, so it arrives with a qBlank release that brings its headers, and that release's downloader fetches it. Up to 6.0 a new major only raised a note to make a test recording, because the command line it ran could cope with most of what changed.

Why downloading rather than bundling

Three reasons, and the licence is not the first of them.

It is the user's call whether ffmpeg is on the machine at all. Recording, remuxing and AVIF screenshots are the only things that need it; everything else — the preview, the composite filters, deinterlacing, the virtual camera, SDR screenshots — runs without. Somebody who only ever watches should not have 120 MB installed on their behalf. And qBlank stays one file of about 3 MB, which is most of what makes "download it and run it" true.

A bundled copy would age. The download fetches the newest release there is, so pressing the button on a machine today produces a current build regardless of how old the qBlank release is. A binary committed to the repository would sit at the version it was committed at, and keeping it current would mean re-committing it — or building ffmpeg from source as part of this project, which is a second project. This one is not tied to the ffmpeg release cycle and should not become tied to it.

The licence is the third reason and it has since become the weakest one. The shared build contains x264 and x265 and is configured --enable-gpl --enable-version3, so it is GPL 3 — the same terms as qBlank, which loading it makes a GPL 3 work as a whole. Shipping the two together would no longer be awkward; it still does not happen, because the first reason did not change. The headers in the repository are LGPL 2.1 or later and contain no code of their own; see THIRD-PARTY.md.

Working out what the hardware can do

The list of encoders in the library 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 two frame test encode of mid grey, flushed so it has to produce its packets, in the pixel format the recorder will hand it. That 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.
  • EnsureUsableEncoder() — 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.

Since 6.1 an encoder that passes is tried once more in 4:4:4, eight bits and then ten, if it can do it at all (FullChromaCandidate(): H.264 and H.265 on NVENC and the CPU; Quick Sync, AMF and AV1 take 4:2:0 only and are not asked). The log says Encoder <name> 4:4:4: 8 bit ok, 10 bit ok. See Encoder settings.

A failed test says why in the log, in the encoder's own words: Encoder h264_amf: not available - Not in this build, or the driver's No capable devices found.

The result is stored in the configuration (encodersAvailable, encodersFullChroma, encodersFullChroma10) and reused on subsequent starts. ApplyCachedProbe() fills the list back in from the stored values, so the tab shows what this machine can actually do without re-running anything. A cache from before 6.1 has no 4:4:4 answers; the tab then says to test again.

"Test encoders" starts from the full list every time. Before 6.1 it reused the previous answers and so changed nothing on a second press.

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.

Clone this wiki locally