为Android编译ffmpeg

近来学习音视频, ffmpeg又是音视频中绕不开的,于是编译ffmpeg. 试了好多天, 总算摸出点规律, 记录下来给需要的朋友提供一点线索.

编译

  1. 从ffmpeg仓库克隆源码
  2. 安装ndk
  3. 执行编译脚本

sysroot

sysroot​被称为逻辑根目录,只在链接过程中起作用,作为交叉编译工具链搜索库文件的根路径,如配置--sysroot=dir​,则dir​作为逻辑根目录,链接器将在dir/usr/lib​中搜索库文件。

脚本

ndk路径的tips:

打开Android Studio中SDK Manager,进到Android SDK Location文件夹下

再进到ndk文件夹,里面是你已经下载好的ndk,进到一个版本中,就是ndk的路径。

建议将脚本保存到克隆下来的ffmpeg仓库路径中.

脚本代码:

#!/bin/bash

# NDK的路径 (在bash.rc、zshrc中设置的路径,其中的NDK_HOME需要配置环境变量,如果你不配,在这改成你自己的也可以。)
NDK_HOME=~/Library/Android/sdk/ndk/25.1.8937393
# NDK_ROOT=你的NDK路径
TOOLCHAIN_PREFIX=$NDK_HOME/toolchains/llvm/prebuilt/darwin-x86_64

echo "<<<<<< FFMPEG 交叉编译 <<<<<<"
echo "<<<<<< 基于当前系统NDK地址: $NDK_HOME <<<<<<"

# 编译的相关参数  
# 目标平台的CPU指令类型 ARMv8
# armv7-a
CPU=armv8-a
# 架构类型 : ARM
ARCH=arm64
# 操作系统
OS=android
# 平台
PLATFORM=aarch64-linux-android
OPTIMIZE_CFLAGS="-march=$CPU"

# 指定输出路径
PREFIX=~/tools/outputs/ffmpeg/aarch64/$CPU

# SYSROOT
SYSROOT=$TOOLCHAIN_PREFIX/sysroot

# 交叉编译工具链
CROSS_PREFIX=$TOOLCHAIN_PREFIX/bin/llvm-

# Android交叉编译工具链的位置
ANDROID_CROSS_PREFIX=$TOOLCHAIN_PREFIX/bin/${PLATFORM}29

echo ">>>>>> FFMPEG 开始编译 >>>>>>"

# ffmpeg所有configure
# https://github.com/FFmpeg/FFmpeg/blob/master/configure
./configure \
    --prefix=$PREFIX \
    --enable-shared \
    --enable-gpl \
    --enable-neon \
    --enable-hwaccels \
    --enable-postproc \
    --enable-jni \
    --enable-small \
    --enable-mediacodec \
    --enable-decoder=h264_mediacodec \
    --enable-ffmpeg \
    --disable-ffplay \
    --disable-ffprobe \
    --disable-ffplay \
    --disable-avdevice \
    --disable-debug \
    --disable-static \
    --disable-symver \
    --cross-prefix=$CROSS_PREFIX \
    --target-os=$OS \
    --arch=$ARCH \
    --cpu=$CPU \
    --cc=${ANDROID_CROSS_PREFIX}-clang \
    --cxx=${ANDROID_CROSS_PREFIX}-clang++ \
    --enable-cross-compile \
    --sysroot=$SYSROOT \
    --extra-cflags="-Os -fPIC $OPTIMIZE_CFLAGS" \
    --extra-ldflags="$ADDI_LDFLAGS"

make clean

# 创建目标路径,如果不存在的话,最终产物存储在Prefix对应路径之下。
mkdir -p $PREFIX

sudo make -j8

sudo make install

echo "<<<<<< 编译完成,产物存储在:$PREFIX <<<<<<"

找不到从哪抄来的了, 找到了再补上

编译成功后产物的路径中生成四个文件夹, 分别是bin, include, lib, share.

文件夹包含内容
bin可执行文件, Android中不需要
libso文件
include头文件
share示例, 文档. 编译configure禁用了文档, 因此只有示例

集成

  1. Android Studio中创建native module.

  2. 将产物中的include文件夹复制到新建module的src/main/cpp文件夹下

  3. 产物lib文件夹下的so文件复制到module的src/main/jniLibs/ABI

ABI取决于你编译的. 比如你编译的是arm64-v8a的, 上面的ABI就是arm64-v8a. 那么你需要的目标路径就是src/main/jniLibs/arm64-v8a

CMakeLists.txt

# cmake version, 创建native module升成
cmake_minimum_required(VERSION 3.22.1)

# 项目名, 创建native module升成
project("native_ffmpeg")

#引入头文件, 引入include文件夹
include_directories(include)

add_library(${CMAKE_PROJECT_NAME} SHARED
        # List C/C++ source files with relative paths to this CMakeLists.txt.
        native_ffmpeg.cpp)

# Set a normal, cache, or environment variable to a given value
# cmake官方这么写解释set, 设置变量
set(ANDROID_SO_DIR ${CMAKE_SOURCE_DIR}/../jniLibs/${ANDROID_ABI})

# add_library: Add a library to the project using the specified source files.
# set_target_properties: Sets properties on targets. The syntax for the command is to list all the targets you want to change, and then provide the values you want to set next.

# avcodec
add_library(avcodec SHARED IMPORTED)
set_target_properties(avcodec PROPERTIES IMPORTED_LOCATION ${ANDROID_SO_DIR}/libavcodec.so)

# avfilter
add_library(avfilter SHARED IMPORTED)
set_target_properties(avfilter PROPERTIES IMPORTED_LOCATION ${ANDROID_SO_DIR}/libavfilter.so)

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

# avutil
add_library(avutil SHARED IMPORTED)
set_target_properties(avutil PROPERTIES IMPORTED_LOCATION ${ANDROID_SO_DIR}/libavutil.so)

# postproc
add_library(postproc SHARED IMPORTED)
set_target_properties(postproc PROPERTIES IMPORTED_LOCATION ${ANDROID_SO_DIR}/libpostproc.so)

# swresample
add_library(swresample SHARED IMPORTED)
set_target_properties(swresample PROPERTIES IMPORTED_LOCATION ${ANDROID_SO_DIR}/libswresample.so)

# swscale
add_library(swscale SHARED IMPORTED)
set_target_properties(swscale PROPERTIES IMPORTED_LOCATION ${ANDROID_SO_DIR}/libswscale.so)

# Specify libraries or flags to use when linking a given target and/or its dependents.
# 还是cmake官方的解释
target_link_libraries(${CMAKE_PROJECT_NAME}
        # List libraries link to the target library
        android
        log
        # ffmpeg libs
        avcodec
        avfilter
        avformat
        avutil
        postproc
        swresample
        swscale)

命令详情参考:

Android NDK开发基础NDK即Native Development Kit,是Android上用来开发c/c++ - 掘金

CMake Tutorial — CMake 3.30.3 Documentation

最后, 点击Android Studio上的小锤子开始build, 完成在module的build/outputs/aar中看到aar.

调用

编辑JNI文件

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

// CPP中添加C的头文件
extern "C" {
#include <libavcodec/avcodec.h>
}

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_native_1ffmpeg_NativeLib_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    return env->NewStringUTF(avcodec_configuration());
}

懒人提示: 先写Java native方法或者Kotlin external方法由IDE提示生成CPP文件更方便

依赖ffmpeg模块

dependencies {
	implementation(project(":native-ffmpeg"))
}

Activity:

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContentView(R.layout.activity_main)
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
            val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
            insets
        }

		// Return the libavcodec build-time configuration
        findViewById<TextView>(R.id.tv_greeting).setText(NativeLib().stringFromJNI())
    }
}