- Helix เป็น แพลตฟอร์ม AI ที่แสดงหน้าจอการทำงานของเอเจนต์เขียนโค้ดอัตโนมัติบนคลาวด์ให้ผู้ใช้เห็น โดยหัวใจสำคัญคือการส่งภาพหน้าจอระยะไกลที่เสถียร
- เมื่อการสตรีมแบบ WebRTC ล้มเหลวเพราะ การบล็อก UDP และข้อจำกัดของไฟร์วอลล์ ในเครือข่ายองค์กร ทีมจึงสร้าง ไปป์ไลน์ H.264 บน WebSocket ขึ้นมาเอง แต่กลับพบปัญหาหน่วงรุนแรงในสภาพแวดล้อม Wi‑Fi ที่ไม่เสถียร
- แทนที่จะใช้โครงสร้างเข้ารหัส/ถอดรหัสที่ซับซ้อน ทีมพบว่าวิธี ส่งภาพหน้าจอ JPEG ผ่าน HTTP เป็นระยะ กลับเสถียรและมีประสิทธิภาพกว่ามาก
- วิธีนี้ ใช้แบนด์วิดท์น้อยกว่า ไม่ต้องกู้คืนเฟรมที่เสียหาย และ ปรับคุณภาพภาพกับอัตราเฟรมอัตโนมัติ ตามคุณภาพเครือข่าย
- สุดท้าย Helix เลือกใช้ โครงสร้างแบบไฮบริด: เมื่อการเชื่อมต่อดีใช้ H.264 แต่ถ้าแย่จะสลับไปใช้ JPEG polling ทำให้ได้ระบบสตรีมหน้าจอระยะไกลที่เรียบง่ายแต่ใช้งานได้จริง
ปัญหาและข้อจำกัดของการสตรีมใน Helix
- Helix เป็นแพลตฟอร์มที่ต้อง แชร์หน้าจอของเอเจนต์ AI สำหรับเขียนโค้ดที่ทำงานอยู่ในคลาวด์แซนด์บ็อกซ์แบบเรียลไทม์
- ผู้ใช้รับชมกระบวนการที่ AI เขียนโค้ดได้เหมือนใช้งานรีโมตเดสก์ท็อป
- ช่วงแรกใช้ WebRTC แต่การเชื่อมต่อล้มเหลวเพราะ เครือข่ายองค์กรบล็อก UDP
- ไม่ว่าจะเป็น TURN server, STUN/ICE หรือพอร์ตแบบกำหนดเอง ก็ถูกนโยบายไฟร์วอลล์บล็อกทั้งหมด
- ทีมจึงพัฒนา ไปป์ไลน์สตรีมมิง H.264 บน WebSocket ที่ใช้เฉพาะ HTTPS (พอร์ต 443) ขึ้นมาเอง
- ใช้ GStreamer + VA-API สำหรับการเข้ารหัสด้วยฮาร์ดแวร์ และ WebCodecs สำหรับการถอดรหัสบนเบราว์เซอร์
- ทำได้ถึง 60fps, 40Mbps และหน่วงต่ำกว่า 100ms
ความหน่วงของเครือข่ายและประสิทธิภาพที่ลดลง
- ในสภาพแวดล้อมเครือข่ายที่ไม่เสถียร เช่น ร้านกาแฟ พบปัญหา ภาพค้างหรือหน่วงเป็นสิบวินาที
- WebSocket ที่ทำงานบน TCP เมื่อเกิด packet loss จะทำให้เฟรมหน่วงต่อคิวกันตามลำดับจน ความเป็นเรียลไทม์พังลง
- ต่อให้ลด bitrate ลง ความหน่วงก็ไม่หาย มีแต่คุณภาพภาพที่แย่ลง
- ทีมยังลองส่งเฉพาะ keyframe แต่ก็ไม่สำเร็จ เพราะ โปรโตคอล Moonlight ต้องการ P-frame
การค้นพบวิธีภาพหน้าจอ JPEG
- ระหว่างดีบัก เมื่อเรียก endpoint
/screenshot?format=jpeg&quality=70 ก็พบว่า ภาพคมชัดโหลดขึ้นมาทันที
- JPEG ขนาด 150KB เพียงภาพเดียวแสดงผลได้โดยแทบไม่มีความหน่วง
- เมื่อเปลี่ยนมาอัปเดตภาพหน้าจอด้วยการยิง HTTP request ซ้ำ ๆ ก็ได้ การรีเฟรชหน้าจอที่ลื่นไหลราว 5fps
- สุดท้ายจึงเปลี่ยนจากไปป์ไลน์วิดีโอที่ซับซ้อน มาเป็นวิธี ขอภาพ JPEG เป็นรอบ ๆ (fetch loop) แทน
ข้อดีของวิธี JPEG
- รายการเปรียบเทียบหลักกับ H.264
- แบนด์วิดท์: H.264 ใช้คงที่ 40Mbps ส่วน JPEG แปรผันที่ 100~500Kbps
- การจัดการสถานะ: H.264 พึ่งพาสถานะ ส่วน JPEG เป็น เฟรมอิสระสมบูรณ์
- ความสามารถในการกู้คืน: H.264 ต้องรอ keyframe แต่ JPEG กู้คืนได้ทันทีในเฟรมถัดไป
- ความซับซ้อน: H.264 ใช้เวลาพัฒนาหลายเดือน ส่วน JPEG ทำได้ด้วยลูป
fetch() ไม่กี่บรรทัด
- ยิ่งคุณภาพเครือข่ายแย่ วิธี JPEG ที่เรียบง่ายก็ยิ่ง เสถียรและมีประสิทธิภาพกว่า
โครงสร้างการสลับแบบไฮบริด
- Helix สลับสองวิธีนี้อัตโนมัติโดยอิงจาก RTT (เวลาไป-กลับ)
- RTT < 150ms → สตรีมมิง H.264
- RTT > 150ms → JPEG polling
- เมื่อการเชื่อมต่อฟื้นตัว ผู้ใช้กดคลิกเพื่อสลับกลับ
- อีเวนต์อินพุต เช่น คีย์บอร์ดและเมาส์ ยังส่งผ่าน WebSocket ต่อไปเพื่อ คงความสามารถในการโต้ตอบ
- เซิร์ฟเวอร์ใช้ข้อความ
{"set_video_enabled": false} เพื่อหยุดส่งวิดีโอและสลับไปโหมดภาพหน้าจอ
ปัญหาการสลับไปมาเองไม่หยุดและวิธีแก้
- เมื่อหยุดการส่งวิดีโอแล้ว ทราฟฟิกบน WebSocket ลดลง ความหน่วงจึงต่ำลงและทำให้ ระบบสลับกลับไปโหมดวิดีโออัตโนมัติวนซ้ำไม่สิ้นสุด
- วิธีแก้คือ หลังเข้าสู่โหมดภาพหน้าจอแล้ว ให้ ค้างโหมดนี้ไว้จนกว่าผู้ใช้จะคลิกเอง
- ใน UI จะแสดงข้อความว่า “หยุดวิดีโอชั่วคราวเพื่อประหยัดแบนด์วิดท์”
ปัญหาการรองรับ JPEG และขั้นตอนบิลด์
- เครื่องมือจับภาพหน้าจอสำหรับ Wayland อย่าง grim ในแพ็กเกจ Ubuntu ที่ติดตั้งมาให้ปิดการรองรับ JPEG ไว้
- เมื่อรัน
grim -t jpeg จะขึ้นข้อผิดพลาด “jpeg support disabled”
- เพื่อแก้ปัญหานี้ ทีมจึง บิลด์ grim จากซอร์สเองใน Dockerfile พร้อมใส่ libjpeg-turbo8-dev
สถาปัตยกรรมสุดท้าย
- การเชื่อมต่อดี: H.264 ที่ 60fps พร้อมฮาร์ดแวร์เร่งความเร็ว
- การเชื่อมต่อแย่: JPEG polling ที่ 2~10fps พร้อมความน่าเชื่อถือเต็มรูปแบบ
- คุณภาพของภาพหน้าจอจะปรับอัตโนมัติตามเวลาที่ใช้ส่งข้อมูล
- ถ้าเกิน 500ms ลดคุณภาพ -10%, ถ้าต่ำกว่า 300ms เพิ่ม +5% และคงขั้นต่ำไว้ที่ 2fps
บทเรียนสำคัญ
- ทางออกที่เรียบง่ายอาจดีกว่าระบบซับซ้อน — การลองแก้ด้วย JPEG ใน 2 ชั่วโมงใช้งานได้จริงกว่าการพัฒนา H.264 นาน 3 เดือน
- graceful degradation คือหัวใจของประสบการณ์ผู้ใช้
- WebSocket เหมาะมากกับการส่งอินพุต แต่ไม่จำเป็นต้องใช้ส่งวิดีโอเสมอไป
- แพ็กเกจ Ubuntu อาจตัดฟีเจอร์บางอย่างออก — ถ้าจำเป็นต้องบิลด์เอง
- ต้องวัดผลก่อนค่อย optimize — สตรีมมิงที่ซับซ้อนไม่ใช่คำตอบเดียวเสมอไป
เปิดซอร์ส
- Helix เปิดซอร์สให้ใช้งานแล้ว โดยมีส่วนหลักดังนี้
api/cmd/screenshot-server/main.go — เซิร์ฟเวอร์ภาพหน้าจอ
MoonlightStreamViewer.tsx — ลอจิกฝั่งไคลเอนต์แบบปรับตัวตามสภาพเครือข่าย
websocket-stream.ts — ตัวควบคุมการสลับวิดีโอ
- Helix กำลังพัฒนาโดยมีเป้าหมายเป็น โครงสร้างพื้นฐาน AI ที่ใช้งานได้จริงในสภาพแวดล้อมจริง
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
ตอนที่เครือข่ายแย่ การที่ JPEG ย่อขนาดลงได้ ไม่ใช่เพราะ UDP แต่เป็นเพราะวิธีที่ TCP ถูก implement
JPEG ไม่ได้แก้ปัญหาเรื่อง buffering หรือ congestion control แต่อย่างใด มีแนวโน้มมากกว่าว่าระบบถูกทำมาให้ลดการส่งเฟรมลงให้น้อยที่สุด
h.264 มี ประสิทธิภาพการเข้ารหัส สูงกว่า JPEG ถ้าขนาดเท่ากัน เฟรม h.264 แบบ IDR อาจให้คุณภาพที่ดีกว่าได้
ปัญหาที่แท้จริงคือ ไม่มีการประเมินแบนด์วิดท์ แม้อยู่บนสภาพแวดล้อม TCP ก็ยังปรับบิตเรตได้ผ่านการ probe แบนด์วิดท์ช่วงเริ่มต้นและการตรวจจับความหน่วงในการส่ง
ถ้าเป็นไปได้ควรใช้ WebRTC และถ้าต้องเลี่ยงไฟร์วอลล์ก็ควรใช้ WebSocket
ต่อให้ไม่พูดถึงปัญหาเรื่องรูปแบบการเขียนหรือสไตล์แบบ LLM เนื้อหาโดยรวมก็ยังมีส่วนที่ผิดเยอะมาก
10Mbps ควรเพียงพอสำหรับหน้าจอที่แทบไม่เปลี่ยนแปลง ปัญหาคือ การตั้งค่า encoding ผิด หรือคุณภาพของ encoder ต่ำ
แนวทางแบบ “ส่งแต่ keyframe” นั้นไม่มีประสิทธิภาพ และจริง ๆ แล้วแค่ตั้ง ช่วง keyframe ให้สั้นลง ก็พอ
สุดท้ายปัญหาคือ โครงสร้างที่ยัดทั้งสตรีมลงใน TCP connection เดียว ซึ่งมีโซลูชันอย่าง DASH สำหรับสถานการณ์แบบนี้อยู่แล้ว
น่าจะลองอ้างอิงวิธีที่ VNC ใช้มาตั้งแต่ปี 1998
มันคงโมเดลแบบ client-pull เอาไว้ พร้อมกับ แบ่ง framebuffer เป็นหน่วย tile แล้วส่งเฉพาะส่วนที่เปลี่ยน
ในหน้าจอเขียนโค้ดที่ค่อนข้างนิ่ง วิธีนี้ลดแบนด์วิดท์ได้มาก และถ้าเพิ่มการตรวจจับการ scroll ก็จะยิ่งมีประสิทธิภาพขึ้น
เคยทำงานกับ video encoding มาก่อน และ 40Mbps คือระดับคุณภาพ เทียบชั้น Blu-ray
มันมากเกินไปสำหรับการสตรีมข้อความธรรมดา หลังจากคุยกับ Claude ก็ได้ข้อสรุปว่า 30FPS, GOP 2 วินาที, ค่าเฉลี่ยราว 1Mbps ก็น่าจะพอ
แม้ในกรณีเลวร้ายที่สุด 1.2Mbps ก็ยังพอรักษาคุณภาพที่เสถียรได้
ปัญหาหลักของบทความนี้คือ ตั้งค่าแบนด์วิดท์ขั้นต่ำของ h.264 สูงเกินไป
H.264 มีประสิทธิภาพสูงกว่า JPEG มาก ควรเริ่มจาก 1Mbps แล้วค่อยปรับ
การใช้แต่ keyframe กลับยิ่งไม่มีประสิทธิภาพ
ถ้าเป็นผมคงเข้าหาปัญหานี้คนละแบบไปเลย
10Mbps ถือว่าสูงเกินไป และวิดีโอเขียนโค้ดบน YouTube ที่ความละเอียด 1080p ก็อยู่แค่ราว 0.6Mbps เท่านั้น แต่ยังคมชัดพอ
ผมคิดว่าลดลงเหลือ 1fps หรือปรับช่วง keyframe น่าจะดีกว่า
การสตรีมวิดีโอแบบเรียลไทม์ผ่านเบราว์เซอร์เป็นเรื่องที่ทรมานจริง ๆ
ถ้า JPEG screenshot ใช้งานได้ดีก็ปล่อยไว้อย่างนั้นน่าจะดีที่สุด
สแตกอย่าง gstreamer หรือ Moonlight ถ้าไม่เข้าใจ backpressure และการส่งต่อข้อผิดพลาด การดีบักจะกลายเป็นนรก
ชุดผสม NVIDIA Video Codec SDK + WebSocket + MediaSource Extensions เป็นทางเลือกที่พอเป็นจริงได้
แต่ถ้าบทความนี้ถูกสร้างโดย LLM ผู้เขียนก็คงไม่มีความตั้งใจจะเข้าใจโครงสร้างภายในพวกนี้
เมื่อก่อนเคยใช้โปรแกรมที่จับภาพหน้าจอทุก 5 วินาที แล้วฮาร์ดดิสก์ก็เต็มอย่างรวดเร็ว
พอรู้ว่าภาพส่วนใหญ่เหมือนเดิม ก็เริ่มคิดหา อัลกอริทึมที่เก็บเฉพาะส่วนที่เปลี่ยน
สุดท้ายถึงได้ตระหนักว่ากำลังประดิษฐ์ video compression ขึ้นมาใหม่เอง
แก้ได้ด้วย ffmpeg แค่บรรทัดเดียว และประหยัดพื้นที่เก็บข้อมูลไป 98%
การสตรีมภาพ LLM กำลังพิมพ์ด้วยแบนด์วิดท์ 40Mbps นั้น มากเกินไปแบบผิดปกติ
วิธีเดียวที่จะได้คำตอบดี ๆ บน HN คือ โพสต์อะไรที่ผิด
ผมคิดว่านี่คือตัวอย่างที่สมดุลอย่างสมบูรณ์แบบของบทความที่ผิด แต่ก็น่าสนใจพอจะกระตุ้นให้เกิดการถกเถียง