Skip to content

Graining

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

Grain is a texture, and there are three separate things you might do with it:

ApproachWhat happensCost
Keep the source grainThe encoder codes the noise as it isA lot of bitrate on grainy sources
Add grain as pixelsYou add noise in the filter chain, and the encoder codes itBitrate, since the added noise has to be coded like any other
AV1 film grain synthesisThe encoder writes a small grain model into the stream, and the decoder generates matching grain on playbackLittle bitrate, and the grain is statistical rather than the original pixels

After denoising or debanding the picture can look plastic, and smooth gradients are exactly what an encoder re-quantises into bands. A little fresh grain restores texture and breaks up banding. Adding grain also gives you a way to reintroduce a film look in an encoder such as x264 or x265 that can’t signal synthetic grain, at the price of the bitrate to encode it.

Grain is added near the end of the chain, after everything else and before the final dither to the output bit depth.

In VapourSynth, Grainer from vsdeband (part of vs-jetpack) offers several noise types, including Gaussian, Perlin, Simplex and Poisson:

from vsdeband import Grainer
clip = Grainer.GAUSS(clip, strength=0.5, luma_scaling=10)

strength can be a pair for luma and chroma. luma_scaling reduces grain in brighter areas. static=True repeats one grain pattern on every frame, temporal smooths the grain across frames, and scale makes it coarser. Pixel values near the limits of the legal range are protected by default.

In FFmpeg, the noise filter is the simple option. alls is the strength on all planes, t makes the pattern change every frame (without it the same noise repeats on every frame), and u makes it uniform instead of Gaussian:

Terminal window
ffmpeg -i input.mkv -vf "noise=alls=3:allf=t+u" -c:v libx264 -crf 18 output.mkv

Coarse grain is expensive to encode, so add no more than the look needs.

AV1 can signal a grain model in the bitstream and have the decoder add the grain. The encoder estimates the grain by comparing the source with a denoised copy and writes the model into the stream; the decoder applies grain on top of the decoded picture. What gets encoded differs: aomenc encodes the denoised frames by default, while SVT-AV1 by default encodes the original source and only adds the model (--film-grain-denoise 0). On a heavy-grain source this can save a large share of the bitrate.

SVT-AV1 enables it with --film-grain:

Terminal window
SvtAv1EncApp -i input.y4m --crf 30 --preset 6 --film-grain 8 -b output.ivf
OptionMeaning
--film-grain 0-500 is off; 1-50 is the level of denoising for film grain.
--film-grain-denoise 0/1With 0 (the default) the source is not denoised and the grain data is still written to the stream; with 1 the source is denoised at the level set by --film-grain.
--fgs-table FILEPath to a pre-generated film grain table to use for synthesis.
--adaptive-film-grain 0/1Vary the grain block size with resolution; on by default.

aomenc has --denoise-noise-level (0-50, where 0 disables denoising), --enable-dnl-denoising and --film-grain-table for a grain table file.

Some practical points:

  • Grain synthesis happens in the decoder. dav1d applies it by default (its --filmgrain option can turn it off), but a player or hardware decoder that ignores grain data will show a clean image.
  • The grain is a statistical match to the original, not a copy of it. That is the intent, and the reason full-reference metrics score a grain-synthesised encode lower than a plain one even when it looks closer to the source; compare by eye, or with grain disabled.
  • Don’t stack sources of grain. If you use synthesis, don’t also add pixel grain to the input, and if you add pixel grain, turn denoising and synthesis off.
  • Stronger denoising removes more detail along with the grain; compare faces and textures against the source.

See SVT-AV1 for the encoder’s other options.