`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>
143 lines
5.8 KiB
Bash
Executable File
143 lines
5.8 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# Builds ffmpeg-kit-next into an Android AAR. Runs INSIDE the container image
|
|
# defined by the sibling Containerfile.
|
|
#
|
|
# Usage: build-ffmpeg.sh [spike|full]
|
|
#
|
|
# spike - minimal library set. Validates that the toolchain works and produces an
|
|
# AAR, without waiting on x264/x265/SVT-AV1. Use this first.
|
|
# full - the shipping configuration (GPL: x264 + x265).
|
|
#
|
|
set -euo pipefail
|
|
|
|
# The upstream scripts use `#!/bin/bash`, but the nixos/nix image ships only /bin/sh
|
|
# (itself bash, via the Nix store). Without this, start-android.sh dies with
|
|
# "cannot execute: required file not found" AFTER the whole toolchain has been built,
|
|
# which is an expensive way to discover a missing symlink.
|
|
if [[ ! -e /bin/bash ]]; then
|
|
ln -sf "$(command -v bash)" /bin/bash
|
|
fi
|
|
|
|
MODE="${1:-spike}"
|
|
TAG="${FFMPEG_KIT_TAG:-v8.1.1}"
|
|
SRC=/work/ffmpeg-kit-next
|
|
OUT=/work/out
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Library selection
|
|
# ---------------------------------------------------------------------------
|
|
# Flag names come from get_library_name() in scripts/function.sh — note it is
|
|
# --enable-lame, NOT --enable-libmp3lame. Read that function before adding one: the
|
|
# names are ffmpeg-kit's, not FFmpeg's, and they agree only sometimes. libvorbis is
|
|
# one that does agree (id 9 is literally "libvorbis"), so --enable-libvorbis is right
|
|
# and the --enable-vorbis this rule would predict is not.
|
|
#
|
|
# android-media-codec gives FFmpeg the h264_mediacodec / hevc_mediacodec wrappers.
|
|
# Those are the fallback-within-the-fallback: hardware encode from the FFmpeg side
|
|
# when a job has been routed away from Media3 for container reasons but still wants
|
|
# hardware encode.
|
|
|
|
COMMON_LIBS=(
|
|
--enable-android-media-codec
|
|
--enable-android-zlib
|
|
--enable-lame # MP3 encode. Android has NO MP3 encoder at any API level,
|
|
# so this is the only way the app can output MP3 at all.
|
|
--enable-opus
|
|
--enable-libvorbis # Ogg Vorbis encode. Android has no Vorbis ENCODER at any API
|
|
# level either, and FFmpeg's own in-tree vorbis encoder is
|
|
# experimental, stereo-only and barely responds to -q:a, so
|
|
# this is the only usable route. Pulls libogg in as its
|
|
# dependency (ffmpeg-kit sets LIBRARY_LIBOGG with it).
|
|
--enable-dav1d # fast AV1 decode
|
|
)
|
|
|
|
SUBTITLE_LIBS=(
|
|
--enable-libass # ISC licensed, NOT GPL - subtitle burn-in is LGPL-safe
|
|
--enable-fontconfig
|
|
--enable-freetype
|
|
--enable-fribidi
|
|
--enable-harfbuzz
|
|
)
|
|
|
|
# GPL. These are the reason the shipped binary is GPL-3.0 rather than LGPL: they are
|
|
# the only route to CRF and 2-pass rate control, which no Android hardware encoder
|
|
# exposes. See LICENSES/README.md.
|
|
GPL_LIBS=(
|
|
--enable-gpl
|
|
--enable-x264
|
|
--enable-x265
|
|
)
|
|
|
|
EXTRA_LIBS=(
|
|
--enable-libvpx # VP8/VP9
|
|
--enable-libsvtav1 # fast AV1 encode
|
|
)
|
|
|
|
case "$MODE" in
|
|
spike) LIBS=("${COMMON_LIBS[@]}") ;;
|
|
full) LIBS=("${COMMON_LIBS[@]}" "${SUBTITLE_LIBS[@]}" "${GPL_LIBS[@]}" "${EXTRA_LIBS[@]}") ;;
|
|
*) echo "unknown mode: $MODE (expected 'spike' or 'full')" >&2; exit 2 ;;
|
|
esac
|
|
|
|
echo "=============================================="
|
|
echo " ffmpeg-kit-next build"
|
|
echo " mode : $MODE"
|
|
echo " tag : $TAG"
|
|
echo " libraries : ${LIBS[*]}"
|
|
echo " started : $(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
echo "=============================================="
|
|
|
|
if [[ ! -d "$SRC" ]]; then
|
|
git clone --branch "$TAG" --depth 1 \
|
|
https://github.com/arthenica/ffmpeg-kit-next.git "$SRC"
|
|
fi
|
|
|
|
cd "$SRC"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# AAPT2 override
|
|
# ---------------------------------------------------------------------------
|
|
# The final step packages the .so files into an AAR with Gradle. Gradle's default
|
|
# AAPT2 comes from Maven as a prebuilt binary dynamically linked against normal FHS
|
|
# paths (/lib64/ld-linux-x86-64.so.2). Those do not exist in a Nix image, so it dies
|
|
# with "AAPT2 ... Daemon startup failed" AFTER the entire native build has succeeded.
|
|
#
|
|
# The Android SDK that Nix provides has an aapt2 that nixpkgs has already patchelf'd,
|
|
# so point Gradle at that one instead.
|
|
AAPT2="$(find /nix/store -maxdepth 6 -name aapt2 -type f 2>/dev/null | head -1)"
|
|
if [[ -n "$AAPT2" ]]; then
|
|
echo "using nix-provided aapt2: $AAPT2"
|
|
grep -v 'aapt2FromMavenOverride' android/gradle.properties > /tmp/gradle.properties.new || true
|
|
mv /tmp/gradle.properties.new android/gradle.properties
|
|
echo "android.aapt2FromMavenOverride=$AAPT2" >> android/gradle.properties
|
|
else
|
|
echo "WARNING: no nix aapt2 found; the AAR packaging step will probably fail." >&2
|
|
fi
|
|
|
|
# 64-bit only, matching the app's abiFilters. Dropping the 32-bit ABIs roughly halves
|
|
# build time and APK size, and Play does not require them.
|
|
# --api-level matches the app's minSdk 33 (default is 24), so the native code may use
|
|
# the newer NDK media APIs. ffmpeg-kit protocols (ffkitsaf, ffkitmem, ffkitstream) are
|
|
# left enabled: ffkitsaf is the SAF bridge that replaces the old getSafParameter trick.
|
|
# Output still stages through a real cache path rather than a SAF fd, because MP4
|
|
# faststart needs to seek back to rewrite the moov atom.
|
|
./nix-android.sh -p android-r27d \
|
|
--api-level=33 \
|
|
--disable-arm-v7a \
|
|
--disable-arm-v7a-neon \
|
|
--disable-x86 \
|
|
"${LIBS[@]}"
|
|
|
|
mkdir -p "$OUT"
|
|
# Only the ffmpeg-kit AAR. A bare '*.aar' find also sweeps up every AAR that Gradle
|
|
# happens to have unpacked into its own caches (junit, espresso, tracing...), which
|
|
# is confusing noise in the output directory.
|
|
find "$SRC/android/ffmpeg-kit-next-android-lib/build/outputs/aar" \
|
|
"$SRC/prebuilt" \
|
|
-name 'ffmpeg-kit-next*.aar' -exec cp -v {} "$OUT/" \; 2>/dev/null
|
|
|
|
echo "=============================================="
|
|
echo " finished : $(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
ls -la "$OUT" || true
|