1 điểm bởi GN⁺ 2025-12-24 | 1 bình luận | Chia sẻ qua WhatsApp
  • Helix là một nền tảng AI cho phép các tác nhân lập trình tự động chạy trên đám mây và hiển thị màn hình hoạt động của chúng cho người dùng, nên truyền màn hình từ xa ổn định là yếu tố cốt lõi
  • Khi streaming dựa trên WebRTC thất bại do chặn UDP và ràng buộc tường lửa trong mạng doanh nghiệp, nhóm đã xây dựng pipeline H.264 dựa trên WebSocket, nhưng trong môi trường Wi‑Fi không ổn định thì độ trễ tăng nghiêm trọng
  • Thay vì cấu trúc mã hóa/giải mã phức tạp, họ phát hiện rằng cách đơn giản là gửi định kỳ ảnh chụp màn hình JPEG qua HTTP lại ổn định và hiệu quả hơn nhiều
  • Cách này dùng ít băng thông hơn, không cần khôi phục frame bị hỏng, đồng thời tự động điều chỉnh chất lượng hình ảnh và tốc độ khung hình theo chất lượng mạng
  • Cuối cùng, Helix áp dụng kiến trúc hybrid chuyển sang H.264 khi kết nối tốt, và sang JPEG polling khi kết nối xấu, hoàn thiện một hệ thống streaming từ xa đơn giản nhưng thực dụng

Vấn đề streaming và các ràng buộc của Helix

  • Helix là nền tảng cần chia sẻ theo thời gian thực màn hình của tác nhân AI viết code đang chạy trong sandbox trên đám mây
    • Người dùng theo dõi quá trình AI viết mã giống như đang xem một desktop từ xa
  • Ban đầu họ dùng WebRTC, nhưng kết nối thất bại do mạng doanh nghiệp chặn UDP
    • TURN server, STUN/ICE, cổng tùy chỉnh đều bị chặn bởi chính sách tường lửa
  • Vì vậy họ tự triển khai pipeline streaming H.264 dựa trên WebSocket chỉ dùng HTTPS (cổng 443)
    • Mã hóa phần cứng bằng GStreamer + VA-API, giải mã trên trình duyệt bằng WebCodecs
    • Đạt 60fps, 40Mbps, độ trễ dưới 100ms

Độ trễ mạng và suy giảm hiệu năng

  • Trong môi trường mạng không ổn định như quán cà phê, xuất hiện vấn đề video bị đứng hình hoặc trễ hàng chục giây
    • WebSocket dựa trên TCP khiến khi mất gói, các frame bị dồn tuần tự và tính thời gian thực sụp đổ
  • Giảm bitrate cũng không giải quyết được độ trễ, chỉ làm giảm chất lượng hình ảnh
  • Họ cũng thử cách chỉ gửi keyframe, nhưng thất bại vì giao thức Moonlight yêu cầu P-frame

Phát hiện ra cách dùng ảnh chụp màn hình JPEG

  • Trong lúc debug, khi gọi endpoint /screenshot?format=jpeg&quality=70, một hình ảnh rõ nét lập tức được tải lên
    • Một ảnh JPEG 150KB hiển thị không có độ trễ
  • Chỉ cần lặp lại yêu cầu HTTP để cập nhật ảnh chụp màn hình là đã có thể làm tươi màn hình mượt ở mức khoảng 5fps
  • Cuối cùng họ chuyển khỏi pipeline video phức tạp sang cách yêu cầu JPEG định kỳ (fetch loop)

Ưu điểm của cách JPEG

  • Các hạng mục so sánh chính với H.264
    • Băng thông: H.264 cố định 40Mbps, JPEG dao động 100~500Kbps
    • Quản lý trạng thái: H.264 phụ thuộc trạng thái, JPEG là các frame hoàn toàn độc lập
    • Khả năng phục hồi: H.264 phải chờ keyframe, JPEG có thể phục hồi ngay ở frame tiếp theo
    • Độ phức tạp: H.264 mất nhiều tháng phát triển, JPEG chỉ cần vài dòng vòng lặp fetch()
  • Mạng càng kém thì cách JPEG đơn giản lại càng ổn định và hiệu quả hơn

Cấu trúc chuyển đổi hybrid

  • Helix tự động chuyển giữa hai cách dựa trên RTT (thời gian khứ hồi)
    1. RTT < 150ms → streaming H.264
    2. RTT > 150ms → JPEG polling
    3. Khi kết nối phục hồi, người dùng bấm để chuyển lại
  • Sự kiện nhập liệu (bàn phím, chuột) vẫn tiếp tục được gửi qua WebSocket để duy trì tính tương tác
  • Server dừng truyền video và chuyển sang chế độ ảnh chụp màn hình bằng thông điệp {"set_video_enabled": false}

Vấn đề dao động chuyển đổi và cách khắc phục

  • Khi ngừng truyền, lưu lượng WebSocket giảm làm độ trễ hạ xuống, dẫn tới vòng lặp vô hạn tự chuyển lại sang chế độ video
  • Cách giải quyết: sau khi vào chế độ ảnh chụp màn hình thì giữ nguyên cho tới khi người dùng bấm
    • UI hiển thị thông báo “Video đã tạm dừng để tiết kiệm băng thông”

Vấn đề hỗ trợ JPEG và quá trình build

  • Công cụ chụp màn hình cho Wayland là grim bị tắt hỗ trợ JPEG trong gói Ubuntu mặc định
    • Khi chạy grim -t jpeg thì xuất hiện lỗi “jpeg support disabled”
  • Để giải quyết, họ build grim trực tiếp từ source trong Dockerfile, kèm libjpeg-turbo8-dev

Kiến trúc cuối cùng

  • Kết nối tốt: H.264 60fps, tăng tốc phần cứng
  • Kết nối xấu: JPEG polling 2~10fps, độ tin cậy hoàn toàn
  • Chất lượng ảnh chụp màn hình được tự động điều chỉnh theo thời gian truyền
    • Nếu vượt 500ms thì chất lượng -10%, nếu dưới 300ms thì +5%, duy trì tối thiểu 2fps

Bài học chính

  1. Giải pháp đơn giản có thể tốt hơn hệ thống phức tạp — 2 giờ “hack” với JPEG thực dụng hơn 3 tháng phát triển H.264
  2. Graceful degradation là cốt lõi của trải nghiệm người dùng
  3. WebSocket rất phù hợp để truyền input, nhưng không nhất thiết phải dùng để truyền video
  4. Gói Ubuntu có thể thiếu tính năng — khi cần thì tự build
  5. Phải đo đạc trước khi tối ưu — streaming phức tạp không phải lúc nào cũng là lời giải duy nhất

Công bố mã nguồn mở

  • Helix được cung cấp dưới dạng mã nguồn mở, với các phần triển khai chính như sau
    • api/cmd/screenshot-server/main.go — server ảnh chụp màn hình
    • MoonlightStreamViewer.tsx — logic client thích ứng
    • websocket-stream.ts — điều khiển chuyển đổi video
  • Helix đang được phát triển với mục tiêu xây dựng hạ tầng AI hoạt động được cả trong môi trường thực tế

1 bình luận

 
GN⁺ 2025-12-24
Ý kiến trên Hacker News
  • Khi mạng kém, việc JPEG co lại không phải do UDP mà do cách triển khai TCP
    JPEG không giải quyết được vấn đề buffering hay kiểm soát tắc nghẽn. Nhiều khả năng nó được triển khai theo cấu trúc giảm tối đa việc truyền frame
    h.264 có hiệu quả mã hóa cao hơn JPEG. Với cùng kích thước, frame IDR của h.264 có thể cho chất lượng tốt hơn
    Vấn đề cốt lõi là thiếu ước lượng băng thông. Ngay cả trong môi trường TCP, vẫn có thể điều chỉnh bitrate thông qua probe băng thông ban đầu và phát hiện độ trễ truyền
    Nếu có thể thì nên dùng WebRTC, còn để vượt tường lửa thì WebSocket là lựa chọn tốt

    • Trong đoạn mã polling trong bài, cấu trúc là phải tải xong JPEG trước đó thì mới gửi yêu cầu tiếp theo. Ngay cả khi không có UDP, kiểu vòng lặp này vẫn khả thi
    • Có lẽ đó là cấu trúc tuần tự hóa hoàn toàn frame, chỉ yêu cầu từng cái một, hoặc mỗi lần đều mở kết nối mới bằng một yêu cầu GET mới
    • Điểm thú vị là, ngay cả JPEG trông giống hệt nhau về mặt thị giác cũng có thể giảm dung lượng xuống còn khoảng 10~15%. Hồi cuối những năm 2000, khi làm tối ưu hiệu năng web, những cải tiến hiệu quả kiểu này rất đáng giá
  • Bỏ qua vấn đề hình thức hay văn phong kiểu LLM, thì toàn bộ nội dung có rất nhiều chỗ sai
    10Mbps lẽ ra là đủ cho màn hình tĩnh. Vấn đề là thiết lập mã hóa sai hoặc chất lượng encoder thấp
    Cách tiếp cận “chỉ gửi keyframe” là không hiệu quả; thay vào đó chỉ cần đặt khoảng cách keyframe ngắn
    Cuối cùng vấn đề nằm ở cấu trúc nhồi toàn bộ stream qua một kết nối TCP duy nhất. Đã có sẵn các giải pháp như DASH cho tình huống này

    • Không hiểu vì sao các bài do AI viết lại được đẩy lên đầu. Đọc những bài viết thiếu tâm huyết như vậy đúng là lãng phí thời gian
    • Trên Apple thì DASH không được hỗ trợ. HLS có thể là phương án thay thế, nhưng nếu không có ffmpeg thì triển khai rất vất vả
    • Bài này ngược lại gần như không có cảm giác văn phong LLM. Chỉ trích vô căn cứ thì không thuyết phục
    • thời gian và chi phí để học công cụ sẵn có là rất lớn, nên ngay cả một “sự tái phát minh sai lầm” đôi khi vẫn hiệu quả hơn tùy hoàn cảnh
  • Có lẽ nên tham khảo cách VNC đã làm từ năm 1998
    Nó giữ mô hình client pull, đồng thời chia framebuffer thành các tile, rồi chỉ truyền phần thay đổi
    Với màn hình code tĩnh, có thể giảm băng thông rất nhiều. Nếu thêm phát hiện cuộn trang thì còn hiệu quả hơn

    • Trong số nhiều đề xuất, đây có vẻ là điểm xuất phát thực tế nhất. Lấy 40Mbps làm mặc định có lẽ là sai ngay từ cách tiếp cận vấn đề
    • Bài viết cho cảm giác còn non tay. Không rõ cách tiếp cận này có thể làm được bằng mã nguồn mở hay không
    • Nên xem trước dự án neko. Nó xử lý độ trễ kết nối và vấn đề backpressure tốt hơn VNC rất nhiều
    • Sao chép cách làm của VNC có lẽ là thử nghiệm đầu tiên tự nhiên nhất. Dùng giải pháp độ trễ thấp cho game như Moonlight lại không phù hợp lắm
  • Tôi từng làm về mã hóa video, và 40Mbps là chất lượng cỡ Blu-ray
    Với việc stream văn bản đơn giản thì quá mức. Sau khi trao đổi với Claude, kết luận là 30FPS, GOP 2 giây, trung bình khoảng 1Mbps là đủ
    Ngay cả trong trường hợp xấu nhất thì 1.2Mbps cũng đủ để giữ chất lượng ổn định

  • Vấn đề chính của bài này là đặt băng thông tối thiểu cho h.264 quá cao
    H.264 hiệu quả hơn JPEG rất nhiều. Đáng ra phải bắt đầu từ 1Mbps rồi điều chỉnh
    Chỉ dùng keyframe thậm chí còn kém hiệu quả hơn

    • Bài viết nói rằng “giảm xuống 10Mbps thì bị trễ 30 giây”, nhưng nhiều khả năng đây là vấn đề thiết lập mã hóa
    • JPEG cũng có thể giảm hiện tượng giật bằng cách tạo hàng đợi phát lại thông qua buffering. Trình phát hiện nay đều giám sát chất lượng mạng theo thời gian thực
  • Nếu là tôi thì tôi sẽ tiếp cận hoàn toàn khác
    10Mbps là quá mức, còn video code trên YouTube ở 1080p chỉ khoảng 0.6Mbps. Vẫn đủ sắc nét
    Tôi nghĩ thà giảm xuống 1fps hoặc chỉnh khoảng cách keyframe còn hơn

    • Văn phong và cách triển khai lập luận của bài viết có mùi LLM. Có lẽ code cũng ở mức tương tự
    • 1fps có thể vẫn chưa đủ. Có thể cần cấu hình biến mọi frame thành keyframe
    • Nhưng với một số người, ngay cả chất lượng YouTube cũng có thể khó chịu đến mức không thể chấp nhận
  • Stream video thời gian thực vào trình duyệt đúng là một việc cực kỳ đau khổ
    Nếu ảnh chụp màn hình JPEG hoạt động tốt thì cứ để vậy còn hơn
    Những stack như gstreamer hay Moonlight sẽ thành địa ngục debug nếu không hiểu backpressure và truyền lỗi
    Tổ hợp NVIDIA Video Codec SDK + WebSocket + MediaSource Extensions là một phương án thay thế thực tế
    Nhưng nếu bài viết là do LLM tạo ra, thì có lẽ tác giả cũng không có ý định hiểu những cấu trúc bên trong này

    • Khi phải xử lý một hệ thống phức tạp như vậy cho một mục đích duy nhất, thì ngược lại LLM có thể khá hữu ích
  • Trước đây tôi từng dùng một chương trình chụp màn hình mỗi 5 giây, và ổ cứng nhanh chóng đầy lên
    Tôi nhận ra phần lớn ảnh đều giống nhau, rồi bắt đầu nghĩ đến thuật toán chỉ lưu phần thay đổi
    Cuối cùng mới nhận ra là mình đang tự phát minh lại nén video
    Giải quyết bằng một dòng ffmpeg và tiết kiệm được 98% dung lượng lưu trữ

  • Việc stream cảnh LLM đang gõ phím ở mức 40Mbps là mức băng thông quá mức một cách bất thường

    • Hơn nữa, việc xem “máy tính đang gõ” ở 60fps cũng rất kỳ lạ. Cách tiếp cận này cho thấy hoàn toàn không hiểu bài toán
  • Cách duy nhất để nhận được câu trả lời hay trên HN là đăng một bài sai
    Theo tôi, một bài sai nhưng thú vị chính là ví dụ hoàn hảo về sự cân bằng để kéo thảo luận đi lên