PeeringがMacの画面をiPhoneに届ける仕組み
リモートデスクトップは映像ストリームですが、見ている人が同時に操作している人でもあるという、特殊な条件があります。以下の設計上の選択はすべて、このループを短く保つことから来ています。
キャプチャ:システムに映像を頼む
MacアプリはAppleのScreenCaptureKitでフレームを取得します。取得元はディスプレイ全体、1つのアプリのすべてのウィンドウ、 または1つのウィンドウで、接続を切らずに切り替えられます。macOSが初回に画面収録の権限を求めるのもこのためで、 許可がないと、Macは真っ黒な画面しか渡せません。
エンコード:低遅延で、並べ替えなし
フレームはMacのAppleのVideoToolboxエンコーダーに渡され、iPhoneがデコードできるほうのH.264またはHEVCになります。 設定は、ストリーミングサービスが使うものとは正反対です。
- Bフレームなし。Bフレームは未来のフレームを必要とするので、使うと映像を保留しなければなりません。使わなければ、デコード順がそのまま表示順になり、待つものがありません。
- 低遅延のレート制御。エンコーダーは先読みをせず、その場でビットを使っていきます。
- 少なくとも2秒ごとのキーフレーム。新しい視聴者が加わったとき、再開したとき、取得元を切り替えたときにはすぐに送るので、失われた映像は素早く回復します。
- 画面に合わせて変わるビットレート。初期値は、画素数にフレームレートと0.12を掛けた値で、4から30 Mbpsの範囲に収め、初期設定は毎秒60フレームです。
デコーダーの設定情報(パラメータセット)は、フレームごとに繰り返さず、独立したメッセージとして1回だけ送ります。 途中から参加したiPhoneは、そのメッセージを受け取り、続いてキーフレームを受け取ります。
送信:捨てられるフレームと、守られるキーフレーム
各フレームには、25バイトの小さなヘッダーが付きます。キーフレームかどうか、ピクセルサイズ、表示時刻です。 リレー経由でも直接の接続でも、フレームは約1,080バイトのデータグラムに分割され、 2台のデバイスだけが知る鍵でChaCha20-Poly1305により封をされます。 各データグラムのカウンターは64エントリのリプレイウィンドウに渡されるので、取得されたパケットを後から再生することはできません。
輻輳に対するルールは単純です。映像は捨ててよく、キーフレームは捨てません。古いフレームがまだ送信待ちのうちに新しいフレームの準備ができたら、 古いほうが置き換えられます。ただし、未配信のキーフレームが通常のフレームに置き換えられることはありません。 存在したすべてのフレームを見せるより、今の画面を見せることのほうが大切です。
自宅のネットワークでは、映像は専用のTCP接続を通ります。ネットワークが十分に信頼できるので、同じルールが働くことはまずありません。
どのソケットもTCP_NODELAYを設定しています。小さな書き込みをまとめる初期の動作は、1ホップごとに数十ミリ秒を足します。
ダウンロードでは気づきませんが、カーソルを動かしているときには非常にはっきり分かります。
回復:損失は遅延ではなく映像で払う
UDPは再送しないので、データグラムが失われるとフレームが乱れます。iPhoneはそれを待ちません。デコードを続け、 乱れは最大2秒後の次のキーフレームで消えます。Wi-Fiの調子が悪かったあとに映像がにじんで見えたら、これが起きています。 映像を一時停止して再開すると、すぐにキーフレームが送られます。
高速な経路がまったく届かなくなった場合、接続についての記事で説明したとおり、 ストリームは低いビットレートで制御用の接続にフォールバックし、高速な経路が回復すると戻ります。
表示:Macではなく、iPhoneに合わせる
iPhoneは画質の段階を選び、毎秒のフレーム数を減らすように頼むこともできます。Mac側はそれを妥当な範囲に収めます。 アプリはデスクトップ全体を画面に収めることも、画面いっぱいに広げることもでき、iPhoneの形をした仮想ディスプレイをMacに頼むこともできるので、 横長のモニターを縦長の画面に押し込むことがありません。映像上のタップは逆方向に届きます。それは タップがクリックになる仕組みで扱っています。
よくある質問
PeeringはどのビデオコーデックをMacで使いますか?
H.264またはHEVCで、MacのVideoToolboxでエンコードします。Bフレームなし、低遅延の設定です。どちらを使うかは、iPhoneがデコードできるほうをMacに伝えます。
映像が一瞬にじむことがあるのはなぜですか?
ネットワークのパケットが失われるとフレームが乱れ、映像は少なくとも2秒ごとに届く次のキーフレームで回復します。Peeringは、再送を待つよりも、一瞬のにじみを選びます。