3 puntos por GN⁺ 2023-08-21 | 1 comentarios | Compartir por WhatsApp
  • La herramienta que se usaba para compartir bocetos de reMarkable 2 durante videollamadas cambió para poder abrirse sin un servicio local en la laptop, así que el presentador ahora puede iniciar streaming al instante solo con el navegador
  • La nueva arquitectura se simplifica con un servidor HTTP dentro del reMarkable y un cliente JavaScript en el navegador que recibe imágenes crudas y las dibuja en canvas
  • La alternativa con WebSocket funcionaba, pero seguían existiendo problemas en iOS y sobrecarga del servidor, así que se consolidó un enfoque de stream crudo que escribe imágenes de resolución fija de forma continua con http.ResponseWriter y las lee con un stream de fetch
  • Como cada frame crudo de 1872x1404 pesa unos 2.5 MB, desde el firmware 3.3 los valores de 16 colores se empaquetan como uint4 para reducirlos 50%, y con RLE el volumen promedio baja hasta unos 200 KB por transmisión
  • Al monitorear /dev/input/event*, si no hay entrada se deja de enviar nuevos frames; incluso con clientes conectados, el uso de CPU cae a 0, y mientras se escribe funciona con cerca de 10% de CPU

Por qué la herramienta anterior resultaba incómoda

  • La herramienta de streaming para reMarkable creada en 2021 se usaba para compartir bocetos durante videollamadas, y como bastaba con compartir una pestaña del navegador, era más fácil concentrarse en la presentación
  • La implementación anterior estaba dividida en tres componentes
    • Servidor: se ejecuta en el dispositivo reMarkable y expone la imagen cruda de la pantalla actual
    • Cliente: en la laptop, obtiene la imagen cruda del servidor y la procesa a un formato que el navegador pueda visualizar
    • Renderizador: muestra en pantalla un stream HTTP MJPEG, como en el navegador o VLC
  • Para reducir el uso de CPU en el dispositivo, el servidor extraía imágenes solo cuando había un cliente conectado, y para la comunicación se usaba gRPC
  • El cliente en la laptop obtenía imágenes repetidamente, las codificaba en JPEG y luego ofrecía un stream MJPEG mediante un servicio HTTP
  • En entornos de presentación, la configuración de red era una carga: la dirección del reMarkable, los permisos para ejecutar el cliente y la IP del cliente que el renderizador debía conocer

Nueva arquitectura que se abre solo con el navegador

  • El nuevo objetivo era permitir acceso al stream desde cualquier navegador con solo ingresar la dirección del reMarkable
  • Se eliminó el cliente separado en la laptop y se cambió a una estructura que integra un servidor HTTP dentro del componente servidor del reMarkable
  • El cliente que se ejecuta en el navegador necesitaba ser JavaScript o WASM
    • Al principio se evaluó compilar a WASM para aprovechar la experiencia con Go, pero se descartó por las limitaciones que exigían modificaciones considerables
    • La segunda versión del cliente terminó escrita en JavaScript
  • Se usó ChatGPT para obtener fragmentos de código JavaScript y explicaciones, pero la dirección de la solución deseada se definió directamente

Método de renderizado con canvas

  • Para salir del stream MJPEG, se utilizó canvas, el elemento base del navegador para manipulación de imágenes
  • La imagen cruda recibida desde el reMarkable se lee como Uint8Array, se coloca el mismo valor en R/G/B dentro de los datos RGBA de ImageData, y se fija el valor alfa en 255 para mostrarla
  • Para permitir visualización responsiva, rotación y posibilidades de coloreado, se mantiene oculto un fixedCanvas de tamaño fijo
  • En el canvas de visualización, el contenido del canvas oculto se copia con drawImage
  • Cuando cambia el tamaño de la ventana del navegador, se ajustan el ancho y la altura del canvas visible según el tamaño del contenedor y la relación 1872/1404

Abandonar WebSocket y pasar a stream crudo

  • Como gRPC no es una opción común en desarrollo web, la primera implementación alternativa usó WebSocket como medio de comunicación y encapsulación
  • Los mensajes de WebSocket contenían imágenes crudas, y el cliente del navegador actualizaba el canvas cada vez que recibía un mensaje para que pareciera streaming
  • Este enfoque permitía gestionar la carga de memoria y CPU del reMarkable ajustando la frecuencia de envío de mensajes del lado del servidor
  • Pero había problemas en iOS, y también era difícil controlar la sobrecarga de la implementación de WebSocket en el servidor
  • La estructura final eliminó la encapsulación y aprovechó el tamaño fijo de la imagen para transmitir directamente la imagen cruda por la red
    • El servidor en Go hace Write repetidamente sobre http.ResponseWriter
    • El cliente del navegador lee el ReadableStream de fetch('/stream') y refleja los fragmentos recibidos en los datos del canvas

Optimización del volumen de transferencia

  • La imagen cruda de reMarkable 2 pesa alrededor de 2.5 MB con resolución 1872x1404, y esos datos deben transmitirse en cada frame
  • Desde el firmware 3.3, los 16 valores de color del reMarkable pueden representarse como un arreglo uint4 en vez de uint8
    • Ni Go ni JavaScript tienen un tipo uint4 nativo
    • Se resolvió guardando dos valores de píxel en un solo byte uint8
    • En Go, los dos valores uint4 se empaquetan en los 4 bits altos y los 4 bits bajos, y en JavaScript luego se desempaquetan
    • Esta representación permite reducir el volumen de datos en 50%
  • Para compresión adicional se usó Run Length Encoding (RLE)
    • RLE es un algoritmo simple que transmite juntos la cantidad de repeticiones consecutivas y el valor del mismo píxel
    • Por ejemplo, 0 0 0 0 0 0 1 1 1 0 0 0 0 se representa como 6 0 3 1 4 0
  • Como el valor del conteo puede crecer hasta 1872*1404, podrían hacer falta tipos como uint64, y en algunos casos existe el riesgo de que el resultado comprimido sea más grande que el original
  • Para evitarlo, se eligió un punto de equilibrio limitando la longitud del conteo a 15 y guardando en un solo byte tanto el conteo como el valor del píxel
  • La implementación de RLE funciona como io.Writer de Go, así que puede reutilizarse; incluso sería posible aplicar RLE dos veces si hiciera falta, aunque por ahora no fue necesario
  • Después del empaquetado y RLE, el volumen promedio de transmisión queda en unos 200 KB

Enviar frames solo cuando hay cambios

  • La última optimización consiste en enviar un frame nuevo solo cuando cambia la pantalla
  • Calcular si hubo cambios mediante checksum podría imponer una carga alta de CPU
  • Como reMarkable está basado en Linux, las entradas del lápiz o del tacto llegan por /dev/input/event*
  • Una goroutine monitorea esos eventos de entrada y transmite imágenes solo cuando hace falta
  • Si no hay eventos, el uso de CPU cae a 0 incluso con clientes conectados
  • Mientras se escribe, el uso de CPU se mantiene alrededor de 10%

Cambios de firmware y carga de mantenimiento

  • Esta aplicación se basa en hacking, y el desafío central es separar de forma efectiva la interfaz para obtener imágenes del cliente/renderizador
  • La implementación anterior separaba completamente cliente y servidor mediante definiciones protobuf
  • El firmware reMarkable 3.3 rompió la herramienta, y hay detalles en el issue 36 de GitHub
    • En ese momento, la corrección afectó solo al componente cliente
  • Según el issue 58 de GitHub, el firmware 3.6 también puede traer cambios incompatibles
    • En ese caso podrían hacer falta modificaciones más amplias
    • Aun así, como el cliente está integrado en una estructura autocontenida dentro del servidor, las actualizaciones del dispositivo podrían simplificarse
  • La aplicación y el código fuente están disponibles en github.com/owulveryck/goMarkableStream

1 comentarios

 
GN⁺ 2023-08-21
Opiniones en Hacker News
  • Se volvió a pulir y publicar una herramienta de 2021 para transmitir la pantalla de una tablet reMarkable a una laptop, y el nuevo artículo profundiza en la arquitectura, los componentes y el proceso de mejora de la experiencia de usuario.
    Desde la perspectiva de un gerente de producto, se simplificó el proceso de activación observando cómo se sentían los usuarios, se hizo que funcionara sin un servicio local y también se optimizó el uso de la red.

    • Parece un proyecto genial. Después de Ctrl-C y reiniciarlo con ./goMarkableStream, funcionó hasta cierto punto, pero el servicio sigue siendo inestable y aparece con frecuencia waiting for reMarkable screen.
      Lo instalé después de actualizar mi reMarkable2 a 3.5.2.1807, pero no respondió al dibujar sobre una laptop, una hoja o un libro, y a veces veo logs como read /dev/input/event2: file already closed y read /dev/input/event1: file already closed.
      Tanto https://192.168.8.143:2001/ como https://10.11.99.1:2001/ sirven HTML y canvas, y también lo probé en Chrome, Firefox y Brave.
      Parece haber un límite de un navegador y una IP por stream, pero a veces aparece waiting for reMarkable screen sin importar qué dirección o navegador use. Después de nohup ./goMarkableStream &, cerré PuTTY y reinicié el cliente, todos los navegadores quedaron en el mismo estado, y al ver https://10.11.99.1:2001/stream devuelve too many requests. Me pregunto cómo habría que reiniciar el stream.
    • Me pregunto si también se podría implementar mediante una conexión USB.
  • Como alternativa, estoy usando SuperNote con muchísima satisfacción. Permite duplicar la pantalla, así que es muy bueno para dibujar diagramas rápidamente durante reuniones.
    La desventaja es que SuperNote levanta un pequeño servidor web y uno se conecta con Firefox, así que la laptop y la SuperNote tienen que estar en la misma red. En la oficina en casa no es un problema, pero podría estar bloqueado por políticas de la empresa.
    Tanto la RM2 como la SuperNote son herramientas excelentes para quienes disfrutan escribir ideas con lápiz y papel; se sienten bastante distintas de una app o un documento de texto, y también se puede garabatear en las notas.
    [0]: https://supernote.com/

    • Onyx Boox Note también funciona bien, y siguen sacando actualizaciones incluso después de más de 5 años.
      Eso sí, al comprarla hay que aceptar las violaciones a la GPL. Aunque está completamente basada en Android, no publican el código fuente del sistema operativo.
    • Estaba buscando una tablet de tinta electrónica para usar como lector de ebooks y para tomar notas, así que la recomendación me ayuda. Estaba dudando entre Remarkable 2 y Boox, y me interesa saber cómo ha sido la experiencia de actualizaciones de software de SuperNote.
      Me preocupa terminar comprando un dispositivo que no reciba actualizaciones de funciones, o al menos de seguridad, durante los próximos 3 a 5 años.
    • Según recuerdo, SuperNote no cumplía con la GPL del software que distribuye junto con el dispositivo; me pregunto si su postura cambió.
    • Todavía no me he topado con el problema de “tener que estar en la misma red”, pero agregar una función nativa de Ngrok parece una tarea sencilla de unos minutos. Eso permitiría hacer streaming a través de internet.
    • Me pregunto cómo se compara la sensación de escritura de SuperNote con la RM2.
  • El renderizado en canvas de HTML podría volverse más rápido si se usan arrays tipados, como se explica aquí: https://hacks.mozilla.org/2011/12/faster-canvas-pixel-manipu...

    • Gracias, le voy a echar un vistazo.
  • Este es exactamente el tipo de contenido que quiero ver aquí. Me gustó cómo ChatGPT ayudó a aprender y resolver problemas en un área que no conoce bien, y me identifiqué con la frase “yo era el desarrollador y ChatGPT era el programador”.
    También es cierto eso de que la simplicidad en realidad es compleja.

  • Supongo que eligieron JPEG porque es fácil convertirlo a MJPEG y, si se lo pasas a algo que lo soporte, la decodificación sale casi gratis. Pero eso podría ser un factor que le meta bastante carga a la CPU de reMarkable.
    JPEG es más adecuado para fotos, pero la pantalla de reMarkable se parece más a una ilustración y, además, es en escala de blanco y negro. Creo que con otro formato de imagen común, como PNG, o incluso una compresión RLE simple, la carga de CPU podría ser menor.

    • En rigor, reMarkable no es monocromática sino de escala de grises y, si mal no recuerdo, admite 16 niveles de gris. En la app complementaria también hay tinta de color que se ve como azul y rojo para el bolígrafo, y amarillo y verde para el resaltador.
      Además, el formato de archivo propietario no es un bitmap, sino que se basa en la entrada manuscrita.
    • Es correcto que inicialmente elegí JPEG por ese motivo. Pero por eso opté por una arquitectura cliente/servidor, y la codificación la hacía el cliente, es decir, la laptop, no la tablet.
      Al perfilarlo, vi que la mayor parte de la CPU se usaba en transferir datos por el cable, así que agregué compresión. Ahora el uso de CPU es bajo.
  • Me pregunto si evaluaron enviar solo las áreas modificadas del framebuffer. Eso podría reducir mucho la tasa de transferencia de datos: https://github.com/pl-semiotics/mxc_epdc_fb_damage
    El proyecto rM VNC también lo hace así, pero me gusta más la experiencia de usuario de esta app, que no requiere software del lado del cliente.

    • El problema con ese enfoque es que requiere cierto nivel de análisis en el dispositivo, y quiero mantener el código lo menos intrusivo posible. Voy a ver si hay alguna forma barata de hacerlo.
  • Es realmente genial y quisiera que me gustara la ReMarkable 2, pero no es fácil por su postura de que es un dispositivo no seguro: https://support.remarkable.com/s/article/Does-reMarkable-off...

    • El contenido del enlace significa que este dispositivo solo tiene el mismo nivel de seguridad física que el papel al que pretende reemplazar. Es decir, si alguien accede al dispositivo, puede leerlo.
      No se trata de las vulnerabilidades de software conocidas en las que uno suele pensar cuando se habla de un dispositivo inseguro conectado a la red.
    • De manera no oficial, es posible usar cifrado del directorio home basado en gocryptfs: https://github.com/RedTeamPentesting/remarkable-encryption
    • El software también es, por ahora, muy limitado. Es una lástima que oficialmente no permitan usar un marketplace ni extensiones en el dispositivo.
    • ¿Existe algún lector de ebooks que ofrezca cifrado de disco completo?
  • Me gustaría leer más sobre la parte que dice: “Al principio intenté compilar el cliente a WASM. Parecía prometedor porque podía aprovechar mi experiencia desarrollando en Go, pero me topé con varias limitaciones que requerían cambios considerables”.

    • El problema central era la biblioteca gRPC, cuyo soporte actual era muy limitado. Además, en Go la compresión JPEG es lenta y consume mucha CPU.
      Incluso si se generaba un stream MJPEG, también estaba el problema de cómo mostrarlo. Pensé en usar canvas, pero era difícil acceder al backend de canvas sin hacer copias grandes entre WASM y JS, y además pesaba 2.5 MB.
      Al final, depender de WASM parecía implicar que tendría que implementar por mi cuenta muchas operaciones básicas de imagen, como la rotación, que en JS están disponibles de forma nativa.
  • Me da curiosidad en qué se diferencia esta herramienta del streaming integrado, es decir, la función de compartir pantalla.

    • Para usar la función integrada hay que instalar la app de escritorio y, hasta donde sé, no existe versión para Linux.
      https://support.remarkable.com/s/article/Screen-Share
      La solución del artículo parece funcionar también en Linux siempre que se tenga un navegador con suficientes funciones.
    • La mayor diferencia es que ahora no hace falta instalar un cliente. Basta con escribir la dirección de reMarkable en el navegador para ver el contenido.
    • Pensé que esta función ya existía. Compartir pantalla funciona bastante bien y también lo estoy usando para streaming en vivo.
  • Me gusta reMarkable, pero preferiría que se enfocaran en esta función de streaming en lugar de en una suscripción por la que no pienso pagar ni ahora ni en el futuro.