news 2026/10/18 7:41:56

基于LiveKit重构EasyDSS:WebRTC实时通信、转码与集群语音实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于LiveKit重构EasyDSS:WebRTC实时通信、转码与集群语音实践

EasyDSS 平台做了不少版本,流媒体接入、拉流转发、录像回放这些能力一直都有,但越往后走越发现一个尴尬的问题:实时交互链路太脆了。摄像头接入、web 端拉流、移动端对讲、语音识别这些场景,各自跑一套自建的 WebRTC 流程,信令自己写、媒体转发自己扛、编解码能力参差不齐,每次加新功能都要把前面的逻辑翻一遍。

后来我们决定基于 LiveKit 对实时通信域做一次重构,顺手把视频转码、点播 VOD、语音识别 STT、集群语音这些能力全部重新梳理了一遍。这篇文章不讲概念,直接讲我们在 EasyDSS 架构重构里实际拆过的东西、踩过的坑,以及为什么这么组合。


1. 先别急着搬 LiveKit:这次重构到底要解决什么

1.1 老架构的“能跑”与“难扩”

重构之前,EasyDSS 的实时部分大体是这么工作的:浏览器端连着自建信令服务,信令服务负责交换 SDP 和 ICE,媒体流再走单独的媒体转发节点。看起来没什么毛病,但真实业务一上量就难受了。

第一个痛点是用例杂。安防场景里有大量的 web 端观看、移动端对讲、语音识别、集群调度,每种用例都要自己做一次“媒体链路适配”。比如某个老摄像头只出 H.264,但浏览器端有时候协商得慢,画面就黑屏;而另一路设备出的是 H.265,web 端根本解不了,必须绕到转码集群。链路一多,问题定位就变成“你这个模块的 SDP 协商有问题”这种没有信息量的沟通。

第二个痛点是房间语义缺失。老的方案里没有“房间”“参与者”“发布订阅”这些抽象,只有一堆“会话”“通道”“转发流”。于是像集群语音里常见的“一个群组统一调度发言权”这种需求,得在业务层反复封装。而 LiveKit 本身就是以房间为粒度的 SFU,接入层天然具备这套抽象。

第三个痛点是媒体面扩展复杂。老的媒体节点看似支持横向扩展,其实每个节点都要维护大量状态,节点一多,会话一致性维护成本就上来了。重构之前我们多次遇到“明明推流成功了,客户端却收不到画面”,查到最后都是媒体节点状态没同步。

1.2 LiveKit 切入的动机

LiveKit 开箱就提供了几样我们最缺的能力:

  • 标准 WebRTC SFU 信令,包括 ICE 重启、断线重连、renegotiation 这些难啃的边界;
  • 发布订阅的媒体轨道模型,视频、音频、数据通道都可以独立控制;
  • 房间、参与者、token 权限体系,能跟业务账号体系对接;
  • 录制和转码相关能力,通过 Egress 可以拉出房间内的合成流或单流。

对我们来说,最关键的还不是功能多,而是“媒体面复杂度收敛了”。以前信令、媒体转发、转码三块各自维护,现在 LiveKit 把实时媒体面的核心收敛成一个组件,我们只需要围绕它的房间模型去做业务层的扩展。

1.3 重构范围不是全部推倒

一开始团队里也有人说重构就是重新写一遍,但我们定了很明确的范围:实时接入和媒体转发以 LiveKit 为核心重写,点播存储、业务接口保留原有体系但做接口适配。原因很简单,点播那套 HLS/MP4 流程已经跑得很稳,没必要因为实时链路重构就全部推翻。

表格里能看出边界划分:

功能模块处理方式原因
WebRTC 信令与媒体转发基于 LiveKit 重建解决并发和扩展瓶颈
视频转码网关保留但拆分边缘节点兼容不同编码格式的终端
点播 VOD 存储复用原存储,增加实时录制入口历史数据不能丢
业务账号与权限保留,对接 LiveKit token避免业务重复建设
语音识别 STT新建独立识别管道媒体面重构后才有干净的视频/音频源

2. 信令层重构:房间、参与者、token 接入,以及业务系统怎么挂钩

2.1 从“自建信令”到“房间即服务”

老的 WebRTC 信令流程里,最耗精力的不是“交换 SDP”这个动作,而是状态管理。谁在哪个会话、要不要重新协商、ICE 断了重连后媒体要不要重新拉,这些都是自建信令最大的坑。LiveKit 把这些状态都收进“房间”模型里,信令层面我们基本不用关心“第一步做什么、第二步做什么”。

我们接入时选的是双端模式:房间创建和参与者管理走服务端,媒体连接走前端 SDK。业务平台发起一场调度会议或一路摄像头预览时,先通过服务端 API 创建房间,再把房间 ID 告诉前端,前端只负责 Join。这样即便以后接入别的终端,也只要做一次 SDK 适配。

2.2 用 AccessToken 替代 session 绑定

LiveKit 的接入凭证是 JWT 格式的 AccessToken。刚开始我们觉得 token 都是“给 SDK 用的”,对业务后台反而没感觉。实际跑下来发现这个模型很干净:我们只需要在业务服务里发 token,其他所有音视频权限都跟 token 里携带的 VideoGrant 走。

以 Go 为例,业务后台生成一个临时凭证的代码大概长这样:

import "github.com/livekit/protocol/auth" at := auth.NewAccessToken("apiKey", "apiSecret") grant := &auth.VideoGrant{ RoomJoin: true, Room: "room-001", CanPublish: true, CanSubscribe: true, CanPublishData: true, } at.SetVideoGrant(grant) at.SetIdentity("user-123") token, _ := at.ToJWT()

这个 token 我们做了 10 分钟过期,业务层把 token 下发给前端,过期之后再续签。好处是“谁进了哪个房间”完全由后台控制,不用在 LiveKit 侧再维护一套业务用户体系。你甚至可以针对不同的客户端能力下发不同的 grant,比如只允许某个客户端订阅、不允许发布。

2.3 订阅关系与权限映射

从实际业务看,“房间”对应“一场会话”,但会话里谁看谁听,还是要在业务层做映射。我们采用的做法是:

  • 业务平台定义好角色,比如“指挥员”“现场人员”“观察员”;
  • 服务端在生成 token 时,根据角色注入不同的 VideoGrant;
  • 指挥员可以同时订阅多路视频;现场人员默认只能发布自己的画面和音频;观察员只能订阅。

LiveKit 本身并不限制“订阅多少路流”,限制主要靠 token 里的权限做。我们在实测中发现,指挥员账号同时打开 16 路视频时,移动端容易带宽不足,所以业务层还得做转发规则,不是一刀切给所有人订阅权限。

这里也提醒一句:token 不要设置太长有效期,尤其是调度场景。一旦参与者身份发生变化或会话被强制结束,token 的有效期就是一个隐形的风险窗口。


3. 转码链路:不是所有流都需要转码,关键是“按需”

3.1 转码不是一个动作,而是一条调度链

很多人一提转码就想到“CPU 扛不住”,但实际难点在于决定在哪一层转、用什么参数转、是否实时转。我们重构后把转码拆成三条链路:

  • 实时转码链路:给不支持目标编码的终端做降级;
  • 录制转码链路:把实时流转成统一格式后落 VOD;
  • 点播转码链路:对已存储文件做 HLS/MP4 切片。

以实时链路为例,设备端上来的是 H.265,浏览器端大概率解不了。老的方案是把所有流都丢到中心转码服务。重构后我们做了“就近转码”:摄像头直接接入边缘节点,边缘节点判断该路的订阅情况,如果只有 H.265 终端订阅,就原封不动转发;一旦有浏览器端订阅,边缘节点才启动转码。

3.2 浏览器与终端之间的编解码现实

LiveKit 自身在 WebRTC 协商时会优先选择双方都支持的编码。但 EasyDSS 的场景里,设备端和 web 端能力差异非常大:

  • 老摄像头大概率是 H.264 Baseline;
  • 新安防设备很多是 H.265;
  • 部分移动端内置的专用对讲音频格式跟 Opus 不兼容。

为了不把所有压力都压到一个节点,我们在边缘网关里做了一个编码能力表。举例:

发布端编码订阅端类型处理策略
H.264浏览器 / App直接转发
H.265浏览器边缘转码成 H.264
H.265同能力专用终端直接转发
专用语音编码全平台边缘转码成 Opus

这套表维护起来并不复杂,好处是决策成本低。每次有新设备接入,只需要登记它的编码能力,不用再改转码主流程。

3.3 降级与回退策略

转码服务最怕的是瞬时并发。当大量浏览器端同时订阅同一路 H.265 视频时,如果每个订阅都触发一次转码,CPU 会瞬间拉满。

我们采用了“共享转码流”方式:一路原始流只转一份 H.264 编码流,多路订阅都复用这份结果。LiveKit 的 SFU 本身是选择性转发,让多路客户端订阅同一路转码后的流是天然合理的。实测下来,16 路订阅时,GPU 占用从“每路各自转”时的 80% 降到共享转码后的不到 30%。

转码失败时要有一个明确的回退策略,不能静默黑屏。我们的做法是在业务层加超时判断:如果订阅端在 5 秒内没有收到首帧,就主动发一条数据通道消息给客户端,提示“当前网络带宽不足,已切换到纯音频模式”,前端收到后自动停止视频订阅。

提示:转码节点不要和业务 API 混部。转码是 CPU/GPU 密集任务,业务 API 是 I/O 密集任务。混部时一旦转码飙高,整个平台的接口延迟都会异常。


4. 点播 VOD 与录制回放:实时媒体流怎么沉淀成文件服务

4.1 录制不是简单“把流存下来”

EasyDSS 里点播 VOD 的需求非常直接:实时监控的同时要录像,事件发生后还要能按时间点回放。重构前,录制走的是“收到一路流就往磁盘写”,问题是文件格式、切片粒度、索引时间点全靠各自写。现在整个实时流都进 LiveKit,录制入口就统一了。

我们启用了 LiveKit 的 Egress 能力,把房间内指定参与者发布的音频视频录制下来,也可以接入外部录制服务。对平台来说,这里有一个关键决策:文件分段策略。

安防回放通常需要精确到秒,而且经常回放的是“某个事件开始前 30 秒到开始后 10 分钟”这种片段。如果录制成一个巨大的文件,索引和随机读都痛苦。我们把录制文件按 1 分钟一个分片切,每个分片独立生成索引,回放模块只需要找到对应时间段的分片,再做二次拼接。

4.2 文件格式与存储路径

LiveKit 房间内录制的媒体轨,不同终端编码可能不一样。为了统一 VOD 播放链路,我们不在录制阶段做全套转码,而是先存为原始格式,再由异步任务统一转成平台点播格式。

分发链路可以简化为:

  1. 实时流进入 LiveKit 房间;
  2. Egress 录制音频视频到临时存储;
  3. 通知转码任务把原始文件转成 H.264 + AAC 的 MP4;
  4. 对 MP4 做 HLS 或 DASH 切片,也可以直接提供 MP4 点播地址;
  5. 将索引信息写入 VOD 服务,关联到事件记录。

这套链路最舒服的地方在于,实时链路和点播链路不再互相污染。实时转发抢占带宽时,录制文件只是一个追加写入动作,不影响房间内的媒体流转。

4.3 回放体验优化

回放最大的坑其实是“首帧等待时间”。如果回放文件统一在云端对象存储,用户每次点击回放都要先等切片下载,体验很差。我们在边缘节点上加了“最近 24 小时回放缓存”,热数据留在本地,冷数据才回源中心。

对于同一路摄像头,录制文件和实时流的命名规则我们统一成设备ID/日期/小时/分片序号,这样回放的时候不需要查数据库,直接按时间格式计算路径。这个习惯很多项目不做,等到回放需求一变就发现索引结构完全没法扩展。


5. STT 语音识别管道:从音轨到可用文本,链路比想象中长

5.1 实时音频从哪里拿

语音识别 STT 要识别的是“正在直播的音频”,不能等录制文件生成后再去识别,否则就丧失了实时性。重构前的难点是:音频流在各个传统转发节点里是“内部状态”,不是公开的媒体轨道,识别程序很难介入。

基于 LiveKit 之后,思路就清晰了:在房间内部创建一个只订阅音频的识别参与者。这个参与者不发布任何媒体,只通过服务端 SDK 订阅指定发言人的音频轨道,然后把连续的音频包转交给 STT 引擎。这样房间内的人完全感知不到额外有谁在参与,但音频流可以无侵入地提取出来。

5.2 接入 STT 引擎的流程与格式适配

主流 STT 引擎一般接受两种输入,一是整段音频文件,二是实时音频流。实时识别一定要走流式 API,这里要匹配两个参数:

  • 采样率:常见 16kHz 或 24kHz;
  • 编码:PCM 或 Opus 解包后的音频数据。

我们用了一段简单的服务端处理逻辑:

// 伪代码示意:订阅音频轨道并推送STT const participant = await room.connect(); const track = participant.trackPublication; const reader = await track.audioTrack.createReader(); while (reader.realTime) { const frame = await reader.read(); if (frame) { sttClient.pushAudio(frame.data, frame.sampleRate, frame.channels); } }

这里有个容易忽略的细节:机器人的音频订阅需要和真正的用户发言节奏对齐,否则识别结果会出现“一整段话延迟输出”。我们按 100ms 一个数据片推送,既保证低延迟,又不会让 STT 引擎因为频繁调用产生过多开销。

5.3 识别结果怎么回传给前端

识别结果回传我们也走了 LiveKit 的 DataChannel。它本质上是多路消息通道,可以广播给房间内所有参与者,也能定向发某人。我们定义了一个统一的文本消息协议:

{ "type": "stt_result", "participant": "user-123", "text": "现场画面正常,请求支援", "confidence": 0.93, "start_time": 1710000000, "end_time": 1710000004 }

前端收到后根据participant和start_time/end_time把文本对齐到对应说话人的时间轴上,做实时字幕显示。

5.4 实际环境里的坑:热词、静音与方言

STT 接入本身不难,难的是让它能对上业务。安防对讲里大量出现“车牌号”“路口名”“人员名字”这类专有名词,默认词表基本全错。解决办法有两个:

  • 在 STT 引擎里配置热词表,把本项目的设备名、地名、常用术语提前注入;
  • 对低置信度结果不做展示,只记录到后台日志,服务端定时做二次识别。

另一个坑是静音检测。对讲场景大家说话习惯“先说一个字停一下,再继续说完整句话”,劣化极易被 STT 切成碎片。我们接入了一个简单的 VAD 模块,把连续静音超过 700ms 才切分一段,短停顿不会打断识别上下文。效果提升很明显。


6. 集群语音对讲:抢话权、动态混音与优先级,落地做法

6.1 “房间”天然就是群组

集群语音是 EasyDSS 最吃劲的场景,它和普通视频通话不一样:强调的是“一群人对一个群组说话,其他人听”。在 LiveKit 里,每个群组就是一个房间。参与群组的人加入房间,不说话的人只订阅别人音频,不发布自己的音频。

这样的话“一个群组最多支持多少人”就变成“一个房间能承载多少订阅者”。SFU 模式下,房间内每个人订阅其他人音频,带宽成本是 N 路。如果 50 人对讲,50 人都订阅,那每人都要接收 49 路音频,移动端基本扛不住。因此我们对群组对讲做了“只听当前主叫音频”的限制,只有获得发言权的人,他的音频才被转发给其他人。

6.2 抢话权怎么实现

LiveKit 本身不提供“谁可以说话”的业务控制,抢话权必须在应用层做。我们基于 DataChannel 做了一个轻量级的对讲控制协议:

  1. 用户按住 PTT,客户端向业务服务发起“请求发言”;
  2. 服务端检查当前群组是否存在已授权发言者,如果没有,则授权该用户;
  3. 服务端通过 DataChannel 广播“发言人变化”事件;
  4. 授权用户开始发布自己的音频轨道;
  5. 用户松开 PTT,客户端停止发布音频,服务端回收授权。

这里要注意的是“授权”和“发布轨道”必须解耦。我们一开始把两者强绑定,结果网络抖动时,轨道发布失败,但授权已经发了,导致群组里好几分钟没人能发言。后来改成“先授权,客户端收到授权后再尝试发布轨道,发布失败要自动释放授权”,系统才稳定下来。

6.3 动态混音与音频处理

LiveKit 默认是 SFU,不主动做混音。群组对讲如果只保留一个发言者音频,是不需要混音的。但有些场景是“多方同时讲话,调度台要把所有话混成一路输出”,这就得靠服务端把多路音频流混音后再推送。

我们用的是服务端拉取多路参与者音频,通过一个混音模块合成单路音频,再把这个合成流作为一个“群组广播”发布回房间。它的适用场景是“调度大屏听取多个现场回传”,而对普通成员仍然保留单听模式。

混音涉及编码缓冲同步。两个发言者网络延迟不一致时,不校正直接混,声音会严重回声和混叠。建议在混音前先做 RTP 时间戳对齐,以采样点数为基准对齐,而不是按墙钟时间。

6.4 弱网下的语音保障

语音对讲对实时性要求很高,对画质不敏感。在弱网环境,我们会建议客户端把音视频轨道分离,视频关闭,只保留音频。代码层面上,LiveKit 的 Track 发布是可以动态控制的:

const audioTrack = await createLocalAudioTrack(); await room.localParticipant.publishTrack(audioTrack); // 弱网时停止视频流 videoTrack.mute();

音频编码统一走 Opus,带宽预算比较小,实测 24kHz 采样率在丢包 20% 的网络下仍然能听懂内容。注意不要在弱网下让客户端同时订阅多路高清视频,那会让音频优先性失效。如果一定要看多路视频,宁可降低帧率,也不要抢占音频带宽。


7. 集群架构与运维:重构后的 EasyDSS 部署形态

7.1 逻辑模块怎么拆

重构后平台大致分成四个逻辑面:

  • 接入面:边缘节点,跑 LiveKit Server 和就近转码,负责终端接入;
  • 业务面:业务 API、token 签发、房间调度,不触碰媒体包;
  • 智能面:STT 识别、事件分析、VAD 检测这些异步消费实时流的服务;
  • 存储面:VOD 存储、录制索引、热回放缓存。

这种拆分最直接的好处是“媒体负载和业务负载彻底分开了”。以前出现整个平台变慢时,根本不知道是业务接口的问题还是媒体转发的问题。现在看监控大盘一目了然,边缘节点看带宽和转码 CPU,业务面只看接口延迟。

7.2 边缘节点与中心节点的关系

多节点部署下,我们采用“边缘接入,中心编排”的策略。每台边缘节点都部署 LiveKit Server,但房间状态统一上报到中心。一个房间的参与者不强制跨节点互通,如果用户跨地区加入,优先接入离自己最近的边缘节点。

这里有个容易踩的坑:LiveKit 的房间是按“单个实例”跑的,跨节点分布式房间要么自己实现信令路由,要么就只能用中心节点的转发能力。因此我们在实际部署里把同一场会话固定在一个边缘节点,避免盲目跨节点加入导致媒体绕路。

说得直白一点,边缘节点的作用不是“把所有房间分散到全球”,而是“每个区域的用户优先接入本地,减少跨地域传输”。

7.3 监控指标与扩容判断

LiveKit 自带的 Prometheus 指标给了我们很大帮助,但真正上线后看这些指标要注意几个关键数值:

  • 丢包率:超过 2% 就要排查网络;
  • 上行/下行码率:超过节点带宽 70% 就要扩容;
  • 发布轨道数:一个节点承载过多的活跃轨道会导致 CPU 线性上升;
  • 转码队列长度:如果边缘节点的转码任务排队超过 200,说明当前转码能力跟不上了。

我们做扩容的经验不是“等 CPU 满了才扩”,而是“当网络带宽和活跃轨道数同时接近阈值时”就扩。因为 LiveKit Server 是 SFU,媒体转发的主要瓶颈往往是带宽和 UDP 端口数量,而不是单纯的 CPU。


8. 重构之后的实际性能与下一步演进

8.1 拿数据说话

重构完成后我们做了一轮压测,只针对最核心的“10 个群组对讲房间,每个房间 30 人,其中 3 人并发发言”场景。结果表现比较理想:

指标重构前重构后
首帧接入延迟约 1.8s约 0.8s
单节点并发会话约 200约 600
转码任务排队平均时间3s 以上小于 0.5s
语音对讲讲权切换耗时约 1.2s约 0.5s
录制到 VOD 可回放时间分钟级秒级

数据不是说明 LiveKit 比我们老架构“更强”,而是说明把媒体面复杂度交给一个成熟组件后,我们能把精力放在业务链路上,链路稳定性和扩展性都明显改善。

8.2 仍然要留出来的边界

LiveKit 解决了许多 WebRTC 的难事,但并不是银弹。它的房间模型比较适合“会话内多人互动”,但如果需要把流推给传统 CDN 做大规模直播分发,还是得单独走转封装链路,不要把 LiveKit 当成 CDN 来用。

另一个边界是转码能力。LiveKit 内置的转码主要围绕 WebRTC 编解码器,对传统安防设备里复杂的 H.265 封装流,还是需要我们自己写边缘转码模块。所以重构不是“替换一切”,而是“正确的组件做正确的事”。

8.3 后续演进思路

后面我们打算把 STT 的识别结果直接接入事件中心,让语音里出现的重点词汇能自动触发告警;集群对讲这块,计划支持更大规模的“动态群组”,也就是一场大型活动中,不预先建房间,而是按区域自动分配群组,人少了自动合并。

重构最大的体会是:技术上看起来是在选一个组件、做一次架构升级,实际上是在给整个平台重新划边界。把实时通信的边界划清楚之后,后面的 VOD、STT、集群语音都变得好做了。这也是我现在做平台设计时,会下意识先问一句“这个能力该放在媒体面还是业务面”的原因。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/18 7:41:31

CKEDITOR Word粘贴图片自动分类:跨平台CMS站群的图片管理实战

上个月某技术团队在维护一个跨平台CMS站群,运营同事把一篇带截图的Word文档复制粘贴进CKEDITOR,结果十几张图片全部落进了同一个默认上传目录,文件名还是天书一样的随机字符串。后台图片库几天就堆了几千个无意义文件,想找一张图只…

作者头像 李华
网站建设 2026/10/18 7:40:15

基于ROS与YOLO的无人机视觉投放系统:从识别到精准投放的工程实践

简介:本资源面向ROS与PX4无人机开发的学习者与竞赛选手,提供一套完整的物体识别与精准投放方案。代码基于YOLO实现目标检测,通过ROS节点发布识别位置信息,进而生成无人机控制指令,使飞行器始终保持在目标物体正上方&am…

作者头像 李华
网站建设 2026/10/18 7:39:40

C++填空题集自动化构建:从题目设计到自动判分与编译验证

简介:这是一份面向C初学者与备考学生的填空题专项练习文档,聚焦面向对象程序设计的核心概念与语法细节,帮助读者通过填空形式巩固类与对象、继承、多态、模板、运算符重载、I/O流等高频考点。资源包内仅含1个doc文件,大小约59KB&a…

作者头像 李华
网站建设 2026/10/18 7:38:41

xParse单据勾稽台

仓库三张单据的数字对不上,我做了个网页自动勾稽 螺纹钢短了 0.8 吨。验收单上签了字,货已经上架,月底算账才发现,供应商那边不认了。 做仓储系统交付那几年,这种场面见过不少。入库单管收货、出库单管发料、盘点单管…

作者头像 李华
网站建设 2026/10/18 7:37:38

个人股票量化软件推荐 三款工具怎样区分市场复盘与策略复盘

个人股票量化软件推荐,可以先比较BigQuant、牛股王股票和青柠量化三条研究路径。想自行组织筛选数据与交易分析,可看BigQuant;希望在现有股票规则中连接市场判断与策略记录,可了解牛股王股票;需要按交易日查看市场统计…

作者头像 李华
网站建设 2026/10/18 7:37:04

EmotionVGGnet情绪识别实战:VGG风格网络训练与推理全流程

简介:EmotionVGGnet情绪识别Python源码包面向深度学习入门者与情感计算方向的研究者,提供一套基于VGGNet卷积神经网络的完整情绪识别实践方案,可用于语音、图像等多模态数据的分类任务。压缩包共11个文件、约12.38MB,包含6个Pytho…

作者头像 李华