簡介
全稱是:
Web browser Real Time Communication 特點如下:
- 是基于浏覽器的實時音視訊(資料)通信技術
- 免插件
- 開源
- 已被W3C納入HTML5标準
- 跨平台,跨浏覽器,跨移動應用
- Mac OSX、Windows、iOS、Android、Linux
應用場景
适用于網頁間音視訊實時通信,點對點資料共享,QQ、騰訊視訊已有應用
優勢
1.友善。對于使用者來說,在WebRTC出現之前想要進行實時通信就需要安裝插件和用戶端,但是對于很多使用者來說,插件的下載下傳、軟體的安裝和更新這些操作是複雜而且容易出現問題的,現在WebRTC技術内置于浏覽器中,使用者不需要使用任何插件或者軟體就能通過浏覽器來實作實時通信。 2.免費。雖然WebRTC技術已經較為成熟,其內建了最佳的音/視訊引擎,十分先進的codec,但是Google對于這些技術不收取任何費用。 3.強大的打洞能力。WebRTC技術包含了使用STUN、ICE、TURN、RTP-over-TCP的關鍵NAT和防火牆穿透技術,并支援代理。
缺點
1.傳輸品質難以保證,比如跨地區、跨營運商、低帶寬、高丢包、P2P連接配接率、呼叫成功率。 2.裝置端适配,如回聲、錄音失敗等問題層出不窮。這一點在安卓裝置上尤為突出。由于安卓裝置廠商衆多,每個廠商都會在标準的安卓架構上進行定制化,導緻很多可用性問題(通路麥克風失敗)和品質問題(如回聲、嘯叫)。
WebRTC媒體會話原理

WebRTC内部結構簡化圖
WebRTC架構圖(截圖來自官網https://webrtc.org/)
WebRTC核心技術點,簡要概括為三部分
下文詳細介紹WebRTC核心API和信令伺服器部分
WebRTC 核心API詳解
運用RTCPeerConnection和RTCDataChannel兩個核心API,能夠實作任意資料的點對點交換,官網Demo如下:
該Demo不需要servers,因為呼叫方(發送資料)和呼叫應答方(接收資料)在同一頁面上,這樣能夠清晰的了解RTCPeerConnection API的原理,頁面上的RTCPeerConnection對象可以直接交換資料和消息,而無需使用信令伺服器。 可以用開發者工具檢視WebRTC統計資訊
- Chrome:chrome://webrtc-internals
- Opera:opera://webrtc-internals
- FireFox:about:webrtc chrome開發者工具中檢視WebRTC,如下圖所示:
Demo 代碼分析
以Demo為例,分析Web P2P建立、通信、傳輸資料等流程,具體分析API中各個關鍵屬性、方法、事件的含義和标準操作姿勢 完整源碼見Github
function createConnection() { sendButton.disabled = true; megsToSend.disabled = true; var servers = null; bytesToSend = Math.round(megsToSend.value) * 1024 * 1024; // 建立連接配接,servers可以傳入一些描述資訊,由于這個demo不需要驗證連接配接資訊,在同一個頁面上可以直接連接配接,該參數傳null即可 localConnection = localConnection = new RTCPeerConnection(servers); //列印log trace('Created local peer connection object localConnection'); var dataChannelParams = {ordered: false}; if (orderedCheckbox.checked) { dataChannelParams.ordered = true; } //建立資料通道 文法:dataChannel = RTCPeerConnection .createDataChannel(label [,options ]);,lable:通道的名稱;optins:是個可選參數,傳入資料通道配置參數,有很多參數可選,例子中的ordered:true表示有序模式,false即為無序模式,還有其他參數maxPacketLifeTime 、maxRetransmits等等 sendChannel = localConnection.createDataChannel( 'sendDataChannel', dataChannelParams); sendChannel.binaryType = 'arraybuffer'; trace('Created send data channel'); //綁定onopen、onclose、onicecandidate(當RTCPeerConnection被createPeerConnection()成功建立時觸發,回調會傳回待連接配接端的配置資訊) sendChannel.onopen = onSendChannelStateChange; sendChannel.onclose = onSendChannelStateChange; localConnection.onicecandidate = function(e) { onIceCandidate(localConnection, e); }; //建立呼叫執行個體 localConnection.createOffer().then( gotDescription1, onCreateSessionDescriptionError ); //建立遠端接收連接配接執行個體 remoteConnection = remoteConnection = new RTCPeerConnection(servers); trace('Created remote peer connection object remoteConnection'); remoteConnection.onicecandidate = function(e) { onIceCandidate(remoteConnection, e); }; //當一個RTC資料通道已被遠端調用createDataChannel()添加到連接配接中時觸發 remoteConnection.ondatachannel = receiveChannelCallback;}function receiveChannelCallback(event) { trace('Receive Channel Callback'); receiveChannel = event.channel; receiveChannel.binaryType = 'arraybuffer'; //接收到資料時觸發 receiveChannel.onmessage = onReceiveMessageCallback; receivedSize = 0;}function onReceiveMessageCallback(event) { receivedSize += event.data.length; receiveProgress.value = receivedSize; if (receivedSize === bytesToSend) { closeDataChannels(); sendButton.disabled = false; megsToSend.disabled = false; }}function onSendChannelStateChange() { var readyState = sendChannel.readyState; trace('Send channel state is: ' + readyState); if (readyState === 'open') { sendGeneratedData(); }}function sendGeneratedData() { sendProgress.max = bytesToSend; receiveProgress.max = sendProgress.max; sendProgress.value = 0; receiveProgress.value = 0; var chunkSize = 16384; var stringToSendRepeatedly = randomAsciiString(chunkSize); var bufferFullThreshold = 5 * chunkSize; var usePolling = true; if (typeof sendChannel.bufferedAmountLowThreshold === 'number') { trace('Using the bufferedamountlow event for flow control'); usePolling = false; // 緩沖區大小限值 bufferFullThreshold = chunkSize / 2; // 緩沖區大小控制 sendChannel.bufferedAmountLowThreshold = bufferFullThreshold; } // bufferedamountlow 事件處理 var listener = function() { sendChannel.removeEventListener('bufferedamountlow', listener); sendAllData(); }; var sendAllData = function() { // 把一堆資料排隊進行處理,在資料通道被填滿時停止,這裡不建議每次發送後設定Timeout,這樣會降低吞吐量 while (sendProgress.value < sendProgress.max) { if (sendChannel.bufferedAmount > bufferFullThreshold) { if (usePolling) { setTimeout(sendAllData, 250); } else { sendChannel.addEventListener('bufferedamountlow', listener); } return; } sendProgress.value += chunkSize; // send方法發送資料,RTCDataChannel的文法跟WebSocket文法非常相似,都有message事件和sand方法 sendChannel.send(stringToSendRepeatedly); } }; setTimeout(sendAllData, 0);}
複制
WebRTC核心API相容性
MediaStream and getUserMedia
- Chrome desktop 18.0.1008+; Chrome for Android 29+
- Opera 18+; Opera for Android 20+
- Opera 12, Opera Mobile 12 (基于Presto引擎)
- Firefox 17+
- Microsoft Edge
RTCPeerConnection
- Chrome desktop 20+ (now ‘flagless’, i.e. no need to set about:flags); * * Chrome for Android 29+ (flagless)
- Opera 18+ (預設開啟); Opera for Android 20+ (預設開啟)
- Firefox 22+ (預設開啟)
RTCDataChannel
- Chrome 25中的實驗版本,在Chrome 26+中更穩定(and with Firefox interoperability); Chrome for Android 29+
- Opera 18+中的穩定版本(and with Firefox interoperability); Opera for * * * Android 20+
- Firefox 22+ (預設開啟)
信令伺服器
信令就是協調通訊的過程,為了建立一個webRTC的通訊過程,用戶端需要交換如下資訊:
- 會話控制資訊,用來開始和結束通話,即開始視訊、結束視訊這些操作指令。
- 處理錯誤的消息。
- 中繼資料,如各自的音視訊解碼方式、帶寬。
- 網絡資料,對方的公網IP、端口、内網IP及端口。 我們需要一個中間伺服器來在用戶端之間交換信令消息和資料,這個過程在WebRTC裡面是沒有實作的,但WebRTC協定沒有規定與伺服器的通信方式,是以可以采用各種方式,比如WebSocket。初學者可以用NodeJS搭建簡易的信令伺服器,交換雙方的中繼資料,真實項目裡還會有STUN和TURN伺服器 。
下面是NodeJS建立信令伺服器的源碼:
'use strict';var os = require('os');var nodeStatic = require('node-static');var http = require('http');var socketIO = require('socket.io');var fileServer = new(nodeStatic.Server)();var app = http.createServer(function(req, res) { fileServer.serve(req, res);}).listen(8080);var io = socketIO.listen(app);io.sockets.on('connection', function(socket) { // 列印日志功能 function log() { var array = ['Message from server:']; array.push.apply(array, arguments); socket.emit('log', array); } socket.on('message', function(message) { log('Client said: ', message); // 本示例使用廣播方式,真實項目中應該是指定房間号(Socket.IO适用于學習WebRTC信号,因為它内置了'房間'的概念) socket.broadcast.emit('message', message); }); socket.on('create or join', function(room) { log('Received request to create or join room ' + room); var clientsInRoom = io.sockets.adapter.rooms[room]; var numClients = clientsInRoom ? Object.keys(clientsInRoom.sockets).length : 0; log('Room ' + room + ' now has ' + numClients + ' client(s)'); if (numClients === 0) { socket.join(room); log('Client ID ' + socket.id + ' created room ' + room); socket.emit('created', room, socket.id); } else if (numClients === 1) { log('Client ID ' + socket.id + ' joined room ' + room); io.sockets.in(room).emit('join', room); socket.join(room); socket.emit('joined', room, socket.id); io.sockets.in(room).emit('ready'); } else { // 最多兩個用戶端 socket.emit('full', room); } }); socket.on('ipaddr', function() { var ifaces = os.networkInterfaces(); for (var dev in ifaces) { ifaces[dev].forEach(function(details) { if (details.family === 'IPv4' && details.address !== '127.0.0.1') { socket.emit('ipaddr', details.address); } }); } });});
複制
利用WebRTC相關技術有很多可以創新的點,比如業界已有創業團隊在做Web P2P,核心技術就是WebRTC + DASH協定,共享空閑資源,基于此可以做霧CDN,節點都在使用者側,去中心化,這裡還是有很多挖掘空間的。
參考資料
https://webrtc.org/
https://developer.mozilla.org/zh-CN/docs/Web/API/WebRTC_API
https://hpbn.co/webrtc/
https://webrtchacks.com/https://codelabs.developers.google.com/codelabs/webrtc-web/#0