webRTC——瀏覽器裡的音視訊通話

simon_z發表於2019-03-11

背景

webRTC是Google在2010年收購GIP公司之後獲得的一項技術。如下圖所示,它提供了音視訊的採集、處理(降噪,回聲消除等)、編解碼、傳輸等技術。

webRTC結構

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穿越技術,它整合了STUNTURN。當穿越網路時,ICE會先嚐試STUN,查出自己位於哪種型別的NAT之後以及NAT為某一個本地埠所繫結的Internet端埠從而建立UDP連線,如果失敗了ICE就會再嘗試TCP(先嚐試HTTP,再嘗試HTTPS),如果仍然失敗就使用中繼的TURN伺服器。

ICE

再來看看建立連線過程中的具體步驟:

  1. 呼叫getUserMedia獲取本地的MediaStreams;
  2. 從STUN獲取自己的外網IP及埠,通過Signaling Server向對方傳送Offer(SDP協議),並收到Answer;
  3. 同時webRTC會生成一些包含自己的內網、外網IP等資訊的candidate,同樣通過Signaling Server相互傳輸;
  4. 建立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佔用率和延時的問題。

參考文章

相關文章