实现音视频互动
本文介绍如何集成声网实时互动 SDK,通过少量代码从 0 开始实现一个简单的实时互动 App,适用于互动直播和视频通话场景。
首先,你需要了解以下有关音视频实时互动的基础概念:
- 声网实时互动 SDK:由声网开发的、帮助开发者在 App 中实现实时音视频互动的 SDK。
- 频道:用于传输数据的通道,在同一个频道内的用户可以进行实时互动。
- 主播:可以在频道内发布音视频,同时也可以订阅其他主播发布的音视频。
- 观众:可以在频道内订阅音视频,不具备发布音视频权限。
更多概念详见关键概念。
下图以直播场景为例,展示在 App 中实现音视频互动的基本工作流程:

- 所有用户调用
joinChannel
方法加入频道,并根据需要设置用户角色:- 互动直播:如果用户需要在频道中发流,则设为主播;如果用户只需要收流,则设为观众。
- 视频通话:将所有的用户角色都为主播。
- 加入频道后,不同角色的用户具备不同的行为:
- 所有用户默认都可以接收频道中的音视频流。
- 主播可以在频道内发布音视频流。
- 观众如果需要发流,可在频道内调用
setClientRole
方法修改用户角色,使其具备发流权限。
前提条件
在实现功能以前,请按照以下要求准备开发环境:
- Windows 7 或以上版本的设备。
- Microsoft Visual Studio 2017 或以上版本。
- C++ 11 或以上版本。
- 如果使用 C# 开发,还需要 .NET 桌面开发组件。
- 可以访问互联网的计算机。如果你的网络环境部署了防火墙,参考应对防火墙限制以正常使用声网服务。
- 一个有效的声网账号以及声网项目。请参考开通服务从声网控制台获得以下信息:
- App ID:声网随机生成的字符串,用于识别你的项目。
- 临时 Token:Token 也称为动态密钥,在客户端加入频道时对用户鉴权。临时 Token 的有效期为 24 小时。
- C++
- C#
创建项目
参考以下操作,在你的 App 中实现实时音视频互动功能:
创建 Windows 项目
参考创建 C++ 控制台应用项目,在 Visual Studio 上创建一个 cpp 控制台应用项目。
集成 SDK
- 从资源获取获取最新的 Windows SDK,解压并打开。
- 打开已下载的 SDK 文件,并将其中的
sdk
文件夹中的所有子文件夹复制到你的解决方案文件夹下。保证这些子文件夹和你的sln
文件处于同一目录。
配置项目属性
在解决方案资源管理器窗口中,右击项目名称并点击属性进行以下配置,配置完成后点击确定。
- 进入C/C++ > 常规 > 附加包含目录菜单,点击编辑,并在弹出窗口中输入
$(SolutionDir)include
。 - 进入链接器 > 常规 > 附加库目录菜单,点击编辑,并在弹出窗口中输入
$(SolutionDir)lib
。 - 进入链接器 > 输入 > 附加依赖项菜单,点击编辑,并在弹出窗口中输入
agora_rtc_sdk.dll.lib
。
实现流程
下图展示了使用声网实时互动 SDK 实现音视频互动的 API 调用时序。
-
创建用户界面
根据应用场景,为你的项目创建一个用户界面。若项目中已有用户界面,直接查看初始化 IRtcEngine。
例如,你可以在 UI 上添加以下控件:
- 主播视频窗口
- 离开频道按钮
当你使用示例项目中的 UI 设计时,你将会看到如下界面:
-
初始化 IRtcEngine
在调用其他声网 API 前,需要创建并初始化
IRtcEngine
对象。调用
createAgoraRtcEngine
方法和initialize
方法并传入 App ID,初始化IRtcEngine
。你也可以根据需求,在初始化时实现其他功能,如注册用户加入频道和离开频道的回调。
C++// 创建一个 IRtcEngine 实例
m_lpAgoraEngine = createAgoraRtcEngine();
RtcEngineContext ctx;
// 添加注册回调和事件
ctx.eventHandler = &m_engineEventHandler;
// 输入你的 App ID。你可以在声网控制台获取你的项目的 App ID
ctx.appId = "Your App ID";
// 初始化 IRtcEngine
m_lpAgoraEngine->initialize(ctx);C++// 继承 IRtcEngineEventHandler 类中的回调与事件
class CAGEngineEventHandler :
public IRtcEngineEventHandler
{
public:
CAGEngineEventHandler();
~CAGEngineEventHandler();
void setMainWnd(HWND wnd);
HWND GetMsgReceiver() {return m_hMainWnd;};
// 注册 onJoinChannelSuccess 回调
// 本地用户成功加入频道时,会触发该回调
virtual void onJoinChannelSuccess(const char* channel, uid_t uid, int elapsed);
// 注册 onLeaveChannel 回调
// 本地主播成功离开频道时,会触发该回调
virtual void onLeaveChannel(const RtcStats& stat);
// 注册 onUserJoined 回调
// 远端主播成功加入频道时,会触发该回调
// 收到该回调后,需立即调用 setupRemoteVideo 设置远端主播的视图
virtual void onUserJoined(uid_t uid, int elapsed) override;
// 注册 onUserOffline 回调
// 远端主播离开频道或掉线时,会触发该回调
virtual void onUserOffline(uid_t uid, USER_OFFLINE_REASON_TYPE reason);
private:
HWND m_hMainWnd;
}; -
设置本地视图
在加入频道前设置本地视图,以便用户在通话中看到本地图像。参考以下步骤设置本地视图:
C++// 启用视频模块
m_lpAgoraEngine->enableVideo();
// 开始本地预览
m_lprtcEngine->startPreview();
// 设置本地视图
VideoCanvas vc;
vc.uid = 0;
vc.view = hVideoWnd;
vc.renderMode = RENDER_MODE_TYPE::RENDER_MODE_HIDDEN;
m_lpAgoraEngine->setupLocalVideo(vc); -
加入频道
设置频道场景和用户角色,并使用临时 Token 加入频道。
C++void CLiveBroadcastingDlg::OnBnClickedButtonJoinchannel()
{
if (!m_rtcEngine || !m_initialize)
return;
CString strInfo;
if (!m_joinChannel) {
CString strChannelName;
m_edtChannelName.GetWindowText(strChannelName);
if (strChannelName.IsEmpty()) {
AfxMessageBox(_T("Fill channel name first"));
return;
}
VideoEncoderConfiguration config;
if (m_cmbVideoEncoder.GetCurSel() < 3)
config.codecType = (VIDEO_CODEC_TYPE)(m_cmbVideoEncoder.GetCurSel() + 1);
else
config.codecType = (VIDEO_CODEC_TYPE)(m_cmbVideoEncoder.GetCurSel() + 2);
m_rtcEngine->setVideoEncoderConfiguration(config);
std::string szChannelId = cs2utf8(strChannelName);
ChannelMediaOptions options;
// 将频道场景设置为 LIVE_BROADCASTING
options.channelProfile = CHANNEL_PROFILE_LIVE_BROADCASTING;
// 设置用户角色为主播
options.clientRoleType = CLIENT_ROLE_TYPE(m_cmbRole.GetCurSel() + 1);
options.autoSubscribeAudio = true;
options.autoSubscribeVideo = true;
// 使用你在声网控制台生成的临时 Token 加入频道,在这里传入你的项目的 Token 和频道名
if (0 == m_rtcEngine->joinChannel(APP_TOKEN, szChannelId.c_str(), 0, options)) {
strInfo.Format(_T("join channel %s, use ChannelMediaOptions"), getCurrentTime());
m_btnJoinChannel.EnableWindow(FALSE);
}
}
else {
if (0 == m_rtcEngine->leaveChannel()) {
strInfo.Format(_T("leave channel %s"), getCurrentTime());
m_btnJoinChannel.EnableWindow(FALSE);
}
}
m_lstInfo.InsertString(m_lstInfo.GetCount(), strInfo);
} -
设置远端视图
在加入频道后,可通过调用
setupRemoteVideo
方法设置远端主播的视图。远端主播成功加入频道后,SDK 会触发
onUserJoined
回调,该回调中会包含这个主播的uid
信息。在该回调中调用setupRemoteVideo
方法,传入获取到的uid
,设置该主播的视图。C++// 远端主播成功加入频道时,会触发该回调
// 收到该回调后,需立即调用 setupRemoteVideo 设置远端主播的视图
void CAGEngineEventHandler::onUserJoined(uid_t uid, int elapsed)
{
LPAGE_USER_JOINED lpData = new AGE_USER_JOINED;
lpData->uid = uid;
lpData->elapsed = elapsed;
if(m_hMainWnd != NULL)
::PostMessage(m_hMainWnd, WM_MSGID(EID_USER_JOINED), (WPARAM)lpData, 0);
}C++VideoCanvas canvas;
canvas.renderMode = RENDER_MODE_FIT;
POSITION pos = m_listWndInfo.GetHeadPosition();
......
AGVIDEO_WNDINFO &agvWndInfo = m_listWndInfo.GetNext(pos);
canvas.uid = agvWndInfo.nUID;
canvas.view = m_wndVideo[nIndex].GetSafeHwnd();
agvWndInfo.nIndex = nIndex;
// 设置远端视图
CAgoraObject::GetEngine()->setupRemoteVideo(canvas); -
离开频道
根据场景需要,如结束通话、关闭 App 或 App 切换至后台时,调用
leaveChannel
离开当前通话频道。C++void CLiveBroadcastingDlg::UnInitAgora()
{
if (m_rtcEngine) {
if(m_joinChannel)
// 离开频道
m_rtcEngine->leaveChannel();
m_lstInfo.InsertString(m_lstInfo.GetCount(), _T("leaveChannel"));
// 停止视频预览.
m_rtcEngine->stopPreview();
m_lstInfo.InsertString(m_lstInfo.GetCount(), _T("stopPreview"));
// 关闭视频模块.
m_rtcEngine->disableVideo();
m_lstInfo.InsertString(m_lstInfo.GetCount(), _T("disableVideo"));
// 释放 IRtcEngine.
m_rtcEngine->release(true);
m_lstInfo.InsertString(m_lstInfo.GetCount(), _T("release rtc engine"));
m_rtcEngine = NULL;
}
}
后续步骤
- 在测试或生产环境中,为确保通信安全,声网推荐使用 Token 服务器来生成 Token,详见使用 Token 鉴权。
- 如果你想要实现极速直播场景,可以在互动直播的基础上,通过修改观众端的延时级别为低延时 (
AUDIENCE_LATENCY_LEVEL_LOW_LATENCY
)实现。详见实现极速直播。
相关信息
本节提供更多参考信息。
示例项目
声网在 GitHub 上提供一个开源的示例项目 API Example 供你参考。
创建项目
参考以下操作,在你的 App 中实现音视频互动:
创建 Windows 项目
- 在 Visual Studio 上创建一个 Windows Forms 应用项目,详见 Create a Windows Forms app in Visual Studio with C#。
- 打开 Configuration Manager 窗口,在 Active solution platform 下拉菜单中选择 New,在弹出的窗口中选择 x64 或 x86 作为目标平台。
集成 SDK
使用 Visual Studio 中的 NuGet 包管理器,将 SDK 集成到项目中。详见快速入门:在 Visual Studio 中安装和使用包。
你可以在 agora_rtc_sdk 中获取最新发布的 SDK 的版本信息。
在客户端实现视频通话
本节介绍如何使用视频 SDK 在你的项目中实现视频通话功能。
创建用户界面
为直观地体验视频通话,你需要根据应用场景创建用户界面 (UI)。若你的项目中已有用户界面,可略过此步骤。
如果你想实现视频通话,推荐在 UI 上添加以下控件:
- 本地视图窗口
- 远端视图窗口
- 加入频道按钮
- 离开频道按钮
参考以下步骤创建 UI。
-
创建 Join 和 Leave 按钮
a. 在你的项目中,打开 Solution Explore 窗口,双击 Form1.cs,打开 Toolbox 窗口,选择 Button 控件,依次添加两个按钮,并将两个按钮拖放至合适位置。 b. 将鼠标移至其中一个按钮上,点击鼠标右键,选中 Properties,在打开的 Properties 窗口中修改 Text 属性为 Join,修改 Name 属性为 btnJoin。 c. 重复上一个步骤来修改另一个按钮的属性:修改 Text 为 Leave;修改 Name 为 btnLeave。
-
创建本地及远端视图窗口
a. 打开 Toolbox 窗口,选中 PictureBox 控件,依次为本地视图和远端视图在合适的位置添加控件。 b. 将鼠标移至本地视图窗口控件上,点击鼠标右键,选中 Properties,在打开的 Properties 窗口中修改 Name 属性为 videoboxLocal;修改 BorderStyle 为 FixedSingle。 c. 重复上一个步骤修改远端视图窗口控件的属性:修改 Name 为 videoboxRemote;修改 BorderStyle 为 FixedSingle。
-
创建频道名输入框
a. 打开 Toolbox 窗口,选中 TextBox 控件,然后将其拖拽至合适位置。 b. 将鼠标移至添加好的输入框控件上,点击鼠标右键,选中 Properties ,修改其属性 Name 为 txChannelName。
-
保存上述步骤的更改。
-
分别双击 Join 和 Leave 按钮,IDE 会自动关联点击事件处理函数。
此时你的 UI 界面如下图所示:
实现视频通话逻辑
参考以下步骤来实现视频通话逻辑:
-
在项目中,鼠标右键点击 Solution Explorer 中的 Form1.cs,单击 View Code。
-
在弹出的
Form1.cs
界面中,将下列代码添加至using System.Windows.Forms;
之后:C#using Agora.Rtc;
-
创建加入频道相关变量 在
Form1.cs
中,将下列代码添加至public Form1()
之前:C#// 填写项目的 App ID,可在声网控制台中生成
private readonly string APP_ID = "";
// 填写声网控制台中生成的临时 Token
private readonly string APP_TOKEN = "";
// 声明 IRtcEngine 实例
private IRtcEngine engine_ = null;
// 声明 RtcEventHandler
private RtcEventHandler handler_ = null; -
声明一个
RtcEventHandler
类,用以处理事件 在Form1.cs
中,将以下代码添加至类Form1
的定义之后:C#internal class RtcEventHandler : IRtcEngineEventHandler
{
// 声明一个代理,用于处理 OnUserJoined 事件
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);
}
} -
创建并初始化
IRtcEngine
a. 调用
CreateAgoraRtcEngine
来创建IRtcEngine
实例。将以下代码添加到InitializeComponent();
的后面:C#// 创建 IRtcEngine
engine_ = RtcEngine.CreateAgoraRtcEngine();
RtcEngineContext ctx = new RtcEngineContext()
{
appId = APP_ID,
areaCode = AREA_CODE.AREA_CODE_GLOB,
logConfig =
{
filePath = "rtc.log"
}
};
// 初始化 IRtcEngine
var ret = engine_.Initialize(ctx);
if(ret != 0)
{
Console.WriteLine("=====>Initialize failed {0}", ret);
return;
}b. 创建并注册事件监听器。将以下代码添加至上一段代码之后:
C#// 注册事件监听器
handler_ = new RtcEventHandler();
handler_.EventOnUserJoined += OnUserJoined;
engine_.InitEventHandler(handler_);c. 开启视频模块。将以下代码添加至上一段代码之后:
C#// 启用视频模块
ret = engine_.EnableVideo();
if (ret != 0)
{
Console.WriteLine("=====>EnableVideo failed {0}", ret);
}d. 开启本地摄像头采集。将以下代码添加至上一段代码之后:
C#// 启用本地视频采集
ret = engine_.EnableLocalVideo(true);
if (ret != 0)
{
Console.WriteLine("=====>EnableLocalVideo failed {0}", ret);
}e. 开启本地摄像头画面预览。将以下代码添加至上一段代码之后:
C#// 开启本地视频预览
ret = engine_.StartPreview(VIDEO_SOURCE_TYPE.VIDEO_SOURCE_CAMERA_PRIMARY);
if (ret != 0)
{
Console.WriteLine("=====>StartPreview failed {0}", ret);
}f. 设置本地摄像头画面预览窗口。将以下代码添加至上一段代码之后:
C#// 设置本地视频渲染属性
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);
} -
实现加入频道逻辑。用以下代码替换
btnJoin_Click
函数:C#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);
// 使用临时 Token 加入频道
var ret = engine_.JoinChannel(APP_TOKEN, txChannelName.Text, 0, options);
Console.WriteLine("=====>JoinChannel result {0}", ret);
}
} -
实现离开频道逻辑。用以下代码替换
btnLeave_Click
函数:C#private void btnLeave_Click(object sender, EventArgs e)
{
if (null != engine_)
{
var ret = engine_.LeaveChannel();
Console.WriteLine("=====>LeaveChannel result {0}", ret);
}
} -
添加
OnUserJoined
函数用以处理远端用户加入事件,并为远端用户设置视频预览窗口。在btnLeave_Click
函数的后面添加以下代码:C#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);
}
测试你的项目
按照以下步骤来测试你的视频通话项目:
- 将你从声网控制台获取的 App ID 和临时 Token 分别填入到
Form1.cs
文件的APP_ID
和APP_TOKEN
中。 - 在 Visual Studio 中点击 Start 按钮运行你的项目,你将在本地视图中看到自己。
- 在输入框中输入你在声网控制台获取的频道名,并点击 Join 按钮加入频道。
- 邀请一位朋友通过另一台设备来使用相同的 App ID、频道名、Token 加入频道。你的朋友加入频道后,你们可以听见、看见对方。
示例项目
声网在 GitHub 上提供了一个开源的示例项目 Agora-C_Sharp-RTC-SDK-API_Example 供你参考,可实现更多的应用场景。