速来报名!AICon北京站鸿蒙专场~ 了解详情
写点什么

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

评论

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

005云原生之Service Mesh(Istio+Envoy)

穿过生命散发芬芳

云原生 10月月更

Webrtc video framerate/resolution自适应

webrtc developer

WebRTC

第 11 章 -《Linux 一学就会》- 重定向和文件的查找

学神来啦

Linux linux运维 linux学习 linux云计算

架构实战训练营|课后作业|模块5

Frode

「架构实战营」

一分钟搞懂FAST Agile

俞凡

敏捷 10月月更

如何激励员工?

石云升

项目管理 管理 引航计划 内容合集 10月月更

网络流量分析场景浅谈

穿过生命散发芬芳

后端 引航计划 网络流量分析

团队管理之如何成为核心员工

小诚信驿站

团队管理 管理 引航计划 内容合集

JavaScript 中的文档对象模型 DOM

devpoint

CSS html DOM 10月月更

Scrum Patterns:Sprint回顾(译)

Bruce Talk

敏捷 译文 Agile Scrum Patterns

linux之autojump命令

入门小站

Linux

深入理解Git submodules

俞凡

git 架构 10月月更

「绝密档案」“爆料”完整秒杀架构的设计到技术关键点的“八卦资料”

洛神灬殇

秒杀系统 秒杀架构 秒杀架构设计 web技术分析 10月月更

【Flutter 专题】40 日常问题小结 (一)

阿策小和尚

Flutter 小菜 0 基础学习 Flutter Android 小菜鸟 10月月更

漫游语音识别技术——带你走进语音识别技术的世界

攻城先森

深度学习 音视频 nlp 语音识别

计算架构模式之接口篇

十二万伏特皮卡丘

【LeetCode】最长回文子串Java题解

Albert

算法 LeetCode 10月月更

最短路径算法

Dobbykim

算法 图论

工业级高精度电磁流量计解决方案

不脱发的程序猿

ADI 工业高精度传感器 流量传感器 优秀论文期刊

容器 & 服务:Helm Charts(二)安装与使用

程序员架构进阶

Kubernetes 容器 Helm Helm Charts 10月月更

【Zookeeper技术专题】从Paxo算法出发认识一下Zookeeper

洛神灬殇

PAXOS ZooKeeper原理 paxos协议 Paxo 10月月更

网络架构知识总结

十二万伏特皮卡丘

关心你的团队,这才是最有效的管理技巧

俞凡

管理 10月月更

阿里开源的这个库,让 Excel 导出不再复杂(简简单单的写)

看山

Java EasyExcel 10月月更

Go dlv <autogenerate> 代码定位

非晓为骁

源码分析 Go 语言 dlv rt0_go autogenerate

在线随机抛硬币工具

入门小站

工具

第 10 章 -《Linux 一学就会》- centos8系统进程管理

学神来啦

Linux 运维 linux学习 linux云计算

3. 有点难~ Python函数式编程中 itertools 模块

梦想橡皮擦

10月月更

SpringMVC源码分析-HandlerAdapter(6)-ModelFactory组件分析

Brave

源码 springmvc 10月月更

006云原生之Service Mesh(Spring Cloud)

穿过生命散发芬芳

云原生 10月月更

一分钟搞懂SOLID原则

俞凡

架构 10月月更

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