1 คะแนน โดย GN⁺ 2025-12-24 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • 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 (เวลาไป-กลับ)
    1. RTT < 150ms → สตรีมมิง H.264
    2. RTT > 150ms → JPEG polling
    3. เมื่อการเชื่อมต่อฟื้นตัว ผู้ใช้กดคลิกเพื่อสลับกลับ
  • อีเวนต์อินพุต เช่น คีย์บอร์ดและเมาส์ ยังส่งผ่าน 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

บทเรียนสำคัญ

  1. ทางออกที่เรียบง่ายอาจดีกว่าระบบซับซ้อน — การลองแก้ด้วย JPEG ใน 2 ชั่วโมงใช้งานได้จริงกว่าการพัฒนา H.264 นาน 3 เดือน
  2. graceful degradation คือหัวใจของประสบการณ์ผู้ใช้
  3. WebSocket เหมาะมากกับการส่งอินพุต แต่ไม่จำเป็นต้องใช้ส่งวิดีโอเสมอไป
  4. แพ็กเกจ Ubuntu อาจตัดฟีเจอร์บางอย่างออก — ถ้าจำเป็นต้องบิลด์เอง
  5. ต้องวัดผลก่อนค่อย optimize — สตรีมมิงที่ซับซ้อนไม่ใช่คำตอบเดียวเสมอไป

เปิดซอร์ส

  • Helix เปิดซอร์สให้ใช้งานแล้ว โดยมีส่วนหลักดังนี้
    • api/cmd/screenshot-server/main.go — เซิร์ฟเวอร์ภาพหน้าจอ
    • MoonlightStreamViewer.tsx — ลอจิกฝั่งไคลเอนต์แบบปรับตัวตามสภาพเครือข่าย
    • websocket-stream.ts — ตัวควบคุมการสลับวิดีโอ
  • Helix กำลังพัฒนาโดยมีเป้าหมายเป็น โครงสร้างพื้นฐาน AI ที่ใช้งานได้จริงในสภาพแวดล้อมจริง

1 ความคิดเห็น

 
GN⁺ 2025-12-24
ความคิดเห็นจาก Hacker News
  • ตอนที่เครือข่ายแย่ การที่ JPEG ย่อขนาดลงได้ ไม่ใช่เพราะ UDP แต่เป็นเพราะวิธีที่ TCP ถูก implement
    JPEG ไม่ได้แก้ปัญหาเรื่อง buffering หรือ congestion control แต่อย่างใด มีแนวโน้มมากกว่าว่าระบบถูกทำมาให้ลดการส่งเฟรมลงให้น้อยที่สุด
    h.264 มี ประสิทธิภาพการเข้ารหัส สูงกว่า JPEG ถ้าขนาดเท่ากัน เฟรม h.264 แบบ IDR อาจให้คุณภาพที่ดีกว่าได้
    ปัญหาที่แท้จริงคือ ไม่มีการประเมินแบนด์วิดท์ แม้อยู่บนสภาพแวดล้อม TCP ก็ยังปรับบิตเรตได้ผ่านการ probe แบนด์วิดท์ช่วงเริ่มต้นและการตรวจจับความหน่วงในการส่ง
    ถ้าเป็นไปได้ควรใช้ WebRTC และถ้าต้องเลี่ยงไฟร์วอลล์ก็ควรใช้ WebSocket

    • ในโค้ด polling ที่อยู่ในบทความ โครงสร้างเป็นแบบที่ต้องรอให้ดาวน์โหลด JPEG ก่อนหน้าจบก่อนถึงจะส่งคำขอถัดไปได้ ถึงไม่มี UDP ก็ทำลูปแบบนี้ได้
    • เป็นไปได้ว่าน่าจะ serialize เฟรมทั้งหมดแบบสมบูรณ์แล้วขอทีละเฟรม หรือไม่ก็เปิดการเชื่อมต่อใหม่ด้วยคำขอ GET ใหม่ทุกครั้ง
    • จุดที่น่าสนใจคือ ต่อให้เป็น JPEG ที่ดูเหมือนกันด้วยตาเปล่า ขนาดไฟล์ก็อาจลดลงเหลือเพียง 10~15% ได้ สมัยปลายยุค 2000 ตอนทำงานเพิ่มประสิทธิภาพเว็บ การปรับปรุงแบบนี้ให้ความรู้สึกคุ้มค่ามาก
  • ต่อให้ไม่พูดถึงปัญหาเรื่องรูปแบบการเขียนหรือสไตล์แบบ LLM เนื้อหาโดยรวมก็ยังมีส่วนที่ผิดเยอะมาก
    10Mbps ควรเพียงพอสำหรับหน้าจอที่แทบไม่เปลี่ยนแปลง ปัญหาคือ การตั้งค่า encoding ผิด หรือคุณภาพของ encoder ต่ำ
    แนวทางแบบ “ส่งแต่ keyframe” นั้นไม่มีประสิทธิภาพ และจริง ๆ แล้วแค่ตั้ง ช่วง keyframe ให้สั้นลง ก็พอ
    สุดท้ายปัญหาคือ โครงสร้างที่ยัดทั้งสตรีมลงใน TCP connection เดียว ซึ่งมีโซลูชันอย่าง DASH สำหรับสถานการณ์แบบนี้อยู่แล้ว

    • ไม่เข้าใจว่าทำไมบทความที่ AI เขียนถึงขึ้นไปอยู่ข้างบนได้ การอ่านบทความที่เขียนแบบไม่ใส่ใจเป็นการเสียเวลา
    • บน Apple ไม่รองรับ DASH โดย HLS อาจเป็นทางเลือกได้ แต่ถ้าไม่มี ffmpeg การทำจริงจะยากมาก
    • บทความนี้กลับแทบไม่มีกลิ่นอายสไตล์ LLM เลย การวิจารณ์แบบไร้หลักฐานไม่น่าโน้มน้าวใจ
    • เพราะ เวลาและต้นทุน ที่ต้องใช้เพื่อเรียนรู้เครื่องมือเดิมนั้นสูง แม้จะเป็น “การประดิษฐ์ใหม่แบบผิดทาง” ก็อาจมีประสิทธิภาพกว่าตามบริบท
  • น่าจะลองอ้างอิงวิธีที่ VNC ใช้มาตั้งแต่ปี 1998
    มันคงโมเดลแบบ client-pull เอาไว้ พร้อมกับ แบ่ง framebuffer เป็นหน่วย tile แล้วส่งเฉพาะส่วนที่เปลี่ยน
    ในหน้าจอเขียนโค้ดที่ค่อนข้างนิ่ง วิธีนี้ลดแบนด์วิดท์ได้มาก และถ้าเพิ่มการตรวจจับการ scroll ก็จะยิ่งมีประสิทธิภาพขึ้น

    • ในบรรดาข้อเสนอทั้งหมด อันนี้น่าจะเป็นจุดเริ่มต้นที่ใช้งานได้จริงที่สุด การตั้งต้นที่ 40Mbps ดูเหมือนเป็นการเข้าหาปัญหาที่ผิดตั้งแต่แรก
    • อ่านแล้วรู้สึกว่าผู้เขียนยังไม่ชำนาญ เลยสงสัยว่าแนวทางแบบนี้ทำด้วย โอเพนซอร์ส ได้หรือไม่
    • แนะนำให้ลองดู โปรเจ็กต์ neko ก่อน เพราะจัดการเรื่อง latency ของการเชื่อมต่อและปัญหา backpressure ได้ดีกว่า VNC มาก
    • การทำซ้ำแนวทางของ VNC น่าจะเป็นความพยายามแรกที่เป็นธรรมชาติที่สุด ส่วนการใช้โซลูชัน low-latency สำหรับเกมอย่าง Moonlight กลับอาจไม่เหมาะ
  • เคยทำงานกับ video encoding มาก่อน และ 40Mbps คือระดับคุณภาพ เทียบชั้น Blu-ray
    มันมากเกินไปสำหรับการสตรีมข้อความธรรมดา หลังจากคุยกับ Claude ก็ได้ข้อสรุปว่า 30FPS, GOP 2 วินาที, ค่าเฉลี่ยราว 1Mbps ก็น่าจะพอ
    แม้ในกรณีเลวร้ายที่สุด 1.2Mbps ก็ยังพอรักษาคุณภาพที่เสถียรได้

  • ปัญหาหลักของบทความนี้คือ ตั้งค่าแบนด์วิดท์ขั้นต่ำของ h.264 สูงเกินไป
    H.264 มีประสิทธิภาพสูงกว่า JPEG มาก ควรเริ่มจาก 1Mbps แล้วค่อยปรับ
    การใช้แต่ keyframe กลับยิ่งไม่มีประสิทธิภาพ

    • ในบทความบอกว่า “พอลดเหลือ 10Mbps แล้วเกิดดีเลย์ 30 วินาที” แต่นี่น่าจะเป็นปัญหาจากการตั้งค่า encoding
    • JPEG ก็สามารถบรรเทาอาการกระตุกได้ด้วยการทำ buffering เพื่อสร้าง คิวรอเล่น ผู้เล่นสมัยนี้เฝ้าตรวจสอบคุณภาพเครือข่ายแบบเรียลไทม์อยู่แล้ว
  • ถ้าเป็นผมคงเข้าหาปัญหานี้คนละแบบไปเลย
    10Mbps ถือว่าสูงเกินไป และวิดีโอเขียนโค้ดบน YouTube ที่ความละเอียด 1080p ก็อยู่แค่ราว 0.6Mbps เท่านั้น แต่ยังคมชัดพอ
    ผมคิดว่าลดลงเหลือ 1fps หรือปรับช่วง keyframe น่าจะดีกว่า

    • สำนวนและลำดับเหตุผลของบทความมีกลิ่น LLM อยู่ และโค้ดก็คงไม่ต่างกันมาก
    • 1fps อาจไม่พอ อาจต้องใช้การตั้งค่าที่ทำให้ทุกเฟรมเป็น keyframe
    • แต่สำหรับบางคน แม้แต่คุณภาพวิดีโอของ YouTube ก็อาจ น่ารำคาญจนทนไม่ได้
  • การสตรีมวิดีโอแบบเรียลไทม์ผ่านเบราว์เซอร์เป็นเรื่องที่ทรมานจริง ๆ
    ถ้า JPEG screenshot ใช้งานได้ดีก็ปล่อยไว้อย่างนั้นน่าจะดีที่สุด
    สแตกอย่าง gstreamer หรือ Moonlight ถ้าไม่เข้าใจ backpressure และการส่งต่อข้อผิดพลาด การดีบักจะกลายเป็นนรก
    ชุดผสม NVIDIA Video Codec SDK + WebSocket + MediaSource Extensions เป็นทางเลือกที่พอเป็นจริงได้
    แต่ถ้าบทความนี้ถูกสร้างโดย LLM ผู้เขียนก็คงไม่มีความตั้งใจจะเข้าใจโครงสร้างภายในพวกนี้

    • เวลาเราต้องจัดการระบบซับซ้อนแบบนี้เพื่อ วัตถุประสงค์เดียว บางครั้ง LLM ก็กลับมีประโยชน์
  • เมื่อก่อนเคยใช้โปรแกรมที่จับภาพหน้าจอทุก 5 วินาที แล้วฮาร์ดดิสก์ก็เต็มอย่างรวดเร็ว
    พอรู้ว่าภาพส่วนใหญ่เหมือนเดิม ก็เริ่มคิดหา อัลกอริทึมที่เก็บเฉพาะส่วนที่เปลี่ยน
    สุดท้ายถึงได้ตระหนักว่ากำลังประดิษฐ์ video compression ขึ้นมาใหม่เอง
    แก้ได้ด้วย ffmpeg แค่บรรทัดเดียว และประหยัดพื้นที่เก็บข้อมูลไป 98%

  • การสตรีมภาพ LLM กำลังพิมพ์ด้วยแบนด์วิดท์ 40Mbps นั้น มากเกินไปแบบผิดปกติ

    • แถมการดู “คอมพิวเตอร์กำลังพิมพ์” ที่ 60fps ก็ยังแปลกอีกด้วย ดูเหมือนเป็นแนวทางที่ไม่เข้าใจ problem domain เลย
  • วิธีเดียวที่จะได้คำตอบดี ๆ บน HN คือ โพสต์อะไรที่ผิด
    ผมคิดว่านี่คือตัวอย่างที่สมดุลอย่างสมบูรณ์แบบของบทความที่ผิด แต่ก็น่าสนใจพอจะกระตุ้นให้เกิดการถกเถียง