编译 WebRTC:环境、拉代码与各平台的构建脚本

从零做一个全平台播放器·约 1629 字

要在自己的工程里用 WebRTC 的音频处理和解码工厂,得先把 WebRTC 这个库编出来。它不走常规的 CMake,代码要用 depottools 拉,构建用 GN 配、ninja 跑,流程在 Mac 和 WSL 上都走通过,WSL 更顺一些。真正费劲的有两处:Android 上它带的那套定制工具链产出的 STL 符号和 NDK 的对不上;接进自己工程时,静态运行库、openssl 这些两边都要对齐。这篇按顺序讲:环境、拉代码、GN 摸底、Android 那道坎,最后是工程这边的接线。

编译前要备的东西

四样:depot_tools、ninja、装了模块的 python、能走代理的网络。缺一样都停在半路。

WSL 这边有几条是踩出来的。不要用 root 跑 fetch 和 gclient:root 编译会被直接拒绝,缓存目录一旦归了 root,后面普通用户连重命名都做不了。Windows 下面那份 depot_tools 不要混用,WSL 会把 Windows 的 PATH 整个套进来,得把 Linux 这份放到 PATH 最前面,不然两边串。容器用 wsl2,网络上方便稳定。

Mac 那边简单些:depot_tools 克隆下来加进 PATH;编译脚本报缺 python 模块时,系统常拦着不让直接装,用虚拟环境装模块解决;报缺 ninja 就装。代理要给 git 也配上,拉代码走的是 git;代理工具开 TUN 模式,而且装在 Mac 本地,别处下载再传过去的安装包装完有问题。

编译前要备齐的东西,以及 WSL 与 Mac 上各自的讲究

拉代码:fetch 一次,断了就 sync

WSL 用 fetch --nohooks webrtc_android,Mac 用 fetch --nohooks webrtc_ios。中途断掉就 gclient sync 续上,重复拉不会重复下载。要某个版本就切分支,比如 git checkout -b m138 branch-heads/7204,切完用 git status 确认真的切上了——WSL 里经过 Windows 的代理,网络不太稳,出现过没切成功还不知道的情况。

依赖装两步:切到 src 目录跑 ./build/install-build-deps.sh --arm 和 ./build/android/envsetup.sh;还缺库就在 .gclient 里加 target_os 再 sync 一次。Mac 上拉第三方库用 gclient sync -D。

拉取过程里最容易撞的一个错,是解压缓存目录时报 rename 权限失败。原因不玄学:之前用 root 跑过一次 fetch,缓存归了 root,现在普通用户没权限动它。把属主改回来,或者删掉缓存重下。实在不行,设环境变量跳过 vpython 检查再重新 fetch,这是最后手段。

从拉代码到出库的流程

用 GN 摸底

GN 用来配构建,查东西就那几条:gn ls 看一个目录下有哪些目标;gn desc ... deps 看一个目标依赖谁,换成 all_deps 看全部;反方向 gn refs 看谁依赖它;gn args --list 把可调参数全列出来。

这些命令的用处是动手之前先摸底:一个模块到底叫什么、它拉了谁、又是谁在编译它。想砍掉哪块、想换哪个开关,先得把目标名查出来,不然连改哪一行都不知道。参数也是一样:gn args --list 把可调项连着说明列出来,照着说明取值,比到网上翻零散帖子可靠。工程这边用到的几组参数——关内置 H264、开 H265 协议、Debug 的迭代器调试——都是这么一个个查出来再定下来的。

Android 的坎在 STL 命名空间

Android 的 libwebrtc 依赖里带了一个定制版 LLVM 工具链,它编出来的 STL 对象全落在 Chromium 专用的 CR_ 命名空间里;自己代码用的是 NDK 的标准命名空间。两边看着类型一样,符号名完全不是一个,要到链接期才炸。

两条路:用它的定制工具链编自己的代码,或者用标准 NDK 工具链编它。第一条试下来满屏指不清位置的错,放弃。第二条在 M138 上走通了,中间两个错其实是一根线:不换工具链,unwind 库就只能用系统那份,而它不在默认搜索路径里——use_custom_libunwind 开着报找不到 _Unwind_Backtrace,关掉又报找不到 libunwind.a。解法是在 WebRTC 的 src/build/config/compiler/BUILD.gn 里,给 Android arm64 显式补上 NDK 里 libunwind.a 的路径。细节是 NDK 26 的 aarch64 那份能用,NDK 21 的不行。

还有一条验证过的死路:Android 的 WebRTC 动态库不带导出符号,readelf 一看便知,所以链动态库这条路走不通,只能静态链。

Mac 上一个 SDK 版本的小坑

Mac 编译报 float16 类型不支持,问题不在代码,在 SDK 版本要求:编译脚本里加一行 mac_sdk_min="10.10" 就过了。这类坑的共性是报错指向类型、根子在环境,往代码里找是找不到的。

接进工程:取头文件、链库与参数对齐

WebRTC 仓库太大,不放进工程里;头文件引用关系又复杂,拷贝不现实。avox 的做法是把检出放在工程旁边的 avox_library 目录里,构建脚本从那里取东西:头文件指向它的 src,库指向它编出来的 build 目录,GN 参数也按这个目录生成。编出来的库找名字时能见到两个变体,一个带符号一个不带,不带的那个正对应前面说的 Android 侧没有导出符号的形态,按平台挑着链。哪天要换版本,重编那份检出就行,工程侧只认目录。

Windows 上有几处要对齐。运行库:WebRTC 静态链接对应 MSVC 的 MT/MTd,工程在 CMake 里把所有 /MD 统一替换成 /MT,两边才是同一个运行时。Debug 想要迭代器调试:WebRTC 默认关着,GN 里开 enable_iterator_debugging,开了之后 M138 又编不过,得把它源码里 protobuf 的 port_def.inc 改一个宏才通。

openssl 是两边最容易撞车的一处。WebRTC 自带一份 BoringSSL,工程这边再链一份 openssl,符号就撞;压下重复定义的链接选项能让它编过、跑起来也不崩,但两个模块各链 ffmpeg 的不同函数,被强制合用之后解出来的数据是错的,这类问题编译期一点声音都没有,要等运行时才炸。所以链接选项不用。现在的解法分平台:非 Windows 的构建脚本把 ZLMediaKit 的 openssl 关掉,让 WebRTC 用自带的;Windows 上 ZLMediaKit 走动态库、单独链自己的 openssl,两边天然隔开,构建配置里另有一道检查,静态库要是带出 SSL 符号会直接报错拦下。

GN 参数里还有一组与解码有关的:rtc_use_h264 关掉,它就不再链 ffmpeg;rtc_use_h265 打开,保留 H265 的协议层。这些参数连同各平台的差异,都收在工程自带的构建脚本里,Windows 一份 powershell、其余平台各一份 sh,编 WebRTC 就是跑对应那份脚本。

WebRTC 接进 avox 工程的几根线:外部目录取头文件与库,运行库与 openssl 两边对齐

到这儿,库编出来了、也接进工程了,后面几篇才有的谈:音频处理怎么接、解码与渲染怎么接管、双向通话怎么搭、源与编码器怎么推出去。

引擎 avox 在 GitHub 开源:github.com/xxxzhou/avox,构建脚本与接线都在里面。

音视频开发 WebRTC 编译 GN ninja Android C++