#12841·FreeRDP

Experimental AVC420/VAAPI/XShm path for xfreerdp to reduce CPU usage during hardware-decoded sessions

Author: datiscumCreated Jun 3, 2026Updated Jul 1, 2026
Labelsfeaturex11client

Experimental AVC420/VAAPI/XShm path for xfreerdp to reduce CPU usage during hardware-decoded sessions

Is your feature request related to a problem? Please describe.

Yes.

When using xfreerdp with AVC420 and hardware decoding, the system can still be heavily loaded on the client side, even though hardware decoding is active.

In my test setup, xfreerdp was used at a resolution of 3440x1600 pixels on an Intel N100 system.

Command line arguments used for testing:

bash
/f +clipboard +grab-keyboard +async-channels +compression /compression-level:5 /multitransport /bpp:32 /gfx:avc420 /gdi:hw /sound:sys:pulse,format:0x704F,rate:48000

CPU usage comparison based on pidstat:

Before optimization: 137.69%
After optimization:   34.02%

The optimization reduced CPU usage from 137.69% to 34.02%. This corresponds to a reduction of approximately 75%, or roughly 4× lower CPU usage.

In practice, this frees up around three quarters of the CPU capacity that was previously required for the same workload.

Describe the solution you'd like

I would like to propose an optimized AVC420/VAAPI/X11 output path for xfreerdp.

The current optimized path is:

H.264/AVC420 -> VAAPI Decode -> VAAPI VPP -> USER_PTR RGB32 -> GDI/XShm -> XShmPutImage

xfreerdp must be built with the new CMake option:

bash
-DWITH_XSHM=ON

Recommended graphics options:

bash
/gfx:avc420 /gdi:hw /bpp:32

These options are relevant for the optimized AVC420/VAAPI/X11 path.

The optimized path can be detected with log messages like:

[xf_shm_create_surface_image]: XShm runtime: enabled version=1.2 sharedPixmaps=yes

This confirms that MIT-SHM/XShm is active for X11 output.

[xf_vaapi_vpp_prepare]: VAAPI VPP v41: ready source NV12/VAAPI -> USER_PTR RGB32

This confirms that the optimized VAAPI-VPP path is active.

If messages such as the following appear instead, the optimized path is not active:

not suitable for custom path
falling back
USER_PTR RGB32 target not available

The following features must not be enabled for this optimized path:

SmartSizing
MultiTouchGestures
RemoteApp

If SmartSizing or MultiTouchGestures are active, the client deliberately falls back to the normal FreeRDP path. Display output still works, but not through the optimized VAAPI/XShm path.

Describe alternatives you've considered

A direct vaPutSurface / EGL / DMABUF render path into the window would theoretically be another possible optimization.

However, this only partially fits the current FreeRDP architecture, because FreeRDP normally controls the image flow through:

SurfaceCommand
Dirty regions
OutputUpdate
SmartSizing
RemoteApp
X11 window logic

The proposed approach deliberately remains within the existing FreeRDP output model:

VAAPI Decode -> VAAPI VPP -> USER_PTR RGB32 -> GDI/XShm -> XShmPutImage

This is not fully GPU-direct, but it avoids the expensive old hwdownload/color-conversion path and remains more compatible with the FreeRDP graphics pipeline.

Additional context

Test system:

lscpu
Architecture:                x86_64
  CPU op-mode(s):            32-bit, 64-bit
  Address sizes:             39 bits physical, 48 bits virtual
  Byte Order:                Little Endian
CPU(s):                      4
  On-line CPU(s) list:       0-3
Vendor ID:                   GenuineIntel
  Model name:                Intel(R) N100

The resolution used during the test was 3440x1600 pixels.

Note: The following pidstat output was generated on a system using a German locale. Durchschn. means average.

Optimized version:

pidstat -u -t -p 5334 1 30
Durchschn.:   999      5334         -   28,76    5,26    0,00    0,00   34,02     -  xfreerdp_111
Durchschn.:   999         -      5334    0,00    0,00    0,00    0,00    0,00     -  |__xfreerdp_111
Durchschn.:   999         -      5335    0,00    0,00    0,00    0,00    0,00     -  |__xfreerdp_111
Durchschn.:   999         -      5336    4,60    3,27    0,00    0,23    7,86     -  |__xfreerdp_111
Durchschn.:   999         -      5337    1,30    0,23    0,00    0,07    1,53     -  |__xfreerdp_111
Durchschn.:   999         -      5338    0,00    0,00    0,00    0,00    0,00     -  |__xfreerdp_111
Durchschn.:   999         -      5342    0,00    0,00    0,00    0,00    0,00     -  |__xfreerdp_111
Durchschn.:   999         -      5343    0,27    0,60    0,00    0,13    0,87     -  |__threaded-ml
Durchschn.:   999         -      5344    0,00    0,00    0,00    0,00    0,00     -  |__xfreerdp_111
Durchschn.:   999         -      5345    0,00    0,00    0,00    0,00    0,00     -  |__xfreerdp_111
Durchschn.:   999         -      5346   22,36    1,43    0,00    0,13   23,79     -  |__xfreerdp_111

Original version:

pidstat -u -t -p 6463 1 30
Durchschn.:   999      6463         -  101,63   36,05    0,00    0,00  137,69     -  xfreerdp
Durchschn.:   999         -      6463    0,00    0,00    0,00    0,00    0,00     -  |__xfreerdp
Durchschn.:   999         -      6464    0,00    0,00    0,00    0,00    0,00     -  |__xfreerdp
Durchschn.:   999         -      6465    3,10    1,73    0,00    1,77    4,83     -  |__xfreerdp
Durchschn.:   999         -      6466    0,93    0,13    0,00    0,37    1,07     -  |__xfreerdp
Durchschn.:   999         -      6467    0,00    0,00    0,00    0,00    0,00     -  |__xfreerdp
Durchschn.:   999         -      6471    0,00    0,00    0,00    0,00    0,00     -  |__xfreerdp
Durchschn.:   999         -      6472    0,20    0,33    0,00    0,70    0,53     -  |__threaded-ml
Durchschn.:   999         -      6473    0,00    0,00    0,00    0,00    0,00     -  |__xfreerdp
Durchschn.:   999         -      6474    0,00    0,00    0,00    0,00    0,00     -  |__xfreerdp
Durchschn.:   999         -      6475   19,49   21,39    0,00    6,13   40,89     -  |__xfreerdp
Durchschn.:   999         -      6476   19,29    3,20    0,00    5,26   22,49     -  |__xfreerdp
Durchschn.:   999         -      6477   19,46    3,20    0,00    5,16   22,66     -  |__xfreerdp
Durchschn.:   999         -      6478   19,23    3,30    0,00    5,26   22,53     -  |__xfreerdp
Durchschn.:   999         -      6479   19,46    3,23    0,00    5,06   22,69     -  |__xfreerdp

Audio sync was also optimized to prevent gradual drift between audio and video.

Synchronization adjustments are now applied dynamically during quiet periods or pauses in the audio stream, helping maintain stable A/V sync without introducing unnecessary artifacts.

Recommended audio option:

bash
/sound:sys:pulse,format:0x704F,rate:48000

0x704F is OPUS.

For this, xrdp had to be patched, because the OPUS codec otherwise works incorrectly there.

Alternative audio option:

bash
/sound:sys:pulse,format:0xA106

0xA106 is AAC_MS.

This works here without modifications.

I started adding VA-API hardware encoding to xrdp/xorg-xrdp about one year ago. After that was working reasonably well, I realized how heavily xfreerdp loads the system despite hardware decoding.

Over the last few months, I have invested some time from time to time, and it now works continuously without problems during daily work for 14 hours.

Debian 13 static test build:

https://datiscum.de/MyPubDownload/xfreerdp_111

If all dependencies are installed, it can be tested directly.