VP9 vs H.265 vs AV1: Video Codec Comparison for Production Pipelines

Codec selection is one of the highest-leverage decisions in a video delivery pipeline. Choosing wrong means paying unnecessary CDN egress costs, shipping unplayable streams to Safari users, or burning server CPU on encode jobs that take 10ร— longer than they should. This guide โ€” part of Core Media Fundamentals & Next-Gen Formats โ€” gives you an engineering-grade breakdown of VP9, H.265 (HEVC), and AV1: their lineage, compression mechanics, encode parameters, hardware decode status, and the browser fallback ladder that ties them together.

Codec Lineage & Architecture

Understanding where each codec came from explains why they make the trade-offs they do.

VP9 (2013, Google) is the second-generation successor to VP8. Its design goal was royalty-free delivery at YouTube scale: good compression for 1080p/4K streaming, fast software decode on mid-range Android hardware, and zero patent licensing friction. VP9 uses a superblock architecture (up to 64ร—64 luma blocks), recursive partitioning, and a two-pass rate control well-suited to constant-quality (-cq-level / --crf) encodes.

H.265 / HEVC (2013, JCT-VC โ€” a joint effort between MPEG and ITU-T) extended H.264โ€™s block-based hybrid prediction model to coding tree units (CTUs) up to 128ร—128 pixels, adding more flexible partitioning, improved intra prediction angular modes, and a sample-adaptive offset (SAO) filter. The result is roughly 40โ€“50% bitrate savings over H.264 at equivalent quality. The cost: a fragmented patent landscape managed by multiple patent pools (MPEG LA, Technicolor, Via Licensing), which drove browser vendors to avoid mandatory H.265 support and pushed streaming providers toward royalty-free alternatives.

AV1 (2018, Alliance for Open Media โ€” a coalition including Google, Apple, Mozilla, Netflix, Amazon, and Intel) was designed from the ground up to be royalty-free while surpassing H.265 compression efficiency. AV1 introduces several key innovations over VP9: larger coding units (up to 128ร—128), compound inter prediction, film-grain synthesis (encoding grain parameters rather than the actual grain signal), constrained directional enhancement filtering (CDEF), and a loop restoration filter. These additions account for both AV1โ€™s superior quality-per-bit and its significantly longer encode time.

The diagram below traces the codec family tree and shows which delivery containers each codec targets:

Video codec lineage and container mapping A diagram showing the lineage of H.264 leading to H.265 (HEVC), VP8 leading to VP9 then AV1, with container mappings to MP4 and WebM. Proprietary / licensed Royalty-free / open H.264 2003 ยท MPEG/ITU H.265 / HEVC 2013 ยท JCT-VC ยท licensed MP4 container VP8 2008 ยท Google VP9 2013 ยท Google AV1 2018 ยท Alliance for Open Media WebM container WebM ยท MP4 ยท MKV WebM ยท MP4 ยท MKV

Compression Benchmark Data

The following table uses published encode benchmarks and Netflix/Bitmovin research data for 1080p source material encoded at VMAF target 93.

Codec Encoder Relative bitrate vs H.264 Encode speed (1080p, real-time factor) Hardware decode
H.264 libx264 fast 1.0ร— (baseline) ~40ร— realtime Universal
VP9 libvpx-vp9 โˆ’35 to โˆ’40% ~3โ€“5ร— realtime Broad (2017+)
H.265 libx265 medium โˆ’40 to โˆ’50% ~8โ€“12ร— realtime (SW); ~40ร— (HW) Apple, Samsung, Intel
H.265 VideoToolbox (Apple Silicon) โˆ’40 to โˆ’50% ~40โ€“80ร— realtime Native on Apple
AV1 libsvtav1 preset 6 โˆ’55 to โˆ’60% ~5โ€“8ร— realtime Chrome 90+, Edge 92+, Safari 16.4+, Android 2022+
AV1 libaom-av1 speed 4 โˆ’55 to โˆ’60% ~0.3โ€“1ร— realtime Same as above

Tradeoff: AV1 with libsvtav1 at preset 6 achieves near-VP9 encode speeds while retaining most of the compression gains. libaom-av1 (the reference encoder) is roughly 10โ€“30ร— slower than libsvtav1 for equivalent quality โ€” use libaom-av1 only for archival masters or when libsvtav1 is unavailable.

Warning: H.265 software decode performance on low-end Android devices can cause frame drops even at 720p. Always profile your target device class before committing to H.265 as your primary delivery format.

Step-by-Step Implementation

Step 1 โ€” Encode VP9 for Chromium/Firefox/Edge delivery

VP9 in a WebM container is the safe royalty-free choice for browsers that do not yet support AV1 hardware decode. The key flags for production use:

ffmpeg -i input.mp4 \
  -c:v libvpx-vp9 \
  -crf 32 \               # constrained quality; lower = higher quality (range 0โ€“63)
  -b:v 0 \                # set to 0 to engage CRF-only mode (no bitrate cap)
  -row-mt 1 \             # row-based multithreading: ~2ร— faster on multicore CPUs
  -tile-columns 2 \       # split frame into 4 tile columns for parallel decode
  -tile-rows 1 \          # 2 tile rows; useful for 1080p+ content
  -cpu-used 2 \           # quality/speed tradeoff: 0=slowest/best, 8=fastest/worst
  -threads 8 \            # match to available cores
  -c:a libopus \          # Opus is the standard audio codec for WebM
  -b:a 128k \
  output_vp9.webm

Note on -b:v 0: Omitting this flag while setting -crf puts VP9 into constrained-quality mode with a bitrate ceiling. Setting -b:v 0 with -crf engages true constant-quality mode, which is correct for VOD.

Step 2 โ€” Encode H.265 for Safari and iOS

Safari on macOS 11+ and iOS 11+ supports H.265 natively. The critical requirement is the hvc1 brand tag โ€” without it, Safari will refuse to play the file even when the codec is otherwise valid:

ffmpeg -i input.mp4 \
  -c:v libx265 \
  -crf 26 \               # CRF range 0โ€“51; 23โ€“28 is typical VOD range (lower = better)
  -preset medium \        # slow/medium balance: quality vs encode time
  -tag:v hvc1 \           # MANDATORY for Safari/iOS: marks the stream as HEVC brand 'hvc1'
  -movflags +faststart \  # relocate moov atom to file start for progressive download
  -c:a aac \              # AAC is required for MP4/Safari compatibility
  -b:a 128k \
  output_h265.mp4

# Apple Silicon / Intel Quick Sync hardware encode (much faster, slightly lower quality ceiling):
ffmpeg -i input.mp4 \
  -c:v hevc_videotoolbox \ # macOS hardware encoder via VideoToolbox
  -q:v 50 \                # VideoToolbox uses -q:v (0โ€“100 scale); 40โ€“60 is typical
  -tag:v hvc1 \            # still required even with hardware encoder
  -movflags +faststart \
  -c:a aac -b:a 128k \
  output_h265_hw.mp4

Step 3 โ€” Encode AV1 with SVT-AV1

SVT-AV1 (libsvtav1) is the production-grade AV1 encoder. It is architecturally different from libaom-av1: it uses a tile-parallel architecture that scales linearly with core count, making it viable for real-time or near-real-time transcoding pipelines:

ffmpeg -i input.mp4 \
  -c:v libsvtav1 \
  -crf 30 \               # CRF range 1โ€“63; 28โ€“35 is typical VOD range
  -preset 6 \             # 0=slowest/best, 13=fastest/worst; 4โ€“7 is production sweet spot
  -svtav1-params \
    "film-grain=8:film-grain-denoise=0" \  # encode grain params, skip denoising pass
  -pix_fmt yuv420p \      # force 4:2:0 for maximum compatibility; AV1 supports 4:4:4 but decode HW may not
  -movflags +faststart \
  -c:a libopus \          # Opus works in MP4 (ISO 23003-3) and MKV; use AAC if targeting iTunes
  -b:a 128k \
  output_av1.mp4

Warning: libsvtav1 and librav1e write AV1 bitstreams but do not write WebM containers directly. Use -f webm with libvpx-vp9 for WebM output. For AV1 in WebM you must use libaom-av1 or a two-step encode + remux.

Step 4 โ€” HTML5 <video> fallback ladder

The <source> order determines which codec the browser selects. Place the most efficient codec first; the browser picks the first type string it can decode. Explicit codec parameter strings prevent the browser from downloading the stream before knowing it is unsupported:

<video
  controls
  playsinline
  preload="metadata"
  width="100%"
  style="aspect-ratio: 16/9;"
>
  <!-- AV1 in MP4: codec string av01.0.05M.08 = AV1 profile 0, level 3.1, Main tier, 8-bit -->
  <source src="video.av1.mp4"  type='video/mp4; codecs="av01.0.05M.08"'>

  <!-- VP9 in WebM: royalty-free fallback for Chrome/Firefox/Edge without AV1 HW decode -->
  <source src="video.vp9.webm" type='video/webm; codecs="vp9"'>

  <!-- H.265 in MP4: Safari-primary; Edge and Chrome support on Win 10+ (OS-dependent) -->
  <source src="video.h265.mp4" type='video/mp4; codecs="hvc1"'>

  <!-- H.264 baseline fallback: last resort, universal support -->
  <source src="video.h264.mp4" type='video/mp4; codecs="avc1.42E01E"'>

  <!-- Always include a caption track; sync survives ABR switches -->
  <track kind="captions" src="captions.vtt" srclang="en" label="English" default>
</video>

For ABR / MSE-based players, use MediaSource.isTypeSupported() to select the manifest at runtime:

function selectOptimalCodec() {
  // AV1 hardware decode landed in Chrome 90, Edge 92, Safari 16.4, Firefox 113
  if (MediaSource.isTypeSupported('video/mp4; codecs="av01.0.05M.08"')) {
    return loadAV1Manifest();
  }
  // VP9 hardware decode available on most discrete GPUs since 2017
  if (MediaSource.isTypeSupported('video/webm; codecs="vp9"')) {
    return loadVP9Manifest();
  }
  // H.265 check: returns true only when the OS-level codec is installed
  if (MediaSource.isTypeSupported('video/mp4; codecs="hvc1"')) {
    return loadH265Manifest();
  }
  return loadH264Fallback();
}

Tradeoff: MediaSource.isTypeSupported() reports codec availability, not hardware acceleration. AV1 software decode on a 2018-era Android phone will pass the check but cause frame drops at 1080p. Supplement with navigator.hardwareConcurrency and navigator.deviceMemory heuristics to downgrade to VP9 on low-end devices.

Parameter Reference

Flag / attribute Context Effect
-crf libvpx-vp9, libx265, libsvtav1 Constant-rate-factor quality target; lower = better quality, larger file
-b:v 0 libvpx-vp9 Disables bitrate ceiling, enabling true CRF mode (mandatory with -crf in VP9)
-row-mt 1 libvpx-vp9 Enables row-based multithreading; speeds up VP9 encode by 1.5โ€“2ร— on 4+ cores
-tile-columns N libvpx-vp9, libsvtav1 Splits the frame into 2^N horizontal tile columns for parallel encode/decode
-tag:v hvc1 libx265, hevc_videotoolbox Sets the MP4 brand to hvc1; required for Safari/iOS H.265 playback
-movflags +faststart MP4 outputs Moves the moov atom to the file start so playback begins before the file fully downloads
-preset N libsvtav1 0 = slowest/best; 13 = fastest/worst; 5โ€“7 is practical for VOD pipelines
-pix_fmt yuv420p AV1 Forces 4:2:0 chroma subsampling for maximum hardware decoder compatibility
codecs="av01.0.05M.08" <source> type string AV1 profile 0 (main), level 3.1, Main tier, 8-bit; matches most 1080p SDR content
codecs="hvc1" <source> type string H.265 brand string; browser checks OS codec availability before downloading

Configuring correct MIME type headers on your media server is equally important: a browser that receives Content-Type: video/mp4 without a codec parameter may attempt to decode an AV1 stream with an H.264 decoder and fail silently.

Tradeoffs & Edge Cases

1. SVT-AV1 does not write WebM. libsvtav1 outputs to MP4 or Matroska. If your CDN or player stack requires AV1 in WebM (e.g. some older Chromium builds), you must use libaom-av1, which is significantly slower. In most cases, AV1 in MP4 is the correct choice.

2. H.265 patent ambiguity affects CDN caching. Some CDN edge nodes have had to disable server-side H.265 transcoding due to licensing. Confirm your CDNโ€™s HEVC support before building H.265 into your primary encode pipeline. AWS Elemental MediaConvert and Cloudflare Stream both support H.265 output but require explicit product tiers.

3. VP9 CRF mode requires -b:v 0. Setting -crf alone on libvpx-vp9 activates constrained quality (VBR with a quality floor), not pure CRF. Omitting -b:v 0 results in the encoder guessing a bitrate target from the resolution, often producing bloated files on high-resolution inputs.

4. AV1 film-grain synthesis can confuse QA tools. When using film-grain=N in SVT-AV1, the encoder strips grain from the source and stores parameters in the bitstream. SSIM and PSNR scores will appear lower than the perceptual quality because the reference frame no longer contains grain. Use VMAF with the neg model (trained on film-grain content) for accurate quality assessment.

5. H.265 on Chrome is OS-dependent, not browser-dependent. Chrome on Windows 10+ will play H.265 if the Media Feature Pack is installed; Chrome on macOS 13+ uses VideoToolbox. Chrome on Linux has no H.265 support regardless of FFmpeg build flags. MediaSource.isTypeSupported('video/mp4; codecs="hvc1"') reflects the OS state, not a Chrome-level decision.

Browser & Hardware Decode Compatibility Matrix

Codec Chrome 85+ Firefox 93+ Safari 14 Safari 16 Edge 18+ iOS 14 iOS 16 Android (Pixel 6+)
VP9 HW SW/HW SW HW HW SW HW HW
H.265 OS-dep No HW HW OS-dep (Win 10+) HW HW No
AV1 HW (90+) HW (113+) No HW (16.4+) HW (92+) No No HW (Tensor GPU)
H.264 HW HW HW HW HW HW HW HW

HW = hardware-accelerated decode available. SW = software only. OS-dep = depends on OS codec installation.

The AVIF vs WebP compression benchmark methodology applies analogous principles to still-image codec evaluation โ€” the SSIM/VMAF tradeoff analysis maps directly to the video codec selection problem.

Cache-Control headers for video assets interact with codec negotiation: if you serve multiple codec variants from the same URL using content negotiation, the CDN must see a Vary: Accept header to cache them separately, or you will serve incorrect variants to cached clients.

Debugging & Validation

Confirm the codec string your server sends:

# Check Content-Type and inspect the actual video codec in the file
curl -sI https://example.com/video.av1.mp4 | grep -i content-type

# Probe the container and codec without downloading the full file
ffprobe -v quiet -print_format json -show_streams \
  -select_streams v:0 \
  https://example.com/video.av1.mp4 2>/dev/null \
  | python3 -m json.tool | grep -E '"codec_name"|"codec_tag_string"|"pix_fmt"'

Verify hardware decode is active in Chrome:

  1. Open chrome://media-internals/ while the video plays.
  2. Find the active player entry and check the video_decoder property.
  3. A value of VpxVideoDecoder (software) vs MojoVideoDecoder or D3D11VideoDecoder (hardware) identifies the decode path.

Check VMAF score for your encode output:

ffmpeg -i output_av1.mp4 -i input.mp4 \
  -lavfi "[0:v][1:v]libvmaf=model=version=vmaf_v0.6.1:log_fmt=json:log_path=vmaf.json" \
  -f null -

# Read the aggregate VMAF score
python3 -c "import json; d=json.load(open('vmaf.json')); print(d['pooled_metrics']['vmaf']['mean'])"

Warning: A VMAF score below 85 on 1080p content at your target bitrate indicates the CRF is too high (quality too low) or the encoder preset is too fast. For VOD, target VMAF 93โ€“96; for live or near-live, VMAF 88โ€“92 is an acceptable trade for encode speed.

Validate the <video> fallback ladder in DevTools:

Open the Network panel, filter by media, and reload the page. The browser must request exactly one video resource. If you see two requests (e.g. both the AV1 and VP9 source files), the first <source> type string is either missing or invalid โ€” the browser downloaded the first file to detect the codec rather than reading the MIME type string.