乐趣区

关于android:如何基于-Agora-Android-SDK-在应用中实现视频通话

在很多产品,实时视频通话曾经不是陈腐的性能了,例如视频会议、社交利用、在线教育,甚至也可能呈现在一些元宇宙的场景中。

本文将教你如何通过声网 Agora 视频 SDK 在 Android 端实现一个视频通话利用。声网 SDK 每个月会提供 10000 分钟的收费应用额度,可实现各类实时音视频场景。话不多说,咱们开始入手实操。

通过开源 Demo,体验视频通话

可能有些人,还不理解咱们要实现的性能最初是怎么的。所以咱们在 GitHub 上提供一个开源的根底视频通话示例我的项目,在开始开发之前你能够通过该示例我的项目体验音视频通话成果。

Github:https://github.com/AgoraIO/API-Examples/blob/master/Android/APIExample/app/src/main/java/io/agora/api/example/examples/basic/JoinChannelVideo.java

视频通话的技术原理

咱们在这里要实现的是一对一的视频通话。你能够了解为是两个用户通过退出同一个频道,实现的音视频的互通。而这个频道的数据,会通过声网的 Agora SD-RTN 实时网络来进行低延时传输的。

那么 App 集成 Agora SDK 后,视频通话的根本工作流程如下图所示:

如图所示,实现视频通话的步骤如下:

  1. 获取 Token:当 app 客户端退出频道时,你须要应用 Token 验证用户身份。在测试或生产环境中,从 app 服务器中获取 Token。
  2. 退出频道:调用 joinChannel 创立并退出频道。应用同一频道名称的 app 客户端默认退出同一频道。频道可了解为专用于传输实时音视频数据的通道。
  3. 在频道内公布和订阅音视频流:退出频道后,app 客户端均能够在频道内公布和订阅音视频。

App 客户端退出频道须要以下信息:

  • App ID:Agora 随机生成的字符串,用于辨认你的 app,可从 Agora 控制台获取,具体办法可见这篇教程。
  • 用户 ID:用户的惟一标识。你须要自行设置用户 ID,并确保它在频道内是惟一的。
  • Token:在测试或生产环境中,app 客户端从你的服务器中获取 Token。在本文介绍的流程中,你能够从 Agora 控制台获取长期 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,具体办法可参考这篇教程;

https://sso2.agora.io/cn/signup?

  • 下载声网官网最新的 视频 SDK

我的项目设置

1. 实现视频通话之前,参考如下步骤设置你的我的项目:

如需创立新我的项目,在 Android Studio 里,顺次抉择 Phone and Tablet > Empty Activity,创立 Android 我的项目。

创立我的项目后,Android Studio 会主动开始同步 gradle。请确保同步胜利再进行下一步操作。

2. 集成 SDK, 本文举荐应用 gradle 形式集成 Agora SDK:

a. 在 /Gradle Scripts/build.gradle(Project:) 文件中增加如下代码,以增加 mavenCentral 依赖:

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

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'
}

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" />

客户端实现

本节介绍如何应用 Agora 视频 SDK 在你的 app 里实现视频通话的几个小贴士:

1. 获取必要权限

启动应用程序时,查看是否已在 app 中授予了实现视频通话所需的权限。

onCreate 函数中调用如下代码:

private static final int PERMISSION_REQ_ID = 22;
 
private static final String[] REQUESTED_PERMISSIONS = {
     Manifest.permission.RECORD_AUDIO,
     Manifest.permission.CAMERA
};
 
private boolean checkSelfPermission(String permission, int requestCode) {if (ContextCompat.checkSelfPermission(this, permission) !=
            PackageManager.PERMISSION_GRANTED) {ActivityCompat.requestPermissions(this, REQUESTED_PERMISSIONS, requestCode);
        return false;
    }
    return true;
}

2. 实现视频通话逻辑

下图展现视频通话的 API 调用时序:

a. 初始化引擎

RtcEngine 类蕴含应用程序调用的次要办法,调用 RtcEngine 的接口最好在同一个线程进行,不倡议在不同的线程同时调用。

目前 Agora Native SDK 只反对一个 RtcEngine 实例,每个应用程序仅创立一个 RtcEngine 对象。RtcEngine 类的所有接口函数,如无非凡阐明,都是异步调用,对接口的调用倡议在同一个线程进行。所有返回值为 int 型的 API,如无非凡阐明,返回值 0 为调用胜利,返回值小于 0 为调用失败。

IRtcEngineEventHandler 接口类用于 SDK 向应用程序发送回调事件告诉,应用程序通过继承该接口类的办法获取 SDK 的事件告诉。

接口类的所有办法都有缺省(空)实现,应用程序能够依据须要只继承关怀的事件。在回调办法中,应用程序不应该做耗时或者调用可能会引起阻塞的 API(如 SendMessage),否则可能影响 SDK 的运行。

engine = RtcEngine.create(context.getApplicationContext(), getString(R.string.agora_app_id), iRtcEngineEventHandler);
...
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);
    }
};

b. 设置本地视频参数

enableVideo() 办法用于关上视频模式。能够在退出频道前或者通话中调用,在退出频道前调用,则主动开启视频模式,在通话中调用则由音频模式切换为视频模式。调用 disableVideo() 办法可敞开视频模式。

setupLocalVideo(VideoCanvas local) 办法用于设置本地视频显示信息。应用程序通过调用此接口绑定本地视频流的显示视窗 (view),并设置视频显示模式。在利用程序开发中,通常在初始化后调用该办法进行本地视频设置,而后再退出频道。退出频道后,绑定依然无效,如果须要解除绑定,能够调用 setupLocalVideo(null)。

// Create render view by RtcEngine
SurfaceView surfaceView = RtcEngine.CreateRendererView(context);
if(fl_local.getChildCount() > 0)
{fl_local.removeAllViews();
}
// Add to the local container
fl_local.addView(surfaceView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
// Setup local video to render your local camera preview
engine.setupLocalVideo(new VideoCanvas(surfaceView, RENDER_MODE_HIDDEN, 0));
// Enable video module
engine.enableVideo();

c . 退出一个频道

joinChannel() 办法让用户退出通话频道,在同一个频道内的用户能够相互通话,多个用户退出同一个频道,能够群聊。应用不同 App ID 的应用程序是不能互通的。如果已在通话中,用户必须调用 leaveChannel() 退出以后通话,能力进入下一个频道。

ChannelMediaOptions option = new ChannelMediaOptions();
option.autoSubscribeAudio = true;
option.autoSubscribeVideo = true;
int res = engine.joinChannel(accessToken, channelId, "Extra Optional Data", 0, option);
if (res != 0)
{
    // Usually happens with invalid parameters
    // Error code description can be found at:
    // en: 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
    // cn: https://docs.agora.io/cn/Voice/API%20Reference/java/classio_1_1agora_1_1rtc_1_1_i_rtc_engine_event_handler_1_1_error_code.html
    showAlert(RtcEngine.getErrorDescription(Math.abs(res)));
    return;
}

d. 来到以后频道

leaveChannel() 办法用于来到频道,即挂断或退出通话。

当调用 joinChannel() API 办法后,必须调用 leaveChannel() 完结通话,否则无奈开始下一次通话。不论以后是否在通话中,都能够调用 leaveChannel(),没有副作用。该办法会把会话相干的所有资源开释掉。该办法是异步操作,调用返回时并没有真正退出频道。在真正退出频道后,SDK 会触发 onLeaveChannel 回调。

e. 治理摄像头

switchCamera() 办法用于在前置 / 后置摄像头间切换。除此以外 Agora 还提供了一下治理摄像头的办法:例如 setCameraTorchOn(boolean isOn) 设置是否关上闪光灯、setCameraAutoFocusFaceModeEnabled(boolean enabled) 设置是否开启人脸对焦性能等等。

f. 当远端用户退出频道时,更新远端用户界面。

private final IRtcEngineEventHandler iRtcEngineEventHandler = new IRtcEngineEventHandler()
{
    ...
    /**Occurs when a remote user (Communication)/host (Live Broadcast) joins the channel.
     * @param uid ID of the user whose audio state changes.
     * @param elapsed Time delay (ms) from the local user calling joinChannel/setClientRole
     *                until this callback is triggered.*/
    @Override
    public void onUserJoined(int uid, int elapsed)
    {super.onUserJoined(uid, elapsed);
        Log.i(TAG, "onUserJoined->" + uid);
        showLongToast(String.format("user %d joined!", uid));
        /**Check if the context is correct*/
        Context context = getContext();
        if (context == null) {return;}
        if(remoteViews.containsKey(uid)){return;}
        else{handler.post(() ->
            {
                /**Display remote video stream*/
                SurfaceView surfaceView = null;
                // Create render view by RtcEngine
                surfaceView = RtcEngine.CreateRendererView(context);
                surfaceView.setZOrderMediaOverlay(true);
                ViewGroup view = getAvailableView();
                remoteViews.put(uid, view);
                // Add to the remote container
                view.addView(surfaceView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
                // Setup remote video to render
                engine.setupRemoteVideo(new VideoCanvas(surfaceView, RENDER_MODE_HIDDEN, uid));
            });
        }
    }
 
    /**Occurs when a remote user (Communication)/host (Live Broadcast) leaves the channel.
     * @param uid ID of the user whose audio state changes.
     * @param reason Reason why the user goes offline:
     *   USER_OFFLINE_QUIT(0): The user left the current channel.
     *   USER_OFFLINE_DROPPED(1): The SDK timed out and the user dropped offline because no data
     *              packet was received within a certain period of time. If a user quits the
     *               call and the message is not passed to the SDK (due to an unreliable channel),
     *               the SDK assumes the user dropped offline.
     *   USER_OFFLINE_BECOME_AUDIENCE(2): (Live broadcast only.) The client role switched from
     *               the host to the audience.*/
    @Override
    public void onUserOffline(int uid, int reason)
    {Log.i(TAG, String.format("user %d offline! reason:%d", uid, reason));
        showLongToast(String.format("user %d offline! reason:%d", uid, reason));
        handler.post(new Runnable() {
            @Override
            public void run() {
                /**Clear render view
                 Note: The video will stay at its last frame, to completely remove it you will need to
                 remove the SurfaceView from its parent*/
                engine.setupRemoteVideo(new VideoCanvas(null, RENDER_MODE_HIDDEN, uid));
                remoteViews.get(uid).removeAllViews();
                remoteViews.remove(uid);
            }
        });
    }
    ...
};

实现,运行

拿两部手机装置编译好的 App,如果能看见两个本人,阐明你胜利了。如果你在开发过程中遇到问题,能够拜访论坛发问与声网工程师交换

https://rtcdeveloper.agora.io/

也能够拜访后盾获取更进一步的技术支持

https://console.agora.io/

退出移动版