Files
LibreMediaConverter/tools/ffmpeg/README.md
T
JMR-devandClaude Opus 5 d45abe7409 Rebuild the FFmpeg AAR with libvorbis, and make Ogg Vorbis reachable (#254)
`FFmpegCommandBuilder` has emitted `-c:a libvorbis` since the day it was
written, and libvorbis was not in the AAR this app ships: the configure line
omitted `--enable-libvorbis`, and `strings` on both ABIs' `libavcodec.so`
named every other external encoder and not that one. The arm was unreachable
from both ends, so nobody ever hit it -- but the first user to pick Ogg
Vorbis would have got `Unknown encoder 'libvorbis'`. That is #238's shape
again: two individually-correct facts, a builder arm and a configure line,
that no test put together, and that no coverage number can see.

So the binary is rebuilt rather than the arm rewritten. FFmpeg's in-tree
`vorbis` encoder was already in there and was tried first; it is
experimental, stereo-only, and its quality knob spans 2x its floor against
libvorbis's 6x. Shipping it would have meant `-strict experimental`, a
forced `-ac 2` that silently upmixes every mono source, and a slider with
nowhere to go. What ships instead is the arm as originally written,
`-c:a libvorbis -q:a 5`, with `OGG_VORBIS` added to the presets, `VORBIS`
added to `ENCODABLE_AUDIO`, and Ogg's per-codec extension fixed so a Vorbis
file is not named `.opus`.

The flag is `--enable-libvorbis`, read out of ffmpeg-kit's
`get_library_name()` rather than guessed: the `--enable-lame` /
`--enable-opus` rule predicts `--enable-vorbis`, and that is not it. An
unrecognised `--enable-*` is ignored silently, so the artifact was checked
before `bin/README.md` was touched -- `libvorbis` present in both ABIs, the
configure line otherwise identical, FFmpeg still n8.1.2, 10 shared libraries
per ABI, every LOAD still `0x4000`.

Both mutations were run on API 34 rather than predicted. Pointing the arm at
`libopus` reddens the e2e test with `expected:<[audio/vorbis]> but
was:<[audio/opus]>` while its `OggS` assertion still passes, which is why
the track MIME is asserted and the container magic is not enough. Adding
`-ac 2` back reddens it with `expected:<[1]> but was:<[2]>`: this class's
own fixture is mono, so mono staying mono is an assertion rather than a
claim.

The unit test's load-bearing assertion inverts with this change and is
rewritten to say so. It used to assert that `libvorbis` was *absent*; it now
asserts the encoder name plus the two flags that must not be there. Nothing
on the JVM can tell a real encoder name from a fictional one -- which is
exactly how this survived four coverage waves -- so the e2e test is the only
thing that proves the positive.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-06 18:31:36 -05:00

140 lines
6.7 KiB
Markdown

# FFmpeg build
Builds [`ffmpeg-kit-next`](https://github.com/arthenica/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
```sh
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`. Three
that are easy to get wrong:
- It is **`--enable-lame`**, not `--enable-libmp3lame`.
- It is **`--enable-libsvtav1`** for SVT-AV1.
- It *is* **`--enable-libvorbis`** — the rule above makes `--enable-vorbis` the natural
guess and it is wrong. Read the function rather than extrapolating from the first two;
library 9 is named `libvorbis` there. Enabling it also enables libogg, which ffmpeg-kit
pulls in as its dependency without being asked.
**An unrecognised `--enable-*` is ignored silently.** ffmpeg-kit does not error on one, so a
build that quietly dropped a library looks exactly like one that worked, and forty minutes
later there is an AAR that is wrong in a way nothing in the log says. #254 is where that
was learned, from the other end: the builder carried `-c:a libvorbis` for months against a
binary with no libvorbis in it — unreachable, so no user ever hit it, and no build log ever
mentioned it. Check the artifact, not the log — `strings jni/*/libavcodec.so | grep -x <name>`.
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`](../../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`.
**Since 2026-09-06 it also carries `--enable-libvorbis`** (#254), which is the only
difference between that build and the one in `bin/` today — same tag, same FFmpeg
version, same 10 shared libraries per ABI, all still `LOAD align 0x4000`.
- Present and verified: `libx264` (with an x264 core banner, so genuinely linked),
`libx265`, `libsvtav1`, `libmp3lame`, `h264_mediacodec`, `hevc_mediacodec`, `libopus`,
`libdav1d`, `libvorbis` (from 2026-09-06), 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.