ARM64_Lab

FFmpeg ARM64 Windows D3D11VA decode: H.264, HEVC, and AV1 measured

In this article
  1. Matched x64 and ARM64 builds from one BtbN commit
  2. Three inputs generated by the ARM64 build
  3. The timed endpoint was CPU-visible NV12
  4. All three codecs selected D3D11VA
  5. Six rounds of wall time and host CPU time
  6. Reconstructed frames matched across four paths
  7. A D3D11 surface could not enter software scale directly
  8. How I would choose the path

On August 11, 2026, I measured FFmpeg ARM64 Windows D3D11VA decoding on a Surface Pro 11. H.264, HEVC, and AV1 all selected the Qualcomm Adreno adapter with both the x64 and ARM64 executables. Each 60-second input produced all 1800 frames with zero decode errors. Per-frame SHA-256 output also matched across all four paths within each codec.

The result changed when I included the transfer of decoded pixels back to CPU-visible NV12 memory. With the native ARM64 executable, median H.264 time was 4.800 seconds in software and 13.191 seconds through D3D11VA. HEVC took 4.220 seconds versus 9.149 seconds, while AV1 took 5.354 seconds versus 5.474 seconds. D3D11VA reduced host-process CPU time by 85.4% to 91.1%, but it increased elapsed time for H.264 and HEVC.

My previous Qualcomm H.264 hardware encode test focused on h264_mf, -hw_encoding 1, nv12, and its quality difference from libx264. The earlier FFmpeg x64 versus ARM64 comparison included a short scale-to-null job. I did not benchmark encoding or scaling speed here. This is a separate investigation of H.264, HEVC, and AV1 decode availability, the bridge from D3D11 surfaces to CPU filters, and equality of reconstructed frames.

Matched x64 and ARM64 builds from one BtbN commit

I downloaded the GPL snapshots from BtbN/FFmpeg-Builds. Both artifacts returned by the August 10, 2026 latest URLs reported ffmpeg version N-126039-g6bbc22dc09-20260810.

Artifact ZIP size ZIP SHA-256 ffmpeg.exe PE ffmpeg.exe SHA-256
win64-gpl 170333563 bytes c91c9bd5e396b7d65f4520e789bd0321b7747c0df3fa6e1cfb4b1bc31bb10f4e x64 0x8664 176ba7100218e3f91260637b4d928301b2ea963842c5758935dc06bd601beb38
winarm64-gpl 116158081 bytes c8a612ba0cad31ff39e48d8b90d1d54d48035d2838565c04e739e8374929fca1 ARM64 0xAA64 d0e6813b1e4904ce5b83d629c833cf72fbbb43af12a6d4b2c88d5c4668428a87

These are the two download endpoints. The latest files will change, so the benchmark refuses to extract or execute an archive unless its ZIP SHA-256 and complete version match the values above.

The x64 executable was built with GCC 15.2.0. The ARM64 executable used Clang 23.1.0-rc2. They share an FFmpeg commit, but they do not share a compiler or necessarily the same SIMD kernels. I therefore do not interpret every x64-to-ARM64 difference as Prism translation overhead.

Three inputs generated by the ARM64 build

The test machine was a Microsoft Surface Pro, 11th Edition with a Snapdragon X Elite X1E80100, 12 cores, and 33893933056 bytes of memory. It ran Windows 11 Pro 10.0.26200 ARM64. I launched the benchmark from Python 3.12.10 ARM64. D3D11VA adapter 0 was Qualcomm(R) Adreno(TM) X1-85 GPU, device ID 4d4f4351:36334330, with driver 31.0.137.0.

The source was synthetic rather than camera footage. I generated 1920x1080, 30fps, 60-second testsrc2 streams separately with libx264, libx265, and libsvtav1 in the ARM64 executable.

ffmpeg.exe -f lavfi -i "testsrc2=size=1920x1080:rate=30" -t 60 `
  -c:v libx264 -preset ultrafast -crf 18 -pix_fmt yuv420p -g 120 h264.mkv

ffmpeg.exe -f lavfi -i "testsrc2=size=1920x1080:rate=30" -t 60 `
  -c:v libx265 -preset ultrafast -crf 22 -pix_fmt yuv420p `
  -x265-params "log-level=error:keyint=120" hevc.mkv

ffmpeg.exe -f lavfi -i "testsrc2=size=1920x1080:rate=30" -t 60 `
  -c:v libsvtav1 -preset 11 -crf 30 -pix_fmt yuv420p -g 120 av1.mkv
Input Profile Size Average bitrate SHA-256
H.264 Constrained Baseline 149738630 bytes 19965150bit/s 8439b926647c43941592dc98a40b93f3683b56793d78ddcd356e2a1d85432b78
HEVC Main 61172162 bytes 8156288bit/s 7c1c6fbb5b44248a5fe4963167864ece2375e869fdb5b9fc763f9e51038c9a4e
AV1 Main 78633454 bytes 10484460bit/s dec17d213e895d3695d8bdf1c89fefcca55dfb95d7cc41ff1425e8f04baeb79b

ffprobe -count_frames confirmed 1800 frames, 60.000 seconds, yuv420p, and 1920x1080 for every input. Bitrate, compression ratio, and decoder implementation are not matched across codecs. The H.264, HEVC, and AV1 elapsed times are therefore not a cross-codec speed ranking.

The timed endpoint was CPU-visible NV12

The software path explicitly selected h264 for H.264, hevc for HEVC, and libdav1d for AV1. It then normalized the output through format=nv12. The D3D11VA path pinned adapter 0, requested d3d11 surfaces, and returned them to system memory with hwdownload,format=nv12.

# software decode
ffmpeg.exe -hwaccel none -c:v h264 -stream_loop 3 -i h264.mkv `
  -map 0:v:0 -an -sn -dn -vf format=nv12 `
  -progress pipe:2 -nostats -f null NUL

# D3D11VA decode, synchronized by readback to system memory
ffmpeg.exe -init_hw_device d3d11va=hw:0 `
  -hwaccel d3d11va -hwaccel_device hw -hwaccel_output_format d3d11 `
  -c:v h264 -stream_loop 3 -i h264.mkv `
  -map 0:v:0 -an -sn -dn -vf "hwdownload,format=nv12" `
  -progress pipe:2 -nostats -f null NUL

Sending D3D11 surfaces directly to the null muxer can release frame references without making the CPU read their pixels. I included hwdownload in the timed command so completion means NV12 pixels reached system memory and could enter a software filter. This is not a pure video-engine measurement. It includes demux, decode, D3D11 surface handling, readback, and the format filter.

I looped inputs to avoid sub-second jobs. H.264 ran for four passes and 7200 frames, HEVC for two passes and 3600 frames, and AV1 for one pass and 1800 frames. Every case received one discarded warm-up. I changed the order of all 12 cases in every round using random seed 20260811, then measured six rounds. Wall time came from Python's perf_counter; CPU time came from Windows GetProcessTimes. The report aggregates minimum, median, maximum, and population standard deviation.

The reproducible benchmark is scripts/ffmpeg_arm64_d3d11va_decode.py in the repository:

python scripts\ffmpeg_arm64_d3d11va_decode.py `
  --duration 60 --rounds 6 `
  --json .bench\ffmpeg-arm64-d3d11va-decode.json

All three codecs selected D3D11VA

Verbose output selected the Qualcomm Adreno adapter 0 in every x64 and ARM64 case. The decoder was h264 for H.264, hevc for HEVC, and av1 for AV1. FFmpeg printed the supported decoder GUID list and pixfmt:d3d11. Each source produced 1800 decoded frames and zero decode errors.

That evidence establishes the D3D11VA device, decoder API path, and hardware-frame format selected by FFmpeg. I did not capture Windows GPU Engine counters or ETW. It does not establish a physical Video Decode engine utilization percentage, and it is not a power measurement.

Six rounds of wall time and host CPU time

Codec / path PE Wall seconds min / median / max Standard deviation max/min Median CPU seconds x64/ARM64
H.264 software NV12 x64 5.547 / 8.285 / 12.901 2.818 2.326 31.875 1.726
H.264 software NV12 ARM64 3.628 / 4.800 / 10.953 2.487 3.019 28.273
H.264 D3D11VA readback x64 13.666 / 16.412 / 24.680 3.616 1.806 4.742 1.244
H.264 D3D11VA readback ARM64 13.033 / 13.191 / 16.399 1.320 1.258 4.117
HEVC software NV12 x64 4.179 / 8.593 / 13.195 3.311 3.157 31.938 2.036
HEVC software NV12 ARM64 2.987 / 4.220 / 7.948 1.745 2.661 20.883
HEVC D3D11VA readback x64 8.972 / 10.133 / 14.550 1.939 1.622 2.250 1.108
HEVC D3D11VA readback ARM64 8.611 / 9.149 / 9.852 0.421 1.144 1.859
AV1 software NV12 x64 9.407 / 10.979 / 14.457 1.810 1.537 19.039 2.051
AV1 software NV12 ARM64 4.722 / 5.354 / 10.764 2.105 2.280 12.015
AV1 D3D11VA readback x64 5.489 / 5.912 / 14.925 3.368 2.719 1.070 1.080
AV1 D3D11VA readback ARM64 5.096 / 5.474 / 14.826 3.511 2.909 1.070

Within the ARM64 executable, D3D11VA/readback wall time was 2.748x the software time for H.264, 2.168x for HEVC, and 1.022x for AV1. The hardware path was slower for H.264 and HEVC after readback. AV1 was effectively tied at the median.

Host-process CPU time tells a different story. ARM64 H.264 dropped from 28.273 to 4.117 CPU seconds, an 85.4% reduction. HEVC dropped from 20.883 to 1.859 seconds, or 91.1%. AV1 moved from 12.015 to 1.070 seconds, also 91.1%. Freeing CPU capacity and finishing the command as quickly as possible were different goals in this test.

For software decoding, the x64-to-ARM64 median wall ratios were 1.726 for H.264, 2.036 for HEVC, and 2.051 for AV1. The D3D11VA/readback ratios narrowed to 1.244, 1.108, and 1.080. In this end-to-end command, native architecture mattered more in the software decoder path than when both executables used the Qualcomm D3D11VA device. Compiler and decoder-optimization differences prevent me from isolating a pure Prism emulation tax.

The wall times were much less stable than I expected. max/min reached 3.019 for ARM64 H.264 software, 3.157 for x64 HEVC software, and 2.909 for ARM64 AV1 D3D11VA. Several later rounds slowed down. I did not isolate background load, temperature, or driver waiting, so this is not evidence for small performance differences.

Reconstructed frames matched across four paths

Separate from timing, I decoded every input once through software x64, software ARM64, D3D11VA x64, and D3D11VA ARM64. Each path produced CPU-visible NV12 and wrote framehash -hash sha256. After normalizing all 1800 per-frame rows, every codec had exactly one unique digest.

Input Normalized framehash SHA-256 shared by all four paths
H.264 a06c1f93f0c178bd45e5331714945f14b9c1d0cfa450140b86223d8bd48ff0b4
HEVC 75c05c687d035d9a5eb04244a89f85b83232c0cde9c4112b7ead6e6e94e3e05a
AV1 afc7b3e1b548979e46ee1fcfae93f5818cfbfa83729fced79ebc33578254dc89

For these synthetic 8-bit 4:2:0 streams, the different PE architectures and software/hardware paths reconstructed bit-identical NV12 frames. On a later run, the script will not start timing unless all normalized framehash output collapses to one digest. No truncated or frame-missing output enters the timing table. This does not establish bit equality for 10-bit, HDR, film grain, or damaged streams.

A D3D11 surface could not enter software scale directly

I hit one reproducible failure. Passing D3D11VA output directly to the software scale filter ended with Function not implemented on the ARM64 executable and produced zero output frames.

ffmpeg.exe -init_hw_device d3d11va=hw:0 `
  -hwaccel d3d11va -hwaccel_device hw -hwaccel_output_format d3d11 `
  -c:v h264 -i h264.mkv -vf "scale=1280:720" -f null NUL

Adding hwdownload,format=nv12 before scale processed all 1800 frames:

ffmpeg.exe -init_hw_device d3d11va=hw:0 `
  -hwaccel d3d11va -hwaccel_device hw -hwaccel_output_format d3d11 `
  -c:v h264 -i h264.mkv `
  -vf "hwdownload,format=nv12,scale=1280:720" -f null NUL

AV1 exposed another trap. Forcing -hwaccel none -c:v av1 failed with return code 69 and Function not implemented in both x64 and ARM64 builds. Explicit libdav1d worked for software AV1, while native av1 worked with D3D11VA. Seeing av1 in ffmpeg -decoders did not guarantee that this stream would decode through that decoder without hardware acceleration.

How I would choose the path

When an FFmpeg ARM64 Windows pipeline sends decoded frames into CPU filters, I would start with the native ARM64 software decoder. It completed H.264 and HEVC faster than D3D11VA plus readback in this test. A software filter after D3D11VA needs the explicit hwdownload,format=nv12 bridge.

I would still consider D3D11VA when freeing host CPU matters. It reduced ARM64 host-process CPU time by 85.4% to 91.1% across the three codecs. That benefit is separate from elapsed-time reduction. AV1 software libdav1d and D3D11VA/readback were nearly tied at 5.354 and 5.474 seconds, so concurrent CPU work could change the practical choice.

I did not measure GPU Engine or ETW counters, power, temperature, battery use, camera footage, 4K, 10-bit, HDR, D3D12VA, player rendering, or long continuous playback. The power-plan probe also returned null. D3D11VA compatibility worked for all three codecs on this machine, but these measurements do not support the claim that hardware decoding is always faster with FFmpeg ARM64.

FFmpeg Video Benchmark Hardware acceleration
a
arm64lab — Independent publisher

Personal test notes from a Surface Pro 11th Edition with Snapdragon X Elite, used as a daily machine since May 2025. Results are based on direct measurements and do not represent any company or organization.