Android IOS WebRTC 音視頻開發總結(九)-- webrtc入門001

下面這篇介紹webrtc的文章不錯,我花了大半天翻譯了一下.javascript

翻譯的時候不是逐字逐句的,而是按照本身的理解翻譯的,同時爲了便於理解,也加入一些本身組織的語言.css

 

本文主要介紹webrtc的信令,stun,turn,轉載請說明出處(博客園RTC.Blacker).html

英文來自:http://www.html5rocks.com/en/tutorials/webrtc/infrastructure/ html5

 

WEBRTC支持點對點通信,可是WEBRTC仍然須要服務端,由於:java

1,爲了協調通信過程客戶端之間須要交換元數據,如一個客戶端找到另外一個客戶端以及通知另外一個客戶端開始通信.node

2,須要處理NAT或防火牆,這是公網上通信首要處理的問題.git

在這篇文章裏咱們將告訴您怎麼建立一個信令服務,怎麼處理現實世界中兩個客戶端的鏈接,以及怎麼處理多方通話和怎麼與VOIP,PSTN的交互.若是您不瞭解webrtc,建議您讀這篇文章前先看:http://www.html5rocks.com/en/tutorials/webrtc/basics/github

 

什麼是信令?web

信令就是協調通信的過程,爲了創建一個webrtc的通信過程,客戶端須要交換以下信息:chrome

1,會話控制消息:用來開始和結束通話(即開始視頻,結束視頻這些操做指令)

2,處理錯誤的消息.

3,元數據:如各自的音視頻編解碼方式,帶寬.

4,網絡數據:對方的公網IP,端口,內網IP,端口.

5,......

信令處理過程須要客戶端可以來回傳遞消息,這個過程在webrtc裏面是沒有實現的,須要您本身建立,下面咱們會告訴您怎麼建立這樣一個過程.

 

爲何WEBRTC沒有定義信令處理?

爲了不重複定義和最大程度兼容現有技術,JSEP(JavaScript Session Establishment Protocol)上已有概述.

現有的SIP協議就能夠較好地處理整個信令過程,另外不一樣的應用程序可能對信令處理有特別的要求,如咱們作的不少項目信令處理都是本身寫的,很靈活.

其實只要你能知足你本身的業務需求,信令處理你徹底能夠本身定義,實現起來也不難,就是客戶端和服務端怎麼通信而已,用得最廣的就是websocket了,後面會介紹.

以下是JSEP定義的客戶端通信架構:

JSEP 架構

JSEP要求客戶端之間交換offer和answer:其實就是上面提到的元數據,他們是以SDP格式進行交換,格式以下:

 1 v=0  2 o=- 7614219274584779017 2 IN IP4 127.0.0.1  3 s=-  4 t=0 0  5 a=group:BUNDLE audio video  6 a=msid-semantic: WMS  7 m=audio 1 RTP/SAVPF 111 103 104 0 8 107 106 105 13 126  8 c=IN IP4 0.0.0.0  9 a=rtcp:1 IN IP4 0.0.0.0 10 a=ice-ufrag:W2TGCZw2NZHuwlnf 11 a=ice-pwd:xdQEccP40E+P0L5qTyzDgfmW 12 a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level 13 a=mid:audio 14 a=rtcp-mux 15 a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:9c1AHz27dZ9xPI91YNfSlI67/EMkjHHIHORiClQe 16 a=rtpmap:111 opus/48000/2 17

 

若是您對SDP格式有興趣,能夠參考:IETF examples

在webrtc架構裏面調用setLocalDiscription,setRemoteDiscription前可經過編輯SDP裏面的值來更改offer和anser.如apprtc.appspot.com 中得preferAudioCodec()能用來設置默認的音頻編碼和碼率,sdp用javascript修改起來可能有點痛苦,W3C組織有在討論經過jason方式來編輯,不過目前這種方式也有些優勢(some advantages).

 

RTCPeerConnection + signaling: offer, answer and candidate

RTCPeerConnection就是webrtc應用程序用來建立客戶端鏈接和視頻通信的API.爲了初始化這個過程 RTCPeerConnection有兩個任務:

  1,肯定本地媒體條件,如分辨率,編解碼能力,這些須要在offer和answer中用到.

  2,取到應用程序所在機器的網絡地址,即稱做candidates.

一旦上面這些東西肯定了,他們將經過信令機制和遠端進行交換.

想象一下Alice呼叫Eve的過程( Alice is trying to call Eve.),下面就是完整offer/answer機制的細節:

1,Alice建立一個 RTCPeerConnection對象.

2,Alice建立一個offer(即SDP會話描述)經過RTCPeerConnection createOffer()方法.

3,Alice調用setLocalDescription()方法用他的offer.

4,Alice經過信令機制將他的offer發給Eve.

5,Eve調用setRemoteDescription()方式設置Alice的offer,所以他的RTCPeerConnection知道了Alice的設置.

6,Eve調用方法createAnswer(),而後會觸發一個callback,這個callback裏面能夠去到本身的answer.

7,Eve設置他本身的anser經過調用方法setLocalDescription().

8,Eve經過信令機制將他的anser發給Alice.

9,Alice設置Eve的anser經過方法setRemoteDescription().

 

另外Alice和Eve也須要交換網絡信息(即candidates),發現candidates參考了ICE framework.

1,Alice建立RTCPeerConnection對象時設置了onicecandidate handler.

2,hander被調用當candidates找到了的時候.

3,當Eve收到來自Alice的candidate消息的時候,他調用方法addIceCandidate(),添加candidate到遠端描述裏面.

JSEP支持ICE Candidate Trickling,他容許呼叫方在offer初始化結束後提供candidates給被叫方.而被叫方開始創建呼叫和鏈接而不須要等到全部candidate到達.

 

Coding WebRTC for signaling

下面是一個W3C的例子(W3C code example)歸納了一個完整的信令過程,他裏面假設已經存在信令機制:SignalingChannel,信令在下面被詳細討論

 1 var signalingChannel = new SignalingChannel();  2 var configuration = {  3   'iceServers': [{  4     'url': 'stun:stun.example.org'
 5  }]  6 };  7 var pc;  8 
 9 // call start() to initiate
10 
11 function start() { 12   pc = new RTCPeerConnection(configuration); 13 
14   // send any ice candidates to the other peer
15   pc.onicecandidate = function (evt) { 16     if (evt.candidate) 17  signalingChannel.send(JSON.stringify({ 18         'candidate': evt.candidate 19  })); 20  }; 21 
22   // let the 'negotiationneeded' event trigger offer generation
23   pc.onnegotiationneeded = function () { 24  pc.createOffer(localDescCreated, logError); 25  } 26 
27   // once remote stream arrives, show it in the remote video element
28   pc.onaddstream = function (evt) { 29     remoteView.src = URL.createObjectURL(evt.stream); 30  }; 31 
32   // get a local stream, show it in a self-view and add it to be sent
33  navigator.getUserMedia({ 34     'audio': true, 35     'video': true
36   }, function (stream) { 37     selfView.src = URL.createObjectURL(stream); 38  pc.addStream(stream); 39  }, logError); 40 } 41 
42 function localDescCreated(desc) { 43   pc.setLocalDescription(desc, function () { 44  signalingChannel.send(JSON.stringify({ 45       'sdp': pc.localDescription 46  })); 47  }, logError); 48 } 49 
50 signalingChannel.onmessage = function (evt) { 51   if (!pc) 52  start(); 53 
54   var message = JSON.parse(evt.data); 55   if (message.sdp) 56     pc.setRemoteDescription(new RTCSessionDescription(message.sdp), function () { 57       // if we received an offer, we need to answer
58       if (pc.remoteDescription.type == 'offer') 59  pc.createAnswer(localDescCreated, logError); 60  }, logError); 61   else
62     pc.addIceCandidate(new RTCIceCandidate(message.candidate)); 63 }; 64 
65 function logError(error) { 66   log(error.name + ': ' + error.message); 67 }

 

瞭解offer,anser,candidate交換過程,可經過simpl.info/pc上視頻聊天的控制檯日誌,若是您想了解更多,能夠下載完整的WebRTC signaling and stats from the chrome://webrtc-internals page in Chrome or the opera://webrtc-internals page in Opera.

 

怎麼發現客戶端

這裏有一種很簡單的表述方式---我怎麼找到別人視頻?

打電話的時候咱們有電話號碼和電話本,知道打給誰,QQ聊天的時候,咱們能夠經過通信錄找到要聊天的人,webrtc也同樣,他的客戶端須要經過一種方式找到要聊天的人或要加入的會議.

webrtc沒有定義這樣一個發現過程,這個其實很簡單,能夠參考 talky.iotawk.com and browsermeeting.com,另外Chris Ball建立了serverless-webrtc,他能夠經過Emai,IM來參與視頻.

 

怎麼建立信令服務?

再次重申:webrtc沒有定義信令機制,所以不管你選擇什麼機制你都的須要一臺中間服務端,用來在客戶端之間交換數據,你總不可能直接說:"跟我朋友視頻?",

因爲信令消息很小,大多數交互都是在開始通話以前,能夠參考 apprtc.appspot.com and samdutton-nodertc.jit.su, 測試發現:一個視頻通話過程大概有35~40消息,數據量在10K左右,

因此相對來講信令服務器不怎麼佔帶寬,也不須要消耗多大的CPU和內存.

從服務端推送消息給客戶端

信令服務器推送消息須要時雙向的,即客戶端能發消息給服務器,服務器也能發消息給服務端,這種雙向機制就將Http給排除了(固然可使用長鏈接,並且不少人都是這麼作的,只不過比較佔資源).

說到這裏不少人會想到WebSocket,沒錯,這是一種很好的解決方案,並且後臺實現框架也不少,如PHP,Python,Ruby.

大約3/4的瀏覽器支持webSocekt,更重要的是支持WEBRTC的瀏覽器都支持WebSocket,包括PC和手機, TLS應該被使用爲了全部鏈接,他能確保爲被加密的消息不被截獲,同時也能減小使用代理帶來的問題(reduce problems with proxy traversal),更多這方面的知識請參考 WebRTC chapterWebSocket Cheat Sheet .

apprtc.appspot.com中的視頻通信使用的信令是 Google App Engine Channel API,他採用的是 Comet技術, HTML5 Rocks WebRTC article有詳細的介紹(detailed code walkthrough)

固然你也能夠經過Ajax來實現這樣一個長鏈接,不過這樣會產生不少重複的網絡請求,並且應用在移動端會有不少問題.

擴展信令的實現

儘管信令服務佔用的CPU和帶寬資源都比較少,但實際應用中若是要考慮到高併發,信令服務仍是有很大負載的.這些咱們不深刻討論了,下面有一些不錯的選擇供參考:

1,eXtensible Messaging and Presence Protocol(XMPP):主要是用來給即時通信用的,開源服務端包括ejabberd and Openfire. 客戶端包括 Strophe.js use BOSH(但由於 various reasons,BOSH沒有WebSocket高效),補充說明:Jingle是XMPP的擴展,支持音視頻,webrtc項目裏面的network和transort組件就是來自 libjingle庫.

2,開源庫如 ZeroMQOpenMQ

Developer Phil Leggetter's Real-Time Web Technologies Guide 提供了一個消息服務和庫的綜合清單.

使用Nodejs上的Socket.io實現一個信令服務

下面這個代碼是一個簡單的web應用,使用了 Socket.io on Node,  socket.io的設計目標就是爲了簡化消息通信服務的建立,特別適合做爲webrtc的信令,由於他內嵌了房間的概念,下面這個樣例設計主要是爲了少許用戶的使用,並無考慮太多的擴展性.

下面代碼主要用來介紹怎麼建立信令服務,能夠經過查看日誌來了解客戶端加入房間時交換的消息過程, WebRTC codelab提供了怎麼集成這個例子到webrtc視頻通信中的一步步的完整說明.你能從 step 5 of the codelab repo 下載代碼或直接進入 samdutton-nodertc.jit.su查看(用瀏覽器打開兩個URL便可).

下面是客戶端的 index.html:

 1 <!DOCTYPE html>
 2 <html>
 3   <head>
 4     <title>WebRTC client</title>
 5   </head>
 6   <body>
 7     <script src='/socket.io/socket.io.js'></script>
 8     <script src='js/main.js'></script>
 9   </body>
10 </html>

 

客戶端的JS

 1 var isInitiator;  2 
 3 room = prompt('Enter room name:');  4 
 5 var socket = io.connect();  6 
 7 if (room !== '') {  8  console.log('Joining room ' + room);  9  socket.emit('create or join', room); 10 } 11 
12 socket.on('full', function (room){ 13  console.log('Room ' + room + ' is full'); 14 }); 15 
16 socket.on('empty', function (room){ 17  isInitiator = true; 18  console.log('Room ' + room + ' is empty'); 19 }); 20 
21 socket.on('join', function (room){ 22  console.log('Making request to join room ' + room); 23  console.log('You are the initiator!'); 24 }); 25 
26 socket.on('log', function (array){ 27  console.log.apply(console, array); 28 });

 

完整服務端代碼:

 1 var static = require('node-static');  2 var http = require('http');  3 var file = new(static.Server)();  4 var app = http.createServer(function (req, res) {  5  file.serve(req, res);  6 }).listen(2013);  7 
 8 var io = require('socket.io').listen(app);  9 
10 io.sockets.on('connection', function (socket){ 11 
12  // convenience function to log server messages to the client 13  function log(){ 14  var array = ['>>> Message from server: ']; 15     for (var i = 0; i < arguments.length; i++) { 16  array.push(arguments[i]); 17  } 18  socket.emit('log', array); 19  } 20 
21  socket.on('message', function (message) { 22  log('Got message:', message); 23  // for a real app, would be room only (not broadcast) 24  socket.broadcast.emit('message', message); 25  }); 26 
27  socket.on('create or join', function (room) { 28  var numClients = io.sockets.clients(room).length; 29 
30     log('Room ' + room + ' has ' + numClients + ' client(s)'); 31  log('Request to create or join room ' + room); 32 
33  if (numClients === 0){ 34  socket.join(room); 35  socket.emit('created', room); 36  } else if (numClients === 1) { 37  io.sockets.in(room).emit('join', room); 38  socket.join(room); 39  socket.emit('joined', room); 40  } else { // max two clients 41  socket.emit('full', room); 42  } 43  socket.emit('emit(): client ' + socket.id + ' joined room ' + room); 44  socket.broadcast.emit('broadcast(): client ' + socket.id + ' joined room ' + room); 45 
46  }); 47 
48 });

 

若是須要運行上面這個app,須要用到node,詳見 nodejs.org,很好很強大的一個東東,我後面會翻譯一篇介紹nodejs的文章.

其實無論你用什麼方式建立信令服務,您的後臺和客戶端最少須要具備樣例代碼中的功能.

使用RTCDataChannel控制信令

一旦信令服務創建好了,兩個客戶端之間創建了鏈接,理論上他們就可使用RTCDataChannel進行點對點通信了,這樣能夠減輕信令服務的壓力和消息傳遞的延遲,這部分沒有提供Demo.

使用已有信令服務

若是您不想本身動手,這裏還有提供幾個webrtc信令服務器,與上述代碼相似他們使用socket.io. 與webrtc客戶端的javascript集成到一塊兒了.

webRTC.io:webrtc的第一個抽想庫.

easyRTC:一個完整的webrtc庫.

Signalmaster:信令服務器,和 SimpleWebRTC做爲客戶端腳本庫配套使用.

若是您不想寫任何代碼的花,能夠直接使用現有商業產品:vLineOpenTok and Asterisk.

若是您想實現錄製功能,可參考 signaling server using PHP on Apache,雖然已通過時了,但代碼可供參考.

信令安全性問題

由於信令使咱們本身定義的,因此安全性問題跟webrtc無關,須要本身處理.一旦黑客掌握了你的信令,那他就是控制會話的開始,結束,重定向等等.

最重要的因素在信令安全中仍是要靠使用安全協議,如HTTPS,WSS(如TLS),他們能確保未加密的消息不能被截取.

爲確保信令安全,強烈推薦使用TLS.

使用ICE處理NATs和防火牆

元數據是經過信令服務器中轉發給另外一個客戶端,可是對於流媒體數據,一旦會話創建,RTCPeerConnection將首先嚐試使用點對點鏈接.

簡單一點說就是:每一個客戶端都有一個惟一的地址,他能用來和其餘客戶端進行通信和數據交換.

A world without NATs and firewalls

現實生活中客戶端都位於一個或多個NAT以後,或者一些殺毒軟件還阻止了某些端口和協議,或者在公司還有防火牆或代理,等等,防火牆和NAT或許是同一個設備,如咱們家裏用的路由器.

The real world

webrtc就是經過 ICE這套框架來處理複雜的網絡環境的,若是想啓用這個功能,你必須讓你得應用程序傳ice服務器的URL給RTCPeerConnection,描述以下:

ICE試着找最好的路徑來讓客戶端創建鏈接,他會嘗試全部可能的選項,而後選擇最合適的方案,ICE首先嚐試P2P鏈接,若是失敗就會經過Turn服務器進行轉接.

換一個說法就是:

1,STUN服務器是用來取外網地址的.

2,TURN服務器是在P2P失敗時進行轉發的.

每一個TURN服務器都支持STUN,ICE處理複雜的NAT設置,同時NAT打洞要求不止一個公網IP和端口.

javascript中ice配置以下:

 1 {  2  'iceServers': [  3  {  4  'url': 'stun:stun.l.google.com:19302'  5  },  6  {  7  'url': 'turn:192.158.29.39:3478?transport=udp',  8  'credential': 'JZEOEt2V3Qb0y27GRntt2u2PAYA=',  9  'username': '28224511:1379330808' 10  }, 11  { 12  'url': 'turn:192.158.29.39:3478?transport=tcp', 13  'credential': 'JZEOEt2V3Qb0y27GRntt2u2PAYA=', 14  'username': '28224511:1379330808' 15  } 16  ] 17 }

 

一旦RTCPeerConnection取到了所要的信息,ICE過程就自動發生了,RTCPeerConnection使用ICE框架取到兩點之間最好的路徑,固然這個過程離不開STUN和TURN的支持.

STUN

NAT的做用就是提供內外網端口的映射,由於在公網上兩個內網客戶端要創建直接鏈接就不準先知道彼此對應的公網地址和端口,這時候知道對方內網IP和地址是沒用的.

而STUN的做用就是讓客戶端發現本身的公網IP和端口,因此負載不大,同時目前免費得STUN服務器也不少.一搜一大把.

經過webrtcstats.com可知85%的狀況下能夠P2P,固然複雜NAT和網絡環境下這個機率會更低.

Using STUN servers to get public IP:port addresses

TURN

RTCPeerConnection首先嚐試使用P2P,若是失敗,他將求助於TCP,使用turn轉發兩個端點的音視頻數據.

重申:turn轉發的是兩個端點之間的音視頻數據,不是信令數據.

由於TURN服務器是在公網上,因此他能被各個客戶端找到,另外TURN服務器轉發的是數據流,很佔用帶寬和資源.

部署STUN和TURN服務器

google提供了stun.l.google.com:19302供測試, apprtc.appspot.com用的就是這個stun服務器,實際應用中,咱們推薦使用rfc5766-turn-server,同時也提供了一些鏈接源: VM image for Amazon Web Services

turn服務器的安裝後面我專門寫篇文章來介紹,做者寫的那種方式我也沒有嘗試過,不過看起來比較複雜.有興趣的能夠去看原文.

 

下面這幾部分我放到下一篇文章介紹,內容太多,你們會看得很暈

Beyond one-to-one: multi-party WebRTC

Multipoint Control Unit

Beyond browsers: VoIP, telephones and messaging

相關文章
相關標籤/搜索