深入理解 WebRTC:实时音视频通信的原理与实现全流程

一、引言

如果你使用过 视频会议(腾讯会议、Zoom) 、语音聊天室、远程协作工具,那么你已经悄悄地使用了 WebRTC ------ 浏览器原生支持的实时通信技术。

但写过 WebRTC 的人都知道:

👉 文档看似很多,但真正从原理到实现的体系化内容却不多。

👉 尤其是「信令怎么写?」「ICE 是啥?」「打洞是什么?」这些概念经常让初学者抓狂。

这篇文章,我会用开发者的视角,带你 从 0 到 1 掌握 WebRTC 的核心原理,并给出 可直接运行的代码模版,帮你快速构建属于自己的实时音视频 DEMO。


二、WebRTC 是什么?(What)

WebRTC(Web Real-Time Communication)是一套让浏览器直接进行实时音视频通信的技术标准。

其目标很简单:

让浏览器之间可以 P2P 传输音视频、文本、文件,且不依赖插件。

核心提供三个能力:

能力 API
获取摄像头、麦克风 getUserMedia()
P2P 数据传输 RTCPeerConnection
任意数据通道 RTCDataChannel

WebRTC 的杀手级特性:

  • 低延迟(几十毫秒)
  • 端到端加密
  • 浏览器原生支持
  • 可穿透绝大多数 NAT
  • 无需依赖中心服务器进行媒体转发(纯 P2P)

三、需求背景(Why)

现代实时应用对通信有越来越高的要求:

  • 远程会议
  • 在线教育
  • 实时客服系统
  • 多人语音房/直播互动
  • 浏览器游戏同步
  • 远程桌面 / 协同办公

它们共同需求:

通信方式 延迟 是否能满足实时
HTTP 轮询 200ms+
WebSocket 20--100ms ❌(不能传音视频)
WebRTC < 50ms ✔✔✔

所以在「实时音视频」场景里,WebRTC 是唯一的正解。


四、WebRTC 工作原理(How)

从工程的角度看,WebRTC 全流程可以分为 5 个关键步骤


1. 获取本地媒体流(摄像头/麦克风)

浏览器向用户请求权限:

javascript 复制代码
const localStream = await navigator.mediaDevices.getUserMedia({
  video: true,
  audio: true
});

2. 创建 PeerConnection

这是 WebRTC 最核心的对象:

javascript 复制代码
const pc = new RTCPeerConnection({
  iceServers: [
    { urls: "stun:stun.l.google.com:19302" } // 用于 NAT 打洞
  ]
});

3. 将本地媒体轨道加入连接

javascript 复制代码
localStream.getTracks().forEach(track => {
  pc.addTrack(track, localStream);
});

4. 信令交换(SDP + ICE)

注意:信令服务器不是 WebRTC 的一部分,需要你自行实现(WebSocket 通常即可)

信令的作用:

👉 只是"帮两台浏览器交换必要信息",不负责媒体传输。

两个核心内容:

类型 用途
SDP(会话描述) 告诉对方我的音视频能力、编码类型等
ICE Candidate(网络候选地址) 告诉对方我可连接的网络地址

这部分流程如下:

  1. A 创建 offer → 发送给 B
  2. B 创建 answer → 回给 A
  3. 双方持续交换 ICE Candidate

5. 建立 P2P 通道并传输音视频

完成 SDP + ICE 交换后,两端会尝试直连(NAT 穿透),连上后即可流式传输音视频。


五、代码实现(可直接运行)

下面提供一个最简版的 WebRTC 点对点视频通话 Demo

信令使用 WebSocket,可自行换成自己的服务器。


1. 信令服务器(Node.js)

使用 ws 创建一个最简单的信令服务:

javascript 复制代码
// server.js
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });

wss.on('connection', ws => {
  ws.on('message', msg => {
    // 广播给所有客户端
    wss.clients.forEach(client => {
      if (client !== ws && client.readyState === WebSocket.OPEN) {
        client.send(msg);
      }
    });
  });
});

console.log('Signaling server listening on ws://localhost:8080');

运行:

bash 复制代码
node server.js

2. 前端 WebRTC Demo(HTML + JavaScript)

javascript 复制代码
// index.js
const localVideo = document.getElementById("localVideo");
const remoteVideo = document.getElementById("remoteVideo");

const ws = new WebSocket("ws://localhost:8080");

const pc = new RTCPeerConnection({
  iceServers: [{ urls: "stun:stun.l.google.com:19302" }]
});

// 接收远端流
pc.ontrack = event => {
  remoteVideo.srcObject = event.streams[0];
};

// 发送 candidate
pc.onicecandidate = event => {
  if (event.candidate) {
    ws.send(JSON.stringify({ type: "candidate", candidate: event.candidate }));
  }
};

// 处理信令消息
ws.onmessage = async event => {
  const data = JSON.parse(event.data);

  if (data.type === "offer") {
    await pc.setRemoteDescription(data.offer);
    const answer = await pc.createAnswer();
    await pc.setLocalDescription(answer);
    ws.send(JSON.stringify({ type: "answer", answer }));
  }

  if (data.type === "answer") {
    await pc.setRemoteDescription(data.answer);
  }

  if (data.type === "candidate") {
    await pc.addIceCandidate(data.candidate);
  }
};

// 获取本地流并加入 PeerConnection
async function start() {
  const stream = await navigator.mediaDevices.getUserMedia({
    video: true,
    audio: true
  });

  localVideo.srcObject = stream;

  stream.getTracks().forEach(track => pc.addTrack(track, stream));
}

// 主动发起呼叫
async function call() {
  const offer = await pc.createOffer();
  await pc.setLocalDescription(offer);

  ws.send(JSON.stringify({ type: "offer", offer }));
}

start();

3. Demo 页面结构(index.html)

javascript 复制代码
<!DOCTYPE html>
<html lang="zh">
<body>
  <h2>WebRTC 简易视频通话 DEMO</h2>

  <video id="localVideo" autoplay muted></video>
  <video id="remoteVideo" autoplay></video>

  <button onclick="call()">发起通话</button>

  <script src="index.js"></script>
</body>
</html>

六、运行效果截图

当你打开两个浏览器并连接同一个信令服务器时,将看到:

  • 左侧:本地摄像头画面(localVideo)
  • 右侧:来自另一台浏览器的远端视频(remoteVideo)
  • 点击"发起通话"后,远端浏览器自动响应该呼叫,连接成功后双方即可互相看到视频画面

最终效果示意:

如果两个设备在不同网络,只要 STUN 可用,依然能成功 P2P 通话。

下面是 重新改写后的总结部分,更偏向收尾与个人感悟,语气更像真实开发者分享:


七、总结

写 WebRTC 是一种很特别的体验:它不像写普通前端逻辑,可以在控制台里一路 console.log;也不像写接口,有报错就能立刻定位。

它更像是在 与浏览器、网络环境、对端应用一起协作完成一件事------稍微一个环节出了问题,你就会陷入「为什么连不上?」的灵魂拷问。

从本地媒体采集、RTCPeerConnection 建立,到 SDP/ICE 的信令交换,再到最终建立真正的 P2P 通道,其实每一步都有很多值得学习的细节。

但当你真的跑通一次之后,你会发现:

WebRTC 本身并没有想象中那么神秘,它只是在帮你做正确且安全的"实时通信"。

我个人非常喜欢 WebRTC 的原因是,它让浏览器变得像"一个可以实时沟通的实时终端"。

当你第一次看到远端的视频画面在自己的浏览器中出现时,那种"我真的让两个浏览器直接连上了!"的感觉,是很有成就感的。

希望这篇文章能帮助你建立 WebRTC 的整体认知,为你后续开发实时音视频相关项目打下一个扎实、可落地的基础。


作者: 王新焱

博客: https://blog.csdn.net/qq_34402069

时间: 2025年12月5日


相关推荐
三十_A2 小时前
WebRTC 入门:一分钟理解一对多直播的完整实现流程
webrtc
三十_1 天前
WebRTC 入门:一分钟理解一对多直播的完整实现流程
webrtc
筏.k1 天前
WebRTC 集成 FFmpeg D3D12VA HEVC 硬件编码 avcodec_open2 返回 -22 问题排查与解决方案
ffmpeg·webrtc
metaRTC2 天前
webRTC IPC客户端UniApp版编程指南
uni-app·webrtc·ipc
hhwyqwqhhwy2 天前
linux 驱动 rtc
linux·运维·实时音视频
hazy1k5 天前
RA6E2基础-RTC时钟与日历介绍及使用
stm32·单片机·嵌入式硬件·esp32·实时音视频·ra
python百炼成钢5 天前
44.Linux RTC
linux·运维·实时音视频
Industio_触觉智能5 天前
RK3576轻松搭建RTMP视频推流,基于FFmpeg+Nginx协同
nginx·ffmpeg·实时音视频·rtmp·瑞芯微·视频推流·rk3576
FinelyYang5 天前
centos7安装coturn,实现WebRTC音视频通话
webrtc