音视频通话 2.0
Windows
新手接入指南
产品简介
产品介绍
功能特性
产品优势
应用场景
基本概念
使用限制
产品计费
按量计费
资源包
更新日志
体验 Demo
下载 SDK 和 示例代码
快速开始
接入流程
创建应用
开通服务
快速跑通 Sample Code
集成 SDK
实现音视频通话
Token 鉴权
高级 Token 鉴权
基础功能
设置音频属性
设置视频属性
设置通话音量
屏幕共享
音频共享
通话前网络质量探测
监测发言者音量
通话中质量监测
进阶功能
音频管理
客户端音频录制
原始音频数据
美声变声与混响
耳返
自定义音频采集与渲染
音效与伴音
设置音频订阅优先级
音频裸流传输
媒体补充增强信息
视频管理
视频截图
水印
云信美颜
相芯美颜
自定义视频采集
虚拟背景
视频裸流传输
加入多房间
媒体流管理
媒体流加密
视频流回退
跨房间媒体流转发
云端录制
使用云代理
AI 融合功能
AI 降噪
AI 超分
AI 虚拟背景
最佳实践
房间连接状态管理
实现音视频安全检测
音视频参数配置推荐
API 参考
Windows API 参考
服务端 API
错误码
控制台指南
常见问题处理
FAQ
错题集
获取音频 Dump 文件
音频常见问题排查
视频常见问题排查
服务协议

实现音视频通话

更新时间: 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 时序图如下图所示。

uml diagram

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

uml diagram

实现音视频通话

步骤一 创建音视频通话的界面

请根据您的业务场景,创建相应的音视频通话界面。若您已实现相应界面,请跳过此步骤。

实现基础的音视频通话,建议您在界面上添加如下控件:

  • 房间 ID
  • 用户昵称
  • 本端视频窗口
  • 远端视频窗口
  • 麦克风按钮
  • 摄像头按钮
  • 结束通话按钮

用户界面-windows.png

步骤二 引入头文件

在您的工程中对应的文件里添加如下代码引入头文件:

  #include "nrtc_engine_ex.h"

步骤三 初始化

在操作 SDK 其他接口前,需要先完成初始化。

  1. 调用 createNERtcEngine 方法创建一个 NERtcEngine 实例。
  2. 调用 initialize 方法完成初始化。
  3. 根据 App 的应用场景,调用setChannelProfile设置房间的场景属性,不同场景的推荐配置请参见音视频参数配置推荐
  4. 通过初始化接口中注册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 接口的 widthheight 参数调整采集分辨率。

  • 实现加入房间前预览。

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

    推荐使用安全模式

channel_name 房间名称,长度为 1 ~ 64 字节。目前支持以下 89 个字符:a-z, A-Z, 0-9, space, !#$%&()+-:;≤.,>? @[]^_{|}~"。
设置相同房间名称的用户会进入同一个通话房间。
您也可以在加入通道前,通过创建房间接口创建房间。加入房间时,若传入的 {channelName} 未事先创建,则云信服务器内部将为其自动创建一个名为 {channelName} 的通话房间。
uid 用户的唯一标识 id,房间内每个用户的 uid 必须是唯一的。此 uid 为用户在您应用中的 ID,请在您的业务服务器上自行管理并维护。

SDK 发起加入房间请求后,服务器会进行响应,您可以通过初始化时设置的 rtc_engine_context_.event_handleronJoinChannel 回调监听加入房间的结果,同时该回调会抛出当前通话房间的 channelId 与加入房间总耗时(毫秒);其中 channelId 即音视频通话的 ID,建议您在业务层保存该数据,以便于后续问题排查。

步骤五 设置远端视图

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

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

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

    • onUserJoined:监听远端用户加入通话房间的事件,并抛出对方的 uid。当本端加入房间后,也会通过此回调抛出通话房间内已有的其他用户。

    • onUserVideoStart:监听远端用户发布视频流的事件,回调中携带对方的 uid 与发布的视频分辨率。

  2. 设置远端视频画布。

    在监听到远端用户加入房间或发布视频流后,本端可以调用 setupRemoteVideoCanvas 方法设置远端用户在本地显示时的缩放模式和镜像模式。

    • 视频缩放模式(NERtcVideoScalingMode):包括适应区域、填充、适应视频。
    • 视频镜像模式:默认为关闭视频镜像模式,您也可以根据需要开启镜像模式。

    示例代码

    // 示例
    NERtcVideoCanvas canvas;
    canvas.cb = nullptr;
    canvas.user_data = nullptr;
    canvas.window = window;
    // 设置视频缩放模式。
    canvas.scaling_mode = mode;
    rtc_engine_->setupRemoteVideoCanvas(uid, &canvas);
    
  3. 监听远端视频流发布。

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

  4. 订阅远端视频流。

    在设置完远端视频画布后,且监听到远端用户发布视频流时,本端可以调用 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);
        }
    } 
    
  5. 监听远端用户离开房间或停止发布视频。

步骤六 音频流

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

步骤七 退出通话房间

调用 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;

建议您在调用 releasedestroyNERtcEngine 方法彻底销毁 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)
{
	//用户网络质量回调,感知并提示用户当前网络状态
}
此文档是否对你有帮助?
有帮助
我要吐槽
  • 前提条件
  • 示例代码
  • 实现流程
  • 实现音视频通话
  • 步骤一 创建音视频通话的界面
  • 步骤二 引入头文件
  • 步骤三 初始化
  • 步骤四 设置本地视图
  • 步骤四 加入房间
  • 步骤五 设置远端视图
  • 步骤六 音频流
  • 步骤七 退出通话房间
  • 步骤八 销毁实例
  • 常用的回调