RTC_SDK集成开发指南1、修订记录2、概述2.1 简介2.2 面向读者2.3 获取Demo2.4 技术支持3、 编写说明4 、环境准备4.1 工程准备4.2 开发工具Xcode4.3 SDK Demo工程运行(导入)4.4 Xcode15报错解决5、音视频开发示例5.1 获取授权5.2 引擎(AVDEngine)模块5.3 房间(AVDRoom)模块5.4 加入房间5.5 视频(AVDVideo)模块5.6 音频(AVDAudio)模块5.7 用户(AVDUserManager)模块5.8 屏幕(AVDScreen)共享模块5.9 电子白板(AVDWhiteboard)模块5.10 聊天(AVDChat)模块5.11 日志(LOG)文件5.12 常见问题
| *修订日期* | *描述* | *作者* | *版本号* |
|---|---|---|---|
| 2019.6.3 | 初始文档 | 张启金 | 3.2.0.46 |
| 2024.08.16 | 修改文档 | 张启金 | 3.2.0.60 |
Open-AVD SDK提供人与人实时沟通协作过程中需要用到的所有基本能力,涵盖了网络会议系统、IM即时通讯系统及直播系统三大类终端产品音视频通讯的主要功能。
Open-AVD SDK由业界资深工程师精心打造,稳定可靠,第三方团队拿来就能用,不必自己去造“轮子”,从而降低了第三方团队的技术风险,减少了项目的开发投入,尤其是能大幅缩短第三方团队开发具有多方音视频+数据协作能力的App/Web应用的时间。
Open-AVD SDK可用于几乎所有行业,很多业务场景中需要用到人与人实时沟通与协作的能力,而类似QQ,微信或会议系统这种通用沟通工具又不能直接使用或不能满足功能,这种情况下,Open-AVD SDK就是您最好的选择。市场调研表明,Open-AVD SDK在医疗、教育、金融、能源、交通等各个领域,都有巨大的市场需求。
Open-AVD SDK 为移动、桌面和互联网应用提供一个完善的音视频及数据互动开发框架,屏蔽掉互动系统的复杂细节,对外提供较为简洁的 API 接口,方便第三方应用快速集成互动功能。
Open-AVD SDK iOS版提供如下功能:
房间管理服务
消息传输服务
实时高清语音通话
实时高清视频通话
桌面共享服务
透明数据通道服务
互动白板服务
服务器外呼SIP、H323、RTSP、RTMP
多路视频合屏
服务器端录制
服务器端旁路直播
本指南是提供给具有一定的iOS编程经验和了解面向对象概念的产品经理及程序员使用,Open-AVD SDK已很好的封装了音视频相关的底层技术细节,因而读者不需要具备音视频开发方面的经验。
公司的github网址(https://github.com/3tee)上会提供各类基于Open-AVD SDK的实例开发的Demo,包括基本音视频能力Demo, 桌面共享直播Demo等。
您在使用本SDK的过程中,遇到任何困难,请与我们联系,我们将热忱为你提供帮助。你可以通过如下方式与我们取得联系。
技术支持工程师:微信号:
Important
shanksJob
本指南编写目的是为了帮助使用AVD SDK的用户快速搭建SDK的开发环境、熟悉开发流程、掌握SDK开发功能接口而编写的。
本指南基于iOS开发IDE Xcode9.0及以上,导入baseVideo Demo进行最简单的音视频能力进行编写,如果需要更多的功能,请参阅SDK API接口开发手册以及官网在线文档
a、点击在XCode工程左侧资源管理器中的工程图标。在右侧菜单TARGETS ->General->Link Frameworks and Libraries 的路径里,点击“+”号增加系统框架。如下图所示:

b、需要添加的系统库如下图:

c、在添加开发框架步骤的同一处,点击“Add other”选项导入avd_sdk.framework包,该framework开发包在“sdk/framework/”目录里。如下图所示

d、创建全局头文件
e、导入SDK API 头文件, avd_sdk.framework下面的avd_sdk.h已经包含了全部功能模块的头文件导入,只需要在全局头文件中导入一次 #import <avd_sdk/avd_sdk.h>即可
f、如需使用 SDK 提供的音视频功能,需要给 App 授权麦克风和摄像头的使用权限。在 App 的 Info.plist 中添加以下两项,分别对应麦克风和摄像头在系统弹出授权对话框时的提示信息。
Privacy - Microphone Usage Description,并填入麦克风使用目的提示语。
Privacy - Camera Usage Description,并填入摄像头使用目的提示语。

g、如需 App 进入后台仍然运行相关功能,可在 XCode 中选中当前工程项目,并在 Capabilities 下将设置项 Background Modes 设定为 ON,并勾选 Voice over ip,如下图所示:

本指南默认的开发工具为Xcode9.0及以上(https://developer.apple.com/support/xcode/
), 它是Apple推出的一款支持iOS macOS的苹果开发软件,运行SDK Demo非常方便。
a、 在2.3中下载的SDK Demo,通过如下如图所示导入:

b、找到下载的demo的文件夹如图示例

c、SDK Demo 导入工程后,选择真机调试,截图如下:

d、点击运行按钮(快捷键:command+R)开始运行demo代码,截图如下:

e、真机调试需要登录个人开发者账号或公司的开发账号,AppleID登录(Xcode->Preferences->Accounts->”+”),截图及步骤如下:

f、如果运行报错请选择登录自己AppleID使用个人开发者选择个人开发证书,截图如下:

a、xcode15以上版本集成sdk3.2.0版本后运行可能会遇到如下报错

b、解决办法如下:(-ld64)

本开发示例只涉及到音视频的基本功能,以baseVideo 基础Demo加以说明,其它进阶的功能,如屏幕共享、电子白板、聊天、音视频的导出、呼叫外呼设备等的支持,可以参阅SDK API接口开发或咨询我们的技术支持工程师发送更完成工程的Demo示例.
针对正式客户及测试客户,公司会提供一对有效的Access Key 和Secret Key, 这对密钥可以产生访问令牌(24小时有效),用于生成会议房间号,及加会房间等操作。
目前SDK Demo提供的测试的授权信息及服务器地址如下(ps:如需修改请在demo中N2Meeting.m文件中修改),公司会不定期的更新对应的key,有问题请咨询技术支持。
//音视频服务器地址//dev.3tee.cn:441"//appKey//secretKey1、 引擎初始化可以放到AppDeleagte中也可能根据需要再使用音视频时在去初始化引擎,引擎初始化成功之后才能正常操作调用音视频相关代码,示例代码如下
[[AVDEngine instance] initWithServerUrl:_serverTf.text accessKey:_accessTf.text secretKey:_sccretTf.text delegate:self];2、引擎初始化是异步回调,需要再回调接口中根据Result判断成功或者失败
- (void)onInitResult:(AVDResult)result{ NSString *msg = @""; if(result == AVD_Success){ msg = @"初始化引擎成功"; [self toastMsg:msg]; }else{//请先确认2个key和音视频服务器是否正确,如果还有问题请联系技术支持 msg = [NSString stringWithFormat:@"请先确认2个key和音视频服务器是否正确,如果还有问题请联系技术支持 code = %ld",(long)result]; [self toastMsg:msg]; }}3、配置本地摄像头分辨率,也可以不设置,使用sdk默认分辨率;
NSInteger fps = [[self.fpsSegment titleForSegmentAtIndex:self.fpsSegment.selectedSegmentIndex] intValue];NSString *resolution = [NSString stringWithFormat:@"{\"width\":720,\"height\":1280,\"maxFPS\":%ld}",(long)fps];[[AVDEngine instance] setOption:camera_capability_default value:resolution];4、初始化引擎错误
| 错误码 | 说明 |
|---|---|
| 401 | 传入的密匙Access Key 和Secret Key错误。 |
| 405 | 服务器授权过期,请联系续期。 |
| 436 | AVD SDK版本和服务器版本不适配,请联系我方对服务器做适配调整。 |
| 1008 | 初始化引擎传入的上下文context无效(为空或未传)。 |
| 1014 | 网络错误,需要排查传入的服务器地址是否正确,设备网络是否正常连接,网络质量是否良好,服务器端部署是否正常。 |
1、创建房间,根据传人的房间信息创建一个房间,roomId不传则系统自己生成一个随机数房间号
AVDRoomInfo *roomInfo = [[AVDRoomInfo alloc]initWithRoomId:self.roomId.text roomName:self.roomId.text appRoomId:self.roomId.text ownerId:self.roomId.text maxAttendee:5 maxAudio:5 maxVideo:5 roomMode:5 voiceActivated:YES];[roomInfo setRoomModeFlag:mcu];[[AVDEngine instance] scheduleRoom:roomInfo];2、房间创建结果异步回调,根据返回的Result判断是否成功
- (void)onScheduleRoomResult:(AVDResult)result roomId:(AVDRoomId)roomId{ if (AVD_Success == result) { msg = @"房间创建成功"; }else { msg = [NSString stringWithFormat:@"房间创建失败,请对照code = %ld 查看失败原因",(long)result]; }}3、创建房间错误码
| 错误码 | 说明 |
|---|---|
| 40003/40005 | 重复创建房间,房间已存在。 |
| 其他非0错误码 | 非0则是创建失败,这类错误码相对较少,出现时和我方确认解决。 |
1、根据传人的roomId加入房间,确保userid的唯一性,如果一个房间有2个相同的userid先入会的user就会被踢出房间
AVDRoom* room = [AVDRoom obtain:roomId]; AVDUser *user = [[AVDUser alloc]initWithUserId:[NSUUID UUID].UUIDString userName:name userData:@""]; [room joinWithUser:user password:nil delegate:self];2、加入房间结果异步回调,根据返回的Result判断是否成功
- (void)onJoinResult:(AVDResult)result{ NSString *msg = @""; if(result == AVD_Success){ msg = @"加入成功!!!!"; }else{ msg = [NSString stringWithFormat:@"加入房间失败 code = %ld",(long)result]; } [self toastMsg:msg];}3、房间网络状态,比如网络不好提示用户
xxxxxxxxxx-(void)onConnectionStatus:(enum AVDConnectionStatus)status{ if(status == connected){ [self toastMsg:@"重连成功。。。。"]; }else if(status == connecting){ [self toastMsg:@"重连中。。。。"]; }}4、自己被踢出房间
xxxxxxxxxx- (void)onLeaveIndication:(AVDResult)reason fromUser:(AVDUserId)fromId{ if(reason == 807){ [self toastMsg:@"udp不通"]; }else if(reason == 808){ [self toastMsg:@"你已经被提出房间"]; }if(reason == 810){ [self toastMsg:@"房间已经被其他用户使用sdk接口关闭"]; }if(reason == 811){ [self toastMsg:@"服务器正在重启房间已经被关闭"]; }if(reason == 815){ [self toastMsg:@"房间已经被其他用户使用rest接口关闭"]; } [self.mRoom destory];//释放sdk资源}| 特征码 | 离会原因 |
|---|---|
| 0 | 正常离会,用户自己调用destoryRoom离会 |
| 804 | 被相同userId用户入会挤下线 |
| 807 | UDP媒体通道建立超时,常见于join加会时。 |
| 808 | 该用户被其他用户踢出房间 |
| 809 | 服务没有授权用户入会 |
| 810 | 用户关闭房间时,所有用户被踢出房间。 |
| 811 | 服务器维护是关闭房间 |
| 812 | 和服务器之间心跳超时,异常离会。例如:网络中断、APP被系统或后台杀掉。 |
5、离开房间 ps:不会影响房间内其他参会者
xxxxxxxxxx[self.mRoom leave:0];
6、关闭房间 ps:当前房间参会者都会被踢出房间
xxxxxxxxxx[self.mRoom close];
6、释放sdk资源
xxxxxxxxxx[self.mRoom destory];
7、加入房间错误码说明
| 错误码 | 说明 |
|---|---|
| 401 | 服务器认证失败 |
| 404 | 房间不存在 |
| 405 | 服务器授权过期,请联系续期。 |
| 1014 | 网络异常,检查设备和服务器整个网络链路是否正常。 |
1、根据room实例获取视频模块实例并设置代理,后续的打开关闭以及房间内其他用户的视频状态都会通过video回调和操作
xxxxxxxxxxself.mVideo = [AVDVideo getVideo:self.mRoom];self.mVideo.delegate = self;2、预览本地摄像头视频
xxxxxxxxxx [self.mVideo previewLocalCamera:front render:self.localRender];33、发布(打开)本地摄像头,默认打开的前置摄像头,也可以调用指定类型打开摄像头
xxxxxxxxxx[self.mVideo publishLocalCamera];4、取消发布(关闭)本地摄像头
xxxxxxxxxx[self.mVideo unpublishLocalCamera];
5、摄像头切换
xxxxxxxxxx[self.mVideo switchToLocalCamera];
6、发布本地摄像头回调,根据result判断是否成功进行本地视频渲染。ps:自己发布的视频不需要订阅(subscribe)操作
xxxxxxxxxx- (void)onPublishLocalResult:(AVDResult)result deviceId:(AVDDeviceId)fromId{ if(result == AVD_Success){ VideoRender *render = [self.renderContainer getFreeRender]; [self.mVideo attachRenderWithDeviceId:fromId render:render]; }else{ msg = [NSString stringWithFormat:@"发布本地摄像头失败 code = %ld",(long)result]; }}7、取消发布本地摄像头回调,根据result判断是否成功,取消视频渲染
x- (void) onUnpublishLocalResult:(AVDResult)result deviceId:(AVDDeviceId)fromId{ if(result == AVD_Success){ AVDVideoView *render = [self.renderContainer getRenderWithDeviceId:camera.id]; [self.mVideo detachRenderWithRender:render]; }else{ msg = [NSString stringWithFormat:@"取消发布本地摄像头失败 code = %ld",(long)result]; }}
8、房间内其他参会者发布摄像头回调,并且进行订阅渲染
xxxxxxxxxx- (void)onPublishCameraNotify:(AVDCamera *)camera{ //因为在onPublishLocalResult里面已经渲染了自己视频所以需要排查自己的摄像头 if([self.mVideo isSelfDevice:camera.id]){ return; } //订阅 [self.mVideo subscribe:camera.id]; //渲染 [self.mVideo attachRenderWithDeviceId:camera.id render:[self.renderContainer getFreeRender]];}9、房间内其他参会者取消发布摄像头回调,并且进行取消订阅和渲染
xxxxxxxxxx- (void)onUnpublishCameraNotify:(AVDCamera *)camera{ [self.mVideo unsubscribe:camera.id]; AVDVideoView *render = [self.renderContainer getRenderWithDeviceId:camera.id]; [self.mVideo detachRenderWithRender:render]; [render setVideoId:@""];}10、获取加入房间之前已经发布的摄像头列表
xxxxxxxxxxself.mVideo.publishedCameras11、视频订阅渲染大致流程图如下
xxxxxxxxxx/**1、获取视频模块2、设置代理3、收到其他参会者发布摄像头回调4、在回调中订阅摄像头id5、在回调中渲染订阅的视频6、收到其他参会者取消发布摄像头回调7、在取消发布回调中取消订阅摄像头id8、在取消发布回调中取消渲染订阅的视频*//*1、self.mVideo = [AVDVideo getVideo:self.mRoom];2、self.mVideo.delegate = self;3、- (void)onPublishCameraNotify:(AVDCamera *)camera4、[self.mVideo subscribe:camera.id];5、[self.mVideo attachRenderWithDeviceId:camera.id render:render];6、- (void) onUnpublishLocalResult:(AVDResult)result deviceId:(AVDDeviceId)fromId{7、[self.mVideo unsubscribe:camera.id];8、[self.mVideo detachRenderWithRender:render];*/1、根据room实例获取音频模块实例并设置代理,后续的打开关闭以及房间内其他用户的音频状态都会通过audio回调和操作
xxxxxxxxxx self.mAudio = [AVDAudio getAudio:self.mRoom];2、打开本地音频
xxxxxxxxxx[self.mAudio openMicrophone];3、关闭本地音频
xxxxxxxxxx[self.mAudio closeMicrophone];4、设置外放/耳麦
xxxxxxxxxx//耳麦[self.mAudio setSpeakerMode:SM_Receiver];//外放[self.mAudio setSpeakerMode:SM_Handfree];/
1、根据room实例获取用户模块实例并设置代理,房间内其他用户的入会离会都会通过user回调和操作
xxxxxxxxxxself.mUser = [AVDUserManager getUserManager:self.mRoom];self.mUser.delegate = self;2、用户入会通知
xxxxxxxxxx- (void)onUserJoinNotify:(AVDUser *)user{ NSString * msg = [NSString stringWithFormat:@"用户:%@加入会议",user.userName]; [self toastMsg:msg];}3、用户离会通知
xxxxxxxxxx- (void)onUserLeaveNotify:(AVDUser *)user reason:(AVDResult)reason{NSString * msg = [NSString stringWithFormat:@"用户:%@离开会议",user.userName];[self toastMsg:msg];}
4、获取房间总的参会者列表
xxxxxxxxxx@property (nonatomic,retain,readonly) NSMutableArray* participants; /**< 参会者列表用户信息,数组中存放AVDUser对象 */1、根据room实例获取屏幕模块实例并设置代理,房间内其他用户的入会离会都会通过screen回调和操作
xxxxxxxxxxself.mScreen = [AVDScreen getScreen:self.mRoom];self.mScreen.delegate = self;2、发布屏幕共享
xxxxxxxxxx[self.mScreen publishScreen:screen_resolution_720p fps:15];3、取消发布
xxxxxxxxxx[self.mScreen unpublishScreen];4、其他人发布屏幕共享,订阅渲染
xxxxxxxxxx- (void)onPublishScreenNotify:(AVDVideoDevice *)screen{ if([self.mScreen isSelfDevice:screen.id]){ return; } [self.mScreen subscribe:screen.id]; [self.mScreen attachRenderWithDeviceId:screen.id render:render];}5、其他人取消发布,取消订阅渲染
xxxxxxxxxx- (void)onUnpublishScreenNotify:(AVDVideoDevice *)screen{ [self.mScreen unsubscribe:screen.id]; [self.mScreen detachRenderWithRender:render];}1、根据room实例获取白板模块实例并设置代理,房间内其他用户的入会离会都会通过board回调和操作
xxxxxxxxxxself.mboard = [AVDWhiteboard getWhiteboard:self.mRoom];self.mboard.delegate = self;2、发布白板
xxxxxxxxxxAVDWhiteboardInfo *info = [[AVDWhiteboardInfo alloc]init];info.boardId = [[NSUUID UUID] UUIDString];info.userId = [self.mboard.usermanager getSelfUserId];info.width = CGRectGetWidth(self.canvasView.frame);info.height = CGRectGetHeight(self.canvasView.frame);info.color = [UIColor colorWithRed:255.0/255 green:255.0/255 blue:255.0/255 alpha:1];self.boardInfo = info;[self.mboard createBoard:info];self.canvasView.hidden = NO;[self.mboard attachView:self.canvasView boardInfo:info];[self.mboard createBoard:info];AVDResult result = [self.mboard shareBoard:info.boardId];3、关闭白板
xxxxxxxxxx[self.mboard closeBoard:self.boardInfo.boardId];[self.mboard removeBoard:self.boardInfo.boardId];4、渲染白板
xxxxxxxxxx- (void)onShareBoardNotify:(AVDWhiteboardInfo *)whiteboardInfo{ self.boardInfo = whiteboardInfo; self.canvasView.hidden = NO; [self.mboard attachView:self.canvasView boardInfo:whiteboardInfo];}5、取消渲染
xxxxxxxxxx- (void)onRemoveBoardNotify:(AVDBoardId)whiteboardId{ [self.mboard dettachView:self.canvasView boardId:whiteboardId]; self.canvasView.hidden = YES;}6、更新白板背景图
xxxxxxxxxx- (void)onUpdateBoardNotify:(AVDWhiteboardInfo *)whiteboardInfo{ if (whiteboardInfo.bgUrl) { NSLog(@"img url = %@",whiteboardInfo.bgUrl); NSURL *imgURL = [NSURL URLWithString:whiteboardInfo.bgUrl]; NSData *imageData = [NSData dataWithContentsOfURL:imgURL]; UIImage *image = [UIImage imageWithData:imageData]; [self.canvasView updateBackgroundImg:image]; }}1、根据room实例获取聊天模块实例并设置代理,房间内其他用户的入会离会都会通过chat回调和操作
xxxxxxxxxxself.mchat = [AVDChat getChat:self.mRoom];self.mchat.delegate = self;2、发送公聊消息
xxxxxxxxxx[self.mchat sendPublicMessage:inputText];3、发送私聊消息
xxxxxxxxxx[self.mchat sendPrivateMessage:inputText toUser:self.currentUser.userId];4、收到公聊消息回调
xxxxxxxxxx- (void)onPublicMessage:(AVDMessage *)message{ msg = @"收到公聊消息";}5、 收到私聊消息回调
xxxxxxxxxx- (void)onPrivateMessage:(AVDMessage *)message{ msg = @"收到公聊消息";}1、设置日志文件
xxxxxxxxxx[[AVDEngine instance] setLogParams:@"info debug" file:[self getLogFileName]];2、创建日志文件存放路径,可以根据自己需求存放到自己想存放的目录
xxxxxxxxxx- (NSString*)getLogFileName { NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; [dateFormatter setDateFormat:@"yyyy-MM-dd-HHmmss"]; NSString *destDateString = [dateFormatter stringFromDate: [NSDate date] ]; NSString *fileName = [NSString stringWithFormat:@"/%@.txt", destDateString]; NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *docDir = [paths objectAtIndex:0]; docDir = [docDir stringByAppendingString:@"/logs"]; fileName = [docDir stringByAppendingPathComponent:fileName]; NSLog(@"getLogFileName fileName:%@", fileName); return fileName;}黑屏:
如果是本地打开视频黑屏则需要排查编码和打开摄像头是否有报错(发布本地摄像头回调result值是否为0、查看日志分析是否有异常);显示其他端的视频黑屏,排查是否视频有正常发布出来,可以加入其他设备进来查看是否黑屏,如果发布端没有问题,则需要排查本地解码视频是否有报错或者当前是否是弱网情况。在网络比较差的情况下,渲染多路视频较大概率出现视频显示黑屏问题,此时需要实时监测视频的媒体统计信息,视频帧率恢复正常后再去做渲染操作。
模糊花屏:
视频模糊(晃动镜头的时候会更明显)一般由于码流设置偏低导致,需要将码流适当增加(详见5.15节)。通常情况下640×480分辨率对应码流为500kbps~1000kbps,1280×720分辨率对应码流为1000kbps~1500kbps,1920×1080分辨率对应码流为2000kbps~4000kbps。注:SDK所有发布的视频(摄像头、共享屏幕、虚拟摄像头)共用一个码流配置。例如:发布一路1280×720摄像头视频,码流设置为1200kbps,帧率20帧。此时再发布一路1280×720共享桌面视频,两路720P视频总码流是1200kbps,可能会出现共享屏幕模糊问题,则需要根据发布的视频路数动态设置码流大小。
卡顿:
卡顿通常情况是编解码帧率输出不稳定(编码或解码丢帧)和网络比较差丢包引起,需要查看SDK日志分析丢帧和丢包情况,结合设备CPU占用(CPU占用过大)情况,路由器带宽占用情况,服务器交互日志具体分析。
延时:
码流设置过大导致设备编解码压力增加或者由于网络环境差存在丢包均会出现视频延时问题,此类情况需要结合终端日志和服务器交互日志具体分析。