iMirror:一个开源的 macOS 菜单栏 AirPlay 投屏接收器
iMirror 是一个 macOS 菜单栏小工具:打开后,iPhone、iPad 或另一台 Mac 可以在“控制中心 → 屏幕镜像”里直接选中这台 Mac,把画面投过来。源码按 GPLv3 公开在 github.com/erniu96/iMirror,Releases 里有可直接安装的 dmg,最新版本是 1.2.1(2026-10-06)。
解决什么问题
macOS 12 起系统自带“隔空播放接收器”,但画面是全屏铺开的,不方便缩在屏幕一角边看边干活。开源的 UxPlay 很好用,但主要面向 Linux 命令行,在 Mac 上要自己装 GStreamer 等依赖。
我想要的是:
- 演示 / 录课 / 调 App 时,把手机画面放在 Mac 屏幕一角,置顶、可缩放,不挡工作区;
- 两台手机同时投,各占一个窗口,一台断开不影响另一台;
- 不用登录账号,不装一堆依赖,菜单栏里一个图标就够。
适合:做 iOS 开发和测试的人、需要在会议或直播里展示手机屏幕的人、家里想把孩子 iPad 画面放到大屏 Mac 上看的人。
功能
- 菜单栏常驻:没有 Dock 图标,菜单里显示接收状态(等待设备 / 等待输入验证码 / 正在接收 N 台设备)和分步投屏说明。
- 多设备同时投屏:每个连接独立的硬件解码器、Metal 视图和窗口。
- 无边框窗口:可缩放、默认置顶;鼠标移入才在左上角出现停止投屏、置顶、全屏按钮。横竖屏切换保持比例,全屏或窗口平铺时加黑边,不拉伸(1.2.1)。
- 随机验证码配对:每次新设备配对时生成新的四位验证码,不保存固定码;验证过的设备下次免码,可在设置里一键忘记全部设备或重置接收器身份。
- 接收器名字带四位本机标记(如
iMirror-A7K2),家里有几台 Mac 也分得清。 - 声音:支持 AAC-ELD、AAC-LC、ALAC、PCM,音量跟随投屏设备,可在菜单里关闭(1.1.0)。
- 登录时自动启动;睡眠时停止网络服务,唤醒后自动恢复。
- 界面简体中文 / 英文,跟随系统语言。
- 广播能力:H.264,最高 1920×1080@60。不广播尚未实现的 HEVC。
怎么实现的
整体分两半:协议层沿用开源项目,Mac 端的应用、解码、显示和音频是我们写的。
SwiftUI 菜单栏与设置
└ AppModel(用例编排)
├ AirPlayReceiverService ─ ObjC++ Bridge ─ AirPlay/RAOP 协议层(C,开源派生)
├ VideoSessionRouter ─ 每个会话:VideoPipeline ─ VideoToolbox ─ Metal 窗口
└ AudioPlaybackEngine ─ 每个会话:解码器 ─ AVAudioPlayerNode(统一混音)
协议层(不是我们写的):AirPlay/ 目录约 3.6 万行 C 代码,来自 UxPlay / RPiPlay 一系的 AirPlay/RAOP 接收实现,包括 RTSP/HTTP 握手、配对、FairPlay(PlayFair)、镜像流解密和 NTP 时钟。里面还带着 libplist、llhttp、csrp、tiny-AES-c、axTLS AES 等组件,各自许可证和版权声明原样保留在 ThirdParty/。我们对这一层只做了减法和少量适配:删掉没用到的 Curve25519/Ed25519、PlayFair 发送端、树莓派调试转储,以及把密钥写进日志和文件的调试代码,每个改过的文件头部都写了“iMirror modifications”。下面“我们写的部分”里的随机配对码和 AWDL 直连,虽然代码在这个目录里,但不是上游的。因为协议层是 GPLv3 派生代码,iMirror 整体也按 GPLv3 发布。
我们写的部分(约 3500 行 Swift / ObjC++,另有测试、构建与发布脚本):
- 协议层里的新增:新设备配对时用系统随机数生成四位验证码(
pairing.c的random_pin()),应用里固定走随机码;设备发现用 Apple 公开的 DNS-SD 接口注册_airplay._tcp/_raop._tcp,带上 P2P 与 AWDL 标志,NTP、音频和镜像连接的 IPv6 链路本地地址指定awdl0接口,为点对点直连做准备(真机效果见下文验收说明)。 - ObjC++ 桥接(
Bridge/):把 C 回调变成类型化事件,只依赖协议层明确的接口,不去猜结构体字段。 - 视频(
Video/、Window/):Annex-B 拆包、H.264 参数集处理,交给 VideoToolbox 硬件解码,解出的 IOSurface/NV12 直接交给 Metal 显示,不经过 CPU 拷贝。每个会话一条独立管线,窗口创建期间只缓存有上限的首帧。 - 音频(
Audio/):AirPlay 不传解码器配置,我们根据 SETUP 协商的编码、采样率和每包帧数自己拼出 AAC 的 AudioSpecificConfig 或 ALAC 配置;排队超过 0.35 秒的音频直接丢弃,避免延迟越积越多;输出设备变化时自动重启引擎。 - 应用外壳:SwiftUI
MenuBarExtra、设置页、首次启动引导、SMAppService登录启动、睡眠唤醒处理、中英文本地化。 - 工程:
build.sh test一次跑完构建、签名检查、协议测试、Swift 单元测试和两路 H.264 解码测试;GitHub Actions 在 Apple Silicon 和 Intel 两种 macOS 上跑;打版本标签后自动构建 universal dmg 并发布 Release。从源码构建不需要 Homebrew,脚本会下载 OpenSSL 3 源码,校验 SHA-256 后再编译。
实际效果
- v1.2.1 这次提交的 CI 和 Release 构建都通过了(Build and test、Release)。
- CI 运行在虚拟机里,允许软件解码,只能证明能构建、协议和解码逻辑正确,不能代表真机上的发现、配对、AWDL 连通和窗口显示。这部分真机验收还在进行,结论出来后再更新本文。
安装
-
从 Releases 下载
iMirror-1.2.1-macOS-universal.dmg(Apple Silicon / Intel 通用,需要 macOS 14+),把图标拖进“应用程序”。
上图是 dmg 窗口里实际使用的背景图(仓库
Resources/DMG/),不是运行截图。 -
发布版只做了 ad-hoc 签名,没有经过 Apple 公证,第一次打开会被拦截:macOS 15 及以上到“系统设置 → 隐私与安全性”底部点“仍要打开”;macOS 14 右键 → 打开;或者在终端运行
xattr -dr com.apple.quarantine /Applications/iMirror.app。 -
首次启动允许“本地网络”权限。在 iPhone 控制中心点“屏幕镜像”,选带标记的
iMirror-XXXX,输入 Mac 上显示的四位验证码。
也可以从源码安装:装好 Xcode Command Line Tools 后运行 bash install.sh。
已知限制
- 只支持 H.264 镜像,不支持 HEVC;不支持“投送视频”(AirPlay 视频 URL 播放)。
- 没有 Developer ID 签名和公证,也没有上架 Mac App Store。
- AWDL 没有给第三方用的公开接收器框架;部分 macOS 版本需要在“系统设置 → 通用 → 隔空投送与接力”里开着“隔空播放接收器”,AWDL 接口才可用。
- 真机发现、配对、AWDL 和窗口显示的验收还没完成(见上文)。
欢迎试用、提 Issue 或贡献翻译:github.com/erniu96/iMirror。