WebRTC-ios 跨平臺音視頻通話

WebRTC的基礎知識和實現原理可以參考這篇文章,ios下音視頻通信的實現-基于WebRTC

上述文章里面也有介紹到WebRTC在ios下的環境搭建,這里介紹一個利用Pod導入的方法。WebRTC官網上有說到。

先附上本人自己寫的demo下載地址,參照demo,會對這篇文章有更好的理解。WebRTCdemo地址

捷徑:下面三步,現在可以一步就可以完成,就是直接在Podfile文件里面pod WebRTCHelper就可以了。這個WebRTCHelper是我傳到pod上面的,只作參考

source 'https://github.com/CocoaPods/Specs.git'
target 'WebRTCHelper_pod' do
platform :ios, '9.0'
pod 'WebRTCHelper'
end

終端進入到項目文件夾中,輸入:

pod install

運行結果如圖:下面三個步驟要導入的工程,全部都pod進項目中了。


pod webRTCHelper

第一步:在你的Podfile文件里面加入下面這段代碼

source 'https://github.com/CocoaPods/Specs.git'
target 'YOUR_APPLICATION_TARGET_NAME_HERE' do
  platform :ios, '9.0'
  pod 'GoogleWebRTC'
end

GoogleWebRTC就是WebRTC的ios版本frameWork。

第二步:導入需要用到的WebSocket庫;在Podfile文件里面加入

pod 'SocketRocket'

第三步:創建WebRTC的管理類,命名為WebRTCHelper,繼承NSObject,這個網上都有,我這里提供了一個WebRTCHelper

完成上面的步驟,基本的工作就做完了,接下來就是測試了。如果是局域網里面測試,我們可以用到前面推薦閱讀的那篇文章快到底的地方有個關于說服務器端WebSocket搭建的,有直接給你寫好的服務器,只需要簡單的操作就可以完成測試服務的搭建。這里就不多闡述。

現在在網絡上還有其他的集成方式主要是自己編譯WebRTC的源碼,然后拉入到自己的項目中,添加依賴庫,這個方式比較繁瑣,在編譯的時候會出現各種問題。既然官方有提供這個pod的庫,直接pod是最簡單的。


第二部分

WebRTC環境搭建就是這樣了!下面具體講解下WebRTCHelper管理類里面的具體方法和代理:

WebRTCHelper.h文件
  • 1-1.聲明一個單例
+(instancetype)shareInstance;
  • 1-2 聲明四個public方法
/**
 * 與服務器建立連接
 * @param server 服務器地址
 * @param port 端口號
 * @param room 房間號
 */
-(void)connectServer:(NSString *)server port:(NSString *)port room:(NSString *)room;
/**
 * 切換攝像頭
 */
-(void)swichCamera;
/**
 * 是否顯示本地視頻
 */
-(void)showLocaolCamera;
/**
 * 退出房間
 */
-(void)exitRoom;
  • 1-3 聲明兩個代理
/*注釋*/
@property (nonatomic,weak) id<WebRTCHelperDelegate> delegate;
/*注釋*/
@property (nonatomic,weak) id<WebRTCHelperFrindDelegate> friendDelegate;

WebRTCHelperDelegate代理方法:

/**
 * 獲取到發送信令消息
 * @param webRTCHelper 本類
 * @param message 消息內容
 */
-(void)webRTCHelper:(WebRTCHelper *)webRTCHelper receiveMessage:(NSString *)message;
/**
 * 獲取本地的localVideoStream數據(舊版本返回localStream方法)
 * @param webRTCHelper 本類
 * @param steam 視頻流
 * @param userId 用戶標識
 */
-(void)webRTCHelper:(WebRTCHelper *)webRTCHelper setLocalStream:(RTCMediaStream *)steam userId:(NSString *)userId;
/**
 * 獲取遠程的remoteVideoStream數據
 * @param webRTCHelper 本類
 * @param stream 視頻流
 * @param userId 用戶標識
 */
-(void)webRTCHelper:(WebRTCHelper *)webRTCHelper addRemoteStream:(RTCMediaStream *)stream userId:(NSString *)userId;
/**
 * 某個用戶退出后,關閉用戶的連接
 * @param webRTCHelper 本類
 * @param userId 用戶標識
 */
- (void)webRTCHelper:(WebRTCHelper *)webRTCHelper closeWithUserId:(NSString *)userId;

/**
 * 獲取socket連接狀態
 * @param webRTCHelper 本類
 * @param connectState 連接狀態,分為
 WebSocketConnectSuccess 成功,
 WebSocketConnectField, 失敗
 WebSocketConnectClosed 關閉
 */
-(void)webRTCHelper:(WebRTCHelper *)webRTCHelper socketConnectState:(WebSocketConnectState)connectState;
/**
 * 獲取本地視頻流的AVCaptureSession(新版本實現本地視頻展示代理)
 * @param webRTCHelper
 * @param captureSession AVCaptureSession類
 */
-(void)webRTCHelper:(WebRTCHelper *)webRTCHelper capturerSession:(AVCaptureSession *)captureSession;

WebRTCHelperFrindDelegate代理方法,這個代理方法是做房間用戶列表所用:

/**
 * 獲取房間內所有的用戶(除了自己)
 * @param friendList 用戶列表
 */
-(void)webRTCHelper:(WebRTCHelper *)webRTCHelper gotFriendList:(NSArray *)friendList;
/**
 * 獲取新加入的用戶信息
 * @param friendId 新用戶的id
 */
-(void)webRTCHelper:(WebRTCHelper *)webRTCHelper gotNewFriend:(NSString *)friendId;
/**
 * 獲取離開房間用戶的信息
 * @param friendId 離開用戶的ID
 */
-(void)webRTCHelper:(WebRTCHelper *)webRTCHelper removeFriend:(NSString *)friendId;
WebRTCHelper.m文件

.m里面的方法實現就按照整體的實現流程來說明:

  • 1、先與服務器建立起socket連接,這里要借助SRWebSocket庫,見方法
/**
 * 與服務器進行連接
 */
- (void)connectServer:(NSString *)server port:(NSString *)port room:(NSString *)room{
    _server = server;
    _room = room;
    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"ws://%@:%@",server,port]] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:20];
    _socket = [[SRWebSocket alloc] initWithURLRequest:request];
    _socket.delegate = self;
    [_socket open];
}
  • 2、在與服務器建立連接之后,會調用SRWebSocket的代理,見代碼
/**
 * webSocket連接成功
 */
- (void)webSocketDidOpen:(SRWebSocket *)webSocket{
    NSLog(@"socket連接成功");
    [self joinRoom:_room];
    dispatch_async(dispatch_get_main_queue(), ^{
        if ([self->_delegate respondsToSelector:@selector(webRTCHelper:socketConnectState:)]) {
            [self->_delegate webRTCHelper:self socketConnectState:WebSocketConnectSuccess];
        }
    });
}
/**
 * webSocket連接失敗,返回失敗原因
 */
- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error{
    NSLog(@"socket連接失敗");
    dispatch_async(dispatch_get_main_queue(), ^{
        if ([self->_delegate respondsToSelector:@selector(webRTCHelper:socketConnectState:)]) {
            [self->_delegate webRTCHelper:self socketConnectState:WebSocketConnectSuccess];
        }
    });
}
/**
 * webSocket關閉連接,返回關閉的原因reason
 */
- (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean{
    NSLog(@"socket關閉。code = %ld,reason = %@",code,reason);
}
  • 3、在SRWebSocket的連接成功的代理里做加入房間操作,2步驟處有調用,加入房間就是socket發送一個__join的信令,現在看下加入房間里面的代碼實現:
/**
 *  加入房間
 *
 *  @param room 房間號
 */
- (void)joinRoom:(NSString *)room
{
    //如果socket是打開狀態
    if (_socket.readyState == SR_OPEN)
    {
        //初始化加入房間的類型參數 room房間號
        NSDictionary *dic = @{@"eventName": @"__join", @"data": @{@"room": room}};
        
        //得到json的data
        NSData *data = [NSJSONSerialization dataWithJSONObject:dic options:NSJSONWritingPrettyPrinted error:nil];
        //發送加入房間的數據
        [_socket send:data];
    }
}
  • 4、調用加入房間joinRoom:時,會發送__join信令到服務器,此時如果房間內有其他的用戶,就會返回信令__peers,告訴你房間內有用戶,需要建立連接,這個接收__Peers信令方法就是SRWebSocket的接收消息代理方法webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message
  • 5、在方法webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message里面的eventName==@"__peers"條件下,做以下操作:
    -- 5-1 對房間內其他的所有用戶建立RTCPeerConnction連接[self createPeerConnections];
    -- 5-2 對建立的所有連接添加本地視頻流[self addStreams];
    -- 5-3 發送本地的offer給到房間內所有的用戶[self createOffers];
    這些操作完成后就可以和房間內其他人建立起視頻聊天了;如果房間內沒有其他用戶的話,就不需要做多余的操作。
  • 6、針對房間內其他用戶的加入,會在webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message方法里面收到_new_peer信令,這個時候要針對這個用戶創建一個RTCPeerConnection連接,然后給這個鏈接添加本地視頻流,然后設置代理回調;
    -- 6-1、新加入的用戶,會發送ice候選地址,就是通過ICEService服務器獲取的地址,在webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message方法里面的eventName_ice_candidate條件下有代碼和說明;
    -- 6-2、先加入的用戶還會發送本身的offer,也就是remote的sdp描述。在接收到新加入用戶發的offer的時候,會獲取到這個先加入用戶的連接peerConnection,然后發送sdp描述對象;這個代碼是在webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message方法里面的eventName_offer的條件下。

注意的地方:

這這里需要注意一個方面就是針對最新庫和比較舊的庫之間在創建本地視頻流創建視頻約束的時候不一樣的地方;

  • 創建本地視頻流,在WebRTCHelper.m文件的createLocalStream方法里面的if(device){}條件下
    -- 最新版的庫創建本地視頻流的方法
//創建RTCVideoSource
RTCVideoSource *videoSource = [_factory videoSource];
//創建RTCCameraVideoCapturer
RTCCameraVideoCapturer * capture = [[RTCCameraVideoCapturer alloc] initWithDelegate:videoSource];
//創建AVCaptureDeviceFormat
AVCaptureDeviceFormat * format = [[RTCCameraVideoCapturer supportedFormatsForDevice:device] lastObject];   
//獲取設備的fbs
CGFloat fps = [[format videoSupportedFrameRateRanges] firstObject].maxFrameRate;
//創建RTCVideoTrack
RTCVideoTrack *videoTrack = [_factory videoTrackWithSource:videoSource trackId:@"ARDAMSv0"];
__weak RTCCameraVideoCapturer *weakCapture = capture;
__weak RTCMediaStream * weakStream = _localStream;
__weak NSString * weakMyId = _myId;
//捕獲攝像頭的視頻數據
[weakCapture startCaptureWithDevice:device format:format fps:fps completionHandler:^(NSError * error) {
      NSLog(@"11111111");
      [weakStream addVideoTrack:videoTrack];
      if ([self->_delegate respondsToSelector:@selector(webRTCHelper:setLocalStream:userId:)])
     {
  //在實現代理中,將weakCapture.captureSession設置到RTCCameraPreviewView的captureSession屬性,視頻才能顯示
         [self->_delegate webRTCHelper:self capturerSession:weakCapture.captureSession];
       }
}];
  • 舊版庫創建本視頻流的方法
 /*舊版本創建videoSource是RTCAVFoundationVideoSource類,新版是RTCVideoSource,且創建的方法也不一樣*/
RTCAVFoundationVideoSource *videoSource = [_factory avFoundationVideoSourceWithConstraints:[self localVideoConstraints]];
//創建RTCVideoTrack,這個方法沒有什么變化
RTCVideoTrack *videoTrack = [_factory videoTrackWithSource:videoSource trackId:@"ARDAMSv0"];
//本地流添加RTCVideoTrack
[_localStream addVideoTrack:videoTrack];
//代理回調
 if ([self->_delegate respondsToSelector:@selector(webRTCHelper:setLocalStream:userId:)])
     {
  //將localStream中的videoTracks數據綁定到RTCEAGLVideoView類型localVideoView上,本地視頻才會顯示
         [self->_delegate webRTCHelper:self setLocalStream:weakStream userId:weakMyId];  
     }

WebRTCHelper.m文件里面就這么多主要的實現;然后看具體怎么去調用這些功能實現音視頻通話。

第一、先定義以下的屬性

/*保存遠端視頻流*/
@property (nonatomic,strong) NSMutableDictionary *videoTracks;
/*房間內其他用戶*/
@property (nonatomic,strong) NSMutableArray *members;
//顯示本地視頻的view
@property (weak, nonatomic) IBOutlet RTCCameraPreviewView *localVideoView;

localVideView在最新庫下,是RTCCameraPreviewView類型,如果是舊版本的話,是RTCEAGLVideoView類型

然后在viewDidLoad設置WebRTCHelper的代理為self

[WebRTCHelper shareInstance].delegate = self;
 [WebRTCHelper shareInstance].friendDelegate = self;

創建連接,連接到socket服務器上面
viewDidLoad

[self connect];
/**
 * 連接服務器
 */
-(void)connect{
//    [[WebRTCHelper shareInstance] connectServer:@"192.168.30.186" port:@"3000" room:@"100"];
    [[WebRTCHelper shareInstance] connectServer:@"115.236.101.203" port:@"18080" room:@"100"];
}

連接服務成功后,會在WebRTCHelper里面的socket代理里面調用joinRoom方法,然后會在WebRTCHelperDelegateWebRTCHelperFrindDelegate代理里面去獲取數據,這里具體說下獲取視頻流的兩個代理方法。

第一個代理方法是獲取本地視頻流的方法:

  • 舊版庫本地代理獲取方法:
/**
 * 舊版本獲取本地視頻流的代理,在這個代理里面會獲取到RTCVideoTrack類,然后添加到RTCEAGLVideoView類型的localVideoView上面
 */
- (void)webRTCHelper:(WebRTCHelper *)webRTCHelper setLocalStream:(RTCMediaStream *)steam userId:(NSString *)userId{
    if (steam) {
        _localSteam = steam;
        RTCVideoTrack * track = [_localSteam.videoTracks lastObject];
        [track addRenderer:self.localVideoView];
    }
    
}

上面這個獲取本地視頻流的代理方法只針對舊版本才可以,注意當中的localVideoViewRTCEAGLVideoView類型的

  • 新版庫獲取本地視頻流的方法
    -- 這個方式主要是獲取到RTCCameraPreviewViewAVCaptureSession類型的屬性數據
/**
 * 新版獲取本地視頻流的方法
 * @param captureSession RTCCameraPreviewView類的參數,通過設置這個,就可以達到顯示本地視頻的功能
 */
- (void)webRTCHelper:(WebRTCHelper *)webRTCHelper capturerSession:(AVCaptureSession *)captureSession{
    self.localVideoView.captureSession = captureSession;
}

第二就是獲取遠端視頻流的方法:

/**
 * 獲取遠端視頻流的方法,主要是獲取到RTCVideoTrack類型的數據,然后保存起來,在刷新列表的時候,添加到對應item里面的RTCEAGLVideoView類型的view上面
 */
- (void)webRTCHelper:(WebRTCHelper *)webRTCHelper addRemoteStream:(RTCMediaStream *)stream userId:(NSString *)userId{
    RTCVideoTrack * track = [stream.videoTracks lastObject];
    if (track != nil) {
        [self.videoTracks setObject:track forKey:userId];
    }
        [self.collectionView reloadData];
}


主要的實現都在這里了,肯定是有不到位的地方,還希望瀏覽有心人士幫忙指出,共同進步。



常見問題

1、遠端視頻流顯示黑屏或者無法顯示問題(只針對客戶端,demo中的問題)

針對網友說的出現遠端視頻看不到或者顯示黑屏,這里給出一個解決方法,自測是好的:
修改ViewController里面connect里面的代碼,如圖:

answer-1.png

注釋代碼是配置自己公司視頻服務器的,對于網友來說是不適用的,所以需要修改成沒有注釋的代碼;
紅框里面的服務器地址是針對你電腦的ip地址,查看路勁:偏好設置->網絡->以太網的ip
這里修改完之后,還需要進到ChatViewController里面修改connect方法,ip地址和上述一致就可以。
在這之前需要啟動視頻服務器,這個服務器不是本人搭的,目前有點問題,顯示不了視頻,但是不影響demo的測試。服務器搭建流程:
下載demo->終端進入到SkyRTC-demo-master->查看REMIND.md文件,跟著REMIND.md文件操作就可以了。服務器要在運行app之前搭建。

注意

注意:本貼中說到的項目運行會存在視頻黑屏的問題,目前還沒解決,希望各位大牛大咖解決或者有思路的能給我留言。先行謝謝了。
郵箱:wonderfulhzh@163.com
qq:1061023083

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。