Android FFmpeg移植总攻略——获取视频帧数(亲测可用)

第一次尝试使用Android 移植FFmpeg算法,一路坎坷,最终做如下总结,适用于Android手机、Android开发板。亲测可用。

一、下载组件

    在Android Studio中下载所需组件:CMake、LLDB、NDK(NDK如果版本太高>r16,则选择后期单独下载)。其中,CMake为构建工具,LLDB为调试工具。

二、创建支持C++的AS工程

新建工程,勾选"Include C++ support":

当新建工程后,就默认新建了最简单的C++算法实现工程。点击运行即可显示如下图:

三、配置环境

接下来进行环境配置,工程需修改文件如下图:

在libs文件夹中,放置FFmpeg库和头文件,另外将opencv库和头文件也一并放入。

在AndroidManifest.xml文件中,添加相关权限:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.jcet.susan.ffmpegdemo">
   
 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.FLASHLIGHT" />
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_SETTINGS" />
    <uses-feature android:name="android.hardware.camera" />
    <uses-feature android:name="android.hardware.camera.autofocus" />

    <application
        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">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

在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文件如下:

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_gravity="center"
        android:gravity="center"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <TextView
            android:id="@+id/intValue"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:text="值" />

        <TextView
            android:id="@+id/time"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:text="耗时" />

    </LinearLayout>
</LinearLayout>

四、结果显示

最终显示结果如下:视频的帧数为31帧,耗时8931ms。

猜你喜欢

转载自blog.csdn.net/qq_33313532/article/details/89180483