从信令到janus的信令刨析
结合文档《31-04web客户端信令分析2-4.pdf》内容,针对publish(发布)和subscribe(订阅)一、publish 的“自动性”本质是信令驱动与 WebRTC 协商的封装信令触发是前提publish需通过信令显式调用,携带SDP Offer完成媒体协商(如文档中jsep字段的使用,)。示例信令:{"handle_id": "插件句柄ID",表明发布需主动发送信令并提供协商参数,
1 信令
**信令(Signaling)**是通信系统中用于协调和管理通信过程的指令和消息机制,主要负责在通信各方之间传递控制信息,确保通信的建立、维护和终止顺利进行。
一、信令的核心作用
-
建立通信连接
- 协商通信参数:如媒体编码格式(音频/视频编码)、网络传输协议(如WebRTC的ICE协议)、加密方式等。
- 示例:在WebRTC中,通过信令交换SDP(会话描述协议)信息,确定双方支持的媒体类型和传输地址。
-
管理通信状态
- 控制用户加入/离开房间、切换角色(如从发布者转为订阅者)。
- 监控和通知房间状态变更(如房间销毁、用户被踢除)或媒体流变化(如开始/停止发布)。
-
协调媒体流交互
- 发布者通过信令告知服务器开始发送媒体流(如
publish信令),订阅者通过信令请求接收特定流(如start信令)。 - 处理媒体流的动态调整(如码率控制、分辨率切换)。
- 发布者通过信令告知服务器开始发送媒体流(如
-
权限与资源管理
- 验证用户身份(如房间密码、Token),控制访问权限(如允许/禁止用户加入房间)。
- 管理资源分配(如限制房间内最大发布者数量、设置音视频码率上限)。
二、信令的分类
根据通信模式和响应机制,信令可分为两类:
-
同步信令
- 特点:请求与响应一一对应,客户端发送请求后立即等待服务器返回结果,适用于需要即时确认的操作(如查询房间是否存在)。
- 示例:
create(创建房间):服务器同步返回房间ID和状态。destroy(销毁房间):服务器同步确认房间已删除。
-
异步信令
- 特点:通过事件(Event)机制通知结果,客户端发送请求后无需阻塞等待,服务器通过事件回调告知状态变化,适用于耗时操作或媒体协商(如发布/订阅流程)。
- 示例:
join(加入房间):服务器通过joined事件返回房间信息和活跃发布者列表。publish(发布媒体流):服务器通过event事件返回SDP Answer,完成媒体协商。
三、信令的组成与格式
-
基本组成
- 操作类型(Request):指定信令的用途(如
create/join/publish)。 - 参数(Body):携带操作所需的具体信息(如房间ID、密码、媒体配置参数)。
- 事务标识(Transaction ID):用于关联请求与响应,避免多个请求混淆(常见于同步信令)。
- 操作类型(Request):指定信令的用途(如
-
典型格式(以JSON为例)
// 同步信令示例:创建房间 { "request": "create", // 操作类型 "room": 1234, // 房间ID(可选) "secret": "admin123", // 管理密码 "is_private": true // 是否为私有房间 } // 异步信令示例:发布媒体流(携带SDP Offer) { "request": "publish", "audio": true, "video": true, "jsep": { // SDP Offer "type": "offer", "sdp": "v=0 ..." } }
四、信令与媒体流的关系
- 信令通道:独立于媒体流传输通道,通常通过WebSocket或HTTP协议传输,用于传递控制指令(如SDP协商、ICE候选地址交换)。
- 媒体通道:负责传输实际的音视频数据(如通过WebRTC的DataChannel或RTP协议),其建立和管理依赖信令完成。
- 关键交互流程:
- 发布者:发送
publish信令(含SDP Offer)→ 服务器返回event信令(含SDP Answer)→ 建立媒体连接。 - 订阅者:发送
join信令(指定订阅的发布者)→ 服务器返回attached事件(含SDP Offer)→ 发送start信令(含SDP Answer)→ 开始接收媒体流。
- 发布者:发送
2 janus的信令
以videoroom为例子
2.1 同步请求
直接在事务( transacation )的上下⽂⾥获取响应,即直接响应( transaction 绑定即可)(事务下面)
- create - 动态创建一个新的房间:该信令用于动态生成新的视频房间,是使用配置文件创建房间的替代方式。请求时需在 body 中设置相关参数,如room(可选的唯一房间 ID,若未提供则由插件自动生成) 、permanent(是否永久保存房间,默认 false) 、description(房间描述)、secret(编辑 / 销毁房间的密码)、pin(加入房间的密码)、is_private(是否为私有房间,私有房间不会出现在列表请求中)、allowed(用户加入房间的 token 数组)等 。成功响应为created,并返回房间 ID;若请求永久房间但返回 false,可能是权限问题。默认情况下,Janus 中所有用户都可创建房间,也可在插件中配置admin_key限制该功能,只有携带正确admin_key才能创建成功 。
- destroy - 移除并销毁房间:此信令用于销毁房间并剔除其中所有用户,可用于静态和动态房间。请求 body 包含room(要销毁的房间唯一 ID)、secret(若配置了房间密码则必填)、permanent(是否从配置文件中移除,默认 false) 。成功响应为destroyed,当房间被销毁时,其他所有参与者会收到destroyed事件通知 。
- edit - 动态修改一些房间属性:在房间创建后,可使用edit信令修改部分房间属性,如room description、secret、pin、private or not等,但不能修改房间 ID、采样率等静态属性。请求 body 需要room(要编辑的房间唯一 ID)、secret(若配置了则必填),还可包含new_description(新的房间描述)、new_secret(新的编辑 / 销毁密码)、new_pin(新的加入密码)、new_is_private(是否在列表中显示)等新属性 。成功响应为edited 。
- exists - 查询是否存在指定的房间:通过该信令可检查指定房间是否存在。请求 body 只需room(要检查的房间唯一 ID) 。成功响应为success,并在响应中通过exists字段显示房间是否存在(true 或 false) 。
- list - 列出所有可用的房间:list信令用于展示所有公开的房间。请求 body 仅需设置request为list 。成功响应为success,响应中rooms字段包含房间对象数组,每个对象包含房间 ID、描述、是否需要密码加入、最大发布者数量、比特率等信息 。
- allowed - 配置观众进入房间权限:此信令可配置是否检查 tokens,以及添加或删除允许加入房间的用户。请求 body 包含secret(若配置了房间密码则必填)、action(取值为enable、disable、add、remove)、room(要更新的房间唯一 ID)、allowed(用户加入房间的 token 数组,仅在add或remove操作时使用) 。成功响应为success,并返回更新后的允许 token 列表(在enable、add或remove操作时) 。
- kick - 管理员踢除用户操作:房间管理员可使用kick信令将指定参与者踢出房间,但无法阻止其再次进入,若要阻止需配合allowed信令使用。请求 body 需要secret(若配置了房间密码则必填)、room(房间唯一 ID)、id(要踢除的参与者唯一 ID) 。成功响应为success 。
2.2 异步请求
在Janus的Video Room中,异步请求信令通过事件机制实现状态回调,需通过handle_id绑定请求与响应,主要用于用户加入房间、媒体发布/订阅及状态变更等场景。以下是对各信令的详细解析:
一、核心机制:异步请求与事件通知
- 流程特点:
- 客户端发送异步请求(如
join),携带handle_id标识操作上下文。 - 服务器处理请求后,通过
event信令推送结果(成功/失败),需依赖handle_id关联响应。
- 客户端发送异步请求(如
- 关键参数:
handle_id:插件会话句柄,用于唯一标识用户连接,区分不同参与者的请求。transaction:事务ID,部分请求需携带以关联请求与响应。
二、异步请求信令详解
1. join - 加入房间
- 功能:
客户端加入指定房间,并指定角色为发布者(publisher)或订阅者(subscriber)。 - 请求参数(body):
{ "request": "join", "ptype": "publisher/subscriber", // 角色类型 "room": <房间ID>, // 目标房间ID "id": <自定义参与者ID>, // 可选,默认由插件生成 "display": "<显示名称>", // 可选 "token": "<邀请令牌>", // 权限验证(若房间配置了ACL) // 订阅者额外参数(当ptype为subscriber时) "feed": <发布者ID>, // 订阅的目标发布者(必填) "private_id": <发布者私有ID>, // 若房间配置require_pvtid,需携带 "audio/video/data": true/false // 音视频中继配置 } - 响应与事件:
- 成功:返回
joined事件,包含房间信息、参与者ID、活跃发布者列表(发布者场景)或attached事件(订阅者场景,含SDP Offer)。 - 失败:返回
event信令,包含error_code和错误描述(如权限不足)。
- 成功:返回
- 应用场景:
- 发布者加入房间:后续需通过
publish或configure开始发布媒体。 - 订阅者加入房间:需指定
feed(目标发布者ID),后续通过start完成媒体协商。
- 发布者加入房间:后续需通过
2. configure - 修改参与者设置
- 功能:
动态调整参与者的媒体配置(如码率限制、编码参数),支持发布者和订阅者场景。 - 请求参数(body):
{ "request": "configure", "audio/video/data": true/false, // 启用/禁用音视频中继 "bitrate": <码率限制>, // 覆盖全局码率配置 "keyframe": true/false, // 强制发送关键帧请求 "record": true/false, // 是否录制当前流(需房间开启录制) // 发布者专属参数 "audiocodec/videocodec": "<编码格式>", // 强制使用的编解码器 // 订阅者专属参数(如多流配置) "substream": <子流索引>, // 接收指定子流(适用于Simulcast) "spatial_layer": <空间层>, // VP9-SVC专属配置 } - 响应与事件:
- 成功:返回
event信令,configured: "ok"。 - 失败:返回
event信令,包含配置错误信息(如不支持的编码格式)。
- 成功:返回
- 典型用途:
- 发布者动态调整码率(如网络波动时降低分辨率)。
- 订阅者切换子流(如从高清流切换至标清流以节省带宽)。
3. joinandconfigure - 加入并配置(发布者专用)
- 功能:
将join(加入房间)和configure(开始发布)合并为一个请求,简化发布者流程。 - 请求参数(body):
包含join和configure的组合参数,需携带JSEP SDP Offer用于媒体协商:{ "request": "joinandconfigure", "ptype": "publisher", "room": <房间ID>, "audio/video/data": true, "jsep": { "type": "offer", "sdp": "<SDP内容>" }, // 媒体协商参数 // 其他配置参数(如bitrate、编码格式) } - 响应与事件:
- 成功:返回
joined事件,包含jsep字段(SDP Answer),完成PeerConnection建立。 - 失败:返回协商失败原因(如房间不支持指定编码)。
- 成功:返回
- 优势:
减少信令交互次数,适用于需要快速启动发布的场景(如直播推流)。
4. publish - 开始发布媒体流
- 功能:
发布者向房间内广播音视频流,需通过JSEP协议完成媒体协商。 - 请求参数(body):
{ "request": "publish", "audio/video/data": true/false, // 启用中继的媒体类型 "jsep": { "type": "offer", "sdp": "<SDP Offer>" }, // 必须携带SDP Offer "bitrate": <码率限制>, // 可选,覆盖房间全局配置 "record": true/false, // 是否单独录制该流 } - 响应与事件:
- 服务器返回
event信令,包含jsep字段(SDP Answer),客户端需将其设置为远程描述完成协商。 - 协商成功后,其他参与者收到
publishers事件,包含新发布者信息(ID、编码格式等)。
- 服务器返回
- 注意:
- 发布者需先通过
join加入房间,再调用publish。 - 若房间配置
require_e2ee(端到端加密),需在SDP中协商加密参数。
- 发布者需先通过
5. unpublish - 停止发布媒体流
- 功能:
发布者停止广播流,销毁相关PeerConnection,并从活跃列表中移除。 - 请求参数(body):
{ "request": "unpublish" } // 无需其他参数,基于当前handle_id操作 - 响应与事件:
- 成功:返回
event信令,unpublished: "ok"。 - 其他参与者收到
unpublished事件,通知流已不可用。
- 成功:返回
- 应用场景:
发布者结束发言、关闭摄像头/麦克风时调用。
6. start - 开始接收订阅的媒体流
- 功能:
订阅者完成媒体协商,开始接收指定发布者的流。 - 请求参数(body):
{ "request": "start", "jsep": { "type": "answer", "sdp": "<SDP Answer>" }, // 必须携带SDP Answer } - 流程:
- 订阅者通过
join请求后,服务器返回attached事件(含SDP Offer)。 - 订阅者生成SDP Answer,通过
start请求发送至服务器完成协商。 - 成功后建立PeerConnection,开始接收媒体流。
- 订阅者通过
- 响应:
返回event信令,started: "ok",流状态变为活跃。
7. pause/resume - 暂停/恢复媒体流
- 功能:
pause:临时停止接收或发送媒体流(保留连接,可通过start恢复)。resume:通过再次调用start实现,无需重新协商SDP。
- 请求参数(body):
{ "request": "pause" } // 停止流传输 { "request": "start" } // 恢复流传输(无需JSEP) - 响应:
pause成功:返回event信令,paused: "ok"。start恢复:返回event信令,started: "ok"。
- 应用场景:
临时静音、关闭视频画面但保持连接时使用,减少带宽占用。
8. switch - 切换订阅的媒体流来源
- 功能:
订阅者在已建立的PeerConnection上切换目标发布者(类似“换台”),复用连接资源。 - 请求参数(body):
{ "request": "switch", "feed": <新发布者ID>, // 目标发布者ID(必填) "audio/video/data": true/false // 可选,覆盖当前媒体中继配置 } - 限制:
- 目标发布者需使用与原发布者相同的编码配置(如音视频编码格式、数据格式),否则可能导致播放异常。
- 仅支持同时切换音视频流,无法单独切换音频或视频。
- 响应:
返回event信令,switched: "ok",并更新订阅的流信息。
9. leave - 离开房间
- 功能:
参与者退出房间,销毁所有关联的PeerConnection(发布者自动停止发布,订阅者停止接收流)。 - 请求参数(body):
{ "request": "leave" } // 无需其他参数,基于当前handle_id操作 - 响应与事件:
- 成功:返回
event信令,leaving: "ok"。 - 其他参与者收到
leaving事件,若为发布者,还会触发unpublished事件。
- 成功:返回
- 清理机制:
服务器自动释放该用户占用的资源(如RTP转发器、录制句柄等)。
3 信令交互体系
3.1 信令体系三种概念
在 Janus 信令体系里,session(会话)、transaction(事务)、handle_id(句柄 ID )是理解信令交互逻辑的核心概念,各自承担不同角色:
1. session(会话)
- 定义:
是客户端与 Janus 服务器建立的逻辑连接上下文,类似通话中的“通话链路”,创建后通过唯一session_id标识。 - 核心作用:
- 上下文绑定:同一
session内的所有操作(如创建房间、加入房间、发布媒体)共享会话状态,服务器通过session_id识别客户端身份。 - 资源管理:一个
session可关联多个handle_id(对应不同插件或媒体流),会话销毁时,关联的所有handle_id也会释放。
- 上下文绑定:同一
- 典型流程:
客户端 → POST /janus/sessions → 服务器返回 session_id(如 12345) 后续操作(attach 插件、publish 流等)需携带该 session_id,确保归属同一会话。 - 类比:
好比去银行开卡,session是你的“客户身份”,后续存钱、转账都基于这个身份,银行通过客户号(session_id)识别你。
2. transaction(事务)
- 定义:
是单次请求 - 响应的唯一标识,每次信令交互(如create、join)需生成唯一transaction_id(通常是 UUID ),用于关联请求与响应。 - 核心作用:
- 解耦请求响应:异步场景下,客户端可能同时发多个请求,服务器通过
transaction_id区分回复,避免混乱。 - 幂等性保障:重复请求(如网络重传)可通过
transaction_id识别,防止重复执行操作(如重复创建房间)。
- 解耦请求响应:异步场景下,客户端可能同时发多个请求,服务器通过
- 典型流程:
// 客户端发 create 房间请求 { "janus": "create", "transaction": "abc-123", // 唯一事务 ID "session_id": 12345 // 关联会话 } // 服务器回复 { "janus": "success", "transaction": "abc-123", // 回显事务 ID,客户端匹配请求 "data": { "id": 67890 } // 房间 ID } - 类比:
像快递单号,你寄多个包裹(请求)时,每个包裹有唯一单号(transaction_id),快递站(服务器)按单号区分回复,你也能凭单号查进度。
3. handle_id(句柄 ID )
- 定义:
是插件实例的唯一标识,客户端通过attach操作绑定到具体插件(如videoroom、echotest),获得handle_id,后续对插件的操作(如发布流、订阅流)需携带该 ID。 - 核心作用:
- 多插件隔离:一个
session可同时操作多个插件(如一边视频通话,一边录屏),handle_id区分不同插件实例。 - 媒体流关联:在
videoroom中,handle_id对应房间内的参与者(发布者/订阅者),控制其媒体流行为(发布、暂停、切换)。
- 多插件隔离:一个
- 典型流程:
// 客户端 attach 到 videoroom 插件 { "janus": "attach", "session_id": 12345, "transaction": "def-456", "plugin": "janus.plugin.videoroom" } // 服务器回复,返回 handle_id { "janus": "success", "transaction": "def-456", "data": { "id": 98765 } // handle_id } // 后续操作(如 join 房间)需携带 handle_id { "janus": "message", "session_id": 12345, "handle_id": 98765, "transaction": "ghi-789", "body": { "request": "join", "room": 123 } } - 类比:
如同你手机里的多个 App(session是手机系统),每个 App 有唯一图标(handle_id),点击图标(操作handle_id)才能用 App 功能(插件操作)。
三者关系总结
- 层级关联:
session(会话)是顶层连接,handle_id(插件句柄)是会话内的功能分支,transaction(事务)是单次操作的“身份证”。 - 交互逻辑:
所有信令需携带session_id(归属会话)、handle_id(归属插件)、transaction_id(归属操作),共同确保信令精准路由和状态管理。
3.2 交互流程
1. 会话创建与插件绑定
-
步骤1:创建会话
客户端发送create信令:{ "janus": "create", "transaction": "8u8NAbBdgVN1" }服务器响应
success,返回session_id。 -
步骤2:绑定插件
客户端通过attach信令指定插件(如videoroom):{ "janus": "attach", "session_id": 6282808472350684, "transaction": "def-456", "plugin": "janus.plugin.videoroom" }服务器响应
success,返回handle_id(如98765)。
2. 插件信令交互(以VideoRoom为例)
-
步骤3:发送插件指令
客户端通过message信令调用插件功能(如创建房间):{ "janus": "message", "session_id": 6282808472350684, "handle_id": 98765, "transaction": "ghi-789", "body": { "request": "create", "room": 1234 } }body字段包含插件特定操作(如create/join)和参数。
-
步骤4:服务器事件推送
插件通过event信令返回结果或状态变更(如房间创建成功):{ "janus": "event", "plugindata": { "plugin": "janus.plugin.videoroom", "data": { "videoroom": "created", "room": 1234 } } }- 异步事件(如新用户加入)通过
event实时推送。
- 异步事件(如新用户加入)通过
3. 媒体协商与传输(WebRTC流程)
-
步骤5:ICE会话建立
客户端通过trickle信令发送ICE候选地址:{ "janus": "trickle", "candidate": "...", "handle_id": 98765 }ICE完成后,服务器发送
webrtcup事件通知通道建立。 -
步骤6:媒体数据传输
- 发布者发送
publish信令(携带SDP Offer):{ "janus": "message", "handle_id": 98765, "body": { "request": "publish" }, "jsep": { "type": "offer", "sdp": "..." } } - 订阅者通过
start信令返回SDP Answer,开始接收流。
- 发布者发送
4. 资源释放与会话销毁
- 步骤7:销毁句柄与会话
- 客户端调用
destroyHandle释放插件资源:{ "janus": "message", "handle_id": 98765, "body": { "request": "destroy" } } - 调用
destroy信令销毁会话:{ "janus": "destroy", "session_id": 6282808472350684 }
- 客户端调用
三、信令格式与关键字段
-
必选字段
janus:消息类型(如create/attach/event)。session_id:会话ID(create信令除外)。handle_id:插件句柄ID(插件操作必选)。transaction:事务ID(event事件除外)。
-
插件专属字段
body:插件操作参数(如room/feed/ptype)。jsep:SDP协商参数(offer/answer),用于WebRTC流程。
-
示例:订阅者加入房间
{ "janus": "message", "session_id": 6282808472350684, "handle_id": 98765, "body": { "request": "join", "ptype": "subscriber", "room": 1234, "feed": 5678 // 目标发布者ID } }
4 具体流程分析
4.1 publish
一、前置准备:会话与插件绑定
在执行 publish 前,需完成 Janus 会话创建 和 VideoRoom 插件绑定,为发布媒体流建立基础通信上下文:
- 创建 Janus 会话(
create信令)
- 客户端动作:
发送create信令,生成全局会话 ID(session_id):{ "janus": "create", "transaction": "随机事务ID" } - 服务器响应:
返回success,包含session_id(如6282808472350684),标识客户端与 Janus 的连接。
- 绑定 VideoRoom 插件(
attach信令)
- 客户端动作:
发送attach信令,指定插件为janus.plugin.videoroom,关联会话:{ "janus": "attach", "session_id": "会话ID", "transaction": "随机事务ID", "plugin": "janus.plugin.videoroom" } - 服务器响应:
返回success,包含handle_id(如433001608215093),标识 VideoRoom 插件实例。
二、核心流程:publish 信令交互与媒体协商
完成会话和插件绑定后,进入 发布者逻辑:通过 join 加入房间,再用 publish 启动媒体流发布,涉及 SDP 协商 和 ICE 候选交换。
- 加入房间(
join信令,发布者角色)
- 客户端动作:
发送join信令,声明角色为publisher,指定房间 ID(如1234):{ "janus": "message", "session_id": "会话ID", "handle_id": "插件句柄ID", "transaction": "随机事务ID", "body": { "request": "join", "room": 1234, "ptype": "publisher", // 发布者角色 "display": "自定义名称" } } - 服务器响应:
返回event信令,包含joined事件,确认加入房间,同时返回房间内现有发布者信息(供订阅者使用)。
- 发布媒体流(
publish信令 + SDP Offer)
-
客户端动作:
调用getUserMedia获取音视频流,创建RTCPeerConnection,生成 SDP Offer,通过publish信令发送:{ "janus": "message", "handle_id": "插件句柄ID", "body": { "request": "publish", "audio": true, "video": true }, "jsep": { "type": "offer", "sdp": "SDP Offer 内容" } // 媒体协商参数 }audio/video:声明是否发布音频/视频流。jsep:携带 SDP Offer,描述本地媒体能力(编码、端口等)。
-
服务器响应:
-
SDP 协商:Janus 生成 SDP Answer,通过
event信令返回:{ "janus": "event", "plugindata": { "plugin": "janus.plugin.videoroom", "data": { "videoroom": "configured" } }, "jsep": { "type": "answer", "sdp": "SDP Answer 内容" } }客户端将 SDP Answer 设置为
RTCPeerConnection的远程描述,完成媒体协商。 -
ICE 候选交换:客户端通过
trickle信令发送 ICE 候选(网络地址),Janus 转发给订阅者,建立 P2P 连接:{ "janus": "trickle", "handle_id": "插件句柄ID", "candidate": { "candidate": "候选内容", "sdpMid": "mid", "sdpMLineIndex": 0 } }
-
三、状态通知与流传输
完成信令协商后,进入 媒体流传输阶段,Janus 会通过事件通知发布状态,并开始转发媒体流。
1. 发布成功通知
- 服务器通过
event信令推送configured事件,确认发布者已成功广播流:{ "janus": "event", "plugindata": { "plugin": "janus.plugin.videoroom", "data": { "videoroom": "configured", "room": 1234 } } }
- 媒体流传输
- 客户端的
RTCPeerConnection开始发送音视频流,Janus 作为 SFU(选择性转发单元),将流转发给房间内的订阅者。 - 订阅者通过
start信令完成协商后,即可接收发布者的媒体流。
四、异常与终止
- 发布失败处理
若 SDP 协商失败(如编码不支持),服务器返回event信令包含error:
{
"janus": "event",
"plugindata": { "plugin": "janus.plugin.videoroom", "data": { "error": "编码不支持" } }
}
- 停止发布(
unpublish信令)
客户端发送unpublish信令,停止媒体流发布:
{ "janus": "message", "handle_id": "插件句柄ID", "body": { "request": "unpublish" } }
服务器响应 event 信令,通知订阅者流已停止。
五、关键逻辑总结
| 阶段 | 核心信令/操作 | 作用 |
|---|---|---|
| 会话准备 | create → attach |
建立客户端与 Janus 的连接,绑定 VideoRoom 插件 |
| 加入房间 | join(ptype=publisher) |
以发布者身份加入房间,获取房间上下文 |
| 媒体协商 | publish + SDP Offer → SDP Answer |
交换媒体能力,建立 WebRTC 连接 |
| 流传输 | ICE 候选交换 + RTCPeerConnection |
传输音视频流,Janus 转发给订阅者 |
| 终止发布 | unpublish |
停止流传输,释放资源 |
六 流程

4.2 subscribe流程
一、Janus 订阅信令流程(以 VideoRoom 插件为例 )
结合 Janus 插件交互逻辑,订阅媒体流的信令流程可拆解为以下步骤,核心围绕 会话建立、插件绑定、房间加入、媒体协商 展开:
- 前置准备:创建会话 + 绑定插件
-
创建 Janus 会话
客户端发送create信令,生成全局会话标识session_id:{ "janus": "create", "transaction": "随机事务ID" }服务器响应
success,返回session_id(如6282808472350684)。 -
绑定 VideoRoom 插件
客户端通过attach信令,将janus.plugin.videoroom插件绑定到会话,获取handle_id:{ "janus": "attach", "session_id": "会话ID", "transaction": "随机事务ID", "plugin": "janus.plugin.videoroom" }服务器响应
success,返回handle_id(如433001608215093)。
- 核心步骤:加入房间 + 订阅流
-
以订阅者身份加入房间(
join信令)
客户端发送join信令,声明角色为subscriber,并指定 目标发布者 ID(feed):{ "janus": "message", "session_id": "会话ID", "handle_id": "插件句柄ID", "transaction": "随机事务ID", "body": { "request": "join", "room": 1234, // 房间 ID "ptype": "subscriber", // 订阅者角色 "feed": 5678 // 要订阅的发布者 ID(从房间事件中获取) } }feed:需提前通过房间事件(如publishers通知)获取发布者的 ID。
-
接收 SDP Offer 并协商(
event信令 +start信令)
服务器响应event信令,推送attached事件,包含 SDP Offer(Janus 生成,描述媒体流能力):{ "janus": "event", "plugindata": { "plugin": "janus.plugin.videoroom", "data": { "videoroom": "attached" } }, "jsep": { "type": "offer", "sdp": "SDP Offer 内容" } }客户端收到后,创建
RTCPeerConnection,设置 SDP Offer 为远程描述,生成 SDP Answer,并通过start信令发送给 Janus:{ "janus": "message", "handle_id": "插件句柄ID", "body": { "request": "start" }, "jsep": { "type": "answer", "sdp": "SDP Answer 内容" } } -
ICE 候选交换(
trickle信令)
客户端收集本地 ICE 候选(网络地址),通过trickle信令发送给 Janus,Janus 透传给发布者,建立 P2P 连接:{ "janus": "trickle", "handle_id": "插件句柄ID", "candidate": { "candidate": "候选内容", "sdpMid": "mid", "sdpMLineIndex": 0 } }
- 状态通知与流接收
-
连接建立确认(
webrtcup事件)
Janus 发送webrtcup事件,通知 ICE 连接建立,媒体流开始传输:{ "janus": "event", "plugindata": { "plugin": "janus.plugin.videoroom", "data": { "videoroom": "webrtcup" } } } -
接收媒体流
客户端的RTCPeerConnection开始接收发布者的媒体流,Janus 作为 SFU 负责转发。
二 流程总结
4.3 总结
结合文档《31-04web客户端信令分析2-4.pdf》内容,针对 publish(发布) 和 subscribe(订阅) 的核心逻辑及疑问,观点总结如下:
一、publish 的“自动性”本质是信令驱动与 WebRTC 协商的封装
-
信令触发是前提
publish需通过信令显式调用,携带 SDP Offer 完成媒体协商(如文档中jsep字段的使用,)。- 示例信令:
表明发布需主动发送信令并提供协商参数,非“自动”行为。{ "janus": "message", "handle_id": "插件句柄ID", "body": { "request": "publish" }, "jsep": { "type": "offer", "sdp": "..." } }
-
WebRTC 流程的自动化
- 浏览器通过
RTCPeerConnection自动处理 ICE 候选交换、编解码等细节(),但协商的触发(如createOffer)仍由信令驱动。
- 浏览器通过
二、订阅通过 join + start 实现,无独立 subscribe 信令
-
角色声明与协商分离
- 订阅者通过
join信令声明角色(ptype: "subscriber")并指定目标发布者(feed),如:{ "janus": "message", "body": { "request": "join", "ptype": "subscriber", "room": 123, "feed": 456 } }
- 订阅者通过
-
设计逻辑:复用信令与角色解耦
- Janus 通过
ptype字段区分发布者/订阅者(),避免信令冗余。 - 插件体系需兼容多种场景(如音视频会议、直播),拆分
join(角色)与start(协商)更灵活()。
- Janus 通过
三、发布者与订阅者的区分核心在于信令参数与流程
-
信令参数差异
- 发布者:
join时ptype="publisher",需调用publish发送流()。 - 订阅者:
join时ptype="subscriber",需调用start接收流()。
- 发布者:
-
事件通知与资源管理
- 发布者通过
publishers事件通知房间内其他订阅者(),订阅者通过subscribers事件被发布者感知()。 - 发布者需管理
RTCPeerConnection的创建与销毁(),订阅者则侧重流的接收与渲染()。
- 发布者通过
火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。
更多推荐
所有评论(0)