Ubuntu 虚拟机编译 WebRTC Android AAR(M140 / branch-heads/7339)

最终结果:编译成功。 libwebrtc_m140.aar(19.7MB),包含 armeabi-v7a + arm64-v8a 两个架构的 libjingle_peerconnection_so.so,已经拿回 Mac,存在 /Users/dev/Documents/AV/webRtc/libwebrtc_m140.aar

这份记录里有两次踩坑导致的返工(ARM64 虚拟机走不通、CPU 核数配置问题),都记下来了, 免得以后重搭环境再踩一遍。

背景

Mac 编译不了 Android 版 WebRTC------build/config/BUILDCONFIG.gn 里有硬性断言:

gn 复制代码
if (target_os == "android") {
  # Targeting android on Mac is best-effort and not guaranteed to work.
  assert(host_os == "linux", "Android builds are only supported on Linux.")
}

官方就是只支持在 Linux 上编译 Android 版本,所以用 UTM 在这台 M4 Mac 上开虚拟机来编。

目标版本 :M140,对应 WebRTC 的 branch-heads/7339 分支(不是 main 上的最新代码)。

目标架构 :armeabi-v7a + arm64-v8a(没有编 x86/x86_64)。

一、大坑:ARM64 虚拟机走不通,必须用 x86_64

一开始图省事,直接在已有的 ARM64 (跟 M4 Mac 同架构,有硬件虚拟化加速,更快)虚拟机上装 Android 编译环境,gclient sync 时卡在:

bash 复制代码
failed to resolve chromium/third_party/android_toolchain/android_toolchain/linux-arm64
  (line 99): no such package: chromium/third_party/android_toolchain/android_toolchain/linux-arm64

cipd instances chromium/third_party/android_toolchain/android_toolchain/linux-arm64 直接查证:这个 CIPD 包从来没有发布过 linux-arm64 版本 (对比 linux-amd64 有正常的 历史版本记录)。也就是说 Android 编译需要的 NDK 工具链,官方压根没编译发布 ARM64 Linux 主机版, 不管怎么配置都不可能在 ARM64 Linux 上跑通,不是设置问题,是上游没有这个架构的产物。

结论:必须用 x86_64 的 Linux 虚拟机,即便这意味着在 Apple Silicon 上要用 QEMU 纯软件模拟 (没有硬件加速,会比 ARM64 虚拟机慢),没有别的选择(除非用真正的 x86_64 物理机/云主机)。

二、UTM 里建 x86_64 虚拟机

关键点:第一步必须选 "Emulate",不能选 "Virtualize"。 UTM 的 Virtualize 模式底层是苹果的 Virtualization.framework,在 Apple Silicon 上只能虚拟化 ARM64,选它的话架构选项根本不会 出现 x86_64。只有 Emulate(纯 QEMU 软件模拟)才能选任意架构。

建虚拟机步骤:

  1. UTM 主窗口 +Emulate(不是 Virtualize)
  2. Architecture 选 x86_64
  3. System/Machine 选 "Intel ICH9 based PC (2009, x86_64)"
  4. 内存给了 16GB,Storage 直接给了 120GB(一步到位,不用后面再扩容)
  5. Boot Image 选下载好的 Ubuntu Server ISO(见下一节)
  6. 名字:Linux-2(跟之前的 ARM64 虚拟机 Linux-one 区分开,避免搞混)

下载 Ubuntu Server ISO

官方镜像,校验过 SHA256:

bash 复制代码
curl -L "https://releases.ubuntu.com/24.04/ubuntu-24.04.4-live-server-amd64.iso" \
  -o ~/Downloads/ubuntu-24.04.4-live-server-amd64.iso

Server 版(没有桌面,纯命令行)------这台机器唯一用途是编译,不需要图形界面,更轻量。

装系统时踩的坑

  • 分区默认只给根分区分一半空间 (LVM,ubuntu-lv 只占 PFree 的一半,另一半留白不分配), 跟磁盘大小无关,是 Ubuntu 安装器的默认行为,装完后需要手动 lvextend(见下文命令)。
  • 装完重启后又跑回安装程序 :因为虚拟光驱里的 ISO 没弹出,虚拟机启动顺序里光驱排在硬盘前面, 一直循环进安装界面。装到出现 "Installation complete!" + "Reboot Now" 提示时, 先在 UTM 里把虚拟光驱的 ISO 弹出(Eject),再点 Reboot Now,不要顺序颠倒。
  • 弹出 ISO 之后,安装程序会有一句 [FAILED] Failed unmounting cdrom.mount + Please remove the installation medium, then press ENTER------这是正常提示,不是报错,直接按 ENTER 继续。

三、装 SSH,后续全部远程操作

Ubuntu Server 是纯命令行,而且没有桌面剪贴板同步,在 UTM 控制台窗口里粘贴长命令基本用不了。 装好 SSH 之后改用 Mac 终端连接,复制粘贴、多任务都正常:

bash 复制代码
sudo apt update
sudo apt install -y openssh-server
sudo systemctl enable --now ssh
ip addr show | grep "inet "   # 找到虚拟机 IP,记下来

免密登录(把 Mac 的公钥塞进虚拟机的 authorized_keys):

bash 复制代码
mkdir -p ~/.ssh && chmod 700 ~/.ssh
echo "<Mac 的 ssh 公钥内容>" >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys

免密 sudo(不然每次远程跑 sudo 命令都要交互输密码,脚本化操作跑不通):

bash 复制代码
echo "dev ALL=(ALL) NOPASSWD:ALL" | sudo tee /etc/sudoers.d/dev-nopasswd
sudo chmod 440 /etc/sudoers.d/dev-nopasswd

之后全部通过 ssh dev@<虚拟机IP> "命令" 远程操作,不用再碰 UTM 窗口。

共享目录这次没用上 :虚拟机建的时候配了 Shared Directory 指向 Mac 的 ~/Documents, 但 Ubuntu Server 版默认不会自动挂载 VirtioFS 共享目录 (桌面版会),编译过程中源码目录 建在了 ~/Documents/webrtc_android,其实只是虚拟机本地磁盘上一个同名目录,跟 Mac 完全没关系 ------编完之后是用 scp.aar 文件拷回 Mac 的,不是靠共享目录。如果想真正用上共享目录, 需要额外手动挂载 virtiofs,这次图省事没折腾,直接 scp 更快。

四、磁盘扩容(LVM)

bash 复制代码
lsblk
sudo vgs
sudo lvs

# VG 里有空闲空间(PFree 不是 0)时,直接分给根逻辑卷
sudo lvextend -l +100%FREE /dev/ubuntu-vg/ubuntu-lv
sudo resize2fs /dev/mapper/ubuntu--vg-ubuntu--lv
df -h

结果:根分区从默认的 58G 扩到了 115G(约 103G 可用)

五、Ubuntu 基础环境 + depot_tools

bash 复制代码
sudo apt update
sudo apt install -y git python3 python3-pip curl lsb-release

cd ~
git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
echo 'export PATH="$HOME/depot_tools:$PATH"' >> ~/.bashrc
export PATH="$HOME/depot_tools:$PATH"

gclient --version   # 第一次跑会自动引导下载 ninja/gn/cipd 的 Linux 原生版本,静默等一会儿

⚠️ 不能把 Mac 上的 depot_tools 直接复制过来用 ------里面 ninja/gn/cipd 是编译好的 二进制,Mac 版是 Mach-O 格式,Linux 里执行会报 Exec format error。必须在 Ubuntu 里 重新 git clone,让引导脚本自己下载 Linux 原生版本。
⚠️ 改了 ~/.bashrc 不会立刻生效 :已经打开的终端/SSH 会话不会自动重新加载,需要 source ~/.bashrc 或者开一个新会话才能用 gclient 命令。

六、拉代码 + 切到 M140(branch-heads/7339)分支

bash 复制代码
mkdir -p ~/Documents/webrtc_android   # 实际路径,跟共享目录同名但没关系(见上文说明)
cd ~/Documents/webrtc_android

fetch --nohooks webrtc_android
cd src

git fetch origin +refs/branch-heads/7339:refs/remotes/branch-heads/7339
git checkout -b m140 refs/remotes/branch-heads/7339

# 验证切对了分支(提交信息里应该带 [M140] 字样)
git log -1 --format='%H %s'

# 按这个分支的 DEPS 重新同步依赖
cd ..
gclient sync -D

⚠️ fetch 报错 "already contain a checkout" :如果之前跑过一次 fetch 但没跑完就 中断了,会留下一个 .gclient 配置文件但没有实际代码,这时候 fetch 会拒绝执行(它只用于 全新 checkout)。检查 .gclient 是否已存在,如果存在就直接跳过 fetch,运行 gclient sync 代替(会读现有配置去真正拉代码)。

⚠️ 别手滑同时跑两个 gclient sync :并发操作同一个目录会导致其中一个进程创建的 src/.git 是残缺状态(git rev-parse HEADfatal: Needed a single revision), 表现为 gclient syncunmanaged solution; skipping src。解决:确认只有一个 gclient 进程在跑,把残缺的 src 目录删掉重新 sync 一次。

实测数据:

  • 第一次(main 分支)gclient sync:约 37GB,其中拉测试素材那个 hook 因为网络问题 单独跑了 2.6 小时 (download_from_google_storage,GCS 资源下载偶发慢,跟 mac 那次 遇到的情况类似)。
  • 切到 M140 分支后重新 gclient sync:因为大部分依赖已经在本地,速度快很多,资源那个 hook 这次只用了 139 秒。同步完磁盘占用降到 32GB(M140 这个发布分支比 main 分支依赖更精简)。

七、装 Linux 编译依赖

bash 复制代码
cd ~/Documents/webrtc_android/src
sudo build/install-build-deps.sh

官方脚本,自动装齐一大堆系统包(包括好几个跨架构的 binutils),比手动一个个装靠谱。

八、编译打包 AAR

bash 复制代码
cd ~/Documents/webrtc_android/src
python3 tools_webrtc/android/build_aar.py \
  --arch armeabi-v7a arm64-v8a \
  --output ~/Documents/webrtc_android/libwebrtc_m140.aar

大坑:虚拟机 CPU 核数默认只有 1 个

建虚拟机时 "CPU Cores" 选项留了 "Default" ,结果 QEMU 实际只分了 1 个核心 (nproc 输出 1)。ninja/siso 这类构建系统的核心优势就是多核并行编译,只有 1 核约等于 完全串行,严重拖慢编译速度。

教训:建虚拟机时一定要显式指定 CPU 核数,不要留 Default。

好在 ninja 是增量式的,已经编译好的 .o 文件不会因为改核数重新编译,如果发现核数不够, 关机改完核数重开、重跑同一条命令即可继续,不会浪费已有进度。这次实际是保持单核跑完的, 两个架构总共耗时约 6 小时 (build finished 日志里能看到 5h58m50.93s)。

九、结果

  • 产物路径(虚拟机内):~/Documents/webrtc_android/libwebrtc_m140.aar,19,677,215 字节(约 19.7MB)

  • unzip -l 校验过两个架构的 .so 都在:

    bash 复制代码
    jni/armeabi-v7a/libjingle_peerconnection_so.so   6,731,180 bytes
    jni/arm64-v8a/libjingle_peerconnection_so.so    11,973,360 bytes
  • 因为共享目录没挂载上,用 scp 直接拷回 Mac:

    bash 复制代码
    scp dev@<虚拟机IP>:~/Documents/webrtc_android/libwebrtc_m140.aar \
      /Users/dev/Documents/AV/webRtc/libwebrtc_m140.aar
  • 最终文件:/Users/dev/Documents/AV/webRtc/libwebrtc_m140.aar

结论:M140(branch-heads/7339)的 WebRTC Android AAR,在 x86_64 Ubuntu 虚拟机(UTM Emulate 模式,QEMU 软件模拟,无硬件加速)上编译成功,armeabi-v7a + arm64-v8a 两个架构都验证过产物存在。 ARM64 虚拟机这条路完全走不通(上游没有对应架构的 NDK 工具链包),CPU 核数配置是影响编译速度的 关键因素,下次建虚拟机时应该一开始就分配足够核数。

相关推荐
默_笙1 小时前
🍕 后端接口还没写好,前端已经跑起来了?Mock 数据 + axios 接口层,前后端再也不互相"等"
前端·javascript
书源1 小时前
AI 时代写给前端同行:什么在贬值,什么在涨价
前端·程序员·ai编程
爱勇宝1 小时前
客户只想看个页面,我却做了一个静态演示发布系统
前端·javascript·后端
渔夫正在掘金1 小时前
告别构建时代:为什么 AI 编程浪潮正让 Vue 与 React 走向落后?
前端
一个有理想的摸鱼选手1 小时前
(四)路书Agnet-综合天气距离交通节奏等多因素来编排旅行路线
前端·后端·gis
神奇的程序员1 小时前
在这个独属于ai的时代,我终究是被裁员了
前端·后端·面试
玉宇夕落1 小时前
React + JWT 登录鉴权系统
前端
hunterandroid1 小时前
[鸿蒙从零到一] HarmonyOS 安全加固与数据保护实战:从密钥管理到反调试的全链路防护
前端
用户921080262861 小时前
从 Bubble 到 BubbleList:加载历史对话时才真正看懂组件通信
前端