前言
现在远程办公的场景多的话,WebRtc有时候在一些公司还是有应用场景的,以直播为例,一起看下webrtc涉及哪些流程
WebRtc处理过程
上图是实现1v1的通话有4个部分,WebRtc终端(这里理解为浏览器端)、Signal(信令)服务器、STUN/TURN服务器
- WebRtc终端,负责音视频的采集、编解码、NAT穿越、音视频数据传输 (这里终端暂时看做浏览器,webRtc不止应用在浏览器)
- Signal服务器,负责信令处理,如有人加入房间、离开房间、媒体协商消息传递等。(类似聊天室的xx加入房间),一般采用WebSocket连接
- STUN/TURN服务,负责获取WebRtc终端在公网的ip地址,以及NAT穿越失败后的数据中转。()
用户A和用户B要语音通过大致过程:
1.用户A和用户B作为WebRtc终端(浏览器)检测你的设备是否支持音视频数据采集,
2.获取音视频数据后加入到信令信令服务器,这样2个用户都加入到一个房间
3.用户A会创建RTCPeerConnection对象,该对象将采集到的音视频数据进行编码和通过P2P传送给对方,P2P穿越失败,就使用TURN进行数据中转,有的公司架构是直接用后者进行传输
音视频采集
浏览器的getUserMedia方法
navigator.mediaDevices.getUserMedia(constraints);
constraints参数视频设置采集分辨率、帧率参数,音频可以设置开启降噪等参数;如设备具备音视频采集能力,它返回的成功的promise里,可以获取到MediaStream对象
1.本地操作视频流:MediaStream对象存放着采集到的音视频轨,直接赋值给video标签的srcObject属性,就可以本地实现看到摄像头和听到声音。
2.拍照:通过canvas的drawImage,将video标签传入
3.保存照片:通过canvas.toDataURL生成本地地址,通过a标签下载图片
// 视频预览
var constraints = { audio: true, video: true };
let media
navigator.mediaDevices
.getUserMedia(constraints)
.then((MediaStream) => {
media = MediaStream;
const video = document.querySelector("video");
video.srcObject = MediaStream;
video.onloadedmetadata = function (e) {
video.play();
};
})
.catch((e) => {
console.warn(e, "e");
});
// 拍照:调用canvas的api,将video标签
const video = document.querySelector("video");
document.querySelector("canvas").getContext('2d').drawImage(video, 0, 0,400,300)
// 保存照片
const url = canvas.toDataURL("image/jpeg");
document.createElement('a').href = url;
媒体协商
作用:让双方找到共同支持的媒体能力,过程有点像TCP的三次握手
1.创建连接,创建RTCPeerConnection,它负责端与端之间建立P2P连接
2.信令,客户端通过信令服务器交换SDP(包含编解码方式、传输协议、ip地址和端口等信息)
媒体协商过程
- Offer, 在双发通讯时,呼叫方发送的SDP消息称Offer
- Answer,在双发通讯时,被呼叫方发送的SDP消息称Answer
- 呼叫方(图左边)创建Offer类型的SDP消息后,调setLocalDescription方法保存到本地Local域,再通过信令将Offer发给被呼叫方
- 被呼叫方收到Offer类型的SDP消息后,调setRemoteDescription保存到它的Remote域。它再创建Answer类型SDP消息,调setLocalDescription保存到本地域,再将消息发给呼叫方。
- 呼叫方收到Answer类型的SDP消息后,调setRemoteDescription保存到它的Remote域。
总结:媒体协商完成后,WebRtc底层会收集Candidate(WebRtc与远端通信时使用的协议、ip地址和端口),进行连通性测试,最终建立一条链路。
// 呼叫方A
// 创建Offer,本地设置,发给对方
const pcA = new RTCPeerConnection();
pcA.createOffer().then((offerSDP)=>{
pcA.setLocalDescription(offerSDP);
sendMessage(offerSDP);
})
// 接受到answer,保存起来