基於 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技術

  • QQ
  • 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介紹

功能劃分

  1. 獲取音頻和視頻數據
  2. 傳輸音頻和視頻數據
  3. 傳輸任意二進位數據

    API劃分:三個JS介面

  4. MediaStream (又叫getUserMedia)

  5. RTCPeerConnection (C++)
  6. RTCDataChannel

MediaStream (getUserMedia)

  • 抽象表示一個音頻或者視頻流
  • 可包含多個音視頻記錄
  • 通過 navigator.getUserMedia() 獲取

(參考:w3c.github.io/mediacapt )

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

  1. 信令處理
  2. 編解碼協商
  3. 點對點傳輸
  4. 通訊安全保護
  5. 帶寬管理(手機可以調得質量差點、PC可以質量高)
  6. 。。。

    (編碼採用的最初不是採用的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用完
  • 私有地址一致,用了同一網段,不能用內網地址,需要用外網地址。

為什麼需要外網地址而非內網地址匿名聊天中,你知道你是在和一個昵稱(內網地址)叫張三的人聊天,其實你是在和一個UserId(外網地址)聊天

理想中的

現實中的:

幾個服務的辨析:

  • 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提供

  1. WebRTC stunserver, turnserver
  2. rfc5766-turn-server
  3. restund

STUN (Session Traversal Utilities for NAT)

NAT路由器

只能UDP,告訴我暴露在廣域網的地址IP port ,我通過映射的廣域網地址進行P2P數據通信。

網路拓撲結構:

TURN( Traversal Using Relays around for NAT)

  • UDP或TCP, 打洞失敗後,提供伺服器中轉數據,通話雙方數據都通過伺服器,占伺服器帶寬較大
    • 為了確保通話在絕大多數環境下可以正常工作。跨網只能用伺服器中轉(測試發現的) ,使用TURN這種情況在視頻通話中佔10%

ICE 網路連接服務

ICE(Interactive Connectivity Establishment)

  • 是一個用來建立P2P連接的編程框架
  • 嘗試去找出建立視頻通話的最佳路徑

WebRTC for iOS 框架

  • Apple的Safari瀏覽器目前不支持WebRTC標準
  • 我們需要移植WebRTC的C/C++代碼實現
  • 用Objective-C封裝成iOS的WebRTC開發框架

  • http://www.webrtc.org

  • webrtc.org/web-apis
  • webrtc.org/native-code
  • github.com/webrtc/apprt

Get the code:

webrtc.org/native-code/

Browse code

chromium.googlesource.com

Changes Log:

chromium.googlesource.com

華為調查,WebRTC市場與前景

  • Enterprise communications
  • Telecom service providers
  • Consumer web & mobile apps
  • M2M & loT
  • WebRTC PaaS Providers & APIs

後續更新會方法在這裡:ChenYilong/WebRTC

本文基於一次線上的分享的PPT製作而成,有興趣的話,可以去聽一下:千聊。

推薦閱讀:

《Learning WebRTC中文版》試讀 + 簽名優惠版
使用 WebRTC 構建簡單的前端視頻通信
Kurento是否可以讓客戶端選擇不同的實時監控視頻?
WebRTC有前途嗎?

TAG:iOS | WebRTC | 即时通讯IM |