【ArchSummit架构师峰会】探讨数据与人工智能相互驱动的关系>>> 了解详情
写点什么

基于 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:171420

评论

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

LocalDateTime和Date的比较与区别

彭阿三

时间格式化 LocalDateTime Date

30岁,就被大厂抛弃了

J.Smile

求职

独立开发者为什么不需要运营也能月薪几万,甚至几十万?

非著名程序员

程序员 独立开发者 副业赚钱 程序人生 提升认知

2020年6月11日 高性能MySQL

瑞克与莫迪

架构第一周-学习总结

seng man

架构第一课学习总结

师哥

钟离昧的第一张架构设计图之旅

XxxxxxxMr

极客大学架构师训练营 听课总结 - 架构视图,设计文档 -- 第二课

John(易筋)

极客时间 极客大学 架构设计 极客大学架构师训练营 架构文档

数据同步,应该如何设计

迹_Jason

作业一:食堂就餐卡系统设计

seng man

极客大学架构师训练营

SpringBoot分布式任务中间件开发 附视频讲解 (手把手教你开发和使用中间件)

小傅哥

小傅哥 中间件 springboot 分布式任务

使用VSCode连接到IBM Cloud区块链网络

程序那些事

智能合约 hyperledger fabric ibm cloud

数据库周刊27丨6月最新国产数据库排行;OB成立新公司奥星贝斯;腾讯云发布图数据库TGDB;Oracle坏块修复;MySQL故障排查导图;经典SQL语句大全...

墨天轮

数据库

架构师训练营第一周学习总结

R20114

搭建websocket消息推送服务,必须要考虑的几个问题

GoEasy消息推送

websocket 消息推送 即时通讯

vs code中使用vetur对eslint格式化

玏佾

vscode

量子技术到底有哪些突破值得重点关注?

蔡芳芳

游戏夜读 | 如何成长为游戏人?

game1night

Intellij IDEA 右击没有run

程李文华

可视化算法网站汇总,从此简单学算法!(附动图)

王磊

Java 算法

从微服务到Service Mesh

博文视点Broadview

架构 微服务 Service Mesh istio 架构师

Android 无埋点从入门到放弃:了解 Java 字节码

GrowingIO技术专栏

关于UML、4+1视图、系统架构的思考

吴建中

系统/子系统/模块/组件/框架/架构

gen_jin

钟离昧的一梭子架构师之旅

XxxxxxxMr

<<架构师训练营>>第一周作业

R20114

极客大学架构师训练营

读笔 | 既然拖延症难以根治,不妨暂且享受它

张鸱鸺

读书笔记 时间管理 随笔杂谈

02-kubernetes自建CA及双向TLS认证

绿星雪碧

Kubernetes TLS CA证书

非结构化数据可视化 —— 现在与未来

做技术BP的文案Gou

人工智能 大数据 数据可视化 非结构化数据

【写作群星榜】6.5~6.11写作平台优秀作者&文章排名

InfoQ写作社区官方

写作平台 排行榜 热门活动

你并不理解i++和++i

flyhero

Java 程序员 JVM i++

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