#!/usr/bin/env bash
# Builds libffmpegJNI.so: Media3 1.8.0's FFmpeg audio module (Apache 2.0) and jni/video_jni.cc with FFmpeg 6.0
# (LGPL 2.1 or later, no GPL or non-free parts). The results are committed in app/src/main/jniLibs, so an ordinary app build
# needs neither the NDK nor FFmpeg. Run this only to change FFmpeg or the decoders.
#
#   build_ffmpeg.sh <work dir> <NDK dir, no spaces in the path> [ABIs...]
#
# It runs in Git Bash on Windows with the NDK's own make and clang. Paths are passed in the short form
# (such as PROGRA~1), because FFmpeg's configure does not take spaces. See README.md for the exact sources.
set -eu

WORK="$1"
NDK="$2"
shift 2
ABIS=("$@")
[ ${#ABIS[@]} -eq 0 ] && ABIS=(arm64-v8a armeabi-v7a x86_64 x86)
HERE="$(cd "$(dirname "$0")" && pwd)"
HOST=windows-x86_64
TC="$NDK/toolchains/llvm/prebuilt/$HOST/bin"
MAKE="$NDK/prebuilt/$HOST/bin/make.exe"
API=24
SRC="$WORK/FFmpeg-release-6.0"
OUT="$WORK/libs"
JOBS="${JOBS:-8}"
# configure takes its temp folder from here, and cannot use a Windows path with backslashes
export TMPDIR="$WORK/tmp"
mkdir -p "$TMPDIR"

# Every codec FfmpegLibrary.getCodecName can name. "mp3" also decodes MPEG layer 1 and 2 (MP2, common on TV).
DECODERS=(aac mp3 ac3 eac3 truehd dca vorbis opus amrnb amrwb flac alac pcm_mulaw pcm_alaw
          h264 hevc mpeg2video mpeg1video mpeg4 h263 vc1 vp8 vp9)

ffmpeg_for() {
  local abi="$1" triple arch cpu extra=()
  case "$abi" in
    arm64-v8a)   triple=aarch64-linux-android$API;     arch=aarch64; cpu=armv8-a ;;
    armeabi-v7a) triple=armv7a-linux-androideabi$API;  arch=arm;     cpu=armv7-a; extra=(--enable-neon --extra-cflags="--target=$triple -march=armv7-a -mfloat-abi=softfp -mfpu=neon" --extra-ldflags="--target=$triple -Wl,--fix-cortex-a8") ;;
    x86)         triple=i686-linux-android$API;        arch=x86;     cpu=i686;    extra=(--disable-asm) ;;
    x86_64)      triple=x86_64-linux-android$API;      arch=x86_64;  cpu=x86-64;  extra=(--disable-asm) ;;
    *) echo "unknown ABI $abi"; exit 1 ;;
  esac
  [ "$abi" = armeabi-v7a ] || extra+=(--extra-cflags="--target=$triple" --extra-ldflags="--target=$triple")
  local enable=()
  for d in "${DECODERS[@]}"; do enable+=(--enable-decoder="$d"); done
  cd "$SRC"
  "$MAKE" distclean > /dev/null 2>&1 || true
  # distclean's own lines get cut off the same way, and an object left from another ABI would join the archive
  find . \( -name '*.o' -o -name '*.a' -o -name '*.d' \) -delete
  ./configure \
    --target-os=android --enable-cross-compile --arch="$arch" --cpu="$cpu" \
    --cc="$TC/clang.exe" --cxx="$TC/clang++.exe" --ld="$TC/clang.exe" --host-cc="$TC/clang.exe" \
    --nm="$TC/llvm-nm.exe" --ar="$TC/llvm-ar.exe" --ranlib="$TC/llvm-ranlib.exe" --strip="$TC/llvm-strip.exe" \
    --pkg-config=false --disable-autodetect \
    --enable-static --disable-shared --disable-doc --disable-programs --disable-everything \
    --disable-avdevice --disable-avformat --disable-postproc --disable-avfilter --enable-pic \
    --disable-symver --enable-swresample --disable-v4l2-m2m --disable-vulkan \
    "${extra[@]}" "${enable[@]}"
  grep -E "^(CONFIG_GPL|CONFIG_NONFREE|CONFIG_VERSION3)=" ffbuild/config.mak || true
  grep -q "^!CONFIG_GPL=yes" ffbuild/config.mak || { echo "this build would be GPL, stopping"; exit 1; }
  # libavcodec's archive line can pass 8191 characters, where Windows cuts a command off (armv7's does), so make's
  # try at it may fail, and it is made here from a list. A file that did not compile still stops the build.
  "$MAKE" -j"$JOBS" libavutil/libavutil.a libswresample/libswresample.a libswscale/libswscale.a
  local log="$WORK/make_libavcodec_$abi.log"
  "$MAKE" -j"$JOBS" -k libavcodec/libavcodec.a > "$log" 2>&1 || true
  if grep -E '\.o\] Error|: error:' "$log" | grep -v 'llvm-ar'; then echo "libavcodec did not compile, see $log"; exit 1; fi
  find libavcodec -name '*.o' > libavcodec.objs
  rm -f libavcodec/libavcodec.a
  "$TC/llvm-ar.exe" rcsD libavcodec/libavcodec.a @libavcodec.objs
  mkdir -p "$OUT/$abi"
  cp libavcodec/libavcodec.a libavutil/libavutil.a libswresample/libswresample.a libswscale/libswscale.a "$OUT/$abi/"
}

jni_for() {
  local abi="$1" dir="$WORK/jni-build/$abi" cmake ninja
  cmake="$(dirname "$NDK")/../cmake/3.22.1/bin/cmake.exe"
  ninja="$(dirname "$NDK")/../cmake/3.22.1/bin/ninja.exe"
  rm -rf "$dir"
  "$cmake" -S "$HERE/jni" -B "$dir" -G Ninja -DCMAKE_MAKE_PROGRAM="$ninja" -DCMAKE_BUILD_TYPE=Release \
    -DCMAKE_TOOLCHAIN_FILE="$NDK/build/cmake/android.toolchain.cmake" -DANDROID_ABI="$abi" -DANDROID_PLATFORM="android-$API" \
    -DFFMPEG_HEADERS="$SRC" -DFFMPEG_LIBS="$OUT/$abi"
  "$cmake" --build "$dir"
  mkdir -p "$HERE/../../app/src/main/jniLibs/$abi"
  "$TC/llvm-strip.exe" --strip-unneeded -o "$HERE/../../app/src/main/jniLibs/$abi/libffmpegJNI.so" "$dir/libffmpegJNI.so"
}

for abi in "${ABIS[@]}"; do
  echo "=== $abi"
  ffmpeg_for "$abi"
  jni_for "$abi"
done
echo "built: ${ABIS[*]}"
