实时传输协议 (RTP) 简介

实时传输协议 (RTP),在 RFC 3550 中定义,是一个 IETF 标准协议,旨在实现实时连接,以交换需要实时优先级的数据。本文概述了 RTP 是什么以及它在 WebRTC 中的功能。

注意:WebRTC 实际上使用 SRTP (安全实时传输协议) 来确保交换数据的安全性和身份验证。

将延迟降至最低对于 WebRTC 尤其重要,因为面对面的通信需要尽可能低的 延迟。一个用户说话与另一个用户听到之间的时间延迟越大,就越容易出现交叉通话和其他形式的混乱。

RTP 的关键特性

在检查 RTP 在 WebRTC 中的使用之前,了解 RTP 的功能和不足之处很有帮助。RTP 是一种数据传输协议,其使命是在当前条件下尽可能高效地在两个端点之间传输数据。这些条件可能受到从网络堆栈的底层到物理网络连接、中间网络、远程端点的性能、噪声水平、流量水平等的各种因素的影响。

由于 RTP 是一个数据传输协议,它得到了密切相关的 RTP 控制协议 (RTCP) 的补充,RTCP 定义在 RFC 3550,第 6 部分。RTCP 增加了包括服务质量 (QoS) 监控、参与者信息共享等功能。它不足以完全管理用户、成员、权限等,但为不受限制的多用户通信会话提供了基本需求。

RTCP 与 RTP 在同一 RFC 中定义的事实,暗示了这两个协议之间紧密相关的程度。

RTP 的能力

RTP 在 WebRTC 方面的主要优势包括

  • 通常较低的延迟。
  • 数据包已进行序号和时间戳标记,以便在乱序到达时重新组装。这使得使用 RTP 发送的数据可以在不保证顺序甚至不保证交付的传输上进行传递。
  • 这意味着 RTP 可以(但并非必须)在 UDP 之上使用,以获得其性能以及其多路复用和校验和特性。
  • RTP 支持组播;虽然这对于 WebRTC 来说尚不重要,但随着 WebRTC 未来(希望)得到增强以支持多用户对话,它可能会变得重要。
  • RTP 不仅限于视听通信。它可以用于任何形式的连续或活动数据传输,包括数据流、活动徽章或状态显示更新,或控制和测量信息传输。

RTP 的不足之处

RTP 本身并未提供所有可能的功能,这就是为什么 WebRTC 还使用其他协议。RTP 不包含的一些更值得注意的方面包括

  • RTP 保证服务质量 (QoS)。
  • 尽管 RTP 旨在用于对延迟敏感的场景,但它本身并不提供任何确保 QoS 的功能。相反,它只提供允许在堆栈的其他地方实现 QoS 所需的信息。
  • RTP 不处理可能需要的资源分配或预留。

在 WebRTC 的相关方面,这些问题在 WebRTC 基础架构的各个地方都有处理。例如,RTCP 处理 QoS 监控。

RTCPeerConnection 和 RTP

每个 RTCPeerConnection 都有方法可以访问服务于对等连接的 RTP 传输列表。这些对应于 RTCPeerConnection 支持的三种传输类型

RTCRtpSender

RTCRtpSender 负责将 MediaStreamTrack 数据编码并传输到远程对等节点。可以通过调用 RTCPeerConnection.getSenders() 来获取给定连接的发送器。

RTCRtpReceiver

RTCRtpReceiver 提供检查和获取有关传入 MediaStreamTrack 数据的信息的能力。可以通过调用 RTCPeerConnection.getReceivers() 来获取连接的接收器。

RTCRtpTransceiver

RTCRtpTransceiver 是一个 RTP 发送器和一个 RTP 接收器的组合,它们共享一个 SDP mid 属性,这意味着它们共享相同的 SDP 媒体 m-line(代表双向 SRTP 流)。这些由 RTCPeerConnection.getTransceivers() 方法返回,每个 mid 和收发器共享一对一的关系,mid 对于每个 RTCPeerConnection 都是唯一的。

利用 RTP 实现“保持”功能

由于 RTCPeerConnection 的流是通过 RTP 和上述接口实现的,因此您可以利用这些接口对流的内部进行访问和调整。您可以做的最简单的事情之一就是实现“保持”功能,即通话参与者可以点击一个按钮,关闭他们的麦克风,而是向另一个对等方发送音乐,并停止接收传入的音频。

注意:本示例使用了现代 JavaScript 功能,包括 async 函数await 表达式。这极大地简化了处理 WebRTC 方法返回的 Promise 的代码,使其更具可读性。

在下面的示例中,我们将把启用和禁用“保持”模式的对等方称为本地对等方,而被保持的用户称为远程对等方。

激活保持模式

本地对等方

当本地用户决定启用保持模式时,将调用下面的 enableHold() 方法。它接受一个包含保持期间播放的音频的 MediaStream 作为输入。

js
async function enableHold(audioStream) {
  try {
    await audioTransceiver.sender.replaceTrack(audioStream.getAudioTracks()[0]);
    audioTransceiver.receiver.track.enabled = false;
    audioTransceiver.direction = "sendonly";
  } catch (err) {
    /* handle the error */
  }
}

try 块中的三行代码执行以下步骤

  1. 用包含保持音乐的 MediaStreamTrack 替换其传出音频轨道。
  2. 禁用传入音频轨道。
  3. 将音频收发器切换到仅发送模式。

这会通过发送 negotiationneeded 事件来触发 RTCPeerConnection 的重新协商,您的代码会通过使用 RTCPeerConnection.createOffer 生成 SDP 提议并将其通过信令服务器发送到远程对等方来响应。

audioStream(包含要播放的音频,而不是本地对等方的麦克风音频)可以来自任何地方。一种可能性是有一个隐藏的 <audio> 元素,并使用 HTMLAudioElement.captureStream() 来获取其音频流。

远程对等方

在远程对等方,当我们收到方向设置为 "sendonly" 的 SDP 提议时,我们使用 holdRequested() 方法来处理它,该方法接受 SDP 提议字符串作为输入。

js
async function holdRequested(offer) {
  try {
    await peerConnection.setRemoteDescription(offer);
    await audioTransceiver.sender.replaceTrack(null);
    audioTransceiver.direction = "recvonly";
    await sendAnswer();
  } catch (err) {
    /* handle the error */
  }
}

这里执行的步骤是

  1. 通过调用 RTCPeerConnection.setRemoteDescription() 将远程描述设置为指定的 offer
  2. 将音频收发器的 RTCRtpSender 的轨道替换为 null,表示没有轨道。这会停止在收发器上发送音频。
  3. 将音频收发器的 direction 属性设置为 "recvonly",指示收发器仅接收音频而不发送任何音频。
  4. SDP 答案通过调用 sendAnswer() 方法生成并发送,该方法使用 createAnswer() 生成答案,然后通过信令服务将生成的 SDP 发送到另一个对等方。

停用保持模式

本地对等方

当本地用户点击界面控件禁用保持模式时,将调用 disableHold() 方法来开始恢复正常功能的流程。

js
async function disableHold(micStream) {
  await audioTransceiver.sender.replaceTrack(micStream.getAudioTracks()[0]);
  audioTransceiver.receiver.track.enabled = true;
  audioTransceiver.direction = "sendrecv";
}

这会按如下方式逆转 enableHold() 中执行的步骤

  1. 音频收发器的 RTCRtpSender 轨道被替换为指定流的第一个音频轨道。
  2. 收发器的传入音频轨道被重新启用。
  3. 音频收发器的方向设置为 "sendrecv",表示它应恢复发送和接收流式音频,而不是仅发送。

就像在启用保持时一样,这会再次触发协商,导致您的代码向远程对等方发送新的提议。

远程对等方

当远程对等方收到 "sendrecv" 提议时,它会调用其 holdEnded() 方法

js
async function holdEnded(offer, micStream) {
  try {
    await peerConnection.setRemoteDescription(offer);
    await audioTransceiver.sender.replaceTrack(micStream.getAudioTracks()[0]);
    audioTransceiver.direction = "sendrecv";
    await sendAnswer();
  } catch (err) {
    /* handle the error */
  }
}

此处 try 块内执行的步骤是

  1. 通过调用 setRemoteDescription() 将收到的提议存储为远程描述。
  2. 音频收发器的 RTCRtpSenderreplaceTrack() 方法用于将传出音频轨道设置为麦克风音频流的第一个轨道。
  3. 收发器的方向设置为 "sendrecv",表示它应恢复发送和接收音频。

从现在开始,麦克风重新激活,远程用户可以再次听到本地用户,并与他们交谈。

另见