
作者:Igor Khomenko
译自:https://medium.com/@igorkhomenko/compiling-webrtc-static-binaries-for-android-in-2025-604481906541
今年夏天早些时候,我遇到一个任务,对于维护旧版 Android 代码的人来说可能并不陌生:
将 WebRTC 从M54(2016年版本)升级到M128(2024年版本)。
该项目是一个大型 Android 应用,包含大量 JNI 粘合代码——通过 NDK 实现的 C++ 与 Java 的交互。当初构建时,WebRTC 提供的 API 和头文件结构早已被弃用。随着时间推移,应用逐渐与极其陈旧的WebRTC版本(M54)绑定,升级几乎成为不可能的任务。
但维持如此过时的构建版本已不可持续。安全补丁、现代 Android 要求以及依赖冲突(涉及OpenSSL、libcurl等库)最终迫使项目必须完成升级。
这意味着需要解决如何将 WebRTC M128 编译为 Android 的静态二进制文件,改造构建系统(NDK + GN/Ninja),并将其与现有的 JNI 桥接器进行精密链接。
本文将详细分享我的具体操作流程,从搭建工具链到生成 .a 静态库,这些成果可安全地应用于2025年的Android项目中。
前提条件
请确保已安装:
- Android NDK(已测试r21e版本)
- Docker
1. 准备 docker-compose 文件
我们的要求是能够在 Linux 和 macOS 上构建 WebRTC。此时 Docker 就能派上用场。
先创建以下 docker-compose.yml 文件,该文件运行 Ubuntu 22 镜像,这是 Google 推荐的镜像:
version: "3.8"
services:
webrtc-builder:
platform: linux/amd64
image: ubuntu:22.04
container_name: webrtc-android-builder
tty: true
environment:
- DEBIAN_FRONTEND=noninteractive
volumes:
- ./:/workspace
- ${HOME}/Downloads/android-ndk-r21e:/ndk:ro
- webrtc-root-volume:/root/webrtc-android
working_dir: /workspace
command: /bin/bash
volumes:
webrtc-root-volume:
现在运行容器:
docker-compose run -d -- rm webrtc-builder
在容器内部运行以下命令以完成环境准备:
apt update && apt install -y \
git python3 curl sudo pkg-config unzip zip gnupg flex bison \
gperf build-essential libnss3-tools python3-distutils python3-venv \
openjdk-11-jdk ninja-build clang nodejs npm rsync file
# 为 depot_tools 设置 Git 身份
git config --global user.email "you@example.com"
git config --global user.name "Your Name"
2. 获取、配置和构建 WebRTC
让我们准备一个名为 build-webrtc-android.sh 的构建脚本:
#!/bin/bash
# https://getstream.io/resources/projects/webrtc/library/android/
# ./build-webrtc-android.sh
set -e
# === 配置 ===
WEBRTC_ROOT="$HOME/webrtc-android"
NDK_PATH="$HOME/Downloads/android-ndk-r21e"
BUILD_TYPE="release" # 或 "debug"
WEBRTC_TAG="refs/branch-heads/5864" # WebRTC m128
DEPOT_TOOLS="$WEBRTC_ROOT/depot_tools"
export ANDROID_NDK_ROOT="$NDK_PATH"
export ANDROID_NDK_HOME="$NDK_PATH"
ARCHS=("armeabi-v7a" "arm64-v8a" "x86")
declare -A GN_ARCH_MAP=( ["armeabi-v7a"]="arm" ["arm64-v8a"]="arm64" ["x86"]="x86" )
# === 克隆 depot tools ===
if [ ! -d "$DEPOT_TOOLS" ]; then
echo "📦 Cloning depot_tools..."
git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git "$DEPOT_TOOLS"
fi
export PATH="$DEPOT_TOOLS:$PATH"
# === 获取 WebRTC 源 ===
mkdir -p "$WEBRTC_ROOT"
cd "$WEBRTC_ROOT"
if [ ! -d "src" ]; then
echo "🌐 Fetching WebRTC (first time)..."
fetch --nohooks webrtc_android
fi
cd src
# === 检查特定版本(M128) ===
echo "🔁 Checking out WebRTC m128..."
git fetch origin ${WEBRTC_TAG}
git checkout FETCH_HEAD
gclient sync
# If getting 'You have uncommitted changes.' error:
#
# cd $WEBRTC_ROOT/src/third_party
# git reset --hard HEAD
# git clean -fdx
echo "🔁 gclient sync done"
# disable THIN archives
# 1. open /root/webrtc-android/src/build/config/compiler/BUILD.gn and in "thin_archive" to comment "arflags = [ "-T" ]"":
sed -i.bak '/^config("thin_archive") {/,/^}/s/^\([[:space:]]*\)arflags = \[ "-T" \]/\1# arflags = [ "-T" ]/' build/config/compiler/BUILD.gn
# OPTIONAL (just to make it clear to future readers):
# 2. open /root/webrtc-android/src/build/config/BUILDCONFIG.gn and remove the line:
# "//build/config/compiler:thin_archive",
# === 循环遍历架构 ===
for ARCH in "${ARCHS[@]}"; do
GN_ARCH="${GN_ARCH_MAP[$ARCH]}"
OUT_DIR="out_android_${ARCH}"
echo "⚙️ Generating GN config for ${ARCH}..."
IS_DEBUG=false
if [ "${BUILD_TYPE,,}" == "debug" ]; then
IS_DEBUG=true
fi
# 为什么需要 use_custom_libcxx=false
# https://stackoverflow.com/questions/77872602/build-webrtc-on-linux-using-host-libc
gn gen "$OUT_DIR" --args="
target_os=\"android\"
target_cpu=\"$GN_ARCH\"
is_debug=$IS_DEBUG
use_custom_libcxx=false
rtc_include_tests=false
use_rtti=true
"
echo "🔨 Building WebRTC for $ARCH..."
ninja -C "$OUT_DIR" webrtc
echo "✅ Done building for $ARCH: output in $OUT_DIR"
done
# === 最终输出信息 ===
echo "🎉 All builds complete!"
for ARCH in "${ARCHS[@]}"; do
echo "📁 lib for $ARCH -> $WEBRTC_ROOT/src/out_android_${ARCH}/obj"
done
echo "📁 includes -> $WEBRTC_ROOT/src"
该脚本执行以下操作:
- 获取 WebRTC 源代码
- 切换到 m128 分支
- 为 3 种架构配置和构建 WebRTC:armeabi-v7a、arm64-v8a和x86
该脚本确保您的 Android NDK 位于$HOME/Downloads/android-ndk-r21e文件夹中,因此请确保根据您的实际路径更新此行。
现在我们可以运行这个脚本:
chmod +x build-webrtc-android.sh # 构建脚本可执行
./build-webrtc-android.sh
构建过程很可能会失败,并出现以下错误,这没关系。只需继续下一步(步骤 3):
[6143/6511] LINK ./stun_prober
FAILED: stun_prober exe.unstripped/stun_prober
"vpython3" "../../build/toolchain/gcc_link_wrapper.py" --output="./stun_prober" --strip="../../third_party/llvm-build/Release+Asserts/bin/llvm-strip" --unstripped-file="./exe.unstripped/stun_prober" -- ../../third_party/llvm-build/Release+Asserts/bin/clang++ -Werror -fuse-ld=lld -Wl,--fatal-warnings -Wl,--build-id -fPIC -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now -Wl,--icf=all -Wl,--color-diagnostics -Wl,--no-rosegment -Wl,--undefined-version -Wl,--no-call-graph-profile-sort -Wl,--exclude-libs=libvpx_assembly_arm.a -Wl,-z,max-page-size=4096 --target=arm-linux-androideabi21 -no-canonical-prefixes -Wl,--warn-shared-textrel -Wl,--gc-sections -Wl,-z,defs -Wl,--as-needed --unwindlib=none --sysroot=../../third_party/android_toolchain/ndk/toolchains/llvm/prebuilt/linux-x86_64/sysroot -pie -Bdynamic -Wl,-z,nocopyreloc -o "./exe.unstripped/stun_prober" -Wl,--start-group @"./stun_prober.rsp" -Wl,--end-group -ldl -lm -llog -lGLESv2
ld.lld: error: undefined symbol: _Unwind_Backtrace
>>> referenced by stacktrace.cc:260 (../../sdk/android/native_api/stacktrace/stacktrace.cc:260)
>>> native_api_stacktrace/stacktrace.o:(webrtc::GetStackTrace()) in archive obj/sdk/android/libnative_api_stacktrace.a
ld.lld: error: undefined symbol: _Unwind_VRS_Get
>>> referenced by unwind.h:219 (../../third_party/llvm-build/Release+Asserts/lib/clang/19/include/unwind.h:219)
>>> native_api_stacktrace/stacktrace.o:(webrtc::(anonymous namespace)::SignalHandlerOutputState::UnwindBacktrace(_Unwind_Context*, void*)) in archive obj/sdk/android/libnative_api_stacktrace.a
clang++: error: linker command failed with exit code 1 (use -v to see invocation)
...
ninja: build stopped: subcommand failed.
3. 修补 WebRTC
默认情况下,Google 使用自定义工具链构建 libwebrtc。
libwebrtc的依赖项实际上包含了一个定制版的 LLVM 编译器工具链。当你用它构建代码时,所有内容都会生成在基于 Chromium 的专用“CR_”命名空间中,因此你的 STL 对象均不匹配。你的代码 STL依赖于 Android NDK 提供的标准常规命名空间符号,但 libwebrtc.a 却需要一个完全不同的命名空间。
(致非C++背景的开发人员:STL包含基础开发必备的核心类库,如std::string。若STL库不匹配,您的项目将陷入困境。)
因此,你有两种解决方案:
- 使用定制版 libwebrtc 工具链构建代码
- 采用标准 Android NDK 工具链构建其代码
经过多次构建和试验后,似乎唯一可行的路径是路径 2。
我们已经在步骤 2 中将 use_custom_libcxx=false 添加到构建脚本中,这导致了上面提到的构建错误。要修复此问题,我们需要修补 WebRTC 源代码。
对此有一个解决方案:https://issues.webrtc.org/issues/42223745#comment9,因此我们只需总结所需的修改:
1. 修改 src/buildtools/third_party/libunwind/BUILD.gn 文件,添加“visibility”:
source_set(“libunwind”){
visibility = [ “//build/config:common_deps” ]
…
}
2. 更改 src/build/config/BUILD.gn(添加“ else ”语句):
if (use_custom_libcxx) {
public_deps += [ "//buildtools/third_party/libc++" ]
} else {
public_deps += [ "//buildtools/third_party/libunwind" ]
}
现在可以重新运行上述构建脚本,它最终应该会以一条成功消息结束:
./build-webrtc-android.sh
4. 收集构建的工件和标头
准备一个名为package-webrtc-sdk.sh的构建脚本,它将收集所有二进制文件(*.a)和标题并将其全部放在一个特殊的文件夹中:
#!/bin/bash
set -e
WEBRTC_ROOT="$HOME/webrtc-android/src"
OUTPUT_DIR="$WEBRTC_ROOT/webrtc-sdk"
# 支持的 archs 及其 GN 输出目录
declare -A BUILD_MAP=(
["armeabi-v7a"]="out_android_armeabi-v7a"
["arm64-v8a"]="out_android_arm64-v8a"
["x86"]="out_android_x86"
)
ARCHS=("armeabi-v7a" "arm64-v8a" "x86")
echo "📆 Packaging WebRTC static libs and headers..."
rm -rf "$OUTPUT_DIR/include"
mkdir -p "$OUTPUT_DIR/include"
# 复制标头 (only .h files recursively)
echo "📂 Copying public headers..."
rsync -av --include='*/' --include='*.h' --exclude='*' \
"$WEBRTC_ROOT/api" \
"$WEBRTC_ROOT/audio" \
"$WEBRTC_ROOT/base" \
"$WEBRTC_ROOT/call" \
"$WEBRTC_ROOT/common_audio" \
"$WEBRTC_ROOT/common_video" \
"$WEBRTC_ROOT/logging" \
"$WEBRTC_ROOT/media" \
"$WEBRTC_ROOT/modules" \
"$WEBRTC_ROOT/net" \
"$WEBRTC_ROOT/p2p" \
"$WEBRTC_ROOT/pc" \
"$WEBRTC_ROOT/rtc_base" \
"$WEBRTC_ROOT/rtc_tools" \
"$WEBRTC_ROOT/sdk" \
"$WEBRTC_ROOT/stats" \
"$WEBRTC_ROOT/system_wrappers" \
"$WEBRTC_ROOT/third_party/abseil-cpp/absl" \
"$WEBRTC_ROOT/tools" \
"$WEBRTC_ROOT/video" \
"$OUTPUT_DIR/include/"
# 复制并修复静态库
for ARCH in "${ARCHS[@]}"; do
BUILD_DIR="${BUILD_MAP[$ARCH]}"
DEST_LIB="$OUTPUT_DIR/lib/$ARCH"
mkdir -p "$DEST_LIB"
echo "📁 Processing $ARCH → $BUILD_DIR"
find "$WEBRTC_ROOT/$BUILD_DIR/obj" -name "*.a" | while read -r LIB; do
REL_PATH="${LIB#$WEBRTC_ROOT/$BUILD_DIR/obj/}"
OUT="$DEST_LIB/$(basename "$REL_PATH")"
echo "📆 Copying $(basename "$REL_PATH")"
cp "$LIB" "$OUT"
done
done
echo "✅ Done! SDK output in: $OUTPUT_DIR"
# check if it's thin archive or not
for ARCH in "${ARCHS[@]}"; do
DEST_LIB="$OUTPUT_DIR/lib/$ARCH"
file $DEST_LIB/*
done
#./package-webrtc-sdk.sh
运行:
chmod + x package -webrtc-sdk.sh #使构建脚本可执行
./package -webrtc-sdk.sh
5. 复制 artifacts
最终脚本 copy-webrtc-artifacts.sh 将把二进制文件和标头从容器内部复制到应用程序所需的位置:
ARTIFACTS_DIR="$HOME/webrtc-android/src/webrtc-sdk"
# headers
cp -r $ARTIFACTS_DIR/include/* $(pwd)/YourApp/ThirdParty/Common/WebRTC/webrtc
# binaries (armeabi-v7a)
cp -r $ARTIFACTS_DIR/lib/armeabi-v7a/libwebrtc.a $(pwd)/YourApp/ThirdParty/lib/android/arm/WebRTC/Debug
cp -r $ARTIFACTS_DIR/lib/armeabi-v7a/libwebrtc.a $(pwd)/YourApp/ThirdParty/lib/android/arm/WebRTC/Release
# binaries (x86)
cp -r $ARTIFACTS_DIR/lib/x86/libwebrtc.a $(pwd)/YourApp/ThirdParty/lib/android/x86/WebRTC/Debug
cp -r $ARTIFACTS_DIR/lib/x86/libwebrtc.a $(pwd)/YourApp/ThirdParty/lib/android/x86/WebRTC/Release
# binaries (arm64-v8a)
cp -r $ARTIFACTS_DIR/lib/arm64-v8a/libwebrtc.a $(pwd)/YourApp/ThirdParty/lib/android/arm64/WebRTC/Debug
cp -r $ARTIFACTS_DIR/lib/arm64-v8a/libwebrtc.a $(pwd)/YourApp/ThirdParty/lib/android/arm64/WebRTC/Release
echo "✅ Done!"
运行:
chmod +x copy-webrtc-artifacts.sh
./copy-webrtc-artifacts.sh
SSL 注意事项:BoringSSL 与 OpenSSL
更新 WebRTC 过程中最具挑战性的环节之一便是处理 SSL 库。
WebRTC 不使用 OpenSSL,相反,它使用BoringSSL (Google 的 OpenSSL 分支)进行构建。BoringSSL 是根据 Google 的需求进行精简的,并非旨在用作通用加密库,这意味着它与 OpenSSL 的 API 不兼容。
这在实践中意味着什么
- 如果你的应用程序仅使用 WebRTC 的网络和安全功能 → 则无需担心,WebRTC 将自带 BoringSSL。
- 如果你的 JNI/C++ 代码(或其他本机库,如 libcurl)也依赖于BoringSSL,则一切都将干净地链接。
- 如果你的项目需要用OpenSSL来做其他事情,那你就麻烦了。你不能把两者直接放在同一个二进制文件中,否则就会产生冲突。
为什么混合使用 BoringSSL 和 OpenSSL 并非易事
这两个库导出的符号存在重叠(如SSL_、EVP_、BIO_*),且因其设计初衷并非共存,因此会引发链接器冲突,甚至导致运行时崩溃。
处理此问题的几种方案:
- 采用BoringSSL:若你掌控项目及依赖项,这是最简洁的解决方案。许多现代项目已迁移至BoringSSL。
- 隔离OpenSSL:若必须使用 OpenSSL,需采取以下任一措施:
- 将 OpenSSL 编译为共享库(.so)并隐藏符号。
- 对 OpenSSL 符号进行命名空间/前缀处理(可通过–with-rand-seed技巧、符号前缀化或objcopy –redefine-sym等工具实现)。
- 修补依赖项:例如将 libcurl 的编译目标从 OpenSSL 切换为 BoringSSL。
推荐
如果可能的话,请将 BoringSSL 应用于整个项目。除非你有非常充分的理由(例如硬件加速加密或 FIPS 认证的版本),否则在 2025 年尝试同时维护 OpenSSL 和 WebRTC 的 BoringSSL 将是一场噩梦。
常见陷阱和总结
在旧的基于 JNI 的 Android 项目中,从 WebRTC M54迁移到M128绝非易事。API 发生了变化,构建系统也发生了演变,依赖堆栈也变得更加复杂。
但一旦编写了脚本,该过程就可以重复,结果是现代 WebRTC 静态库可以顺利集成到传统应用程序中。
以下是我在这次旅程中遇到的一些常见陷阱:
- NDK 不匹配:WebRTC 的构建脚本通常落后于最新的 NDK。
r21e对于交叉编译来说仍然是最稳定的。 - libc++ 冲突:Chromium 的自定义 libc++ 可能会导致与其他库的链接中断。请使用
use_custom_libcxx=false。
关键调整涉及静态链接、NDK 兼容性及 libc++ 冲突问题。完成这些步骤后,你将获得可直接部署到任何 Android 原生项目的.a文件,无需再为跨 ABI 追踪 .so 依赖关系而烦恼。
版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至1393616908@qq.com 举报,一经查实,本站将立刻删除。