zenrav1e [](https://github.com/imazen/zenrav1e/actions/workflows/ci.yml) [](https://crates.io/crates/zenrav1e) [](https://lib.rs/crates/zenrav1e) [](https://docs.rs/zenrav1e) [](https://doc.rust-lang.org/cargo/reference/manifest.html#the-rust-version-field) [](#license)
June 28, 2026 · View on GitHub
zenrav1e is an AV1 encoder tuned for still and animated AVIF images — an Imazen fork of rav1e that adds frequency-dependent quantization matrices (~10% BD-rate), recursive filter-intra prediction, opt-in trellis RDOQ, and a mathematically-lossless mode, all additive on top of the battle-tested upstream encoder. The library default is pure Rust and toolchain-free (no NASM/C needed); the x86_64 SIMD assembly is one feature flag away. Rust 2024 edition, dual-licensed AGPL-3.0 / commercial.
Quick start
zenrav1e is a library that emits a raw AV1 bitstream. If you just want to write AVIF image files, reach for ravif or zenavif — they wrap zenrav1e with a higher-level API that does the RGB→YCbCr conversion and AVIF/HEIF muxing for you. Use the raw API below only when you need direct control over the encoder.
[dependencies]
# Pure-Rust default (no NASM/C toolchain). The default feature is `threading`.
zenrav1e = "0.2.0"
# For the x86_64 SIMD assembly kernels (needs NASM 2.14+ at build time):
# zenrav1e = { version = "0.2.0", features = ["asm"] }
Input is planar YCbCr, not RGB. rav1e (like AV1 itself) encodes Y′CbCr planes — there is no RGB entry point. If you fill the planes with interleaved RGB bytes the encode succeeds but the colors come out wrong, because the encoder reads plane 0 as luma and planes 1/2 as chroma. Convert to YCbCr (e.g. BT.601/709) and lay the samples out one plane at a time. The defaults below are 8-bit (
Context<u8>) 4:2:0 (ChromaSampling::Cs420), so the U and V planes are half-resolution in each dimension. For 10/12-bit useContext<u16>and pass asource_bytewidthof2tocopy_from_raw_u8.
use zenrav1e::prelude::*;
// `y`, `u`, `v`: tightly packed planar YCbCr 4:2:0, 8-bit (U/V half-size).
fn encode_still(
y: &[u8], u: &[u8], v: &[u8],
) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
let mut enc = EncoderConfig::default();
enc.width = 640;
enc.height = 480;
enc.still_picture = true; // single-frame AVIF still
enc.chroma_sampling = ChromaSampling::Cs420; // YCbCr 4:2:0 (the default)
enc.bit_depth = 8; // 8-bit (the default)
enc.quantizer = 80; // see the quantizer note below
enc.speed_settings = SpeedSettings::from_preset(6);
enc.enable_qm = true; // quantization matrices (~10% BD-rate)
let cfg = Config::new().with_encoder_config(enc);
let mut ctx: Context<u8> = cfg.new_context()?;
// Allocate a frame sized to the config and copy each YCbCr plane in.
// `copy_from_raw_u8(src, src_stride_in_bytes, src_bytewidth)`:
// - src_bytewidth = 1 for `Context<u8>`, 2 for `Context<u16>` (10/12-bit)
// - src_stride is the row stride of *your* buffer; the call handles the
// frame's internal (padded) stride. Here each plane is tightly packed,
// so the stride is just that plane's width.
let mut frame = ctx.new_frame();
let planes = [y, u, v];
for (plane, src) in frame.planes.iter_mut().zip(planes) {
// Chroma planes are subsampled by `xdec`/`ydec` for 4:2:0, so derive
// each plane's row width from its own decimation factor.
let plane_width = (enc.width + (1 << plane.cfg.xdec) - 1) >> plane.cfg.xdec;
plane.copy_from_raw_u8(src, plane_width, 1);
}
ctx.send_frame(frame)?;
ctx.flush(); // signal end-of-stream so the still gets emitted
// Drain packets. For a still picture this yields exactly one packet.
let mut bitstream = Vec::new();
loop {
match ctx.receive_packet() {
Ok(packet) => bitstream.extend_from_slice(&packet.data),
Err(EncoderStatus::Encoded) => {} // frame consumed, no packet yet
Err(EncoderStatus::LimitReached) => break, // all frames emitted
Err(EncoderStatus::NeedMoreData) => break, // nothing left after flush
Err(e) => return Err(e.into()),
}
}
// `bitstream` is a raw AV1 bitstream (OBUs / temporal units) — NOT a `.avif`
// file. To get a playable image, mux it into an AVIF/HEIF container with
// `zenavif`, `ravif`, or `zenavif-serialize`. (Those crates also do the
// RGB→YCbCr conversion, so prefer them unless you need this level of control.)
Ok(bitstream)
}
Quantizer scale
EncoderConfig::quantizer is the AV1 base q-index, a usize in 0..=255
(default 100). Lower means higher quality and larger output; higher means
smaller and lower quality — the opposite direction from a "quality %" dial.
quantizer = 0 is the special mathematically-lossless mode (see the lossless
feature below); 255 is the most aggressive. For perceptual quality targeting,
the ravif/zenavif layers map a friendlier quality scale onto this q-index.
Cooperative cancellation
The stop feature (features = ["stop"]) lets a watchdog or request-deadline
thread abort an in-progress encode. Pass any enough::Stop token to
Context::set_stop; it is checked once per superblock. The simplest
constructible token is almost_enough::Stopper (cargo add almost-enough) —
#[derive(Clone)], and all clones share one cancellation flag:
use std::sync::Arc;
use zenrav1e::prelude::*;
// Continuing from the example above, with the `stop` feature enabled:
let mut ctx: Context<u8> = cfg.new_context().unwrap();
// Hand the encoder a clone; keep `stopper` to trigger cancellation elsewhere.
let stopper = almost_enough::Stopper::new();
ctx.set_stop(Arc::new(stopper.clone()));
// From a deadline/watchdog thread (or on client disconnect):
stopper.cancel();
// `send_frame` / `receive_packet` then return `Err(EncoderStatus::Cancelled)`.
// To clear cancellation again, restore the no-op token:
ctx.set_stop(Arc::new(zenrav1e::Unstoppable));
zenrav1e re-exports Stop, StopReason, and Unstoppable from enough
so you can name them without a direct dependency; the concrete Stopper token
lives in the companion almost-enough crate.
Fork of rav1e
All changes are additive on top of upstream rav1e — every upstream video encoding capability is preserved.
Still-image encoding features
- Quantization matrices (
enable_qm) — frequency-dependent quantization weights; ~10% BD-rate improvement on photographic stills. Off by default, recommended on. - Filter-intra prediction — 5 recursive filter modes, auto-enabled at speed ≤ 6.
Tune::StillImage— a tuning preset for photographic content (perceptual distortion with activity masking).- Lossless mode — mathematically lossless encoding via
quantizer = 0. - Trellis RDOQ (
enable_trellis, opt-in) — multi-level Viterbi coefficient optimization; −0.94% mean BD-rate(Y) on a 38-image photo corpus (regression-free across photo classes) at ~+72% encode time. Off by default because it mainly helps photographic content. - Variance-adaptive quantization (
enable_vaq,vaq_strength) and segment boost (seg_boost) — experimental knobs, off by default (see the benchmarks for why they didn't make the default config). - Cooperative cancellation —
enough::Stopsupport behind thestopfeature.
Modernization
- Rust 2024 edition (MSRV 1.89).
- Pure-Rust, toolchain-free default. The default feature set is
threadingonly;asm(NASM SIMD),scenechange, andsignal_supportmoved to thebinariesfeature, so library consumers need no C/NASM toolchain by default. safe_unaligned_simdfor safe SIMD load/store in entropy coding (underasm).
Building
# Pure Rust (no asm) — the default; primary development target
cargo check
cargo test
# With x86_64 asm SIMD kernels (requires NASM 2.14.02+)
cargo check --features asm
Requires Rust 1.89+. The asm feature needs NASM 2.14.02+
on x86_64.
Benchmarks
BD-rate is measured against upstream rav1e with SSIMULACRA2 (negative = better compression at equal quality). Headline measured results:
| Configuration | BD-rate vs upstream | Encode time | Corpus |
|---|---|---|---|
enable_qm (default-recommended) | −10.1% mean | ~1× | 63 images, speed 6 |
enable_qm + forced rdo_tx_decision | −10.3% mean | ~3× | 63 images, speed 6 |
enable_trellis (opt-in) | −0.94% mean (Y) | ~+72% | 38 photos, speed 4 |
Quantization matrices are the big, cheap win and the recommended default-on
feature. Trellis RDOQ is opt-in because it mainly helps photographic content (it
can mildly regress born-digital figures and line-art). Several other knobs
(VAQ, Tune::StillImage, variance boost, segment boost, bottom-up partition
search) were measured and left off because they shift the operating point
or regress without improving compression efficiency.
Full methodology, per-image data, and the negative results are in
benchmarks/
(see benchmarks/README.md).
Kernel-level microbenchmarks live in benches/
(cargo bench --features bench).
License
Dual-licensed: AGPL-3.0 or commercial.
I've maintained and developed open-source image server software — and the 40+ library ecosystem it depends on — full-time since 2011. Fifteen years of continual maintenance, backwards compatibility, support, and the (very rare) security patch. That kind of stability requires sustainable funding, and dual-licensing is how we make it work without venture capital or rug-pulls. Support sustainable and secure software; swap patch tuesday for patch leap-year.
Your options:
- Startup license — $1 if your company has under $1M revenue and fewer than 5 employees. Get a key →
- Commercial subscription — Governed by the Imazen Site-wide Subscription License v1.1 or later. Apache 2.0-like terms, no source-sharing requirement. Sliding scale by company size. Pricing & 60-day free trial →
- AGPL v3 — Free and open. Share your source if you distribute.
See LICENSE-COMMERCIAL for details.
Upstream code from xiph/rav1e is licensed under BSD-2-Clause, with the Alliance for Open Media Patent License 1.0 (see PATENTS). Our additions and improvements are dual-licensed (AGPL-3.0 or commercial) as above.
Upstream contribution
We are willing to release our improvements under the original BSD-2-Clause license if upstream takes over maintenance of those improvements. We'd rather contribute back than maintain a parallel codebase. Open an issue or reach out.
Image tech I maintain
| Codecs ¹ | zenjpeg · zenpng · zenwebp · zengif · zenavif · zenjxl · zenbitmaps · heic · zentiff · zenpdf · zensvg · zenjp2 · zenraw · ultrahdr |
| Codec internals | zenjxl-decoder · jxl-encoder · zenrav1e · rav1d-safe · zenavif-parse · zenavif-serialize |
| Compression | zenflate · zenzop · zenzstd |
| Processing | zenresize · zenquant · zenblend · zenfilters · zensally · zentone |
| Pixels & color | zenpixels · zenpixels-convert · linear-srgb · garb |
| Pipeline & framework | zenpipe · zencodec · zencodecs · zenlayout · zennode · zenwasm · zentract |
| Metrics | zensim · fast-ssim2 · butteraugli · zenmetrics · resamplescope-rs |
| Pickers & ML | zenanalyze · zenpredict · zenpicker |
| Products | Imageflow image engine (.NET · Node · Go) · Imageflow Server · ImageResizer (C#) |
¹ pure-Rust, #![forbid(unsafe_code)] codecs, as of 2026
General Rust awesomeness
zenbench · archmage · magetypes · enough · whereat · cargo-copter