Near-Perfect Low-Latency 4K 5.1 Game Streaming

Have you ever wished you could use your main computer tower as a game console or Steam Machine, but your TV isn't close enough to your computer? Of course you have! You may have also seen the common solutions to this problem: Steam Remote Streaming, Sunshine/Moonlight, or even a super-long fiber HDMI and USB cable. These solutions on their own are _okay_ and maybe even the best option if you can't put a small/tiny system with a modern GPU near your TV. However, if you can use or are willing to use a thin game box for your TV, you can make your experience even better.

This is an overkill solution, but one that makes me happy! The simple TLDR is: Use Sunshine + Moonlight on AV1 capable GPUs. This post goes a few steps further for maximum fidelity.

Caveat: My main tower runs labwc without a lock screen to simplify things. You could get this to work in GNOME or KDE, but you'd have to add automation for unlocking the screen, and the display configuration can get more finicky.

With the power of AV1, EDIDs, Sunshine, Moonlight, and some Linux automation scripts, you too can have a relatively painless high-quality local remote game streaming setup!

Hardware Requirements

  • Powerful Linux Computer

    • GPU with hardware-accelerated AV1 encoding
    • Spare physical video port
    • Well-behaved Wayland compositor
    • Ideally, a usable ethernet port
  • Small Linux Computer

    • GPU with hardware-accelerated AV1 decoding
    • Ideally, a usable ethernet port

For example, my setup is a big Arch Linux box with an NVIDIA 4090 attached to 2 monitors, and a small Bazzite box with an Intel Arc A380 attached to an Onkyo receiver and TV.

Low-Latency Video

On my Bazzite box, if I want to stream a game from my main tower, all I do is start a Non-Steam Game I've added called moonlight_wrapper.sh, which is the first of many dominoes:

#!/usr/bin/bash

ssh Ashalanoray.local 'echo "on" > /sys/class/drm/card1-DP-3/status'
flatpak run com.moonlight_stream.Moonlight
ssh Ashalanoray.local 'echo "off" > /sys/class/drm/card1-DP-3/status'

Effectively, this is my Bazzite system telling my main system, "Enter game streaming mode now!". I'll walk through what happens next, after explaining what these values in the script are. Ashalanoray.local is my main tower, and DP-3 is the spare port on its GPU that we are pretending is connected to the TV.

Of course, the status file needs to be writable, ideally by your local user. By default, it is not. I solve this with a simple SystemD service:

Description=Allow anyone to write to DP-3 status file
[Service]
Type=oneshot
ExecStart=chmod go+w /sys/class/drm/card1-DP-3/status
[Install]
WantedBy=multi-user.target

The Wayland compositor notices that a new display has been "plugged in" and switches the monitor layout. Because I am running labwc + kanshi for monitor management, this is achieved with the following kanshi config:

profile {
    output DP-1 enable mode 1920x1200 transform 90 position 2560,0
    output DP-2 enable mode [email protected] position 0,200
}
profile {
    output DP-3 mode 3840x2160@60Hz position 0,0
    output DP-1 disable
    output DP-2 disable
}

The first profile is my dual monitor setup at my desk; the second profile turns off those monitors and enables the fake TV output.

Now, Ashalanoray.local needs to know what TV we have. In fact, it already knows exactly what TV DP-3 should be from this SystemD service named edid_override.service:

[Unit]
Description=Override DP-3 EDID to be 4K Onkyo
[Service]
Type=oneshot
ExecStart=cp /root/tv.edid /sys/kernel/debug/dri/1/DP-3/edid_override
[Install]
WantedBy=multi-user.target

What is /root/tv.edid? EDID is the format your display uses to tell computers what it is capable of: resolutions, refresh rates, audio channels, etc. You can find many TV EDIDs online, but I recommend capturing your own to be sure you get it exactly right. Especially if you have any devices, like a surround sound receiver, between your smaller Linux device and the TV. It's pretty easy these days to capture your own EDID. For example, to capture the EDID of one of my monitors: cp /sys/class/drm/card1-DP-2/edid ~/s/Documents/Pixio.edid. You can use edid-decode to read these files if you aren't sure which display is which.

And with that, the big Linux machine now thinks your living room setup is plugged into it! All that's left is the more boring and typical configurations.

For Moonlight on Bazzite to access your main tower, you need to have Sunshine automatically start on login (~/.config/labwc/autostart for example). You could have Sunshine start within the moonlight_wrapper.sh script so long as you know the correct WAYLAND_DISPLAY value ahead of time (and possibly more variables). The benefit of having Sunshine start on login is that it will be launched with all the correct environment variables it needs to run properly.

Side Notes

Status file discovery

Figuring out how to do the echo "on" > /sys/class/drm/card1-DP-3/status thing was fun! This functionality isn't really documented. I found it by snooping around the /sys/class/drm folder, getting curious about the status file being writable, and then finding the source code in the kernel for it here: Elixir.bootlin Linux 6.12 Source Code

Input latency + USB/IP

Input latency using just Sunshine and Moonlight used to be a lot more noticeable. It is still there, but over the last year or so it has gotten quite a bit better. In fact, it is now as good as my old overkill solution for this: USB/IP. I used to use usbip (along with my own helper functions) to stream the entire USB controller device over the network. This sounds like a lot, and it is, but it did bypass all the various controller translation layers. But after recent testing, the latency is the same! Which probably means the software input side of things is about as good as it is ever going to get!

USB/IP is still super useful, but that's a different blog (TODO). I'll at least link my repo after I publish it (TODO).

📅 2026-06-12