aadhar.sh/garage/av2
Address
aadhar.shGarageAV2, before anyone can see it

AV2, before anyone can see it

AV2 is the successor to AV1, the codec inside every AVIF image, and its reference encoder shipped 1.0.0 in May. AOM's own all-intra report puts it 18 to 24% ahead of AV1 on stills. That number comes from video test sequences scored with PSNR, SSIM and VMAF. I wanted the answer for photographs, scored the way this site already scores its own images, at the exact byte budgets it ships. No browser can decode an AV2 image yet, so the figures below are decodes I made offline, and one section brings its own decoder to draw AV2 in your browser anyway.

Your browser, right now

These are three copies of the same 128 px crop. The first is AV1, the codec inside the AVIF files browsers decode today. The second is AV2 in the same container, at 2,030 bytes against AV1's 2,097. The third is how you'd try to ship AV2 safely: a <picture> source typed image/avif, with a WebP fallback.

AV1 control
AV1 in AVIF
AV2 in AVIF
AV2 in AVIF
AV2 with a WebP fallback
AV2, with a fallback

Enable JavaScript and this line reports what each image did.

In Chrome 154 and Canary 156 the third image is broken, and the WebP never loads. Of everything on this page, that's the finding most likely to cost somebody an afternoon, so it comes first.

Decoding it here instead

Your browser couldn't draw that second file, but this page can. The button below fetches AOM's reference decoder, AVM 1.0.0, compiled to WebAssembly, and runs it on a background thread. It's about 550 KB over the wire, which is why it waits for a click. It draws exactly the pixels the native build does: I compared every decoded sample on 16 files, from 8-bit monochrome to 10-bit 4:4:4, and all of them matched.

Nothing has been fetched yet.

Why the fallback doesn't catch it

A <picture> element chooses a source by its declared type, before any byte decodes. Chrome supports image/avif, so it commits to the source, fetches it, and finds an image it can't read. Nothing sends it back to the <img>, because the fallback only covers types a browser has never heard of.

The file explains the rest. libavif writes AV2 inside the same box structure as AV1, and the brand still says avif, mif1 and miaf. Only the image item changes, from av01 to av02, with an av2C config box beside it. A careful decoder like avifdec skips the item it doesn't know and reports Primary item not found. A browser draws a broken image.

So an AV2 image needs a name a browser can decline, the way image/avif gave AV1 stills a name that browsers without AV1 could skip. Until one exists, serving AV2 under the AVIF name breaks the image in every browser that supports AVIF, which is every current release of Chrome, Firefox, Safari and Edge.

At the bytes AVIF actually costs

Corrected 2026-09-28. The first version of this section scored only the 16 detail crops further down, found AV2 losing every call, and called that the answer for photographs. It was the answer for the densest detail in each photo. On the thumbnails this site actually ships, AV2 wins.

Corrected 2026-10-07. That note overreached too. The 38 tiles below average 0.46 bits per pixel, and the 255 tiles this site ships at 600 px have a median of 0.67. The probe reads only JPEG originals, and the 43 it had to draw from are a sparse run shot from August to December 2025. Bucketed the way the table further down pools them, 82 of the 255 shipped tiles sit under half a bit per pixel, where AV2 won 23 of 24 calls. The other 173 sit above it, where AV2 won 6 of 30. So AV2 wins on the sparse third of this site's thumbnails, and the other two thirds sit where it loses.

The thumbnails are 38 tiles at 600 px, cut the way the site's own pipeline cuts them: the whole frame, squared and reduced about 8x in linear light by zenc square. They come from Fuji photos that none of the tuning below ever saw. Each tile's budget is the file size of the AVIF tier this site ships (avifenc -q 63 -d 10 --speed 2 --yuv 444), and AV2 is encoded onto that budget, file against file. Both are scored against the source with the two perceptual metrics this site ships by: ssimulacra2, where higher is better, and butteraugli, where lower is better.

Matched bytes is the whole method, and AV2 makes it harder than usual. libavif's quality scale is far too coarse for it: on one crop, -q 49 wrote 6,865 bytes of AV2 and -q 50 wrote 9,608, a 40% step. So the probe steps AV2's own quantizer instead, one notch at a time.

38 tiles, AV2 speed 6Δ s2, meanΔ s2, medians2 winsbutteraugli wins
the shipped AVIF (AV1, speed 2)referencereference
AV2, libavif default (tune ssim)+0.77+0.6528 of 3824 of 38
AV2 with per-segment QM (see below)+0.89+0.9030 of 3828 of 38

With no tuning at all, AV2 wins 28 of 38 tiles by ssimulacra2 and 24 by butteraugli. The average margin is 0.77 points. That is modest, but it comes against an AV1 encoder with a still-image tune, while AV2's encoder has only the video defaults. It costs about 14 times the encode time, single-threaded, on a 600 px tile.

The crops that disagree

The first version of this page ran on the 16 detail crops from /pixel-peeper, each 320 px, from an X-T50. Each is the single most detailed 320 px window in its photo, at full resolution, picked to be the hardest thing an encoder sees. Each crop gets two budgets. The first is the file size of the shipped AVIF tier, and the second is the size of the JPEG fallback tier, filled with AVIF instead.

AV2 encoderΔ s2, trainΔ s2, holdoutwins of 32, s2 / buone 320 px encode
the shipped AVIF (AV1, speed 2)referencereference0.45 s
AV2 speed 6, libavif default (tune ssim)-1.75-2.160 / 75.4 s
AV2 speed 6, tune psnr-2.04-2.810 / 145.4 s
AV2 speed 3, libavif default (tune ssim)-1.03-1.534 / 1118.3 s
cross-check: raw avmenc, cpu-used 6 (tune psnr)-1.46-2.320 / 18
cross-check: raw avmenc, cpu-used 3 (tune psnr)-0.81-1.666 / 21

Each Δ is AV2 minus the shipped AVIF in ssimulacra2, averaged over 16 crop and budget pairs. Train is the 8 crops /pixel-peeper uses and holdout is 8 it never sees, so a result has to survive a set nobody tuned on. The two cross-check rows drive AV2's raw encoder through my own colour conversion, which shares no code with libavif above the codec. They score AV2 about half a point better than libavif does at the same tune. I haven't traced that gap, and neither instrument puts AV2 ahead on the crops.

On the crops, at speed 6, ssimulacra2 prefers the shipped AV1 on all 32 calls. Speed 3 beats speed 6 on every one of them and closes about a third of the gap, to 1.03 and 1.53, winning 4 of 32. It costs 41 times the encode time of the AVIF this site ships, and none of the speeds I could afford to run closes the whole gap.

These rows let AV2's encoder choose its own superblock size, and at 320 px it picks 64. Pinning the 128 that a real 600 px tile gets moves speed 6 to -1.57 on train and -2.12 on holdout.

What decides it: bits per pixel

The tiles and the crops sit at different densities. At the AVIF tier's budget these 38 tiles average 0.46 bits per pixel and a crop 1.08, about 2.3 times as many, because a crop is all detail and a tile is mostly sky, skin and shadow at thumbnail scale. Pool the two, sort by bits per pixel, and AV2's standing follows one curve. The last column counts this site's 255 shipped 600 px tiles in each band.

bits per pixelcallsΔ s2AV2 winsshipped tiles
under 0.312 tiles+1.6012 of 1230 of 255
0.3 to 0.512 tiles+0.7011 of 1252 of 255
0.5 to 0.811 tiles, 4 crops-0.474 of 1571 of 255
0.8 to 1.22 tiles, 8 crops-0.942 of 1086 of 255
over 1.21 tile, 4 crops-2.360 of 516 of 255

AV2 wins where the budget is thin and loses where every pixel carries detail, and the flip sits near half a bit per pixel. The trend holds within the tiles alone (a correlation of -0.33 between bits per pixel and AV2's margin), so it is more than a difference between two ways of cutting images. A third of this site's shipped thumbnails live at the thin end: 82 of 255 sit under half a bit per pixel, and the median, 0.67, sits past the flip. A pixel-peeping crop lives at the thick end, which is where the next section finds the loss.

Where AV2 loses

At the dense end, the loss has a shape. My first guess was that AV2 smooths away sensor grain, and I nearly published it. It's wrong. Measured on the decodes, AV2 reproduces the source's fine, pixel-scale texture more faithfully than AV1 on 21 of the 32 calls. The correlation between each decode's fine detail and the source's rises from 0.773 to 0.811.

What AV2 gets wrong is the coarse picture. Average each image over 8 by 8 pixel blocks, which is roughly what you'd see from across the room, and compare those averages with the source's. AV2's error is higher than AV1's on 32 of 32 calls, by 50% in brightness (0.26 to 0.39 on an 8-bit scale) and 64% in colour (0.25 to 0.41). It holds on the 8 calls where the two files sit within 1% of the same size, so it isn't a byte shortfall.

So this encoder moves its bits from the broad strokes to the fine ones: slightly truer texture, slightly wronger tone and colour in flat areas. ssimulacra2 scores the picture at several scales and weighs colour heavily, and it reads that trade as a loss.

Source crop from XT509535, losslessThe shipped AV1 encode of the same crop, decodedThe AV2 encode at the same budget, decoded
XT509535, at the AVIF tier's budget. AV1 11,166 B: ssimulacra2 71.00, butteraugli 2.204. AV2 10,824 B (speed 3, qp 107): ssimulacra2 65.91, butteraugli 2.419. Fine texture tracks the source at 0.57 for AV1 and 0.70 for AV2; block-scale error runs 0.26 against 0.40 in brightness and 0.23 against 0.38 in colour.
Source crop from XT509540, losslessThe shipped AV1 encode of the same crop, decodedThe AV2 encode at the same budget, decoded
XT509540, at the AVIF tier's budget. AV1 8,906 B: ssimulacra2 73.54, butteraugli 2.006. AV2 8,308 B (speed 3, qp 105): ssimulacra2 69.70, butteraugli 2.561. Fine texture tracks the source at 0.50 for AV1 and 0.59 for AV2; block-scale error runs 0.26 against 0.45 in brightness and 0.21 against 0.45 in colour.
Source crop from XT509278, losslessThe shipped AV1 encode of the same crop, decodedThe AV2 encode at the same budget, decoded
XT509278, at the JPEG fallback's budget. AV1 20,866 B: ssimulacra2 82.94, butteraugli 1.824. AV2 20,561 B (speed 3, qp 103): ssimulacra2 82.99, butteraugli 1.568. Fine texture tracks the source at 0.80 for AV1 and 0.87 for AV2; block-scale error runs 0.24 against 0.37 in brightness and 0.26 against 0.40 in colour.

Each figure shows the 200 px region where the AV1 and AV2 decodes differ most, at 2x, in lossless WebP, because re-encoding a decode would add artifacts of its own. Click or tap a figure to flip between the three in place, which is the easiest way to see a codec difference. Each AV2 file is the largest that fits at or under the AV1 file, so AV2 never gets the extra bytes.

The tune you didn't choose

butteraugli's verdict turned out to depend on a setting nobody asked for. libavif tells AV2's encoder to tune for SSIM unless you pick a tune yourself, and AV2's own command-line encoder defaults to PSNR. At speed 6, switching libavif to tune=psnr costs AV2 about half a point of ssimulacra2 and doubles its butteraugli wins, from 7 of 32 to 14.

AV2's encoder accepts those two tunes and refuses a third: tune=iq, the image-quality tune libaom added for stills in 3.13, which the shipped AVIF uses. So the comparison pits a codec with a tune built for photographs against one that has only the video defaults. That's a fair picture of today, and a reason to expect the gap to narrow.

Where the literature's number comes from

AOM's report measures something real and different. Its AV2 beats an AV1 anchor by 17.82% on PSNR-Y and 23.58% on VMAF in all-intra mode, which codes every frame as a still. The anchor is a branch of libaom, the test material is video sequences, and the metrics are PSNR-HVS, PSNR, SSIM, MS-SSIM, CIEDE2000 and VMAF. Neither ssimulacra2 nor butteraugli appears, and I couldn't find the encoder speeds in it.

It also reports cost: on average AV2 took 33 times longer to encode than the anchor and 3.86 times longer to decode. The encode column above agrees in kind. Through libavif, one 320 px thumbnail takes 5.4 seconds at speed 6 and 18.3 at speed 3, against 0.45 for the AVIF this site ships. AV2's raw encoder at cpu-used 1 was still running after 8 minutes when I stopped it.

So the two results don't contradict each other. By the metrics video is judged by, AV2 is the better codec. On photographs, its first encoder turns that into a win by a photo metric on the sparsest thumbnails, and gives it back as the detail rises. AV1 froze its bitstream in June 2018, and Chrome shipped AVIF in August 2020, after faster encoders had arrived. I expect AV2 to need the same run-up before the dense end turns too.

Lossless

AV2's lossless mode round-trips all 16 crops bit for bit. It's also 29% larger than AV1's, which surprised me, because AOM lists improved lossless coding among AV2's new tools.

16 crops, losslesstotal bytesvs PNG
PNG (the source)3,436,4410%
AV2 (avifenc -c avm -l)3,048,575-11.3%
AV1 (avifenc -c aom -l)2,363,430-31.2%
JPEG XL (cjxl -d 0 -e 9)1,571,053-54.3%

Both AVIF files used the same setup: 8-bit 4:4:4, full range, and the identity matrix, so the colour conversion isn't the difference. The slower speed 3 made one crop slightly bigger, 161,336 bytes against 160,357. I think libavif's experimental AV2 path is the likelier cause than the format, but I haven't proved it. JPEG XL's lead is the older, bigger story, and nothing here changes it.

What the reference encoder leaves on the table

AVM is a reference encoder, and a reference encoder leaves tuning to whoever comes next. I ported four ideas from the encoders that tune AV1 for stills, and one that only AV2 can express. Each is a patch that is off by default, and the untouched reference decoder reads its output pixel for pixel. Each was scored on the crops, where AV2 needs the help most.

patchwhat it doesΔ s2 on cropsverdict
base-delta-qexposes the sequence-level DC and chroma offsets AVM hard-codes to zero-0.06 (holdout, chroma +4)about zero
variance-boostlibaom and SVT-AV1's Variance Boost: finer quantizers for flat superblocks-0.54 (train)loses
jpegli-maskjpegli's adaptive-quantization field, mapped onto AV2's quantizer-0.25 (train)loses
qm-curveslibaom's all-intra quantization-matrix curve-0.47 (holdout)loses
qm-per-segmenttwo matrix sets per frame, split by texture+0.14 (holdout)wins

Only the last one pays, and it is the one AV1 had no syntax for. AV1 signals one quantization-matrix level per plane for the whole frame. AV2 can carry up to four sets and let each segment pick one. The all-intra curve helps smooth crops and costs 2 to 3.5 points on busy ones, so one frame-wide matrix is the wrong granularity. Splitting the frame with jpegli's masking field, a flatter matrix for textured areas and the curve for smooth ones, turns the curve's 0.47 loss into a 0.14 gain on the holdout crops, winning 11 of 16. On the tiles it adds 0.12 over plain AV2 and wins 28 of 38.

The same build compiles AVM's own AVX2 kernels for Apple silicon through SIMDe. AVM ships almost no NEON code, so on a Mac most of its hot loops ran in plain C. The output is byte-identical to the plain build, and a 600 px encode runs 1.35 times as fast.

Run it yourself

Everything above comes from three committed tools. tools/photos/libavif-avm/build.sh builds libavif at a pinned commit with its experimental AV2 codec switched on. No tagged libavif release carries AVM 1.0.0 yet, so the pin is a commit. --tuned adds the five patches and the SIMDe kernels, and refuses to finish unless the result matches the pristine build byte for byte with every option off. av2-tile-probe.ts runs the tile comparison and codec-knob-probe.ts --codec avm runs the crops. tools/photos/av2-wasm/build.sh compiles the decoder this page runs, and samples.ts beside it writes the four AV2 thumbnails, refusing any photo whose shipped AVIF it can't reproduce byte for byte.

tools/photos/libavif-avm/build.sh --tuned
bun tools/photos/av2-tile-probe.ts --src <a folder of originals>
bun tools/photos/codec-knob-probe.ts --codec avm --variants base,tunepsnr
tools/photos/av2-wasm/build.sh

With every scheduling and caching concern stripped out, the probe is four steps per crop. Encode the shipped AVIF and take its size as the budget. Find the two AV2 quantizer settings whose files straddle that size. Decode both and score them against the source. Interpolate the score to the exact budget. The rest of the file is caching, crop selection and parallelism, which only change how long it takes.

What would change this

The result is small, and it cuts both ways. It covers 38 tiles, sparser than the tier they sample, and 16 crops from one camera. It runs the reference encoder rather than a production one, in a container libavif itself labels experimental, and scores with two metrics that both come from the JPEG XL project. A production AV2 encoder is the likeliest thing to move the dense end, the way SVT-AV1 and later libaom releases did for AV1, and a still-image tune is the first place to look. The thin end is already a win, and it covers a third of this site's thumbnails. To ship that third, AV2 waits on a browser that decodes it and a name that browser can decline. When either arrives, both probes re-run in an afternoon.

watching · the understanding check is part of the page, not a gate

garagewatching2026-09-27