ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Mac上从源码编译WebRTC:从环境准备到自定义SDK编译指南

Mac上从源码编译WebRTC:从环境准备到自定义SDK编译指南 第一次动编译WebRTC的念头八成不是因为好奇而是因为预编译SDK不够用了。我当时要在项目里调音频丢包隐藏的参数翻遍了官方SDK接口文档发现这一层API根本不向你开放——它只管把编解码和网络传输封装好丢给你几个回调你插不上手。于是只能走“自己编译”这条路。这是Mac OS上编译WebRTC最典型的动机拿到带源码、能改参数、能裁剪模块的完整产物。这篇教程就是给那些和我一样需要真正“碰”WebRTC源码而不仅仅是调SDK的开发者准备的。1. 为什么非要在Mac上自己编译WebRTC预编译SDK满足不了的定制需求想清楚这个问题后面才不会有中途放弃的念头。WebRTC的预编译产物无论是Google官方维护的二进制包还是第三方打包的framework解决的都只是“即时可用”的问题配置好证书拖进工程调用API跑通音视频通话。但一旦你遇到下面这几种情况预编译SDK基本就无能为力了。1.1 算法级定制预编译SDK不开放的内部参数WebRTC里丢包隐藏、抖动缓冲、拥塞控制比如GCC算法的很多参数都写在源码里。你如果想改neteq的延迟参数或者调整pacer的发送节奏在二进制SDK里找不到任何入口。预编译包最多给你几个RTCPeerConnection级别的接口粒度离底层算法隔着十万八千里。只有拿到源码自己编译才能直接在modules/audio_coding/或modules/pacing/里改代码验证效果。1.2 模块裁剪把上百MB打薄到几十MB预编译包通常把音频Codec、视频Codec、网络协议栈全部打进去体积可能上百MB。而如果你只做一套纯音频的1v1通话或者只需要自定义的采集/渲染通道完全可以把用不到的模块去掉。比如只保留Opus、去掉所有视频Codec产物体积能缩小一大截。这个只有在你掌握编译参数之后才能实现预编译SDK从来不会给你这种选项。1.3 调试定位源码和符号是线上排查的底气线上音视频通话出了诡异的问题你要在RtpPacketizer里加日志、在VideoStreamEncoder里断点查看码率分配没有源码和调试符号就只能靠猜测。自己编一个is_debugtrue的版本能帮多大忙实践过的人都知道。断点下进去变量一查问题定位往往就在十分钟内。此外Mac平台本身有个特殊性一份WebRTC源码编译产物既可以给macOS用也能交叉编译出iOS版framework。两边共用一套代码和构建体系这对做跨端音视频SDK的人来说是巨大的便利也是我坚持在Mac上折腾自编译的核心原因。所以这篇教程面向的读者是不再满足于“能调通”而是想在WebRTC里做实事的人。2. 编译前的硬条件自查磁盘、内存、Xcode、网络这四关WebRTC编译不是“装个包就完事”的普通开源项目。它整个构建系统是为Chromium这种巨型代码库设计的对机器环境有硬性要求。很多人编译失败不是命令敲错而是前面这四关没过。2.1 磁盘空间预留60GB是最低诚意这是个最容易被低估的数字。我自己的项目源码加依赖大概12GBDebug构建目录峰值超过25GBRelease构建也要20GB上下。如果你中间再切换几次target_cpu磁盘分分钟爆掉。建议在开始前用df -h看一下根目录剩余空间60GB以上是起步线低于40GB我建议先清理完再动手。其实有个取巧的办法只要保留构建产物和用到的源码目录depot_tools里的.git历史可以适当精简不过这个属于进阶操作新手别乱来。一定要在开始前留足空间而不是等报错再去想办法。2.2 内存和CPU16GB以上才谈得上体验编译过程基本是“CPU全核拉满、内存吃到80%”的画风。我最初在8GB内存的MacBook上试过开编译之后整个系统基本处于半瘫痪状态而且并行度太高会让内存耗尽编译进程被系统直接杀掉。16GB内存、四核以上CPU是比较稳妥的起步配置。如果你和我一样是8GB内存的老机器至少编译的时候把浏览器、模拟器全关掉再用ninja -j4限制并行任务数能熬过去但会比较痛苦。2.3 Xcode环境Command Line Tools不够用这个坑非常隐蔽。很多教程让你安装Xcode有人觉得“反正只是命令行编译装Command Line Tools就行了吧”——不行。WebRTC的构建脚本会调用xcode-select查找完整Xcode路径还需要系统的iOS SDK和macOS SDK来做交叉编译配置。就算你只编macOS版本也建议装完整的Xcode。装完之后检查一下路径xcode-select -p正常情况下输出是/Applications/Xcode.app/Contents/Developer。如果不是这个路径执行sudo xcode-select -s /Applications/Xcode.app/Contents/Developer另外第一次安装完Xcode记得启动一次让它跑完License协议否则后续clang调用会直接报错。2.4 网络环境与下载策略fetch源码和gclient同步依赖时需要在多个代码托管节点之间来回下载数据量很大。这个过程对网络稳定性非常敏感。如果你的网络本身不太稳定不要用“一次到底”的心态跑gclient sync它其实支持断点续传中途失败后重跑就能接着下载。我的建议是选择一个网络相对空闲的时段跑sync过程中不要去动网络。如果反复在同一个文件上报错可以用gclient sync --force强制重新拉取但不要手动去删目录容易越修越乱。3. 拉源码的正确姿势depot_tools、fetch和gclient syncWebRTC的源码管理跟一般Git项目不太一样它依赖一套Chromium体系下的工具链。这套工具初看很反直觉但理解了它的逻辑之后你会觉得整个过程其实很顺。3.1 安装depot_toolsdepot_tools是Google为Chromium系项目准备的一套开发工具集里面包含了gclient、gn、ninja等我们今天全都要用到的东西。安装方式非常简单git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git export PATH$PWD/depot_tools:$PATH注意这两行命令最后要落到~/.zshrc或~/.bash_profile里否则新终端窗口又找不到命令了。加完之后执行echo $PATH确认。3.2 用fetch拉取WebRTC源码fetch是depot_tools里的一层包装脚本它会根据你传入的项目名自动创建.gclient文件并同步初始代码。创建工程目录mkdir ~/webrtc-checkout cd ~/webrtc-checkout fetch --nohooks webrtc_ios这里我用的是webrtc_ios而不是webrtc区别在于.gclient文件里会额外写入target_os [ios, mac]这样后续既能编macOS库也能交叉编译iOS库。如果你确定只做macOS桌面端用fetch webrtc也可以。但我个人建议直接webrtc_ios因为绝大多数在Mac上折腾WebRTC的人最终都逃不过“顺手编个iOS版本”的需求。注意这里我故意加了--nohooks意思是先只拉代码不执行后面的初始化钩子。等代码下来之后再单独执行同步gclient syncgclient sync会补齐所有子模块依赖并生成构建所需的各类脚本。这一步消耗的时间和网络带宽都很可观第一次跑建议做好准备别开着流量套餐硬冲。3.3 sync过程中的常见状况最常遇到的状况就是“卡住”。比如屏幕停在一行Syncing projects: 45% (54/120)不动了。我的经验是不要慌也不要直接CtrlC然后删目录。如果超过20分钟没有变化才考虑中断重试。很多节点下载速度慢它其实是在传输只是进度显示不够实时而已。中断后重新执行gclient sync已经下载的部分会保留不会从头再来。还有一种是反复报Unable to connect或者Timeout。这种基本是网络质量问题。换个时间再跑或者用手机热点换一个网络出口一般能解决。网上流传的各种网络加速方案我个人的态度是不建议在下载阶段引入额外变量先重试再说。真实情况是WebRTC依赖的下载节点不止一个大部分时候多跑两次就能通过。4. GN参数表才是编译的“灵魂”关键配置项逐一拆解源码拉下来之后下一步不是直接编译而是先配置构建参数。WebRTC现在用的构建系统是GN Ninja这跟很多开源项目用CMake完全不是一回事。很多初学者第一次接触gn gen会一脸懵这里先花点篇幅讲清楚它的逻辑。4.1 GN到底是什么为什么不是CMakeGNGenerate Ninja是Chromium团队自己开发的元构建系统它的作用是“根据配置生成Ninja能直接使用的构建文件”。Ninja本身是一个极快的构建执行器但配置起来非常不友好于是GN就负责把人对配置的“意图”翻译成Ninja能执行的具体指令。你可以简单理解为CMake的产物是Makefile而GN的产物是ninja.build文件。WebRTC选GN不选CMake本质是因为源码规模太庞大模块依赖关系极其复杂CMake在这种体量下无论是配置速度还是增量编译效率都跟不上。这个背景了解之后再看接下来的命令就不会觉得奇怪了。4.2 核心参数一览GN参数的设置方式有两种一种是在命令行里直接用--args传gn gen out/mac-arm64-release --argstarget_osmac target_cpuarm64 is_debugfalse另一种是先生成默认配置再打开编辑器调整gn args out/mac-arm64-release两种等价推荐第二种因为每次改动时GN会自动重新生成Ninja文件省得记一长串命令行。下面这张表是我认为在Mac编译场景下优先级最高的参数参数可选值作用说明target_osmac / ios / android目标操作系统类型target_cpux64 / arm64 / x86目标CPU架构is_debugtrue / false是否Debug构建is_component_buildtrue / false动态库/静态库模式rtc_include_teststrue / false是否包含单元测试代码rtc_build_frameworktrue / false是否生成WebRTC.frameworksymbol_level0 / 1 / 2调试符号级别rtc_use_h264true / false是否集成H.264编解码器mac_deployment_target10.14 / 11.0最低支持的macOS版本每个参数单独说明一下target_os和target_cpu决定产物运行在什么平台上。Apple Silicon机器默认生成arm64Intel机器默认x64但你可以手动指定交叉编译。is_debug决定是否带完整调试信息。Debug版本编译更慢、产物更大但调试体验好。线上分发给第三方用一定要关掉。is_component_build尽量设置成 false。true 会编译出几十个dylib开发迭代快但最后交付给他人集成时相当麻烦false 会把所有代码打进一个静态库或framework部署简单。rtc_include_tests设置为 false能省掉大量测试相关源码的编译时间是个很划算的参数。rtc_build_framework设为 true编译完成后直接得到WebRTC.framework省去自己折腾头文件和库路径的时间。symbol_level设为0编译速度和产物体积都会有明显改善。线上发布版本建议0Debug用默认值即可。rtc_use_h264默认可能是false涉及H.264的专利问题。如果不需要H.264硬编硬解保持默认即可。mac_deployment_target就是要支持的最低macOS版本按你的目标用户群体设置不设的话默认给到比较新的系统。4.3 我常用的三种参数组合前面铺垫完了直接给可以直接抄作业的模板。第一种Apple Silicon Mac上构建macOS arm64 Release版frameworktarget_os mac target_cpu arm64 is_debug false is_component_build false rtc_include_tests false rtc_build_framework true symbol_level 0第二种Intel Mac上构建macOS x64 Release版静态库target_os mac target_cpu x64 is_debug false is_component_build false rtc_include_tests false symbol_level 0注意这种不会生成framework后面需要手动找静态库和头文件。第三种交叉编译iOS arm64 Release版frameworktarget_os ios target_cpu arm64 is_debug false is_component_build false rtc_include_tests false rtc_build_framework true symbol_level 0这里能编iOS版本正得益于前面用了fetch webrtc_ios把iOS的依赖也拉全了。这些参数都不是一次定死的后续想改随时用gn args out/xxx重新改GN会智能地只重编受影响的部分不用全部重来。5. 正式编译全流程从gn gen到拿到第一个WebRTC.framework参数定好之后正式编译反而没什么技术含量了。因为前面已经把最难的配置和源码准备做完了。这一章我把完整命令串起来并告诉你每一步大概要等多久。5.1 生成构建目录进入src目录cd ~/webrtc-checkout/src然后创建构建目录。我习惯用out/平台-架构-模式这样的命名方式便于多配置共存gn gen out/mac-arm64-release --argstarget_osmac target_cpuarm64 is_debugfalse is_component_buildfalse rtc_include_testsfalse rtc_build_frameworktrue symbol_level0如果参数很多我更推荐用gn args交互式编辑。执行后GN会检查环境依赖这一步通常几秒到十几秒。如果看到类似ERROR at //build/config/mac/BUILD.gn的报错多半是Xcode路径或SDK版本不对回到第2章检查。生成成功后out目录下会看到一个args.gn文件里面就是刚才的配置下次可以直接复用。5.2 执行ninja编译构建配置已生成编译就是一条命令ninja -C out/mac-arm64-release WebRTC.framework如果你没有设置rtc_build_frameworktrue则直接编译默认目标ninja -C out/mac-arm64-release-C指定的是构建目录。ninja会自动读取该目录下GN生成的构建文件。等待时间方面第一次全量编译非常劝退在Apple Silicon的MacBook Pro上关掉测试、symbol_level0的前提下大概需要一个多小时Intel Mac可能要到两三个小时。过程中风扇会保持高位运转这是正常的不用慌。编译期间最好不要同时开虚拟机或大型IDE避免内存不够导致编译进程被系统杀死。如果机器内存小可以限制并行度ninja -C out/mac-arm64-release -j4这会牺牲一定速度但能保证编译不中途崩溃。5.3 产物在哪编译成功之后按是否启用framework产物位置不一样。framework模式out/mac-arm64-release/WebRTC.framework这个目录就是一个完整的framework包里面包含Headers、Modules和二进制文件之后集成到你自己的工程只需要把这个framework拖进去并设置Header Search Path。静态库模式需要先搜索一下库文件find out/mac-arm64-release -name libwebrtc.a大概率输出路径是out/mac-arm64-release/obj/webrtc/libwebrtc.a对应的头文件就在源码根目录的api/、rtc_base/等子目录里集成的时候要把这些头文件目录都加进Header Search Path。到这里编译流水线就走通了。但产物是不是真的能用来做集成接下来还有几个常见的坑要专门说。6. 编译到一半失败的完整排查链路我实际踩过的坑前面几章是理想路径。真实操作中大概率会在某个环节翻车。我自己从第一次尝试到稳定复现前前后后踩了不少坑这里把最有代表性的五个整理出来每个都给出完整的排查思路方便你对照。6.1 磁盘写满最阴间的“No space left on device”编译到86%左右突然抛出一片类似clang: error: unable to open output file ... No space left on device的报错这是磁盘爆了。ninja的报错机制在这种时候很迷惑可能只是一两个文件编译失败但实际上整个磁盘已经没有可写空间后面就算修好单个文件也过不去。排查链路先用df -h /确认磁盘剩余空间如果显示100%基本坐实。再跑du -sh ~/webrtc-checkout/*看哪个目录最占空间。修复方案是把旧的out目录或其他项目临时移走给webrtc留出空间。清理后直接重跑ninja -C out/mac-arm64-releaseninja会跳过已经编译好的部分只继续剩下的。不要天真地以为删几个小文件就能腾出空间这一行至少要留出20GB余量。6.2 Xcode路径漂移所有工具链集体罢工如果你机器上装了多个版本的Xcode或者重装过系统经常会遇到一类诡异报错gn gen能过但在执行ninja时大量xcrun: error: unable to find utility clang或SDK not found。排查链路执行xcode-select -p看当前路径。如果路径不是/Applications/Xcode.app/Contents/Developer执行sudo xcode-select -s /Applications/Xcode.app/Contents/Developer。再跑一次xcodebuild -version能正常输出版本号说明Xcode本身没问题。重跑ninja。这类问题通常不是构建配置损坏而是系统工具链指向错了。6.3 Python脚本版本不匹配WebRTC的构建脚本很长一段时间依赖Python 2。如果你的系统干净只有Python 3某些旧版本在跑gclient sync时会报python: command not found。不过现在官方已经切到Python 3新拉取的代码基本不会碰到这个问题。如果你使用了很久以前的分支或旧版depot_tools可能还需要额外安装Python 2。思路要么升级depot_tools要么按报错路径给Python 2提供对应环境具体看报错信息再定。6.4 第三方依赖的符号链接损坏gclient sync在网络中断后偶尔会留下失效的符号链接。典型症状是ninja编译时找不到third_party/xxx下的头文件而这种报错看不出规律时有时无。排查链路进到src/third_party下用find . -type l -exec test ! -e {} \; -print找出坏掉的符号链接。大概率发现某些目录是空的或链接指向不存在的路径。修复方式是删掉这些坏链接然后重新执行gclient sync --force。如果修复后仍反复出现就把third_party目录里的异常子目录移走在.gclient文件不变的情况下再同步一次。注意不要单独重新clone第三方仓库除非你非常确定自己在干什么否则容易把版本搞乱。6.5 arm64与x64混用链接阶段符号找不到Apple Silicon机器上Xcode默认支持的编译架构可能和你手动指定的不一致。比如你用target_cpux64编出了x64的库但在测试程序里用默认arm64的clang去链接就会报Undefined symbols或者building for macOS-arm64 but attempting to link with file built for macOS-x86_64。排查链路用file out/mac-x64-release/WebRTC.framework/WebRTC查看二进制的真实架构。确认测试程序是用相同架构编译的比如clang -arch x86_64。在Apple Silicon上跑x64程序记得终端要支持Rosetta或者直接用arm64版重新编译。这个坑好多人踩踩了还不好查因为报错会指向某个具体的WebRTC符号看起来像代码问题其实是架构不匹配。7. 写一个最小C工程验证编译出来的WebRTC真的能跑编译出framework之后最担心的是“是不是真的能用”。直接扔进大工程里试出了问题又不好定位。我的习惯是先做一个最小的C工程把核心的PeerConnectionFactory创建出来跑通确认库本身没问题再逐步集成到真实业务里。7.1 准备一个测试源文件新建一个目录比如~/webrtc-demo/在里面创建main.cc#include cstdio #include api/peer_connection_interface.h #include rtc_base/thread.h int main() { auto network_thread rtc::Thread::CreateWithSocketServer(); auto worker_thread rtc::Thread::Create(); auto signaling_thread rtc::Thread::Create(); if (!network_thread-Start() || !worker_thread-Start() || !signaling_thread-Start()) { std::fprintf(stderr, failed to start threads\n); return 1; } auto factory webrtc::CreatePeerConnectionFactory( network_thread.get(), worker_thread.get(), signaling_thread.get(), nullptr, nullptr, nullptr, nullptr); if (!factory) { std::fprintf(stderr, CreatePeerConnectionFactory failed\n); return 1; } std::printf(WebRTC factory created successfully\n); return 0; }这段代码不涉及任何推流和采集只是把WebRTC最核心的工厂对象创建出来。如果连这步都能成功说明编译产物在运行时环境上基本没问题。7.2 头文件路径与链接配置使用framework模式编译clang main.cc \ -stdc17 \ -I ~/webrtc-checkout/src/out/mac-arm64-release/WebRTC.framework/Headers \ -F ~/webrtc-checkout/src/out/mac-arm64-release \ -framework WebRTC \ -o demo关键点是-I指向framework内的Headers目录-F指向framework所在目录-framework WebRTC告诉链接器使用WebRTC这个framework。编译器会把api/...和rtc_base/...这两个头文件路径自动解析出来因为它们相对Headers目录存在。如果用的是静态库模式编译器要额外指定一堆系统framework依赖比如CoreAudio、CoreVideo、AudioToolbox等等。所以在条件允许的情况下我强烈建议用framework模式来验证。7.3 运行结果判定链接成功后执行./demo如果看到输出WebRTC factory created successfully说明整个编译链路是通的。如果运行时报dyld找不到库检查一下DYLD_LIBRARY_PATH是否需要指向framework父目录如果崩在CreatePeerConnectionFactory内部大概率是线程或日志模块初始化问题可以再检查rtc::InitializeSSL()这类初始化调用是否遗漏。我在实际项目中会在这个基础上继续封装一层比如把AudioDeviceModule换成自采集的实现把VideoEncoderFactory换成带硬编的自定义工厂。但作为WebRTC的“hello world”上面这个demo已经足够验证编译成果了。最后说点实在的。第一次完整跑通WebRTC编译的人多少都会经历从兴奋到崩溃再到平静的过程。最痛苦的不是敲命令而是遇到一个错误之后搜遍全网发现每个说法都不一样。上面这七个坑是我在Mac上实际编译时一个一个踩出来、又一条一条验证过的。如果你也是被某个编译错误卡住了不妨按这套流程重新捋一遍大概率能省下一天时间。我自己的经验是编译这种事第一次完整跑通之后后面所有的定制、调参、裁模块就都变成了水磨工夫再也没有想象中那么神秘了。
返回列表