Unity 語音和視頻通話快速解決方案——聲網 SDK接入指南(Android)
一、前言
當前游戲為了增加社交互動和代入感,比如狼人殺、團隊競技游戲等,經常會產生需要實時語音和視頻通話的需求。但是對於個人開發者和小團隊,這種需要前后端配合,重網絡的開發需求會帶來很大的挑戰。
為此我們需要尋找一個成熟、可靠的解決方案。每個月提供 10000 分鍾免費使用時長的聲網 Agora成為了我的最佳選擇。並且聲網的 SDK(Software Development Kit) 包體積很小,運行時CPU和內存占用率低,對於移動端的游戲開發很友好。2019年7月聲網正式成為了 Unity 官方認證合作伙伴,語音和視頻的 SDK 也已經發布在了 Unity 資源商店中,能夠非常方便的接入。
注意:語音和視頻的包有沖突,不兼容(一些庫和平台配置不同,可以自己手動修改),請根據需求,第四步和第五步二選一。引擎支持開關視頻和聲音,所以可以接入視頻 SDK 包,關閉視頻,僅僅使用語音通話。
二、后台創建應用
為了方便后續接入操作,這里先注冊和登錄到 官方后台 創建應用,獲取我們之后需要的 App ID。可以根據官網的新手引導來創建應用,也可以參考如下步驟。
輸入項目名稱,目前暫時使用 APP ID 的鑒權模式,后續根據需要也可以在項目編輯界面切換到 Token 鑒權模式,該模式更加安全,生成 Token 程序需要搭建在服務器上,可以參考官方文檔。
創建成功后,我們點擊 APP ID 下的顯示按鈕,APPID 就會復制到粘貼板。
三、獲取 SDK
這里我使用的 Unity 版本為 2019.3.14,進入商店我們搜索 Agora,可以發現視頻和語音兩個 SDK 包。
如果打不開 Unity Store 的同學還可以從官網開發者中心下載。
四、接入 Agora Voice 語音 SDK
1. 導入工程
從 Asset Store 的我的資源(My Asset)中找到我們下載的 Agora Voice SDK For Unity ,點擊 Import 導入到工程中。
導入的文件結構如下。
- Demo:官方提供的測試語音 Demo
- Edior:iOS 構建后處理腳本
- Plugins:不同平台所依賴的庫
- Scripts:SDK 源碼
2. 搭建測試場景
為了驗證 Agora Voice 的效果,我們打開官方的 Demo,或者是自己搭建一個類似的簡單場景。主要有三個按鈕,分別用來測試加入頻道、離開頻道和靜音。
3. 申請麥克風權限
在 Unity 2018.3 版本以后的新版本中,需要我們主動申請麥克風權限。
#if(UNITY_2018_3_OR_NEWER)
using UnityEngine.Android;
#endif
// ...
private void PermissionRequest()
{
#if (UNITY_2018_3_OR_NEWER)
if (!Permission.HasUserAuthorizedPermission(Permission.Microphone)) // 判斷是否有麥克風權限
{
Permission.RequestUserPermission(Permission.Microphone); // 申請麥克風權限
}
#endif
}
// ...
4. 初始化 IRtcEngine
我們在調用 Agora 的接口前,需要先初始化 IRtcEngine。此時我們第二步獲取的 APP ID,就在這里派上用處了。在通過 APP ID 創建 IRtcEngine 后,可以根據需求增加回調事件,這里我接入了一些比較常用的回調。
using agora_gaming_rtc;
// ...
private IRtcEngine mRtcEngine = null;
public const string APP_ID = "你自己的應用 APP ID";
private void InitEngine()
{
// 通過 APP ID 創建
mRtcEngine = IRtcEngine.GetEngine(APP_ID);
// 加入頻道成功后的回調
// channelName:頻道名稱
// uid:用戶ID(發起請求時候如果沒有指定,服務器會自動分配一個)
// elapsed:從本地用戶調用 JoinChannelByKey 到該回調觸發的延遲(毫秒)。
mRtcEngine.OnJoinChannelSuccess += (string channelName, uint uid, int elapsed) =>
{
// ...
};
// 離開頻道的回調
// stats:通話統計的數據
// duration:通話時長
// txBytes:發送字節數(bytes)
// rxBytes:接收字節數(bytes)
// txKBitRate:發送碼率(kbps)
// rxKBitRate:接收碼率(kbps)
mRtcEngine.OnLeaveChannel += (RtcStats stats) =>
{
string leaveChannelMessage = string.Format("onLeaveChannel callback duration {0}, tx: {1}, rx: {2}, tx kbps: {3}, rx kbps: {4}", stats.duration, stats.txBytes, stats.rxBytes, stats.txKBitRate, stats.rxKBitRate);
// ...
};
// 用戶加入回調
// uid:新加入頻道的遠端用戶/主播 ID
// elapsed:從本地用戶調用 JoinChannelByKey 到該回調觸發的延遲(毫秒)。
mRtcEngine.OnUserJoined += (uint uid, int elapsed) =>
{
// ...
};
// 用戶離開回調
// uid:離線用戶或主播的用戶 ID
// reason:離線原因(主動離開、超時、直播模式身份切換)
mRtcEngine.OnUserOffline += (uint uid, USER_OFFLINE_REASON reason) =>
{
string userOfflineMessage = string.Format("onUserOffline callback uid {0} {1}", uid, reason);
Debug.Log(userOfflineMessage);
};
// 提示頻道內誰在說話
// speakers:說話人信息
// speakerNumber:說話人數[0,3]
// totalVolume:總音量
mRtcEngine.OnVolumeIndication += (AudioVolumeInfo[] speakers, int speakerNumber, int totalVolume) =>
{
// ...
};
// 用戶靜音提示回調
// uid:用戶 ID
// muted:是否靜音
mRtcEngine.OnUserMutedAudio += (uint uid, bool muted) =>
{
// ...
};
// 發生警告回調
mRtcEngine.OnWarning += (int warn, string msg) =>
{
// ...
};
// 發生錯誤回調
mRtcEngine.OnError += (int error, string msg) =>
{
// ...
};
// 當前通話統計回調,每兩秒觸發一次。
mRtcEngine.OnRtcStats += (RtcStats stats) =>
{
// ...
};
// 語音路由已發生變化回調。(只在移動平台生效)
mRtcEngine.OnAudioRouteChanged += (AUDIO_ROUTE route) =>
{
// ...
};
// Token 過期回調
mRtcEngine.OnRequestToken += () =>
{
// ...
};
// 網絡中斷回調(建立成功后才會觸發)
mRtcEngine.OnConnectionInterrupted += () =>
{
// ...
};
// 網絡連接丟失回調
mRtcEngine.OnConnectionLost += () =>
{
// ...
};
// 設置 Log 級別
mRtcEngine.SetLogFilter(LOG_FILTER.INFO);
// 設置為自由說話模式,常用於一對一或者群聊
mRtcEngine.SetChannelProfile(CHANNEL_PROFILE.CHANNEL_PROFILE_COMMUNICATION);
}
...
5. 常用 API
5.1 加入頻道
public void JoinChannel()
{
// 從界面的輸入框獲取頻道名稱
string channelName = mChannelNameInputField.text.Trim();
Debug.Log(string.Format("tap joinChannel with channel name {0}", channelName));
if (string.IsNullOrEmpty(channelName))
{
return;
}
// 加入頻道
// channelKey: 動態秘鑰,我們最開始沒有選擇 Token 模式,這里就可以傳入 null;否則需要傳入服務器生成的 Token
// channelName: 頻道名稱
// info: 開發者附帶信息(非必要),不會傳遞給頻道內其他用戶
// uid: 用戶ID,0 為自動分配
mRtcEngine.JoinChannelByKey(channelKey: null, channelName: channelName, info:"extra",uid: 0);
}
5.2 靜音
void MuteButtonTapped()
{
string labeltext = isMuted ? "靜音" : "取消靜音";
Text label = muteButton.GetComponentInChildren<Text>();
if (label != null)
{
label.text = labeltext;
}
isMuted = !isMuted;
// 設置靜音(停止推送本地音頻)
mRtcEngine.MuteLocalAudioStream(!isMuted);
}
5.3 離開頻道 & 銷毀 IRtcEngine
public void LeaveChannel()
{
// 離開頻道
mRtcEngine.LeaveChannel();
string channelName = mChannelNameInputField.text.Trim();
Debug.Log(string.Format("left channel name {0}", channelName));
}
void OnApplicationQuit()
{
if (mRtcEngine != null)
{
// 銷毀 IRtcEngine
IRtcEngine.Destroy();
mRtcEngine = null;
}
}
6. 最終效果
我們可以先在編輯器上驗證,能夠正常運行后,將平台切換到 Android 后,直接出包就行,Agora SDK 中已經提供了 Android 中所需要的庫。
最后運行在 Android 上的效果如下:
- 輸入1234 成功加入頻道,分配給我們用戶 id:1186284123
- 有其他用戶加入,用戶id:2996662973
- 用戶id:2996662973 開啟靜音
- 用戶id:2996662973 離開頻道
- 我們離開頻道,顯示通話統計數據
語音通話的 API 時序圖如下:
五、接入 Agora Video 視頻 SDK
1. 導入工程
如果你按照第五步導入過音頻了,這里類似直接從商店導入 Agora Video SDK。要注意的是兩個包的內容不同,當然兩個 SDK 包的本質還是相同的,只是不同平台中的配置相關有些不同,如果同時使用,會出現問題。有能力的同學也可以嘗試修改兼容,這里還是比較推薦直接刪除音頻 Voice 的包,再導入新的 Video 的包,這樣就不用我們費盡的設置不同平台配置了。
2. 搭建測試場景
同樣的我們可以直接使用Demo 中提供的場景,SceneHome 是啟動場景,在最后測試的時候,注意需要修改 Build Setting 中場景列表。或者可以搭建一個簡易如下的場景。
3. 申請麥克風+相機權限
與音頻不同,視頻需要我們添加相機權限的申請。
private void PermissionRequest () {
#if (UNITY_2018_3_OR_NEWER)
if (!Permission.HasUserAuthorizedPermission (Permission.Microphone)) {
Permission.RequestUserPermission (Permission.Microphone);
}
// 增加相機權限
if (!Permission.HasUserAuthorizedPermission (Permission.Camera)) {
Permission.RequestUserPermission (Permission.Camera);
}
#endif
}
4. 初始化 IRtcEngine
與音頻唯一不同的是需要打開視頻功能。
private void InitEngine () {
mRtcEngine = IRtcEngine.GetEngine (APP_ID);
// 啟用視頻
mRtcEngine.EnableVideo ();
// 允許相機回調
mRtcEngine.EnableVideoObserver ();
// 后續與音頻相同添加回調...
}
5. 常用 API
5.1 設置自己畫面
官方已經提供好了一個用於顯示的 VideoSurface 類,我們只要把它添加到需要顯示的對象上即可,默認顯示的即為自己相機拍攝畫面。
private void CreateMyCamera()
{
GameObject myCamera = GameObject.Find("MyCamera");
if (ReferenceEquals(myCamera, null))
{
Debug.LogError("沒有找到 MyCamera 對象!");
return;
}
else
{
// 添加顯示畫面類
myCamera.AddComponent<VideoSurface>();
// 畫面需要垂直翻轉
myCamera.transform.Rotate(0f, 0.0f, 180.0f);
}
}
5.2 設置其他用戶畫面
private void CreateUserCamera(uint uid)
{
VideoSurface videoSurface;
GameObject userCamera = GameObject.Find("UserCamera");
if (ReferenceEquals(userCamera, null))
{
Debug.LogError("沒有找到 UserCamera 對象!");
return;
}
else
{
videoSurface = userCamera.AddComponent<VideoSurface>();
userCamera.transform.Rotate(0f, 0.0f, 180.0f);
}
// 設置顯示用戶
videoSurface.SetForUser(uid);
videoSurface.SetEnable(true);
// 設置平面類型
videoSurface.SetVideoSurfaceType(AgoraVideoSurfaceType.RawImage);
// 設置畫面幀率
videoSurface.SetGameFps(30);
}
5.3 開關視頻
我們可以在應用暫停的時候,停止視頻,畫面將不會再更新。
public void EnableVideo(bool pauseVideo)
{
if (mRtcEngine != null)
{
if (!pauseVideo)
{
// 啟動視頻
mRtcEngine.EnableVideo();
}
else
{
// 關閉視頻
mRtcEngine.DisableVideo();
}
}
}
6. 最終效果
最后在 Android 上運行的效果如下:
- 我們加入到 123 頻道
- 給我們分配id 722438456,並顯示出我們自己的畫面
- 顯示頻道內另一個用戶 1173951071 的畫面
- 離開頻道,視頻畫面停止
視頻通話的 API 時序圖如下:
六、總結
總的來說,聲網的接入還是較為簡單的。我們可以從商店導入對應的包,就能夠直接使用,最麻煩的不同平台的配置,官方已經幫忙解決了。而且通話和視頻質量不錯,再加上有免費使用時長,對於開發者來說是相當友好的。具體細節官方文檔說的也比較清楚。
幾行就搞定了麻煩的語音和視頻,光速下班,真香。