Files
LibreMediaConverter/tools/ffmpeg/README.md
T
JMR-devandClaude Opus 5 b082cea889 Add containerized FFmpeg build producing a 16 KB-aligned GPL AAR
There is no usable prebuilt FFmpeg for Android any more. arthenica/ffmpeg-kit
is archived and its binaries were deleted from Maven Central, so every
com.arthenica:ffmpeg-kit-* coordinate 404s and all of its release tags have
zero assets. Maven Central's search index still lists the old versions, which
misleads; the files behind those entries are gone. The successor,
ffmpeg-kit-next, is source-only by design. Building it ourselves is the only
remaining option, not a preference.

ffmpeg-kit-next is Nix-only -- there is no plain android.sh, only
nix-android.sh and a flake -- so the toolchain lives in a container rather
than on the developer's machine. The recipe doubles as the reproducibility
artifact F-Droid expects and as the GPL corresponding-source obligation.

Four problems this path hits, none of them documented upstream:

- The nixos/nix base image already ships bash, coreutils and git; installing
  them collides with the existing profile entries and fails the image build.
- Upstream scripts use #!/bin/bash but the image provides only /bin/sh, so
  start-android.sh dies with "cannot execute: required file not found" after
  the entire toolchain has been built.
- Gradle's AAPT2 comes from Maven as a prebuilt binary linked against FHS
  paths that do not exist under Nix, failing with "Daemon startup failed"
  after the whole native build succeeds. Nixpkgs' Android SDK ships an
  already-patched aapt2, so Gradle is pointed at that.
- A bare '*.aar' find also collects every AAR Gradle unpacked into its own
  caches, so the copy is scoped to the ffmpeg-kit outputs.

The NDK stays at r27d as the flake pins it. Do not "upgrade" to r28+:
android/jni/Android.mk applies -Wl,-z,max-page-size=16384 manually precisely
because r27 predates automatic alignment, and the result is verified 16 KB
compliant as-is.

Verified against the produced artifact: every .so on both ABIs reports LOAD
align 0x4000, libraries are separate rather than a static monolith as the
GPL relinking obligation requires, and the embedded configure line confirms
--enable-gpl --enable-version3 with x264, x265, SVT-AV1, LAME, libass and
the MediaCodec wrappers. Note that --enable-small and --enable-lto
internalize symbols, so absence from strings output proves nothing; check
the configure line instead.

The 35 MB AAR itself is gitignored. F-Droid strips checked-in prebuilt
native libraries, and the recipe is the artifact of record.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 21:19:01 -05:00

5.6 KiB

FFmpeg build

Builds ffmpeg-kit-next into an Android AAR that the app consumes.

Why this exists

arthenica/ffmpeg-kit — the library nearly every Android FFmpeg tutorial still points at — was archived, and its binaries were deleted from Maven Central. Every com.arthenica:ffmpeg-kit-* coordinate now returns 404, and all of its GitHub release tags have zero attached assets. Maven Central's search index still lists the old versions, which is misleading; the files behind those entries are gone.

Its successor, ffmpeg-kit-next, is source-only by design and publishes no prebuilt packages. So building FFmpeg ourselves is not a preference, it is the only remaining option.

Community forks publishing prebuilt 16 KB-aligned AARs do exist, but each fails on license, ABI coverage, or publisher credibility — and at least one redistributes a non-free FDK-AAC build, which FFmpeg states is unredistributable.

Why a container

ffmpeg-kit-next is Nix-only. There is no plain android.sh; the repository ships nix-android.sh plus a flake, and the flake pins the entire toolchain including Android NDK 27.3.13750724 (r27d).

Rather than install Nix on a developer machine, the toolchain lives in a container image. That keeps the host clean and doubles as the reproducibility artifact F-Droid wants.

Note the NDK version: do not "helpfully" upgrade to r28+. The flake pins r27d and android/jni/Android.mk applies -Wl,-z,max-page-size=16384 manually for arm64-v8a and x86_64 precisely because r27 predates automatic 16 KB alignment. The output is 16 KB compliant as-is.

Usage

podman build -t ffmpeg-kit-builder:local -f Containerfile .

mkdir -p out
podman run --name ffmpeg-build -v "$PWD/out":/work/out:Z \
    localhost/ffmpeg-kit-builder:local full

Two modes:

Mode Libraries Purpose
spike minimal Validates the toolchain end to end without waiting on x264/x265/SVT-AV1
full shipping set The GPL configuration that ships

The run deliberately omits --rm: the container's writable layer retains the several gigabytes of Nix store contents (Android SDK and NDK), so subsequent builds skip the download. Reuse it with podman start -a ffmpeg-build.

Only arm64-v8a and x86_64 are built, matching the app's abiFilters. Dropping the 32-bit ABIs roughly halves both build time and APK size, and Play does not require them.

Library selection

Flag names come from get_library_name() in the upstream scripts/function.sh. Two that are easy to get wrong:

  • It is --enable-lame, not --enable-libmp3lame.
  • It is --enable-libsvtav1 for SVT-AV1.

MP3 deserves a note: Android has no MP3 encoder at any API level. That is a platform gap, not a Media3 limitation, so --enable-lame is the only way the app can output MP3.

--enable-android-media-codec gives FFmpeg the h264_mediacodec / hevc_mediacodec wrappers (added in FFmpeg 6.0). These act as a fallback-within-the-fallback: hardware encode from the FFmpeg side when a job was routed away from Media3 for container reasons but still wants hardware speed.

Licensing

The full build passes --enable-gpl with x264 and x265, which makes the distributed binary GPL-3.0. This is deliberate — see ../../LICENSES/README.md.

Worth recording, because it is widely misunderstood: libass is ISC licensed, not GPL, so subtitle burn-in does not require the GPL flag. The only things GPL genuinely buys are x264/x265 software encode (and with them CRF and 2-pass rate control), vidstab, and the GPL filter set.

Never build with --enable-nonfree. FFmpeg states it renders the binary unredistributable.

Verified build output (2026-08-19, ffmpeg-kit-next v8.1.1 / FFmpeg 8.1.2)

The full build produced a 35 MB AAR with 10 shared libraries per ABI for arm64-v8a and x86_64. Confirmed against the artifact rather than assumed:

  • 16 KB page alignment: every .so on both ABIs reports LOAD align 0x4000 (readelf -lW). This is the hard Play gate.
  • Separate shared libraries, not a static monolith — which is what the LGPL/GPL relinking obligation requires.
  • Embedded configure line (from strings libavutil.so): --enable-gpl --enable-version3 --enable-libx264 --enable-libx265 --enable-libsvtav1 --enable-libvpx --enable-libmp3lame --enable-libopus --enable-libdav1d --enable-libass --enable-libfontconfig --enable-libfreetype --enable-libfribidi --enable-libharfbuzz --enable-mediacodec --enable-jni --enable-shared --enable-small --enable-lto
  • Present and verified: libx264 (with an x264 core banner, so genuinely linked), libx265, libsvtav1, libmp3lame, h264_mediacodec, hevc_mediacodec, libopus, libdav1d, the GIF encoder and muxer, libass internals (ass_shaper_new), and the subtitles, scale, palettegen, paletteuse and concat filters.

Note --enable-version3: combined with --enable-gpl this makes the binary GPL-3.0, which is what LICENSES/README.md states.

A caution on verifying this yourself: the build uses --enable-small and --enable-lto, so internal symbols like ff_libx264_encoder do not appear in strings output. Their absence proves nothing. Check the configure line and the registered codec names instead.

Release checklist

GPL-3.0 requires corresponding source alongside the binary. For each release, attach to the GitHub Release next to the APK:

  • the exact ffmpeg-kit-next tag and FFmpeg version used,
  • the full configure line (printed in this script's build log), and
  • any patches applied.