1 pontos por GN⁺ 2025-12-24 | 1 comentários | Compartilhar no WhatsApp
  • 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)
    1. RTT < 150ms → streaming H.264
    2. RTT > 150ms → polling de JPEG
    3. 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

  1. 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
  2. Graceful degradation é essencial para a experiência do usuário
  3. WebSocket é ideal para transmissão de entrada, mas não é obrigatório para transmissão de vídeo
  4. Pacotes do Ubuntu podem vir com recursos ausentes — faça o build manualmente se necessário
  5. 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

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

    • No código de polling mostrado no artigo, a próxima requisição só era enviada depois que o download do JPEG anterior terminava. Esse tipo de loop é possível mesmo sem UDP
    • Provavelmente a estrutura serializava completamente os frames e fazia só uma requisição por vez, ou então abria uma nova conexão com um GET novo a cada vez
    • Um ponto interessante é que até JPEGs visualmente idênticos podem ter o tamanho reduzido para algo como 10 a 15% disso. Trabalhando com otimização de performance web no fim dos anos 2000, esse tipo de ganho de eficiência era muito gratificante
  • 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

    • Não entendo por que texto escrito por IA vai parar no topo. Ler algo feito sem capricho parece perda de tempo
    • Na Apple, DASH não é suportado. HLS pode ser uma alternativa, mas sem ffmpeg a implementação fica muito difícil
    • Este texto, na verdade, quase não passa uma sensação de estilo de LLM. Crítica sem base não convence
    • Como tempo e custo para aprender as ferramentas existentes são altos, até uma “reinvenção errada” pode ser mais eficiente dependendo do contexto
  • 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

    • Entre várias sugestões, esta parece o ponto de partida mais realista. Assumir 40Mbps como padrão parece um erro já na forma de abordar o problema
    • O texto passou uma impressão de inexperiência. Fiquei curioso para saber se essa abordagem também é viável em código aberto
    • Recomendo olhar primeiro para o projeto neko. Ele lida com latência de conexão e backpressure muito melhor do que VNC
    • Copiar a abordagem do VNC parece a primeira tentativa mais natural. Usar uma solução de baixa latência voltada para jogos, como Moonlight, parece até inadequado aqui
  • 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

    • O texto diz que “ao baixar para 10Mbps surgiu um atraso de 30 segundos”, mas isso provavelmente era problema de configuração de codificação
    • JPEG também pode aliviar travamentos se usar buffering para formar uma fila de reprodução. Players modernos monitoram a qualidade da rede em tempo real
  • 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

    • O estilo do texto e a progressão da lógica têm cheiro de LLM. O código provavelmente está no mesmo nível
    • 1fps pode não ser suficiente. Seria necessário configurar todos os frames como keyframes
    • Mas, para algumas pessoas, até a qualidade do YouTube pode ser insuportavelmente incômoda
  • 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

    • Quando se precisa lidar com um sistema tão complexo para um objetivo único, LLM pode até ser útil
  • 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

    • Além disso, já é estranho assistir, a 60fps, a “um computador digitando”. Parece uma abordagem que não entendeu nada do domínio do problema
  • 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