JNI(三) - FFmpeg for android

郗学
2023-12-01

ffmpeg 编译 Android 动态库,静态库

1. 环境

  • 编译系统环境

    • win10 自带Ubuntu 子系统(linux)

    • 执行如下命令安装 一些工具

      • apt-get install yasm
        apt-get install nasm
        apt-get install pkg-config
        
  • ndk版本 r20b

    • 下载 android-ndk-r20b-linux-x86_64.zip

      • # 1 执行scp命令 将下载下来的ndk 文件复制到 linux 子系统中 比如我安装再子系统的/usr/bin位置
        scp -r [/mnt/ndk路径] [要放在linux子系统中的位置]
        # 2 执行unzip 来解压 ndkzip
        unzip android-ndk-r20b-windows-x86_64.zip
        
        # 3 配置 ndk路径 执行如下命令
        vim /etc/profile  # 打开配置文件 按i 进入编辑模式
        # 4 在文件最后配置path处 添加ndk配置 按ESC 输入:WQ 保存并退出
        export NDK=/usr/bin/android-ndk-r20b
        export PATH=$JAVA_HOME/bin:$JRE_HOME/bin:$NDK:$PATH
        # 5 执行如下命令 立即 更新配置
         source /etc/profile
        
    • 可选最新ndk版本 在配置文件中注意对应路径即可(比如r17b 之后不支持mips 等区别)

  • ffmpeg 版本 4.0.2

    • 下载 ffmpeg-4.0.2.tar.bz2

      • # 如上述ndk 
        # 1 scp 命令 复制 ffmpeg 到 linux 子系统
        scp -r [/mnt/ffmpeg存在windows目录] [要放在linux子系统中的位置]
        # 2 执行tar 解压缩命令 解压ffmpeg 压缩包
        tar -jxvf ffmpeg-4.2.2.tar.bz2
        # 3 cd 命令进入到ffmpeg 目录下
        cd [ffmpeg 路径]
        # 执行 如下命令  生成一些编译时需要的文件
        ./configure --disable-x86asm
        
    • 当然可官网选最新版本或者稳定版

2 编译ffmpeg

  • 编写 编译脚本 build_ffmpeg.sh 内容如下

    • #!/bin/bash
      
      export NDK=/usr/bin/android-ndk-r20b #这里配置先你的 NDK 路径
      TOOLCHAIN=$NDK/toolchains/llvm/prebuilt/linux-x86_64
      
      
      function build_android
      {
      
      ./configure \
      --prefix=$PREFIX \
      --enable-neon  \
      --enable-hwaccels  \
      --enable-gpl   \
      --disable-postproc \
      --disable-debug \
      --enable-small \
      --enable-jni \
      --enable-mediacodec \
      --enable-decoder=h264_mediacodec \
      --enable-static \
      --enable-shared \
      --disable-doc \
      --enable-ffmpeg \
      --disable-ffplay \
      --disable-ffprobe \
      --disable-avdevice \
      --disable-doc \
      --disable-symver \
      --cross-prefix=$CROSS_PREFIX \
      --target-os=android \
      --arch=$ARCH \
      --cpu=$CPU \
      --cc=$CC \
      --cxx=$CXX \
      --enable-cross-compile \
      --sysroot=$SYSROOT \
      --extra-cflags="-Os -fpic $OPTIMIZE_CFLAGS" \
      --extra-ldflags="$ADDI_LDFLAGS"
      
      make clean
      make -j16
      make install
      
      echo "============================ build android arm64-v8a success =========================="
      
      }
      
      #arm64-v8a -> arm64-v8a
      ARCH=arm64
      CPU=armv8-a
      API=21
      CC=$TOOLCHAIN/bin/aarch64-linux-android$API-clang
      CXX=$TOOLCHAIN/bin/aarch64-linux-android$API-clang++
      SYSROOT=$NDK/toolchains/llvm/prebuilt/linux-x86_64/sysroot
      CROSS_PREFIX=$TOOLCHAIN/bin/aarch64-linux-android-
      PREFIX=$(pwd)/android/$CPU
      OPTIMIZE_CFLAGS="-march=$CPU"
      
      build_android
      
      #armv7-a -> armeabi-v7a
      # ARCH=arm
      # CPU=armv7-a
      # API=21
      # CC=$TOOLCHAIN/bin/armv7a-linux-androideabi$API-clang
      # CXX=$TOOLCHAIN/bin/armv7a-linux-androideabi$API-clang++
      # SYSROOT=$NDK/toolchains/llvm/prebuilt/linux-x86_64/sysroot
      # CROSS_PREFIX=$TOOLCHAIN/bin/arm-linux-androideabi-
      # PREFIX=$(pwd)/android/$CPU
      # OPTIMIZE_CFLAGS="-mfloat-abi=softfp -mfpu=vfp -marm -march=$CPU "
      
      # build_android
      
      
      #i686 -> x86
      # ARCH=x86
      # CPU=i686
      # API=21
      # CC=$TOOLCHAIN/bin/i686-linux-android$API-clang
      # CXX=$TOOLCHAIN/bin/i686-linux-android$API-clang++
      # SYSROOT=$NDK/toolchains/llvm/prebuilt/linux-x86_64/sysroot
      # CROSS_PREFIX=$TOOLCHAIN/bin/i686-linux-android-
      # PREFIX=$(pwd)/android/$CPU
      # OPTIMIZE_CFLAGS="-march=i686 -mtune=intel -mssse3 -mfpmath=sse -m32 -mno-stackrealign"
      
      # build_android
      
      # #x86-64 -> x86-64
      # ARCH=x86_64 
      # CPU=x86_64 
      # API=21
      # CC=$TOOLCHAIN/bin/x86_64-linux-android$API-clang
      # CXX=$TOOLCHAIN/bin/x86_64-linux-android$API-clang++
      # SYSROOT=$NDK/toolchains/llvm/prebuilt/linux-x86_64/sysroot
      # CROSS_PREFIX=$TOOLCHAIN/bin/x86_64-linux-android-
      # PREFIX=$(pwd)/android/$CPU
      # OPTIMIZE_CFLAGS="-march=x86-64 -msse4.2 -mpopcnt -m64 -mtune=intel"
      
      # build_android
      
      #armv5-a ->armeabi  r20b ndk 里没有armv5
      # ARCH=arm
      # CPU=armv5te
      # API=21
      # CC=$TOOLCHAIN/bin/armv-linux-androideabi$API-clang
      # CXX=$TOOLCHAIN/bin/armv-linux-androideabi$API-clang++
      # SYSROOT=$NDK/toolchains/llvm/prebuilt/linux-x86_64/sysroot
      # CROSS_PREFIX=$TOOLCHAIN/bin/arm-linux-androideabi-
      # PREFIX=$(pwd)/android/$CPU
      # OPTIMIZE_CFLAGS="-march=$CPU"
      
      # build_android
      
    • --enable-static \ # 编译静态库
      --enable-shared \  # 编译动态库 我们这里都编译
      PREFIX=$(pwd)/android/$CPU # 输出路径 当前目录下android 以cpu为子文件夹的位置
      
  • 执行scp 命令将 build_ffmpeg.sh 复制到linux子系统中ffmpeg安装目录下

    • 执行 如下命令开始编译 等待编译结束即可

      • ./buildFfmpeg 
        
      • 编译结束后会在当前目录的android目录下根据cpu架构生成的目录

        • 其中 include下 为头文件
        • lib 下为编译好的库

3.导入android项目中使用

由于ffmpeg 是c,c++ 编译的要使用 我们需要使用ndk 所以新建项目需要支持 native 这里我们打印下版本号 ----- 步骤如下

  • 新建支android项目 player Phone and table 中选择 Native c++

  • 将main->cpp 目录下的CMakeLists.txt 文件复制到 main 目录下

    • 这一步是方便CMakeLists.txt 中设置库路径
  • 将第二步编译好的include文件夹复制到cpp目录下

    • 这一步将头文件复制到项目中
  • main 目录下新建jniLibs 将 对应架构的ffmpeg 的 静态文件 复制到该文件夹下

    • 导入静态库
  • 配置cmake

    • 指定头文件路径

    • 指定静态库路径

    • 连接静态库

    • 具体配置文件如下

    • cmake_minimum_required(VERSION 3.10.2)
      
      # Declares and names the project.
      set(CMAKE_VERBOSE_MAKEFILE on)
      project("player")
      
      
      
      add_library( # Sets the name of the library.
              player
      
              # Sets the library as a shared library.
              SHARED
      
              # Provides a relative path to your source file(s).
              cpp/native-lib.cpp)
      
      # 引入当前头文件
      include_directories(${CMAKE_SOURCE_DIR}/cpp/include)
      #打印当前 cmakeLists 的路径
      message("当前cmakel路径: ${CMAKE_SOURCE_DIR} \n cpu架构:${CMAKE_ANDROID_ARCH_ABI}")
      #设置C++ 编译 -L 设置库路径 -L设置的路径与下面的link_directories 应该类似
      set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -L${CMAKE_CURRENT_SOURCE_DIR}/jniLibs/${CMAKE_ANDROID_ARCH_ABI}")
      #指定静态库或动态库的搜索路径 该指令的作用主要是指定要链接的库文件的路径
      # 自己写的动态库文件放在自己新建的目录下时,可以用该指令指定该目录的路径以便工程能够找到。
      link_directories(
              ${CMAKE_SOURCE_DIR}/../jniLibs/${CMAKE_ANDROID_ARCH_ABI}
      )
      find_library(
              log-lib
              log)
      #将指定的静态库连接到可执行文件上
      target_link_libraries(
              player
      
              avformat avcodec avfilter avutil swresample swscale
      
              ${log-lib})
      
  • 在MainActivity中声明native函数

    • public native String stringFromJNI();
      
  • 在native-lib.cpp中实现

    • #include <jni.h>
      #include <string>
      
      //由于 FFmpeg 库是 C 语言实现的,我们这里是c++文件,所以要告诉编译器按照 C 的规则进行编译
      //导入 version头文件
      extern "C" {
      #include "libavformat/version.h"
      }
      extern "C" JNIEXPORT jstring JNICALL
      Java_com_example_player_MainActivity_stringFromJNI(
              JNIEnv *env,
              jobject /* this */) {
          std::string hello = "Hello from C++";
      	// 读取version
          return env->NewStringUTF(AV_STRINGIFY(LIBAVFORMAT_VERSION));
      }
      
  • 在MainActivity 中调用

    •     @Override
          protected void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
      
              binding = ActivityMainBinding.inflate(getLayoutInflater());
              setContentView(binding.getRoot());
      
              // 调用native 方法获取版本信息
              TextView tv = binding.sampleText;
              tv.setText(stringFromJNI());
          }
      
  • 图就不加了 代码库在下面的连接里

 类似资料: