DarkString
暗弦科技

VISION PRO / GEMINI LIVE / OPEN SOURCE

SpatialYOLO 教程:在 Vision Pro 上接入 Gemini Live 实时视觉助手

从真实演示到开源代码:配置 Vision Pro 主摄像头权限、运行 SpatialYOLO,并理解 Gemini 3.1 Flash Live 的音视频链路、迁移细节与延迟排查。

Gunner源码基线 b079306

我把 SpatialYOLO 接入 Gemini 3.1 Flash Live 后,最直观的感受是对话等待变短了。3 月 30 日发布演示时,我还特意记下:英语对话感觉更跟得上节奏,中文仍能感觉到一些停顿。这是当时的体验记录,没有统一条件下的延迟统计,也不能据此认定英语一定比中文快

另一个更值得分享的细节是:我起初以为从 2.5 升到 3.1 只要换模型名,结果遇到了错误。随后参照官方示例,让 Codex 调整 API 调用,才完成迁移。这段过程记录在我的 YouTube 演示说明中,也发布到了 Reddit Vision Pro 社区

这篇教程把演示拆成一条可以检查的路径:先确认摄像头可用,再接通声音,最后让 AI 围绕眼前的画面回答。

先看结果:SpatialYOLO 做了什么?

SpatialYOLO 是我开源的 Apple Vision Pro 项目,包含两条相邻的功能链路:Spatial YOLO 在设备端做物体检测和双目深度估计;AI Live 把采样画面与语音接入实时多模态服务,显示回复字幕并播放语音。本文聚焦 Gemini 路径,Qwen 和 OpenClaw 扩展可以在完成基础流程后再研究。

Gunner 的 Gemini 3.1 Flash Live 演示封面:Vision Pro 窗口显示房间画面,右下角是佩戴头显的作者
原始视频封面,来源:Gunner Guan 的 YouTube 更新演示;不是生成的产品效果图。
2026 年 3 月 30 日发布的更新演示。播放器不可用时,可前往 YouTube 观看

阅读前提:会在 Xcode 中选择 target、配置签名和运行真机。预计需要完成代码下载、权限配置、模型资源检查和一次完整对话;企业权限申请时间不包含在操作时间内。

版本基线:本文核对的是公开仓库与本地一致的提交 b079306(2026-03-29),资料查阅日期为 2026-09-15。README 仍保留 2.5 模型与 Xcode 16.2+ 的旧说明;代码中的 Gemini 模型已更新,应用 target 的部署目标为 visionOS 26.0。复现这个提交应使用包含 visionOS 26 SDK 的 Xcode 与符合部署目标的真机,不能只照 README 的旧最低版本准备环境。查看固定版本代码

一、先分清本地检测和云端对话

SpatialYOLO 数据流:Vision Pro 摄像头在本地进入 YOLO 检测,同时采样 JPEG;JPEG 与麦克风 PCM 经 WebSocket 送入 Gemini,再返回语音和字幕
根据项目代码绘制的数据流说明图。箭头表示数据方向,不表示实测延迟。

AI Live 并没有把摄像头的每一帧都上传。AppModel.swift 用 1 秒间隔限制采样;AppModel+GeminiLive.swift 在后台缩放画面,长边不超过 1024 像素,以 0.8 质量编码为 JPEG,再交给服务发送。非 Auto 模式还受语音活动控制:代码检测到输入音量超过阈值后激活采样,AI 开始回复时停止这轮采样。因此,“摄像头预览在刷新”与“正在发送新画面”要分别观察。

数据 这个提交的处理方式 排查重点
摄像头图像 左摄像头采样 → JPEG → realtimeInput.video 是否有新帧、是否进入采样状态
麦克风 单声道、16-bit PCM、16 kHz → realtimeInput.audio 权限、转换格式、输入音量
模型声音 24 kHz PCM → AVAudioEngine 播放格式和音频队列
字幕 输入/输出转写 → 对话格式化 → 界面 是否收到转写事件
检测上下文 先缓存在服务内,再合入显式文本请求 缓存不等于每帧已发给模型

这些数值来自项目的 Gemini 服务及帧处理代码。理解这张表,就能把黑屏、无声、旧画面和字幕缺失分开定位。

二、准备主摄像头权限

这一步往往先于 API Key 决定能否复现。Apple Vision Pro 的透视显示,并不自动意味着应用可以读取主摄像头像素。

按 Apple 的企业 API 说明,为应用申请 Main Camera Access;获批后,把对应且有效的 .license 文件加入 target,并在 Signing & Capabilities → + Capability → Main Camera Access 中添加能力。许可证有有效期,签名或授权不匹配都需要先处理。

Xcode 的 Capability 搜索界面,选中 Main Camera Access 主摄像头访问权限
项目原始配置截图:添加 Main Camera Access。不同 Xcode 版本的界面可能略有变化。

同时检查 Info.plist 中的主摄像头用途说明 NSMainCameraUsageDescription,以及麦克风用途说明。首次在设备上运行时允许相应访问。Apple 的主摄像头示例解释了许可证与用途说明的要求。

当前项目启动时会检查许可证状态和 mainCameraAccess 批准情况。先让真机摄像头预览正常,再排查 Gemini;模拟器不能作为实际摄像头、麦克风和空间体验的验收证据。

三、下载固定版本,检查模型资源

新建一个目录克隆仓库,固定到本教程核对的提交:

git clone https://github.com/lazygunner/SpatialYOLO.git
cd SpatialYOLO
git checkout b079306f3f0035c4d6f73afe8a784f821733b53c
open SpatialYOLO.xcodeproj

固定提交会进入 detached HEAD,适合复现;准备修改时可以自行新建开发分支。项目已包含 yolo11n.mlpackageRaftStereo512.mlpackage,第一次运行优先使用这些资源,不必先训练模型。

Xcode 打开项目内 yolo11n Core ML 模型包,显示模型类和输入信息
项目原始截图:确认 Xcode 能识别 yolo11n 模型包。截图中的模型信息属于该素材版本。

如果你需要重新导出 YOLO11n,可以在独立 Python 环境中按项目流程执行:

python3 -m venv .venv
source .venv/bin/activate
python -m pip install ultralytics
yolo export model=yolo11n.pt format=coreml nms=true

这是可选步骤,本次写作没有重新执行模型导出。记录实际使用的 ultralytics、coremltools 和 Python 版本;如果导出失败,按错误信息核对工具兼容性。把导出的 .mlpackage 加入应用 target,避免重复添加同名资源。nms=true 对应导出时包含非极大值抑制;具体支持情况参考 Ultralytics Core ML 导出文档

四、配置 Gemini Key,并确认它被打包

Google AI Studio 获取具有所需模型访问能力的 Gemini API Key。在仓库根目录执行:

cp SpatialYOLO/Config.plist.example SpatialYOLO/Config.plist

Config.plistGEMINI_API_KEY 中填入自己的 Key。下面仅展示对应条目,不是完整 plist:

<key>GEMINI_API_KEY</key>
<string>YOUR_GEMINI_API_KEY</string>

确认 Xcode 会将这个文件打包到应用:检查 target membership 或 Build Phases 中的资源设置,保证 Bundle.main 能找到 Config.plist文件在磁盘上存在,不等于已经进入应用包。

AppModel.loadGeminiAPIKey() 从应用资源读取这个键;找不到有效值会打印配置警告。只测试 Gemini 时不需要先申请 Qwen 的 Key。仓库忽略了真实配置和许可证,但把长期 Key 打包进客户端只适合作为个人开发配置;面向其他用户分发时,应另行设计服务端凭据与访问控制,不要把个人密钥随应用分享。

五、理解 2.5 → 3.1 的四处迁移

Google 模型页确认了 gemini-3.1-flash-live-preview 这个 Live API 模型标识。普通文本模型的名字不能直接替代它。

对照本项目,迁移至少要检查四处:

  1. 模型名。服务内部使用 models/gemini-3.1-flash-live-preview
  2. 文本发送。sendTextMessage() 发送 realtimeInput.textsendDetectionContext() 只暂存上下文,等待显式文本请求合并发送。
  3. 回合包含哪些画面。setup 显式指定 TURN_INCLUDES_ONLY_ACTIVITY;这会影响视觉输入如何参与回合。照抄不同版本示例前要核对默认行为。
  4. 响应解析。遍历 modelTurn.parts,并继续处理转写字段,不要读取一个文本片段后就跳过同一事件的音频。

下面摘录 setup 的关键结构,省略了声音选择和系统提示词,便于定位;不是完整替换函数:

let setup: [String: Any] = [
    "setup": [
        "model": "models/gemini-3.1-flash-live-preview",
        "generationConfig": ["responseModalities": ["AUDIO"]],
        "inputAudioTranscription": [:],
        "outputAudioTranscription": [:],
        "realtimeInputConfig": [
            "turnCoverage": "TURN_INCLUDES_ONLY_ACTIVITY"
        ]
    ]
]

完整实现见固定版本的 GeminiLiveService.swift。迁移规则和数据格式再与 Live API capabilities 对照。代码中的 setupComplete 才表示会话已配置就绪,仅仅打开 WebSocket 还不够。

六、在 Vision Pro 上完成第一次对话

在 Xcode 中配置自己的 Team、Bundle Identifier 和授权匹配的签名,选择已配对的 Vision Pro 真机运行。依次检查:

  1. 进入 AI Live 功能,确认摄像头画面正常显示。
  2. 选择 Gemini,点击 启动 / START
  3. 在日志里确认 [GeminiLive] Setup 完成,连接就绪,再检查麦克风输入反馈。
  4. 先关闭 Auto 模式,面对一个清晰、静止的物体,开口问“请用一句话描述我面前的物体”。观察是否开始发送帧,以及是否同时出现声音与字幕。
  5. 换一个物体,再问一次。答案应根据新画面变化;这样可以排除模型只沿用上一轮上下文的情况。
  6. 停止再启动一次,然后持续使用超过两分钟,观察断开与重连是否符合预期。

这里是建议的真机验收流程。本次文章制作完成了源码与资料核对,没有替读者在另一台 Vision Pro 上重新跑过这些步骤。

七、怎样排查“慢”,以及两处现有边界

不要把所有等待都记在模型名下。一次回答涉及语音结束判断、当前画面采样、网络传输、服务生成、音频接收与播放队列。可以给每轮记录五个时间点:最后一个用户语音采样、最后一帧提交、首个响应音频块接收、首个音频块实际播放、回答结束。

先用“首个音频实际播放 − 用户最后一个语音采样”衡量用户可感知的等待,再分开记录网络错误、重连和首次连接。播放节点排队的时刻不能当作已经听到声音。保持设备、网络、提示词、回答长度、采样方式一致,分别做中英文样本,报告样本量、中位数与 P95,才能讨论差异。本教程不提供未经测量的毫秒数字。

现象 优先检查
摄像头黑屏 许可证有效期、entitlement、签名、用途说明与用户授权
连接后没有 setupComplete Key 权限、模型标识、setup 字段和网络错误
能对话,但看不到新物体 isVoiceSamplingActive、输入音量阈值、帧发送日志;确认提问时物体已经入镜
有字幕但无声 是否收到 inlineData,24 kHz PCM 播放格式和音频引擎状态
总在两分钟左右重连 当前客户端有 120 秒计时器,会主动断开并触发重连
打断后旧语音还在播放 收到中断事件后是否真正停止并清空已排队音频

最后两项值得单独看代码。120 秒是这个提交的客户端行为,不能写成 Gemini 3.1 的统一服务上限。当前 sessionResumptionUpdate 处理只打印事件,没有完成恢复句柄的持久化与复用;自动重连也不能直接等同于无损续聊。

中断同样有边界:显式发送文本时会停止并重启播放节点,但 interrupted 分支只更新说话状态和字幕,没有同样清理播放队列。要改造成稳定的语音产品,需要补充这条路径并在真机验证。这是对源码的检查结果,不是本次听到了某个特定故障。

常见问题

没有 Main Camera Access,能完整复现吗?

无法完整复现主摄像头画面驱动的体验。可以先独立验证 API 连接或音频链路,但这不代表已经接入 Vision Pro 的真实环境画面。

必须自己训练 YOLO 吗?

不需要。这个提交已包含模型包,先验证现有资源和 target 配置。只有识别类别或模型需求改变时,才进一步研究训练、导出和性能。

为什么 README 写 Gemini 2.5?

README 的模型说明尚未同步。本文固定提交中的服务代码已使用 Gemini 3.1 Flash Live;后续复现请同时记录提交号、实际模型名和官方文档日期。

这是一套完全离线的助手吗?

不是。YOLO 检测在设备上运行,Gemini 对话需要把采样图像和音频发送给云端服务。请用适合测试的环境和素材验证,不要把设备端检测误认为整条链路都在本地。

换成 3.1 就一定更快吗?

我在视频说明里记录了主观改善,但没有提供受控对比数据。网络、语言、回合检测和播放队列都会影响结果,具体改善应在相同条件下测量。

从演示走向自己的空间助手

先拿一个物体完成一次“看到—提问—听到回答”,然后验证换物体、打断、重连。这几项都稳定后,再考虑连续讲解、设备说明、展品导览或其他业务场景。

SpatialYOLO 留下的可复用经验,是把画面是否更新、会话是否就绪、声音是否真正播放分别做成可观察的状态。这样,下一次换模型或增加服务商时,就有一条能重新检查的路径。