如何基于 Agora Android SDK 在应用中实现视频通话?

article/2025/10/13 7:27:53

在很多产品,实时视频通话已经不是新鲜的功能了,例如视频会议、社交应用、在线教育,甚至也可能出现在一些元宇宙的场景中。

本文将教你如何通过声网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*/@Overridepublic 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*/@Overridepublic 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.*/@Overridepublic 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*/@Overridepublic 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(){@Overridepublic void run(){join.setEnabled(true);join.setText(getString(R.string.leave));}});}@Overridepublic void onRemoteAudioStats(io.agora.rtc.IRtcEngineEventHandler.RemoteAudioStats remoteAudioStats) {statisticsInfo.setRemoteAudioStats(remoteAudioStats);updateRemoteStats();}@Overridepublic void onLocalAudioStats(io.agora.rtc.IRtcEngineEventHandler.LocalAudioStats localAudioStats) {statisticsInfo.setLocalAudioStats(localAudioStats);updateLocalStats();}@Overridepublic void onRemoteVideoStats(io.agora.rtc.IRtcEngineEventHandler.RemoteVideoStats remoteVideoStats) {statisticsInfo.setRemoteVideoStats(remoteVideoStats);updateRemoteStats();}@Overridepublic void onLocalVideoStats(io.agora.rtc.IRtcEngineEventHandler.LocalVideoStats localVideoStats) {statisticsInfo.setLocalVideoStats(localVideoStats);updateLocalStats();}@Overridepublic 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.htmlshowAlert(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.*/@Overridepublic 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 RtcEnginesurfaceView = RtcEngine.CreateRendererView(context);surfaceView.setZOrderMediaOverlay(true);ViewGroup view = getAvailableView();remoteViews.put(uid, view);// Add to the remote containerview.addView(surfaceView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));// Setup remote video to renderengine.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.*/@Overridepublic 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() {@Overridepublic void run() {/**Clear render viewNote: The video will stay at its last frame, to completely remove it you will need toremove 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/


http://chatgpt.dhexx.cn/article/MsvfQmQB.shtml

相关文章

Android uni-app实现音视频通话

前言 上一篇讲解了怎么实现Android uni-app封装原生插件&#xff0c;这篇讲解一下&#xff0c;把anyRTC的RTC&#xff08;音视频通讯&#xff09;封装uni-app 实现音视频通话。 不了解anyRTC的小伙伴&#xff0c;可以点击下面链接&#xff1a; 开发者官网 1.效果图 先上图&a…

使用 Agora SDK 开发 React Native 视频通话 App

在 React Native 的应用中&#xff0c;从头开始添加视频通话功能是很复杂的。要保证低延迟、负载平衡&#xff0c;还要注意管理用户事件状态&#xff0c;非常繁琐。除此之外&#xff0c;还必须保证跨平台的兼容性。 当然有个简单的方法可以做到这一点。在本次的教程中&#xf…

MySQL联合索引底层数据结构是怎样的

目录 1. 联合索引数据结构图 2.联合索引是如何进行排序的 3. 联合索引查询特点 1. 联合索引数据结构图 如下图所示联合索引的数据结构, 通过name&#xff0c;age&#xff0c;position三个字典进行一个联合索引&#xff0c;构建B树索引结构。 2.联合索引是如何进行排序的 B树…

MySQL联合索引在B+树如何存储以及寻址

文章目录 引入Innodb B树联合索引存储以及寻址总结 引入 最近找工作&#xff0c; 去一家三方支付公司面试&#xff0c;前面得过程还挺好&#xff0c;所有的提都回答对了&#xff08;心里暗自窃喜应该能拿到高工资offer&#xff0c;迎娶白富美&#xff0c;然后走向人生巅峰&…

MySQL联合索引生效规则

最近项目中一张表数量测试达到千万级别&#xff0c;考虑加索引&#xff0c;对于单列索引及联合索引&#xff08;复合索引&#xff09;简单学习了下&#xff0c;做一下笔记。 联合索引生效前提&#xff1a;因为联合索引有顺序讲究&#xff0c;联合索引的第一个字段是引导列&…

mysql联合索引的数据结构

一、本文主要讲解的内容有&#xff1a; 联合索引在B树上的存储结构联合索引的查找方式为什么会有最左前缀匹配原则 在分享这篇文章之前&#xff0c;我在网上查了关于MySQL联合索引在B树上的存储结构这个问题&#xff0c;翻阅了很多博客和技术文章&#xff0c;其中有几篇讲述的…

mysql联合索引有效和失效的情况分析

关于mysql的索引&#xff0c;是mysql优化一个非常重要的方面。那么关于索引是否有效就是非常关键了。很多人设计了索引&#xff0c;但是发现依旧很慢。那么这个时候就判断sql的索引执行情况非常重要了。网上有大量的博主也写过不少类似的文章&#xff0c;但是关于联合索引的具体…

MySQL联合索引(abc)命中规则

1.建表 mysql创建一张表&#xff0c;表名&#xff1a;‘test_models’ id列为 主键&#xff0c;int类型 &#xff0c;自增a,b,c,d,e 全部是int&#xff08;11&#xff09;为&#xff08;a,b,c&#xff09;添加一个联合索引 index_abc 执行语句&#xff1a; CREATE TABLE te…

mysql联合索引

mysql联合索引的使用 命名规则&#xff1a;表名_字段名 1、需要加索引的字段&#xff0c;要在where条件中 2、数据量少的字段不需要加索引 3、如果where条件中是OR关系&#xff0c;加索引不起作用 4、符合最左原则 以下是我的建表语句 CREATE TABLE test ( id int(11) uns…

mysql 联合索引结构与索引匹配原则

联合索引结构与索引匹配原则 最左前缀匹配原则&#xff1a;在MySQL建立联合索引时会遵守最左前缀匹配原则&#xff0c;即最左优先&#xff0c;在检索数据时从联合索引的最左边开始匹配。 要想理解联合索引的最左匹配原则&#xff0c;先来理解下索引的底层原理。索引的底层是一…

详解MySQL联合索引

引言 本文预计分为两个部分:(1)联合索引部分的基础知识 在这个部分&#xff0c;我们温习一下联合索引的基础(2)联合索引部分的实战题 在这个部分&#xff0c;列举几个我认为算是实战中的代表题&#xff0c;挑出来说说。 正文 基础 讲联合索引&#xff0c;一定要扯最左匹配!…

mysql联合索引的使用

这篇笔记主要记录联合索引的使用 设置了shopId、userId、relationId三个字段&#xff0c;作为联合索引&#xff0c;这三个字段&#xff0c;都是long类型的&#xff0c;也就是bigint 分别验证以下几个场景&#xff1a; 场景一&#xff1a;explain select * from testIndex wher…

MySQL联合索引的原理

面试中被问到了联合索引&#xff0c;突然就涉及到了知识盲区&#xff0c;对不起&#xff0c;我只知道B树&#xff0c;B树&#xff0c;哈希索引&#xff0c;聚簇索引&#xff0c;非聚簇索引&#xff0c;联合索引的原理&#xff1f;。。 对不起涉及到了我的知识盲区了。 这里对联…

Mysql 联合索引

联合索引底层数据结构 MySQL可以使用多个字段同时建立一个索引,叫做联合索引。上文中讲到索引的底层结构就是一个二叉树&#xff0c;联合索引也是一样&#xff0c;它的非叶子节点中存的就不只是一个列&#xff0c;是索引的所有列&#xff0c;并且它的排序就是根据索引列的先后顺…

mysql联合索引详解

比较简单的是单列索引&#xff08;btree&#xff09;。遇到多条件查询时&#xff0c;不可避免会使用到多列索引。联合索引又叫复合索引。 btree结构如下&#xff1a; 每一个磁盘块在mysql中是一个页&#xff0c;页大小是固定的&#xff0c;mysql innodb的默认的页大小是16k&a…

MySQL索引详解

本文主要介绍MySQL索引底层原理及优化&#xff0c;理解SQL是如何执行&#xff0c;MySQL如何选择合适的索引以及时间都消耗在哪些地方&#xff0c;再加上一些优化的知识&#xff0c;可以帮助大家更好的理解MySQL&#xff0c;理解常见优化技巧背后的原理。希望本文中的原理、示例…

MySQL索引之联合索引

目录 1. 联合索引1.1. 联合索引的存储结构1.2. 联合索引的查询流程1.3. 最左前缀匹配原则1.3.1. 最左前缀匹配原则说明 2. 索引下推2.1. 无索引下推的执行流程2.2. 有索引下推的执行流程 1. 联合索引 在平时开发中&#xff0c;我们最常见的是聚集索引&#xff0c;但在我们需要…

jedis和redisTemplate

使用原生jedis和spring的redisTemplate调用连接池&#xff0c;发现差别巨大&#xff1a; redis配置&#xff1a; redis:database: 0host: 127.0.0.1port: 6379password: 123456timeout: 5000lettuce:shutdown-timeout: 200pool:max-active: 500max-idle: 100min-idle: 50max-w…

16.Jedis

目录 一、Jedis知识点总览。 二、连接池配置&#xff1a; 三、测试类&#xff1a; 一、Jedis知识点总览。 public class ProvinceServiceImpl implements ProvinceService {private ProvinceDao dao new ProvinceDaoImpl();Overridepublic List<Province> findAll(…

Jedis的配置和使用

什么是jedis 是官方推荐的java连接开发工具&#xff0c;使用java操作Redis的中间件&#xff0c;如果要使用java操作redis&#xff0c;那么要对jedis十分熟悉 测试 导入对应的依赖(Jedis和fastjson)&#xff1a; <dependencies><dependency><groupId>redi…