ADR-0281: vmaf-tune Intel QSV codec adapters (h264_qsv, hevc_qsv, av1_qsv)¶
- Status: Accepted
- Date: 2026-05-03
- Deciders: Lusoris
- Tags: tooling, ffmpeg, codec, qsv, intel, fork-local
Context¶
ADR-0237 Phase A pinned the codec-adapter contract under tools/vmaf-tune/src/vmaftune/codec_adapters/ with the explicit expectation that "new codecs are one-file additions" off the same registry. That adapter contract was deliberately multi-codec from day one — Phase B (target-VMAF bisect) and Phase C (per-title CRF predictor) need a representative spread of codecs in the corpus, not just the libx264 baseline.
The fork has no Intel hardware-encode coverage in the harness yet. QSV is the only path to drive the integrated GPU on consumer Intel silicon (Kaby Lake and newer) and the Arc / Battlemage discrete parts; without it the fork can corpus-train CPU encodes only and the vmaf_tiny codec-bucket one-hot (ADR-0235) keeps the QSV column empty. NVENC and AMF land in parallel sibling PRs; this ADR scopes the QSV slice.
The three QSV encoders (h264_qsv, hevc_qsv, av1_qsv) share the same FFmpeg parameter shape — seven preset levels with x264-style names (veryslow through veryfast, no ultrafast / superfast), one quality knob (-global_quality N, ICQ rate control, integer range 1..51). The shared shape invites a factored common module rather than three independent adapter files each repeating the preset list and the validation helpers.
Decision¶
We will ship three new codec adapters under tools/vmaf-tune/src/vmaftune/codec_adapters/ — h264_qsv.py, hevc_qsv.py, av1_qsv.py — each backed by a shared private module _qsv_common.py that pins the QSV preset vocabulary, the ICQ quality range, the preset identity check (QSV uses x264 names verbatim, so the "translation" is a guarded identity), the global_quality validator, and an ffmpeg -encoders probe that fails fast when libmfx / VPL is not compiled into the FFmpeg on PATH. Each adapter is a thin frozen dataclass that only pins name / encoder and delegates validate() to the shared helpers. The registry entry for all three goes into codec_adapters/__init__.py; the search loop continues to route through the registry without branching on codec identity.
The encode pipeline (encode.py) is currently x264-CRF-tied and will be widened in a later PR to dispatch on adapter.quality_knob. That widening is explicitly out of scope here — the adapter classes pin the contract and the parameter-validation surface so the encode-side wiring lands as a follow-up without requiring further adapter changes.
Alternatives considered¶
| Option | Pros | Cons | Why not chosen |
|---|---|---|---|
| Three independent adapter files, no shared module | Trivial; matches the libx264 single-file shape | Triplicates the preset list, the ICQ window constant, and the validate() body; drift waiting to happen | QSV's three encoders share exactly the same parameter shape — the duplication carries no information |
One unified qsv.py adapter parametrised by FFmpeg encoder name | Maximally compact | Breaks the registry-as-flat-table invariant from ADR-0237 (each adapter is one file, one class); makes per-codec quirks (e.g. AV1's narrower hardware availability) harder to attach later | Future per-codec divergence (10-bit support flags, 4:2:2 chroma support, B-frame handling, codec-specific extra params) is more naturally hung off three classes than off one parametric one |
Skip the FFmpeg probe; let ffmpeg emit "Unknown encoder" at run time | Less code | Hides the libmfx / VPL build-flag mismatch behind a generic FFmpeg error buried in stderr; harness behaviour is "encode failed for unclear reasons" | The probe is ~20 LOC and the diagnostic is the difference between "rebuild FFmpeg" and "is your driver wedged" |
| Use VBR / CQP instead of ICQ for the quality knob | VBR maps cleanly to bitrate budgets used in Phase E | ICQ is the QSV-native perceptual-quality knob; CRF-style search spaces are what the corpus is structured around; libmfx documents ICQ as the recommended single-pass quality mode | ICQ is the equivalent of CRF for QSV; using anything else would force the harness to translate quality knobs per codec |
Consequences¶
- Positive: corpus generation can target Intel hardware encode for H.264 / HEVC / AV1 from the same harness; codec-bucket one-hot (ADR-0235) gains real samples in the QSV slot; per-title CRF predictor (Phase C) trains on a representative codec spread; the shared
_qsv_common.pyis the natural seam for any future QSV-specific quirks (chroma support flags, low-power vs full encode mode). - Negative: the encode pipeline still hardcodes
-crfand will not yet successfully drive a QSV encode end-to-end — that wiring is a follow-up. The adapter classes are valid but inert until the search loop dispatches onadapter.quality_knob. The doc flags this clearly so users do not file "QSV doesn't work" issues against the adapter PR. - Neutral / follow-ups: an encode-pipeline widening PR (separate ADR) flips
build_ffmpeg_commandto consultadapter.quality_knoband emit-global_qualityinstead of-crffor QSV; the same widening unlocks NVENC / AMF (sibling PRs) and HEVC / SVT-AV1 / VP9 / VVenC (later PRs).
References¶
- Parent: ADR-0237 (
vmaf-tuneumbrella). - Companion: ADR-0235 (codec one-hot consumes the QSV bucket).
- Sibling PRs (parallel work): NVENC adapter ADR (forthcoming), AMF adapter ADR (forthcoming).
- FFmpeg QSV docs:
ffmpeg-qsv.html. - libmfx ICQ rate control: Intel Media SDK Developer's Guide,
MFX_RATECONTROL_ICQmode. - Source:
req("Add Intel QSV (Quick Sync Video) hardware encoder adapters (h264_qsv,hevc_qsv,av1_qsv) tovmaf-tune. Companion to the NVENC + AMF adapter PRs running in parallel.").
Status update 2026-05-08: install discoverability backfill¶
Per ADR-0028 (immutable-once-Accepted bodies — appendix only), recording a follow-up landing in a separate doc-only PR.
The SYCL audit (research-0086, Topic C / issue #464) found that none of the six per-OS install pages (docs/getting-started/install/{arch,fedora,ubuntu,macos,windows}.md) referenced the runtime dependency these adapters require. The adapters' own runtime probe (_qsv_common.ffmpeg_supports_encoder) was already correct and covered both libmfx and libvpl; the gap was discoverability — a user landing on the install page from mkdocs did not learn that h264_qsv / hevc_qsv / av1_qsv need an FFmpeg built against libvpl (or, for legacy FFmpeg < n6.0, against the now archived Media SDK libmfx).
The backfill ships a per-OS QSV section with verified package names (Arch libvpl + vpl-gpu-rt; Fedora libvpl + libvpl-tools; Ubuntu libvpl2 + libvpl-dev), an explicit "unsupported" note on macOS, an Intel-driver-bundle pointer on Windows, and a hardware-capability matrix mapping Intel CPU / GPU generations to which of the three QSV codecs they actually support. No adapter code changed in the backfill.