# 实现音视频互动 (/zh-CN/realtime-media/rtc/get-started/quick-start/windows)

> For AI agents: see the complete documentation index at [llms.txt](/llms.txt).

本文介绍如何集成声网实时互动 SDK，通过少量代码从 0 开始实现一个简单的实时互动 App，适用于互动直播和视频通话场景。
首先，你需要了解以下有关音视频实时互动的基础概念：

- 声网实时互动 SDK：由声网开发的、帮助开发者在 App 中实现实时音视频互动的 SDK。
- 频道：用于传输数据的通道，在同一个频道内的用户可以进行实时互动。
- 主播：可以在频道内**发布**音视频，同时也可以**订阅**其他主播发布的音视频。
- 观众：可以在频道内**订阅**音视频，不具备**发布**音视频权限。

更多概念详见[关键概念](/zh-CN/realtime-media/rtc/reference/key-concept)。

下图展示在 App 中实现音视频互动的基本工作流程：

<DocsImage alt="实现音视频互动" src="https://web-cdn.agora.io/docs-portal/img/rtc/quick-start-tech.png" displayWidth="60%" />

1. 所有用户调用 `joinChannel` 方法加入频道，并根据需要设置用户角色：

   - 互动直播：如果用户需要在频道中发流，则设为主播；如果用户只需要收流，则设为观众。
   - 视频通话：将所有用户的角色都设为主播。
2. 加入频道后，不同角色的用户具备不同的行为：

   - 所有用户默认都可以接收频道中的音视频流。
   - 主播可以在频道内发布音视频流。
   - 观众如果需要发流，可在频道内调用 `setClientRole` 方法修改用户角色，使其具备发流权限。

## 前提条件 [#前提条件-4]

在实现功能以前，请按照以下要求准备开发环境：

- Windows 7 或以上版本的设备。
- Microsoft Visual Studio 2017 或以上版本。
- C++ 11 或以上版本。
- 如果使用 C# 开发，还需要 [.NET 桌面开发组件](https://learn.microsoft.com/en-us/dotnet/framework/install/guide-for-developers)。
- 可以访问互联网的计算机。如果你的网络环境部署了防火墙，参考[应对防火墙限制](/zh-CN/realtime-media/rtc/build/setup-and-access/firewall)以正常使用声网服务。
- 一个有效的声网账号以及声网项目。请参考[开通 RTC 服务](/zh-CN/realtime-media/rtc/build/setup-and-access/enable-service)从声网控制台获得 App ID 和临时 Token。

:::warning[注意]
      临时 Token 的有效期是 24 小时。Token 过期会导致加入频道失败。
      :::

<Tabs defaultValue="windows" groupId="release" persist>
<TabsList>
  <TabsTrigger value="windows">C++</TabsTrigger>
  <TabsTrigger value="csharp">C#</TabsTrigger>
</TabsList>

<TabsContent value="windows">

## 创建项目 [#创建项目-3]

    以 Windows 11 为例，参考以下操作或[官方文档](https://learn.microsoft.com/en-us/visualstudio/ide/create-new-project?view=vs-2022)在 Visual Studio 2022 中创建一个项目来实现实时音视频互动功能，如果你已有自己的项目，可跳过这一步骤。

    1. 在 Visual Studio 中，选择**文件** > **新建** > **项目** 来创建一个新项目。在弹出的窗口中，选择 **MFC 应用**作为项目模板，点击**下一步**将项目名称设为 **AgoraQuickStart** 并设置项目储存位置，然后点击**创建**。

    2. 在弹出的 MFC 应用程序窗口中，将**应用程序类型**设为**基于对话框**，将**使用 MFC** 设为**在共享 DLL 中使用 MFC**。进入**生成的类**，将**生成的类**设为 **Dlg**，将**基类**设为 **CDialog**，最后点击**完成**。

    ## 集成 SDK [#集成-sdk-3]

    1. 从[下载](/zh-CN/realtime-media/rtc/reference/downloads)获取最新的 Windows SDK，解压并打开。
    2. 打开已下载的 SDK 文件，并将其中的 `sdk` 文件夹复制到你的项目路径下。保证 `sdk` 文件夹和你的 `sln` 文件处于同一目录。

    :::warning[注意]
    自 4.5.0 起，RTC SDK 和 RTM SDK (2.2.0 及以上版本) 都包含 `libaosl.dll` 库。如果你通过 CDN 手动集成 RTC SDK 且同时集成了 RTM SDK，为避免冲突，请手动删除版本较低的 `libaosl.dll` 库。4.6.0 RTC SDK `libaosl.dll` 库版本为 1.3.0。你可以通过查看 `libaosl.dll` 文件的属性来得知库的版本信息。
    :::

    ## 配置项目属性 [#配置项目属性]

    在**解决方案资源管理器**窗口中，右击项目名称并点击**属性**进行以下配置：

    1. 进入 **C/C++ > 常规 > 附加包含目录**菜单，输入 `$(SolutionDir)sdk\high_level_api\include`。
    2. 进入**链接器 > 常规 > 附加库目录**菜单，输入 `$(SolutionDir)sdk\x86_64`。

       :::info[信息]
       如果你是 x86 的 Windows 操作系统，请输入 `$(SolutionDir)sdk\x86`。
       :::

    3. 进入**链接器 > 输入 > 附加依赖项**菜单，输入 `$(SolutionDir)sdk\x86_64\agora_rtc_sdk.dll.lib`。

       :::info[信息]
       如果你是 x86 的 Windows 操作系统，请输入 `$(SolutionDir)sdk\x86\agora_rtc_sdk.dll.lib`。
       :::

    4. 进入**高级**菜单，在**高级属性**中，把**将内容复制到 OutDir** 和**将 C++ 运行时复制到输出目录**设为**是**。
    5. 进入**生成事件 > 生成后事件 >命令行** 菜单，输入 `copy $(SolutionDir)sdk\x86_64\*.dll $(SolutionDir)$(Platform)\$(Configuration)`。
    6. 完成上述配置后点击**应用**。

    ## 创建用户界面 [#创建用户界面-3]

    根据实时音视频互动的场景需要，你需要在你的应用中添加如下 UI：

    - 展示本地视频的视图框
    - 展示远端视频的视图框
    - 输入频道名称的输入框
    - 加入和离开频道按钮

    你可以参考下列步骤来创建用户界面。

<Accordions type="multiple">
<Accordion title="创建用户界面步骤">

1. 在右侧菜单栏将项目切换为资源视图，然后打开 `.Dialog` 文件，此时你的界面如下图所示：

   <DocsImage alt="ui" src="https://web-cdn.agora.io/docs-portal/img/rtc/windows-quickstart-ui1.png" displayWidth="70%" />

2. 添加展示远端视频的视图框。在**视图 > 工具箱**中，选择添加 **Picture Control** 控件，在**属性 > 杂项**中，将该控件的 ID 设为 **IDC_STATIC_REMOTE**，如下图所示：

   <DocsImage alt="remote users" src="https://web-cdn.agora.io/docs-portal/img/rtc/windows-quickstart-remote.png" displayWidth="70%" />

3. 添加展示本地视频的视图框。在**视图 > 工具箱**中，选择添加 **Picture Control** 控件，在**属性 > 杂项**中，将该控件的 ID 设为 **IDC_STATIC_LOCAL**。
4. 添加输入频道名称的输入框。在**视图 > 工具箱**中，选择添加 **Static Text** 控件，在**属性**中将**描述文字**改为**频道名**。然后再添加一个 **Edit Control** 控件作为输入框，在**属性 > 杂项**中，将该控件的 ID 设为 **IDC_EDIT_CHANNEL**。
5. 添加加入和离开频道按钮。在**视图 > 工具箱**中，选择添加两个 **Button** 控件，在**属性 > 杂项**中，将控件的 ID 分设为 **ID_BTN_JOIN** 和 **ID_BTN_LEAVE**，将**描述文字**分别设为**加入**、**离开**。
   此时的用户界面如下图所示：

   <DocsImage alt="ui" src="https://web-cdn.agora.io/docs-portal/img/rtc/windows-quickstart-ui2.png" displayWidth="70%" />

</Accordion>
</Accordions>


    ## 实现步骤 [#实现步骤-4]

    本小节介绍如何实现一个实时音视频互动应用。你可以先复制完整的示例代码到你的项目中，快速体验实时音视频互动的基础功能，再按照实现步骤了解核心 API 调用。

    下图展示了使用声网 RTC SDK 实现音视频互动的基本流程。

<DocsImage alt="实现流程" src="https://web-cdn.agora.io/docs-portal/img/rtc/windows-quick-start-sequence.svg" displayWidth="70%" />

    以下展示实现实时互动基本流程的完整示例代码：
    :::warning[注意]
    复制完整示例代码到你的项目之后，你需要将代码中的 `APP_ID` 和 `token` 替换成你在声网控制台获取的 App ID 和临时 Token。
    :::
<Accordions>
<Accordion title="AgoraQuickStartDlg.h">

```cpp
#pragma once
#include <string>
// 引入相关头文件
#include <IAgoraRtcEngine.h>

using namespace agora;
using namespace agora::rtc;
using namespace agora::media;
using namespace agora::media::base;

// 定义消息 ID
#define WM_MSGID(code) (WM_USER+0x200+code)
#define EID_JOIN_CHANNEL_SUCCESS	0x00000002
#define EID_USER_JOINED	0x00000004
#define EID_USER_OFFLINE	0x00000004

// 定义 CAgoraQuickStartRtcEngineEventHandler 类，用于处理用户加入、离开频道等回调事件
class CAgoraQuickStartRtcEngineEventHandler : public IRtcEngineEventHandler {
public:
    // 设置消息接收窗口的句柄
    void SetMsgReceiver(HWND hWnd) {
        m_hMsgHanlder = hWnd;
    }

    // 注册 onJoinChannelSuccess 回调，本地用户成功加入频道时，会触发该回调
    virtual void onJoinChannelSuccess(const char* channel, uid_t uid, int elapsed) {
        if (m_hMsgHanlder) {
            ::PostMessage(m_hMsgHanlder, WM_MSGID(EID_JOIN_CHANNEL_SUCCESS), uid, 0);
        }
    }
    // 注册 onUserJoined 回调，远端主播成功加入频道时，会触发该回调
    virtual void onUserJoined(uid_t uid, int elapsed) {
        if (m_hMsgHanlder) {
            ::PostMessage(m_hMsgHanlder, WM_MSGID(EID_USER_JOINED), uid, 0);
        }
    }
    // 注册 onUserOffline 回调，远端主播离开频道或掉线时，会触发该回调
    virtual void onUserOffline(uid_t uid, USER_OFFLINE_REASON_TYPE reason) {
        if (m_hMsgHanlder) {
            ::PostMessage(m_hMsgHanlder, WM_MSGID(EID_USER_OFFLINE), uid, 0);
        }
    }

private:
    HWND m_hMsgHanlder;
};
// CAgoraQuickStartDlg 对话框
class CAgoraQuickStartDlg : public CDialog {
public:
    CAgoraQuickStartDlg(CWnd* pParent = nullptr);
    virtual ~CAgoraQuickStartDlg();

#ifdef AFX_DESIGN_TIME
    enum { IDD = IDD_AGORAQUICKSTART_DIALOG };
#endif
    // 处理加入/离开按钮点击事件
    afx_msg void OnBnClickedBtnJoin();
    afx_msg void OnBnClickedBtnLeave();
    // 处理用户加入频道、用户离开等回调事件
    afx_msg LRESULT OnEIDJoinChannelSuccess(WPARAM wParam, LPARAM lParam);
    afx_msg LRESULT OnEIDUserJoined(WPARAM wParam, LPARAM lParam);
    afx_msg LRESULT OnEIDUserOffline(WPARAM wParam, LPARAM lParam);

protected:
    HICON m_hIcon;
    CStatic m_staRemote;
    CStatic m_staLocal;
    CEdit m_edtChannelName;

    // DDX/DDV 支持
    virtual void DoDataExchange(CDataExchange* pDX);
    virtual BOOL OnInitDialog();
    afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
    afx_msg void OnPaint();
    afx_msg HCURSOR OnQueryDragIcon();
    DECLARE_MESSAGE_MAP()

    std::string cs2utf8(CString str);

private:
    IRtcEngine* m_rtcEngine = nullptr;
    CAgoraQuickStartRtcEngineEventHandler m_eventHandler;
bool m_initialize = false;
bool m_remoteRender = false;
};
```

</Accordion>

<Accordion title="AgoraQuickStartDlg.cpp">

```cpp
// AgoraQuickStartDlg.cpp: 实现文件
//
#include "pch.h"
#include "framework.h"
#include "AgoraQuickStart.h"
#include "AgoraQuickStartDlg.h"
#include "afxdialogex.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
// 用于应用程序“关于”菜单项的 CAboutDlg 对话框
class CAboutDlg : public CDialogEx {
public:
    CAboutDlg();
    // 对话框数据
#ifdef AFX_DESIGN_TIME
    enum {
        IDD = IDD_ABOUTBOX
    }
    ;
#endif
protected:
    virtual void DoDataExchange(CDataExchange* pDX);
    // DDX/DDV 支持
    // 实现
protected:
    DECLARE_MESSAGE_MAP()
}
;
CAboutDlg::CAboutDlg() : CDialogEx(IDD_ABOUTBOX) {
}
void CAboutDlg::DoDataExchange(CDataExchange* pDX) {
    CDialogEx::DoDataExchange(pDX);
}
BEGIN_MESSAGE_MAP(CAboutDlg, CDialogEx)
END_MESSAGE_MAP()
// CAgoraQuickStartDlg 对话框，用于处理主要的用户交互和回调事件
CAgoraQuickStartDlg::CAgoraQuickStartDlg(CWnd* pParent
        /*=nullptr*/
                                        )
    : CDialog(IDD_AGORAQUICKSTART_DIALOG, pParent) {
    m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
CAgoraQuickStartDlg::~CAgoraQuickStartDlg() {
    CDialog::~CDialog();
    // 在删除 CAgoraQuickStartDlg 对象时，释放引擎和相关资源
    if(m_rtcEngine) {
        m_rtcEngine->release(true);
        m_rtcEngine = NULL;
    }
}
void CAgoraQuickStartDlg::DoDataExchange(CDataExchange* pDX) {
    CDialog::DoDataExchange(pDX);
    // 将控件和变量关联起来，以便于读写控件的数据
    DDX_Control(pDX, IDC_EDIT_CHANNEL, m_edtChannelName);
    DDX_Control(pDX, IDC_STATIC_REMOTE, m_staRemote);
    DDX_Control(pDX, IDC_STATIC_LOCAL, m_staLocal);
}
BEGIN_MESSAGE_MAP(CAgoraQuickStartDlg, CDialog)
    // 声明消息映射，用于处理 Windows 消息和用户加入、离开频道等事件
    ON_WM_SYSCOMMAND()
    ON_WM_PAINT()
    ON_WM_QUERYDRAGICON()
    ON_BN_CLICKED(ID_BTN_JOIN, &CAgoraQuickStartDlg::OnBnClickedBtnJoin)
    ON_BN_CLICKED(ID_BTN_LEAVE, &CAgoraQuickStartDlg::OnBnClickedBtnLeave)
    ON_MESSAGE(WM_MSGID(EID_JOIN_CHANNEL_SUCCESS), CAgoraQuickStartDlg::OnEIDJoinChannelSuccess)
    ON_MESSAGE(WM_MSGID(EID_USER_JOINED), &CAgoraQuickStartDlg::OnEIDUserJoined)
    ON_MESSAGE(WM_MSGID(EID_USER_OFFLINE), &CAgoraQuickStartDlg::OnEIDUserOffline)
END_MESSAGE_MAP()
// CAgoraQuickStartDlg 消息处理程序
// 填入你项目的 App ID，在声网控制台获取
#define "<APP_ID>"
// 填入你在声网控制台获取的临时 Token
#define "<token>"
BOOL CAgoraQuickStartDlg::OnInitDialog() {
    CDialog::OnInitDialog();
    // 将“关于...”菜单项添加到系统菜单中
    // IDM_ABOUTBOX 必须在系统命令范围内
    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);
    // 设置小图
    m_eventHandler.SetMsgReceiver(m_hWnd);
    // 创建 rtcEngine 对象
    m_rtcEngine = createAgoraRtcEngine();
    RtcEngineContext context;
    context.appId = APP_ID;
    context.eventHandler = &m_eventHandler;
    //初始化
    int ret = m_rtcEngine->initialize(context);
    if (ret == 0) {
        m_initialize = true;
    } else {
        m_initialize = false;
    }
    // 启用视频模块
    m_rtcEngine->enableVideo();
    return TRUE;
    // 除非将焦点设置到控件，否则返回 TRUE
}
void CAgoraQuickStartDlg::OnSysCommand(UINT nID, LPARAM lParam) {
    if ((nID & 0xFFF0) == IDM_ABOUTBOX) {
        CAboutDlg dlgAbout;
        dlgAbout.DoModal();
    } else {
        CDialog::OnSysCommand(nID, lParam);
    }
}
// 如果向对话框添加最小化按钮，则需要下面的代码
// 来绘制该图标。对于使用文档/视图模型的 MFC 应用程序，这将由框架自动完成
void CAgoraQuickStartDlg::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 {
        CDialog::OnPaint();
    }
}
//当用户拖动最小化窗口时系统调用此函数取得光标
HCURSOR CAgoraQuickStartDlg::OnQueryDragIcon() {
    return static_cast<HCURSOR>(m_hIcon);
}
std::string CAgoraQuickStartDlg::cs2utf8(CString str) {
    char szBuf[2 * MAX_PATH] = {
        0
    }
    ;
    WideCharToMultiByte(CP_UTF8, 0, str.GetBuffer(0), str.GetLength(), szBuf, 2 *MAX_PATH, NULL, NULL);
    return szBuf;
}
void CAgoraQuickStartDlg::OnBnClickedBtnJoin() {
    // 加入频道
    // 获取频道名
    CString strChannelName;
    m_edtChannelName.GetWindowText(strChannelName);
    if (strChannelName.IsEmpty()) {
        AfxMessageBox(_T("Fill channel name first"));
        return;
    }
    ChannelMediaOptions option;
    // 设置频道场景为直播
    options.channelProfile = CHANNEL_PROFILE_LIVE_BROADCASTING;
    // 设置用户角色为主播
    options.clientRoleType = CLIENT_ROLE_BROADCASTER;
    // 发布麦克风采集的音频流
    options.publishMicrophoneTrack = true;
    // 发布摄像头采集的视频流
    options.publishCameraTrack = true;
    // 自动订阅频道内的音频流
    options.autoSubscribeAudio = true;
    // 自动订阅频道内的视频流
    options.autoSubscribeVideo = true;
    // 填入你在控制台获取的临时 Token 加入频道
    int ret = m_rtcEngine->joinChannel(token, cs2utf8(strChannelName).c_str(), 0,option);
    // 渲染本地视图
    VideoCanvas canvas;
    canvas.renderMode = RENDER_MODE_TYPE::RENDER_MODE_HIDDEN;
    canvas.uid = 0;
    canvas.view = m_staLocal.GetSafeHwnd();
    m_rtcEngine->setupLocalVideo(canvas);
    // 开启本地视频预览
    m_rtcEngine->startPreview();
}
void CAgoraQuickStartDlg::OnBnClickedBtnLeave() {
    // 停止本地视频预览
    m_rtcEngine->stopPreview();
    // 离开频道
    m_rtcEngine->leaveChannel();
    // 清除本地视图
    VideoCanvas canvas;
    canvas.uid = 0;
    m_rtcEngine->setupLocalVideo(canvas);
    m_remoteRender = false;
}
LRESULT CAgoraQuickStartDlg::OnEIDJoinChannelSuccess(WPARAM wParam, LPARAM lParam) {
    // 加入频道成功回调
    uid_t localUid = wParam;
    return 0;
}
LRESULT CAgoraQuickStartDlg::OnEIDUserJoined(WPARAM wParam, LPARAM lParam) {
    // 远端用户加入回调
    uid_t remoteUid = wParam;
    if (m_remoteRender) {
        return 0;
    }
    // 渲染远端视图
    VideoCanvas canvas;
    canvas.renderMode = RENDER_MODE_TYPE::RENDER_MODE_HIDDEN;
    canvas.uid = remoteUid;
    canvas.view = m_staRemote.GetSafeHwnd();
    m_rtcEngine->setupRemoteVideo(canvas);
    m_remoteRender = true;
    return 0;
}
LRESULT CAgoraQuickStartDlg::OnEIDUserOffline(WPARAM wParam, LPARAM lParam) {
    // 远端用户离开回调
    uid_t remoteUid = wParam;
    if (!m_remoteRender) {
        return 0;
    }
    // 清除远端视图
    VideoCanvas canvas;
    canvas.uid = remoteUid;
    m_rtcEngine->setupRemoteVideo(canvas);
    m_remoteRender = false;
    return 0;
}
```

</Accordion>
</Accordions>

    参考下列步骤来在你的应用中实现实时互动：

    ### 初始化引擎 [#初始化引擎-3]

    在调用其他声网 API 前，你需要调用 `createAgoraRtcEngine` 创建一个`IRtcEngine` 对象，然后调用 `initialize` 并传入 App ID，初始化 `IRtcEngine`。

    :::warning[注意]
    在初始化 SDK 前，需确保终端用户已经充分了解并同意相关的隐私政策。
    :::

    ```cpp
    // 创建 IRtcEngine 对象
    m_rtcEngine = createAgoraRtcEngine();

    // 创建 IRtcEngine 上下文对象
    RtcEngineContext context;

    // 输入你的 App ID。你可以在声网控制台获取你的项目的 App ID
    context.appId = APP_ID;

    // 添加注册回调和事件
    context.eventHandler = &m_eventHandler;

    // 初始化
    int ret = m_rtcEngine->initialize(context);
    if (ret == 0) {
        m_initialize = true;
    } else {
        m_initialize = false;
    }
    ```

    ### 启用视频模块 [#启用视频模块-3]

    1. 调用 `enableVideo` 方法，启用视频模块。
    2. 调用 `setupLocalVideo` 初始化本地视图，同时设置本地的视频显示属性。
    3. 调用 `startPreview` 方法，开启本地视频预览。

    ```cpp
    // 启用视频模块
    m_rtcEngine->enableVideo();
    ...

    // 设置本地视频显示属性
	VideoCanvas canvas;
    // 设置视频尺寸等比缩放
	canvas.renderMode = RENDER_MODE_TYPE::RENDER_MODE_HIDDEN;
    // 用户 ID
	canvas.uid = 0;
    // 视频显示窗口
	canvas.view = m_staLocal.GetSafeHwnd();
	m_rtcEngine->setupLocalVideo(canvas);
    // 开启本地视频预览
    m_rtcEngine->startPreview();
    ```

    ### 加入频道并发布音视频流 [#加入频道并发布音视频流-3]

    调用 `joinChannel`[2/2] 方法、填入你在控制台获取的临时 Token，以及获取 Token 时填入的频道名加入频道，并设置用户角色。

    ```cpp
    void CAgoraQuickStartDlg::OnBnClickedBtnJoin() {
        CString strChannelName;
        // 获取频道名
        m_edtChannelName.GetWindowText(strChannelName);
        if (strChannelName.IsEmpty()) {
            AfxMessageBox(_T("Fill channel name first"));
            return;
        }

        ChannelMediaOptions option;
        // 设置频道场景为直播场景
        options.channelProfile = CHANNEL_PROFILE_LIVE_BROADCASTING;
        // 设置用户角色为主播；如果要将用户角色设置为观众，保持默认值即可
        options.clientRoleType = CLIENT_ROLE_BROADCASTER;
        // 发布麦克风采集的音频流
        options.publishMicrophoneTrack = true;
        // 发布摄像头采集的视频流
        options.publishCameraTrack = true;
        // 自动订阅所有音频流
        options.autoSubscribeAudio = true;
        // 自动订阅所有视频流
        options.autoSubscribeVideo = true;
        // 填入你在控制台获取的临时 Token 加入频道
        m_rtcEngine->joinChannel(token, cs2utf8(strChannelName).c_str(), 0, option);
    }
    ```

    ### 实现常用回调 [#实现常用回调-3]

   你可以根据需求，在初始化时实现其他功能，如注册用户加入、离开频道等回调。

    ```cpp
    class CAgoraQuickStartRtcEngineEventHandler : public IRtcEngineEventHandler {
    public:
        // 注册 onJoinChannelSuccess 回调
        // 本地用户成功加入频道时，会触发该回调
        virtual void onJoinChannelSuccess(const char* channel, uid_t uid, int elapsed) {
            if (m_hMsgHanlder) {
                ::PostMessage(m_hMsgHanlder, WM_MSGID(EID_JOIN_CHANNEL_SUCCESS), uid, 0);
            }
        }

        // 注册 onUserJoined 回调
        // 远端主播成功加入频道时，会触发该回调
        virtual void onUserJoined(uid_t uid, int elapsed) {
            if (m_hMsgHanlder) {
                ::PostMessage(m_hMsgHanlder, WM_MSGID(EID_USER_JOINED), uid, 0);
            }
        }

        // 注册 onUserOffline 回调
        // 远端主播离开频道或掉线时，会触发该回调
        virtual void onUserOffline(uid_t uid, USER_OFFLINE_REASON_TYPE reason) {
            if (m_hMsgHanlder) {
                ::PostMessage(m_hMsgHanlder, WM_MSGID(EID_USER_OFFLINE), uid, 0);
            }
        }

    private:
        HWND m_hMsgHanlder;
    };
    ```

    ### 设置远端视图 [#设置远端视图-1]

    当远端用户加入频道时，从远端用户加入频道的回调中获取到的远端用户的 uid，然后调用 `setupRemoteVideo` 设置并渲染远端视图。

    ```cpp
    LRESULT CAgoraQuickStartDlg::OnEIDUserJoined(WPARAM wParam, LPARAM lParam) {
        // 远端用户加入回调
        uid_t remoteUid = wParam;
        if (m_remoteRender) {
            return;
        }
        // 设置远端视频显示属性
        VideoCanvas canvas;
        // 设置视频尺寸等比缩放
        canvas.renderMode = RENDER_MODE_TYPE::RENDER_MODE_HIDDEN;
        // 远端用户 ID
        canvas.uid = remoteUid;
        canvas.view = m_staRemote.GetSafeHwnd();
        m_rtcEngine->setupRemoteVideo(canvas);
        m_remoteRender = true;
    }
    ```

    ### 离开频道 [#离开频道]

    根据场景需要，如关闭应用或应用切换至后台时，调用 `leaveChannel` 离开当前频道。

    ```cpp
    void CAgoraQuickStartDlg::OnBnClickedBtnLeave() {
        // 停止本地视频预览
        m_rtcEngine->stopPreview();
        // 离开频道
        m_rtcEngine->leaveChannel();

        // 清除本地视图
        VideoCanvas canvas;
        canvas.uid = 0;
        m_rtcEngine->setupLocalVideo(canvas);

        m_remoteRender = false;
    }
    ```

    如果你不再需要互动，调用 `release` 方法释放引擎资源。

    :::warning[注意]
    - 调用 `release` 后，你将无法再使用 SDK 的所有方法和回调。如需再次使用实时音视频互动功能，你必须重新创建一个新的引擎。详见[初始化引擎](#初始化引擎-3)。
        该方法为同步调用。需要等待引擎资源释放后才能执行其他操作，因此建议在子线程中调用该方法，避免主线程阻塞。
    :::

    ```cpp
    CDialog::~CDialog() {
        // 在对象被销毁时释放资源
        if(m_rtcEngine){
           m_rtcEngine->release(true);
           m_rtcEngine = NULL;
        }
    }
    ```

    ## 调试应用 [#调试应用]

    按照以下步骤来测试你的应用：

    1. 在 Visual Studio 中，选择**本地 Windows 调试器**开始编译应用。
    2. 在输入框中输入你要加入的频道名，并点击**加入**按钮加入频道，你将在本地视图中看到自己。
    3. 邀请一位朋友通过另一台设备来使用相同的 App ID、频道名、Token 加入频道。你的朋友加入频道后，你们可以听见、看见对方。

<DocsImage alt="互动界面" src="https://web-cdn.agora.io/docs-portal/img/rtc/windows-quickstart-inchannel.png" displayWidth="70%" />

</TabsContent>

<TabsContent value="csharp">

## 创建项目 [#创建项目-4]

    参考以下操作，在 Visual Studio 中创建一个 Windows 项目：

    1. 在 Visual Studio 上创建一个 Windows Forms 应用项目，详见 [Create a Windows Forms app in Visual Studio with C#](https://learn.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2017/ide/create-csharp-winform-visual-studio?view=vs-2017)。
    3. 打开 **Configuration Manager** 窗口，在 **Active solution platform** 下拉菜单中选择 **New**，在弹出的窗口中选择 **x64** 或 **x86** 作为目标平台。

    ## 集成 SDK [#集成-sdk-4]

    使用 Visual Studio 中的 NuGet 包管理器，将 SDK 集成到项目中。详见[快速入门：在 Visual Studio 中安装和使用包](https://learn.microsoft.com/zh-cn/nuget/quickstart/install-and-use-a-package-in-visual-studio)。

    :::info[信息]
    你可以在 [agora_rtc_sdk](https://www.nuget.org/packages/agora_rtc_sdk/#versions-body-tab) 中获取最新发布的 SDK 的版本信息。
    :::

    ## 创建用户界面 [#创建用户界面-4]

    为直观地体验视频通话，你需要根据应用场景创建用户界面（UI）。若你的项目中已有用户界面，可略过此步骤。

    如果你想实现音视频互动，推荐在 UI 上添加以下控件：

    - 本地视图窗口
    - 远端视图窗口
    - 加入频道按钮
    - 离开频道按钮

    参考以下步骤创建 UI。

    1. 创建 **Join** 和 **Leave** 按钮

       1. 在你的项目中，打开 **Solution Explore** 窗口，双击 **Form1.cs**，打开 **Toolbox** 窗口，选择 **Button** 控件，依次添加两个按钮，并将两个按钮拖放至合适位置。
       1. 将鼠标移至其中一个按钮上，点击鼠标右键，选中 **Properties**，在打开的 **Properties** 窗口中修改 **Text** 属性为 **Join**，修改 **Name** 属性为 **btnJoin**。
       1. 重复上一个步骤来修改另一个按钮的属性：修改 **Text** 为 **Leave**；修改 **Name** 为 **btnLeave**。

    2. 创建本地及远端视图窗口

       1. 打开 **Toolbox** 窗口，选中 **PictureBox** 控件，依次为本地视图和远端视图在合适的位置添加控件。
       1. 将鼠标移至本地视图窗口控件上，点击鼠标右键，选中 **Properties**，在打开的 **Properties** 窗口中修改 **Name** 属性为 **videoboxLocal**；修改 **BorderStyle** 为 **FixedSingle**。
       1. 重复上一个步骤修改远端视图窗口控件的属性：修改 **Name** 为 **videoboxRemote**；修改 **BorderStyle** 为 **FixedSingle**。

    3. 创建频道名输入框

       1. 打开 **Toolbox** 窗口，选中 **TextBox** 控件，然后将其拖拽至合适位置。
       1. 将鼠标移至添加好的输入框控件上，点击鼠标右键，选中 **Properties** ，修改其属性 **Name** 为 **txChannelName**。

    4. 保存上述步骤的更改。

    5. 分别双击 **Join** 和 **Leave** 按钮，IDE 会自动关联点击事件处理函数。

    此时你的 UI 界面如下图所示：

![Windows Forms 用户界面示意图](https://web-cdn.agora.io/docs-files/1684404223720)

    ## 实现步骤 [#实现步骤-5]

    本小节介绍如何实现一个实时音视频互动 App。你可以先复制完整的示例代码到你的项目中，快速体验实时音视频互动的基础功能，再按照实现步骤了解核心 API 调用。

    下图展示了使用声网 RTC SDK 实现音视频互动的基本流程。

<DocsImage alt="实现流程" src="https://web-cdn.agora.io/docs-portal/img/rtc/windows-quick-start-sequence.svg" displayWidth="70%" />

    下面列出了一段实现实时互动基本流程的完整代码以供参考。

    :::info[信息]
    在 `APP_ID` 和 `Token` 字段中传入你在控制台获取到的 App ID 和临时 Token。
    :::

<Accordions type="multiple">
<Accordion title="实现音视频互动示例代码">

```csharp
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Agora.Rtc;


namespace WindowsFormsApp2
{
    public partial class Form1 : Form
    {
        private readonly string APP_ID = "<APP_ID>";
        private readonly String APP_TOKEN = "<Token>";
        private IRtcEngine engine_ = null;
        private RtcEventHandler handler_ = null;

        public Form1()
        {
            InitializeComponent();

            // 创建引擎
            engine_ = RtcEngine.CreateAgoraRtcEngine();

            // 配置引擎实例
            RtcEngineContext ctx = new RtcEngineContext()
            {
                appId = APP_ID,
                areaCode = AREA_CODE.AREA_CODE_GLOB,
                logConfig = { filePath = "rtc.log" }
            };

            // 初始化引擎
            var ret = engine_.Initialize(ctx);
            if (ret != 0)
            {
                Console.WriteLine("=====>Initialize failed {0}", ret);
                return;
            }
            // 注册事件句柄
            handler_ = new RtcEventHandler();
            handler_.EventOnUserJoined += OnUserJoined;
            engine_.InitEventHandler(handler_);

            // 启用视频模块
            ret = engine_.EnableVideo();
            if (ret != 0)
            {
                Console.WriteLine("=====>EnableVideo failed {0}", ret);
            }

            // 开启本地视频采集
            ret = engine_.EnableLocalVideo(true);
            if (ret != 0)
            {
                Console.WriteLine("=====>EnableLocalVideo failed {0}", ret);
            }

            // 开始本地视频预览
            ret = engine_.StartPreview(VIDEO_SOURCE_TYPE.VIDEO_SOURCE_CAMERA_PRIMARY);
            if (ret != 0)
            {
                Console.WriteLine("=====>StartPreview failed {0}", ret);
            }

            // 设置本地视图
            VideoCanvas canvas = new VideoCanvas();
            canvas.view = (long)videoboxLocal.Handle;
            canvas.renderMode = RENDER_MODE_TYPE.RENDER_MODE_FIT;

            ret = engine_.SetupLocalVideo(canvas);
            if (ret != 0)
            {
                Console.WriteLine("=====>SetupLocalVideo failed {0}", ret);
            }
        }

        private void btnJoin_Click(object sender, EventArgs e)
        {
            if (null != engine_)
            {
                ChannelMediaOptions options = new ChannelMediaOptions();
                // 设置频道场景为直播
                options.channelProfile.SetValue(CHANNEL_PROFILE_TYPE.CHANNEL_PROFILE_LIVE_BROADCASTING);
                // 设置用户角色为主播
                options.clientRoleType.SetValue(CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER);

                // 发布麦克风采集的音频流
                options.publishMicrophoneTrack = true;
                // 发布摄像头采集的视频流
                options.publishCameraTrack = true;
                // 自动订阅所有音频流
                options.autoSubscribeAudio = true;
                // 自动订阅所有视频流
                options.autoSubscribeVideo = true;

                var ret = engine_.JoinChannel(APP_TOKEN, txChannelName.Text, 0, options);

                Console.WriteLine("=====>JoinChannel result {0}", ret);
            }
        }

        private void btnLeave_Click(object sender, EventArgs e)
        {
            if (null != engine_)
            {
                var ret = engine_.StopPreview();
                var ret = engine_.LeaveChannel();
                Console.WriteLine("=====>LeaveChannel result {0}", ret);
            }
        }

        private void OnUserJoined(RtcConnection connection, uint remoteUid, int elapsed)
        {
            VideoCanvas canvas = new VideoCanvas();
            canvas.view = (long)videoboxRemote.Handle;
            canvas.renderMode = RENDER_MODE_TYPE.RENDER_MODE_FIT;
            canvas.uid = remoteUid;

            int ret = engine_.SetupRemoteVideo(canvas);
            Console.WriteLine("----->SetupRemoteVideo, ret={0}", ret);
        }
    }

    internal class RtcEventHandler : IRtcEngineEventHandler
    {
        public delegate void OnUserJoinedHandler(
            RtcConnection connection,
            uint remoteUid,
            int elapsed
        );

        // 声明 OnUserJoined 回调
        public event OnUserJoinedHandler EventOnUserJoined;

        public RtcEventHandler() { }

        public override void OnError(int error, string msg)
        {
            Console.WriteLine("=====>OnError {0} {1}", error, msg);
        }

        public override void OnJoinChannelSuccess(RtcConnection connection, int elapsed)
        {
            Console.WriteLine("----->OnJoinChannelSuccess channel={0} uid={1}", connection.channelId,connection.localUid);
        }

        public override void OnLeaveChannel(RtcConnection connection, RtcStats stats)
        {
            Console.WriteLine("----->OnLeaveChannel duration={0}", stats.duration);
        }

        public override void OnUserJoined(RtcConnection connection, uint remoteUid, int elapsed)
        {
            Console.WriteLine("----->OnUserJoined uid={0}", remoteUid);

            if (EventOnUserJoined != null)
                EventOnUserJoined.Invoke(connection, remoteUid, elapsed);
        }

        public override void OnUserOffline(
            RtcConnection connection,
            uint remoteUid,
            USER_OFFLINE_REASON_TYPE reason
        )
        {
            Console.WriteLine("----->OnUserOffline, channel={0}, remoteUid={1}, reason={2}", connection.channelId, remoteUid, reason);
        }

        public override void OnRemoteVideoStateChanged(
            RtcConnection connection,
            uint remoteUid,
            REMOTE_VIDEO_STATE state,
            REMOTE_VIDEO_STATE_REASON reason,
            int elapsed
        )
        {
            Console.WriteLine("----->OnRemoteVideoStateChanged, channel={0}, remoteUid={1}, state={2}, reason={3}", connection.channelId, remoteUid, state, reason);
        }
    }
}
```

</Accordion>
</Accordions>


    ### 导入声网相关的类 [#导入声网相关的类-1]

    导入声网 RTC SDK 相关的类。

    ```csharp
    using Agora.Rtc;
    ```

    <a id="cs-initialize"></a>
### 初始化引擎 [#初始化引擎-4]

    调用 `CreateAgoraRtcEngine` 方法创建一个 `RtcEngine` 对象，然后调用 `initialize` 初始化 `RtcEngine`。

    ```csharp
    // 创建引擎
    engine_ = RtcEngine.CreateAgoraRtcEngine();

    // 配置引擎实例
    RtcEngineContext ctx = new RtcEngineContext()
    {
        appId = APP_ID,
        areaCode = AREA_CODE.AREA_CODE_GLOB,
        logConfig = { filePath = "rtc.log" }
    };

    //  初始化引擎
    var ret = engine_.Initialize(ctx);
    if (ret != 0)
    {
        Console.WriteLine("=====>Initialize failed {0}", ret);
        return;
    }
    ```

    ### 启用视频模块 [#启用视频模块-4]

    1. 调用 `EnableVideo` 方法，启用视频模块。
    1. 调用 `SetupLocalVideo` 初始化本地视图，同时设置本地的视频显示属性。
    1. 调用 `StartPreview` 方法，开启本地视频预览。

    ```csharp
    // 启用视频模块
    ret = engine_.EnableVideo();
    if (ret != 0)
    {
        Console.WriteLine("=====>EnableVideo failed {0}", ret);
    }

    // 设置本地视图
    VideoCanvas canvas = new VideoCanvas();
    canvas.view = (long)videoboxLocal.Handle;
    canvas.renderMode = RENDER_MODE_TYPE.RENDER_MODE_FIT;

    ret = engine_.SetupLocalVideo(canvas);
    if (ret != 0)
    {
        Console.WriteLine("=====>SetupLocalVideo failed {0}", ret);
    }

    // 开启本地视频预览
    ret = engine_.StartPreview(VIDEO_SOURCE_TYPE.VIDEO_SOURCE_CAMERA_PRIMARY);
    if (ret != 0)
    {
        Console.WriteLine("=====>StartPreview failed {0}", ret);
    }

    ```

    ### 加入频道并发布音视频流 [#加入频道并发布音视频流-4]

    调用 `JoinChannel`[2/2] 加入频道,填入你在控制台获取的临时 Token，以及获取 Token 时填入的频道名加入频道，并设置用户角色。

    ```csharp
    ChannelMediaOptions options = new ChannelMediaOptions();
    // 设置频道场景为直播
    options.channelProfile.SetValue(CHANNEL_PROFILE_TYPE.CHANNEL_PROFILE_LIVE_BROADCASTING);
    // 设置用户角色为主播
    options.clientRoleType.SetValue(CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER);

    // 发布麦克风采集的音频流
    options.publishMicrophoneTrack = true;
    // 发布摄像头采集的视频流
    options.publishCameraTrack = true;
    // 自动订阅所有音频流
    options.autoSubscribeAudio = true;
    // 自动订阅所有视频流
    options.autoSubscribeVideo = true;

    var ret = engine_.JoinChannel(APP_TOKEN, txChannelName.Text, 0, options);
    ```

    ### 设置远端用户视图 [#设置远端用户视图-2]

    调用 `SetupRemoteVideo` 方法初始化远端用户视图，同时设置远端用户的视图在本地显示属性。你可以通过 `OnUserJoined` 回调获取远端用户的 `uid`。

    ```csharp
    private void OnUserJoined(RtcConnection connection, uint remoteUid, int elapsed)
    {
        VideoCanvas canvas = new VideoCanvas();
        canvas.view = (long)videoboxRemote.Handle;
        canvas.renderMode = RENDER_MODE_TYPE.RENDER_MODE_FIT;
        canvas.uid = remoteUid
        int ret = engine_.SetupRemoteVideo(canvas);
        Console.WriteLine("----->SetupRemoteVideo, ret={0}", ret);
    }
    ```

    ### 实现常用回调 [#实现常用回调-4]

    根据使用场景，定义必要的回调。以下示例代码展示如何实现 `OnJoinChannelSuccess`、 `OnUserJoined` 和 `OnUserOffline` 回调。

    ```csharp
    public override void OnJoinChannelSuccess(RtcConnection connection, int elapsed)
    {
        Console.WriteLine("----->OnJoinChannelSuccess channel={0} uid={1}", connection.channelId,connection.localUid);
    }
    public override void OnUserJoined(RtcConnection connection, uint remoteUid, int elapsed)
    {
        Console.WriteLine("----->OnUserJoined uid={0}", remoteUid);
        if (EventOnUserJoined != null)
            EventOnUserJoined.Invoke(connection, remoteUid, elapsed);
    }
    public override void OnUserOffline(
        RtcConnection connection,
        uint remoteUid,
        USER_OFFLINE_REASON_TYPE reason
    )

    ```

    ### 开始音视频互动 [#开始音视频互动-3]

    在 `btnJoin_Click` 函数中调用 `JoinChannel`[2/2] 方法，当用户点击 **Join** 按钮时开始音视频互动。

    ```csharp
    private void btnJoin_Click(object sender, EventArgs e)
    {
        if (null != engine_)
        {
            ...
            var ret = engine_.JoinChannel(APP_TOKEN, txChannelName.Text, 0, options);
            Console.WriteLine("=====>JoinChannel result {0}", ret);
        }
    }

    ```

    ### 结束音视频互动 [#结束音视频互动-3]

    1. 在 **btnLeave_Click** 函数中调用 `LeaveChannel` 方法，当用户点击 **Leave** 按钮时结束音视频互动。

        ```csharp
        private void btnLeave_Click(object sender, EventArgs e)
        {
            if (null != engine_)
            {
                var ret = engine_.StopPreview();
                var ret = engine_.LeaveChannel();
                Console.WriteLine("=====>LeaveChannel result {0}", ret);
            }
        }
        ```

    2. 调用 `Dispose` 销毁引擎，并释放声网 SDK 中使用的所有资源。

        ```csharp
        engine_.Dispose()
        ```

        :::error[警告]
        - 调用 `Dispose` 后，你将无法再使用 SDK 的所有方法和回调。如需再次使用实时音视频互动功能，你必须重新创建一个新的引擎。详见[初始化引擎](#cs-initialize)。
                - 该方法为同步调用。需要等待引擎资源释放后才能执行其他操作，因此建议在子线程中调用该方法，避免主线程阻塞。
        :::

    ## 测试你的项目 [#测试你的项目]
    按照以下步骤来测试你的视频通话项目：

    1. 将你从声网控制台获取的 App ID 和临时 Token 分别填入到 `Form1.cs` 文件的 `APP_ID` 和 `APP_TOKEN` 中。
    1. 在 Visual Studio 中点击 **Start** 按钮运行你的项目，你将在本地视图中看到自己。
    2. 在输入框中输入你在声网控制台生成临时 Token 时填写的频道名，并点击 **Join** 按钮加入频道。
    4. 邀请一位朋友通过另一台设备来使用相同的 App ID、频道名、Token 加入频道。你的朋友加入频道后，你们可以听见、看见对方。

</TabsContent>
</Tabs>

## 后续步骤 [#后续步骤-3]

- 在测试或生产环境中，为确保通信安全，声网推荐使用 Token 服务器来生成 Token，详见[使用 Token 鉴权](/zh-CN/realtime-media/rtc/build/setup-and-access/token-authentication)。
- 如果你想要实现极速直播场景，可以在互动直播的基础上，通过修改观众端的延时级别为低延时 (`AUDIENCE_LATENCY_LEVEL_LOW_LATENCY`)实现。详见[实现极速直播](/zh-CN/realtime-media/rtc/build/advanced-channel/ls-quick-start)。

## 参考信息 [#参考信息-4]

### 示例项目 [#示例项目-4]

声网提供了开源的音视频互动示例项目供你参考，你可以前往下载或查看其中的源代码。

<Tabs defaultValue="windows" groupId="sample" persist>
<TabsList>
  <TabsTrigger value="windows">C++</TabsTrigger>
  <TabsTrigger value="csharp">C#</TabsTrigger>
</TabsList>

<TabsContent value="windows">

<Cards>
  <Card title={<RepositoryCardTitle provider="gitee">LiveBroadcasting</RepositoryCardTitle>} href="https://gitee.com/agoraio-community/API-Examples/tree/main/windows/APIExample/APIExample/Basic/LiveBroadcasting" />
  <Card title={<RepositoryCardTitle provider="github">LiveBroadcasting</RepositoryCardTitle>} href="https://github.com/Shengwang-Community/API-Examples/tree/main/windows/APIExample/APIExample/Basic/LiveBroadcasting" />
</Cards>

</TabsContent>

<TabsContent value="csharp">

<Cards>
  <Card title={<RepositoryCardTitle provider="github">JoinChannelVideo</RepositoryCardTitle>} href="https://github.com/AgoraIO-Extensions/Agora-C_Sharp-SDK/tree/master/APIExample/src/Basic/JoinChannelVideo" />
  <Card title={<RepositoryCardTitle provider="gitee">JoinChannelVideo</RepositoryCardTitle>} href="https://gitee.com/agoraio-community/Agora-C_Sharp-SDK/tree/main/Agora-C_Sharp-RTC-SDK-API_Example/src/Basic/JoinChannelVideo" />
</Cards>

</TabsContent>
</Tabs>

### 常见问题 [#常见问题-4]

- [直播场景下，如何监听远端观众角色用户加入/离开频道的事件？](/zh-CN/reference/faq/integration/audience_event)
- [如何处理视频黑屏问题？](/zh-CN/reference/faq/quality/video_blank)
- [为什么我无法打开摄像头？](/zh-CN/reference/faq/quality/video_camera)
- [如何处理频道相关常见问题？](/zh-CN/reference/faq/integration/channel)
- [如何设置日志文件](/zh-CN/reference/faq/integration/set_log_file)
- [为什么 SDK 中使用的是动态库而不是静态库？](/zh-CN/reference/faq/integration/dynamic_or_static_library)

### 相关文档 [#相关文档-3]

- [SDK 错误码](/zh-CN/realtime-media/rtc/reference/error-code)
- [频道连接状态管理](/zh-CN/realtime-media/rtc/build/initialize-and-channel/channel-connection)


















