WebRTC介绍
1. 什么是 WebRTC?
1.1 WebRTC 介绍
WebRTC(Web 实时通信)是一个可以用在视频聊天、音频聊天或 P2P 文件分享等 Web 应用中的 API。------摘自:MDN - WebRTC
MDN 对 WebRTC 的定义我们可以拆分为以下三点:
- WebRTC 是网页即时通信(Web Real-Time Communication)的缩写
- 它提供了支持网页浏览器进行实时语音和视频对话的 API
- 允许浏览器之间直接建立连接,实现点对点的通信
1.2 WebRTC vs WebSocket
乍一看,我们所熟悉的 WebSocket 好像和 WebRTC 是同一种技术,然而它们之间的差别就像短信和视频通话------虽然都是通信工具,但一个让你"见字如面",另一个则是"身临其境"。
WebSocket 的主要特点
- 基于 TCP 连接:WebSocket 是一种基于单个 TCP 连接的全双工通信协议
- 服务器主动推送:它允许服务器主动向客户端推送数据
- 持久连接:在 WebSocket API 中,浏览器和服务器只需完成一次握手,之后就能建立持久性的连接,并进行双向的数据传输
关键区别
| 方面 | WebSocket | WebRTC |
|---|---|---|
| 通信类型 | 文本和二进制数据的实时交换,非常适合聊天应用、实时游戏等需要低延迟的数据传输场景 | 专注于媒体流的实时传输,如视频和音频通话,具有更复杂的媒体处理能力 |
| 建立连接 | 连接是建立在 TCP 上的,全双工的、持久的连接 | 点对点的,利用 NAT 穿透技术(NAT 指的是网络地址转换,Network Address Translation)直接在浏览器之间建立连接,支持多种实时通信场景 |
| 功能支持 | 不直接支持媒体流的传输和编解码功能 | 内置了对视频、音频流的编解码和传输功能,提供更高层次的实时通信能力 |
| 适用场景 | 实时聊天、实时游戏、实时数据推送 | 视频通话、音频通话、屏幕分享、文件传输 |
1.3 WebRTC 的浏览器兼容性
https://caniuse.com/?search=WebRTC

兼容性注意事项
- 移动端支持:大多数移动浏览器也支持 WebRTC。需要注意的是,在 iOS 设备上,Safari 浏览器的版本需大于 10.1。
- 旧版浏览器:一些老旧的浏览器或 WebView 组件可能不支持 WebRTC,建议在发布应用前进行全面的兼容性测试。
- 网络环境与隐私权限:不同浏览器对摄像头、麦克风等隐私权限的处理方式有所不同,用户授权和连接稳定性可能受此影响。
1.4 WebRTC 适用场景
传统应用场景
- 在线教育:如新东方云教室、智慧树、猿辅导、网易云课堂,通过 WebRTC 实现实时互动教学
- 社交媒体:增强用户互动体验,如 Soul 和陌陌的视频聊天功能等
- 视频会议:如腾讯会议、钉钉,提供高质量的实时视频会议体验
- 直播平台:利用 WebRTC 技术进行低延迟的实时直播
- 物联网(IoT):提供了低延迟的音视频通信能力,可以用于物联网设备之间的实时视频监控,如小米智能家居、小天才等
1.5 WebRTC 的优缺点
✅ 优点
| 优点 | 描述 |
|---|---|
| 实时通信 | WebRTC 支持浏览器之间的实时音频、视频和数据通信,无需任何插件或第三方软件 |
| 高质量的音视频通信 | WebRTC 使用最先进的音频和视频编解码器,如 Opus 和 VP8/VP9/H.264,可以提供高质量的通信体验 |
| 端到端加密 | WebRTC 的所有通信都是端到端加密的,保护了用户的隐私和数据安全 |
| P2P 连接 | WebRTC 支持直接的 P2P 连接,可以减少延迟和带宽消耗,提高通信效率 |
| 跨平台和跨浏览器 | WebRTC 是一个开源的标准,被大多数现代浏览器和平台支持 |
| 灵活的架构 | 可以结合 WebSocket 等协议,实现信令和媒体流的分离,提高系统的灵活性和可扩展性 |
❌ 缺点
| 缺点 | 描述 | 解决方案 |
|---|---|---|
| 复杂的信令过程 | WebRTC 本身并不包含信令协议,开发者需要自己实现信令过程,增加了开发的复杂性 | 使用 WebSocket 实现信令服务器,处理 SDP 和 ICE Candidate 交换 |
| 防火墙和 NAT 问题 | 在某些网络环境下,建立 P2P 连接可能会受到防火墙和 NAT 的限制 | 使用 STUN 服务器(如 Google 公共 STUN 服务器)进行 NAT 穿透 |
| 隐私问题 | 虽然 WebRTC 的通信是加密的,但仍有可能泄露用户的 IP 地址,可能会引发一些隐私问题 | 使用 SRTP 加密,保护媒体流传输安全 |
| 资源消耗 | 实时音视频通信需要消耗大量的 CPU 和带宽资源,可能会影响设备的性能 | 在后端进行视频解码和编码,减少前端设备的负担 |
| 后端实现复杂 | 在后端实现 WebRTC 推流需要额外的库支持(如nodejs需要 @koush/wrtc) |
使用 Node.js + @koush/wrtc+ FFmpeg 实现后端推流 |
2. Web 端基础常用相关 API
WebRTC 提供了一些 API,用于实现 Web 端的音视频通信。作为 W3C 标准,它涉及到用户的隐私设备如摄像头和麦克风。接下来,我们将简要介绍这些常用的 WebRTC API。
2.1 getUserMedia
getUserMedia 是什么?
getUserMedia 是 WebRTC API 中用于访问用户音视频设备的接口,包括摄像头和麦克风。无论是通过 USB 连接的设备还是虚拟设备,都可以通过这个 API 进行访问。
如何使用 getUserMedia?
在简单场景下,直接调用 getUserMedia 的默认参数即可获取 PC 的默认摄像头和麦克风。然而,在处理复杂场景时,例如选择特定的设备,可以按以下步骤操作:
-
列出所有可用的媒体设备:获取设备列表以便选择
-
选择所需的设备:从设备列表中选择适合的摄像头和麦克风
-
配置并传递设备信息:将所选设备的信息传递给浏览器 API 以进行设置
// 1. 列出所有可用的媒体设备
navigator.mediaDevices.enumerateDevices()
.then(devices => {
devices.forEach(device => {
console.log(device.kind, device.label, device.deviceId);
});// 2. 根据用户选择的 deviceId 请求媒体流 const constraints = { audio: { deviceId: { exact: selectedAudioDeviceId } }, video: { deviceId: { exact: selectedVideoDeviceId } } }; // 3. 请求用户媒体流 return navigator.mediaDevices.getUserMedia(constraints);})
.then(stream => {
// 将媒体流绑定到视频或音频元素上
const videoElement.srcObject = stream;
})
.catch(error => {
console.error('媒体设备访问失败:', error);
});
媒体约束 constraints
在上述代码片段中,constraints 参数用于指定音视频设备和其属性。以下是几种常见配置及其应用场景:
同时获取视频和音频输入
const constraints = { audio: true, video: true }
如果没有视频设备,调用时会报错。可以先使用 enumerateDevices 判断是否有视频输入源,再决定是否设置 video 为 false。
指定设备
const constraints = { audio: { deviceId: audioId }, video: { deviceId: videoId } }
指定分辨率
// 高分辨率
const constraints = {
audio: true,
video: {
width: { min: 320, ideal: 1280, max: 1920 },
height: { min: 240, ideal: 720, max: 1080 }
}
}
// 低分辨率
const constraints = {
audio: true,
video: { width: 720, height: 480 }
}
指定摄像头方向
// 前置
const constraints = { audio: true, video: { facingMode: "user" } }
// 后置
const constraints = { audio: true, video: { facingMode: { exact: "environment" } } }
指定帧速率 frameRate
const constraints = {
audio: true,
video: {
width: 1920,
height: 1080,
frameRate: { ideal: 10, max: 15 }
}
}
2.2 getDisplayMedia
getDisplayMedia 是什么?
getDisplayMedia API 用于在浏览器中实现屏幕分享功能。它允许用户选择并分享整个屏幕或特定应用窗口,适用于远程会议和在线演示等场景。
如何使用 getDisplayMedia?
调用 getDisplayMedia 获取屏幕分享的媒体流。此 API 返回一个 Promise,解析值为包含屏幕视频流的 MediaStream 对象。
async function getShareMedia() {
const constraints = { video: { width: 1920, height: 1080 }, audio: false };
// 停止之前的媒体流
if (window.stream) {
window.stream.getTracks().forEach(track => track.stop());
}
try {
return await navigator.mediaDevices.getDisplayMedia(constraints);
} catch (error) {
console.error('屏幕分享失败:', error);
}
}
媒体约束 Constraints
基本配置
在屏幕分享中 video 属性不能设置为 false。
const constraints = { video: true };
指定分辨率
const constraints = { video: { width: 1920, height: 1080 } };
音频设置
如果需要分享系统音频,可以将 audio 设置为 true。注意,并非所有浏览器都支持音频分享功能。
const constraints = {
audio: true,
video: { width: 1920, height: 1080 }
};
小提示:在获取新的媒体流之前,建议停止之前的媒体流,以避免设备使用提示,并确保应用逻辑清晰。
if (window.stream) {
window.stream.getTracks().forEach(track => track.stop());
}
2.3 RTCPeerConnection
RTCPeerConnection 是什么?
RTCPeerConnection 用于管理音视频连接。它帮助你建立和维护与其他用户的实时通信,处理媒体流、网络连接等问题。
如何使用 RTCPeerConnection?
创建 RTCPeerConnection 需要提供一个配置对象,通常包含用于网络穿透的服务器信息(如 STUN 服务器)。
const configuration = {
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
};
const peerConnection = new RTCPeerConnection(configuration);
主要功能
1. 创建连接请求
-
createOffer(): 发起连接请求 -
createAnswer(): 响应连接请求peerConnection.createOffer()
.then(offer => peerConnection.setLocalDescription(offer))
.then(() => {
// 发送 offer 给对端
});
2. 设置描述信息
-
setLocalDescription(description): 设置本地的连接信息 -
setRemoteDescription(description): 设置对端的连接信息peerConnection.setRemoteDescription(new RTCSessionDescription(remoteOffer))
.then(() => peerConnection.createAnswer())
.then(answer => peerConnection.setLocalDescription(answer));
3. 处理媒体流
-
addTrack(track, stream): 添加音视频轨道到连接中 -
addIceCandidate(candidate): 添加网络候选地址navigator.mediaDevices.getUserMedia({ video: true, audio: true })
.then(stream => {
stream.getTracks().forEach(track => peerConnection.addTrack(track, stream));
});peerConnection.addIceCandidate(new RTCIceCandidate(candidate));
4. 事件处理
-
onicecandidate: 当新的网络候选地址出现时触发 -
ontrack: 当接收到对端的媒体流时触发 -
oniceconnectionstatechange: 当连接状态变化时触发peerConnection.onicecandidate = (event) => {
if (event.candidate) {
// 发送候选地址到信令服务器
}
};peerConnection.ontrack = (event) => {
const remoteStream = event.streams[0];
// 显示远程媒体流
videoElement.srcObject = remoteStream;
};
关键点:
- 使用
addTransceiver('video', { direction: 'recvonly' })表示前端只接收视频流,不发送 - 通过
ontrack事件接收后端推送的视频流 - 使用 STUN 服务器进行 NAT 穿透
2.4 RTCDataChannel
RTCDataChannel 是什么?
RTCDataChannel 是 WebRTC 提供的一个 API,用于在对等端之间传输任意数据。它支持低延迟、可靠性可选的数据传输方式,使得在音视频通信之外,还可以传输文本、文件等数据。
如何使用 RTCDataChannel?
通过 RTCPeerConnection 创建一个数据通道,并定义其配置。
const dataChannel = peerConnection.createDataChannel("myDataChannel");
主要功能
1. 发送和接收数据
-
send(data): 通过数据通道发送数据,可以是字符串、二进制数据等 -
onmessage: 当接收到数据时触发dataChannel.send("Hello, WebRTC!");
dataChannel.onmessage = (event) => {
console.log("Received message:", event.data);
};
2. 事件处理
-
onopen: 当数据通道打开时触发 -
onclose: 当数据通道关闭时触发dataChannel.onopen = () => {
console.log("Data channel is open");
};dataChannel.onclose = () => {
console.log("Data channel is closed");
};
2.5 API 协同工作
通过 getUserMedia 获取用户的摄像头和麦克风流,结合 getDisplayMedia 进行屏幕分享,再利用 RTCPeerConnection 管理音视频连接,最终通过 RTCDataChannel 实现数据传输。通过这些 API 的协同工作,我们可以轻松实现一个功能齐全的视频通话应用。
3. WebRTC + WebSocket 协同工作
3.1 为什么需要 WebSocket?
WebRTC 本身并不包含信令协议,需要开发者自己实现信令过程。我们使用 WebSocket 作为信令服务器,处理以下内容:
- SDP Offer/Answer 交换:协商媒体格式和网络信息
- ICE Candidate 交换:交换网络候选地址,建立 P2P 连接
3.2 双通道架构
采用 WebRTC + WebSocket 的双通道架构:
┌─────────────────────────────────────────────────────────┐
│ 前端 (Browser) │
├─────────────────────────────────────────────────────────┤
│ WebSocket 通道 │ WebRTC MediaStream │ DataChannel │
│ (信令交换) │ (视频流传输) │ (控制消息) │
└─────────────────────────────────────────────────────────┘
│ │ │
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────────────┐
│ 后端 │
├─────────────────────────────────────────────────────────┤
│ WebSocket Server │ RTCPeerConnection │ DataChannel │
│ (信令处理) │ (视频推流) │ (控制处理) │
└─────────────────────────────────────────────────────────┘
3.3 连接建立流程
步骤 1:WebSocket 连接建立
// 前端
this.ws = new WebSocket('ws://localhost:8080');
// 后端
const wss = new WebSocketServer({port: 8080});
wss.on('connection', async (ws) => {
// 处理 WebSocket 连接
});
步骤 2:WebRTC 信令交换
// 前端:创建 Offer
const offer = await this.pc.createOffer();
await this.pc.setLocalDescription(offer);
this.ws.send(JSON.stringify({ type: 'offer', offer }));
// 后端:处理 Offer 并创建 Answer
const offer = new RTCSessionDescription(message.offer);
await this.pc.setRemoteDescription(offer);
const answer = await this.pc.createAnswer();
await this.pc.setLocalDescription(answer);
ws.send(JSON.stringify({ type: 'answer', answer }));
步骤 3:ICE Candidate 交换
// 前端:发送 ICE Candidate
this.pc.onicecandidate = (event) => {
if (event.candidate) {
this.ws.send(JSON.stringify({
type: 'ice-candidate',
candidate: event.candidate
}));
}
};
// 后端:处理 ICE Candidate
await this.pc.addIceCandidate(candidate);
步骤 4:DataChannel 建立
// 前端:创建 DataChannel
this.dataChannel = this.pc.createDataChannel('control', {ordered: true});
// 后端:监听 DataChannel
this.pc.ondatachannel = (event) => {
this.dataChannel = event.channel;
this.dataChannel.onopen = () => {
// DataChannel 已打开,可以发送消息
};
};
步骤 5:媒体流传输
// 后端:推送视频流
const videoSource = await this.streamManager.getCurrentSource();
const videoTrack = videoSource.getTrack();
this.currentSender = this.pc.addTrack(videoTrack);
// 前端:接收视频流
this.pc.ontrack = (event) => {
const stream = event.streams[0];
videoElement.srcObject = stream;
};
3.4 优势分析
WebSocket 的优势
- 可靠性:基于 TCP 协议,保证数据可靠传输
- 双向通信:支持服务器主动向客户端推送数据
- 简单易用:API 简单,易于实现
WebRTC 的优势
- 低延迟:直接 P2P 连接,减少延迟
- 高效传输:使用 SRTP 加密,支持 VP8/H264 编码
- 媒体处理:内置编解码功能,无需额外处理
双通道架构的优势
- 职责分离:信令和媒体流分离,提高系统的灵活性
- 性能优化:WebSocket 处理信令,WebRTC 处理媒体流,各司其职
- 易于扩展:可以轻松添加新的功能(如文件传输、文本聊天等)