本文介绍如何集成火山引擎 RTC SDK,并实现实时音视频通话。根据如下步骤操作,即可从 0 开始构建一个简单的音视频通话应用。
你也可以参考示例项目,了解更完整的项目实现。
在开始集成 RTC SDK 前,请确保满足以下要求:
Windows 7 或以上版本设备,且可以正常访问互联网
Microsoft Visual Studio 2017 或以上版本
Qt 5.9 或以上版本,且 Qt 与 VS 版本对应
在 Visual Studio 中安装 Qt 扩展,详细操作参看如何在 Visual Studio 中安装 Qt 扩展?
麦克风、摄像头等音视频设备
火山引擎 RTC SDK 文件
打开 Visual Studio,选择 文件 > 新建 > 项目,选择 Qt Widget Application,单击下一步。


配置新项目,将项目名称修改为 RTCTest。

选择使用的 Qt 类型为 32 位或 64 位,单击 Next,然后单击 Finish。

根据你的开发需求下载 Win32 或 x64 的 RTC SDK,解压后将 VolcEngineRTC 文件夹放在 RTCTest.sln 同级目录下,完成后的项目目录结构如下:
|-- RTCTest
| |-- Debug
| |-- RTCTest.aps
| |-- RTCTest.cpp
| |-- RTCTest.h
| |-- RTCTest.qrc
| |-- RTCTest.rc
| |-- RTCTest.ui
| |-- RTCTest.vcxproj
| |-- RTCTest.vcxproj.filters
| |-- RTCTest.vcxproj.user
| |-- main.cpp
| `-- resource.h
|-- RTCTest.sln
`-- VolcEngineRTC #VolcEngineRTC 库放在 .sln 同级目录下
|-- bin
|-- include
`-- lib
在 Visual Studio 的菜单栏中,选择项目,然后选择 RTCTest 属性,并按照以下步骤进行配置。
将 include 目录加入到头文件搜索路径。选择配置属性 > C/C++ > 常规,在附加包含目录下拉列表中选择 <编辑...>,然后追加 $(SolutionDir)\VolcEngineRTC\include。

将 lib 目录加入到库搜索路径。选择配置属性 > 链接器 > 常规,根据项目类型,在附加库目录下拉列表中选择 <编辑...>,然后追加 $(SolutionDir)\VolcEngineRTC\lib\Win32 或 $(SolutionDir)\VolcEngineRTC\lib\x64。

选择配置属性 > 链接器 > 输入,在附加依赖项下拉列表中选择 <编辑...>,然后追加 VolcEngineRTC.lib。

选择配置属性 > 生成事件 > 生成后事件,根据项目类型,在命令行后增加 xcopy /Y /S "$(SolutionDir)\VolcEngineRTC\bin\Win32\*" "$(TargetDir)" 或 xcopy /Y /S "$(SolutionDir)\VolcEngineRTC\bin\x64\*" "$(TargetDir)",用于生成 exe 后将依赖库拷贝到目标目录下。

说明
本章节将先向你提供 API 调用时序图和完整的实现代码,再对具体的实现步骤展开介绍。
下图为使用火山引擎 RTC SDK 实现基础音视频通话的 API 调用时序图。

将下面两段代码分别替换 RTCTest.h 和 RTCTest.cpp 文件中的全部内容,单击本地 Windows 调试器,即可快速实现音视频通话。
RTCTest.h 代码内容 说明
你需要将 RTCTest.h 中的 m_roomid、m_uid、m_appid、m_token 替换为你在控制台上生成临时 Token 时所使用的房间 ID 和用户 ID,以及获取到的 AppID 和临时 Token。
// 以下为 RTCTest.h 的完整代码内容
#pragma once
#include <QtWidgets/QMainWindow>
#include "ui_RTCTest.h"
#include "bytertc_video.h"
#include "bytertc_room.h"
#include "bytertc_room_event_handler.h"
#include "bytertc_video_event_handler.h"
#include <QHBoxLayout>
#include <QMessageBox>
#include <QDebug>
#include <QMetaType>
#include <memory>
Q_DECLARE_METATYPE(std::string)
Q_DECLARE_METATYPE(bytertc::MediaStreamType)
Q_DECLARE_METATYPE(bytertc::StreamRemoveReason)
class EventHandler : public QObject, public bytertc::IRTCVideoEventHandler, public bytertc::IRTCRoomEventHandler {
Q_OBJECT
public:
void onRoomStateChanged(
const char* room_id, const char* uid, int state, const char* extra_info) override {
if (room_id != nullptr && uid != nullptr) {
std::string str_extra_info = extra_info ? extra_info : "";
emit sigRoomStateChanged(std::string(room_id), std::string(uid), state, str_extra_info);
}
}
void onUserPublishStream(const char* uid, bytertc::MediaStreamType type) override {
if (uid != nullptr) {
emit sigUserPublishStream(std::string(uid), type);
}
}
void onUserUnpublishStream(const char* uid, bytertc::MediaStreamType type, bytertc::StreamRemoveReason reason) override {
if (uid != nullptr) {
emit sigUserUnpublishStream(std::string(uid), type, reason);
}
}
signals:
void sigRoomStateChanged(std::string roomid, std::string uid, int state, std::string extra_info);
void sigUserPublishStream(std::string uid, bytertc::MediaStreamType type);
void sigUserUnpublishStream(std::string uid, bytertc::MediaStreamType type, bytertc::StreamRemoveReason reason);
};
class RTCTest : public QMainWindow
{
Q_OBJECT
public:
RTCTest(QWidget *parent = nullptr);
~RTCTest();
private:
Ui::RTCTestClass ui;
QWidget* widget_local = nullptr;
QWidget* widget_remote = nullptr;
bytertc::IRTCVideo* m_video = nullptr;
bytertc::IRTCRoom* m_room = nullptr;
std::string m_roomid = ""; //房间id,请在此处填写自己的房间id
std::string m_uid = ""; //用户id,请在此处填写自己的用户id
std::string m_appid = ""; //appid, 请在此处填写应用的appid
std::string m_token = ""; //token, 请将控制台生成的token填写在此处,要求与上面的roomid、uid对应
std::shared_ptr<EventHandler> m_handler;
};
cppRTCTest.cpp 代码内容 // 以下为 RTCTest.cpp 的完整代码内容
#include "RTCTest.h"
RTCTest::RTCTest(QWidget *parent)
: QMainWindow(parent)
{
ui.setupUi(this);
//init data type
qRegisterMetaType<std::string>();
qRegisterMetaType<bytertc::MediaStreamType>("bytertc::MediaStreamType");
qRegisterMetaType<bytertc::StreamRemoveReason>("bytertc::StreamRemoveReason");
//init UI
this->resize(500, 500);
QWidget* centralWidget = new QWidget(this);
QHBoxLayout* lay = new QHBoxLayout(centralWidget);
widget_local = new QWidget(this);
widget_remote = new QWidget(this);
lay->addWidget(widget_local);
lay->addWidget(widget_remote);
widget_local->setFixedSize(200, 200);
widget_remote->setFixedSize(200, 200);
widget_local->show();
widget_remote->show();
this->setCentralWidget(centralWidget);
//开始RTC接口调用,检查参数是否为空
if (m_appid.empty() /*|| m_token.empty()*/ || m_uid.empty() || m_roomid.empty()) {
QMessageBox box(QMessageBox::Warning, QStringLiteral("提示"), QString("paras is empty"), QMessageBox::Ok);
box.exec();
return;
}
//createRTCVideo
m_handler = std::make_shared<EventHandler>();
connect(m_handler.get(), &EventHandler::sigRoomStateChanged, this, [this](std::string roomid, std::string uid, int state, std::string extra_info) {
//onRoomStateChanged
qDebug() << Q_FUNC_INFO << "roomid=" << QString::fromStdString(roomid) << ",uid=" << QString::fromStdString(uid) << ",state=" << QString::number(state);
});
connect(m_handler.get(), &EventHandler::sigUserPublishStream, this, [this](std::string uid, bytertc::MediaStreamType type) {
qDebug() << Q_FUNC_INFO << "onUserPublishStream,uid=" << QString::fromStdString(uid) << ",type=" << QString::number(type);
if (m_video) {
bytertc::VideoCanvas cas;
bytertc::RemoteStreamKey key;
key.room_id = m_roomid.c_str();
key.user_id = uid.c_str();
key.stream_index = bytertc::kStreamIndexMain;
cas.background_color = 0;
cas.render_mode = bytertc::RenderMode::kRenderModeFit;
cas.view = (void*)widget_remote->winId();
m_video->setRemoteVideoCanvas(key, cas);
}
});
connect(m_handler.get(), &EventHandler::sigUserUnpublishStream, this, [this](std::string uid, bytertc::MediaStreamType type, bytertc::StreamRemoveReason reason) {
qDebug() << Q_FUNC_INFO << "onUserUnpublishStream,uid=" << QString::fromStdString(uid) << ",type=" << QString::number(type) <<",reason=" << QString::number(reason);
if (m_video) {
bytertc::RemoteStreamKey key;
key.room_id = m_roomid.c_str();
key.user_id = uid.c_str();
bytertc::VideoCanvas cas;
cas.view = nullptr;
m_video->setRemoteVideoCanvas(key, cas);
}
});
m_video = bytertc::createRTCVideo(m_appid.c_str(), m_handler.get(), nullptr);
if (m_video == nullptr) return;
//开启音视频采集
m_video->startAudioCapture();
m_video->startVideoCapture();
//设置本地预览窗口
bytertc::VideoCanvas cas;
cas.background_color = 0;
cas.render_mode = bytertc::RenderMode::kRenderModeFit;
cas.view = (void*)widget_local->winId();
m_video->setLocalVideoCanvas(bytertc::kStreamIndexMain, cas);
//进房
m_room = m_video->createRTCRoom(m_roomid.c_str());
if (m_room == nullptr) return;
bytertc::UserInfo uinfo;
bytertc::RTCRoomConfig conf;
uinfo.extra_info = nullptr;
uinfo.uid = m_uid.c_str();
conf.is_auto_publish = true;
conf.is_auto_subscribe_audio = true;
conf.is_auto_subscribe_video = true;
m_room->joinRoom(m_token.c_str(), uinfo, conf);
m_room->setRTCRoomEventHandler(m_handler.get());
}
RTCTest::~RTCTest()
{
if (m_room) {
m_room->leaveRoom();
m_room->destroy();
m_room = nullptr;
}
if (m_video) {
bytertc::destroyRTCVideo();
m_video = nullptr;
}
}
cpp在 RTCTest.h 中引入以下头文件。
#include "bytertc_video.h"
#include "bytertc_room.h"
#include "bytertc_room_event_handler.h"
#include "bytertc_video_event_handler.h"
#include <QHBoxLayout>
#include <QMessageBox>
#include <QDebug>
#include <QMetaType>
#include <memory>
cppEventHandler 类定义在 RTCTest.h 中。EventHandler 用于接收 bytertc::IRTCVideoEventHandler 及 bytertc::IRTCRoomEventHandler 的回调事件,其中 onRoomStateChanged 表示本端进房状态回调,onUserPublishStream 表示同房间内远端用户发流回调,onUserUnpublishStream 表示同房间内远端用户停止发流回调。
//EventHandler 类继承了 QObject,用于发送信号
//EventHandler 类继承了 bytertc::IRTCRoomEventHandler 用于接收房间内通知消息
//声明信号槽中要用到的数据类型
Q_DECLARE_METATYPE(std::string)
Q_DECLARE_METATYPE(bytertc::MediaStreamType)
Q_DECLARE_METATYPE(bytertc::StreamRemoveReason)
class EventHandler : public QObject, public bytertc::IRTCVideoEventHandler, public bytertc::IRTCRoomEventHandler {
Q_OBJECT
public:
void onRoomStateChanged(
const char* room_id, const char* uid, int state, const char* extra_info) override {
if (room_id != nullptr && uid != nullptr) {
std::string str_extra_info = extra_info ? extra_info : "";
emit sigRoomStateChanged(std::string(room_id), std::string(uid), state, str_extra_info);
}
}
void onUserPublishStream(const char* uid, bytertc::MediaStreamType type) override {
if (uid != nullptr) {
emit sigUserPublishStream(std::string(uid), type);
}
}
void onUserUnpublishStream(const char* uid, bytertc::MediaStreamType type, bytertc::StreamRemoveReason reason) override {
if (uid != nullptr) {
emit sigUserUnpublishStream(std::string(uid), type, reason);
}
}
signals:
void sigRoomStateChanged(std::string roomid, std::string uid, int state, std::string extra_info);
void sigUserPublishStream(std::string uid, bytertc::MediaStreamType type);
void sigUserUnpublishStream(std::string uid, bytertc::MediaStreamType type, bytertc::StreamRemoveReason reason);
};
cpp将以下成员变量定义在 RTCTest.h class RTCTest 内。
private:
QWidget* widget_local = nullptr;
QWidget* widget_remote = nullptr;
bytertc::IRTCVideo* m_video = nullptr;
bytertc::IRTCRoom* m_room = nullptr;
std::string m_roomid = ""; //房间id,请在此处填写自己的房间id
std::string m_appid = ""; //appid, 请在此处填写应用的appid
std::string m_token = ""; //token, 请将控制台生成的token填写在此处,要求与上面的roomid、uid对应
std::shared_ptr<EventHandler> m_handler;
cpp将以下数据类型的定义放在 RTCTest.cpp RTCTest 构造函数 RTCTest::RTCTest 中。
qRegisterMetaType<std::string>();
qRegisterMetaType<bytertc::MediaStreamType>("bytertc::MediaStreamType");
qRegisterMetaType<bytertc::StreamRemoveReason>("bytertc::StreamRemoveReason");
//初始化UI样式
this->resize(500, 500);
QWidget* centralWidget = new QWidget(this);
QHBoxLayout* lay = new QHBoxLayout(centralWidget);
widget_local = new QWidget(this);
widget_remote = new QWidget(this);
lay->addWidget(widget_local);
lay->addWidget(widget_remote);
widget_local->setFixedSize(200, 200);
widget_remote->setFixedSize(200, 200);
widget_local->show();
widget_remote->show();
this->setCentralWidget(centralWidget);
cpp创建引擎放在 RTCTest.cpp RTCTest 构造函数中。bytertc::createRTCVideo 用于创建 RTC 引擎,所有 RTC 相关的 API 调用都要在创建引擎之后。
//开始 RTC 接口调用,检查参数是否为空
if (m_appid.empty() || m_token.empty() || m_uid.empty() || m_roomid.empty()) {
QMessageBox box(QMessageBox::Warning, QStringLiteral("提示"), QString("paras is empty"), QMessageBox::Ok);
box.exec();
return;
}
//创建 EventHandler 对象,用于接收房间、引擎通知
m_handler = std::make_shared<EventHandler>();
connect(m_handler.get(), &EventHandler::sigRoomStateChanged, this, [this](std::string roomid, std::string uid, int state, std::string extra_info) {
//房间状态变化
qDebug() << Q_FUNC_INFO << "roomid=" << QString::fromStdString(roomid) << ",uid=" << QString::fromStdString(uid) << ",state=" << QString::number(state);
});
connect(m_handler.get(), &EventHandler::sigUserPublishStream, this, [this](std::string uid, bytertc::MediaStreamType type) {
//远端用户发流
qDebug() << Q_FUNC_INFO << "onUserPublishStream,uid=" << QString::fromStdString(uid) << ",type=" << QString::number(type);
if (m_video) {
bytertc::VideoCanvas cas;
bytertc::RemoteStreamKey key;
key.room_id = m_roomid.c_str();
key.user_id = uid.c_str();
key.stream_index = bytertc::kStreamIndexMain;
cas.background_color = 0;
cas.render_mode = bytertc::RenderMode::kRenderModeFit;
cas.view = (void*)widget_remote->winId();
m_video->setRemoteVideoCanvas(key, cas);
}
});
connect(m_handler.get(), &EventHandler::sigUserUnpublishStream, this, [this](std::string uid, bytertc::MediaStreamType type, bytertc::StreamRemoveReason reason) {
//远端用户停止发流
qDebug() << Q_FUNC_INFO << "onUserUnpublishStream,uid=" << QString::fromStdString(uid) << ",type=" << QString::number(type) <<",reason=" << QString::number(reason);
if (m_video) {
bytertc::RemoteStreamKey key;
key.room_id = m_roomid.c_str();
key.user_id = uid.c_str();
bytertc::VideoCanvas cas;
cas.view = nullptr;
m_video->setRemoteVideoCanvas(key, cas);
}
});
m_video = bytertc::createRTCVideo(m_appid.c_str(), m_handler.get(), nullptr);
if (m_video == nullptr) return;
cpp创建引擎后,调用 startAudioCapture 开启音频采集,调用 startVideoCapture 开启视频采集。
//开启音视频采集
m_video->startAudioCapture();
m_video->startVideoCapture();
cpp调用 setLocalVideoCanvas 设置本端渲染窗口。
//设置本地预览窗口
bytertc::VideoCanvas cas;
cas.background_color = 0;
cas.render_mode = bytertc::RenderMode::kRenderModeFit;
cas.view = (void*)widget_local->winId();
m_video->setLocalVideoCanvas(bytertc::kStreamIndexMain, cas);
cpp调用 createRTCRoom 创建 RTC 房间,所有和房间相关的 API 都在 bytertc::IRTCRoom 内。
joinRoom 表示进房,进房状态可以通过 bytertc::IRTCRoomEventHandler 中 onRoomStateChanged 回调。
//进房
m_room = m_video->createRTCRoom(m_roomid.c_str());
if (m_room == nullptr) return;
bytertc::UserInfo uinfo;
bytertc::RTCRoomConfig conf;
uinfo.extra_info = nullptr;
uinfo.uid = m_uid.c_str();
conf.is_auto_publish = true;
conf.is_auto_subscribe_audio = true;
conf.is_auto_subscribe_video = true;
m_room->joinRoom(m_token.c_str(), uinfo, conf);
m_room->setRTCRoomEventHandler(m_handler.get());
cpp说明
以下代码在 sigUserPublishStream 的槽函数中,已在创建引擎步骤中实现,不必重复实现。
EventHandler 类继承了 bytertc::IRTCRoomEventHandler,设置 setRTCRoomEventHandler 接收回调通知,回调接口内发射 sigUserPublishStream 信号,connect 槽中调用 setRemoteVideoCanvas 接口,设置远端视频窗口渲染。
connect(m_handler.get(), &EventHandler::sigUserPublishStream, this, [this](std::string uid, bytertc::MediaStreamType type) {
//远端用户发流
qDebug() << Q_FUNC_INFO << "onUserPublishStream,uid=" << QString::fromStdString(uid) << ",type=" << QString::number(type);
if (m_video) {
bytertc::VideoCanvas cas;
bytertc::RemoteStreamKey key;
key.room_id = m_roomid.c_str();
key.user_id = uid.c_str();
key.stream_index = bytertc::kStreamIndexMain;
cas.background_color = 0;
cas.render_mode = bytertc::RenderMode::kRenderModeFit;
cas.view = (void*)widget_remote->winId();
m_video->setRemoteVideoCanvas(key, cas);
}
});
cpp说明
以下代码在 sigUserPublishStream 的槽函数中,已在创建引擎步骤中实现,不必重复实现。
实现原理同渲染远端视频流,当远端用户停止发流后,本端收到 onUserUnpublishStream 回调,通过 sigUserUnpublishStream 信号,在槽函数中设置远端渲染窗口为空。
connect(m_handler.get(), &EventHandler::sigUserUnpublishStream, this, [this](std::string uid, bytertc::MediaStreamType type, bytertc::StreamRemoveReason reason) {
//远端用户停止发流
qDebug() << Q_FUNC_INFO << "onUserUnpublishStream,uid=" << QString::fromStdString(uid) << ",type=" << QString::number(type) <<",reason=" << QString::number(reason);
if (m_video) {
bytertc::RemoteStreamKey key;
key.room_id = m_roomid.c_str();
key.user_id = uid.c_str();
bytertc::VideoCanvas cas;
cas.view = nullptr;
m_video->setRemoteVideoCanvas(key, cas);
}
});
cpp以下代码在 RTCTest 析构函数 RTCTest::~RTCTest 中。调用 leaveRoom 离开房间,destroy 销毁房间;调用 stopAudioCapture、stopVideoCapture 停止音视频采集;调用 destroyRTCVideo 销毁 RTC 引擎。
//销毁房间
if (m_room) {
m_room->leaveRoom();
m_room->destroy();
m_room = nullptr;
}
//销毁引擎
if (m_video) {
m_video->stopAudioCapture();
m_video->stopVideoCapture();
bytertc::destroyRTCVideo();
m_video = nullptr;
}
cpp至此,代码已经基本搭建完成。将 RTCTest.h 中的 m_roomid、m_uid、m_appid、m_token 替换为你在控制台上获取的 AppID、临时 Token,以及你在生成临时 Token 时使用的房间 ID 和用户 ID。
单击本地 Windows 调试器进行编译与调试。

如果你想体验双端通话,请使用另一台设备上加入同一个 RTC 房间。你需要使用相同的 AppID、相同的房间 ID、不同的用户 ID 生成新的临时 Token。双端通话效果如下:
