实现纯语音互动
本文介绍如何集成声网实时互动 SDK,通过少量代码从 0 开始实现一个简单的纯语音互动 App,适用于语音通话场景。
首先,你需要了解以下有关音视频实时互动的基础概念:
- 声网实时互动 SDK:由声网开发的、帮助开发者在 App 中实现实时音视频互动的 SDK。
- 频道:用于传输数据的通道,在同一个频道内的用户可以进行实时互动。
- 主播:可以在频道内发布音视频,同时也可以订阅其他主播发布的音视频。
- 观众:可以在频道内订阅音视频,不具备发布音视频权限。
更多概念详见关键概念。
下图展示在 App 中实现纯语音互动的基本工作流程:
- 所有用户调用
joinChannel
方法加入频道,并将所有用户角色都设置为主播。 - 加入频道后,所有用户都可以在频道内发布音频流,并订阅对方的音频流。
前提条件
在实现功能以前,请按照以下要求准备开发环境:
-
已下载 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,并调整按钮的位置,例如:
- Pos X:
- 350
- Pos Y:
- 172
c. 选中 Leave 按钮的 Text 控件,在 Inspector 面板中将 Text 的文本内容修改为 Leave。
d. 重复上述步骤再创建一个 Join 按钮来加入语音互动。
- Pos X:
-
保存上述步骤的更改。
实现步骤
本节介绍如何使用声网实时互动 SDK 在你的项目中实现纯语音互动功能。
下图展示了实现纯语音互动的基本流程。
下面列出了一段实现实时纯语音互动基本流程的完整代码以供参考。复制以下代码到 JoinChannelAudio.cs
文件中替换原有内容即可。
在 _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 JoinChannelAudio : MonoBehaviour
{
// 填入你的 app ID
private string _appID= "";
// 填入你的频道名
private string _channelName = "";
// 填入 Token
private string _token = "";
internal IRtcEngine RtcEngine;
#if (UNITY_2018_3_OR_NEWER && UNITY_ANDROID)
private ArrayList permissionList = new ArrayList() { Permission.Microphone };
#endif
void Start()
{
SetupAudioSDKEngine();
InitEventHandler();
SetupUI();
}
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 SetupUI()
{
GameObject go = GameObject.Find("Leave");
go.GetComponent<Button>().onClick.AddListener(Leave);
go = GameObject.Find("Join");
go.GetComponent<Button>().onClick.AddListener(Join);
}
private void SetupAudioSDKEngine()
{
// 创建 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()
{
Debug.Log("Joining _channelName");
// 启用音频模块
RtcEngine.EnableAudio();
// 设置频道媒体选项
ChannelMediaOptions options = new ChannelMediaOptions();
// 发布麦克风采集的音频流
options.publishMicrophoneTrack.SetValue(true);
// 自动订阅所有音频流
options.autoSubscribeAudio.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.LeaveChannel();
// 关闭音频模块
RtcEngine.DisableAudio();
}
// 实现你自己的回调类,可以继承 IRtcEngineEventHandler 接口类实现
internal class UserEventHandler : IRtcEngineEventHandler
{
private readonly JoinChannelAudio _audioSample;
internal UserEventHandler(JoinChannelAudio audioSample)
{
_audioSample = audioSample;
}
// 本地用户成功加入频道时,会触发该回调
public override void OnJoinChannelSuccess(RtcConnection connection, int elapsed)
{
Debug.Log("OnJoinChannelSuccess _channelName");
}
// 远端用户成功加入频道时,会触发该回调
public override void OnUserJoined(RtcConnection connection, uint uid, int elapsed)
{
Debug.Log("Remote user joined");
}
// 远端用户离开当前频道时会触发该回调
public override void OnUserOffline(RtcConnection connection, uint uid, USER_OFFLINE_REASON_TYPE reason) {
}
}
}
导入类
新建脚本并导入 Unity 相关的类和声网 RTC Unity SDK 相关的类和接口。
-
在 Project 中,打开 Assets > Agora-Unity-RTC-SDK > Code >Rtc,右击然后选择 Create > C# Script,此时你的 Assets 中会有一个
NewBehaviourScript.cs
文件,将这个文件重新命名为JoinChannelAudio.cs
。后续步骤中的示例代码均指该文件中的代码。 -
将脚本和 Canvas 绑定。在
Assets/Agora-Unity-RTC-SDK/Code/Rtc
中,选择JoinChannelAudio.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("Leave");
go.GetComponent<Button>().onClick.AddListener(Leave);
go = GameObject.Find("Join");
go.GetComponent<Button>().onClick.AddListener(Join);
}
加入频道并发布音频流
调用 JoinChannel
[2/2] 方法加入频道。在 ChannelMediaOptions
中进行如下配置:
- 设置频道场景为
CHANNEL_PROFILE_LIVE_BROADCASTING
(直播场景) 并设置用户角色设置为BROADCASTER
(主播)。 - 将
publishMicrophoneTrack
设置为true
,发布麦克风采集的音频流。 - 将
autoSubscribeAudio
设置为true
,自动订阅所有音频流。
public void Join() {
Debug.Log("Joining _channelName");
// 启用音频模块
RtcEngine.EnableAudio();
// 设置频道媒体选项
ChannelMediaOptions options = new ChannelMediaOptions();
// 发布麦克风采集的音频流
options.publishMicrophoneTrack.SetValue(true);
// 自动订阅所有音频流
options.autoSubscribeAudio.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);
}
实现常用回调
根据使用场景,定义必要的回调。下列示例代码展示本地用户加入频道、远端用户加入、离开频道的回调。
// 创建用户回调类实例,并设置回调
private void InitEventHandler() {
UserEventHandler handler = new UserEventHandler(this);
RtcEngine.InitEventHandler(handler);
}
// 实现你自己的回调类,可以继承 IRtcEngineEventHandler 接口类实现
internal class UserEventHandler : IRtcEngineEventHandler {
private readonly JoinChannelAudio _audioSample;
internal UserEventHandler(JoinChannelAudio audioSample) {
_audioSample = audioSample;
}
// 本地用户成功加入频道时,会触发该回调
public override void OnJoinChannelSuccess(RtcConnection connection, int elapsed) {
Debug.Log("OnJoinChannelSuccess _channelName");
}
// 远端用户成功加入频道时,会触发该回调
public override void OnUserJoined(RtcConnection connection, uint uid, int elapsed) {
Debug.Log("Remote user joined");
}
// 远端用户离开当前频道时,会触发该回调
public override void OnUserOffline(RtcConnection connection, uint uid, USER_OFFLINE_REASON_TYPE reason) {
}
}
开始音频互动
在开始音频互动前,先检查是否已获取设备权限,然后在 Start
中调用一系列方法来初始化引擎、注册事件回调、加入频道,开始音频互动。
-
检查是否已获取设备权限。调用
CheckPermissions
方法检查是否已获取实现语音互动所需的权限。C#void Update() {
CheckPermissions();
} -
开始语音互动。在
Start
方法中初始化引擎并开始实时音频互动。C#void Start()
{
SetupAudioSDKEngine();
InitEventHandler();
SetupUI();
}
结束音频互动
如果你需要退出频道,调用 LeaveChannel
离开当前频道,调用 DisableAudio
关闭音频模块。
public void Leave() {
Debug.Log("Leaving _channelName");
// 离开频道
RtcEngine.LeaveChannel();
// 关闭音频模块
RtcEngine.DisableAudio();
}
如果你需要退出应用或者释放 IRtcEngine
内存,调用 Dispose
方法销毁 IRtcEngine
。
调用 Dispose
后,你将无法再使用 SDK 的所有方法和回调。如需再次使用实时音视频互动功能,你必须重新创建一个新的引擎。详见初始化引擎。
void OnApplicationQuit()
{
if (RtcEngine != null)
{
Leave();
// 销毁 IRtcEngine
RtcEngine.Dispose();
RtcEngine = null;
}
}
测试你的项目
按照以下步骤来测试你的语音互动项目:
- 在声网控制台获取一个临时 Token。请参考开通服务获取。
- 在
Assets/Agora-Unity-RTC-SDK/Code/Rtc/JoinChannelAudio.cs
中,将你的项目的 App ID、频道名以及临时 Token 分别填入到_appID
、_channelName
、_token
之后。 - 在 Unity 编辑器中,点击播放按钮来运行你的项目,然后点击 Join 按钮加入语音互动。
- 邀请一位朋友通过另一台设备来使用相同的 App ID、Token、频道名加入频道。你的朋友加入频道后,你们可以听见对方。
参考信息
示例项目
声网提供了开源的纯语音互动示例项目供你参考,你可以前往下载或查看其中的源代码。