实现音视频互动
本文介绍如何集成声网实时互动 SDK,通过少量代码从 0 开始实现一个简单的实时互动 App,适用于互动直播和视频通话场景。
首先,你需要了解以下有关音视频实时互动的基础概念:
- 声网实时互动 SDK:由声网开发的、帮助开发者在 App 中实现实时音视频互动的 SDK。
- 频道:用于传输数据的通道,在同一个频道内的用户可以进行实时互动。
- 主播:可以在频道内发布音视频,同时也可以订阅其他主播发布的音视频。
- 观众:可以在频道内订阅音视频,不具备发布音视频权限。
更多概念详见关键概念。
下图展示在 App 中实现音视频互动的基本工作流程:
- 所有用户调用
joinChannel
方法加入频道,并根据需要设置用户角色:- 互动直播:如果用户需要在频道中发流,则设为主播;如果用户只需要收流,则设为观众。
- 视频通话:将所有的用户角色都为主播。
- 加入频道后,不同角色的用户具备不同的行为:
- 所有用户默认都可以接收频道中的音视频流。
- 主播可以在频道内发布音视频流。
- 观众如果需要发流,可在频道内调用
setClientRole
方法修改用户角色,使其具备发流权限。
前提条件
在实现功能以前,请按照以下要求准备开发环境:
-
已下载 Unity Hub。
-
Unity 2018.4.0 或以上版本。
-
可以访问互联网的计算机。如果你的网络环境部署了防火墙,参考应对防火墙限制以正常使用声网服务。
-
不同开发平台的操作系统与编译器需满足下列版本要求:
开发平台 操作系统版本 编译器版本 Android Android 4.1 或以上 Android Studio 3.0 或以上 iOS iOS 9.0 或以上 Xcode 9.0 或以上 macOS macOS 10.10 或以上 Xcode 9.0 或以上 Windows Windows 7 或以上 Microsoft Visual Studio 2017 或以上 -
一个有效的声网账号以及声网项目。请参考请参考开通服务从声网控制台获得 和临时 。
注意临时 Token 的有效期是 24 小时。Token 过期会导致加入频道失败。
创建项目
参考以下步骤或 Unity 官方文档创建一个 Unity 项目。若已有 Unity 项目,可略过此步骤至下一步集成 SDK。
- 开始前确保你已安装 Unity。若未安装,点击此处下载。
- 打开 Unity Editor,点击 New 新建一个项目。
- 依次填入以下内容后,点击 Create project 创建一个 Unity 项目。
- Project name:项目名称。
- Location:项目存储路径。
- Template:项目类型。选择 3D。
集成 SDK
参考下列步骤来集成声网实时互动 SDK:
- 前往下载页面,下载最新版的 Unity SDK。
- 下载完成后在你的 Unity 编辑器中打开下载好的 SDK 包并取消勾选不需要的插件(默认会勾选 SDK 包含的所有插件),然后点击 Import 来导入 SDK。
创建用户界面
根据应用场景为你的项目创建一个用户界面 (UI)。
例如,你可以在 UI 上添加以下控件:
- 本地视图窗口
- 远端视图窗口
- 加入/离开频道按钮
创建好的用户界面如下图所示:
你可以参考以下步骤创建 UI。若你的项目中已有用户界面可略过此步骤。
-
创建加入/离开频道按钮。
a. 在你的 Unity 项目中,右击 Sample Scene,选择 Game Object > UI > Button。此时你可以在场景画布中看到你刚刚创建的按钮。
b. 在右侧的 Inspector 面板中,将 Button 重命名为 Leave。选中 Button 按钮的 Text 控件,将 Text 的文本内容修改为 Leave,并调整按钮的位置,例如:
- Pos X:
- 350
- Pos Y:
- 172
c. 重复上述步骤来创建一个 Join 按钮。
- Pos X:
-
创建本地及远端视图窗口。
a. 右击 Canvas,选择 UI > Raw Image。
b. 创建本地视图窗口。在右侧的 Inspector 面板中,将 Raw Image 重命名为LocalView,并调整在画布中位置,例如:
- Pos X:
-250
- Pos Y:
0
- Width:
250
- Height:
250
c. 重复上述步骤创建远端视图窗口,将其命名为 RemoteView,并调整在画布中的位置,例如:
- Pos X:
250
- Pos Y:
0
- Width:
250
- Height:
250
- Pos X:
-
保存上述步骤的更改。
实现步骤
本小节介绍如何实现一个实时音视频互动 App。你可以先复制完整的示例代码到你的项目中,快速体验实时音视频互动的基础功能,再按照实现步骤了解核心 API 调用。
下图展示了使用声网 RTC SDK 实现音视频互动的基本流程:
在 _appId
、_token
和 _channelName
字段中传入你在控制台获取到的 App ID、临时 Token,以及生成临时 Token 时填入的频道名。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using Agora.Rtc;
#if (UNITY_2018_3_OR_NEWER && UNITY_ANDROID)
using UnityEngine.Android;
#endif
public class JoinChannelVideo : MonoBehaviour
{
// 填入你的 app ID
private string _appID= "";
// 填入你的频道名
private string _channelName = "";
// 填入 Token
private string _token = "";
internal VideoSurface LocalView;
internal VideoSurface RemoteView;
internal IRtcEngine RtcEngine;
#if (UNITY_2018_3_OR_NEWER && UNITY_ANDROID)
private ArrayList permissionList = new ArrayList() { Permission.Camera, Permission.Microphone };
#endif
void Start()
{
SetupVideoSDKEngine();
InitEventHandler();
SetupUI();
PreviewSelf();
}
void Update()
{
CheckPermissions();
}
void OnApplicationQuit()
{
if (RtcEngine != null)
{
Leave();
// 销毁 IRtcEngine
RtcEngine.Dispose();
RtcEngine = null;
}
}
private void CheckPermissions() {
#if (UNITY_2018_3_OR_NEWER && UNITY_ANDROID)
foreach (string permission in permissionList)
{
if (!Permission.HasUserAuthorizedPermission(permission))
{
Permission.RequestUserPermission(permission);
}
}
#endif
}
private void PreviewSelf()
{
// 启用视频模块
RtcEngine.EnableVideo();
// 开启本地视频预览
RtcEngine.StartPreview();
// 设置本地视频显示
LocalView.SetForUser(0, "");
// 渲染视频
LocalView.SetEnable(true);
}
private void SetupUI()
{
GameObject go = GameObject.Find("LocalView");
LocalView = go.AddComponent<VideoSurface>();
go.transform.Rotate(0.0f, 0.0f, -180.0f);
go = GameObject.Find("RemoteView");
RemoteView = go.AddComponent<VideoSurface>();
go.transform.Rotate(0.0f, 0.0f, -180.0f);
go = GameObject.Find("Leave");
go.GetComponent<Button>().onClick.AddListener(Leave);
go = GameObject.Find("Join");
go.GetComponent<Button>().onClick.AddListener(Join);
}
private void SetupVideoSDKEngine()
{
// 创建 IRtcEngine 实例
RtcEngine = Agora.Rtc.RtcEngine.CreateAgoraRtcEngine();
RtcEngineContext context = new RtcEngineContext();
context.appId = _appID;
context.channelProfile = CHANNEL_PROFILE_TYPE.CHANNEL_PROFILE_LIVE_BROADCASTING;
context.audioScenario = AUDIO_SCENARIO_TYPE.AUDIO_SCENARIO_DEFAULT;
// 初始化 IRtcEngine
RtcEngine.Initialize(context);
}
// 创建用户回调类实例,并设置回调
private void InitEventHandler()
{
UserEventHandler handler = new UserEventHandler(this);
RtcEngine.InitEventHandler(handler);
}
public void Join()
{
// 设置频道媒体选项
ChannelMediaOptions options = new ChannelMediaOptions();
// 开始视频渲染
LocalView.SetEnable(true);
// 发布麦克风采集的音频流
options.publishMicrophoneTrack.SetValue(true);
// 发布摄像头采集的视频流
options.publishCameraTrack.SetValue(true);
// 自动订阅所有音频流
options.autoSubscribeAudio.SetValue(true);
// 自动订阅所有视频流
options.autoSubscribeVideo.SetValue(true);
// 将频道场景设为直播
options.channelProfile.SetValue(CHANNEL_PROFILE_TYPE.CHANNEL_PROFILE_LIVE_BROADCASTING);
// 将用户角色设为主播
options.clientRoleType.SetValue(CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER);
// 加入频道
RtcEngine.JoinChannel(_token, _channelName, 0, options);
}
public void Leave()
{
Debug.Log("Leaving _channelName");
// 关闭视频模块
RtcEngine.StopPreview();
// 离开频道
RtcEngine.LeaveChannel();
// 停止远端视频渲染
RemoteView.SetEnable(false);
}
// 实现你自己的回调类,可以继承 IRtcEngineEventHandler 接口类实现
internal class UserEventHandler : IRtcEngineEventHandler
{
private readonly JoinChannelVideo _videoSample;
internal UserEventHandler(JoinChannelVideo videoSample)
{
_videoSample = videoSample;
}
// 发生错误回调
public override void OnError(int err, string msg)
{
}
// 本地用户成功加入频道时,会触发该回调
public override void OnJoinChannelSuccess(RtcConnection connection, int elapsed)
{
}
// SDK 接收到第一帧远端视频并成功解码时,会触发 OnUserJoined 回调
public override void OnUserJoined(RtcConnection connection, uint uid, int elapsed)
{
// 设置远端视频显示
_videoSample.RemoteView.SetForUser(uid, connection.channelId, VIDEO_SOURCE_TYPE.VIDEO_SOURCE_REMOTE);
// 开始视频渲染
_videoSample.RemoteView.SetEnable(true);
Debug.Log("Remote user joined");
}
// 远端用户离开当前频道时会触发该回调
public override void OnUserOffline(RtcConnection connection, uint uid, USER_OFFLINE_REASON_TYPE reason)
{
_videoSample.RemoteView.SetEnable(false);
Debug.Log("Remote user offline");
}
}
}
导入类
新建脚本并导入 Unity 相关的类和声网 RTC Unity SDK 相关的类和接口。
-
在 Project 中,打开 Assets > Agora-Unity-RTC-SDK > Code >Rtc,右击然后选择 Create > C# Script,此时你的 Assets 中会有一个
NewBehaviourScript.cs
文件,将这个文件重新命名为JoinChannelVideo.cs
。后续步骤中的示例代码均指该文件中的代码。 -
将新建的脚本和 Canvas 绑定。选择
JoinChannelVideo.cs
文件,将其拖至左侧的 Canvas。在 Inspector 面板中你可以看到该文件已经和 Canvas 绑定。 -
导入 Unity 相关的类。
C#using UnityEngine;
using UnityEngine.UI; -
导入
Agora.Rtc
命名空间,其中包含了实现实时音视频功能所需的各种类和接口。C#using Agora.Rtc;
处理权限请求
本小节介绍如何获取设备的摄像头、麦克风等权限。在 Unity 2018.3 或以上版本中,Unity 不会主动向用户获取麦克风和相机权限,因此你需要请求获取权限。
Windows 系统会在应用程序首次尝试使用摄像头或麦克风等功能时,自动向用户弹出权限请求提示,因此你无需额外处理权限请求。
- Android
- iOS 和 macOS
如果你开发的目标平台是 Android,Unity 提供了权限请求的 API CheckPermission
。你可以调用该方法来获取相关权限。参考下列步骤来获取安卓权限:
-
导入 Unity 的 Android 命名空间。
C##if (UNITY_2018_3_OR_NEWER && UNITY_ANDROID)
using UnityEngine.Android;
#endif -
创建需要获取的权限列表,包括摄像头和麦克风。
C##if (UNITY_2018_3_OR_NEWER && UNITY_ANDROID)
private ArrayList permissionList = new ArrayList() { Permission.Camera, Permission.Microphone };
#endif -
检查是否已获取权限。
C#private void CheckPermissions() {
#if (UNITY_2018_3_OR_NEWER && UNITY_ANDROID)
foreach (string permission in permissionList) {
if (!Permission.HasUserAuthorizedPermission(permission)) {
Permission.RequestUserPermission(permission);
}
}
#endif
}
如果你开发的目标平台是 iOS 或 macOS,声网 RTC Unity SDK 在 Editor
文件夹下提供一个构建后处理脚本 BL_BuildPostProcess.cs
。在你将项目从 Unity Editor 中构建并导出为 iOS 项目后,该脚本会自动向 Info.plist
文件添加摄像头和麦克风权限,无需你手动处理。
定义 App ID 和 Token
传入从声网控制台获取的 App ID、临时 Token,以及生成临时 Token 时填入的频道名,用于后续初始化引擎和加入频道。
// 填入你的 App ID
private string _appID= "";
// 填入你的频道名
private string _channelName = "";
// 填入 Token
private string _token = "";
初始化引擎
在调用其他声网 API 前,需要创建并初始化 IRtcEngine
实例。调用 CreateAgoraRtcEngine
来创建 IRtcEngine
实例,调用 Initialize
进行初始化。
你需要在用户同意 App 的隐私政策之后再调用下列代码来初始化 SDK。
private void SetupVideoSDKEngine()
{
// 创建 IRtcEngine 实例
RtcEngine = Agora.Rtc.RtcEngine.CreateAgoraRtcEngine();
RtcEngineContext context = new RtcEngineContext();
context.appId = _appID;
context.channelProfile = CHANNEL_PROFILE_TYPE.CHANNEL_PROFILE_LIVE_BROADCASTING;
context.audioScenario = AUDIO_SCENARIO_TYPE.AUDIO_SCENARIO_DEFAULT;
// 初始化 IRtcEngine
RtcEngine.Initialize(context);
}
设置 UI 元素
将 UI 组件与代码进行绑定,包括加入频道、离开频道按钮的点击事件,以及本地视频显示和远端视频显示。
private void SetupUI()
{
GameObject go = GameObject.Find("LocalView");
LocalView = go.AddComponent<VideoSurface>();
go.transform.Rotate(0.0f, 0.0f, -180.0f);
go = GameObject.Find("RemoteView");
RemoteView = go.AddComponent<VideoSurface>();
go.transform.Rotate(0.0f, 0.0f, -180.0f);
go = GameObject.Find("Leave");
go.GetComponent<Button>().onClick.AddListener(Leave);
go = GameObject.Find("Join");
go.GetComponent<Button>().onClick.AddListener(Join);
}
启用视频模块
按照以下步骤启用视频模块并开启本地视频预览:
- 调用
EnableVideo
启用视频模块。 - 调用
StartPreview
开启本地视频预览。 - 调用
SetForUser
设置本地视频显示。 - 调用
SetEnable(true)
渲染本地视频。
private void PreviewSelf()
{
// 启用视频模块
RtcEngine.EnableVideo();
// 开启本地视频预览
RtcEngine.StartPreview();
// 设置本地视频显示
LocalView.SetForUser(0, "");
// 渲染视频
LocalView.SetEnable(true);
}
加入频道并发布音视频流
调用 JoinChannel
[2/2] 加入频道。在 ChannelMediaOptions
中进行如下配置:
- 设置频道场景为
CHANNEL_PROFILE_LIVE_BROADCASTING
(直播场景) 并设置用户角色设置为BROADCASTER
(主播)。 - 将
publishMicrophoneTrack
和publishCameraTrack
设置为true
,发布麦克风采集的音频和摄像头采集的视频。 - 将
autoSubscribeAudio
和autoSubscribeVideo
设置为true
,自动订阅所有音视频流。
public void Join()
{
// 设置频道媒体选项
ChannelMediaOptions options = new ChannelMediaOptions();
// 发布麦克风采集的音频流
options.publishMicrophoneTrack.SetValue(true);
// 发布摄像头采集的视频流
options.publishCameraTrack.SetValue(true);
// 自动订阅所有音频流
options.autoSubscribeAudio.SetValue(true);
// 自动订阅所有视频流
options.autoSubscribeVideo.SetValue(true);
// 将频道场景设为直播
options.channelProfile.SetValue(CHANNEL_PROFILE_TYPE.CHANNEL_PROFILE_LIVE_BROADCASTING);
// 将用户角色设为主播
options.clientRoleType.SetValue(CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER);
// 加入频道
RtcEngine.JoinChannel(_token, _channelName, 0, options);
}
实现常用回调
根据使用场景,定义必要的回调。下列示例代码展示本地用户加入频道、远端用户加入、离开频道的回调。
// 实现你自己的回调类,可以继承 IRtcEngineEventHandler 接口类实现
internal class UserEventHandler : IRtcEngineEventHandler
{
private readonly JoinChannelVideo _videoSample;
internal UserEventHandler(JoinChannelVideo videoSample)
{
_videoSample = videoSample;
}
// 本地用户成功加入频道时,会触发该回调
public override void OnJoinChannelSuccess(RtcConnection connection, int elapsed)
{
}
// SDK 接收到第一帧远端视频并成功解码时,会触发 OnUserJoined 回调
public override void OnUserJoined(RtcConnection connection, uint uid, int elapsed)
{
// 设置远端视频显示
_videoSample.RemoteView.SetForUser(uid, connection.channelId, VIDEO_SOURCE_TYPE.VIDEO_SOURCE_REMOTE);
// 开始视频渲染
_videoSample.RemoteView.SetEnable(true);
Debug.Log("Remote user joined");
}
// 远端用户离开当前频道时会触发该回调
public override void OnUserOffline(RtcConnection connection, uint uid, USER_OFFLINE_REASON_TYPE reason)
{
_videoSample.RemoteView.SetEnable(false);
}
}
设置远端视图
当远端用户加入频道时,触发 OnUserJoined
回调。调用 SetForUser
设置远端视频显示,调用 SetEnable(true)
渲染视频。
// SDK 接收到第一帧远端视频并成功解码时,会触发 OnUserJoined 回调
public override void OnUserJoined(RtcConnection connection, uint uid, int elapsed) {
// 设置远端视频显示
_videoSample.RemoteView.SetForUser(uid, connection.channelId, VIDEO_SOURCE_TYPE.VIDEO_SOURCE_REMOTE);
// 开始视频渲染
_videoSample.RemoteView.SetEnable(true);
Debug.Log("Remote user joined");
}
设置远端用户离开频道或掉线后停止远端视频显示。远端用户离开频道后,触发 OnUserOffline
回调。调用 SetEnable(false)
停止显示视频。
// 远端用户离开当前频道时会触发该回调
public override void OnUserOffline(RtcConnection connection, uint uid, USER_OFFLINE_REASON_TYPE reason)
{
_videoSample.RemoteView.SetEnable(false);
Debug.Log("Remote user offline");
}
开始音视频互动
在开始音频互动前,先检查是否已获取设备权限,然后在 Start
中调用一系列方法来初始化引擎、注册事件回调、加入频道,开始音频互动。
-
检查是否已获取设备权限。调用
CheckPermissions
方法检查是否已获取实现音视频互动所需的权限。C#void Update() {
CheckPermissions();
} -
开始音视频互动。在
Start
方法中初始化引擎并开始实时音视频互动。C#void Start()
{
SetupVideoSDKEngine();
InitEventHandler();
SetupUI();
PreviewSelf();
}
结束音视频互动
按照以下步骤结束音视频互动:
- 调用
StopPreview
停止本地视频预览。 - 调用
LeaveChannel
来离开当前频道,释放所有会话相关的资源。 - 调用
SetEnable
停止远端视频渲染。
public void Leave()
{
Debug.Log("Leaving _channelName");
// 离开频道
RtcEngine.LeaveChannel();
// 关闭视频模块
RtcEngine.StopPreview();
// 停止远端视频渲染
RemoteView.SetEnable(false);
}
如果你想在退出时释放 IRtcEngine
内存,调用 Dispose
方法销毁 IRtcEngine
。
调用 Dispose
后,你将无法再使用 SDK 的所有方法和回调。如需再次使用实时音视频互动功能,你必须重新创建一个新的引擎。详见初始化引擎。
void OnApplicationQuit()
{
if (RtcEngine != null)
{
Leave();
// 销毁 IRtcEngine
RtcEngine.Dispose();
RtcEngine = null;
}
}
测试你的项目
按照以下步骤来测试你的音视频互动项目:
- 在声网控制台获取一个临时 Token。请参考开通服务获取。
- 在
JoinChannelVideo.cs
中,将你的项目的 App ID、频道名以及临时 Token 分别填入到_appID
、_channelName
、_token
之后。 - 在 Unity Editor 中,点击 Play 按钮来运行你的项目,然后点击 Join 按钮加入频道。
- 邀请一位朋友通过另一台设备来使用相同的 App ID、Token、频道名加入频道。你的朋友加入频道后,你们可以听见、看见对方。
后续步骤
在完成音视频互动后,你可以阅读以下文档进一步了解:
- 本文的示例使用了临时 Token 加入频道。在测试或生产环境中,为确保通信安全,声网推荐使用 Token 服务器来生成 Token,详见使用 Token 鉴权。
- 如果你想要实现极速直播场景,可以在互动直播的基础上,通过修改观众端的延时级别为低延时 (
AUDIENCE_LATENCY_LEVEL_LOW_LATENCY
)实现。详见实现极速直播。
参考信息
示例项目
声网提供了开源的音视频互动示例项目供你参考,你可以前往下载或查看其中的源代码。
常见问题
- 直播场景下,如何监听远端观众角色用户加入/离开频道的事件?
- 为什么使用 Unity 4.x SDK 开发的 iOS App 上传到 App Store 会报错?
- 如何处理视频黑屏问题?
- 为什么我无法打开摄像头?
- 如何处理频道相关常见问题?
- 为什么 SDK 中使用的是动态库而不是静态库?