WebRTC,即Web Real-Time Communication,web實時通訊技術。簡單地說就是在web瀏覽器裏面引入實時通訊,包括音視頻通話等。javascript
- WebRTC實時通訊技術介紹
- 如何使用
- 媒體介紹
- 信令
- STUN和TURN介紹
- 對等鏈接和提議/應答協商
- 數據通道
- NAT和防火牆穿透
- 簡單應用
- 其它
WebRTC實現了基於網頁的語音對話或視頻通話,目的是無插件實現web端的實時通訊的能力。java
WebRTC提供了視頻會議的核心技術,包括音視頻的採集、編解碼、網絡傳輸、展現等功能,而且還支持跨平臺,包括linux、windows、mac、android等。linux
WebRTC支持多個瀏覽器參與的多方會話或會議會話,要創建這類會話有以下兩種模式:android
WebRTC易於使用,只需極少步驟即可創建媒體會話。有些消息在瀏覽器和服務器之間流動,有些則直接在兩個瀏覽器(成爲對等端)之間流動。git
創建WebRTC鏈接須要以下幾個步驟:github
- 獲取本地媒體(
getUserMedia()
,MediaStream API)- 在瀏覽器和對等端(其它瀏覽器或終端)之間創建對等鏈接(RTCPeerConnection API)
- 將媒體和數據通道關聯至該鏈接
- 交換會話描述(RTCSessionDescription)
- 瀏覽器M從Web服務器請求網頁
- Web服務器向M返回帶有WebRTC js的網頁
- 瀏覽器L從Web服務器請求網頁
- Web服務器向L返回帶有WebRTC js的網頁
- M決定與L通訊,經過M自身的js將M的會話描述對象(offer,提議)發送至Web服務器
- Web服務器將M的會話描述對象發送至L上的js
- L上的js將L的會話描述對象(answer,應答)發送至Web服務器
- Web服務器轉發應答至M上的js
- M和L開始交互,肯定訪問對方的最佳方式
- 完成後,M和L開始協商通訊密鑰
- M和L開始交換語音、視頻或數據
WebRTC三角形會話具體的調用流程:web
說明: SDP對象的傳輸多是一個來回反覆的過程,而且該過程採用的協議並未標準化
WebRTC梯形會話方式具體的調用流程:windows
說明: 此場景中,瀏覽器M和L直接交換媒體,只是它們運行的Web服務器不用而已。每一個瀏覽器的會話描述對象都會映射至Jingle[XEP-0166]session-initiate消息和session-accept方法。
先來看下WebRTC中的本地媒體:瀏覽器
- 軌道(MediaStreamTrack,表明設備或錄製內容可返回的單一類型的媒體,惟一關聯一個「源」,WebRTC不能直接訪問或控制「源」,對「源」的一切控制都經過軌道實施;一個「源」可能對應多個軌道對象)
- 流(MediaStream,軌道對象的集合)
軌道和流的示意以下:服務器
以下代碼展現了本地媒體的簡單獲取,並展現:
// 注意getUserMedia()在各瀏覽器中的區別 // Opera --> getUserMedia // Chrome --> webkitGetUserMedia // Firefox --> mozGetUserMedia navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia; // 只獲取video: var constraints = {audio: false, video: true}; var video = document.querySelector("video"); function successCallback(stream) { // Note: make the returned stream available to console for inspection window.stream = stream; if (window.URL) { // Chrome瀏覽器 video.srcObject = stream; } else { // Firefox和Opera: 能夠直接把視頻源設置爲stream video.src = stream; } // 播放 video.play(); } function errorCallback(error){ console.log("navigator.getUserMedia error: ", error); } navigator.getUserMedia(constraints, successCallback, errorCallback);
運行效果以下:
完整代碼查看:https://github.com/caiya/webrtc-demo.git
在WebRTC中,信令起着舉足輕重的做用。但實現沒有標準化,好比http、websocket、xmpp等。
- 協商媒體功能和設置
- 標識和驗證會話參與者的身份(交換SDP對象中的信息:媒體類型、編解碼器、帶寬等元數據)
- 控制媒體會話、指示進度、更改會話、終止會話
- 雙佔用分解
簡單地說,信令就是協調通信的過程,一旦信令服務創建好了,兩個客戶端之間創建了鏈接,理論上它們就能夠進行點對點通信了。
WebRTC要求在兩個對等端創建雙向的信令通道,一般有三種方式來傳輸WebRTC信令:http、websocket、數據通道
http方式以下:
websocket代理信令傳輸:
WebRTC提供了瀏覽器端的P2P通訊,但並不意味着WebRTC不須要服務器。撇開應用服務器不說,至少如下兩種服務器是必須的:
- 瀏覽器之間創建通訊前交換各類元數據(信令)的服務器(信令服務)
- 穿越NAT和防火牆的服務器(stun、turn、rsip等)
說明: 元數據是經過信令服務器中轉發給另外一個客戶端,可是對於流媒體數據,一旦會話創建,首先嚐試使用點對點鏈接。簡單一點說就是:每一個客戶端都有一個惟一的地址,他能用來和其餘客戶端進行通信和數據交換。 STUN服務器:用來取外網地址的。(見下節) TURN服務器:在P2P失敗時進行轉發的。(見下節) ICE:*Interactive Connectivity Establishment*,即交互式連通創建方式。並不是一種新的協議,它經過綜合利用現有NAT穿透協議,以一種更有效的方式來組織會話創建過程,使之在不增長任何延遲同時比STUN等單一協議更具備健壯性、靈活性。
WebRTC使用RTCPeerConnection創建鏈接傳送流數據,在創建RTCPeerConnection實例以後,想要創建點對點的信道,須要作兩件事:
- 肯定本機上的媒體流的特性,好比分辨率、編解碼能力啥的(SDP描述符)
- 鏈接兩端的主機的網絡地址(ICE Candidate)
經過offer和answer交換SDP描述符:
- 甲和乙各自創建一個PC實例
- 甲經過PC所提供的createOffer()方法創建一個包含甲的SDP描述符的offer信令
- 甲經過PC所提供的setLocalDescription()方法,將甲的SDP描述符交給甲的PC實例
- 甲將offer信令經過服務器發送給乙
- 乙將甲的offer信令中所包含的的SDP描述符提取出來,經過PC所提供的setRemoteDescription()方法交給乙的PC實例
- 乙經過PC所提供的createAnswer()方法創建一個包含乙的SDP描述符answer信令
- 乙經過PC所提供的setLocalDescription()方法,將乙的SDP描述符交給乙的PC實例
- 乙將answer信令經過服務器發送給甲
- 甲接收到乙的answer信令後,將其中乙的SDP描述符提取出來,調用setRemoteDescripttion()方法交給甲本身的PC實例
經過ICE框架創建NAT/防火牆穿越的鏈接:
WebRTC使用ICE框架來得到這個外界能夠直接訪問的地址,RTCPeerConnection在創立的時候能夠將ICE服務器的地址傳遞進去,如:
var iceServer = { "iceServers": [{ "url": "stun:stun.l.google.com:19302" }] }; var pc = new RTCPeerConnection(iceServer);
- 甲、乙各建立配置了ICE服務器的PC實例,併爲其添加onicecandidate事件回調
- 當網絡候選可用時,將會調用onicecandidate函數
- 在回調函數內部,甲或乙將網絡候選的消息封裝在ICE Candidate信令中,經過服務器中轉,傳遞給對方
- 甲或乙接收到對方經過服務器中轉所發送過來ICE Candidate信令時,將其解析並得到網絡候選,將其經過PC實例的addIceCandidate()方法加入到PC實例中
這樣鏈接就創立完成了,能夠向RTCPeerConnection中經過addStream()加入流來傳輸媒體流數據。
瀏覽器位於網絡地址轉換設備(NAT)以後是一種極爲廣泛的設計。舉個栗子:
再來看個圖,瞭解下「公共地址」和「私有地址」:
NAT主要負責維護內部ip地址和端口號與外部ip地址和端口號之間的映射表。
STUN,Session Traversal Utilities for NAT,稱爲NAT會話遍歷實用工具服務器。簡單地說,就是獲取內網設備的最外層NAT(公共ip地址)信息。
TURN,Traversal Using Relay around NAT,稱爲中繼型NAT遍歷服務器。
說明: 媒體中繼地址是一個公共地址,用於轉發接收到的包,或者將收到的數據包轉發給瀏覽器。若是兩個對等端由於NAT類型等緣由不能直接創建P2P鏈接的話,那麼可使用中繼地址。 ps:相比較直接使用web服務器提供媒體中繼理想點。
上一節中有簡單介紹對等鏈接和offer/answer交互流程,這節再說明下。
其實WebRTC定義了兩組主要的功能,分別是:媒體捕獲(getUserMedia(),前面已介紹)、媒體傳輸。對等鏈接和提議/應答協商的概念是媒體傳輸的核心。
RTCPeerConnection接口是WebRTC的主要API,用來在P2P端創建媒體鏈接及數據鏈接路徑。RTCPeerConnection對象的構造函數有一系列屬性,最主要的是iceServers屬性,表示服務器地址列表。用於幫助透過NAT和防火牆創建會話。
var pc = new RTCPeerConnection({ iceServers: [{ url: 'stun:stun.l.google.com:19302' },{ url: 'turn:user@turn.myserver.com', credential: 'test' }] }) getUserMedia({ audio: true, video: true }, successCB, failureCB) function successCB(stream) { // 告知瀏覽器,我要發送MediaStream pc.addStream(stream) // removeStream() }
要在兩者之間創建鏈接,必須在兩者之間創建會話。offer/answer是一種「一次性經過」型協商機制。實際中該過程可能會反覆屢次。
WebRTC使用RTCSessionDescription對象表示提議和應答。每一個瀏覽器都將生成一個該對象。
本地瀏覽器只關注兩個特定的調用:
// 將個人會話描述告知個人瀏覽器 pc.setLocalDescription(mySessionDescription) ... // 將對等端的會話描述告知個人瀏覽器 pc.setRemoteDescription(yourSessionDescription)
生成提議、應答:
// 生成提議 pc.createOffer(gotOffer, didntGetOffer) function gotOffer(aSessionDescription) { setLocalDescription(aSessionDescription) ... // 如今能夠將會話描述(提議offer)發送給對等端,以便對等端 // a)、將提議傳遞給setRemoteDescription // b)、調用createAnswer } // 生成應答 pc.createAnswer(gotAnswer, didntGetAnswer) function gotAnswer(aSessionDescription) { setLocalDescription(aSessionDescription) ... // 如今將會話描述(應答answer)發送給對等端,以便對等端 // a)、將應答傳遞給setRemoteDescription }
如下測試demo展現在兩個瀏覽器中進行實時視頻通話,源碼地址:https://github.com/caiya/webrtc-p2p.git
RTCDataChannel,數據通道是瀏覽器之間創建的非媒體的交互鏈接。即不傳遞媒體消息,繞過服務器直接傳遞數據。相比WebSocket、http消息,數據通道支持流量大、延遲低。
注意: 單個對等鏈接中的多個數據通道底層共享一個流,因此只需一次offer、answer便可創建首個數據通道。以後再創建數據通道無需再次進行offer、answer交換。 典型應用:遊戲實時狀態更新。
只有在建立完RTCPeerConnection實例以後才能建立數據通道,以下:
pc = new RTCPeerConnection() dc = pc.createDataChannel('')
一端建立完數據通道後,另外一端只須要監聽ondatachannel事件便可:
pc = new RTCPeerConnection() pc.ondatachannel = function(e) { dc = e.channel }
此時,兩個對等端已經彼此創建數據通道,能夠直接相互發送消息:
dc.send('i am a text string for sending') dc.send(new Blob(['i am a blob object'], {type: 'text/plain'})) dc.send(new arrayBuffer(32)) // 發送arrayBuffer dc.onmessage = function(e) { console.log('收到消息:', e.data) }
項目源代碼地址:https://github.com/caiya/webrtc-p2p-datachannel
部分截圖:
做者 @晁州 2017 年 11月 27日