reMarkable Streaming Tool v2: mais eficiência no trabalho remoto
(blog.owulveryck.info)- A ferramenta usada para compartilhar esboços do reMarkable 2 durante videoconferências foi reformulada para abrir sem um serviço local no notebook, permitindo que o apresentador inicie streaming instantâneo apenas com o navegador
- A nova arquitetura foi simplificada: um servidor HTTP dentro do reMarkable e um cliente JavaScript no navegador recebem imagens brutas e as desenham em um
canvas - A alternativa com WebSocket funcionava, mas ainda apresentava problemas no iOS e overhead no servidor, então a solução final adotou um stream bruto: imagens em resolução fixa escritas continuamente com
http.ResponseWritere lidas por um stream defetch - Como cada frame bruto em 1872x1404 tem cerca de 2,5 MB, após o firmware 3.3 os 16 valores de cor passaram a ser empacotados em uint4, reduzindo 50% do volume, e com RLE a média caiu para cerca de 200 KB por transmissão
- Ao monitorar
/dev/input/event*, o envio de novos frames para quando não há entrada; mesmo com clientes conectados, o uso de CPU cai para 0, e durante a escrita fica em cerca de 10% de CPU
Por que a ferramenta antiga era incômoda
- A ferramenta de streaming para reMarkable criada em 2021 era usada para compartilhar esboços em videoconferências, e bastava compartilhar a aba do navegador para facilitar o foco no conteúdo da apresentação
- A implementação anterior era dividida em três componentes
- Servidor: executado no dispositivo reMarkable e expunha a imagem bruta da tela atual
- Cliente: no notebook, buscava a imagem bruta do servidor e a processava em um formato visível no navegador
- Renderizador: lia um stream HTTP MJPEG e exibia a tela, como no navegador ou no VLC
- Para reduzir o uso de CPU no dispositivo, o servidor extraía imagens apenas quando havia cliente conectado, e a comunicação usava gRPC
- O cliente no notebook buscava repetidamente as imagens, codificava em JPEG e então oferecia um serviço HTTP com stream MJPEG
- Em ambientes de apresentação, a configuração de rede era um incômodo: endereço do reMarkable, permissão para executar o cliente e o IP do cliente que o renderizador precisava conhecer
Nova arquitetura aberta só com o navegador
- O novo objetivo era permitir acesso ao stream em qualquer navegador apenas informando o endereço do reMarkable
- O cliente separado no notebook foi eliminado, e a estrutura passou a incluir um servidor HTTP dentro do componente de servidor no próprio reMarkable
- O cliente a ser executado no navegador precisava estar em JavaScript ou WASM
- A princípio foi considerada a compilação para WASM para aproveitar a experiência com Go, mas a ideia foi abandonada por exigir muitas adaptações devido às limitações
- A segunda versão do cliente acabou sendo escrita em JavaScript
- ChatGPT foi usado no processo de obtenção de trechos de código JavaScript e explicações, mas a direção da solução desejada foi definida diretamente pelo autor
Forma de renderização com canvas
- Para sair do stream MJPEG, foi usado o
canvas, elemento básico de manipulação de imagens no navegador - A imagem bruta recebida do reMarkable é lida como
Uint8Array, os mesmos valores são colocados nos canais R/G/B dos pixels RGBA deImageData, e o canal alfa é definido como 255 para exibição - Para possibilitar exibição responsiva, rotação e potencial colorização, um
fixedCanvasde tamanho fixo é mantido oculto - O conteúdo do canvas oculto é copiado para o canvas de exibição com
drawImage - Quando o tamanho da janela do navegador muda, a largura e a altura do canvas de exibição são ajustadas com base no tamanho do contêiner e na proporção 1872/1404
Abandono do WebSocket em favor de stream bruto
- Como gRPC não é uma escolha comum no desenvolvimento web, a primeira implementação substituta usou WebSocket como meio de comunicação e encapsulamento
- As mensagens WebSocket continham imagens brutas, e o cliente no navegador atualizava o canvas a cada mensagem recebida para parecer streaming
- Essa abordagem permitia controlar, no lado do servidor, a frequência de envio das mensagens para gerenciar carga de memória e CPU no reMarkable
- Mas havia problemas no iOS, e também era difícil controlar o overhead da implementação de WebSocket no servidor
- A estrutura final removeu o encapsulamento e passou a transmitir a imagem bruta diretamente pela rede, aproveitando o tamanho fixo da imagem
- O servidor em Go faz
Writerepetidamente da imagem emhttp.ResponseWriter - O cliente no navegador lê o
ReadableStreamdefetch('/stream')e aplica os chunks recebidos aos dados do canvas
- O servidor em Go faz
Otimização do volume de transmissão
- A imagem bruta do reMarkable 2 tem resolução 1872x1404 e tamanho de cerca de 2,5 MB, e esses dados precisam ser transmitidos a cada frame
- Depois do firmware 3.3, os 16 valores de cor do reMarkable puderam ser representados como um array uint4 em vez de uint8
- Go e JavaScript não têm tipo uint4 nativo
- A solução foi armazenar dois valores de pixel em um único byte uint8
- Em Go, os dois valores uint4 são empacotados nos 4 bits mais altos e nos 4 bits mais baixos; em JavaScript, eles são desempacotados novamente
- Essa representação reduz o volume de dados em 50%
- Para compressão adicional, foi usado Run Length Encoding (RLE)
- RLE é um algoritmo simples que transmite juntos a quantidade de repetições consecutivas de um mesmo valor de pixel e o próprio valor
- Por exemplo,
0 0 0 0 0 0 1 1 1 0 0 0 0pode ser representado como6 0 3 1 4 0
- O valor de contagem pode chegar a até 1872*1404, o que exigiria tipos como uint64, e em alguns casos o resultado comprimido poderia até ficar maior que o original
- Para evitar isso, foi escolhido um ponto de equilíbrio limitando o comprimento da contagem a 15 e armazenando contagem e valor do pixel em um único byte
- A implementação de RLE funciona como o
io.Writerdo Go, podendo ser reutilizada; em tese seria possível aplicar RLE duas vezes, mas isso não foi necessário - Após o empacotamento e o RLE, o volume médio de transmissão ficou em cerca de 200 KB
Envio de frames apenas quando há mudanças
- A última otimização foi enviar novos frames apenas quando a tela muda
- Calcular se houve mudança por checksum poderia gerar carga excessiva de CPU
- O reMarkable é baseado em Linux, então entradas de caneta ou toque chegam por
/dev/input/event* - Uma goroutine monitora esses eventos de entrada e envia a imagem apenas quando necessário
- Sem eventos, mesmo com o cliente conectado, o uso de CPU cai para 0
- Durante a escrita, o uso de CPU fica em cerca de 10%
Mudanças de firmware e custo de manutenção
- Esta aplicação é baseada em hacking, e o desafio central é separar de forma eficaz a interface de captura de imagem do cliente/renderizador
- Na implementação anterior, cliente e servidor estavam totalmente separados por meio de definições protobuf
- O firmware reMarkable 3.3 quebrou a ferramenta, e os detalhes estão na issue 36 do GitHub
- Naquele momento, a correção afetou apenas o componente cliente
- Segundo a issue 58 do GitHub, o firmware 3.6 também pode trazer mudanças incompatíveis
- Nesse caso, correções mais amplas podem ser necessárias
- Ainda assim, como o cliente está integrado ao servidor em uma estrutura autocontida, a atualização no dispositivo pode ficar mais simples
- A aplicação e o código-fonte estão disponíveis em github.com/owulveryck/goMarkableStream
1 comentários
Opiniões no Hacker News
A ferramenta de 2021 que fazia streaming da tela do tablet reMarkable para um notebook foi refinada e relançada, e o novo post se aprofunda em arquitetura, componentes e no processo de melhoria da experiência do usuário
Do ponto de vista de um gerente de produto, simplificou-se o processo de ativação observando como o usuário se sentia, fez-se a ferramenta funcionar sem serviço local e também se otimizou o uso de rede
Ctrl-Ce reiniciar com./goMarkableStream, funcionou em certa medida, mas ainda aparecewaiting for reMarkable screencom frequência, e o serviço está instávelInstalei depois de atualizar o reMarkable2 para 3.5.2.1807, mas não houve reação ao desenhar sobre o notebook, uma folha ou um livro, e às vezes aparecem logs como
read /dev/input/event2: file already closederead /dev/input/event1: file already closedTanto https://192.168.8.143:2001/ quanto https://10.11.99.1:2001/ servem HTML e canvas, e também tentei no Chrome, Firefox e Brave
Parece haver uma limitação de um navegador e um IP por stream, mas, seja qual for o endereço e o navegador usados, às vezes aparece
waiting for reMarkable screen. Depois denohup ./goMarkableStream &, fechei o PuTTY e reiniciei o cliente, e todos os navegadores ficaram no mesmo estado; ao acessar https://10.11.99.1:2001/stream, retornatoo many requests. Gostaria de saber como reiniciar o streamComo alternativa, uso o SuperNote com muita satisfação. Ele permite espelhamento de tela, então é ótimo para desenhar diagramas rapidamente durante reuniões
A desvantagem é que o SuperNote sobe um pequeno servidor web e você acessa pelo Firefox, então o notebook e o SuperNote precisam estar na mesma rede. No home office isso não é problema, mas pode ser bloqueado por políticas da empresa
Seja o RM2 ou o SuperNote, para quem gosta de escrever ideias com caneta e papel, são ferramentas excelentes; a sensação é bem diferente de apps ou documentos de texto, e também dá para rabiscar nas notas
[0]: https://supernote.com/
Porém, ao comprar, é preciso aceitar a violação da GPL. Embora seja totalmente baseado em Android, eles não publicam o código-fonte do sistema operacional
Tenho receio de acabar comprando um dispositivo que não receba atualizações de recursos — ou ao menos atualizações de segurança — nos próximos 3 a 5 anos
A renderização em canvas HTML pode ficar mais rápida usando arrays tipados, como explicado aqui: https://hacks.mozilla.org/2011/12/faster-canvas-pixel-manipu...
Esse é exatamente o tipo de conteúdo que quero ver por aqui. Gostei de como o ChatGPT ajudou a aprender e resolver problemas em uma área que ele não conhece bem, e me identifiquei com a frase “eu era o desenvolvedor e o ChatGPT era o programador”
Também é verdade que simplicidade, na prática, é complexa
Imagino que a escolha por JPEG tenha sido porque é fácil transformar em MJPEG e, ao entregar para algo que suporte o formato, a decodificação sai quase “de graça”. Mas isso pode ser um fator que pesa bastante na CPU do reMarkable
JPEG é mais adequado para fotos, enquanto a tela do reMarkable se parece mais com ilustrações e, além disso, é em tons de preto e branco. Usar outro formato comum de imagem, como PNG, ou apenas uma compressão RLE simples, provavelmente reduziria a carga de CPU
Além disso, o formato de arquivo próprio não é bitmap, mas baseado na entrada da escrita à mão
Ao fazer profiling, vi que a maior parte da CPU era usada na transferência de dados pelo fio, então adicionei compressão. Agora o uso de CPU está baixo
Fico curioso se vocês chegaram a avaliar uma abordagem de enviar apenas as áreas alteradas do framebuffer. Isso poderia reduzir bastante a taxa de transferência de dados: https://github.com/pl-semiotics/mxc_epdc_fb_damage
O projeto rM VNC também faz isso, mas gosto mais da experiência de usuário deste app, que não exige software no lado do cliente
É muito legal e eu gostaria de gostar do ReMarkable 2, mas não é fácil por causa da postura de que é um dispositivo inseguro: https://support.remarkable.com/s/article/Does-reMarkable-off...
Não se trata daquelas vulnerabilidades de software conhecidas que normalmente vêm à mente quando se fala em um dispositivo inseguro conectado à rede
Gostaria de ler mais sobre a parte: “No início, tentei compilar o cliente para WASM. Parecia promissor, pois poderia aproveitar minha experiência de desenvolvimento em Go, mas esbarrei em várias limitações que exigiriam modificações consideráveis”
Mesmo que eu gerasse um stream MJPEG, ainda havia a questão de como exibi-lo. Pensei em usar canvas, mas era difícil acessar o backend do canvas sem fazer grandes cópias entre WASM e JS, e o tamanho também era de 2,5 MB
No fim, ao depender de WASM, parecia que eu teria de implementar diretamente muitas operações básicas de imagem que em JS já são acessíveis de forma nativa, como rotação de imagem
Fico curioso para saber como esta ferramenta difere do streaming integrado, ou seja, da função de compartilhamento de tela
https://support.remarkable.com/s/article/Screen-Share
A solução do artigo parece funcionar também no Linux, desde que haja um navegador com recursos suficientes
Gosto do reMarkable, mas gostaria que eles se concentrassem mais em recursos de streaming como este do que em uma assinatura pela qual não pretendo pagar no futuro