WebRTC 在 iOS 端实现一对一通信

avatar
作者
筋斗云
阅读量:0

WebRTC 在 iOS 端实现一对一通信

WebRTC 在 iOS 端实现一对一通信

在 iOS 端,我们将按以下几个步骤实现 WebRTC 一对一通信:

  1. 申请权限
  2. 引入 WebRTC 库
  3. 构造 PeerConnectionFactory
  4. 创建音视频源
  5. 视频采集
  6. 本地视频预览
  7. 建立信令系统
  8. 创建 RTCPeerConnection
  9. 远端视频渲染

申请权限

为了让您的应用能够使用麦克风和摄像头,您需要在应用的Info.plist文件中添加相应的权限配置。以下是设置应用权限的步骤:

  1. 在Xcode中打开您的项目,点击项目导航器中的项目名称。
  2. 找到Info.plist文件,并展开它。
  3. 在Info.plist文件中,右键点击空白处,选择"Add Row"选项。
  4. 在弹出的窗口中,选择"Privacy - Microphone Usage Description"选项。
  5. 在右侧的值字段中,输入一条描述您应用使用麦克风的信息,例如"我们需要使用麦克风进行音频通话"。
  6. 再次右键点击空白处,选择"Add Row"选项。
  7. 在弹出的窗口中,选择"Privacy - Camera Usage Description"选项。
  8. 在右侧的值字段中,输入一条描述您应用使用摄像头的信息,例如"我们需要使用摄像头进行视频通话"。
  9. 保存并关闭Info.plist文件。

引入 WebRTC 库

接下来,您需要导入WebRTC框架和库到您的iOS项目中。通过WebRTC源码编译出WebRTC库,然后再项目中手动引入它。

以下是导入WebRTC的步骤:

  1. 在Xcode中打开您的项目,点击项目导航器中的项目名称。
  2. 在项目设置中,选择"General"选项卡。
  3. 在"Embedded Binaries"部分点击"+"按钮。
  4. 在弹出的窗口中,点击"Add Other…"按钮,并选择WebRTC.framework文件。
  5. 确保在"Add to targets"选项中勾选您的项目。
  6. 在弹出的窗口中,选择"Copy items if needed"选项,并点击"Finish"按钮。
  7. 等待Xcode将WebRTC.framework文件导入到项目中。

WebRTC官方会定期发布编译好的WebRTC库,也可以使用Pod方式进行安装(GoogleWebRTC)。我们只需要写个 Podfile 文件就可以了。在 Podfile 中可以指定下载 WebRTC 库的地址,以及我们要安装的库的名字。

Podfile 文件的具体格式如下:

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

有了 Podfile 之后,在当前目录下执行 pod install 命令,这样 Pod 工具就可以将 WebRTC 库从源上来载下来。

在执行 pod install 之后,它除了下载库文件之外,会为我们产生一个新的工作空间文件,即 {project}.xcworkspace。在该文件里,会同时加载项目文件及刚才安装好的 Pod 依赖库,并使两者建立好关联。

这样,WebRTC库就算引入成功了。下面就可以开始写我们自己的代码了。

构造 RTCPeerConnectionFactory

iOS 端的工厂与 Android 端一样,只是命名上要加上 RTC 前缀。

在 WebRTC Native 层,factory 可以说是 “万物的根源”,像 RTCVideoSource、RTCVideoTrack、RTCPeerConnection这些类型的对象,都需要通过 factory 来创建。

[RTCPeerConnectionFactory initialize];      //如果点对点工厂为空 if (!factory) { 	RTCDefaultVideoDecoderFactory* decoderFactory =  		[[RTCDefaultVideoDecoderFactory alloc] init];     RTCDefaultVideoEncoderFactory* encoderFactory =      	[[RTCDefaultVideoEncoderFactory alloc] init];     NSArray* codecs = [encoderFactory supportedCodecs];     [encoderFactory setPreferredCodec:codecs[2]];          factory = [[RTCPeerConnectionFactory alloc]      	initWithEncoderFactory: encoderFactory     	decoderFactory: decoderFactory]; } 

首先要调用 RTCPeerConnectionFactory 类的 initialize 方法进行初始化。然后创建 factory 对象。需要注意的是,在创建 factory 对象时,传入了两个参数:一个是默认的编码器;一个是默认的解码器。我们可以通过修改这两个参数来达到使用不同编解码器的目的。

创建音视频源

分别创建音视频数据源对象(Source),分别创建音视频 Track,分别将音视频源绑定到对应的 Track 上。

RTCAudioSource* audioSource = [factory audioSource]; RTCAudioTrack* audioTrack =  	[factory audioTrackWithSource:audioSource trackId:@"ARDAMSa0"]  RTCVideoSource* videoSource = [factory videoSource]; RTCVideoTrack* videoTrack =  	[factory videoTrackWithSource:videoSource trackId:@"ARDAMSv0"] 

视频采集

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

创建对象:

capture = [[RTCCameraVideoCapturer alloc] initWithDelegate:videoSource]; 

获取所有视频设备:

NSArray<AVCaptureDevice*>* devices = [RTCCameraVideoCapture captureDevices]; AVCaptureDevice* device = devices[0]; 

开启摄像头:

[capture startCaptureWithDevice:device                     format:format 					fps:fps]; 

现在已经可以通过RTCCameraVideoCapturer类控制视频设备来采集视频了, 那如何获取采集的视频流呢?上面的代码我们已经将视频采集到视频源RTCVideoSource了,那RTCVideoSource就是我们的视频流吗?显然不是。这里要提到的是WebRTC三大对象中的其中一个对象RTCMediaStream,它才是我们说的视频流。

视频采集的流程:

  1. RTCCameraVideoCapturer 将采集的视频数据交给RTCVideoSource
  2. 通过RTCVideoSource 创建 RTCVideoTrack
  3. RTCMediaStream 添加视频轨 videoTrack。

本地视频预览

在 iOS 端,WebRTC 准备了两种 View:

  1. RTCCameraPreviewView:专门用于预览本地视频。不再从 RTCVideoTrack 获得数据,而是直接从 RTCCameraVideoCapturer 获取,效率更高。
  2. RTCEAGLVideoView:显示远端视频。

viewDidLoad() 在应用程序启动后被调用,属于应用程序生命周期的开始阶段。

@property (strong, nonatomic) RTCCameraPreviewView *localVideoView;  - (void)viewDidLoad { 	CGRect bounds = self.view.bounds; 	self.localVideoView = [[RTCCameraPreviewView alloc] 		initWithFrame:CGRectZero]; 	[self.view addSubview:self.localVideoView];  	CGRect localVideoFrame = 		CGRectMake(0, 0, bounds.size.width, bounds.size.height); 	[self.localVideoView setFrame:localVideoFrame]; } 

在 viewDidLoad() 函数里我们创建并初始化了一个 RTCCameraPreviewView,将 localVideoView 对象添加到应用程序的 Main View 中,最后设置了大小和显示位置。

关联 localVideoView 和 RTCCameraVideoCapturer:

self.localVideoView.captureSession = capture.captureSession; 

传递 captureSession 后,localVideoView 就可以从 RTCCameraVideoCapturer 上获取数据并渲染了。

建立信令系统

在 iOS 端我们仍然使用 socket.io 与信令服务器连接。

Podfile:

source 'https://github.com.CocoaPods.Specs.git'  use_frameworks! platform : ios, '9.0' target 'YourProjectName' do   pod 'Socket.IO-Client-Swift', '~> 1.0' end 

信令的使用:

  1. 通过url获取socket。有了socket之后就可建立与服务器的连接了。
  2. 注册侦听的消息,并为每个侦听的消息绑定一个处理函数。当收到服务器的消息后,随之会触发绑定的函数。
  3. 通过socket建立连接。
  4. 发送信令。

通过url获取socket:

SocketIOClient* socket; NSURL* url =[[NSURL alloc]initWithString:addr]; manager = [[SocketManager alloc] initWithSocketURL:url 	config:@{ 		@"log": @YES, 		@"forcePolling":@YES, 		@"forceWebsockets":@YES 	}]; socket = manager.defaultSocket; 

为socket注册侦听消息,以 joined 消息为例:

[socket on:@"joined" callback:^(NSArray* data,SocketAckEmitter* ack) { 	NSString* room =[data objectAtIndex:0]; 	NSLog(@"joined room(%@)", room); 	[self.delegate joined:room];     }]; 

连接信令服务器:

[socket connect]; 

使用 emit 方法发送信令:

if(socket.status == SocketIOStatusConnected) { 	[socket emit:@"join" with:@[room]]; } 

创建 RCTPeerConnection

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

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

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

创建 RCTPeerConnection:

 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 建立好之后,在建立物理连接之前,还需要进行媒体协商。

创建Offer类型的SDP消息:

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

当整个协商完成后,紧接着会交换 Candidate,在WebRTC底层开始建立物理连接。网络连接完成后,双方就会进行音视频数据的传输。

远端视频渲染

将 RTCEAGLVideoView 与远端视频的 Track 关联:

RTCEAGLVideoView* remoteVideoView;  (void)peerConnection: 	didAddReceiver:(RTCRtpReceiver *)rtpReceiver 	streams:(NSArray *)mediaStreams { 	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]; 	} 

peerConnection:didAddReceiver:streams 函数与 JS 的 ontrack 类似,当有远端的流传来时,就会触发该函数。从 rtpReceiver 中获取远端的 track 后,把它添加到 remoteVideoTrack 中,这样 remoteVideoView 就可以从 track 中获取视频数据了。

参考

  1. https://webrtc.org.cn/20190517_tutorial4_webrtc_ios/

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!