Repository navigation
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.
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.
The button in the Encoder tab, or from the command line:
qBlank.exe --fetch-ffmpegWhat 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:
-
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. -
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. -
Downloads the archive straight to disk,
ffmpeg-<version>-full_build-shared.7z, 57 MB. Where the system'starcannot read 7z the zip instead, 95 MB. -
Verifies SHA-256 with
BCrypt, streaming the file rather than loading it. -
Extracts only the four libraries into
ffmpeg\new, deletes the archive, and writes acompletefile 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.
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.
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.
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.
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.