实时传输协议(RTP)介绍
实时传输协议(RTP,Real-time Transport Protocol),定义于 RFC 3550,是一个 IETF 标准协议,是为需要实时优先级的数据交换提供实时连接。本文概述了什么是 RTP 以及它在 WebRTC 上下文中的工作原理。
备注:WebRTC 实际上使用的是 SRTP(安全实时传输协议,Secure Real-time Transport Protocol)来确保交换数据的安全性和身份验证。
对于 WebRTC 来说,将延迟降至最低尤为重要,因为面对面的通信需要尽可能低的延迟。从一个用户说话到另一个用户听到之间的时间延迟越大,就越容易出现串音和其他形式的混乱。
RTP 的主要特性
在探讨 RTP 在 WebRTC 上下文中的使用之前,有必要先了解 RTP 能做什么和不能做什么。RTP 是一种数据传输协议,其任务是在当前条件下尽可能高效地在两个端点之间传输数据。这些条件可能受到从网络栈底层到物理网络连接、中间网络、远程端点的性能、噪声水平、流量水平等各种因素的影响。
由于 RTP 是一种数据传输协议,因此它由紧密相关的 RTP 控制协议(RTCP,RTP Control Protocol)增强,后者定义于 RFC 3550, section 6。RTCP 增加了包括服务质量(QoS,Quality of Service)监控、参与者信息共享等功能。它不足以完全管理用户、成员资格、权限等,但为不受限制的多用户通信会话提供了所需的基础功能。
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 接收器,它们共享一个 SDPmid属性,这意味着它们共享相同的 SDP 媒体 m 行(代表双向 SRTP 流)。这些由RTCPeerConnection.getTransceivers()方法返回,每个mid和收发器一一对应,mid对于每个RTCPeerConnection是唯一的。
利用 RTP 实现“保持”功能
由于 RTCPeerConnection 的流是使用 RTP 和上述接口实现的,因此你可以利用这种访问权限来调整流的内部机制。其中最简单的做法之一是实现“保持”功能,即通话中的参与者可以点击按钮关闭麦克风,开始向另一个对等端播放音乐,并停止接收传入的音频。
在下面的示例中,我们将开启和关闭"保持"模式的对等端称为本地对等端,而处于保持状态的用户称为远程对等端。
激活保持模式
本地对等端
当本地用户决定启用保持模式时,会调用下面的 enableHold() 方法。它接受一个 MediaStream 作为输入,其中包含在通话保持期间播放的音频。
async function enableHold(audioStream) {
try {
await audioTransceiver.sender.replaceTrack(audioStream.getAudioTracks()[0]);
audioTransceiver.receiver.track.enabled = false;
audioTransceiver.direction = "sendonly";
} catch (err) {
/* 处理错误 */
}
}
try 块中的三行代码执行以下步骤:
- 将其输出音频轨道替换为包含等待音乐的
MediaStreamTrack。 - 禁用传入的音频轨道。
- 将音频收发器切换为仅发送模式。
这会触发 RTCPeerConnection 的重新协商,通过向其发送 negotiationneeded 事件,你的代码会据此使用 RTCPeerConnection.createOffer 生成 SDP 提议,并通过信令服务器将其发送给远程对等端。
audioStream 包含用于替代本地对等端麦克风音频的音频,可以来自任何地方。一种可能的方法是使用隐藏的 <audio> 元素,并使用 HTMLAudioElement.captureStream() 获取其音频流。
远程对等端
在远程对等端,当我们收到方向设置为 "sendonly" 的 SDP 提议时,我们使用 holdRequested() 方法处理它,该方法接受一个 SDP 提议字符串作为输入。
async function holdRequested(offer) {
try {
await peerConnection.setRemoteDescription(offer);
await audioTransceiver.sender.replaceTrack(null);
audioTransceiver.direction = "recvonly";
await sendAnswer();
} catch (err) {
/* 处理错误 */
}
}
这里执行的步骤是:
- 通过调用
RTCPeerConnection.setRemoteDescription()将远程描述设置为指定的offer。 - 将音频收发器的
RTCRtpSender的轨道替换为null,表示没有轨道。这会停止在收发器上发送音频。 - 将音频收发器的
direction属性设置为"recvonly",指示收发器仅接收音频而不发送任何音频。 - 使用名为
sendAnswer()的方法生成并发送 SDP 应答,该方法使用createAnswer()生成应答,然后将生成的 SDP 通过信令服务发送给另一个对等端。
停用保持模式
本地对等端
当本地用户点击界面控件以禁用保持模式时,会调用 disableHold() 方法来开始恢复常规功能。
async function disableHold(micStream) {
await audioTransceiver.sender.replaceTrack(micStream.getAudioTracks()[0]);
audioTransceiver.receiver.track.enabled = true;
audioTransceiver.direction = "sendrecv";
}
这逆转了 enableHold() 中的步骤,具体如下:
- 音频收发器的
RTCRtpSender的轨道被替换为指定流的第一个音频轨道。 - 收发器的传入音频轨道被重新启用。
- 音频收发器的方向设置为
"sendrecv",表示它应恢复发送和接收音频流,而不是仅发送。
就像启用保持模式时一样,这会再次触发协商,导致你的代码向远程对等端发送新的提议。
远程对等端
当远程对等端接收到 "sendrecv" 提议时,它会调用其 holdEnded() 方法:
async function holdEnded(offer, micStream) {
try {
await peerConnection.setRemoteDescription(offer);
await audioTransceiver.sender.replaceTrack(micStream.getAudioTracks()[0]);
audioTransceiver.direction = "sendrecv";
await sendAnswer();
} catch (err) {
/* 处理错误 */
}
}
这里 try 块中执行的步骤是:
- 通过调用
setRemoteDescription()将接收到的提议存储为远程描述。 - 使用音频收发器的
RTCRtpSender的replaceTrack()方法将传出音频轨道设置为麦克风音频流的第一个轨道。 - 收发器的方向设置为
"sendrecv",表示其应恢复发送和接收音频。
从此时起,麦克风重新启用,远程用户再次能够听到本地用户的声音,也可以与本地用户通话。