WebRTC 的核心:RTCPeerConnection

WebRTC 的核心:RTCPeerConnection

  • [WebRTC 的核心:RTCPeerConnection](#WebRTC 的核心:RTCPeerConnection)
    • [创建 RTCPeerConnection 对象](#创建 RTCPeerConnection 对象)
    • [RTCPeerConnection 与本地音视频数据绑定](#RTCPeerConnection 与本地音视频数据绑定)
    • 媒体协商
    • ICE
      • [什么是 Candidate?](#什么是 Candidate?)
      • [收集 Candidate](#收集 Candidate)
      • [交换 Candidate](#交换 Candidate)
      • 尝试连接
    • [SDP 与 Candidate 消息的互换](#SDP 与 Candidate 消息的互换)
    • 远端音视频渲染
    • 参考

WebRTC 的核心:RTCPeerConnection

RTCPeerConnection 是 WebRTC API 中的一个对象,它提供了两个浏览器之间的实时通信。它使用音频、视频和数据流来进行点对点通讯,没有中介服务器的参与。这是一个极其有用的特性,使得实时通信变得更加高效和私密。

RTCPeerConnection的作用是在两个浏览器之间建立WebRTC通信通道。它使用ICE(Interactive Connectivity Establishment)协议决定浏览器之间的最佳路径来传输音频、视频和数据流。它让浏览器之间的网络连接变得简单、快捷和高效。

RTCPeerConnection API可以用于:

  1. 视频会议
  2. 实时游戏
  3. 文件共享
  4. 流媒体
  5. 实时语音聊天

创建 RTCPeerConnection 对象

语法:

javascript 复制代码
new RTCPeerConnection()
new RTCPeerConnection(configuration)

参数 configuration 是一个 JSON 对象,用于提供配置新连接的选项。

  1. iceServers:一个由 RTCIceServer 对象组成的数组,每个对象描述一个可能被 ICE 代理使用的服务器。这些通常是 STUN 或 TURN 服务器。如果未指定,则将在没有可用的 STUN 或 TURN 服务器的情况下进行连接尝试,这将连接限制为本地对等点。
  2. iceTransportPolicy:当前的 ICE 传输策略。如果未指定策略,则默认使用 all 策略,允许考虑所有的候选。可能的值有:
    • "all":所有的 ICE 候选都会被考虑。
    • "public":只有拥有公共 IP 地址的 ICE 候选才会被考虑,已被移除。
    • "relay":只有 IP 地址被中继的 ICE 候选,例如那些通过 STUN 或 TURN 服务器传递的,才会被考虑。
  3. bundlePolicy:指定当远程对等点与 SDP BUNDLE 标准不兼容时,应如何处理候选的协商。如果远程端点可以感知 BUNDLE,则在协商完成时,所有媒体轨道和数据通道都将捆绑到单个传输上,而不管使用何种策略,并且最初创建的任何多余传输都将在此时关闭。用技术术语来说,BUNDLE 允许两个对等点之间的所有媒体流流经单个 5 元组;也就是说,使用相同的传输协议从一个对等点的单个 IP 和端口到另一个对等点的单个 IP 和端口。如果假定为不平衡(balanced),这必须是以下字符串值之一:
    • "balanced":ICE 代理最初为每一种内容类型(音频、视频、数据通道)创建一个 RTCDtlsTransport。如果远程端点无法感知 BUNDLE,那么每一个 DTLS 传输用于处理一种数据类型的通信。
    • "max-compat":ICE 代理最初为每个媒体轨道创建一个 RTCDtlsTransport,对数据通道则创建一个单独的传输。如果远程端点无法感知 BUNDLE,那么对于所有的内容都会协商一个单独的 DTLS 传输。
    • "max-bundle":ICE 代理最初仅创建一个 RTCDtlsTransport 来承载所有的 RTCPeerConnection 的数据。如果远程端点无法感知 BUNDLE,那么仅会协商一个轨道而忽略其余的轨道。
  4. rtcpMuxPolicy:收集 ICE 候选时使用的 RTCP mux 策略,以支持非复用的 RTCP。可能的值有:
    • "require":告诉 ICE 代理仅收集 RTP 的 ICE 候选,并在它们之上多路复用 RTCP。如果远程对等点不支持 RTCP 多路复用,则会话协商失败。这是默认值。
    • "negotiate":指示 ICE 代理收集 RTP 和 RTCP 候选。如果远程对等点支持 RTCP 复用,那么 RTCP 候选将在相应的 RTP 候选之上多路复用。否则,分别返回 RTP 和 RTCP 候选。
  5. peerIdentity:一个字符串,用于指定 RTCPeerConnection 目标对等点的标识。如果设置了该值(其默认为 null),则在成功验证远程对等点的身份为给定的名称之前,RTCPeerConnection 不会与其建立连接。
  6. certificates:一个由 RTCCertificate 对象组成的数组,用于连接的身份验证。如果未指定该属性,则会为每一个 RTCPeerConnection 实例自动创建一组证书。尽管一个给定的连接只使用一个证书,但提供多个证书可以支持多种算法,从而提高某些情况下的连接成功的机率。此配置选项在首次指定后便不能更改;一旦设置了证书,此属性将在之后调用 RTCPeerConnection.setConfiguration() 时被忽略。
  7. iceCandidatePoolSize:一个无符号 16 位整数,其指定了预获取的 ICE 候选池的大小。其默认值为 0(表示不会发生候选的预获取)。如果这个值发生变化了,那么会重新收集候选者。

RTCPeerConnection 与本地音视频数据绑定

RTCPeerConnection 提供2种方法进行数据绑定:addTrack() 和 addStream()。

其中 addStream() 已经被 WebRTC 标记为过时,因此建议使用 addTrack() 方法。

当客户端从服务端接收到 joined 消息后,它会创建 RTCPeerConnection 对象,然后调用 bindTrack() 函数将其与之前通过 getUserMedia() 采集到的音视频数据绑定:

javascript 复制代码
function bindTrack() {
	...
	ls.getTracks().foreach((track) => {
		pc.addTrack(track, ls);
		...
	})
	...
}

其中,ls 是一个全局变量,当通过 getUserMedia() 采集到 MediaStream 后,需要将其交由 ls 管理;pc 是 RTCPeerConnection 的缩写,也是一个全局变量,当 RTCPeerConnection 创建好后,交由 pc 管理。

媒体协商

当 RTCPeerConnection 对象与音视频数据绑定后,紧接着需要进行媒体协商。看双方都支持那些编码方式,支持哪些分辨率等。协商的方法是通过信令服务器交换媒体能力信息,交换的内容是 SDP 格式的。

在 WebRTC 中,媒体协商是有严格的协商顺序的,如下图所示:

整个过程分为 8 步:

  1. Amy 调用 createOffer 方法创建 offer 消息。offer 消息中的内容是 Amy 的 SDP 信息。
  2. Amy 调用 setLocalDescription 方法,将本端的 SDP 信息保存起来。
  3. Amy 将 offer 消息通过信令服务器传给 Bob。
  4. Bob 收到 offer 消息后,调用 setRemoteDescription 方法将其存储起来。
  5. Bob 调用 createAnswer 方法创建 answer 消息, 同样,answer 消息中的内容是 Bob 的 SDP 信息。
  6. Bob 调用 setLocalDescription 方法,将本端的 SDP 信息保存起来。
  7. Bob 将 anwser 消息通过信令服务器传给 Amy。
  8. Amy 收到 anwser 消息后,调用 setRemoteDescription 方法,将其保存起来。

通过以上步骤就完成了通信双方媒体能力的交换。

ICE

当媒体协商完成后,WebRTC 就开始建立网络连接了,其过程叫 ICE(Interactive Connectivity Establishment,交互式连接建立)。更确切地说,ICE 是在各端调用 setRemoteDescription 方法后就开始了。其操作过程如下:

  1. 收集 Candidate
  2. 交换 Candidate
  3. 按优先级尝试连接

什么是 Candidate?

Candidate 是 WebRTC API 的接口之一,表示用于建立 RTCPeerConnection 的候选交互连接建立(ICE)的配置信息。它是至少包含 {address, port, protocol} 三元组的一个信息集。

RTCIceCandidate 实例属性:

  1. candidate:表示用于连接检查的候选者的传输地址的字符串。
    格式为:a=candidate:{foundation} {component} {protocol} {priority} {ip} {port} typ {type} generation {generation} ufrag {username} network_id {id} network_cost {cost}。
    以如下 candidate 为例说明其代表的含义:a=candidate:1221703924 1 udp 2122260223 192.168.0.105 51417 typ host generation 0 ufrag Q8Wv network-id 1 network-cost 10。
    typ host 表示本地候选者,使用的 IP 是 192.168.0.105,端口为 51417,使用 UDP 协议,其优先级为 2122260223,generation 表示代数,初始值为 0,用户名为 Q8Wv,如果更新 candidate 则 generation 值会递增,替换老的 candidate。
  2. sdpMid:表示候选者的媒体流标识标签的字符串,该标签在候选者关联的组件中唯一标识媒体流,如果不存在这样的关联,则为 null。
  3. sdpMLineIndex:如果值不为 null,sdpMLineIndex 表示 SDP 中候选者关联的从零开始的媒体描述索引编号。

其他更多属性见于:RTCIceCandidate

WebRTC 还将 Candidate 分为了四种类型:host、srflx、prflx、relay,它们的优先级依次递减。假如 WebRTC 收集到两个 Candidate,一个是 host 类型,另一个是 relay 类型,那么 WebRTC 会先尝试与 host 类型的 Candidate 建立连接,如果不成功,才会使用 relay 类型的 Candidate。

收集 Candidate

WebRTC 收集 Candidate 时有几种途径:

  1. host 类型的 Candidate 由主机的网卡个数决定。一般一个网卡对于一个 IP 地址,每个 IP 地址随机分配一个端口从而生成一个 host 类型的 Candidate;
  2. srflx 类型的 Candidate 由 STUN 服务器获得的 IP 地址和端口生成;
  3. relay 类型的 Candidate 由 TRUN 服务器获得的 IP 地址和端口生成。

收集到 Candidate 后,为了通知上层,WebRTC 还在 RTCPeerConnection 对象中提供了 onicecandidate 事件。为了将收集到的 Candidate 交换给对端,需要为 onicecandidate 事件设置一个回调函数:

javascript 复制代码
pc.onicecandidate = (e) => {
	if (e.candidate) {
		...
	}
}

通过该回调函数就可以获得 WebRTC 底层收集到的所有 Candidate 了。同时,还可以在函数内实现发送给对端的操作。

交换 Candidate

WebRTC 收集 Candidate 后,会通过信令系统将它们发送给对端。对端接收后,会与本地的 Candidate 形成 CandidatePair(连接候选者对)。

有了 CandidatePair,WebRTC 就可以开始尝试建立连接了。

注意,Candidate 的交换不是等所有 Candidate 收集好了再进行的,而是边收集边交换。

尝试连接

当 WebRTC 形成 CandidatePair 后,便开始尝试进行连接。一旦发现一个可以连通的 CandidatePair 时,就不再进行其他的连接尝试了,但发现新的 Candidate 时仍然可以继续交换。

SDP 与 Candidate 消息的互换

媒体协商和 ICE 都需要通信双方做信息的交换,如交换 SDP 和 Candidate。这种信息交换使用的也是信令系统,只不过需要为这种需求专门设置一个新的信令,即 message。

信息交换的过程:

  1. 发起方向信令服务器发送 message;
  2. 服务器收到 message 后不做任何处理,直接转发给目标用户;
  3. 目标用户接收 message。

客户端发送消息:

javascript 复制代码
function sendMsg(roomid, data) {
	...
	socket.emit('message', roomid, data);
}

服务器转发:

javascript 复制代码
socket.on('message', (room, data) => {
	socket.to(room).emit('message', room, data);
});

客户端接收:

javascript 复制代码
socket.on('message', (room, data) => {
	...
	if (data.hasOwnProperty('type') && data.type === 'offer') {
		...
	} else if (data.hasOwnProperty('type') && data.type === 'answer') {
		...
	} else if (data.hasOwnProperty('type') && data.type === 'candidate') {
		...
	} else {
		...
	}
});

远端音视频渲染

当各端将收集到的 Candidate 通过信令系统交换给对方后,WebRTC 内部就开始就开始尝试建立连接了。连接一旦建成,音视频数据就开始源源不断地由发送端发送给接收端。

每当有远端的音视频数据传过来时,RTCPeerConnection 对象的 ontrack() 事件就会被触发。因此只需要给 ontrack() 事件设置一个回调函数,就可以拿到远端的 MediaStream 了。再将远端音视频流赋值给本地 <video> 标签的 srcObject 属性,就可以播放音视频数据了。

具体代码如下所示:

javascript 复制代码
function getRemoteStream(mediaStream) {
	...
	// 将远端音视频流赋值给本地 <video> 标签的 srcObject 属性
	videoElement.srcObject = mediaStream;
}
let pc = new RTCPeerConnection(...);
...
pc.ontrack = getRemoteStream;
...

参考

  1. https://developer.mozilla.org/zh-CN/docs/Web/API/RTCPeerConnection
  2. https://www.cnblogs.com/ssyfj/p/14811253.html
  3. https://webrtc.org.cn/webrtc-tutorial-2-signaling-stun-turn/
相关推荐
换个昵称都难17 小时前
webrtc源码解析概要介绍
webrtc
换个昵称都难17 小时前
WebRTC 完整调用流程(前端纯 JS 实现,最简可运行)
webrtc
换个昵称都难2 天前
webrtc 拥塞控制GCC 和PCC
webrtc
Cxiaomu2 天前
React接入WebRTC实时视频实践
react.js·音视频·webrtc
AndyHuang19762 天前
WebRTC 强制 Relay 模式下 TCP 重连失败深度排查与优化实战
webrtc
换个昵称都难2 天前
webrtc pacing 平滑发包模块
webrtc
换个昵称都难2 天前
webrtc 音频混音介绍
音视频·webrtc
换个昵称都难2 天前
webrtc QOS-RemoteBitrateEstimator接收端带宽估计(1)
webrtc
换个昵称都难2 天前
webrtc QOS-RemoteBitrateEstimator接收端带宽估计-四个实例(2)
webrtc
都在酒里3 天前
【极致低延时】香橙派部署 MediaMTX 实现 WebRTC 推流,延时仅 500-800ms,比局域网 ffmpeg 拉流快近 10 倍!(附踩坑全记录)
linux·arm开发·ffmpeg·webrtc·orangepi·嵌入式软件