前言
你有没有遇到过这样的场景?
你有一台 Mac 和一台 PC(比如 Surface),想把 PC 作为 Mac 的扩展屏幕使用,但发现:
- Mac 不支持 Miracast
(Windows 的无线投屏协议) - AirPlay 只能投到 Apple 设备
(Apple TV、其他 Mac) - 第三方软件要么收费,要么体验差
于是,刚好有一台吃灰很久的surface,抱着试一试的态度,我决定自己动手,用代码解决这个问题,本次使用的编程工具是智谱刚发布的Zcode 3.0,以下是操作台界面,整体还是很强的。

接下来通过这篇文章,我会完整记录整个实现过程,包括:
🔍 技术调研与方案选择 🏗️ 架构设计与实现 🐛 踩过的坑与解决方案 ✅ 最终成果展示
一、需求分析
目标
让 Mac 能够通过无线方式,将 PC(Windows/Linux)作为扩展屏幕使用。
技术约束
- Mac 端
:希望使用系统原生的"隔空播放"功能,无需安装额外软件 - PC 端
:通过浏览器即可查看投屏内容,无需安装客户端 - 网络要求
:两台设备在同一局域网内
技术选型
最终选择:先尝试实现 AirPlay 接收器,如果太复杂则使用 FFmpeg + WebSocket 方案。
二、AirPlay 协议调研
AirPlay 是什么?
AirPlay 是苹果公司的专有协议,用于在设备间流式传输音频、视频和屏幕镜像。
AirPlay 的工作原理
┌─────────────┐ ┌─────────────┐│ │ 1. mDNS 发现 │ ││ Mac │ <──────────────── │ PC ││ │ │ ││ │ 2. RTSP 握手 │ 广播服务 ││ 发送端 │ <──────────────── │ 接收端 ││ │ │ ││ │ 3. 视频流传输 │ ││ │ ────────────────> │ │└─────────────┘ └─────────────┘
关键技术点
- mDNS/Bonjour
:服务发现协议,让 Mac 能找到 PC - RTSP
:实时流协议,用于建立连接和控制 - H.264
:视频编码格式 - FairPlay DRM
:苹果的数字版权管理(这是最难的部分)
遇到的问题
在实现 AirPlay 接收器时,遇到了几个关键问题:
- 协议文档缺失
:AirPlay 是专有协议,没有官方文档 - 加密复杂
:需要处理 FairPlay DRM 加密 - 版本兼容
:不同 macOS 版本的 AirPlay 协议有差异 - 认证机制
:需要模拟 Apple TV 的认证流程
结论:从零实现完整的 AirPlay 接收器非常困难,需要大量逆向工程工作。
三、最终方案:FFmpeg + WebSocket
既然原生 AirPlay 实现太复杂,我选择了一个更实用的方案:
架构设计
┌─────────────────┐ WebSocket ┌──────────────────┐│ │ ────────────────────────> │ ││ Mac (发送端) │ FFmpeg 屏幕捕获 │ PC (接收端) ││ │ H.264 编码 │ ││ │ WebSocket 推流 │ Node.js 服务 │└─────────────────┘ └────────┬─────────┘│▼┌──────────────────┐│ Web 浏览器 ││ ││ - 接收视频流 ││ - Canvas 渲染 │└──────────────────┘
技术栈
四、实现细节
4.1 Mac 端:屏幕捕获与推流
# 使用 FFmpeg 捕获 macOS 屏幕cmd = ['ffmpeg','-f', 'avfoundation',# macOS 屏幕捕获'-capture_cursor', '1',# 捕获鼠标'-framerate', '24',# 帧率'-i', '0:none',# 屏幕索引 0,无音频'-vf', 'scale=1280x720',# 缩放分辨率'-pix_fmt', 'rgba',# RGBA 格式'-f', 'rawvideo','pipe:1'# 输出到 stdout]
关键点:
avfoundation是 macOS 的屏幕捕获框架 0:none表示第一个屏幕,无音频 输出为 RGBA 原始像素数据
4.2 帧格式设计
每帧数据的格式:
┌──────────────────────────────────────────────────┐│ 消息头 (20 bytes) │├──────┬──────┬──────┬──────────┬──────────┬───────┤│ 类型 │ 宽度 │ 高度 │ 时间戳 │ 帧大小 │ 保留 ││ 1B │ 2B │ 2B │ 8B │ 4B │ 3B │└──────┴──────┴──────┴──────────┴──────────┴───────┘┌──────────────────────────────────────────────────┐│ 帧数据 (width × height × 4 bytes) ││ RGBA 像素数据 │└──────────────────────────────────────────────────┘
4.3 PC 端:WebSocket 服务器
wss.on('connection', (ws) => {ws.on('message', (data) => {if (Buffer.isBuffer(data) && data[0] === 0x01) {// 视频帧 - 广播给所有查看者for (const viewer of viewers) {viewer.send(data);}}});});
4.4 浏览器端:Canvas 渲染
function handleFrame(data) {const view = new DataView(data.buffer);const width = view.getUint16(1);const height = view.getUint16(3);const frameData = data.slice(20);// 创建 ImageData 并绘制到 Canvasconst imageData = new ImageData(new Uint8ClampedArray(frameData),width, height);ctx.putImageData(imageData, 0, 0);}
五、踩坑记录
坑 1:mDNS 服务发现
问题:Mac 无法发现 PC 上的 AirPlay 服务
原因:AirPlay 需要注册 _airplay._tcp 和 _raop._tcp 两种服务
解决:使用 bonjour-service 库同时注册两种服务
// 注册 AirPlay 服务bonjour.publish({name: 'AirPlay-Extended-Screen',type: 'airplay',port: 7100,txt: { deviceid: 'AABBCCDDEEFF', features: '0x5A7FFFF7,0x1E' }});
坑 2:FFmpeg 屏幕捕获权限
问题:FFmpeg 无法捕获屏幕
原因:macOS 需要授予终端屏幕录制权限
解决:
系统设置 → 隐私与安全性 → 屏幕录制 添加终端应用
坑 3:屏幕设备索引错误
问题:FFmpeg 报错 "Input/output error"
原因:屏幕设备索引应该是 0,不是 1
解决:使用 ffmpeg -f avfoundation -list_devices true -i "" 查看设备列表
坑 4:WebSocket 消息类型
问题:服务器无法区分视频帧和控制消息
原因:WebSocket 消息没有统一的格式
解决:定义消息头格式,第一个字节表示消息类型
六、最终效果
运行步骤
1. PC 端启动服务器
cd airplay-extended-screen/pc-servernpm installnode server-simple.js
2. Mac 端启动推流
cd airplay-extended-screen/mac-clientpython3 screen_stream.py
3. PC 浏览器查看
打开 http://PC的IP:7100
性能指标
- 延迟
:< 100ms(局域网内) - 帧率
:24 FPS(可配置) - 分辨率
:1280x720(可配置) - 带宽
:约 5-10 Mbps
七、项目结构
airplay-extended-screen/├── pc-server/# PC 端服务器│ ├── package.json│ ├── server-simple.js# 主服务器│ └── public/│ └── index.html# Web 界面│├── mac-client/# Mac 端客户端│ ├── requirements.txt│ └── screen_stream.py# 推流脚本│└── README.md
八、总结与展望
收获
- 技术调研很重要
:在动手之前,先了解技术的可行性和复杂度 - 灵活调整方案
:当原方案太复杂时,及时切换到更实用的方案 - 用户体验优先
:最终方案虽然不是原生 AirPlay,但使用简单、效果不错
不足
- 非原生 AirPlay
:需要在 Mac 上运行脚本,不是系统级集成 - 没有音频
:当前只传输视频,不支持音频 - 性能优化空间
:可以使用 H.264 编码减少带宽
后续计划
- 实现原生 AirPlay
:继续研究 AirPlay 协议,争取实现原生支持 - 添加音频支持
:使用 CoreAudio 捕获系统音频 - 优化性能
:使用硬件加速编码,降低 CPU 占用 - 双向控制
:支持从 PC 端控制 Mac(鼠标、键盘)
九、参考资料
AirPlay 协议分析 FFmpeg 屏幕捕获 WebSocket MDN 文档 Bonjour 协议
写在最后
首先感谢这个伟大的AI时代,这个项目让我深刻体会到:
"Talk is cheap, show me the code." —— Linus Torvalds
有时候,与其抱怨工具不好用,不如自己动手做一个。
虽然最终方案不是最完美的,但它确实解决了我的实际问题。而且,整个过程中的学习和探索,才是最有价值的部分。
如果你也有类似的需求,欢迎尝试这个方案,也欢迎提出改进建议!
项目最终效果展示:

夜雨聆风