- Helix ist eine AI-Plattform, die Nutzern den Bildschirm autonomer Coding-Agenten in der Cloud zeigt; stabile Remote-Bildübertragung ist dabei entscheidend
- Als WebRTC-basiertes Streaming an UDP-Sperren und Firewall-Beschränkungen in Unternehmensnetzwerken scheiterte, baute das Team eine WebSocket-basierte H.264-Pipeline auf, doch in instabilen WLAN-Umgebungen kam es zu massiver Latenz
- Statt einer komplexen Encoding-/Decoding-Struktur stellte sich heraus, dass das einfache periodische Übertragen von JPEG-Screenshots per HTTP deutlich stabiler und effizienter war
- Dieser Ansatz verbraucht weniger Bandbreite, benötigt keine Wiederherstellung beschädigter Frames und passt Bildqualität und Framerate automatisch an die Netzwerkqualität an
- Helix entschied sich schließlich für eine hybride Architektur: bei guten Verbindungen H.264, bei schlechten Verbindungen JPEG-Polling – ein simples, aber praxisnahes Remote-Streaming-System
Die Streaming-Probleme und Einschränkungen von Helix
- Helix ist eine Plattform, die den Bildschirm eines AI-Coding-Agenten, der in einer Cloud-Sandbox läuft, in Echtzeit teilen muss
- Nutzer verfolgen dabei wie bei einem Remote-Desktop, wie die AI Code schreibt
- Anfangs wurde WebRTC verwendet, doch Verbindungen scheiterten an der UDP-Blockierung in Unternehmensnetzwerken
- TURN-Server, STUN/ICE und benutzerdefinierte Ports wurden allesamt von Firewall-Richtlinien blockiert
- Deshalb wurde eine WebSocket-basierte H.264-Streaming-Pipeline, die ausschließlich HTTPS (Port 443) nutzt, direkt selbst implementiert
- Hardware-Encoding mit GStreamer + VA-API, Browser-Decoding mit WebCodecs
- Erreicht wurden 60fps, 40Mbps und weniger als 100ms Latenz
Netzwerklatenz und Leistungsabfall
- In instabilen Netzwerkumgebungen wie Cafés kam es dazu, dass das Bild stehen blieb oder um mehrere Dutzend Sekunden verzögert war
- Das TCP-basierte WebSocket führt bei Paketverlust dazu, dass sich Frames der Reihe nach verzögern und dadurch die Echtzeitfähigkeit zusammenbricht
- Auch eine niedrigere Bitrate löste das Latenzproblem nicht, sondern verschlechterte nur die Bildqualität
- Es wurde auch versucht, nur Keyframes zu senden, doch das scheiterte, weil das Moonlight-Protokoll P-Frames voraussetzt
Entdeckung des JPEG-Screenshot-Ansatzes
- Beim Debugging wurde der Endpunkt
/screenshot?format=jpeg&quality=70aufgerufen, woraufhin sofort ein scharfes Bild geladen wurde- Ein einzelnes JPEG mit 150KB wurde ohne Verzögerung angezeigt
- Durch einfaches wiederholtes Senden von HTTP-Anfragen zur Aktualisierung des Screenshots war eine flüssige Bildschirmaktualisierung auf dem Niveau von 5fps möglich
- Am Ende wurde die komplexe Video-Pipeline durch einen periodischen JPEG-Request-Ansatz (fetch loop) ersetzt
Vorteile des JPEG-Ansatzes
- Wichtige Vergleichspunkte gegenüber H.264
- Bandbreite: H.264 konstant 40Mbps, JPEG variabel zwischen 100~500Kbps
- Zustandsverwaltung: H.264 ist zustandsabhängig, JPEG sind vollständig unabhängige Frames
- Wiederherstellbarkeit: H.264 muss auf Keyframes warten, JPEG erholt sich sofort mit dem nächsten Frame
- Komplexität: H.264 erforderte monatelange Entwicklung, JPEG ließ sich mit ein paar Zeilen
fetch()-Loop implementieren
- Je schlechter die Netzwerkqualität, desto stabiler und effizienter erwies sich der einfache JPEG-Ansatz
Hybride Umschaltarchitektur
- Helix schaltet die beiden Verfahren automatisch anhand der RTT (Round-Trip Time) um
- RTT < 150ms → H.264-Streaming
- RTT > 150ms → JPEG-Polling
- Bei wiederhergestellter Verbindung kann der Nutzer per Klick zurückwechseln
- Eingabeereignisse (Tastatur/Maus) werden weiterhin per WebSocket übertragen, sodass die Interaktivität erhalten bleibt
- Der Server stoppt die Videoübertragung mit der Nachricht
{"set_video_enabled": false}und wechselt in den Screenshot-Modus
Problem mit instabilem Umschalten (Oscillation) und Lösung
- Wenn nach dem Stoppen der Übertragung der WebSocket-Traffic sinkt, fällt auch die Latenz – dadurch entstand eine Endlosschleife, in der automatisch wieder in den Videomodus gewechselt wurde
- Lösung: Nach dem Wechsel in den Screenshot-Modus bleibt dieser fix bestehen, bis der Nutzer klickt
- In der UI wird die Meldung „Video pausiert, um Bandbreite zu sparen“ angezeigt
Probleme mit JPEG-Unterstützung und Build-Prozess
- Beim Wayland-Screenshot-Tool grim ist in den Standardpaketen von Ubuntu die JPEG-Unterstützung deaktiviert
- Beim Ausführen von
grim -t jpegerscheint der Fehler „jpeg support disabled“
- Beim Ausführen von
- Zur Behebung wurde grim im Dockerfile direkt aus dem Quellcode gebaut, inklusive libjpeg-turbo8-dev
Endgültige Architektur
- Gute Verbindung: 60fps H.264, hardwarebeschleunigt
- Schlechte Verbindung: 2~10fps JPEG-Polling, vollständig zuverlässig
- Die Screenshot-Qualität wird abhängig von der Übertragungszeit automatisch angepasst
- Über 500ms: Qualität -10%, unter 300ms: +5%, mindestens 2fps
Zentrale Erkenntnisse
- Eine einfache Lösung ist besser als ein komplexes System — 2 Stunden JPEG-Hacking waren praxisnäher als 3 Monate H.264-Entwicklung
- Graceful Degradation ist entscheidend für die User Experience
- WebSocket eignet sich optimal für die Übertragung von Eingaben, ist für Videoübertragung aber nicht zwingend nötig
- Ubuntu-Pakete können Funktionen weglassen — bei Bedarf selbst bauen
- Vor der Optimierung erst messen — komplexes Streaming ist nicht zwangsläufig die einzige Lösung
Open-Source-Veröffentlichung
- Helix wird als Open Source bereitgestellt; die Kernimplementierung umfasst unter anderem
api/cmd/screenshot-server/main.go— Screenshot-ServerMoonlightStreamViewer.tsx— adaptive Client-Logikwebsocket-stream.ts— Steuerung der Video-Umschaltung
- Helix wird mit dem Ziel entwickelt, AI-Infrastruktur zu schaffen, die auch in realen Umgebungen funktioniert
1 Kommentare
Kommentare auf Hacker News
Dass JPEG bei schlechtem Netzwerk besser zurechtkommt, liegt nicht an UDP, sondern an der TCP-Implementierung
JPEG löst weder Buffering- noch Congestion-Control-Probleme. Wahrscheinlich wurde es einfach so implementiert, dass möglichst wenige Frames übertragen werden
h.264 hat eine höhere Kodierungseffizienz als JPEG. Bei gleicher Größe kann ein h.264-IDR-Frame eine bessere Qualität liefern
Das eigentliche Problem ist die fehlende Bandbreitenschätzung. Auch in einer TCP-Umgebung kann man die Bitrate über anfängliche Bandbreiten-Probes und die Erkennung von Übertragungsverzögerungen anpassen
Wenn möglich, ist WebRTC die bessere Wahl, und für Firewall-Umgehung eignet sich WebSocket
Selbst wenn man die Formatprobleme des Textes oder den LLM-Stil beiseitelässt, ist inhaltlich vieles daran falsch
10 Mbps sollten für einen statischen Bildschirm ausreichen. Das Problem sind falsche Encoding-Einstellungen oder eine geringe Encoder-Qualität
Der Ansatz „nur Keyframes senden“ ist ineffizient; stattdessen sollte man ein kurzes Keyframe-Intervall setzen
Letztlich liegt das Problem an einer Architektur, die den gesamten Stream über eine einzelne TCP-Verbindung presst. Dafür gibt es bereits Lösungen wie DASH
Es wäre sinnvoll, sich anzusehen, wie VNC das schon seit 1998 macht
Das Client-Pull-Modell bleibt erhalten, während der Framebuffer in Kacheln aufgeteilt und nur geänderte Bereiche übertragen werden
Bei einem statischen Coding-Bildschirm kann das die Bandbreite stark senken. Mit Scroll-Erkennung wäre es noch effizienter
Ich habe früher mit Video-Encoding gearbeitet, und 40 Mbps sind Blu-ray-Niveau
Für einfaches Text-Streaming ist das völlig überzogen. Nach einem Gespräch mit Claude kam ich zu dem Schluss, dass 30 FPS, ein GOP von 2 Sekunden und durchschnittlich etwa 1 Mbps ausreichen sollten
Selbst im schlimmsten Fall sollten 1,2 Mbps für stabil brauchbare Qualität genügen
Das Kernproblem dieses Textes ist, dass die Mindestbandbreite für h.264 viel zu hoch angesetzt wurde
H.264 ist deutlich effizienter als JPEG. Man hätte bei 1 Mbps anfangen und dann nachregeln sollen
Nur Keyframes zu verwenden, ist im Gegenteil ineffizient
Ich wäre das komplett anders angegangen
10 Mbps sind übertrieben, und Coding-Videos auf YouTube liegen selbst in 1080p bei etwa 0,6 Mbps. Das ist ausreichend scharf
Ich würde eher auf 1 fps heruntergehen oder das Keyframe-Intervall anpassen
Echtzeit-Video in den Browser zu streamen, ist wirklich schmerzhaft
Wenn JPEG-Screenshots gut funktionieren, sollte man es einfach dabei belassen
Bei Stacks wie gstreamer oder Moonlight ist das Debugging die Hölle, wenn man Backpressure und Fehlerweitergabe nicht versteht
Eine Kombination aus NVIDIA Video Codec SDK + WebSocket + MediaSource Extensions ist eine realistische Alternative
Wenn der Text allerdings ein LLM-Erzeugnis ist, fehlt dem Autor vermutlich ohnehin die Bereitschaft, solche Interna zu verstehen
Ich habe früher ein Programm benutzt, das alle 5 Sekunden einen Screenshot machte, und die Festplatte war schnell voll
Als mir klar wurde, dass die meisten Bilder identisch waren, dachte ich über einen Algorithmus nach, der nur Änderungen speichert,
und merkte dann, dass ich im Grunde Videokompression neu erfand
Eine einzige ffmpeg-Zeile hat das gelöst und 98 % Speicherplatz gespart
Ein Video davon zu streamen, wie ein LLM tippt, und dafür 40 Mbps zu verwenden, ist absurd übertrieben
Der einzige Weg, auf HN gute Antworten zu bekommen, ist, etwas Falsches zu posten
Ein Text, der falsch, aber interessant ist, trifft meiner Meinung nach genau die perfekte Balance, um eine Diskussion auszulösen