一七九、WebRTC介绍

WebRTC介绍

1. 什么是 WebRTC?

1.1 WebRTC 介绍

WebRTC(Web 实时通信)是一个可以用在视频聊天、音频聊天或 P2P 文件分享等 Web 应用中的 API。------摘自:MDN - WebRTC

MDN 对 WebRTC 的定义我们可以拆分为以下三点:

  1. WebRTC 是网页即时通信(Web Real-Time Communication)的缩写
  2. 它提供了支持网页浏览器进行实时语音和视频对话的 API
  3. 允许浏览器之间直接建立连接,实现点对点的通信

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

兼容性注意事项
  1. 移动端支持:大多数移动浏览器也支持 WebRTC。需要注意的是,在 iOS 设备上,Safari 浏览器的版本需大于 10.1。
  2. 旧版浏览器:一些老旧的浏览器或 WebView 组件可能不支持 WebRTC,建议在发布应用前进行全面的兼容性测试。
  3. 网络环境与隐私权限:不同浏览器对摄像头、麦克风等隐私权限的处理方式有所不同,用户授权和连接稳定性可能受此影响。

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 的默认摄像头和麦克风。然而,在处理复杂场景时,例如选择特定的设备,可以按以下步骤操作:

  1. 列出所有可用的媒体设备:获取设备列表以便选择

  2. 选择所需的设备:从设备列表中选择适合的摄像头和麦克风

  3. 配置并传递设备信息:将所选设备的信息传递给浏览器 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 判断是否有视频输入源,再决定是否设置 videofalse

指定设备

复制代码
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 处理媒体流,各司其职
  • 易于扩展:可以轻松添加新的功能(如文件传输、文本聊天等)

参考资料


相关推荐
六月June June2 小时前
vue3 antd3.x ant-table组件 鼠标移入行出现tooltip
前端·vue.js·table·ant-design-vue
2501_944424123 小时前
Flutter for OpenHarmony游戏集合App实战之连连看路径连线
android·开发语言·前端·javascript·flutter·游戏·php
市象4 小时前
字节AI撒“豆”成兵
人工智能
康康的AI博客10 小时前
腾讯王炸:CodeMoment - 全球首个产设研一体 AI IDE
ide·人工智能
中达瑞和-高光谱·多光谱10 小时前
中达瑞和LCTF:精准调控光谱,赋能显微成像新突破
人工智能
mahtengdbb110 小时前
【目标检测实战】基于YOLOv8-DynamicHGNetV2的猪面部检测系统搭建与优化
人工智能·yolo·目标检测
Pyeako10 小时前
深度学习--BP神经网络&梯度下降&损失函数
人工智能·python·深度学习·bp神经网络·损失函数·梯度下降·正则化惩罚
search711 小时前
前端设计:CRG 3--CDC error
前端