You need to enable JavaScript to run this app.
导航

macOS (C++)

最近更新时间2023.12.24 10:56:56

首次发布时间2023.11.14 20:26:11

本文介绍如何集成火山引擎 RTC SDK,并实现实时音视频通话。根据如下步骤操作,即可从 0 开始构建一个简单的音视频通话应用。
你也可以参考示例项目,了解更完整的项目实现。

前提条件

在开始集成 RTC SDK 前,请确保满足以下要求:

创建项目
  1. 打开 Qt 安装路径下的 Qt Creator。

  2. 单击文件 > New Project

  3. 选择 Qt Widgets Application 模版,输入项目名称 RTCTest,选择 qmake 构建,类名无需修改,选择自己安装的构建套件,单击完成

引入 SDK

根据你的设备情况下载 x86 或 ARM 架构版本的 RTC SDK,解压后将 RTC SDK 文件夹拷贝到工程目录中,与 RTCTest.pro 同级,并将其重命名为 VolcEngineRTC,完成后的项目目录结构如下:

.
├── RTCTest.pro
├── RTCTest.pro.user
├── VolcEngineRTC
│   ├── ByteRTCFFmpegAudioExtension.framework
│   ├── ByteRTCNICOExtension.framework
│   ├── RTCFFmpeg.framework
│   ├── VolcEngineRTC.framework
│   ├── libbytenn.dylib
│   ├── libbytevc0.dylib
│   └── libeffect.dylib
├── main.cpp
├── mainwindow.cpp
├── mainwindow.h
└── 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)
    
  2. RTCTest.pro 同级目录下新建 Info.plist,并用 Xcode 打开,分别添加麦克风和摄像头权限。

实现音视频通话

说明

本章节将先向你提供 API 调用时序图和完整的实现代码,再对具体的实现步骤展开介绍。

时序图

下图为使用火山引擎 RTC SDK 实现基础音视频通话的 API 调用时序图。

alt

完整代码示例

将下面两段代码分别替换 mainwindow.hmainwindow.cpp 文件中的全部内容,单击 Qt Creator 窗口左下角的运行按钮(或使用 Command ⌘ + R 快捷键),即可快速实现音视频通话。

mainwindow.h 代码内容

说明

你需要将 mainwindow.h 中的 m_roomidm_uidm_appidm_token 替换为你在控制台上生成临时 Token 时所使用的房间 ID 和用户 ID,以及获取到的 AppID 和临时 Token。

// 以下为 mainwindow.h 的完整代码内容

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

#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>

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE


//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);
};

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::IRTCVideo* m_video = 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>();
    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);

//开始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, [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;
    //开启音视频采集
    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());
}

MainWindow::~MainWindow()
{
    //销毁房间
    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;
    }
    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 表示本端进房状态回调,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);
};

初始化界面及参数

将以下成员变量定义在 mainwindow.h class MainWindow 内。

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_uid = "";    //用户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 中。

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);

创建引擎

创建引擎放在 mainwindow.cpp MainWindow::MainWindow 构造函数中。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;

采集音视频

创建引擎后,调用 startAudioCapture 开启音频采集,调用 startVideoCapture 开启视频采集。

//开启音视频采集
   m_video->startAudioCapture();
   m_video->startVideoCapture();

渲染本端视频流

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

创建并加入房间

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

渲染远端视频流

说明

以下代码在 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);
    }
    });

停止渲染远端视频流

说明

以下代码在 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);
	}
	});

停止音视频通话

以下代码在 mainwindow.cpp 析构函数 MainWindow::~MainWindow 中。调用 leaveRoom 离开房间,destroy 销毁房间;调用 stopAudioCapturestopVideoCapture 停止音视频采集;调用 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;
}

检查进房参数

至此,代码已经基本搭建完成。将 mainwindow.h 中的 m_roomidm_uidm_appidm_token 替换为你在控制台上获取的 AppID、临时 Token,以及你在生成临时 Token 时使用的房间 ID 和用户 ID。

编译与运行

单击 Qt Creator 窗口左下角的运行按钮(或使用 Command ⌘ + R 快捷键)进行编译与调试。

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

常见问题