ffmpeg(3)-Android环境下的编译与集成

336 阅读4分钟

1、FFmpeg简单介绍

前面编译好了资源文件,如下:

image.png

做下简单说明:

  • libavformat:多媒体文件或协议的封装和解封装库,如 Mp4、Flv 等文件封装格式,RTMP、RTSP 等网络协议封装格式;
  • libavcodec:音视频编解码库;
  • libavfilter:音视频、字幕滤镜库;
  • libswscale:图像格式转换库;
  • libswresample:音频重采样库;
  • libavutil:工具库;

2、FFmpeg集成

2.1 创建C++工程

1、直接借助Android Studio创建C++工程

7c0b49b3-ade7-444a-af78-af26a899f153.bmp

2、看一下工程目录

c1f79289-1dc9-4228-8a34-1497752f3541.bmp

2.2 引用ffmpeg编译出来的资源

1、首先,在 app/src/main/ 目录下,新建文件夹,并命名为 jniLibs

app/src/main/jniLibs 是 Android Studio 默认的放置 so 动态库的目录。

接着,在 jniLibs 目录下,新建 armeabi-v7a 目录。

最后把 FFmpeg 编译得到的所有 so 库粘贴到 armeabi-v7a 目录。如下:

image.png

2、添加 FFmpeg so 的头文件

在编译 FFmpeg 的时候,除了生成 so 外,还会生成对应的 .h 头文件,也就是 FFmpeg 对外暴露的所有接口。

cpp 目录下,新建 ffmpeg 目录,然后把编译时生成的 include 文件粘贴进来。

image.png

3、添加、链接 FFmpeg so 库

上面已经把 so头文件 放置到对应的目录中了,但是编译器是不会把它们编译、链接、并打包到 Apk 中的,我们还需要在 CMakeLists.txt 中显性的把相关的 so 添加和链接起来。完整的 CMakeLists.txt 如下:

bash
复制代码
cmake_minimum_required(VERSION 3.4.1)

# 支持gnu++11
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11")

# 1. 定义so库和头文件所在目录,方面后面使用
set(ffmpeg_lib_dir ${CMAKE_SOURCE_DIR}/../jniLibs/${ANDROID_ABI})
set(ffmpeg_head_dir ${CMAKE_SOURCE_DIR}/ffmpeg)

# 2. 添加头文件目录
include_directories(${ffmpeg_head_dir}/include)

# 3. 添加ffmpeg相关的so库
add_library( avutil
        SHARED
        IMPORTED )
set_target_properties( avutil
        PROPERTIES IMPORTED_LOCATION
        ${ffmpeg_lib_dir}/libavutil.so )

add_library( swresample
        SHARED
        IMPORTED )
set_target_properties( swresample
        PROPERTIES IMPORTED_LOCATION
        ${ffmpeg_lib_dir}/libswresample.so )
        
add_library( avcodec
        SHARED
        IMPORTED )
set_target_properties( avcodec
        PROPERTIES IMPORTED_LOCATION
        ${ffmpeg_lib_dir}/libavcodec.so )
        
add_library( avfilter
        SHARED
        IMPORTED)
set_target_properties( avfilter
        PROPERTIES IMPORTED_LOCATION
        ${ffmpeg_lib_dir}/libavfilter.so )
        
add_library( swscale
        SHARED
        IMPORTED)
set_target_properties( swscale
        PROPERTIES IMPORTED_LOCATION
        ${ffmpeg_lib_dir}/libswscale.so )

add_library( avformat
        SHARED
        IMPORTED)
set_target_properties( avformat
        PROPERTIES IMPORTED_LOCATION
        ${ffmpeg_lib_dir}/libavformat.so )

add_library( avdevice
        SHARED
        IMPORTED)
set_target_properties( avdevice
        PROPERTIES IMPORTED_LOCATION
        ${ffmpeg_lib_dir}/libavdevice.so )

# 查找代码中使用到的系统库
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 )

# 配置目标so库编译信息
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).
        native-lib.cpp
        )

# 指定编译目标库时,cmake要链接的库        
target_link_libraries( 

        # 指定目标库,native-lib 是在上面 add_library 中配置的目标库
        native-lib

# 4. 连接 FFmpeg 相关的库
        avutil
        swresample
        avcodec
        avfilter
        swscale
        avformat
        avdevice

        # Links the target library to the log library
        # included in the NDK.
        ${log-lib} )

主要看看注释中新加入的 1~4 点。

1)通过 set 方法定义了 so头文件 所在目录,方便后面使用。

其中 CMAKE_SOURCE_DIR 为系统变量,指向 CMakeLists.txt 所在目录。 ANDROID_ABI 也是系统变量,指向 so 对应的 CPU 框架目录:armeabi、armeabi-v7a、x86 ...

bash
复制代码
set(ffmpeg_lib_dir ${CMAKE_SOURCE_DIR}/../jniLibs/${ANDROID_ABI})
set(ffmpeg_head_dir ${CMAKE_SOURCE_DIR}/ffmpeg)

2)通过 include_directories 设置头文件查找目录

bash
复制代码
include_directories(${ffmpeg_head_dir}/include)

3)通过 add_library 添加 FFmpeg 相关的 so 库,以及 set_target_properties 设置 so 对应的目录。

其中,add_library 第一个参数为 so 名字,SHARED 表示引入方式为动态库引入。

scss
复制代码
add_library( avcodec
        SHARED
        IMPORTED )
set_target_properties( avcodec
        PROPERTIES IMPORTED_LOCATION
        ${ffmpeg_lib_dir}/libavcodec.so )

4)最后,通过 target_link_libraries 把前面添加进来的 FFMpeg so 库都链接到目标库 native-lib 上。

这样,我们就将 FFMpeg 相关的 so 库都引入到当前工程中了。下面就要来测试一下,是否可以正常调用到 FFmpeg 相关的方法了。

3、使用FFmpeg集成

要检查 FFmpeg 是否可以使用,可以通过获取 FFmpeg 基础信息来验证。

1、在 FFmpegAcrtivity 中添加一个外部方法 stringFromJNI

把获取到的 FFmpeg 信息显示出来。

public class MainActivity extends AppCompatActivity {

    // Used to load the 'ffmpeglearn1' library on application startup.
    static {
        System.loadLibrary("ffmpeglearn1");
    }

    private ActivityMainBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        // Example of a call to a native method
        TextView tv = binding.sampleText;
        tv.setText(stringFromJNI());
    }

    /**
     * A native method that is implemented by the 'ffmpeglearn1' native library,
     * which is packaged with this application.
     */
    public native String stringFromJNI();
}

2、C++代码如下

#include <jni.h>
#include <string>
#include <unistd.h>

extern "C" {
    #include <libavcodec/avcodec.h>
    #include <libavformat/avformat.h>
    #include <libavfilter/avfilter.h>
    #include <libavcodec/jni.h>

    JNIEXPORT jstring JNICALL
    Java_com_example_ffmpeglearn1_MainActivity_stringFromJNI(
            JNIEnv* env,
            jobject /* this */) {
        char strBuffer[1024 * 4] = {0};
        strcat(strBuffer, "libavcodec : ");
        strcat(strBuffer, AV_STRINGIFY(LIBAVCODEC_VERSION));
        strcat(strBuffer, "\nlibavformat : ");
        strcat(strBuffer, AV_STRINGIFY(LIBAVFORMAT_VERSION));
        strcat(strBuffer, "\nlibavutil : ");
        strcat(strBuffer, AV_STRINGIFY(LIBAVUTIL_VERSION));
        strcat(strBuffer, "\nlibavfilter : ");
        strcat(strBuffer, AV_STRINGIFY(LIBAVFILTER_VERSION));
        strcat(strBuffer, "\nlibswresample : ");
        strcat(strBuffer, AV_STRINGIFY(LIBSWRESAMPLE_VERSION));
        strcat(strBuffer, "\nlibswscale : ");
        strcat(strBuffer, AV_STRINGIFY(LIBSWSCALE_VERSION));
        strcat(strBuffer, "\navcodec_configure : \n");
        strcat(strBuffer, avcodec_configuration());
        strcat(strBuffer, "\navcodec_license : ");
        strcat(strBuffer, avcodec_license());
        return env->NewStringUTF(strBuffer);
    }

}

3、需要声明arm版本和libs路径 由于这里,我只使用了arm64-v8的资源,所以需要先在build.gradle里面声明ndk,不然后续一系列工作都会报错

plugins {
    id 'com.android.application'
}

android {
    namespace 'com.example.ffmpeglearn1'
    compileSdk 33

    defaultConfig {
        applicationId "com.example.ffmpeglearn1"
        minSdk 29
        targetSdk 33
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

        ndk {
//            abiFilters  "armeabi-v7a", 'arm64-v8a'
            abiFilters  "armeabi-v7a"
        }

        externalNativeBuild {
            cmake {
                //cppFlags '-std=c++11'
//                arguments '-DANDROID_TOOLCHAIN=clang', '-DANDROID_ARM_MODE=arm', '-DANDROID_STL=c++_static'
//                cFlags "-ferror-limit=0 -fPIC"
//                cppFlags "-std=c++11 -frtti -fexceptions"
                cppFlags ""
            }
        }
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }


    // 2) 配置 CMakeLists 路径
    externalNativeBuild {
        cmake {
            path file('src/main/cpp/CMakeLists.txt')
            version '3.22.1'
        }
    }
    buildFeatures {
        viewBinding true
    }

    // 3) 解决2 files found with path 'lib/armeabi-v7a/libavcodec.so' from inputs:的问题
    sourceSets {
        main {
            jniLibs.srcDirs = ['libs']
        }
    }
}

dependencies {

    implementation 'androidx.appcompat:appcompat:1.6.1'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.5'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
}

image.png

4、Java层简单调用:

c1f79289-1dc9-4228-8a34-1497752f3541.bmp

5、最终的结果如下

集成成功

Screenshot_20230224_173650.png