WebRTC 在 iOS 端实现一对一通信

UestcXiye 2024-08-01 12:33:01 阅读 57

WebRTC 在 iOS 端实现一对一通信

WebRTC 在 iOS 端实现一对一通信申请权限引入 WebRTC 库构造 RTCPeerConnectionFactory创建音视频源视频采集本地视频预览建立信令系统创建 RCTPeerConnection远端视频渲染参考

WebRTC 在 iOS 端实现一对一通信

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

申请权限引入 WebRTC 库构造 PeerConnectionFactory创建音视频源视频采集本地视频预览建立信令系统创建 RTCPeerConnection远端视频渲染

申请权限

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

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

引入 WebRTC 库

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

以下是导入WebRTC的步骤:

在Xcode中打开您的项目,点击项目导航器中的项目名称。在项目设置中,选择"General"选项卡。在"Embedded Binaries"部分点击"+"按钮。在弹出的窗口中,点击"Add Other…"按钮,并选择WebRTC.framework文件。确保在"Add to targets"选项中勾选您的项目。在弹出的窗口中,选择"Copy items if needed"选项,并点击"Finish"按钮。等待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,它才是我们说的视频流。

视频采集的流程:

RTCCameraVideoCapturer 将采集的视频数据交给RTCVideoSource通过RTCVideoSource 创建 RTCVideoTrackRTCMediaStream 添加视频轨 videoTrack。

本地视频预览

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

RTCCameraPreviewView:专门用于预览本地视频。不再从 RTCVideoTrack 获得数据,而是直接从 RTCCameraVideoCapturer 获取,效率更高。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

信令的使用:

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

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

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

如:是否接受视频数据?是否接受音频数据?如果要与浏览器互通还要开启DtlsSrtpKeyAgreement选项委托类型。相当于给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。它有两个参数:

RTCMediaConstraints类型的参数。匿名回调函数。可以通过对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 中获取视频数据了。

参考

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



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。