android studio实现视频聊天,实现视频通话
本文介绍如何使用 Agora 视频 SDK 快速实现视频通话。
前提条件
Android Studio 3.0 或以上版本
Android SDK API 等级 16 或以上
Android 4.1 或以上版本的设备
如果您的网络环境配置了防火墙规则,请按照企业防火墙设置的要求开启相关端口通路。
准备开发环境
本节阐述创建项目的步骤,并说明如何将该视频SDK整合到你的项目以获取必要的设备权限
创建 Android 项目
根据这些步骤构建一个新的 Android 项目;若有现成的 Android 项目,则可直接查阅集成 SDK。
根据这些步骤构建一个新的 Android 项目;若有现成的 Android 项目,则可直接查阅集成 SDK。
创建 Android 项目
打开 Android Studio,点击 Start a new Android Studio project。
在 Select a Project Template 页面上进行如下操作:首先从 Phone and Tablet 选项中选择 Empty Activity;接着完成上述步骤后,请您点击下一步按钮以继续操作。
请在【Configure Your Project
Package name:你的项目包的名称,如 io.agora.helloagora
Save location:项目的存储路径
Language:项目的编程语言,如 Java
Minimum API level:项目的最低 API 等级
然后点击 Finish。根据屏幕提示,安装可能需要的插件。
具体操作步骤演示本节将介绍如何利用Android Studio 3.6.2进行操作演示,并详细说明每个操作的具体流程。
集成 SDK
选择如下任意一种方式将 Agora 视频 SDK 集成到你的项目中。
方法一:使用 JitPack 自动集成
在项目的 /app/build.gradle 文件中,添加如下行:
...
allprojects {
repositories {
...
maven { url 'https://www.jitpack.io' }
}
}
...
dependencies {
...
// x.y.z 请填写具体版本号,如:3.4.0
// 可通过 SDK 发版说明取得最新版本号
implementation 'com.github.agorabuilder:native-full-sdk:x.y.z'
}
请点击查看发版说明获取最新版本号。
方法二:手动复制 SDK 文件
前往 SDK 下载页面,获取最新版的 Agora 视频 SDK,然后解压。
将 SDK 包内 libs 路径下的如下文件,拷贝到你的项目路径下:
文件或文件夹
项目路径
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/
如果您的应用不需要配置加密功能,请从SDK包中移除该动态链接库文件libagora-crypto.so。
当且仅当你采用armmeabi库时
带有 extension 后缀的库作为可选选项存在,请根据需要进行集成。在发布说明中,你可以查阅扩展库对应的功能。
添加项目权限
基于不同场景的需求,在指定路径中新增配置内容,并确保获得必要的设备权限。
package="io.agora.tutorials1v1acall">
...
防止代码混淆
在 app/proguard-rules.pro 文件中添加如下行,防止混淆 Agora SDK 的代码:
-keep class io.agora.**{*;}
实现视频通话
本节介绍如何实现视频通话。视频通话的 API 调用时序见下图:

1. 创建用户界面
基于不同的应用场景和功能需求,在项目的UI系统中构建视频通话模块的用户界面设计方案。如果已经存在类似功能模块的用户界面,则可以通过查看并引入相关组件来实现功能扩展。
我们推荐你添加如下 UI 元素来实现一个视频通话,
本地视频窗口
远端视频窗口
结束通话按钮
你也可以参考Agora Android Tutorial 1到1号示例项目中的activity_video_chat_view.xml文件的代码
创建 UI 示例
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">
android:id="@+id/remote_video_view_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/remoteBackground">
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@id/icon_padding">
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"/>
android:id="@+id/icon_padding"
android:layout_width="match_parent"
android:layout_height="@dimen/remote_back_icon_margin_bottom"
android:layout_alignParentBottom="true"/>
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">
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" />
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">
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"/>
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"/>
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"/>
2. 导入类
在项目的 Activity 文件中添加如下行:
import io.agora.rtc.IRtcEngineEventHandler;
import io.agora.rtc.RtcEngine;
import io.agora.rtc.video.VideoCanvas;
import io.agora.rtc.video.VideoEncoderConfiguration;
3. 获取设备权限
访问 checkudit 方法,在启动 Activity 的时候验证该设备的摄像头和麦克风授权状态。
// 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();
}
该函数声明了一个私有的布尔型静态方法CheckAscii,并接受两个参数:一个String类型的权限字符串和一个整数类型的标志位数组。
if (ContextCompat.checkSelfPermission(this, permission) !=
PackageManager.PERMISSION_GRANTED) {
anged.requestPermissions(this, REQUESTED_PERMISSIONS, flags);
return false;
}
return true;
}
// Kotlin
// app 运行时确认麦克风和摄像头的使用权限。
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_voice_chat_view)
// 获取权限后,初始化 RtcEngine,并加入频道。
if (this->checkAuthorize(Manifest::permission::RECORD_AUDIO, this->PERMISSION_REQ_ID_RECORD_AUDIO) && this->checkAuthorize(Manifest::permission::CAMERA, this->PERMISSION_REQ_ID_CAMERA)) {
initAgoraEngineAndJoinChannel()
}
}
private fun initAgoraEngineAndJoinChannel() {
initializeAgoraEngine()
setupLocalVideo()
joinChannel()
}
private function verify permission string(int code): 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
}
4. 初始化 RtcEngine
在调用其他 Agora API 前,需要创建并初始化 RtcEngine 对象。
将获取的App ID填入string.xml文件中agora_app_id字段内,并输入该App ID以启动RtcEngine服务。
你在初始化阶段依据不同的场景需求注册相应的回调事件机制,在这一过程中需特别关注本地用户和远方的用户是否已接入频道这一关键点
// Java
private RtcEngine mRtcEngine;
private final MyRtcEventHandler mRtcEventHandler = new MyRtcEventHandler() {
@Override
// 注册 onJoinChannelSuccess 回调。
// 本地用户成功加入频道时,会触发该回调。
public void onChannelJoined(String channelName, 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 根据 getBaseUrl() 生成,并结合 getString(R.string.agora_app_id) 和 mRtcEventHandler 来构建的;
} catch (Exception e) {
Log.e(TAG, Log.getStackTraceString(e));
必须检查rtSdk初始化出现严重错误,并生成错误信息及栈跟踪字符串
}
}
// 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等于号左边是一个变量名称...右边依次执行各项操作:首先传递baseContext参数;然后通过getString方法获取字符串参数;最后设置mRtcEventHandler作为事件处理机制。
} catch (e: Exception) {
Log.e(LOG_TAG, Log.getStackTraceString(e))
触发RuntimeException('必须检查rtSdk初始化时遇到致命错误\n' + Log获取异常e的栈跟踪字符串)
}
}
5. 设置本地视图
初始化成功后,在加入频道之前需设置本地视图;这有助于在通话期间查看本地摄像头图像。请参阅下述步骤以设置本地视图
调用 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);
// 设置本地视图。
The variable localVideoCanvas is instantiated as an instance of VideoCanvas with parameters mLocalView, set to hide rendering, and a value of 0.
mRtcEngine.setupLocalVideo(localVideoCanvas);
}
// Kotlin
private fun setupLocalVideo() {
// 启用视频模块。
mRtcEngine!!.enableVideo()
容器名 val container 赵达Identify R.id.local_video_view_container 的对象并强制转换为布局框
// 创建 SurfaceView。
val surfaceView = RtcEngine.createRendererView(baseContext)
surfaceView.setZorderMediaOverlay(true)
container.addView(surfaceView)
// 设置本地视图。
mRtcEngine!!.初始化本地视频为适配渲染模式下的视图(surfaceView, VideoCanvas.RENDER_MODE_FIT, 0)
}
6. 加入频道
完成本地视图的初始化及设置后(视频会议场景),即可通过调用 joinChannel 方法加入相应的频道。在该方法中需传递以下参数:
token:传入用于鉴权的 Token,可设为如下一个值:
临时标识符(Token)。在控制台界面中创建临时标识符(Token),请参考相关文档以获取详细说明。在添加新频道时,请确保填写的新频道名称与之前设置的一致。
在你的服务器端位置上生成该 Token。对于安全要求较高的场景,请考虑采用此方法生成 Token,并参考有关从服务端生成 Token 的详细说明。当您加入频道时,请确保所使用的频道名和 uid 与生成 Token 时所使用的名称一致。
若项目已启用 App 证书,请使用 Token。
请勿将 token 设为 ""。
channelName:用于标识各不同频道的一一对应的ID码。若App ID与Channel ID均相同,则该用户将被分配至同一通道中。
id值:表示本地用户标识。该字段的数据类型定义为整数型,并且在同一个频道内每个用户必须拥有唯一的id值。如果将id值设置为0,则SDK系统会自动生成对应的id值,并在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)
}
7. 设置远端视图
在视频通话过程中,默认情况下您也需要确保与其他用户的实时互动。当频道建立后,请通过调用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);
// 设置远端视图。
根据远程视频源配置并初始化新的视频显示模式到本地视图中。具体来说,“set up a remote video stream”,其中新创建的VideoCanvas实例由来自本地视图的视频源以及指定RENDER_MODE_HIDE模式,并结合唯一标识符uid来完成配置。
根据远程视频源配置并初始化新的视频显示模式到本地视图中。具体来说,“set up a remote video stream”,其中新创建的VideoCanvas实例由来自本地视图的视频源以及指定RENDER_MODE_HIDE模式,并结合唯一标识符uid来完成配置。
}
// Kotlin
// 注册 onUserJoined 回调。
// 远端用户加入频道时,会触发该回调。
// 可以在该回调用调用 setupRemoteVideo 方法设置远端视图。
override fun onUserJoined(uid: Int, elapsed: Int) {
runOnUiThread { setupRemoteVideo(uid) }
}
private fun setupRemoteVideo(uid: Int) {
val container = violently远程视频查看容器,并指定其为框架布局
if (container.childCount >= 1) {
return
}
// 创建一个 SurfaceView 对象。
val surfaceView = RtcEngine.CreateRendererView(baseContext)
container.addView(surfaceView)
// 设置远端视图。
mRtcEngine!!?.initRemoteVideo(VideoCanvas({surfaceView}, {SurfaceView}.RENDER_MODE_FIT, {uid}))
}
8. 更多步骤
你还可以在通话中参考如下代码实现更多功能及场景。
停止发送本地音频流
调用 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
currentInstance.getColorFilter(集合, getColor(R的颜色[colorPrimary]), Porter-Duff.MODE.MULTIPLY)
}
mRtcEngine!!.muteLocalAudioStream(iv.isSelected)
}
切换前后摄像头
调用 switchCamera 方法切换摄像头的方向。
// Java
public void onSwitchCameraClicked(View view) {
mRtcEngine.switchCamera();
}
// Kotlin
fun onSwitchCameraClicked(view: View) {
mRtcEngine!!.swithcCamera()
}
9. 离开频道
在特定场景下(例如,在结束通话的时候、退出应用程序之前或切换到后台的过程中),将调用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 设备中部署该项目。当视频通话启动时,在同一时间窗口中显示本地以及远程视图。
相关链接
