WebRTC在iOS端的使用

權限申請:

Info.plist中添加 Camera和Microphone訪問權限

引入WebRTC庫:

1暖哨、通過WebRTC源碼編譯出WebRTC庫,然后再項目中手動引入它

2、WebRTC官方會定期發(fā)布編譯好的WebRTC庫,可以使用Pod方式進行安裝(GoogleWebRTC)

獲取本地視頻:

WebRTC庫引入成功后,就可以開始真正的WebRTC之旅了脐雪。

????在獲取視頻之前,首先要選擇使用哪個視頻設備采集數(shù)據(jù)恢共。在WebRTC中战秋,我們可以通過RTCCameraVideoCapture類獲取所有的視頻設備。

????NSArray *devices =[RTCCameraVideoCapture captureDevices];

????AVCaptureDevice *device = devices[0];

光有設備不行讨韭,還要清楚從設備中采集的數(shù)據(jù)放到哪里获询,這樣才能將其展示出來。

????WebRTC提供了一個專門的類拐袜,即RTCVideoSource,有兩層含義:

????1梢薪、表明它是視頻源蹬铺。當要展示視頻的時候,就從這里獲取數(shù)據(jù)

????2秉撇、它也是一個終點甜攀,即:當從視頻設備采集到視頻數(shù)據(jù)時,要交給它暫存起來

????RTCCameraVideoCapture琐馆,專門用于操作設備的類规阀,通過它,可以自如的控制視頻設備了瘦麸。

信令驅(qū)動

在任何系統(tǒng)中谁撼,都可以說是信令是系統(tǒng)的靈魂。例如滋饲,由誰來發(fā)起呼叫厉碟;媒體協(xié)商時,什么時間發(fā)哪種SDP都是有信令控制的屠缭。

????客戶端命令:

? ? ? 1箍鼓、join,用戶加入房間

? ? ? 2呵曹、leave款咖,用戶離開房間

? ? ? 3何暮、message,端到端命令(offer铐殃,answer海洼,candidate)

????服務端命令:

? ? ? 1、joined背稼,用戶已加入

? ? ? 2贰军、leaved,用戶已離開

? ? ? 3蟹肘、other_joined词疼,其他用戶已加入

? ? ? 4、bye帘腹,其他用戶已離開

? ? ? 5贰盗、full,房間已滿

????信令狀態(tài)機

????通過信令狀態(tài)機來管理信令阳欲,不同的狀態(tài)下舵盈,需要發(fā)不同的信令。同樣的球化,當收到服務端或?qū)Χ说男帕詈蠡嗤恚瑺顟B(tài)會隨之發(fā)生改變。

????在初始時筒愚,客戶端處于init/leaved狀態(tài)赴蝇。

????在 init/leaved 狀態(tài)下,用戶只能發(fā)送join消息巢掺。服務端收到join消息后句伶,會返回joined消息。此時陆淀,客戶端會更新為joined狀態(tài)考余。

????在joined狀態(tài)下,客戶端有多種選擇轧苫,收到不同的消息會切到不同的狀態(tài):

????如果用戶離開房間楚堤,那客戶端又回到了初始狀態(tài),即 init/leaved 狀態(tài)含懊。

????如果客戶端收到second user join消息钾军,則切換到join_conn狀態(tài)。在這種狀態(tài)下绢要,兩個用戶就可以進行通話了吏恭。

????如果客戶端收到second user leave消息,則切換到join_unbind狀態(tài)重罪。其實join_unbind狀態(tài)與joined狀態(tài)基本是一致的樱哼。

????如果客戶端處于join_conn狀態(tài)哀九,當它收到second user leave消息時,也會轉(zhuǎn)成joined_unbind狀態(tài)搅幅。

????如果客戶端是joined_unbind狀態(tài)阅束,當它收到second user join消息時,會切到join_conn狀態(tài)茄唐。

socket.io? (信令的基礎庫)

信令的使用:

????1息裸、通過url獲取socket。有了socket之后就可建立與服務器的連接了

????2沪编、注冊偵聽的消息呼盆,并為每個偵聽的消息綁定一個處理函數(shù)。當收到服務器的消息后蚁廓,隨之會觸發(fā)綁定的函數(shù)

????3访圃、通過socket建立連接

????4、發(fā)送消息

????獲取socket

????NSURL* url =[[NSURL alloc]initWithString:addr];

????manager =[[SocketManager alloc]initWithSocketURL:url

????????? config:@{

????????????????????@"log": @YES,

????????????????????@"forcePolling":@YES,

? ? ? ? ? ? ? ? ? ? @"forceWebsockets":@YES

???? }];

????socket = manager.defaultSocket;

注冊偵聽消息:

????[socket on:@"joined" callback:^(NSArray * data,SocketAckEmitter * ack){

????????NSString* room =[data objectAtIndex:0];

????????NSLog(@"joined room(%@)",room);

????????[self.delegate joined:room];

????}];

建立連接:

????[socket connect];

發(fā)送消息:

????if(socket.status == SocketIOStatusConnected){

????????[socket emit:@"join" with:@[room]];

????}

創(chuàng)建RCTPeerConnection

????當信令系統(tǒng)建立好后相嵌,后面的邏輯都是圍繞著信令系統(tǒng)建立起來的

????客戶端用戶想要與遠端通話腿时,首先要發(fā)送join消息,也就是要先進入房間饭宾。此時批糟,如果服務器判斷用戶是合法的,則會給客戶端會joined消息

????客戶端收到joined消息后看铆,就要創(chuàng)建RTCPeerConnection了跃赚,也就是要建立一條與遠端通話的音視頻數(shù)據(jù)傳輸通道

????if(!ICEServers){

????????ICEServers =[NSMutableArray array];

????????[ICEServers addObject:[self defaultSTUNServer]];

????}

????RTCConfiguration* configuration =[[RTCConfiguration alloc]init];

????[configuration setIceServers:ICEServers];

????RTCPeerConnection* conn =[factory

?? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? peerConnectionWithConfiguration:configuration

?? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? constraints:[self defaultPeerConnContraints]

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? delegate:self];

????RTCPeerConnection對象有三個參數(shù):

????????1、RTCConfiguration類型的對象性湿,該對象中最重要的一個字段是iceservers。它里面存放了stun/turn服務器地址满败。其主要作用是用于NAT穿越肤频。

????????2、RTCMediaConstraints類型對象算墨,也就是對RTCPeerConnection的限制宵荒。

????????????如:是否接受視頻數(shù)據(jù)?是否接受音頻數(shù)據(jù)净嘀?如果要與瀏覽器互通還要開啟DtlsSrtpKeyAgreement選項

????????3报咳、委托類型。相當于給RTCPeerConnection設置一個觀察者挖藏。這樣RTCPeerConnection可以將一個狀態(tài)/信息通過它通知給觀察者暑刃。

????RTCPeerConnection建立好之后,接下來就是整個實時通話過程中,最重要的部分,媒體協(xié)商

媒體協(xié)商

????媒體協(xié)商內(nèi)容使用的是SDP協(xié)議

????Amy與Bob進行通話绸狐,通話的發(fā)起方(Amy)摇邦,首先要創(chuàng)建Offer類型的SDP消息簇爆,之后調(diào)用RTCPeerConnection對象的setLocalDescription方法夫啊,將Offer保存到本地

????緊接著瓜客,將Offer發(fā)送給服務器君纫。然后通過信令服務器中轉(zhuǎn)到被呼叫方(Bob)谷扣。被呼叫方收到Offer后土全,調(diào)用它的RTCPeerConnection對象的setRemoteDescription方法,將遠端的Offer保存起來

????之后会涎,被呼叫方創(chuàng)建Answer類型的SDP內(nèi)容裹匙,并調(diào)用RTCPeerConnection對象的setLocalDescription方法將它存儲到恩地

????同樣的,它也要將Answer發(fā)送給服務器在塔。服務器收到該消息后幻件,不做任何處理,直接中轉(zhuǎn)給呼叫方蛔溃。呼叫方收到Answer后绰沥,調(diào)用setRemoteDescription將其保存起來


手繪圖

????通過上面的步驟,整個媒體協(xié)商部分就完成了

????[peerConnection offerForConstraints:[self defaultPeerConnContraints]

? ? ? ? ? ? ? ? ? completionHandler:^(RTCSessionDescription * _Nullable sdp,NSError * _Nullable error){

? ? ? ? ? ? ? ? ? ? ? if(error){

? ? ? ? ? ? ? ? ? ? ? ? ? NSLog(@"Failed to create offer SDP,err=%@",error);

? ? ? ? ? ? ? ? ? ? ? } else {

? ? ? ? ? ? ? ? ? ? ? ? ? __weak RTCPeerConnection* weakPeerConnction = self->peerConnection;

? ? ? ? ? ? ? ? ? ? ? ? ? [self setLocalOffer: weakPeerConnction withSdp: sdp];

? ? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? }];

????iOS端使用RTCPeerConnection對象的offerForConstraints方法創(chuàng)建Offer SDP贺待。它有兩個參數(shù)

????????1徽曲、RTCMediaConstraints類型的參數(shù)

????????2、匿名回調(diào)函數(shù)麸塞⊥撼迹可以通過對error是否為空來判定offerForConstraints方法有沒有執(zhí)行成功。如果執(zhí)行成功啦哪工,參數(shù)sdp就是創(chuàng)建好的SDP內(nèi)容

????如果成功獲得了SDP奥此,首先存到本地,然后再將它發(fā)送給服務端雁比,服務器中轉(zhuǎn)給另一端

????[pc setLocalDescription:sdp completionHandler:^(NSError * _Nullable error){

????????if(!error){

????????????NSLog(@"Successed to set local offer sdp!");

????????}else{

????????????NSLog(@"Failed to set local offer sdp,err=%@",error);

????????}

????}];

? ? __weak NSString* weakMyRoom = myRoom;

????dispatch_async(dispatch_get_main_queue(),^{

????????????NSDictionary* dict =[[NSDictionary alloc]initWithObjects:@[@"offer",sdp.sdp]? forKeys: @[@"type",@"sdp"]];

? ? ? ????? [[SignalClient getInstance]sendMessage: weakMyRoom? withMsg: dict];

????});

????其實就是做了兩件事稚虎。一是調(diào)用setLocalDescription方法將SDP保存到本地,另一件事就是發(fā)消息偎捎。

????當整個協(xié)商完成后蠢终,緊接著,在WebRTC底層就會進行音視頻數(shù)據(jù)的傳輸茴她。

渲染遠端視頻:

????在創(chuàng)建RTCPeerConnection對象時寻拂,同時給RTCPeerConnection設置了一個委托

????該委托對象中,實現(xiàn)了所有RTCPeerConnection對象的代理方法丈牢,其中比較關鍵的有下面幾個:

? ? 1祭钉、- (void)peerConnection:(RTCPeerConnection *)peerConnection didGenerateIceCandidate:(RTCIceCandidate *)candidate;//該方法用于收集可用的Candidate己沛。

? ? 2朴皆、-(void)peerConnection:(RTCPeerConnection *)peerConnection

didChangeIceConnectionState:(RTCIceConnectionState)newState帕识;//當ICE連接狀態(tài)發(fā)生變化時會觸發(fā)該方法

? ? 3、-(void)peerConnection:(RTCPeerConnection *)peerConnection

didAddReceiver:(RTCRtpReceiver *)rtpReceiver streams:(NSArray *)mediaStreams遂铡;//該方法在偵聽到遠端track時會觸發(fā)肮疗。

????那么,什么時候開始渲染遠端視頻呢扒接?當有遠端視頻流過來的時候伪货,就會觸發(fā)?

? ? ? -(void)peerConnection:(RTCPeerConnection *)peerConnection didAddReceiver:(RTCRtpReceiver *)rtpReceiver streams: (NSArray *)mediaStreams方法。所以我們只需要在該方法中寫一些邏輯即可钾怔。

????當上面的函數(shù)被調(diào)用后碱呼,我們可以通過rtpReceiver參數(shù)獲取到track。這個track有可能是音頻trak宗侦,也有可能是視頻trak愚臀。所以,我們首先要對 track 做個判斷矾利,看其是視頻還是音頻姑裂。

????如果是視頻的話,就將remoteVideoView加入到trak中男旗,相當于給track添加了一個觀察者舶斧,這樣remoteVideoView就可以從track獲取到視頻數(shù)據(jù)了。在remoteVideoView實現(xiàn)了渲染方法察皇,一量收到數(shù)據(jù)就會直接進行渲染茴厉。最終,我們就可以看到遠端的視頻了什荣。

????具體代碼如下:

????RTCMediaStreamTrack* track = rtpReceiver.track;

????if([track.kind isEqualToString:kRTCMediaStreamTrackKindVideo]){

????????if(!self.remoteVideoView){

????????????NSLog(@"error:remoteVideoView have not been created!");

????????????return;

????????}

????????remoteVideoTrack =(RTCVideoTrack*)track;

????????[remoteVideoTrack addRenderer: self.remoteVideoView];

????}

通過上面的代碼矾缓,我們就可以將遠端傳來的視頻展示出來了。

**********************************************************************************************************************

總結(jié):(七步驟)

權限申請

引入WebRTC庫

獲取本地視頻

信令驅(qū)動

創(chuàng)建音視頻數(shù)據(jù)通道

媒體協(xié)商

渲染遠端視頻

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末稻爬,一起剝皮案震驚了整個濱河市嗜闻,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌因篇,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,807評論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件笔横,死亡現(xiàn)場離奇詭異竞滓,居然都是意外死亡,警方通過查閱死者的電腦和手機吹缔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,284評論 3 399
  • 文/潘曉璐 我一進店門商佑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人厢塘,你說我怎么就攤上這事茶没〖∮模” “怎么了?”我有些...
    開封第一講書人閱讀 169,589評論 0 363
  • 文/不壞的土叔 我叫張陵抓半,是天一觀的道長喂急。 經(jīng)常有香客問我,道長笛求,這世上最難降的妖魔是什么廊移? 我笑而不...
    開封第一講書人閱讀 60,188評論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮探入,結(jié)果婚禮上狡孔,老公的妹妹穿的比我還像新娘。我一直安慰自己蜂嗽,他們只是感情好苗膝,可當我...
    茶點故事閱讀 69,185評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著植旧,像睡著了一般辱揭。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上隆嗅,一...
    開封第一講書人閱讀 52,785評論 1 314
  • 那天界阁,我揣著相機與錄音,去河邊找鬼胖喳。 笑死泡躯,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的丽焊。 我是一名探鬼主播较剃,決...
    沈念sama閱讀 41,220評論 3 423
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼技健!你這毒婦竟也來了写穴?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,167評論 0 277
  • 序言:老撾萬榮一對情侶失蹤雌贱,失蹤者是張志新(化名)和其女友劉穎啊送,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體欣孤,經(jīng)...
    沈念sama閱讀 46,698評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡馋没,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,767評論 3 343
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了降传。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片篷朵。...
    茶點故事閱讀 40,912評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出声旺,到底是詐尸還是另有隱情笔链,我是刑警寧澤,帶...
    沈念sama閱讀 36,572評論 5 351
  • 正文 年R本政府宣布腮猖,位于F島的核電站鉴扫,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏缚够。R本人自食惡果不足惜幔妨,卻給世界環(huán)境...
    茶點故事閱讀 42,254評論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望谍椅。 院中可真熱鬧误堡,春花似錦、人聲如沸雏吭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,746評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽杖们。三九已至悉抵,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間摘完,已是汗流浹背姥饰。 一陣腳步聲響...
    開封第一講書人閱讀 33,859評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留孝治,地道東北人列粪。 一個月前我還...
    沈念sama閱讀 49,359評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像谈飒,于是被迫代替她去往敵國和親岂座。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,922評論 2 361