vmaf-tune compare — codec comparison¶
vmaf-tune compare answers "should I migrate from x264 to SVT-AV1 yet?" per source. Given one reference and a target VMAF, it runs each codec's target-VMAF bisect in parallel and ranks the results by smallest file. It is Bucket #7 of the vmaf-tune capability audit; the default backend is the target-VMAF bisect of ADR-0326. The tool overview is in vmaf-tune.md.
Quick start¶
vmaf-tune compare \
--src ref.yuv \
--width 1920 --height 1080 --pix-fmt yuv420p \
--framerate 24 --duration 10 \
--sample-clip-seconds 4 \
--target-vmaf 92 \
--encoders libx264,libx265,libsvtav1,libaom-av1,libvvenc \
--crf-min 15 --crf-max 40 \
--format markdown
Abridged output (--format markdown):
# Codec comparison — target VMAF 92
- Source: `ref.yuv`
- Tool: `vmaf-tune <version>`
- Wall time: 6421.3 ms
| Rank | Codec | Encoder | Best CRF | Bitrate (kbps) | Encode time (ms) | VMAF | Status |
|---:|---|---|---:|---:|---:|---:|---|
| 1 | libaom-av1 | libaom-3.8.0 | 30 | 1500.0 | 18000.0 | 92.40 | ok |
| 2 | libx265 | libx265-3.5 | 26 | 1700.0 | 4200.0 | 92.00 | ok |
| 3 | libsvtav1 | libsvtav1-1.7.0 | 32 | 1900.0 | 2800.0 | 92.30 | ok |
| 4 | libx264 | libx264-164 | 23 | 2400.0 | 1500.0 | 92.10 | ok |
**Smallest file**: `libaom-av1` at CRF 30 → 1500.0 kbps (VMAF 92.40).
Exit codes: 0 when at least one codec row succeeded, 1 when none did, and 2 for an argument error or an unavailable score backend.
How a run works¶
- Source geometry. Raw YUV does not describe itself, so the real bisect backend needs
--widthand--height. Set--pix-fmt,--framerateand--durationtoo: they default to the common SDR 24 fps shape, and wrong values skew scoring and bitrate math. - Sample window.
--sample-clip-seconds Nevaluates the centreNseconds of the source on every bisect iteration. Compare forwards matching--frame_skip_ref/--frame_cntbounds to the scorer and normalises bitrate against the sample duration. - Custom rankers.
--predicate-module MODULE:CALLABLEaccepts any importable(codec, src, target_vmaf) -> RecommendResultcallable and bypasses the bisect backend. It is meant for custom rankers and tests. - Hardware encoders. An encoder that is missing or has no compatible GPU yields a row that is skipped with a reason. It does not fail the whole run.
- Search window. The bisect starts from the encoder's absolute CRF range, which makes VMAF 95 and above reachable. The full contract is in
vmaf-tune-bisect.md.
Container sources auto-probe their framerate¶
When --src is a container (mp4, mkv, mov, Y4M, ...) and you leave --framerate and --duration at their defaults, the CLI calls ffprobe and substitutes the probed values before the bisect starts (ADR-0509). This avoids a silent failure: the 24 default against a 60 fps source misaligns the reference and distorted decodes and collapses VMAF into the 4 to 90 band regardless of CRF.
- An explicit
--framerateor--durationstill wins. - A stderr warning fires when an explicit value disagrees with the probed rate (subsampling is legitimate, but it should be deliberate).
- Raw
.yuvsources are not probed, because raw YUV does not self-describe.
Flags¶
| Flag | Default | Meaning |
|---|---|---|
--src PATH | none (required) | Single reference clip. |
--target-vmaf F | 92.0 | Single VMAF target. Passed explicitly with --target-vmafs left at its default, it selects the single-target v1 schema. |
--target-vmafs LIST | 94,96,97,98 | Comma-separated targets to sweep per codec (ADR-0538). One value selects the v1 schema; two or more select v2. |
--encoders LIST | libx265,libsvtav1 | Comma-separated encoders. Accepts any registered adapter, including hardware ones such as h264_nvenc, av1_qsv and hevc_amf, and ADAPTER@VARIANT tokens. |
--encoder-ffmpeg-bin ENCODER=PATH | off | Repeatable. Binds one compare token to a specific FFmpeg binary, for example libsvtav1@svt-av1-hdr=/opt/ffmpeg-8.1.1-svtav1-hdr/bin/ffmpeg. Unbound tokens use --ffmpeg-bin. |
--width, --height | none | Source geometry. Required for the real bisect backend. |
--pix-fmt | yuv420p | Source pixel format forwarded to the scorer. |
--framerate | 24.0 (auto-probed for containers) | Source framerate, see auto-probe. |
--duration | 0.0 (auto-probed for containers) | Source duration in seconds, used for bitrate math. |
--sample-clip-seconds | 0.0 | 0.0 scores the full source. A positive value shorter than --duration uses a centre window for encode, score and bitrate math (ADR-0301). |
--preset | medium | Preset forwarded to every codec adapter, which maps it onto its own preset vocabulary. |
--crf-min, --crf-max | encoder absolute range | Inclusive CRF search window. Pass both or neither. |
--max-iterations | 8 | Encode and score round-trip cap per codec. |
--vmaf-model | vmaf_v1.0.16_3d0h | VMAF model forwarded to the scorer. |
--neg | off | Use the VMAF NEG model variant. |
--fast-nr | off | NR early-elimination inside each bisect, see vmaf-tune-fast-nr.md. |
--score-backend | unset | cpu, cuda, sycl, hip, metal or auto. See score backends. |
--ffmpeg-bin, --vmaf-bin | ffmpeg, vmaf | Binary overrides. |
--vaapi-device PATH | auto | VA-API render node for Intel QSV device initialisation, for example /dev/dri/renderD129, used by the availability probe and by every QSV encode of the run. auto takes the first Intel render node from /sys/class/drm. The VMAFTUNE_VAAPI_DEVICE variable also works, and the flag wins. |
--format | markdown | markdown, json, csv, html or both. html and both render the profile card directly. both writes .json, .html and .md next to --output and therefore requires it. |
--json-sidecar | off | With a single HTML or Markdown report, also write <output>.json with the report payload. --format both includes it already. Requires --output. |
--output PATH | stdout | Write the report to PATH. |
--no-parallel | off | Run codecs one after another. The default is a thread pool with one worker per codec. |
--max-workers N | number of encoders | Cap on the thread pool size. |
--predicate-module MOD:FN | off | Advanced hook that bypasses the bisect backend. |
--no-bisect | off | CRF sweep mode, see CRF sweep mode. |
--crf-sweep LIST | none | CRF values for --no-bisect, for example 18,23,28,33. Required with --no-bisect. |
--workdir PATH | none | Parent of the per-run scratch directory that holds the decoded reference YUV and the encodes. Falls back to VMAFTUNE_WORKDIR, then the OS default (/tmp). See workdir. |
--max-concurrent-decodes N | 1 | Cap on simultaneous reference-YUV decodes across all codec threads. See workdir. |
Workdir and decode concurrency¶
--workdir matters when the source is large and /tmp is a small tmpfs: a 634-second 1080p60 source decodes to about 118 GB of raw YUV (ADR-0598).
--max-concurrent-decodes bounds the peak disk use (ADR-0577). At the default of 1, decodes are serial and the peak stays at one YUV regardless of how many codecs run. With three codecs and a 110 GB source, that is 110 GB instead of the 330 GB peak that caused an ENOSPC failure. Raise N only when --workdir sits on a volume with enough free space and I/O for N parallel decode streams. Encoder runs are always parallel; only the decode-to-raw-YUV step is serialised.
Multi-target rate-quality sweep¶
compare defaults to a 4-point sweep, --target-vmafs 94,96,97,98 (ADR-0516, ADR-0534, ADR-0538). The JSON output stamps schema_version: 2 and has one row per (codec, target_vmaf) pair. Each row can also carry a bisect_samples list with every encode and score probe the bisect computed.
vmaf-tune report --compare-json sweep.json renders a per-codec rate-quality curve from those probes. Picked-CRF rows appear as larger circled markers, and the Pareto frontier (lowest bitrate at each target) is a heavier dashed overlay.
# Defaults: 4 targets, two encoders in this example, GPU scoring.
vmaf-tune compare \
--src bbb_1080p_60fps.mp4 \
--width 1920 --height 1080 --framerate 60 \
--encoders libx265,libsvtav1 \
--sample-clip-seconds 3 --max-iterations 3 \
--score-backend cuda \
--format json --output sweep.json
vmaf-tune report \
--src bbb_1080p_60fps.mp4 \
--compare-json sweep.json --target-vmaf 92 \
--format both --output sweep_report.html
Profile cards straight from compare¶
compare can render the same profile card directly, and encode-profile can then reuse one recommendation without re-running the sweep:
vmaf-tune compare \
--src bbb_1080p_60fps.mp4 \
--width 1920 --height 1080 --framerate 60 \
--encoders libx265,libsvtav1,av1_nvenc,av1_qsv \
--sample-clip-seconds 3 --max-iterations 3 \
--score-backend cuda \
--format both --output sweep_profile.html
vmaf-tune encode-profile \
--profile sweep_profile.html \
--src bbb_1080p_60fps.mp4 \
--codec libsvtav1 --target-vmaf 96 \
--output bbb_svtav1_vmaf96.mkv
For both compare and report, --format both writes the full three-file bundle next to --output: machine-readable .json plus the .html and .md renders. On a single-format HTML or Markdown run, --json-sidecar adds the same JSON payload without requesting both renders.
The HTML and Markdown profile cards contain:
- a Quick takeaways block before the charts: the smallest successful row at each target, the failed or unavailable row count, the ladder span and the per-shot CRF spread;
- short "how to read this" notes;
- report-local codec identity chips (text badges linking to the upstream codec or vendor project);
- per-codec failure status;
- an embedded
encoder_profileJSON payload, whichencode-profilecan read from the HTML, the Markdown or the raw JSON to build a concrete FFmpeg encode. Seevmaf-tune-report.md.
Why these defaults¶
ADR-0538 supersedes the target defaults of ADR-0534.
94,96,97,98covers premium-archival operating points. The fork's primary use is archival masters at VMAF 95 and above. VMAF 94 is the subjectively transparent floor on 4K source and 98 is the near-lossless ceiling. The earlier ADR-0534 default (75,80,85,90,93) served streaming and broadcast workflows, so its chart held no points that an archival user picks CRFs from.- VMAF 95 and above is reachable. The earlier "top stops at 93" limit was a bisect-harness artefact: the search window defaulted to the adapter's narrow
quality_range, which the adapter validator also enforced as a hard gate. ADR-0538 widens the window to the encoder's absolute CRF range, seevmaf-tune-bisect.md. - The chart renders from
bisect_samples. Connecting the picked-CRF rows per codec across targets gave physically impossible downward dips, because the bisect overshoots each target by a different amount. Plotting every probe the bisect already computed shows the real per-codec curve.
Output schema¶
The JSON and CSV columns are exported as vmaftune.compare.COMPARE_ROW_KEYS: codec, adapter, runtime_variant, ffmpeg_bin, encoder_version, best_crf, bitrate_kbps, encode_time_ms, vmaf_score, target_vmaf, ok and error.
- Failed rows trail successful ones in the ranking.
- A row with
ok=falsecarries a human-readableerror,-1forbest_crfand non-finite floats, which JSON writes asnull. adapter,runtime_variantandffmpeg_binare provenance fields forADAPTER@VARIANTruns. They are empty on rows from old programmatic predicates that bind no runtime variant.
Every payload also has the top-level keys src, tool_version, wall_time_ms and rows.
| Schema | Emitted when | Extra top-level keys | Rows |
|---|---|---|---|
| v1 (single target) | One target results: --target-vmaf passed explicitly with the default sweep, or --target-vmafs with one value. | none (no schema_version), plus target_vmaf | One per codec. |
| v2 (multi-target sweep) | --target-vmafs lists two or more targets (the default). | schema_version: 2, target_vmafs (for example [94.0, 96.0, 97.0, 98.0]) | One per (codec, target_vmaf) pair. Each can carry bisect_samples. |
| v3 (CRF sweep) | --no-bisect. | schema_version: 3, mode: "crf_sweep", crf_sweep, target_vmaf | One per (codec, crf) pair. |
bisect_samples¶
bisect_samples is an optional list of {crf, bitrate_kbps, vmaf_score, encode_time_ms} objects, one per encode and score probe of the underlying bisect (ADR-0534). It is additive and absent on v2 dumps that predate ADR-0534. The CSV emitter drops it (extrasaction="ignore"), so it stays a JSON-only field.
How report reads the schema¶
vmaf-tune report tells v1 from v2 by the schema_version key, or by the presence of target_vmafs.
- v1 renders the bar-and-dot chart.
- v2 with
bisect_samplesplots every probe per codec, deduplicated by CRF and sorted by bitrate. It overlays the picked-CRF rows as larger circled markers and keeps the Pareto frontier as the dashed overlay. - v2 without
bisect_samples(old dumps) falls back to the legacy connect-the-dots line, with a caveat note in the title. That line can show physically impossible dips when the per-target overshoot varies, which is the failure that ADR-0534 fixes for the new path.
Encode time is wall clock
encode_time_ms is wall clock on whatever machine ran the predicate. Cross-codec time comparisons only make sense when every predicate ran on the same hardware in the same configuration, see Research-0061 Bucket #7.
CRF sweep mode¶
With --no-bisect and --crf-sweep, compare skips the target-VMAF bisect and encodes each (codec, CRF) pair exactly once. Use it to sweep a fixed CRF ladder such as 18, 23, 28, 33 and inspect the resulting bitrate and VMAF pairs. It is faster than four separate bisect sweeps and more direct than the corpus pipeline.
vmaf-tune compare \
--src clip.mp4 \
--encoders libx264,libx265,libsvtav1 \
--no-bisect --crf-sweep 18,23,28,33 \
--duration 60 --sample-clip-seconds 30 \
--format json --output cmp_sweep.json
Three codecs times four CRFs give 12 rows in cmp_sweep.json.
- Schema. The output is v3 (ADR-0548):
schema_version: 3,mode: "crf_sweep",crf_sweep: [18, 23, 28, 33]. Each row carriescodec,adapter,runtime_variant,ffmpeg_bin,crf,bitrate_kbps,vmaf_score,encode_time_ms,encoder_version,okanderror. - Targets.
--target-vmafand--target-vmafsare accepted but do not drive the encode loop; the payload only recordstarget_vmaf. - Format. The mode always writes JSON.
--format htmland--format bothexit with code2; emit JSON and pass it tovmaf-tune reportinstead. - Availability. Hardware encoders are probed as on the bisect path. An unavailable encoder (for example
h264_nvencwithout an NVIDIA device) producesok=falserows for each CRF and does not abort the run.
See also¶
vmaf-tune.md— the tool overview.vmaf-tune-bisect.md— the bisect thatcompareruns per codec, including the high-VMAF contract (ADR-0538).vmaf-tune-report.md—reportandencode-profile.vmaf-tune-fast-nr.md—--fast-nr.vmaf-tune-codec-adapters.md— adapters andADAPTER@VARIANTtokens.vmaf-tune-score-backend.md—--score-backend.vmaf-tune-workdir.md— scratch space andVMAFTUNE_WORKDIR.- Research-0061 — capability audit.