本文介绍如何集成火山引擎 RTC SDK,并实现实时音视频通话。根据如下步骤操作,即可从 0 开始构建一个简单的音视频通话应用。
你也可以参考示例项目,了解更完整的项目实现。
在开始集成 RTC SDK 前,请确保满足以下要求:
Windows 7 或以上版本设备,且可以正常访问互联网。
麦克风、摄像头等音视频设备。
火山引擎 RTC SDK 文件。
在火山引擎控制台上开通实时音视频服务,你需要从控制台获取 AppID 和临时 Token 用于项目跑通。
临时 Token 仅用于测试阶段,有使用时间限制,且安全性较低。若该项目准备正式上线,务必参考鉴权全流程生成 Token。
开发环境:RTC SDK 对开发环境没有特殊要求。本文以如下开发环境为例,说明构建音视频应用的基本流程。
Microsoft Visual Studio 2017 或以上版本
Qt 5.15 或以上版本,且 Qt 与 VS 版本对应
打开 Qt 安装路径下的 Qt Creator。
单击文件 > New Project。
选择 Qt Widgets Application 模版,输入项目名称 RTCTest,选择 qmake 构建,类名无需修改,选择自己安装的构建套件,单击完成。
#备注:使用文档中的图片
根据你的开发需求下载 Win32 或 x64 的 RTC SDK,解压后将 VolcEngineRTC 文件夹放在 RTCTest.pro 同级目录下,完成后的项目目录结构如下:
|-- RTCTest | |-- Debug | |-- mainwindow.aps | |-- mainwindow.cpp | |-- mainwindow.h | |-- mainwindow.qrc | |-- mainwindow.rc | |-- mainwindow.ui | |-- RTCTest.pro | |-- main.cpp | `-- resource.h `-- VolcEngineRTC #VolcEngineRTC 库放在 `RTCTest.pro` 同级目录下 |-- bin |-- include `-- lib
拷贝以下内容到 RTCTest.pro 中,用于配置项目的火山RTC库
msvc { INCLUDEPATH += $$PWD/VolcEngineRTC/include LIBS += $$PWD/VolcEngineRTC/lib/Win32/VolcEngineRTC.lib PATH=$$PWD/VolcEngineRTC/bin/Win32/*.dll QMAKE_POST_LINK += $$[QT_INSTALL_BINS]/windeployqt.exe $$DESTDIR/RTCTest.exe QMAKE_POST_LINK += && xcopy /I /Y /R $$system_path($$PATH) $$system_path($$DESTDIR/) message($$[QT_INSTALL_BINS]/windeployqt.exe) message($$PATH) message($$DESTDIR) }
说明
本章节将先向你提供 API 调用时序图和完整的实现代码,再对具体的实现步骤展开介绍。
下图为使用火山引擎 RTC SDK 实现基础音视频通话的 API 调用时序图。

将下面两段代码分别替换 mainwindow.h 和 mainwindow.cpp 文件中的全部内容,单击本地 Windows 调试器,即可快速实现音视频通话。
mainwindow.h 代码内容说明
你需要将 mainwindow.h 中的 m_roomid、m_uid、m_appid、m_token 替换为你在控制台上生成临时 Token 时所使用的房间 ID 和用户 ID,以及获取到的 AppID 和临时 Token。
// 以下为 mainwindow.h 的完整代码内容 #ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> #include "bytertc_engine.h" #include "bytertc_room.h" #include "bytertc_room_event_handler.h" #include "bytertc_engine_event_handler.h" #include <QHBoxLayout> #include <QMessageBox> #include <QDebug> #include <QMetaType> #include <memory> QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } QT_END_NAMESPACE //EventHandler 类继承了 QObject,用于发送信号 //EventHandler 类继承了 bytertc::IRTCRoomEventHandler 用于接收房间内通知消息 //声明信号槽中要用到的数据类型 Q_DECLARE_METATYPE(std::string) class EventHandler : public QObject, public bytertc::IRTCEngineEventHandler, 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 : ""; qDebug() << Q_FUNC_INFO << QString::fromStdString(room_id) << " xxxx :" << QString::fromStdString(uid) << ",state:" << state << ",info:" << QString::fromStdString(str_extra_info); emit sigRoomStateChanged(QString::fromStdString(room_id),QString::fromStdString(uid), state, QString::fromStdString(str_extra_info)); } } void onUserPublishStreamVideo(const char* streamId, const bytertc::StreamInfo info, bool is_publish) override { if (streamId != nullptr) { emit sigUserPublishStreamVideo(QString::fromStdString(streamId), info, is_publish); } } void onUserPublishStreamAudio(const char* streamId, const bytertc::StreamInfo info, bool is_publish) override { if (streamId != nullptr) { emit sigUserPublishStreamAudio(QString::fromStdString(streamId), info, is_publish); } } signals: void sigRoomStateChanged(QString roomid, QString uid, int state, QString extra_info); void sigUserPublishStreamVideo(QString roomid, bytertc::StreamInfo info, bool publish); void sigUserPublishStreamAudio(QString roomid, bytertc::StreamInfo info, bool publish); }; class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent = nullptr); ~MainWindow(); private: Ui::MainWindow *ui; private: QWidget* widget_local = nullptr; QWidget* widget_remote = nullptr; bytertc::IRTCEngine* m_engine = nullptr; bytertc::IRTCRoom* m_room = nullptr; std::string m_roomid = "123"; // 房间id,请在此处填写自己的房间id std::string m_uid = "123"; // 用户id,请在此处填写自己的用户id std::string m_appid = ""; // appid, 请在此处填写应用的appid std::string m_token = ""; // token, 请将控制台生成的token填写在此处,要求与上面的roomid、uid对应 std::shared_ptr<EventHandler> m_handler; }; #endif
mainwindow.cpp 代码内容// 以下为 mainwindow.cpp 的完整代码内容 #include "mainwindow.h" #include "ui_mainwindow.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ui->setupUi(this); qDebug() << Q_FUNC_INFO; qRegisterMetaType<std::string>(); //初始化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); // check paras if (m_appid.empty() || m_uid.empty() || m_roomid.empty()) { QMessageBox box(QMessageBox::Warning, QString("Warning"), QString("paras is empty"), QMessageBox::Ok); box.exec(); return; } //create EventHandler m_handler = std::make_shared<EventHandler>(); connect(m_handler.get(), &EventHandler::sigRoomStateChanged, this, [](QString roomid, QString uid, int state, QString extra_info) { qDebug() << Q_FUNC_INFO << "onRoomStateChanged roomid=" << (roomid) << ",uid=" << (uid) << ",state=" << QString::number(state); }); connect(m_handler.get(), &EventHandler::sigUserPublishStreamVideo, this, [this](QString streamId, bytertc::streamInfo info, bool publish) { qDebug() << Q_FUNC_INFO << "onUserPublishStreamVideo" << " streamId:" << (streamId) << " info:" << (&info) << " pub:" << publish; if (publish) { if (m_engine) { bytertc::VideoCanvas cas; cas.background_color = 0; cas.render_mode = bytertc::RenderMode::kRenderModeFit; cas.view = (void*)widget_remote->winId(); m_engine->setRemoteVideoCanvas(cas); } } else { if (m_engine) { bytertc::VideoCanvas cas; cas.view = nullptr; m_engine->setRemoteVideoCanvas(cas); } } }); bytertc::EngineConfig engConf; engConf.app_id = m_appid.c_str(); m_engine = bytertc::IRTCEngine::createRTCEngine(engConf, m_handler.get()); if (m_engine == nullptr) return; //开启音视频this m_engine->startAudioCapture(); m_engine->startVideoCapture(); //设置本地预览窗口 bytertc::VideoCanvas cas; cas.background_color = 0; cas.render_mode = bytertc::RenderMode::kRenderModeFit; cas.view = (void*)widget_local->winId(); m_engine->setLocalVideoCanvas(cas); //进房 m_room = m_engine->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_publish_audio = true; conf.is_publish_video = true; conf.is_auto_subscribe_audio = true; conf.is_auto_subscribe_video = true; m_room->setRTCRoomEventHandler(m_handler.get()); m_room->joinRoom(m_token.c_str(), uinfo, true, conf); } MainWindow::~MainWindow() { if (m_room) { m_room->leaveRoom(); m_room->destroy(); m_room = nullptr; } if (m_engine) { m_engine->stopAudioCapture(); m_engine->stopVideoCapture(); bytertc::IRTCEngine::destroyRTCEngine(); m_engine = nullptr; } delete ui; }
在 mainwindow.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>
EventHandler 类定义在 mainwindow.h 中。EventHandler 用于接收 bytertc::IRTCVideoEventHandler 及 bytertc::IRTCRoomEventHandler 的回调事件,其中 onRoomStateChanged 表示本端进房状态回调,onUserPublishStreamAudio 表示同房间内远端用户发流回调。
//EventHandler 类继承了 QObject,用于发送信号 //EventHandler 类继承了 bytertc::IRTCRoomEventHandler 用于接收房间内通知消息 //声明信号槽中要用到的数据类型 Q_DECLARE_METATYPE(std::string) class EventHandler : public QObject, public bytertc::IRTCEngineEventHandler, 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 : ""; qDebug() << Q_FUNC_INFO << QString::fromStdString(room_id) << " xxxx :" << QString::fromStdString(uid) << ",state:" << state << ",info:" << QString::fromStdString(str_extra_info); emit sigRoomStateChanged(QString::fromStdString(room_id),QString::fromStdString(uid), state, QString::fromStdString(str_extra_info)); } } void onUserPublishStreamVideo(const char* streamId, const bytertc::StreamInfo info, bool is_publish) override { if (streamId != nullptr) { emit sigUserPublishStreamVideo(QString::fromStdString(streamId), info, is_publish); } } void onUserPublishStreamAudio(const char* streamId, const bytertc::StreamInfo info, bool is_publish) override { if (streamId != nullptr) { emit sigUserPublishStreamAudio(QString::fromStdString(streamId), info, is_publish); } } signals: void sigRoomStateChanged(QString roomid, QString uid, int state, QString extra_info); void sigUserPublishStreamVideo(QString roomid, bytertc::StreamInfo info, bool publish); void sigUserPublishStreamAudio(QString roomid, bytertc::StreamInfo info, bool publish); };
将以下成员变量定义在 mainwindow.h 的 MainWindow 类内。
private: QWidget* widget_local = nullptr; QWidget* widget_remote = nullptr; bytertc::IRTCEngine* m_engine = nullptr; bytertc::IRTCRoom* m_room = nullptr; std::string m_roomid = "123"; // 房间id,请在此处填写自己的房间id std::string m_uid = "123"; // 用户id,请在此处填写自己的用户id std::string m_appid = ""; // appid, 请在此处填写应用的appid std::string m_token = ""; // token, 请将控制台生成的token填写在此处,要求与上面的roomid、uid对应 std::shared_ptr<EventHandler> m_handler;
将以下数据类型的定义放在 mainwindow.cpp 中 MainWindow 类的构造函数 MainWindow::MainWindow 中。
qRegisterMetaType<std::string>(); //初始化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);
创建引擎放在 mainwindow.cpp MainWindow 构造函数中。createRTCEngine 用于创建 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, [](QString roomid, QString uid, int state, QString extra_info) { qDebug() << Q_FUNC_INFO << "onRoomStateChanged roomid=" << (roomid) << ",uid=" << (uid) << ",state=" << QString::number(state); }); connect(m_handler.get(), &EventHandler::sigFirstRemoteVideoFrameDecoded, this, [this](QString streamId, bytertc::streamInfo stream_info, bytertc::VideoFrameInfo info) { qDebug() << Q_FUNC_INFO << "onFirstRemoteVideoFrameDecoded" << " streamId:" << (streamId) << " stream_info:" << (&stream_info) << " info:" << (&info); if (m_engine) { bytertc::VideoCanvas cas; cas.background_color = 0; cas.render_mode = bytertc::RenderMode::kRenderModeFit; cas.view = (void*)widget_remote->winId(); m_engine->setRemoteVideoCanvas(streamId, cas); } }); connect(m_handler.get(), &EventHandler::sigUserPublishStreamVideo, this, [this](QString streamId, bytertc::streamInfo info, bool publish) { qDebug() << Q_FUNC_INFO << "onUserPublishStreamVideo" << " streamId:" << (streamId) << " info:" << (&info) << " pub:" << publish; if (!publish) { if (m_engine) { bytertc::VideoCanvas cas; cas.view = nullptr; m_engine->setRemoteVideoCanvas(streamId, cas); } } }); bytertc::EngineConfig engConf; engConf.app_id = m_appid.c_str(); m_engine = bytertc::IRTCEngine::createRTCEngine(engConf, m_handler.get()); if (m_engine == nullptr) return;
创建引擎后,调用 startAudioCapture 开启音频采集,调用 startVideoCapture 开启视频采集。
//开启音视频采集 m_engine->startAudioCapture(); m_engine->startVideoCapture();
调用 setLocalVideoCanvas 设置本端渲染窗口。
//设置本地预览窗口 bytertc::VideoCanvas cas; cas.background_color = 0; cas.render_mode = bytertc::RenderMode::kRenderModeFit; cas.view = (void*)widget_local->winId(); m_engine->setLocalVideoCanvas(cas);
调用 createRTCRoom 创建 RTC 房间,所有和房间相关的 API 都在 bytertc::IRTCRoom 内。
joinRoom 表示进房,进房状态可以通过 bytertc::IRTCRoomEventHandler 中 onRoomStateChanged 回调。
//进房 m_room = m_engine->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_publish_audio = true; conf.is_publish_video = true; conf.is_auto_subscribe_audio = true; conf.is_auto_subscribe_video = true; m_room->setRTCRoomEventHandler(m_handler.get()); m_room->joinRoom(m_token.c_str(), uinfo, true, conf);
说明
以下代码在 sigFirstRemoteVideoFrameDecoded 的槽函数中,已在创建引擎步骤中实现,不必重复实现。
EventHandler 类继承了 bytertc::IRTCEngineEventHandler,接收引擎回调通知,回调接口内发射 sigFirstRemoteVideoFrameDecoded 信号,connect 槽中调用 setRemoteVideoCanvas 接口,设置远端视频窗口渲染。
connect(m_handler.get(), &EventHandler::sigFirstRemoteVideoFrameDecoded, this, [this](QString streamId, bytertc::streamInfo stream_info, bytertc::VideoFrameInfo info) { qDebug() << Q_FUNC_INFO << "onFirstRemoteVideoFrameDecoded" << " streamId:" << (streamId) << " stream_info:" << (&stream_info) << " info:" << (&info); if (m_engine) { bytertc::VideoCanvas cas; cas.background_color = 0; cas.render_mode = bytertc::RenderMode::kRenderModeFit; cas.view = (void*)widget_remote->winId(); m_engine->setRemoteVideoCanvas(streamId, cas); } });
说明
以下代码在 sigUserPublishStreamVideo 的槽函数中,已在创建引擎步骤中实现,不必重复实现。
实现原理同渲染远端视频流,当远端用户停止发流后,本端收到 onUserPublishStreamVideo 回调,通过 sigUserUnpublishStream 信号,在槽函数中设置远端渲染窗口为空。
connect(m_handler.get(), &EventHandler::sigUserPublishStreamVideo, this, [this](QString streamId, bytertc::streamInfo info, bool publish) { qDebug() << Q_FUNC_INFO << "onUserPublishStreamVideo" << " streamId:" << (streamId) << " info:" << (&info) << " pub:" << publish; if (!publish) { if (m_engine) { bytertc::VideoCanvas cas; cas.view = nullptr; m_engine->setRemoteVideoCanvas(cas); } } });
以下代码在 MainWindow 析构函数 MainWindow::~MainWindow 中。调用 leaveRoom 离开房间,destroy 销毁房间;调用 stopAudioCapture、stopVideoCapture 停止音视频采集;调用 destroyRTCEngine 销毁 RTC 引擎。
//销毁房间 if (m_room) { m_room->leaveRoom(); m_room->destroy(); m_room = nullptr; } if (m_engine) { m_engine->stopAudioCapture(); m_engine->stopVideoCapture(); bytertc::IRTCEngine::destroyRTCEngine(); m_engine = nullptr; }
至此,代码已经基本搭建完成。将 mainwindow.h 中的 m_roomid、m_uid、m_appid、m_token 替换为你在控制台上获取的 AppID、临时 Token,以及你在生成临时 Token 时使用的房间 ID 和用户 ID。
右击项目 构建&运行进行编译与调试。

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

在实现音视频通话后,如遇无声音、无画面、视频卡顿等问题时,您可以使用诊断工具快速排查和定位异常房间及用户,并获取异常根因分析、处理建议、分析报告等。