Apollo 迁移指南:从 GameStream 到 UPnP 公网串流
【免费下载链接】ApolloSunshine fork - The easiest way to stream with the native resolution of your client device项目地址: https://gitcode.com/gh_mirrors/apollo18/Apollo
NVIDIA 在 2023 年 2 月终止了面向 NVIDIA Games 客户端的 GameStream 服务,而 Apollo(一个以"客户端设备原生分辨率串流"为目标的 Sunshine fork)的性能已达到或超过 GameStream,客户端仍可直接使用 Moonlight,无需更换。本文讲清两件事:如何用 GSMS 工具把原有游戏条目搬进apps.json,以及 Apollo 内置的 UPnP 公网串流机制、配置项和常见故障的排查思路。
结论先行:什么情况值得直接迁移
判断很简单:只要你的客户端是 Moonlight,迁移就是"换服务端、不换客户端",成本最低。
- 串流质量:官方文档明确 Sunshine 性能已达到或超过 GameStream(见 docs/gamestream_migration.md)
- 公网串流:原来依赖 Moonlight Internet Hosting Tool 打洞的,迁移后可直接移除该工具,改用 Apollo 内置的 UPnP 端口映射
- 需要接受的差异:
| 能力 | GameStream | Apollo |
|---|---|---|
| 自动扫描并列出已安装游戏 | 支持 | 不支持,依赖apps.json条目或 GSMS 迁移结果 |
| 自动调整游戏内设置优化串流 | 支持 | 不支持,需用prep-cmd等手段自行编排 |
| 互联网串流 | 依赖 NVIDIA 云服务 | 本地 UPnP 端口映射,无需额外服务 |
如果游戏库很小,手工在apps.json里写几条就够用;条目多、怕出错,再上 GSMS 自动迁移。
GSMS:把游戏条目一次性搬进 apps.json
GSMS(GameStream Migration Service)是官方为迁移场景提供的自动化工具。它能把你 GameStream 里的自定义应用和自动检测到的游戏一起迁走,具体落点是:
- 每个条目的工作目录、启动命令写入
apps.json - 封面图(box-art)复制到你指定的目录
apps.json默认与主配置文件同目录(Linux/macOS 为~/.config/sunshine,Windows 为%ProgramFiles%\Sunshine\config,Docker 为/config),首次启动时由内置模板复制生成(src/config.cpp 中APPS_JSON_PATH的拷贝逻辑)。文件默认模板长这样:
// src_assets/linux/assets/apps.json { "env": { "PATH": "$(PATH):$(HOME)/.local/bin" }, "apps": [ { "name": "Low Res Desktop", "image-path": "desktop.png", "allow-client-commands": false, "prep-cmd": [ { "do": "xrandr --output HDMI-1 --mode 1920x1080", "undo": "xrandr --output HDMI-1 --mode 1920x1200" } ] } ] }顶层env定义所有应用共享的环境变量,$(PATH)、$(HOME)这类占位符在运行时展开;apps数组里每个条目就是一个可串流启动的应用。解析逻辑在 src/process.cpp,常用字段如下:
| 字段 | 类型 | 默认值 | 含义 |
|---|---|---|---|
name | string | 必填 | 显示在客户端的名称 |
cmd | string | 空 | 启动命令 |
working-dir | string | 空 | 工作目录(Windows 上会自动去引号并补反斜杠) |
image-path | string | 空 | 封面图路径,须为 PNG |
elevated | bool | false | 是否提升权限运行 |
auto-detach | bool | true | 自动分离进程 |
wait-all | bool | true | 等待所有子进程退出 |
exit-timeout | int | 5 | 退出判定超时(秒) |
prep-cmd | array | 空 | 启动前/退出后执行的命令,每项含do、undo、elevated |
detached | array | 空 | 独立于主进程启动的附加命令 |
virtual-display | bool | false | 是否使用虚拟显示器 |
allow-client-commands | bool | true | 是否放行 Alt+Tab 等客户端命令 |
uuid | string | 必填 | 应用唯一标识,Web UI 增删改依赖它 |
两个实用细节:
- 全局准备命令
global_prep_cmd会在所有应用启动前执行,任一条失败就中止启动(docs/configuration.md 中的示例:用nircmd.exe临时切换分辨率,undo再切回) - Web UI 提供
GET /api/apps和POST /api/apps两个 REST 接口(src/confighttp.cpp),新增或编辑应用后调用proc::refresh热刷新,不用重启 Sunshine。迁移完成后建议在这个页面上逐条核对cmd、working-dir、image-path是否指向正确路径
UPnP 公网串流的内部机制
想从外网串流,前提只有一个:路由器支持 UPnP。开启方式是 Web UI 打开 UPnP 选项,或在配置文件中写:
# docs/configuration.md(Network 一节) upnp = enabled配置解析在 src/config.cpp:读到upnp = enabled后翻转flag::UPNP标志位;src/upnp.cpp 的upnp::start()发现该标志未置位就什么都不启动,否则创建一个后台映射线程。
线程启动时先组装端口映射清单,基址是port(默认 47989),实际映射的 6 个端口为:
| 端口 | 协议 | 路由表中的描述 |
|---|---|---|
| 48010 | TCP | Sunshine - RTSP |
| 47998 | UDP | Sunshine - Video |
| 48000 | UDP | Sunshine - Audio |
| 47999 | UDP | Sunshine - Control |
| 47989 | TCP | Sunshine - Client HTTP |
| 47984 | TCP | Sunshine - Client HTTPS |
只有当origin_web_ui_allowed设为wan时,才会额外映射 47990(Web UI 端口)——默认lan下管理端口不对外开放,这是安全设计,不是 bug。
映射线程的工作节奏(src/upnp.h中两个常量):
REFRESH_INTERVAL = 120s:每 120 秒重新发现 IGD(Internet Gateway Device,即支持 UPnP 的路由器)并逐端口核对映射。注释里写明原因:路由器重启、WAN IP 变化等都会让映射丢失,必须周期刷新PORT_MAPPING_LIFETIME = 3600s:映射租约 1 小时,到期前被刷新逻辑续上
每个端口的处理顺序:先用UPNP_GetSpecificPortMappingEntry查是否已映射;目标 IP 一致就直接跳过;被别的设备占用则记UPnP conflict detected并尝试删除重建;如果路由器返回异常错误(非 606 Unauthorized),会判定为老旧的 IGDv1 设备,退化为 0 秒租约的永久映射来兼容。另外,若address_family = both且 IGD 支持 IPv6 防火墙控制,会额外为每个 LAN 端口打 IPv6 pinhole(IPv6 下没有 WAN→LAN 端口映射概念)。Sunshine 退出时调用unmap_all_upnp_ports清理全部映射。
相关配置项速查:
| 配置项 | 默认值 | 说明 |
|---|---|---|
upnp | disabled | 是否启用内置端口映射 |
port | 47989 | 端口族基址,改动后上表所有端口整体偏移 |
address_family | ipv4 | ipv4仅 IPv4;both为 IPv4+IPv6 |
origin_web_ui_allowed | lan | pc/lan/wan三级,决定 Web UI 是否对外 |
external_ip | 自动检测 | 手动指定公网 IP,留空则自动 |
⚠️ 一条官方警告:Moonlight Internet Hosting Tool 低于 v5.6 的版本与 Sunshine 同时运行,会导致 UPnP 端口转发变得不可靠。迁移时直接卸载它,或至少升级到 v5.6 及以上。
连不上时,按这个顺序排查
外网串流失败大多是"映射没生效"或"映射被占",日志里都有对应线索:
- 发现不到 IGD:日志出现
Couldn't discover any IPv4 UPNP devices或No IGD device found,说明路由器没开 UPnP(或根本不支持)。到路由器后台开启 UPnP/远程端口映射后,等 120 秒内线程自动重试即可 - 映射存在但指向别处:日志出现
UPnP conflict detected with: <IP>,表示端口被局域网内其他设备占用。Apollo 会尝试删除对方映射后重建;部分路由器出于安全禁止未认证客户端删除他人映射,此时需手动到路由器后台清理旧规则 - 路由器重启后短暂连不上:属正常现象。映射丢失后线程会在下一轮(最多 120 秒)重新发现 IGD 并补建映射,同时打印
Completed UPnP port mappings提示 - 改了
port却忘了客户端:port一改,上表 6 个端口全部偏移,Moonlight 侧需要按新端口重连;手动在路由器上转发端口的用户要同步更新规则 - 想从外网管 Web UI 却连不上:确认
origin_web_ui_allowed已设为wan,否则 47990 端口根本不会进入映射清单 - 公网 IP 探测不对:多出口/NAT 环境下自动检测可能失败,用
external_ip手动指定
日志级别调到 debug 可以看到每一步:查映射、建 pinhole、失败回退永久映射等细节(Successfully mapped TCP 47989这类输出)都在这条线程里。
什么时候不适合这套方案
- 路由器不支持 UPnP(部分运营商定制路由关闭了该功能):内置映射无从生效,只能手动配置路由器端口转发,本文机制不覆盖
- 游戏库更新频繁:Apollo 不会自动扫描新装游戏,新游戏不会自己出现在串流列表里,需要重新跑一次 GSMS 或在 Web UI 手工添加
- 希望服务端替你调优游戏:GameStream 会自动改游戏内设置适配串流,Apollo 不会干预游戏内部配置。替代做法是用
prep-cmd/state-cmd在串流前后执行自己的命令(切分辨率、开低延迟模式等),把 GameStream 的"自动化"换成"脚本化"
收个尾:迁移本身是一次性动作(GSMS 跑一遍 + 核对apps.json),真正的长期成本在网络侧。UPnP 开关只影响 6 个固定端口的映射,日志每 120 秒一轮自检,出问题时先看映射状态再看 IP,基本能定位九成故障。
【免费下载链接】ApolloSunshine fork - The easiest way to stream with the native resolution of your client device项目地址: https://gitcode.com/gh_mirrors/apollo18/Apollo
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考