作者:声网Agora用户,资深Android开发者吴东洋。
本系列文章分享了基于Agora SDK 2.1实现多人视频通话的实践经验。
自从2016年,鼓吹“互联网寒冬”的论调甚嚣尘上,2017年亦有愈演愈烈之势。但连麦直播、在线抓娃娃、直播问答、远程狼人杀等类型的项目却异军突起,成了投资人的风口,创业者的蓝海和用户的必装App,而这些方向的项目都有一个共同的特点——都依赖视频通话和全互动直播技术。
声网Agora.io的SDK让App和网站都可以实现高质量的音频通话、视频通话、全互动直播。我试着通过该SDK实现一个多人视频通话应用。本文先分享集成的部分。
环境声网Agora.io SDK的兼容性良好,对硬件设备和软件系统的要求不高,开发环境和测试环境满足以下条件即可:
Android SDK API Level >= 16
Android Studio 2.0 或以上版本
支持语音和视频功能的真机
App 要求 Android 4.1 或以上设备
以下是我试用声网Agora.io SDK的开发环境和测试环境:
开发环境
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
步骤二:既然我们要把声网Agora.io集成到自己的项目里,所以不必运行sample,我们自己新建一个HelloAgora项目,注意一定要支持C++哦。
步骤三:把libs文件夹里的arm64-v8a、、armeabi-v7a以及x86文件夹复制粘贴到app module的libs里。如果有NDK开发的必要,则把libs->include文件夹里的两个.h头文件复制粘贴到合适位置。
步骤四:首先在app module的build.gradle文件的android代码块中添加如下代码:
sourceSets { main { jniLibs.srcDirs = ["../../../libs"] } }
然后在app module的build.gradle文件的android->defaultConfig代码块中添加如下代码:
ndk { abiFilters "armeabi-v7a", "x86" }
接下来在app module的build.gradle文件的dependencies代码块中添加如下代码:
compile "io.agora.rtc:full-sdk:2.0.0"
如果用复制粘贴jar的方式,那么此处添加如下代码:
compile fileTree(dir: "../../../libs", include: ["*.jar"])
如果有自定义NDK的必要,可以继续在app module的build.gradle文件的android代码块中添加如下代码:
externalNativeBuild { ndkBuild { path "src/main/cpp/Android.mk" } }
然后在app module的build.gradle文件的android->defaultConfig代码块中添加如下代码:
externalNativeBuild { ndkBuild { arguments "NDK_APPLICATION_MK:=src/main/cpp/Application.mk" } }
最后sync一下,声网Agora.io的SDK就集成到项目中来了。
权限SDK集成完毕后,为了保证SDK能正常运行,我们需要在AndroidManisfest.xml 文件中声明以下权限:
这些权限都是Android开发过程中的常见权限,有经验的程序员都会感觉眼熟,WRITE_EXTERNAL_STORAGE等敏感权限适配Android 6.0以后版本的问题并非本文关注重点,在此不做赘述。
混淆代码集成SDK并声明了权限后,就该考虑混淆的问题了,我们需要在project的proguard-rules.pro文件里添加以下代码:
-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/pr...“项目”中点击“添加新项目”,只需输入项目名就可生成APP ID,全过程如下图所示:
找到,把“<#YOUR APP ID#>”替换为图中的马赛克里的字符串。
<#YOUR APP ID#>
以上就是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 的运行。
private RtcEngine mRtcEngine; /** * Tutorial Step 1 * 初始化Agora,创建 RtcEngine 对象 */ private void initializeAgoraEngine() { try { mRtcEngine = RtcEngine.create(getBaseContext(), getString(R.string.agora_app_id), mRtcEventHandler); } catch (Exception e) { Log.e(LOG_TAG, Log.getStackTraceString(e)); throw new RuntimeException("Agora初始化失败了,检查一下是哪儿出错了 " + Log.getStackTraceString(e)); } } private final IRtcEngineEventHandler mRtcEventHandler = new IRtcEngineEventHandler() { @Override public void onFirstRemoteVideoDecoded(final int uid, int width, int height, int elapsed) { runOnUiThread(new Runnable() { @Override public void run() { //设置远端视频显示属性 setupRemoteVideo(uid); } }); } @Override public void onUserOffline(int uid, int reason) { runOnUiThread(new Runnable() { @Override public void run() { //其他用户离开当前频道回调 onRemoteUserLeft(); } }); } @Override public void onUserMuteVideo(final int uid, final boolean muted) { runOnUiThread(new Runnable() { @Override public void run() { //其他用户已停发/已重发视频流回调 onRemoteUserVideoMuted(uid, muted); } }); } }; private void onRemoteUserLeft() { FrameLayout container = (FrameLayout) findViewById(R.id.remote_video_view_container); container.removeAllViews(); //文案可随意定制 View tipMsg = findViewById(R.id.quick_tips_when_use_agora_sdk); tipMsg.setVisibility(View.VISIBLE); } private void onRemoteUserVideoMuted(int uid, boolean muted) { FrameLayout container = (FrameLayout) findViewById(R.id.remote_video_view_container); SurfaceView surfaceView = (SurfaceView) container.getChildAt(0); Object tag = surfaceView.getTag(); if (tag != null && (Integer) tag == uid) { surfaceView.setVisibility(muted ? View.GONE : View.VISIBLE); } }
打开视频模式
enableVideo()方法用于打开视频模式。可以在加入频道前或者通话中调用,在加入频道前调用,则自动开启视频模式,在通话中调用则由音频模式切换为视频模式。调用 disableVideo() 方法可关闭视频模式。
setVideoProfile()方法设置视频编码属性(Profile)。每个属性对应一套视频参数,如分辨率、帧率、码率等。 当设备的摄像头不支持指定的分辨率时,SDK 会自动选择一个合适的摄像头分辨率,但是编码分辨率仍然用 setVideoProfile() 指定的。
该方法仅设置编码器编出的码流属性,可能跟最终显示的属性不一致,例如编码码流分辨率为 640x480,码流的旋转属性为 90 度,则显示出来的分辨率为竖屏模式。
/** * Tutorial Step 2 * 打开视频模式,并设置本地视频属性 */ private void setupVideoProfile() { //打开视频模式 mRtcEngine.enableVideo(); //设置本地视频属性 mRtcEngine.setVideoProfile(Constants.VIDEO_PROFILE_360P, false); }
设置本地视频显示属性
setupLocalVideo( VideoCanvas local )方法用于设置本地视频显示信息。应用程序通过调用此接口绑定本地视频流的显示视窗(view),并设置视频显示模式。 在应用程序开发中,通常在初始化后调用该方法进行本地视频设置,然后再加入频道。退出频道后,绑定仍然有效,如果需要解除绑定,可以调用 setupLocalVideo(null) 。
/** * Tutorial Step 3 * 设置本地视频显示属性 */ private void setupLocalVideo() { FrameLayout container = (FrameLayout) findViewById(R.id.local_video_view_container); SurfaceView surfaceView = RtcEngine.CreateRendererView(getBaseContext()); surfaceView.setZOrderMediaOverlay(true); container.addView(surfaceView); mRtcEngine.setupLocalVideo(new VideoCanvas(surfaceView, VideoCanvas.RENDER_MODE_ADAPTIVE, 0)); }
加入一个频道
joinChannel(String token,String channelName,String optionalInfo,int optionalUid )方法让用户加入通话频道,在同一个频道内的用户可以互相通话,多个用户加入同一个频道,可以群聊。 使用不同 App ID 的应用程序是不能互通的。如果已在通话中,用户必须调用 leaveChannel() 退出当前通话,才能进入下一个频道。
/** * Tutorial Step 4 * 加入一个频道 */ private void joinChannel() { //如果不指定UID,Agroa将自动生成并分配一个UID mRtcEngine.joinChannel(null, "demoChannel1", "Extra Optional Data", 0); }
设置远端视频显示属性
setupRemoteVideo( VideoCanvas remote)方法用于绑定远程用户和显示视图,即设定 uid 指定的用户用哪个视图显示。调用该接口时需要指定远程视频的 uid,一般可以在进频道前提前设置好。
如果应用程序不能事先知道对方的 uid,可以在 APP 收到 onUserJoined 事件时设置。如果启用了视频录制功能,视频录制服务会做为一个哑客户端加入频道,因此其他客户端也会收到它的 onUserJoined 事件,APP 不应给它绑定视图(因为它不会发送视频流),如果 APP 不能识别哑客户端,可以在 onFirstRemoteVideoDecoded 事件时再绑定视图。解除某个用户的绑定视图可以把 view 设置为空。退出频道后,SDK 会把远程用户的绑定关系清除掉。
/** * Tutorial Step 5 * 设置远端视频显示属性 */ private void setupRemoteVideo(int uid) { FrameLayout container = (FrameLayout) findViewById(R.id.remote_video_view_container); if (container.getChildCount() >= 1) { return; } SurfaceView surfaceView = RtcEngine.CreateRendererView(getBaseContext()); container.addView(surfaceView); mRtcEngine.setupRemoteVideo(new VideoCanvas(surfaceView, VideoCanvas.RENDER_MODE_ADAPTIVE, uid)); surfaceView.setTag(uid); //文案可随意定制 View tipMsg = findViewById(R.id.quick_tips_when_use_agora_sdk); tipMsg.setVisibility(View.GONE); }
离开当前频道
leaveChannel()方法用于离开频道,即挂断或退出通话。
当调用 joinChannel() API 方法后,必须调用 leaveChannel() 结束通话,否则无法开始下一次通话。 不管当前是否在通话中,都可以调用 leaveChannel(),没有副作用。该方法会把会话相关的所有资源释放掉。该方法是异步操作,调用返回时并没有真正退出频道。在真正退出频道后,SDK 会触发 onLeaveChannel 回调。
/** * Tutorial Step 6 * 离开当前频道 */ private void leaveChannel() { mRtcEngine.leaveChannel(); } public void onEncCallClicked(View view) { finish(); } @Override protected void onDestroy() { super.onDestroy(); leaveChannel(); RtcEngine.destroy(); mRtcEngine = null; }
管理摄像头
switchCamera()方法用于在前置/后置摄像头间切换。除此以外Agora还提供了一下管理摄像头的方法:例如setCameraTorchOn(boolean isOn)设置是否打开闪光灯、setCameraAutoFocusFaceModeEnabled(boolean enabled)设置是否开启人脸对焦功能等等。
/** * Tutorial Step 7 * 切换前置/后置摄像头 */ public void onSwitchCameraClicked(View view) { mRtcEngine.switchCamera(); }
将自己静音
muteLocalAudioStream(boolean muted)方法用于静音/取消静音。该方法可以允许/禁止往网络发送本地音频流。但该方法并没有禁用麦克风,不影响录音状态。
/** * Tutorial Step 8 * 将自己静音 */ public void onLocalAudioMuteClicked(View view) { ImageView iv = (ImageView) view; if (iv.isSelected()) { iv.setSelected(false); iv.clearColorFilter(); } else { iv.setSelected(true); iv.setColorFilter(getResources().getColor(R.color.colorPrimary), PorterDuff.Mode.MULTIPLY); } mRtcEngine.muteLocalAudioStream(iv.isSelected()); }
暂停本地视频流
muteLocalVideoStream(boolean muted)方法用于暂停发送本地视频流,但该方法并没有禁用摄像头,不影响本地视频流获取。
/** * Tutorial Step 9 * 暂停本地视频流 */ public void onLocalVideoMuteClicked(View view) { ImageView iv = (ImageView) view; if (iv.isSelected()) { iv.setSelected(false); iv.clearColorFilter(); } else { iv.setSelected(true); iv.setColorFilter(getResources().getColor(R.color.colorPrimary), PorterDuff.Mode.MULTIPLY); } mRtcEngine.muteLocalVideoStream(iv.isSelected()); FrameLayout container = (FrameLayout) findViewById(R.id.local_video_view_container); SurfaceView surfaceView = (SurfaceView) container.getChildAt(0); surfaceView.setZOrderMediaOverlay(!iv.isSelected()); surfaceView.setVisibility(iv.isSelected() ? View.GONE : View.VISIBLE); }
运行效果
拿两部手机安装编译好的App,如果能看见两个自己,说明你成功了。
通过本文的学习,我们已经掌握了利用Agora SDK进行一对一聊天的技巧,接下来的文章中,我将继续介绍如何实现多人聊天室。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/69029.html
摘要:我们先实现一个瀑布流瀑布流的实现方式很多,本文采用结合的来实现。有了一个可用的瀑布流之后,下面我们就可以实现动态聊天窗了动态聊天窗的要点在于的大小由视频的宽高比决定,因此及其对应的就该注意不要写死尺寸。 作者:声网用户,资深Android工程师吴东洋本系列文章分享了基于Agora SDK 2.1实现多人视频通话的实践经验。 在上一篇《Android 多人视频聊天应用的开发(一)一对一聊...
阅读 2066·2023-04-25 22:58
阅读 1411·2021-09-22 15:20
阅读 2696·2019-08-30 15:56
阅读 1989·2019-08-30 15:54
阅读 2105·2019-08-29 12:31
阅读 2730·2019-08-26 13:37
阅读 594·2019-08-26 13:25
阅读 2099·2019-08-26 11:58