3 Punkte von GN⁺ 2023-08-21 | 1 Kommentare | Auf WhatsApp teilen
  • Das Tool, mit dem während Videokonferenzen Skizzen vom reMarkable 2 geteilt wurden, wurde so umgestellt, dass es sich ohne lokalen Dienst auf dem Laptop öffnen lässt, sodass Vortragende spontan mit dem Streaming allein im Browser beginnen können
  • Die neue Architektur wurde vereinfacht: Ein interner HTTP-Server auf dem reMarkable und ein JavaScript-Client im Browser empfangen Rohbilder und zeichnen sie auf ein canvas
  • Eine WebSocket-Alternative funktionierte zwar, ließ aber iOS-Probleme und Server-Overhead offen; daher fiel die Wahl schließlich auf einen Rohdaten-Stream, bei dem Bilder mit fester Auflösung fortlaufend über http.ResponseWriter geschrieben und per fetch-Stream gelesen werden
  • Ein Rohbild mit 1872x1404 Pixeln ist etwa 2,5 MB groß; seit Firmware 3.3 werden 16 Farbwerte als uint4 gepackt, was die Größe um 50 % reduziert, und mit RLE sinkt das durchschnittliche Übertragungsvolumen auf etwa 200 KB
  • Durch Überwachung von /dev/input/event* wird das Senden neuer Frames gestoppt, wenn keine Eingaben vorliegen; selbst bei verbundenen Clients fällt die CPU-Auslastung dann auf 0, und beim Schreiben liegt sie bei etwa 10 % CPU

Warum das bisherige Tool unpraktisch war

  • Das 2021 entwickelte reMarkable-Streaming-Tool wurde genutzt, um während Videokonferenzen Skizzen zu teilen, und da nur ein Browser-Tab geteilt werden musste, konnte man sich leichter auf die Präsentation konzentrieren
  • Die bisherige Implementierung bestand aus drei Komponenten
    • Server: Läuft auf dem reMarkable-Gerät und stellt das Rohbild des aktuellen Bildschirms bereit
    • Client: Holt auf dem Laptop die Rohbilder vom Server und verarbeitet sie in ein vom Browser darstellbares Format
    • Renderer: Liest einen HTTP-MJPEG-Stream, etwa im Browser oder in VLC, und zeigt ihn an
  • Um die CPU-Auslastung des Geräts zu senken, extrahierte der Server Bilder nur dann, wenn ein Client verbunden war; für die Kommunikation wurde gRPC verwendet
  • Der Laptop-Client holte die Bilder wiederholt ab, kodierte sie als JPEG und stellte dann per HTTP einen MJPEG-Stream bereit
  • In Präsentationsumgebungen waren Netzwerkeinstellungen wie die reMarkable-Adresse, Ausführungsrechte für den Client und die Client-IP, die der Renderer kennen musste, eine Belastung

Die neue Architektur: nur noch der Browser

  • Das neue Ziel war, den Zugriff auf den Stream in jedem Browser allein durch Eingabe der reMarkable-Adresse zu ermöglichen
  • Der separate Laptop-Client wurde entfernt und stattdessen ein HTTP-Server direkt in die Server-Komponente des reMarkable integriert
  • Der im Browser laufende Client musste als JavaScript oder WASM umgesetzt werden
    • Zunächst wurde geprüft, den vorhandenen Go-Erfahrungsschatz für eine WASM-Kompilierung zu nutzen, doch wegen Einschränkungen, die umfangreiche Änderungen erfordert hätten, wurde das verworfen
    • Der Client der zweiten Version wurde schließlich in JavaScript geschrieben
  • Beim Erzeugen von JavaScript-Codefragmenten und Erklärungen wurde ChatGPT verwendet, die Richtung der gewünschten Lösung wurde jedoch selbst festgelegt

Rendering mit canvas

  • Um sich vom MJPEG-Stream zu lösen, wurde das grundlegende Browser-Element zur Bildmanipulation verwendet: canvas
  • Das vom reMarkable empfangene Rohbild wird als Uint8Array gelesen; anschließend werden dieselben Werte in die RGBA-Pixeldaten von ImageData für R/G/B eingetragen und der Alphawert auf 255 gesetzt
  • Für responsives Anzeigen, Rotation und mögliche Einfärbung wird ein fixedCanvas mit fester Größe verborgen vorgehalten
  • In das sichtbare Canvas wird der Inhalt des verborgenen Canvas per drawImage kopiert
  • Wenn sich die Fenstergröße des Browsers ändert, werden Breite und Höhe des Anzeigecanvas anhand der Containergröße und des Verhältnisses 1872/1404 angepasst

WebSocket verworfen, Umstieg auf Rohdaten-Streaming

  • Da gRPC in der Webentwicklung keine übliche Wahl ist, nutzte die erste alternative Implementierung WebSocket als Kommunikations- und Kapselungsmittel
  • Die WebSocket-Nachrichten enthielten Rohbilder, und der Browser-Client aktualisierte das Canvas bei jeder empfangenen Nachricht, sodass es wie Streaming wirkte
  • Mit diesem Ansatz ließ sich serverseitig die Versandfrequenz der Nachrichten steuern, um Speicher- und CPU-Last des reMarkable zu verwalten
  • Allerdings gab es Probleme unter iOS, und auch der Overhead der serverseitigen WebSocket-Implementierung ließ sich nur schwer kontrollieren
  • In der finalen Architektur wurde auf Kapselung verzichtet und stattdessen die feste Bildgröße genutzt, um Rohbilder direkt über das Netzwerk zu übertragen
    • Der Go-Server schreibt die Bilder wiederholt per Write auf http.ResponseWriter
    • Der Browser-Client liest den ReadableStream von fetch('/stream') und überträgt eingehende Chunks in Canvas-Daten

Optimierung des Übertragungsvolumens

  • Das Rohbild des reMarkable 2 ist bei einer Auflösung von 1872x1404 etwa 2,5 MB groß, und diese Daten müssen für jeden Frame übertragen werden
  • Seit Firmware 3.3 lassen sich die 16 Farbwerte des reMarkable als uint4-Array statt als uint8 darstellen
    • Weder Go noch JavaScript haben einen nativen uint4-Typ
    • Daher werden je zwei Pixelwerte in einem uint8-Byte gespeichert
    • In Go werden zwei uint4-Werte in die oberen und unteren 4 Bit gepackt, in JavaScript wieder entpackt
    • Diese Darstellung reduziert die Datenmenge um 50 %
  • Für zusätzliche Kompression wird Run Length Encoding (RLE) verwendet
    • RLE ist ein einfacher Algorithmus, der die Anzahl aufeinanderfolgender gleicher Pixelwerte zusammen mit dem Wert überträgt
    • Beispiel: 0 0 0 0 0 0 1 1 1 0 0 0 0 wird als 6 0 3 1 4 0 dargestellt
  • Der Zählwert kann bis auf 1872*1404 anwachsen, was Typen wie uint64 erfordern könnte; zudem besteht das Risiko, dass das komprimierte Ergebnis in manchen Fällen größer als das Original wird
  • Um das zu vermeiden, wurde die Zähllänge auf 15 begrenzt und ein Kompromiss gewählt, bei dem Zählwert und Pixelwert in einem Byte untergebracht werden
  • Die RLE-Implementierung verhält sich wie ein io.Writer in Go und ist dadurch wiederverwendbar; bei Bedarf könnte RLE sogar zweimal angewendet werden, was hier aber nicht nötig war
  • Nach Packing und RLE liegt das durchschnittliche Übertragungsvolumen bei etwa 200 KB

Frames nur bei Änderungen senden

  • Die letzte Optimierung besteht darin, neue Frames nur dann zu senden, wenn sich der Bildschirm ändert
  • Eine Prüfung per Checksumme könnte die CPU unnötig belasten
  • Das reMarkable basiert auf Linux, daher werden Stift- oder Berührungseingaben über /dev/input/event* geliefert
  • Eine Goroutine überwacht diese Eingabeereignisse und sendet Bilder nur bei Bedarf
  • Wenn keine Ereignisse auftreten, fällt die CPU-Auslastung selbst bei verbundenen Clients auf 0
  • Während des Schreibens liegt die CPU-Auslastung bei etwa 10 %

Firmware-Änderungen und Wartungsaufwand

  • Diese Anwendung basiert auf Hacking; die zentrale Herausforderung ist, die Schnittstelle zum Abrufen von Bildern sowie Client und Renderer wirksam voneinander zu trennen
  • In der früheren Implementierung waren Client und Server über Protobuf-Definitionen vollständig getrennt
  • Die Firmware 3.3 des reMarkable machte das Tool funktionsunfähig; Details dazu stehen im GitHub-Issue 36
    • Die damalige Korrektur betraf nur die Client-Komponente
  • Auch Firmware 3.6 könnte laut GitHub-Issue 58 inkompatible Änderungen mit sich bringen
    • In diesem Fall könnten umfassendere Anpassungen nötig sein
    • Da der Client jedoch in den Server integriert ist und die Struktur in sich geschlossen ist, könnten Updates auf dem Gerät einfacher werden
  • Die Anwendung und der Quellcode sind unter github.com/owulveryck/goMarkableStream verfügbar

1 Kommentare

 
GN⁺ 2023-08-21
Hacker-News-Kommentare
  • Das Tool von 2021, mit dem der Bildschirm eines reMarkable-Tablets auf einen Laptop gestreamt wurde, wurde überarbeitet und erneut veröffentlicht; der neue Beitrag behandelt ausführlich Architektur, Komponenten und den Prozess zur Verbesserung der User Experience.
    Aus Sicht eines Produktmanagers wurde anhand des Nutzererlebnisses der Aktivierungsprozess vereinfacht, der Betrieb ohne lokalen Dienst ermöglicht und auch die Netzwerknutzung optimiert.

    • Sieht nach einem tollen Projekt aus. Nach Ctrl-C und einem Neustart mit ./goMarkableStream funktionierte es einigermaßen, aber weiterhin erscheint häufig waiting for reMarkable screen, und der Dienst ist instabil.
      Ich habe es installiert, nachdem ich das reMarkable2 auf 3.5.2.1807 aktualisiert hatte, aber beim Zeichnen auf Laptop, Blatt oder Buch gab es keine Reaktion; gelegentlich sehe ich Logs wie read /dev/input/event2: file already closed und read /dev/input/event1: file already closed.
      Sowohl https://192.168.8.143:2001/ als auch https://10.11.99.1:2001/ liefern HTML und Canvas aus, und ich habe es auch mit Chrome, Firefox und Brave versucht.
      Es wirkt so, als gäbe es eine Beschränkung auf einen Browser und eine IP pro Stream, aber egal welche Adresse und welchen Browser ich verwende, manchmal erscheint waiting for reMarkable screen. Nachdem ich nohup ./goMarkableStream & ausgeführt, PuTTY geschlossen und den Client neu gestartet hatte, waren alle Browser im selben Zustand; bei https://10.11.99.1:2001/stream kommt too many requests zurück. Ich frage mich, wie man den Stream neu starten soll.
    • Ich frage mich, ob sich das auch über eine USB-Verbindung umsetzen ließe.
  • Als Alternative nutze ich SuperNote und bin sehr zufrieden damit. Screen Mirroring ist möglich, was sehr praktisch ist, um während Meetings schnell Diagramme zu zeichnen.
    Der Nachteil ist, dass SuperNote einen kleinen Webserver startet und man mit Firefox darauf zugreift; Laptop und SuperNote müssen also im selben Netzwerk sein. Im Homeoffice ist das kein Problem, könnte aber durch Unternehmensrichtlinien blockiert werden.
    Ob RM2 oder SuperNote: Für Leute, die Ideen gern mit Stift und Papier festhalten, sind es großartige Werkzeuge; es fühlt sich ziemlich anders an als Apps oder Textdokumente, und man kann auch in Notizen herumkritzeln.
    [0]: https://supernote.com/

    • Auch Onyx Boox Note funktioniert gut und bekommt selbst nach über 5 Jahren noch Updates.
      Beim Kauf muss man allerdings GPL-Verstöße in Kauf nehmen. Obwohl es vollständig auf Android basiert, wird der Betriebssystem-Quellcode nicht veröffentlicht.
    • Ich war auf der Suche nach einem E-Ink-Tablet als E-Book-Reader und zum Notizenmachen, daher ist die Empfehlung hilfreich. Ich schwankte zwischen Remarkable 2 und Boox und frage mich, wie die Erfahrungen mit Software-Updates bei SuperNote sind.
      Ich mache mir Sorgen, am Ende ein Gerät zu kaufen, das in den nächsten 3 bis 5 Jahren keine Funktionsupdates oder zumindest Sicherheitsupdates mehr bekommt.
    • Soweit ich mich erinnere, hat SuperNote die GPL der mitgelieferten Software nicht eingehalten; ich frage mich, ob sich ihre Haltung geändert hat.
    • Das Problem, „im selben Netzwerk sein zu müssen“, hatte ich zwar noch nicht, aber eine native Ngrok-Funktion einzubauen wirkt wie eine einfache Arbeit von ein paar Minuten. Dann könnte man über das Internet streamen.
    • Ich frage mich, wie sich das Schreibgefühl von SuperNote im Vergleich zum RM2 anfühlt.
  • Das Rendering auf dem HTML-Canvas kann schneller werden, wenn man wie hier beschrieben Typed Arrays verwendet: https://hacks.mozilla.org/2011/12/faster-canvas-pixel-manipu...

    • Danke, ich werde es mir ansehen.
  • Genau solche Beiträge möchte ich hier sehen. Mir gefiel, wie ChatGPT dabei geholfen hat, ein Problem in einem Bereich zu verstehen und zu lösen, den es nicht gut kennt, und ich konnte mich mit der Formulierung „ich war der Entwickler und ChatGPT war der Coder“ identifizieren.
    Auch die Aussage, dass Einfachheit in Wirklichkeit komplex ist, stimmt.

  • JPEG wurde vermutlich gewählt, weil es sich leicht in MJPEG umwandeln lässt und man die Decodierung quasi kostenlos bekommt, wenn man sie an eine unterstützende Seite weiterreicht. Allerdings könnte genau das die reMarkable-CPU stark belasten.
    JPEG ist eher für Fotos geeignet, der reMarkable-Bildschirm ähnelt aber eher Illustrationen und ist zudem in Schwarzweiß-/Graustufen gehalten. Andere gängige Bildformate wie PNG oder auch nur einfache RLE-Komprimierung dürften die CPU weniger belasten.

    • Streng genommen ist reMarkable nicht monochrom, sondern Graustufen, und soweit ich mich erinnere, werden 16 Graustufen unterstützt. In der Companion-App gibt es außerdem farbige Tinten, bei denen der Stift etwa blau oder rot und der Textmarker gelb oder grün erscheint.
      Und das proprietäre Dateiformat basiert nicht auf Bitmaps, sondern auf Stifteingaben.
    • Ja, der ursprüngliche Grund für JPEG stimmt. Genau deshalb habe ich mich aber für eine Client/Server-Architektur entschieden, und das Encoding wurde nicht auf dem Tablet, sondern auf dem Laptop als Client ausgeführt.
      Beim Profiling zeigte sich, dass der Großteil der CPU-Zeit für die Datenübertragung über die Leitung draufging, daher habe ich Kompression eingebaut. Jetzt ist die CPU-Auslastung niedrig.
  • Ich frage mich, ob ihr einen Ansatz geprüft habt, bei dem nur die geänderten Bereiche des Framebuffers übertragen werden. Damit ließe sich die Datenrate deutlich senken: https://github.com/pl-semiotics/mxc_epdc_fb_damage
    Das rM-VNC-Projekt macht es ebenfalls so, aber mir gefällt die User Experience dieser App besser, weil auf Client-Seite keine Software nötig ist.

    • Das Problem bei diesem Ansatz ist, dass auf dem Gerät ein gewisses Maß an Analyse nötig ist, und ich möchte den Code möglichst wenig invasiv halten. Ich werde mir ansehen, ob es dafür eine günstige Lösung gibt.
  • Wirklich cool, und ich würde das ReMarkable 2 gern mögen, aber wegen der Haltung, dass es ein unsicheres Gerät ist, fällt mir das nicht leicht: https://support.remarkable.com/s/article/Does-reMarkable-off...

    • Der Inhalt des Links bedeutet, dass dieses Gerät nur dasselbe Maß an physischer Sicherheit bietet wie das Papier, das es ersetzen soll. Das heißt: Wenn jemand Zugriff auf das Gerät hat, kann er es lesen.
      Es geht nicht um bekannte Software-Schwachstellen, an die man typischerweise denkt, wenn von einem unsicheren, vernetzten Gerät die Rede ist.
    • Inoffiziell ist eine Home-Directory-Verschlüsselung auf Basis von gocryptfs möglich: https://github.com/RedTeamPentesting/remarkable-encryption
    • Auch die Software ist derzeit sehr eingeschränkt. Schade, dass es offiziell keinen Marketplace oder Erweiterungen gibt, die man auf dem Gerät nutzen kann.
    • Gibt es E-Book-Reader, die Full-Disk-Encryption bieten?
  • Ich würde gern mehr über die Stelle lesen: „Anfangs versuchte ich, den Client nach WASM zu kompilieren. Das sah vielversprechend aus, weil ich meine Go-Entwicklungserfahrung nutzen konnte, aber ich stieß auf mehrere Einschränkungen, die erhebliche Anpassungen erfordert hätten.“

    • Das Kernproblem war die gRPC-Bibliothek, deren Unterstützung aktuell sehr begrenzt ist. Außerdem ist JPEG-Kompression in Go langsam und CPU-intensiv.
      Selbst wenn man einen MJPEG-Stream erzeugen würde, bliebe die Frage, wie man ihn anzeigt. Ich dachte an einen Canvas-Ansatz, aber ohne große Kopien zwischen WASM und JS war der Zugriff auf das Canvas-Backend schwierig, und die Größe lag bei 2,5 MB.
      Am Ende sah es so aus, als müsste man bei einer Abhängigkeit von WASM viele grundlegende Bildoperationen, die in JS nativ zugänglich sind, etwa Bildrotation, selbst implementieren.
  • Ich frage mich, wie sich dieses Tool vom eingebauten Streaming, also der Screen-Sharing-Funktion, unterscheidet.

    • Für die eingebaute Funktion muss man die Desktop-App installieren, und soweit ich weiß, gibt es keine Linux-Version.
      https://support.remarkable.com/s/article/Screen-Share
      Die im Artikel beschriebene Lösung scheint auch unter Linux zu funktionieren, solange man nur einen ausreichend leistungsfähigen Browser hat.
    • Der größte Unterschied ist, dass jetzt keine Client-Installation mehr nötig ist. Man gibt einfach die Adresse des reMarkable im Browser ein und kann den Inhalt sehen.
    • Ich dachte, diese Funktion gäbe es bereits. Screen Sharing ist ziemlich okay und ich nutze es auch für Livestreaming.
  • Ich mag reMarkable, aber ich wünschte, sie würden sich auf solche Streaming-Funktionen konzentrieren statt auf ein Abo, für das ich auch in Zukunft nicht zu zahlen gedenke.