实现音视频通话

更新时间: 2024/03/15 16:57:58

网易云信音视频通话产品的基本功能包括高质量的实时音视频通话。当您成功初始化 SDK 之后,您可以简单体验本产品的基本业务流程。本文档为您展示音视频通话提供的基本业务流程。

前提条件

请确认您已完成以下操作:

示例代码

网易云信为您提供完整的实现基础音视频通话的示例代码作为参考,您可以直接拷贝用于运行测试。

实现音视频通话的完整示例代码
Objective-C// 初始化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 调用时序如下图所示。

sequenceDiagram
    autonumber
    participant App
    participant NERtcSDK
    participant 云信服务端


    App->>NERtcSDK: initialize 初始化 NERtcEngine
    App->>NERtcSDK: joinChannelWithToken 加入房间
    NERtcSDK->>云信服务端: 请求加入房间
    NERtcSDK-->>App: onNERtcEngineUserDidJoinWithUserID
    App->>NERtcSDK: leaveChannel 离开房间
    NERtcSDK->>云信服务端: 请求离开房间
    App->>NERtcSDK: release 销毁实例

实现视频通话的 API 调用时序如下图所示。

sequenceDiagram
    autonumber
    participant App
    participant NERtcSDK
    participant 云信服务端

    App->>NERtcSDK: initialize 初始化 NERtcEngine

    Note over App, NERtcSDK: 设置本地视图
    App->>NERtcSDK: setupLocalVideoCanvas
    App->>NERtcSDK: enableLocallVideo

    Note over App, NERtcSDK: 加入房间
    App->>NERtcSDK: joinChannelWithToken
    NERtcSDK->>云信服务端: 请求加入房间
    NERtcSDK-->>App: onNERtcEngineUserDidJoinWithUserID

    Note over App, NERtcSDK: 设置远端视图
    Note right of NERtcSDK: 远端用户加入房间
    NERtcSDK-->>App: onNERtcEngineUserDidJoinWithUserID 远端用户加入房间的回调
    NERtcSDK-->>App: onNERtcEngineUserVideoDidStartWithUserID 远端用户发布视频流的回调
    App->>NERtcSDK: setupRemoteVideoCanvas 设置远端视频画布
    App->>NERtcSDK: subscribeRemoteVideo 订阅远端视频流
    云信服务端-->>App : onEngineFirstVideoFrameDecoded 已接收到远端视频首帧并完成解码的回调

    Note over App, NERtcSDK: 离开房间
    App->>NERtcSDK: leaveChannel
    NERtcSDK->>云信服务端: 请求离开房间

    App->>NERtcSDK: release 销毁实例

实现方法

步骤一 (可选)创建音视频通话界面

您可以参考此步骤根据业务场景创建相应的音视频通话界面,若您已实现相应界面,请忽略该步骤。

实现基础的音视频通话,建议您参考 SampleCode 中的 Main.storyboard 文件在界面上添加以下控件。

  • 本端视频窗口
  • 远端视频窗口
  • 麦克风按钮
  • 摄像头按钮
  • 结束通话按钮

效果图如下图所示。

ios效果图.png

步骤二 导入类

在项目中导入 NERtcSDK 类:

objc#import <NERtcSDK/NERtcSDK.h>

步骤三 初始化

调用 setupEngineWithContext 方法完成初始化。

您需要将 AppKey 替换为您的应用对应的 App Key。

示例代码如下:

objc@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 参数调整采集分辨率。
  • 实现加入房间前预览。

    1. 调用 setupLocalVideoCanvas 方法设置本地视图,可以通过 renderModemirrorMode 参数设置视频的渲染模式和镜像模式;再调用 startPreview(streamType) 方法预览本地图像。

      示例代码如下:

      objc//设置本地视频画布,以适应区域模式、开启镜像为例
      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];
      
    2. 若要结束预览,或者准备加入房间时,调用 stopPreview(streamType) 方法停止预览。

      stopPreview(streamType)streamType 参数请与 startPreview(streamType) 的保持一致,即同为主流或辅流的开启和停止预览。

  • 实现加入房间后预览。

    在成功加入房间后,调用 setupLocalVideoCanvas 方法设置本地视图,可以通过 renderModemirrorMode 参数设置视频的渲染模式和镜像模式;再调用 enableLocalVideo 方法进行视频的采集发送与预览。成功加入房间后,即可预览本地图像。

    示例代码如下:

    objc//设置本地视频画布,以适应区域模式、开启镜像为例
    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: 方法加入房间。

objc[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)。
  • 调试模式下:可设置为 null。产品默认为安全模式,您可以在网易云信控制台将鉴权模式修改为调试模式,具体请参见Token 鉴权
    调试模式的安全性不高,请在产品正式上线前修改为安全模式。
  • 产品正式上线后:请设置为已获取的NERTC Token。安全模式下必须设置为获取到的 Token 。若未传入正确的 Token 将无法进入房间。

    推荐使用安全模式

channelName 房间名称,长度为 1 ~ 64 字节。目前支持以下 89 个字符:a-z, A-Z, 0-9, space, !#$%&()+-:;≤.,>? @[]^_{|}~"。
设置相同房间名称的用户会进入同一个通话房间。
您也可以在加入通道前,通过创建房间接口创建房间。加入房间时,若传入的 {channelName} 未事先创建,则云信服务器内部将为其自动创建一个名为 {channelName} 的通话房间。
myUid 用户的唯一标识 id,为数字串,房间内每个用户的 uid 必须是唯一的。此 uid 为用户在您应用中的 ID,请在您的业务服务器上自行管理并维护。
completion 操作完成的 block 回调,返回 channelId 和 uid(未指定 uid 时),其中 channelId 即音视频通话的 ID,建议您在业务层保存该数据,以便于后续问题排查。
channelOptions 加入房间时可以设置携带一些特定信息。默认值为 nil,具体请参考 `NERtcJoinChannelOptions`

成功加入房间之后,您可以通过监听 onNERtcEngineConnectionStateChangeWithState:reason: 回调实时监控自己在本房间内的连接状态。

步骤六 设置远端视图并发起订阅

音视频通话过程中,除了要显示本地的视频画面,通常也要显示参与互动的其他连麦者/主播的远端视频画面。

  1. 监听远端用户进出频道。

NERtcEngineDelegate 通过以下回调获取相关信息:

  1. 设置远端视频画布。

在监听到远端用户加入房间或发布视频流后,本端可以调用 setupRemoteVideoCanvas:forUserID: 方法设置远端用户视频画布,用于显示其视频画面。

示例代码如下:

objective-c- (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];
    ...
}
  1. 监听远端视频流发布。

当房间中的其他用户发布视频流时,本端会触发 onNERtcEngineUserVideoDidStartWithUserID: 回调:

@protocol NERtcEngineDelegate <NSObject>
/**
 其他用户打开视频的回调


 @param userID 用户id
 @param profile 用户发送视频的最大分辨率类型
 */
- (void)onNERtcEngineUserVideoDidStartWithUserID:(uint64_t)userID videoProfile:(NERtcVideoProfileType)profile;
  1. 订阅远端视频流。

在设置完远端视频画布后,且监听到远端用户有视频发布时,本端可以调用 subscribeRemoteVideo:forUserID:streamType: 方法订阅远端用户的视频流。

示例代码如下:

objective-c// 监听到远端用户有视频流发布
- (void)onNERtcEngineUserVideoDidStartWithUserID:(uint64_t)userID
                                    videoProfile:(NERtcVideoProfileType)profile {
    // 如果已经订阅过远端视频流,则不需要再订阅了
    ...
    if (_remoteCanvas.subscribedVideo) {
        return;
    }

    // 订阅远端视频流
    _remoteCanvas.subscribedVideo = YES;
    [NERtcEngine.sharedEngine subscribeRemoteVideo:YES
                                      forUserID:userID
                                      streamType:kNERtcRemoteVideoStreamTypeHigh];
    ...
}
  1. 监听远端用户离开房间或停止发布视频。

示例代码如下:

// 监听到远端用户停止视频流发布
- (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 方法释放对应的对象资源。

释放资源需要在子线程中进行。

示例代码如下:

objective-cdispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
    // 开始销毁
    [NERtcEngine destroyEngine];
    // 到这里表示销毁完成
});

常见问题

如何设置画布的缩放模式

设置画布缩放模式接口setLocalRenderScaleMode/setRemoteRenderScaleMode和设置画布接口setupLocalVideoCanvas/setupRemoteVideoCanvas都能修改缩放模式,两者的调用时机不一样,对应的集成建议说明如下:

此文档是否对你有帮助?
有帮助
去反馈
  • 前提条件
  • 示例代码
  • 实现流程
  • 实现方法
  • 步骤一 (可选)创建音视频通话界面
  • 步骤二 导入类
  • 步骤三 初始化
  • 步骤四 设置本地视图
  • 步骤五 加入房间
  • 步骤六 设置远端视图并发起订阅
  • 步骤七 音频流
  • 步骤八 退出通话房间
  • 步骤九 销毁音视频实例
  • 常见问题