声网Android端集成与一对一音视频功能实现

该sdk集成的前提条件

(现在一般都符合的)

  1. Android Studio 3.0 或以上版本
  2. Android SDK API 等级 16 或以上
  3. 支持 Android 4.1 或以上版本的移动设备
  4. 有效的 Agora 账户 和 App ID

集成SDK

集成方式一:使用JCenter自动集成

(该方法也是目前Android端项目使用的方式)

在项目的/app/build.gradle文件中,添加如下行:

dependencies {
 //…
 implementation 'io.agora.rtc:full-sdk:3.1.3'
}

具体的版本号可以查看官网文档的发版说明

集成方式二:手动复制sdk文件

  1. 前往 SDK 下载页面,获取最新版的 Agora 视频 SDK,然后解压。
  2. 将 SDK 包内 libs 路径下的如下文件,拷贝到你的项目路径下:
    在这里插入图片描述

添加项目权限

根据场景需要,在 /app/src/main/AndroidManifest.xml 文件中添加如下行,获取相应的设备权限:

 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
   package="io.agora.tutorials1v1acall">

   <uses-permission android:name="android.permission.READ_PHONE_STATE" />   
   <uses-permission android:name="android.permission.INTERNET" />
   <uses-permission android:name="android.permission.RECORD_AUDIO" />
   <uses-permission android:name="android.permission.CAMERA" />
   <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
   <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
   <uses-permission android:name="android.permission.BLUETOOTH" />
   <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
   // 如果你的场景中涉及读取外部存储,需添加如下权限:
   <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
   // 如果你使用的是 Android 10.0 及以上设备,还需要添加如下权限:
   <uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE" />

...
</manifest>

如果你的 targetSdkVersion ≥ 29,还需要在 AndroidManifest.xml 文件的 区域添加如下行:

   <application
  android:requestLegacyExternalStorage="true">
  ...
   </application>

防止代码混淆

在 app/proguard-rules.pro 文件中添加如下行,防止混淆 Agora SDK 的代码:

-keep class io.agora.**{*;}

音视频功能实现

大致步骤如下:

  1. 创建用户界面
  2. 获取设备权限
  3. 初始化RtcEngine
  4. 设置本地视图
  5. 加入频道
  6. 设置远端视图
  7. 更多步骤
  8. 离开频道

具体实现可以直接看

import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.SurfaceView;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import io.agora.rtc.IRtcEngineEventHandler;
import io.agora.rtc.RtcEngine;
import io.agora.rtc.video.VideoCanvas;
import io.agora.rtc.video.VideoEncoderConfiguration;

public class VideoChatViewActivity extends AppCompatActivity {
    private static final String TAG = VideoChatViewActivity.class.getSimpleName();

    private static final int PERMISSION_REQ_ID = 22;

    private static final String[] REQUESTED_PERMISSIONS = {
            Manifest.permission.RECORD_AUDIO,
            Manifest.permission.CAMERA,
            Manifest.permission.WRITE_EXTERNAL_STORAGE
    };

    private RtcEngine mRtcEngine;
    private boolean mCallEnd;
    private boolean mMuted;

    private FrameLayout mLocalContainer;
    private RelativeLayout mRemoteContainer;
    private SurfaceView mLocalView;
    private SurfaceView mRemoteView;

    private ImageView mCallBtn;
    private ImageView mMuteBtn;
    private ImageView mSwitchCameraBtn;


    private LoggerRecyclerView mLogView;

    private final IRtcEngineEventHandler mRtcEventHandler = new IRtcEngineEventHandler() {

        @Override
        public void onJoinChannelSuccess(String channel, final int uid, int elapsed) {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    mLogView.logI("Join channel success, uid: " + (uid & 0xFFFFFFFFL));
                }
            });
        }

        @Override
        public void onFirstRemoteVideoDecoded(final int uid, int width, int height, int elapsed) {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    mLogView.logI("First remote video decoded, uid: " + (uid & 0xFFFFFFFFL));
                    setupRemoteVideo(uid);
                }
            });
        }

        @Override
        public void onUserOffline(final int uid, int reason) {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    mLogView.logI("User offline, uid: " + (uid & 0xFFFFFFFFL));
                    onRemoteUserLeft();
                }
            });
        }
    };

    private void setupRemoteVideo(int uid) {

        int count = mRemoteContainer.getChildCount();
        View view = null;
        for (int i = 0; i < count; i++) {
            View v = mRemoteContainer.getChildAt(i);
            if (v.getTag() instanceof Integer && ((int) v.getTag()) == uid) {
                view = v;
            }
        }

        if (view != null) {
            return;
        }


        mRemoteView = RtcEngine.CreateRendererView(getBaseContext());
        mRemoteContainer.addView(mRemoteView);

        mRtcEngine.setupRemoteVideo(new VideoCanvas(mRemoteView, VideoCanvas.RENDER_MODE_HIDDEN, uid));
        mRemoteView.setTag(uid);
    }

    private void onRemoteUserLeft() {
        removeRemoteVideo();
    }

    private void removeRemoteVideo() {
        if (mRemoteView != null) {
            mRemoteContainer.removeView(mRemoteView);
        }
        mRemoteView = null;
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_video_chat_view);
        initUI();

        if (checkSelfPermission(REQUESTED_PERMISSIONS[0], PERMISSION_REQ_ID) &&
                checkSelfPermission(REQUESTED_PERMISSIONS[1], PERMISSION_REQ_ID) &&
                checkSelfPermission(REQUESTED_PERMISSIONS[2], PERMISSION_REQ_ID)) {
            initEngineAndJoinChannel();
        }
    }

    private void initUI() {
        mLocalContainer = findViewById(R.id.local_video_view_container);
        mRemoteContainer = findViewById(R.id.remote_video_view_container);

        mCallBtn = findViewById(R.id.btn_call);
        mMuteBtn = findViewById(R.id.btn_mute);
        mSwitchCameraBtn = findViewById(R.id.btn_switch_camera);

        mLogView = findViewById(R.id.log_recycler_view);

        // Sample logs are optional.
        showSampleLogs();
    }

    private void showSampleLogs() {
        mLogView.logI("Welcome to Agora 1v1 video call");
        mLogView.logW("You will see custom logs here");
        mLogView.logE("You can also use this to show errors");
    }

    private boolean checkSelfPermission(String permission, int 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) {

        if (requestCode == 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();
                return;
            }

            initEngineAndJoinChannel();
        }
    }

    private void showLongToast(final String msg) {
        this.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_LONG).show();
            }
        });
    }

    private void initEngineAndJoinChannel() {

        initializeEngine();
        setupVideoConfig();
        setupLocalVideo();
        joinChannel();
    }

    private void initializeEngine() {
        try {
            mRtcEngine = RtcEngine.create(getBaseContext(), getString(R.string.agora_app_id), mRtcEventHandler);
        } catch (Exception e) {
            Log.e(TAG, Log.getStackTraceString(e));
            throw new RuntimeException("NEED TO check rtc sdk init fatal error\n" + Log.getStackTraceString(e));
        }
    }

    private void setupVideoConfig() {
        mRtcEngine.enableVideo();

        mRtcEngine.setVideoEncoderConfiguration(new VideoEncoderConfiguration(
                VideoEncoderConfiguration.VD_640x360,
                VideoEncoderConfiguration.FRAME_RATE.FRAME_RATE_FPS_15,
                VideoEncoderConfiguration.STANDARD_BITRATE,
                VideoEncoderConfiguration.ORIENTATION_MODE.ORIENTATION_MODE_FIXED_PORTRAIT));
    }

    private void setupLocalVideo() {

        mLocalView = RtcEngine.CreateRendererView(getBaseContext());
        mLocalView.setZOrderMediaOverlay(true);
        mLocalContainer.addView(mLocalView);

        mRtcEngine.setupLocalVideo(new VideoCanvas(mLocalView, VideoCanvas.RENDER_MODE_HIDDEN, 0));
    }

    private void joinChannel() {

        String token = getString(R.string.agora_access_token);
        if (TextUtils.isEmpty(token) || TextUtils.equals(token, "#YOUR ACCESS TOKEN#")) {
            token = null;
        }
        mRtcEngine.joinChannel(token, "demochannel", "Extra Optional Data", 1);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (!mCallEnd) {
            leaveChannel();
        }

        RtcEngine.destroy();
    }

    private void leaveChannel() {
        mRtcEngine.leaveChannel();
    }

    public void onLocalAudioMuteClicked(View view) {
        mMuted = !mMuted;
        mRtcEngine.muteLocalAudioStream(mMuted);
        int res = mMuted ? R.drawable.btn_mute : R.drawable.btn_unmute;
        mMuteBtn.setImageResource(res);
    }

    public void onSwitchCameraClicked(View view) {
        mRtcEngine.switchCamera();
    }

    public void onCallClicked(View view) {
        if (mCallEnd) {
            startCall();
            mCallEnd = false;
            mCallBtn.setImageResource(R.drawable.btn_endcall);
        } else {
            endCall();
            mCallEnd = true;
            mCallBtn.setImageResource(R.drawable.btn_startcall);
        }

        showButtons(!mCallEnd);
    }

    private void startCall() {
        setupLocalVideo();
        joinChannel();
    }

    private void endCall() {
        removeLocalVideo();
        removeRemoteVideo();
        leaveChannel();
    }

    private void removeLocalVideo() {
        if (mLocalView != null) {
            mLocalContainer.removeView(mLocalView);
        }
        mLocalView = null;
    }

    private void showButtons(boolean show) {
        int visibility = show ? View.VISIBLE : View.GONE;
        mMuteBtn.setVisibility(visibility);
        mSwitchCameraBtn.setVisibility(visibility);
    }
}

踩坑:

坑一:不能为null

几个值都要有值,不然相机是黑屏的。

 public abstract int joinChannel(String token, String channelName, String optionalInfo, int optionalUid);

还有app_id:agora_app_id;
在这里插入图片描述
坑二:
在这里插入图片描述

常用API

官方api文档

  1. RtcEngine 类包含应用程序调用的主要方法。
  2. IRtcEngineEventHandler 类用于向应用程序发送回调通知。
  3. RtcChannel 类在指定频道中实现实时音视频功能。通过创建多个 RtcChannel 对象,用户可以同时加入多个频道。
  4. IRtcChannelEventHandler 类监听和报告指定频道的事件和数据。
  5. IAudioEffectManager 类提供管理音效文件的方法。

其中:
IRtcEngineEventHandler 接口类用于SDK向 App 发送回调事件通知,App 通过继承该接口类的方法获取 SDK 的事件通知。 接口类的所有方法都有缺省(空)实现, App 可以根据需要只继承关心的事件。在回调方法中,App 不应该做耗时或者调用可能会引起阻塞的 API(如 SendMessage),否则可能影响 SDK 的运行。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 在Android 10中,FFmpeg可以使用Android NDK进行集成。以下是一些步骤: 1. 下载最新版本的Android NDK,并将其解压缩到您的计算机上。 2. 下载最新版本的FFmpeg,并将其解压缩到您的计算机上。 3. 在您的Android Studio项目中创建一个名为“jni”的文件夹。 4. 将FFmpeg的“libavcodec”,“libavformat”和“libavutil”文件夹复制到您的“jni”文件夹中。 5. 在您的“jni”文件夹中创建一个名为“Android.mk”的文件,并将以下内容添加到该文件中: ``` LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := libavcodec LOCAL_SRC_FILES := libavcodec/libavcodec.so LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/libavcodec include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := libavformat LOCAL_SRC_FILES := libavformat/libavformat.so LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/libavformat include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := libavutil LOCAL_SRC_FILES := libavutil/libavutil.so LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/libavutil include $(PREBUILT_SHARED_LIBRARY) ``` 6. 在您的“jni”文件夹中创建名为“Application.mk”的文件,并将以下内容添加到该文件中: ``` APP_ABI := all ``` 7. 打开终并导航到您的“jni”文件夹。 8. 在终中,运行以下命令: ``` <path to ndk>/ndk-build ``` 9. 将生成的库文件复制到您的Android Studio项目中的“libs”文件夹中。 10. 在您的Android Studio项目中,添加以下代码: ``` static { System.loadLibrary("avcodec"); System.loadLibrary("avformat"); System.loadLibrary("avutil"); } ``` 11. 现在,您可以在您的应用程序中使用FFmpeg库了。 ### 回答2: 在Android 10中集成ffmpeg是一项相对复杂的任务。ffmpeg是一个开源的多媒体框架,用于处理音频和视频的编码、解码、转换等操作。以下是将ffmpeg集成Android 10的步骤: 1.下载和配置NDK:首先,需要下载最新版本的Android NDK,并设置NDK环境变量。 2.创建Android项目:在Android Studio中创建一个新的Android项目,并选择合适的minSdkVersion和targetSdkVersion,以确保与Android 10兼容。 3.下载ffmpeg源码:从ffmpeg官方网站下载最新的源码,并解压到合适的位置。 4.配置build.gradle文件:在项目的build.gradle文件中添加适当的配置,包括NDK版本和支持的ABI(Application Binary Interface)。 5.编写JNI代码:创建一个JNI(Java Native Interface)文件,并编写C/C++代码来调用ffmpeg库函数。在JNI代码中,可以定义各种音视频处理操作,如编码、解码、剪辑等。 6.编译和构建:使用ndk-build或CMake来编译JNI代码,并生成包含ffmpeg库的.so文件。 7.集成Android项目:将生成的.so文件复制到项目的jniLibs目录下,并在AndroidManifest.xml文件中声明使用的权限和使用的ffmpeg库文件。 8.在Java代码中调用JNI接口:通过Java代码调用JNI接口,实现对ffmpeg函数的调用。可以使用ffmpeg提供的命令行选项,或者在JNI代码中使用ffmpeg库的特定函数。 9.测试和调试:在Android模拟器或真机上运行项目,测试集成的ffmpeg功能。根据需要,在调试过程中修复代码错误和逻辑问题。 10.构建APK并发布:完成测试后,使用Android Studio或其他打包工具构建APK文件,并发布到Google Play商店或其他渠道。 总之,将ffmpeg集成Android 10需要在NDK环境中配置项目、编写JNI接口代码、构建,并在项目中使用JNI接口实现相应的功能。这是一个需要一定开发经验和技术知识的任务,但一旦成功集成,就可以在Android设备上进行各种音视频处理操作。 ### 回答3: 在Android 10中集成FFmpeg的过程需要经过以下几个步骤: 1. 下载FFmpeg源代码:在FFmpeg官方网站上下载最新版本的FFmpeg源代码,解压到本地计算机上。 2. 配置NDK环境:安装Android NDK并配置环境变量,以便在命令行中运行NDK命令。 3. 创建Android Studio项目:在Android Studio中创建一个新的Android项目,并将FFmpeg源代码的目录复制到项目的jni目录下。 4. 创建编译脚本:在jni目录下创建一个名为"Android.mk"的文件,编写FFmpeg的编译脚本,指定需要编译的文件和库的路径。 5. 编译FFmpeg库:打开命令行,进入项目的jni目录,并运行"ndk-build"命令,编译FFmpeg库。这一步可能需要较长时间,具体时间取决于计算机性能和FFmpeg源代码的大小。 6. 导入FFmpeg库:编译完成后,在Android Studio的项目中的"build.gradle"文件中添加对FFmpeg库的引用,以便在代码中使用。 7. 在代码中使用FFmpeg:通过调用FFmpeg库的API,可以在Android应用程序中执行各种音视频处理操作,如解码、编码、剪切、拼接等。 通过以上步骤,就可以将FFmpeg集成Android 10中。请注意,集成FFmpeg可能涉及到一些特定的编译配置和依赖项,具体操作可能因项目需求而异。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值