实现音视频通话
更新时间: 2023/02/22 14:51:46
网易云信音视频通话产品的基本功能包括高质量的实时音视频通话。当您成功初始化 SDK 之后,您可以简单体验本产品的基本业务流程。本文档为您展示音视频通话提供的基本业务流程。
前提条件
请确认您已完成以下操作:
示例代码
通过 MFC 实现基础音视频通话的示例代码
#pragma once
#include "videoDlg.h"
#include "nertc_event_handler.h"
#include "./utils/windows_util.h"
#include "nertc_engine_ex.h"
#include "nertc_audio_device_manager.h"
#include "nertc_video_device_manager.h"
#include <set>
#include <memory>
#include <string>
#include <unordered_map>
using namespace std;
class CmainWindow : public CDialogEx
{
public:
CmainWindow(CWnd* pParent = nullptr);
virtual ~CmainWindow();
#ifdef AFX_DESIGN_TIME
enum { IDD = IDD_NERTCSAMPLE_DIALOG };
#endif
protected:
virtual void DoDataExchange(CDataExchange* pDX);
protected:
HICON m_hIcon;
virtual BOOL OnInitDialog();
afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
afx_msg void OnPaint();
afx_msg HCURSOR OnQueryDragIcon();
//处理nertc回调相关
afx_msg LRESULT OnUserLeft(WPARAM wParam, LPARAM lParam);
afx_msg LRESULT OnUserVideoStart(WPARAM wParam, LPARAM lParam);
afx_msg LRESULT OnUserVideoStop(WPARAM wParam, LPARAM lParam);
afx_msg LRESULT OnUserSubStreamVideoStart(WPARAM wParam, LPARAM lParam);
afx_msg LRESULT OnUserSubStreamVideoStop(WPARAM wParam, LPARAM lParam);
DECLARE_MESSAGE_MAP()
public:
//加入房间
afx_msg void OnJoinChannelBtnClicked();
//离开房间
afx_msg void OnLeavechannelBtnClicked();
//开始取屏
afx_msg void OnStartScreen();
//关闭取屏
afx_msg void StopScreenCapturer();
private:
//枚举屏幕窗口信息
size_t RefreshCaptureTarget();
private:
nertc::IRtcEngineEx *rtc_engine_ = nullptr; //nertc实例
nertc::IAudioDeviceManager *_adm = nullptr; //音频设备管理
nertc::IVideoDeviceManager *_vdm = nullptr; //视频设备管理
NertcEventHandler *event_hander_; //回调事件子类
std::unordered_map<nertc::uid_t, CvideoDlg * > _remote_video; //远端用户视频窗口管理
CvideoDlg * _plocal_sub_video = nullptr; //本地视频辅流窗口
std::unordered_map<nertc::uid_t, CvideoDlg * > _remote_sub_video; //远端视频辅流窗口
//视屏辅流(屏幕共享)相关
std::set<HWND> exclude_wnd_list_;
tools::CaptureTargetInfoList s_capture_windows_;
tools::CaptureTargetInfo cur_external_capture_info_;
};
//mainWindow.cpp
#include "framework.h"
#include "afxdialogex.h"
#include "wnd_msg_define.h"
#include "nertcApp.h"
#include "mainWindow.h"
#include "nertc_engine_defines.h"
#include "./utils/time_util.h"
using namespace tools;
#define SDK_CHECK \
if(!rtc_engine_) \
{\
return;\
}\
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
CmainWindow::CmainWindow(CWnd* pParent /*=nullptr*/)
: CDialogEx(IDD_NERTCSAMPLE_DIALOG, pParent)
{
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
CmainWindow::~CmainWindow()
{
if (nullptr != rtc_engine_) {
rtc_engine_->enableLocalVideo(false);
rtc_engine_->release();
destroyNERtcEngine((void *&)rtc_engine_);
_adm = nullptr;
_vdm = nullptr;
rtc_engine_ = nullptr;
delete event_hander_;
event_hander_ = nullptr;
}
if (nullptr != _plocal_sub_video) {
delete _plocal_sub_video;
_plocal_sub_video = nullptr;
}
}
void CmainWindow::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
}
BEGIN_MESSAGE_MAP(CmainWindow, CDialogEx)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_BN_CLICKED(IDC_BUTTON2_JOINCHANNEL, &CmainWindow::OnJoinChannelBtnClicked)
ON_BN_CLICKED(IDC_BUTTON3_LEAVECHANNEL, &CmainWindow::OnLeavechannelBtnClicked)
ON_BN_CLICKED(IDC_BUTTON1_START, &CmainWindow::OnStartScreen)
ON_BN_CLICKED(IDC_BUTTON2_CLOSE, &CmainWindow::StopScreenCapturer)
//sdk回调事件转发
ON_MESSAGE(WM_ON_USER_LEFT, &CmainWindow::OnUserLeft)
ON_MESSAGE(WM_ON_USER_VIDEO_START, &CmainWindow::OnUserVideoStart)
ON_MESSAGE(WM_ON_USER_VIDEO_STOP, &CmainWindow::OnUserVideoStop)
ON_MESSAGE(WM_ON_USER_SUB_VIDEO_START, &CmainWindow::OnUserSubStreamVideoStart)
ON_MESSAGE(WM_ON_USER__SUB_VIDEO_STOP, &CmainWindow::OnUserSubStreamVideoStop)
END_MESSAGE_MAP()
BOOL CmainWindow::OnInitDialog()
{
CDialogEx::OnInitDialog();
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);
CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != nullptr)
{
BOOL bNameValid;
CString strAboutMenu;
bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);
ASSERT(bNameValid);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
}
}
SetIcon(m_hIcon, TRUE);
SetIcon(m_hIcon, FALSE);
return TRUE;
}
void CmainWindow::OnSysCommand(UINT nID, LPARAM lParam)
{
if ((nID & 0xFFF0) == IDM_ABOUTBOX)
{
/*CAboutDlg dlgAbout;
dlgAbout.DoModal();*/
}
else
{
CDialogEx::OnSysCommand(nID, lParam);
}
}
void CmainWindow::OnPaint()
{
if (IsIconic())
{
CPaintDC dc(this);
SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CDialogEx::OnPaint();
}
}
HCURSOR CmainWindow::OnQueryDragIcon()
{
return static_cast<HCURSOR>(m_hIcon);
}
LRESULT CmainWindow::OnUserLeft(WPARAM wParam, LPARAM lParam)
{
//关闭远端视频窗口
LPNERtcUserInfo lpData = (LPNERtcUserInfo)wParam;
if (_remote_video.end() != _remote_video.find(lpData->uid)) {
CvideoDlg * pdlg = _remote_video[lpData->uid];
pdlg->ShowWindow(SW_HIDE);
_remote_video.erase(lpData->uid);
delete pdlg;
pdlg = nullptr;
}
//关闭远端屏幕共享窗口
if (_remote_sub_video.end() != _remote_sub_video.find(lpData->uid)) {
CvideoDlg * pdlg = _remote_sub_video.find(lpData->uid)->second;
pdlg->ShowWindow(SW_HIDE);
_remote_sub_video.erase(lpData->uid);
delete pdlg;
pdlg = nullptr;
}
return 0;
}
LRESULT CmainWindow::OnUserVideoStart(WPARAM wParam, LPARAM lParam)
{
LPNERtcUserInfo lpData = (LPNERtcUserInfo)wParam;
if (_remote_video.end() == _remote_video.find(lpData->uid)) {
CvideoDlg * p_remote_video_dlg = new CvideoDlg();
p_remote_video_dlg->Create(IDD_DIALOG1_VIDEO);
p_remote_video_dlg->ShowWindow(SW_HIDE);
_remote_video[lpData->uid] = p_remote_video_dlg;
HWND hwd = p_remote_video_dlg->GetDlgItem(IDC_REMOTE_VIDEO)->m_hWnd;
nertc::NERtcVideoCanvas canvas;
canvas.cb = nullptr;
canvas.user_data = nullptr;
canvas.window = hwd;
//设置远端用户视频画布
rtc_engine_->setupRemoteVideoCanvas(lpData->uid, &canvas);
}
//订阅远端用户视频流
rtc_engine_->subscribeRemoteVideoStream(lpData->uid, nertc::kNERtcRemoteVideoStreamTypeHigh, true);
_remote_video[lpData->uid]->ShowWindow(SW_SHOWNORMAL);
return 0;
}
LRESULT CmainWindow::OnUserVideoStop(WPARAM wParam, LPARAM lParam)
{
nertc::uid_t uid = (nertc::uid_t)wParam;
//取消订阅远端用户视频流
rtc_engine_->subscribeRemoteVideoStream(uid, nertc::kNERtcRemoteVideoStreamTypeHigh, false);
if (_remote_video.end() != _remote_video.find(uid)) {
CvideoDlg * pdlg = _remote_video[uid];
pdlg->ShowWindow(SW_HIDE);
_remote_video.erase(uid);
delete pdlg;
pdlg = nullptr;
}
return 0;
}
LRESULT CmainWindow::OnUserSubStreamVideoStart(WPARAM wParam, LPARAM lParam)
{
LPNERtcUserInfo lpData = (LPNERtcUserInfo)wParam;
if (_remote_sub_video.end() == _remote_sub_video.find(lpData->uid)) {
CvideoDlg * pdlg = new CvideoDlg();
pdlg->Create(IDD_DIALOG1_VIDEO);
pdlg->ShowWindow(SW_HIDE);
_remote_sub_video[lpData->uid] = pdlg;
HWND hwd = pdlg->GetDlgItem(IDC_REMOTE_VIDEO)->m_hWnd;
nertc::NERtcVideoCanvas canvas;
canvas.cb = nullptr;
canvas.user_data = nullptr;
canvas.window = hwd;
//设置远端用户视频辅流画布
rtc_engine_->setupRemoteSubStreamVideoCanvas(lpData->uid, &canvas);
}
//订阅远端用户视频辅流
int ret = rtc_engine_->subscribeRemoteVideoSubStream(lpData->uid, true);
_remote_sub_video[lpData->uid]->ShowWindow(SW_SHOWNORMAL);
return 0;
}
LRESULT CmainWindow::OnUserSubStreamVideoStop(WPARAM wParam, LPARAM lParam)
{
nertc::uid_t uid = (nertc::uid_t)wParam;
//取消订阅远端视频辅流
rtc_engine_->subscribeRemoteVideoSubStream(uid, false);
if (_remote_sub_video.end() != _remote_sub_video.find(uid)) {
CvideoDlg * pdlg = _remote_sub_video.find(uid)->second;
pdlg->ShowWindow(SW_HIDE);
_remote_sub_video.erase(uid);
delete pdlg;
pdlg = nullptr;
}
return 0;
}
void CmainWindow::OnJoinChannelBtnClicked()
{
//初始化sdk
if (nullptr == rtc_engine_) {
rtc_engine_ = (nertc::IRtcEngineEx *)createNERtcEngine();
event_hander_ = new NertcEventHandler(this->m_hWnd);
}
std::string appKey = ""; //用户自己的appkey
nertc::NERtcEngineContext context;
context.app_key = appKey.c_str();
context.event_handler = event_hander_;
context.video_use_exnternal_render = true;
context.video_prefer_hw_decoder = false;
context.video_prefer_hw_encoder = false;
context.log_level = nertc::kNERtcLogLevelInfo;
context.log_file_max_size_KBytes = 20 * 1024;
context.log_dir_path = "D:/";
int ret = rtc_engine_->initialize(context);
if (ret == 0)
{
rtc_engine_->queryInterface(nertc::kNERtcIIDAudioDeviceManager, (void **)&_adm);
rtc_engine_->queryInterface(nertc::kNERtcIIDVideoDeviceManager, (void **)&_vdm);
}
HWND hwd = GetDlgItem(ID_LOCAL_VIDEO)->m_hWnd;
//设置本地视频画布
nertc::NERtcVideoCanvas canvas;
canvas.cb = nullptr;
canvas.user_data = nullptr;
canvas.window = hwd;
ret = rtc_engine_->setupLocalVideoCanvas(&canvas);
//是否开启本地视频
ret = rtc_engine_->enableLocalVideo(true);
//设置用户角色
rtc_engine_->setClientRole(nertc::kNERtcClientRoleBroadcaster);
//设置通话场景
rtc_engine_->setChannelProfile(nertc::kNERtcChannelProfileCommunication);
//设置视频属性(可房间内调用,需重启摄像头)
nertc::NERtcVideoConfig config;
config.max_profile = nertc::kNERtcVideoProfileHD720P;
config.crop_mode_ = nertc::kNERtcVideoCropModeDefault;
config.mirror_mode = nertc::kNERtcVideoMirrorModeDisabled;
rtc_engine_->setVideoConfig(config);
string token = ""; //用户自己的真实token
std::string channelName = "159369";
nertc::uid_t uid = 1133;
//加入房间
ret = rtc_engine_->joinChannel(token.c_str(), channelName.c_str(), uid);
}
void CmainWindow::OnLeavechannelBtnClicked()
{
//关闭本地视频
rtc_engine_->enableLocalVideo(false);
nertc::NERtcVideoCanvas canvas;
canvas.cb = nullptr;
canvas.user_data = nullptr;
canvas.window = nullptr;
rtc_engine_->setupLocalVideoCanvas(&canvas);
//离开房间
int ret = rtc_engine_->leaveChannel();
}
size_t CmainWindow::RefreshCaptureTarget()
{
s_capture_windows_.clear();
int i = 0;
s_capture_windows_.push_back({ tools::CaptureTargetType_Desktop, 0, L"桌面", RECT{0, 0, 0, 0} });
EnumDisplayMonitors(nullptr, nullptr,
[](HMONITOR hmon, HDC, LPRECT pRC, LPARAM lparam) {
auto& monitors = *reinterpret_cast<tools::CaptureTargetInfoList*>(lparam);
wchar_t buf[100] = { 0 };
swprintf_s(buf, L"Monitor:(%d,%d,%d,%d)", pRC->left, pRC->top, pRC->right, pRC->bottom);
monitors.push_back({ tools::CaptureTargetType_Monitor, (HWND)hmon, buf, *pRC });
return TRUE;
},
reinterpret_cast<LPARAM>(&s_capture_windows_));
/// for windows
GetCaptureWindowList(&s_capture_windows_);
return s_capture_windows_.size();
}
//开始屏幕共享
void CmainWindow::OnStartScreen()
{
RefreshCaptureTarget();
tools::CaptureTargetInfo info = s_capture_windows_[0];
nertc::NERtcScreenCaptureParameters capture_params;
capture_params.profile = nertc::kNERtcScreenProfileHD720P;
capture_params.profile = nertc::kNERtcScreenProfileHD720P;
capture_params.dimensions = { 1280, 720 };
capture_params.frame_rate = 5;
capture_params.bitrate = 0;
capture_params.capture_mouse_cursor = true;
capture_params.window_focus = true;
capture_params.excluded_window_list = nullptr;
capture_params.excluded_window_count = 0;
if (nullptr == _plocal_sub_video) {
_plocal_sub_video = new CvideoDlg();
_plocal_sub_video->Create(IDD_DIALOG1_VIDEO);
HWND hwd = _plocal_sub_video->GetDlgItem(IDC_REMOTE_VIDEO)->m_hWnd;
nertc::NERtcVideoCanvas canvas;
canvas.cb = nullptr;
canvas.user_data = nullptr;
canvas.window = hwd;
//设置本地预览画布
rtc_engine_->setupLocalSubStreamVideoCanvas(&canvas);
}
_plocal_sub_video->ShowWindow(SW_SHOWNORMAL);
if (info.type == CaptureTargetType_Window) {
//开始共享
rtc_engine_->startScreenCaptureByWindowId(info.id, { 0, 0, 0, 0 }, capture_params);
}
else {
HWND* wnd_list = NULL;
if (!exclude_wnd_list_.empty()) {
wnd_list = new HWND[exclude_wnd_list_.size()];
for (auto e : exclude_wnd_list_) {
*(wnd_list + capture_params.excluded_window_count++) = e;
}
}
capture_params.excluded_window_list = (nertc::source_id_t*)wnd_list;
nertc::NERtcRectangle rc{ info.rc.left, info.rc.top, info.rc.right - info.rc.left, info.rc.bottom - info.rc.top };
//开始共享
rtc_engine_->startScreenCaptureByScreenRect(rc, { 0, 0, 0, 0 }, capture_params);
if (wnd_list) {
delete[] wnd_list;
}
}
}
//停止屏幕共享
void CmainWindow::StopScreenCapturer()
{
rtc_engine_->stopScreenCapture();
_plocal_sub_video->ShowWindow(SW_HIDE);
}
实现流程
实现音频通话的 API 时序图如下图所示。
实现视频通话的 API 时序图如下图所示。
实现音视频通话
步骤一 创建音视频通话的界面
请根据您的业务场景,创建相应的音视频通话界面。若您已实现相应界面,请跳过此步骤。
实现基础的音视频通话,建议您在界面上添加如下控件:
- 房间 ID
- 用户昵称
- 本端视频窗口
- 远端视频窗口
- 麦克风按钮
- 摄像头按钮
- 结束通话按钮
步骤二 引入头文件
在您的工程中对应的文件里添加如下代码引入头文件:
#include "nrtc_engine_ex.h"
步骤三 初始化
在操作 SDK 其他接口前,需要先完成初始化。
- 调用
createNERtcEngine
方法创建一个 NERtcEngine 实例。 - 调用
initialize
方法完成初始化。 - 根据 App 的应用场景,调用setChannelProfile设置房间的场景属性,不同场景的推荐配置请参见音视频参数配置推荐。
- 通过初始化接口中注册
IRtcEngineEventHandlerEx
子类,监听事件,您需要重点关注的事件类型请参见常用的回调。
示例代码如下:
// 创建 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 = event_hander_;
// 初始化 NERTC SDK 服务。
if (kNERtcNoError != rtc_engine_->initialize(rtc_engine_context_))
{
//获取音频设备管理指针
rtc_engine_->queryInterface(nertc::kNERtcIIDAudioDeviceManager, (void **)&_adm);
//获取视频谁管理指针
rtc_engine_->queryInterface(nertc::kNERtcIIDVideoDeviceManager, (void **)&_vdm);
//设置房间场景
rtc_engine_->setChannelProfile(nertc::kNERtcChannelProfileCommunication);
//设置用户角色
rtc_engine_->setClientRole(nertc::kNERtcClientRoleBroadcaster);
}
步骤四 设置本地视图
初始化成功后,可以设置本地视图,来预览本地图像。您可以根据业务需要实现加入房间之前预览或加入房间后预览。
在加入房间前,默认预览分辨率为 640*480,您可以通过 setVideoConfig
接口的 width
和 height
参数调整采集分辨率。
-
实现加入房间前预览。
- 调用
setupLocalVideoCanvas
与startVideoPreview(type)
方法,在加入房间前设置本地视图,预览本地图像。
示例代码如下:
NERtcVideoCanvas canvas; canvas.cb = nullptr; canvas.user_data = nullptr; canvas.window = window; //设置视频缩放模式。 canvas.scaling_mode = mode; //设置本地视频画布 rtc_engine_->setupLocalVideoCanvas(&canvas); //以开启本地视频主流预览为例 rtc_engine_->startVideoPreview(kNERTCVideoStreamMain);
- 若要结束预览,或者准备加入房间时,调用
stopVideoPreview(type)
方法停止预览。
stopVideoPreview(type)
的type
参数请与startVideoPreview(type)
的保持一致,即同为主流或辅流的开启和停止预览。 - 调用
-
实现加入房间后预览。
调用
enableLocalVideo
方法进行视频的采集发送与预览。成功加入房间后,即可预览本地图像。示例代码如下:
NERtcVideoCanvas canvas; canvas.cb = nullptr; canvas.user_data = nullptr; canvas.window = window; //设置视频缩放模式。 canvas.scaling_mode = mode; //设置本地视频画布 rtc_engine_->setupLocalVideoCanvas(&canvas); bool enabled = true; //以开启本地视频主流采集并发送为例 rtc_engine_->enableLocalVideo(nertc::kNERTCVideoStreamMain, enabled);
步骤四 加入房间
加入房间前,请确保已完成初始化相关事项。若您的业务中涉及呼叫邀请等机制,建议通过信令实现,总体实现流程请参见一对一会话操作流程,具体呼叫邀请机制的实现请参见邀请机制。
调用 joinChannel
方法加入房间。
示例代码如下:
rtc_engine_->joinChannel(token, channel_name, uid);
重要参数说明
参数 | 说明 |
---|---|
token | 安全认证签名(NERTC Token)。
|
channel_name | 房间名称,长度为 1 ~ 64 字节。目前支持以下 89 个字符:a-z, A-Z, 0-9, space, !#$%&()+-:;≤.,>? @[]^_{|}~"。 设置相同房间名称的用户会进入同一个通话房间。 |
uid | 用户的唯一标识 id,房间内每个用户的 uid 必须是唯一的。 |
SDK 发起加入房间请求后,服务器会进行响应,您可以通过初始化时设置的 rtc_engine_context_.event_handler
的 onJoinChannel
回调监听加入房间的结果,同时该回调会抛出当前通话房间的 channelId 与加入房间总耗时(毫秒);其中 channelId 即音视频通话的 ID,建议您在业务层保存该数据,以便于后续问题排查。
步骤五 设置远端视图
音视频通话过程中,除了要显示本地的视频画面,通常也要显示参与互动的其他连麦者/主播的远端视频画面。
-
监听远端用户进出频道。
IRtcEngineEventHandler
通过以下回调获取相关信息:-
onUserJoined
:监听远端用户加入通话房间的事件,并抛出对方的 uid。当本端加入房间后,也会通过此回调抛出通话房间内已有的其他用户。 -
onUserVideoStart
:监听远端用户发布视频流的事件,回调中携带对方的 uid 与发布的视频分辨率。
-
-
设置远端视频画布。
在监听到远端用户加入房间或发布视频流后,本端可以调用
setupRemoteVideoCanvas
方法设置远端用户在本地显示时的缩放模式和镜像模式。- 视频缩放模式(NERtcVideoScalingMode):包括适应区域、填充、适应视频。
- 视频镜像模式:默认为关闭视频镜像模式,您也可以根据需要开启镜像模式。
示例代码
// 示例 NERtcVideoCanvas canvas; canvas.cb = nullptr; canvas.user_data = nullptr; canvas.window = window; // 设置视频缩放模式。 canvas.scaling_mode = mode; rtc_engine_->setupRemoteVideoCanvas(uid, &canvas);
-
监听远端视频流发布。
当房间中的其他用户发布视频流时,本端会触发
onUserVideoStart
回调。 -
订阅远端视频流。
在设置完远端视频画布后,且监听到远端用户发布视频流时,本端可以调用
subscribeRemoteVideoStream
方法对其发起视频流的订阅,来将对方的视频流渲染到视频画布上。示例代码如下:
//以订阅指定远端用户的视频主流为例 void NRTCEngine::subscribeRemoteUserVideoStream(nertc::uid_t uid) { int ret_temp = rtc_engine_->subscribeRemoteVideoStream(uid, nertc::kNERtcRemoteVideoStreamTypeHigh, true); if (ret_temp) { qDebug("[ERROR] can not subscribe remote video stream! ERROR CODE: %d", ret_temp); } }
-
监听远端用户离开房间或停止发布视频。
-
onLeaveChannel
:远端用户离开房间回调。 -
onUserVideoStop
:远端用户关闭视频功能回调。
-
步骤六 音频流
本地音频的采集发布和远端音频订阅播放默认启动,正常情况下无需开发者主动干预。
步骤七 退出通话房间
调用 leaveChannel
方法退出通话房间。
示例代码如下:
rtc_engine_->leaveChannel();
真正退出房间后,SDK 会走入初始化时设置的 rtc_engine_context_.event_handler 回调事件通知中的 onLeaveChannel
。
步骤八 销毁实例
当确定短期内不再使用音视频通话实例时,可以释放对应的对象资源。
示例代码如下:
// 同步销毁 IRtcEngine 对象
rtc_engine_->release(true);
// 销毁 RTC 引擎对象
destroyNERtcEngine((void*&)rtc_engine_);
rtc_engine_ = nullptr;
// 音频设备管理指针
_adm = nullptr;
// 视频设备管理指针
_vdm = nullptr;
建议您在调用 release
和 destroyNERtcEngine
方法彻底销毁 NERtc 实例后再卸载库,否则可能会导致异常崩溃。
常用的回调
//nertc_event_handler.h
#pragma once
#include "nertc_engine_event_handler_ex.h"
#include "nertc_engine_media_stats_observer.h"
//用户继承nertc::IRtcEngineEventHandlerEx,根据实际业务场景重写相关回调即可
class NertcEventHandler: public nertc::IRtcEngineEventHandlerEx,
public nertc::IRtcMediaStatsObserver
{
public:
NertcEventHandler();
~NertcEventHandler();
public:
virtual void onError(int error_code, const char* msg) override;
virtual void onWarning(int warn_code, const char* msg) override;
virtual void onJoinChannel(nertc::channel_id_t cid, nertc::uid_t uid, nertc::NERtcErrorCode result, uint64_t elapsed) override;
virtual void onConnectionStateChange(nertc::NERtcConnectionStateType state, nertc::NERtcReasonConnectionChangedType reason) override;
virtual void onLeaveChannel(nertc::NERtcErrorCode result) override;
virtual void onUserJoined(nertc::uid_t uid, const char * user_name) override;
virtual void onUserLeft(nertc::uid_t uid, nertc::NERtcSessionLeaveReason reason) override;
virtual void onUserAudioStart(nertc::uid_t uid) override;
virtual void onUserAudioStop(nertc::uid_t uid) override;
virtual void onUserVideoStart(nertc::uid_t uid, nertc::NERtcVideoProfileType max_profile) override;
virtual void onUserVideoStop(nertc::uid_t uid) override;
virtual void onUserSubStreamVideoStart(nertc::uid_t uid, nertc::NERtcVideoProfileType max_profile) override;
virtual void onUserSubStreamVideoStop(nertc::uid_t uid) override;
virtual void onUserAudioMute(nertc::uid_t uid, bool mute) override;
virtual void onUserVideoMute(nertc::uid_t uid, bool mute) override;
virtual void onNetworkQuality(const nertc::NERtcNetworkQualityInfo *infos, unsigned int user_count) override;
};
//nertc_event_handler.cpp
#include "nertc_event_impl.h"
NertcEventHandler::NertcEventHandler()
{
}
NertcEventHandler::~NertcEventHandler()
{
}
void NertcEventHandler::onError(int error_code, const char* msg)
{
//错误信息回调
}
void NertcEventHandler::onWarning(int warn_code, const char* msg)
{
//警告信息回调
}
void NertcEventHandler::onJoinChannel(nertc::channel_id_t cid, nertc::uid_t uid, nertc::NERtcErrorCode code, uint64_t elapsed)
{
//本端用户成功加入房间通知回调,建议在收到此回调后再进行推拉流或订阅的操作
}
void NertcEventHandler::onConnectionStateChange(nertc::NERtcConnectionStateType state, nertc::NERtcReasonConnectionChangedType reason)
{
//房间连接状态改变通知回调
}
void NertcEventHandler::onLeaveChannel(nertc::NERtcErrorCode result)
{
//本端用户成功退出房间通知回调
}
void NertcEventHandler::onUserJoined(nertc::uid_t uid, const char * user_name)
{
//远端用户加入房间通知回调,建议在收到此回调后再进行设置远端视图等的操作
}
void NertcEventHandler::onUserLeft(nertc::uid_t uid, nertc::NERtcSessionLeaveReason reason)
{
//本端用户成功退出房间通知回调
}
void NertcEventHandler::onUserAudioStart(nertc::uid_t uid)
{
//远端用户推音频流通知回调,建议在收到此回调后再进行订阅音频流的操作
}
void NertcEventHandler::onUserAudioStop(nertc::uid_t uid)
{
//远端用户停止推音频流通知回调,建议在收到此回调后再进行取消订阅音频流的操作
}
void NertcEventHandler::onUserVideoStart(nertc::uid_t uid, nertc::NERtcVideoProfileType max_profile)
{
//远端用户推视频流通知回调,建议在收到此回调后再进行订阅视频流的操作
}
void NertcEventHandler::onUserVideoStop(nertc::uid_t uid)
{
//远端用户停止推视频流通知回调,建议在收到此回调后再进行取消订阅视频流的操作
}
void NertcEventHandler::onUserSubStreamVideoStart(nertc::uid_t uid, nertc::NERtcVideoProfileType max_profile)
{
//远端用户开启屏幕共享回调,建议在收到此回调后再进行设置视频辅流画布设置及订阅操作
}
void NertcEventHandler::onUserSubStreamVideoStop(nertc::uid_t uid)
{
//远端用户停止屏幕共享回调,建议在收到此回调后再取消订阅视频辅流及清空画布
}
void NertcEventHandler::onUserAudioMute(nertc::uid_t uid, bool mute)
{
// 远端用户是否静音回调,感知并刷新界面展示远端用户麦克风状态
}
void NertcEventHandler::onUserVideoMute(nertc::uid_t uid, bool mute)
{
//远端用户暂停/恢复发送视频流回调,感知并刷新界面,展示远端用户摄像头状态
}
void NertcEventHandler::onNetworkQuality(const nertc::NERtcNetworkQualityInfo *infos, unsigned int user_count)
{
//用户网络质量回调,感知并提示用户当前网络状态
}