- Helix est une plateforme d’IA qui montre à l’utilisateur l’écran sur lequel tournent des agents de codage autonomes dans le cloud, et la transmission distante fiable de l’écran y est essentielle
- Quand le streaming basé sur WebRTC a échoué à cause du blocage de l’UDP et des contraintes de pare-feu sur les réseaux d’entreprise, l’équipe a construit un pipeline H.264 basé sur WebSocket, mais dans des environnements Wi-Fi instables, la latence est devenue très importante
- Au lieu d’une architecture complexe d’encodage et de décodage, l’équipe a découvert qu’une approche consistant simplement à envoyer périodiquement des captures d’écran JPEG via HTTP était bien plus stable et efficace
- Cette méthode consomme moins de bande passante, n’a pas besoin de récupération de trames corrompues et ajuste automatiquement la qualité d’image et la fréquence d’images selon la qualité du réseau
- Au final, Helix a adopté une architecture hybride : H.264 sur une bonne connexion et bascule vers le polling JPEG sur une mauvaise connexion, aboutissant à un système de streaming distant simple mais pragmatique
Le problème de streaming et les contraintes de Helix
- Helix est une plateforme qui doit partager en temps réel l’écran d’un agent de codage IA exécuté dans un sandbox cloud
- L’utilisateur regarde l’IA écrire du code comme s’il s’agissait d’un bureau à distance
- Au départ, l’équipe utilisait WebRTC, mais la connexion échouait à cause du blocage de l’UDP sur les réseaux d’entreprise
- Serveurs TURN, STUN/ICE, ports personnalisés : tout était bloqué par les politiques de pare-feu
- L’équipe a donc implémenté elle-même un pipeline de streaming H.264 basé sur WebSocket n’utilisant que HTTPS (port 443)
- Encodage matériel avec GStreamer + VA-API, décodage navigateur avec WebCodecs
- 60fps, 40Mbps et moins de 100ms de latence atteints
Latence réseau et dégradation des performances
- Dans des environnements réseau instables comme les cafés, la vidéo se figeait ou prenait plusieurs dizaines de secondes de retard
- Avec WebSocket basé sur TCP, en cas de perte de paquets, les trames étaient retardées séquentiellement et le temps réel s’effondrait
- Réduire le bitrate ne résolvait pas la latence et ne faisait que dégrader la qualité d’image
- L’équipe a aussi tenté d’envoyer uniquement des keyframes, mais cela a échoué parce que le protocole Moonlight exige des P-frames
La découverte de l’approche par captures JPEG
- Pendant le débogage, l’appel à l’endpoint
/screenshot?format=jpeg&quality=70 a chargé instantanément une image nette
- Un JPEG unique de 150KB s’affichait sans latence
- Le simple fait de répéter des requêtes HTTP pour rafraîchir la capture permettait une mise à jour fluide de l’écran autour de 5fps
- L’équipe a donc abandonné le pipeline vidéo complexe au profit d’une méthode de requêtes JPEG périodiques (boucle
fetch())
Les avantages de l’approche JPEG
- Principaux points de comparaison face à H.264
- Bande passante : H.264 reste fixe à 40Mbps, JPEG varie entre 100 et 500Kbps
- Gestion d’état : H.264 dépend de l’état, JPEG fournit des trames totalement indépendantes
- Récupération : H.264 impose d’attendre une keyframe, JPEG récupère immédiatement à la trame suivante
- Complexité : H.264 a demandé plusieurs mois de développement, JPEG s’implémente avec quelques lignes de boucle
fetch()
- Plus la qualité du réseau est mauvaise, plus l’approche JPEG, pourtant simple, se montre stable et efficace
Architecture hybride de bascule
- Helix bascule automatiquement entre les deux méthodes selon le RTT (temps d’aller-retour)
- RTT < 150ms → streaming H.264
- RTT > 150ms → polling JPEG
- Une fois la connexion rétablie, l’utilisateur clique pour rebascule
- Les événements d’entrée (clavier, souris) continuent d’être envoyés via WebSocket, ce qui préserve l’interactivité
- Le serveur arrête l’envoi vidéo et passe en mode capture avec le message
{"set_video_enabled": false}
Problème d’oscillation et solution
- Une fois la transmission vidéo arrêtée, le trafic WebSocket diminuait, la latence baissait et cela déclenchait une boucle infinie de retour automatique en mode vidéo
- Solution : après l’entrée en mode capture, rester verrouillé jusqu’au clic de l’utilisateur
- L’interface affiche le message « Vidéo mise en pause pour économiser la bande passante »
Problème de support JPEG et processus de build
- L’outil de capture Wayland grim est livré dans les paquets Ubuntu par défaut avec le support JPEG désactivé
- La commande
grim -t jpeg renvoie l’erreur « jpeg support disabled »
- Pour corriger cela, l’équipe a compilé directement grim depuis les sources dans le Dockerfile en incluant
libjpeg-turbo8-dev
Architecture finale
- Bonne connexion : H.264 à 60fps, accélération matérielle
- Mauvaise connexion : polling JPEG à 2~10fps, fiabilité totale
- La qualité des captures est ajustée automatiquement selon le temps de transfert
- Au-delà de 500ms : qualité -10 %, en dessous de 300ms : +5 %, avec un minimum maintenu à 2fps
Enseignements clés
- Une solution simple vaut mieux qu’un système complexe — 2 heures de bricolage JPEG ont été plus utiles que 3 mois de développement H.264
- La dégradation élégante (graceful degradation) est au cœur de l’expérience utilisateur
- WebSocket est optimal pour transmettre les entrées, mais pas indispensable pour la vidéo
- Les paquets Ubuntu peuvent manquer de fonctionnalités — il faut parfois compiler soi-même
- Mesurer avant d’optimiser est indispensable — un streaming complexe n’est pas forcément la seule solution
Publication en open source
- Helix est proposé en open source, avec notamment ces éléments clés
api/cmd/screenshot-server/main.go — serveur de captures d’écran
MoonlightStreamViewer.tsx — logique client adaptative
websocket-stream.ts — contrôle de la bascule vidéo
- Helix est développé avec l’objectif de fournir une infrastructure IA capable de fonctionner aussi en conditions réelles
Aucun commentaire pour le moment.