1对1音视频通话


1对1音视频通话分为视频通话和语音通话,SDK 提供简单的 API,方便开发者简单的接入实时通话功能。

1对1音视频通话主要涉及到的环信SDK头文件如下:

// 通话会话部分,包含通话id,通话类型等
EMCallSession.h

// 实时通话配置部分,包含视频通话分辨率,码率,被叫方不在线时是否推送来电通知等设置
EMCallOptions.h

// 视频通话页面设置
EMCallVideoView.h

// 实时通话方法调用部分,比如添加代理,移除代理,发起实时通话,结束实时通话等
IEMCallManager.h

// 实时通话的协议回调方法部分,比如监听接收实时通话,拨打实时通话对方不在线的回调方法等
EMCallManagerDelegate.h
EMCallBuilderDelegate.h

实时通话的数据流量

实时语音和实时视频通话的数据流量如下:

实时语音:

  • 双向 170k bytes/minute

实时视频(单路):

  • 240p: 0.75M ~ 3M
  • 480p: 2.2M ~ 7.5M
  • 720p: 6.5M ~ 18.5M
  • 1080p: 15M ~ 37.5M

配置工程

1. 在项目中导入库

Hyphenate.framework   //包含实时音视频的库

AVFoundation.framework

2. 在项目中导入头文件

#import <Hyphenate/Hyphenate.h>

3. 配置属性:

进行音视频之前,设置全局的音视频属性,具体属性有哪些请查看头文件 *EMCallOptions*

EMCallOptions *options = [[EMClient sharedClient].callManager getCallOptions];
//当对方不在线时,是否给对方发送离线消息和推送,并等待对方回应
options.isSendPushIfOffline = NO;
//设置视频分辨率:自适应分辨率、352 * 288、640 * 480、1280 * 720
options.videoResolution = EMCallVideoResolutionAdaptive;
//最大视频码率,范围 50 < videoKbps < 5000, 默认0, 0为自适应,建议设置为0
options.maxVideoKbps = 0;
//最小视频码率
options.minVideoKbps = 0;
//是否固定视频分辨率,默认为NO
options.isFixedVideoResolution = NO;
[[EMClient sharedClient].callManager setCallOptions:options];

具体实现可以参考 Demo: DemoCallManager 和 EMCallViewController

发起实时通话

用户可以调用发起语音或者视频 API 向在线用户发起实时通话。

/*!
 *  发起实时会话
 *
 *  @param aType            通话类型
 *  @param aRemoteName      被呼叫的用户(不能与自己通话)
 *  @param isRecord             是否开启服务端录制
 *  @param isMerge              录制时是否合并数据流
 *  @param aExt             通话扩展信息,会传给被呼叫方
 *  @param aCompletionBlock 完成的回调
 */
- (void)startCall:(EMCallType)aType
       remoteName:(NSString *)aRemoteName
           record:(BOOL)isRecord
      mergeStream:(BOOL)isMerge
              ext:(NSString *)aExt
       completion:(void (^)(EMCallSession *aCallSession, EMError *aError))aCompletionBlock;

示例代码:创建视频通话

void (^completionBlock)(EMCallSession *, EMError *) = ^(EMCallSession *aCallSession, EMError *aError){
    if (!aError) {
        NSLog(@"发起实时通话成功");
    } else {
        NSLog(@"发起实时通话失败的原因 --- %@", aError.errorDescription);
    }
};
   
[[EMClient sharedClient].callManager startCall:EMCallTypeVideo remoteName:@"aUsername" record:NO mergeStream:NO ext:nil completion:^(EMCallSession *aCallSession, EMError *aError) {
    // 返回的 EMCallSession 对象(aCallSession)中有一些常用的属性,比如callId(通话会话标识符,即通话ID),type(通话的类型)以及通话会话相关的设置等,具体请到环信SDK的 EMCallSession.h 头文件中查看
    completionBlock(aCallSession, aError);
}];

被叫方同意实时通话

接收到通话时调用此 API 同意实时通话。

/*!
 *  接收方同意通话请求
 *
 *  @param  aCallId     通话ID
 *
 *  @result 错误信息
 */
- (EMError *)answerIncomingCall:(NSString *)aCallId;

// 调用:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // EMCallSession -> callId
    EMError *error = [[EMClient sharedClient].callManager answerIncomingCall:@"callId"];
    if (!error) {
        NSLog(@"接收方同意通话请求成功");
    } else {
        NSLog(@"接收方同意通话请求失败的原因 --- %@", error.errorDescription);
    }
});                      

结束实时通话

根据不同场景可以选择结束会话的原因。

例如:拒接选择 EMCallEndReasonDecline,主动挂断选择 EMCallEndReasonHangup。

typedef enum{
    EMCallEndReasonHangup   = 0,    /*! 对方挂断 */
    EMCallEndReasonNoResponse,      /*! 对方没有响应 */
    EMCallEndReasonDecline,         /*! 对方拒接 */
    EMCallEndReasonBusy,            /*! 对方占线 */
    EMCallEndReasonFailed,          /*! 失败 */
    EMCallEndReasonUnsupported,     /*! 功能不支持 */
}EMCallEndReason;


/*!
 *  结束通话
 *
 *  @param aCallId     通话的ID
 *  @param aReason     结束原因
 *
 *  @result 错误
 */
- (EMError *)endCall:(NSString *)aCallId
              reason:(EMCallEndReason)aReason;
// 调用:
[[EMClient sharedClient].callManager endCall:@"callId" reason:aReason];

创建通话页面

SDK提供了用于显示本地视频的页面类*EMCallLocalView*,显示对方视频的页面类*EMCallRemoteView*,建议在同意接通视频通话之后再初始化 EMCallRemoteView页面。

//前提:EMCallSession *callSession 存在
CGFloat width = 80;
CGFloat height = self.view.frame.size.height / self.view.frame.size.width * width;
callSession.localVideoView = [[EMCallLocalView alloc] initWithFrame:CGRectMake(self.view.frame.size.width - 90, CGRectGetMaxY(_statusLabel.frame), width, height)];
[self.view addSubview:callSession.localVideoView];
	
//同意接听视频通话之后
callSession.remoteVideoView = [[EMCallRemoteView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
//设置视频页面缩放方式
callSession.remoteVideoView.scaleMode = EMCallViewScaleModeAspectFill;
[self.view addSubview:_callSession.remoteVideoView];

实时通话相关 API

暂停恢复实时通话的数据传输相关 API。

使用EMCallSession的对象调用以下方法,在环信SDK发起实时通话的方法以及监听接收实时通话的回调方法中都会返回EMCallSession的对象,自己保存这个对象用于通话会话相关的设置

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

// 调用:
[aCallSession pauseVoice];

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

// 调用:
[aCallSession resumeVoice];

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

// 调用:
[aCallSession pauseVideo];

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

// 调用:
[aCallSession resumeVideo];

实时通话前后摄像头切换相关API

#pragma mark - Camera

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

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

实时通话相关的回调

注册实时通话回调

//注册实时通话回调
[[EMClient sharedClient].callManager addDelegate:self delegateQueue:nil];
//移除实时通话回调
[[EMClient sharedClient].callManager removeDelegate:self];

相关回调说明:

/*!
 *  用户A拨打用户B,用户B会收到这个回调
 *
 *  @param aSession  会话实例
 */
- (void)callDidReceive:(EMCallSession *)aSession;

/*!
 *  通话通道建立完成,用户A和用户B都会收到这个回调
 *
 *  @param aSession  会话实例
 */
- (void)callDidConnect:(EMCallSession *)aSession;

/*!
 *  用户B同意用户A拨打的通话后,双方都会收到这个回调
 *
 *  @param aSession  会话实例
 */
- (void)callDidAccept:(EMCallSession *)aSession;

/*!
 *  1. 用户A或用户B结束通话后,对方会收到该回调
 *  2. 通话出现错误,双方都会收到该回调
 *
 *  @param aSession  会话实例
 *  @param aReason   结束原因
 *  @param aError    错误
 */
- (void)callDidEnd:(EMCallSession *)aSession
            reason:(EMCallEndReason)aReason
             error:(EMError *)aError;

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

弱网检测

通过回调通知应用当前实时通话网络状态。

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

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

离线发推送

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

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;
    
    [[EMClient sharedClient].chatManager sendMessage:message progress:nil completion:nil];
}

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

自定义音频数据

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

配置属性

用户使用自定义音频数据时,需要配置外部输入音频数据的开关,以及音频采样率,通道数(当前通道数只支持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;

上一页:聊天室管理

下一页:多人实时通话