背景
webRTC是Google在2010年收購GIP公司之後獲得的一項技術。如下圖所示,它提供了音視訊的採集、處理(降噪,回聲消除等)、編解碼、傳輸等技術。
webRTC的目標是實現無需安裝任何外掛就可以通過瀏覽器進行P2P的實時音視訊通話及檔案傳輸,來看看Google的demo,是不是很酷?本文將帶你分析webRTC的原理,並逐步編寫一個簡單的demo。
原理
如圖,瀏覽器之間媒體流的傳輸是P2P的,但是這並不意味著webRTC不需要伺服器支援。建立P2P視訊連線需要的資訊,如用來初始化通訊的session資訊,雙方的ip、埠,視訊解析度,編解碼格式等等,還是需要通過伺服器來傳輸的。webRTC沒有規定這些資訊傳輸的機制,XHR、webSocket、Socket.io等都是可以的,因為Socket.io自帶了房間的概念,便於雙向視訊的撮合,所以我在demo裡選擇了Socket.io。當然,連線建立的過程不會這麼簡單。首先,提到P2P就繞不開NAT(Network Address Translation),webRTC使用ICE(Interactive Connectivity Establishment)框架,ICE是一種綜合性的NAT穿越技術,它整合了STUN、TURN。當穿越網路時,ICE會先嚐試STUN,查出自己位於哪種型別的NAT之後以及NAT為某一個本地埠所繫結的Internet端埠從而建立UDP連線,如果失敗了ICE就會再嘗試TCP(先嚐試HTTP,再嘗試HTTPS),如果仍然失敗就使用中繼的TURN伺服器。
再來看看建立連線過程中的具體步驟:
- 呼叫getUserMedia獲取本地的MediaStreams;
- 從STUN獲取自己的外網IP及埠,通過Signaling Server向對方傳送Offer(SDP協議),並收到Answer;
- 同時webRTC會生成一些包含自己的內網、外網IP等資訊的candidate,同樣通過Signaling Server相互傳輸;
- 建立P2P連線,傳輸媒體資訊。
API
- getUserMedia: 獲取本地視訊、音訊,可以傳入constraints調整解析度、幀率,返回一個promise;
- RTCPeerConnection: 生成一個RTCPeerConnection例項,傳輸視訊、音訊流;
- createOffer: 會話發起方生成SDP Offer,包含了本地媒體流資訊;
- setLocalDescription:在此方法被呼叫之前oncandidate事件不會被觸發;
- setRemoteDescription: 接收到offer或者answer之後呼叫;
- addIceCandidate: 接收到icecandidate之後呼叫;
Steps
獲取媒體流
var constraints = {
audio: false,
video: true
};
navigator.mediaDevices.getUserMedia(constraints)
.then(gotStream)
.catch(function(e) {
alert('getUserMedia() error: ' + e.name);
});
function gotStream(stream) {
localVideo.srcObeject = stream;
localStream = stream;
}
複製程式碼
getUserMedia存在相容性問題,需要在專案中引用webRTC官方給出的adapter.js。constraints還可以配置video的解析度、幀率、對移動端還可以選擇前後攝像頭: var constraints = { video: { width: { min:640, ideal: 1280, max: 1920 }, height: { min: 480 ideal: 720, max: 1080 }, facingMode: 'user' // 前置攝像頭 } };
定義RTCPeerConnection
var serverConfig = {
'iceServers': [{
'urls': 'stun:stun.l.google.com:19302'
}]
};
function createPeerConnection() {
var pc = new RTCPeerConnection(serverConfig);
pc.onicecandidate = function(e) {
if (e.candidate) {
pc.addIceCandidate(e.candidate);
}
};
// 新增對方的媒體流
pc.onaddstream = function(e) {
remoteVideo.srcObeject = e.stream;
remoteStream = stream;
};
}
複製程式碼
由STUN、TURN配置生成對應的RTCPeerConnection例項,再定義相關的事件處理函式,如onicecandidate、onaddstream、onremovestream等。
建立連線
function start() {
pc.addstream(localStream);
if (isCaller) {
pc.createOffer(function(sessionDescription) {
pc.setLocalDescription(sessionDescription);
send(sessionDescription); // 根據不同的Signaling方式實現
});
if (receiveAnswer) {
pc.setRemoteDescription(answer.sessionDescription);
}
} else {
if (receiveOffer) {
pc.setRemoteDescription(offer.sessionDescription);
}
pc.createAnswer(function(sessionDescription) {
pc.setLocalDescription(sessionDescription);
send(sessionDescription);
});
}
}
複製程式碼
必須先getUserMedia後才能生成sessionDescription,並且只有在setLocalDescription後onicecandidate事件才會觸發。上面程式碼中的只是為了說明大致流程,實際專案中結合socket.io的事件更容易實現。
中斷會話
function stop() {
pc.stop();
pc = null;
}
複製程式碼
關於socket.io有關的程式碼本文沒有貼出,詳情可參考socket.io的用法。
可行性
按照上面的步驟可以成功地搭建webRTC的小demo,但是能否將webRTC運用到實際專案中去呢?下面從瀏覽器相容性和webRTC本身的效能兩個方面去分析。
相容性
-
IOS: 只有最新的ios11支援webRTC,且僅限safari瀏覽器,微信內建瀏覽器尚不支援getUserMedia,不支援DataChannel,視訊編解碼格式為H.264;
-
Android: 安卓4.4以上(不含4.4),經測試各大手機廠商自帶瀏覽器均不支援getUserMedia,但微信內建瀏覽器可以正常執行,另外61版本以上的Chrome for Android也都支援;
-
PC: Chrome49以上,Firefox55以上,Edge支援,Safari只有11支援,IE不支援。
效能
誠然webRTC在回聲消除,影象編解碼等方面已經做得十分出色,但它在效能上的問題還是不可忽視的:
- 由於需要進行視訊編解碼,所以CPU佔用率非常高,尤其是在移動裝置上;
- 在移動裝置上獲取的視訊解析度有限,最高只能達到640 * 480;
- 頻寬有限時,音視訊質量較差,延時明顯;
綜上所述,雖然webRTC具有不需安裝外掛或者客戶端,開源免費,強大的網路穿透能力,出色的音視訊處理技術等等優點,但由於相容性及效能上的問題,要投入到生產中還需要時間,主要是IOS11的普及以及CPU佔用率和延時的問題。