reMarkable Streaming Tool v2: tăng hiệu quả làm việc từ xa
(blog.owulveryck.info)- 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.ResponseWritervà đọc bằng luồngfetch - 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ủaImageDatavà đặt alpha thành 255 để hiển thị - Để hỗ trợ hiển thị responsive, xoay và khả năng tô màu, một
fixedCanvaskí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àohttp.ResponseWriter - Client trên trình duyệt đọc
ReadableStreamcủafetch('/stream')và phản ánh các chunk nhận được vào dữ liệu canvas
- Server Go lặp lại việc
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ành6 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.Writercủ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
Ý 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
Ctrl-Crồi khởi động lại bằng./goMarkableStreamthì nó hoạt động phần nào, nhưng vẫn thường hiệnwaiting for reMarkable screenvà dịch vụ không ổn địnhTô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 closedCả https://192.168.8.143:2001/ và 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ạynohup ./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àoTô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/
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 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
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...
Đâ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
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
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
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...
Đâ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
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ể”
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
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
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