深入理解WebRTC

Web Real-Time Communication(Web實時通信,WebRTC)由一組標準煌恢、協(xié)議和JavaScript API組成骇陈,用于實現(xiàn)瀏覽器之間(端到端)的音頻、視頻及數(shù)據(jù)共享瑰抵。

WebRTC使得實時通信變成一種標準功能你雌,任何Web應用都無需借助第三方插件和專有軟件,而是通過簡單地JavaScript API即可完成二汛。

在WebRTC中婿崭,有三個主要的知識點,理解了這三個知識點肴颊,也就理解了WebRTC的底層實現(xiàn)原理逛球。這三個知識點分別是:

  • MediaStream:獲取音頻和視頻流
  • RTCPeerConnection:音頻和視頻數(shù)據(jù)通信
  • RTCDataChannel:任意應用數(shù)據(jù)通信

MediaStream

如上所說,MediaStream主要是用于獲取音頻和視頻流苫昌。其JS實現(xiàn)也比較簡單颤绕,代碼如下:

'use strict';

navigator.getUserMedia = navigator.getUserMedia ||
    navigator.webkitGetUserMedia || navigator.mozGetUserMedia;

var constraints = { // 音頻、視頻約束
  audio: true, // 指定請求音頻Track
  video: {  // 指定請求視頻Track
      mandatory: { // 對視頻Track的強制約束條件
          width: {min: 320},
          height: {min: 180}
      },
      optional: [ // 對視頻Track的可選約束條件
          {frameRate: 30}
      ]
  }
};

var video = document.querySelector('video');

function successCallback(stream) {
  if (window.URL) {
    video.src = window.URL.createObjectURL(stream);
  } else {
    video.src = stream;
  }
}

function errorCallback(error) {
  console.log('navigator.getUserMedia error: ', error);
}

navigator.getUserMedia(constraints, successCallback, errorCallback);

在JS中祟身,我們通過getUserMedia函數(shù)來處理音頻和視頻奥务,該函數(shù)接收三個參數(shù),分別是音視頻的約束袜硫,成功的回調以及失敗的回調氯葬。

在底層,瀏覽器通過音頻和視頻引擎對捕獲的原始音頻和視頻流加以處理婉陷,除了對畫質和音質增強之外帚称,還得保證音頻和視頻的同步。

由于音頻和視頻是用來傳輸?shù)幕喟模虼舜扯茫l(fā)送方還要適應不斷變化的帶寬和客戶端之間的網(wǎng)絡延遲調整輸出的比特率。

對于接收方來說担神,則必須實時解碼音頻和視頻流楼吃,并適應網(wǎng)絡抖動和時延。其工作原理如下圖所示:

image.png

如上成功回調的stream對象中攜帶者一個或多個同步的Track妄讯,如果你同時在約束中設置了音頻和視頻為true孩锡,則在stream中會攜帶有音頻Track和視頻Track,每個Track在時間上是同步的亥贸。

stream的輸出可以被發(fā)送到一或多個目的地:本地的音頻或視頻元素躬窜、后期處理的JavaScript代理,或者遠程另一端炕置。如下圖所示:

image.png

RTCPeerConnection

在獲取到音頻和視頻流后荣挨,下一步要做的就是將其發(fā)送出去溜族。但這個跟client-server模式不同,這是client-client之間的傳輸垦沉,因此煌抒,在協(xié)議層面就必須解決NAT穿透問題,否則傳輸就無從談起厕倍。

另外寡壮,由于WebRTC主要是用來解決實時通信的問題,可靠性并不是很重要讹弯,因此况既,WebRTC使用UDP作為傳輸層協(xié)議:低延遲和及時性才是關鍵。

在更深入講解之前组民,我們先來思考一下棒仍,是不是只要打開音頻、視頻臭胜,然后發(fā)送UDP包就搞定了莫其?

當然沒那么簡單,除了要解決我們上面說的NAT穿透問題之外耸三,還需要為每個流協(xié)商參數(shù)乱陡,對用戶數(shù)據(jù)進行加密,并且需要實現(xiàn)擁塞和流量控制仪壮。

我們來看一張WebRTC的分層協(xié)議圖:

image.png

ICE憨颠、STUN和TURN是通過UDP建立并維護端到端連接所必需的;SDP 是一種數(shù)據(jù)格式积锅,用于端到端連接時協(xié)商參數(shù)爽彤;DTLS用于保障傳輸數(shù)據(jù)的安全;SCTP和SRTP屬于應用層協(xié)議,用于在UDP之上提供不同流的多路復用、擁塞和流量控制,以及部分可靠的交付和其他服務。

ICE(Interactive Connectivity Establishment斯稳,交互連接建立):由于端與端之間存在多層防火墻和NAT設備阻隔,因此我們需要一種機制來收集兩端之間公共線路的IP建车,而ICE則是干這件事的好幫手傀顾。

  • ICE代理向操作系統(tǒng)查詢本地IP地址
  • 如果配置了STUN服務器,ICE代理會查詢外部STUN服務器薄货,以取得本地端的公共IP和端口
  • 如果配置了TURN服務器翁都,ICE則會將TURN服務器作為一個候選項,當端到端的連接失敗谅猾,數(shù)據(jù)將通過指定的中間設備轉發(fā)柄慰。

WebRTC使用SDP(Session Description Protocol,會話描述協(xié)議)描述端到端連接的參數(shù)坐搔。
SDP不包含媒體本身的任何信息,僅用于描述"會話狀況"概行,表現(xiàn)為一系列的連接屬性:要交換的媒體類型(音頻、視頻及應用數(shù)據(jù))凳忙、網(wǎng)絡傳輸協(xié)議业踏、使用的編解碼器及其設置、帶寬及其他元數(shù)據(jù)勤家。

image.png
image.png

DTLS對TLS協(xié)議進行了擴展柳恐,為每條握手記錄明確添加了偏移字段和序號伐脖,這樣就滿足了有序交付的條件,也能讓大記錄可以被分段成多個分組并在另一端再進行組裝乐设。
DTLS握手記錄嚴格按照TLS協(xié)議規(guī)定的順序傳輸晓殊,順序不對就報錯伤提。最后,DTLS還要處理丟包問題:兩端都是用計時器肿男,如果預定時間沒有收到應答舶沛,就重傳握手記錄嘹承。
為保證過程完整如庭,兩端都要生成自己簽名的證書,然后按照常規(guī)的TLS握手協(xié)議走坪它。但這樣的證書不能用于驗證身份,因為沒有要驗證的信任鏈蒙揣。因此开瞭,在必要情況下罩息,
應用必須自己參與各端的身份驗證:

  • 應用可以通過登錄來驗證用戶
  • 每一端也可以在生成SDP提議/應答時指定各自的"身份頒發(fā)機構"个扰,等對端接收到SDP消息后,可以聯(lián)系指定的身份頒發(fā)機構驗證收到的證書

SRTP為通過IP網(wǎng)絡交付音頻和視頻定義了標準的分組格式递宅。SRTP本身并不對傳輸數(shù)據(jù)的及時性、可靠性或數(shù)據(jù)恢復提供任何保證機制茅主,
它只負責把數(shù)字化的音頻采樣和視頻幀用一些元數(shù)據(jù)封裝起來土榴,以輔助接收方處理這些流。

SCTP是一個傳輸層協(xié)議玷禽,直接在IP協(xié)議上運行,這一點跟TCP和UDP類似糯笙。不過在WebRTC這里撩银,SCTP是在一個安全的DTLS信道中運行,而這個信道又運行在UDP之上够庙。
由于WebRTC支持通過DataChannel API在端與端之間傳輸任意應用數(shù)據(jù)抄邀,而DataChannel就依賴于SCTP。

image.png

以上講了這么多境肾,終于到我們的主角RTCPeerConnection,RTCPeerConnection接口負責維護每一個端到端連接的完整生命周期:

  • RTCPeerConnection管理穿越NAT的完整ICE工作流
  • RTCPeerConnection發(fā)送自動(STUN)持久化信號
  • RTCPeerConnection跟蹤本地流
  • RTCPeerConnection跟蹤遠程流
  • RTCPeerConnection按需觸發(fā)自動流協(xié)商
  • RTCPeerConnection提供必要的API偶宫,以生成連接提議衫嵌,接收應答,允許我們查詢連接的當前狀態(tài)结闸,等等
image.png

我們來看一下示例代碼:

var signalingChannel = new SignalingChannel();
var pc = null;
var ice = {
    "iceServers": [
        { "url": "stun:stun.l.google.com:19302" }, //使用google公共測試服務器
        { "url": "turn:user@turnserver.com", "credential": "pass" } // 如有turn服務器酒朵,可在此配置
    ]
};
signalingChannel.onmessage = function (msg) {
    if (msg.offer) { // 監(jiān)聽并處理通過發(fā)信通道交付的遠程提議
        pc = new RTCPeerConnection(ice);
        pc.setRemoteDescription(msg.offer);
        navigator.getUserMedia({ "audio": true, "video": true }, gotStream, logError);
    } else if (msg.candidate) { // 注冊遠程ICE候選項以開始連接檢查
        pc.addIceCandidate(msg.candidate);
    }
}
function gotStream(evt) {
    pc.addstream(evt.stream);
    var local_video = document.getElementById('local_video');
    local_video.src = window.URL.createObjectURL(evt.stream);
    pc.createAnswer(function (answer) { // 生成描述端連接的SDP應答并發(fā)送到對端
        pc.setLocalDescription(answer);
        signalingChannel.send(answer.sdp);
    });
}
pc.onicecandidate = function (evt) {
    if (evt.candidate) {
        signalingChannel.send(evt.candidate);
    }
}
pc.onaddstream = function (evt) {
    var remote_video = document.getElementById('remote_video');
    remote_video.src = window.URL.createObjectURL(evt.stream);
}
function logError() { ... }

DataChannel

DataChannel支持端到端的任意應用數(shù)據(jù)交換蔫耽,就像WebSocket一樣,但是是端到端的匙铡。
建立RTCPeerConnection連接之后,兩端可以打開一或多個信道交換文本或二進制數(shù)據(jù)黑毅。

image.png

其示例demo如下:

var ice = {
    'iceServers': [
        {'url': 'stun:stun.l.google.com:19302'},   // google公共測試服務器
        // {"url": "turn:user@turnservera.com", "credential": "pass"}
    ]
};

// var signalingChannel =  new SignalingChannel();

var pc = new RTCPeerConnection(ice);

navigator.getUserMedia({'audio': true}, gotStream, logError);

function gotStream(stram) {
    pc.addStream(stram);

    pc.createOffer().then(function(offer){
        pc.setLocalDescription(offer);
    });
}

pc.onicecandidate = function(evt) {
    // console.log(evt);
    if(evt.target.iceGatheringState == 'complete') {
        pc.createOffer().then(function(offer){
            // console.log(offer.sdp);
            // signalingChannel.send(sdp);
        })
    }
}

function handleChannel(chan) {
    console.log(chan);
    chan.onerror = function(err) {}
    chan.onclose = function() {}
    chan.onopen = function(evt) {
        console.log('established');
        chan.send('DataChannel connection established.');
    }

    chan.onmessage = function(msg){
        // do something
    }
}

// 以合適的交付語義初始化新的DataChannel
var dc = pc.createDataChannel('namedChannel', {reliable: false});

handleChannel(dc);
pc.onDataChannel = handleChannel;

function logError(){
    console.log('error');
}
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市愿卒,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌易结,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,907評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件搞动,死亡現(xiàn)場離奇詭異改橘,居然都是意外死亡,警方通過查閱死者的電腦和手機狮惜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評論 3 395
  • 文/潘曉璐 我一進店門碌识,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人开泽,你說我怎么就攤上這事魁瞪』莺簦” “怎么了峦耘?”我有些...
    開封第一講書人閱讀 164,298評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長辅髓。 經常有香客問我,道長矫付,這世上最難降的妖魔是什么第焰? 我笑而不...
    開封第一講書人閱讀 58,586評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮而叼,結果婚禮上豹悬,老公的妹妹穿的比我還像新娘。我一直安慰自己瞻佛,他們只是感情好,可當我...
    茶點故事閱讀 67,633評論 6 392
  • 文/花漫 我一把揭開白布绊困。 她就那樣靜靜地躺著适刀,像睡著了一般。 火紅的嫁衣襯著肌膚如雪笔喉。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,488評論 1 302
  • 那天作谭,我揣著相機與錄音奄毡,去河邊找鬼。 笑死锐秦,一個胖子當著我的面吹牛,可吹牛的內容都是我干的农猬。 我是一名探鬼主播售淡,決...
    沈念sama閱讀 40,275評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼揍堕!你這毒婦竟也來了汤纸?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,176評論 0 276
  • 序言:老撾萬榮一對情侶失蹤楞慈,失蹤者是張志新(化名)和其女友劉穎啃擦,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體令蛉,經...
    沈念sama閱讀 45,619評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,819評論 3 336
  • 正文 我和宋清朗相戀三年蝎宇,在試婚紗的時候發(fā)現(xiàn)自己被綠了祷安。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,932評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡凉唐,死狀恐怖虱咧,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情腕巡,我是刑警寧澤,帶...
    沈念sama閱讀 35,655評論 5 346
  • 正文 年R本政府宣布煎楣,位于F島的核電站,受9級特大地震影響择懂,放射性物質發(fā)生泄漏。R本人自食惡果不足惜表伦,卻給世界環(huán)境...
    茶點故事閱讀 41,265評論 3 329
  • 文/蒙蒙 一慷丽、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧纲熏,春花似錦、人聲如沸局劲。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽剔氏。三九已至,卻和暖如春谈跛,著一層夾襖步出監(jiān)牢的瞬間塑陵,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評論 1 269
  • 我被黑心中介騙來泰國打工阻桅, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人嫂沉。 一個月前我還...
    沈念sama閱讀 48,095評論 3 370
  • 正文 我出身青樓扮碧,卻偏偏與公主長得像杏糙,于是被迫代替她去往敵國和親蚓土。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,884評論 2 354