拓冰建站拓冰建站
首页 / 资讯中心 / 正文

iOS FFmpeg交叉编译一键脚本:集成x264、fdk-aac与lame

简介面向iOS平台开发者的FFmpeg多库一键编译脚本解决在Mac环境下手动下载FFmpeg、x264、fdk-aac、lame并交叉编译为iOS可用库的繁琐问题。资源共包含4个shell脚本压缩包整体仅8KB脚本按库拆分分别负责FFmpeg主框架及x264、fdk-aac、lame各依赖库的拉取与编译模块清晰便于单独维护。使用方式非常直接解压后进入终端cd到脚本目录执行./build-ffmpeg.sh即可自动完成下载、配置、编译全流程最终生成fdk-aac-ios、lame-ios、x264-ios、FFmpeg-iOS四个产物目录。目前已有343人学习下载适合需要快速为iOS项目集成音视频编解码能力的开发者也可作为理解交叉编译工具链的入门参考。脚本注释与结构兼顾实用性与可读性按需修改版本参数后即可复用能显著节省环境搭建和排错时间。1. 拿到 iOS 一键自动下载并编译脚本.shffmpeg x264 fdk-aac lame后要做什么拿到这个压缩包你面对的其实不是某个 App而是一段替你走完 iOS 编译流程的 shell 脚本。iOS 平台上要真正用上 FFmpeg通常得先把 x264、fdk-aac、lame 三份源码分别交叉编译成静态库再让 FFmpeg 的 configure 找到它们如果全手动做光是在“真机 arm64、模拟器 x86_64、Apple Silicon 模拟器 arm64”三套参数之间来回切换就能消耗掉半天。脚本的意义在于把这套重复且容易出错的流程固定下来自动下载源码、按架构编译、最后合成可供 Xcode 链接的.a文件。以下内容会把它拆成能直接改的骨架并逐个解释 configure 参数为什么必须存在适合想自己维护 iOS 多媒体库、而不是拿到旧编译产物就用的开发者。2. iOS 平台上 ffmpeg 交叉编译的最小原理以及 x264、fdk-aac、lame 为什么先编译2.1 桌面 ffmpeg 和 iOS 静态库之间隔了一道交叉工具链iOS 应用进程的运行环境与 macOS 桌面不同绝大多数 iOS 设备都是 ARM64 架构模拟器则可能跑在 x86_64 或 Apple Silicon 的 arm64 之上。直接从网上下载的 ffmpeg 二进制是为 macOS 桌面编译的无法放进 iOS App就算架构一致iOS 也不允许像 macOS 那样从任意路径加载动态库。所以脚本的默认产物一定是静态库也就是.a文件在 Xcode 链接阶段打进最终可执行文件。交叉编译的核心动作是用xcrun --sdk iphoneos --find clang找到 iOS 工具链用xcrun --sdk iphoneos --show-sdk-path拿到 SDK 路径然后用-arch和-isysroot把编译目标限定在 iOS 平台。FFmpeg 本身支持--enable-cross-compile但前提是编译器、头文件、链接器全部指向 iOS SDK。脚本里大量重复出现的extra-cflags、extra-ldflags本质就是把这些信息透传给第三方库。这里有一个容易忽略的点一个.a文件里可以同时包含多个架构也就是 fat binary。脚本在单架构编译完成后会用lipo -create把真机和模拟器版本合并成一个文件。这样 Xcode 无需针对真机和模拟器分别引库但这要求每个架构编译时使用不同的前缀目录否则后编译的架构会覆盖先编译的产物。2.2 x264、fdk-aac、lame 在脚本里的定位和构建顺序四个组件里FFmpeg 是容器与转码框架x264 提供 H.264 编码fdk-aac 提供高质量的 AAC-LC/HE-AAC 编码lame 提供 MP3 编码。它们不是平等的依赖关系而是“FFmpeg 调用其余三者”的层级关系。因此脚本必须先编译 x264、fdk-aac、lame把各自的.a装进一个独立前缀目录再让 FFmpeg 的 configure 通过-I和-L找到头文件与库文件。组件在 FFmpeg 中的角色configure 开关构建注意点x264H.264 编码器--enable-libx264需要--enable-gpl源码来自 git 仓库fdk-aacAAC 编码器--enable-libfdk-aac属于 nonfree需要--enable-nonfreelameMP3 编码器--enable-libmp3lame需要下载 tar 包configure 后才有 libmp3lameffmpeg封装、解封装、音视频处理上述三个开关必须最后编译负责串联所有依赖构建顺序一旦颠倒FFmpeg 的 configure 就会报“libx264 not found”或“fdk-aac not found”。这类报错并不代表库没编成功而往往是因为 FFmpeg 的extra-cflags和extra-ldflags没有指向先前安装的前缀目录。2.3 一键脚本自动做的三件事拉源码、按架构编译、合并产物整个脚本无论写成几百行还是上千行主干都只有三层。第一层是“下载”根据 URL 或 git 仓库把源码拉取到固定目录并且判断目录是否已存在避免重复下载。第二层是“编译”定义一个接收sdk、arch、host三个参数的函数依次调用 x264、fdk-aac、lame、FFmpeg 的 configure然后make install。第三层是“合并”用lipo -create把不同架构下同名.a合并并拷贝头文件。理解了这三件事就不会被脚本中的大量 export 变量吓住。下面章节给出的骨架实现会把这三层分别落成可执行的 bash 函数。3. 一键自动下载并编译脚本.sh 的骨架从源码目录到 .a 文件3.1 脚本入口、目录布局与公共变量脚本放在仓库根目录时最常见的方式是在脚本开头通过$(cd $(dirname $0) pwd)拿到自己的绝对路径再在这个路径下创建src和out。我把set -euo pipefail放在第一行作用是让任何一步失败都立刻终止而不是带着错误继续编译出不可用的库。#!/usr/bin/env bash set -euo pipefail BASE_DIR$(cd $(dirname $0) pwd) SOURCE_DIR$BASE_DIR/src OUTPUT_DIR$BASE_DIR/out IOS_DEPLOY_TARGET13.0 mkdir -p $SOURCE_DIR $OUTPUT_DIRIOS_DEPLOY_TARGET是部署版本直接影响-miphoneos-version-min参数。如果你的 App 最低支持 iOS 12就改成12.0改成 13.0 能让编译器放心使用更新的 API也能略微减小二进制体积。脚本里所有架构相关的编译函数都要引用这个变量所以它必须放在顶部而不是散落在各个函数里。3.2 下载 ffmpeg、x264、fdk-aac、lame 的源码下载函数的关键是幂等性目录存在就跳过否则再拉取。FFmpeg 和 x264、fdk-aac 用git clone --depth 1只取本分支最新代码速度更快lame 没有官方 git 仓库稳定发布常用 SourceForge 的 tar.gz。fetch_all_sources() { local ffmpeg_vern7.0.1 local lame_ver3.100 if [ ! -d $SOURCE_DIR/ffmpeg ]; then git clone --depth 1 --branch $ffmpeg_ver \ https://git.ffmpeg.org/ffmpeg.git $SOURCE_DIR/ffmpeg fi if [ ! -d $SOURCE_DIR/x264 ]; then git clone --depth 1 \ https://code.videolan.org/videolan/x264.git $SOURCE_DIR/x264 fi if [ ! -d $SOURCE_DIR/fdk-aac ]; then git clone --depth 1 \ https://github.com/mstorsjo/fdk-aac.git $SOURCE_DIR/fdk-aac fi if [ ! -d $SOURCE_DIR/lame-$lame_ver ]; then curl -L --fail --retry 3 \ -o $SOURCE_DIR/lame.tar.gz \ https://downloads.sourceforge.net/project/lame/lame/$lame_ver/lame-$lame_ver.tar.gz tar -xzf $SOURCE_DIR/lame.tar.gz -C $SOURCE_DIR fi }这里的--depth 1会显著减少仓库体积。fdk-aac 的 git 源码没有预先生成 configure后面编译前必须执行./autogen.sh这是它和 x264、lame 最大的区别。FFmpeg 的 n7.0.1 分支也可以换成你需要的 tag只要--branch参数改成对应的nX.Y.Z即可。3.3 编译 x264 的 configure 命令与参数解释x264 的 configure 接受--host、--prefix、--cc等参数。因为同一份源码会连续编译 arm64 真机和 x86_64 模拟器两个版本所以每次编译前都要先make distclean否则上一次架构的目标文件会被链接进当前架构。build_x264() { local prefix$1 local cc$2 local host$3 local cflags$4 local ldflags$5 ( cd $SOURCE_DIR/x264 make distclean /dev/null 21 || true ./configure \ --prefix$prefix \ --host$host \ --cc$cc \ --extra-cflags$cflags \ --extra-ldflags$ldflags \ --enable-static \ --disable-cli \ --disable-opencl make -j$(sysctl -n hw.ncpu) make install ) }--enable-static指定生成静态库--disable-cli去掉 x264 命令行工具因为 iOS 上需要的是编码库而不是可执行程序。--disable-opencl可以避免链接 OpenCL 相关符号iOS 工程通常用不上。host参数在真机场景是aarch64-apple-darwin在 x86_64 模拟器场景是x86_64-apple-darwincc则对应xcrun --sdk iphoneos --find clang的输出。3.4 编译 fdk-aac 和 lame 的 autotools 套路fdk-aac 和 lame 都属于 autotools 项目configure 参数结构类似。fdk-aac 因为是从 git 克隆的必须先跑 autogen 生成 configurebuild_fdk_aac() { local prefix$1 local cc$2 local host$3 local cflags$4 local ldflags$5 ( cd $SOURCE_DIR/fdk-aac [ -f configure ] || ./autogen.sh make distclean /dev/null 21 || true CC$cc CFLAGS$cflags LDFLAGS$ldflags \ ./configure \ --prefix$prefix \ --host$host \ --disable-shared \ --enable-static make -j$(sysctl -n hw.ncpu) make install ) }这里没有使用--extra-cflags而是通过CC、CFLAGS、LDFLAGS环境变量传入。两种写法都能工作但 autotools 项目更习惯读环境变量。lame 的构建与 fdk-aac 几乎一样只需要额外加一个--disable-frontend因为它会把--prefix/bin下的 lame 可执行文件也装进来而 iOS 上用不到build_lame() { local prefix$1 local cc$2 local host$3 local cflags$4 local ldflags$5 ( cd $SOURCE_DIR/lame-3.100 make distclean /dev/null 21 || true CC$cc CFLAGS$cflags LDFLAGS$ldflags \ ./configure \ --prefix$prefix \ --host$host \ --disable-shared \ --enable-static \ --disable-frontend make -j$(sysctl -n hw.ncpu) make install ) }如果下载的是新版本 lame目录名会不同。脚本里可以把lame-3.100写成一个变量例如LAME_DIR$SOURCE_DIR/lame-$LAME_VERSION然后统一引用避免后续升级时到处改路径。3.5 最后编译 ffmpeg 并把第三方库接进来到了 FFmpeg 这一步前面三个库已经分别安装到各自的$prefix下。FFmpeg 的 configure 是整套脚本里参数最多、也最需要解释的一段build_ffmpeg() { local prefix$1 local cc$2 local arch$3 local sysroot$4 local cflags$5 local ldflags$6 ( cd $SOURCE_DIR/ffmpeg make distclean /dev/null 21 || true ./configure \ --prefix$prefix \ --enable-cross-compile \ --arch$arch \ --target-osdarwin \ --cc$cc \ --sysroot$sysroot \ --enable-static \ --disable-shared \ --enable-pic \ --disable-programs \ --disable-doc \ --enable-gpl \ --enable-libx264 \ --enable-nonfree \ --enable-libfdk-aac \ --enable-libmp3lame \ --extra-cflags$cflags -I$prefix/include \ --extra-ldflags$ldflags -L$prefix/lib make -j$(sysctl -n hw.ncpu) make install ) }--extra-cflags里的-I$prefix/include让 FFmpeg 找到 x264.h、fdk-aac 的头文件和 lame 的头文件--extra-ldflags里的-L$prefix/lib让链接器找到同名.a。--enable-pic在 iOS 静态库场景下几乎必须保留否则后续链接到 Xcode 工程时可能产生relocation错误。--disable-programs表示不生成ffmpeg命令行工具因为 iOS 沙盒环境里无法直接执行它上位机或调试时如果需要命令行版再单独为 macOS 编译一份不要放在 iOS 库配置里。整个编译循环会按架构反复调用这四个函数。以“真机 arm64 Intel 模拟器 x86_64”为例build_for_ios() { local sdk$1 local arch$2 local host$3 local min_flag$4 local prefix$OUTPUT_DIR/$sdk-$arch local sysroot sysroot$(xcrun --sdk $sdk --show-sdk-path) local cc cc$(xcrun --sdk $sdk --find clang) local cflags-arch $arch -isysroot $sysroot $min_flag local ldflags$cflags mkdir -p $prefix build_x264 $prefix $cc $host $cflags $ldflags build_fdk_aac $prefix $cc $host $cflags $ldflags build_lame $prefix $cc $host $cflags $ldflags build_ffmpeg $prefix $cc $arch $sysroot $cflags $ldflags } build_for_ios iphoneos arm64 aarch64-apple-darwin -miphoneos-version-min$IOS_DEPLOY_TARGET build_for_ios iphonesimulator x86_64 x86_64-apple-darwin -mios-simulator-version-min$IOS_DEPLOY_TARGET每次调用都会在out/iphoneos-arm64或out/iphonesimulator-x86_64下生成一份完整的库。这样做的代价是多占磁盘空间但好处是后续 lipo 合并时来源清晰不会出现架构污染。4. 脚本里真正要调的参数架构、最低系统版本和 configure 开关4.1 架构组合怎么设真机、Intel 模拟器和 Apple Silicon 模拟器最省事的做法是把所有架构都编一遍但代价是编译时间翻倍、库体积变大。实际项目中多数人只会保留三种组合真机 arm64、Intel 模拟器 x86_64、Apple Silicon 模拟器 arm64。如果团队没有 Intel Macx86_64 可以去掉如果 App 不再支持 32 位 armv7 设备就不要为了旧教程里的armv7参数增加编译负担。SDKarchhost最低版本参数适用设备iphoneosarm64aarch64-apple-darwin-miphoneos-version-min13.0所有现代 iPhoneiphonesimulatorx86_64x86_64-apple-darwin-mios-simulator-version-min13.0Intel Mac 模拟器iphonesimulatorarm64aarch64-apple-darwin-mios-simulator-version-min13.0Apple Silicon 模拟器模拟器场景的min_flag不要照抄真机参数。真机用-miphoneos-version-min模拟器用-mios-simulator-version-min两者含义不同。脚本中可以用 if 判断sdk是否包含simulator来动态赋值避免在调用处写错。4.2 从“能编译”到“够小”ffmpeg 的裁剪开关和库裁剪如果照抄全量 FFmpeg 配置编出来的libavcodec.a可能有几十甚至上百 MB。iOS App 对安装包体积敏感所以脚本里会在全量开关之后追加一段--disable-everything再按业务放行需要的编码器、解码器、封装器和解封装器--disable-everything --enable-decoderh264,aac,mp3 --enable-encoderlibx264,libfdk_aac,libmp3lame --enable-muxermp4,mov --enable-demuxermov,mp4,m4a --enable-protocolfile --enable-parserh264,aac,mpegaudio注意--enable-encoderlibfdk_aac里的下划线。fdk-aac 的库名在 FFmpeg 内部用下划线表示和 configure 开关--enable-libfdk-aac的连字符不是一回事。如果你只要 H.264 和 AAC--enable-libmp3lame可以直接去掉同时把mp3从 decoder 列表移除这样脚本能少链接一个依赖。裁剪还会影响链接阶段。iOS 工程引入 FFmpeg 库后如果遇到了Undefined symbols不一定是你少了libmp3lame.a也有可能是 FFmpeg 内部某些 decoder 依赖了 zlib、bz2、iconv 等系统库。在 Xcode 的 Other Linker Flags 中通常要补上-lz -lbz2 -liconv -lxml2并根据需求引入 AudioToolbox、VideoToolbox、CoreMedia 等系统框架。4.3 最常卡的四个点nasm、gas-preprocessor、make distclean、链接顺序第一x264 的汇编优化依赖 yasm 或 nasmconfigure找不到汇编器时会降级到纯 C 实现或者直接报错。macOS 上可以用brew install nasm安装脚本里建议在下载源码后先执行command -v nasm检查环境。第二iOS 真机 arm64 编译过程中FFmpeg 的汇编文件通常需要gas-preprocessor.pl做预处理。FFmpeg 源码树的tools/gas-preprocessor.pl就是官方维护的副本脚本可以在export PATH$SOURCE_DIR/ffmpeg/tools:$PATH之后再做编译这样不用把文件复制进/usr/local/bin也不会污染系统目录。第三make distclean必须放在 configure 之前。很多人第二次编译时报错是因为源码目录里残留了上一个架构的config.h、.o和可执行文件。这里用|| true是因为有些模块在尚未 configure 时没有 distclean 目标。第四链接顺序。iOS 工程的一次典型链接命令要求 FFmpeg 的库在前第三方库在后因为链接器是从左到右解析未定义符号的。如果顺序反了即使所有.a都添加进 Build Phases依然会报Undefined _avcodec_send_packet或_x264_encoder_encode。5. 把产物用起来用 lipo 合成 xcframework并在 Xcode 里验证编码结果5.1 用 lipo 合并多架构库脚本编译出来的每个$OUTPUT_DIR/$sdk-$arch下都会有libavcodec.a、libx264.a、libfdk-aac.a、libmp3lame.a等文件。下一步是把不同架构下同名文件合并。merge_lipo() { local lib_name$1 lipo -create \ $OUTPUT_DIR/iphoneos-arm64/lib/$lib_name \ $OUTPUT_DIR/iphonesimulator-x86_64/lib/$lib_name \ $OUTPUT_DIR/iphonesimulator-arm64/lib/$lib_name \ -output $OUTPUT_DIR/universal/$lib_name } Lib_sources$(ls $OUTPUT_DIR/iphoneos-arm64/lib | grep \.a$) for name in $Lib_sources; do merge_lipo $name donelipo -create的输入必须是同一个库的不同架构切片输出位于out/universal。合并后用lipo -info检查lipo -info $OUTPUT_DIR/universal/libavcodec.a正常输出会列出arm64 x86_64说明一个文件覆盖了真机和模拟器。5.2 用 xcodebuild 生成可发布的 xcframework直接用.a文件添加进 Xcode 工程是可行的但新版 Xcode 更推荐xcframework因为它能把每个平台对应的架构放到独立目录并且头文件跟随框架一起暴露。以下命令把单个库打包xcodebuild -create-xcframework \ -library $OUTPUT_DIR/iphoneos-arm64/lib/libavcodec.a \ -headers $OUTPUT_DIR/iphoneos-arm64/include \ -library $OUTPUT_DIR/iphonesimulator-x86_64/lib/libavcodec.a \ -headers $OUTPUT_DIR/iphonesimulator-x86_64/include \ -library $OUTPUT_DIR/iphonesimulator-arm64/lib/libavcodec.a \ -headers $OUTPUT_DIR/iphonesimulator-arm64/include \ -output $OUTPUT_DIR/AVFFmpeg.xcframework-library接收的是静态库路径每个平台都要带一份。由于 iOS 上不会为所有库只做一个 xcframework常见做法是把 libavcodec、libavformat、libavutil、libswscale、libswresample 以及 libx264 等分别打包或者用libtool -static先合并成一个总的静态库再创建单个 framework。我一般倾向后者因为第三方库数量少、头文件都在同一个 include 目录合并后工程引用更简洁。5.3 在 Xcode 工程里验证 h264/aac/mp3 是否真的编得出来把 xcframework 拖进工程后先用一句ffmpegAPI 验证库能被链接和调用#include libavformat/avformat.h AVFormatContext *fmt_ctx NULL; AVDictionary *opts NULL; avformat_open_input(fmt_ctx, path, NULL, opts); avformat_close_input(fmt_ctx);如果能编译通过再准备一个带 PCM 音轨的视频文件放到模拟器沙盒里调用 FFmpeg 转成 H.264 AAC MP4。这里不展开完整 C 代码重点验证编码器是否真的编进了库ffmpeg -i input.mov -c:v libx264 -b:v 2000k -c:a libfdk_aac -b:a 128k output.mp4只要你构建的静态库包含脚本里配置的三个第三方开关输出文件中的Video: h264和Audio: aac就说明编译链路是通的。如果日志显示Unknown encoder libx264优先检查 FFmpeg configure 阶段--enable-libx264之后有没有真的找到x264.h而不是看.a是否被拖进工程。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门