Initial commit
This commit is contained in:
Executable
+336
@@ -0,0 +1,336 @@
|
||||
#!/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
|
||||
Reference in New Issue
Block a user