环信提供开源的1v1实时音视频通话项目EMiOSDemo,在使用SDK集成App前,您可以参考相关代码
进入官网下载页面下载环信demo,点击iOS平台的SDK+Demo源码,下载最新Demo源码。
运行Demo前你需要具备以下条件
目录 EMiOSDemo —>Class 中的 Demo 目录介绍
进入EMiOSDemo目录,打开EMiOSDemo.xcworkspace ,进入工程设置的Signing & Capaabilities菜单,修改签名Team和bundleId为自己的团队开发。
连接iPhone手机,选择目标设备,点击运行
本章节介绍如何使用HyhpenateSDK 快速实现1v1音视频通话
在开始集成前,你需要注册环信开发者账号并创建后台应用,参见注册并创建应用
参考以下步骤创建一个iOS 应用项目,如果已有项目,可以直接进行下一步集成。创建过程如下
集成SDK有两种方法,分别是使用cocoapods和手动导入SDK
开始前确保你已安装 Cocoapods。
target 'AppName' do
pod 'Hyphenate', '~> version'
end
工程中引入SDK,需要引用头文件Hyphenate.h
#import <Hyphenate/Hyphenate.h>
应用需要音频设备及摄像头权限,在 info.plist 文件中,点击 + 图标,添加如下信息
Key | Type | Value |
---|---|---|
Privacy - Microphone Usage Description | String | 描述信息,如“环信需要使用您的麦克风” |
Privacy - Camera Usage Description | String | 描述信息,如“环信需要使用您的摄像头” |
如果希望在后台运行,还需要添加后台运行音视频权限,在info.plist文件中,点击 + 图标,添加Required background modes ,Type为Array,在Array下添加元素App plays audio or streams audio/video using AirPlay
音视频通话窗口中,一般包括以下几个UI控件
通话界面可以参考Demo中Call1v1VideoViewController,效果如下:
初始化HyhpenateSDK使用initializeSDKWithOptions:接口,需要设置自己的appkey,调用如下:
// 这里替换成自己的appkey
EMOptions *retOpt = [EMOptions optionsWithAppkey:@"easemob-demo#chatdemoui"];
// 这里打开日志输出
retOpt.enableConsoleLog = YES;
[[EMClient sharedClient] initializeSDKWithOptions:retOpt];
账号登录成功后,需要进行音视频通话功能的初始化,设置监听类
[[EMClient sharedClient].callManager addDelegate:self delegateQueue:nil];
主叫方发起呼叫通话请求的过程如下:
[[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,被叫方应该在本地保存。
被叫方在收到通话请求回调后,可以选择接听/拒绝通话,若选择接听通话,调用如下:
[[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;
}
被叫方收到呼叫请求后,可以选择拒绝通话,调用过程如下:
[[EMClient sharedClient].callManager endCall:self.callSession.callId reason:EMCallEndReasonDecline];
拒绝后,主叫方收到如下回调, aReason为EMCallEndReasonDecline
- (void)callDidEnd:(EMCallSession *)aSession
reason:(EMCallEndReason)aReason
error:(EMError *)aError
通话中双方随时都可以结束通话,调用过程如下:
[[EMClient sharedClient].callManager endCall:@"callId" reason:EMCallEndReasonHangup];
结束后双方收到以下回调:
- (void)callDidEnd:(EMCallSession *)aSession
reason:(EMCallEndReason)aReason
error:(EMError *)aError
在实现基本视频通话的基础上,SDK提供更为丰富的API,可以实现更为复杂的音视频通话场景
SDK会写入日志文件到本地。日志文件路径如下:沙箱Documents/HyphenateSDK/easemoblog,以真机为例,获取过程如下:
日志文件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推送证书上传和pushKit推送集成
配置属性(在登录环信服务器成功之后设置)
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秒以内,此处可以参考文档自定义铃声
协议,代理,回调方法建议写到工程的根控制器或者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,添加位置.参见EMWaterMarkOption。
清除水印使用[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;
1v1通话支持不同集群区域的人员通话使用代理,减小延迟。使用多集群代理需要音视频后台配置IP及端口的映射文件rtcconfig.json,并禁用相关appkey的直连。启用多集群代理功能需要配置如下属性:
[EMClient sharedClient].options.isUseRtcConfig = YES;
私有部署设置方法参见私有云sdk集成配置
1V1音视频通话的API包括以下接口
属性 | 描述 |
---|---|
pingInterval | 心跳时间间隔,单位秒,默认30s,最小10s |
isSendPushIfOffline | 被叫方不在线时,是否推送来电通知 |
offlineMessageText | 当isSendPushIfOffline=YES时起作用,离线推送显示的内容 |
maxAudioKbps | 最大音频码率 |
maxVideoKbps | 最大视频码率 |
minVideoKbps | 最小视频码率 |
maxVideoFrameRate | 最大视频帧率 |
videoResolution | 视频分辨率 |
enableReportQuality | 是否监听通话质量 |
enableCustomAudioData | 是否使用自定义音频数据 |
audioCustomSamples | 自定义音频数据的采样率,默认48000 |
enableCustomizeVideoData | 是否使用自定义视频数据 |
方法 | 功能 |
---|---|
addDelegate:delegateQueue: | 添加回调代理 |
removeDelegate: | 移除回调代理 |
setBuilderDelegate: | 添加离线推送回调代理,该代理只能设置一个 |
setCallOptions: | 设置设置项 |
getCallOptions | 获取设置项 |
startCall:remoteName:ext:completion: | 发起实时会话 |
startCall:remoteName:record:mergeStream:ext:completion: | 发起实时会话,可选择是否录制 |
answerIncomingCall: | 接收方同意通话请求 |
endCall:reason: | 结束通话 |
inputCustomAudioData: | 自定义外部音频数据 |
inputVideoSampleBuffer:rotation:callId:completion: | 自定义本地视频数据 |
inputVideoPixelBuffer:sampleBufferTime:rotation:callId:completion: | 自定义本地视频数据 |
addVideoWatermark: | 开启水印 |
clearVideoWatermark | 清除水印 |
回调事件 | 描述 |
---|---|
callDidReceive: | 用户A拨打用户B,用户B会收到这个回调 |
callDidConnect: | 通话通道建立完成,用户A和用户B都会收到这个回调 |
callDidAccept: | 用户B同意用户A拨打的通话后,用户A和B会收到这个回调 |
callDidEnd:reason:error: | 1. 用户A或用户B结束通话后,双方会收到该回调. 2. 通话出现错误,双方都会收到该回调 |
callStateDidChange: type: | 用户A和用户B正在通话中,用户A中断或者继续数据流传输时,用户B会收到该回调 |
callNetworkDidChange:status: | 用户A和用户B正在通话中,用户A的网络状态出现不稳定,用户A会收到该回调。若未开启录制,用户B也会收到该回调 |
回调事件 | 描述 |
---|---|
callRemoteOffline: | 用户A给用户B拨打实时通话,用户B不在线,并且用户A设置了[EMCallOptions.isSendPushIfOffline == YES],则用户A会收到该回调 |
属性
属性 | 描述 |
---|---|
callId | 会话标识符 |
localName | 通话本地的username |
type | 通话的类型 |
isCaller | 是否为主叫方 |
remoteName | 对方的username |
status | 通话的状态 |
localVideoView | 视频通话时本地的图像显示区域 |
remoteVideoView | 视频通话时对方的图像显示区域 |
connectType | 连接类型 |
videoLatency | 视频的延迟时间,单位是毫秒 |
localVideoFrameRate | 本地视频的帧率,实时变化 未获取到返回-1 |
remoteVideoFrameRate | 对方视频丢包率,实时变化 未获取到返回-1 |
localVideoBitrate | 本地视频通话对方的比特率kbps,实时变化 未获取到返回-1 |
remoteVideoBitrate | 对方视频通话对方的比特率kbps,实时变化 未获取到返回-1 |
localVideoLostRateInPercent | 本地视频丢包率,实时变化 未获取到返回-1 |
remoteVideoLostRateInPercent | 对方视频丢包率,实时变化 未获取到返回-1 |
remoteVideoResolution | 对方视频分辨率 未获取到返回 (-1,-1) |
serverVideoId | 服务端录制文件的id |
willRecord | 是否启用服务器录制 |
ext | 消息扩展 |
方法
方法 | 功能 |
---|---|
pauseVoice | 暂停语音数据传输 |
resumeVoice | 恢复语音数据传输 |
pauseVideo | 暂停视频传输 |
resumeVideo | 恢复视频传输 |
switchCameraPosition: | 切换前后摄像头 |