实时传输协议 (RTP) 简介

**实时传输协议**(**RTP**),在 RFC 3550 中定义,是一种 IETF 标准协议,用于启用实时连接以交换需要实时优先级的数据。本文概述了 RTP 的功能以及它在 WebRTC 上下文中的工作原理。

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

将延迟降至最低对于 WebRTC 尤其重要,因为面对面通信需要以尽可能少的 延迟 执行。一个用户说某些话与另一个用户听到它之间的时间延迟越大,发生交叉对话和其他形式的混淆的可能性就越大。

RTP 的主要功能

在检查 RTP 在 WebRTC 上下文中的使用之前,了解 RTP 的功能和局限性很有用。RTP 是一种数据传输协议,其任务是在当前条件下尽可能高效地在两个端点之间移动数据。这些条件可能会受到网络协议栈底层、物理网络连接、中间网络、远程端点的性能、噪音水平、流量水平等等的影响。

由于 RTP 是一种数据传输,因此它由密切相关的 **RTP 控制协议**(**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 行(表示双向 SRTP 流)。这些由 RTCPeerConnection.getTransceivers() 方法返回,并且每个 mid 和 transceiver 之间存在一对一关系,其中 mid 对于每个 RTCPeerConnection 都是唯一的。

利用 RTP 实现“保持”功能

因为 RTCPeerConnection 的流是使用 RTP 和 上面 的接口实现的,所以您可以利用由此提供的对流内部的访问权限进行调整。您可以执行的最简单的事情之一是实现“保持”功能,其中通话参与者可以点击一个按钮并关闭其麦克风,开始向另一方发送音乐,并停止接受传入的音频。

注意:此示例使用了现代 JavaScript 功能,包括 异步函数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. 将音频转发器切换到仅发送模式。

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

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

远程对等方

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

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. null替换音频转发器的RTCRtpSender的轨道,表示没有轨道。这停止了在转发器上发送音频。
  3. 将音频转发器的direction属性设置为"recvonly",指示转发器只接收音频而不发送任何音频。
  4. SDP answer是使用名为sendAnswer()的方法生成并发送的,该方法使用createAnswer()生成answer,然后通过信令服务将生成的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",表示它应该返回到同时发送和接收流式音频,而不是仅发送。

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

远程对等方

当远程对等方收到"sendrecv" offer时,它会调用其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()将接收到的offer存储为远程描述。
  2. 音频转发器的RTCRtpSenderreplaceTrack()方法用于将输出音频轨道设置为麦克风音频流的第一个轨道。
  3. 将转发器的方向设置为"sendrecv",表示它应该恢复发送和接收音频。

从这一点开始,麦克风重新启用,远程用户能够再次听到本地用户,并与他们交谈。

另请参阅