WebRTC

312 阅读5分钟

WebRTC(Web Real-Time Communications)是Google公司开源的一项实时通讯技术,它允许网络应用或者站点,在不借助中间媒介的情况下,建立浏览器之间点对点(Peer-to-Peer)的连接,实现视频流、音频流或者其他任意数据的传输。

基本条件

WebRTC 终端(两个):本地和远端,负责音视频采集、编解码、NAT 穿越以及音视频数据传输等;

信令服务器:自行实现的信令服务,负责信令处理,如加入房间、离开房间、媒体协商消息的传递等;

STUN/TURN 服务器:负责获取 WebRTC 终端在公网的 IP 地址,以及 NAT 穿越失败后的数据中转服务。

示例

连接过程

  1. 客户端A/客户端B 分别连接信令服务器;
  2. 客户端A(发起方)开始执行,创建RTCPeerConnection实例,开启音视频采集,将音视频流挂载到peerConnection实例,生成offer sdp
  3. 通过信令服务器中转将客户端A生成的offer sdp发送到客户端B;
  4. 客户端B(接收方)开始执行,创建RTCPeerConnection实例,将客户端A的offer设置进去,生成answer
  5. 通过信令服务器中转将answer发送回客户端A;
  6. 客户端A设置本地offer后,通过onicecandidate回调收集的candidate发送给客户端B,客户端B也通过设置本地answer后,将收集的candidate发送回客户端A。
  7. 客户端A/客户端B拿到对方的candidate后,通过addIceCandidate添加到本端,如此就可以建立p2p连接;
  8. 最后Client B在连接之上拿到Client A的音视频数据;

注:

  • offer sdp/answer:用于建立RTC连接
  • candidate(ICE Candidate),双方通过交换ICE,来识别候选者身份
  • ICE(Interactive Connectivity Establishment,交互式连接建立技术):用户之间建立连接的方式,用来选取用户之间最佳的连接方式(双方进行通信的必要条件)

步骤

说明

发送方:用于发送音视频流的客户端A

接收方:用于接受音视频流的客户端B

第一步:初始化

// 链接socket
message.log('信令通道(WebSocket)创建中......');
const socket = new WebSocket('ws://localhost:8080');
socket.onopen = () => {
    message.log('信令通道创建成功!');
}
socket.onmessage = e => {
    const { type, sdp, iceCandidate } = JSON.parse(e.data)
    if (type === 'answer') {
        peer.setRemoteDescription(new RTCSessionDescription({ type, sdp }));
    } else if (type === 'answer_ice') {
        peer.addIceCandidate(iceCandidate);
    } else if (type === 'offer') {
        startLive(new RTCSessionDescription({ type, sdp }));
    } else if (type === 'offer_ice') {
        peer.addIceCandidate(iceCandidate);
    }
};
const PeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
!PeerConnection && message.error('浏览器不支持WebRTC!');
const peer = new PeerConnection();
​
​
peer.ontrack = e => {
    if (e && e.streams) {
        message.log('收到对方音频/视频流数据...');
        remoteVideo.srcObject = e.streams[0];
    }
};peer.onicecandidate = e => {  // 发送方设置offer/接收方设置answer 
    if (e.candidate) {
        message.log('搜集并发送候选人');
        socket.send(JSON.stringify({
            type: `${target}_ice`,
            iceCandidate: e.candidate
        }));
    } else {
        message.log('候选人收集完成!');
    }
};

第一步:音视频采集

1.mediaDevices

let stream;
try {
    message.log('尝试调取本地摄像头/麦克风');
    stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
    message.log('摄像头/麦克风获取成功!');
    localVideo.srcObject = stream; // 本地播放
} catch {
    message.error('摄像头/麦克风获取失败!');
    return;
}
stream.getTracks().forEach(track => {
    peer.addTrack(track, stream); // 添加媒体轨道到轨道集
});

image.png

第二步:创建并发送sdp/answer

// 发送方
message.log('创建本地SDP');
const offer = await peer.createOffer();
await peer.setLocalDescription(offer);  // 设置本地描述(获取candidate)  会调用上面绑定的onicecandidate事件传递candidate
message.log('传输发起方本地SDP');
socket.send(JSON.stringify(offer));
​
​
// 接收方
await peer.setRemoteDescription(offerSdp); // 设置远程描述(获取candidate)  会调用上面绑定的onicecandidate事件传递candidate
message.log('创建接收方(应答)SDP');
const answer = await peer.createAnswer();
message.log('传输接收方(应答)SDP');
socket.send(JSON.stringify(answer));
await peer.setLocalDescription(answer);
​
​
// 发送方接受到接收方传递的answer,建立远程描述ice,并发送自己的描述ice
peer.setRemoteDescription(new RTCSessionDescription({ type, sdp })); // 执行完这行代码会执行 上面的onicecandidate事件,将自己的 candidate ice 分别传递给对方建立ice链接

发送方生成的offer数据格式:

image.png 接收方生成的answer数据格式:

image.png 描述设置后会执行的内容:

image.png 执行完上面的内容传递给socket推送给对方会执行的内容:

image.png

完结:

offer、answer、ice处理完毕之后,完成的WebRTC连接就建立完成,任意一方推送音频流数据会同步到另一方上。

数据通道

WebRTC擅长进行数据传输,不仅仅是音频和视频流,还可以进行x相对于复杂的数据,创建一个数据通道这个主要功能可以通过RTCDataConnection实现:

let dataCannel = peer.createDataChannel("label",dataChannelOptions);
​
dataChannel.onerror = function (error){
    console.log(error)
}
dataChannel.onmessage = function (event){
    console.log(event.data)
}
dataChannel.onopen = function (error){
    console.log('data channel opened')
    // 当创建一个数据通道后,你必须等onopen事件触发后才能发送消息
    dataChannel.send('Hello world')
}
dataChannel.onclose = function (error){
    console.log('data channel closed')
}
​
//接收方此时并不需要再次调用createDataChannel方法,只需要监听RTCPeerConnection实例对象上的ondatachannel事件就可以在回调中拿到发送方的请求,数据通道就建立起来了。
peer.ondatachannel = function(event){
    console.log(event.data)
}

传输方式

WebRTC也是如此,在信令控制方面采用了可靠的TCP, 但是音视频数据传输上,使用了UDP作为传输层协议。只使用UDP是不够的,还需要RTP(实时传输协议)

音视频中一个视频帧数据量需要多个包来传送,并在接收端组成对应帧,正确还原出视频信号。需要正确的接受顺序,而UDP并没有这个能力,所以音视频传输中,并不直接使用UDP,而是需要RTP作为实时音视频中的应用层协议。

WebRTC使用了一些关键的协议和标准:

1、SDP(Session Description Protocol):SDP用于在对等连接之间交换会话描述信息。它描述了媒体流的类型、编码参数和网络地址。

2、ICE(Interactive Connectivity Establishment):ICE协议用于通过STUN和TURN服务器解决对等连接的网络可达性问题。

3、STUN(Session Traversal Utilities for NAT):STUN协议用于发现本地IP地址和绕过NAT。

4、TURN(Traversal Using Relays around NAT):TURN协议用于在对等连接无法直接建立时,通过中继服务器中转流量。

5、RTP(Real-Time Transport Protocol):RTP协议用于传输音频和视频流。

6、RTCP(Real-Time Control Protocol):RTCP协议用于实时传输控制信息,如丢包率、延迟等。

公网使用

STUNSession Traversal Utilities for NAT,用来帮助我们获取本地计算机的公网 IP 地址,以及端口号。 TURNTraversal Using Relays around NAT,用来帮助我们穿越 NAT 网关,实现公网中的 WebRTC 连接。

const pc = new RTCPeerConnection({
  iceServers: [
    // STUN 服务器
    {
      urls: 'stun:stun.voipbuster.com ',
    },
    // TURN 服务器
    {
      urls: 'turn:turn.xxxx.org',
      username: 'webrtc',
      credential: 'turnserver',
    },
  ],
})

场景

视频会议、实时音视频通话、数据共享、直播、远程协作等