Side quest
The same 2009 netbook from mtune-mirror · made to play modern H.264 anyway.
That Aspire One's Intel GMA950 has real video-decode hardware — but only for MPEG-2, only through XvMC, an X11 extension almost nothing speaks anymore. I'd always wondered if that chip could be made to play something modern instead. Never the priority, never the time — until now. Direct MPEG-2 playback uses the actual 2009 hardware; anything else gets relayed to a small Rust transcode server that converts it to MPEG-2 on the fly, so this 16-year-old chip ends up playing content it could never have understood natively.
The same 2009 Acer Aspire One (Intel Atom N270, GMA950) that anchors mtune-mirror's companion kernel work has real, working video decode hardware that almost nothing modern can use — VA-API dropped first-class XvMC support years ago, and the chip itself never accelerated anything but MPEG-2. This driver implements a real VA-API entry point on top of that legacy XvMC path: direct MPEG-2 decode uses the actual hardware motion-compensation block, and everything else — H.264, which this chip cannot touch at all — gets forwarded over the network to a small relay server that transcodes it to MPEG-2 on the fly.
Every claim below is measured on the real machine, not simulated: the 720×576 resolution ceiling was found by bisection (and now fails gracefully instead of crashing the caller), the XvMC-vs-software performance gap is timed with isolated decode+render benchmarks, and the H.264-relay pipeline has been run end-to-end — real network round trip, real hardware decode, real display — with a passing test on the actual screen.
Also ships a Proxmox VE installer for the relay server, with optional Nvidia GPU passthrough for hardware-accelerated decode on the server side — validated end-to-end on real Proxmox + Nvidia hardware, fresh install and in-place update both covered.
The Aspire One on the left is the real target: Intel Atom N270 @ 1.6GHz (Bonnell — MMX/SSE/ SSE2/SSE3/SSSE3, no SSE4.1+, single physical core), real Intel GMA950 XvMC hardware. Every number on this page was measured on this exact machine, over SSH, with a real X server and a real screen (1024×600) — not emulated, not cross-compiled and assumed.
Its successor, the Aspire 725 pictured alongside it, has no GMA950/XvMC hardware at all and isn't involved in this project — it's here purely because it's the same photo used for mtune-mirror, which targets both machines for a different reason (compiler tuning, not video decode).
Two components: a C VA-API driver that owns the real XvMC hardware path, and a Rust relay server that exists purely to hand it MPEG-2 it can actually decode.
%%{init: {'theme': 'base', 'themeVariables': {'fontSize': '13px', 'fontFamily': 'Inter, ui-sans-serif, sans-serif', 'lineColor': '#a5b4fc', 'primaryTextColor': '#e2e4ea', 'primaryColor': '#1e2330', 'primaryBorderColor': '#6366f1', 'secondaryColor': '#252b3b', 'tertiaryColor': '#181c26', 'background': '#0d0f14', 'clusterBkg': '#181c26', 'clusterBorder': '#374151', 'titleColor': '#e2e4ea'}}}%%
flowchart LR
A(["Real app\nffmpeg / mpv\nVA-API client"])
subgraph netbook["Acer Aspire One — real GMA950 hardware"]
B["vaapi-xvmc-driver (C)\nMPEG-2 VLD entropy decode\nH.264 relay-client forwarding"]
C(["Real i915/945 XvMC\nhardware MoComp"])
B -->|XvMCRenderSurface| C
C -->|vaPutSurface\nzero-copy blit| B
end
D["relay-server (Rust)\nwraps ffmpeg"]
E(["ffmpeg\ndecode + downscale\n+ mpeg2video encode"])
A -->|VA-API calls| B
B -->|MPEG-2 path: direct| C
B -->|H.264 path: raw bitstream\nover TCP, push mode| D
D --> E
E -->|MPEG-2 elementary stream| B
subgraph deploy["Optional: Proxmox LXC deploy"]
D
F(["Nvidia NVDEC\ndecode-only hwaccel"])
D -.->|RELAY_HWACCEL=nvdec| F
end
XvMCCreateContext doesn't fail gracefully — it raises a fatal X protocol error that kills the whole calling process. Found by bisection (720×576 works, 720×608 and 736×480 both don't), then gated in the driver itself so callers get an ordinary VA_STATUS_ERROR_RESOLUTION_NOT_SUPPORTED instead.
mpeg2video's encode cost scales with output pixel count. The relay path isn't just the only way to reach this hardware above its ceiling — it's actively cheaper for doing so.
vaPutSurface — a direct hardware-scaled blit, no pixels ever touch system memory. Generic tools like ffmpeg's own CLI display output always go through vaGetImage instead, which reads back the composited root window and is capped by the real screen's physical resolution — a real constraint this project hit and documented, not a bug in the zero-copy path itself.
simple_idct — verified bit-exact against a real ffmpeg reference decode, zero differences across every sample.
_NET_WM_STATE_FULLSCREEN ClientMessage the first time it's seen — the caller never finds out, it just thinks it owns a small window. Verified visually via real xwd captures on the actual hardware.
size * num_elements in 32-bit before allocating, while other call sites recomputed the same product in 64-bit — a large caller-supplied count could wrap the allocation to something tiny while later code still copied the full 64-bit-computed length. Also added bounds-checking on a caller-supplied MPEG-2 slice offset/size that was previously trusted unchecked.
Every number below is a real measurement on real hardware — see docs/benchmark-results.md for full methodology and reproduction steps.
XvMC hybrid decode+render, isolated (no image transfer)
Five consecutive runs of the same real broadcast-derived MPEG-2 file (640×480, 23.976fps,
239 pictures) on the real Atom N270 — this driver's own software entropy-decode/IDCT plus
the real hardware XvMCRenderSurface call, zero
vaGetImage/readback cost:
| Run | 1 | 2 | 3 | 4 | 5 |
|---|---|---|---|---|---|
| avg/picture | 9.38ms | 9.35ms | 9.31ms | 9.33ms | 9.32ms |
<1% variance, ~9.34ms/picture average — ~4.5× the headroom this content's real-time rate needs. Compared against ffmpeg's own single-threaded software MPEG-2 decode on the identical file (1.576s wall / 240 frames, ~198fps steady-state per ffmpeg's own reporting, ~5.05ms/frame): the XvMC hybrid path runs ~85% slower per picture than plain software decode — consistent with this project's broader finding that, across the entire usable resolution range (up to the 720×576 ceiling), the hybrid path was consistently 40–70% slower than software, with no narrowing of that gap as resolution grew. The real hardware motion-compensation block still does work, but this driver's own unavoidable software entropy-decode dominates the cost on this hardware generation — likely-cause: per-slice/macroblock VA-API buffer create/destroy overhead (many small X round-trips) outweighs whatever the hardware motion-comp saves at these frame sizes.
SIMD + multi-threaded NV12 conversion: real, measured wins
The readback path's RGB→NV12 conversion (needed by vaGetImage
callers, not the zero-copy display path) got two real speedups on the actual netbook: SSE2/SSSE3
SIMD, then splitting the conversion across the machine's 2 logical CPUs (Atom N270 is
single-physical-core with hyperthreading). Measured at 1024×576 — the tightest-margin scenario
tested — the conversion step dropped from ~7.7–9.3ms to ~6.0–6.2ms
(~20–25%, consistent with HT's real but limited benefit), pushing that scenario's real end-to-end
ffmpeg speed from ~1.25× to ~1.32× real-time. The native
640×480 case improved too (~3.2–4.5ms → ~2.5–3.5ms), with no regression from thread overhead —
verified bit-exact against a forced-single-threaded reference build (zero byte differences).
Memory stability over extended, looped playback
ffmpeg -stream_loop -1 (local MPEG-2, no relay) for
~5.5 minutes on the real netbook, sampling RSS via ps:
flat at 36588→36648KB from t=4s through t=321s — 30+
full loop iterations of a 10s clip, the 60KB delta being one-time startup buffer allocation, not
drift. No leak found over extended looped operation.
relay-server: downscaling costs nothing extra — it's faster
| Config | Output size | Wall time (avg of 3) | Steady-state |
|---|---|---|---|
| No real downscale | 1920×1080 |
11.4s | ~69fps |
| Downscale to driver's target | 640×480 |
7.35s | ~97fps |
Same 750-frame 1080p source, same box (a real Proxmox LXC relay-server deployment), same encoder settings — only the output size differs. Decode cost is identical either way; encode cost scales with output pixel count, so the downscaled path is the faster one.