`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>
Prebuilt FFmpeg
ffmpeg-kit-next-8.1.1.aar is committed here deliberately, and this file records what
it is so the binary is auditable rather than opaque.
Why it is committed
Tests must be deterministic. When CI rebuilt FFmpeg on every run, a red build could mean "the code is broken" or "a 40-minute cross-compile hiccuped", and those are not the same signal. Committing the artifact removes the second possibility entirely: a test run either passes or points at real code.
It also removes roughly forty minutes from every cold CI run.
Provenance
| Upstream | arthenica/ffmpeg-kit-next v8.1.1 |
| FFmpeg | 8.1.2 |
| NDK | r27d (27.3.13750724), pinned by the upstream flake |
| API level | 33, matching the app's minSdk |
| ABIs | arm64-v8a, x86_64 |
| Shared libraries | 20 (10 per ABI) |
| SHA-256 | c8f4491d2c626566cbf18d5035513c1a5d8049e6696531342ea030c5427df507 |
| Rebuilt | 2026-09-06, to add libvorbis (#254). Previous archive: ae188c9a…, same tag and FFmpeg version, one library fewer |
Configure line, read back out of the shipped libavutil.so:
--enable-asm --enable-cross-compile --enable-gpl --enable-iconv
--enable-inline-asm --enable-jni --enable-libass --enable-libdav1d
--enable-libfontconfig --enable-libfreetype --enable-libfribidi
--enable-libharfbuzz --enable-libjxl --enable-libmp3lame --enable-libopus
--enable-libsvtav1 --enable-libvorbis --enable-libvpx --enable-libx264
--enable-libx265 --enable-lto --enable-mediacodec --enable-neon
--enable-optimizations --enable-pic --enable-pthreads --enable-shared
--enable-small --enable-swscale --enable-v4l2-m2m --enable-version3
--enable-zlib
--enable-libvorbis is the one that arrived late, in #254, and the two ways to get it wrong are
worth having written down. ffmpeg-kit's --enable-* names are its own — --enable-lame for
libmp3lame, --enable-opus for libopus — so --enable-vorbis is the plausible guess and it is not
the flag; get_library_name() in the upstream scripts/function.sh calls library 9 libvorbis.
And an unrecognised --enable-* is ignored silently, so a build that dropped it looks exactly
like one that worked. What tells them apart is the binary:
unzip -p bin/ffmpeg-kit-next-8.1.1.aar 'jni/x86_64/libavcodec.so' > /tmp/libavcodec.so
strings /tmp/libavcodec.so | grep -x libvorbis # and the same for arm64-v8a
Every .so reports LOAD align 0x4000, so the archive satisfies the 16 KB page-size
requirement. Verify with:
unzip -o bin/ffmpeg-kit-next-8.1.1.aar 'jni/*' -d /tmp/aarcheck
for f in /tmp/aarcheck/jni/*/*.so; do
readelf -lW "$f" | awk -v f="$f" '$1=="LOAD"{print f, $NF}'
done | sort -u -k2
Licensing
Built with --enable-gpl and --enable-version3, so this binary is GPL-3.0 and the
distributed APK is GPL-3.0 with it. That is deliberate: x264 and x265 are the only route
to CRF and two-pass rate control, which no Android hardware encoder exposes. See
../LICENSES/README.md.
GPL-3.0 obliges us to ship corresponding source with the binary. The recipe in
../tools/ffmpeg is that source, and it remains the authority: this
archive is its output, not a substitute for it.
Rebuilding
cd tools/ffmpeg
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
cp out/ffmpeg-kit-next-*.aar ../../bin/
Update the SHA-256 above when you do. Note that replacing this file adds another ~34 MB blob to git history permanently, so rebuild only when the FFmpeg version or the configure flags actually change.
F-Droid
F-Droid's scanner flags checked-in prebuilt native libraries. If the app is submitted
there, the metadata needs a scandelete entry for bin/ so their build uses the recipe
in tools/ffmpeg rather than this archive. Nothing here prevents a from-source build;
the recipe is complete on its own.