
2025/12/24 3:00
H.264 のストリーミングを JPEG スクリーンショットに置き換えた結果、より安定して動作しました。
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
以下は、すべての重要ポイントを取り入れ、根拠のない推測を排除し、不明瞭な表現を明確にした改訂版まとめです。
Summary(要約)
Helix は制限された企業ネットワーク上で信頼性の高いリモートビデオを提供するため、2 つの補完的なパスウェイを使用します。
- 主経路:カスタム WebSocket パイプライン(H.264/GStreamer + VA‑API)を HTTP/443 上で実行し、約 40 Mbps の帯域幅で 60 fps を達成し、サブ 100 ms の遅延を実現します。これは往復時間(RTT)が約 150 ms 未満の場合に機能します。
- フォールバック経路:RTT が約 150 ms を超える場合、クライアントはプレーン HTTP 上で JPEG スクリーンショットをポーリングします。各スクリーンショット(~70 % 品質、100–150 KB)は原子的かつ確実に到着し、TCP バッファリング遅延によって発生する最大 45 秒のラグを回避します。
切替は、キーボード/マウス入力に使用される同じ WebSocket 上で送信される制御メッセージ
{"set_video_enabled": false} によってトリガーされます。ビデオが一時停止している間も、低遅延チャネル上でキーボードとマウスイベントは継続します。振動を防ぐために、スクリーンショットモードが有効になった後はクライアントが「retry video」をクリックして H.264 ストリーミングを再開する必要があります。そうしない場合、ユーザーの操作があるまでスクリーンショットモードのままとなります。
企業ネットワークでは UDP、TURN、およびカスタムポートがブロックされるため WebRTC は使用できません。Helix の純粋 TCP/WebSocket ソリューションはこの制限を克服します。チームは Docker コンテナ内でスクリーンショットを生成するために、Ubuntu の
grim を JPEG サポート付き(--Djpeg=enabled)に再構築しました。すべてのコードは GitHub(github.com/helixml/helix)でオープンソース化されており、Rust、Go、および TypeScript で書かれています。
このプロジェクトは、ネットワーク条件が不安定な場合に HTTP ベースの JPEG ポーリングなどの単純でステートレスなソリューションが複雑なリアルタイムパイプラインを上回り得ることを示し、特別なポートやプロトコルを必要とせずに堅牢なリモートアクセス体験を提供できることを実証しています。
本文
パート 2 – ビデオストリーミングのサガ(Helix)
「Part 1: How we replaced WebRTC with WebSockets →」を読んでください
私たちは3か月間、WebSocket 上にハードウェアアクセラレーション付きの WebCodecs‑駆動 60fps H.264 ストリーミングパイプラインを構築しました…その後、Wi‑Fi が少し不安定になるとそれを「グリム」に置き換えました。
エンタープライズ制約
企業ネットワークは ポート 443 の HTTP/HTTPS を愛しています。
UDP は ブロックされる・優先度が下げられる・セキュリティリスクとしてドロップされる ため嫌います。つまり:
- WebRTC → TURN サーバー → UDP(ブロック)
- カスタムポート → ファイアウォールでブロック
- STUN/ICE → コーポレートネットワークで NAT トラバーサル失敗
従って、HTTPS/TCP のみを厳守する必要がありました。
私たちのピュア WebSocket パイプライン
| コンポーネント | 詳細 |
|---|---|
| エンコード | GStreamer + VA‑API で H.264(ハードウェアアクセラレーション) |
| トランスポート | バイナリフレームを WebSocket 上で送信(L7 のみ、プロキシは通過) |
| デコード | ブラウザの WebCodecs API |
| 性能 | 約 40 Mbps で 60fps、<100 ms ラティンシー |
サーバーは Rust、クライアントは TypeScript、独自バイナリプロトコルを実装。すべてをマイクロ秒単位で測定しました。
Wi‑Fi の問題
カフェでビデオがフリーズした:
- アウトバウンド UDP がブロック → TURN に到達不可 → ICE 失敗
- 40 Mbps ストリームで >200 ms ラティンシー
- TCP/WebSocket 上のフレームは順序通りに届くが、遅延が増加
- 視聴者は 45 秒前の映像を見ていた – バグは表示される前にコミット済み
ビットレートを 10 Mbps に下げても 30 秒の遅れが残った。
キーフレームだけで試す
キーフレーム(IDR)だけ送れば解決できると考えた:
- H.264 のキーは自己完結型、前フレームに依存しない
- GOP を 60 に設定(60fps で秒あたり1フレーム)
結果:1 フレームだけ – 1080p の美しい IDR とその後の無音。
Moonlight プロトコルスタックはすべてのフレームを期待しており、P‑フレームが欠落するとサーバーはデータ送信を停止した。
スクリーンショットで解決
簡易テストで JPEG スクリーンショットが最適だと判明:
// 可能な限り高速にスクリーンショットをポール(10fps に制限) const fetchScreenshot = async () => { const response = await fetch(`/api/v1/external-agents/${sessionId}/screenshot`); const blob = await response.blob(); screenshotImg.src = URL.createObjectURL(blob); setTimeout(fetchScreenshot, 100); // ヤロウ };
スクリーンショットが優れている理由:
| 特性 | H.264 ストリーム | JPEG スクリーンショット |
|---|---|---|
| 帯域幅 | 約 40 Mbps 定数 | 100–500 kbps(複雑さに応じて) |
| 状態 | 状態依存 – 欠損=死 | 独立性 – 各フレームは独自 |
| ラティンシー感度 | 非常に高い | 低い |
| パケットロスからの回復 | キーフレーム待ち(秒) | 即時 – JPEG が届けば完了 |
| 実装 | 3か月 Rust + 独自プロトコル | 簡易フェッチループ |
70 % 品質の 1080p JPEG は約 100–150 KB、単一 H.264 キーフレーム(200–500 KB)より遥かに小さい。ネットワークが劣化すると JPEG が少なくなるが、それぞれは完璧。
適応切替
-
良好な接続(RTT < 150 ms)
- WebSocket 上で 60fps H.264 をフルスピードで送信。
-
悪い接続が検知されたら
- ビデオストリームを一時停止し、
を WebSocket 経由で送信。{"set_video_enabled": false} - クライアントは HTTP でスクリーンショットポールへ切り替え。
- ビデオストリームを一時停止し、
-
接続が回復したら
- ユーザーが「Retry Video」をクリック → H.264 に戻る。
一度スクリーンショットに落ち込んだら、ユーザーが明示的に再試行するまでそこに留まるようにしてオシレーションを防止。
JPEG 対応の Grim をビルド
Ubuntu の
grim は Wayland スクリーンショットツールで JPEG 出力をサポートしますが、公式イメージには libjpeg が付属しません。Dockerfile でソースからコンパイル:
FROM ubuntu:25.04 AS grim-build RUN apt-get update && apt-get install -y \ meson ninja-build libjpeg-turbo8-dev git RUN git clone https://git.sr.ht/~emersion/grim && \ cd grim && \ meson setup build -Djpeg=enabled && \ ninja -C build
これで 2025 年版の JPEG 対応
grim を入手。
最終アーキテクチャ
┌─────────────────────────────────────────────────────────────┐ │ User's Browser │ ├─────────────────────────────────────────────────────────────┤ │ WebSocket (常に接続) │ │ ├── Video frames (H.264) ──────────── when RTT < 150ms │ │ ├── Input events (keyboard/mouse) ── always │ │ └── Control messages ─────────────── {"set_video_enabled"}│ │ │ │ HTTP (スクリーンショットポール) ──────────── when RTT > 150ms │ │ └── GET /screenshot?quality=70 │ └─────────────────────────────────────────────────────────────┘
- 良好接続時: 60fps H.264、ハードウェアアクセラレーション付き。
- 悪い接続時: 2–10fps JPEG、極めて信頼性高い。
教訓
シンプルな解決策が複雑なものより勝ることが多い。
3か月の H.264 作業 + 夜遅くにスクリーンショットを使うハックで Wi‑Fi 問題をエレガントに解決しました。
Helix はオープンソースです: https://github.com/helixml/helix
GitHub でスターを付け、実際の世界(ひとつは壊れた Wi‑Fi)で動くインフラ構築を続けてください。