Peering 如何把 Mac 屏幕串流到你的 iPhone
远程桌面是一路有着特殊要求的视频流:观看的人同时也是操控的人。下面的每个设计选择,都是为了让这个回路尽可能短。
捕获:向系统索取画面
Mac 应用使用 Apple 的 ScreenCaptureKit 来获取帧。来源可以是整个显示器、某个应用的所有窗口,或单个窗口, 你可以在它们之间切换而不会断开连接。这也是 macOS 第一次会请求“屏幕录制”权限的原因: 没有它,Mac 只能交出一幅黑屏。
编码:低延迟,不重排序
帧会交给 Mac 上 Apple 的 VideoToolbox 编码器,编码为 H.264 或 HEVC,取决于手机声明自己能解码哪一种。 这些设置与流媒体服务所用的恰好相反:
- 没有 B 帧。B 帧需要一个来自未来的帧,所以使用它们就意味着要扣住画面。没有它们,解码顺序就是显示顺序,没有任何东西需要等待。
- 低延迟码率控制。编码器边走边花费比特,而不是往前看。
- 至少每两秒一个关键帧,并且在有新的观看者加入、恢复或切换来源时立即发送,所以丢失的画面能很快恢复。
- 随屏幕缩放的码率。默认值是像素数乘以帧率再乘以 0.12,限制在 4 到 30 Mbps 之间,默认帧率为每秒 60 帧。
解码器的设置信息(参数集)只作为一条独立的消息发送一次,不会在每一帧中重复。 中途加入的手机会先收到这条消息,然后是一个关键帧。
发送:帧可以丢弃,关键帧受保护
每一帧都带有一个 25 字节的小型头部:是否为关键帧、像素尺寸和呈现时间。在经中继或直连的连接上, 它会被切成约 1,080 字节的数据报,每个数据报都用 ChaCha20-Poly1305 加密,密钥只有这两台设备知道。 每个数据报中的计数器会送入一个 64 项的重放窗口,所以被截获的数据包无法在之后被重放。
拥塞时的规则很直接:视频可以丢弃,关键帧不可以。如果新的一帧已准备好,而旧的一帧还在等待发出, 旧帧就会被替换,但尚未送达的关键帧永远不会被普通帧替换。显示屏幕此刻的样子, 比显示曾经存在过的每一帧更重要。
在家庭网络中,画面改走它自己的 TCP 连接,那里的网络足够可靠,同样的规则很少需要触发。
每个套接字都设置了 TCP_NODELAY。默认把小写入批量合并的行为,每一跳都会增加几十毫秒,
这在下载时察觉不到,在你移动光标时却非常明显。
恢复:用画面的损失来换,而不是用延迟来换
UDP 不会重传,所以丢失一个数据报就意味着一帧受损。手机不会等它,而是继续解码, 损坏会在下一个关键帧时消除,最多相隔两秒。如果你在 Wi-Fi 不好的时候注意到画面有拖影, 看到的就是这个。暂停再恢复画面,会立即强制生成一个关键帧。
如果快速路径完全停止传送,串流就会以较低的码率退回到控制连接, 正如连接那篇文章所述,并在快速路径恢复后切回。
显示:适配手机,而不是适配 Mac
手机会选择一个质量档位,也可以请求更少的每秒帧数,Mac 会把它限制在合理的范围内。 应用可以让整个桌面适配屏幕,也可以填满屏幕,甚至可以请 Mac 提供一个形状与手机相同的虚拟显示器, 这样横向的显示器就不会被挤进竖向的屏幕。点击画面的操作则沿相反方向传送, 这是一次轻点如何变成一次点击要讲的内容。
常见问题
Peering 使用什么视频编解码器?
H.264 或 HEVC,由 Mac 通过 VideoToolbox 编码,没有 B 帧,并采用低延迟设置。iPhone 会告诉 Mac 它能解码哪一种。
为什么画面有时会短暂拖影?
丢失的网络数据包会让一帧受损,画面会在下一个关键帧时恢复,关键帧至少每两秒出现一次。Peering 宁可短暂拖影,也不等待重传。