- Helix é uma plataforma de IA que mostra aos usuários a tela onde agentes autônomos de programação operam na nuvem, e a transmissão remota de tela com estabilidade é essencial
- Como o streaming baseado em WebRTC falhava por causa do bloqueio de UDP e restrições de firewall em redes corporativas, a equipe construiu um pipeline H.264 baseado em WebSocket, mas em ambientes de Wi-Fi instáveis a latência se tornava grave
- Em vez de uma estrutura complexa de codificação e decodificação, descobriram que uma abordagem simples de enviar screenshots em JPEG periodicamente via HTTP era muito mais estável e eficiente
- Esse método usa menos largura de banda, não exige recuperação de quadros corrompidos e ajusta automaticamente a qualidade da imagem e a taxa de quadros de acordo com a qualidade da rede
- Como resultado, a Helix adotou uma arquitetura híbrida que usa H.264 em conexões boas e muda para polling de JPEG em conexões ruins, concluindo um sistema de streaming remoto simples, mas prático
Problema e limitações de streaming da Helix
- A Helix é uma plataforma que precisa compartilhar em tempo real a tela de agentes de IA para programação executados em sandboxes na nuvem
- O usuário acompanha o processo em que a IA escreve código como se estivesse vendo uma área de trabalho remota
- No início, usaram WebRTC, mas a conexão falhava por causa do bloqueio de UDP em redes corporativas
- Servidores TURN, STUN/ICE e portas personalizadas eram todos bloqueados por políticas de firewall
- Por isso, implementaram diretamente um pipeline de streaming H.264 baseado em WebSocket usando apenas HTTPS (porta 443)
- Codificação por hardware com GStreamer + VA-API, decodificação no navegador com WebCodecs
- Alcançaram 60fps, 40Mbps e latência abaixo de 100ms
Latência de rede e queda de desempenho
- Em ambientes de rede instáveis, como cafeterias, surgia o problema de o vídeo travar ou ficar atrasado em dezenas de segundos
- Como o WebSocket baseado em TCP atrasa os quadros em sequência quando há perda de pacotes, a capacidade de tempo real colapsa
- Reduzir o bitrate não resolvia a latência; só piorava a qualidade da imagem
- Também tentaram enviar apenas keyframes, mas isso falhou porque o protocolo Moonlight exigia P-frames
Descoberta do método com screenshots em JPEG
- Durante a depuração, ao chamar o endpoint
/screenshot?format=jpeg&quality=70, uma imagem nítida foi carregada imediatamente
- Um único JPEG de 150KB era exibido sem latência
- Ao simplesmente repetir requisições HTTP para atualizar a screenshot, foi possível obter uma atualização de tela fluida no nível de 5fps
- No fim, trocaram o pipeline de vídeo complexo por um método de requisições periódicas de JPEG (loop de
fetch)
Vantagens do método com JPEG
- Principais itens de comparação em relação ao H.264
- Largura de banda: H.264 ficava fixo em 40Mbps, enquanto JPEG variava entre 100~500Kbps
- Gerenciamento de estado: H.264 depende de estado; JPEG tem quadros totalmente independentes
- Recuperação: H.264 precisa esperar por um keyframe; JPEG se recupera imediatamente com o próximo quadro
- Complexidade: H.264 exigiu meses de desenvolvimento; JPEG foi implementado com algumas linhas de loop de
fetch()
- Quanto pior a qualidade da rede, mais o método simples com JPEG se mostrava estável e eficiente
Estrutura híbrida de alternância
- A Helix alterna automaticamente entre os dois métodos com base no RTT (tempo de ida e volta)
- RTT < 150ms → streaming H.264
- RTT > 150ms → polling de JPEG
- Quando a conexão se recupera, o usuário clica para voltar
- Eventos de entrada (teclado e mouse) continuam sendo enviados por WebSocket, preservando a interatividade
- O servidor interrompe a transmissão de vídeo com a mensagem
{"set_video_enabled": false} e muda para o modo de screenshots
Problema de instabilidade na alternância (oscillation) e solução
- Quando o tráfego WebSocket cai após interromper a transmissão, a latência diminui e surge um loop infinito de retorno automático ao modo de vídeo
- Solução: ao entrar no modo de screenshots, ele permanece fixo até o usuário clicar
- A UI exibe a mensagem “Vídeo pausado para economizar largura de banda”
Problema de suporte a JPEG e processo de build
- A ferramenta de screenshot para Wayland, grim, vem com o suporte a JPEG desativado no pacote padrão do Ubuntu
- Ao executar
grim -t jpeg, ocorre o erro “jpeg support disabled”
- Para resolver isso, incluíram libjpeg-turbo8-dev no Dockerfile e fizeram o build do grim diretamente a partir do código-fonte
Arquitetura final
- Boa conexão: H.264 a 60fps, com aceleração por hardware
- Conexão ruim: polling de JPEG a 2~10fps, com confiabilidade total
- A qualidade da screenshot é ajustada automaticamente conforme o tempo de transmissão
- Acima de 500ms, qualidade -10%; abaixo de 300ms, +5%; mantendo no mínimo 2fps
Principais lições
- Uma solução simples é melhor do que um sistema complexo — 3 meses de desenvolvimento em H.264 foram menos práticos do que 2 horas improvisando com JPEG
- Graceful degradation é essencial para a experiência do usuário
- WebSocket é ideal para transmissão de entrada, mas não é obrigatório para transmissão de vídeo
- Pacotes do Ubuntu podem vir com recursos ausentes — faça o build manualmente se necessário
- Medir antes de otimizar é essencial — streaming complexo não é a única solução
Open source
- A Helix é disponibilizada como open source, e as implementações principais são as seguintes
api/cmd/screenshot-server/main.go — servidor de screenshots
MoonlightStreamViewer.tsx — lógica adaptativa do cliente
websocket-stream.ts — controle de alternância de vídeo
- A Helix está sendo desenvolvida com o objetivo de criar uma infraestrutura de IA que funcione também em ambientes reais
1 comentários
Comentários do Hacker News
Quando a rede está ruim, o JPEG degradar não acontece por causa do UDP, e sim da forma como o TCP foi implementado
JPEG não resolve problemas de buffering nem de controle de congestionamento. O mais provável é que tenha sido implementado de um jeito que minimiza a transmissão de frames
h.264 tem eficiência de codificação maior do que JPEG. No mesmo tamanho, um frame IDR em h.264 pode entregar qualidade melhor
O problema fundamental é a ausência de estimativa de largura de banda. Mesmo em ambiente TCP, dá para ajustar o bitrate com sondagem inicial de banda e detecção de latência de transmissão
Se possível, é melhor usar WebRTC, e para contornar firewall o ideal é usar WebSocket
Mesmo deixando de lado os problemas de formato do texto ou o estilo de LLM, há muita coisa errada no conteúdo como um todo
10Mbps deveria ser suficiente para uma tela estática. O problema é que as configurações de codificação estavam erradas ou a qualidade do encoder era baixa
A abordagem de “mandar só keyframes” é ineficiente; o certo seria configurar um intervalo curto entre keyframes
No fim, o problema é a estrutura de empurrar o stream inteiro por uma única conexão TCP. Já existem soluções como DASH feitas para esse tipo de situação
Acho que valeria a pena olhar para o que o VNC faz desde 1998
A ideia é manter o modelo de polling do cliente, mas dividir o framebuffer em blocos (tiles) e transmitir só as partes alteradas
Em uma tela de código estática, isso pode reduzir bastante a largura de banda. Se ainda detectar scroll, fica mais eficiente ainda
Já trabalhei com codificação de vídeo antes, e 40Mbps é qualidade de nível Blu-ray
É exagerado para streaming de texto simples. Conversando com o Claude, cheguei à conclusão de que 30FPS, GOP de 2 segundos e média de 1Mbps já seriam suficientes
Mesmo no pior caso, 1.2Mbps já manteria qualidade estável o bastante
O problema central deste texto é que ele configurou a largura de banda mínima do h.264 alta demais
H.264 é muito mais eficiente do que JPEG. Deveria ter começado em 1Mbps e ajustado a partir daí
Usar só keyframes é, na verdade, ineficiente
Eu teria abordado isso de um jeito completamente diferente
10Mbps é exagerado, e vídeos de programação no YouTube ficam na faixa de 0.6Mbps mesmo em 1080p. Já ficam suficientemente nítidos
Eu preferiria reduzir para 1fps ou ajustar o intervalo entre keyframes
Fazer streaming de vídeo em tempo real no navegador é realmente doloroso
Se screenshots em JPEG estão funcionando bem, melhor deixar assim
Pilhas como gstreamer ou Moonlight viram um inferno de depuração se você não entende backpressure e propagação de erros
Uma alternativa realista é a combinação NVIDIA Video Codec SDK + WebSocket + MediaSource Extensions
Mas, se o texto foi gerado por LLM, o autor provavelmente não tem disposição para entender essa estrutura interna
Há muito tempo usei um programa que tirava screenshots a cada 5 segundos, mas o disco rígido enchia muito rápido
Percebi que a maior parte das imagens era igual e comecei a pensar em um algoritmo que salvasse só as partes alteradas
No fim, percebi que estava reinventando compressão de vídeo
Resolvi com uma linha de ffmpeg e economizei 98% de espaço de armazenamento
Fazer streaming de um LLM digitando em vídeo a 40Mbps é uma largura de banda absurdamente excessiva
A única forma de conseguir boas respostas no HN é postar algo errado
Acho que um texto errado, mas interessante, é exatamente o equilíbrio perfeito para provocar discussão