Skip to content

VMAF

currentMigrated from docs/metrics/VMAF.mdx at 37c3317fd058; modified for Encode Wiki · CC BY-SA 4.0

VMAF (Video Multimethod Assessment Fusion) is Netflix’s full-reference video quality model. It computes a few elementary features, such as detail loss and motion, and a regression model trained on viewer ratings turns them into a score, normally 0–100, predicting how an average viewer would rate the video under one specific viewing condition. It was built for streaming: choosing bitrates and resolutions for an encoding ladder. It is good at that, but sharpening can inflate it, it barely separates high-quality encodes, and the classic models ignore colour, so a VMAF number alone won’t tell you which encoder or setting looks better.

What VMAF is good for, and what it isn’t

Section titled “What VMAF is good for, and what it isn’t”

For that job it tracks viewer opinion better than PSNR or SSIM when you compare encodes of the same content at different bitrates and resolutions, and it is supported almost everywhere, from FFmpeg to Av1an’s target-quality mode.

It is widely criticised as a judge for encoder and settings comparisons:

  • Enhancement inflates it. Sharpening and contrast boosts raise the score even though they move the picture away from the source. Encoders exploit this: SVT-AV1’s --tune 5 and libaom’s --tune=vmaf sharpen the source before encoding to gain VMAF points. Netflix added “no enhancement gain” (NEG) models for codec evaluation for exactly this reason, and made NEG behaviour the default in v1.
  • It discriminates poorly near transparency. It was trained on streaming-quality video, and differences between high-quality encodes get squeezed into the top few points. On the KonFiG-IQA image dataset, which focuses on fine, near-threshold differences, Cloudinary measured a rank correlation of 0.49 for VMAF against 0.92 for SSIMULACRA2 and Butteraugli.
  • Grain and psychovisual tuning are open problems. Netflix itself lists film-grain noise and perceptual optimisations such as adaptive quantisation among the things v1 still doesn’t handle well.
  • The classic models are luma-only. Every v0 model ignores chroma entirely. v1 adds a chroma feature.
  • It is SDR-only. No released model is designed for PQ or HLG; Netflix says an HDR version of v1 is planned. Feeding it HDR produces numbers, not meaningful ones.
  • Scores depend on viewing assumptions. The default model predicts a 1080p TV viewed from three picture heights. Computed on 480p video at native size, it effectively assumes a much longer viewing distance and scores high.

For comparing encoders or settings, use SSIMULACRA2, Butteraugli, XPSNR or CVVDP, with a NEG VMAF score as an optional extra, and look at the video. See Compare encoding quality.

libvmaf compiles its models into the library, so you select them with version= and need no model files. Use path= for a JSON model from the repository’s model/ directory.

Model (version=)Viewing conditionNotes
vmaf_v0.6.11080p TV at 3× picture height (3H)Default in FFmpeg and the vmaf tool, and what most published numbers and tools use
vmaf_v0.6.1neg1080p TV at 3HNo enhancement gain; the v0 model to use for encoder comparisons
vmaf_4k_v0.6.1, vmaf_4k_v0.6.1neg4K TV at 1.5HCompute at 2160p
vmaf_v0.6.1 with enable_transform=truePhonev0 phone mode; scores higher than the TV model for the same video
vmaf_v1.0.16_3d0h1080p at 3Hv1 standard model
vmaf_v1.0.16_5d0hPhone (1080p at 5H)
vmaf_v1.0.16_1d5h_21604K at 1.5HThe default choice for 4K
vmaf_v1.0.16_3d0h_21604K at 3H (consumer TV)Range 0–110, to show the benefit of 4K over 1080p at that distance
vmaf_v1.0.16_hfr_*As above, for ~50–60 fps contentSame four variants with a wider motion window

VMAF v1, released with libvmaf 3.2.0 in June 2026, adds a banding feature (CAMBI), a chroma feature and an additive-impairment term for blocking, drops VIF, and has NEG behaviour built in. Netflix recommends computing SDR v1 at 10 bits, even for 8-bit encodes, so banding is captured. v1 and v0 scores come from different models and aren’t interchangeable, so don’t mix them in one comparison.

Your FFmpeg needs to be built with --enable-libvmaf. Put the distorted file first and the reference second:

Terminal window
ffmpeg -i distorted.mkv -i reference.mkv -lavfi libvmaf -f null -

This uses vmaf_v0.6.1 and prints the mean score over all frames. A more useful run picks the model, writes per-frame scores and uses more than one thread:

Terminal window
ffmpeg -i distorted.mkv -i reference.mkv -lavfi "[0:v]format=yuv420p10le[d];[1:v]format=yuv420p10le[r];[d][r]libvmaf=model=version=vmaf_v1.0.16_3d0h:log_fmt=json:log_path=vmaf.json:n_threads=8" -f null -
  • n_threads: libvmaf is single-threaded by default. Set it to the number of cores you can spare.
  • log_fmt=json:log_path=vmaf.json: per-frame scores, plus the min, max, mean and harmonic mean over the clip.
  • format=yuv420p10le on both inputs: the 10-bit measurement Netflix recommends for v1. Leave it out for v0 models.

To compute the regular and NEG scores in one pass, give each model a name:

Terminal window
ffmpeg -i distorted.mkv -i reference.mkv -lavfi libvmaf='model=version=vmaf_v0.6.1\\:name=vmaf|version=vmaf_v0.6.1neg\\:name=vmaf_neg' -f null -

Compare at the model’s resolution. If the encode is smaller than the reference, upscale it with bicubic, as Netflix does, rather than downscaling the reference:

Terminal window
ffmpeg -i distorted.mkv -i reference.mkv -lavfi "[0:v]scale=1920:1080:flags=bicubic[d];[d][1:v]libvmaf" -f null -

For a 4K source, use a 4K model at 2160p. Both inputs need the same frame count; FFmpeg silently repeats the last frame of a shorter input. Add setpts=PTS-STARTPTS to both inputs only if one of them starts at a non-zero timestamp.

Find the worst frames. The mean hides short bad sections, which is what viewers notice. With a JSON log:

Terminal window
jq '.frames | sort_by(.metrics.vmaf) | .[:5] | map({frameNum, vmaf: .metrics.vmaf})' vmaf.json

Setting the filter’s pool option to harmonic_mean or min makes the printed summary weigh bad frames more heavily.

The vmaf command-line tool from libvmaf reads only Y4M or raw YUV, but it doesn’t need an FFmpeg build with libvmaf and exposes every libvmaf option. Prebuilt binaries for Linux and Windows are on the libvmaf releases page.

Terminal window
vmaf -r reference.y4m -d distorted.y4m -m version=vmaf_v1.0.16_3d0h --json -o vmaf.json

Raw YUV input also needs --width, --height, --pixel_format and --bitdepth. Like the filter, it is single-threaded unless you add --threads N. --feature psnr, --feature float_ssim and similar add extra metrics to the log.

When a v1 measurement uses an upscaled or 8-bit encode, give the banding feature the encode’s real size and bit depth by appending them to the model, e.g. -m version=vmaf_v1.0.16_3d0h:cambi.enc_width=1280:cambi.enc_height=720:cambi.enc_bitdepth=8.

To avoid writing huge Y4M files, decode into named pipes:

Terminal window
mkfifo ref.y4m dist.y4m
ffmpeg -nostdin -v error -i reference.mkv -pix_fmt yuv420p10le -strict -1 -y ref.y4m &
ffmpeg -nostdin -v error -i distorted.mkv -pix_fmt yuv420p10le -strict -1 -y dist.y4m &
vmaf -r ref.y4m -d dist.y4m -m version=vmaf_v1.0.16_3d0h --json -o vmaf.json
rm ref.y4m dist.y4m

FFmpeg needs -strict -1 to write Y4M above 8 bits, -y because the pipe file already exists, and -nostdin because the shell stops a background FFmpeg that touches the terminal, which hangs the pipeline.