Morten Linderud

F/OSS Developer, Arch Linux Developer and security team.

github mastodon twitter email
Streaming the Steam Deck to OBS
Mar 26, 2022
6 minutes read

Valve was kind enough to send Steam Deck devkits to Arch Linux maintainers and developers which gave us an opportunity to mess around with the device.

Personally I find it a bit fun to mess around with video streaming, thus one of the first things I wanted to try figure out was how I could stream the gamemode on the Steam Deck. Installing the OBS flatpak and adding it to the menu doesn’t actually work so we sadly have to be a bit more clever.

Essentially the goal is to run an RTMP server locally which OBS can read from using the “Media Source” layer, and figure out a way to have the Steam Deck to stream towards that.

Lets setup the RTMP stream first and check that it works.

Local Setup

We are going to use a quick docker container for this so we don’t have to deal with nginx directly.

λ ~ » podman run -d -p 1935:1935 --name rtmp-stream alfg/nginx-rtmp

This is going to give us a rtmp-stream container which will be serving us RTMP streams on port 1935. It accepts arbitrary path names with the prefix stream so for the purpose of this post we are going to use /stream/deck as our RTMP stream.

To test the stream we can simply use ffmpeg with a special testsrc input. Shamelessly stolen from Stack Overflow.

λ ~ » ffmpeg -r 30 -f lavfi -i testsrc  -vf scale=1280:960 -vcodec libx264 \
     -profile:v baseline -pix_fmt yuv420p  -f flv rtmp://localhost/stream/deck

To validate that this works we can just open the RTMP stream in vlc or mpv and get some test output back.

λ ~ » mpv rtmp://localhost/stream/deck

Now we can also make an OBS source for this. We are using the “Media Source” unhooking “Local File”. Add the RTMP source as “Input” and you should see the same test image in OBS.

For the next stems you need to figure out the local IP of your machine. This can be done by looking at ip -br addr, and for the purpose of this post the IP is 192.168.1.4 but this is going to be different on your system.

Steam Deck Setup

This setup assumes you have sshd running on your Steam Deck. The easiest way to get this done is to get into the Desktop Mode (Power Button -> Desktop Mode). Open konsole, set a password on the deck user with passwd and start ssh with systemctl start sshd.

Then you can run ip -br addr and find the IP of the wlan0 device and login with ssh deck@<localip>

Keep in mind that everything we do beyond this point is not going to be persistent and we need to rerun several of these steps after updating the Steam Deck.

Ffmpeg?

Ffmpeg is the bread and butter of modern video processing. It’s used everywhere and does any kind of media conversion and processing you can think of.

The first thing I tried was to get ffmpeg to stream from the Deck. There is a gist with instruction on how to use kmsgrab. But it overcomplicates a few things by trying to use obs-kmsgrab instead of having an outgoing RTMP stream.

For using the RTMP stream one could do the following

(deck@steamdeck ~)$ sudo ffmpeg -f kmsgrab -i - -vaapi_device /dev/dri/renderD128 \
-vf hwmap=derive_device=vaapi,scale_vaapi=format=nv12 -c:v h264_vaapi -bf 1 \
-f flv rtmp://192.168.1.4/stream/deck

The issue is that gamescope seems to change the byte layout of the buffer whenever you change from the gamemode UI to any games. ffmpeg seems expects this to never change. The “solution” to this is to wrap the ffmpeg command in a while loop so you restart the stream whenever it crashes, but you are still going to be left with green artifacts in the menu and some staggering.

The entire setup is less than stellar.

I made an issue on gamescope, but I don’t see how it’s going to be solved on their end. However, someone comments that gstreamer might work better. I gave that a shot instead.

Gstreamer

gstreamer is essentially a framework for video and audio where you can make pipelines. It’s super powerful and offers a bit more flexibility then the command line of ffmpeg.

First off we are going to install packages on the Deck. The first step to do this is to disable the readonly attribute of the system.

(deck@steamdeck ~)$ sudo steamos-readonly disable
(deck@steamdeck ~)$ sudo pacman -S gstreamer-vaapi gst-plugin-pipewire

We are using the vaapi plugins as we want hardware accelerated encoding, then the pipewire plugin for fetching the video itself.

Note that any changes to /etc is persistent, but since / is ephemeral it will actually loose track of any files pacman has written to this directory. If you install the above packages after an update you’ll find several file conflicts. You can ignore these by adding --overwrite='*' to pacman.

You might also need to initialize the pacman keyring if you see keyring error.

(deck@steamdeck ~)$ sudo pacman-key --init
(deck@steamdeck ~)$ sudo pacman-key --populate

The gstreamer pipeline itself took me a few days to figure out. We fetch the video from pipewiresrc and convert it to a h264 stream. We mux this with the pulseaudio sound source of the speakers. We encode this with aac and throw it towards the FLV muxer and onwards to the RTMP sink.

(deck@steamdeck ~)$ gst-launch-1.0 -e \
    pipewiresrc do-timestamp=True \
        ! queue \
        ! videoconvert \
        ! queue \
        ! vaapih264enc \
        ! h264parse \
        ! mux. \
    pulsesrc device="alsa_output.pci-0000_04_00.5-platform-acp5x_mach.0.HiFi__hw_acp5x_1__sink.monitor" \
        ! queue \
        ! fdkaacenc bitrate=8000 \
        ! mux. \
    flvmux name=mux streamable=True \
        ! rtmpsink location='rtmp://192.168.1.4/stream/deck live=1'

This seems works on my end. There seems to be some performance impact when doing this, even with vaapi. But it is probably negligible unless you are playing performance heavy games like Cyberpunk or Elden Ring.

And for the demonstration:

Improvements

There are probably several ways to make this easier to use. Asking people to deal with the terminal and ssh isn’t very approachable for non-Linux users. Someone on reddit added the script as a library application which can probably be a handy way of starting and stopping the stream.

It would also be neat to see if it would be possible to offload the encoding of the stream to the remote device and do less on the Deck itself.

This is a little bit of progress to enable streaming on the steam deck. Hopefully someone finds this useful! Thanks again to Valve for sending Devkits and thanks to folks on IRC for entertaining my cluelessness around gstreamer :)


Back to posts