写点什么

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

评论

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

Flutter 安卓 Platform 与 Dart 端消息通信方式 Channel 源码解析

工匠若水

flutter android 8月日更

作业

Li. Mr

python通过PyQt5实现登录界面

Python研究者

8月日更

01. 你身边的AI

Databri_AI

人工智能

图像分类-cifar100 实验研究

毛显新

人工智能 神经网络 tensorflow 图像识别 keras

初识html,一文搞懂HTMl骨架标签都有哪些含义及浏览器内核

你好bk

html html5 大前端 浏览器 html/css

模块一作业

potti

架构实战营

HTTP接口测试基础【FunTester框架教程】

FunTester

自动化测试 教程 接口测试 测试框架 FunTester

网络安全小白别拜师了,求人不如求己

网络安全学海

黑客 网络安全 信息安全 渗透测试 安全漏洞

🚀【Guava技术指南】「RateLimiter类」服务请求流控实现方案

码界西柚

Java ratelimiter Guava 8月日更

Linux之nohup命令

入门小站

Linux

仓储执行系统(WES)

申扬科技

WCS wms WES 仓储执行系统

【DPDK工程师手册】 —— 官方文档,最新视频,开源项目,论文,大厂内部ppt,知名工程师一览表

奔着腾讯去

Linux DPDK VPP

在线JSON转XML工具

入门小站

工具

微信的业务架构图

Rabbit

架构实战营

学习心得-架构训练营-第一课

Fm

☕【Java技术指南】「OpenJDK专题」想不想编译属于你自己的JDK呢?(Windows10环境)

码界西柚

Java jdk Openjdk 8月日更

正经人一辈子都用不到的 JavaScript 方法总结 (二)

编程三昧

JavaScript 大前端 8月日更

极客时间【架构实战营】第二期 模块一作业

Geek_91606e

架构实战营

graphql中的'子查询'

杜艮魁

开源 后端 graphql

架构师实战营作业[模块一]

看,有只猪

微信业务架构图-作业

Geek_a772a7

架构训练营模块一作业

guangbao

百度地图开发-实现离线地图功能 05

Andy阿辉

android 百度地图 Android 小菜鸟 Android端 8月日更

架构训练营 模块一作业

初一

OceanBase 源码解读(三)分区的一生

OceanBase 数据库

数据库 分布式数据库 oceanbase OceanBase 开源 OceanBase 社区版

学生管理系统(作业)

Geek_a772a7

AI巨头们建造的“新世界”,进展如何?

脑极体

架构实战营模块六作业

老猎人

架构实战营

[架构实战营]模块一

Amy

架构实战营 业务架构图

搜索引擎渐行渐远,未来路在何方

石头IT视角

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