快速入门

本文为您介绍如何使用 NERTC SDK 快速实现互动直播功能。

前提条件

在开始运行工程之前,需要准备好如下材料:

集成SDK

  1. 下载 macOS SDK,解压并打开。
  2. 将解压得到的 framework 库文件拷贝到 APP 项目文件夹下。

  3. 打开 APP 的 Xcode 工程文件(以 Xcode 11.2 为例),进入 TARGETS > Project Name > Build Phases > Link Binary with Libraries 菜单,点击 + 添加如下库:

    • NEFundation_Mac.framework
    • nertc_sdk_Mac.framework
  4. 编译,如果提示需要 Link 其他系统依赖,可以点击 Add Other…,找到并添加上。

设置好开发环境后就可以开始使用 NERTC SDK 了。

实现互动直播

初始化

在创建 NERtcEngine 实例并初始化前,请确保你已完成环境准备、安装包获取等步骤,详见“集成客户端”。

进入频道之前,调用 create 方法创建一个 NERtcEngine 实例,并调用 initialize 方法完成初始化。

填入获取到的 App Key 。只有 App Key 相同的应用程序才能进入同一个频道进行互通。

// 创建 RTC 引擎对象并返回指针。
nertc::IRtcEngineEx *rtc_engine_ = (IRtcEngineEx *)createNERtcEngine();
// 设置已开通音视频功能的云信应用的AppKey。
rtc_engine_context_.app_key = app_key_.c_str();
// 设置日志目录的完整路径,采用UTF-8 编码。可选。
rtc_engine_context_.log_dir_path = log_dir_path_.c_str();
// 设置日志级别,默认级别为 kNERtcLogLevelInfo。
rtc_engine_context_.log_level = log_level;
// 指定 SDK 输出日志文件的大小上限,单位为 KB。如果设置为 0,则默认为 20 M。
rtc_engine_context_.log_file_max_size_KBytes = log_file_max_size_KBytes;
// 设置SDK向应用发送回调事件的通知。
rtc_engine_context_.event_handler = this;
// 初始化 NERTC SDK 服务。
if (kNERtcNoError != rtc_engine_->initialize(rtc_engine_context_))
{
    //TODO
}

设置本地视图

设置本地视频画布

调用 setupLocalVideoCanvas 方法,使应用程序绑定本地视频流的显示视窗,并设置本地视频视图。

NERtcVideoCanvas canvas;
canvas.cb = nullptr;
canvas.user_data = nullptr;
canvas.window = window;
// 设置视频缩放模式。
canvas.scaling_mode = mode;
rtc_engine_->setupLocalVideoCanvas(&canvas);

本地视频预览

若要在进入频道前启动本地视频预览,需要调用 startVideoPreview 方法启动本地视频预览。

int ret = rtc_engine_->startVideoPreview();

启动本地视频预览后,本地视频画面会显示在 setupLocalVideoCanvas 方法设置的画布中。

注意:正式执行加入频道操作前,需要先关闭本地视频预览。

int ret = rtc_engine_->stopVideoPreview();

启动视频流

在加入频道前或加入后需要调用 enableLocalVideo 启动本地视频流,以便可以将本地视频流发布到云信服务器。

bool enabled = true;
int ret = rtc_engine_->enableLocalVideo( enabled );

启动视频流后,本地视频画面会显示在 setupLocalVideoCanvas 方法设置的画布中。

请注意与 startVideoPreview 方法区分:

设置直播模式

在互动直播的场景中,我们建议在加入频道前,设置频道模式为直播模式。当前默认为通信模式。

//kNERtcChannelProfileCommunication为通话模式,kNERtcChannelProfileLiveBroadcasting为直播模式
NERtcChannelProfileType channelProfileType = kNERtcChannelProfileLiveBroadcasting;
rtc_engine_->setChannelProfile(channelProfileType);

设置推流开关

在加入频道前,还需要打开推流开关,这样拉流的观众可以拉取到对应的音视频流。

打开推流开关engine->setParameters("{\"publish_self_stream_enabled\":true}")

加入频道

加入频道前,请确保已完成初始化相关事项。若您的业务中涉及呼叫邀请等机制,可以使用信令

int ret = rtc_engine_->joinChannel(token, channel_name, uid);

当加入频道成功后,会走入初始化时设置的 rtc_engine_context_.event_handler 回调事件通知中的 onJoinChannel

/** 加入频道回调

     @param cid  频道 ID。
     @param uid  用户 ID。
     @param result  返回结果。
     @param elapsed 从 joinChannel 开始到发生此事件过去的时间(毫秒)。
     */
    virtual void onJoinChannel(channel_id_t cid, uid_t uid, NERtcErrorCode result, uint64_t elapsed) {
        (void)cid;
        (void)uid;
        (void)result;
        (void)elapsed;
    }

推流任务

在成功加入房间后,需要设置推流任务。典型的业务场景里是由主播进行设置。注意:此处的主播业务层面的角色,NERTC SDK体系内不区分主播与连麦者。

推流任务也可通过 服务端API 进行管理,请根据您的业务需求选择对应的方式。

增加推流任务

在成功加入频道后(参考onJoinChannel 的回调),即可以动态的添加推流任务。示例代码:

   // 添加推流任务的异步callback , 更多错误码请参考onAddLiveStreamTask API文档
    void onAddLiveStreamTask(const char* task_id, const char* url, int error_code){
        if (code == kNERtcNoError) {
            showToast("添加推流任务成功 : taskId " + taskId);
        } else {
            showToast("添加推流任务失败 : taskId " + taskId + " , code : " + code);
        }
    };

    //推流任务数据结构
    NERtcLiveStreamTaskInfo ls_task_info_;
    //保证一个url 只能设置一个task , 要不然会有问题的,使用上层自己维护的task_id、url
    strncpy(ls_task_info_.task_id, task_id.c_str(), kNERtcMaxTaskIDLength);
    strncpy(ls_task_info_.stream_url, url.c_str(), kNERtcMaxURILength);
    ls_task_info_.ls_mode = kNERtcLsModeVideo;
    ls_task_info_.server_record_enabled = false;

    //设置整体布局
    ls_task_info_.layout.background_color = 0x3399ff; // 整体背景色
    ls_task_info_.layout.width = 1280;//整体布局宽度
    ls_task_info_.layout.height = 640;//整体布局高度

    ls_task_info_.layout.user_count = 2;
    ls_task_info_.layout.users = new NERtcLiveStreamUserTranscoding[2];
    ls_task_info_.layout.users[0].uid = uid;
    ls_task_info_.layout.users[1].uid = remote_uid;
    for (int i = 0; i < ls_task_info_.layout.user_count; i++)
    {
        ls_task_info_.layout.users[i].adaption = kNERtcLsModeVideoScaleFit;
        ls_task_info_.layout.users[i].video_push = true;// 推流是否发布user的视频
        ls_task_info_.layout.users[i].x = 640 * i;      // user 的视频布局x偏移,相对整体布局的左上角
        ls_task_info_.layout.users[i].y = 0;            // user 的视频布局y偏移,相对整体布局的左上角
        ls_task_info_.layout.users[i].width = 640;      // user 的视频布局宽度
        ls_task_info_.layout.users[i].height = 640;     //user1 的视频布局高度
        ls_task_info_.layout.users[i].audio_push = true;// 推流是否发布user的音频
    }
    if (0)
    {
        ls_task_info_.layout.bg_image = new NERtcLiveStreamImageInfo;
        ls_task_info_.layout.bg_image->x = 0;
        ls_task_info_.layout.bg_image->y = 0;
        ls_task_info_.layout.bg_image->width = 640;
        ls_task_info_.layout.bg_image->height = 480;
        strncpy(ls_task_info_.layout.bg_image->url, url2.c_str(), kNERtcMaxURILength);
    }
    else
    {
        ls_task_info_.layout.bg_image = nullptr;
    }

    // 如果需要,可以配置多个用户,配置的用户不用已经在频道中,如果用户不在频道中,那么会有相应的视频占位
    // 之后用户以配置了的uid进入频道,开启推流开关即可推流
    int ret = addLiveStreamTask(ls_task_info_);
    if (ret != 0) {
        showToast("调用添加推流任务失败 , ret : " + ret);
    }

更新推流任务

在成功添加推流任务后,可以随时去更新之前配置的推流任务,注意,这里的更新是全量更新,会覆盖之前对于这条任务的所有配置, 示例代码:

    // 更新推流任务的异步callback , 更多错误码请参考onUpdateLiveStreamTask API文档
    void onUpdateLiveStreamTask(const char* task_id, const char* url, int error_code) {
        if (code == kNERtcNoError) {
        showToast("更新推流任务成功 : taskId " + taskId);
        } else {
        showToast("更新推流任务失败 : taskId " + taskId + " , code : " + code);
        }
    };

    NERtcLiveStreamTaskInfo updateLiveTask1 = preLiveTask;
    // todo 重新配置相关配置 , 具体的配置与添加推流任务时一模一样 ,唯一不能修改的就是 taskId

    int ret = updateLiveStreamTask(taskInfo);
    if (ret != 0) {
        showToast("调用更新推流任务失败 , ret : " + ret);
    }

删除推流任务

当不在需要推流时,可以选择删掉之前配置的推流任务,注意:推流任务不会随着成员的离开而自动结束,成员离开只是结束了自己的推流,只有在手动删除或者频道所有成员都离开时才会结束,示例代码:

   // 删除推流任务的异步callback , 更多错误码请参考onRemoveLiveStreamTask API文档
    void onRemoveLiveStreamTask(const char* task_id, int error_code){
        if (code == kNERtcNoError) {
            showToast("删除推流任务成功 : taskId " + taskId);
        } else {
            showToast("删除推流任务失败 : taskId " + taskId + " , code : " + code);
        }
    };

    int ret = removeLiveStreamTask(preLiveTask.taskId);
    if (ret != 0) {
        showToast("调用删除推流任务失败 : ret : " + ret);
    }

常见的异步回调错误码

以下列举下在添加(AddLiveTaskCallback)、更新(UpdateLiveTaskCallback)、删除(DeleteLiveTaskCallback)推流任务的回调中一些重要的错误码,更多的详细情况请参考RtcCode.LiveCode的API文档。

错误码 含义
kNERtcNoError (0) 成功
kNERtcErrLsTaskIsInvaild (1400) task参数格式错误
kNERtcErrLsTaskRoomExited (1401) 房间已经退出
kNERtcErrLsTaskNumLimit (1402) 推流任务超出上限
kNERtcErrLsTaskDuplicateId (1403) 推流ID重复
kNERtcErrLsTaskNotFound(1404) taskId任务不存在,或频道不存在
kNERtcErrLsTaskRequestErr (1417) 请求失败
kNERtcErrLsTaskInternalServerErr (1500) 服务器内部错误

推流状态回调

在推流过程,可以通过监听onLiveStreamState的来接收与推流状态相关的回调, 详情如下:

    /** 通知直播推流状态

     @param task_id 任务id
     @param url 推流地址
     @param state #NERtcLiveStreamStateCode, 直播推流状态
     - 505: 推流中;
     - 506: 推流失败;
     - 511: 推流结束;
     */
    virtual void onLiveStreamState(const char* task_id, const char* url, NERtcLiveStreamStateCode state) {
        (void)task_id;
        (void)url;
        (void)state;
    }

常见状态码如下,详情参考 NERtcLiveStreamStateCode API 文档:

状态码 含义
kNERtcLsStatePushing (505) 推流中
kNERtcLsStatePushFail (506) 互动直播推流失败
kNERtcLsStatePushStopped (511) 推流结束

API参考

方法 功能描述
addLiveStreamTask 添加房间推流任务
updateLiveStreamTask 更新修改房间推流任务
removeLiveStreamTask 删除房间推流任务
事件 功能描述
onLiveStreamState 通知直播推流状态

设置远端视图

视频通话过程中,除了要显示本地的视频画面,通常也要显示参与通话的其他用户的远端视频画面。

监听远端用户进出频道

当有远端用户进出频道时,会走入初始化时设置的 rtc_engine_context_.event_handler 回调事件通知中的:

通信模式下,该回调提示有远端用户加入了频道,并返回新加入用户的 uid;如果本端加入频道之前,频道内已经有其他用户了,在本端加入成功时,也会收到这些已有用户加入频道的回调。

    /** 远端用户加入当前频道回调。

     @param uid 新加入频道的远端用户ID。
     @param user_name 新加入频道的远端用户名。
     */
    virtual void onUserJoined(uid_t uid, const char * user_name) {
        (void)uid;
        (void)user_name;
    }
    /** 远端用户离开当前频道回调。

     提示有远端用户离开了频道(或掉线)。

     @param uid 远端用户ID。
     @param reason 远端用户离开原因。
     */
    virtual void onUserLeft(uid_t uid, NERtcSessionLeaveReason reason) {
        (void)uid;
        (void)reason;
    }

设置远端视频画布

监听到远端用户加入频道后,可以调用 setupRemoteVideoCanvas 方法设置远端视频画布,用来显示远端用户的视频画面。

NERtcVideoCanvas canvas;
canvas.cb = nullptr;
canvas.user_data = nullptr;
canvas.window = window;
// 设置视频缩放模式。
canvas.scaling_mode = mode;
rtc_engine_->setupRemoteVideoCanvas(uid, &canvas);

监听远端视频流发布

当频道中的其他用户有视频流发出/关闭时,分别会走入初始化时设置的 rtc_engine_context_.event_handler 回调事件通知中的以下回调:

/** 远端用户开启视频回调。

     @param uid 远端用户ID。
     @param max_profile 最大分辨率。
     */
    virtual void onUserVideoStart(uid_t uid, NERtcVideoProfileType max_profile) {
        (void)uid;
        (void)max_profile;
    }
    /** 远端用户停用视频回调。

     @param uid 远端用户ID。
     */
    virtual void onUserVideoStop(uid_t uid) {
        (void)uid;
    }

订阅远端视频流

在设置完远端视频画布后,且监听到远端用户有视频发布时,可以订阅远端用户的视频流。

/** 订阅/取消订阅 指定远端用户的视频流。对方打开视频后需要主动订阅。

    @param[in] uid 指定用户的用户 ID。
    @param[in] type 流类型。 #NERtcRemoteVideoStreamType
    @param[in] subscribe
    - true: 订阅指定远端用户的视频流;
    - false: 取消订阅指定远端用户的视频流。

    @return
    - 0: 方法调用成功;
    - 其他: 方法调用失败。
    */
    virtual int subscribeRemoteVideoStream(uid_t uid, NERtcRemoteVideoStreamType type, bool subscribe) = 0;
// 订阅指定用户的 kNERtcRemoteVideoStreamTypeHigh 类型的视频流
void NRTCEngine::subscribeRemoteUserVideoStream(nertc::uid_t uid) {
    int ret_temp = rtc_engine_->subscribeRemoteVideoStream(uid, kNERtcRemoteVideoStreamTypeHigh, true);
    if (ret_temp) {
        qDebug("[ERROR] can not subscribe remote video stream! ERROR CODE: %d", ret_temp);
    }
}

订阅成功后,即可显示远端的视频画面。

音频流

在 NERtc SDK 中,本地音频的采集发布和远端音频订阅播放是默认启动的,正常情况下无需开发者主动干预。

离开频道

当通话结束,需要离开频道,可以调用以下接口:

int ret = rtc_engine_->leaveChannel();

不论当前是否还在通话中,调用该方法会把相关的所有资源释放掉。真正退出频道后,SDK 会走入初始化时设置的 rtc_engine_context_.event_handler 回调事件通知中的 onLeaveChannel

/** 离开频道回调。

     App 调用  IRtcEngine::leaveChannel "leaveChannel" 方法时,SDK提示 App 离开频道是否成功。

     @param result 返回结果。
     */
    virtual void onLeaveChannel(NERtcErrorCode result) {
        (void)result;
    }

销毁音视频实例

释放当前的音视频实例,建议在应用确定不再需要使用音视频时,通过该接口释放对象资源。

void NRTCEngine::Uninit() {
    if (rtc_engine_) {
        ...

        //同步销毁 IRtcEngine 对象
        rtc_engine_->release(true);
        // 销毁 RTC 引擎对象
        destroyNERtcEngine((void*&)rtc_engine_);
        rtc_engine_ = nullptr;
    }
}