美文网首页Android开发经验谈Android技术知识程序员
Agora SDK 在Android中的使用(在线视频通话)

Agora SDK 在Android中的使用(在线视频通话)

作者: 06fd4cf1f427 | 来源:发表于2019-05-09 20:57 被阅读33次

    作者:张风捷特烈
    首先声明本文是Agora SDK入门的小白文章


    一.集成

    1.注册账号创建项目

    其中最重要的要数 App ID 了


    2.下载Agora SDK

    二、学会看示例代码(可跳过)

    1.整体了解项目结构(1v1的视频通信示例)

    以前看一个Android项目先看AndroidManifest.xml,我更喜欢先把文件夹内的结构树打印出来
    打印文件夹内的结构树可详见:杂篇-从整理文件发起的杂谈[-File-]

    |---app
        |---.gitignore
        |---build.gradle
        |---libs
            |---PLACEHOLDER
        |---proguard-rules.pro
        |---src
            |---main
                |---AndroidManifest.xml
                |---java
                    |---io
                        |---agora
                            |---tutorials1v1vcall
                                |---VideoChatViewActivity.java
                |---jniLibs
                    |---arm64-v8a
                        |---PLACEHOLDER
                    |---armeabi-v7a
                        |---PLACEHOLDER
                    |---x86
                        |---PLACEHOLDER
                |---res
                    |---drawable-xxxhdpi
                        |---btn_end_call.png
                        |---btn_mute.png
                        |---btn_switch_camera.png
                        |---btn_video.png
                        |---btn_voice.png
                        |---ic_launcher.png
                    |---layout
                        |---activity_video_chat_view.xml
                    |---values
                        |---colors.xml
                        |---dimens.xml
                        |---strings.xml
                        |---styles.xml
    |---build.gradle
    |---gradle
        |---wrapper
            |---gradle-wrapper.jar
            |---gradle-wrapper.properties
    |---gradle.properties
    |---gradlew
    |---gradlew.bat
    |---images
        |---ActivityViewChat.png
    |---LICENSE.md
    |---README.md
    |---README.zh.md
    |---settings.gradle
    

    2.查看最项目的settings.gradlebuild.gradle(最外层)

    如果你想导入AS中查看,可以看一下com.android.tools.build:gradle的版本修改一下

    ---->[settings.gradle]----------------看一下项目包含的模块------------
    include ':app'
    
    ---->[build.gradle]----------------看一下项目的一些信息------------
    buildscript {
        repositories {
            jcenter()
            google()
        }
        dependencies {
            classpath 'com.android.tools.build:gradle:3.1.4'
        }
    }
    allprojects {
        repositories {
            jcenter()
            google()
        }
    }
    task clean(type: Delete) {
        delete rootProject.buildDir
    }
    

    3.查看模块下的build.gradle
    ---->[app/build.gradle]----------------看一下项目的具体信息------------
    apply plugin: 'com.android.application'
    android {
        compileSdkVersion 26
        defaultConfig {
            applicationId "io.agora.tutorials1v1vcall"
            minSdkVersion 16
            targetSdkVersion 26
            versionCode 1
            versionName "1.0"
        }
        buildTypes {
            release {
                minifyEnabled false
                proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            }
        }
        sourceSets {//这里是jniLibs的目录
            main {
                jniLibs.srcDirs = ['../../../libs']
            }
        }
    }
    
    dependencies {//这里是依赖
        implementation fileTree(dir: '../../../libs', include: ['*.jar']) // DO NOT CHANGE, CI may needs it when packaging
        implementation 'com.android.support:appcompat-v7:26.1.0'
    }
    

    4.查看AndroidManifest.xml,得到入口Activity

    可见示例的入口是VideoChatViewActivity,并看一下权限

    <activity
        android:name=".VideoChatViewActivity"
        android:screenOrientation="sensorPortrait"
        android:theme="@style/FullScreenVideoTheme">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
    
    |--- 权限 ------------
    <!--网络权限-->
    <uses-permission android:name="android.permission.INTERNET"/>
    <!--录音权限-->
    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
    <!--更改录音设置-->
    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
    <!--网络状态权限-->
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
    <!--相机权限-->
    <uses-permission android:name="android.permission.CAMERA"/>
    <!--蓝牙权限-->
    <uses-permission android:name="android.permission.BLUETOOTH"/>
    <!--SD卡写权限-->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    

    三、创建IChat项目


    1、配置项目

    项目的配置如图,将依赖包以及.so文件放在对应位置

    为了方便些,将res文件夹的资源拷贝一下


    2、配置APP ID

    3.视频通话Activity的分析

    一共也就200多行,还包括一大坨权限申请的代码,这里权限申请的代码单独拎出来,就当复习一下。

    3.1:权限申请(非要点,可忽略)
    ---->[成员变量]----------------------------------
    private static final int PERMISSION_REQ_ID = 22;
    //WRITE_EXTERNAL_STORAGE 权限只是为了保存日志到SD卡
    private static final String[] REQUESTED_PERMISSIONS = {
            Manifest.permission.RECORD_AUDIO,//录音权限
            Manifest.permission.CAMERA,//相机权限
            Manifest.permission.WRITE_EXTERNAL_STORAGE//SD卡写权限
    };
    
    if (checkSelfPermission(REQUESTED_PERMISSIONS[0], PERMISSION_REQ_ID) &&
            checkSelfPermission(REQUESTED_PERMISSIONS[1], PERMISSION_REQ_ID) &&
            checkSelfPermission(REQUESTED_PERMISSIONS[2], PERMISSION_REQ_ID)) {
        //执行到此处说明已有权限成功     
        initAgoraEngineAndJoinChannel();
    }
    
    /**
     * 检查权限的方法
     *
     * @param permission  权限
     * @param requestCode 请求码
     * @return 是否拥有权限
     */
    public boolean checkSelfPermission(String permission, int requestCode) {
        Log.i(LOG_TAG, "checkSelfPermission " + permission + " " + requestCode);
        if (ContextCompat.checkSelfPermission(this, permission) 
                != PackageManager.PERMISSION_GRANTED) {
            //发送权限请求
            ActivityCompat.requestPermissions(this, REQUESTED_PERMISSIONS, requestCode);
            return false;
        }
        return true;
    }
    
    @Override
    public void onRequestPermissionsResult(
            int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        Log.i(LOG_TAG, "onRequestPermissionsResult " + grantResults[0] + " " + requestCode);
        switch (requestCode) {
            case PERMISSION_REQ_ID: {//请求码
                if (grantResults[0] != PackageManager.PERMISSION_GRANTED ||
                        grantResults[1] != PackageManager.PERMISSION_GRANTED ||
                        grantResults[2] != PackageManager.PERMISSION_GRANTED) {
                    //三个权限有任意的未被允许,弹吐司,退出
                    showLongToast("Need permissions " +
                            Manifest.permission.RECORD_AUDIO + "/" +
                            Manifest.permission.CAMERA + "/" +
                            Manifest.permission.WRITE_EXTERNAL_STORAGE);
                    finish();
                    break;
                }
                //执行到此处说明用户已允许权限
                initAgoraEngineAndJoinChannel();
                break;
            }
        }
    }
    

    4.初始化Agora引擎和连接频道
    /**
     * 初始化Agora引擎和连接频道
     */
    private void initAgoraEngineAndJoinChannel() {
        initializeAgoraEngine();//初始化Agora引擎
        setupVideoProfile();//设置视频信息
        setupLocalVideo();//设置本地的视频窗
        joinChannel();//连接频道
    }
    
    /**
     * 初始化Agora引擎
     */
    private void initializeAgoraEngine() {
        try {
            mRtcEngine = RtcEngine.create(//实例化Rtc引擎
                getBaseContext(),//传入Context 
                getString(R.string.agora_app_id), //传入APP ID
                mRtcEventHandler);//RTC事件处理器
        } catch (Exception e) {//发生异常时捕获异常
            Log.e(LOG_TAG, Log.getStackTraceString(e));
            throw new RuntimeException("NEED TO check rtc sdk init fatal error\n" + Log.getStackTraceString(e));
        }
    }
    
    /**
     * 设置视频信息
     */
    private void setupVideoProfile() {
        mRtcEngine.enableVideo();//启用视屏
        mRtcEngine.setVideoEncoderConfiguration(//视频解码配置
                new VideoEncoderConfiguration(//实例化对象
                        VideoEncoderConfiguration.VD_120x120,//尺寸
                        VideoEncoderConfiguration.FRAME_RATE.FRAME_RATE_FPS_15,//帧率
                        VideoEncoderConfiguration.STANDARD_BITRATE,//比特率
                        VideoEncoderConfiguration.ORIENTATION_MODE.ORIENTATION_MODE_FIXED_PORTRAIT));//旋转模式
    }
    |---关于VideoEncoderConfiguration对象---->[VideoEncoderConfiguration构造方法]------------------
     public VideoEncoderConfiguration(
     VideoEncoderConfiguration.VideoDimensions dimensions, //尺寸
     VideoEncoderConfiguration.FRAME_RATE frameRate,//帧率 
     int bitrate, //比特率
     VideoEncoderConfiguration.ORIENTATION_MODE orientationMode)//旋转模式
    
    /**
     * 设置本地视频窗
     */
    private void setupLocalVideo() {
        FrameLayout container = findViewById(R.id.local_video_view_container);//FrameLayout视图
        SurfaceView surfaceView = RtcEngine.CreateRendererView(getBaseContext());//创建SurfaceView
        surfaceView.setZOrderMediaOverlay(true);
        container.addView(surfaceView);
        mRtcEngine.setupLocalVideo(new VideoCanvas(surfaceView, VideoCanvas.RENDER_MODE_FIT, 0));
    }
    
     /**
      * 连接到频道
      */
     private void joinChannel() {
         mRtcEngine.joinChannel(null, "demoChannel1", "Extra Optional Data", 0);
         // 如果你不指定 uid(第四参), 我们会为你生成一个 uid
     }
    

    5.RTC事件处理器:IRtcEngineEventHandler

    IRtcEngineEventHandler是一个抽象类,定义了非常多的抽象方法还有一些静态内部类

    private final IRtcEngineEventHandler mRtcEventHandler = new IRtcEngineEventHandler() {
        @Override//已完成远端视频首帧解码回调。
        public void onFirstRemoteVideoDecoded(final int uid, int width, int height, int elapsed) {
            runOnUiThread(() -> setupRemoteVideo(uid));
        }
        @Override//远端用户(通信模式)/主播(直播模式)离开当前频道回调。
        public void onUserOffline(int uid, int reason) {
            runOnUiThread(() -> onRemoteUserLeft());
        }
        @Override//远端用户静音回调。
        public void onUserMuteVideo(final int uid, final boolean muted) {
            runOnUiThread(() -> onRemoteUserVideoMuted(uid, muted));
        }
    };
    
    /**
     * 根据uid设置远端视频
     * @param uid 唯一标识符
     */
    private void setupRemoteVideo(int uid) {
        FrameLayout container = 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_FIT, u
        surfaceView.setTag(uid); // 用uid为surfaceView打标签
        View tipMsg = findViewById(R.id.quick_tips_when_use_agora_sdk); // 隐藏文字UI
        tipMsg.setVisibility(View.GONE);
    }
    
    /**
     * 远端用户挂断
     */
    private void onRemoteUserLeft() {
        FrameLayout container = findViewById(R.id.remote_video_view_container);
        container.removeAllViews();
        View tipMsg = findViewById(R.id.quick_tips_when_use_agora_sdk); // 显示文字UI
        tipMsg.setVisibility(View.VISIBLE);
    }
    
    /**
     * 远端用户静音
     * @param uid 标识符
     * @param muted 是否静音
     */
    private void onRemoteUserVideoMuted(int uid, boolean muted) {
        FrameLayout container = 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);
        }
    }
    

    6.几个点击事件
    /**
     * 是否屏蔽视频
     * @param view
     */
    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());//核心的一句API,
        FrameLayout container = 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);
    }
    
    /**
     * 是否静音
     * @param view
     */
    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());//核心的一句API,
    }
    
    /**
     * 切换摄像头
     * @param view
     */
    public void onSwitchCameraClicked(View view) {
        mRtcEngine.switchCamera();
    }
    
    /**
     * 关闭
     * @param view
     */
    public void onEncCallClicked(View view) {
        finish();
    }
    
     @Override
     protected void onDestroy() {
         super.onDestroy();
         leaveChannel();//离开频道
         RtcEngine.destroy();//引擎销毁
         mRtcEngine = null;//引擎置空
     }
    
     /**
     * 离开频道
     */
    private void leaveChannel() {
        mRtcEngine.leaveChannel();
    }
    

    最后

    小编整理了一些关于这方面的深入讲解的视频,如需要的话可以加群免费领取。

    本人Java开发4年Android开发5年,定期分享Android高级技术及经验分享,欢迎大家关注~(分享内容包括不限于高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter等全方面的Android进阶实践技术;希望能帮助到大家,也节省大家在网上搜索资料的时间来学习,也可以分享动态给身边好友一起学习!)

    最后文末放上一个技术交流群:Android IOC架构设计

    群内有许多技术大牛,有任何问题,欢迎广大网友一起来交流,群内还不定期免费分享高阶Android学习视频资料和面试资料包~

    再推荐一篇文章:“寒冬未过”,阿里P9架构分享Android必备技术点,让你offer拿到手软!

    相关文章

      网友评论

        本文标题:Agora SDK 在Android中的使用(在线视频通话)

        本文链接:https://www.haomeiwen.com/subject/iuogoqtx.html