iOS的技术论坛音视频从入门到放弃

WebRTC在iOS端的使用

2019-06-21  本文已影响0人  吕建雄

权限申请:

Info.plist中添加 Camera和Microphone访问权限

引入WebRTC库:

1、通过WebRTC源码编译出WebRTC库,然后再项目中手动引入它

2、WebRTC官方会定期发布编译好的WebRTC库,可以使用Pod方式进行安装(GoogleWebRTC)

获取本地视频:

WebRTC库引入成功后,就可以开始真正的WebRTC之旅了。

    在获取视频之前,首先要选择使用哪个视频设备采集数据。在WebRTC中,我们可以通过RTCCameraVideoCapture类获取所有的视频设备。

    NSArray *devices =[RTCCameraVideoCapture captureDevices];

    AVCaptureDevice *device = devices[0];

光有设备不行,还要清楚从设备中采集的数据放到哪里,这样才能将其展示出来。

    WebRTC提供了一个专门的类,即RTCVideoSource,有两层含义:

    1、表明它是视频源。当要展示视频的时候,就从这里获取数据

    2、它也是一个终点,即:当从视频设备采集到视频数据时,要交给它暂存起来

    RTCCameraVideoCapture,专门用于操作设备的类,通过它,可以自如的控制视频设备了。

信令驱动

在任何系统中,都可以说是信令是系统的灵魂。例如,由谁来发起呼叫;媒体协商时,什么时间发哪种SDP都是有信令控制的。

    客户端命令:

      1、join,用户加入房间

      2、leave,用户离开房间

      3、message,端到端命令(offer,answer,candidate)

    服务端命令:

      1、joined,用户已加入

      2、leaved,用户已离开

      3、other_joined,其他用户已加入

      4、bye,其他用户已离开

      5、full,房间已满

    信令状态机

    通过信令状态机来管理信令,不同的状态下,需要发不同的信令。同样的,当收到服务端或对端的信令后,状态会随之发生改变。

    在初始时,客户端处于init/leaved状态。

    在 init/leaved 状态下,用户只能发送join消息。服务端收到join消息后,会返回joined消息。此时,客户端会更新为joined状态。

    在joined状态下,客户端有多种选择,收到不同的消息会切到不同的状态:

    如果用户离开房间,那客户端又回到了初始状态,即 init/leaved 状态。

    如果客户端收到second user join消息,则切换到join_conn状态。在这种状态下,两个用户就可以进行通话了。

    如果客户端收到second user leave消息,则切换到join_unbind状态。其实join_unbind状态与joined状态基本是一致的。

    如果客户端处于join_conn状态,当它收到second user leave消息时,也会转成joined_unbind状态。

    如果客户端是joined_unbind状态,当它收到second user join消息时,会切到join_conn状态。

socket.io  (信令的基础库)

信令的使用:

    1、通过url获取socket。有了socket之后就可建立与服务器的连接了

    2、注册侦听的消息,并为每个侦听的消息绑定一个处理函数。当收到服务器的消息后,随之会触发绑定的函数

    3、通过socket建立连接

    4、发送消息

    获取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];

发送消息:

    if(socket.status == SocketIOStatusConnected){

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

    }

创建RCTPeerConnection

    当信令系统建立好后,后面的逻辑都是围绕着信令系统建立起来的

    客户端用户想要与远端通话,首先要发送join消息,也就是要先进入房间。此时,如果服务器判断用户是合法的,则会给客户端会joined消息

    客户端收到joined消息后,就要创建RTCPeerConnection了,也就是要建立一条与远端通话的音视频数据传输通道

    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对象有三个参数:

        1、RTCConfiguration类型的对象,该对象中最重要的一个字段是iceservers。它里面存放了stun/turn服务器地址。其主要作用是用于NAT穿越。

        2、RTCMediaConstraints类型对象,也就是对RTCPeerConnection的限制。

            如:是否接受视频数据?是否接受音频数据?如果要与浏览器互通还要开启DtlsSrtpKeyAgreement选项

        3、委托类型。相当于给RTCPeerConnection设置一个观察者。这样RTCPeerConnection可以将一个状态/信息通过它通知给观察者。

    RTCPeerConnection建立好之后,接下来就是整个实时通话过程中,最重要的部分,媒体协商

媒体协商

    媒体协商内容使用的是SDP协议

    Amy与Bob进行通话,通话的发起方(Amy),首先要创建Offer类型的SDP消息,之后调用RTCPeerConnection对象的setLocalDescription方法,将Offer保存到本地

    紧接着,将Offer发送给服务器。然后通过信令服务器中转到被呼叫方(Bob)。被呼叫方收到Offer后,调用它的RTCPeerConnection对象的setRemoteDescription方法,将远端的Offer保存起来

    之后,被呼叫方创建Answer类型的SDP内容,并调用RTCPeerConnection对象的setLocalDescription方法将它存储到恩地

    同样的,它也要将Answer发送给服务器。服务器收到该消息后,不做任何处理,直接中转给呼叫方。呼叫方收到Answer后,调用setRemoteDescription将其保存起来

手绘图

    通过上面的步骤,整个媒体协商部分就完成了

    [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方法创建Offer SDP。它有两个参数

        1、RTCMediaConstraints类型的参数

        2、匿名回调函数。可以通过对error是否为空来判定offerForConstraints方法有没有执行成功。如果执行成功啦,参数sdp就是创建好的SDP内容

    如果成功获得了SDP,首先存到本地,然后再将它发送给服务端,服务器中转给另一端

    [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];

    });

    其实就是做了两件事。一是调用setLocalDescription方法将SDP保存到本地,另一件事就是发消息。

    当整个协商完成后,紧接着,在WebRTC底层就会进行音视频数据的传输。

渲染远端视频:

    在创建RTCPeerConnection对象时,同时给RTCPeerConnection设置了一个委托

    该委托对象中,实现了所有RTCPeerConnection对象的代理方法,其中比较关键的有下面几个:

    1、- (void)peerConnection:(RTCPeerConnection *)peerConnection didGenerateIceCandidate:(RTCIceCandidate *)candidate;//该方法用于收集可用的Candidate。

    2、-(void)peerConnection:(RTCPeerConnection *)peerConnection

didChangeIceConnectionState:(RTCIceConnectionState)newState;//当ICE连接状态发生变化时会触发该方法

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

didAddReceiver:(RTCRtpReceiver *)rtpReceiver streams:(NSArray *)mediaStreams;//该方法在侦听到远端track时会触发。

    那么,什么时候开始渲染远端视频呢?当有远端视频流过来的时候,就会触发 

      -(void)peerConnection:(RTCPeerConnection *)peerConnection didAddReceiver:(RTCRtpReceiver *)rtpReceiver streams: (NSArray *)mediaStreams方法。所以我们只需要在该方法中写一些逻辑即可。

    当上面的函数被调用后,我们可以通过rtpReceiver参数获取到track。这个track有可能是音频trak,也有可能是视频trak。所以,我们首先要对 track 做个判断,看其是视频还是音频。

    如果是视频的话,就将remoteVideoView加入到trak中,相当于给track添加了一个观察者,这样remoteVideoView就可以从track获取到视频数据了。在remoteVideoView实现了渲染方法,一量收到数据就会直接进行渲染。最终,我们就可以看到远端的视频了。

    具体代码如下:

    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];

    }

通过上面的代码,我们就可以将远端传来的视频展示出来了。

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

总结:(七步骤)

权限申请

引入WebRTC库

获取本地视频

信令驱动

创建音视频数据通道

媒体协商

渲染远端视频

上一篇下一篇

猜你喜欢

热点阅读