Android音视频学习(六) — FFmpeg结合x264交叉编译移植到Android平台( arm64-v8a 和 armeabi-v7a )

102 阅读10分钟

一、编译的环境

1. MacOS 系统

系统:MacOS 15.1


2. FFmpeg 版本

FFmpeg 版本:7.1.1 注意是需要下载压缩包 FFmpeg 的下载链接


3. x264的版本

x264的版本:0.165.x 注意是需要下载压缩包 x264的版本下载的链接


4. Android Studio版本

Android Studio版本:Android Studio Ladybug | 2024.2.1 Patch 3


5. NDK的版本

NDK的版本:android-ndk-r27 ,注意这里是在mac环境进行编译,需要的NDK版本也是mac的版本,其他平台的自行下载对应的版本,NDK下载的链接


注意:这里是是编译的架构是 arm64-v8a(重点) 还有 armeabi-v7a , x86和x86_64已经用的不多,加上这里没有x86和x86_64的测试环境,所以这里就忽略了,需要的可以自行去进行编译。




二、NDK的压缩包解压

下载下来的NDK压缩包使用下面的命令来进行解压缩:

tar -xvf android-ndk-r27-darwin.zip

NDK的内容


二、交叉编译x264的压缩包

下载下来的x264压缩包使用下面的命令来进行解压缩:

tar -xvf x264-master.tar.bz2

解压缩后的内容结果是: x264的压缩包内容

创建一个sh文件,内容如下,根据自行的需求进行编译按照不同的架构:

#!/bin/bash
set -ex

# 替换为你的 NDK 路径,重点重点重点重点重点重点重点重点重点重点重点重点
NDK=/Users/renjianfa/Desktop/tool/audiovideo/android-ndk-r27
TOOLCHAIN=$NDK/toolchains/llvm/prebuilt/darwin-x86_64

API=29

# arm64是arm64-v8a架构的,如果需要编译armeabi-v7a,需要修改为arm
ARCH=arm64 
# ARCH=arm

# aarch64-linux-android是arm64-v8a架构的,如果需要编译armeabi-v7a,需要修改为arm-linux-androideabi
TARGET=aarch64-linux-android
# TARGET=arm-linux-androideabi

# x264 编译输出路径,自行修改成为的
X264_PATH=/Users/renjianfa/Desktop/tool/audiovideo/x264/x264-master/android/arm64
# X264_PATH=/Users/renjianfa/Desktop/tool/audiovideo/x264/x264-master/android/armv7

# 设置 sysroot 为 llvm 自带的 sysroot,兼容新 NDK
SYSROOT=$TOOLCHAIN/sysroot

make distclean || true
echo "输出路径: $X264_PATH"

# 显式指定编译器
# aarch64-linux-android29-clang是arm64-v8a架构的,如果需要编译armeabi-v7a,需要修改为armv7a-linux-androideabi29-clang
export CC=$TOOLCHAIN/bin/aarch64-linux-android29-clang
# export CC=$TOOLCHAIN/bin/armv7a-linux-androideabi29-clang

export CFLAGS="--sysroot=$SYSROOT"
export LDFLAGS="--sysroot=$SYSROOT"

./configure \
  --prefix=$X264_PATH \
  --host=$TARGET \
  --enable-shared \  #这里输出是so包的格式
  --disable-static \ #这里输出的是.a 文件的格式
  --enable-pic \
  --disable-cli \
  --disable-opencl \
  --sysroot="$SYSROOT"

make -j$(nproc) install

echo "成功输出到:$X264_PATH"
find $X264_PATH

上面的sh文件自行命名,例如这边命名是: build_x264_android_arm64.sh (arm64-v8a架构) build_x264_android_armv7.sh (armeabi-v7a架构)

然后把 sh 文件拉进去解压后x264文件夹里面,然后执行下面的命令:

1. 编译arm64-v8a架构

chmod +x build_x264_android_arm64.sh
./build_x264_android_arm64.sh

最后在x264目录下生成 android / arm64 的文件夹,arm64文件夹里面有两个文件夹,分别是include文件夹和lib文件夹

lib文件夹 include文件夹



2. 编译armeabi-v7a架构

chmod +x build_x264_android_armv7.sh
./build_x264_android_armv7.sh

最后在x264目录下生成 android / armv7 的文件夹,armv7文件夹里面同样有两个文件夹,分别是include文件夹和lib文件夹 lib文件夹 include文件夹




三、使用FFmpeg结合x264一起编译

从FFmpeg的官网下载后的FFmpeg压缩包,使用下面的命令来进行解压缩:

tar -xvf ffmpeg-7.1.1.tar.xz

ffmpeg-7.1.1的文件夹

1. 编译arm64-v8a架构

创建一个sh文件,命名为: build_android_arm64_with_x264.sh (arm64-v8a架构) 内容如下:

#!/bin/bash
set -ex
# 目标Android版本
API=29
ARCH=arm64
CPU=armv8-a
TOOL_CPU_NAME=aarch64
#so库输出目录,根据自己的要求自行修改设置
OUTPUT=/Users/renjianfa/Desktop/tool/audiovideo/ffmpeg/ffmpeg-7.1.1/ffmpeg-7.1.1-uncompiled/ffmpeg-7.1.1/android/$CPU
# NDK的路径,根据自己的NDK位置进行设置
NDK=/Users/renjianfa/Desktop/tool/audiovideo/android-ndk-r27
# 编译工具链路径
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"

# x264 编译输出路径,从上面的输出目录获取,根据自己的要求自行修改设置
X264_PATH=/Users/renjianfa/Desktop/tool/audiovideo/x264/x264-master/android/arm64

function build
{

  # 重点,需要导入x264 的输出目录下的pkgconfig文件夹
  export PKG_CONFIG_PATH=$X264_PATH/lib/pkgconfig

  ./configure \
  --prefix=$OUTPUT \
  --target-os=android \
  --arch=$ARCH  \
  --cpu=$CPU \
  --enable-neon \
  --enable-jni \
  --enable-gpl \
  --enable-mediacodec \
  --enable-cross-compile \
  --disable-static \
  --enable-shared \
  --disable-doc \
  --disable-ffplay \
  --disable-ffprobe \
  --disable-ffmpeg \
  --enable-avfilter \
  --enable-avdevice \
  --disable-symver \
  --enable-filters \
  --enable-swscale \
  --enable-swresample \
  --enable-libx264 \
  --enable-encoder=libx264 \
  --extra-cflags="-I$X264_PATH/include" \
  --extra-ldflags="-L$X264_PATH/lib" \
  --extra-libs="-lx264" \
  --cc=$CC \
  --cxx=$CXX \
  --strip=$TOOLCHAIN/bin/llvm-strip \
  --nm=$TOOLCHAIN/bin/llvm-nm \
  --ar=$TOOLCHAIN/bin/llvm-ar \
  --ranlib=$TOOLCHAIN/bin/llvm-ranlib \
  --sysroot=$SYSROOT \
  --enable-network \
  --enable-pthreads \
  --extra-cflags="-Os -fpic $OPTIMIZE_CFLAGS" \
  --extra-ldflags="-mno-stackrealign -m64" \
  --enable-neon  \
  --enable-decoder=aac \    
  --enable-encoder=aac
  
  make -j$(nproc)
  make install

  echo "============================ build android arm64-v8a with libx264 success =========================="
}


build

保存成功后运行下面的 sh 脚本文件:

chmod +x build_android_arm64_with_x264.sh
./build_android_arm64_with_x264.sh

编译成功后会生成四个文件夹,bin文件夹,include文件夹,lib文件夹,share文件夹:
FFmpeg编译成功的输出 这里我们重点关心是include文件夹和lib文件夹即可:

lib的文件夹: lib文件夹

include的文件夹: include文件夹



2. 编译armeabi-v7a架构

创建一个sh文件,命名为: build_android_armv7_with_x264.sh (armeabi-v7a架构) 内容如下:

#!/bin/bash
set -ex
# 目标Android版本
API=29
ARCH=arm
CPU=armv7-a
TOOL_CPU_NAME=armv7a
#so库输出目录,根据自己的要求自行修改设置
OUTPUT=/Users/renjianfa/Desktop/tool/audiovideo/ffmpeg/ffmpeg-7.1.1/ffmpeg-7.1.1-uncompiled/ffmpeg-7.1.1/android/$CPU
# NDK的路径,根据自己的NDK位置进行设置
NDK=/Users/renjianfa/Desktop/tool/audiovideo/android-ndk-r27
# 编译工具链路径
TOOLCHAIN=$NDK/toolchains/llvm/prebuilt/darwin-x86_64
# 编译环境
SYSROOT=$TOOLCHAIN/sysroot

TOOL_PREFIX="$TOOLCHAIN/bin/$TOOL_CPU_NAME-linux-androideabi"
 
CC="$TOOL_PREFIX$API-clang"
CXX="$TOOL_PREFIX$API-clang++"
OPTIMIZE_CFLAGS="-march=$CPU"

# x264 编译输出路径,从上面的输出目录获取,根据自己的要求自行修改设置
X264_PATH=/Users/renjianfa/Desktop/tool/audiovideo/x264/x264-master/android/armv7

function build
{

  # 重点,需要导入x264 的输出目录下的pkgconfig文件夹
  export PKG_CONFIG_PATH=$X264_PATH/lib/pkgconfig

  ./configure \
  --prefix=$OUTPUT \      
  --target-os=android \
  --arch=$ARCH  \
  --cpu=$CPU \
  --enable-neon \
  --enable-jni \
  --enable-gpl \
  --enable-mediacodec \
  --enable-cross-compile \
  --disable-static \
  --enable-shared \
  --disable-doc \
  --disable-ffplay \
  --disable-ffprobe \
  --disable-ffmpeg \
  --enable-avfilter \
  --enable-avdevice \
  --disable-symver \
  --enable-filters \
  --enable-swscale \
  --enable-swresample \
  --enable-libx264 \
  --enable-encoder=libx264 \
  --extra-cflags="-I$X264_PATH/include" \
  --extra-ldflags="-L$X264_PATH/lib" \
  --extra-libs="-lx264" \
  --cc=$CC \
  --cxx=$CXX \
  --strip=$TOOLCHAIN/bin/llvm-strip \
  --nm=$TOOLCHAIN/bin/llvm-nm \
  --ar=$TOOLCHAIN/bin/llvm-ar \
  --ranlib=$TOOLCHAIN/bin/llvm-ranlib \
  --sysroot=$SYSROOT \
  --enable-network \
  --enable-pthreads \
  --extra-cflags="-Os -fpic $OPTIMIZE_CFLAGS" \
  --extra-cflags="-mno-stackrealign -m32" \
  --enable-neon  \
  --enable-decoder=aac \
  --enable-encoder=aac
  
  make -j$(nproc)
  make install

  echo "============================ build android armv7-a with libx264 success =========================="
}


build

保存成功后运行下面的 sh 脚本文件:

chmod +x build_android_armv7_with_x264.sh
./build_android_armv7_with_x264.sh

编译成功后会生成四个文件夹,bin文件夹,include文件夹,lib文件夹,share文件夹: FFmpeg编译成功的输出

这里我们重点关心是include文件夹和lib文件夹即可:

lib的文件夹: lib文件夹

include的文件夹:

include的文件夹



3. 编译configure的参数解析

注意:这里是常用的参数,根据自己的需求来进行增添和修改

configure的参数作用解析
prefix=$OUTPUT指定安装输出路径
target-os=android目标系统是 Android
arch=$ARCH指定目标架构(arm, arm64, x86, x86_64)
cpu=$CPU指定目标架构CPU(armv7-a,armv8-a,i686, x86_64)
enable-neon开启 NEON SIMD 优化(只对 ARM 架构有用))
enable-jni启用 JNI 支持,可以在 Android Java 层调用 FFmpeg API
enable-gpl打开 GPL 协议功能,这里主要是为了支持 x264(否则无法启用))
enable-mediacode启用 Android MediaCodec 硬件编解码支持
enable-cross-compile启用交叉编译模式,这里需要加上,结合x264进行编译
disable-static 和 enable-shared生成 .so 动态库,禁用 .a 静态库。根据自己的需要来进行
disable-doc, disable-ffplay, disable-ffprobe生成文档、播放器和探测工具, 这里一般不用,因为移植到android平台,节省体积
disable-ffmpeg启用 ffmpeg 命令行工具,因为移植到android平台,一般用不到,选择disable就行,有内部的ffmpeg的api
enable-avfilter, enable-avdevice ,enable-filters , enable-swscale ,enable-swresample开启滤镜、设备接口、缩放、重采样等功能
enable-libx264开启 libx264 支持,这里混合交叉编译,必须加上
enable-encoder=libx264启用 x264 编码器,这里混合交叉编译,必须加上
extra-cflags="-I$X264_PATH/include"指定 x264 的头文件、库路径和链接库,这里混合交叉编译,必须加上
extra-ldflags="-L$X264_PATH/lib"
extra-libs="-lx264"
cc=$CC指定编译器和工具,确保走 Android NDK 提供的 clang/llvm 工具链
cxx=$CXX指定编译器和工具,确保走 Android NDK 提供的 clang/llvm 工具链
strip=$TOOLCHAIN/bin/llvm-strip指定编译器和工具,确保走 Android NDK 提供的 clang/llvm 工具链
nm=$TOOLCHAIN/bin/llvm-nm指定编译器和工具,确保走 Android NDK 提供的 clang/llvm 工具链
ar=$TOOLCHAIN/bin/llvm-ar指定编译器和工具,确保走 Android NDK 提供的 clang/llvm 工具链
ranlib=$TOOLCHAIN/bin/llvm-ranlib指定编译器和工具,确保走 Android NDK 提供的 clang/llvm 工具链
sysroot=$SYSROOT指定编译器和工具,确保走 Android NDK 提供的 clang/llvm 工具链
enable-network启用网络(RTMP/HTTP 等)协议
enable-pthreads启用 pthreads 多线程
enable-decoder=aac显式调用AAC解码器
enable-encoder=aac显式调用AAC编码器
disable-symver禁用符号版本控制(Android 上通常需要禁用,否则链接问题)




四、移植到Android平台

1. 新建jniLIbs文件夹,位于main目录下

在上面的x264编译后我们看到生成了include文件夹的内容和lib文件夹的内容, 也看到FFmpeg结合x264交叉编译后生成了include文件夹的内容和lib文件夹的内容。

在 Android Studio的 main文件夹目录下,创建一个jniLibs 的目录,用来添加FFmpeg和 x264的lib文件夹里面的内容和include文件夹里面的内容,如下:

同时新建一个arm64-v8a和armeabi-v7a 的文件夹,用来存放不同架构的内容,然后我们把FFmpeg和x264编译后的结果各自放到里面,include的文件夹和lib的文件夹, ,如下: jniLibs的目录

2. 配置CMakeLists.txt 文件,位于cpp文件夹里面

cpp的目录参考: cpp的目录

CMakeLists.txt 文件代码参考:

# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html.
# For more examples on how to use CMake, see https://github.com/android/ndk-samples.

# Sets the minimum CMake version required for this project.
# 指定 cmake 的最小版本
# 这行命令是可选的,我们可以不写这句话,但在有些情况下,如果 CMakeLists.txt 文件中使用了一些高版本 cmake 特有的一些命令的时候,就需要加上这样一行,提醒用户升级到该版本之后再执行 cmake。
cmake_minimum_required(VERSION 3.22.1)

# Declares the project name. The project name can be accessed via ${ PROJECT_NAME},
# Since this is the top level CMakeLists.txt, the project name is also accessible
# with ${CMAKE_PROJECT_NAME} (both CMake variables are in-sync within the top level
# build script scope).
# 设置项目名称
# 这个命令不是强制性的,但最好都加上。它会引入两个变量 demo_BINARY_DIR 和 demo_SOURCE_DIR,同时,cmake 自动定义了两个等价的变量 PROJECT_BINARY_DIR 和 PROJECT_SOURCE_DIR。
project("native-lib")

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

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


# 配置目标so库编译信息
# add_library() 的作用:
# add_library() 是 CMake 用于**创建库(静态库 .a 或 动态库 .so)**的命令。它可以用来:
#	•	从源码编译一个库(静态 STATIC / 共享 SHARED)。
#	•	引用一个已有的预编译库(IMPORTED)。

#  native-lib → 目标库的名称。
#  SHARED → 生成 动态库(.so),用于 JNI 调用。
#  native-lib.cpp → 参与编译的源文件。
add_library( # Sets the name of the library.
        h264-info
        # Sets the library as a shared library.
        SHARED
        # Provides a relative path to your source file(s).
        h264_info.cpp
)

# 3. 添加ffmpeg相关的so库

# add_library 用于创建一个库目标(静态库、动态库或模块库)。
# add_library(<target> [STATIC | SHARED | MODULE] [EXCLUDE_FROM_ALL|IMPORTED] <source1> <source2> ...)
# 参数说明:
#	•	<target>:指定库的名称。
#	•	STATIC:创建静态库(.a 或 .lib)。
#	•	SHARED:创建动态库(.so 或 .dll)。
#	•	MODULE:创建无导出符号的动态库,通常用于插件。
#	•	EXCLUDE_FROM_ALL:表示该库不会默认参与 make all 构建。
#   •	IMPORTED 用于引用外部库,而不是由当前 CMake 构建的库。
#	•	<source1> <source2> ...:库的源文件。

# set_target_properties
# set_target_properties 用于设置库或可执行目标的属性,例如库名称、输出目录等。
# set_target_properties(<target> PROPERTIES <property1> <value1> <property2> <value2> ...)
# 	•	OUTPUT_NAME:指定生成的库或可执行文件的名称。
#	•	ARCHIVE_OUTPUT_DIRECTORY:指定静态库的输出目录。
#	•	LIBRARY_OUTPUT_DIRECTORY:指定动态库的输出目录。
#	•	RUNTIME_OUTPUT_DIRECTORY:指定可执行文件的输出目录。
#   •	IMPORTED_LOCATION 是 IMPORTED 目标的一个属性,它用于指定外部库(已编译的 .so 或 .a 文件)的实际存放路径。

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(avdevice
        SHARED
        IMPORTED)
set_target_properties(avdevice
        PROPERTIES IMPORTED_LOCATION
        ${ffmpeg_lib_dir}/libavdevice.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(postproc
        SHARED
        IMPORTED)
set_target_properties(postproc
        PROPERTIES IMPORTED_LOCATION
        ${ffmpeg_lib_dir}/libpostproc.so)

add_library(x264
        SHARED
        IMPORTED)
set_target_properties(x264
        PROPERTIES IMPORTED_LOCATION
        ${ffmpeg_lib_dir}/libx264.so)


# 查找代码中使用到的系统库
# find_library() 的作用
# find_library() 是 CMake 提供的一个命令,用于在 系统路径或指定路径中查找已编译的库文件(如 .so、.a、.lib 等),并返回其绝对路径。
# 在 Android NDK、FFmpeg、OpenCV、Oboe 等项目中,find_library() 主要用于查找 预编译库 的路径,并存储到变量中,以便后续 target_link_libraries() 进行链接。


# find_library 常用的库
# 	1.	log:这是最常用的库之一,提供 Android 原生日志功能,允许你在 C/C++ 代码中使用 __android_log_print 来输出调试信息。
#	2.	android:这个库包含了 Native Activity 支持,用于与 Android 操作系统交互,比如处理输入事件、窗口管理等,常在完全使用 native 层开发应用时使用。
#	3.	EGL:这是用于 OpenGL ES 的上下文管理的库,在需要使用 OpenGL 渲染时用于创建上下文、配置显示等。
#	4.	GLESv1_CM:提供 OpenGL ES 1.x 的接口,已经较少使用,主要用于老旧设备或兼容老项目。
#	5.	GLESv2:提供 OpenGL ES 2.0 接口,是很多游戏和图形渲染程序使用的图形库。
#	6.	GLESv3:提供 OpenGL ES 3.0 及更高版本接口,适用于需要更高性能图形渲染的应用。
#	7.	jnigraphics:这个库可以让 native 层直接访问 Java 层的 Bitmap 图像数据,比如对图片进行像素级别处理。
#	8.	OpenSLES:这是 Android 提供的音频播放和录制库,适合对音频处理有较高实时性需求的项目,比如音频播放器或实时通信。
#	9.	mediandk:这是 Android NDK 中的媒体处理库,支持音视频的编解码和媒体流的处理,适用于更底层的音视频应用。
#	10.	z:也叫 zlib,是一个压缩和解压缩库,很多第三方库(比如 FFmpeg、libpng)都依赖它。
#	11.	m:这是标准的数学库,包含了常用的数学函数,比如 sin, cos, sqrt 等,很多图形或科学计算相关的项目中都会用到。
#	12.	dl:这是动态链接库的支持库,提供 dlopen, dlsym 等函数,用于在运行时动态加载 .so 文件。

find_library(log-lib log)
#出现 error: undefined symbol: ANativeWindow_fromSurface 说明你的 NDK 环境没有正确链接 android_native_app_glue 或者 android 库。你需要确保:
#	1.	正确包含 android/native_window_jni.h(这通常不是问题)。
#	2.	正确链接 android 库(通常是关键问题)。
find_library(android-lib android)
find_library(z-lib z)
find_library(m-lib m)
find_library(dl-lib dl)

# 指定编译目标库时,cmake要链接的库
# target_link_libraries() 的作用
# target_link_libraries() 是 CMake 中的一个命令,用于将一个或多个库链接到目标(可执行文件或库)。
# 它指定了该目标依赖的外部库文件(例如 .so、.a、.lib 等),从而使得目标在编译和链接时能够正确找到这些库。
target_link_libraries(

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

        # 4. 连接 FFmpeg 相关的库
        avutil swresample avcodec avdevice avfilter swscale avformat postproc x264
        ${log-lib} ${android-lib} ${z-lib} ${m-lib} ${dl-lib}
)


3. 配置build.gradle文件

android {
    defaultConfig {

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

        externalNativeBuild {
            cmake {
                cppFlags ""
            }
        }
    }

    externalNativeBuild {
        cmake {
            path file('src/main/cpp/CMakeLists.txt')
            version '3.22.1'
        }
    }

    sourceSets {
        main {
            jniLibs.srcDirs = ['src/main/jniLibs']
        }
    }

    buildFeatures {
        viewBinding true
    }
}

dependencies {

    implementation 'androidx.core:core-ktx:1.9.0'
    implementation 'androidx.appcompat:appcompat:1.6.1'
    implementation 'com.google.android.material:material:1.8.0'
    implementation 'androidx.compose.ui:ui-desktop:1.7.0'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.5'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
    //列表适配器
    implementation "io.github.cymchad:BaseRecyclerViewAdapterHelper4:4.1.4"
}

4. 检查集成的结果

4-1. h264_info.cpp 的目录参考:

cpp的目录


4-2. 新建一个h264_info.cpp 的文件,命名根据自己的需要进行修改,但是记得在cMakeList.txt 文件进行配置好,不然会报错,在cMakeList.txt 文件里面add_library 和 target_link_libraries

add_library( # Sets the name of the library.
        h264-info
        # Sets the library as a shared library.
        SHARED
        # Provides a relative path to your source file(s).
        h264_info.cpp
)

target_link_libraries(

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

        # 4. 连接 FFmpeg 相关的库
        avutil swresample avcodec avdevice avfilter swscale avformat postproc x264
        ${log-lib} ${android-lib} ${z-lib} ${m-lib} ${dl-lib}
)

4-3. 在h264-info.cpp 文件里面,加上这段JNI的代码,用来检查FFmpeg和x264的集成,也可以根据自己的要求来进行,这里只是作为参考

#include <cstdio>
#include <cstring>
#include <android/log.h>
#include "jni.h"


#include <string>
#include <unistd.h>
#include "iomanip"
#include "sstream"
#include <android/native_window_jni.h>
#include <android/native_window.h>

//由于 FFmpeg 库是 C 语言实现的,告诉编译器按照 C 的规则进行编译
extern "C" {
#include <libavcodec/version.h>
#include <libavcodec/avcodec.h>
#include <libavformat/version.h>
#include <libavutil/version.h>
#include <libavfilter/version.h>
#include <libswresample/version.h>
#include <libswscale/version.h>
#include <libpostproc//version.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libavdevice/version.h>
#include <libavutil/imgutils.h>
#include <x264_config.h>
#include <x264.h>
}


/*
 * Class:     com_byteflow_learnffmpeg_media_FFMediaPlayer
 * Method:    native_GetFFmpegVersion
 * Signature: ()Ljava/lang/String;
 */


// 查看FFmpeg的核心库版本
extern "C"
JNIEXPORT jstring JNICALL
Java_com_android_ffmpeg_demo_view_ffmpeg_H264EncoderInfoActivity_getFFmpegCoreVersion(JNIEnv *env,
                                                                                 jobject thiz) {

    std::string strBuilder = "";

    //AV_STRINGIFY 的主要作用是 把宏参数转为字符串常量。
    strBuilder.append("libavcodec:").append(AV_STRINGIFY(LIBAVCODEC_VERSION)).append("\n");
    strBuilder.append("libavfilter:").append(AV_STRINGIFY(LIBAVFILTER_VERSION)).append("\n");
    strBuilder.append("libavdevice:").append(AV_STRINGIFY(LIBAVDEVICE_VERSION)).append("\n");
    strBuilder.append("libavformat:").append(AV_STRINGIFY(LIBAVFORMAT_VERSION)).append("\n");
    strBuilder.append("libavutil:").append(AV_STRINGIFY(LIBAVUTIL_VERSION)).append("\n");
    strBuilder.append("libpostproc:").append(AV_STRINGIFY(LIBPOSTPROC_VERSION)).append("\n");
    strBuilder.append("libswresample:").append(AV_STRINGIFY(LIBSWRESAMPLE_VERSION)).append("\n");
    strBuilder.append("libswscale:").append(AV_STRINGIFY(LIBSWSCALE_VERSION)).append("\n");

    return env->NewStringUTF(strBuilder.c_str());
}



extern "C"
JNIEXPORT jstring JNICALL
Java_com_android_ffmpeg_demo_view_ffmpeg_H264EncoderInfoActivity_getH264Version(JNIEnv *env,
                                                                                jobject thiz) {
    const char* ver = X264_POINTVER; // 获取 x264 版本字符串
    return env->NewStringUTF(ver);
}

extern "C"
JNIEXPORT jstring JNICALL Java_com_android_ffmpeg_demo_view_ffmpeg_H264EncoderInfoActivity_getH264EncoderInfo
        (JNIEnv *env, jobject thiz) {


    std::string strBuilder = "";

    strBuilder.append("H264 Encoder Info:").append("\n\n");

    // 查找 H264 编码器
    const AVCodec *codec = avcodec_find_encoder_by_name("libx264");
    if (!codec) {
        strBuilder.append("H264 encoder not found in this FFmpeg build.").append("\n");
    } else {
        strBuilder.append("H264 encoder found in this FFmpeg build.").append("\n");


        if (codec->name && strlen(codec->name) > 0) {
            strBuilder.append("H264 encoder found, name:").append(codec->name).append("\n");
        } else {
            strBuilder.append("H264 encoder found, but name is empty.").append("\n");
        }

        if (codec->long_name && strlen(codec->long_name) > 0) {
            strBuilder.append("H264 encoder found,long name:").append(codec->long_name).append(
                    "\n");
        } else {
            strBuilder.append("H264 encoder found, but long_name is empty.").append("\n");
        }
    }


    // 判断是否是 libx264
    if (codec->long_name && strstr(codec->long_name, "x264") && strstr(codec->name, "x264")) {
        strBuilder.append("libx264 encoder is available!").append("\n");
    } else {
        strBuilder.append("libx264 encoder NOT found.").append("\n");
    }

    return env->NewStringUTF(strBuilder.c_str());
}


4-4. 在Activity里面调用JNI的方法,然后记得在AndroidManifest.xml文件里面配置Activity

//查看H264视频编码器的信息
class H264EncoderInfoActivity : AppCompatActivity() {


    companion object {
        init {
            System.loadLibrary("x264")
            //根据自己的需求进行修改命名,但是cMakeList.txt也需要修改
            System.loadLibrary("h264-info")
        }
    }

    private lateinit var binding: ActivityH264EncoderInfoBinding

    //layout文件里面只是三个TextView,作为显示而已,所以不贴layout的代码了
    // 获取x264的版本的信息
    private external fun getH264EncoderInfo(): String
    // 查看FFmpeg的核心库版本
    private external fun getFFmpegCoreVersion(): String
    // 获取x264的版本
    private external fun getH264Version(): String

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = ActivityH264EncoderInfoBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.tvH264EncoderInfo.text = getH264Version()
        binding.tvH264Version.text = getH264EncoderInfo()
        binding.tvFfmpegVersion.text = getFFmpegCoreVersion()
    }
}

4-5. Android Studio 编译 Apk 输出结果

如果输出下面的信息,说明FFmpeg 集成到Android 平台是成功了,同时H264 已经成功集成到 FFmpeg里面。 为后面的开发做准备。

apk输出结果




五、更多FFmpeg编译参数查询链接地址

FFmpeg 移植到 Android 后,如果想要使用FFmpeg 命令的话,可以直接用开源库,例如: MobileFFmpeg,已经帮你封装好 JNI,直接 FFmpeg.execute(cmd) 就能跑。 FFmpegKit(MobileFFmpeg 的后继版本)。如果使用命令的方式,需要的话可以参考前几篇命令的学习。

后面的学习这边是打算学习使用FFmpeg 的API 的方式。

上面的编译是so包的方式,如果是需要.a 静态库的方式,需要自行修改configure得参数,并且正确配置好CmakeList.txt 文件。

上面部分命令的参数可以按照自己的需求去进行添加或者修改。(FFmpeg的configure参数) 上面的环境是Mac系统环境,需要使用Window或者Linux的,请自行查询对应的文档修改参数。 上面知识列举了部分的命令,如果没有合适的话,可以根据自己的需求到该网址进行查询并使用。 FFmpeg文档地址

本人在学习的过程中也遇到不少困难,毕竟是属于自学,可能有很多不足的地方,谢谢大家的支持。