De RTMP-stream van de PS5 kapen

Voor third-party Bluetooth-apparaten geldt hetzelfde. Sony vergrendelt de draadloze stack tot hun eigen randapparatuur, waardoor koptelefoons of controllers van andere merken simpelweg niet kunnen koppelen.

Het Probleem

Ik stream vaak games met vrienden op Discord die kijken hoe ik speel, maar de PS5 ondersteunt geen schermdeling naar Discord. De voor de hand liggende oplossing is een capture card: de HDMI-uitgang aansluiten op een capture card, deze koppelen aan OBS op je Mac en vanaf daar streamen. Maar fatsoenlijke kaarten zijn niet goedkoop, en ik wilde niet meer dan 100 dollar uitgeven aan enkel dit doel.

Remote Play

Remote Play werkte voor mij gedeeltelijk. Ik kon de PS5 verbinden met mijn MacBook, het scherm van de Mac delen met Discord en vanaf daar spelen.

Het probleem is dat je alles moet verbinden met het Remote Play-apparaat: controller, koptelefoon, etc. Daarnaast liep ik af en toe tegen inputlag aan, en de streamkwaliteit wordt volledig beheerd door de PS5. Je kunt eigenlijk niets configureren. Ik wilde mijn fysieke setup niet elke keer hoeven aanpassen wanneer ik wilde streamen.

Hoe PS5-streaming werkt

De PS5 ondersteunt standaard streaming naar YouTube en Twitch als je bent ingelogd op die accounts. Het gebruikte protocol hiervoor is RTMP, of Real-Time Messaging Protocol, wat algemeen wordt gebruikt voor live audio- en videostreaming.

Wanneer je een uitzending start, doet de PS5 ongeveer het volgende:

  1. De console zoekt via DNS op waar de streamingdienst zich bevindt.
  2. De console maakt verbinding met die server.
  3. De videodata wordt via RTMP naar die server verzonden.

Wat als we ons eigen apparaat als Twitch konden laten fungeren en die RTMP-stream in plaats daarvan konden ontvangen?

Dat is het idee. De PS5 heeft het IP-adres van Twitch niet hardgecodeerd, maar zoekt dit elke keer op via DNS. Als we controleren wat de DNS teruggeeft, controleren we waar de stream naartoe gaat.

De juiste hostname vinden

De eerste logische poging was om ingest.twitch.tv direct te spoofen. Dat is de hostname die de PS5 resolveert wanneer je op broadcast drukt, dus het richten hiervan op de Mac zou moeten werken, toch?

Niet helemaal. ingest.twitch.tv:443 is in feite een discovery-endpoint, niet de RTMP-server zelf. De PS5 maakt een HTTPS-call naar dit endpoint met de vraag: "Welke regionale ingest-server moet ik gebruiken?". Twitch antwoordt dan met iets als ap-southeast-1.prod.fi.contribute.live-video.net. Vervolgens pusht de PS5 de eigenlijke stream daarheen.

Het spoofen van die hostname leidde tot een ander probleem: de eigenlijke Twitch-ingest maakt gebruik van RTMPS (RTMP over TLS op poort 443), en de PS5 valideert het certificaat tegen vertrouwde CA's (Certificate Authorities). Een zelfondertekend certificaat werkt niet, en er is geen manier om custom CA's te installeren op een PS5.

Ik probeerde vervolgens YouTube als workaround. De RTMP-ingest daarvan gebruikt gewoon RTMP op poort 1935, dus er was geen TLS-certificaat om mee te dealen. De PS5 stuurde de stream gelukkig naar mijn Mac, dus ik wist dat de basisaanpak werkte.

Het probleem was dat de PS5 periodiek de API van YouTube controleert om er zeker van te zijn dat de stream daadwerkelijk live is. Omdat YouTube de stream nooit ontving, mislukte die controle en stopte de uitzending na ongeveer 60 seconden.

Dit betekende dat ik een Twitch-endpoint moest vinden dat gebruikmaakte van gewone RTMP. Het antwoord kwam voort uit het bekijken van DNS-logs tijdens het streamen:

sudo tail -f /tmp/dnsmasq.log
# Sep 22 23:20:28 dnsmasq: query[A] ingest.global-contribute.live-video.net from 192.168.8.171
# Sep 22 23:20:28 dnsmasq: reply aps30.contribute.live-video.net is 35.55.13.0

De PS5 resolveerde ingest.global-contribute.live-video.net, wat uiteindelijk leidde naar aps30.contribute.live-video.net. Dat is de echte RTMP-server. Door contribute.live-video.net te spoofen, worden alle subdomeinen gedekt en wordt de eigenlijke stream naar de Mac omgeleid zonder certificaatproblemen.

De DNS-truc

De opstelling bestaat uit twee hoofdonderdelen: dnsmasq en nginx-rtmp. Ik heb een kleine macOS menu bar-app gebouwd die beide bundelt en beheert.

Ik draai dnsmasq op mijn Mac en configureer deze om de ingest-domeinen van Twitch te resolven naar het LAN-adres van mijn Mac:

server=1.1.1.1
server=8.8.8.8

# Leid Twitch ingest-verkeer om naar de Mac
address=/contribute.live-video.net/192.168.8.175
address=/ingest.global-contribute.live-video.net/192.168.8.175
address=/live.twitch.tv/192.168.8.175
address=/live-sin.twitch.tv/192.168.8.175
address=/live-nrt.twitch.tv/192.168.8.175
address=/live-syd.twitch.tv/192.168.8.175
address=/live-fra.twitch.tv/192.168.8.175
address=/live-ams.twitch.tv/192.168.8.175
address=/live-lhr.twitch.tv/192.168.8.175
address=/live-jfk.twitch.tv/192.168.8.175
address=/live-lax.twitch.tv/192.168.8.175
address=/live-sea.twitch.tv/192.168.8.175

log-queries
log-facility=/tmp/dnsmasq.log
no-hosts
listen-address=0.0.0.0

192.168.8.175 is het IP-adres van mijn Mac. Wanneer de PS5 via DNS vraagt naar een van deze Twitch-endpoints, geeft dnsmasq het IP-adres van mijn Mac terug. De PS5 maakt verbinding met mijn Mac in de veronderstelling dat dit Twitch is.

De laatste stap is om de PS5 naar deze DNS-server te laten wijzen. Ik heb een GL.iNet-router met OpenWRT, dus ik heb deze geconfigureerd om het IP-adres van mijn Mac als DNS-server door te geven, specifiek voor de DHCP-lease van de PS5.

SSH in de router en voer uit:

uci add_list dhcp.lan.dhcp_option="tag:PS5,6,192.168.8.175"
uci commit dhcp
/etc/init.d/dnsmasq restart

Het gedeelte tag:PS5 werkt omdat de statische lease van de PS5 al die tag heeft in /etc/config/dhcp. Optie 6 is de DHCP-optie voor de DNS-server. De PS5 pikt dit op bij de volgende DHCP-vernieuwing; er is geen handmatige DNS-configuratie op de console vereist.

De stream ontvangen

Hiervoor gebruik ik nginx-rtmp:

worker_processes 1;
error_log /tmp/nginx-error.log warn;
pid /tmp/nginx.pid;

events {
    worker_connections 512;
}

rtmp {
    server {
        listen 1935;
        chunk_size 4096;
        application ps5 {
            live on;
            record off;
            sync 10ms;
            # Meld onze app wanneer een stream start
            on_publish http://127.0.0.1:9988/on_publish;
        }
    }
}

http {
    server {
        listen 8080;
        location /stat {
            rtmp_stat all;
        }
    }
}

De on_publish callback is de manier waarop de menu bar-app detecteert wanneer de PS5 begint met uitzenden. Nginx vuurt een POST-request naar localhost:9988 met de streamnaam, waarna de app de volledige RTMP-URL tonen die direct gekopieerd kan worden.

Op dit punt pusht de PS5 zijn stream (1080p60, H.264, AAC stereo) rechtstreeks naar mijn Mac in plaats van naar Twitch. Vanaf hier kan ik de stream in alles trekken: OBS om het opnieuw te streamen, lokaal opnemen, of het simpelweg direct afspelen.

De stream bekijken

In plaats van via OBS te gaan, heb ik mpv gebruikt om de stream op te halen en het venster te delen met Discord. Het low-latency profiel houdt de vertraging onder de seconde:

mpv --profile=low-latency --audio-buffer=0.3 rtmp://127.0.0.1/ps5/stream-key

Verrassend genoeg is dit behoorlijk betrouwbaar gebleken. Ik gebruik het nu een paar weken en heb geen problemen ervaren.