A battle-tested, high-performance cross-compilation pipeline designed to build custom FFmpeg Android shared libraries (libffmpeg.so) optimized for Expo Nitro Modules, FFMPEG Expo Module, React Native Native Modules, and mobile video editor applications.
This repository automates the compilation of FFmpeg for Android using modern toolchains and optimized target settings:
aarch64 (arm64-v8a ABI)r26d with Clang/LLVM cross-compilationlibffmpeg.so) combining FFmpeg core, libass subtitle stack, x264, x265, and libvpxENABLE_LTO=yes), symbol visibility hiding (-Wl,--exclude-libs,ALL), and post-build binary stripping (llvm-strip)Running bash scripts/build.sh produces:
output/
├── libs/
│ └── libffmpeg.so # Monolithic shared library containing all codecs & libass
├── ffmpeg # Standalone CLI tool (optional, controlled by BUILD_FFMPEG)
├── ffprobe # Standalone probe tool (optional, controlled by BUILD_FFPROBE)
└── build-info.txt # Detailed compilation metadata & enabled features summary
GitHub Actions creates:
release/
├── ffmpeg-android-arm64-v8a.tar.gz # Full compressed output archive
├── libffmpeg.so # Standalone shared library for direct download
├── build-info.txt # Compilation summary and feature list
└── release-notes.md # Markdown summary for GitHub Releases
The build process is structured into decoupled, maintainable shell scripts in the scripts/ directory:
.
├── configs/ # Configuration files defining feature flags
│ ├── ffmpeg.conf # Core build target, API level, & feature switches
│ ├── codecs.conf # Software & MediaCodec codec configurations
│ ├── containers.conf # Muxers & demuxers configuration
│ ├── filters.conf # Audio/video/text filter switches
│ └── subtitles.conf # Subtitle engine & font configuration
├── scripts/
│ ├── common.sh # Environment loader (NDK paths, CC/CXX/AR/STRIP definitions)
│ ├── validate_config.sh # Pre-build validation of host tools & environment variables
│ ├── prepare_sources.sh # Downloads & extracts FFmpeg and dependency source code
│ ├── build_deps.sh # Compiles dependency libraries (Expat, FreeType, HarfBuzz, FriBidi, FontConfig, libass, x264, x265, libvpx)
│ ├── build_ffmpeg.sh # Configures FFmpeg core, links static deps, and generates single libffmpeg.so
│ └── build.sh # Top-level orchestrator script
graph TD
A[scripts/build.sh] --> B[scripts/common.sh]
A --> C[scripts/validate_config.sh]
A --> D[scripts/build_deps.sh]
D --> E[scripts/prepare_sources.sh]
D --> F[Build Expat, FreeType, HarfBuzz, FriBidi, FontConfig, libass]
D --> G[Build x264, x265, libvpx]
A --> H[scripts/build_ffmpeg.sh]
H --> I[Configure FFmpeg with MediaCodec & External Libs]
H --> J[Compile FFmpeg Static Archives]
H --> K[Merge Static Archives into libffmpeg.so]
A --> L[Strip libffmpeg.so]
A --> M[Generate build-info.txt]
The single libffmpeg.so shared library is flattened from the following static components:
libavcodec — Audio/video codecslibavformat — Container demuxers and muxerslibavfilter — Video and audio processing filterslibavdevice — Input/output deviceslibavutil — Core utility functionslibswscale — Color conversion and scalinglibswresample — Audio resampling and channel mixinglibass (0.17.1) — Advanced SubStation Alpha subtitle rendererFontConfig (2.15.0) — Font configuration and matching libraryFreeType (2.13.2) — Font rasterization engineHarfBuzz (8.5.0) — Text shaping engine for complex scriptsFriBidi (1.0.15) — Unicode Bidirectional Algorithm implementationExpat (2.6.2) — XML parser (FontConfig dependency)x264 (stable branch) — H.264 software encoderx265 (4.1) — H.265 / HEVC software encoderlibvpx (1.15.2) — VP8 / VP9 software encoder and decoderlibmp3lame (3.100) — High quality MP3 audio encoderlibavif (1.1.1) — AVIF image format encoder/decoder (powered by libaom 3.9.1)Android MediaCodec hardware acceleration is enabled to offload video encoding and decoding to device NPU/GPU/VPU hardware:
h264_mediacodechevc_mediacodecvp8_mediacodecvp9_mediacodech264_mediacodechevc_mediacodecNote: VP8 and VP9 hardware support on Android is decode-only.
libx264: H.264 software encoding (ENABLE_X264=yes)libx265: H.265 / HEVC software encoding (ENABLE_X265=yes)libvpx: VP8 and VP9 software encoding and decoding (ENABLE_LIBVPX=yes)libmp3lame: High quality MP3 software encoding (ENABLE_LIBMP3LAME=yes)libavif: AVIF image format encoding and decoding (ENABLE_LIBAVIF=yes)file, http, httpsThe build includes a full libass subtitle rendering engine supporting styled SSA/ASS, SRT, and WebVTT captions:
-vf subtitles=...)/system/fonts) and embedded font cache via FontConfigBuild options are configured via files in configs/:
configs/ffmpeg.conf)| Flag | Default | Description |
|---|---|---|
ANDROID_API |
33 |
Target Android API level |
TARGET_ARCH |
aarch64 |
Target CPU architecture |
TARGET_ABI |
arm64-v8a |
Target Android ABI |
ENABLE_MEDIACODEC |
yes |
Enables Android MediaCodec hardware acceleration |
ENABLE_LIBASS |
yes |
Enables libass subtitle renderer |
ENABLE_X264 |
yes |
Enables x264 software encoder |
ENABLE_X265 |
yes |
Enables x265 HEVC software encoder |
ENABLE_LIBVPX |
yes |
Enables libvpx VP8/VP9 software encoder/decoder |
ENABLE_LTO |
yes |
Enables Link-Time Optimization |
STRIP_BINARIES |
yes |
Strips symbol tables from final output .so |
OUTPUT_SINGLE_SO |
yes |
Flattens all static archives into single libffmpeg.so |
llvm-strip Failure during libvpx Compilation/home/runner/work/ffmpeg/ffmpeg/android-ndk-r26d/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip: error: 'libvpx_g.a(vpx_decoder.c.o)': The file was not recognized as a valid object file
During cross-compilation with Android NDK LLVM toolchain, libvpx configure step was attempting to strip intermediate object files (libvpx_g.a(vpx_decoder.c.o)) using llvm-strip, causing file format misinterpretation.
Set STRIP="true" (a shell no-op) during the libvpx build step in scripts/build_deps.sh:
STRIP="true" \
./configure \
--target=arm64-android-gcc \
--prefix="$PREFIX" \
--enable-vp8 \
--enable-vp9 \
--enable-pic \
--enable-static \
--disable-shared \
--disable-examples \
--disable-tools \
--disable-docs \
--disable-unit-tests \
--disable-install-bins \
--extra-cflags="$CFLAGS"
This ensures libvpx bypasses intermediate stripping during compilation without passing non-standard configure flags, while preserving binary stripping in build.sh for libffmpeg.so.
r26dsudo apt update && sudo apt install -y \
git \
wget \
unzip \
curl \
pkg-config \
build-essential \
autoconf \
automake \
cmake \
gettext \
gperf \
libtool \
xz-utils \
yasm \
nasm \
meson \
ninja-build
Download Android NDK r26d and export its path:
wget https://dl.google.com/android/repository/android-ndk-r26d-linux.zip
unzip android-ndk-r26d-linux.zip
export NDK=$PWD/android-ndk-r26d
Make scripts executable:
chmod +x scripts/*.sh
Run the full build pipeline:
bash scripts/build.sh
Workflow configuration is located in .github/workflows/android.yml.
workflow_dispatch or push of git tag v*output/ directory as an artifact (ffmpeg-android-arm64-v8a)libffmpeg.so and ffmpeg-android-arm64-v8a.tar.gz directly to GitHub Releases using softprops/action-gh-release@v3libffmpeg.so in your Android projectCopy libffmpeg.so into your native Android module structure:
android/app/src/main/jniLibs/arm64-v8a/libffmpeg.so
In your React Native Native Module or Expo Nitro Module:
package com.mycompany.ffmpeg
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.Promise
class FFmpegModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
companion object {
init {
System.loadLibrary("ffmpeg")
}
}
override fun getName(): String = "FFmpegModule"
external fun executeFFmpegCommand(cmd: String): Int
}
In your CMakeLists.txt:
add_library(ffmpeg SHARED IMPORTED)
set_target_properties(ffmpeg PROPERTIES
IMPORTED_LOCATION "${CMAKE_CURRENT_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/libffmpeg.so"
)
target_link_libraries(
your_nitro_module
ffmpeg
log
)
ffmpeg -ss 00:00:05 -i input.mp4 -t 10 -c copy output.mp4
ffmpeg -c:v h264_mediacodec -i input.mp4 -c:v h264_mediacodec -b:v 2M output.mp4
ffmpeg -i input.mp4 -vf "subtitles=captions.ass" output.mp4
ffmpeg -i video.mp4 -i watermark.png -filter_complex "overlay=W-w-10:H-h-10" output.mp4
ffmpeg -i video.mp4 -i background_music.mp3 -filter_complex "[0:a][1:a]amix=inputs=2:duration=first[a]" -map 0:v -map "[a]" output.mp4
x264 and x265 static libraries).ENABLE_GPL=no, ENABLE_X264=no, and ENABLE_X265=no in configs/ffmpeg.conf and configs/codecs.conf.Fazi gondal
This is what I want to achieve