WebRTC通信原理
什么是WebRTC
WebRTC(Web Real-Time Communication)是一个由 Google、Mozilla、Opera 等公司发起的开源项目,它使得网页浏览器能够直接进行实时的音视频对话,而无需借助外部插件或中间服务器。WebRTC 支持浏览器之间建立点对点(Peer-to-Peer)的连接,从而实现音频流、视频流和任意数据的高效传输。
对于开发者来说,WebRTC 提供了一整套基于 W3C 的
JavaScript API,包括:音视频采集、编解码、网络传输和显示等功能,允许开发者无需额外插件即可构建音视频应用。WebRTC 的标准在较高层次上涉及两项核心技术:媒体捕获和点对点连接,极大地简化了实时通讯的实现过程。通过 WebRTC,开发者能够快速创建高效、安全的实时音视频通讯应用,为用户提供流畅的在线互动体验。
媒体捕获
媒体捕获是 WebRTC 中的一个核心功能,指的是从本地设备(如麦克风、摄像头、屏幕等)获取音频、视频或屏幕共享流。它是 WebRTC 实现实时通信的基础,允许浏览器从用户设备中采集音视频数据,以便进行后续的编码、传输和显示。
媒体捕获设备指的是用于获取音视频数据的硬件设备,主要包括:摄像头、麦克风、屏幕捕获设备。通过浏览器内置的
navigator对象及其提供的相关 API,我们可以轻松地从用户设备中获取音频流、视频流或屏幕内容。
navigator.mediaDevices.getUserMedia():此方法用于获取摄像头和麦克风的媒体流。它可以用于获取音频和视频流,常用于视频通话、音频通话等场景。navigator.mediaDevices.getDisplayMedia():此方法用于获取屏幕的共享流,常用于屏幕共享、在线会议等应用场景。API的具体使用可以参考 MDN。
从技术手册可以知道,
getUserMedia()方法需要传入一个constraints约束对象作为参数。该参数用来指定请求的媒体类型和相对应的参数,而且必须指定至少一个类型。媒体类型具体可以配置哪些属性,可以使用navigator.mediaDevices.getSupportedConstraints()来获取。
获取媒体流
// 指定约束对象,获取音频流和视频流,并设置视频流的分辨率为 1280 x 720 const constraints = { audio: true, video: { width: 1280, height: 720, }, }; // 获取媒体流 const stream = navigator.mediaDevices.getUserMedia(constraints); console.log(stream);运行上面的代码,浏览器会询问是否打开摄像头和麦克风,设置为允许就能获取媒体流了。
打开控制台,发现
getUserMedia()方法返回的是一个Promise。
渲染媒体流
获取到的媒体流可以绑定到
<video>标签的srcObject属性上进行渲染。通过这种方式,视频流将显示在页面上。在使用时,需要注意以下两点:
getUserMedia()方法返回的是一个Promise,应该通过then方法或async、await来处理它,确保获取到媒体流数据。<video>标签需要设置autoplay属性,确保视频能够自动播放。
获取共享流
上面使用了
getUserMedia()这个API来获取摄像头和麦克风的媒体流。接下来使用getDisplayMedia()这个API来捕获屏幕的媒体流数据,该方法的使用和getUserMedia()一致。
点对点连接
通过
getUserMedia()和getDisplayMedia()这两个API获取到的媒体流,包括音频流和视频流,是 WebRTC 建立点对点通信的基础。在这个阶段,我们已经具备了通信所需的音视频数据,接下来需要将这些数据传输到远端浏览器。WebRTC要建立点对点的连接,首先有两个关键的问题要解决:
媒体协商媒体协商的目的是确保通信双方能够理解并接收对方的音视频流。这包括音频的编码方式、采样率、视频的分辨率、帧率等参数。由于不同设备、浏览器和网络条件,媒体的编解码方式可能有所不同,所以需要通过一种标准化的协议进行协商。
WebRTC 使用
Session Description Protocol(SDP)来进行媒体协商。SDP 通过信令(如 WebSocket 或其他信令通道)在通信双方之间交换。通过这一步,双方能够了解对方支持的编解码器和参数,并进行匹配。例如,视频流可以采用 VP8 编解码器,音频流可以采用 Opus 编解码器,确保双方能够处理彼此的媒体流。网络协商网络协商的目标是确保两台设备能够找到一条可靠且稳定的通信路径。由于大多数设备都处在局域网中,无法直接通过公网 IP 地址进行通信,所以 WebRTC 使用了
STUN和TURN协议来帮助设备穿越 NAT(网络地址转换)并建立连接。
STUN(Session Traversal Utilities for NAT):STUN 协议帮助设备发现其公网 IP 地址和端口号。在 WebRTC 连接的初始阶段,STUN 服务器的作用是确定通信双方的公网地址。STUN 协议是轻量级的,它通过向公共 STUN 服务器发送请求来获取对方的公网 IP 地址。
TURN(Traversal Using Relays around NAT):如果双方无法通过 STUN 进行直接连接,WebRTC 会通过 TURN 服务器进行中继。TURN 协议用于在 NAT 穿透失败时提供一个中继路径。TURN 的引入可以解决大部分情况下的 NAT 穿透问题,但是 TURN 的延迟和带宽消耗相对较高,因此通常仅在需要时才会使用。也就是说,要确保WebRTC的成功通信,需要通过媒体协商来确定双方都可用的编解码方式,还需要通过网络协商确定两端可建立正确通信链路的ip地址。
实际上,STUN 和 TURN 服务器信息通常通过 iceServers 选项传入 RTCPeerConnection 的配置中。
const pc = new RTCPeerConnection({
  iceServers: [{ urls: 'stun:stun.l.google.com:19302' }],
});流程解析
下图展示了 WebRTC 建立通信的完整流程,下面将对每个环节进行详细说明。
RTCPeerConnection是WebRTC(Web实时通信)API的一部分,它用于在浏览器之间建立点对点(peer-to-peer)连接,以支持实时通信,如音频、视频和数据交换。RTCPeerConnection提供了创建和管理这些连接所需的底层机制。
根据上面的图解,可以知道WebRTC建立通信的流程大致有以下过程:
- 
连接信令服务器
- 客户端 A 与客户端 B 通过 WebSocket(或其他信令机制)与信令服务器建立连接,用于交换必要的会话控制消息(SDP、ICE Candidate 等)。
 
 - 
创建连接对象与添加媒体流
- 
双方各自创建
RTCPeerConnection对象。 - 
使用
addTrack方法将本地媒体流(音频/视频)添加到该对象中。 
 - 
 - 
客户端 A 创建并发送 Offer
- 
调用
createOffer()生成本地 SDP 描述。 - 
使用
setLocalDescription()设置本地描述。 - 
将 SDP Offer 通过信令服务器发送给客户端 B。
 
 - 
 - 
客户端 B 接收并处理 Offer
- 
使用
setRemoteDescription()将收到的 SDP Offer 设置为远端描述。 - 
调用
createAnswer()生成本地 SDP 描述。 - 
使用
setLocalDescription()设置本地描述。 
 - 
 - 
客户端 B 发送 Answer
- 将生成的 SDP Answer 通过信令服务器发送给客户端 A。
 
 - 
客户端 A 接收并处理 Answer
- 使用 
setRemoteDescription()将收到的 SDP Answer 设置为远端描述。 
 - 使用 
 
过程演示
按照以上流程,我们可以封装一个
WebRtcClient类,用于管理 WebRTC 连接。该类封装了 WebRTC 连接的核心逻辑,包括绑定本地媒体流、创建与处理 Offer/Answer 的方法,以简化点对点通信的实现。💡注意:在获取本地流时,我获取的是屏幕捕获的流,这样方便看到效果。
代码实现
class WebRtcClient { // 初始化 RTCPeerConnection 对象 constructor() { this.pc = new RTCPeerConnection() this.offer = '' this.answer = '' } // 获取本地媒体流,并绑定到本地 video标签 async init() { const localVideoBox = document.querySelector('.local') const remoteVideoBox = document.querySelector('.remote') // 设置约束 const constraints = { audio: true, video: { width: { ideal: 1280 }, height: { ideal: 720 }, }, } // 获取视频流 (切换选择使用摄像头还是屏幕) // const stream = await navigator.mediaDevices.getUserMedia(constraints); const stream = await navigator.mediaDevices.getDisplayMedia(constraints) localVideoBox.srcObject = stream // 将本地媒体流轨道添加到 RTCPeerConnection对象 中 stream.getTracks().forEach((track) => { this.pc.addTrack(track, stream) }) // 监听远程流变化 this.pc.ontrack = (e) => { console.log('远端流变化了', e) remoteVideoBox.srcObject = e.streams[0] } } // 创建offer async createOffer() { // 存在新的候选,需要更新SDP信息 this.pc.onicecandidate = async (e) => { if (e.candidate) { this.offer = this.pc.localDescription } } // 创建offer, 并设置为本地SDP描述 const offer = await this.pc.createOffer() await this.pc.setLocalDescription(offer) } // 创建answer async createAnswer(offer) { // 存在新的候选,需要更新SDP信息 this.pc.onicecandidate = async (e) => { if (e.candidate) { this.answer = this.pc.localDescription } } // 收到对端发送的offerSDP,设置为远端SDP await this.pc.setRemoteDescription(offer) // 创建answer,设置为本地SDP const answer = await this.pc.createAnswer() await this.pc.setLocalDescription(answer) } // 添加answerSDP async addAnswer(answer) { await this.pc.setRemoteDescription(answer) } } const client = new WebRtcClient() client.init()
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> .local, .remote { width: 533px; height: 300px; } </style> </head> <body> <video class="local" autoplay></video> <video class="remote" autoplay></video> <script src="./webrtcClient.js"></script> </body> </html>
演示步骤
- 打开两个浏览器窗口,分别共享不同的画面
 
- 左边浏览器中调用client对象的createOffer方法
 
复制左边client对象的offerSDP,调用右边client对象的createAnswer方法,传入offerSDP
⚠️ 注意:由于这里没有设计信令服务器对 SDP 进行转发,所以需要手动复制,直接右键复制offer即可
然后,调用右边client对象的createAnswer方法,传入offerSDP
复制右边client对象的answerSDP,调用左边client对象的addAnswer方法,并传入answerSDP
💡注意:直接输出右边client对象,就可以看到answerSDP
然后,调用左边client对象的addAnswer方法
- 两个浏览器页面就可以进行视频通话了
 
上面的SDP交换过程,是手动进行的,主要是为了了解整个通信流程。后面可以创建一个信令服务器,通过WebSocket实现SDP的自动交换。
 












