mediacodec.dev

Bit depth and banding

Why 8-bit video bands in skies and gradients, and why encoding in 10-bit helps even when the source and the screen are both 8-bit.

Updated

Point a camera at a sunset, encode it, and watch the sky break into visible stripes. Banding is the most common colour artifact in delivered video, and the fix is counter-intuitive enough that people resist it: encode in a higher bit depth than your source.

What bit depth counts

Bit depth is how many distinct values each channel can take. 8-bit gives 256 levels per channel; 10-bit gives 1024; 12-bit gives 4096.

256 levels sounds ample, and for most of an image it is. The problem is gradients. A clear sky might traverse only twenty of those 256 levels across the whole frame. Spread twenty steps over 1000 pixels and each step is a 50-pixel-wide stripe of flat colour, with a visible edge where it changes. Your eye is extremely good at spotting those edges, a quirk called Mach banding that exaggerates the boundary between adjacent flat areas.

10-bit gives that same sky eighty steps instead of twenty. The stripes become too fine to resolve.

Why compression makes it worse

Banding in the source is one thing; encoding manufactures more of it. Quantisation rounds coefficients, and in a smooth area nearly all the coefficients are small, so rounding wipes out the gentle variation that was hiding the steps. The encoder is doing exactly its job: that variation is low-energy detail, and low-energy detail is what gets thrown away first.

This is why banding shows up in the sky and not in the grass. Textured regions have plenty of high-energy detail to mask the steps; flat regions have nothing.

Encode 10-bit even from 8-bit sources

Here is the part that sounds wrong. If your source is 8-bit and your viewer’s screen is 8-bit, encoding in 10-bit still reduces banding.

The reason is that the encoder works internally in the depth you give it. At 10-bit, its intermediate maths, its prediction, and its quantisation all carry extra precision, so rounding errors are four times smaller before they ever reach the output. Less error accumulation means fewer manufactured steps. The decoder converts back down to 8-bit for display and the result is visibly cleaner than an 8-bit encode of the same source, at essentially the same bitrate. Sometimes smaller, because clean gradients compress better than banded ones.

This has been standard practice in the anime encoding community for well over a decade, where flat colour areas are everywhere and banding is the cardinal sin. Streaming services do it too.

The catch is compatibility, and it differs sharply by codec:

  • H.264 10-bit requires the High 10 profile, which consumer hardware decoders largely do not support. Excellent for archives, risky for delivery. See profiles and levels.
  • HEVC, VP9 and AV1 treat 10-bit as normal. HEVC Main 10 is the common broadcast profile; AV1 hardware decoders handle 10-bit as a matter of course.

So: 10-bit freely in HEVC and AV1, carefully in H.264.

The other tools

Dithering. Adding a small amount of structured noise before quantisation breaks up the flat steps, trading a visible edge for invisible grain. FFmpeg applies dither automatically when converting depths; you can control it with -sws_dither.

Grain. Film grain does the same job by accident, which is why grainy footage rarely bands. It is also why aggressive denoising can create banding that was not in the source. AV1 goes further and encodes grain as a synthesis recipe rather than as pixels, removing it before encoding and regenerating it at playback.

Higher bitrate. It works, but it is the expensive answer. 10-bit encoding costs nothing extra and fixes more.

Banding is also the artifact most likely to be misdiagnosed as a colour problem, when the actual cause is a range or matrix mismatch. If your gradients look wrong and your blacks look grey, read colour spaces and washed-out video first.