Convert RGB to YUV420p with FFmpeg Zscale

This article explains how to convert an RGB video to the widely compatible YUV420p format using FFmpeg's high-quality zscale filter. While the standard scale filter (swscale) is commonly used for color space conversion, the zscale filter leverages the zimg library to provide superior color accuracy, high-bit-depth precision, and advanced dithering algorithms to prevent color banding.

Why Use Zscale Instead of Scale?

FFmpeg's default scale filter can sometimes introduce color shifting, ringing artifacts, or banding when converting from RGB to YUV. The zscale filter solves these issues by using floating-point precision for internal calculations and offering superior algorithms for chroma subsampling and color space transformation.

The Basic Command

To convert an RGB video to YUV420p (using the BT.709 color standard, which is standard for HD video), use the following FFmpeg command:

ffmpeg -i input_rgb.mp4 -vf "zscale=matrix=709:primaries=709:transfer=709:range=limited,format=yuv420p" -c:v libx264 -crf 18 -c:a copy output_yuv420p.mp4

Parameter Breakdown

  • zscale=...: Invokes the zscale filter.
  • matrix=709: Defines the color space matrix coefficients as BT.709. This is critical for converting the RGB channels into correct luminance (Y) and chrominance (U/V) values.
  • primaries=709: Sets the color primaries to BT.709, ensuring the colors are mapped correctly for modern HD displays.
  • transfer=709: Sets the transfer characteristics (gamma curve) to BT.709.
  • range=limited: Standardizes the output to limited (broadcast) color range (16-235), which is required for maximum compatibility with hardware players and streaming platforms.
  • format=yuv420p: Force-outputs the final pixel format to 8-bit planar YUV 4:2:0.

Advanced: Adding Dithering to Prevent Banding

When downsampling from RGB (which often acts as 10-bit or high-precision color internally) to 8-bit YUV420p, color banding can occur in gradients (like skies or shadows). You can enable dithering within zscale to eliminate this:

ffmpeg -i input_rgb.mp4 -vf "zscale=matrix=709:primaries=709:transfer=709:range=limited:dither=ordered,format=yuv420p" -c:v libx264 -crf 18 output_dithered.mp4

By adding :dither=ordered (or :dither=error_diffusion), zscale will apply a high-quality dither pattern to smooth out transitions, resulting in a visually flawless 8-bit YUV420p file.