想和朋友一起看个片子、看一局游戏、看自己画图,最顺手的三条路都不太对劲:云会议要账号、画面过别人的服务器;公开直播等于把链接撒到大街上;远程桌面只能一对一,还得对方装客户端。Piik 走的是第四条路:房主开一个程序,观众用浏览器打开链接就能看,画面优先在设备之间直连,观众之间还能互相转发。它后端是纯 Go 的,编译完一个二进制就把站点、房间、信令一起带走。在云栈社区搜索开源屏幕共享方案,这个项目的出镜率其实不低。这篇拆它的树状转发、打洞逻辑、编码复用,以及 Windows、macOS、Linux 三套完全不同的原生采集管线。
周五晚上十点,群里四个人同时冒泡:开一把,看你怎么打。
你手头一局单机游戏,想让他们看全过程;或者你下好了片子,想一起吐槽;再或者你在画板上涂鸦,想让人围观。这些需求翻译成一句话就是:一个人播,几个人看,画面别过第三方服务器。
听着简单,做起来处处是坑。先看现成的路。
一、三条现成的路,各有各的别扭
第一条,拉个云会议。 腾讯会议、Zoom、Discord,都有屏幕共享。能用,但你会一路撞上这些事:每个人都要账号;免费版有会议时长和人数限制;画面进的是厂商的服务器,清晰度由会议引擎的「会议」标准决定——它优先保证声音不断,画面糊一点无所谓。更要命的是,你们的屏幕内容在一台你管不着的机器上过了一趟。
第二条,开个直播间。 B 站、抖音、Twitch,或者自己搭一套 Owncast。这条路的问题是「公开」:链接虽然只有四个人知道,但它是面向公众的服务,谁拿到谁就能看,平台也留着记录。而且直播走的是分段分发,延迟按秒算,你在游戏里被打了,三秒后弹幕才「啊?」——一起看的气氛全没了。
第三条,远程桌面。 TeamViewer、Parsec、Sunshine 配 Moonlight。延迟低、能操作,但它是一对一的模型:一个人看,一个人控。四个人同时看,你要么开四次,要么把四个人拉进一个会议——绕回第一条。
这三条路我都不太喜欢。它们共同的前提是:要么把画面交给一台中心服务器,要么把内容变成公开广播,要么把观众限制成一个人。
Piik 的前提换了:房间里就一个房主和最多二十个朋友,画面上限不是「能播给多少人看」,而是「这几个人看得清不清」。

二、它长什么样:一个房间,一张沙发
第一次打开 Piik 的分享页,你会怀疑这是个休闲小游戏。
画面是一个带圆角的播放器框,中间是正在共享的内容。框下面有一张沙发,坐着一排小人——房主头上顶一顶小金冠,其他人的名字写在身下:摸鱼办主任、派大星、大聪明、咸鱼突刺。底部一条白色控制条:房间号 9527,旁边是房主名,右边三个按钮——编辑、连接详情、连接拓扑。
播放控制条该有的都有:音量、音量显示、画中画、弹幕开关、播放设置、全屏。右上角还有三个小东西:一颗「服务器已连接」的绿点、一个「App」按钮、一个「中 / EN / ✦」的语言切换——最后一个 ✦ 是纯视觉模式,把文字全换成图形。

沙发不是装饰。它是这个产品的世界观:这是一间客厅,不是一间会议室。 会议室里有主持人、有举手、有静音所有人;客厅里只有「你播,我们看,顺便聊两句」。
真正的功能藏在两个地方。一个是开始分享时的「选择分享来源」面板,四个页签:浏览器(标签页)、摄像头、程序 / 窗口、屏幕。

另一个是分享设置里的画质面板:三张预设卡片——轻量 720p 30 帧、高清 1080p 30 帧、高帧率 1080p 60 帧;下面一行「画面细调」写着当前值 1920 × 1080、30 fps、5 Mbps,可以展开改成别的。

这三个数字不是拍脑袋写的,是代码里的默认值:分辨率 1920×1080、帧率 30、码率 5 Mbps、音频 128 kbps。想更高就自己调,但默认档给的是「大多数家庭上行扛得住」的那一档。
三、房主装的不是播放器,是一个本地服务
这一点很多人第一次用会愣一下:Piik App 打开之后,没有主窗口。它启动一个本地服务(默认端口 8787),然后调用系统默认浏览器打开页面。
macOS 上是 open,Windows 上是 rundll32 url.dll,FileProtocolHandler,Linux 上是 xdg-open——就是这三个系统命令,没有内嵌浏览器引擎,没有 Electron,也没有 Tauri 的 WebView 壳。
这个决定换来的是体积:macOS ARM 版解压后三十几兆,Windows 版三十几兆,Linux 版三十几兆;连服务端程序本身也才十四兆出头。作为对比,一个空壳的 Electron 应用起步就上百兆。
但「不开窗口」不只是为了省体积。它真正的理由是:这个产品的界面,本来就应该由浏览器来渲染。
WebRTC 的发送端能力,浏览器里是最全的:编码器选择、拥塞控制、码率自适应、丢包重传,全是现成的、跟着 Chrome 一起更新的实现。自己写一个播放器去连 WebRTC,等于把这些活重新做一遍,还做不过浏览器。所以 Piik 的做法是——界面、播放、语音、聊天全在浏览器里跑,只有浏览器给不了的那部分,才做成原生程序。
浏览器给不了什么?屏幕采集的质量控制。这一条后面单独讲,它是整个项目里工程量最大的地方。
于是 App 的形态就定了:一个 Go 写的本地服务,负责房间、信令、媒体转发和原生采集的调度;界面用系统浏览器打开;分享时按需拉起采集子进程。下面这张图是它的骨架。

顺带一个细节:App 里同时有「本地房间」和「公网邀请」两种模式。本地房间给你一个局域网地址,同一个 Wi-Fi 下的朋友直接打开;公网邀请则临时把房间映射到公网——这个机制很有意思,后面单开一节。
四、一棵树,不是一张网
现在进入正题。二十个观众同时看,画面怎么送到?
最朴素的想法是房主连二十条连接,一人发一份。这在技术上可行,在物理上不行:一份 1080p 画面按 5 Mbps 算,二十份就是 100 Mbps 的上行,家庭宽带上行通常只有几十兆,而且二十条连接同时做拥塞控制,互相踩踏,最后谁都看不清。
第二个想法是全部走服务器转发(SFU),像会议软件那样。这能解决上行问题,但把成本换到了服务器:一路 5 Mbps 进来、二十路出去,一百兆带宽,谁的服务器谁心疼。
Piik 选了第三条:观众之间互相转发,长成一棵树。
服务端有一个叫路由控制器的组件,它维护的是一张有版本号、无环、从源可达的图。规则很干脆:房主没有上游;每个观众最多一个上游;健康且没受影响的边尽量不动——粘住比重新平衡更划算。

为什么是树不是网状?因为网状意味着每个人给每个人发,连接数按平方涨,五个人的房间里就有十对连接互相比带宽。树只有 N-1 条边,每个观众只收一份流、只往上游要一份,其余的分给下游。
这也是它跟「SFU 会议」的本质区别:会议是星形,所有人都对着服务器;Piik 是树形,服务器大多数时候只负责告诉大家「你该连谁」。
五、二十个人怎么挤进两份副本
树要立得住,得先回答一个问题:一个节点能同时给几个人发流?
Piik 的答案是一个统一的容量规则:每个非服务器端点,稳态下允许的上行副本数 C,默认 2,可以配成 1、2 或 3。
具体怎么算:
- 一个普通的下游观众,占用一份副本;
- 房主那条通往服务器 SFU 的发布流,也占用一份副本;
- 上游接收不占副本;
- 角色、浏览器、设备、是否可见、编码格式,都不额外加一档;
- 客户端可以上报「我现在只剩 0 到 C 份」,服务端按上报值算有效容量。
还有一条容易被忽略的细节:在换父节点的过渡期,允许临时多占一份,也就是最多到 3 份——但第 4 份永远不会被批准。这个上限是硬编码在协议常量里的。
于是问题来了:房主只有 2 份,二十个人怎么办?
答案是「让愿意的人当中继」。房间里任何一个观众,都可以被指定为转发志愿者,它的准入扇出上限会放开——代码里那个常量直接写成了房间观众上限,也就是 20。也就是说,一个带宽富余的观众,理论上可以一个人喂饱全场。
这听起来像是把房主的问题转嫁给了某个倒霉朋友,实际上不是:只有当那个客户端主动上报「我还有余量」时,服务端才会把下游派给它;而且它一旦断开或者容量下降,服务端会把它的下游重新挂到别处,而不是让整棵树塌掉。
选谁当上游,规则写在注释里,按顺序比五条:
- 挂上去之后深度最浅的;
- 剩余稳定容量最大的;
- 一个稳定的父子排序位次;
- 加入房间的先后;
- 对端身份。
注意这个列表里没有什么:没有 IP 地址、没有地理位置、没有 NAT 类型猜测、没有浏览器型号、没有「质量评分」。这是一个很克制的选择——离你近的不一定带宽够,带宽够的不一定稳定,与其猜,不如用能测量的事实排序。
质量也是靠测量的:每个接收端每两秒上报一次观测结果,五秒不报就作废。服务端拿这些事实去判断「这条边还行不行」,而不是拿一个自己发明的分数。

这里有个反常识的地方:这不叫「P2P 加速」,这叫「把上行当成稀缺资源来分配」。 大多数 P2P 方案想的是「多找几个源并行下载」,Piik 想的是「谁还有余量,就把谁挂上去」。前者优化速度,后者优化「二十个人能不能同时看完」。
六、打洞:先试直连,再谈别的
树的结构定了,接下来是每一对父子怎么连上。这一步是 WebRTC 的老问题:两个人在各自的 NAT 后面,怎么建立一条直连?
标准答案是 STUN:双方各自问一台公网服务器「你看到我的地址端口是什么」,拿到自己的公网映射,然后互相往对方的映射上发包,运气好就打穿了。Piik 的 STUN 是内嵌在程序里的,不需要你另外跑一个 coturn。
但标准答案有个明显的缺口:对称型 NAT 会给每个目标分配不同的端口,你从 STUN 那儿学到的端口,跟发往对方的那个端口不是同一个,打洞失败。
Piik 在这里做了一件稍微进阶的事:NAT 端口预测。
原理不复杂:很多 NAT 分配端口是有规律的,比如每次加 4。所以它不只试 STUN 给的那个端口,还会按步长往上猜几个,猜的范围有明确上限——步长最多 16 步,每一轮都拿一个 32 字节的密钥做校验,确认对面确实是那个该连的人。
这个机制默认是关的,属于「可选增强」。因为它有代价:多试端口意味着多发包,也可能在某些网络里触发防护。所以它被设计成一个开关,而不是默认行为。
还有一条要写清楚的硬约束:媒体必须走 UDP。 网页能打开不代表画面能连上——网页走的是 HTTPS,媒体走的是另外一条 UDP 通道。公司网络、校园网、某些 VPN 会把 UDP 掐掉,这时候你看到的现象是「页面正常、画面转圈」。
遇到这种情况,Piik 的文档给的建议很实在:先拿手机热点开同一个邀请,热点能看、原网络不能看,那就去查原网络的出口限制,而不是怀疑软件坏了。

七、SFU 兜底:一个除法,不是加法
打洞失败、树也挂不上,最后一道保险是服务器转发(SFU)。但这里有个很容易做错的地方,也是 Piik 文档里写得最较真的一段。
想象一个场景:房主发一路 1080p,十个观众里有六个能直连,四个只能走服务器。服务器应该向房主要多高的画质?
直觉答案:四个人的需求相加?不对。正确公式是取最大值:
服务器的上游需求 = 它直接订阅者的需求里的最大值
房主的总需求 = max(直连观众的需求,服务器的上游需求)
也就是说,服务器不是把四个人的需求加起来向房主要,而是要一份「够最高那个用的」就够了——因为它转发的是同一份编码,不是给每个人单独转码。四个人的需求不一样怎么办?服务端会让房主产出「最高需求那一档,加上低一档的回退」,让带宽差的人取低档。
这个区分很重要:转发是复制,不是相加。 只有当服务器要做转码(把 1080p 转成 720p 再发)时,它才真的需要更多算力;而 Piik 的 SFU 只做转发,不做转码,所以它的上游需求永远是「最大的那一个」。
顺带一个产品层面的设计:服务器转发不是白给的。自建站点默认不带这个能力,要管理员显式开启;而且房主这边有个「隐私模式」——一旦打开,就退化成纯 P2P,服务器连流都看不到。
这两个开关是互斥的:想要「服务器帮忙兜底」,就得接受「服务器能看到画面」;想要「服务器什么都看不到」,就得接受「打洞失败就没法看」。Piik 没有假装这件事可以两全,它让你自己选。

八、编码一次,分给所有人
树和 SFU 解决的是「流怎么走」,还有一个更底层的问题:同一个人要看不同画质,要编码几次?
如果每个下游单独编码,房主的 CPU 会被吃干:五个人看,五个编码器,1080p60 的实时编码一个就够呛。
Piik 的做法是分层的:一路共享编码,最多三层(simulcast),格式是 H.264 或 VP8。每一层是一个分辨率档,下游按自己的带宽取一层。房主只编码一次(严格说是一次编码产出多层),服务器和中间节点只做转发。
实现上还有一条「需求包络」的规则:一个已经在用的编码组,会产出「当前最高需求那一档,加上它的低档回退」,然后停掉没有下游需要的更高档。不是对每个人都开一个编码器,也不是无脑编到最高。
更妙的是浏览器这一侧的优化。普通 WebRTC 里,一路画面要发给三个下游,浏览器会编三次——因为它把编码绑定在发送连接上。Piik 在浏览器里做了一个「本地编码池」:用一个独立的本地编码器编一次,把编码后的帧直接喂给多个发送连接,靠的是 Chromium 暴露的 createEncodedStreams() 接口。
这个改动听起来只是省 CPU,但它解决的是一个体验问题:低配笔记本开播时,风扇不再起飞。 一个人看和十个人看,编码开销几乎一样。
当然有代价:这个接口不是所有浏览器都支持,不支持的会静默退回普通路径;而且编码出来的帧要按每条连接自己的时钟重新打时间戳,参考帧的管理也得跟着走——文档里花了整篇 ADR 讨论这件事的边界,包括「不支持时不要伪造统计」。

九、为什么要自己写采集:浏览器给不了的东西
前面说过,Piik 把界面交给浏览器,只把采集做成原生。这一节解释为什么这件事绕不过去。
浏览器采屏(getDisplayMedia)能满足「能看到画面」,但满足不了这几个需求:
第一,声音。 浏览器能采系统声音,但通常只能采「全部」;Piik 要的是「除了某个程序之外的全部」——比如你在共享游戏,同时开着语音软件,你不想让朋友听到语音软件里别人的声音在回音。Windows 上这件事可以做到按进程排除:把选中的进程树从系统声音里摘出去,其余照采。
第二,画质控制。 浏览器对采集的分辨率、帧率、码率控制得很粗,你没法告诉它「保持这个窗口的原生分辨率,别缩放」,也没法在 Windows 上做 HDR 到 SDR 的正确转换。而共享 HDR 游戏画面到 SDR 显示器的朋友,不做色调映射就是一片惨白。
第三,边框。 屏幕采集时系统会画一圈高亮边框,Windows 11 之后的 API 允许隐藏它。这个细节在「我要给朋友看电影」的场景里很关键——边框一闪,沉浸感全没。
于是三个平台各写一套:
Windows 走 Windows Graphics Capture 抓帧、D3D11 做处理,编码用 Media Foundation 的硬件 H.264,或者 libvpx 的 VP8;HDR 画面保持 scRGB 浮点,最后做色调映射转 SDR;声音用应用级回环接口,支持按进程包含或排除。视频和音频跑在两个独立的有界子进程里,音频初始化失败时视频还能继续,只在界面上报「音频不可用」,而不是整个源失败。
macOS 走 ScreenCaptureKit 选源和抓帧,编码强制要求 VideoToolbox 的硬件 H.264(没有硬件编码器就不干这活),输出 Annex-B 格式的码流;声音从 ScreenCaptureKit 采到后转成双声道 PCM,再交给 Go 那侧的 Opus 编码。
Linux 把选择权交给系统:XDG Desktop Portal 负责让你选屏幕或窗口,PipeWire 提供流,GStreamer 里挑一个被标记为硬件 H.264 的编码元件。它明确不提供按进程录音——这是平台能力的边界,不硬撑。
三个平台的实现细节差别巨大,但对外暴露的是同一套契约:拿到当前的分辨率、帧率、码率、画质偏好,输出编码后的帧。所以你在界面上改画质,Go 那边不用重建整个会话,只要换掉采集侧的那一代进程。
还有一个工程细节值得说:Windows 的编码器选择有个「自动」模式,它不是查表猜的,而是真的跑四秒钟,量一下实际帧率——H.264 能稳住目标帧率就用硬件 H.264,稳不住就换 VP8 软件编码。选定之后不随画质和源切换而变,避免一会儿硬编一会儿软编的抖动。

十、公网邀请:把家搬到 Cloudflare 后面
现在回到那个「不买域名也能让别人打开」的问题。
自建站点当然可以:一个二进制跑起来,配好域名和 HTTPS 反代,就有了自己的 Piik 站。但大部分人没有服务器,也没有域名。所以 App 里有个「公网邀请」。
它的实现方式我看到的时候笑了:拉起一个 cloudflared 子进程,从它的输出里把 https://xxxx.trycloudflare.com 这个临时域名抓出来,然后拿这个域名当邀请地址。
对,就是 Cloudflare 那个免费的快速隧道。不用注册、不用备案、不用开路由器端口,几秒钟给你一个能公网访问的临时地址,指向你本机的 8787。
代码里对它的处理相当务实:启动最多等三十秒,失败重试四次,每次间隔一秒;超时或者进程提前退出,就报一个「服务没连上,重开 Piik 再试」的明确错误,而不是让你对着转圈猜。
这个设计的取舍也很清楚:临时域名每次都不一样,所以「邀请链接」是临时的;隧道本身依赖 Cloudflare 的可用性;而且它只是把「网页」搬到公网,媒体还是走 P2P——也就是说,朋友能打开页面不等于能看画面,中间还隔着 UDP 打洞那一关。

十一、从 Node 搬回 Go:省下的那个运行时
这个项目早期是 TypeScript 写的服务端。后来作者把整个后端迁到了 Go,理由写在架构决策记录里,算的是一笔很实在的账。
原来的形态是:一个 Node 服务管 HTTP、房间、准入、信令和路由;打包成 App 的时候,要在旁边塞一个 Node 运行时和一整棵已安装的依赖树,还得有个进程守护去管两边的生命周期;生产环境装一次,得先有 Node 和 npm。
问题是——App 本来就已经有一个 Go 进程了,它管着 Pion 的媒体、管着采集子进程。再带一个 Node,只为跑那份服务端代码,等于为了同一件事养两个运行时。
迁完之后:一个 Go module 管全部核心;piik-server 和 piik-app 两个入口共用同一套房间语义;浏览器界面还是 React/TypeScript,但 Node 和 Vite 退回到「构建工具」的位置,不再是任何一台机器上的运行时依赖;编译产物里内嵌了打包好的前端,一个二进制就是完整站点。
迁移里最容易被忽略的是并发模型。Node 是单线程,所有房间操作天然串行;Go 是多线程,一不小心就有竞态。作者的处理方式是:用一个互斥锁守卫所有房间、会话、分享、路由和 SFU 账本的变更,把 Node 的单线程语义原样复刻过来,然后在每个原本 await 的地方显式放锁、做完 I/O、重新拿锁,并且重新校验 await 期间可能变化的所有身份。
注释里对这件事的定性很清醒:这是一个「已记录的天花板」,升级路径是按房间分锁,而不是宣称它能扛多少并发。
数据库也换成了纯 Go 的 SQLite 驱动(modernc.org/sqlite),好处是编译不需要 C 工具链——三平台交叉编译不用配 gcc,这对一个要发五个平台安装包的项目来说省了很多事。
存储策略也值得一记:只有一张 rooms 表;打开数据库时用排他锁模式,启动做一次完整性快检,坏得救不回来就直接结束进程,而不是带着不一致的状态继续跑。这个「fail fast」的选择不讨喜,但对一个管着房间权限的程序来说是对的。

十二、让朋友接手:输入怎么过去
Piik 有个功能是别的屏幕共享工具少见的:让朋友直接操作你的电脑。
房主通过 App 分享一个窗口或整个屏幕,打开「远程控制」,默认是「每次询问」。朋友在播放控制条上点「请求控制」,房主在页面上点「允许」,然后就真的能动了——键盘、鼠标,还能多人同时控制。
多人同时控制这件事比听起来难。两个人共用一个鼠标指针,你按 W 他按方向键,游戏收到的应该是两个独立的输入源,而不是一串互相覆盖的事件。Piik 的做法是分别记录每个人的按键和鼠标按钮,退出时只释放这个人的那一份,不碰别人的。
手柄是另一条线。Windows 上要装一个虚拟手柄驱动(ViGEmBus),Piik 检测到驱动就创建一个虚拟 Xbox 360 手柄给每位远程玩家;Linux 走系统 uinput;macOS 目前没有——文档写得很直白:原生后端需要 Apple 授权。
输入本身怎么传?不是走聊天通道,而是走媒体路径上的加密数据通道。信令阶段只做身份和密钥的交换:一个 16 字节的端点 ID,配一个 32 字节的密钥。输入内容不进房间聊天、不进存储、不进诊断日志——这一点在代码注释里写得很明确。
这个设计的安全含义值得说清楚:远程控制是「房主点了允许」才建立的,而且允许是每次询问。它不是一个常开的通道,房主关掉分享或者结束控制,通道就没了。

十三、和同类比:它站在哪个位置
讲完机制,该摆一摆货架了。同类工具我按「它到底解决什么问题」分成四类。
第一类,只做屏幕共享的开源工具。 Screego 是这个位置的代表:Go 写的,自带 TURN,单二进制或 Docker 部署,浏览器看,延迟低、清晰度高。它的自我定位写得非常清楚——「只帮你共享屏幕,别的什么都不做」。没有语音、没有聊天、没有远程控制、没有一起看的氛围。Piik 在这个基础上加了语音、聊天、弹幕、远控和「房间」这个概念。
第二类,自建直播站。 Owncast 是典型:RTMP 推流进去,HLS 分发出来,自带聊天,观众规模可以很大。代价是延迟按秒算,而且它本质是「播给一群人看」,不是「几个朋友一起看」——你想暂停、想倒回去吐槽一句,HLS 那套分发不配合。
第三类,会议软件。 Jitsi、腾讯会议、Zoom、Discord。优势是成熟、稳定、大家都装了。代价是账号、是服务器中转、是「会议」这个框架带来的画质策略。Jitsi 可以自建,但你要为一台 SFU 服务器付带宽钱。
第四类,远程桌面 / 游戏串流。 Sunshine 配 Moonlight、Parsec。延迟做到极致,控制也是原生支持,但模型是一对一:一个人玩,一个人看。Moonlight 还得装客户端。
Piik 的位置挺特别:它把「屏幕共享」和「一起看」缝在了一起,用 P2P 树压低服务器成本,用浏览器解决观众的安装成本,用自建解决「数据归谁」的问题。
| 方案 |
观众要装什么 |
画面怎么走 |
主要代价 |
| Piik |
浏览器,零安装 |
P2P 树优先,自建可开服务器兜底 |
最多二十人;必须 UDP;无 iOS |
| Screego |
浏览器 |
P2P + 自带 TURN |
只有共享,没有语音/聊天/远控 |
| Owncast |
浏览器 |
服务器分发(HLS) |
延迟按秒;要一台服务器 |
| Jitsi / 会议软件 |
客户端或浏览器 |
服务器转发(SFU) |
账号;服务器带宽成本;画质按会议标准 |
| Discord |
客户端 |
厂商服务器中转 |
账号;内容过第三方;画质看套餐 |
| Sunshine + Moonlight |
必须装客户端 |
点对点串流 |
一对一;要端口映射;偏游戏场景 |
这张表不是要证明谁更强,而是想说:这几个工具解决的不是同一个问题。 你要开会,用会议软件;你要播给几千人看,用直播站;你要低延迟玩别人的电脑,用串流。你要「四个人周五晚上一起看一局游戏,画面别过别人的服务器」,这中间确实有个空档。

补一句公道话:拿 Piik 和会议软件比「稳定性和功能完整度」,它一定输。它是个年轻项目,用户量、平台覆盖、稳定性都还在长。但它在「私密、低延迟、观众零安装」这个交叉点上,给出的解法比上面任何一个都更贴题。
十四、代价与边界
好话说完,账要算清楚。以下每一条都不是我推测的,是它的文档和代码里明写的。
第一,二十个人是硬上限。 房间上限写在协议常量里:二十个观众加一个房主。这不是「建议值」,是协议的极限。想播给五十个人看,这个工具不适合。
第二,媒体必须走 UDP。 公司网络、校园网、某些 VPN 会掐掉 UDP,这时页面能打开但画面连不上。它给了绕法:自建站点开 SFU,或者用虚拟局域网工具(Tailscale、ZeroTier 那类)走本地房间。但绕法都需要额外的机器或配置。
第三,聊天不加密。 文档里直接写着:聊天消息经过房间所在的服务器,不提供端到端加密。画面是 WebRTC 的 DTLS-SRTP 加密的,聊天不是。别在里面发密码。
第四,P2P 会暴露地址。 直连意味着对方能看到你的网络地址。这是 P2P 的物理事实,不是 bug。介意的话,用自建站点的 SFU 模式,代价是服务器能看到画面。
第五,平台覆盖不均。 Windows、macOS、Linux、安卓都有客户端;iOS 没有。手柄只支持 Windows(要装驱动)和 Linux;macOS 明确不支持。Linux 的按进程录音也不提供。
第六,公网邀请依赖 Cloudflare。 临时域名每次不一样,隧道挂了就换一个;如果 Cloudflare 的快速隧道服务哪天变了,这个功能就得改。
第七,它不是会议软件。 没有举手、没有主持人权限、没有录制、没有会议纪要。它就是个客厅。
这一节最后那张三栏表,是整篇里最该被截图保存的一张。

十五、装机:四步开播
装起来比想象中简单。
第一步,选对包。 到发布页找以 piik-app 开头的压缩包:Windows 是 piik-app-windows-amd64.zip,macOS 分 Intel 和 ARM 两个(M 系列选 arm64),Linux 分 amd64 和 arm64。安卓有通用 APK。服务端是另一个包,名字里带 runtime。
第二步,解压运行。 没有安装向导,解压出来直接跑。第一次运行会问你要不要用公网邀请,选它,浏览器会自动打开。
第三步,选源。 「开始分享」→ 选屏幕 / 窗口 / 标签页 / 摄像头 → 选要不要带系统声音 → 调画质。默认 1080p30 五兆,够用;想更清楚就展开细调。
第四步,发链接。 「复制邀请链接」发给朋友,他们用浏览器打开就行。分享期间别关那个标签页。
自建的话,下载 runtime 包解压运行 ./piik-server,打开 http://localhost:8787 就是自己的站。公网用要配域名、HTTPS 反代和 STUN 地址;房间数据存在本地 SQLite 里。也有 Docker Compose 模板。
一个小提醒:浏览器采集需要 HTTPS 或者 localhost,这是浏览器的安全限制,不是 Piik 的。所以「用网页版开播」必须在 https:// 的站点上,本地文件打开是不行的。
结语
回头看,这个项目最值得学的地方不是某个算法,而是它对「什么该自己做、什么该交给别人」的分寸。
界面交给浏览器,因为浏览器的 WebRTC 实现比你自己写的强;编码复用交给 Chromium 的接口,因为它已经把编码器优化到极致;NAT 打洞交给内嵌的 STUN,因为这是标准活;公网映射交给 Cloudflare 的免费隧道,因为自己搞域名和证书是纯粹的浪费。
反过来,采集自己写。因为浏览器给不了按进程排除声音、给不了 HDR 正确转换、给不了「别给我画边框」。这些细节没有一处是「技术炫耀」,全是从「我要给朋友看个电影」这个场景里长出来的。
而那棵 P2P 树,本质上是一次资源分配的重新表述:不要问「怎么把画面推给二十个人」,要问「这二十个人的上行带宽,怎么凑出一份完整的画面」。
工具的价值往往就在这种表述的转换里。会议软件问的是「怎么让所有人进同一个房间」,直播平台问的是「怎么让一万个人同时看到」,Piik 问的是「怎么让四个朋友看得像坐在同一张沙发上」。
判断一个共享工具值不值得装,看它把你的画面送到了哪里:送到别人的服务器上,你租的是方便;留在你和朋友的机器之间,你拥有的是这件事本身。
参考
- Piik 仓库:
https://github.com/TNTcraftHIM/Piik — 源码、架构决策记录与部署文档
- Piik 官网:
https://piik.tv/ — 下载页与文档中心
- 在线版演示:
https://demo.piik.tv — 不开客户端也能建个房间试试
- Screego:
https://github.com/screego/server — 同类的开源屏幕共享,可对照看差异