第一次尝试使用Android 移植FFmpeg算法,一路坎坷,最终做如下总结,适用于Android手机、Android开发板。亲测可用。
在Android Studio中下载所需组件:CMake、LLDB、NDK(NDK如果版本太高>r16,则选择后期单独下载)。其中,CMake为构建工具,LLDB为调试工具。
新建工程,勾选"Include C++ support":
当新建工程后,就默认新建了最简单的C++算法实现工程。点击运行即可显示如下图:
接下来进行环境配置,工程需修改文件如下图:
在libs文件夹中,放置FFmpeg库和头文件,另外将opencv库和头文件也一并放入。
在AndroidManifest.xml文件中,添加相关权限:
package="com.jcet.susan.ffmpegdemo">
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
在cpp文件中放置第三方算法文件,其中native-lib.cpp为新建C++支持工程后自动生成的C++算法文件,直接修改此文件如下:
获取视频帧数算法涉及到相关算法,都放置于cpp文件夹中:
在MainActivity中进行具体的java代码操作,注意:针对Android6.0(23版本及以上时,需要动态获取权限);需将所有动态库加载其中;具体代码如下:
package com.XXXX.XXXXX.ffmpegdemo; import android.Manifest; import android.annotation.TargetApi; import android.content.pm.PackageManager; import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.os.Message; import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; import android.support.v7.app.AppCompatActivity; import android.widget.TextView; import com.XXXX.XXXXX.NativeCaller; import java.io.File; public class MainActivity extends AppCompatActivity { private final int GET_PERMISSION_REQUEST = 100; //权限申请自定义码 private TextView intValue, time, objectValue; // Used to load the 'native-lib' library on application startup. static { System.loadLibrary("opencv_java3"); System.loadLibrary("avutil-55"); System.loadLibrary("swresample-2"); System.loadLibrary("avcodec-57"); System.loadLibrary("avformat-57"); System.loadLibrary("swscale-4"); System.loadLibrary("postproc-54"); System.loadLibrary("avfilter-6"); System.loadLibrary("avdevice-57"); System.loadLibrary("native-lib"); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initView(); checkPermissions(); } /** * 初始化View */ private void initView() { time = (TextView) this.findViewById(R.id.time); intValue = (TextView) this.findViewById(R.id.intValue); objectValue = (TextView) this.findViewById(R.id.objectValue); } /** * 获取权限 */ private void checkPermissions() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager .PERMISSION_GRANTED && ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) == PackageManager .PERMISSION_GRANTED && ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager .PERMISSION_GRANTED) { doWork(); } else { //不具有获取权限,需要进行权限申请 ActivityCompat.requestPermissions(MainActivity.this, new String[]{ Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA}, GET_PERMISSION_REQUEST); } } else { doWork(); } } @TargetApi(23) @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); switch (requestCode) { // requestCode即所声明的权限获取码,在checkSelfPermission时传入 case GET_PERMISSION_REQUEST: { int size = 0; if (grantResults.length >= 1) { int writeResult = grantResults[0]; //读写内存权限 boolean writeGranted = writeResult == PackageManager.PERMISSION_GRANTED;//读写内存权限 if (!writeGranted) { size++; } //录音权限 int recordPermissionResult = grantResults[1]; boolean recordPermissionGranted = recordPermissionResult == PackageManager.PERMISSION_GRANTED; if (!recordPermissionGranted) { size++; } //相机权限 int cameraPermissionResult = grantResults[2]; boolean cameraPermissionGranted = cameraPermissionResult == PackageManager.PERMISSION_GRANTED; if (!cameraPermissionGranted) { size++; } if (size == 0) { doWork();//必须要写,不然会有问题 } else { checkPermissions(); } } } } } /** * 在此处做获得权限的操作,否则强制一直在获取权限 */ private void doWork() { new Thread() { @Override public void run() { super.run(); // String path = Environment.getExternalStorageDirectory() + File.separator + "DCIM/Camera/CELLDETECT/clip_20190314093232.mp4"; String path = Environment.getExternalStorageDirectory() + File.separator + "DCIM/clip_20190314092927.mp4"; Message msg = new Message(); Bundle bundle = new Bundle(); long time1 = System.currentTimeMillis(); int retValue = NativeCaller.getIntValueFromJNI(path); bundle.putInt("intValue", retValue); long time2 = System.currentTimeMillis(); bundle.putInt("time2-1", (int) (time2 - time1)); msg.obj = bundle; handler.sendMessage(msg); } }.start(); } Handler handler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); Bundle bundle = (Bundle) msg.obj; intValue.setText("帧数-->" + bundle.getInt("intValue")); time.setText("耗时-->time2-1:" + bundle.getInt("time2-1")); } }; }
最重要的就是CMakeList.txt文件配置了!!!
具体代码如下,附注释:
# For more information about using CMake with Android Studio, read the # documentation: https://d.android.com/studio/projects/add-native-code.html # Sets the minimum version of CMake required to build the native library. #CMake最低版本:3.4.1 cmake_minimum_required(VERSION 3.4.1) set(CMAKE_VERBOSE_MAKEFILE on) # Creates and names a library, sets it as either STATIC # or SHARED, and provides the relative paths to its source code. # You can define multiple libraries, and CMake builds them for you. # Gradle automatically packages shared libraries with your APK. #为生成的CPP文件生成的动态库命名:native-lib add_library( # Sets the name of the library. native-lib # Sets the library as a shared library. SHARED # Provides a relative path to your source file(s). src/main/cpp/native-lib.cpp src/main/cpp/FrameCvMat.cpp src/main/cpp/Dynamic.cpp) # Searches for a specified prebuilt library and stores the path as a # variable. Because CMake includes system libraries in the search path by # default, you only need to specify the name of the public NDK library # you want to add. CMake verifies that the library exists before # completing its build. find_library( # Sets the name of the path variable. log-lib # Specifies the name of the NDK library that # you want CMake to locate. log ) # Specifies libraries CMake should link to your target library. You # can link multiple libraries, such as libraries you define in this # build script, prebuilt third-party libraries, or system libraries. #动态库路径 set(distribution_DIR ${CMAKE_SOURCE_DIR}/../../../../libs) #添加动态库 #添加libopencv_java3.so库,其名称为:opencv_java3 SHARED->动态库 IMPORTED->导入方式 add_library(libopencv_java3 SHARED IMPORTED ) #opencv_java3库的路径,以CMake路径为基础进行查找 set_target_properties(libopencv_java3 PROPERTIES IMPORTED_LOCATION ../../../../libs/armeabi/libopencv_java3.so) add_library( avutil-55 SHARED IMPORTED ) set_target_properties( avutil-55 PROPERTIES IMPORTED_LOCATION ../../../../libs/armeabi/libavutil-55.so ) add_library( swresample-2 SHARED IMPORTED ) set_target_properties( swresample-2 PROPERTIES IMPORTED_LOCATION ../../../../libs/armeabi/libswresample-2.so ) add_library( avcodec-57 SHARED IMPORTED ) set_target_properties( avcodec-57 PROPERTIES IMPORTED_LOCATION ../../../../libs/armeabi/libavcodec-57.so ) add_library( avfilter-6 SHARED IMPORTED) set_target_properties( avfilter-6 PROPERTIES IMPORTED_LOCATION ../../../../libs/armeabi/libavfilter-6.so ) add_library( swscale-4 SHARED IMPORTED) set_target_properties( swscale-4 PROPERTIES IMPORTED_LOCATION ../../../../libs/armeabi/libswscale-4.so ) add_library( avdevice-57 SHARED IMPORTED) set_target_properties( avdevice-57 PROPERTIES IMPORTED_LOCATION ../../../../libs/armeabi/libavdevice-57.so ) add_library( avformat-57 SHARED IMPORTED) set_target_properties( avformat-57 PROPERTIES IMPORTED_LOCATION ../../../../libs/armeabi/libavformat-57.so ) add_library( postproc-54 SHARED IMPORTED) set_target_properties( postproc-54 PROPERTIES IMPORTED_LOCATION ../../../../libs/armeabi/libpostproc-54.so) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11") #导入FFmpeg头文件,在include文件夹下 include_directories(libs/include) #target_include_directories(native-lib PRIVATE libs/include) #连接FFmpeg、opencv动态库 target_link_libraries( native-lib android log libopencv_java3 avutil-55 swresample-2 avcodec-57 avfilter-6 swscale-4 avdevice-57 avformat-57 postproc-54 ${log-lib} )
修改app\build.gradle文件
在build.gradle文件中需修改编译的CPU类型,并对Android架构作一定限制。通过多款机型测试,armeabi版本符合所有机型和开发板。具体代码如下:
apply plugin: 'com.android.application' android { compileSdkVersion 25 buildToolsVersion "25.0.2" defaultConfig { applicationId "com.XXXX.XXXXX.ffmpegdemo" minSdkVersion 15 targetSdkVersion 25 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" externalNativeBuild { cmake { cppFlags "" arguments '-DANDROID_PLATFORM=android-15', '-DANDROID_TOOLCHAIN=clang', '-DANDROID_STL=gnustl_static' } } ndk { abiFilters "armeabi" } } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } lintOptions { checkReleaseBuilds false // Or, if you prefer, you can continue to check for errors in release builds, // but continue the build even when errors are found: abortOnError false } //给系统指定jniLibs的目录 sourceSets.main { jniLibs.srcDirs = ['libs'] jni.srcDirs = [] } externalNativeBuild { cmake { path "CMakeLists.txt" } } } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { exclude group: 'com.android.support', module: 'support-annotations' }) compile 'com.android.support:appcompat-v7:25.3.1' compile 'com.android.support.constraint:constraint-layout:1.0.2' testCompile 'junit:junit:4.12' }
activity——main.xml文件如下:
最终显示结果如下:视频的帧数为31帧,耗时8931ms。