基於 WebRTC 的視頻聊天技術在 iOS 端的實現
簡介
全稱是: Web browser Real Time Communication
特點如下:
- 是基於瀏覽器的實時音視頻(數據)通信技術
- 免插件
- 開源
- 已被W3C納入HTML5標準
- 跨平台,跨瀏覽器,跨移動應用
- Mac OSX、Windows、iOS、Android、Linux
Google的推動
最初是谷歌2010年以6820萬美元收購Global IP Solutions(GIPS)公司而獲得的一項技術,它使得Web中的實時通訊成為可能,是一項能夠在瀏覽器內部進行實時音頻和視頻通信的技術
當瀏覽器實現對應音視頻組件後,開發者可以容易地通過JS API 實現他們自己的RTC web 應用
現在已經被推為W3C的標準,名稱為WebRTC
已經支持不支持Chrome,FireFox,Opera,微軟edgeSafari,IE誰在使用WebRTC技術
- YY
- skype
- VoIP電話:KC網路電話;
- 在線教育:猿題庫
QQ多年前開始,騰訊QQ視頻聊天室購買的國外Global IP(GIPS)公司技術
應用場景
- 視頻聊天,如QQ、YY、Skype
- VoIP與視頻通話產品:KC網路電話
- 在線會議
- 遠程醫療
- 在線教育
- 互聯網安防監控
目前支持的平台
- Chrome
- Chrome for Android
- Firefox
- Opera
- Native C++、Java and Objective-C bindings
WebRTC相關API介紹
功能劃分
- 獲取音頻和視頻數據
- 傳輸音頻和視頻數據
傳輸任意二進位數據
API劃分:三個JS介面
MediaStream (又叫getUserMedia)
- RTCPeerConnection (C++)
- RTCDataChannel
MediaStream (getUserMedia)
- 抽象表示一個音頻或者視頻流
- 可包含多個音視頻記錄
- 通過 navigator.getUserMedia() 獲取
(參考:https://w3c.github.io/mediacapture-main/archives/20140909/getusermedia.html )
getUserMedia:
JS
var constraints = {video: true};nfunction successCallback(stream) {n var video = document.querySelector("video")n video.src = window.URL.createObjectURL(stream);n}nnfunction errorCallback(error) {n console.log("navigator.getUserMedia error:", error);n}nnnavigator.getUserMedia(constraints, successCallback, errorCallback);n
Objective-C
- (RTCVideoTrack *)createLocalVideoTrackBackCamera {n RTCVideoTrack *videoTrack = nil;n RTCMediaConstraints *constraints = [[RTCMediaConstraints alloc] initWithMandatoryConstraints:nil optionalConstraints:nil];n [videoSource setUseBackCamera:YES];n RTCPeerConnectionFactory *factory = [[RTCPeerConnectionFactory alloc] init];n RTCAVFoundationVideoSource *videoSource = [factory avFoundationVideoSourceWithConstraints:constraints];n videoTrack = [factory videoTrackWithSource:videoSource trackId:[self videoTrackId]];n return videoTrack;n}nn- (RTCMediaStream *)createLocalMediaStream {n RTCMediaStream *localStream = _peerConnection.localStreams[0];n [localStream removeVideoTrack:localStream.videoTracks[0]];n RTCMediaConstraints *videoConstraints = [[RTCMediaConstraints alloc] initWithMandatoryConstraints:nil optionalConstraints:nil];n RTCVideoTrack *localVideoTrack = [self localVideoTrackWithConstraints:videoConstraints];n if (localVideoTrack) {n [localStream addVideoTrack:localVideoTrack];n [self didReceiveLocalVideoTrack:localVideoTrack];n }n return localStream;n}n
其中的 constraints 介紹下:
控制MediaStream的內容:媒體類型、解析度、幀率;
JS
video: {n mandatory: {n minWidth: 640,n minHeight: 360n },n optional [{n minWidth: 1280,n minHeight: 720n }]n}n
Objective-C
//RTCMediaConstraints.hnnRTC_EXTERN NSString * const kRTCMediaConstraintsMinAspectRatio;nRTC_EXTERN NSString * const kRTCMediaConstraintsMaxAspectRatio;nRTC_EXTERN NSString * const kRTCMediaConstraintsMaxWidth;nRTC_EXTERN NSString * const kRTCMediaConstraintsMinWidth;nRTC_EXTERN NSString * const kRTCMediaConstraintsMaxHeight;nRTC_EXTERN NSString * const kRTCMediaConstraintsMinHeight;nRTC_EXTERN NSString * const kRTCMediaConstraintsMaxFrameRate;nRTC_EXTERN NSString * const kRTCMediaConstraintsMinFrameRate;nnRTC_EXPORTn@interface RTCMediaConstraints : NSObjectnn- (instancetype)init NS_UNAVAILABLE;nn/** Initialize with mandatory and/or optional constraints. */n- (instancetype)initWithMandatoryConstraints:n (nullable NSDictionary<NSString *, NSString *> *)mandatoryn optionalConstraints:n (nullable NSDictionary<NSString *, NSString *> *)optionaln NS_DESIGNATED_INITIALIZER;nn@endn
RTCPeerConnection
- 信令處理
- 編解碼協商
- 點對點傳輸
- 通訊安全保護
- 帶寬管理(手機可以調得質量差點、PC可以質量高)
。。。
(編碼採用的最初不是採用的h264,而是VP8, 最新版本已經支持)
RTCPeerConnection 示例
JS
pc = new RTCPeerConnection(null);npc.onaddstream = gotRemoteStram;npc.addStream(localStream);npc.createOffer(gotOffer);nnfunction gotOffer(desc) {n pc.setLocalDescription(desc);n sendOffer(desc);n}nnfunction gotAnswer(desc) {n pc.setRemoteDescription(desc);n}nnfunction gotRemoteStream(e) {n attachMediaStream(remoteVideo, e.stream);n}n
Objective-C
- (void)startSignalingIfReady {n self.state = kARDAppClientStateConnected;nn // Create peer connection.n RTCMediaConstraints *constraints = [self offerConstraints];n RTCConfiguration *config = [[RTCConfiguration alloc] init];n [config setIceServers:_iceServers];n _peerConnection = [_factory peerConnectionWithConfiguration:confign constraints:constraintsn delegate:self];nn if(self.startLocalMedia){n [_peerConnection addStream:self.localStream];n [self sendOffer];n }n}nn- (void)sendOffer {n RTCMediaStream *localStream = [self createLocalMediaStream];n [_peerConnection removeStream:localStream];n [_peerConnection addStream:localStream];n [_peerConnection offerForConstraints:[self offerConstraints] completionHandler:^(RTCSessionDescription * _Nullable sdp, NSError * _Nullable error) {n // [self peerConnection:_peerConnection didCreateSessionDescription:sdp error:error];n }];n}nn- (void)waitForAnswer {n [self drainMessageQueueIfReady];n}nn- (void)drainMessageQueueIfReady {n if (!_peerConnection || !_hasReceivedSdp) {n return;n }nn for (ARDSignalingMessage *message in _messageQueue) {n [self processSignalingMessage:message];n }n [_messageQueue removeAllObjects];n}nn- (void)processSignalingMessage:(ARDSignalingMessage *)message {n switch (message.type == kARDSignalingMessageTypeAnswer) {n case kARDSignalingMessageTypeAnswer:n case kARDSignalingMessageStartCommunication:{n ARDStartCommunicationMessage *sdpMessage = (ARDStartCommunicationMessage *) message;n [_peerConnection setRemoteDescription:sdpMessage.sessionDescription completionHandler:^(NSError * _Nullable error) {n // some code when remote description was set (was a delegate before - see below)n }];n break;n }n default:n break;n }n}nn#pragma mark - RTCPeerConnectionDelegatenn- (void)peerConnection:(RTCPeerConnection *)peerConnection didAddStream:(RTCMediaStream *)stream {n RTCVideoTrack *videoTrack = stream.videoTracks[0];n [self.remoteVideoTrack addRenderer:self.remoteView];n}n
WebRTC 架構
TURN 做中轉的
比如:如果兩個人私有路由器都是192.168開頭 ,會被認為是同一個網路下。。就需要這個
信令服務
理想中的
但實際中:
- 雙方需要交換 Session Description 對象:
- 某一方支持什麼格式以及將要發什麼格式
- 某一方建立點對點通訊的網路信息
- 可以用任何消息機制和消息協議來進行交換
信令服務原理圖:
手搖電話 呼叫總部接線員起到的就是類似"信令服務"的作用打洞伺服器(防火牆穿越伺服器)
- 在有防火牆和地址轉換時P2P需要UDP打洞:
- NAT後不能直接向廣域網那樣IP直接連接
- 採用UDP進行防火牆和NAT進行穿越
- STUN/TURN/ICE服務
只有 UDP 能打洞,TCP無法打洞,TCP需要建立連接,會容錯。應該避免容錯。
Client --UDP--》 Server (獲知外網 IP 地址,埠號)
不是所有 NAT 網路都能打洞成功,連接就會建立失敗,只能伺服器中轉。
為什麼要有打洞服務:
- IPv4用完
- 私有地址一致,用了同一網段,不能用內網地址,需要用外網地址。
理想中的
現實中的:
幾個服務的辨析:
- STUN (Session Traversal Utilities for NAT) 只能UDP,告訴我暴露在廣域網的地址IP port ,我通過映射的廣域網地址進行P2P數據通信。
- TURN( Traversal Using Relays around for NAT)UDP或TCP, 打洞失敗後,提供伺服器中轉數據,通話雙方數據都通過伺服器,占伺服器帶寬較大 - 為了確保通話在絕大多數環境下可以正常工作。跨網只能用伺服器中轉(測試發現的) ,使用TURN這種情況在視頻通話中佔10%
- ICE 網路連接服務
WebRTC的時序圖:
部署STUN和TURN
一般是一個APP提供
- WebRTC stunserver, turnserver
- rfc5766-turn-server
- restund
STUN (Session Traversal Utilities for NAT)
NAT路由器
只能UDP,告訴我暴露在廣域網的地址IP port ,我通過映射的廣域網地址進行P2P數據通信。
網路拓撲結構:
TURN( Traversal Using Relays around for NAT)
- UDP或TCP, 打洞失敗後,提供伺服器中轉數據,通話雙方數據都通過伺服器,占伺服器帶寬較大
- 為了確保通話在絕大多數環境下可以正常工作。跨網只能用伺服器中轉(測試發現的) ,使用TURN這種情況在視頻通話中佔10%
ICE(Interactive Connectivity Establishment)
- 是一個用來建立P2P連接的編程框架
- 嘗試去找出建立視頻通話的最佳路徑
WebRTC for iOS 框架
- Apple的Safari瀏覽器目前不支持WebRTC標準
- 我們需要移植WebRTC的C/C++代碼實現
用Objective-C封裝成iOS的WebRTC開發框架
http://www.webrtc.org
- http://www.webrtc.org/web-apis
- http://www.webrtc.org/native-code
- http://github.com/webrtc/apprtc
Get the code:
http://www.webrtc.org/native-code/development
Browse code
https://chromium.googlesource.com/external/webrtc/+/master
Changes Log:
https://chromium.googlesource.com/external/webrtc
華為調查,WebRTC市場與前景
- Enterprise communications
- Telecom service providers
- Consumer web & mobile apps
- M2M & loT
- WebRTC PaaS Providers & APIs
後續更新會方法在這裡:ChenYilong/WebRTC
本文基於一次線上的分享的PPT製作而成,有興趣的話,可以去聽一下:千聊。
推薦閱讀:
※《Learning WebRTC中文版》試讀 + 簽名優惠版
※使用 WebRTC 構建簡單的前端視頻通信
※Kurento是否可以讓客戶端選擇不同的實時監控視頻?
※WebRTC有前途嗎?