iOS集成1对1通话

大约 17 分钟

iOS集成1对1通话

跑通Demo

1. 示例代码

或进入环信客户端下载页面,选择iOS SDK + Demo下载

2. 前提条件

运行Demo前你需要具备以下条件

  • mac操作系统10.11以上
  • 安装Xcode 11以上
  • iPhone设备iPhone 6以上,安装系统iOS 9.0以上

3. 运行Demo

3.1 Demo代码目录简介

目录 EaseIM —>Class 中的 Demo 目录介绍

img

  • Account:主要是 demo 的注册,登录
  • AppDelegate:主要是 demo 中初始化环信SDK,注册推送等
  • Chat:demo 的聊天功能页面
  • Contact:demo 的好友功能页面
  • Conversation:demo 的会话列表功能页面
  • EMIMHelper:demo 的单例类,包含全局监听接收消息,好友,群组,聊天室等相关事件的回调,从而进行对应的处理
  • Group:demo 的群组功能页面
  • Helper:demo 的功能性文件,全局通用的配置
  • Home:demo 的根控制器页面
  • Settings:demo 的功能设置页面

3.2 工程设置

进入iOS Demo目录,打开EMiOSDemo.xcworkspace ,进入工程设置的Signing & Capaabilities菜单,修改签名Team和bundleId为自己的团队开发。

3.3 运行

连接iPhone手机,选择目标设备,点击运行

快速集成

本章节介绍如何使用HyhpenateSDK 快速实现1v1音视频通话

1. 环信后台注册appkey

在开始集成前,你需要注册环信开发者账号并在后台创建应用,参见创建应用

2. 创建项目

参考以下步骤创建一个iOS 应用项目,如果已有项目,可以直接进行下一步集成。创建过程如下:

  • 打开 Xcode 并点击 Create a new Xcode project。
  • 选择项目类型为 Single View App,并点击 Next。
  • 输入项目信息,如项目名称、开发团队信息、组织名称和语言,语言为Object-C,并点击 Next。
  • 选择项目存储路径,并点击 Create。
  • 进入工程设置页面的Signing & Capaabilities菜单,选择 Automatically manage signing,并在弹出菜单中点击 Enable Automatic

3. 导入SDK到工程

集成SDK有两种方法,分别是使用cocoapods和手动导入SDK

使用cocoapods导入SDK

开始前确保你已安装 Cocoapods。

  • 在 Terminal 里进入项目根目录,并运行 pod init 命令。项目文件夹下会生成一个 Podfile 文本文件。
  • 打开 Podfile 文件,修改文件为如下内容。注意将 AppName 替换为你的 Target 名称,并将 version 替换为你需集成的 SDK 版本,如3.7.0。
target 'AppName' do
    pod 'Hyphenate', '~> version'
end
  • 在 Terminal 内运行 pod update 命令更新本地库版本。
  • 运行 pod install 命令安装 Agora SDK。成功安装后,Terminal 中会显示 Pod installation complete!,此时项目文件夹下会生成一个 xcworkspace 文件。
  • 打开新生成的 xcworkspace 文件。

手动导入SDK

  • 将在跑通Demo阶段下载的HyphenateFullSDK下的Hyphenate.framework拷贝到项目工程目录下
  • 打开工程设置/Genaral菜单下,将Hyphenate.framework拖拽到Frameworks,libraries,and Embedded Content下,并设置为Embed and Signed

工程中引入SDK,需要引用头文件Hyphenate.h

#import <Hyphenate/Hyphenate.h>

4. 添加权限

应用需要音频设备及摄像头权限,在 info.plist 文件中,点击 + 图标,添加如下信息

KeyTypeValue
Privacy - Microphone Usage DescriptionString描述信息,如“环信需要使用您的麦克风”
Privacy - Camera Usage DescriptionString描述信息,如“环信需要使用您的摄像头”

如果希望在后台运行,还需要添加后台运行音视频权限,在info.plist文件中,点击 + 图标,添加Required background modes,Type为Array,在Array下添加元素App plays audio or streams audio/video using AirPlay

5. 创建UI

音视频通话窗口中,一般包括以下几个UI控件

  • 暂停/恢复语音按钮
  • 结束通话按钮
  • 扬声器/耳机切换按钮
  • 后置摄像头切换按钮(视频)
  • 打开/关闭摄像头按钮(视频)
  • 本地图像显示与对端图像显示(视频)

通话界面可以参考Demo中Call1v1VideoViewController,效果如下:

img

6. 初始化SDK

初始化HyhpenateSDK使用initializeSDKWithOptions:接口,需要设置自己的appkey,调用如下:

// 这里替换成自己的appkey
EMOptions *retOpt = [EMOptions optionsWithAppkey:@"easemob-demo#chatdemoui"];
// 这里打开日志输出
retOpt.enableConsoleLog = YES;
[[EMClient sharedClient] initializeSDKWithOptions:retOpt];

7. 环信ID注册、登录

在进行音视频通话前,需要首先登录IM账户,登录过程参见账号登录open in new window

若您还没有IM账户,需要先注册账户,注册过程参见账号注册open in new window

8. 音视频功能初始化

账号登录成功后,需要进行音视频通话功能的初始化,设置监听类

[[EMClient sharedClient].callManager addDelegate:self delegateQueue:nil];

9. 发起通话请求

主叫方发起呼叫通话请求的过程如下:

[[EMClient sharedClient].callManager startCall:EMCallTypeVideo 
                                    remoteName:aUsername
                                           ext:@"123" 
                                    completion:^(EMCallSession *aCallSession, EMError *aError) {
                                        self.callSession = aCallSession;
}];

回调的aCallSession为本次会话session,需要在本地保存下来。

在发起视频通话,等待对方接听过程中,已经可以显示本地图像,显示本地图像的过程如下

self.callSession.localVideoView = [[EMCallLocalView alloc] init];
    self.callSession.localVideoView.scaleMode = EMCallViewScaleModeAspectFill;
    [self.minVideoView addSubview:self.callSession.localVideoView];
    [self.view bringSubviewToFront:self.minVideoView];
    [self.callSession.localVideoView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.edges.equalTo(self.minVideoView);
    }];

主叫方发起通话请求后,被叫方若已登录,将会收到如下回调通知

- (void)callDidReceive:(EMCallSession *)aSession
{
    self.callSession = aSession;
}

aSession为本次通话的session,被叫方应该在本地保存。

10. 接听通话

被叫方在收到通话请求回调后,可以选择接听/拒绝通话,若选择接听通话,调用如下:

[[EMClient sharedClient].callManager answerIncomingCall:self.callSession.callId];

此时被叫方可以显示本地视频图像

self.callSession.localVideoView = [[EMCallLocalView alloc] init];
    self.callSession.localVideoView.scaleMode = EMCallViewScaleModeAspectFill;
    [self.minVideoView addSubview:self.callSession.localVideoView];
    [self.view bringSubviewToFront:self.minVideoView];
    [self.callSession.localVideoView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.edges.equalTo(self.minVideoView);
    }];

当通话接通后,双方会收到callDidAccept回调通知,在这里可以设置远端图像

if (self.callSession.remoteVideoView == nil) {
        self.callSession.remoteVideoView = [[EMCallRemoteView alloc] init];
        self.callSession.remoteVideoView.backgroundColor = [UIColor clearColor];
        self.callSession.remoteVideoView.scaleMode = EMCallViewScaleModeAspectFit;
        self.callSession.remoteVideoView.userInteractionEnabled = YES;
    }

11. 拒绝通话

被叫方收到呼叫请求后,可以选择拒绝通话,调用过程如下:

[[EMClient sharedClient].callManager endCall:self.callSession.callId reason:EMCallEndReasonDecline];

拒绝后,主叫方收到如下回调, aReason为EMCallEndReasonDecline

- (void)callDidEnd:(EMCallSession *)aSession
            reason:(EMCallEndReason)aReason
             error:(EMError *)aError

12. 结束通话

通话中双方随时都可以结束通话,调用过程如下:

[[EMClient sharedClient].callManager endCall:@"callId" reason:EMCallEndReasonHangup];

结束后双方收到以下回调:

- (void)callDidEnd:(EMCallSession *)aSession
            reason:(EMCallEndReason)aReason
             error:(EMError *)aError

进阶功能

在实现基本视频通话的基础上,SDK提供更为丰富的API,可以实现更为复杂的音视频通话场景

取日志

SDK会写入日志文件到本地。日志文件路径如下:沙箱Documents/HyphenateSDK/easemoblog,以真机为例,获取过程如下:

  • 打开Xcode连接设备,前往Xcode –> Window –> Devices and Simulators
  • 进入Devices选项卡,在左侧选择目标设备,界面如下:

imgopen in new window

日志文件easemob.log文件在下载包内容的AppData/Library/Application Support/HyphenateSDK/easemobLog目录下

音视频控制

使用通话过程中保存的EMCallSession的对象,可以分别进行音频、视频的开关控制,切换前后摄像头等操作,操作过程如下

/*!
 *  暂停语音数据传输
 *
 *  @result 错误
 */
- (EMError *)pauseVoice;

// 调用:
[aCallSession pauseVoice];

/*!
 *  恢复语音数据传输
 *
 *  @result 错误
 */
- (EMError *)resumeVoice;

// 调用:
[aCallSession resumeVoice];

/*!
 *  暂停视频图像数据传输
 *
 *  @result 错误
 */
- (EMError *)pauseVideo;

// 调用:
[aCallSession pauseVideo];

/*!
 *  恢复视频图像数据传输
 *
 *  @result 错误
 */
- (EMError *)resumeVideo;

// 调用:
[aCallSession resumeVideo];

通话过程中可以切换前后摄像头

#pragma mark - Camera

/*!
 *  设置使用前置摄像头还是后置摄像头,默认使用前置摄像头
 *
 *  @param  aIsFrontCamera    是否使用前置摄像头, YES使用前置, NO使用后置
 */
- (void)switchCameraPosition:(BOOL)aIsFrontCamera;

// 调用:
[aCallSession switchCameraPosition:YES];

当通话一方进行音频、视频的开关控制时,另一方会收到如下回调通知

/*!
 *  用户A和用户B正在通话中,用户A中断或者继续数据流传输时,用户B会收到该回调
 *
 *  @param aSession  会话实例
 *  @param aType     改变类型
 */
- (void)callStateDidChange:(EMCallSession *)aSession
                      type:(EMCallStreamingStatus)aType;

设置通话参数

通话之前,可以设置音频通话的最大音频码率,最小视频码率、最大视频码率、分辨率和是否清晰度优先,设置方法如下

EMCallOptions *options = [[EMClient sharedClient].callManager getCallOptions];
options.maxAudioKBps = 32;
options.maxVideoKBps = 3000;
options.minVideoKBps = 500;
options.maxVideoFrameRate = 20;
options.videoResolution = EMCallVideoResolution352_288;
options.isClarityFirst = YES;//若设为清晰度优先,将在弱网环境下保证视频的分辨率

离线推送

iOS离线推送分为pushKit强推送和APNs普通推送,开启离线推送需要上传推送证书,参见APNs推送证书上传open in new windowpushKit推送集成open in new window

配置属性(在登录环信服务器成功之后设置)

EMCallOptions *options = [[EMClient sharedClient].callManager getCallOptions];
//当对方不在线时,是否给对方发送离线消息和推送,并等待对方回应
options.isSendPushIfOffline = YES;
[[EMClient sharedClient].callManager setCallOptions:options];

协议

<EMCallBuilderDelegate>

添加代理

[[EMClient sharedClient].callManager setBuilderDelegate:self];

监听回调

- (void)callRemoteOffline:(NSString *)aRemoteName
{
    NSString *text = [[EMClient sharedClient].callManager getCallOptions].offlineMessageText;
    EMTextMessageBody *body = [[EMTextMessageBody alloc] initWithText:text];
    NSString *fromStr = [EMClient sharedClient].currentUsername;
    EMMessage *message = [[EMMessage alloc] initWithConversationID:aRemoteName from:fromStr to:aRemoteName body:body ext:@{@"em_apns_ext":@{@"em_push_title":text}}];
    message.chatType = EMChatTypeChat;
    // 通过消息的ext来自定义提示铃声,其中customSound.caf为自定义铃声名称
    message.ext = @{ 
        @"em_apns_ext":@{
                @"em_push_sound":@"customSound.caf" 
        }
    };
    [[EMClient sharedClient].chatManager sendMessage:message progress:nil completion:nil];
}

因为该消息为提示铃声,所以使用自定义铃声来播放,时长需要控制在30秒以内,此处可以参考文档自定义铃声open in new window

协议,代理,回调方法建议写到工程的根控制器或者appdelegate中监听,起到全局监听的作用。

云端录制

主叫方呼叫时可以指定是否开启服务器录制,如要录制,使用以下方法呼叫,isRecord输入YES,isMerge为是否录制合流,根据需要进行设置

- (void)startCall:(EMCallType)aType
  remoteName:(NSString *)aRemoteName
      record:(BOOL)isRecord
 mergeStream:(BOOL)isMerge
         ext:(NSString *)aExt
  completion:(void (^)(EMCallSession *aCallSession, EMError *aError))aCompletionBlock;

通话统计数据

通话数据的统计功能需要主动开启,开启方法为在通话前进行如下设置

EMCallOptions *options = [[EMClient sharedClient].callManager getCallOptions];
options.enableReportQuality = YES;

开启后可以从保存的会话callSession中获取到通话的实时码率、帧率、分辨率等数据

弱网检测

SDK提供实时检测通话网络质量的功能,同样需要开启通话数据统计,开启方法同上。开启后,可以通过回调通知应用当前实时通话网络状态。

typedef enum{
    EMCallNetworkStatusNormal = 0,  /*! 正常 */
    EMCallNetworkStatusUnstable,    /*! 不稳定 */
    EMCallNetworkStatusNoData,      /*! 没有数据 */
}EMCallNetworkStatus;

/*!
 *  用户A和用户B正在通话中,用户A的网络状态出现不稳定,用户A会收到该回调
 *
 *  @param aSession  会话实例
 *  @param aStatus   当前状态
 */
- (void)callNetworkDidChange:(EMCallSession *)aSession
                      status:(EMCallNetworkStatus)aStatus

变声/自定义音频

用户可以通过自己采集音频数据,使用外部输入音频数据的接口进行通话,从而实现变声等音频数据加工功能

配置属性

用户使用自定义音频数据时,需要配置外部输入音频数据的开关,以及音频采样率,通道数(当前通道数只支持1),配置方法如下:

EMCallOptions *options = [[EMClient sharedClient].callManager getCallOptions];
options.enableCustomAudioData = YES;
options.audioCustomSamples = 48000;
options.audioCustomChannels = 1;
[[EMClient sharedClient].callManager startCall:aType remoteName:aUsername ext:@"123" completion:^(EMCallSession *aCallSession, EMError *aError) {
        completionBlock(aCallSession, aError);
}];
输入音频数据

音频数据采集可参考Demo中的AudioRecord类实现,音频数据的输入必须在会话接通后开始,否则会导致网络阻塞,影响通话质量。建议用户将音频数据采集的开始放在会话接通的回调里,及callDidAccept回调中

- (void)callDidAccept:(EMCallSession *)aSession
{
    if ([aSession.callId isEqualToString:self.currentCall.callId]) {
        [self _stopCallTimeoutTimer];
        self.currentController.callStatus = EMCallSessionStatusAccepted;
    }
    EMCallOptions *options = [[EMClient sharedClient].callManager getCallOptions];
    if(options.enableCustomAudioData){
        [self audioRecorder].channels = options.audioCustomChannels;
        [self audioRecorder].samples = options.audioCustomSamples;
        [[self audioRecorder] startAudioDataRecord];
    }
}

音频采集过程开始后,在音频数据的回调里调用外部输入音频数据接口

[[[EMClient sharedClient] callManager] inputCustomAudioData:data

会话挂断时,停止音频采集及输入过程

EMCallOptions *options = [[EMClient sharedClient].callManager getCallOptions];
if(options.enableCustomAudioData) {
    [[self audioRecorder] stopAudioDataRecord];
}

美颜/自定义视频

用户可以通过自己采集视频数据,使用外部输入视频数据的接口,实现自定义视频传输功能,可以对视频数据进行添加滤镜、美颜等功能。

配置属性

使用外部输入视频数据接口前,需要先进行配置,配置如下

//进行1v1自定义视频之前,必须设置 EMCallOptions.enableCustomizeVideoData=YES
EMCallOptions *options = [[EMClient sharedClient].callManager getCallOptions];
options.enableCustomizeVideoData = YES;
[[EMClient sharedClient].callManager startCall:aType remoteName:aUsername ext:@"123" completion:^(EMCallSession *aCallSession, EMError *aError) {
        completionBlock(aCallSession, aError);
}];
自定义摄像头数据

设置 EMCallOptions.enableCustomizeVideoData=YES 后,必须自定义摄像头数据。采集视频数据可使用AVCaptureSession实现,呼叫方的视频数据采集可以在呼叫对方时开始,而接听方的数据采集可以在按下接听按钮后开始。 外部输入视频数据的接口如下:

/*!
 *  \~chinese
 *  自定义本地视频数据
 *
 *  @param aSampleBuffer      视频采样缓冲区
 *  @param aRotation          旋转方向
 *  @param aCallId            1v1会话实例ID,即[EMCallSession callId]
 *  @param aCompletionBlock   完成后的回调
 */
- (void)inputVideoSampleBuffer:(CMSampleBufferRef)aSampleBuffer
                      rotation:(UIDeviceOrientation)aRotation
                        callId:(NSString *)aCallId
                    completion:(void (^)(EMError *aError))aCompletionBlock;

/*!
 *  \~chinese
 *  自定义本地视频数据
 *
 *  @param aPixelBuffer      视频像素缓冲区
 *  @param aCallId           1v1会话实例ID,即[EMCallSession callId]
 *  @param aTime             视频原始数据时间戳,CMTime time = CMSampleBufferGetPresentationTimeStamp((CMSampleBufferRef)sampleBuffer);
 *  @param aRotation         旋转方向
 *  @param aCompletionBlock  完成后的回调
 */
- (void)inputVideoPixelBuffer:(CVPixelBufferRef)aPixelBuffer
             sampleBufferTime:(CMTime)aTime
                     rotation:(UIDeviceOrientation)aRotation
                       callId:(NSString *)aCallId
                   completion:(void (^)(EMError *aError))aCompletionBlock;

接口的调用在视频数据的回调中,即captureOutput:didOutputSampleBuffer:fromConnection中调用,调用前需要判断当前会话通话状态,若状态为EMCallSessionStatusAccepted,则可以调用外部输入视频数据接口。

当视频通话挂断时,需要终止视频数据采集过程

水印

视频通话时,可以添加图片作为水印,添加时使用[IEMCallManager addVideoWatermark]接口,需要指定水印图片的NSUrl,添加位置.参见EMWaterMarkOptionopen in new window

清除水印使用[IEMCallManager clearVideoWatermark]接口。

显示remoteVideo需要使用EMCallViewScaleModeAspectFit模式,否则对方的水印设在边缘位置可能显示不出来。

接口
/*!
*  \~chinese
*  开启水印功能
*
*  @param option 水印配置项,包括图片URL,marginX,marginY以及起始点
*
*  \~english
*  Enable water mark feature
*
*  @param option the option of watermark picture,include url,margingX,marginY,margin point
 */
- (void)addVideoWatermark:(EMWaterMarkOption*)option;
/*!
*  \~chinese
*  取消水印功能
*
*  \~english
*  Disable water mark feature
*
 */
- (void)clearVideoWatermark;

私有部署

私有部署设置方法参见私有云sdk集成配置

客户端API

1V1音视频通话的API包括以下接口

  • EMCallOption 视频通话配置类
  • EMCallManager 是视频通话的主要管理类,提供了语音通话的拨打、接听、挂断等接口
  • EMCallManagerDelegate 是视频通话的监听回调类,实时语音通话相关的回调
  • EMCallBuilderDelegate 提供了拨打视频通话时对方不在线的回调
  • EMCallSession 语音通话的会话实例接口类

EMCallOption

属性描述
pingIntervalopen in new window心跳时间间隔,单位秒,默认30s,最小10s
isSendPushIfOfflineopen in new window被叫方不在线时,是否推送来电通知
offlineMessageTextopen in new window当isSendPushIfOffline=YES时起作用,离线推送显示的内容
maxAudioKbpsopen in new window最大音频码率
maxVideoKbpsopen in new window最大视频码率
minVideoKbpsopen in new window最小视频码率
maxVideoFrameRateopen in new window最大视频帧率
videoResolutionopen in new window视频分辨率
enableReportQualityopen in new window是否监听通话质量
enableCustomAudioDataopen in new window是否使用自定义音频数据
audioCustomSamplesopen in new window自定义音频数据的采样率,默认48000
enableCustomizeVideoDataopen in new window是否使用自定义视频数据

EMCallManager

方法功能
addDelegate:delegateQueue:open in new window添加回调代理
removeDelegate:open in new window移除回调代理
setBuilderDelegate:open in new window添加离线推送回调代理,该代理只能设置一个
setCallOptions:open in new window配置设置项
getCallOptionsopen in new window获取设置项
startCall:remoteName:ext:completion:open in new window发起实时会话
startCall:remoteName:record:mergeStream:ext:completion:open in new window发起实时会话,可选择是否录制
answerIncomingCall:open in new window接收方同意通话请求
endCall:reason:open in new window结束通话
inputCustomAudioData:open in new window自定义外部音频数据
inputVideoSampleBuffer:rotation:callId:completion:open in new window自定义本地视频数据
inputVideoPixelBuffer:sampleBufferTime:rotation:callId:completion:open in new window自定义本地视频数据
addVideoWatermark:open in new window开启水印
clearVideoWatermarkopen in new window清除水印

EMCallManagerDelegate

回调事件描述
callDidReceive:open in new window用户A拨打用户B,用户B会收到这个回调
callDidConnect:open in new window通话通道建立完成,用户A和用户B都会收到这个回调
callDidAccept:open in new window用户B同意用户A拨打的通话后,用户A和B会收到这个回调
callDidEnd:reason:error:open in new window1. 用户A或用户B结束通话后,双方会收到该回调. 2. 通话出现错误,双方都会收到该回调
callStateDidChange: type:open in new window用户A和用户B正在通话中,用户A中断或者继续数据流传输时,用户B会收到该回调
callNetworkDidChange:status:open in new window用户A和用户B正在通话中,用户A的网络状态出现不稳定,用户A会收到该回调。若未开启录制,用户B也会收到该回调

EMCallBuilderDelegate

回调事件描述
callRemoteOffline:open in new window用户A给用户B拨打实时通话,用户B不在线,并且用户A设置了[EMCallOptions.isSendPushIfOffline == YES],则用户A会收到该回调

EMCallSession

属性

属性描述
callIdopen in new window会话标识符
localNameopen in new window通话本地的username
typeopen in new window通话的类型
isCalleropen in new window是否为主叫方
remoteNameopen in new window对方的username
statusopen in new window通话的状态
localVideoViewopen in new window视频通话时本地的图像显示区域
remoteVideoViewopen in new window视频通话时对方的图像显示区域
connectTypeopen in new window连接类型
videoLatencyopen in new window视频的延迟时间,单位是毫秒
localVideoFrameRateopen in new window本地视频的帧率,实时变化 未获取到返回-1
remoteVideoFrameRateopen in new window对方视频丢包率,实时变化 未获取到返回-1
localVideoBitrateopen in new window本地视频通话对方的比特率kbps,实时变化 未获取到返回-1
remoteVideoBitrateopen in new window对方视频通话对方的比特率kbps,实时变化 未获取到返回-1
localVideoLostRateInPercentopen in new window本地视频丢包率,实时变化 未获取到返回-1
remoteVideoLostRateInPercentopen in new window对方视频丢包率,实时变化 未获取到返回-1
remoteVideoResolutionopen in new window对方视频分辨率 未获取到返回 (-1,-1)
serverVideoIdopen in new window服务端录制文件的id
willRecordopen in new window是否启用服务器录制
extopen in new window消息扩展

方法

方法功能
pauseVoiceopen in new window暂停语音数据传输
resumeVoiceopen in new window恢复语音数据传输
pauseVideoopen in new window暂停视频传输
resumeVideoopen in new window恢复视频传输
switchCameraPosition:open in new window切换前后摄像头