#!/usr/bin/env bash # Build libvpx, libopus, and libspeex for Android ABIs → build/native//*.a # CMake in ndk/ autolinks when those archives exist (ANDROIDCAST_HAVE_* = 1). # # Prerequisites: # git submodule update --init third-party/libvpx # Android NDK (auto-detected via scripts/android-ndk.sh) # # Usage: # ./scripts/build-native-codecs.sh arm64-v8a # ANDROIDCAST_LIBVPX_LIB=$PWD/build/native/arm64-v8a/libvpx.a ./gradlew assembleDebug # # Produces (when sources present): libvpx.a, libopus.a, libspeex.a under build/native// set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" ABI="${1:-arm64-v8a}" VPX_SRC_REAL="$ROOT/third-party/libvpx" PROJECT_OUT="$ROOT/build/native/$ABI" OUT="$PROJECT_OUT" BUILD_DIR="$OUT/libvpx-build" if [[ ! -d "$VPX_SRC_REAL" ]]; then echo "ERROR: $VPX_SRC_REAL missing. Run:" echo " git submodule update --init third-party/libvpx" exit 1 fi # shellcheck source=android-ndk.sh source "$ROOT/scripts/android-ndk.sh" if ! NDK="$(androidcast_find_ndk_root "$ROOT")"; then exit 1 fi NDK_HOST_BIN="$(androidcast_ndk_host_bin "$NDK")" if [[ ! -d "$NDK_HOST_BIN" ]]; then echo "ERROR: NDK host toolchain bin not found under $NDK" exit 1 fi case "$ABI" in arm64-v8a) VPX_TARGET=arm64-android-gcc ;; armeabi-v7a) VPX_TARGET=armv7-android-gcc ;; x86_64) VPX_TARGET=x86_64-android-gcc ;; x86) VPX_TARGET=x86-android-gcc ;; *) echo "Unsupported ABI: $ABI" exit 1 ;; esac # libvpx Makefiles break on spaces in SRC_PATH_BARE. path_has_space() { case "$1" in *" "*) return 0 ;; *) return 1 ;; esac } VPX_SRC="$VPX_SRC_REAL" STAGING="" if path_has_space "$ROOT" || path_has_space "$VPX_SRC_REAL"; then STAGING="/tmp/androidcast-libvpx-${USER:-build}" VPX_SRC="$STAGING/src" BUILD_DIR="$STAGING/build/$ABI" OUT="$STAGING/out/$ABI" mkdir -p "$STAGING/build" "$STAGING/out" ln -sfn "$VPX_SRC_REAL" "$VPX_SRC" echo "Repo path contains spaces; using space-free staging under $STAGING" fi mkdir -p "$BUILD_DIR" "$OUT" "$PROJECT_OUT" export ANDROID_NDK_HOME="$NDK" export ANDROID_NDK_ROOT="$NDK" export PATH="$NDK_HOST_BIN:$PATH" # shellcheck source=native-build-cache.sh source "$ROOT/scripts/native-build-cache.sh" androidcast_apply_native_build_accelerators "$ROOT" "$ABI" "$NDK" "$NDK_HOST_BIN" VPX_CONFIGURE_EXTRA=() APP_CFLAGS_EXTRA="" case "$ABI" in x86) # NDK does not infer -msse4.1/-mavx from libvpx source names; AVX-512 is unused on Android x86. VPX_CONFIGURE_EXTRA+=(--disable-avx512) APP_CFLAGS_EXTRA="-msse4.1 -mavx" ;; x86_64) VPX_CONFIGURE_EXTRA+=(--disable-avx512) APP_CFLAGS_EXTRA="-msse4.1 -mavx -mavx2" ;; esac ## common native optimization options APP_CFLAGS_EXTRA+=" -O2 -fdata-sections -ffunction-sections -Wl,--gc-sections -ftree-vectorize -O2 -s -funroll-loops -fomit-frame-pointer " echo "Building libvpx for $ABI (target=$VPX_TARGET) ..." echo " source: $VPX_SRC" echo " build: $BUILD_DIR" cd "$BUILD_DIR" # Out-of-tree build fails if someone previously ran configure inside third-party/libvpx. if [[ -f "$VPX_SRC/vpx_config.h" ]]; then src_real="$(cd "$VPX_SRC" && pwd)" build_real="$(pwd)" if [[ "$src_real" != "$build_real" ]]; then echo "Cleaning in-tree configure artifacts from libvpx source (needed for out-of-tree build)..." make -C "$VPX_SRC" distclean >/dev/null 2>&1 || true rm -f "$VPX_SRC"/vpx_config.h "$VPX_SRC"/vpx_config.c "$VPX_SRC"/vpx_version.h \ "$VPX_SRC"/config.mk "$VPX_SRC"/Makefile rm -f "$VPX_SRC"/libs-*.mk "$VPX_SRC"/vp*_rtcd.h "$VPX_SRC"/libvpx*.a 2>/dev/null || true fi fi LIBS_MK="$BUILD_DIR/libs-${VPX_TARGET}.mk" need_configure=false if [[ ! -f Makefile ]]; then need_configure=true elif [[ ! -f "$LIBS_MK" ]] || ! grep -q "^TOOLCHAIN := ${VPX_TARGET}\$" "$BUILD_DIR/config.mk" 2>/dev/null; then echo "Re-configuring (stale or wrong ABI in $BUILD_DIR)..." make distclean >/dev/null 2>&1 || rm -f Makefile config.mk "$LIBS_MK" vpx_config.h vpx_config.c vpx_config.asm \ vpx_version.h vp8_rtcd.h vp9_rtcd.h vpx_scale_rtcd.h vpx_dsp_rtcd.h need_configure=true elif [[ "$ABI" == x86 || "$ABI" == x86_64 ]] && [[ -f "$BUILD_DIR/vpx_config.h" ]] \ && grep -q '#define HAVE_AVX512 1' "$BUILD_DIR/vpx_config.h"; then echo "Re-configuring (AVX-512 disabled for Android NDK on $ABI)..." make distclean >/dev/null 2>&1 || rm -f Makefile config.mk "$LIBS_MK" vpx_config.h vpx_config.c vpx_config.asm \ vpx_version.h vp8_rtcd.h vp9_rtcd.h vpx_scale_rtcd.h vpx_dsp_rtcd.h need_configure=true fi if $need_configure; then # CC/CXX may be ccache-wrapped NDK clang (see native-build-cache.sh). "$VPX_SRC/configure" \ --target="$VPX_TARGET" \ --disable-examples \ --disable-tools \ --disable-unit-tests \ --disable-webm-io \ --enable-vp8 \ --enable-vp9 \ --enable-static \ --disable-shared \ --extra-cflags="-fPIC -O2" \ --enable-multi-res-encoding \ --enable-vp9-temporal-denoising \ --enable-onthefly-bitpacking \ --enable-error-concealment \ --enable-realtime-only \ --enable-postproc \ --enable-vp9-postproc \ --enable-multithread \ --enable-spatial-resampling \ --disable-debug-libs \ --disable-docs \ --enable-external-build \ "${VPX_CONFIGURE_EXTRA[@]}" fi # armeabi-v7a ndk-build runs make in ndk-app/ and needs vpx_config.asm (not required for arm64). make vpx_config.asm 2>/dev/null || true if [[ -f "$BUILD_DIR/vpx_config.asm" ]] && [[ ! -s "$BUILD_DIR/vpx_config.asm" ]]; then rm -f "$BUILD_DIR/vpx_config.asm" make vpx_config.asm fi # RTCD headers (configure creates them; refresh if missing). make -j1 vp8_rtcd.h vp9_rtcd.h vpx_scale_rtcd.h vpx_dsp_rtcd.h # NDK project root must not contain libvpx's top-level Makefile (ndk-build would invoke it). NDK_ROOT="$BUILD_DIR/ndk-app" JNI_DIR="$NDK_ROOT/jni" rm -rf "$NDK_ROOT" mkdir -p "$JNI_DIR" ln -sfn "$VPX_SRC" "$JNI_DIR/libvpx" # NDK Android.mk prepends jni/ to ASM_CONVERSION; keep the configure-generated absolute path # in BUILD_DIR for `make vpx_config.asm`, and use a jni-only copy for ndk-build. LIBS_MK_NDK="$BUILD_DIR/libs-ndk-${VPX_TARGET}.mk" if [[ -f "$LIBS_MK" ]]; then cp -f "$LIBS_MK" "$LIBS_MK_NDK" sed -i 's|^ASM_CONVERSION=.*|ASM_CONVERSION=libvpx/build/make/ads2gas.pl|' "$LIBS_MK_NDK" fi for f in config.mk vpx_config.c vpx_config.h vpx_config.asm vpx_version.h \ vp8_rtcd.h vp9_rtcd.h vpx_scale_rtcd.h vpx_dsp_rtcd.h; do if [[ -f "$BUILD_DIR/$f" ]]; then ln -sfn "$BUILD_DIR/$f" "$JNI_DIR/$f" # ndk-build may invoke make in ndk-app/ (not jni/) for RTCD / vpx_config.asm on armeabi-v7a. ln -sfn "$BUILD_DIR/$f" "$NDK_ROOT/$f" fi done if [[ -f "$LIBS_MK_NDK" ]]; then ln -sfn "$LIBS_MK_NDK" "$JNI_DIR/libs-${VPX_TARGET}.mk" fi { echo "APP_ABI := $ABI" echo "APP_PLATFORM := android-29" echo "APP_STL := c++_static" if [[ -n "$APP_CFLAGS_EXTRA" ]]; then echo "APP_CFLAGS += $APP_CFLAGS_EXTRA" fi } > "$JNI_DIR/Application.mk" cat > "$JNI_DIR/Android.mk" <<'EOF' LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) # configure outputs (RTCD headers, vpx_config.h) live in jni/ — not inside libvpx sources LOCAL_CFLAGS += -I$(LOCAL_PATH) include $(LOCAL_PATH)/libvpx/build/make/Android.mk EOF "$NDK/ndk-build" -C "$NDK_ROOT" -j"$(nproc)" LIBVPX_A="$NDK_ROOT/obj/local/$ABI/libvpx.a" if [[ ! -f "$LIBVPX_A" ]]; then echo "ERROR: expected $LIBVPX_A after ndk-build" exit 1 fi cp -f "$LIBVPX_A" "$OUT/libvpx.a" if [[ "$OUT" != "$PROJECT_OUT" ]]; then cp -f "$OUT/libvpx.a" "$PROJECT_OUT/libvpx.a" fi androidcast_ndk_api() { echo 29; } androidcast_ndk_clang() { local abi="$1" local api api="$(androidcast_ndk_api)" case "$abi" in arm64-v8a) echo "$NDK_HOST_BIN/aarch64-linux-android${api}-clang" ;; armeabi-v7a) echo "$NDK_HOST_BIN/armv7a-linux-androideabi${api}-clang" ;; x86_64) echo "$NDK_HOST_BIN/x86_64-linux-android${api}-clang" ;; x86) echo "$NDK_HOST_BIN/i686-linux-android${api}-clang" ;; *) echo "ERROR: unsupported ABI $abi" >&2; return 1 ;; esac } androidcast_ndk_configure_host() { case "$1" in arm64-v8a) echo aarch64-linux-android ;; armeabi-v7a) echo armv7a-linux-androideabi ;; x86_64) echo x86_64-linux-android ;; x86) echo i686-linux-android ;; *) echo "$1" ;; esac } build_opus_for_abi() { local opus_src="$ROOT/third-party/opus" if [[ ! -f "$opus_src/CMakeLists.txt" ]]; then echo "Skip libopus: $opus_src missing (git submodule update --init third-party/opus)" return 0 fi local opus_build="$BUILD_DIR/opus-cmake" rm -rf "$opus_build" echo "Building libopus for $ABI (CMake) ..." cmake -DCMAKE_TOOLCHAIN_FILE="$NDK/build/cmake/android.toolchain.cmake" \ -DANDROID_ABI="$ABI" \ -DANDROID_PLATFORM="android-$(androidcast_ndk_api)" \ -DBUILD_SHARED_LIBS=OFF \ -DOPUS_BUILD_SHARED_LIBRARY=OFF \ -DOPUS_BUILD_TESTING=OFF \ -DOPUS_BUILD_PROGRAMS=OFF \ -DCMAKE_BUILD_TYPE=Release \ -B "$opus_build" -S "$opus_src" cmake --build "$opus_build" --target opus -j"$(nproc)" local opus_a="$opus_build/libopus.a" if [[ ! -f "$opus_a" ]]; then opus_a="$(find "$opus_build" -name 'libopus.a' | head -1)" fi if [[ ! -f "$opus_a" ]]; then echo "ERROR: libopus.a not found after Opus build" exit 1 fi cp -f "$opus_a" "$PROJECT_OUT/libopus.a" echo "Built: $PROJECT_OUT/libopus.a (size $(ls -lah "${PROJECT_OUT}/libopus.a" | awk '{print $5}'))" } build_speex_for_abi() { local speex_src_real="$ROOT/third-party/speex" if [[ ! -d "$speex_src_real" ]]; then echo "Skip libspeex: $speex_src_real missing (git submodule update --init third-party/speex)" return 0 fi local speex_src="$speex_src_real" local speex_build="$BUILD_DIR/speex-autotools" if path_has_space "$speex_src_real"; then speex_src="$STAGING/speex-src" ln -sfn "$speex_src_real" "$speex_src" fi if [[ ! -f "$speex_src/configure" ]]; then echo "Running speex autogen.sh ..." (cd "$speex_src" && ./autogen.sh) fi rm -rf "$speex_build" mkdir -p "$speex_build" local cc host cc="$(androidcast_ndk_clang "$ABI")" host="$(androidcast_ndk_configure_host "$ABI")" echo "Building libspeex for $ABI (autotools) ..." ( cd "$speex_build" "$speex_src/configure" \ --host="$host" \ --prefix="$speex_build/install" \ --disable-shared \ --enable-static \ --disable-binaries \ --with-pic \ CC="$cc" \ CFLAGS="-fPIC -O2" make -j"$(nproc)" ) local speex_a="$speex_build/libspeex/.libs/libspeex.a" if [[ ! -f "$speex_a" ]]; then speex_a="$(find "$speex_build" -name 'libspeex.a' | head -1)" fi if [[ ! -f "$speex_a" ]]; then echo "ERROR: libspeex.a not found after Speex build" exit 1 fi cp -f "$speex_a" "$PROJECT_OUT/libspeex.a" echo "Built: $PROJECT_OUT/libspeex.a (size $(ls -lah "${PROJECT_OUT}/libspeex.a" | awk '{print $5}'))" } build_opus_for_abi build_speex_for_abi echo "" echo "Built: $PROJECT_OUT/libvpx.a (size $(ls -lah "${PROJECT_OUT}/libvpx.a" | awk '{print $5}'))" if [[ -n "$STAGING" ]]; then echo "(staged under $STAGING because of spaces in repo path)" fi echo "Build other ABIs as needed, e.g.:" echo " ./scripts/build-native-codecs.sh armeabi-v7a" echo "Then rebuild the APK (CMake autolinks build/native//*.a per ABI):" echo " ./gradlew assembleDebug"