
OpenDisplay信道复用设计32KB启发式如何区分JSON控制消息与H.264视频帧【免费下载链接】opendisplayFree, open-source Sidecar/Duet alternative — use your iPhone, iPad or Mac as a true second monitor for your primary Mac over USB or WiFi. Low latency H.264, Retina HiDPI, touch input.项目地址: https://gitcode.com/GitHub_Trending/op/opendisplayOpenDisplay 是一款免费开源的 Sidecar 替代工具能把 iPhone / iPad / Mac 变成主 Mac 的真正第二屏幕通过 USB 或 WiFi 以低延迟 H.264 流传输画面并支持触控输入。而它整个协议最巧妙也最野的地方在于信道复用设计一条 TCP 连接里H.264 视频帧和 JSON 控制消息混跑在同一个字节流里靠一条32KB 启发式规则就把两者区分开了。这篇文章带你从零看懂这套设计为什么成立、边界藏在哪里。为什么要信道复用一条TCP连接上跑三类流量 先看效果——把手机立在 Mac 旁边桌面直接扩展出去还能用手指操作OpenDisplay 的传输层只有一个角色约定接收端手机监听 TCP 9000 端口发送端Mac主动拨号连接。这样无论走 WiFi 还是 USB 隧道usbmuxd发送端都用同一段代码。这条连接上同时跑着三类流量流量典型大小例子H.264 视频帧每帧数百 KB屏幕画面Annex B 格式JSON 控制消息几十字节到几十 KBping/pong心跳、cursor光标位置、welcome版本握手遥测数据每 ~5 秒一条stats帧率、码率、端到端延迟不复用当然也可以——多开几条连接各走各的。但 OpenDisplay 选择了单连接多路复用USB 隧道只建一条流、一条连接断了整个会话就干净地结束、接收端也只需管理一个 socket。代价是TCP 本身不知道消息边界更不知道一条数据是什么于是就有了信道复用里最核心的一步——拆帧 分流demux。拆帧很简单所有消息都套同一个信封[4字节长度大端无符号] [payload]长度只统计 payload。TCP 不保证消息边界所以接收端要不断缓冲、按长度重新拼装。这一段在 Shared/StreamReceiver.swift 的drainFrames()里实现。但payload 到底是 JSON 还是视频帧协议文档 PROTOCOL.md 第 4 节Channel demux给出的答案只有三句话payload 长度 32768 字节即 32KB并且第一个字节是{0x7B并且payload 里不含任何 NUL 字节0x00三条全满足 → JSON 控制消息否则 → 视频帧。就这么短。32KB启发式的三条件一条3行的判断逻辑 把上面的规则翻译成接收端的代码Shared/StreamReceiver.swift 的handleAnnexBif data.count 32_768, data.first UInt8(ascii: {), !data.contains(0x00) { handleVideoChannelJSON(data) // 走 JSON 控制通道 return } // 否则当作 H.264 Annex B 视频帧解码三个条件各自守一道门长度 32KB视频帧通常比控制消息大得多一帧画面常有几百 KB先用尺寸粗筛首字节{JSON 对象一定以左花括号开头这是最便宜的类型线索无 NUL 字节真正的照妖镜下面细说。走 JSON 通道后消息再按type字段分发到pong时钟同步、pingMac 侧健康状态、cursor光标位置、cursorImg光标贴图等处理逻辑见 Shared/StreamReceiver.swift 中的handleVideoChannelJSON源码位置。H.264视频帧与NUL字节为什么绝不会被误判 ⚠️最容易被问倒的问题是万一一个视频帧很小小于 32KB、又恰好以{开头岂不是会被当成 JSON答案是H.264 的编码格式从根上堵死了这条路。OpenDisplay 的视频流采用Annex B 格式每个 NALU 之前都跟一个 4 字节起始码00 00 00 01。也就是说每一个视频帧都必然含有 NUL 字节——直接挂死在无 NUL这条规则上。而且真有一个陷阱视频帧确实以{开头。Mac 端发送时会在每帧视频前面打一行遥测前缀{cap:1755000000123,snd:1755000000145}[00 00 00 01][NALU]...cap是采集时刻、snd是发送时刻Unix 毫秒用来端到端测延迟发送侧代码见 Mac/MacSender.swift。所以首字节是{这条规则对视频帧也成立——NUL 字节检查才是真正起区分作用的条件。反过来JSON 侧也不可能出现 NUL控制消息要么是纯文本 JSON要么是 base64 编码base64 字符集只有A-Z a-z 0-9 / 天然无 NUL。两类数据在是否含 NUL这个维度上完全分得开启发式因此对合法负载是可证明正确的。24KB vs 32KB光标贴图里的隐藏契约 32KB 这条线不是随手画的它反过来约束了发送端的行为。协议文档PROTOCOL.md 第 4 节对发送端有两条硬性规定控制消息不得≥ 32768 字节、不得不以{开头、不得含 NUL 字节发送端不得发出能通过 JSON 三条件测试的视频帧。最接近红线的是cursorImg消息它要把 Mac 的光标图标PNG以 base64 塞进 JSON。base64 会让体积膨胀约 4/3所以 Mac 端在 Mac/MacSender.swift 里把原始 PNG 卡在24000 字节以下——膨胀后约 32000 字节稳稳落在 32768 以内。这就是24KB 光标和32KB 启发式之间那条心照不宣的契约。为什么不一开始就用类型字节协议演进路径 看到启发式很多工程师的第一反应是加一个 1 字节的类型标记0 视频1 JSON不就行了行但协议已经发布在野了。pv 1 版本的实现包括第三方移植都按启发式工作直接改线格式会切断所有旧对端。COMPATIBILITY.md 给出的演进策略是增量改动免费新字段、新消息类型随便加双方遇到不认识的必须忽略即可破坏性改动必须两阶段先发一个两种格式都认识的版本等普及后再发一个只认新格式的版本。所以文档把这套启发式明确标注为deprecated heuristic设计债而非特性并预留了pv 4一个带类型判别字节的帧头discriminator插在长度前缀和 payload 之间来彻底取代三条件判断。文档还给了移植者的实用建议——把 demux 判断隔离在一处将来换帧头时改动最小。仓库里的 tools/fake-receiver.swift74 行的最小接收端演示了拆帧 → 分流 → 解码的完整骨架是理解这套信道复用设计的好起点。关键文件导读去源码里验证 ️位置内容PROTOCOL.md权威线协议规范第 4 节即信道复用启发式Shared/StreamReceiver.swift接收端拆帧drainFrames 三条件分流handleAnnexBMac/MacSender.swift发送端遥测前缀{cap:…,snd:…}Mac/MacSender.swiftcursorImg光标贴图 24000 字节上限COMPATIBILITY.md协议版本演进政策与两阶段迁移tools/fake-receiver.swift74 行最小接收端实现小结OpenDisplay 的 32KB 启发式是先跑起来再讲卫生的典范它借 H.264 Annex B 起始码里必然存在的 NUL 字节用一条 3 行判断就分开了混跑在一条 TCP 连接上的 JSON 控制消息与视频帧又用 24KB 光标贴图上限把边界写死成规范最后用版本协议两阶段迁移为 pv 4 的类型字节帧头铺路。对想自己写投屏 / 第二屏幕工具的同学来说这份 PROTOCOL.md 连同上面的源码路径值得一读。【免费下载链接】opendisplayFree, open-source Sidecar/Duet alternative — use your iPhone, iPad or Mac as a true second monitor for your primary Mac over USB or WiFi. Low latency H.264, Retina HiDPI, touch input.项目地址: https://gitcode.com/GitHub_Trending/op/opendisplay创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考