Files
sm-emby-share/scripts/build_media3_ffmpeg.sh
T
2026-07-14 11:11:36 +08:00

337 lines
10 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
set -euo pipefail
MEDIA3_TAG='1.10.1'
FFMPEG_BRANCH='release/6.0'
NDK_PATH="${ANDROID_NDK_HOME:-}"
HOST_PLATFORM=''
CLEAN=0
ANDROID_ABI='21'
ENABLED_DECODERS=(dca ac3 eac3 truehd opus vorbis flac)
usage() {
cat <<'USAGE'
Usage: scripts/build_media3_ffmpeg.sh [options]
Options:
--clean 编前先清空工作目录
--media3-tag <tag> androidx/media tag (default: 1.10.1)
--ffmpeg-branch <branch> FFmpeg branch (default: release/6.0)
--ndk-path <path> Android NDK 路径(推荐 r27d LTS;默认 $ANDROID_NDK_HOME
--host-platform <name> darwin-x86_64 / linux-x86_64
(省略时根据 uname 推断;Apple Silicon 强烈建议
通过 Docker linux-x86_64 容器执行)
-h, --help 显示本帮助
USAGE
}
while [[ $# -gt 0 ]]; do
case "$1" in
--clean) CLEAN=1; shift ;;
--media3-tag) MEDIA3_TAG="$2"; shift 2 ;;
--ffmpeg-branch) FFMPEG_BRANCH="$2"; shift 2 ;;
--ndk-path) NDK_PATH="$2"; shift 2 ;;
--host-platform) HOST_PLATFORM="$2"; shift 2 ;;
-h|--help) usage; exit 0 ;;
*) echo "unknown option: $1" >&2; usage; exit 2 ;;
esac
done
require_cmd() {
if ! command -v "$1" >/dev/null 2>&1; then
echo "ERROR: $1 not found in PATH" >&2
exit 1
fi
}
require_cmd git
require_cmd shasum
if [[ -z "${NDK_PATH}" ]]; then
echo 'ERROR: NDK path not specified. Use --ndk-path or set ANDROID_NDK_HOME.' >&2
echo 'Pin to NDK r27d LTS (27.3.13750724) — ABI-compatible with Media3 1.10.1 build_ffmpeg.sh.' >&2
exit 1
fi
if [[ ! -d "${NDK_PATH}" ]]; then
echo "ERROR: NDK path not a directory: ${NDK_PATH}" >&2
exit 1
fi
SDK_PATH="${ANDROID_HOME:-${ANDROID_SDK_ROOT:-}}"
if [[ -z "${SDK_PATH}" ]]; then
for candidate in \
"${HOME}/Library/Android/sdk" \
"${HOME}/Android/Sdk" \
"/opt/android-sdk" \
"/usr/local/share/android-sdk"; do
if [[ -d "${candidate}/platforms" ]]; then
SDK_PATH="${candidate}"
echo "[detect] Android SDK at ${SDK_PATH}"
break
fi
done
fi
if [[ -z "${SDK_PATH}" || ! -d "${SDK_PATH}/platforms" ]]; then
echo 'ERROR: Android SDK not found. Set ANDROID_HOME or install Android Studio SDK.' >&2
echo ' Required: $ANDROID_HOME/platforms (android-34+) + build-tools.' >&2
exit 1
fi
if [[ -z "${HOST_PLATFORM}" ]]; then
uname_s="$(uname -s)"
uname_m="$(uname -m)"
case "${uname_s}-${uname_m}" in
Darwin-x86_64) HOST_PLATFORM='darwin-x86_64' ;;
Darwin-arm64)
HOST_PLATFORM='darwin-x86_64'
echo 'WARN: host is Apple Silicon; Media3 README only documents darwin-x86_64.' >&2
echo ' Build will proceed under Rosetta. For reproducibility prefer a' >&2
echo ' Docker linux-x86_64 container (see plugins/media3_engine/README.md).' >&2
;;
Linux-x86_64) HOST_PLATFORM='linux-x86_64' ;;
*) echo "ERROR: unsupported host ${uname_s}-${uname_m}. Pass --host-platform." >&2; exit 1 ;;
esac
fi
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
WORK_ROOT="${REPO_ROOT}/.media3-build"
MEDIA_REPO="${WORK_ROOT}/media"
FFMPEG_MODULE_PATH="${MEDIA_REPO}/libraries/decoder_ffmpeg/src/main"
FFMPEG_DIR="${FFMPEG_MODULE_PATH}/jni/ffmpeg"
OUT_LIBS_DIR="${REPO_ROOT}/android/app/libs"
AAR_NAME="media3-decoder-ffmpeg-${MEDIA3_TAG}-arm64.aar"
OUT_AAR="${OUT_LIBS_DIR}/${AAR_NAME}"
PATCH_BACKUP_DIR=''
PATCHED_FILES=()
backup_patch_file() {
local file="$1"
if [[ -z "${PATCH_BACKUP_DIR}" ]]; then
PATCH_BACKUP_DIR="$(mktemp -d)"
fi
local backup="${PATCH_BACKUP_DIR}/${#PATCHED_FILES[@]}"
cp "${file}" "${backup}"
PATCHED_FILES+=("${file}:${backup}")
}
restore_patched_files() {
local entry file backup
for entry in "${PATCHED_FILES[@]}"; do
file="${entry%%:*}"
backup="${entry#*:}"
if [[ -f "${backup}" ]]; then
cp "${backup}" "${file}"
fi
done
if [[ -n "${PATCH_BACKUP_DIR}" ]]; then
rm -rf "${PATCH_BACKUP_DIR}"
fi
}
trap restore_patched_files EXIT
if [[ ${CLEAN} -eq 1 && -d "${WORK_ROOT}" ]]; then
echo "[clean] removing ${WORK_ROOT}"
rm -rf "${WORK_ROOT}"
fi
mkdir -p "${WORK_ROOT}" "${OUT_LIBS_DIR}"
if [[ ! -d "${MEDIA_REPO}/.git" ]]; then
echo "[clone] androidx/media @ ${MEDIA3_TAG}"
git clone --depth 1 --branch "${MEDIA3_TAG}" \
https://github.com/androidx/media.git "${MEDIA_REPO}"
else
echo "[skip] ${MEDIA_REPO} already exists; pass --clean to refetch"
fi
if [[ ! -d "${FFMPEG_DIR}/.git" ]]; then
echo "[clone] FFmpeg ${FFMPEG_BRANCH}"
mkdir -p "$(dirname "${FFMPEG_DIR}")"
git clone --depth 1 --branch "${FFMPEG_BRANCH}" \
https://git.ffmpeg.org/ffmpeg.git "${FFMPEG_DIR}"
else
echo "[skip] ${FFMPEG_DIR} already exists"
fi
JNI_BUILD_SH="${FFMPEG_MODULE_PATH}/jni/build_ffmpeg.sh"
if [[ ! -f "${JNI_BUILD_SH}" ]]; then
echo "ERROR: ${JNI_BUILD_SH} not found (androidx/media tag ${MEDIA3_TAG} 内未包含)" >&2
exit 1
fi
if ! grep -q '# SM-PATCH arm64-only' "${JNI_BUILD_SH}"; then
echo "[patch] forcing arm64-v8a only in ${JNI_BUILD_SH##*/}"
backup_patch_file "${JNI_BUILD_SH}"
python3 - "${JNI_BUILD_SH}" <<'PY' || { echo 'ERROR: failed to patch build_ffmpeg.sh (upstream layout changed?)' >&2; exit 1; }
import re, sys
p = sys.argv[1]
src = open(p, 'r', encoding='utf-8').read()
if '# SM-PATCH arm64-only' in src:
sys.exit(0)
KEEP = 'arm64-v8a'
block_re = re.compile(
r'\./configure\s*\\\s*\n'
r'(?:.*\n)*?'
r'\s*--libdir=android-libs/([\w-]+)\s*\\\s*\n'
r'(?:.*\n)*?'
r'make clean\s*\n',
re.M,
)
removed = []
chunks = []
last = 0
for m in block_re.finditer(src):
abi = m.group(1)
if abi == KEEP:
continue
chunks.append(src[last:m.start()])
last = m.end()
removed.append(abi)
chunks.append(src[last:])
if not removed:
sys.stderr.write('ERROR: no non-arm64 configure blocks found; upstream layout may have changed.\n')
sys.exit(2)
result = ''.join(chunks).replace(
'set -eu\n',
f'set -eu\n# SM-PATCH arm64-only (removed: {",".join(removed)})\n',
1,
)
open(p, 'w', encoding='utf-8').write(result)
sys.stderr.write(f'[ok] removed ABI blocks: {",".join(removed)}\n')
PY
fi
echo "[build] FFmpeg native libs (decoders: ${ENABLED_DECODERS[*]})"
pushd "${FFMPEG_MODULE_PATH}/jni" >/dev/null
NDK_PATH="${NDK_PATH}" \
HOST_PLATFORM="${HOST_PLATFORM}" \
ANDROID_ABI="${ANDROID_ABI}" \
./build_ffmpeg.sh \
"${FFMPEG_MODULE_PATH}" \
"${NDK_PATH}" \
"${HOST_PLATFORM}" \
"${ANDROID_ABI}" \
"${ENABLED_DECODERS[@]}"
popd >/dev/null
echo '[build] Gradle :libraries:decoder_ffmpeg:assembleRelease'
cat >"${MEDIA_REPO}/local.properties" <<EOF
sdk.dir=${SDK_PATH}
ndk.dir=${NDK_PATH}
EOF
NDK_VERSION="$(awk -F'= *' '/^Pkg\.Revision/{print $2}' "${NDK_PATH}/source.properties" | tr -d '\r')"
if [[ -z "${NDK_VERSION}" ]]; then
echo "ERROR: cannot read Pkg.Revision from ${NDK_PATH}/source.properties" >&2
exit 1
fi
COMMON_CONFIG="${MEDIA_REPO}/common_config.gradle"
if ! grep -q '// SM-PATCH ndkVersion' "${COMMON_CONFIG}"; then
echo "[patch] injecting ndkVersion=${NDK_VERSION} into common_config.gradle"
backup_patch_file "${COMMON_CONFIG}"
python3 - "${COMMON_CONFIG}" "${NDK_VERSION}" <<'PY' || { echo 'ERROR: failed to inject ndkVersion' >&2; exit 1; }
import re, sys
p, version = sys.argv[1], sys.argv[2]
src = open(p, 'r', encoding='utf-8').read()
patched, n = re.subn(
r'^android\s*\{\s*\n',
f'android {{\n ndkVersion = "{version}" // SM-PATCH ndkVersion\n',
src, count=1, flags=re.M,
)
if n != 1:
sys.stderr.write('ERROR: failed to find `android {` in common_config.gradle\n')
sys.exit(2)
open(p, 'w', encoding='utf-8').write(patched)
PY
fi
DECODER_BUILD_GRADLE="${MEDIA_REPO}/libraries/decoder_ffmpeg/build.gradle"
if ! grep -q '// SM-PATCH abiFilters' "${DECODER_BUILD_GRADLE}"; then
echo "[patch] restricting decoder_ffmpeg abiFilters to arm64-v8a"
backup_patch_file "${DECODER_BUILD_GRADLE}"
python3 - "${DECODER_BUILD_GRADLE}" <<'PY' || { echo 'ERROR: failed to inject abiFilters' >&2; exit 1; }
import re, sys
p = sys.argv[1]
src = open(p, 'r', encoding='utf-8').read()
inject = (
"android {\n"
" defaultConfig {\n"
" ndk {\n"
" abiFilters 'arm64-v8a' // SM-PATCH abiFilters\n"
" }\n"
" }\n"
"}\n\n"
)
patched, n = re.subn(
r'(apply from: "\$gradle\.ext\.androidxMediaSettingsDir/common_config\.gradle"\n)',
r'\1\n' + inject,
src, count=1,
)
if n != 1:
sys.stderr.write('ERROR: failed to find common_config apply line in decoder_ffmpeg/build.gradle\n')
sys.exit(2)
open(p, 'w', encoding='utf-8').write(patched)
PY
fi
pushd "${MEDIA_REPO}" >/dev/null
ANDROID_HOME="${SDK_PATH}" ANDROID_SDK_ROOT="${SDK_PATH}" \
./gradlew --no-daemon :lib-decoder-ffmpeg:assembleRelease
popd >/dev/null
GRADLE_AAR=''
for pattern in \
"${MEDIA_REPO}/libraries/decoder_ffmpeg/buildout/outputs/aar/lib-decoder-ffmpeg-release.aar" \
"${MEDIA_REPO}/libraries/decoder_ffmpeg/build/outputs/aar/lib-decoder-ffmpeg-release.aar" \
"${MEDIA_REPO}/libraries/decoder_ffmpeg/buildout/outputs/aar/decoder_ffmpeg-release.aar" \
"${MEDIA_REPO}/libraries/decoder_ffmpeg/build/outputs/aar/decoder_ffmpeg-release.aar"; do
if [[ -f "${pattern}" ]]; then
GRADLE_AAR="${pattern}"; break
fi
done
if [[ -z "${GRADLE_AAR}" ]]; then
echo 'ERROR: AAR not found under decoder_ffmpeg/{buildout,build}/outputs/aar/. 检查 Gradle 输出' >&2
exit 1
fi
cp "${GRADLE_AAR}" "${OUT_AAR}"
SHA256="$(shasum -a 256 "${OUT_AAR}" | awk '{print $1}')"
CHECKSUMS="${OUT_LIBS_DIR}/CHECKSUMS.txt"
touch "${CHECKSUMS}"
python3 - "${CHECKSUMS}" "${AAR_NAME}" "${SHA256}" <<'PY'
import sys, re
p, name, sha = sys.argv[1], sys.argv[2], sys.argv[3]
with open(p, 'r', encoding='utf-8') as f:
lines = [ln for ln in f.read().splitlines() if not ln.endswith(name)]
lines.append(f'{sha} {name}')
with open(p, 'w', encoding='utf-8') as f:
f.write('\n'.join(lines) + '\n')
PY
cat <<INFO
[done] media3-decoder-ffmpeg AAR build complete.
output : ${OUT_AAR}
size : $(wc -c <"${OUT_AAR}") bytes
sha256 : ${SHA256}
tag : ${MEDIA3_TAG}
ffmpeg : ${FFMPEG_BRANCH}
ndk : ${NDK_PATH}
下一步:
1. 核对 SHA-256 是否与团队约定一致
2. git add ${OUT_AAR##*/Desktop/cc-env/player/sm-emby/} android/app/libs/CHECKSUMS.txt
3. cd ${REPO_ROOT} && flutter build apk --debug 验证 AAR 加载
INFO