三、步骤一:开通服务
大班课场景通常需要依赖腾讯云 即时通信 IM 和 实时音视频 TRTC 两项付费 PaaS 服务构建,互动白板可选或者自建也可以。
1. 首先,你需要登录 实时音视频 TRTC 控制台 创建应用,完成后在 即时通信 IM 控制台 即会自动创建一个对应的体验版 IM 应用。你可根据需要选择升级 TRTC 及 IM 应用版本,例如旗舰版可解锁更多增值功能服务。
注意:说明:
2. 创建应用完毕之后,你可以在应用管理-应用概览栏目看到该应用的基本信息,其中需要你保管好 SDKAppID、SDKSecretKey 便于后续的使用,同时应避免密钥泄露造成流量盗刷。
四、步骤二:导入SDK
分别把 TRTC SDK 和 IM SDK 的 Windows 端 zip 压缩包下载到本地并解压,可在项目根目录下创建一个目录 thirdparty 用于存放所有 SDK,把 TRTC SDK 和 IM SDK 移动到 thirdparty 目录下备用。
1. 使用 QTCreator0 集成。
// *.proINCLUDEPATH += $$PWD/thirdparty/TRTC_SDK/CPlusPlus/Win64/include \\ $$PWD/thirdparty/TRTC_SDK/CPlusPlus/Win64/include/TRTC \\ $$PWD/thirdparty/IM_SDK/includeLIBS += -L'$$PWD/thirdparty/TRTC_SDK/CPlusPlus/Win64/lib' -lliteav \\ -L'$$PWD/thirdparty/IM_SDK/lib/Win64' -lImSDK
2. 使用 Visual Studio 集成。
添加头文件目录,配置 – C/C++ – 常规 – 附加头文件目录。
$(SolutionDir)thirdparty/TRTC_SDK/CPlusPlus/Win64/include$(SolutionDir)thirdparty/TRTC_SDK/CPlusPlus/Win64/include/TRTC$(SolutionDir)thirdparty/IM_SDK/include
添加库文件目录,配置 – 链接 – 常规 – 附加库目录。
$(SolutionDir)thirdparty/TRTC_SDK/CPlusPlus/Win64/lib$(SolutionDir)thirdparty/IM_SDK/lib/Win64
引用库文件。
#pragma comment(lib,"liteav.lib")#pragma comment(lib,"ImSDK.lib")
六、步骤一:生成鉴权凭证
UserSig 是腾讯云设计的一种安全保护签名,目的是为了阻止恶意攻击者盗用你的云服务使用权。腾讯云实时音视频(TRTC)、即时通信(IM)等服务都采用了该套安全保护机制,TRTC 在进房时鉴权,IM 在登录时鉴权。
·调试跑通阶段:可以通过 客户端示例代码 和 控制台 两种方法计算生成 UserSig,仅用于调试测试。
·正式运行阶段:推荐安全等级更高的服务端计算 UserSig方案,防止客户端被逆向破解泄露密钥。
具体实现流程如下:
1. 你的 App 在调用 SDK 的初始化函数之前,首先要向你的服务器请求 UserSig。
2. 你的服务器根据 SDKAppID 和 UserID 计算 UserSig。
3. 服务器将计算好的 UserSig 返回给你的 App。
4. 你的 App 将获得的 UserSig 通过特定 API 传递给 SDK。
5. SDK 将 SDKAppID + UserID + UserSig 提交给腾讯云服务器进行校验。
6. 腾讯云校验 UserSig,确认合法性。
7. 校验通过后,会向 IM SDK 提供即时通信服务、TRTC SDK 提供实时音视频服务。
注意:说明:
八、API 时序图
1. IM SDK 初始化与添加事件监听器。
IMSDK 是函数式的回调方式,把它封装一层,封装成回调类,方便使用。
// IMWrapperCallback.hclass IMWrapperCallback{public: virtual void OnLogin(int errCode, const char* errMsg) = 0; virtual void OnLogout(int errCode, const char* errMsg) = 0; virtual void OnError(int code, const char* errMsg) = 0; virtual void OnCreateGroup(int errCode, const char* errMsg) = 0; virtual void OnJoinGroup(int errCode, const char* errMsg) = 0; virtual void OnRecvNewMsg(const char* msg) = 0; // ……};// IMWrapper.hclass IMWrapper{public: IMWrapper(); bool InitIM(const char* path); bool UnInitIM(); void SetCallback(IMWrapperCallback* callback); bool LoginIM(const char* id, const char* sig); bool LogoutIM(); bool CreateGroup(const char* group_name, const char* group_type, const char* group_id); bool JoinGroup(const char* group_id); bool SendGroupTextMsg(const char* group_id, const char* text);private: IMWrapperCallback* m_callback; std::string m_userId;};// IMWrapper.cppbool IMWrapper::InitIM(const char* path){ Json::Value json_value_init; json_value_init[kTIMSdkConfigLogFilePath] = path; int nRet = TIMInit(SDKAppID_IM, json_value_init.toStyledString().c_str()); if(nRet != TIM_SUCC){ return false; } TIMAddRecvNewMsgCallback([](const char* json_param, const void* user_data) { if(user_data != nullptr){ IMWrapper* wrapper = (IMWrapper*)user_data; if(wrapper->m_callback != nullptr){ wrapper->m_callback->OnRecvNewMsg(json_param); } } }, this); return true;}
注意:说明:
注意:如果你的应用生命周期跟 SDK 生命周期一致,退出应用前可以不进行反初始化。若你只在进入特定界面后才初始化 SDK,退出界面后不再使用,可以对 SDK 进行反初始化。
2. TRTC SDK 创建实例与设置事件监听器。
//BigClass.h#include "ITRTCCloud.h"class BigClass : public ITRTCCloudCallback{public: BigClass(); ~BigClass(); virtual void onWarning(TXLiteAVWarning warningCode, const char* warningMsg, void* extraInfo) override; virtual void onError(TXLiteAVError errCode, const char *errMsg, void *extraInfo) override; virtual void onEnterRoom(int result) override; virtual void onExitRoom(int reason) override; //……}BigClass::BigClass(){ getTRTCShareInstance()->addCallback(this);//创建单例模式,设置事件监听}BigClass::~BigClass(){ getTRTCShareInstance()->removeCallback(this);//取消事件监听 destroyTRTCShareInstance();//销毁实例}
注意:说明:
注意:建议监听 SDK 事件通知,对一些常见错误进行日志打印和处理,详情请参见 错误码表。
九、步骤三:登录与登出
初始化 IM SDK 后,你需要调用 SDK 登录接口验证账号身份,获得账号的功能使用权限。因此在使用其他功能之前,请务必确保登录成功,否则可能导致功能异常或不可用。如你仅需使用 TRTC 音视频服务,可忽略此步骤。
十、API 时序图
// 登录:userID 可自定义,userSig 即步骤一生成获取bool IMWrapper::LoginIM(const char* id, const char* sig){ m_userId = id; int nRet = TIMLogin(id, sig, [](int32_t code, const char* desc, const char* json_param, const void* user_data) { if(user_data != nullptr){ IMWrapper* wrapper = (IMWrapper*)user_data; if(wrapper->m_callback != nullptr){ wrapper->m_callback->OnLogin(code, desc); } } }, this); if(nRet != TIM_SUCC){ return false; } return true;}// 登出bool IMWrapper::LogoutIM(){ int nRet = TIMLogout([](int32_t code, const char* desc, const char* json_param, const void* user_data) { if(user_data != nullptr){ IMWrapper* wrapper = (IMWrapper*)user_data; if(wrapper->m_callback != nullptr){ wrapper->m_callback->OnLogout(code, desc); } } }, this); if(nRet != TIM_SUCC){ return false; } return true;}
注意:说明:
注意:如果你的应用生命周期跟 IM SDK 生命周期一致,退出应用前可以不登出。若你只在进入特定界面后才使用 IM SDK,退出界面后不再使用,可以进行登出操作和对 IM SDK 进行反初始化。
十二、API 时序图
1. 创建房间。
主播(房主)开播时需要创建房间,这里的“房间”概念对应 IM 中的“群组”。本例展示客户端创建 IM 群组的方式,实际也可在服务端创建。
bool IMWrapper::CreateGroup(const char* name, const char* type, const char* id){ Json::Value param; //群id param[kTIMCreateGroupParamGroupId] = id; //群类型 if (strcmp(type, "Public") == 0) { param[kTIMCreateGroupParamGroupType] = kTIMGroup_Public; } else if(strcmp(type, "Work") == 0) { param[kTIMCreateGroupParamGroupType] = kTIMGroup_Private; } else if(strcmp(type, "Meeting") == 0) { param[kTIMCreateGroupParamGroupType] = kTIMGroup_ChatRoom; } else if(strcmp(type, "AVChatRoom") == 0) { param[kTIMCreateGroupParamGroupType] = kTIMGroup_AVChatRoom; //邀请进群方式 param[kTIMCreateGroupParamApproveOption] = kTIMGroupAddOpt_Forbid;//AnyJoin } //群名称 param[kTIMCreateGroupParamGroupName] = name; std::string createParams = param.toStyledString(); int nRet = TIMGroupCreate(createParams.c_str(), [](int32_t code, const char* desc, const char* json_params, const void* user_data) { if(user_data != nullptr){ IMWrapper* wrapper = (IMWrapper*)user_data; if(wrapper->m_callback != nullptr){ wrapper->m_callback->OnCreateGroup(code, desc); } } }, this); if(nRet != TIM_SUCC){ return false; } return true;}
注意:说明:
2. 进入房间。
·加入 IM 群组
bool IMWrapper::JoinGroup(const char* id){ int nRet = TIMGroupJoin(id, "Want Join Group", [](int32_t code, const char* desc, const char* json_param, const void* user_data) { if(user_data != nullptr){ IMWrapper* wrapper = (IMWrapper*)user_data; if(wrapper->m_callback != nullptr){ wrapper->m_callback->OnJoinGroup(code, desc); } } }, this); if(nRet != TIM_SUCC){ return false; } return true;}
·进入 TRTC 房间
// 进入房间void BigClass::EnterBigClass(String roomId, String userId, String userName, int roleType, String userSig){ TRTCParams param; param.sdkAppId = SDKAppID_TRTC; param.strRoomId = roomID.c_str(); param.userId = userID.c_str(); param.userSig = userSig.c_str(); if(roleType == 老师) param.role = TRTCRoleAnchor; else param.role = TRTCRoleAudience; getTRTCShareInstance()->enterRoom(param, TRTCAppSceneLIVE);}// 进房结果事件回调void BigClass::onEnterRoom(int result){ if (result > 0) { // 进房成功 } else { // 进房失败 }}
3. 退出房间。
// 退出房间void BigClass::ExitBigClass(){ getTRTCShareInstance()->exitRoom();}// 退房结果事件回调void BigClass::onExitRoom(int reason){ // 0:主动调用 exitRoom 退出房间;1:被服务器踢出当前房间;2:当前房间整个被解散}
十三、步骤五:音频流管理
TRTC SDK 默认为自动订阅音频流逻辑,用户进房会自动开始播放远端用户的声音。如有手动订阅音频流的需求,请参见 设置订阅模式。
1. 老师端推流。
// 在进房成功后,开始推音频和视频流void BigClass::onEnterRoom(int result){ if (result > 0) { // 进房成功 getTRTCShareInstance()->startLocalAudio(TRTCAudioQualitySpeech); getTRTCShareInstance()->startLocalPreview(hwndView); } else { // 进房失败 }}// 上课中途,暂停推流void BigClass::onButtonClicked(){ getTRTCShareInstance()->muteLocalAudio(true); getTRTCShareInstance()->muteLocalVideo(TRTCVideoStreamTypeBig, true);}// 上课结束,停止推流void BigClass::ExitBigClass(){ getTRTCShareInstance()->stopLocalAudio(); getTRTCShareInstance()->stopLocalPreview(); getTRTCShareInstance()->exitRoom();}
2. 学生端拉流。
当收到远端流事件回调后,则可以开始拉流。
void BigClass::onUserVideoAvailable(const char* userId, bool available){ if(available){ if(userId == 老师){ getTRTCShareInstance()->startRemoteView(userId, TRTCVideoStreamTypeBig, hwndView); //声音是自动接收的,不需要手工调用 } }}
十五、举手提问/上讲台
TRTC 用户角色分为主播和观众,其中只有主播才有推流权限,观众如需推流要先切换至主播角色。
·老师进房建议使用主播角色,默认可以推流。
·学生进房建议使用观众角色,默认不可以推流,当需要上讲台时,则可以切换到主播角色。
十六、API 时序图
// 学生端void BigClass::acceptConnectMic(){ getTRTCShareInstance()->switchRole(TRTCRoleAnchor);}void BigClass::onSwitchRole(TXLiteAVError errCode, const char* errMsg){ getTRTCShareInstance()->startLocalAudio(TRTCAudioQualitySpeech); getTRTCShareInstance()->startLocalPreview(hwndView);}// 老师端void BigClass::onUserVideoAvailable(const char* userId, bool available){ if(available){ if(userId == 当前上讲台学生){ getTRTCShareInstance()->startRemoteView(userId, TRTCVideoStreamTypeBig, hwndView); } }}
十七、屏幕分享
老师在上课过程中,通常需要给学生分享自己电脑本地内容达到教学目的。
·如分享 PPT、网页、Photoshop 等工具,这类内容和工具都有一个最外层的窗口句柄,属于窗口分享的形式;
·如果分享的内容需要在多个窗口直接来回切换,则可以按屏幕 id 的方式进行分享,这样所有的窗口都可以分享出去;
·如果只想分享窗口或者屏幕上指定的区域,则可以用区域分享;
·如果不想把指定的窗口分享出去,如课堂窗口、第三方聊天工具窗口,则可以把这些窗口加入到排除列表中,当屏幕分享和区域分享时,则不会把这些窗口分享出去。
首先,获取当前所有可以分享的屏幕或者窗口列表。
void ShareScreen::getAllScreenAndWindow(){ clearItemList(); ITRTCScreenCaptureSourceList* sourceList = nullptr; SIZE thumb_size; thumb_size.cx = 300; thumb_size.cy = 300; SIZE icon_size = thumb_size; sourceList = getTRTCShareInstance()->getScreenCaptureSources(thumb_size, icon_size); if (sourceList == nullptr){ return; } int count = sourceList->getCount(); TRTCScreenCaptureSourceInfo sourceInfo; int windowsIndex = 0; int screenIndex = 0; for (int index = 0; index < count; index++){ sourceInfo = sourceList->getSourceInfo(index); if(sourceInfo.type == TRTCScreenCaptureSourceTypeWindow){ addWindowItem(sourceInfo, windowsIndex); windowsIndex++; } else if(sourceInfo.type == TRTCScreenCaptureSourceTypeScreen){ addScreenItem(sourceInfo, screenIndex); screenIndex++; } else continue; }}
以下是常用的三种分享方式:
1. 按屏幕 id 分享。
void ShareScreen::on_shareButton_clicked(){ RECT rc; rc.left = 0; rc.top = 0; rc.right = 0; rc.bottom = 0; TRTCScreenCaptureProperty property; TRTCScreenCaptureSourceInfo sourceInfo; sourceInfo.type = TRTCScreenCaptureSourceTypeScreen; sourceInfo.sourceId = TXView(1); TRTCVideoEncParam params; params.videoFps = 10; // 低于720P,界面上的文字会变得模糊不清,建议不低于720P params.videoResolution = TRTCVideoResolution_1280_720; params.videoBitrate = 1200; getTRTCShareInstance()->selectScreenCaptureTarget(sourceInfo, rc, property); getTRTCShareInstance()->startScreenCapture(0, TRTCVideoStreamTypeSub, ¶ms);}
2. 按窗口句柄分享。
void ShareScreen::on_shareButton_clicked(){ RECT rc; rc.left = 0; rc.top = 0; rc.right = 0; rc.bottom = 0; TRTCScreenCaptureProperty property; TRTCScreenCaptureSourceInfo sourceInfo; sourceInfo.type = TRTCScreenCaptureSourceTypeWindow; sourceInfo.sourceId = TXView(hwnd); TRTCVideoEncParam params; params.videoFps = 10; // 实际情况,根据窗口大小选择相近的分辨率和码率 params.videoResolution = TRTCVideoResolution_1280_720; params.videoBitrate = 1200; getTRTCShareInstance()->selectScreenCaptureTarget(sourceInfo, rc, property); getTRTCShareInstance()->startScreenCapture(0, TRTCVideoStreamTypeSub, ¶ms);}}
3. 按自定义区域分享,并排除指定窗口。
void ShareScreen::on_shareButton_clicked(){ RECT rc; TRTCScreenCaptureProperty property; TRTCScreenCaptureSourceInfo sourceInfo; TRTCVideoEncParam params; if((m_RectShare.width()==0) || (m_RectShare.height()==0)){ // 区域选择不正确,请重新选择! return; } rc.left = m_RectShare.left(); rc.top = m_RectShare.top(); rc.right = m_RectShare.right(); rc.bottom = m_RectShare.bottom(); sourceInfo.type = TRTCScreenCaptureSourceTypeScreen; // 抗遮挡窗口 for(int i = 0; i < m_itemExcludedList.size(); i++){ if(m_itemExcludedList[i]->getStatus()){ TRTCScreenCaptureSourceInfo info = m_itemExcludedList[i]->getScreenCaptureSourceinfo(); getTRTCShareInstance()->addExcludedShareWindow(info.sourceId); } } TRTCVideoEncParam params; params.videoFps = 10; // 实际情况,根据区域大小选择相近的分辨率和码率 params.videoResolution = TRTCVideoResolution_1280_720; params.videoBitrate = 1200; getTRTCShareInstance()->selectScreenCaptureTarget(sourceInfo, rc, property); getTRTCShareInstance()->startScreenCapture(0, TRTCVideoStreamTypeSub, ¶ms);}
十八、CDN 直播
如果大班课需要做直播,可以旁路转推一路视频到腾讯云直播,也可以转推到第三方直播,这里只列举了旁路转推到腾讯云直播的情况。
1. 在控制台打开旁路转推配置。
打开 应用管理 页面,点击目标应用的配置,点击左侧列表的功能配置下的基础功能,在旁路转推配置中参考下图的配置进行操作。
2. 老师端进房时,设置 CDN 推流 streamId。
在 Windows 客户端的代码逻辑中,调用 enterRoom 接口,把 streamId 参数设置为业务自己定义的 CDN 流 id。
TRTCParams param;param.sdkAppId = SDKAppID_TRTC;param.strRoomId = m_roomID.c_str();param.userId = m_userID.c_str();param.userSig = m_userSig.c_str();param.streamId = "test";// CDN推流IdgetTRTCShareInstance()->enterRoom(param, TRTCAppSceneLIVE);
3. 拼接 CDN 播放地址
打开云直播下的 流管理 页面,点击在线流即可看到正在直播的 CDN 流。
当你需要从客户端的代码逻辑中,获取完整的 CDN 直播拉流地址时,你需要参考下图进行拼接完整的 URL 拉流地址。
注意:这里 AppName 不是默认的 live,而是trtc_sdkappid 这样的格式,例如 trtc_1400xxxxxxx。
注意:说明:
十九、云端录制
大班课的上课过程通常需要录制下来,学生可以观看录像,作为课后复习的内容。
1. 手工录制。勾选手动自定义录制,进房时,设置 userDefineRecordId 的值,则会触发手工录制。
TRTCParams param;param.sdkAppId = SDKAppID_TRTC;param.strRoomId = m_roomID.c_str();param.userId = m_userID.c_str();param.userSig = m_userSig.c_str();param.streamId = "test";param.userDefineRecordId = "recordId";// 录制IdgetTRTCShareInstance()->enterRoom(param, TRTCAppSceneLIVE);
2. 自动录制。勾选全局自动录制后,则不需要在进房时设置 userDefineRecordId ,会自动录制。
注意:说明:
注意:当前应用下每一个 TRTC 房间中的每个用户的音视频上行流都会按照你的配置全局自动录制模板自动录制下来,录制任务的启动和停止都是自动的,不需要额外开发。具体的使用方法请参阅 云端录制与回放。
21、TRTC 做旁路直播的时候如何维护用户列表、准确统计直播间的观看人数?
如果开发者项目工程中有集成 即时通信 IM,可以直接通过 IM 群人数统计接口进行统计。但该方案统计出的人数不是很准确,如果开发者对在线人数要求不高,可以直接采用上述方案。
如果开发者需要很准确的统计在线人数,建议自行实现统计逻辑:
·增加观众数(Client -> Server)当有新的观众加入时,意味着某个房间的观众数要 + 1,可以让 App 的观众端在进入房间时向 Server 发送一次累加请求。
·减少观众数(Client -> Server)当有观众退出房间时,意味着某个房间的观众数要 – 1,可以让 App 的观众端在退出房间时向 Server 发送一次累减请求。
TRTC 和 IM 在控制台也提供了服务端回调,上面的逻辑也可以用进退房回调来实现。