3 điểm bởi GN⁺ 2023-08-21 | 1 bình luận | Chia sẻ qua WhatsApp
  • Công cụ dùng để chia sẻ bản phác thảo trên reMarkable 2 trong lúc họp video đã được thay đổi để có thể mở mà không cần dịch vụ cục bộ trên laptop, giúp người thuyết trình có thể bắt đầu streaming tức thì chỉ với trình duyệt
  • Kiến trúc mới được đơn giản hóa theo cách máy chủ HTTP bên trong reMarkable và JavaScript client trên trình duyệt nhận ảnh thô rồi vẽ lên canvas
  • Phương án thay thế bằng WebSocket hoạt động được nhưng vẫn còn vấn đề trên iOS và overhead phía máy chủ, nên cuối cùng được chốt thành cách stream thô bằng việc liên tục ghi ảnh có độ phân giải cố định bằng http.ResponseWriter và đọc bằng luồng fetch
  • Mỗi khung hình thô 1872x1404 có dung lượng khoảng 2.5MB, nên từ firmware 3.3 trở đi các giá trị 16 màu được đóng gói bằng uint4 để giảm 50%, rồi tiếp tục dùng RLE để hạ xuống mức trung bình khoảng 200KB dữ liệu truyền tải
  • Bằng cách theo dõi /dev/input/event*, hệ thống sẽ ngừng gửi khung hình mới khi không có đầu vào; ngay cả khi có client đang kết nối thì mức dùng CPU vẫn giảm về 0, và khi đang viết chỉ chạy ở khoảng 10% CPU

Vì sao công cụ cũ lại bất tiện

  • Công cụ streaming reMarkable được tạo từ năm 2021 từng được dùng để chia sẻ bản phác thảo trong các cuộc họp video, và chỉ cần chia sẻ tab trình duyệt nên dễ tập trung hơn vào phần trình bày
  • Cách triển khai cũ được chia thành ba thành phần
    • Server: chạy trên thiết bị reMarkable và xuất ảnh thô của màn hình hiện tại
    • Client: trên laptop, lấy ảnh thô từ server rồi xử lý sang định dạng mà trình duyệt có thể xem
    • Renderer: hiển thị lên màn hình bằng cách đọc luồng HTTP MJPEG như trong trình duyệt hoặc VLC
  • Để giảm mức dùng CPU trên thiết bị, server chỉ trích xuất ảnh khi có client kết nối, và dùng gRPC cho việc giao tiếp
  • Client trên laptop lặp lại việc lấy ảnh, mã hóa sang JPEG rồi cung cấp luồng MJPEG dưới dạng dịch vụ HTTP
  • Trong môi trường thuyết trình, việc cấu hình mạng như địa chỉ reMarkable, quyền chạy client và IP của client mà renderer cần biết trở thành gánh nặng

Kiến trúc mới mở chỉ bằng trình duyệt

  • Mục tiêu mới là chỉ cần nhập địa chỉ reMarkable trên bất kỳ trình duyệt nào cũng có thể truy cập luồng stream
  • Kiến trúc được đổi sang loại bỏ client riêng trên laptop và đưa máy chủ HTTP vào bên trong thành phần server của reMarkable
  • Client chạy trong trình duyệt cần ở dạng JavaScript hoặc WASM
    • Ban đầu đã cân nhắc biên dịch sang WASM để tận dụng kinh nghiệm với Go, nhưng phải từ bỏ vì những hạn chế đòi hỏi sửa đổi đáng kể
    • Phiên bản client thứ hai cuối cùng được viết bằng JavaScript
  • ChatGPT được dùng trong quá trình lấy các đoạn mã JavaScript và phần giải thích, nhưng hướng giải pháp mong muốn là do tác giả tự quyết định

Cách render bằng canvas

  • Để rời khỏi luồng MJPEG, công cụ sử dụng canvas — thành phần cơ bản để thao tác ảnh trong trình duyệt
  • Ảnh thô nhận từ reMarkable được đọc thành Uint8Array, sau đó cùng một giá trị được ghi vào R/G/B trong dữ liệu pixel RGBA của ImageData và đặt alpha thành 255 để hiển thị
  • Để hỗ trợ hiển thị responsive, xoay và khả năng tô màu, một fixedCanvas kích thước cố định được giữ ở trạng thái ẩn
  • Nội dung của canvas ẩn được sao chép sang canvas hiển thị bằng drawImage
  • Khi kích thước cửa sổ trình duyệt thay đổi, chiều rộng và chiều cao của canvas hiển thị được điều chỉnh theo kích thước container và tỷ lệ 1872/1404

Bỏ WebSocket và chuyển sang stream thô

  • Vì gRPC không phải lựa chọn phổ biến trong phát triển web, cách thay thế đầu tiên dùng WebSocket làm phương tiện giao tiếp và đóng gói
  • Mỗi thông điệp WebSocket chứa ảnh thô, và client trên trình duyệt cập nhật canvas mỗi khi nhận được thông điệp để tạo cảm giác như đang streaming
  • Cách này cho phép kiểm soát tải bộ nhớ và CPU trên reMarkable bằng cách điều chỉnh tần suất gửi thông điệp phía server
  • Nhưng nó gặp vấn đề trên iOS, và overhead của phần cài đặt WebSocket phía server cũng khó kiểm soát
  • Kiến trúc cuối cùng loại bỏ lớp đóng gói, tận dụng kích thước ảnh cố định để truyền ảnh thô trực tiếp qua mạng
    • Server Go lặp lại việc Write ảnh vào http.ResponseWriter
    • Client trên trình duyệt đọc ReadableStream của fetch('/stream') và phản ánh các chunk nhận được vào dữ liệu canvas

Tối ưu dung lượng truyền tải

  • Ảnh thô của reMarkable 2 có độ phân giải 1872x1404 và dung lượng khoảng 2.5MB, nên lượng dữ liệu này phải được truyền ở mỗi khung hình
  • Từ firmware 3.3 trở đi, 16 giá trị màu của reMarkable có thể được biểu diễn bằng mảng uint4 thay vì uint8
    • Go và JavaScript không có kiểu uint4 native
    • Cách vòng qua là lưu hai giá trị pixel vào một byte uint8
    • Trong Go, hai giá trị uint4 được pack vào 4 bit cao và 4 bit thấp; trong JavaScript thì unpack ngược lại
    • Cách biểu diễn này có thể giảm 50% lượng dữ liệu
  • Phần nén bổ sung dùng Run Length Encoding (RLE)
    • RLE là thuật toán đơn giản gửi cùng nhau số lần lặp liên tiếp của cùng một giá trị pixel và chính giá trị đó
    • Ví dụ 0 0 0 0 0 0 1 1 1 0 0 0 0 được biểu diễn thành 6 0 3 1 4 0
  • Giá trị đếm có thể tăng tới 1872*1404 nên có thể cần kiểu như uint64, và trong một số trường hợp còn có nguy cơ kết quả nén lớn hơn dữ liệu gốc
  • Để tránh điều đó, tác giả giới hạn độ dài đếm ở 15 và chọn điểm cân bằng là đặt cả giá trị đếm lẫn giá trị pixel vào cùng một byte
  • Phần cài đặt RLE hoạt động giống io.Writer của Go nên có thể tái sử dụng; nếu cần còn có thể áp dụng RLE hai lần, nhưng hiện tại chưa cần
  • Sau khi pack và áp dụng RLE, dung lượng truyền tải trung bình còn khoảng 200KB

Chỉ gửi khung hình khi có thay đổi

  • Tối ưu cuối cùng là chỉ gửi khung hình mới khi màn hình thay đổi
  • Nếu dùng checksum để kiểm tra thay đổi thì có thể tạo thêm gánh nặng CPU
  • reMarkable chạy trên nền Linux nên đầu vào từ bút hoặc cảm ứng được chuyển qua /dev/input/event*
  • Một goroutine theo dõi các sự kiện đầu vào này và chỉ gửi ảnh khi thực sự cần
  • Nếu không có sự kiện, ngay cả khi client vẫn đang kết nối thì mức dùng CPU cũng giảm về 0
  • Khi đang viết, mức dùng CPU vào khoảng 10%

Thay đổi firmware và gánh nặng bảo trì

  • Ứng dụng này dựa trên hack, và bài toán cốt lõi là tách hiệu quả giao diện lấy ảnh khỏi client/renderer
  • Cách triển khai trước đây tách biệt hoàn toàn client và server thông qua định nghĩa protobuf
  • Firmware reMarkable 3.3 đã làm công cụ ngừng hoạt động, nội dung liên quan có trong GitHub issue 36
    • Khi đó bản sửa chỉ ảnh hưởng đến thành phần client
  • Theo GitHub issue 58, firmware 3.6 cũng có thể mang tới thay đổi phá vỡ tương thích
    • Trường hợp này có thể cần sửa đổi rộng hơn
    • Tuy vậy, vì client đã được tích hợp vào server trong một cấu trúc tự chứa nên việc cập nhật trên thiết bị có thể trở nên đơn giản hơn
  • Ứng dụng và mã nguồn được cung cấp tại github.com/owulveryck/goMarkableStream

1 bình luận

 
GN⁺ 2023-08-21
Ý kiến trên Hacker News
  • Công cụ năm 2021 dùng để stream màn hình máy tính bảng reMarkable lên laptop đã được trau chuốt lại và công bố, còn bài viết mới đi sâu vào kiến trúc, các thành phần và quá trình cải thiện trải nghiệm người dùng
    Từ góc nhìn của một quản lý sản phẩm, tác giả quan sát cảm nhận của người dùng để đơn giản hóa quy trình kích hoạt, khiến công cụ hoạt động không cần dịch vụ cục bộ và tối ưu cả lưu lượng mạng

    • Trông như một dự án thú vị. Sau khi Ctrl-C rồi khởi động lại bằng ./goMarkableStream thì nó hoạt động phần nào, nhưng vẫn thường hiện waiting for reMarkable screen và dịch vụ không ổn định
      Tôi đã nâng reMarkable2 lên 3.5.2.1807 rồi cài đặt, nhưng vẽ lên laptop, sheet hay sách đều không có phản hồi, thỉnh thoảng thấy log như read /dev/input/event2: file already closed, read /dev/input/event1: file already closed
      Cả https://192.168.8.143:2001/https://10.11.99.1:2001/ đều trả về HTML và canvas, tôi cũng đã thử trên Chrome, Firefox và Brave
      Có vẻ như giới hạn một trình duyệt, một IP cho mỗi stream, nhưng dùng địa chỉ và trình duyệt nào thì đôi khi vẫn hiện waiting for reMarkable screen. Sau khi chạy nohup ./goMarkableStream &, đóng PuTTY rồi khởi động lại client, tất cả trình duyệt đều rơi vào cùng trạng thái, và khi xem https://10.11.99.1:2001/stream thì trả về too many requests. Tôi muốn biết phải khởi động lại stream như thế nào
    • Không biết có thể triển khai qua kết nối USB không
  • Tôi đang dùng SuperNote như một lựa chọn thay thế và rất hài lòng. Nó có thể mirror màn hình nên rất tuyệt để vẽ nhanh sơ đồ trong cuộc họp
    Điểm trừ là SuperNote chạy một web server nhỏ rồi truy cập bằng Firefox, nên laptop và SuperNote phải ở cùng một mạng. Ở home office thì không vấn đề gì, nhưng có thể bị chặn theo chính sách công ty
    Dù là RM2 hay SuperNote, đây đều là công cụ tuyệt vời cho những ai thích viết ý tưởng bằng bút và giấy; cảm giác khá khác so với ứng dụng hay tài liệu văn bản, và cũng có thể vẽ nguệch ngoạc vào ghi chú
    [0]: https://supernote.com/

    • Onyx Boox Note cũng hoạt động tốt và vẫn phát hành cập nhật sau hơn 5 năm
      Tuy nhiên khi mua thì phải chấp nhận việc họ vi phạm GPL. Dù hoàn toàn dựa trên Android, họ không công bố mã nguồn hệ điều hành
    • Tôi đang tìm một máy tính bảng mực điện tử để vừa đọc ebook vừa ghi chú, nên lời giới thiệu này rất hữu ích. Tôi đang phân vân giữa Remarkable 2 và Boox, và muốn biết trải nghiệm cập nhật phần mềm của SuperNote ra sao
      Tôi lo sẽ mua phải một thiết bị không được hỗ trợ cập nhật tính năng, hoặc ít nhất là cập nhật bảo mật, trong 3–5 năm tới
    • Theo tôi nhớ thì SuperNote không tuân thủ GPL của phần mềm được phân phối kèm, không biết họ đã thay đổi lập trường chưa
    • Tôi chưa gặp vấn đề “phải ở cùng một mạng”, nhưng việc thêm tính năng Ngrok native có vẻ là việc đơn giản chỉ mất vài phút. Như vậy có thể stream qua Internet
    • Không biết cảm giác viết của SuperNote so với RM2 như thế nào
  • Việc render HTML canvas có thể nhanh hơn nếu dùng typed array như mô tả ở đây: https://hacks.mozilla.org/2011/12/faster-canvas-pixel-manipu...

    • Cảm ơn, tôi sẽ xem thử
  • Đây chính là kiểu nội dung tôi muốn thấy ở đây. Tôi thích cách bài viết cho thấy ChatGPT đã giúp học và giải quyết một vấn đề trong lĩnh vực mà nó không rành lắm, và tôi đồng cảm với câu “tôi là developer còn ChatGPT là coder
    Câu nói rằng sự đơn giản thực ra rất phức tạp cũng đúng

  • Có lẽ chọn JPEG là vì dễ chuyển sang MJPEG, và nếu giao cho bên hỗ trợ xử lý thì việc decode gần như miễn phí. Tuy nhiên đó có thể là yếu tố tạo tải lớn cho CPU của reMarkable
    JPEG phù hợp hơn với ảnh chụp, còn màn hình reMarkable gần với minh họa hơn, lại còn chủ yếu là đen trắng. Dùng một định dạng ảnh phổ biến khác như PNG, hoặc chỉ nén RLE đơn giản, có lẽ sẽ giảm tải CPU hơn

    • Nói chính xác thì reMarkable không phải đơn sắc mà là thang xám, và theo tôi nhớ hỗ trợ 16 mức xám. Trong ứng dụng đi kèm còn có mực màu, như bút màu xanh dương/đỏ và bút highlight màu vàng/xanh lá
      Và định dạng file riêng của nó dựa trên nét bút nhập vào, không phải bitmap
    • Lý do ban đầu chọn JPEG là đúng. Tuy nhiên vì vậy tôi đã chọn cấu trúc client/server, và việc mã hóa được thực hiện trên laptop là client, không phải trên máy tính bảng
      Khi profiling, phần lớn CPU được dùng cho việc truyền dữ liệu qua đường truyền, nên tôi đã thêm nén. Hiện tại mức sử dụng CPU thấp
  • Không biết đã cân nhắc cách chỉ truyền những vùng thay đổi của framebuffer chưa. Như vậy có thể giảm đáng kể tốc độ truyền dữ liệu: https://github.com/pl-semiotics/mxc_epdc_fb_damage
    Dự án rM VNC cũng làm như vậy, nhưng tôi thích trải nghiệm người dùng của ứng dụng này hơn vì không cần phần mềm phía client

    • Vấn đề của cách đó là nó đòi hỏi một mức phân tích nhất định trên thiết bị, còn tôi muốn giữ code ít xâm lấn nhất có thể. Tôi sẽ xem có cách nào xử lý rẻ hơn không
  • Thật sự rất tuyệt và tôi muốn thích ReMarkable 2, nhưng vì lập trường rằng đây là thiết bị không an toàn, nên không dễ: https://support.remarkable.com/s/article/Does-reMarkable-off...

    • Nội dung trong liên kết có nghĩa là thiết bị này chỉ có mức bảo mật vật lý tương đương giấy mà nó muốn thay thế. Tức là nếu ai đó tiếp cận được thiết bị thì có thể đọc được
      Đây không phải là chuyện về các lỗ hổng phần mềm đã biết mà người ta thường nghĩ đến khi nói về một thiết bị không an toàn được kết nối mạng
    • Một cách không chính thức, có thể mã hóa thư mục home dựa trên gocryptfs: https://github.com/RedTeamPentesting/remarkable-encryption
    • Phần mềm hiện tại cũng rất hạn chế. Tiếc là hãng không chính thức cho phép dùng marketplace hay tiện ích mở rộng trên thiết bị
    • Có máy đọc sách điện tử nào cung cấp mã hóa toàn bộ đĩa không?
  • Tôi muốn đọc thêm về đoạn “Ban đầu tôi định biên dịch client sang WASM. Việc có thể tận dụng kinh nghiệm phát triển Go trông có vẻ hứa hẹn, nhưng tôi đã gặp nhiều giới hạn đòi hỏi phải sửa đổi đáng kể”

    • Vấn đề cốt lõi là thư viện gRPC, và hiện mức hỗ trợ rất hạn chế. Ngoài ra, nén JPEG trong Go chậm và tốn nhiều CPU
      Ngay cả khi tạo được luồng MJPEG thì hiển thị nó thế nào cũng là vấn đề. Tôi đã nghĩ đến cách dùng canvas, nhưng khó truy cập backend canvas mà không sao chép lượng lớn dữ liệu giữa WASM và JS, và kích thước cũng là 2,5MB
      Cuối cùng, nếu dựa vào WASM thì có vẻ sẽ phải tự triển khai rất nhiều thao tác hình ảnh cơ bản vốn có thể truy cập sẵn trong JS, như xoay ảnh
  • Tôi tò mò công cụ này khác gì với streaming tích hợp sẵn, tức tính năng chia sẻ màn hình

    • Để dùng tính năng tích hợp sẵn thì phải cài ứng dụng desktop, và theo tôi biết thì không có phiên bản Linux
      https://support.remarkable.com/s/article/Screen-Share
      Giải pháp trong bài có vẻ chạy được trên Linux miễn là có một trình duyệt đủ đầy đủ tính năng
    • Khác biệt lớn nhất là giờ không cần cài client nữa. Chỉ cần nhập địa chỉ reMarkable vào trình duyệt là có thể xem nội dung
    • Tôi cứ tưởng tính năng này đã có rồi. Chia sẻ màn hình đủ ổn và tôi cũng đang dùng cho live streaming
  • Tôi thích reMarkable, nhưng mong họ tập trung vào những tính năng streaming như thế này hơn là gói đăng ký mà tôi cũng sẽ không có ý định trả tiền trong tương lai