Turn on AV1 film grain synthesis and measure what it saves on your own footage
DEV Community

Turn on AV1 film grain synthesis and measure what it saves on your own footage

Check your toolchain ๐Ÿ› ๏ธ

You need FFmpeg built with libsvtav1. FFmpeg 5.1+ passes SVT-AV1 parameters through; anything current (FFmpeg 7.x or the 8.x line, which is on 8.1 as of March 2026) is fine, ideally with a recent SVT-AV1 (4.x shipped early this year):

ffmpeg -version | head -1
# ffmpeg version 8.1 ...

ffmpeg -hide_banner -encoders | grep svtav1
# V....D libsvtav1  SVT-AV1(Scalable Video Technology for AV1) encoder

No libsvtav1 line? Grab a static build or brew install ffmpeg / your distro's current package.

Pick a genuinely grainy test clip: a film scan, low-light footage, a concert recording. FGS on clean screen-capture content will show you nothing, because there's no grain to model. Cut a 60 second sample so iterations are fast:

ffmpeg -ss 00:05:00 -i source-master.mov -t 60 -c copy sample.mov

Encode the baseline ๐Ÿ“ผ

First, a normal SVT-AV1 encode, no grain handling:

ffmpeg -i sample.mov -c:v libsvtav1 -preset 5 -crf 32 \
  -pix_fmt yuv420p10le \
  -svtav1-params tune=0 \
  -an baseline.mkv

Notes on the flags: preset 5 is a reasonable quality/speed midpoint, crf 32 is a sane starting quality target for 1080p, tune=0 selects the perceptual tuning, and 10-bit (yuv420p10le) is standard practice for AV1 even with 8-bit sources since it reduces banding.

Encode with film grain synthesis ๐ŸŽž๏ธ

One parameter changes:

ffmpeg -i sample.mov -c:v libsvtav1 -preset 5 -crf 32 \
  -pix_fmt yuv420p10le \
  -svtav1-params tune=0:film-grain=8:film-grain-denoise=1 \
  -an grain-synth.mkv

What the two new knobs do:

  • film-grain=8 sets the grain modeling level (0 to 50). Community guidance that holds up in practice: around 8 for normally grainy live action, around 4 for animation or clean digital sources. Crank it only for genuinely noisy scans.
  • film-grain-denoise=1 tells the encoder to actually remove the modeled grain from the frames before encoding them. This is where large savings come from on noisy sources, because the encoder now sees a clean, predictable image. With it off, you're layering synthetic grain over grain that's still (expensively) in the encode.

โš ๏ธ Note: FGS is off by default in SVT-AV1. If you didn't ask for it, you don't have it.

If the encode dies immediately with something like this:

# Svt[error]: Error parsing svtav1-params: unrecognized option film-grain-denoise
# Error setting option svtav1-params to value ...

your libsvtav1 predates the option spelling your build expects, or your FFmpeg predates 5.1's svtav1-params passthrough entirely. Update the FFmpeg build rather than fighting it; current static builds bundle a recent SVT-AV1.

Compare the numbers ๐Ÿ“Š

# compare.sh
for f in baseline.mkv grain-synth.mkv; do
  ffprobe -v error -select_streams v:0 \
    -show_entries format=size,bit_rate,duration \
    -of default=noprint_wrappers=1 "$f" | sed "s/^/$f /"
done
./compare.sh
# baseline.mkv duration=60.000000
# baseline.mkv size=<yours here>
# baseline.mkv bit_rate=<yours here>
# grain-synth.mkv duration=60.000000
# grain-synth.mkv size=<yours here>
# grain-synth.mkv bit_rate=<yours here>

I'm deliberately not printing my numbers, and I'd side-eye any article that quotes one as if it were general. The delta depends entirely on how much of your bitrate was grain. Noisy film scans can shrink dramatically; lightly grainy content shifts modestly; clean content barely moves. Run it on your library sample, not mine.

Verify with your eyes, not a metric ๐Ÿ‘€

Here's the part most write-ups skip: full-reference quality metrics are structurally unreliable on FGS output. The synthesized grain is intentionally not the same pixels as the source grain, so pixel-comparing metrics can punish a frame that looks right and reward a smeared one compared against a denoised reference. The community docs are blunt that heavy grain remains an AV1 weak point even with FGS.

So we A-B it:

# play both in sync-ish loops, switch between windows
mpv --loop baseline.mkv &
mpv --loop grain-synth.mkv &

Watch for the three classic FGS failure smells:

  • Uniformity. Real grain varies with brightness; synthetic grain can look same-everywhere, especially in shadows.
  • Twinkle. Grain that reads as animated static rather than texture.
  • Plastic mid-tones. If film-grain-denoise was too aggressive for the source, faces go waxy underneath the synthetic layer.

If level 8 shows artifacts, step down to 6 or 4 and re-run; the sweep takes minutes on a 60 second sample. For archival or authorial grain (a director chose that film stock), keep a human review in the loop before committing a whole catalog.

Ladder notes ๐Ÿชœ

Two practical integration details before you wire this into a pipeline:

  • Apply FGS on the renditions where grain survives anyway. Your 240p rung has no grain worth preserving after downscaling; spend the analysis on the top rungs.
  • Decoder-side cost is real but small on modern devices (dav1d handles synthesis efficiently). If your audience includes very old or very cheap hardware, put one such device on the test matrix before rollout.

Automate the sweep ๐Ÿ”

Once the two-encode version works, the per-title version is a loop. This emits a size table so the grain level becomes a data-driven, per-content decision:

# sweep.sh
set -e
for g in 0 4 8 12; do
  ffmpeg -y -v error -i sample.mov -c:v libsvtav1 -preset 5 -crf 32 \
    -pix_fmt yuv420p10le \
    -svtav1-params "tune=0:film-grain=${g}:film-grain-denoise=1" \
    -an "out-fg${g}.mkv"
  printf "film-grain=%-3s %s bytes\n" "$g" "$(wc -c < out-fg${g}.mkv)"
done

Run it overnight against one representative sample per title category (film scan, talking head, animation, screen capture) and you'll have an encoding policy instead of a default.

What's next ๐Ÿš€

Two directions from here. First, read the SVT-AV1 film grain appendix (in the repo's Docs/ folder) to understand the underlying noise model; it's short and it explains why the synthesis behaves the way it does. Second, if you're already doing per-title encoding, FGS slots naturally into the same "let the content decide" philosophy, and the sweep script above is the seed of exactly that pipeline.

Comments

No comments yet. Start the discussion.