# FFmpeg CRF explained and measured

Canonical: https://rendobar.com/blog/ffmpeg-crf-measured/
Author: Abdelrahman Essawy
Published: 2026-08-20
Updated: 2026-08-20

---

## Key takeaways

- Every 3 points of CRF removed roughly a fifth of the file. CRF 18 produced 666 KB and CRF 32 produced 187 KB, a 3.6x spread across the usable range.
- Encode time did not track CRF. The slowest run in the sweep was CRF 18 at 2,216 ms and the fastest was CRF 32 at 854 ms, but the middle values scattered rather than sloping.
- CRF 23 to CRF 26 is the cheapest single decision in the range: 22% fewer bytes for a change most viewers will not notice on typical content.
- CRF is a quality target, not a size target. The same CRF on a grainy source produces a much larger file, so these byte counts describe this clip, not your library.
- Seven encodes, $0.0211 total.

CRF is the dial almost everyone turns and almost nobody measures. The advice is
always a range, usually "18 to 28, lower is better", which tells you nothing
about what a step actually buys.

Short version. On this clip the relationship is close to a clean 20% reduction
per 3 points of CRF, and encode time does not meaningfully track it. Choosing a
higher CRF is one of the few pure wins available: smaller files at no extra
compute cost.

## The size curve is remarkably regular

Reading the sizes:

| CRF | Size | Change |
|---:|---:|---:|
| 18 | 665.8 KB | — |
| 20 | 551.9 KB | −17% |
| 23 | 433.0 KB | −22% |
| 26 | 338.1 KB | −22% |
| 28 | 282.5 KB | −16% |
| 30 | 233.8 KB | −17% |
| 32 | 186.8 KB | −20% |

Across the whole usable range, CRF 18 to CRF 32 is a **3.6x** difference in file
size. Each 3-point step removes roughly a fifth of what is left, which is the
compounding behaviour you would expect from a quantiser and is a useful mental
model to carry: **+3 CRF is about −20%.**

## Encode time does not follow

This is the part worth internalising. The fastest run was CRF 32 at **854 ms**
and the slowest was CRF 18 at **2,216 ms**, but the values in between did not
form a slope. CRF 26 took 2,384 ms while CRF 28 took 967 ms, on the same preset
and the same source.

Do not read a trend into that. What it says is that at a fixed preset, encode
time is dominated by the preset's search behaviour and by ordinary run-to-run
variance on a short clip, not by the quantiser setting. There is no meaningful
compute penalty for choosing a lower CRF, and no compute saving from choosing a
higher one.

So the decision is unusually clean: **CRF trades file size against visual
quality, and essentially nothing else.**

## Where the free lunch is

If you are on CRF 23, which is x264's default and the most common setting in the
wild, moving to CRF 26 removes **22% of your bytes** for a quality difference
that most viewers will not spot on typical content at typical viewing distances.

That is the single cheapest decision in the range. Below CRF 20 you are spending
a lot of bytes on detail that survives neither a phone screen nor a second
transcode downstream. Above CRF 30 the artefacts start being visible on flat
areas and gradients, which is where banding shows up first.

## The caveat that actually matters

**CRF is a quality target, not a size target**, and that is the whole reason it
works. The encoder spends whatever bytes it needs to hold the quality you asked
for. Feed it a grainy, high-motion source and the same CRF will produce a far
larger file, because holding quality on grain is expensive.

This is why "use CRF 23 and expect 400 KB" is not a thing anyone can promise
you. The 20%-per-3-points relationship should travel to your content. The
absolute byte counts will not.

If your requirement is a *size*, CRF is the wrong tool and you want a
target-size search instead, which is a different job entirely.

## Method

Seven encodes, libx264, preset `medium`, audio stripped, five seconds of the
same 1280x720 source, run through the Rendobar API. Encode time is the FFmpeg
step alone, separated from the download and the upload. Total cost **$0.0211**.

Every command is in the table above, and each is a single API call.

CRF is one of seven settings measured on this same source. The rest are in [FFmpeg encoding settings compared](/blog/ffmpeg-encoding-settings/).
