去年把主机从书房搬到客厅之后,我一直在纠结要不要再买一台放在卧室。后来想明白了,真正的问题从来不是主机不够,而是显示设备不够——电视只有一台,手边的手机、平板、笔记本却全在吃灰。于是我用一个周末搭了个叫 AnyPS5 的小项目:把客厅主机的画面通过局域网推送到家里任意一台有浏览器的设备上,手机、平板、旧笔记本甚至客厅角落的智能电视都能秒变第二块屏幕。
核心思路不复杂:采集卡取画面,WebRTC 分发,浏览器回传按键。做完之后家里那台主机基本就“住”进了所有设备里,躺在床上用 iPad 玩、在厨房用手机瞄一眼状态、在书房拿笔记本继续肝,全部不用搬机器。这篇东西更像是我的项目手记,从选型到跑通再到踩坑,适合想在局域网里折腾串流、又不想被官方客户端绑死的玩家参考。
1. 选定方向:为什么是采集卡加 WebRTC,而不是官方串流通道
1.1 官方串流通道的体验限制
很多人的第一反应是:官方不是自带串流功能吗?直接在手机、平板上装官方 App 不就行了。
实际用下来会发现几个很具体的问题。设备兼容性上,官方客户端对系统版本有要求,旧平板、个别安卓电视盒子装不上或者装上了跑不动;画面策略上,官方的码率和分辨率是固定档位,没法针对自家网络环境精调,遇到 Wi-Fi 信号波动时会突然掉画质;输入端上,手机触摸屏模拟手柄本身手感一般,想要连实体手柄又得配官方推荐的那几款。
另外还有一个很实在的场景:智能电视。官方 App 在电视上的版本体验参差不齐,有些电视直接没有对应客户端。我想实现的目标是“任何一块屏幕都能变成主机的第二块屏”,官方串流通道在这个目标上限制太多。
1.2 采集卡方案的底层底气
采集卡方案的本质,是把主机当成一台 HDMI 摄像头来看。
主机输出画面走 HDMI 接口,这是公开的音视频标准。采集卡把 HDMI 信号转成 USB 视频设备(UVC),推流工具再把它编码成网络流。整个过程完全不触碰主机系统、不修改任何游戏文件、不涉及任何破解类操作,主机只当自己接了一台普通的 HDMI 显示器。比起在协议层面逆向官方串流通道,这套路子干净得多,也稳得多。
实际项目中我验证过:游戏画面可以正常采集,分辨率、帧率、HDR 等参数都能完整穿过 HDMI 链路。唯一受限的是在主机上播放正版影碟或者某些流媒体 App 时,HDCP 版权保护协议会让采集画面直接黑屏,这类场景的正确做法是切回电视看,游戏场景完全不受影响。
1.3 AnyPS5 的整体架构与工作流程
AnyPS5 跑起来之后是这样一个链路:主机 HDMI 输出接到采集卡,采集卡插到一台常开的迷你主机上做服务端;服务端把画面编码成 H.264 之后通过 WebRTC 分发给局域网内所有接入设备;按键输入则是反向的:浏览器收集触摸或手柄事件,经 WebSocket 送回服务端,再通过 USB HID 适配器把按键事件回传到主机。
四层结构大致如下:
| 层级 | 职责 | 主要组件 |
|---|---|---|
| 采集层 | 抓取 HDMI 输入,生成原始帧 | USB 采集卡、UVC 驱动 |
| 编码分发层 | 编码、推流、多终端分发 | ffmpeg、GStreamer、WebRTC Gateway |
| 控制回传层 | 接收浏览器按键,转换成主机可识别信号 | WebSocket 服务、USB HID 适配器 |
| 前端接入层 | 展示画面、虚拟按键、会话管理 | Web 页面、浏览器手柄 API |
服务端我放在一台旧笔记本上,Ubuntu 系统,局域网内用固定 IP 访问;前端就是把一个网页挂在同一内网里。整个过程跑下来,画质和延迟基本能做到“能玩动作游戏”的程度,后面第 4 章我会放实测数据。
2. 服务端搭建:从 HDMI 信号到局域网 WebRTC 流
2.1 采集硬件怎么选:分辨率、帧率与 HDMI 环出
采集卡是整个链路里最容易翻车的硬件。我先后试了三种类型:最便宜的 USB 2.0 免驱卡、标称 4K 输入但实际只有 1080p 输出的卡、带 HDMI 环出功能的 USB 3.0 卡。
结论是:别买 USB 2.0 的采集卡,别信“高清免驱”这种模糊宣传,认准 USB 3.0 和 HDMI 环出两个硬指标。
USB 2.0 的带宽撑不起 1080p60 的无损采集,实际抓出来画面会掉到 30fps 左右,延迟还飘,玩塞尔达那种需要精确冲刺的场景直接劝退。USB 3.0 卡稳定跑 1080p60 没问题,注意线材也要用质量好的 USB 3.0 数据线,劣质线会导致随机断开。
HDMI 环出功能非常关键,它能让信号同时走采集卡和电视。这样客厅电视正常玩游戏,手机平板同时看画面,不用二选一。我最后用的是一款带环出、免驱、支持到 1080p60 的 USB 3.0 采集卡,价格不高,胜在稳定。
2.2 编码参数:不糊眼的 1080p60 配置
采集卡出来的原始流是未压缩的,必须编码后才能走网络。编码参数我反复调过,最终确定了一套适合局域网内自用的方案:
| 参数项 | 推荐值 | 说明 |
|---|---|---|
| 编码格式 | H.264 | 兼容性最好,WebRTC 原生支持 |
| 分辨率 | 1080p | 再往上 4K 对局域网带宽和设备解压压力都太大 |
| 帧率 | 60fps | 动作游戏刚需,30fps 会明显卡手 |
| 码率 | 主码率 10Mbps | 局域网内完全跑得动,低一点画面会出块 |
| 编码档次 | CBR | 恒定码率,避免画面波动时延迟漂移 |
| GOP 大小 | 1 秒 | 关键帧间隔控制在 1 秒内,切设备时恢复画面快 |
| B 帧 | 关闭 | 低延迟场景下关 B 帧能明显降低帧队列延迟 |
| 音频 | AAC 128kbps | 串流够用,杜比全景声这类环绕音频不适合局域网分发 |
这套参数在局域网内实测,画面在快速转动场景下依然干净,码率 10Mbps 对千兆内网来说毫无压力。如果家里还在用百兆交换机,建议降到 8Mbps,画质损失肉眼勉强能察觉,但更稳妥。
2.3 用 ffmpeg 和 GStreamer 搭出串流管道
服务端第一步是把采集卡 UVC 设备拉起来,用 ffmpeg 直接把采集帧推给本地 RTSP 服务或者直接推给 WebRTC 网关。最初验证可行性时我用的是一条很短的 ffmpeg 命令:
ffmpeg -f v4l2 -framerate 60 -video_size 1920x1080 -i /dev/video0 \ -c:v libx264 -preset veryfast -tune zerolatency -b:v 10M \ -maxrate 10M -bufsize 20M -g 60 \ -c:a aac -b:a 128k -f rtsp rtsp://localhost:8554/ps5这里几个参数重点解释一下:-preset veryfast牺牲一点压缩率换编码速度,低延迟场景的常规操作;-tune zerolatency让 x264 不以画质优先,而是以低延迟优先,实测能砍掉不少缓冲延迟;-g 60配合 60fps 就是每秒一个关键帧。
验证这个管道没问题之后,我再叠加 WebRTC 网关做浏览器分发。GStreamer 的好处是原生支持 webrtcbin,可以把 RTSP 流转成 WebRTC 流。我在服务端跑了一个小服务,从 RTSP 拉流,然后通过 webrtcbin 发给所有连接到页面的浏览器。
2.4 信令服务:浏览器和流网关之间怎么打招呼
WebRTC 本身只负责音视频数据传输,但两端的“握手”需要一个信令通道。我用的是很轻量的方案:一个 Node.js 写的 WebSocket 服务,充当浏览器和流网关之间的信令中转。
流程大概是:浏览器打开页面后,通过 WebSocket 向服务端发一个join消息;服务端收到后创建 RTCPeerConnection,生成 SDP 和 ICE candidate,再通过同一个 WebSocket 转发给浏览器;浏览器同样回复自己的 SDP 和 ICE,双方完成穿透后,音视频流自动开始传输。
伪代码大概长这样:
// 简化版信令逻辑 ws.on('message', (msg) => { const data = JSON.parse(msg); switch (data.type) { case 'join': // 让网关创建 WebRTC 会话,把 offer 发回给浏览器 gateway.createSession(sessionId); ws.send(JSON.stringify(gateway.getOffer(sessionId))); break; case 'answer': gateway.setAnswer(sessionId, data.sdp); break; case 'ice': gateway.addIceCandidate(sessionId, data.candidate); break; } });这个阶段我踩过的最大坑是:把信令服务写在公网上,本地和服务器来回兜一圈,延迟直接翻倍。后面我把它改成纯局域网服务,浏览器访问固定的内网地址,握手时间从几百毫秒降到几十毫秒。很多局域网串流项目卡在延迟上,其实就是信令走了冤枉路。
3. 手柄回传链路:把手机触摸屏变成主机的无线手柄
3.1 为什么不能直接蓝牙配对
刚开始我想简单点:主机不是支持蓝牙手柄吗,手机直接连主机多好。
蓝牙一搜确实能搜到主机,但配对协议跟手柄完全不同,手机不会把自己声明成一个 DualSense 兼容设备,主机也不会认它。实际想做到“任意终端变成手柄”,得让终端上的输入事件回到服务端,再由服务端转发给一个主机能认出的“外设”。这才是回传链路的核心。
3.2 输入端到端:浏览器的 Gamepad API 和触屏模拟
浏览器这边能做两层输入:一是实体手柄插到终端上,通过 Gamepad API 直接读取按键状态;二是触屏虚拟按键,在页面上渲染一套手柄布局,触摸事件转换成对应的按键值。
两层输入都汇总成统一的事件结构,然后通过 WebSocket 发给服务端:
// 统一按键事件格式 { "action": "key_down", "key": "cross", "session": "session-abc-123" }实体手柄直插终端时延迟最低,因为走的是 Gamepad API 原生轮询;触屏虚拟按键会多一层触摸事件解析,但实测在手机 Chrome 上延迟也能控制在可玩范围内,玩回合制游戏完全没问题,动作游戏会稍有滞后。
3.3 从 WebSocket 到主机:USB HID 适配器回传
服务端收到按键事件后,最后一步是把事件转成 USB HID 信号注入主机。
这一步我用的不是自制硬件,而是一个现成的 USB Host 适配板,刷了兼容手柄协议的固件,主机把这块板子识别为一个标准手柄。服务端通过串口把按键值发给适配板,适配板再通过 USB 口告诉主机“玩家按下了 X 键”。
延迟链路是:触摸屏到浏览器(约 10ms),浏览器到服务端(局域网 <5ms),服务端到适配器再到主机(约 15ms),总共加起来在 30ms 上下。这个量级对比蓝牙手柄直连主机的 10ms 来说多了两倍,但局域网串流的画面传输本来就有几十毫秒延迟,整体仍处于可接受范围。
移动端虚拟手柄的键位布局我按官方手柄映射来排:左半屏方向键,右半屏动作键,L1/R1 和 L2/R2 放在顶部左右两侧,触屏区域支持多点触控,同时按多个键也不会冲突。组合键这块我单独做了处理:Share + 上截屏这类系统级操作,需要等几个触摸事件合并成一条组合指令再回传,否则主机收不到完整组合。
4. 实测数据与调优记录:延迟、画质、掉线那些事
4.1 不同网络环境下的延迟数据
我没有用专门仪器测,但通过服务端日志和前端渲染时间戳做了粗略统计。在纯局域网环境下,延迟分布大致如下:
| 接入方式 | 画面延迟 | 输入端到端延迟 | 体感 |
|---|---|---|---|
| 有线网 | 45ms 上下 | 18ms 上下 | 可玩动作游戏,手感接近直连 |
| Wi-Fi 6 | 60~80ms | 25ms 上下 | 大多数游戏没问题 |
| Wi-Fi 5 | 90~120ms | 30ms 上下 | 适合回合制/休闲游戏 |
| 移动网络 | 波动很大 | 不稳定 | 实际不建议 |
最令我意外的是有线网的延迟居然能做到 45ms 以下。玩射击类游戏时,准星移动会有轻微拖尾感,但足够打完一局;玩像素风平台跳跃、回合制 RPG、模拟经营类游戏,基本察觉不到延迟。
Wi-Fi 6 和 Wi-Fi 5 的差距主要来自网络拥塞,同一个 AP 下只要有人在看高清视频,串流延迟就会增加 20ms 左右。我给页面加了一个实时延迟仪表盘,开局前瞄一眼就知道这局适不适合玩对战游戏。
4.2 最常见的问题与完整排查链路
AnyPS5 调试阶段我遇到最频繁的问题是:手机页面黑屏但声音正常。
排查链路是这样的:先看浏览器控制台有没有报 WebRTCiceFailed;没有的话,再看服务端 ffmpeg 进程的输出,确认 RTSP 流是否正常;最后检查采集卡是否被系统识别。最终定位到根因:UVC 设备被 Linux 的电源管理挂起了,休眠之后没有自动唤醒。
解决方法是关掉采集卡的 USB 自动休眠,再给 udev 规则加一条常驻权限,问题就没再出现。
第二个高频坑是画面间歇性花屏。
一开始我怀疑是码率不够,把码率从 10M 提到 15M 还是花。后面用dmesg查 USB 错误,发现采集卡和移动硬盘抢带宽,两个设备挂在同一个 USB 控制器上。换了个 USB 口让采集卡独占一个控制器,花屏当场消失。
这类问题汇总成一张排查表:
| 症状 | 可能原因 | 排查顺序 |
|---|---|---|
| 黑屏无画面 | HDCP 保护 / 采集卡休眠 / 信号线松动 | 先换 HDMI 线,再看 USB 休眠,最后查 HDCP |
| 花屏 | USB 带宽争抢 / 线材质量差 | 看 dmesg,换 USB 口,换线 |
| 卡顿掉帧 | 网络拥塞 / 编码器跟不上 | 看延迟仪表盘,关 B 帧,降码率 |
| 音画不同步 | 时钟漂移 / 缓冲策略错误 | 切音频缓冲策略,开启 RTCP 反馈 |
| 手柄随机断开 | USB 节能策略 | 关闭主机 USB 休眠,换稳定供电的 HUB |
| 页面打不开 | 防火墙拦了信令端口 | 放行内网端口,检查固定 IP |
4.3 一篮子调优清单:从编码、传输到解码
延迟优化的本质是砍掉每一级缓冲。我整理了一份调优清单,照着做基本能把延迟压到最低:
- 编码端:关 B 帧,
tune=zerolatency,GOP 缩短到 1 秒; - 传输端:开启 WebRTC 的 FEC(前向纠错),局域网内丢包率高于 0.5% 时收益明显;关闭拥塞控制里的“自动降码率”,否则网络波动时画质会突然变糊;
- 解码端:播放器设置里把缓冲策略调整为“低延迟模式”,避免音频音视频缓冲池过大;
- 显示端:终端浏览器开启硬件加速,特别是电视盒子和 iPad 上,解码负担全交给 GPU 能省下一截延迟。
按这个清单调完之后,Wi-Fi 6 环境下的画面延迟从最初的 110ms 稳定到了 70ms 左右,已经进入可玩区间。
5. 踩过的坑与给后来者的建议
5.1 采集卡和 HDCP 的那些坑
市面上大量标着“4K 高清”的采集卡,实际只能采集 1080p 甚至 720p 输入,宣传里的“4K 输入”指的是能通过信号但内部直接降采样,买之前最好看有没有人实测过环出和直通的真实参数。
HDCP 这块单独提醒一句:主机游戏画面目前不受影响,但影碟播放和部分流媒体 App 会黑屏。这不是采集卡坏了,是版权保护机制在工作。我的处理是给电视机 HDMI 环出接了一个切换器,需要看影碟时切回电视直连。
5.2 浏览器的选择:Chrome 最省心,iOS 需要额外操作
浏览器兼容性直接影响体验。桌面端 Chrome 和 Edge 对 WebRTC 支持最完整,延迟表现最好;安卓手机上 Chrome 也可以,但要注意屏幕常亮设置,玩一会儿自动锁屏会导致回传链路断开。
iOS 端 Safari 对 WebRTC 支持得比前几年好多了,但部分老版本对 WebSocket 长连接的保活策略过于激进,串流 20 分钟左右就会自动断线。我在页面里加了心跳机制,每 30 秒发一次空事件保活,问题解决。iPad 用户如果遇到画面偏色,记得检查是否开启了“夜览”模式,这个会导致画面被加一层暖色调。
电视盒子是一个容易踩的环境:许多盒子的浏览器内核老旧,不支持 WebRTC。我的建议是直接装一个支持 WebRTC 的浏览器 App,别用自带浏览器凑合。
5.3 后续可以扩展的方向
AnyPS5 跑通之后,动手改的兴趣反而更大了。我目前留了几个扩展方向,打算后面陆续加进去:
- 自动录像:把串流流同时落地保存,配合主机自带的录制功能做双备份;
- 多用户会话:目前只支持单会话,改成多用户后可以同时让卧室和书房的设备观看比赛,但带宽和编码压力会成倍增加;
- 状态面板:在页面底部显示主机运行温度、当前游戏时长统计等本地能采集到的数据;
- 智能家居联动:进门自动唤醒串流页面,不过这个更多是折腾,实用意义不大。
最后分享一个小技巧:整个项目里最容易犯的错是“一上来就写代码”。我建议你先把采集卡插上电脑,用 OBS 之类的现成软件确认画面能正常出来,再着手写服务端逻辑。采集卡本身如果有兼容问题,再多的代码也救不回来。
AnyPS5 目前就在我家客厅到卧室的这条局域网里服役,日常使用率比预想高很多:工作日晚上用平板玩两把,周末在客厅沙发上用大屏玩,串流端成了“备用屏幕”。手头有闲置迷你主机或者旧笔记本的话,完全可以复刻这套方案,成本主要花在一张带环出的采集卡上,剩下的基本都是免费软件。