15、Janus网关:Janus架构、插件机制、视频会议插件使用
聊到WebRTC的服务端,很多人第一个想到的就是Janus。说实话,我在刚接触WebRTC那会儿,也被各种SFU、MCU搞得一头雾水。后来真正上手Janus,才发现它的设计思路其实非常清晰——说白了,它就是一个“插件化”的WebRTC网关。
我个人习惯把Janus比作一个“乐高底座”。它本身不提供具体的业务功能,但你可以往上面插各种“积木”(插件),来实现视频会议、直播、录音、推流等等。这种设计让Janus非常灵活,也是它能在众多WebRTC服务器中脱颖而出的原因。
15.1 Janus的整体架构
先来看一张我画的架构图,帮你快速建立整体认知:
Janus的架构分为三层:
- 传输层:负责与客户端通信。支持WebSocket、HTTP(REST API)、RabbitMQ等多种方式。我个人最常用的是WebSocket,延迟低,双向通信方便。
- 核心层(Janus Core):这是Janus的心脏。它处理所有WebRTC的底层细节——ICE连接、DTLS握手、SRTP加密、媒体流的转发。嗯,这里要注意,Core本身不关心你传的是视频还是音频,它只负责把数据从A点搬到B点。
- 插件层:真正的业务逻辑都在这里。每个插件是一个独立的动态库(.so文件),Core通过标准接口调用它们。你想做视频会议?加载VideoRoom插件。想做直播?加载Streaming插件。
核心要点:Janus的Core和插件之间通过JSON消息通信。插件不直接接触WebRTC的底层细节,它只需要告诉Core“我要发什么数据给谁”,剩下的ICE/DTLS/SRTP都由Core搞定。
15.2 插件机制详解
插件机制是Janus的灵魂。我刚开始研究Janus源码时,最困惑的就是插件到底怎么工作的。后来自己动手写了一个简单的Echo插件,才算真正搞明白。
15.2.1 插件的生命周期
每个Janus插件都要实现一组固定的回调函数。Core会在不同时机调用它们:
| 回调函数 | 触发时机 | 说明 |
|---|---|---|
init() |
Janus启动时 | 插件初始化,加载配置 |
create_session() |
客户端创建会话时 | 分配插件级别的资源 |
handle_message() |
收到客户端消息时 | 处理业务逻辑,比如加入房间、发布流 |
setup_media() |
媒体通道就绪时 | 开始接收/发送媒体数据 |
destroy_session() |
会话结束时 | 清理资源 |
说白了,插件就是一个“事件处理器”。客户端发来一个JSON请求,插件解析后做出响应,然后通过Core把结果推回给客户端。
小技巧:调试插件时,可以开启Janus的debug日志。在启动命令加上 -d 4(日志级别4),就能看到插件和Core之间交互的完整JSON消息。我曾经靠这个定位过一个诡异的“房间创建失败”问题——最后发现是插件配置文件的JSON格式写错了。
15.2.2 插件与客户端的通信模型
客户端和插件之间的通信,走的是“请求-响应”+“事件推送”混合模式:
- 请求-响应:客户端发一个
{ "request": "join", "room": 1234 },插件处理完后回复{ "janus": "success", ... }。 - 事件推送:当房间里有其他人加入/离开,或者有人开始/停止推流时,插件主动向相关客户端推送事件。比如
{ "event": "joined", "id": 5678 }。
这种模型的好处是——客户端不需要轮询,实时性很好。坏处是——你得自己维护好会话状态,不然事件推送给谁都不知道。
15.3 视频会议插件(VideoRoom)实战
VideoRoom是Janus最常用的插件之一。它实现了一个SFU(Selective Forwarding Unit)模式的视频会议系统。每个参与者把自己的音视频流推送到服务器,服务器再转发给房间里的其他人。
我在项目中遇到过一个问题:当房间里有20个人同时开摄像头时,客户端的带宽直接爆了。后来才发现,VideoRoom默认会把所有人的视频流都转发给每个人。解决办法是开启simulcast(联播)或者VP9-SVC,让客户端根据自身带宽选择接收不同质量的流。
15.3.1 创建房间
首先,客户端需要向VideoRoom插件发送一个创建房间的请求:
{
"janus": "message",
"body": {
"request": "create",
"room": 1234,
"description": "技术分享会议室",
"publishers": 10,
"bitrate": 1024000,
"fir_freq": 10,
"audiocodec": "opus",
"videocodec": "vp8"
}
}
参数说明:
room:房间号,唯一标识。publishers:最大发布者数量。超过这个数,新人就进不来了。bitrate:每个发布者的视频码率上限(单位bps)。fir_freq:关键帧请求频率。值越小,延迟越低,但带宽消耗越大。
避坑指南:我曾经把publishers设成100,以为能支持100人同时开摄像头。结果Janus直接OOM了。后来查文档才发现,这个值不是房间总人数,而是同时推流的人数。如果你只需要少数人发言,其他人只观看,建议把publishers设小一点(比如5~10),然后通过subscribers参数控制观看人数。
15.3.2 加入房间并推流
创建好房间后,客户端就可以加入了。加入时需要指定自己的角色:
{
"janus": "message",
"body": {
"request": "join",
"room": 1234,
"ptype": "publisher",
"display": "张三"
}
}
这里的ptype有两个选项:
publisher:发布者,把自己的音视频流推送到服务器。subscriber:订阅者,只接收别人的流,自己不推流。
加入成功后,插件会返回一个id,这是你在房间里的唯一标识。然后你就可以开始推流了——其实就是标准的WebRTC流程:创建PeerConnection,添加音视频轨道,设置ICE候选,等待连接建立。
15.3.3 订阅别人的流
如果你是订阅者,或者想同时观看别人的流,需要发送订阅请求:
{
"janus": "message",
"body": {
"request": "join",
"room": 1234,
"ptype": "subscriber",
"feed": 5678
}
}
feed就是你要订阅的那个发布者的id。VideoRoom会为每个订阅者创建一个独立的PeerConnection,专门用来接收这个发布者的流。
嗯,这里有个细节要注意:每个订阅者只能订阅一个发布者。如果你想同时看多个人,就得创建多个订阅者会话。这也是SFU的典型做法——每个订阅关系都是一条独立的媒体通道。
15.3.4 事件监听
作为客户端,你需要监听插件推送的事件。常见的事件有:
| 事件类型 | 触发条件 | 数据示例 |
|---|---|---|
joined |
有人加入房间 | { "id": 5678, "display": "李四" } |
leaving |
有人离开房间 | { "id": 5678 } |
unpublished |
有人停止推流 | { "id": 5678 } |
talking |
有人开始/停止说话 | { "id": 5678, "talking": true } |
这些事件非常有用。比如你可以根据talking事件,在UI上高亮当前说话的人。或者根据joined/leaving事件,动态更新参会者列表。
个人经验:我在做视频会议项目时,发现talking事件有时候会误触发(比如背景噪音大)。后来我在客户端加了一个“能量阈值”判断——只有当检测到的音频能量持续超过某个值超过500ms,才认为真的有人在说话。这样用户体验好了很多。
15.4 部署与配置要点
最后聊几个部署Janus时的实用配置。说实话,这些坑我都是踩过一遍才记住的。
15.4.1 核心配置(janus.jcfg)
general: {
configs_folder = "/etc/janus" # 配置文件目录
plugins_folder = "/usr/lib/janus/plugins" # 插件目录
debug_level = 4 # 调试级别(0-7)
}
nat: {
stun_server = "stun.l.google.com"
stun_port = 19302
turn_server = "turn.example.com"
turn_port = 3478
turn_type = "udp"
turn_user = "myuser"
turn_pwd = "mypass"
}
media: {
rtp_port_range = "20000-40000" # RTP端口范围
dtls_mtu = 1200 # DTLS MTU,避免分片
}
这里重点说下rtp_port_range。Janus默认会占用大量UDP端口(每个媒体流至少需要2个端口)。如果你的服务器在NAT后面,记得在防火墙上开放这个端口范围。我曾经因为忘了开端口,折腾了一整天才发现是防火墙的问题。
15.4.2 VideoRoom插件配置(janus.plugin.videoroom.jcfg)
videoroom: {
string_ids = true # 使用字符串ID
admin_key = "supersecret" # 管理员密钥
lock_room = false # 是否锁定房间
default_bitrate = 1024000 # 默认码率
bitrate_cap = 5120000 # 码率上限
}
string_ids这个选项我强烈建议开启。默认情况下,房间号和用户ID都是数字,但数字ID在调试时很难一眼看出是谁。开启字符串ID后,你可以用“room_tech_share”这样的名字,清晰多了。
15.5 小结
Janus的架构设计非常优雅——Core负责脏活累活,插件专注业务逻辑。VideoRoom插件则是SFU模式的一个典型实现,适合中小规模的视频会议场景(我个人经验是50人以内效果不错,再往上就需要考虑集群部署了)。
如果你打算在生产环境使用Janus,建议先搭一个EchoTest插件测试网络连通性,再逐步切换到VideoRoom。这样排查问题会快很多。
公众号:蓝海资料掘金营,微信deep3321