写点什么

基于 Agora SDK 实现 Android 一对一音视频聊天应用

  • 2020-02-25
  • 本文字数:6797 字

    阅读完需:约 22 分钟

基于Agora SDK实现Android一对一音视频聊天应用

本系列教程将分为三期,分享基于 Agora SDK 在各系统平台应用中实现一对一视频通话、多人互动直播,以及结合跨平台技术进行开发。本期推送在 Android、iOS、Windows、Web、macOS 上实现一对一视频通话。


声网 Agora SDK 让应用和网站都可以实现高质量的音频通话、视频通话、全互动直播。本文通过 Agora Native SDK 在 Android 端实现一个视频通话应用。

环境

声网 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)

集成

步骤一:首先点此下载完整的 SDK 和官方 demo


步骤二:新建一个 HelloAgora 项目,注意一定要支持 C++哦。


步骤三:把 libs 文件夹里的 arm64-v8a、、armeabi-v7a 以及 x86 文件夹复制粘贴到 app module 的 libs 里。如果有 NDK 开发的必要,则把 libs->include 文件夹里的两个.h 头文件复制粘贴到合适位置。


步骤四:首先在 app module 的 build.gradle 文件的 android 代码块中添加如下代码:


1sourceSets {2    main {3        jniLibs.srcDirs = ['../../../libs']4    }5}
复制代码


然后在 app module 的 build.gradle 文件的 android->defaultConfig 代码块中添加如下代码:


1ndk {2    abiFilters "armeabi-v7a", "x86" 3}
复制代码


接下来在 app module 的 build.gradle 文件的 dependencies 代码块中添加如下代码:


1compile 'io.agora.rtc:full-sdk:2.0.0'
复制代码


如果用复制粘贴 jar 的方式,那么此处添加如下代码:


1compile fileTree(dir: '../../../libs', include: ['*.jar'])
复制代码


如果有自定义 NDK 的必要,可以继续在 app module 的 build.gradle 文件的 android 代码块中添加如下代码:


1externalNativeBuild {2    ndkBuild {3        path 'src/main/cpp/Android.mk'4    }5}然后在app module的build.gradle文件的android->defaultConfig代码块中添加如下代码:1externalNativeBuild {2    ndkBuild {3        arguments "NDK_APPLICATION_MK:=src/main/cpp/Application.mk"4    }5}
复制代码


最后 sync 一下,声网 Agora.io 的 SDK 就集成到项目中来了。

权限

SDK 集成完毕后,为了保证 SDK 能正常运行,我们需要在 AndroidManisfest.xml 文件中声明以下权限:


 1<!--允许程序连接网络--> 2<uses-permission android:name="android.permission.INTERNET" /> 3<!--允许程序录制音频--> 4<uses-permission android:name="android.permission.RECORD_AUDIO" /> 5<!--允许程序使用照相设备--> 6<uses-permission android:name="android.permission.CAMERA" /> 7<!--允许程序修改全局音频设置--> 8<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" /> 9<!--允许程序获取网络状态-->10<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />11<!--允许对存储空间进行读写-->12<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />13<!--允许程序连接到已配对的蓝牙设备-->14<uses-permission android:name="android.permission.BLUETOOTH" />
复制代码


这些权限都是 Android 开发过程中的常见权限,有经验的程序员都会感觉眼熟,WRITE_EXTERNAL_STORAGE 等敏感权限适配 Android 6.0 以后版本的问题并非本文关注重点,在此不做赘述。

混淆代码

集成 SDK 并声明了权限后,就该考虑混淆的问题了,我们需要在 project 的 proguard-rules.pro 文件里添加以下代码:


1-keep class io.agora.**{*;}
复制代码


经过以上过程后,我们已经完成了声网 Agora.io SDK 的快速集成,迈出了走向连麦直播、在线抓娃娃、直播问答、远程狼人杀等风口的第一步。在接下来的文章里,我将继续分享 APP ID 鉴权、Token 鉴权、一对一视频聊天、创建群聊 room、分屏、窗口切换和前后摄像头切换等内容。

鉴权

APP ID 鉴权

所谓 APP ID,就是在 Agora 创建每个项目都有的一个唯一标识。App ID 可以明确你的项目及组织身份,并在 joinChannel 方法中作为参数,连接到 Agora 实时网络中,实现实时通信或直播功能。不同的 App ID 在 Agora 实时网络中的通话是完全隔离的;Agora 提供的频道信息、计费、管理服务也都是基于 App ID。


申请 APP ID 的操作很简便,只要在 Agora 官网https://dashboard.agora.io/projects右侧栏目的“项目”中点击“添加新项目”,只需输入项目名就可生成 APP ID,全过程如下图所示:



找到,把“<#YOUR APP ID#>”替换为图中的马赛克里的字符串。


1<string name="agora_app_id"><#YOUR APP ID#></string>
复制代码


以上就是 APP ID 鉴权的全过程。


尽管 App ID 鉴权在最大程度上方便了开发者使用 Agora 的服务。但 App ID 鉴权的安全性不佳,一旦有别有用心的人非法获取了你的 App ID,他就可以在 Agora 提供的 SDK 中使用你的 App ID。如果你的项目对安全性要求高,或者增加用户权限设置的话,建议采用 Token 鉴权。

Token 鉴权

在通信和直播场景中存在着多个角色,而每种角色又对应着一些默认权限。比如在直播场景中,主播可以发布流、订阅流、邀请嘉宾;观众可以订阅流、申请连麦;管理员则可以踢人或禁言。


Token 鉴权的步骤比 APP ID 鉴权稍微复杂一些,在上文项目列表中查看 App ID 的地方,启用该项目的 App Certificate:


首先,点击激活项目右上方的 编辑 按钮。



将你的 App Certificate 保存在服务器端,且对任何客户端均不可见。当项目的 App Certificate 被启用后,你必须使用 Token。例如: 在启用 App Certificate 前,你可以使用 App ID 加入频道。但启用了 App Certificate 后,你就必须使用 Token 加入频道。后台如何用 App Certificate 生成 Token 本文不做赘述。

初始化 Agora

RtcEngine 类包含应用程序调用的主要方法,调用 RtcEngine 的接口最好在同一个线程进行,不建议在不同的线程同时调用。


目前 Agora Native SDK 只支持一个 RtcEngine 实例,每个应用程序仅创建一个 RtcEngine 对象 。 RtcEngine 类的所有接口函数,如无特殊说明,都是异步调用,对接口的调用建议在同一个线程进行。所有返回值为 int 型的 API,如无特殊说明,返回值 0 为调用成功,返回值小于 0 为调用失败。


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


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


 1private RtcEngine mRtcEngine; 2/** 3 * Tutorial Step 1 4 * 初始化Agora,创建 RtcEngine 对象 5 */ 6private void initializeAgoraEngine() { 7    try { 8        mRtcEngine = RtcEngine.create(getBaseContext(), getString(R.string.agora_app_id), mRtcEventHandler); 9    } catch (Exception e) {10        Log.e(LOG_TAG, Log.getStackTraceString(e));11        throw new RuntimeException("Agora初始化失败了,检查一下是哪儿出错了\n" + Log.getStackTraceString(e));12    }13}14private final IRtcEngineEventHandler mRtcEventHandler = new IRtcEngineEventHandler() {15    @Override16    public void onFirstRemoteVideoDecoded(final int uid, int width, int height, int elapsed) {17        runOnUiThread(new Runnable() {18            @Override19            public void run() {20                //设置远端视频显示属性21                setupRemoteVideo(uid);22            }23        });24    }25    @Override26    public void onUserOffline(int uid, int reason) {27        runOnUiThread(new Runnable() {28            @Override29            public void run() {30                //其他用户离开当前频道回调31                onRemoteUserLeft();32            }33        });34    }35    @Override36    public void onUserMuteVideo(final int uid, final boolean muted) {37        runOnUiThread(new Runnable() {38            @Override39            public void run() {40                //其他用户已停发/已重发视频流回调41                onRemoteUserVideoMuted(uid, muted);42            }43        });44    }45};46private void onRemoteUserLeft() {47    FrameLayout container = (FrameLayout) findViewById(R.id.remote_video_view_container);48    container.removeAllViews();49    //文案可随意定制50    View tipMsg = findViewById(R.id.quick_tips_when_use_agora_sdk);51    tipMsg.setVisibility(View.VISIBLE);52}53private void onRemoteUserVideoMuted(int uid, boolean muted) {54    FrameLayout container = (FrameLayout) findViewById(R.id.remote_video_view_container);55    SurfaceView surfaceView = (SurfaceView) container.getChildAt(0);56    Object tag = surfaceView.getTag();57    if (tag != null && (Integer) tag == uid) {58        surfaceView.setVisibility(muted ? View.GONE : View.VISIBLE);59    }60}
复制代码

打开视频模式

enableVideo()方法用于打开视频模式。可以在加入频道前或者通话中调用,在加入频道前调用,则自动开启视频模式,在通话中调用则由音频模式切换为视频模式。调用 disableVideo() 方法可关闭视频模式。


setVideoProfile()方法设置视频编码属性(Profile)。每个属性对应一套视频参数,如分辨率、帧率、码率等。 当设备的摄像头不支持指定的分辨率时,SDK 会自动选择一个合适的摄像头分辨率,但是编码分辨率仍然用 setVideoProfile() 指定的。


该方法仅设置编码器编出的码流属性,可能跟最终显示的属性不一致,例如编码码流分辨率为 640x480,码流的旋转属性为 90 度,则显示出来的分辨率为竖屏模式。


 1/** 2 * Tutorial Step 2 3 * 打开视频模式,并设置本地视频属性 4 */ 5private void setupVideoProfile() { 6    //打开视频模式 7    mRtcEngine.enableVideo(); 8    //设置本地视频属性 9    mRtcEngine.setVideoProfile(Constants.VIDEO_PROFILE_360P, false);10}
复制代码

设置本地视频显示属性

setupLocalVideo( VideoCanvas local )方法用于设置本地视频显示信息。应用程序通过调用此接口绑定本地视频流的显示视窗(view),并设置视频显示模式。 在应用程序开发中,通常在初始化后调用该方法进行本地视频设置,然后再加入频道。退出频道后,绑定仍然有效,如果需要解除绑定,可以调用 setupLocalVideo(null) 。


 1/** 2 * Tutorial Step 3 3 * 设置本地视频显示属性 4 */ 5private void setupLocalVideo() { 6    FrameLayout container = (FrameLayout) findViewById(R.id.local_video_view_container); 7    SurfaceView surfaceView = RtcEngine.CreateRendererView(getBaseContext()); 8    surfaceView.setZOrderMediaOverlay(true); 9    container.addView(surfaceView);10    mRtcEngine.setupLocalVideo(new VideoCanvas(surfaceView, VideoCanvas.RENDER_MODE_ADAPTIVE, 0));11}
复制代码

加入一个频道

joinChannel(String token,String channelName,String optionalInfo,int optionalUid )方法让用户加入通话频道,在同一个频道内的用户可以互相通话,多个用户加入同一个频道,可以群聊。 使用不同 App ID 的应用程序是不能互通的。如果已在通话中,用户必须调用 leaveChannel() 退出当前通话,才能进入下一个频道。


1/**2 * Tutorial Step 43 * 加入一个频道4 */5private void joinChannel() {6    //如果不指定UID,Agroa将自动生成并分配一个UID7    mRtcEngine.joinChannel(null, "demoChannel1", "Extra Optional Data", 0);8}
复制代码

设置远端视频显示属性

setupRemoteVideo( VideoCanvas remote)方法用于绑定远程用户和显示视图,即设定 uid 指定的用户用哪个视图显示。调用该接口时需要指定远程视频的 uid,一般可以在进频道前提前设置好。


如果应用程序不能事先知道对方的 uid,可以在 APP 收到 onUserJoined 事件时设置。如果启用了视频录制功能,视频录制服务会做为一个哑客户端加入频道,因此其他客户端也会收到它的 onUserJoined 事件,APP 不应给它绑定视图(因为它不会发送视频流),如果 APP 不能识别哑客户端,可以在 onFirstRemoteVideoDecoded 事件时再绑定视图。解除某个用户的绑定视图可以把 view 设置为空。退出频道后,SDK 会把远程用户的绑定关系清除掉。


 1/** 2 * Tutorial Step 5 3 * 设置远端视频显示属性 4 */ 5private void setupRemoteVideo(int uid) { 6    FrameLayout container = (FrameLayout) findViewById(R.id.remote_video_view_container); 7    if (container.getChildCount() >= 1) { 8        return; 9    }10    SurfaceView surfaceView = RtcEngine.CreateRendererView(getBaseContext());11    container.addView(surfaceView);12    mRtcEngine.setupRemoteVideo(new VideoCanvas(surfaceView, VideoCanvas.RENDER_MODE_ADAPTIVE, uid));13    surfaceView.setTag(uid);14    //文案可随意定制15    View tipMsg = findViewById(R.id.quick_tips_when_use_agora_sdk);16    tipMsg.setVisibility(View.GONE);17}
复制代码

离开当前频道

leaveChannel()方法用于离开频道,即挂断或退出通话。


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


 1/** 2 * Tutorial Step 6 3 * 离开当前频道 4 */ 5private void leaveChannel() { 6    mRtcEngine.leaveChannel(); 7} 8public void onEncCallClicked(View view) { 9    finish();10}11@Override12protected void onDestroy() {13    super.onDestroy();14    leaveChannel();15    RtcEngine.destroy();16    mRtcEngine = null;17}
复制代码

管理摄像头

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


1/**2 * Tutorial Step 73 * 切换前置/后置摄像头4 */5public void onSwitchCameraClicked(View view) {6    mRtcEngine.switchCamera();7}
复制代码

将自己静音

muteLocalAudioStream(boolean muted)方法用于静音/取消静音。该方法可以允许/禁止往网络发送本地音频流。但该方法并没有禁用麦克风,不影响录音状态。


 1/** 2 * Tutorial Step 8 3 * 将自己静音 4 */ 5public void onLocalAudioMuteClicked(View view) { 6    ImageView iv = (ImageView) view; 7    if (iv.isSelected()) { 8        iv.setSelected(false); 9        iv.clearColorFilter();10    } else {11        iv.setSelected(true);12        iv.setColorFilter(getResources().getColor(R.color.colorPrimary), PorterDuff.Mode.MULTIPLY);13    }14    mRtcEngine.muteLocalAudioStream(iv.isSelected());15}
复制代码

暂停本地视频流

muteLocalVideoStream(boolean muted)方法用于暂停发送本地视频流,但该方法并没有禁用摄像头,不影响本地视频流获取。


 1/** 2 * Tutorial Step 9 3 * 暂停本地视频流 4 */ 5public void onLocalVideoMuteClicked(View view) { 6    ImageView iv = (ImageView) view; 7    if (iv.isSelected()) { 8        iv.setSelected(false); 9        iv.clearColorFilter();10    } else {11        iv.setSelected(true);12        iv.setColorFilter(getResources().getColor(R.color.colorPrimary), PorterDuff.Mode.MULTIPLY);13    }14    mRtcEngine.muteLocalVideoStream(iv.isSelected());15    FrameLayout container = (FrameLayout) findViewById(R.id.local_video_view_container);16    SurfaceView surfaceView = (SurfaceView) container.getChildAt(0);17    surfaceView.setZOrderMediaOverlay(!iv.isSelected());18    surfaceView.setVisibility(iv.isSelected() ? View.GONE : View.VISIBLE);19}
复制代码

运行效果

拿两部手机安装编译好的 App,如果能看见两个自己,说明你成功了。


本文转载自声网 Agora 公众号。


原文链接:https://mp.weixin.qq.com/s/mP8MOWwkKv71dFKIH2UAlA


2020-02-25 15:171735

评论

发布
暂无评论
发现更多内容

分而治之——D&C

Kylin

3月日更 21天挑战 分而治之

量化策略软件搭建,马丁策略交易软件开发

2021年DevOps的四大趋势

禅道项目管理

DevOps 工具 趋势 Redis开发与运维

美女师姐说给你听!我成为蚂蚁安全工程师的初体验

DT极客

智慧公安重点人员管控系统大数据分析平台的搭建

13828808769

智慧城市 智慧交通

终于知道为啥网页不让我复制粘贴了!

华为云开发者联盟

js 代码 button事件 复制粘贴 输入框

畅想数据湖

数据社

数据仓库 数据湖 ETL ELT

网络连接总超时?从四层模型上解析网络是怎么连接的

京东科技开发者

计算机网络 服务器 域名

超详细!手把手带你快速入门 GitHub!

JackTian

git GitHub 开源

技术杂谈 | Flutter 的性能分析、工程架构与细节处理

有道技术团队

flutter

神策大数据技术直播系列课第二季,开讲啦

神策技术社区

大数据 性能优化 大前端 工程师 事件分析

MoviePy - 中文文档(一个专业的python音视频编辑库)教程

ucsheep

Python 音视频 视频剪辑 Moviepy 视频合成

带你了解数据库的“吸尘器”:VACUUM

华为云开发者联盟

数据库 数据 GaussDB(DWS) VACUUM

PHP程序员如何简单的开展服务治理架构(一)

CrazyCodes

php 服务治理

区块链BaaS应用服务平台的搭建

13828808769

区块链+ #区块链#

Python OpenCV 彩色图像与灰度图像的转换

梦想橡皮擦

3月日更

直播预告 | 数据操作加速器,CloudQuery v1.3.5 发布

BinTools图尔兹

sql 编辑器 数据治理 数据安全 数据库管理工具

云原生技术及其未来发展趋势展望 | 趋势解读

云原生

PostgreSQL 集群宕机后恢复

桜喵ノねこ

IAP:物联网终端软件升级技术

华为云开发者联盟

IoT LiteOS iap 物联网终端 OTA

力扣(LeetCode)刷题,简单题(第14期)

不脱发的程序猿

面试 LeetCode 28天写作 算法攻关 3月日更

用 WebRTC 打造一个音乐教育 App,要解决哪些音质难题?

阿里云视频云

音视频 WebRTC 在线教育 RTC

云原生数据库风起云涌,华为云GaussDB破浪前行

华为云开发者联盟

数据库 架构 云原生 华为云 GaussDB

“刷脸”日益泛滥,“掌经脉”开辟生物识别新路

E科讯

商品溯源之痛,区块链对商品假冒的解决方案

13828808769

区块链+ 区块链应用 区块链发展 #区块链#

自媒体平台数据统计分析爬虫之【趣头条】模拟登陆分析详解及数据统计接口详解

ucsheep

接口 爬虫 趣头条 模拟登录

区块链技术或加速企业“碳中和”战略落地

CECBC

区块

情指勤指挥调度平台搭建,公安局情报指挥系统

k8s(Kubernetes)中Pod,Deployment,ReplicaSet,Service之间关系分析

ucsheep

Kubernetes k8s pod Deployment ReplicaSet

【LeetCode】132模式Java题解

Albert

算法 LeetCode 3月日更

“数字云南”建设成效逐渐显现 区块链财政电子票据带来民生与环保效益

CECBC

区块链

基于Agora SDK实现Android一对一音视频聊天应用_行业深度_声网_InfoQ精选文章