本文介绍如何使用 Agora 视频 SDK 快速实现视频通话。
本节介绍如何创建项目,将 Agora 视频 SDK 集成进你的项目中,并添加相应的设备权限。
参考以下步骤创建一个 Android 项目。若已有 Android 项目,可以直接查看集成 SDK。
然后点击 Finish。根据屏幕提示,安装可能需要的插件。
选择如下任意一种方式将 Agora 视频 SDK 集成到你的项目中。
方法一:使用 JitPack 自动集成
在项目根目录的 build.gradle 文件中,添加如下行:
...
allprojects {
repositories {
...
maven { url 'https://www.jitpack.io' }
}
}
在 /app/build.gradle 中添加如下依赖:
...
dependencies {
...
// x.y.z 请填写具体版本号,如:3.4.0
// 可通过 SDK 发版说明取得最新版本号
implementation 'com.github.agorabuilder:native-full-sdk:x.y.z'
}
方法二:手动复制 SDK 文件
文件或文件夹 | 项目路径 |
---|---|
agora-rtc-sdk.jar 文件 | /app/libs/ |
arm64-v8a 文件夹 | /app/src/main/jniLibs/ |
armeabi-v7a 文件夹 | /app/src/main/jniLibs/ |
include 文件夹 | /app/src/main/jniLibs/ |
x86 文件夹 | /app/src/main/jniLibs/ |
x86_64 文件夹 | /app/src/main/jniLibs/ |
libagora-crypto.so
文件。extension
的库是可选项,请按需集成。你可以在发版说明中查看扩展库对应的功能。根据场景需要,在 /app/src/main/AndroidManifest.xml 文件中添加如下行,获取相应的设备权限:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="io.agora.tutorials1v1acall">
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />
<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" />
...
</manifest>
在 app/proguard-rules.pro 文件中添加如下行,防止混淆 Agora SDK 的代码:
-keep class io.agora.**{*;}
本节介绍如何实现视频通话。视频通话的 API 调用时序见下图:
根据场景需要,为你的项目创建视频通话的用户界面。若已有界面,可以直接查看导入类。
我们推荐你添加如下 UI 元素来实现一个视频通话,
你也可以参考 Agora-Android-Tutorial-1to1 示例项目的 activity_video_chat_view.xml 文件中的代码。
<?xml version="1.0" encoding="UTF-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_video_chat_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="io.agora.tutorials1v1vcall.VideoChatViewActivity">
<RelativeLayout
android:id="@+id/remote_video_view_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/remoteBackground">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@id/icon_padding">
<ImageView
android:layout_width="@dimen/remote_back_icon_size"
android:layout_height="@dimen/remote_back_icon_size"
android:layout_centerInParent="true"
android:src="@drawable/icon_agora_largest"/>
</RelativeLayout>
<RelativeLayout
android:id="@+id/icon_padding"
android:layout_width="match_parent"
android:layout_height="@dimen/remote_back_icon_margin_bottom"
android:layout_alignParentBottom="true"/>
</RelativeLayout>
<FrameLayout
android:id="@+id/local_video_view_container"
android:layout_width="@dimen/local_preview_width"
android:layout_height="@dimen/local_preview_height"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:layout_marginEnd="@dimen/local_preview_margin_right"
android:layout_marginRight="@dimen/local_preview_margin_right"
android:layout_marginTop="@dimen/local_preview_margin_top"
android:background="@color/localBackground">
<ImageView
android:layout_width="@dimen/local_back_icon_size"
android:layout_height="@dimen/local_back_icon_size"
android:layout_gravity="center"
android:scaleType="centerCrop"
android:src="@drawable/icon_agora_large" />
</FrameLayout>
<RelativeLayout
android:id="@+id/control_panel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_marginBottom="@dimen/control_bottom_margin">
<ImageView
android:id="@+id/btn_call"
android:layout_width="@dimen/call_button_size"
android:layout_height="@dimen/call_button_size"
android:layout_centerInParent="true"
android:onClick="onCallClicked"
android:src="@drawable/btn_endcall"
android:scaleType="centerCrop"/>
<ImageView
android:id="@+id/btn_switch_camera"
android:layout_width="@dimen/other_button_size"
android:layout_height="@dimen/other_button_size"
android:layout_toRightOf="@id/btn_call"
android:layout_toEndOf="@id/btn_call"
android:layout_marginLeft="@dimen/control_bottom_horizontal_margin"
android:layout_centerVertical="true"
android:onClick="onSwitchCameraClicked"
android:src="@drawable/btn_switch_camera"
android:scaleType="centerCrop"/>
<ImageView
android:id="@+id/btn_mute"
android:layout_width="@dimen/other_button_size"
android:layout_height="@dimen/other_button_size"
android:layout_toLeftOf="@id/btn_call"
android:layout_toStartOf="@id/btn_call"
android:layout_marginRight="@dimen/control_bottom_horizontal_margin"
android:layout_centerVertical="true"
android:onClick="onLocalAudioMuteClicked"
android:src="@drawable/btn_unmute"
android:scaleType="centerCrop"/>
</RelativeLayout>
</RelativeLayout>
在项目的 Activity 文件中添加如下行:
import io.agora.rtc.IRtcEngineEventHandler;
import io.agora.rtc.RtcEngine;
import io.agora.rtc.video.VideoCanvas;
import io.agora.rtc.video.VideoEncoderConfiguration;
调用 checkSelfPermission
方法,在开启 Activity 时检查并获取 Android 移动设备的摄像头和麦克风使用权限。
// Java
private static final int PERMISSION_REQ_ID = 22;
// App 运行时确认麦克风和摄像头设备的使用权限。
private static final String[] REQUESTED_PERMISSIONS = {
Manifest.permission.RECORD_AUDIO,
Manifest.permission.CAMERA
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_video_chat_view);
// 获取权限后,初始化 RtcEngine,并加入频道。
if (checkSelfPermission(REQUESTED_PERMISSIONS[0], PERMISSION_REQ_ID) &&
checkSelfPermission(REQUESTED_PERMISSIONS[1], PERMISSION_REQ_ID)) {
initEngineAndJoinChannel();
}
}
private void initEngineAndJoinChannel() {
initializeEngine();
setupLocalVideo();
joinChannel();
}
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;
}
// Kotlin
// app 运行时确认麦克风和摄像头的使用权限。
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_voice_chat_view)
// 获取权限后,初始化 RtcEngine,并加入频道。
if (checkSelfPermission(Manifest.permission.RECORD_AUDIO, PERMISSION_REQ_ID_RECORD_AUDIO) && checkSelfPermission(Manifest.permission.CAMERA, PERMISSION_REQ_ID_CAMERA)) {
initAgoraEngineAndJoinChannel()
}
}
private fun initAgoraEngineAndJoinChannel() {
initializeAgoraEngine()
setupLocalVideo()
joinChannel()
}
private fun checkSelfPermission(permission: String, requestCode: Int): Boolean {
Log.i(LOG_TAG, "checkSelfPermission $permission $requestCode")
if (ContextCompat.checkSelfPermission(this,
permission) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
arrayOf(permission),
requestCode)
return false
}
return true
}
在调用其他 Agora API 前,需要创建并初始化 RtcEngine 对象。
将获取到的 App ID 添加到 string.xml
文件中的 agora_app_id
一栏。调用 create
方法,传入获取到的 App ID,即可初始化 RtcEngine。
你还根据场景需要,在初始化时注册想要监听的回调事件,如本地用户加入频道,及远端用户加入频道等。注意不要在这些回调中进行 UI 操作。
// Java
private RtcEngine mRtcEngine;
private final IRtcEngineEventHandler mRtcEventHandler = new IRtcEngineEventHandler() {
@Override
// 注册 onJoinChannelSuccess 回调。
// 本地用户成功加入频道时,会触发该回调。
public void onJoinChannelSuccess(String channel, final int uid, int elapsed) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Log.i("agora","Join channel success, uid: " + (uid & 0xFFFFFFFFL));
}
});
}
@Override
// 注册 onUserJoined 回调。
// 远端用户成功加入频道时,会触发该回调。
// 可以在该回调中调用 setupRemoteVideo 方法设置远端视图。
public void onUserJoined(final int uid, int elapsed) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Log.i("agora","Remote user joined, uid: " + (uid & 0xFFFFFFFFL));
setupRemoteVideo(uid);
}
});
}
@Override
// 注册 onUserOffline 回调。
// 远端用户离开频道或掉线时,会触发该回调。
public void onUserOffline(final int uid, int reason) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Log.i("agora","User offline, uid: " + (uid & 0xFFFFFFFFL));
onRemoteUserLeft();
}
});
}
};
...
// 初始化 RtcEngine 对象。
private void initializeEngine() {
try {
mRtcEngine = RtcEngine.create(getBaseContext(), getString(R.string.agora_app_id), mRtcEventHandler);
} catch (Exception e) {
Log.e(TAG, Log.getStackTraceString(e));
throw new RuntimeException("NEED TO check rtc sdk init fatal error\n" + Log.getStackTraceString(e));
}
}
// Kotlin
private var mRtcEngine: RtcEngine? = null
private val mRtcEventHandler = object : IRtcEngineEventHandler() {
// 注册 onUserJoined 回调。
// 远端用户成功加入频道时,会触发该回调。
// 可以在该回调用调用 setupRemoteVideo 方法设置远端视图。
override fun onUserJoined(uid: Int, elapsed: Int) {
runOnUiThread { setupRemoteVideo(uid) }
}
// 注册 onUserOffline 回调。远端用户离开频道后,会触发该回调。
override fun onUserOffline(uid: Int, reason: Int) {
runOnUiThread { onRemoteUserLeft() }
}
}
...
// 调用 Agora SDK 的方法初始化 RtcEngine。
private fun initializeAgoraEngine() {
try {
mRtcEngine = RtcEngine.create(baseContext, getString(R.string.agora_app_id), mRtcEventHandler)
} catch (e: Exception) {
Log.e(LOG_TAG, Log.getStackTraceString(e))
throw RuntimeException("NEED TO check rtc sdk init fatal error\n" + Log.getStackTraceString(e))
}
}
成功初始化 RtcEngine 对象后,需要在加入频道前设置本地视图,以便在通话中看到本地图像。参考以下步骤设置本地视图:
enableVideo
方法启用视频模块。createRendererView
方法创建一个 SurfaceView 对象。setupLocalVideo
方法设置本地视图。// Java
private void setupLocalVideo() {
// 启用视频模块。
mRtcEngine.enableVideo();
// 创建 SurfaceView 对象。
private FrameLayout mLocalContainer;
private SurfaceView mLocalView;
mLocalView = RtcEngine.CreateRendererView(getBaseContext());
mLocalView.setZOrderMediaOverlay(true);
mLocalContainer.addView(mLocalView);
// 设置本地视图。
VideoCanvas localVideoCanvas = new VideoCanvas(mLocalView, VideoCanvas.RENDER_MODE_HIDDEN, 0);
mRtcEngine.setupLocalVideo(localVideoCanvas);
}
// Kotlin
private fun setupLocalVideo() {
// 启用视频模块。
mRtcEngine!!.enableVideo()
val container = findViewById(R.id.local_video_view_container) as FrameLayout
// 创建 SurfaceView。
val surfaceView = RtcEngine.createRendererView(baseContext)
surfaceView.setZorderMediaOverlay(true)
container.addView(surfaceView)
// 设置本地视图。
mRtcEngine!!.setupLocalVideo(VideoCanvas(surfaceView, VideoCanvas.RENDER_MODE_FIT, 0))
}
完成初始化和设置本地视图后(视频通话场景),你就可以调用 joinChannel
方法加入频道。你需要在该方法中传入如下参数:
token
:传入用于鉴权的 Token,可设为如下一个值:
token
设为 ""。channelName:传入能标识频道的频道 ID。App ID 相同、频道 ID 相同的用户会进入同一个频道。
uid: 本地用户的 ID。数据类型为整型,且频道内每个用户的 uid 必须是唯一的。若将 uid 设为 0,则 SDK 会自动分配一个 uid,并在 onJoinChannelSuccess
回调中报告。
mute
方法实现。更多的参数设置注意事项请参考 joinChannel
接口中的参数描述。
// Java
private void joinChannel() {
// 调用 joinChannel 方法 加入频道。
mRtcEngine.joinChannel(YOUR_TOKEN, "demoChannel1", "Extra Optional Data", 0);
}
// Kotlin
private fun joinChannel() {
// 调用 joinChannel 方法加入频道。
mRtcEngine!!.joinChannel(token, "demoChannel1", "Extra Optional Data", 0)
}
视频通话中,通常你也需要看到其他用户。在加入频道后,可通过调用 setupRemoteVideo
方法设置远端用户的视图。远端视图和本地视图的区别就是需要设置远端用户的 UID。
远端用户成功加入频道后,SDK 会触发 onUserJoined
回调,该回调中会包含这个远端用户的 uid 信息。在该回调中调用 setupRemoteVideo
方法,传入获取到的 uid,设置远端用户的视图。
// Java
@Override
// 监听 onUserJoined 回调。
// 远端用户加入频道时,会触发该回调。
// 可以在该回调中调用 setupRemoteVideo 方法设置远端视图。
public void onUserJoined(final int uid, int elapsed) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Log.i("agora","Remote user joined, uid: " + (uid & 0xFFFFFFFFL));
setupRemoteVideo(uid);
}
});
}
private void setupRemoteVideo(int uid) {
// 创建一个 SurfaceView 对象。
private RelativeLayout mRemoteContainer;
private SurfaceView mRemoteView;
mRemoteView = RtcEngine.CreateRendererView(getBaseContext());
mRemoteContainer.addView(mRemoteView);
// 设置远端视图。
mRtcEngine.setupRemoteVideo(new VideoCanvas(mRemoteView, VideoCanvas.RENDER_MODE_HIDDEN, uid));
}
// Kotlin
// 注册 onUserJoined 回调。
// 远端用户加入频道时,会触发该回调。
// 可以在该回调用调用 setupRemoteVideo 方法设置远端视图。
override fun onUserJoined(uid: Int, elapsed: Int) {
runOnUiThread { setupRemoteVideo(uid) }
}
private fun setupRemoteVideo(uid: Int) {
val container = findViewById(R.id.remote_video_view_container) as FrameLayout
if (container.childCount >= 1) {
return
}
// 创建一个 SurfaceView 对象。
val surfaceView = RtcEngine.CreateRendererView(baseContext)
container.addView(surfaceView)
// 设置远端视图。
mRtcEngine!!.setupRemoteVideo(VideoCanvas(surfaceView, VideoCanvas.RENDER_MODE_FIT, uid))
}
你还可以在通话中参考如下代码实现更多功能及场景。
调用 muteLocalAudioStream
停止或恢复发送本地音频流,实现或取消本地静音。
// Java
public void onLocalAudioMuteClicked(View view) {
mMuted = !mMuted;
mRtcEngine.muteLocalAudioStream(mMuted);
}
// Kotlin
fun onLocalAudioMuteClicked(view: View) {
val iv = view as ImageView
if (iv.isSelected) {
iv.isSelected = false
iv.clearColorFilter()
} else {
iv.isSelected = true
iv.setColorFilter(resources,getColor(R.color.colorPrimary), PorterDuff.Mode.MULTIPLY)
}
mRtcEngine!!.muteLocalAudioStream(iv.isSelected)
}
调用 switchCamera
方法切换摄像头的方向。
// Java
public void onSwitchCameraClicked(View view) {
mRtcEngine.switchCamera();
}
// Kotlin
fun onSwitchCameraClicked(view: View) {
mRtcEngine!!.swithcCamera()
}
根据场景需要,如结束通话、关闭 App 或 App 切换至后台时,调用 leaveChannel
离开当前通话频道。
// Java
@Override
protected void onDestroy() {
super.onDestroy();
if (!mCallEnd) {
leaveChannel();
}
RtcEngine.destroy();
}
private void leaveChannel() {
// 离开当前频道。
mRtcEngine.leaveChannel();
}
// Kotlin
override fun onDestroy() {
super.onDestroy()
leaveChannel()
RtcEngine.destroy()
mRtcEngine = null
}
private fun leaveChannel() {
// 离开当前频道。
mRtcEngine!!.leaveChannel()
}
你可以在 Agora-Android-Tutorial-1to1 示例项目的 VideoChatViewActivity.java 文件中查看完整的源码和代码逻辑。
在 Android 设备中运行该项目。当成功开始视频通话时,你可以同时看到本地和远端的视图。