docs-matrix-spec/locales/zh-Hans/client-server-api/modules/voip_events.md
2025-04-20 16:13:37 +08:00

11 KiB
Raw Blame History

语音通信VoIP

本模块描述了房间内两位用户如何建立语音通信VoIP通话。语音和视频通话均基于 WebRTC 1.0 标准构建。通话信令通过向房间发送消息事件来实现。在本规范版本中,仅支持双方通信(如两点对两点,或点对多点会议设备)。虽然通话可在包含多位成员的房间中发起,但同一时间仅允许两台设备参与通话。

所有 VoIP 事件均包含一个 version 字段。该字段用于判断设备是否支持此新版本的协议。例如,客户端可利用该字段判断是否应期待来自对方的 m.call.select_answer 事件。如果客户端接收到的事件 version 字段不是 0"1"(包括如数值型 1),则应视同其 version == "1" 处理。

需注意,这意味着未来任何版本的 VoIP 事件均应保持向后兼容性。如有必要引入非兼容性新规范,则将采用一套独立的事件类型。

通话方标识符

每当客户端首次参与新通话时,应为自身生成一个 party_id,在通话期间一直使用。此标识符应足够长,确保即使多设备同时生成应答,其间发生冲突的概率极低:建议使用 8 个大小写字母+数字字符。通话方通过 (user_id, party_id) 元组进行识别。

客户端将包含该 party_id 字段,并放置于所有 VoIP 事件内容的顶层,包括 m.call.invite。客户端用此字段识别自身事件的远端回显:由于用户可能与自己通话,无法简单地忽略来自自身用户的事件。此外,该字段还能区分不同客户端对同一邀请发出的不同应答,并将 m.call.candidates 事件与相应的应答/邀请进行匹配。

客户端实现可选择使用端到端加密中使用的设备 ID用作此目的或者可为每次通话生成不同 ID以避免在未加密房间中泄露使用设备信息或隐藏单一设备即 Access Token被用于多个通话方信令发送等信息。

party_id 的语法定义见下文

礼让规则

根据 WebRTC 完美协商示例在重新协商过程中存在礼让politeness规则。被叫方始终为礼让方。在碰撞glare情况下通话方的礼让状态由是采纳呼入通话还是主动呼叫决定如果客户端舍弃本地呼出而采用呼入通话则其为礼让方。

通话事件存活性

m.call.invite 中包含 lifetime 字段,指示邀约有效的时长。收到邀请后,客户端应结合事件同步响应中的 age 字段与自接收到该事件以来的时间,判断邀约是否依然有效。使用 age 字段可确保客户端设备错误时钟不会导致通话异常。

若邀约有效且在用户可接受通话期间保持有效,应提示有来电。用户可接受通话的时长可由不同客户端自行决定,例如在锁定的移动设备上,可以比在解锁的桌面设备上更长。

在处理完整个同步响应sync response对于加密房间尝试解密该房间全部加密事件后客户端才应提示来电。这样可以避免同步响应中含有随后指示通话已挂断、被拒绝或已被他处接听的事件时重复提示来电。

若客户端启动后,在处理本地已存储事件后,发现仍有有效的邀约,应在与 homeserver 完成一次同步后再予以提示。

建议的最小有效时长为 90 秒——这样可确保用户有充足时间接听来电。

ICE 候选Candidate批量发送

客户端应设法只发送少量候选事件,具体指引如下:

  • 在邀请/应答事件本身中,应立即或几乎立即发现的 ICE 候选(例如 host 类型 candidate。如能在短时间内收集到服务器反射或中继 candidate应一并发送。建议初始延迟约 200ms。
  • 之后,客户端应等待一段时间以收集更多候选,实现批量发送,而非每获一个立刻发送。建议在发送邀请后等待 2 秒,或发送应答后等待 500ms因发送邀请后客户端本就等待用户接听可利用此延迟

结束候选通知End-of-candidates

值为空字符串的 ICE 候选表示不会再发送新的 ICE 候选。客户端必须在 m.call.candidates 消息中发送此类候选。虽然 WebRTC 规范要求浏览器生成此候选但截止目前并非所有浏览器都实现Chrome 不生成,但会产生 icegatheringstatechange 事件)。候选生成结束时,客户端应立即发送全部候选,而不应再等待上文时间间隔。这可方便桥接到不支持逐步 ICEtrickle ICE协议时对候选的批量处理。

DTMF

Matrix 客户端可按 WebRTC 规范发送 DTMF。截至 2020 年 8 月WebRTC 标准尚不支持接收 DTMF但 Matrix 客户端可接收并解析 RTP 负载中的 DTMF 信号。

VoIP 标识符的语法

call_idparty_id 必须遵循不透明标识符语法

离开房间时的行为

若客户端检测到正在通话的用户离开房间,应将其视作所有正在进行中的通话的挂断事件。对于发送邀约而被邀请方离开房间的情形,规范未做硬性规定,但若房间内已无可接听用户,客户端可选择将其视作被拒绝(如仅剩发送方本人,或邀约的 invitee 字段被设置后未被接听)。

历史通话回溯时亦应如此处理。

支持的编解码器

Matrix 规范未强制指定特定音视频编解码器,完全遵循 WebRTC 规范。兼容的 Matrix VoIP 客户端将像被支持的“浏览器”一样,根据所支持的编解码器及其变体运作。需遵循最新的 WebRTC 规范版本,因此客户端应及时跟进 WebRTC 规范的新版本,无论 Matrix 规范是否变更。

事件

通用字段

{{% event-fields event_type="call_event" %}}

事件

{{% event-group group_name="m.call" %}}

客户端行为

通话建立流程如下,双方通过消息事件进行通信:

    呼叫方                    被叫方
    [发起呼叫]
    m.call.invite ----------->  
    m.call.candidate -------->
    [..candidates..] -------->
                          [接听来电]
         <--------------- m.call.answer
    m.call.select_answer ----------->
     [通话正在进行中]
         <--------------- m.call.hangup

或通话被拒绝时:

    呼叫方                      被叫方
    m.call.invite ------------>
    m.call.candidate --------->
    [..candidates..] --------->
                           [拒绝通话]
             <-------------- m.call.hangup

通话协商遵循 WebRTC 规范进行。

面对来电,客户端可采取多种操作:

  • 发送 m.call.answer,尝试接听通话。
  • 主动在所有设备上拒绝通话:如上图,发送 m.call.reject,则所有用户设备均停止响铃,并通知呼叫方其来电被拒。
  • 忽略通话:不发送任何事件,仅本地停止通话提示。用户的其他设备仍会继续响铃,呼叫方设备继续显示响铃,若无设备响应,通话将自动超时。
多流Streams

客户端可在一次 VoIP 通话中发送多路流。应通过在 m.call.invitem.call.answer 以及 m.call.negotiate 事件中加入 sdp_stream_metadata 属性来区分流。当元数据发生变更但无需重新协商时,可发送 m.call.sdp_stream_metadata_changed 事件。

推荐客户端对于带 audio_muted 字段且值设为 true 的来流,不要本地关闭 WebRTC 音轨。这是因为当对方取消静音后,客户端发送音频与 m.call.sdp_stream_metadata_changed 事件到达之间可能略有延迟,这段音频会被错过。对方静音后将停止发送音频,无需担心无意识的音频发送。

对于 video_muted,建议仍应本地关闭视频流,避免接收端看到黑屏。

sdp_stream_metadata 存在,但来流未被列在其中,应直接忽略。若某流用途未知(purpose 类型未知)也应忽略。

为保证兼容性,若对方首次发来的 m.call.invitem.call.answer 缺少 sdp_stream_metadata 属性,客户端应假定对方不支持此属性,即无法区分多流,客户端仅应使用第一条来流,并勿发送多于一条。

实现本规范的客户端应忽略无流的轨道streamless tracks

被邀请方

若通话仅面向特定用户,invitee 字段应被加入,并设置为该用户的 Matrix ID。不含 invitee 字段的邀请,默认为面向房间内除发送者以外任何成员。

客户端在接收到未过期的邀请,invitee 字段缺失或等于本用户 Matrix ID 时,应视为有效来电,但是否响铃需根据与呼叫方关系和来电地点判定。建议客户端默认忽略来自公共房间的呼叫邀请。强烈建议即便未为来电响铃,客户端也应在房间中展示来电并标记其被忽略。

通话碰撞Glare

“通话碰撞”指两位用户几乎在同一时间互相呼叫对方,导致已有呼入/呼出通话而无法建立。可使用碰撞解决算法决定应挂断哪个通话,应接听哪个通话。如双方客户端实用相同算法,将会选择同一个通话,通话得以正常建立。

因通话目标为房间而非具体用户,以下碰撞解决算法仅适用于同一房间的通话:

  • 若客户端在准备发送 m.call.invite 至某房间时,收到了同一房间的 m.call.invite
    • 客户端应取消本地外呼,转而自动为用户接听来电。
  • 若客户端已向某房间发送 m.call.invite 并在等待响应时收到同房间的 m.call.invite
    • 客户端应对两个通话的 call_id 按字典序比较,保留较小者,挂断较大者;如来电为较小者,客户端应代表用户接听之。

对用户而言,通话建立过程应如同直接接通,无需察觉背后切换。任何初始化媒体流应当平滑迁移至被采纳的通话。

服务器行为

Homeserver 可以(可选)向客户端提供 TURN 服务器信息,便于客户端通过 TURN 实现点对点通信。客户端可通过下述 HTTP API 获取 TURN 服务器信息。

{{% http-api spec="client-server" api="voip" %}}

安全性考量

通话仅应在仅有两位用户的房间发起。若在多人聊天室发起,其他用户可能会拦截并接听该通话。