- L’outil qui servait à partager des croquis reMarkable 2 pendant les visioconférences a été modifié pour s’ouvrir sans service local sur l’ordinateur portable, ce qui permet au présentateur de lancer un streaming instantané avec un simple navigateur
- La nouvelle architecture est simplifiée autour d’un serveur HTTP interne au reMarkable et d’un client JavaScript dans le navigateur, qui reçoit les images brutes et les dessine dans un
canvas - L’alternative WebSocket fonctionnait, mais des problèmes sur iOS et la surcharge côté serveur subsistaient ; l’approche finale utilise donc un flux brut, en écrivant en continu des images de résolution fixe via
http.ResponseWriteret en les lisant avec un fluxfetch - Une trame brute 1872x1404 pèse environ 2,5 Mo ; depuis le firmware 3.3, les 16 valeurs de couleur sont empaquetées en uint4, ce qui réduit la taille de 50 %, puis RLE abaisse le volume moyen transmis à environ 200 Ko
- La surveillance de
/dev/input/event*arrête l’envoi de nouvelles trames lorsqu’il n’y a pas d’entrée ; même avec des clients connectés, l’utilisation CPU retombe à 0, et pendant l’écriture elle est d’environ 10 % de CPU
Pourquoi l’ancien outil était peu pratique
- L’outil de streaming reMarkable créé en 2021 servait à partager des croquis pendant les visioconférences ; il suffisait de partager un onglet de navigateur, ce qui facilitait la concentration sur la présentation
- L’ancienne implémentation était divisée en trois composants
- Serveur : exécuté sur l’appareil reMarkable, il expose l’image brute de l’écran actuel
- Client : sur l’ordinateur portable, il récupère l’image brute du serveur et la traite dans un format lisible par le navigateur
- Moteur de rendu : lit un flux HTTP MJPEG, comme un navigateur ou VLC, et l’affiche à l’écran
- Pour réduire l’utilisation CPU de l’appareil, le serveur n’extrayait les images que lorsqu’un client était connecté, et la communication utilisait gRPC
- Le client sur l’ordinateur portable récupérait les images en boucle, les encodait en JPEG, puis exposait un flux MJPEG via un service HTTP
- Dans un contexte de présentation, la configuration réseau — adresse du reMarkable, droits d’exécution du client, IP du client que le moteur de rendu devait connaître — devenait contraignante
Une nouvelle architecture accessible uniquement depuis le navigateur
- Le nouvel objectif était de permettre l’accès au flux depuis n’importe quel navigateur en saisissant simplement l’adresse du reMarkable
- Le client séparé sur ordinateur portable a été supprimé, et un serveur HTTP a été intégré dans le composant serveur du reMarkable
- Le client à exécuter dans le navigateur devait prendre la forme de JavaScript ou de WASM
- Au départ, une compilation WASM a été envisagée afin de tirer parti de l’expérience en Go, mais elle a été abandonnée en raison de limitations nécessitant des modifications importantes
- La deuxième version du client a finalement été écrite en JavaScript
- ChatGPT a été utilisé pour obtenir des extraits de code JavaScript et des explications, mais l’orientation de la solution recherchée a été définie directement
Rendu avec canvas
- Pour s’affranchir du flux MJPEG, l’implémentation utilise
canvas, l’élément de base du navigateur pour la manipulation d’images - L’image brute reçue du reMarkable est lue dans un
Uint8Array, puis la même valeur est placée dans les canaux R/G/B des données de pixels RGBA d’unImageData, avec une valeur alpha fixée à 255 pour l’affichage - Afin de permettre un affichage responsive, la rotation et une éventuelle colorisation, un
fixedCanvasde taille fixe est conservé masqué - Le canevas d’affichage copie le contenu du canevas masqué avec
drawImage - Lorsque la taille de la fenêtre du navigateur change, la largeur et la hauteur du canevas d’affichage sont ajustées à partir de la taille du conteneur et du ratio 1872/1404
Abandon de WebSocket au profit d’un flux brut
- gRPC n’étant pas un choix courant en développement web, la première implémentation de remplacement utilisait WebSocket comme moyen de communication et d’encapsulation
- Les messages WebSocket contenaient les images brutes, et le client navigateur mettait à jour le canevas à chaque message reçu pour donner l’impression d’un streaming
- Cette approche permettait de gérer la charge mémoire et CPU du reMarkable en ajustant la fréquence d’envoi des messages côté serveur
- Mais des problèmes apparaissaient sur iOS, et la surcharge de l’implémentation WebSocket côté serveur restait difficile à contrôler
- L’architecture finale supprime l’encapsulation et transmet directement l’image brute sur le réseau en s’appuyant sur une taille d’image fixe
- Le serveur Go
Writeles images en boucle danshttp.ResponseWriter - Le client navigateur lit le
ReadableStreamdefetch('/stream')et répercute les chunks reçus dans les données du canevas
- Le serveur Go
Optimisation du volume transmis
- L’image brute du reMarkable 2 pèse environ 2,5 Mo en résolution 1872x1404, et ces données doivent être transmises à chaque trame
- Depuis le firmware 3.3, les 16 valeurs de couleur du reMarkable peuvent être représentées sous forme de tableau uint4 au lieu de uint8
- Go et JavaScript ne disposent pas de type uint4 natif
- Le contournement consiste à stocker deux valeurs de pixel dans un seul octet uint8
- En Go, deux valeurs uint4 sont empaquetées dans les 4 bits de poids fort et les 4 bits de poids faible ; en JavaScript, elles sont ensuite dépaquetées
- Cette représentation permet de réduire le volume de données de 50 %
- La compression supplémentaire utilise le Run Length Encoding (RLE)
- RLE est un algorithme simple qui transmet ensemble le nombre de répétitions consécutives d’une même valeur de pixel et cette valeur
- L’exemple
0 0 0 0 0 0 1 1 1 0 0 0 0devient6 0 3 1 4 0
- La valeur de comptage pouvant atteindre 1872*1404, un type comme uint64 pourrait être nécessaire, et dans certains cas le résultat compressé pourrait devenir plus gros que l’original
- Pour éviter cela, la longueur du compteur est limitée à 15, et un compromis a été choisi en plaçant le compteur et la valeur de pixel dans un seul octet
- L’implémentation RLE se comporte comme un
io.Writeren Go, ce qui la rend réutilisable ; RLE pourrait aussi être appliqué deux fois si nécessaire, mais cela n’a pas été utile jusqu’à présent - Après empaquetage et RLE, le volume moyen transmis est d’environ 200 Ko
N’envoyer des trames qu’en cas de changement
- La dernière optimisation consiste à n’envoyer une nouvelle trame que lorsque l’écran change
- Calculer le changement via une somme de contrôle pourrait alourdir la charge CPU
- Comme le reMarkable repose sur Linux, les entrées stylet ou tactiles sont transmises via
/dev/input/event* - Une goroutine surveille ces événements d’entrée et n’envoie l’image qu’en cas de besoin
- En l’absence d’événement, l’utilisation CPU retombe à 0 même lorsqu’un client est connecté
- Pendant l’écriture, l’utilisation CPU est d’environ 10 %
Changements de firmware et charge de maintenance
- Cette application repose sur du hacking, et le principal défi consiste à bien séparer l’interface de récupération des images du client et du moteur de rendu
- L’implémentation précédente découplait complètement client et serveur au moyen de définitions protobuf
- Le firmware reMarkable 3.3 a cassé l’outil ; les détails figurent dans l’issue 36 sur GitHub
- À l’époque, le correctif ne touchait que le composant client
- Le firmware 3.6 pourrait également introduire des changements cassants d’après l’issue 58 sur GitHub
- Dans ce cas, des modifications plus larges pourraient être nécessaires
- Toutefois, comme le client est intégré au serveur dans une structure autonome, la mise à jour sur l’appareil pourrait devenir plus simple
- L’application et le code source sont disponibles sur github.com/owulveryck/goMarkableStream
1 commentaires
Avis de Hacker News
L’outil de 2021 qui permettait de streamer l’écran d’une tablette reMarkable vers un ordinateur portable a été repris, peaufiné et publié à nouveau ; le nouvel article détaille en profondeur l’architecture, les composants et le processus d’amélioration de l’expérience utilisateur.
Du point de vue d’un product manager, l’auteur a observé le ressenti des utilisateurs, simplifié le processus d’activation, fait en sorte que l’outil fonctionne sans service local et optimisé l’usage du réseau.
Ctrl-Cpuis un redémarrage avec./goMarkableStream, ça fonctionne dans une certaine mesure, maiswaiting for reMarkable screens’affiche encore souvent et le service reste instable.Je l’ai installé après avoir mis mon reMarkable2 à jour en 3.5.2.1807, mais rien ne réagit quand je dessine sur un carnet, une feuille ou un livre, et je vois parfois des logs comme
read /dev/input/event2: file already closedouread /dev/input/event1: file already closed.https://192.168.8.143:2001/ et https://10.11.99.1:2001/ servent tous les deux le HTML et le canvas, et j’ai aussi essayé avec Chrome, Firefox et Brave.
On dirait qu’il y a une limite d’un navigateur et d’une IP par stream, mais quelle que soit l’adresse ou le navigateur utilisés,
waiting for reMarkable screenapparaît parfois. Aprèsnohup ./goMarkableStream &, puis fermeture de PuTTY et redémarrage du client, tous les navigateurs se sont retrouvés dans le même état, et https://10.11.99.1:2001/stream renvoietoo many requests. Je me demande comment redémarrer le stream.Comme alternative, j’utilise SuperNote avec beaucoup de satisfaction. Le mirroring d’écran est possible, ce qui est très pratique pour dessiner rapidement des diagrammes pendant des réunions.
L’inconvénient, c’est que SuperNote lance un petit serveur web auquel on se connecte avec Firefox ; il faut donc que l’ordinateur portable et la SuperNote soient sur le même réseau. En télétravail, ce n’est pas un problème, mais la politique de l’entreprise peut le bloquer.
Que ce soit la RM2 ou la SuperNote, ce sont d’excellents outils pour les personnes qui aiment poser leurs idées au stylo et au papier ; la sensation est assez différente de celle d’une app ou d’un document texte, et on peut aussi gribouiller dans ses notes.
[0] : https://supernote.com/.
En revanche, à l’achat, il faut accepter une violation de la GPL. Même si l’appareil est entièrement basé sur Android, ils ne publient pas les sources du système d’exploitation.
J’ai peur d’acheter un appareil qui ne recevra pas de mises à jour fonctionnelles, ni même au minimum de mises à jour de sécurité, pendant les 3 à 5 prochaines années.
Le rendu sur canvas HTML pourrait être plus rapide en utilisant des tableaux typés, comme expliqué ici : https://hacks.mozilla.org/2011/12/faster-canvas-pixel-manipu....
C’est exactement le genre de contenu que j’aimerais voir ici. J’ai aimé la façon dont ChatGPT a aidé à apprendre et à résoudre des problèmes dans un domaine qu’il connaît mal, et la formule « j’étais le développeur et ChatGPT était le codeur » m’a parlé.
L’idée que la simplicité est en réalité complexe est juste aussi.
Le choix de JPEG vient probablement du fait qu’il est facile de passer à MJPEG et que, si on le transmet à un composant qui le prend en charge, le décodage est pratiquement gratuit. Cela dit, c’est peut-être aussi ce qui met une forte charge sur le CPU du reMarkable.
JPEG est plus adapté aux photos, alors que l’écran du reMarkable ressemble davantage à de l’illustration et, en plus, il est en niveaux de noir et blanc. D’autres formats d’image courants comme PNG, ou même une simple compression RLE, réduiraient sans doute la charge CPU.
Et le format de fichier propriétaire est basé sur les entrées manuscrites, pas sur un bitmap.
Le profiling a montré que la majeure partie du CPU était utilisée pour le transfert des données par le câble, d’où l’ajout de la compression. Aujourd’hui, l’utilisation CPU est faible.
Je me demande si vous avez envisagé de n’envoyer que les zones modifiées du framebuffer. Cela pourrait réduire fortement le débit de données : https://github.com/pl-semiotics/mxc_epdc_fb_damage
Le projet rM VNC fait aussi comme ça, mais je préfère l’expérience utilisateur de cette app, qui ne nécessite aucun logiciel côté client.
C’est vraiment sympa et j’aimerais aimer la ReMarkable 2, mais ce n’est pas simple à cause de sa réputation d’appareil non sécurisé : https://support.remarkable.com/s/article/Does-reMarkable-off...
Il ne s’agit pas de vulnérabilités logicielles connues, comme celles auxquelles on pense souvent quand on parle d’un appareil non sécurisé connecté au réseau
J’aimerais en savoir plus sur la partie : « Au début, j’ai essayé de compiler le client en WASM. Cela semblait prometteur, car je pouvais tirer parti de mon expérience de développement en Go, mais je me suis heurté à plusieurs limites qui nécessitaient d’importantes modifications »
Même en générant un flux MJPEG, il restait à savoir comment l’afficher. J’ai pensé à une approche avec canvas, mais il était difficile d’accéder au backend du canvas sans effectuer de grosses copies entre WASM et JS, et la taille atteignait 2,5 Mo
Au final, en dépendant de WASM, il semblait qu’il faudrait réimplémenter soi-même beaucoup d’opérations d’image de base accessibles nativement en JS, comme la rotation d’image
Je me demande en quoi cet outil diffère du streaming intégré, c’est-à-dire de la fonction de partage d’écran
https://support.remarkable.com/s/article/Screen-Share
La solution décrite dans l’article semble fonctionner aussi sous Linux, tant qu’on dispose d’un navigateur suffisamment capable
J’aime bien reMarkable, mais j’aimerais qu’ils se concentrent sur ce genre de fonction de streaming plutôt que sur un abonnement que je n’ai pas l’intention de payer à l’avenir