1 Punkte von GN⁺ 2025-12-24 | 1 Kommentare | Auf WhatsApp teilen
  • 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=70 aufgerufen, 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
    1. RTT < 150ms → H.264-Streaming
    2. RTT > 150ms → JPEG-Polling
    3. 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 jpeg erscheint der Fehler „jpeg support disabled“
  • 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

  1. Eine einfache Lösung ist besser als ein komplexes System — 2 Stunden JPEG-Hacking waren praxisnäher als 3 Monate H.264-Entwicklung
  2. Graceful Degradation ist entscheidend für die User Experience
  3. WebSocket eignet sich optimal für die Übertragung von Eingaben, ist für Videoübertragung aber nicht zwingend nötig
  4. Ubuntu-Pakete können Funktionen weglassen — bei Bedarf selbst bauen
  5. 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-Server
    • MoonlightStreamViewer.tsx — adaptive Client-Logik
    • websocket-stream.ts — Steuerung der Video-Umschaltung
  • Helix wird mit dem Ziel entwickelt, AI-Infrastruktur zu schaffen, die auch in realen Umgebungen funktioniert

1 Kommentare

 
GN⁺ 2025-12-24
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

    • Im im Artikel gezeigten Polling-Code wurde die nächste Anfrage erst gesendet, nachdem der vorherige JPEG-Download abgeschlossen war. So eine Schleife ist auch ohne UDP möglich
    • Vermutlich war die Struktur so, dass Frames vollständig serialisiert und nur einzeln angefordert wurden, oder dass jedes Mal eine neue Verbindung mit einer frischen GET-Anfrage geöffnet wurde
    • Interessant ist, dass sich visuell identische JPEGs trotzdem auf 10–15 % der Dateigröße reduzieren lassen. Bei Web-Performance-Optimierungen in den späten 2000ern waren solche Effizienzgewinne sehr befriedigend
  • 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

    • Ich verstehe nicht, warum von KI geschriebene Texte so weit nach oben kommen. Solche lieblos geschriebenen Beiträge zu lesen, ist Zeitverschwendung
    • Auf Apple wird DASH nicht unterstützt. HLS könnte eine Alternative sein, aber ohne ffmpeg ist die Umsetzung sehr schwierig
    • Dieser Text wirkt im Gegenteil kaum wie im LLM-Stil. Unbelegte Kritik ist nicht überzeugend
    • Weil Zeit und Kosten für das Erlernen bestehender Tools hoch sind, kann selbst eine „falsche Neuerfindung“ je nach Situation effizienter sein
  • 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

    • Unter den vielen Vorschlägen scheint das der realistischste Ausgangspunkt zu sein. 40 Mbps als Basis anzusetzen, wirkt wie ein grundlegend falscher Problemansatz
    • Der Text wirkte unausgereift. Ich frage mich, ob so ein Ansatz auch als Open Source möglich ist
    • Ich würde empfehlen, sich zuerst das neko-Projekt anzusehen. Es geht mit Verbindungsverzögerung und Backpressure deutlich besser um als VNC
    • Den VNC-Ansatz zu kopieren, wäre wohl der natürlichste erste Versuch. Eine Low-Latency-Lösung für Spiele wie Moonlight zu verwenden, wäre eher unpassend
  • 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

    • Im Text hieß es: „Als ich auf 10 Mbps gesenkt habe, entstand eine Verzögerung von 30 Sekunden“, aber das liegt wahrscheinlich an den Encoding-Einstellungen
    • Auch bei JPEG kann man durch Buffering eine Wiedergabewarteschlange aufbauen und so Ruckler abmildern. Moderne Player überwachen die Netzwerkqualität in Echtzeit
  • 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

    • Der Schreibstil und der Aufbau der Argumentation riechen nach LLM. Der Code dürfte auf ähnlichem Niveau sein
    • 1 fps könnte zu wenig sein. Dann braucht man eine Einstellung, bei der alle Frames zu Keyframes werden
    • Für manche Leute kann aber selbst die YouTube-Qualität so störend sein, dass sie kaum auszuhalten ist
  • 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

    • Wenn man so ein komplexes System für einen einzigen Zweck behandeln muss, können LLMs im Gegenteil nützlich sein
  • 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

    • Dazu kommt, dass es auch seltsam ist, eine „tippende Maschine“ mit 60 fps anzuschauen. Das wirkt wie ein Ansatz, der die Problem-Domäne überhaupt nicht verstanden hat
  • 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