本文檔將介紹如何在您的Android專案中整合 ARTC SDK, 快速實現一個簡單的純音頻互動App,適用於語音通話、語聊房等情境。
功能介紹
在開始前,您需要瞭解以下有關音視頻即時互動的基本概念:
ARTC SDK:阿里雲即時音視頻產品,協助開發中快速實現即時音視頻互動的SDK。
頻道:房間的概念,在同一個頻道內的使用者可以進行即時互動。
主播:可在頻道內發布音視頻流,並可訂閱其他主播發布的音視頻流。
觀眾:可在頻道內訂閱音視頻流,不能發布音視頻流。
下圖展示了實現語音通話及語聊房的基本流程:
使用者需要先調用
joinChannel加入頻道,才能進行推流、拉流:普通純語音通話情境:所有使用者都是主播角色,可以進行推流和拉流;
語聊房情境:需要在頻道內推流的使用者佈建主播角色;如果使用者只需要拉流,不需要推流,則設定觀眾角色;
通過
setClientRole為使用者佈建不同的角色。
加入頻道後,不同角色的使用者有不同的推拉流行為:
所有頻道內的使用者都可以接收相同頻道內的音視頻流;
主播角色可以在頻道內推音視頻流;
觀眾如果需要推流,需要調用
setClientRole方法,將使用者角色切換成主播,便可以推流。
樣本專案
阿里雲ARTC SDK提供了開源的樣本專案供客戶參考,您可以前往下載或查看樣本源碼。
前提條件
在實現功能以前,請確保您的開發環境滿足:
開發工具:Android Studio 2020.3.1 及以上版本。
測試裝置:Android 5.0(SDK API Level 21)及以上版本的測試裝置。
推薦使用真機測試,類比機可能存在功能缺失。
網路環境:需要穩定的網路連接。
應用準備:擷取即時音視頻應用的AppID和AppKey,詳情請參見建立應用。
建立專案和配置:已建立專案並為專案添加了音頻、網路等音視頻互動的相關許可權,此外需要整合 ARTC SDK,相關步驟請參考Android端實現音視訊通話。
實現步驟
下面將以語聊房情境為例進行示範,相關功能時序如下:
語聊房情境主要特點如下:
純音頻:頻道內僅包含音頻,不包含視頻。
主播/觀眾角色:頻道內角色分為主播和觀眾角色,主播角色可以推拉音頻流,觀眾角色只能拉取主播推送的音頻流;觀眾角色可以切換為主播角色。
實現純音頻互動
1、申請許可權請求
進入音視訊通話時,檢查是否已在App中授予了所需要的許可權:
private static final int REQUEST_PERMISSION_CODE = 101;
private static final String[] PERMISSION_MANIFEST = {
Manifest.permission.RECORD_AUDIO,
Manifest.permission.READ_PHONE_STATE,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.CAMERA
};
private static final String[] PERMISSION_MANIFEST33 = {
Manifest.permission.RECORD_AUDIO,
Manifest.permission.READ_PHONE_STATE,
Manifest.permission.CAMERA
};
private static String[] getPermissions() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
return PERMISSION_MANIFEST;
}
return PERMISSION_MANIFEST33;
}
public boolean checkOrRequestPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (ContextCompat.checkSelfPermission(this, "android.permission.CAMERA") != PackageManager.PERMISSION_GRANTED
|| ContextCompat.checkSelfPermission(this, "android.permission.RECORD_AUDIO") != PackageManager.PERMISSION_GRANTED) {
requestPermissions(getPermissions(), REQUEST_PERMISSION_CODE);
return false;
}
}
return true;
}2、鑒權Token
加入ARTC頻道需要一個鑒權Token,用於鑒權使用者的合法身份,其鑒權Token建置規則詳情請參見:Token鑒權。Token 產生有兩種方式:單參數方式和多參數方式,不同的Token產生方式需要調用SDK不同的加入頻道(joinChannel)的介面。
上線發布階段:
由於Token的產生需要使用AppKey,寫死在用戶端存在泄露的風險,因此強烈建議線上業務通過業務Server產生下發給用戶端。
開發調試階段:
開發調試階段,如果業務Server還沒有產生Token的邏輯,可以暫時參考APIExample上的Token產生邏輯,產生臨時Token,其參考代碼如下:
public final class ARTCTokenHelper {
/**
* RTC AppId
*/
public static String AppId = "";
/**
* RTC AppKey
*/
public static String AppKey = "";
/**
* 根據channelId,userId, timestamp, nonce 產生單參數入會 的token
* Generate a single-parameter meeting token based on channelId, userId, and nonce
*/
public static String generateSingleParameterToken(String appId, String appKey, String channelId, String userId, long timestamp, String nonce) {
StringBuilder stringBuilder = new StringBuilder()
.append(appId)
.append(appKey)
.append(channelId)
.append(userId)
.append(timestamp);
String token = getSHA256(stringBuilder.toString());
try{
JSONObject tokenJson = new JSONObject();
tokenJson.put("appid", AppId);
tokenJson.put("channelid", channelId);
tokenJson.put("userid", userId);
tokenJson.put("nonce", nonce);
tokenJson.put("timestamp", timestamp);
tokenJson.put("token", token);
String base64Token = Base64.encodeToString(tokenJson.toString().getBytes(StandardCharsets.UTF_8), Base64.NO_WRAP);
return base64Token;
}catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 根據channelId,userId, timestamp 產生單參數入會 的token
* Generate a single-parameter meeting token based on channelId, userId, and timestamp
*/
public static String generateSingleParameterToken(String appId, String appKey, String channelId, String userId, long timestamp) {
return generateSingleParameterToken(appId, appKey, channelId, userId, timestamp, "");
}
public static String getSHA256(String str) {
try {
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
byte[] hash = messageDigest.digest(str.getBytes(StandardCharsets.UTF_8));
return byte2Hex(hash);
} catch (NoSuchAlgorithmException e) {
// Consider logging the exception and/or re-throwing as a RuntimeException
e.printStackTrace();
}
return "";
}
private static String byte2Hex(byte[] bytes) {
StringBuilder stringBuilder = new StringBuilder();
for (byte b : bytes) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) {
// Use single quote for char
stringBuilder.append('0');
}
stringBuilder.append(hex);
}
return stringBuilder.toString();
}
public static long getTimesTamp() {
return System.currentTimeMillis() / 1000 + 60 * 60 * 24;
}
}3. 建立並初始化引擎
建立 RTC 引擎
調用getInstance建立 RTC 引擎對象。
private AliRtcEngine mAliRtcEngine = null;
if(mAliRtcEngine == null) {
mAliRtcEngine = AliRtcEngine.getInstance(this);
}初始化引擎
調用
setChannelProfile介面設定頻道為互動模式。根據業務情境中使用者的角色,調用
setClientRole介面為使用者佈建主播/觀眾角色。調用
setAudioProfile介面設定音頻品質與情境模式。
// 設定頻道模式為互動模式,RTC下都使用AliRTCSdkInteractiveLive
mAliRtcEngine.setChannelProfile(AliRtcEngine.AliRTCSdkChannelProfile.AliRTCSdkInteractiveLive);
// 設定使用者角色,既需要推流也需要拉流使用AliRTCSdkInteractive, 只拉流不推流使用AliRTCSdkLive
if(isAnchor) {
//如果需要推音視頻流,則設定AliRTCSdkInteractive
mAliRtcEngine.setClientRole(AliRtcEngine.AliRTCSdkClientRole.AliRTCSdkInteractive);
} else {
//如果只需要拉流,不需要推音視頻流,AliRTCSdkLive
mAliRtcEngine.setClientRole(AliRtcEngine.AliRTCSdkClientRole.AliRTCSdkLive);
}
//設定音頻Profile,預設使用高音質模式AliRtcEngineHighQualityMode及音樂模式AliRtcSceneMusicMode
mAliRtcEngine.setAudioProfile(AliRtcEngine.AliRtcAudioProfile.AliRtcEngineHighQualityMode, AliRtcEngine.AliRtcAudioScenario.AliRtcSceneMusicMode);實現常用回調
SDK 在運行過程中如遇到異常情況,會優先嘗試內部重試機制以自動回復。對於無法自行解決的錯誤,SDK 會通過預定義的回調介面通知您的應用程式。
以下是一些 SDK 無法處理、需由應用程式層監聽和響應的關鍵回調:
異常發生原因 | 回調及參數 | 解決方案 | 說明 |
鑒權失敗 |
| 發生錯誤時App需要檢查Token是否正確。 | 在使用者主動調用API時,若鑒權失敗,系統將在調用API的回調中返回鑒權失敗的錯誤資訊。 |
鑒權將要到期 |
| 發生該異常時App需要重新擷取最新的鑒權資訊後,再調用 | 鑒權到期錯誤在兩種情況下出現:使用者調用API或程式執行期間。因此,錯誤反饋將通過API回調或通過獨立的錯誤回調通知。 |
鑒權到期 |
| 發生該異常時App需要重新入會。 | 鑒權到期錯誤在兩種情況下出現:使用者調用API或程式執行期間。因此,錯誤反饋將通過API回調或通過獨立的錯誤回調通知。 |
網路連接異常 |
| 發生該異常時APP需要重新入會。 | SDK具備一定時間斷網自動回復能力,但若斷線時間超出預設閾值,會觸發逾時並中斷連線。此時,App應檢查網路狀態並指導使用者重新加入會議。 |
被踢下線 |
|
| RTC服務提供了管理員可以主動移除參與者的功能。 |
本地裝置異常 |
| 發生該異常時App需要檢測許可權、裝置硬體是否正常。 | RTC服務支援裝置檢測和異常診斷的能力;當本地裝置發生異常時,RTC服務會通過回調的方式通知客戶本地裝置異常,此時,若SDK無法自行解決問題,則App需要介入以查看裝置是否正常。 |
private AliRtcEngineEventListener mRtcEngineEventListener = new AliRtcEngineEventListener() {
@Override
public void onJoinChannelResult(int result, String channel, String userId, int elapsed) {
super.onJoinChannelResult(result, channel, userId, elapsed);
handleJoinResult(result, channel, userId);
}
@Override
public void onLeaveChannelResult(int result, AliRtcEngine.AliRtcStats stats){
super.onLeaveChannelResult(result, stats);
}
@Override
public void onConnectionStatusChange(AliRtcEngine.AliRtcConnectionStatus status, AliRtcEngine.AliRtcConnectionStatusChangeReason reason){
super.onConnectionStatusChange(status, reason);
handler.post(new Runnable() {
@Override
public void run() {
if(status == AliRtcEngine.AliRtcConnectionStatus.AliRtcConnectionStatusFailed) {
/* TODO: 務必處理;建議業務提示客戶,此時SDK內部已經嘗試了各種恢複策略已經無法繼續使用時才會上報 */
ToastHelper.showToast(VideoChatActivity.this, R.string.video_chat_connection_failed, Toast.LENGTH_SHORT);
} else {
/* TODO: 可選處理;增加業務代碼,一般用於資料統計、UI變化 */
}
}
});
}
@Override
public void OnLocalDeviceException(AliRtcEngine.AliRtcEngineLocalDeviceType deviceType, AliRtcEngine.AliRtcEngineLocalDeviceExceptionType exceptionType, String msg){
super.OnLocalDeviceException(deviceType, exceptionType, msg);
/* TODO: 務必處理;建議業務提示裝置錯誤,此時SDK內部已經嘗試了各種恢複策略已經無法繼續使用時才會上報 */
handler.post(new Runnable() {
@Override
public void run() {
String str = "OnLocalDeviceException deviceType: " + deviceType + " exceptionType: " + exceptionType + " msg: " + msg;
ToastHelper.showToast(VideoChatActivity.this, str, Toast.LENGTH_SHORT);
}
});
}
};
private AliRtcEngineNotify mRtcEngineNotify = new AliRtcEngineNotify() {
@Override
public void onAuthInfoWillExpire() {
super.onAuthInfoWillExpire();
/* TODO: 務必處理;Token即將到期,需要業務觸發重新擷取當前channel,user的鑒權資訊,然後設定refreshAuthInfo即可 */
}
@Override
public void onRemoteUserOnLineNotify(String uid, int elapsed){
super.onRemoteUserOnLineNotify(uid, elapsed);
}
//在onRemoteUserOffLineNotify回調中解除遠端視頻流渲染控制項的設定
@Override
public void onRemoteUserOffLineNotify(String uid, AliRtcEngine.AliRtcUserOfflineReason reason){
super.onRemoteUserOffLineNotify(uid, reason);
}
//在onRemoteTrackAvailableNotify回調中設定遠端視頻流渲染控制項
@Override
public void onRemoteTrackAvailableNotify(String uid, AliRtcEngine.AliRtcAudioTrack audioTrack, AliRtcEngine.AliRtcVideoTrack videoTrack){
handler.post(new Runnable() {
@Override
public void run() {
if(videoTrack == AliRtcVideoTrackCamera) {
SurfaceView surfaceView = mAliRtcEngine.createRenderSurfaceView(VideoChatActivity.this);
surfaceView.setZOrderMediaOverlay(true);
FrameLayout view = getAvailableView();
if (view == null) {
return;
}
remoteViews.put(uid, view);
view.addView(surfaceView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
AliRtcEngine.AliRtcVideoCanvas remoteVideoCanvas = new AliRtcEngine.AliRtcVideoCanvas();
remoteVideoCanvas.view = surfaceView;
mAliRtcEngine.setRemoteViewConfig(remoteVideoCanvas, uid, AliRtcVideoTrackCamera);
} else if(videoTrack == AliRtcVideoTrackNo) {
if(remoteViews.containsKey(uid)) {
ViewGroup view = remoteViews.get(uid);
if(view != null) {
view.removeAllViews();
remoteViews.remove(uid);
mAliRtcEngine.setRemoteViewConfig(null, uid, AliRtcVideoTrackCamera);
}
}
}
}
});
}
/* 業務可能會觸發同一個UserID的不同裝置搶佔的情況,所以這個地方也需要處理 */
@Override
public void onBye(int code){
handler.post(new Runnable() {
@Override
public void run() {
String msg = "onBye code:" + code;
ToastHelper.showToast(VideoChatActivity.this, msg, Toast.LENGTH_SHORT);
}
});
}
};
// 設定回調
mAliRtcEngine.setRtcEngineEventListener(mRtcEngineEventListener);
mAliRtcEngine.setRtcEngineNotify(mRtcEngineNotify);4. 設定推拉流屬性
SDK 預設情況下會自動推送和拉取頻道內的音視頻流
設定為觀眾模式後只能拉流,
publishLocalAudioStream無效。對於主播和觀眾均可以設定為下面的配置。
//SDK預設會publish音頻,對於觀眾此介面無效
mAliRtcEngine.publishLocalAudioStream(true);
//語聊情境,不需要publish視頻
mAliRtcEngine.publishLocalVideoStream(false);
//設定預設訂閱遠端的音頻
mAliRtcEngine.setDefaultSubscribeAllRemoteAudioStreams(true);
mAliRtcEngine.subscribeAllRemoteAudioStreams(true);5. 加入頻道開始純音頻互動
調用joinChannel介面加入頻道。
如果token是單參數規則產生的,需要調用SDK單參數的joinChannel[1/3]介面,如果是多參數規則產生的,需要調用SDK多參數的joinChannel[2/3]介面。調用完加入頻道後,可以在onJoinChannelResult回調中拿到加入頻道結果,如果result為0,則表示加入頻道成功,否則需要檢查傳進來的Token是否非法。
mAliRtcEngine.joinChannel(token, null, null, null);6. 結束純音頻互動
音頻互動結束,需要離開房間並銷毀引擎,按照下列步驟結束音視頻互動
調用
leaveChannel離會。調用
destroy銷毀引擎,並釋放相關資源。
mAliRtcEngine.leaveChannel();
mAliRtcEngine.destroy();
mAliRtcEngine = null;7. (可選)觀眾上下麥
業務情境中,如果觀眾角色的使用者想要推流,需要調用setClientRole將觀眾角色切換為主播角色。
// 切換為主播角色
mAliRtcEngine.setClientRole(AliRtcEngine.AliRTCSdkClientRole.AliRTCSdkInteractive);
// 切換為觀眾角色
mAliRtcEngine.setClientRole(AliRtcEngine.AliRTCSdkClientRole.AliRTCSdkLive);相關文檔
有關音訊更多操作,例如耳返、音量和說話人回調等,請參考音頻常用操作和配置。
設定人聲效果,例如變聲、美聲、混響等,請參考設定變聲、混響、美聲。
如果需要播放背景音樂、伴奏音樂檔案等,請參考播放與推流外部輸入音頻(包括音效、伴奏)。