摘要:對(duì)于接收方來說,則必須實(shí)時(shí)解碼音頻和視頻流,并適應(yīng)網(wǎng)絡(luò)抖動(dòng)和時(shí)延。另外,由于主要是用來解決實(shí)時(shí)通信的問題,可靠性并不是很重要,因此,使用作為傳輸層協(xié)議低延遲和及時(shí)性才是關(guān)鍵。握手記錄嚴(yán)格按照協(xié)議規(guī)定的順序傳輸,順序不對(duì)就報(bào)錯(cuò)。
Web Real-Time Communication(Web實(shí)時(shí)通信,WebRTC)由一組標(biāo)準(zhǔn)、協(xié)議和JavaScript API組成,用于實(shí)現(xiàn)瀏覽器之間(端到端)的音頻、視頻及數(shù)據(jù)共享。
WebRTC使得實(shí)時(shí)通信變成一種標(biāo)準(zhǔn)功能,任何Web應(yīng)用都無需借助第三方插件和專有軟件,而是通過簡(jiǎn)單地JavaScript API即可完成。
在WebRTC中,有三個(gè)主要的知識(shí)點(diǎn),理解了這三個(gè)知識(shí)點(diǎn),也就理解了WebRTC的底層實(shí)現(xiàn)原理。這三個(gè)知識(shí)點(diǎn)分別是:
MediaStream:獲取音頻和視頻流
RTCPeerConnection:音頻和視頻數(shù)據(jù)通信
RTCDataChannel:任意應(yīng)用數(shù)據(jù)通信
MediaStream如上所說,MediaStream主要是用于獲取音頻和視頻流。其JS實(shí)現(xiàn)也比較簡(jiǎn)單,代碼如下:
"use strict"; navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia; var constraints = { // 音頻、視頻約束 audio: true, // 指定請(qǐng)求音頻Track video: { // 指定請(qǐng)求視頻Track mandatory: { // 對(duì)視頻Track的強(qiáng)制約束條件 width: {min: 320}, height: {min: 180} }, optional: [ // 對(duì)視頻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ù)接收三個(gè)參數(shù),分別是音視頻的約束,成功的回調(diào)以及失敗的回調(diào)。
在底層,瀏覽器通過音頻和視頻引擎對(duì)捕獲的原始音頻和視頻流加以處理,除了對(duì)畫質(zhì)和音質(zhì)增強(qiáng)之外,還得保證音頻和視頻的同步。
由于音頻和視頻是用來傳輸?shù)?,因此,發(fā)送方還要適應(yīng)不斷變化的帶寬和客戶端之間的網(wǎng)絡(luò)延遲調(diào)整輸出的比特率。
對(duì)于接收方來說,則必須實(shí)時(shí)解碼音頻和視頻流,并適應(yīng)網(wǎng)絡(luò)抖動(dòng)和時(shí)延。其工作原理如下圖所示:
如上成功回調(diào)的stream對(duì)象中攜帶者一個(gè)或多個(gè)同步的Track,如果你同時(shí)在約束中設(shè)置了音頻和視頻為true,則在stream中會(huì)攜帶有音頻Track和視頻Track,每個(gè)Track在時(shí)間上是同步的。
stream的輸出可以被發(fā)送到一或多個(gè)目的地:本地的音頻或視頻元素、后期處理的JavaScript代理,或者遠(yuǎn)程另一端。如下圖所示:
RTCPeerConnection在獲取到音頻和視頻流后,下一步要做的就是將其發(fā)送出去。但這個(gè)跟client-server模式不同,這是client-client之間的傳輸,因此,在協(xié)議層面就必須解決NAT穿透問題,否則傳輸就無從談起。
另外,由于WebRTC主要是用來解決實(shí)時(shí)通信的問題,可靠性并不是很重要,因此,WebRTC使用UDP作為傳輸層協(xié)議:低延遲和及時(shí)性才是關(guān)鍵。
在更深入講解之前,我們先來思考一下,是不是只要打開音頻、視頻,然后發(fā)送UDP包就搞定了?
當(dāng)然沒那么簡(jiǎn)單,除了要解決我們上面說的NAT穿透問題之外,還需要為每個(gè)流協(xié)商參數(shù),對(duì)用戶數(shù)據(jù)進(jìn)行加密,并且需要實(shí)現(xiàn)擁塞和流量控制。
我們來看一張WebRTC的分層協(xié)議圖:
ICE、STUN和TURN是通過UDP建立并維護(hù)端到端連接所必需的;SDP 是一種數(shù)據(jù)格式,用于端到端連接時(shí)協(xié)商參數(shù);DTLS用于保障傳輸數(shù)據(jù)的安全;SCTP和SRTP屬于應(yīng)用層協(xié)議,用于在UDP之上提供不同流的多路復(fù)用、擁塞和流量控制,以及部分可靠的交付和其他服務(wù)。
ICE(Interactive Connectivity Establishment,交互連接建立):由于端與端之間存在多層防火墻和NAT設(shè)備阻隔,因此我們需要一種機(jī)制來收集兩端之間公共線路的IP,而ICE則是干這件事的好幫手。
ICE代理向操作系統(tǒng)查詢本地IP地址
如果配置了STUN服務(wù)器,ICE代理會(huì)查詢外部STUN服務(wù)器,以取得本地端的公共IP和端口
如果配置了TURN服務(wù)器,ICE則會(huì)將TURN服務(wù)器作為一個(gè)候選項(xiàng),當(dāng)端到端的連接失敗,數(shù)據(jù)將通過指定的中間設(shè)備轉(zhuǎn)發(fā)。
WebRTC使用SDP(Session Description Protocol,會(huì)話描述協(xié)議)描述端到端連接的參數(shù)。
SDP不包含媒體本身的任何信息,僅用于描述"會(huì)話狀況",表現(xiàn)為一系列的連接屬性:要交換的媒體類型(音頻、視頻及應(yīng)用數(shù)據(jù))、網(wǎng)絡(luò)傳輸協(xié)議、使用的編解碼器及其設(shè)置、帶寬及其他元數(shù)據(jù)。
DTLS對(duì)TLS協(xié)議進(jìn)行了擴(kuò)展,為每條握手記錄明確添加了偏移字段和序號(hào),這樣就滿足了有序交付的條件,也能讓大記錄可以被分段成多個(gè)分組并在另一端再進(jìn)行組裝。
DTLS握手記錄嚴(yán)格按照TLS協(xié)議規(guī)定的順序傳輸,順序不對(duì)就報(bào)錯(cuò)。最后,DTLS還要處理丟包問題:兩端都是用計(jì)時(shí)器,如果預(yù)定時(shí)間沒有收到應(yīng)答,就重傳握手記錄。
為保證過程完整,兩端都要生成自己簽名的證書,然后按照常規(guī)的TLS握手協(xié)議走。但這樣的證書不能用于驗(yàn)證身份,因?yàn)闆]有要驗(yàn)證的信任鏈。因此,在必要情況下,
應(yīng)用必須自己參與各端的身份驗(yàn)證:
應(yīng)用可以通過登錄來驗(yàn)證用戶
每一端也可以在生成SDP提議/應(yīng)答時(shí)指定各自的"身份頒發(fā)機(jī)構(gòu)",等對(duì)端接收到SDP消息后,可以聯(lián)系指定的身份頒發(fā)機(jī)構(gòu)驗(yàn)證收到的證書
SRTP為通過IP網(wǎng)絡(luò)交付音頻和視頻定義了標(biāo)準(zhǔn)的分組格式。SRTP本身并不對(duì)傳輸數(shù)據(jù)的及時(shí)性、可靠性或數(shù)據(jù)恢復(fù)提供任何保證機(jī)制,
它只負(fù)責(zé)把數(shù)字化的音頻采樣和視頻幀用一些元數(shù)據(jù)封裝起來,以輔助接收方處理這些流。
SCTP是一個(gè)傳輸層協(xié)議,直接在IP協(xié)議上運(yùn)行,這一點(diǎn)跟TCP和UDP類似。不過在WebRTC這里,SCTP是在一個(gè)安全的DTLS信道中運(yùn)行,而這個(gè)信道又運(yùn)行在UDP之上。
由于WebRTC支持通過DataChannel API在端與端之間傳輸任意應(yīng)用數(shù)據(jù),而DataChannel就依賴于SCTP。
以上講了這么多,終于到我們的主角RTCPeerConnection,RTCPeerConnection接口負(fù)責(zé)維護(hù)每一個(gè)端到端連接的完整生命周期:
RTCPeerConnection管理穿越NAT的完整ICE工作流
RTCPeerConnection發(fā)送自動(dòng)(STUN)持久化信號(hào)
RTCPeerConnection跟蹤本地流
RTCPeerConnection跟蹤遠(yuǎn)程流
RTCPeerConnection按需觸發(fā)自動(dòng)流協(xié)商
RTCPeerConnection提供必要的API,以生成連接提議,接收應(yīng)答,允許我們查詢連接的當(dāng)前狀態(tài),等等
我們來看一下示例代碼:
var signalingChannel = new SignalingChannel(); var pc = null; var ice = { "iceServers": [ { "url": "stun:stun.l.google.com:19302" }, //使用google公共測(cè)試服務(wù)器 { "url": "turn:[email protected]", "credential": "pass" } // 如有turn服務(wù)器,可在此配置 ] }; signalingChannel.onmessage = function (msg) { if (msg.offer) { // 監(jiān)聽并處理通過發(fā)信通道交付的遠(yuǎn)程提議 pc = new RTCPeerConnection(ice); pc.setRemoteDescription(msg.offer); navigator.getUserMedia({ "audio": true, "video": true }, gotStream, logError); } else if (msg.candidate) { // 注冊(cè)遠(yuǎn)程ICE候選項(xiàng)以開始連接檢查 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應(yīng)答并發(fā)送到對(duì)端 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支持端到端的任意應(yīng)用數(shù)據(jù)交換,就像WebSocket一樣,但是是端到端的。
建立RTCPeerConnection連接之后,兩端可以打開一或多個(gè)信道交換文本或二進(jìn)制數(shù)據(jù)。
其示例demo如下:
var ice = { "iceServers": [ {"url": "stun:stun.l.google.com:19302"}, // google公共測(cè)試服務(wù)器 // {"url": "turn:[email protected]", "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"); }
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/88764.html
摘要:為了使連接起作用,對(duì)等方必須獲取元數(shù)據(jù)的本地媒體條件例如,分辨率和編解碼器功能,并收集應(yīng)用程序主機(jī)的可能網(wǎng)絡(luò)地址,用于來回傳遞這些關(guān)鍵信息的信令機(jī)制并未內(nèi)置到中。所有特定于多媒體的元數(shù)據(jù)都使用協(xié)議傳遞。 這是專門探索 JavaScript 及其所構(gòu)建的組件的系列文章的第 18 篇。 想閱讀更多優(yōu)質(zhì)文章請(qǐng)猛戳GitHub博客,一年百來篇優(yōu)質(zhì)文章等著你! 如果你錯(cuò)過了前面的章節(jié),可以在這里...
摘要:實(shí)時(shí)通訊系統(tǒng)是最近互聯(lián)網(wǎng)應(yīng)用的一個(gè)新領(lǐng)域。現(xiàn)在的問題是,開發(fā)一個(gè)優(yōu)秀的系統(tǒng)需要具備哪些技術(shù)儲(chǔ)備呢先看終端方面。各個(gè)平臺(tái),,,底層音頻系統(tǒng)也需要深入了解?;ヂ?lián)網(wǎng)不是一個(gè)可靠的實(shí)時(shí)音視頻傳輸網(wǎng)絡(luò)。現(xiàn)在我們知道開發(fā)一個(gè)系統(tǒng)需要什么技術(shù)了。 RTC(real time communication)實(shí)時(shí)通訊系統(tǒng)是最近互聯(lián)網(wǎng)應(yīng)用的一個(gè)新領(lǐng)域。RTC系統(tǒng)的應(yīng)用極其廣泛,我們常見的視頻電話,會(huì)議系統(tǒng),...
閱讀 2167·2023-04-26 02:19
閱讀 1951·2021-11-19 09:40
閱讀 1735·2021-09-29 09:35
閱讀 3598·2021-09-29 09:34
閱讀 4406·2021-09-07 10:16
閱讀 5611·2021-08-11 11:14
閱讀 3612·2019-08-30 15:54
閱讀 1657·2019-08-30 15:53