乐于分享
好东西不私藏

Ai实战记录:让 Mac 通过 AirPlay 将 PC 作为扩展屏幕

Ai实战记录:让 Mac 通过 AirPlay 将 PC 作为扩展屏幕

前言

你有没有遇到过这样的场景?

你有一台 Mac 和一台 PC(比如 Surface),想把 PC 作为 Mac 的扩展屏幕使用,但发现:

  • Mac 不支持 Miracast
    (Windows 的无线投屏协议)
  • AirPlay 只能投到 Apple 设备
    (Apple TV、其他 Mac)
  • 第三方软件要么收费,要么体验差

于是,刚好有一台吃灰很久的surface,抱着试一试的态度,我决定自己动手,用代码解决这个问题,本次使用的编程工具是智谱刚发布的Zcode 3.0,以下是操作台界面,整体还是很强的。

接下来通过这篇文章,我会完整记录整个实现过程,包括:

  • 🔍 技术调研与方案选择
  • 🏗️ 架构设计与实现
  • 🐛 踩过的坑与解决方案
  • ✅ 最终成果展示

一、需求分析

目标

让 Mac 能够通过无线方式,将 PC(Windows/Linux)作为扩展屏幕使用。

技术约束

  1. Mac 端
    :希望使用系统原生的"隔空播放"功能,无需安装额外软件
  2. PC 端
    :通过浏览器即可查看投屏内容,无需安装客户端
  3. 网络要求
    :两台设备在同一局域网内

技术选型

方案
优点
缺点
实现 AirPlay 接收器
原生体验,Mac 无需安装软件
协议复杂,需要逆向工程
VNC/RDP
成熟稳定
需要安装客户端,体验一般
WebRTC
现代化,低延迟
需要 Mac 端安装软件
FFmpeg + WebSocket
简单可靠,立即可用
非原生 AirPlay

最终选择:先尝试实现 AirPlay 接收器,如果太复杂则使用 FFmpeg + WebSocket 方案。

二、AirPlay 协议调研

AirPlay 是什么?

AirPlay 是苹果公司的专有协议,用于在设备间流式传输音频、视频和屏幕镜像。

AirPlay 的工作原理

┌─────────────┐ ┌─────────────┐│ │ 1. mDNS 发现 │ ││ Mac │ <──────────────── │ PC ││ │ │ ││ │ 2. RTSP 握手 │ 广播服务 ││ 发送端 │ <──────────────── │ 接收端 ││ │ │ ││ │ 3. 视频流传输 │ ││ │ ────────────────> │ │└─────────────┘ └─────────────┘

关键技术点

  1. mDNS/Bonjour
    :服务发现协议,让 Mac 能找到 PC
  2. RTSP
    :实时流协议,用于建立连接和控制
  3. H.264
    :视频编码格式
  4. FairPlay DRM
    :苹果的数字版权管理(这是最难的部分)

遇到的问题

在实现 AirPlay 接收器时,遇到了几个关键问题:

  1. 协议文档缺失
    :AirPlay 是专有协议,没有官方文档
  2. 加密复杂
    :需要处理 FairPlay DRM 加密
  3. 版本兼容
    :不同 macOS 版本的 AirPlay 协议有差异
  4. 认证机制
    :需要模拟 Apple TV 的认证流程

结论:从零实现完整的 AirPlay 接收器非常困难,需要大量逆向工程工作。

三、最终方案:FFmpeg + WebSocket

既然原生 AirPlay 实现太复杂,我选择了一个更实用的方案:

架构设计

┌─────────────────┐ WebSocket ┌──────────────────┐│ │ ────────────────────────> │ ││ Mac (发送端) │ FFmpeg 屏幕捕获 │ PC (接收端) ││ │ H.264 编码 │ ││ │ WebSocket 推流 │ Node.js 服务 │└─────────────────┘ └────────┬─────────┘ │ ▼ ┌──────────────────┐ │ Web 浏览器 │ │ │ │ - 接收视频流 │ │ - Canvas 渲染 │ └──────────────────┘

技术栈

组件
技术
Mac 端
Python + FFmpeg(屏幕捕获)
PC 端
Node.js + Express + WebSocket
视频格式
RGBA 原始帧
传输协议
WebSocket
浏览器渲染
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 并绘制到 Canvas const 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 需要授予终端屏幕录制权限

解决:

  1. 系统设置 → 隐私与安全性 → 屏幕录制
  2. 添加终端应用

坑 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

八、总结与展望

收获

  1. 技术调研很重要
    :在动手之前,先了解技术的可行性和复杂度
  2. 灵活调整方案
    :当原方案太复杂时,及时切换到更实用的方案
  3. 用户体验优先
    :最终方案虽然不是原生 AirPlay,但使用简单、效果不错

不足

  1. 非原生 AirPlay
    :需要在 Mac 上运行脚本,不是系统级集成
  2. 没有音频
    :当前只传输视频,不支持音频
  3. 性能优化空间
    :可以使用 H.264 编码减少带宽

后续计划

  1. 实现原生 AirPlay
    :继续研究 AirPlay 协议,争取实现原生支持
  2. 添加音频支持
    :使用 CoreAudio 捕获系统音频
  3. 优化性能
    :使用硬件加速编码,降低 CPU 占用
  4. 双向控制
    :支持从 PC 端控制 Mac(鼠标、键盘)

九、参考资料

  • AirPlay 协议分析
  • FFmpeg 屏幕捕获
  • WebSocket MDN 文档
  • Bonjour 协议

写在最后

首先感谢这个伟大的AI时代,这个项目让我深刻体会到:

"Talk is cheap, show me the code." —— Linus Torvalds

有时候,与其抱怨工具不好用,不如自己动手做一个。

虽然最终方案不是最完美的,但它确实解决了我的实际问题。而且,整个过程中的学习和探索,才是最有价值的部分。

如果你也有类似的需求,欢迎尝试这个方案,也欢迎提出改进建议!

项目最终效果展示:

相关学习资料