1 信令

**信令(Signaling)**是通信系统中用于协调和管理通信过程的指令和消息机制,主要负责在通信各方之间传递控制信息,确保通信的建立、维护和终止顺利进行。

一、信令的核心作用

  1. 建立通信连接

    • 协商通信参数:如媒体编码格式(音频/视频编码)、网络传输协议(如WebRTC的ICE协议)、加密方式等。
    • 示例:在WebRTC中,通过信令交换SDP(会话描述协议)信息,确定双方支持的媒体类型和传输地址。
  2. 管理通信状态

    • 控制用户加入/离开房间、切换角色(如从发布者转为订阅者)。
    • 监控和通知房间状态变更(如房间销毁、用户被踢除)或媒体流变化(如开始/停止发布)。
  3. 协调媒体流交互

    • 发布者通过信令告知服务器开始发送媒体流(如publish信令),订阅者通过信令请求接收特定流(如start信令)。
    • 处理媒体流的动态调整(如码率控制、分辨率切换)。
  4. 权限与资源管理

    • 验证用户身份(如房间密码、Token),控制访问权限(如允许/禁止用户加入房间)。
    • 管理资源分配(如限制房间内最大发布者数量、设置音视频码率上限)。

二、信令的分类
根据通信模式和响应机制,信令可分为两类:

  1. 同步信令

    • 特点:请求与响应一一对应,客户端发送请求后立即等待服务器返回结果,适用于需要即时确认的操作(如查询房间是否存在)。
    • 示例
      • create(创建房间):服务器同步返回房间ID和状态。
      • destroy(销毁房间):服务器同步确认房间已删除。
  2. 异步信令

    • 特点:通过事件(Event)机制通知结果,客户端发送请求后无需阻塞等待,服务器通过事件回调告知状态变化,适用于耗时操作或媒体协商(如发布/订阅流程)。
    • 示例
      • join(加入房间):服务器通过joined事件返回房间信息和活跃发布者列表。
      • publish(发布媒体流):服务器通过event事件返回SDP Answer,完成媒体协商。

三、信令的组成与格式

  1. 基本组成

    • 操作类型(Request):指定信令的用途(如create/join/publish)。
    • 参数(Body):携带操作所需的具体信息(如房间ID、密码、媒体配置参数)。
    • 事务标识(Transaction ID):用于关联请求与响应,避免多个请求混淆(常见于同步信令)。
  2. 典型格式(以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协议),其建立和管理依赖信令完成。
  • 关键交互流程
    1. 发布者:发送publish信令(含SDP Offer)→ 服务器返回event信令(含SDP Answer)→ 建立媒体连接。
    2. 订阅者:发送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绑定请求与响应,主要用于用户加入房间、媒体发布/订阅及状态变更等场景。以下是对各信令的详细解析:

一、核心机制:异步请求与事件通知

  • 流程特点
    1. 客户端发送异步请求(如join),携带handle_id标识操作上下文。
    2. 服务器处理请求后,通过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和错误描述(如权限不足)。
  • 应用场景
    • 发布者加入房间:后续需通过publishconfigure开始发布媒体。
    • 订阅者加入房间:需指定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)
    包含joinconfigure的组合参数,需携带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, // 是否单独录制该流
    }
    
  • 响应与事件
    1. 服务器返回event信令,包含jsep字段(SDP Answer),客户端需将其设置为远程描述完成协商。
    2. 协商成功后,其他参与者收到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
    }
    
  • 流程
    1. 订阅者通过join请求后,服务器返回attached事件(含SDP Offer)。
    2. 订阅者生成SDP Answer,通过start请求发送至服务器完成协商。
    3. 成功后建立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(事务)

  • 定义
    单次请求 - 响应的唯一标识,每次信令交互(如 createjoin)需生成唯一 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 操作绑定到具体插件(如 videoroomechotest),获得 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 }
      
    服务器清理ICE会话和插件实例。

三、信令格式与关键字段

  1. 必选字段

    • janus:消息类型(如create/attach/event)。
    • session_id:会话ID(create信令除外)。
    • handle_id:插件句柄ID(插件操作必选)。
    • transaction:事务ID(event事件除外)。
  2. 插件专属字段

    • body:插件操作参数(如room/feed/ptype)。
    • jsep:SDP协商参数(offer/answer),用于WebRTC流程。
  3. 示例:订阅者加入房间

    {
      "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 插件绑定,为发布媒体流建立基础通信上下文:

  1. 创建 Janus 会话(create 信令)
  • 客户端动作
    发送 create 信令,生成全局会话 ID(session_id):
    { "janus": "create", "transaction": "随机事务ID" }
    
  • 服务器响应
    返回 success,包含 session_id(如 6282808472350684),标识客户端与 Janus 的连接。
  1. 绑定 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 候选交换

  1. 加入房间(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 事件,确认加入房间,同时返回房间内现有发布者信息(供订阅者使用)。
  1. 发布媒体流(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,描述本地媒体能力(编码、端口等)。
  • 服务器响应

    1. SDP 协商:Janus 生成 SDP Answer,通过 event 信令返回:

      {
        "janus": "event",
        "plugindata": { "plugin": "janus.plugin.videoroom", "data": { "videoroom": "configured" } },
        "jsep": { "type": "answer", "sdp": "SDP Answer 内容" }
      }
      

      客户端将 SDP Answer 设置为 RTCPeerConnection 的远程描述,完成媒体协商。

    2. 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 }
      }
    }
    
  1. 媒体流传输
  • 客户端的 RTCPeerConnection 开始发送音视频流,Janus 作为 SFU(选择性转发单元),将流转发给房间内的订阅者。
  • 订阅者通过 start 信令完成协商后,即可接收发布者的媒体流。

四、异常与终止

  1. 发布失败处理
    若 SDP 协商失败(如编码不支持),服务器返回 event 信令包含 error
{
  "janus": "event",
  "plugindata": { "plugin": "janus.plugin.videoroom", "data": { "error": "编码不支持" } }
}
  1. 停止发布(unpublish 信令)
    客户端发送 unpublish 信令,停止媒体流发布:
{ "janus": "message", "handle_id": "插件句柄ID", "body": { "request": "unpublish" } }

服务器响应 event 信令,通知订阅者流已停止。


五、关键逻辑总结

阶段 核心信令/操作 作用
会话准备 createattach 建立客户端与 Janus 的连接,绑定 VideoRoom 插件
加入房间 joinptype=publisher 以发布者身份加入房间,获取房间上下文
媒体协商 publish + SDP Offer → SDP Answer 交换媒体能力,建立 WebRTC 连接
流传输 ICE 候选交换 + RTCPeerConnection 传输音视频流,Janus 转发给订阅者
终止发布 unpublish 停止流传输,释放资源

六 流程

在这里插入图片描述

4.2 subscribe流程

一、Janus 订阅信令流程(以 VideoRoom 插件为例 )
结合 Janus 插件交互逻辑,订阅媒体流的信令流程可拆解为以下步骤,核心围绕 会话建立、插件绑定、房间加入、媒体协商 展开:

  1. 前置准备:创建会话 + 绑定插件
  • 创建 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)。

  1. 核心步骤:加入房间 + 订阅流
  • 以订阅者身份加入房间(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 }
    }
    
  1. 状态通知与流接收
  • 连接建立确认(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 协商的封装

  1. 信令触发是前提

    • publish 需通过信令显式调用,携带 SDP Offer 完成媒体协商(如文档中 jsep 字段的使用,)。
    • 示例信令:
      { 
        "janus": "message", 
        "handle_id": "插件句柄ID", 
        "body": { "request": "publish" }, 
        "jsep": { "type": "offer", "sdp": "..." } 
      }
      
      表明发布需主动发送信令并提供协商参数,非“自动”行为。
  2. WebRTC 流程的自动化

    • 浏览器通过 RTCPeerConnection 自动处理 ICE 候选交换、编解码等细节(),但协商的触发(如 createOffer)仍由信令驱动。

二、订阅通过 join + start 实现,无独立 subscribe 信令

  1. 角色声明与协商分离

    • 订阅者通过 join 信令声明角色(ptype: "subscriber")并指定目标发布者(feed),如:
      { 
        "janus": "message", 
        "body": { 
          "request": "join", 
          "ptype": "subscriber", 
          "room": 123, 
          "feed": 456 
        } 
      }
      
  2. 设计逻辑:复用信令与角色解耦

    • Janus 通过 ptype 字段区分发布者/订阅者(),避免信令冗余。
    • 插件体系需兼容多种场景(如音视频会议、直播),拆分 join(角色)与 start(协商)更灵活()。

三、发布者与订阅者的区分核心在于信令参数与流程

  1. 信令参数差异

    • 发布者joinptype="publisher",需调用 publish 发送流()。
    • 订阅者joinptype="subscriber",需调用 start 接收流()。
  2. 事件通知与资源管理

    • 发布者通过 publishers 事件通知房间内其他订阅者(),订阅者通过 subscribers 事件被发布者感知()。
    • 发布者需管理 RTCPeerConnection 的创建与销毁(),订阅者则侧重流的接收与渲染()。
Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐