一、什么是WebRTC
WebRTC技术是激烈的开放的Web战争中一大突破-Brendan Eich, inventor of JavaScript。
简单来说,WebRTC 是一个音视频处理+及时通讯的开源库。在实时通信中,音视频的采集和处理是一个很复杂的过程。比如音视频流的编解码、降噪和回声消除等。由Google发起开源,其中包含视频音频采集,编解码,数据传输,音视频展示等功能,我们可以通过技术快速地构建出一个音视频通讯应用。虽然其名为WebRTC,但是实际上它不只是支持Web之间的音视频通讯,还支持Android以及IOS端,此外由于该项目是开源的,我们也可以通过编译C++代码,从而达到全平台的互通。
WebRTC的架构图为:
(图片来自网络)
我们可以看到模块化和分层的设计,我们文章的目的是演示浏览器端对端的连接流程,焦点是服务端信令服务器的实现方式,但需要提前介绍一些WebRTC的基本概念和连接流程。
二、基础概念
流和轨
Track 轨道,可以理解每一路音频或视频,为一个轨,互不相交,类比火车轨道。
MediaStream 媒体流,每个媒体流中包含若干轨道,可以将音频轨,视频轨打包在一起。
三、几个关键类
MediaStream 媒体流类,MeidiaStream用于将多个MediaStreamTrack对象打包到一起。一个MediaStream可包含audio track 与video track,并且可以添加或者删除。
RTCPeerConnection 连接类,包含非常多重要功能,屏蔽复杂技术细节,便于应用层使用,包括但不限于连接管理,P2P类型检测,NAT穿透,中转等。
RTCDataChannel 非音视频数据传输类,这个类在我们的例子中没有涉及到。可以简单理解为将媒体流信息或者数据信息塞到连接中,进行传输。
四、端对端连接流程
两个不同网络环境浏览器,要实现点对点的实时音视频对话,需要处理那些问题?
媒体协商
双方需要知道对方支持的媒体格式,SDP(Session Description Protocol)是一种会话描述协议,视频通讯的双方必须先交换SDP信息,才能进一步互相通信。
网络协商
双方要了解对方的网络情况,尝试寻求一个可以互相通讯的链路,其中有寻路选择,如果确实没办法建立点对点链路,会使用中继服务器来进行转发。如果是内网,或者大部分NAT网络环境下,是可以建立端到端连接。在解决网络打通问题时候,有几个概念。
STUN(Session Traversal Utilities for NAT,NAT会话穿越应用程序)是一种网络协议,它允许位于NAT后的客户端找出自己的公网地址,查出自己位于哪种类型的NAT之后以及NAT在公网的端口映射信息。这些信息被用来在两端创建UDP连接通信。
TURN (Traversal Using Relays around NAT),如果客户端在NAT之后, 那么在一些网络情景下,有可能建立点对点的通讯连接,这时就需要公网的服务器作为一个中继, 对数据进行转发。
学习过程中,STUN和TURN服务器我们可使用coturn开源项目来搭建。
数据交换服务-信令服务器
WebRTC实现并没有规定信令服务器的实现方式和相关协议,这给了业务方技术选型极大的灵活。我们今天就是使用PHP+Swoole协程实现一个简单信令服务器。下面是一个端到端连接的流程图,整个核心流程逻辑都在图里面。
(图片来自网络)
五、使用Swoole实现信令服务器
客户端代码模拟
<body>
<div style="display: block">
<button class="btn" onclick="start()">连接</button>
<button class="btn" onclick="leave()">离开</button>
</div>
<div>
<div class="videos">
<h1>Local</h1>
<video id="localVideo" autoplay></video>
</div>
<div class="videos">
<h1>Remote</h1>
<video id="remoteVideo" autoplay></video>
</div>
</div>
<script src="assets/js/adapter.js"></script>
<script type="text/javascript">
const ws_config = '<?= $signaling_server ?>';
const localVideo = document.getElementById('localVideo');
const remoteVideo = document.getElementById('remoteVideo');
const configuration = {
iceServers: [{
urls: '<?= $stun_server ?>'
}]
};
let room_id = getQueryVariable('room_id');
if (room_id == '' || room_id == null) {
room_id = Math.random().toString(36).slice(-8);
location.href = '?room_id=' + room_id;
}
let subject = 'room-' + room_id;//当前主题
let answer = 0;
let ws = null;
let pc, localStream;
function getMediaStream(stream) {
localVideo.srcObject = localStream;
localStream = stream;
}
function start() {
ws = new WebSocket(ws_config);
ws.onopen = function (e) {
subscribe(subject);
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
console.error('the getUserMedia is not supported!');