前阵子因为一个实时互动项目需要做音视频通话,我从头把 LiveKit 摸了一遍。当时网上关于它的中文资料还比较零散,官方文档虽然全,但对于只想“先跑起来”的人来说,信息量反而有点大。这个标题叫“简单搭建 LiveKit 环境并初步认识”,实际上我搭的时候踩了不少坑。这篇文不打算写得太深,主要把从零到一跑通环境的完整过程、核心概念和几个容易卡住的地方讲清楚。如果你正准备接触实时音视频,想试试 LiveKit,可以直接照着下面的流程走一遍。

LiveKit 是一个开源的实时音视频通信平台,提供 SFU(选择性转发单元)架构的 WebRTC 能力。做音视频通话、直播连麦、在线会议、远程协作这类场景,它把最复杂的 WebRTC 信令协商、媒体转发、房间管理、录制这些能力都封装成了标准服务,开发者只需要接好客户端 SDK,再写一个签 token 的服务端接口就行。

1. 初步认识 LiveKit:它到底解决什么问题

1.1 实时音视频开发的基本盘

做实时音视频前,先得知道这个领域有哪些“重活”。直接裸用 WebRTC 做两个人之间的视频通话,最简化链路也需要自己处理信令交换、ICE 候选收集、STUN/TURN 打洞穿透、媒体协商、带宽估计、丢包重传、音视频同步这些问题。两人通话勉强能扛,一旦场景变成多人会议、连麦直播,点对点网状网络对上行带宽和 CPU 的压力会指数级上升,普通机器根本撑不住。

LiveKit 用的是 SFU 架构,即每个客户端把自己的音视频流推到服务器,服务器再按需把这些流转发给房间里的其他人。这种方式好处很明显:客户端只上推一路,订阅需要的下行流,大房间场景下的性能和带宽问题被服务端统一解决。和传统的 MCU(多点控制单元)混流方式相比,SFU 不重新编码,延迟更低,CPU 开销也更小,还保留了每路流的独立清晰度。

1.2 LiveKit 整体架构速览

LiveKit 的核心是 livekit-server ,一个 Go 语言编写的服务进程。它内部同时承担信令服务和媒体转发两个职责:客户端通过 WebSocket 和 HTTP 跟它完成入房、发令牌、交换 offer/answer 的信令流程;媒体数据则依赖 WebRTC 的 SRTP 通道,在服务端完成 SFU 转发。

在这个架构里,有几个最基础的概念必须提前建立:房间、参与者、音轨。房间是一个逻辑上的会话单位,比如一个会议就是一间房;参与者是房间里的人,既可能是真实用户,也可能是机器人客户端,甚至 CLI 工具模拟的连接;音轨则是参与者发布的音频或视频流。客户端 SDK 负责把麦克风、摄像头、屏幕窗口包装成音轨发布到房间,同时订阅其他参与者的音轨。理解这三个概念,后面看任何 demo 代码都会清晰很多。

1.3 为什么选 LiveKit 而不是其他方案

项目选型时我对比过 Janus、mediasoup、以及一些商业服务。LiveKit 的定位是“自带电池的实时音视频平台”,和 mediasoup 这种需要自己组装信令层的库相比,LiveKit 开箱即用:服务端装好,客户端 SDK 一接,跑通一条完整链路的时间可以控制在个位数小时内。与 Janus 相比,LiveKit 的插件体系和配置方式更现代,默认就带 TURN、录制(Egress)、实时消息等能力,社区活跃度也高。

下面是几个关键维度的对比,方便你做选型时参考:

维度 LiveKit mediasoup Janus 商业云端方案
信令服务 内置完整实现 需自研 需对接插件 云端提供
SFU 媒体转发 内置 内置 插件化 云端提供
客户端 SDK 官方多端齐全 官方基础 + 社区 官方基础 官方齐全
TURN 中继 内置 需另装 需另装 云端提供
录制、采集 Egress 内置 需扩展 插件 云端提供
上手成本 低 中 中高 最低,但按量付费

总结一句:想快速做出一个可靠的自建实时音视频系统,LiveKit 是目前综合性价比很高的选择。

2. 搭建前的准备与核心概念

2.1 环境规划与端口要求

搭建之前,先把环境规划和端口要求讲清楚。LiveKit 服务端对硬件要求其实很低,2C4G 的轻量服务器足够跑几十路媒体转发的测试负载。系统推荐 Debian/Ubuntu 系 Linux,Windows 和 macOS 也可以跑,但生产环境不建议。如果你只是本机体验,直接用 Docker Desktop 也行。

端口方面,严格按官方默认值记:

  • 7880 :HTTP/WebSocket 信令端口,客户端 SDK 会连接这个端口。
  • 7881 :RTC 的 TCP 端口,用于媒体传输的备份通道。
  • 50000 到 50100 :媒体传输的 UDP 端口范围,这是最重要的部分,SFU 转发媒体流依赖这段端口。

注意:云服务商的防火墙和安全组里,除了 7880 的 TCP,一定要放行 7881 TCP 和 50000-50100 UDP。很多人跑通服务但连不上媒体,就是只放了 TCP 端口,UDP 被防火墙挡了。

生产环境如果要再用内置 TURN,还需要开 3478 UDP 和 5349 TCP。测试阶段可以先不碰 TURN。

2.2 API Key/Secret 与 token 鉴权机制

这是 LiveKit 安全模型里最关键的部分。LiveKit 使用 API Key 和 API Secret 两个东西做服务端鉴权。API Key 是公钥性质的标识,可以理解为一个用户名;API Secret 是对称密钥,类似密码。这两个字符串在服务端配置中指定,客户端 SDK 连接房间时并不直接携带它们,而是携带一个由业务服务端生成的 JWT token。

token 的生成规则本质上是一个 HS256 签名的 JWT。payload 里要包含用户身份、房间名、参与者权限等关键字段,并用 API Secret 做签名。业务服务端持有 Key/Secret,所以只有你信任的服务器才能签发有效的 token,客户端本身是无法伪造的。

这种设计把敏感密钥从客户端剥离,同时让你在业务后端可以灵活控制:验证用户是否付费、是否允许发布音轨、能否订阅别人,这些逻辑都集中在 token 签发处。比如一个免费用户只能进房看,不能发布画面,直接在 token 里把 video.canPublish 设为 false 就行。

2.3 用 Python 生成一个最小 token

官方提供了多种语言的 token 生成 SDK,包括 Go、Python、Node.js、Java 等。如果不想引入 SDK,手写一个 JWT 也很简单。下面是一个最小可用的 Python 版本,只依赖 PyJWT 库:

import jwt
import time

API_KEY = "YourAPIKey"
API_SECRET = "YourAPISecret"

def generate_token(identity, room):
    now = int(time.time())
    payload = {
        # 子用户身份,在房间内显示的名字,必须唯一
        "sub": identity,
        # 签发客户端权限
        "video": {
            "room": room,
            "roomJoin": True,
            "canPublish": True,
            "canSubscribe": True,
        },
        # 有效期
        "iat": now,
        "exp": now + 6 * 3600,
    }
    token = jwt.encode(payload, API_SECRET, algorithm="HS256", headers={"kid": API_KEY})
    return token

if __name__ == "__main__":
    print(generate_token("test_user", "my-first-room"))

注意 JWT 的 header 里必须带 kid 字段,值是 API Key,否则 LiveKit 服务端不认。以前我做过一个项目,迁移到 LiveKit 时漏了这个 header,调试了很久才发现问题是 token 头缺字段。

如果你用 Java 写服务端,逻辑完全相同,只是换了 JWT 库。LiveKit 官方还提供了一个 Java SDK io.livekit:livekit-server ,里面封装了 AccessToken 、 RoomServiceClient 等组件,直接用封装会更省事,但核心鉴权逻辑还是上面这套 JWT 规则。掌握 JWT 本身比背 SDK 更重要,因为它解释了整个鉴权链路的本质。

3. 环境搭建实战:从零跑通完整链路

3.1 两条命令把 LiveKit Server 跑起来

最简单的部署方式是 Docker。用一条命令就能把服务拉起来测试:

docker run --rm -p 7880:7880 -p 7881:7881 -p 50000:50000/udp livekit/livekit-server --dev --bind 0.0.0.0
  • --dev 模式会自动生成一个开发配置,包括临时的 API Key/Secret 和自签 TLS 证书。
  • --bind 0.0.0.0 允许外部网络访问,否则 Docker 默认只监听本机回环。
  • 生产环境不要用这段命令,它只用于快速验证。

跑起来后日志里会出现自动生成的 API Key 和 API Secret ,还有服务监听的地址。开发模式方便是真的方便,但有个坑:自签证书在 Web 端调用麦克风摄像头时会遇到浏览器安全限制,后面会专门讲。

3.2 用配置文件启动正式服务

--dev 模式适合第一次验证,正式环境还是用配置文件启动。LiveKit 的配置文件是 YAML 格式,最简配置如下:

port: 7880
rtc:
  tcp_port: 7881
  port_range_start: 50000
  port_range_end: 50100
  use_external_ip: true
keys:
  DevAPIKey: DevAPISecret
logging:
  level: info

保存为 livekit.yaml ,然后执行:

./livekit-server --config livekit.yaml

启动成功后,日志中会显示当前配置的端口范围、节点 ID、版本号。这里有一个小技巧:第一次启动时把 logging.level 设为 debug ,后面排查媒体不通的问题会轻松很多,因为 debug 日志会把每个 RTCPeerConnection 的状态变化都打出来。

3.3 签发 token 并用客户端验证

服务起来了,token 工具有了,接下来就用一个最简单的 HTML 页面验证全链路。LiveKit 官方维护了一个示例项目 livekit-examples/room ,是纯前端实现。你只需要做一件事:把页面里的 ws://localhost:7880 和刚才生成的 token 填进去。

如果你不想拉官方示例,用官方 JS SDK 手写一个最小验证页也不复杂:

<!DOCTYPE html>
<html>
<head>
    <script src="https://cdn.jsdelivr.net/npm/livekit-client/dist/livekit-client.umd.min.js"></script>
</head>
<body>
    <video id="local-video" autoplay playsinline muted></video>
    <button id="join-btn">入房</button>
    <script>
        const url = "ws://localhost:7880";
        const token = "在这里粘贴token";
        const room = new LivekitClient.Room();
        room.on(LivekitClient.RoomEvent.TrackSubscribed, (track, publication, participant) => {
            const el = track.attach();
            document.body.appendChild(el);
        });
        document.getElementById("join-btn").onclick = async () => {
            await room.connect(url, token);
            const localTrack = await LivekitClient.createLocalVideoTrack();
            await room.localParticipant.publishTrack(localTrack);
            const el = localTrack.attach();
            document.getElementById("local-video").replaceWith(el);
        };
    </script>
</body>
</html>

注意两点:使用本地视频时,页面上那个 <video> 标签要加 muted 属性,否则开启麦克风后会有刺耳的音频回声;浏览器只会在 HTTPS 或 localhost 环境下授权使用摄像头,如果你通过 IP 地址访问页面,摄像头权限会被拒绝,这是 WebRTC 应用的通用限制,也是很多人在本地测试时遇到的第一个拦路虎。

3.4 用 livekit-cli 做无界面验证

有时候没有摄像头、没有浏览器环境,可以通过官方 CLI 工具 livekit-cli 模拟一个参与者加入房间。这个工具对排查服务端问题特别有用。

# 安装 CLI(Go 环境)
go install github.com/livekit/livekit-cli@latest

# 模拟加入房间
livekit-cli join-room --url ws://localhost:7880 --api-key DevAPIKey --api-secret DevAPISecret --room my-first-room --identity cli-user

CLI 成功加入房间后,服务端日志会打印参与者加入事件。此时再打开一个浏览器端页面加入同一个房间,就能看到两个参与者都在房间内。CLI 这种方式在我后续排查“服务端正常但客户端连不上”的问题时立了大功,因为排查双方在净化:客户端问题还是服务端问题,用 CLI 一加就知道。

3.5 验证媒体流是否真正转发

成功入房只是第一步,媒体流是否完整转发才是实时音视频的核心验证点。我的验证思路是:打开两个浏览器页面,分别用两个不同身份入房,打开摄像头,观察对端页面是否出现对方的视频画面。如果画面出现,说明完整链路已经通了;如果画面没出现,可以参考后面“常见问题”章节逐项排查。

这里要额外说明 NAT 穿透问题。同一台机器或同一内网下测试,媒体流大概率能直接连通,因为内网没有 NAT 障碍。但如果你用公网服务器测试,客户端之间的媒体传输极有可能因 NAT 类型问题失败,此时需要依赖 TURN 服务做转发兜底。LiveKit 内置 TURN,生产环境配置证书并开启即可,我在下一章的核心配置里会展开讲。

4. 核心配置解析与扩展方向

4.1 配置文件里的关键选项逐项看

配置文件里面每一项都值得理解清楚,下面是几个高优选项的解读:

  • port :信令服务监听端口,客户端 SDK room.connect 就是连这个端口。默认 7880 。
  • rtc.port_range_start/end :媒体流的 UDP 端口范围。端口范围太小会导致并发媒体流不够用,每路流的上下行可能需要多个 UDP 端口。轻量测试场景 50000-50100 的一百个端口足够。
  • rtc.tcp_port :WebRTC 在 UDP 不通时会尝试 TCP 连接,例如某些防火墙只放行 TCP。填 7881 ,和信令端口区分开。
  • rtc.use_external_ip :建议设置为 true ,让服务端自动探测公网 IP 并把它编入 ICE 候选。如果部署在 NAT 后面的服务器上,不开启这个选项,客户端拿到的候选地址都是内网 IP,NAT 外用户无法连接。
  • logging.level :生产用 info ,排查媒体问题用 debug 。
  • keys :API Key/Secret 的静态配置。生产环境可以有多个 key 对,方便对不同产品线做区分。

4.2 跨网联通的关键:TURN 的开启与配置

TURN 是 WebRTC 的最后一层保障:当两个客户端无法通过 P2P 穿透时,媒体流会经过 TURN 服务器中继。LiveKit 服务端默认内置了 TURN 实现,但需要配置 TLS 证书和端口才能启用。

配置片段参考:

turn:
  enabled: true
  domain: turn.example.com
  tls_port: 5349
  udp_port: 3478

启用后客户端会自动从服务端获取 TURN 凭证并使用。这里有个常见的疑问:为什么测试环境下没配 TURN 也能本地流程跑通?因为本机测试是同一个网络环境,UDP 直连即可,不需要中继。一旦客户端分布在不同网络,尤其是双方都在对称型 NAT 后面,没有 TURN 几乎不可能建立媒体连接。所以生产环境一定不要省掉 TURN。

4.3 录制、实时消息与 Agents 扩展

LiveKit 环境跑通后,通常下一步会关心几个扩展能力:录制、实时数据、以及最近很热的 LiveKit Agents。

录制功能叫 Egress,可以录制房间内音视频为文件,也可以直接推流到直播平台,配置采集中间件模板即可。实时数据通道(DataChannel)用来传输字幕、表情、白板笔迹一类不媒体流数据,官方称为 DataPacket,适合做一些业务消息联动的轻量方案。

LiveKit Agents 是官方在 2024 年起重点推的新方向,核心思路是把智能语音助手接入房间:一个机器人参与者加入房间,客户端发布音频后,Agents 框架负责语音转文字、调用大模型、再把结果合成语音播放回房间。 livekit agents 的使用需要单独环境,官方推荐 Python 框架 livekit-agents 。它本质上也是参与者,只是在服务端跑,不会占用客户端资源。我建议先把基础环境玩熟、把房间和音轨模型吃透,再上手 Agents,这样理解 agent 发布/订阅音轨的流程会顺很多。

5. 常见问题与排查技巧实录

5.1 连不上信令:服务活着但客户端报错

症状: room.connect 一直超时,但服务端进程还在跑。

排查顺序先看端口监听是否正常:

ss -lntup | grep 7880
ss -lntup | grep 7881
ss -lntup | grep 50000

再看防火墙和安全组是否放行了 7880 TCP 和 UDP 端口段。云服务器常常只放行 80/443 和 SSH 端口,LiveKit 的三类端口全被挡。实测中“服务端日志没动静,客户端卡在连接”的场景大概率是这一层问题。

5.2 信令通了但黑屏无声

症状:入房成功,能看到参与者列表,但看不到对方画面。

这种问题通常涉及媒体链路。按顺序排查:

  • 确认浏览器已授予摄像头/麦克风权限,页面必须是 HTTPS 或 localhost,直接 IP 访问会导致权限申请被拒。
  • 确认 UDP 端口段在防火墙放行。媒体流主要走 UDP 50000-50100 ,只放行 TCP 端口是不行的。
  • 确认服务端是否开启了 use_external_ip 。假如服务器有公网 IP 但在 NAT 后面,没有这个配置,媒体候选地址会是内网 IP,外部客户端连接不上。
  • 检查是否跨网络。如果双方分别在两个不同运营商网络下,建议快速开启 TURN 验证是否为穿透问题。

还有一个无声的特殊场景:WebRTC 的自动增益控制有时会产生沉闷噪声,但那是体验问题;真正的问题是 Android 端 Chrome 需要 HTTPS 才能拿到音轨权限,内部测试时容易忽略。

5.3 token 无效:类型、时间与密钥不匹配

LiveKit 连接时报 token is invalid 或 token is expired ,结合经验看基本是三类原因:

表现 原因 修复
提示 token 无效 JWT header 缺少 kid 或 header/secret 不匹配 header 中 kid 必须等于 API Key,签名密钥必须等于 API Secret
提示 token 过期 服务器和客户端系统时间不一致 同步 NTP 时间,或把 token 过期时间设长一些
能解析但不能入房 payload 里 video.room 与实际入房房间不一致 确认 token 里 room 字段和 room.connect 传的房间名完全一致

系统时间不一致这个坑很容易踩,尤其是云服务器时区或 NTP 未同步时, exp 判断会出问题。实测一条命令能救回来:

ntpdate -u ntp.aliyun.com

5.4 调试工具与日志经验

LiveKit 的日志是排查问题的第一手情报。启动时加 --log-level debug ,入房和发布轨时服务端会打印大量信令和媒体信息。如果你能看到 PeerConnectionState=connected ,说明媒体链路已经建立;如果卡在 checking ,说明 ICE 穿透还没完成。另外官方 CLI 工具还带 load-test 命令,可以模拟大量参与者同时入房,很适合验证服务器的并发承载能力。

5.5 速查表:常见问题一页纸

问题 可能原因 解决动作
客户端无法连接 防火墙 / 安全组未放行端口 放行 7880 TCP、7881 TCP、50000-50100 UDP
入房但看不到画面 跨 NAT 无 TURN、浏览器无权限 开启 TURN;使用 HTTPS 域名访问
麦克风有杂音 浏览器自动增益问题 开启 LiveKit 音频处理,或引导用户戴耳机
token 报错 head 缺 kid / 时间不同步 在 JWT header 设置 kid;同步服务器时间
livekit-cli 无法加入 版本与服务端不兼容 确保 livekit-cli 升级到与服务端同代版本

结尾:个人实操中的几点体会

环境搭建只是第一步,真正把 LiveKit 用顺还得多踩几次坑。我个人最深的体会是:任何实时音视频项目的复杂度都不在客户端渲染视频那么简单,媒体链路里任何一个断点都会导致黑屏无声。所以每换一个部署环境,都要先跑通“两个页面互看”的最小验证,再叠加业务逻辑。还有一个很实用的小技巧,如果你暂时没有合法域名和 HTTPS 证书,建议用 localhost 做 WebRTC 测试(浏览器会放行设备的权限),业务正式上线前再换成公网 HTTPS 域名,可以少挨很多浏览器的安全拦截。LiveKit 的价值是帮你省掉了造轮子的时间,但你要对它的房间模型和媒体链路建立起直觉,后面用 Agents、用 Egress、做多节点部署时才不至于迷路。

Logo

邀请您加入社区

更多推荐