实现音视频通话
更新时间: 2023/02/23 10:30:27
网易云信音视频通话产品的基本功能包括高质量的实时音视频通话。当您成功初始化 SDK 之后,您可以简单体验本产品的基本业务流程。本文档为您展示音视频通话提供的基本业务流程。
前提条件
请确认您已完成以下操作:
- 创建应用并获取 App Key。
- 开通音视频通话 2.0 服务。
- 集成 SDK(iOS),其中需要设置签名并添加媒体设备权限。
示例代码
网易云信为您提供完整的实现基础音视频通话的示例代码作为参考,您可以直接拷贝用于运行测试。
实现音视频通话的完整示例代码
//初始化SDK
- (void)setupRTCEngine {
//默认情况下日志会存储在App沙盒的Documents目录下
NERtcLogSetting *logSetting = [[NERtcLogSetting alloc] init];
#if DEBUG
logSetting.logLevel = kNERtcLogLevelInfo;
#else
logSetting.logLevel = kNERtcLogLevelWarning;
#endif
NERtcEngineContext *context = [[NERtcEngineContext alloc] init];
context.engineDelegate = self;
context.appKey = AppKey;
context.logSetting = logSetting;
[[NERtcEngine sharedEngine] setupEngineWithContext:context];
}
//释放SDK资源
- (void)destroyRTCEngineWithCompletion:(void(^)(void))completion {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[NERtcEngine destroyEngine];
if (completion) {
completion();
}
});
}
//加入房间
- (void)joinChannelWithRoomId:(NSString *)roomId
userId:(uint64_t)userId {
NERtcEngine *coreEngine = [NERtcEngine sharedEngine];
//1v1音视频通话场景的视频推荐配置
//其他场景下请联系云信技术支持获取配置
NERtcVideoEncodeConfiguration *config = [[NERtcVideoEncodeConfiguration alloc] init];
config.width = 640;
config.height = 360;
config.frameRate = kNERtcVideoFrameRateFps15;
[coreEngine setLocalVideoConfig:config];
//1v1音视频通话场景的音频推荐配置
//其他场景下请联系云信技术支持获取配置
[coreEngine setAudioProfile:kNERtcAudioProfileStandard
scenario:kNERtcAudioScenarioSpeech];
[coreEngine enableLocalAudio:YES];
[coreEngine enableLocalVideo:YES];
__weak typeof(self) weakSelf = self;
[coreEngine joinChannelWithToken:@""
channelName:roomId
myUid:userId
completion:^(NSError * _Nullable error, uint64_t channelId, uint64_t elapesd) {
if (error) {
//加入失败,弹框之后退出当前页面
NSString *message = [NSString stringWithFormat:@"join channel fail.code:%@", @(error.code)];
[weakSelf showDismissAlertWithMessage:message actionBlock:^{
[weakSelf destroyRTCEngineWithCompletion:^{
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf.navigationController popViewControllerAnimated:YES];
});
}];
}];
} else {
//加入成功,建立本地canvas渲染本地视图
NERtcVideoCanvas *canvas = [weakSelf setupLocalCanvas];
[coreEngine setupLocalVideoCanvas:canvas];
}
}];
}
#pragma mark - SDK回调(含义请参考NERtcEngineDelegateEx定义)
- (void)onNERtcEngineDidError:(NERtcError)errCode {
NSString *message = [NSString stringWithFormat:@"nertc engine did error.code:%@", @(errCode)];
[self showDismissAlertWithMessage:message actionBlock:^{
[[NERtcEngine sharedEngine] leaveChannel];
}];
}
- (void)onNERtcEngineUserDidJoinWithUserID:(uint64_t)userID
userName:(NSString *)userName {
//如果已经setup了一个远端的canvas,则不需要再建立了
if (_remoteCanvas != nil) {
return;
}
}
//建立远端canvas,用来渲染远端画面
NERtcVideoCanvas *canvas = [self setupRemoteCanvasWithUid:userID];
[NERtcEngine.sharedEngine setupRemoteVideoCanvas:canvas
forUserID:userID];
}
- (void)onNERtcEngineUserVideoDidStartWithUserID:(uint64_t)userID
videoProfile:(NERtcVideoProfileType)profile {
//如果已经订阅过远端视频流,则不需要再订阅了
if (_remoteCanvas.subscribedVideo) {
return;
}
}
//订阅远端视频流
_remoteCanvas.subscribedVideo = YES;
[NERtcEngine.sharedEngine subscribeRemoteVideo:YES
forUserID:userID
streamType:kNERtcRemoteVideoStreamTypeHigh];
}
- (void)onNERtcEngineUserVideoDidStop:(uint64_t)userID {
if (userID == _remoteCanvas.uid) {
_remoteStatLab.hidden = YES;
}
}
- (void)onNERtcEngineUserDidLeaveWithUserID:(uint64_t)userID
reason:(NERtcSessionLeaveReason)reason {
//如果远端的人离开了,重置远端模型和UI
if (userID == _remoteCanvas.uid) {
_remoteStatLab.hidden = NO;
[_remoteCanvas resetCanvas];
_remoteCanvas = nil;
}
}
- (void)onNERtcEngineDidDisconnectWithReason:(NERtcError)reason {
//网络连接中断时会触发该回调,触发之后的操作则由开发者按需实现
//此时已与房间断开连接,如果需要重新加入房间,必须再次调用join接口
}
- (void)onNERtcEngineDidLeaveChannelWithResult:(NERtcError)result {
//调用leaveChannel之后,若需要释放SDK资源,建议在收到该回调之后,再调用destroyEngine
[self destroyRTCEngineWithCompletion:^{
dispatch_async(dispatch_get_main_queue(), ^{
[self.navigationController popViewControllerAnimated:YES];
});
}];
}
实现流程
实现音频通话的 API 调用时序如下图所示。
实现视频通话的 API 调用时序如下图所示。
实现方法
步骤一 (可选)创建音视频通话界面
您可以参考此步骤根据业务场景创建相应的音视频通话界面,若您已实现相应界面,请忽略该步骤。
实现基础的音视频通话,建议您参考 SampleCode 中的 Main.storyboard
文件在界面上添加以下控件。
- 本端视频窗口
- 远端视频窗口
- 麦克风按钮
- 摄像头按钮
- 结束通话按钮
效果图如下图所示。
步骤二 导入类
在项目中导入 NERtcSDK
类:
#import <NERtcSDK/NERtcSDK.h>
步骤三 初始化
调用 setupEngineWithContext
方法完成初始化。
您需要将 AppKey
替换为您的应用对应的 App Key。
示例代码如下:
@interface Myapp ()<NERtcEngineDelegateEx>
...
NERtcEngine *coreEngine = [NERtcEngine sharedEngine];
NERtcEngineContext *context = [[NERtcEngineContext alloc] init];
// 设置通话相关信息的回调
context.engineDelegate = self;
// 设置当前应用的 App Key
context.appKey = AppKey;//请替换为您自己的 App Key
[coreEngine setupEngineWithContext:context];
为了实现标准音视频通话业务,您还需要在初始化时注册相关必要回调,建议您请在初始化方法中传入原型为 NERtcEngineDelegate / NERtcEngineDelegateEx 的以下回调:
//本端用户连接中断通知,收到回调后,表示已从房间内断开。
- (void)onNERtcEngineDidDisconnectWithReason:(NERtcError)reason {
}
//远端用户加入房间通知回调,建议在收到此回调后再进行设置远端视图等的操作
- (void)onNERtcEngineUserDidJoinWithUserID:(uint64_t)userID
userName:(NSString *)userName {
{
}
}
//远端用户退出房间通知回调
- (void)onNERtcEngineUserDidLeaveWithUserID:(uint64_t)userID
reason:(NERtcSessionLeaveReason)reason {
{
}
}
//远端用户推流/停止推流通知回调,建议在收到此回调后再进行订阅或取消订阅音视频流的操作
- (void)onNERtcEngineUserVideoDidStartWithUserID:(uint64_t)userID
videoProfile:(NERtcVideoProfileType)profile {
{
}
}
- (void)onNERtcEngineUserVideoDidStop:(uint64_t)userID {
{
}
}
步骤四 设置本地视图
初始化成功后,可以设置本地视图,来预览本地图像。您可以根据业务需要实现加入房间之前预览或加入房间后预览。
- 若您想调整摄像头的相关参数,请参考视频设备管理进行设置。
- 在加入房间前,默认预览分辨率为 640*480,您可以通过
setLocalVideoConfig
接口的width
和height
参数调整采集分辨率。
-
实现加入房间前预览。
-
调用
setupLocalVideoCanvas
方法设置本地视图,可以通过renderMode
和mirrorMode
参数设置视频的渲染模式和镜像模式;再调用startPreview(streamType)
方法预览本地图像。示例代码如下:
//设置本地视频画布,以适应区域模式、开启镜像为例 UIView *localUserView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)]; NERtcVideoCanvas *canvas = [[NERtcVideoCanvas alloc] init]; canvas.container = localUserView; canvas.renderMode = kNERtcVideoRenderScaleCropFill; canvas.mirrorMode = kNERtcVideoMirrorModeEnabled; [[NERtcEngine sharedEngine] setupLocalVideoCanvas:canvas]; //以开启本地视频主流预览为例 [[NERtcEngine sharedEngine] startPreview:kNERtcStreamChannelTypeMainStream];
-
若要结束预览,或者准备加入房间时,调用
stopPreview(streamType)
方法停止预览。stopPreview(streamType)
的streamType
参数请与startPreview(streamType)
的保持一致,即同为主流或辅流的开启和停止预览。
-
-
实现加入房间后预览。
在成功加入房间后,调用
setupLocalVideoCanvas
方法设置本地视图,可以通过renderMode
和mirrorMode
参数设置视频的渲染模式和镜像模式;再调用enableLocalVideo
方法进行视频的采集发送与预览。成功加入房间后,即可预览本地图像。示例代码如下:
//设置本地视频画布,以适应区域模式、开启镜像为例 UIView *localUserView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)]; NERtcVideoCanvas *canvas = [[NERtcVideoCanvas alloc] init]; canvas.container = localUserView; canvas.renderMode = kNERtcVideoRenderScaleCropFill; canvas.mirrorMode = kNERtcVideoMirrorModeEnabled; [[NERtcEngine sharedEngine] setupLocalVideoCanvas:canvas]; //以开启本地视频主流采集并发送为例 [NERtcEngine.sharedEngine enableLocalVideo:YES,kNERtcStreamChannelTypeMainStream];
步骤五 加入房间
加入房间前,请确保已完成初始化相关事项。若您的业务中涉及呼叫邀请等机制,建议通过 信令 实现,总体实现流程请参见一对一会话操作流程,具体呼叫邀请机制的实现请参见邀请机制。
调用 joinChannelWithToken:channelName:myUid:channelOptions:completion:
方法加入房间。
[NERtcEngine.sharedEngine joinChannelWithToken:@"Your Token"
channelName: Your roomId
myUid:Your userId
channelOptions:NERtcJoinChannelOptions
completion:^(NSError * _Nullable error, uint64_t channelId, uint64_t elapesd) {
if (error) {
//加入失败
} else {
//加入成功
}
}];
参数说明
参数 | 说明 |
---|---|
Token | 安全认证签名(NERTC Token)。
|
channelName | 房间名称,长度为 1 ~ 64 字节。目前支持以下 89 个字符:a-z, A-Z, 0-9, space, !#$%&()+-:;≤.,>? @[]^_{|}~"。 设置相同房间名称的用户会进入同一个通话房间。 |
myUid | 用户的唯一标识 id,为数字串,房间内每个用户的 uid 必须是唯一的。 |
completion | 操作完成的 block 回调,返回 channelId 和 uid(未指定 uid 时),其中 channelId 即音视频通话的 ID,建议您在业务层保存该数据,以便于后续问题排查。 |
channelOptions | 加入房间时可以设置携带一些特定信息。默认值为 nil,具体请参考 `NERtcJoinChannelOptions`。 |
成功加入房间之后,您可以通过监听 onNERtcEngineConnectionStateChangeWithState:reason:
回调实时监控自己在本房间内的连接状态。
步骤六 设置远端视图并发起订阅
音视频通话过程中,除了要显示本地的视频画面,通常也要显示参与互动的其他连麦者/主播的远端视频画面。
- 监听远端用户进出频道。
NERtcEngineDelegate
通过以下回调获取相关信息:
-
onNERtcEngineUserDidJoinWithUserID:userName
:监听远端用户加入通话房间的事件,并抛出对方的 uid。当本端加入房间后,也会通过此回调抛出通话房间内已有的其他用户。 -
onNERtcEngineUserVideoDidStartWithUserID:videoProfile
:监听远端用户发布视频流的事件,回调中携带对方的 uid 与发布的视频分辨率。
- 设置远端视频画布。
在监听到远端用户加入房间或发布视频流后,本端可以调用 setupRemoteVideoCanvas:forUserID:
方法设置远端用户视频画布,用于显示其视频画面。
示例代码如下:
- (void)onNERtcEngineUserDidJoinWithUserID:(uint64_t)userID userName:(NSString *)userName {
//如果已经setup了一个远端的canvas,则不需要再建立了
...
if (_remoteCanvas != nil) {
return;
}
//建立远端canvas,用来渲染远端画面
NERtcVideoCanvas *canvas = [[NERtcVideoCanvas alloc] init];
canvas.container = _remoteCanvas;
[NERtcEngine.sharedEngine setupRemoteVideoCanvas:canvas forUserID:userID];
- (void)onNERtcEngineUserDidLeaveWithUserID:(uint64_t)userID reason:(NERtcSessionLeaveReason)reason {
//如果远端的人离开了,重置远端模型和UI
...
[NERtcEngine.sharedEngine setupRemoteVideoCanvas:nil forUserID:userID];
...
}
- 监听远端视频流发布。
当房间中的其他用户发布视频流时,本端会触发 onNERtcEngineUserVideoDidStartWithUserID:
回调:
@protocol NERtcEngineDelegate <NSObject>
/**
其他用户打开视频的回调
@param userID 用户id
@param profile 用户发送视频的最大分辨率类型
*/
- (void)onNERtcEngineUserVideoDidStartWithUserID:(uint64_t)userID videoProfile:(NERtcVideoProfileType)profile;
- 订阅远端视频流。
在设置完远端视频画布后,且监听到远端用户有视频发布时,本端可以调用 subscribeRemoteVideo:forUserID:streamType:
方法订阅远端用户的视频流。
示例代码如下:
// 监听到远端用户有视频流发布
- (void)onNERtcEngineUserVideoDidStartWithUserID:(uint64_t)userID
videoProfile:(NERtcVideoProfileType)profile {
//如果已经订阅过远端视频流,则不需要再订阅了
...
if (_remoteCanvas.subscribedVideo) {
return;
}
//订阅远端视频流
_remoteCanvas.subscribedVideo = YES;
[NERtcEngine.sharedEngine subscribeRemoteVideo:YES
forUserID:userID
streamType:kNERtcRemoteVideoStreamTypeHigh];
...}
- 监听远端用户离开房间或停止发布视频。
-
onNERtcEngineUserDidLeaveWithUserID:reason
:远端用户离开房间回调。 -
onNERtcEngineUserVideoDidStop
:远端用户关闭视频功能回调。
示例代码如下:
// 监听到远端用户停止视频流发布
- (void)onNERtcEngineUserVideoDidStop:(uint64_t)userID {
if (userID == _remoteCanvas.uid) {
// 收到此回调后,SDK 内部会取消对应的视频流订阅,无需开发者主动取消订阅。
...
_remoteStatLab.hidden = YES;
...
}
}
步骤七 音频流
本地音频的采集发布和远端音频订阅播放是默认启动的,正常情况下无需开发者主动干预。
步骤八 退出通话房间
调用 leaveChannel
方法退出通话房间。
示例代码如下:
//UI 挂断按钮事件- (IBAction)onHungupAction:(UIButton *)sender {
[NERtcEngine.sharedEngine leaveChannel];
[self dismiss];
}
执行完 leaveChannel
方法后,SDK 会触发离开房间回调 onNERtcEngineDidLeaveChannelWithResult
,通知当前用户退出房间的结果。
示例代码如下:
- (void)onNERtcEngineDidLeaveChannelWithResult:(NERtcError)result{
// 进行业务数据清理
}
步骤九 销毁音视频实例
当确定 App 短期内不再使用音视频通话实例时,可以调用 destroyEngine
方法释放对应的对象资源。
释放资源需要在子线程中进行。
示例代码如下:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
//开始销毁
[NERtcEngine destroyEngine];
//到这里表示销毁完成
});