WebM: The Royalty-Free Web Video Format
WebM is an open, royalty-free video container format developed by Google and released in 2010 alongside VP8. It is based on a subset of the Matroska container (MKV) and is designed exclusively for web delivery. WebM supports three video codecs (VP8, VP9, AV1) and two audio codecs (Vorbis, Opus), all royalty-free — making it the fully open alternative to MP4/H.264.
Container Architecture
WebM is built on the EBML (Extensible Binary Meta Language) format, which uses the same variable-length ID and size encoding as Matroska. An EBML document consists of nested elements, each with a variable-length ID, variable-length data size, and a data payload.
The top-level structure of a WebM file:
EBML Header
EBMLVersion: 1
EBMLReadVersion: 1
DocType: "webm"
DocTypeVersion: 2 (VP8) or 4 (VP9/AV1)
Segment
SeekHead — index of top-level element positions
Info — duration, TimestampScale, MuxingApp, WritingApp
Tracks — track descriptions (video, audio)
Cues — seek point table (like MP4's stss box)
Cluster — timed blocks of encoded data
Timestamp — cluster's base timestamp
SimpleBlock — audio or video frame data
BlockGroup — block + additional block data (references, discard padding)
The Segment element wraps all content. Clusters contain the actual coded frames. Each SimpleBlock carries the track number, a relative timestamp, flags (keyframe, invisible, lacing), and the raw encoded frame data. The Cues element provides a seek table mapping timestamps to cluster byte offsets — essential for seeking in large files.
Video Codecs: VP8, VP9, AV1
VP8 (WebM Profile 0 and 1)
VP8 was WebM's launch codec in 2010. It shares architectural similarities with H.264 but uses simpler entropy coding (boolean arithmetic coder) and a different intra-prediction scheme. VP8 is now largely superseded by VP9 but remains useful for:
- Devices with hardware VP8 decode (many Android devices)
- WebRTC (VP8 is still a mandatory WebRTC codec alongside H.264)
- Legacy browser compatibility
VP9 (WebM Profile 2 and 3)
VP9 (2013) improves over VP8 with:
- Larger coding units (superblocks): 64×64 superblocks vs VP8's 16×16 macroblocks
- Asymmetric partition sizes: 4×4 to 64×64 in L-shaped and rectangular configurations
- Reference frame types: 3 reference frames per inter frame vs VP8's 3 fixed types
- Transform sizes: 4×4, 8×8, 16×16, 32×32 (vs VP8's fixed 4×4/8×8)
- 10/12-bit colour support (Profile 2/3): enables HDR content
- Bitrate savings: approximately 40–50% better than VP8 at the same quality
AV1 (WebM Profile 0 and 1)
AV1 (2018) from the Alliance for Open Media offers:
- ~30% better compression than VP9 at the same quality
- Intra/inter prediction with 77 intra modes, obmc, warped motion compensation
- Loop filters: deblocking, CDEF (constrained directional enhancement), loop restoration
- Film grain synthesis
- 10/12-bit support for HDR
- Encode is ~10× slower than VP9 but decode hardware is growing rapidly (Intel Gen 11+, Apple M1+, Nvidia Ampere+)
FFmpeg WebM Encoding
# VP9 + Opus (recommended for most web use)
# Two-pass for best quality at target bitrate
ffmpeg -i input.mp4 -c:v libvpx-vp9 -b:v 0 -crf 33 \
-pass 1 -an -f null /dev/null
ffmpeg -i input.mp4 -c:v libvpx-vp9 -b:v 0 -crf 33 \
-pass 2 -c:a libopus -b:a 128k \
-row-mt 1 -threads 4 \
output.webm
# VP9 constant quality (one-pass, variable bitrate)
ffmpeg -i input.mp4 \
-c:v libvpx-vp9 -crf 33 -b:v 0 \
-c:a libopus -b:a 128k \
output.webm
# AV1 with libaom-av1 (slow but best quality)
ffmpeg -i input.mp4 \
-c:v libaom-av1 -crf 30 -b:v 0 \
-cpu-used 4 \
-c:a libopus -b:a 128k \
output_av1.webm
# AV1 with SVT-AV1 (faster, near-libaom quality)
ffmpeg -i input.mp4 \
-c:v libsvtav1 -crf 35 -preset 4 \
-c:a libopus -b:a 128k \
output_svtav1.webm
# VP8 (legacy / WebRTC interoperability)
ffmpeg -i input.mp4 \
-c:v libvpx -b:v 1M -crf 10 \
-c:a libvorbis -q:a 4 \
output_vp8.webm
# Fragmented WebM for DASH adaptive streaming
ffmpeg -i input.mp4 \
-c:v libvpx-vp9 -crf 33 -b:v 0 \
-c:a libopus -b:a 128k \
-f webm_dash_manifest -adaptation_sets "id=0,streams=v id=1,streams=a" \
manifest.mpd
VP9 CRF quality scale:
| CRF | Approximate quality | Use case |
|---|---|---|
| 15–25 | Near-lossless | Archival |
| 28–35 | High quality | Web VOD |
| 36–45 | Acceptable | Low-bandwidth streaming |
| 46–63 | Low quality | Thumbnails/previews |
Python WebM Automation
import subprocess
import json
def probe_webm(path: str) -> dict:
"""Return codec, resolution, duration info for a WebM file."""
cmd = ['ffprobe', '-v', 'quiet', '-print_format', 'json',
'-show_streams', '-show_format', path]
data = json.loads(subprocess.check_output(cmd))
video = next((s for s in data['streams'] if s['codec_type'] == 'video'), {})
audio = next((s for s in data['streams'] if s['codec_type'] == 'audio'), {})
return {
'video_codec': video.get('codec_name'),
'resolution': f"{video.get('width')}x{video.get('height')}",
'duration': float(data['format'].get('duration', 0)),
'audio_codec': audio.get('codec_name'),
'bitrate': int(data['format'].get('bit_rate', 0)) // 1000,
}
def encode_webm_vp9(src: str, dst: str, crf: int = 33) -> bool:
"""Two-pass VP9 encode with Opus audio."""
# Pass 1
p1 = subprocess.run([
'ffmpeg', '-i', src,
'-c:v', 'libvpx-vp9', '-b:v', '0', '-crf', str(crf),
'-pass', '1', '-an', '-f', 'null', '/dev/null', '-y'
], capture_output=True)
if p1.returncode != 0:
return False
# Pass 2
p2 = subprocess.run([
'ffmpeg', '-i', src,
'-c:v', 'libvpx-vp9', '-b:v', '0', '-crf', str(crf),
'-pass', '2',
'-c:a', 'libopus', '-b:a', '128k',
'-row-mt', '1',
dst, '-y'
], capture_output=True)
return p2.returncode == 0
info = probe_webm('sample.webm')
print(info)
# {'video_codec': 'vp9', 'resolution': '1920x1080', 'duration': 120.5, ...}
WebM vs MP4 / H.264
| Feature | WebM | MP4/H.264 |
|---|---|---|
| Royalties | None | MPEG-LA licensed |
| Browser support | Chrome, Firefox, Edge, Opera | All (including Safari, iOS) |
| Hardware decode | Growing | Universal |
| Encode speed | Slower (VP9/AV1) | Faster (H.264) |
| Compression | Better (VP9/AV1) | Good (H.264) |
| Streaming (DASH) | WebM DASH | MPEG-DASH / HLS |
| HDR support | VP9/AV1 | H.265 (not H.264) |
Practical recommendation: Use WebM/VP9 as a complement to MP4/H.264 in an HTML <video> element with <source> fallback. Chrome and Firefox prefer WebM; Safari falls back to MP4:
<video controls width="1280" height="720">
<source src="video.webm" type="video/webm">
<source src="video.mp4" type="video/mp4">
Your browser does not support the video element.
</video>
Common Pitfalls
- Safari does not support WebM. As of 2025, Safari on macOS/iOS does not include VP8/VP9/AV1 decoders (though AV1 support appeared in Safari 17 for some formats). Always provide an MP4 fallback.
- VP9 encode is significantly slower than H.264. Enable
-row-mt 1and-threads Nto use multiple CPU cores; this dramatically reduces encode time. - b:v 0 is required for CRF mode in libvpx-vp9. Unlike libx264 where
-crfalone enables quality mode, VP9 requires explicitly setting the bitrate target to 0 (-b:v 0) to activate constrained quality mode. - The Cues element must be at the start for web streaming. Some muxers write Cues at the end of the file. Use FFmpeg's
-cluster_size_limitand webm-specific options or remux withmkvmergeif seeking is slow.
Related conversions
Common video conversions that pair well with this guide: