本系列教程将分为三期,分享基于 Agora SDK 在各系统平台应用中实现一对一视频通话、多人互动直播,以及结合跨平台技术进行开发。本期推送在 Android、iOS、Windows、Web、macOS 上实现一对一视频通话。
声网 Agora SDK 让应用和网站都可以实现高质量的音频通话、视频通话、全互动直播。本文通过Agora Native SDK 在 Android 端实现一个视频通话应用。
1sourceSets {
2 main {
3 jniLibs.srcDirs = ['../../../libs']
4 }
5}
1ndk {
2 abiFilters "armeabi-v7a", "x86"
3}
1compile 'io.agora.rtc:full-sdk:2.0.0'
1compile fileTree(dir: '../../../libs', include: ['*.jar'])
如果有自定义NDK的必要,可以继续在app module的build.gradle文件的android代码块中添加如下代码:
1externalNativeBuild {
2 ndkBuild {
3 path 'src/main/cpp/Android.mk'
4 }
5}
1externalNativeBuild {
2 ndkBuild {
3 arguments "NDK_APPLICATION_MK:=src/main/cpp/Application.mk"
4 }
5}
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" />
1-keep class io.agora.**{*;}
经过以上过程后,我们已经完成了声网Agora.io SDK的快速集成,迈出了走向连麦直播、在线抓娃娃、直播问答、远程狼人杀等风口的第一步。在接下来的文章里,我将继续分享APP ID鉴权、Token鉴权、一对一视频聊天、创建群聊room、分屏、窗口切换和前后摄像头切换等内容。
APP ID鉴权
1<string name="agora_app_id"><#YOUR APP ID#></string>
Token鉴权
将你的 App Certificate 保存在服务器端,且对任何客户端均不可见。当项目的 App Certificate 被启用后,你必须使用 Token。例如: 在启用 App Certificate 前,你可以使用 App ID 加入频道。但启用了 App Certificate 后,你就必须使用 Token 加入频道。后台如何用App Certificate生成Token本文不做赘述。
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 @Override
16 public void onFirstRemoteVideoDecoded(final int uid, int width, int height, int elapsed) {
17 runOnUiThread(new Runnable() {
18 @Override
19 public void run() {
20 //设置远端视频显示属性
21 setupRemoteVideo(uid);
22 }
23 });
24 }
25 @Override
26 public void onUserOffline(int uid, int reason) {
27 runOnUiThread(new Runnable() {
28 @Override
29 public void run() {
30 //其他用户离开当前频道回调
31 onRemoteUserLeft();
32 }
33 });
34 }
35 @Override
36 public void onUserMuteVideo(final int uid, final boolean muted) {
37 runOnUiThread(new Runnable() {
38 @Override
39 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 4
3 * 加入一个频道
4 */
5private void joinChannel() {
6 //如果不指定UID,Agroa将自动生成并分配一个UID
7 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@Override
12protected 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 7
3 * 切换前置/后置摄像头
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,如果能看见两个自己,说明你成功了。
如开发中遇到问题,可访问 RTC 开发者社区发帖提问