Android.bp 构建 FFmpeg 库:从搭建编译框架到处理编译错误的全过程

前言

本文基于 FFmpeg 5.1.4 版本,详细讲述了使用 Android.bp 构建 FFmpeg 库的过程,旨在简化 Android 源码中 FFmpeg 库的构建以及集成过程。

搭建编译框架

以编译 libavutil、libavcodec、libavformat、libswresample、libswscale 模块为例,在 FFmpeg 源码根目录下,创建以下 7 个文件:
Android.bp
 ├── compat_files.bp
 ├── avutil_files.bp
 ├── avcodec_files.bp
 ├── avformat_files.bp
 ├── swresample_files.bp
 └── swscale_files.bp

Android.bp 作为编译入口,定义整体编译架构,其余 6 个 bp 文件作为 Android.bp 的依赖项,声明 FFmpeg 各模块需编译的源文件:

  • compat_files.bp 声明 compat 目录下需编译的源码文件,该目录中存在很多为兼容各种平台而创建的头文件
  • 其他 bp 文件则分别声明 libavutil、libavcodec、libavformat、libswresample、libswscale 目录下需编译的源文件

1. 创建 Android.bp 文件

首先,搭建整体编译框架,后续章节再补充具体编译参数。

// 依赖项
build = [
    "compat_files.bp",
    "avutil_files.bp",
    "avcodec_files.bp",
    "avformat_files.bp",
    "swresample_files.bp",
    "swscale_files.bp",
]

// 编译选项默认值
cc_defaults {
    name: "libFFmpeg_defaults",
    
    cflags: [
    ],
    
    arch: {
        arm: {
            cflags: [
            ],
            asflags: [
            ],
        },
        arm64: {
            cflags: [
            ],
            asflags: [
            ],
        },
    },
}

cc_library {
    name: "libFFmpeg",
    
    defaults: ["libFFmpeg_defaults"],
    
    local_include_dirs: [
    ],
    
    vendor_available: true,
    
    export_include_dirs: [
        ".",
    ],
    
    // android 10 平台需设置该选项为 false 以停用 XOM
    // xom: false,
    
    // 默认同时编译 32 和 64 位的代码。若放开下列注释,则只编译 64 位的代码
    // compile_multilib: "64",
    arch: {
        arm: {
            instruction_set: "arm",
            srcs: [
                ":avutil-src-arm",
                ":avcodec-src-arm",
                ":swresample-src-arm",
                ":swrscale-src-arm",
            ],
            // ARM 架构编译选项
            cflags: [
            ],
            // ARM 架构链接选项
            ldflags: [
            ],
            // ARM 架构汇编选项
            asflags: [
            ]
        },
        arm64: {
            srcs: [
                ":avutil-src-arm64",
                ":avcodec-src-arm64",
                ":swresample-src-arm64",
                ":swrscale-src-arm64",
            ],
            // ARM64 架构编译选项
            cflags: [
            ],
            // ARM64 架构链接选项
            ldflags: [
            ],
            // ARM64 架构汇编选项
            asflags: [
            ],
        },
    },
    srcs: [
        ":compat-src",
        ":avutil-src-common",
        ":avcodec-src-common",
        ":swresample-src-common",
        ":swrscale-src-common",
        ":avformat-src",
    ],
    shared_libs: [
    ],
    static_libs: [
    ],
    // 全局编译选项
    cflags: [
    ],
    // 全局链接选项
    ldflags: [
    ],
    // 全局汇编选项
    asflags: [
    ],
}
  • XOM 官当说明:AArch64 二进制文件的只执行内存 (XOM)

  • Android.bp 中定义的模块间依赖关系如下:
    Android.bp
     └── libFFmpeg
        ├── libFFmpeg_defaults
        │
        ├── compat-src
        │
        ├── avutil-src-common
        ├── avutil-src-arm
        ├── avutil-src-arm64
        │
        ├── avcodec-src-common
        ├── avcodec-src-arm
        ├── avcodec-src-arm64
        │
        ├── swresample-src-common
        ├── swresample-src-arm
        ├── swresample-src-arm64
        │
        ├── swrscale-src-common
        ├── swrscale-src-arm
        ├── swrscale-src-arm64
        │
        └── avformat-src

2. 创建 avutil_files.bp

声明 avutil-src-arm、avutil-src-arm64、avutil-src-common 模块,后续章节再补充这些模块所依赖的源文件。

filegroup {
  name: "avutil-src-arm",
  // ARM 架构代码
  srcs: [
  ],
}

filegroup {
  name: "avutil-src-arm64",
  // ARM64 架构代码
  srcs: [
  ],
}

filegroup {
  name: "avutil-src-common",
  // 多平台共用代码
  srcs: [
  ],
}

3. 创建其他 bp 文件

参考 avutil_files.bp,创建 compat_files.bp、avcodec_files.bp、avformat_files.bp、swresample_files.bp、swscale_files.bp 文件,并添加相应的模块声明。

  • compat_files.bp
filegroup {
  name: "compat-src",
  // 无需区分 ARM 和 ARM64 架构
  srcs: [
  ],
}
  • avcodec_files.bp
filegroup {
  name: "avcodec-src-arm",
  // ARM 架构代码
  srcs: [
  ],
}

filegroup {
  name: "avcodec-src-arm64",
  // ARM64 架构代码
  srcs: [
  ],
}

filegroup {
  name: "avcodec-src-common",
  // 多平台共用代码
  srcs: [
  ],
}
  • avformat_files.bp
filegroup {
  name: "avformat-src",
  srcs: [
  ]
}
  • swresample_files.bp
filegroup {
  name: "swresample-src-arm",
  // ARM 架构代码
  srcs: [
  ],
}
filegroup {
  name: "swresample-src-arm64",
  // ARM64 架构代码
  srcs: [
  ],
}
filegroup {
  name: "swresample-src-common",
  // 多平台共用代码
  srcs: [
  ],
}
  • swscale_files.bp
filegroup {
  name: "swrscale-src-arm",
  // ARM 架构代码
  srcs: [
  ],
}
filegroup {
  name: "swrscale-src-arm64",
  // ARM64 架构代码
  srcs: [
  ],
}
filegroup {
  name: "swrscale-src-common",
  // 多平台共用代码
  srcs: [
  ],
}

armv8-a 编译参数

1. configure 源代码

cpu 架构配置为 armv8-a,参考专栏之前的文章:使用 NDK 编译 FFmpeg

2. 查看编译参数

configure 指令执行完成后,在生成的 ffbuild/config.mak 文件中,可查看编译过程各阶段所需参数:

CPPFLAGS:预处理标志
CFLAGS:C编译标志
CXXFLAGS:C++编译标志
ASFLAGS:汇编标志
LDFLAGS:链接标志
LDEXEFLAGS:用于链接可执行文件的标志

3. 添加编译参数

前一小节 CPPFLAGS、CFLAGS、CXXFLAGS 中的 flags 可分为以下两类:

  • cpu 特定 flags:如 -mfpu=neon、-march=armv8-a
  • 多平台共用 flags:如 -fPIC、-fPIE

FFmpeg 源码根目录下,执行 vim find_flags.sh 编辑脚本文件,用于查找 ffbuild/config.mak 中的编译参数:

#!/bin/bash

#if [ "$#" -ne 1 ]; then
#    echo "Usage: $0 config_file"
#    exit 1
#fi

# 获取配置文件
#config_file=$1
config_file=ffbuild/config.mak

# 定义需要查找的变量
vars=("CPPFLAGS" "CFLAGS" "CXXFLAGS" "ASFLAGS" "LDFLAGS")

# 定义需打印的 cpu 特定参数
cpu_specific_prefixes=("-mfpu" "-march")

# 定义需要过滤的标志
filter_prefixes=("CPPFLAGS" "CFLAGS" "CXXFLAGS" "ASFLAGS" "LDFLAGS" "--sysroot" "-mfpu" "-march" "-Wl,-rpath-link")

print_flags() {
    # 将参数转换为数组
    local -a local_filter_prefixes="($(echo "$1"))"
	
    local local_should_print=$2
	
    local var_prefix=$3

    for var in "${vars[@]}"; do	
        flags=$(grep "^$var" "$config_file")

        # 分割参数
        IFS=' ' read -r -a flagarray <<< "$flags"

        # 过滤重复参数参数
        flagarray=($(printf "%s\n" "${flagarray[@]}" | sort -u))

        echo "$3_$var: {"

        for flag in "${flagarray[@]}"; do
            # 检查参数是否需要过滤
            if [ "$local_should_print" = true ] ; then
                should_print=false
            else
                should_print=true
            fi

            for prefix in "${local_filter_prefixes[@]}"; do
                if [[ "$flag" == $prefix* ]]; then
                    should_print=$local_should_print
                fi
            done

            # 如果不需要过滤,则打印
            if $should_print ; then
                echo "    \"$flag\","
            fi
        done

        echo "}"
    done
}

# 将数组转换为字符串并传递给函数
print_flags "${filter_prefixes[*]}" false common
echo -e "\n"
print_flags "${cpu_specific_prefixes[*]}" true cpu_specific

执行 ./find_flags.sh 指令,查找编译参数:

common_CPPFLAGS: {
    "-D_FILE_OFFSET_BITS=64",
    "-D_ISOC99_SOURCE",
    "-D_LARGEFILE_SOURCE",
    "-DPIC",
    "-Dstrtod=avpriv_strtod",
    "-DZLIB_CONST",
}
common_CFLAGS: {
    "-fno-math-errno",
    "-fno-signed-zeros",
    "-fomit-frame-pointer",
    "-fpic",
    "-fPIC",
    "-fPIE",
    "-g",
    "-mno-stackrealign",
    "-mstack-alignment=16",
    "-Os",
    "-Oz",
    "-pthread",
    "-Qunused-arguments",
    "-std=c11",
    "-Wall",
    "-Wdeclaration-after-statement",
    "-Wdisabled-optimization",
    "-Wempty-body",
    "-Werror=implicit-function-declaration",
    "-Werror=missing-prototypes",
    "-Werror=return-type",
    "-Wmissing-prototypes",
    "-Wno-bool-operation",
    "-Wno-char-subscripts",
    "-Wno-format-zero-length",
    "-Wno-parentheses",
    "-Wno-pointer-sign",
    "-Wno-switch",
    "-Wno-unused-const-variable",
    "-Wpointer-arith",
    "-Wredundant-decls",
    "-Wstrict-prototypes",
    "-Wtype-limits",
    "-Wundef",
    "-Wwrite-strings",
}
common_CXXFLAGS: {
    "-D__STDC_CONSTANT_MACROS",
    "-std=c++11",
}
common_ASFLAGS: {
    "-fpic",
    "-fPIC",
    "-g",
    "-mno-stackrealign",
    "-Os",
    "-Qunused-arguments",
}
common_LDFLAGS: {
    "-Qunused-arguments",
    "-Wl,--as-needed",
    "-Wl,--warn-common",
    "-Wl,-z,noexecstack",
}


cpu_specific_CPPFLAGS: {
}
cpu_specific_CFLAGS: {
    "-march=armv8-a",
    "-mfpu=neon",
}
cpu_specific_CXXFLAGS: {
}
cpu_specific_ASFLAGS: {
    "-march=armv8-a",
    "-mfpu=neon",
}
cpu_specific_LDFLAGS: {
    "-march=armv8-a",
}

将上述 cpu_specific_CPPFLAGS、cpu_specific_CFLAGS、cpu_specific_CXXFLAGS 添加到 Android.bp libFFmpeg 模块 arm64 cflags 中,common_CPPFLAGS、common_CFLAGS、common_CXXFLAGS 添加到全局编译选项中:

cc_library {
    name: "libFFmpeg",
    
    ... 省略部分代码
    
    arch: {
        arm: {
            ... 省略部分代码
        },
        
        arm64: {
            srcs: [
                ":avutil-src-arm64",
                ":avcodec-src-arm64",
                ":swresample-src-arm64",
                ":swrscale-src-arm64",
            ],
            
            // ARM64 架构编译选项
            cflags: [
                "-mfpu=neon",
                "-march=armv8-a",
            ],
            
            // ARM64 架构链接选项
            ldflags: [
            ],
            
            // ARM64 架构汇编选项
            asflags: [
            ],
        },
    },
    
    ... 省略部分代码
    
    srcs: [
        ":compat-src",
        ":avutil-src-common",
        ":avcodec-src-common",
        ":swresample-src-common",
        ":swrscale-src-common",
        ":avformat-src",
    ],
    
    // 全局编译选项
    cflags: [
        // CPPFLAGS
        "-D_FILE_OFFSET_BITS=64",
        "-D_ISOC99_SOURCE",
        "-D_LARGEFILE_SOURCE",
        "-DPIC",
        "-Dstrtod=avpriv_strtod",
        "-DZLIB_CONST",
        
        // CFLAGS
        "-fno-math-errno",
        "-fno-signed-zeros",
        "-fomit-frame-pointer",
        "-fpic",
        "-fPIC",
        "-fPIE",
        "-g",
        "-mno-stackrealign",
        "-mstack-alignment=16",
        "-Os",
        "-Oz",
        "-pthread",
        "-Qunused-arguments",
        "-std=c11",
        "-Wall",
        "-Wdeclaration-after-statement",
        "-Wdisabled-optimization",
        "-Wempty-body",
        "-Werror=implicit-function-declaration",
        "-Werror=missing-prototypes",
        "-Werror=return-type",
        "-Wmissing-prototypes",
        "-Wno-bool-operation",
        "-Wno-char-subscripts",
        "-Wno-format-zero-length",
        "-Wno-parentheses",
        "-Wno-pointer-sign",
        "-Wno-switch",
        "-Wno-unused-const-variable",
        "-Wpointer-arith",
        "-Wredundant-decls",
        "-Wstrict-prototypes",
        "-Wtype-limits",
        "-Wundef",
        "-Wwrite-strings",
        
        // CXXFLAGS
        "-D__STDC_CONSTANT_MACROS",
        "-std=c++11",
    ],
    
    // 全局链接选项
    ldflags: [
    ],
    
    // 全局汇编选项
    asflags: [
    ],
}

4. 添加汇编参数

上一章节编译参数中的汇编参数,cpu_specific_ASFLAGS 添加到 Android.bp libFFmpeg 模块 arm64 asflags 中,common_ASFLAGS 添加到全局汇编选项中:

cc_library {
    name: "libFFmpeg",
    
    ... 省略部分代码
    
    arch: {
        arm: {
            ... 省略部分代码
        },
        
        arm64: {
            ... 省略部分代码
            
            // ARM64 架构链接选项
            ldflags: [
            ],
            
            // ARM64 架构汇编选项
            asflags: [
                "-march=armv8-a",
                "-mfpu=neon",
            ],
        },
    },
    
    ... 省略部分代码
    
    // 全局链接选项
    ldflags: [
    ],
    
    // 全局汇编选项
    asflags: [
        "-fpic",
        "-fPIC",
        "-g",
        "-mno-stackrealign",
        "-Os",
        "-Qunused-arguments",
    ],
}

5. 添加链接参数

参考前面章节内容,过程不在赘述,部分代码如下:

cc_library {
    name: "libFFmpeg",
    
    ... 省略部分代码
    
    arch: {
        arm: {
            ... 省略部分代码
        },
        
        arm64: {
            ... 省略部分代码
            
            // ARM64 架构汇编选项
            ldflags: [
                "-march=armv8-a",
            ],
            
            ... 省略部分代码
        },
    },
    
    ... 省略部分代码
    
    // 全局链接选项
    ldflags: [
        "-Qunused-arguments",
        "-Wl,--as-needed",
        "-Wl,--warn-common",
        "-Wl,-z,noexecstack",
    ],
    
    ... 省略部分代码
}

6. HAVE_AV_CONFIG_H 参数

FFmpeg 源码执行完 configure 指定后,根目录下会生成一个 config.h 文件。该文件包含了编译 FFmpeg 时配置的所有配置选项。config.h 文件中的每个宏都代表一个特定的配置选项,例如是否启用某个库(如 libavcodec、libavformat 等),是否启用某个功能(如硬件加速),以及一些特定的编译选项(如优化级别、调试选项等)。
FFmpeg 源码编译时,会根据 HAVE_AV_CONFIG_H 宏来确认是否引用 config.h 文件,如 libavutil/bswap.h 中的代码:

#ifdef HAVE_AV_CONFIG_H

#include "config.h"

#endif

因此我们需要将 -DHAVE_AV_CONFIG_H 添加到编译选项中。在 Android.bp 文件中,我们将该选项添加到 libFFmpeg_defaults 模块:

// 编译选项默认值
cc_defaults {
    name: "libFFmpeg_defaults",
	
    cflags: [
         "-DHAVE_AV_CONFIG_H",
    ],

    ... 省略部分代码
}

armv7-a 编译参数

1. 查看编译参数

参考前面章节 armv8-a 编译参数查找流程:

  1. configure FFmpeg 源代码,指定 cpu 架构为 armv8-a
  2. 使用 find_flags.sh 脚本查找编译参数

首先,比较 armv7-a 和 armv8-a 编译参数差异:

  • 编译参数差异
    编译参数差异
  • 汇编参数差异
    汇编参数差异
  • cpu 特定参数差异
    CPU 特定参数差异

2. 添加编译、汇编、链接参数

根据上一章节 armv7-a 和 armv8-a 编译参数差异,两种 cpu 架构绝大部分编译参数可复用,我们仅需将上述 armv7-a 特定参数添加到 Android.bp arm 架构特定编译选项中:

cc_library {
    name: "libFFmpeg",
    
    ... 省略部分代码
    
    arch: {
        arm: {
            instruction_set: "arm",

            ... 省略部分代码

            // ARM 架构编译选项
            cflags: [
                "-marm",
                "-mfloat-abi=softfp",
                "-march=armv7-a",
                "-mfpu=neon",
            ],

            // ARM 架构链接选项
            ldflags: [
                "-march=armv7-a",
            ],

            // ARM 架构汇编选项
            asflags: [
                "-mfloat-abi=softfp",
                "-march=armv7-a",
                "-mfpu=neon",
            ]
        },
    },
    
    ... 省略部分代码
}

添加 armv8-a 源文件

首先,我们需要使用 NDK 对源码进行编译,参考专栏之前的文章:使用 NDK 编译 FFmpeg 。然后根据编译后的 .o 目标文件,从源码中匹配对应的源文件(.c 或 .S 文件)。

1. 查找需编译的源文件

配置 cpu 架构为 armv8-a,并编译 FFmpeg 源代码。然后,在 FFmpeg 源码目录下,执行 vim find_srcs.sh,编辑脚本文件如下:

#!/bin/bash

# 打印帮助提示
if [ "$#" -eq 0 ] || [ "$1" == "-h" ] || [ "$1" == "--help" ]; then
    echo "Usage: $0 dir1 [dir2 ...]"
    echo "Example: $0 libavutil libavutil/aarch64"
    exit 0
fi

# 定义查找源文件的函数
findSrc() {
    local dir=$1
    local f=`ls $dir/*.o 2>/dev/null`

	echo "$dir: {"
    
	for d in $f
    do
        local s=${d//.o/.c}
        if [ -f "$s" ]; then
            echo "    \"${d//.o/.c}\","
        else
            echo "    \"${d//.o/.S}\","
        fi
    done
	
	echo "}"
}

# 遍历每个参数并查找源文件
for dir in "$@"; do
    echo "Source files in $dir:"
    findSrc $dir
done

在源码目录下执行该脚本,查找需编译的源文件,示例如下:

  • 执行 ./find_srcs.sh compat,查找 compat 目录
Source files in compat:
compat: {
    "compat/strtod.c",
}
  • 执行 ./find_srcs.sh libavutil libavutil/aarch64,查找 libavutil、libavutil/aarch64 目录
Source files in libavutil:
libavutil: {
    "libavutil/adler32.c",
    "libavutil/aes_ctr.c",
    "libavutil/aes.c",
    "libavutil/audio_fifo.c",
    "libavutil/avsscanf.c",
    "libavutil/avstring.c",
    "libavutil/base64.c",
    "libavutil/blowfish.c",
    "libavutil/bprint.c",
    "libavutil/buffer.c",
    "libavutil/camellia.c",
    "libavutil/cast5.c",
    "libavutil/channel_layout.c",
    "libavutil/color_utils.c",
    "libavutil/cpu.c",
    "libavutil/crc.c",
    "libavutil/csp.c",
    "libavutil/des.c",
    "libavutil/detection_bbox.c",
    "libavutil/dict.c",
    "libavutil/display.c",
    "libavutil/dovi_meta.c",
    "libavutil/downmix_info.c",
    "libavutil/encryption_info.c",
    "libavutil/error.c",
    "libavutil/eval.c",
    "libavutil/fifo.c",
    "libavutil/file.c",
    "libavutil/file_open.c",
    "libavutil/film_grain_params.c",
    "libavutil/fixed_dsp.c",
    "libavutil/float_dsp.c",
    "libavutil/frame.c",
    "libavutil/hash.c",
    "libavutil/hdr_dynamic_metadata.c",
    "libavutil/hdr_dynamic_vivid_metadata.c",
    "libavutil/hmac.c",
    "libavutil/hwcontext.c",
    "libavutil/hwcontext_stub.c",
    "libavutil/imgutils.c",
    "libavutil/integer.c",
    "libavutil/intmath.c",
    "libavutil/lfg.c",
    "libavutil/lls.c",
    "libavutil/log2_tab.c",
    "libavutil/log.c",
    "libavutil/lzo.c",
    "libavutil/mastering_display_metadata.c",
    "libavutil/mathematics.c",
    "libavutil/md5.c",
    "libavutil/mem.c",
    "libavutil/murmur3.c",
    "libavutil/opt.c",
    "libavutil/parseutils.c",
    "libavutil/pixdesc.c",
    "libavutil/pixelutils.c",
    "libavutil/random_seed.c",
    "libavutil/rational.c",
    "libavutil/rc4.c",
    "libavutil/reverse.c",
    "libavutil/ripemd.c",
    "libavutil/samplefmt.c",
    "libavutil/sha512.c",
    "libavutil/sha.c",
    "libavutil/slicethread.c",
    "libavutil/spherical.c",
    "libavutil/stereo3d.c",
    "libavutil/tea.c",
    "libavutil/threadmessage.c",
    "libavutil/timecode.c",
    "libavutil/time.c",
    "libavutil/tree.c",
    "libavutil/twofish.c",
    "libavutil/tx_double.c",
    "libavutil/tx_float.c",
    "libavutil/tx_int32.c",
    "libavutil/tx.c",
    "libavutil/utils.c",
    "libavutil/uuid.c",
    "libavutil/version.c",
    "libavutil/video_enc_params.c",
    "libavutil/xga_font_data.c",
    "libavutil/xtea.c",
}
Source files in libavutil/aarch64:
libavutil/aarch64: {
    "libavutil/aarch64/cpu.c",
    "libavutil/aarch64/float_dsp_init.c",
    "libavutil/aarch64/float_dsp_neon.S",
}

2. 添加 compat 源文件

编辑 compat_files.bp 文件,添加上一章节中查找到的 compat 源文件:

filegroup {
  name: "compat-src",
  srcs: [
	"compat/strtod.c",
  ],
}

3. 添加 libavutil 源文件

编辑 avutil_files.bp 文件,添加上一章节中查找到的 libavutil 源文件。libavutil/aarch64、libavutil 目录下源文件分别添加到 avutil-src-arm64、avutil-src-common 目录下:

filegroup {
  name: "avutil-src-arm",

  // ARM 架构代码
  srcs: [
  ],
}

filegroup {
  name: "avutil-src-arm64",

  // ARM64 架构代码
  srcs: [
	"libavutil/aarch64/cpu.c",
    "libavutil/aarch64/float_dsp_init.c",
    "libavutil/aarch64/float_dsp_neon.S",
  ],
}

filegroup {
  name: "avutil-src-common",

  // 多平台共用代码
  srcs: [
    "libavutil/adler32.c",
    "libavutil/aes_ctr.c",
    "libavutil/aes.c",
    "libavutil/audio_fifo.c",
    "libavutil/avsscanf.c",
    "libavutil/avstring.c",
    "libavutil/base64.c",
    "libavutil/blowfish.c",
    "libavutil/bprint.c",
    "libavutil/buffer.c",
    "libavutil/camellia.c",
    "libavutil/cast5.c",
    "libavutil/channel_layout.c",
    "libavutil/color_utils.c",
    "libavutil/cpu.c",
    "libavutil/crc.c",
    "libavutil/csp.c",
    "libavutil/des.c",
    "libavutil/detection_bbox.c",
    "libavutil/dict.c",
    "libavutil/display.c",
    "libavutil/dovi_meta.c",
    "libavutil/downmix_info.c",
    "libavutil/encryption_info.c",
    "libavutil/error.c",
    "libavutil/eval.c",
    "libavutil/fifo.c",
    "libavutil/file.c",
    "libavutil/file_open.c",
    "libavutil/film_grain_params.c",
    "libavutil/fixed_dsp.c",
    "libavutil/float_dsp.c",
    "libavutil/frame.c",
    "libavutil/hash.c",
    "libavutil/hdr_dynamic_metadata.c",
    "libavutil/hdr_dynamic_vivid_metadata.c",
    "libavutil/hmac.c",
    "libavutil/hwcontext.c",
    "libavutil/hwcontext_stub.c",
    "libavutil/imgutils.c",
    "libavutil/integer.c",
    "libavutil/intmath.c",
    "libavutil/lfg.c",
    "libavutil/lls.c",
    "libavutil/log2_tab.c",
    "libavutil/log.c",
    "libavutil/lzo.c",
    "libavutil/mastering_display_metadata.c",
    "libavutil/mathematics.c",
    "libavutil/md5.c",
    "libavutil/mem.c",
    "libavutil/murmur3.c",
    "libavutil/opt.c",
    "libavutil/parseutils.c",
    "libavutil/pixdesc.c",
    "libavutil/pixelutils.c",
    "libavutil/random_seed.c",
    "libavutil/rational.c",
    "libavutil/rc4.c",
    "libavutil/reverse.c",
    "libavutil/ripemd.c",
    "libavutil/samplefmt.c",
    "libavutil/sha512.c",
    "libavutil/sha.c",
    "libavutil/slicethread.c",
    "libavutil/spherical.c",
    "libavutil/stereo3d.c",
    "libavutil/tea.c",
    "libavutil/threadmessage.c",
    "libavutil/timecode.c",
    "libavutil/time.c",
    "libavutil/tree.c",
    "libavutil/twofish.c",
    "libavutil/tx_double.c",
    "libavutil/tx_float.c",
    "libavutil/tx_int32.c",
    "libavutil/tx.c",
    "libavutil/utils.c",
    "libavutil/uuid.c",
    "libavutil/version.c",
    "libavutil/video_enc_params.c",
    "libavutil/xga_font_data.c",
    "libavutil/xtea.c",
  ],
}

4. 添加其他模块源文件

从 ffbuild/config.sh 中查看模块间依赖关系:

swscale_deps="avutil"
avformat_deps="avcodec swresample avutil"
avcodec_deps="swresample avutil"
swresample_deps="avutil"

根据上述依赖关系依次添加 libswscale、libswresample、libavcodec、libavformat 模块源文件(参考前面章节添加 compat 和 libavutil 源文件的过程,本章节不再赘述)。需要查找的目录分为以下几类,完整的 bp 文件请查找后续章节代码仓库:

  • libxxx/arm、libxxx/aarch64:平台相关源文件路径
  • libavxxx、libswxxx:公共源文件路径
  • libxxx/neon:neon 相关源文件路径

添加 armv7-a 源文件

除平台相关代码(位于 libxxx/arm,如 libavtuil/arm),其余代码可与 armv8-a 复用。

1. 重新编写 config.h 文件

为了同时兼容 armv8-a 和 armv7-a,编译时需根据 cpu 架构,引用不同的 config.h。因此,我们需要重新编写 config.h 文件:

  • 配置 cpu 为 armv8-a,将 config.h 文件重命名为 config_arm64.h
  • 执行 make clean ,配置 cpu 架构为 armv7-a 并重新编译 FFmpeg 源码
  • 将生成的 config.h 重命名为 config_arm.h
  • 对比 config_arm64.h 和 config_arm.h,发现 config.h 使用了 ARCH_AARCH64 和 ARCH_ARM 来区分不同的 cpu 架构
    config.h 文件对比
  • 将 ARCH_AARCH64、ARCH_ARM 宏定义移到 Android.bp 的 libFFmpeg_defaults 模块编译和汇编选项中
// 编译选项默认值
cc_defaults {
    name: "libFFmpeg_defaults",
	
    cflags: [
        "-DHAVE_AV_CONFIG_H",
    ],

    arch: {
        arm: {
            cflags: [
                "-DARCH_ARM=1",
                "-DARCH_AARCH64=0",
            ],
            
            asflags: [
                "-DARCH_ARM=1",
                "-DARCH_AARCH64=0",
            ],
        },

        arm64: {
            cflags: [
                "-DARCH_AARCH64=1",
                "-DARCH_ARM=0",
            ],
            
            asflags: [
                "-DARCH_AARCH64=1",
                "-DARCH_ARM=0",
            ],
        },
    },
}
  • 重新创建和编辑 config.h 文件,根据 cpu 架构使用不同的配置
#if   ARCH_AARCH64
	#include "config_arm64.h"
#elif ARCH_ARM
	#include "config_arm.h"
#else
	#error "Unsupported CPU architecture"
#endif

2. 查找和添加源文件

参考前面 添加 armv8-a 源文件 章节的内容,查找 libavutil/arm、libswscale/arm、libswresample/arm、libavcodec/arm 目录下 armv7-a 平台相关源文件,并添加到对应 bp 文件中,以 libavutil/arm 为例:

  • 执行 ./find_srcs.sh libavutil/arm,查找 libavutil/arm 目录下需编译的源文件
Source files in libavutil/arm:
libavutil/arm: {
    "libavutil/arm/cpu.c",
    "libavutil/arm/float_dsp_init_arm.c",
    "libavutil/arm/float_dsp_init_neon.c",
    "libavutil/arm/float_dsp_init_vfp.c",
    "libavutil/arm/float_dsp_neon.S",
    "libavutil/arm/float_dsp_vfp.S",
}
  • 添加源文件到 avutil_files.bp 中
filegroup {
  name: "avutil-src-arm",

  // ARM 架构代码
  srcs: [
        "libavutil/arm/cpu.c",
        "libavutil/arm/float_dsp_init_arm.c",
        "libavutil/arm/float_dsp_init_neon.c",
        "libavutil/arm/float_dsp_init_vfp.c",
        "libavutil/arm/float_dsp_neon.S",
        "libavutil/arm/float_dsp_vfp.S",
  ],
}

参考上述流程,依次添加源文件到其余 bp 文件,过程不再赘述。完整的 bp 文件请查找后续章节代码仓库。

编译错误处理

  • 各模块中存在一些相同的源文件。如 llibavutil/log2_tab.c,只能添加在 avutil_files.bp 中,否则会导致编译错误 ld.lld: error: duplicate symbol: ff_log2_tab。使用 cat ./libavformat/log2_tab.c 指令,可以发现 libavformat 等模块其实是引用了 llibavutil 目录中的 log2_tab.c 文件, #include "libavutil/log2_tab.c"
./libavformat/log2_tab.c
./libavutil/log2_tab.c
./libavcodec/log2_tab.c
./libswscale/log2_tab.c
./libswresample/log2_tab.c
./libavfilter/log2_tab.c

类似的源文件还有
libavutil/reverse.c
libavcodec/mpegaudiotabs.c
libavcodec/to_upper4.c
libavcodec/mpeg4audio_sample_rates.c
libavcodec/jpegtables.c
libavcodec/ac3_channel_layout_tab.c
libavcodec/dca_sample_rate_tab.c
libavcodec/golomb.c(在 libavformat 中被 libavformat/golomb_tab.c 引用)

  • 添加 -Wl,-Bsymbolic 到全局 ldflags 中处理以下类型的链接错误,详细说明请查阅最后章节中参考资料
ld.lld: error: relocation R_AARCH64_ADR_PREL_PG_HI21 cannot be used against symbol ff_cos_32; recompile with -fPIC

编译源代码

至此,我们已搭建好构建 FFmpeg 库的完整框架,将代码拷贝到 Android 源码路径下,例如:vendor/qcom/packages/apps/ffmpeg-5.1.4,进入源码目录,执行 Android 编译指令 mma,即可编译 FFmpeg 源码。当前代码已在 Android 10 和 Android 11 平台编译测试通过。编译完成后,在 Android 源码 out 路径下执行 find . -name "libFFmpeg.so",即可看到已生成适用于 arm 及 arm64 平台的库文件:

./target/product/msmnile_gvmq/system/product/lib/libFFmpeg.so
./target/product/msmnile_gvmq/system/product/lib64/libFFmpeg.so
./target/product/msmnile_gvmq/system/lib/libFFmpeg.so
./target/product/msmnile_gvmq/system/lib64/libFFmpeg.so
./target/product/msmnile_gvmq/vendor/lib/libFFmpeg.so
./target/product/msmnile_gvmq/vendor/lib64/libFFmpeg.so

整体目录结构

下图左侧为 FFmpeg 未修改过的源码,右侧为修改且 configure 后的源码:
整体目录结构
绿框部分为我们新创建的文件,黄框为修改后的 config.h 配置文件,其余高亮部分由 configure 指令生成文件。

源码仓库

参考资料

  • 20
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值