WebRTC 在 iOS 端实现一对一通信

WebRTC 在 iOS 端实现一对一通信

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

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' dopod '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: encoderFactorydecoderFactory: 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:deviceformat:formatfps: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' dopod '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:urlconfig:@{@"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 = [factorypeerConnectionWithConfiguration:configurationconstraints:[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:sdpcompletionHandler:^(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 *)rtpReceiverstreams:(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/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/19326.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python高级编程:并发编程与异步编程

Python高级编程:并发编程与异步编程 在前几篇文章中,我们介绍了Python的基础语法、面向对象编程、标准库以及第三方库的使用。在这篇文章中,我们将深入探讨Python中的并发编程和异步编程。这些技术对于提高程序性能和响应速度至关重要,特别是在处理I/O密集型任务时。通过本…

【kubernetes】探索k8s集群的存储卷、pvc和pv

目录 一、emptyDir存储卷 1.1 特点 1.2 用途 1.3部署 二、hostPath存储卷 2.1部署 2.1.1在 node01 节点上创建挂载目录 2.1.2在 node02 节点上创建挂载目录 2.1.3创建 Pod 资源 2.1.4访问测试 2.2 特点 2.3 用途 三、nfs共享存储卷 3.1特点 3.2用途 3.3部署 …

token 无感刷新

什么是 token 无感刷新&#xff1f;为什么需要 token 无感刷新&#xff1f;让我们想象一下有这么个场景&#xff1a;你登录一个系统成功后&#xff0c;玩了 10 分钟&#xff0c;发现登录失效了&#xff0c;又要你重新登录&#xff0c;然后又过 10 分钟&#xff0c;又失效了&…

Python Flask框架(五)数据库

数据库是大多数动态Web程序的基础设施&#xff0c;本章主要介绍如何给Flask程序添加数据库支持&#xff0c;具体来说就是在Python中使用DBMS来对数据库进行管理和操作。 使用ORM不光可以解决SQL注入的问题&#xff0c;而且它为不同的DBMS提供统一的Python接口库&#xff0c;使…

芝浦工业大学利用 NetApp 提供的多层 AI 驱动型安全解决方案抵御网络攻击

成效 10,000 名学生和教职工受到现代存储基础架构的保护NetApp AI 技术可自动检测异常并即时创建数据备份借助 NetApp 重复数据删除和数据压缩将数据缩减了 60% NETAPP 打造现代化的数据保护 众所周知&#xff0c;教育机构很难防范网络攻击。学生、员工和教职工都需要使用自己…

全面解析API网关:动态路由、协议转换及安全防护策略

1.API网关简介与架构演进 1.1. 什么是API网关 API 网关是一种服务器&#xff0c;是多个客户端应用程序至后端服务数据流的中间层。它作为单一的接入点&#xff0c;处理所有应用程序之间的请求通信。API 网关的主要功能包括请求路由、API 组合、策略执行和转换请求和响应。 1…

Web程序设计-实验05 DOM与BOM编程

题目 【实验主题】 影视网站后台影视记录管理页设计 【实验任务】 1、浏览并分析多个网站后台的列表页面、编辑页面&#xff08;详见参考资源&#xff0c;建议自行搜索更多后台页面&#xff09;的主要元素构成和版面设计&#xff0c;借鉴并构思预期效果。 2、新建 index.h…

正则匹配优化:匹配排除多个字符串的其他字符串

(^entity|^with|...)\w优化 (?!entity|with|has|index|associations|input)\w(?!): 匹配排除项 效果 继续优化 匹配会过滤掉带有关键字的字段&#xff0c;在过滤的时候是可以加上尾部结束匹配符的 效果&#xff1a;

thinkphp6 自定义的查询构造器类

前景需求&#xff1a;在查询的 时候我们经常会有一些通用的&#xff0c;查询条件&#xff0c;但是又不想每次都填写一遍条件&#xff0c;这个时候就需要重写查询类&#xff08;Query&#xff09; 我目前使用的thinkphp版本是6.1 首先自定义CustomQuery类继承于Query <?p…

【C语言回顾】预处理

前言1. 简单概要2. 预处理命令讲解结语 上期回顾: 【C语言回顾】编译和链接 个人主页&#xff1a;C_GUIQU 归属专栏&#xff1a;【C语言学习】 前言 各位小伙伴大家好&#xff01;上期小编给大家讲解了C语言中的编译和链接&#xff0c;接下来我们讲解一下预处理&#xff01; …

【香橙派 AIpro】新手保姆级开箱教程:Linux镜像+vscode远程连接

香橙派 AIpro 开发板 AI 应用部署测评 写在最前面一、开发板概述官方资料试用印象适用场景 二、详细开发前准备步骤1. 环境准备2. 环境搭建3. vscode安装ssh插件4. 香橙派 AIpro 添加连接配置5. 连接香橙派 AIpro6. SSH配置 二、详细开发步骤1. 登录 juypter lab2. 样例运行3. …

MySQL主从搭建--保姆级教学

MYSQL主从搭建步骤 主节点 # 进入目录 cd /opt# 下载安装包 wget https://dev.mysql.com/get/Downloads/MySQL-8.0/mysql-8.0.20-linux-glibc2.12-x86_64.tar.xz# 解压 tar -xvf mysql-8.0.20-linux-glibc2.12-x86_64.tar.xz# 拷贝到/usr/local mv /opt/mysql-8.0.20-linux-g…

【IDEA】-使用IDEA查看类之间的依赖关系

1、父子类的继承、实现关系 1.1、使用CTRL Alt U 选择 java class 依据光标实际指向的类位置 用实心箭头表示泛化关系 是一种继承的关系&#xff0c;指向父类 可以提前设置需要显示的类的属性、方法等信息 快捷键 Ctrl Alt S &#xff0c;然后搜索 Diagrams 1.2、使用…

python知识继续学习

1、计算机表示小数是有误差的&#xff0c;下面的5就是误差 2、在python中&#xff0c;所有的非0数字都是True&#xff0c;零是False。所有的非空字符串都是True&#xff0c;空字符串是False。空列表是False。在python的基本数据类型中&#xff0c;表示空的东西都是False&#x…

数据结构(三)循环链表 约瑟夫环

文章目录 一、循环链表&#xff08;一&#xff09;概念&#xff08;二&#xff09;示意图&#xff08;三&#xff09;操作1. 创建循环链表&#xff08;1&#xff09;函数声明&#xff08;2&#xff09;注意点&#xff08;3&#xff09;代码实现 2. 插入&#xff08;头插&#x…

【linux】运维-基础知识-认知hahoop周边

1. HDFS HDFS&#xff08;Hadoop Distributed File System&#xff09;–Hadoop分布式文件存储系统 源自于Google的GFS论文&#xff0c;HDFS是GFS的克隆版 HDFS是Hadoop中数据存储和管理的基础 他是一个高容错的系统&#xff0c;能够自动解决硬件故障&#xff0c;eg&#xff1a…

【Linux 网络编程】网络的背景、协议的分层知识!

文章目录 1. 计算机网络背景2. 认识 "协议"3. 协议分层 1. 计算机网络背景 网络互联: 多台计算机连接在一起, 完成数据共享; &#x1f34e;局域网&#xff08;LAN----Local Area Network&#xff09;: 计算机数量更多了, 通过交换机和路由器连接。 &#x1f34e; 广…

多模态模型入门:BLIP与OWL-ViT

BLIP 数据预处理 CapFilt&#xff1a;标题和过滤 由于多模态模型需要大量数据集&#xff0c;因此通常必须使用图像和替代文本 (alt-text) 对从互联网上抓取这些数据集。然而&#xff0c;替代文本通常不能准确描述图像的视觉内容&#xff0c;使其成为噪声信号&#xff0c;对于…

MAC M1 —— Install

文章目录 MAC M1 —— Install安装IDEA安装JDK安装Maven安装brew无法创建文件 /data/serverMac 修改终端用户名&#xff08;主机名&#xff09;PyCharm MAC M1 —— Install 安装IDEA 关键词&#xff1a;2020到2021.3的激活步骤。找下Download文件夹 安装JDK 在个人的电脑上…

思维+滑动窗口,LeetCode 2831. 找出最长等值子数组

目录 一、题目 1、题目描述 2、接口描述 python3 cpp JS 3、原题链接 二、解题报告 1、思路分析 2、复杂度 3、代码详解 python3 cpp JS 一、题目 1、题目描述 给你一个下标从 0 开始的整数数组 nums 和一个整数 k 。 如果子数组中所有元素都相等&#xff0c;则认…