You need to enable JavaScript to run this app.
文档中心
实时音视频

实时音视频

复制全文
下载 pdf
实现音视频通话
macOS (C++)
复制全文
下载 pdf
macOS (C++)
本文介绍如何集成火山引擎 RTC SDK,并实现实时音视频通话。根据如下步骤操作,即可从 0 开始构建一个简单的音视频通话应用。​
你也可以参考示例项目,了解更完整的项目实现。​
前提条件​
在开始集成 RTC SDK 前,请确保满足以下要求:​
  • macOS 10.13 或以上版本设备,且可以正常访问互联网​
  • Xcode 13 或以上版本​
  • 麦克风、摄像头等音视频设备​
创建项目​
  1. 打开 Qt 安装路径下的 Qt Creator。​
  1. 单击文件 > New Project。​
  • ​图片​​
  1. 选择 Qt Widgets Application 模版,输入项目名称 RTCTest,选择 qmake 构建,类名无需修改,选择自己安装的构建套件,单击完成。​
引入 SDK​
根据你的设备情况下载 x86 或 ARM 架构版本的 RTC SDK,解压后将 RTC SDK 文件夹拷贝到工程目录中,与 RTCTest.pro 同级,并将其重命名为 VolcEngineRTC,完成后的项目目录结构如下:​
​
.​
├── RTCTest.pro​
├── VolcEngineRTC​
│ ├── ByteRTCFDK-AACExtension.framework​
│ ├── ByteRTCFFmpegAudioExtension.framework​
│ ├── ByteRTCNICOExtension.framework​
│ ├── ByteRTCVp8CodecExtension.framework​
│ ├── libbdaudioeffect.dylib​
│ ├── libbytenn.dylib​
│ ├── libbytevc0.dylib​
│ ├── libeffect.dylib​
│ ├── RTCFFmpeg.framework​
│ └── VolcEngineRTC.framework​
├── main.cpp​
├── mainwindow.cpp​
├── mainwindow.h​
├── Info.plist​
└── mainwindow.ui​
​
配置项目属性​
  1. 修改工程配置,打开 RTCTest.pro 并在文件中添加如下内容:​
  • ​
    QMAKE_INFO_PLIST = $$PWD/Info.plist​
    TARGET = RTCTest​
    TEMPLATE = app​
    DESTDIR = $$PWD/bin​
    ​
    INCLUDEPATH += $$PWD/VolcEngineRTC/VolcEngineRTC.framework/Headers/native​
    LIBS += $$PWD/VolcEngineRTC/VolcEngineRTC.framework/Versions/3/VolcEngineRTC​
    ​
    PATH=$$PWD//VolcEngineRTC/*​
    QMAKE_POST_LINK += $$[QT_INSTALL_BINS]/macdeployqt $$DESTDIR/RTCTest.app​
    QMAKE_POST_LINK += && cp -R $$system_path($$PATH) $$system_path($$DESTDIR/RTCTest.app/Contents/Frameworks)​
    ​
  1. 在 RTCTest.pro 同级目录下新建 Info.plist,并用 Xcode 打开,分别添加麦克风和摄像头权限。​
  • ​图片描述​​
  • ​
实现音视频通话​
说明​
本章节将先向你提供 API 调用时序图和完整的实现代码,再对具体的实现步骤展开介绍。​
时序图​
下图为使用火山引擎 RTC SDK 实现基础音视频通话的 API 调用时序图。​
​图片​​
完整代码示例​
将下面两段代码分别替换 mainwindow.h 和 mainwindow.cpp 文件中的全部内容,单击 Qt Creator 窗口左下角的运行按钮(或使用 Command ⌘ + R 快捷键),即可快速实现音视频通话。​
mainwindow.h 代码内容​
说明​
你需要将 mainwindow.h 中的 m_roomid、m_uid、m_appid、m_token 替换为你在控制台上生成临时 Token 时所使用的房间 ID 和用户 ID,以及获取到的 AppID 和临时 Token。​
​
// 以下为 mainwindow.h 的完整代码内容​
// 以下为 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 : "";​
emit sigRoomStateChanged(std::string(room_id), std::string(uid), state, str_extra_info);​
}​
}​
​
void onUserPublishStreamVideo(const char* room_id, const char* uid, bool is_publish) override{​
if (room_id != nullptr && uid != nullptr) {​
emit sigUserPublishStreamVideo(room_id, uid, is_publish);​
}​
​
}​
void onUserPublishStreamAudio(const char* room_id, const char* uid, bool is_publish) override{​
if (room_id != nullptr && uid != nullptr) {​
emit sigUserPublishStreamAudio(room_id, uid, is_publish);​
}​
}​
​
​
signals:​
void sigRoomStateChanged(std::string roomid, std::string uid, int state, std::string extra_info);​
void sigUserPublishStreamVideo(std::string roomid, std::string uid, bool publish);​
void sigUserPublishStreamAudio(std::string roomid, std::string uid, 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 = "MAC1"; //用户id,请在此处填写自己的用户id​
std::string m_appid = "123"; //appid, 请在此处填写应用的appid​
std::string m_token = ""; //token, 请将控制台生成的token填写在此处,要求与上面的roomid、uid对应​
​
std::shared_ptr<EventHandler> m_handler;​
};​
#endif // MAINWINDOW_H​
​
​
​
mainwindow.cpp 代码内容​
​
// 以下为 mainwindow.cpp 的完整代码内容​
​
#include "mainwindow.h"​
#include "ui_mainwindow.h"​
​
MainWindow::MainWindow(QWidget *parent)​
: QMainWindow(parent)​
, ui(new Ui::MainWindow)​
{​
ui->setupUi(this);​
​
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);​
​
//开始RTC接口调用,检查参数是否为空​
if (m_appid.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, [](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::sigUserPublishStreamVideo, this, [this](std::string streamId, bytertc::StreamInfo info, bool publish) {​
qDebug() << Q_FUNC_INFO << "onUserPublishStreamVideo"​
<< " streamId:" << QString::fromStdString(streamId)​
<< " info:" << (&info)​
<< " pub:" << publish​
<< " streamId:" << streamId;​
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(streamId.c_str(), cas);​
}​
} else {​
if (m_engine) {​
bytertc::VideoCanvas cas;​
cas.view = nullptr;​
m_engine->setRemoteVideoCanvas(streamId.c_str(), 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_auto_publish_audio = true; // 原为 is_publish_audio​
conf.is_auto_publish_video = true; // 原为 is_publish_video​
conf.is_auto_subscribe_audio = true;​
conf.is_auto_subscribe_video = true;​
​
int joinRoomResult = m_room->joinRoom(m_token.c_str(), uinfo, true, conf);​
QMessageBox box(QMessageBox::Warning, QStringLiteral("加入房间状态"), QString::number(joinRoomResult), QMessageBox::Ok);​
box.exec();​
​
m_room->setRTCRoomEventHandler(m_handler.get());​
}​
​
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_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>​
​
继承回调类​
EventHandler 类定义在 mainwindow.h 中。 EventHandler 用于接收 bytertc::IRTCEngineEventHandler 及 bytertc::IRTCRoomEventHandler 的回调事件,其中 onRoomStateChanged 表示本端进房状态回调,onUserPublishStreamVideo 表示同房间内远端是否发视频流回调,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 : "";​
emit sigRoomStateChanged(std::string(room_id), std::string(uid), state, str_extra_info);​
}​
}​
​
void onUserPublishStreamVideo(const char* streamId, const bytertc::StreamInfo& info, bool is_publish) override {​
if (streamId != nullptr) {​
emit sigUserPublishStreamVideo(streamId, info, is_publish);​
}​
​
}​
​
void onUserPublishStreamAudio(const char* streamId, const bytertc::StreamInfo& info, bool is_publish) override {​
if (streamId != nullptr) {​
emit sigUserPublishStreamAudio(streamId, info, is_publish);​
}​
}​
​
signals:​
void sigRoomStateChanged(std::string roomid, std::string uid, int state, std::string extra_info);​
void sigUserPublishStreamVideo(std::string streamId, bytertc::StreamInfo info, bool publish);​
void sigUserPublishStreamAudio(std::string streamId, 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 = "MAC1"; //用户id,请在此处填写自己的用户id​
std::string m_appid = "123"; //appid, 请在此处填写应用的appid​
std::string m_token = ""; //token, 请将控制台生成的token填写在此处,要求与上面的roomid、uid对应​
​
std::shared_ptr<EventHandler> m_handler;​
​
将以下数据类型的定义放在 mainwindow.cpp 的构造函数 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::MainWindow 构造函数中。createRTCEngine 用于创建 RTC 引擎,所有 RTC 相关的 API 调用都要在创建引擎之后。​
​
//开始RTC接口调用,检查参数是否为空​
if (m_appid.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, [](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);​
});​
​
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_auto_publish_audio = true;​
conf.is_auto_publish_video = true;​
conf.is_auto_subscribe_audio = true;​
conf.is_auto_subscribe_video = true;​
​
// 加入房间是否成功的提示消息,此部分为可选项目​
int joinRoomResult = m_room->joinRoom(m_token.c_str(), uinfo, true, conf);​
QMessageBox box(QMessageBox::Warning, QStringLiteral("加入房间状态"), QString::number(joinRoomResult), QMessageBox::Ok);​
box.exec();​
​
m_room->setRTCRoomEventHandler(m_handler.get());​
​
渲染远端视频流​
说明​
以下代码在 sigUserPublishStreamVideo 的槽函数中,已在创建引擎步骤中实现,不必重复实现。​
EventHandler 类继承了 bytertc::IRTCRoomEventHandler,设置 setRTCRoomEventHandler 接收回调通知,回调接口内发射 sigUserPublishStreamVideo 信号,connect 槽中调用 setRemoteVideoCanvas 接口,设置远端视频窗口渲染。​
​
connect(m_handler.get(), &EventHandler::sigUserPublishStreamVideo, this, [this](std::string streamId, bytertc::StreamInfo info, bool publish) {​
qDebug() << Q_FUNC_INFO << "onUserPublishStreamVideo"​
<< " streamId:" << QString::fromStdString(streamId)​
<< " info:" << (&info)​
<< " pub:" << publish​
<< " streamId:" << streamId;​
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(streamId.c_str(), cas);​
}​
} else {​
if (m_engine) {​
bytertc::VideoCanvas cas;​
cas.view = nullptr;​
m_engine->setRemoteVideoCanvas(streamId.c_str(), cas);​
}​
}​
});​
​
停止渲染远端视频流​
说明​
以下代码在 sigUserPublishStreamVideo 的槽函数中,已在创建引擎步骤中实现,不必重复实现。​
实现原理同渲染远端视频流,当远端用户停止发流后,本端收到 onUserPublishStreamAudio 回调,通过 sigUserUnpublishStream 信号,在槽函数中设置远端渲染窗口为空。​
停止音视频通话​
以下代码在 mainwindow.cpp 析构函数 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。​
编译与运行​
单击 Qt Creator 窗口左下角的运行按钮(或使用 Command ⌘ + R 快捷键)进行编译与调试。​
​图片​​
如果你想体验双端通话,请在另一台设备上加入同一个 RTC 房间。你需要使用相同的 AppID、相同的房间 ID、不同的用户 ID 生成新的临时 Token。双端通话效果如下:​
​图片​​
后续步骤​
在实现音视频通话后,如遇无声音、无画面、视频卡顿等问题时,您可以使用诊断工具快速排查和定位异常房间及用户,并获取异常根因分析、处理建议、分析报告等。​
常见问题​
​
最近更新时间:2026.09.21 16:42:01
这个页面对您有帮助吗?
有用
有用
无用
无用