前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android FFmpeg 编译和集成(十四)

Android FFmpeg 编译和集成(十四)

原创
作者头像
PengJie
修改2021-01-17 14:20:11
9K1
修改2021-01-17 14:20:11
举报
文章被收录于专栏:音视频修炼路音视频修炼路

直接使用FFmpeg

因为FFmpeg是一套集录制、转换以及流化音视频的完整的跨平台解决方案,如果我们开发者想直接在自己开发的Android应用中使用ffmpeg的提供的功能,则需要引入so静态库,比如制作一些音视频编辑应用。

交叉编译生成,so动态库

编译工具链

对于C/C++的编译,通常有两个工具 GCC 和 CLANG 。

如果有用过c/c++的开发者应该都知道GCC,是一个编译工具,不仅可以编译C/C++,也可以编译Java,Object-C,Go等语言。

CLANG 则是更高效的C/C++编译工具,Google在ndk 17 以后,把 GCC 移除了,全面推行使用 CLANG 。 所以网上一些比较旧的ffmpeg编译教程没来得及更新,我们容易踩坑,导致ffmpeg源码编译失败。

使用CLANG编译FFmpeg

笔者的本文用的编译环境是:

编译机器: Mac OS Big Sur Version 11.1

NDK版本:android-ndk-r21d-darwin-x86_64.zip

测试机:华为Mate 30

FFmpeg版本:目前最新版本4.2.2

本文是使用目前最新的 NDK r21d 版本来编译。

NDK 下载地址:Android-NDK

NDK目录

代码语言:txt
复制
编译工具链目录:
toolchains/llvm/prebuilt/darwin-x86_64/bin

交叉编译环境目录:
toolchains/llvm/prebuilt/darwin-x86_64/sysroot

如下图所示

因为笔者的测试机器是华为的Mate 30,所以选择 CPU 架构 aarch64,Android版本 29,我们可以按照自己的实际需求选择编译工具

代码语言:txt
复制
aarch64-linux-androideabi21-clang
aarch64-linux-androideabi21-clang++
下载FFmpeg源码

FFmpeg官网下载,直接DownLoad即可。 本文使用的是目前最新的版本 ffmpeg-4.2.2。 下载解压源码后,进入根目录,找到congfigure 的文件,它是一个shell脚本,用于生成一些 FFmpeg 编译需要的配置文件。这个文件非常重要,FFmpeg 的编译配置是依赖它完成的。

修改 configure 脚本 (可以用Subline打开)

我们需要修改ffmpeg-4.2.2 根目录下的 configure 文件,实际上是因为Google 在新版ndk把 GCC 移除了,全面推行使用 CLANG,所以我们需要把编译工具配置进行修改。修改流程如下:

1.新增 cross_prefix_clang 参数

我们可以搜索 CMDLINE_SET ,可以找到以下代码,然后新增一个命令行选项:cross_prefix_clang

2.修改编译工具路径设置

我们可以搜索 ar_default="${cross_prefix}${ar_default}" , 找到以下代码:

代码语言:txt
复制
ar_default="${cross_prefix}${ar_default}"

cc_default="${cross_prefix}${cc_default}"

cxx_default="${cross_prefix}${cxx_default}"

nm_default="${cross_prefix}${nm_default}"

pkg_config_default="${cross_prefix}${pkg_config_default}"

将中间两行修改为:

代码语言:txt
复制
ar_default="${cross_prefix}${ar_default}"
#------------------------------------------------
cc_default="${cross_prefix_clang}${cc_default}"
cxx_default="${cross_prefix_clang}${cxx_default}"
#------------------------------------------------
nm_default="${cross_prefix}${nm_default}"
pkg_config_default="${cross_prefix}${pkg_config_default}"

3.新建编译配置脚本

在 ffmpeg-4.2.2 根目录下新建 shell 脚本,命名为: build_android_clang.sh,脚本代码如下:

代码语言:txt
复制
#!/bin/bash
set -x
# 目标Android版本
API=29
ARCH=arm64
CPU=armv8-a
TOOL_CPU_NAME=aarch64
#so库输出目录
OUTPUT=/Users/pj1053/Downloads/ffmpeg_source/ffmpeg/android/$CPU
# NDK的路径,根据自己的NDK位置进行设置
NDK=/Users/pj1053/Downloads/android-ndk-r21d
# 编译工具链路径
TOOLCHAIN=$NDK/toolchains/llvm/prebuilt/darwin-x86_64
# 编译环境
SYSROOT=$TOOLCHAIN/sysroot

TOOL_PREFIX="$TOOLCHAIN/bin/$TOOL_CPU_NAME-linux-android"
 
CC="$TOOL_PREFIX$API-clang"
CXX="$TOOL_PREFIX$API-clang++"
OPTIMIZE_CFLAGS="-march=$CPU"
function build
{
  ./configure \
  --prefix=$OUTPUT \
  --target-os=android \
  --arch=$ARCH  \
  --cpu=$CPU \
  --disable-asm \
  --enable-neon \
  --enable-cross-compile \
  --enable-shared \
  --disable-static \
  --disable-doc \
  --disable-ffplay \
  --disable-ffprobe \
  --disable-symver \
  --disable-ffmpeg \
  --cc=$CC \
  --cxx=$CXX \
  --sysroot=$SYSROOT \
  --extra-cflags="-Os -fpic $OPTIMIZE_CFLAGS" \

  make clean all
  # 这里是定义用几个CPU编译
  make -j8
  make install
}
build

其中有两个地方要修改成自己的ffmpeg源码项目和NDK编译工具的本地路径,如下图:

4.添加脚本权限

编写完脚本文件,需要添加权限。

代码语言:txt
复制
chmod +x  build_android_clang.sh

5.脚本执行

添加脚本权限之后,我们可以直接运行脚本。

代码语言:txt
复制
 ./build_android_clang.sh

等待编译完成,将会在 当前文件夹的/android/armv8-a目录下得到 include 和 lib 两个目录,分别是 头文件 和 so库文件,就是我们需要编译生成的ffmpeg静态库文件和头文件。

使用FFmpeg so动态库

1.使用Android Studio 创建Native C++工程

新建项目的时候有一个选项是选择Native C++的模板

点击next,配置项目的信息

点击next,选择使用哪种C++标准,选择Toolchain Default会使用默认的CMake设置即可

点击finish即可完成工程的创建。

2.工程结构

这时候主工程目录下会有cpp文件夹

cpp文件夹:存放C/C++代码文件,native-lib.cpp文件默认生成的;

cpp文件夹下有两个文件,一个是native-lib.cpp文件,一个是CMakeLists.txt文件。CMakeLists.txt文件是cmake脚本配置文件,cmake会根据该脚本文件中的指令去编译相关的C/C++源文件,并将编译后产物生成共享库或静态块,然后Gradle将其打包到APK中。

3.Java调用native层c/c++代码

在MainActivity.java,static{}语句中使用了加载so库,在类加载中只执行一次。

代码语言:txt
复制
 static {
        System.loadLibrary("native-lib");
    }

然后,编写了原生的函数,函数名中要带有native。

代码语言:txt
复制
public native String stringFromJNI();

最后,编写相对应的c函数,注意函数名的构成Java_com_pengjie0668_demo_myapplication_MainActivity_stringFromJNI加上包名、类型、方法名的下划线连成一起。

注意:要按照jni的规范定义方法(Java包名类名native方法名,其中包名中的点用代替)

native-lib.cpp文件

代码语言:txt
复制
#include <jni.h>
#include <string>

extern "C" JNIEXPORT jstring JNICALL
Java_com_pengjie0668_demo_myapplication_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

4.引入 FFmpeg so库

  • 添加ffmpeg so库文件

首先,在 app/src/main/ 目录下,新建文件夹,并命名为 jniLibs ,接着,在 jniLibs 目录下,新建 arm64-v8a 目录,

最后把 FFmpeg 编译得到的所有 so 库粘贴到 arm64-v8a 目录。如图:

  • 添加 FFmpeg so库的头文件

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

  • 配置CMakeLists.txt

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

代码语言:javascript
复制
cmake_minimum_required(VERSION 3.10.2)

# 支持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})

# 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} )

5.验证使用 FFmpeg

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

(1)在 native-lib.cpp 中添加对应的 JNI 层方法。

代码语言:javascript
复制
#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_cxp_learningvideo_FFmpegActivity_ffmpegInfo(JNIEnv *env, jobject  /* this */) {

        char info[40000] = {0};
        AVCodec *c_temp = av_codec_next(NULL);
        while (c_temp != NULL) {
            if (c_temp->decode != NULL) {
                sprintf(info, "%sdecode:", info);
            } else {
                sprintf(info, "%sencode:", info);
            }
            switch (c_temp->type) {
                case AVMEDIA_TYPE_VIDEO:
                    sprintf(info, "%s(video):", info);
                    break;
                case AVMEDIA_TYPE_AUDIO:
                    sprintf(info, "%s(audio):", info);
                    break;
                default:
                    sprintf(info, "%s(other):", info);
                    break;
            }
            sprintf(info, "%s[%s]\n", info, c_temp->name);
            c_temp = c_temp->next;
        }
        
        return env->NewStringUTF(info);
    }
}

首先,我们看到代码被包裹在 extern "C" { } 当中,和前面的系统创建的稍微有些不同,通过这个大括号包裹,我们就不需要每个方法都添加单独的 extern "C" 开头了。 另外,由于 FFmpeg 是使用 C 语言编写的,所在 C++ 文件中引用 #include 的时候,也需要包裹在 extern "C" { },才能正确的编译。

(2)在 MainActivity 中添加一个外部方法 ffmpegInfo

代码语言:javascript
复制
   public native String ffmpegInfo();

(3)如果一切正常,App运行后,就会显示出 FFmpeg 音视频编解码器的信息

小结:

使用Android NDK工具对ffmpeg 源码进行交叉编译动态库的原理比较简单,但是在实践操作过程中,需要主要编译工具中路径的设置,和编译脚本内参数的设置。

Github Demo下载链接

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 直接使用FFmpeg
  • 交叉编译生成,so动态库
    • 使用CLANG编译FFmpeg
      • NDK目录
        • 下载FFmpeg源码
    • 修改 configure 脚本 (可以用Subline打开)
      • 1.新增 cross_prefix_clang 参数
        • 2.修改编译工具路径设置
          • 3.新建编译配置脚本
            • 4.添加脚本权限
              • 5.脚本执行
              • 使用FFmpeg so动态库
                • 1.使用Android Studio 创建Native C++工程
                  • 2.工程结构
                    • 3.Java调用native层c/c++代码
                      • 4.引入 FFmpeg so库
                        • 5.验证使用 FFmpeg
                        • 小结:
                        领券
                        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档