使用 Agora 為Android APP新增視訊直播

聲網 發表於 2022-05-20
Android Go

在這裡插入圖片描述

add-live-streaming-to-your-android-app-using-agora-featured1024×512 121 KB

視訊互動直播是當前比較熱門的玩法,我們經常見到有PK 連麥、直播答題、一起 KTV、電商直播、互動大班課、視訊相親等。

本文將演示如何通過聲網Agora 視訊 SDK 在 Android 端實現一個視訊直播應用。註冊聲網賬號後,開發者每個月可獲得 10000 分鐘的免費使用額度,可實現各類實時音視訊場景。

話不多說,我們開始動手實操。

一些前提條件

一、 通過開源Demo,體驗視訊直播

可能有些人,還不瞭解我們要實現的功能最後是怎樣的。所以我們在 GitHub上提供一個開源的基礎視訊直播示例專案,在開始開發之前你可以通過該示例專案體驗視訊直播的體驗效果。
Github:GitHub - Meherdeep/agora-android-live-streaming 1

在這裡插入圖片描述

588×1228 79.9 KB

在這裡,我新增了兩個直播流,同時可以讓多個觀眾訂閱它。

二、 視訊直播的技術原理

我們在這裡要實現的是視訊直播,Agora 的視訊直播可以實現互動效果,所以也經常叫互動直播。你可以理解為是多個使用者通過加入同一個頻道,實現的音視訊的互通,而這個頻道的資料,會通過聲網的 Agora SD-RTN 實時網路來進行低延時傳輸的。

需要特別說明的是,Agora互動直播不同於視訊通話。視訊通話不區分主播和觀眾,所有使用者都可以發言並看見彼此;而互動直播的使用者分為主播和觀眾,只有主播可以自由發言,且被其他使用者看見。
下圖展示在 App 中整合 Agora 互動直播的基本工作流程:
image

實現互動直播的步驟如下:

1.設定角色:互動直播頻道中,使用者角色可以是主播或者觀眾。主播在頻道內釋出音視訊流,觀眾僅可訂閱音視訊流。

2.獲取 Token:當 App 客戶端加入頻道時,你需要通過 Token 驗證使用者身份。App 客戶端向 App 伺服器傳送請求,並獲取 Token,然後在客戶端加入頻道時驗證使用者身份。

3.加入頻道:呼叫 joinChannel 建立並加入頻道。使用同一頻道名稱的 App 客戶端預設加入同一頻道。

4.在頻道內釋出和訂閱音視訊:加入頻道後,角色為主播的 App 客戶端可以釋出音視訊。對於角色為觀眾的客戶端,如果想要釋出音視訊,可以呼叫 setClientRole 切換使用者角色。

App 客戶端加入頻道需要以下資訊:

  • 頻道名稱:用於標識直播頻道的字串。
  • App ID:Agora 隨機生成的字串,用於識別你的 App,可從 Agora 控制檯獲取,(Agora控制檯連結:Dashboard
  • 使用者ID:使用者的唯一標識。你需要自行設定使用者 ID,並確保它在頻道內是唯一的。
  • Token:在測試或生產環境中,你的 App 客戶端會從你的伺服器中獲取 Token。為方便快速測試,你也可以獲取臨時 Token。臨時 Token 的有效期為 24 小時。

三、 開發環境

聲網Agora SDK 的相容性良好,對硬體裝置和軟體系統的要求不高,開發環境和測試環境滿足以下條件即可:
• Android SDK API Level >= 16
• Android Studio 2.0 或以上版本
• 支援語音和視訊功能的真機
• App 要求 Android 4.1 或以上裝置

以下是本文的開發環境和測試環境:

開發環境

• Windows 10 家庭中文版
• Java Version SE 8
• Android Studio 3.2 Canary 4

測試環境

• Samsung Nexus (Android 4.4.2 API 19)
• Mi Note 3 (Android 7.1.1 API 25)

如果你此前還未接觸過聲網 Agora SDK,那麼你還需要做以下準備工作:

• 註冊一個聲網賬號,進入後臺建立 AppID、獲取 Token,詳細方法可參考這篇教程;(這篇教程:404 - 知乎
• 下載聲網官方最新的互動直播SDK;(互動直播SDK連結:下載 - 全部產品 - 文件中心 - 聲網Agora

四、 專案設定

1. 實現互動直播之前,參考如下步驟設定你的專案:

如需建立新專案,在 Android Studio裡,依次選擇 Phone and Tablet > Empty Activity,建立 Android 專案。(建立 Android 專案連結:https://developer.android.com...
建立專案後,Android Studio會自動開始同步 gradle。請確保同步成功再進行下一步操作。

2. 整合SDK, 本文推薦使用gradle方式整合Agora SDK:

a. 在 /Gradle Scripts/build.gradle(Project: ) 檔案中新增如下程式碼,以新增 jcenter依賴:

buildscript {
     repositories {
         ...
         jcenter()
     }
     ...
}
 
  allprojects {
     repositories {
         ...
         jcenter()
     }
}

b. 在 /Gradle Scripts/build.gradle(Module: .App) 檔案中新增如下程式碼,將 Agora 視訊 SDK 整合到你的 Android 專案中:

...
dependencies {
 ...
 // x.y.z,請填寫具體的 SDK 版本號,如:3.5.0。
 // 通過發版說明獲取最新版本號。
 implementation 'io.agora.rtc:full-sdk:x.y.z'
//本例使用佈局相關設定constraintlayout
implementation  'androidx.constraintlayout:constraintlayout:2.0.4'
}

3. 許可權設定

在 /App/Manifests/AndroidManifest.xml 檔案中的 `` 後面新增如下網路和裝置許可權:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.BLUETOOTH" />

4. 匯入Agora相關的類

在/app/src/main/java/com/agora/samtan/agorabroadcast/VideoActivity檔案中,加入如下程式碼:

package com.agora.samtan.agorabroadcast;
import io.agora.rtc.Constants;
import io.agora.rtc.IRtcEngineEventHandler;
import io.agora.rtc.RtcEngine;
import io.agora.rtc.video.VideoCanvas;
import io.agora.rtc.video.VideoEncoderConfiguration;

5. 設定Agora賬號資訊

在/app/src/main/res/values/strings.xml檔案中,將你的AppID填寫到private_App_id中:

<resources>
    ……
<string name="private_App_id">填寫位置</string>
……
</resources>

五、 客戶端實現

本節介紹如何使用Agora視訊SDK在你的App裡實現視訊直播的幾個小貼士:

1. 檢查並獲取必要許可權

啟動應用程式時,檢查是否已在App中授予了實現視訊直播所需的許可權。在onCreate函式中呼叫如下程式碼:

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        int MY_PERMISSIONS_REQUEST_CAMERA = 0;
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO}, MY_PERMISSIONS_REQUEST_CAMERA);

        }
}

2. 實現互動直播邏輯

開啟你的App,建立RtcEngine例項,啟用視訊後加入頻道。如果本地使用者是主播,則將本地視訊釋出到使用者介面下方的檢視中。如果另一主播加入該頻道,你的App會捕捉到這一加入事件,並將遠端視訊新增到使用者介面右上角的檢視中。
互動直播的API使用時序見下圖:

在這裡插入圖片描述

image822×1048 106 KB

按照以下步驟實現該邏輯:

a) 初始化RtcEngine
RtcEngine類包含應用程式呼叫的主要方法,呼叫RtcEngine的介面最好在同一個執行緒進行,不建議在不同的執行緒同時呼叫。

目前Agora Native SDK只支援一個RtcEngine例項,每個應用程式僅建立一個RtcEngine物件。RtcEngine類的所有介面函式,如無特殊說明,都是非同步呼叫,對介面的呼叫建議在同一個執行緒進行。所有返回值為int型的API,如無特殊說明,返回值0為呼叫成功,返回值小於0為呼叫失敗。

在VideoActivity檔案中,通過initializeAgoraEngine用於初始化RtcEngine的方法:

    private void initalizeAgoraEngine() {
        try {
            mRtcEngine = RtcEngine.create(getBaseContext(), getString(R.string.private_App_id), mRtcEventHandler);
        } catch (Exception e) {
            e.printStackTrace();
        }
}

另外,有個重要的IRtcEngineEventHandler介面類用於SDK嚮應用程式傳送回撥事件通知,應用程式通過繼承該介面類的方法獲取SDK的事件通知。

介面類的所有方法都有預設(空)實現,應用程式可以根據需要只繼承關心的事件。在回撥方法中,應用程式不應該做耗時或者呼叫可能會引起阻塞的API(如SendMessage),否則可能影響SDK的執行。內容如下:

private final IRtcEngineEventHandler iRtcEngineEventHandler = new IRtcEngineEventHandler()
{
    /**Reports a warning during SDK runtime.
     * Warning code: https://docs.agora.io/en/Voice/API%20Reference/java/classio_1_1agora_1_1rtc_1_1_i_rtc_engine_event_handler_1_1_warn_code.html*/
    @Override
    public void onWarning(int warn)
    {
        Log.w(TAG, String.format("onWarning code %d message %s", warn, RtcEngine.getErrorDescription(warn)));
    }
 
    /**Reports an error during SDK runtime.
     * Error code: https://docs.agora.io/en/Voice/API%20Reference/java/classio_1_1agora_1_1rtc_1_1_i_rtc_engine_event_handler_1_1_error_code.html*/
    @Override
    public void onError(int err)
    {
        Log.e(TAG, String.format("onError code %d message %s", err, RtcEngine.getErrorDescription(err)));
        showAlert(String.format("onError code %d message %s", err, RtcEngine.getErrorDescription(err)));
    }
 
    /**Occurs when a user leaves the channel.
     * @param stats With this callback, the Application retrieves the channel information,
     *              such as the call duration and statistics.*/
    @Override
    public void onLeaveChannel(RtcStats stats)
    {
        super.onLeaveChannel(stats);
        Log.i(TAG, String.format("local user %d leaveChannel!", myUid));
        showLongToast(String.format("local user %d leaveChannel!", myUid));
    }
 
    /**Occurs when the local user joins a specified channel.
     * The channel name assignment is based on channelName specified in the joinChannel method.
     * If the uid is not specified when joinChannel is called, the server automatically assigns a uid.
     * @param channel Channel name
     * @param uid User ID
     * @param elapsed Time elapsed (ms) from the user calling joinChannel until this callback is triggered*/
    @Override
    public void onJoinChannelSuccess(String channel, int uid, int elapsed)
    {
        Log.i(TAG, String.format("onJoinChannelSuccess channel %s uid %d", channel, uid));
        showLongToast(String.format("onJoinChannelSuccess channel %s uid %d", channel, uid));
        myUid = uid;
        joined = true;
        handler.post(new Runnable()
        {
            @Override
            public void run()
            {
                join.setEnabled(true);
                join.setText(getString(R.string.leave));
            }
        });
    }
 
    @Override
    public void onRemoteAudioStats(io.agora.rtc.IRtcEngineEventHandler.RemoteAudioStats remoteAudioStats) {
        statisticsInfo.setRemoteAudioStats(remoteAudioStats);
        updateRemoteStats();
    }
 
    @Override
    public void onLocalAudioStats(io.agora.rtc.IRtcEngineEventHandler.LocalAudioStats localAudioStats) {
        statisticsInfo.setLocalAudioStats(localAudioStats);
        updateLocalStats();
    }
 
    @Override
    public void onRemoteVideoStats(io.agora.rtc.IRtcEngineEventHandler.RemoteVideoStats remoteVideoStats) {
        statisticsInfo.setRemoteVideoStats(remoteVideoStats);
        updateRemoteStats();
    }
 
    @Override
    public void onLocalVideoStats(io.agora.rtc.IRtcEngineEventHandler.LocalVideoStats localVideoStats) {
        statisticsInfo.setLocalVideoStats(localVideoStats);
        updateLocalStats();
    }
 
    @Override
    public void onRtcStats(io.agora.rtc.IRtcEngineEventHandler.RtcStats rtcStats) {
        statisticsInfo.setRtcStats(rtcStats);
    }
};

所以,在我們的initialize函式中,我們將mRtcEventHandler作為引數之一傳遞給了create方法,這設定了一系列回撥事件,每當使用者加入頻道或離開頻道時就會觸發這些事件。

private IRtcEngineEventHandler mRtcEventHandler = new IRtcEngineEventHandler() {

        @Override
        public void onUserJoined(final int uid, int elapsed) {
            super.onUserJoined(uid, elapsed);
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    setupRemoteVideo(uid);
                }
            });
        }

        @Override
        public void onUserOffline(int uid, int reason) {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    onRemoteUserLeft();
                }
            });
        }
    };

b) 設定頻道場景和角色
setChannelProfile()是一個使用我們AgoraRtcEngine物件引用的方法。Agora提供了各種配置檔案,可以通過該方法呼叫並整合到應用中。

setClientRole()方法,將使用者的角色設定為主播或觀眾(預設)。這個方法應該在加入頻道之前呼叫。加入頻道後可以再次呼叫,切換客戶端角色。

為了方便體驗互動直播中主播角色和觀眾角色的效果,我們將在我們的MainActivity類中新增兩個方法:
• 當使用者從單選按鈕中選擇一個選項時,將呼叫第一個方法。我們將相應地設定一個變數。我們將其設定為一個值,該值將確定使用者是主播還是觀眾。

public void onRadioButtonClicked(View view) {
        boolean checked = ((RadioButton) view).isChecked();
        switch (view.getId()) {
            case R.id.host:
                if (checked) {
                    channelProfile = Constants.CLIENT_ROLE_BROADCASTER;
                }
                break;
            case R.id.audience:
                if (checked) {
                    channelProfile = Constants.CLIENT_ROLE_AUDIENCE;
                }
                break;
        }
}

• 然後我們實現一個在使用者提交詳細資訊時呼叫的函式。在這裡,我們將獲得我們需要的所有詳細資訊,並將它們傳送到下一個activity。

public void onSubmit(View view) {
        EditText channel = (EditText) findViewById(R.id.channel);
        String channelName = channel.getText().toString();
        Intent intent = new Intent(this, VideoActivity.class);
        intent.putExtra(channelMessage, channelName);
        intent.putExtra(profileMessage, channelProfile);
        startActivity(intent);
}

c) 開始視訊
setupVideoProfile()函式用於定義視訊需要渲染的方式。你可以對幀速率、位元率、方向、映象模式和降級偏好等屬性使用自己的自定義配置。

private void setupVideoProfile() {
        mRtcEngine.enableVideo();

        mRtcEngine.setVideoEncoderConfiguration(new VideoEncoderConfiguration(VideoEncoderConfiguration.VD_640x480, VideoEncoderConfiguration.FRAME_RATE.FRAME_RATE_FPS_15,
                VideoEncoderConfiguration.STANDARD_BITRATE,
                VideoEncoderConfiguration.ORIENTATION_MODE.ORIENTATION_MODE_FIXED_PORTRAIT));
}

d) 設定本地視訊
setupLocalVideo()函式用於從我們的AgoraRtcEngine中引用setupLocalVideo方法,我們通過它為我們的本地使用者設定一個在直播流中使用的表面檢視:

private void setupLocalVideo() {
        FrameLayout container = (FrameLayout) findViewById(R.id.local_video_view_container);
        SurfaceView surfaceView = RtcEngine.CreateRendererView(getBaseContext());
        surfaceView.setZOrderMediaOverlay(true);
        container.addView(surfaceView);
        mRtcEngine.setupLocalVideo(new VideoCanvas(surfaceView, VideoCanvas.RENDER_MODE_FIT, 0));
    }

e) 加入頻道
頻道是人們在同一個視訊通話中的公共空間。joinChannel()方法可以這樣呼叫:

private void joinChannel() {
        mRtcEngine.joinChannel(token, channelName, "Optional Data", 0);
}

該方法需要四個引數才能成功執行:
• Token:建議對在生產環境中執行的所有RTE APP進行Token身份驗證。更多關於聲網Agora平臺基於令牌的認證資訊,請參見https://docs.agora.io/cn/Vide...
• 頻道名稱:需要一個字串,讓使用者進入視訊通話。
• 可選資訊:這是一個可選欄位,你可以通過它傳遞有關頻道的其他資訊。
• uid:每個加入頻道的使用者的唯一ID。如果傳入0或null值,Agora會自動為每個使用者分配一個uid。

注意:此專案僅供參考和開發環境使用,不適用於生產環境。建議對在生產環境中執行的所有RTE APP進行Token身份驗證。

本例中初始化App,呼叫核心方法來建立並加入Agora直播頻道。在VideoActivity檔案中,在onCreate函式後新增如下程式碼:

package com.agora.samtan.agorabroadcast;
import android.content.Intent;
import android.graphics.PorterDuff;
import android.os.Bundle;
import android.util.Log;
import android.view.SurfaceView;
import android.view.View;   ;//;.;
import android.widget.FrameLayout;
import android.widget.ImageView;
import androidx.Appcompat.App.AppCompatActivity;
import io.agora.rtc.Constants;
import io.agora.rtc.IRtcEngineEventHandler;
import io.agora.rtc.RtcEngine;
import io.agora.rtc.video.VideoCanvas;
import io.agora.rtc.video.VideoEncoderConfiguration;

public class VideoActivity extends AppCompatActivity {
  private RtcEngine mRtcEngine;
  private String channelName;
  private int channelProfile;
  
  @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_video);

        Intent intent = getIntent();
        channelName = intent.getStringExtra(MainActivity.channelMessage);
        channelProfile = intent.getIntExtra(MainActivity.profileMessage, -1);

        if (channelProfile == -1) {
            Log.e("TAG: ", "No profile");
        }

        initAgoraEngineAndJoinChannel();
    }

}

我們宣佈了一個名為initAgoraEngineAndJoinChannel的方法,它將呼叫直播過程中所需的所有其他方法。我們還定義了事件處理程式,它將決定當遠端使用者加入或離開或靜音時呼叫哪些方法。

private void initAgoraEngineAndJoinChannel() {
        initalizeAgoraEngine();
        mRtcEngine.setChannelProfile(Constants.CHANNEL_PROFILE_LIVE_BROADCASTING);
        mRtcEngine.setClientRole(channelProfile);
        setupVideoProfile();
        setupLocalVideo();
        joinChannel();
}

f) 當遠端主播加入頻道時新增遠端介面
在VideoActivity檔案中,initializeAndJoinChannel函式後加入如下程式碼:

    private void setupRemoteVideo(int uid) {
        FrameLayout container = (FrameLayout) findViewById(R.id.remote_video_view_container);
        SurfaceView surfaceView = RtcEngine.CreateRendererView(getBaseContext());
        container.addView(surfaceView);
        mRtcEngine.setupRemoteVideo(new VideoCanvas(surfaceView, VideoCanvas.RENDER_MODE_FIT, uid));
    }

g) 釋放資源
最後,我們新增onDestroy方法來釋放我們使用過的資源。相關程式碼如下:

@Override
    protected void onDestroy() {
        super.onDestroy();

        leaveChannel();
        RtcEngine.destroy();
        mRtcEngine = null;
}

至此,完成,執行看看效果。拿兩部手機安裝編譯好的App,加入同一個頻道名,分別選擇主播角色和觀眾角色,如果2個手機都能看見同一個自己,說明你成功了。

如果你在開發過程中遇到問題,可以訪問論壇提問與聲網工程師交流(連結:https://rtcdeveloper.agora.io/) 1
也可以訪問後臺獲取更進一步的技術支援(連結:Agora Support 1