FFmpeg ARM64 Windows D3D11VA decode: H.264, HEVC, and AV1 measured
In this article
- Matched x64 and ARM64 builds from one BtbN commit
- Three inputs generated by the ARM64 build
- The timed endpoint was CPU-visible NV12
- All three codecs selected D3D11VA
- Six rounds of wall time and host CPU time
- Reconstructed frames matched across four paths
- A D3D11 surface could not enter software scale directly
- 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.