:環(huán)境、配置與排錯)
1. 為什么要在 Linux 上自己編譯 WebRTC如果你正在做實時音視頻相關(guān)的開發(fā)大概率繞不開 WebRTC 這個龐然大物。官方預編譯庫雖然能用但一旦你要改編碼參數(shù)、裁剪模塊體積、接入私有編解碼器或者只是想在特定發(fā)行版上跑通自己從源碼編譯幾乎是唯一的路。我在幾個實時通信項目里反復折騰過這套流程從最早被 depot_tools 的代理配置卡住到后來能穩(wěn)定產(chǎn)出一個可用的靜態(tài)庫中間踩的坑足夠?qū)懸黄L文。這篇內(nèi)容面向的是已經(jīng)了解 C 構(gòu)建基本流程、準備在 Linux 上從源碼編譯 WebRTC 的開發(fā)者。無論你是第一次嘗試還是之前編譯失敗想找原因下面的拆解應該都能對上你的場景。核心關(guān)鍵詞就一個Linux 平臺編譯 WebRTC。我會把整個鏈路拆成環(huán)境準備、源碼獲取、構(gòu)建配置、編譯執(zhí)行、產(chǎn)物驗證和問題排查幾個部分每一步都說明白“為什么這么做”而不只是“照著敲”。先說清楚一件事WebRTC 的構(gòu)建系統(tǒng)不是 CMake也不是簡單的 Makefile而是 Google 那套 GN Ninja 的組合外加 depot_tools 這套工具鏈管理腳本。理解這一點非常關(guān)鍵因為網(wǎng)上大量教程直接甩幾條命令你照著跑大概率會在某一步報錯而報錯信息往往和真正的原因隔了十萬八千里。我見過太多人卡在gclient sync階段以為是網(wǎng)絡問題實際上是 Python 版本或者代理環(huán)境變量沒配對。另外要提醒的是WebRTC 源碼體積非常大完整拉取加上構(gòu)建中間產(chǎn)物輕松超過 30GB編譯時間在普通開發(fā)機上動輒一兩個小時。所以磁盤空間和時間預算要提前規(guī)劃好別到一半發(fā)現(xiàn)磁盤滿了。我一般建議至少預留 100GB 的空閑空間內(nèi)存 16GB 起步8GB 會非常吃力鏈接階段容易 OOM。2. 編譯前的環(huán)境準備與工具鏈選型2.1 操作系統(tǒng)與基礎(chǔ)依賴的選擇邏輯WebRTC 官方主要支持 Ubuntu 的 LTS 版本我在 Ubuntu 20.04 和 22.04 上都驗證過流程基本一致。為什么不推薦 CentOS 或其它發(fā)行版不是不能編而是官方腳本里大量依賴 apt 包管理器和特定版本的庫換發(fā)行版意味著你要自己解決一堆依賴差異性價比太低。如果你非要在別的發(fā)行版上做建議直接用容器起一個 Ubuntu 環(huán)境把編譯環(huán)境隔離出來省得污染宿主機?;A(chǔ)依賴這塊核心是編譯工具鏈和一堆系統(tǒng)庫。下面這條命令是我常用的依賴安裝清單基于 Ubuntu 環(huán)境sudo apt update sudo apt install -y git python3 python3-pip \ build-essential pkg-config \ libglib2.0-dev libgtk-3-dev libnss3-dev \ libasound2-dev libpulse-dev libjpeg-dev \ libssl-dev libcurl4-openssl-dev \ lsb-release sudo這里每一項都不是隨便加的。build-essential提供 gcc/g/make 基礎(chǔ)工具libglib2.0-dev和libgtk-3-dev是桌面相關(guān)模塊的依賴即使你只編無界面的庫部分目標也會引用到libasound2-dev和libpulse-dev對應 Linux 下的音頻采集與播放后端做音頻功能繞不開libnss3-dev涉及證書和安全相關(guān)模塊。少裝一個編譯到某個目標時就會報頭文件找不到而且報錯位置往往和你以為的模塊無關(guān)排查起來很費勁。注意不要用 root 用戶直接跑整個編譯流程。depot_tools 和 GN 在某些環(huán)節(jié)對權(quán)限敏感用普通用戶加 sudo 處理系統(tǒng)級安裝更穩(wěn)妥。2.2 Python 版本這個隱形坑WebRTC 的構(gòu)建腳本對 Python 版本有要求早期版本依賴 Python 2現(xiàn)在主流分支已經(jīng)全面轉(zhuǎn)向 Python 3。問題在于系統(tǒng)里可能同時存在多個 Python 版本python命令指向哪個、python3又是哪個會直接影響腳本行為。我的做法是明確用python3并且在環(huán)境變量里確認優(yōu)先級python3 --version which python3如果版本低于 3.8建議升級。有些腳本用了較新的語法特性低版本會直接語法報錯。另外 pip 也要確保是 Python 3 對應的那個避免裝包裝到了 Python 2 的環(huán)境里然后腳本運行時提示模塊缺失你卻明明“裝過了”。2.3 depot_tools 的獲取與 PATH 配置depot_tools 是 Google 用來管理 Chromium 及其相關(guān)項目WebRTC 就是其中之一的工具集合包含gclient、gn、ninja等關(guān)鍵命令。獲取方式很簡單git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git克隆下來之后必須把它的目錄加到PATH的最前面注意是最前面因為系統(tǒng)里可能已經(jīng)裝了別的 gn 或 ninja版本不匹配會出各種詭異問題。在~/.bashrc里加一行export PATH/path/to/depot_tools:$PATH然后source ~/.bashrc讓它生效。驗證一下which gn which ninja which gclient三個命令都應該指向 depot_tools 目錄下的腳本。如果指向了/usr/bin下的版本說明 PATH 順序不對回去檢查。這一步看著簡單但它是后面所有操作的基礎(chǔ)我見過不少人因為 PATH 沒配對用系統(tǒng)自帶的 ninja 去跑 GN 生成的構(gòu)建文件結(jié)果報版本不兼容白白浪費半天。2.4 網(wǎng)絡環(huán)境的現(xiàn)實考量源碼拉取階段需要訪問 Google 的代碼倉庫這一步對網(wǎng)絡環(huán)境有要求。我不展開講具體方案只說原則確保git能正??寺hromium.googlesource.com上的倉庫gclient sync能順利下載依賴。如果中途頻繁超時可以調(diào)整 git 的緩沖配置減少大倉庫克隆時的失敗率git config --global http.postBuffer 524288000 git config --global core.compression 0這兩個配置的作用分別是增大 HTTP 傳輸緩沖、關(guān)閉壓縮以降低 CPU 開銷對大倉庫克隆的穩(wěn)定性有一定幫助。另外gclient sync支持斷點續(xù)傳失敗了重新執(zhí)行通常能接著下不用從頭來。3. 源碼獲取從 fetch 到 gclient sync 的完整鏈路3.1 用 fetch 拉取 WebRTC 源碼depot_tools 配好之后找一個空間充足的目錄執(zhí)行mkdir webrtc_build cd webrtc_build fetch --nohooks webrtcfetch是 depot_tools 提供的封裝命令它會幫你完成初始的倉庫克隆和.gclient配置文件生成。--nohooks參數(shù)的意思是先不執(zhí)行鉤子腳本因為鉤子腳本里包含大量依賴下載和環(huán)境檢查我們想先把源碼主體拉下來再統(tǒng)一處理依賴這樣出問題時更容易定位。這一步拉取的是 WebRTC 的主倉庫體積已經(jīng)不小了。完成后當前目錄下會有一個src文件夾這就是源碼根目錄后續(xù)所有操作都在這里面進行。3.2 切換到指定分支或版本默認拉下來的是主分支main但主分支代碼變動頻繁可能引入未穩(wěn)定的改動。生產(chǎn)項目里我一般會切到一個相對穩(wěn)定的發(fā)布分支。查看可用分支cd src git branch -r然后切換比如切到某個里程碑分支git checkout -b my_branch origin/branch-heads/xxxx具體用哪個分支號取決于你的項目對穩(wěn)定性的要求和所需特性的版本。這里不寫死具體數(shù)字因為版本迭代很快你按自己項目需要選即可。切換分支后.gclient里的配置可能需要同步調(diào)整gclient sync會自動處理。3.3 gclient sync 同步依賴這是整個流程里最容易出問題的一步。執(zhí)行g(shù)client sync它會根據(jù) DEPS 文件把 WebRTC 依賴的所有第三方庫全部拉下來包括 libvpx、opus、ffmpeg 的定制版本、boringssl 等等。這些依賴數(shù)量龐大下載時間長中途失敗很常見。失敗后不要慌直接重新執(zhí)行g(shù)client sync它會跳過已完成的部分繼續(xù)。如果反復失敗在同一個依賴上可以單獨進到那個依賴目錄手動處理或者檢查是不是磁盤空間不夠了。我遇到過一次 sync 卡在某個大文件上最后發(fā)現(xiàn)是磁盤只剩幾百 MB清理后才通過。所以前面強調(diào)預留 100GB 不是嚇唬人。sync 完成后建議執(zhí)行一次鉤子gclient runhooks它會跑一些代碼生成和環(huán)境配置腳本比如生成某些頭文件、配置工具鏈路徑。跳過這步直接編譯可能會報找不到生成的頭文件。3.4 目錄結(jié)構(gòu)速覽同步完成后src目錄下的關(guān)鍵結(jié)構(gòu)值得先認識一下后面配置構(gòu)建參數(shù)時會用到路徑作用src/out構(gòu)建輸出目錄GN 生成的構(gòu)建文件放這里src/build構(gòu)建系統(tǒng)腳本和工具鏈配置src/third_party所有第三方依賴庫src/apiWebRTC 對外暴露的 C APIsrc/pcPeerConnection 相關(guān)實現(xiàn)src/modules音視頻處理、編解碼等核心模塊src/tools_webrtcWebRTC 專用的構(gòu)建和測試腳本認識這些目錄能幫你在編譯報錯時快速判斷問題出在哪個模塊而不是對著一堆路徑發(fā)懵。4. 構(gòu)建配置GN 參數(shù)怎么設(shè)才合理4.1 GN 與 Ninja 的分工GN 負責“生成構(gòu)建文件”Ninja 負責“執(zhí)行構(gòu)建”。你可以把 GN 理解成一個更現(xiàn)代、更快的 CMake它讀取.gn和BUILD.gn文件輸出 Ninja 能識別的build.ninja。所以流程永遠是先gn gen再ninja。理解這個分工你就知道報錯時該看哪一步gn gen階段報錯通常是配置參數(shù)或依賴聲明問題ninja階段報錯則是實際的編譯鏈接問題。4.2 生成構(gòu)建目錄與關(guān)鍵參數(shù)在src目錄下執(zhí)行g(shù)n gen out/Release --argsis_debugfalse rtc_use_h264true rtc_build_examplesfalse rtc_build_toolsfalse use_rttitrue逐個解釋這些參數(shù)因為每一個都直接影響產(chǎn)物形態(tài)is_debugfalse生成 Release 版本開啟優(yōu)化體積更小、運行更快。調(diào)試階段可以設(shè)true但產(chǎn)物會大很多。rtc_use_h264true啟用 H.264 支持。默認情況下 WebRTC 對 H.264 的支持是關(guān)閉的因為涉及專利問題但實際項目里 H.264 兼容性最好通常需要打開。rtc_build_examplesfalse不編譯示例程序。示例會引入額外的依賴和編譯時間只做庫的話沒必要編。rtc_build_toolsfalse不編譯測試工具。同理減少編譯量和潛在的錯誤點。use_rttitrue啟用 RTTI。如果你的項目代碼用了dynamic_cast或typeid必須打開否則鏈接時會報錯。WebRTC 默認關(guān)閉 RTTI 是為了減小體積但和很多現(xiàn)有 C 代碼不兼容。這些參數(shù)不是固定的你可以根據(jù)自己的需求增刪。比如需要做屏幕共享可能要關(guān)注rtc_include_tests相關(guān)的開關(guān)需要特定音頻后端要確認rtc_use_pulseaudio之類的參數(shù)。查看所有可用參數(shù)的方法是gn args out/Release --list它會列出所有參數(shù)及默認值和說明非常有用。我建議第一次編譯前花十分鐘掃一遍這個列表比事后查報錯高效得多。4.3 參數(shù)選擇的取舍邏輯這里展開說一下為什么很多參數(shù)要顯式設(shè)置而不是用默認值。WebRTC 的默認配置是面向 Chromium 瀏覽器集成的很多選項為了體積和特定場景做了裁剪。你把它當獨立庫用時默認配置往往不合適。比如默認不開 H.264、默認關(guān) RTTI、默認不編某些音頻后端這些都是“瀏覽器視角”的合理選擇但對獨立應用就是坑。另一個典型是is_component_build。默認在 Release 下是false生成靜態(tài)庫設(shè)成true會生成動態(tài)庫加一堆分散的.so適合快速迭代調(diào)試但部署時依賴管理麻煩。我一般 Release 用靜態(tài)庫調(diào)試階段才考慮 component build。還有symbol_level參數(shù)控制調(diào)試符號級別默認 Release 下是 0無符號如果你需要排查崩潰棧設(shè)成 1 或 2但產(chǎn)物會顯著變大。這個按需選擇。5. 編譯執(zhí)行與產(chǎn)物定位5.1 啟動編譯配置生成成功后進入編譯ninja -C out/Release-C指定構(gòu)建目錄。Ninja 會自動并行編譯并行度默認等于 CPU 核心數(shù)。如果你的機器核心多但內(nèi)存小可以限制并行度避免 OOMninja -C out/Release -j 8-j 8表示最多 8 個并行任務。我一般按“內(nèi)存 GB 數(shù)除以 2”來估算安全的并行度比如 16GB 內(nèi)存用-j 8比較穩(wěn)。編譯過程會持續(xù)很久輸出大量編譯日志。如果中途有錯誤Ninja 會停下來并打印錯誤位置。第一次編譯建議把日志重定向到文件方便事后翻查ninja -C out/Release 21 | tee build.log5.2 產(chǎn)物在哪里編譯成功后核心產(chǎn)物在out/Release目錄下。主要關(guān)注這幾個產(chǎn)物路徑說明靜態(tài)庫out/Release/obj/libwebrtc.a主庫包含大部分實現(xiàn)頭文件src/下各模塊目錄使用時需要把src加入 include 路徑部分獨立庫out/Release/obj/下如音頻處理、編解碼相關(guān)注意 WebRTC 的靜態(tài)庫不是單一一個文件鏈接時可能需要把obj目錄下多個.a一起鏈進去。具體需要哪些取決于你調(diào)用了哪些 API。最省事的做法是參考官方示例的鏈接配置或者用gn desc命令查看某個目標的依賴gn desc out/Release //:webrtc --tree這個命令會打印出目標的完整依賴樹幫你搞清楚鏈接時需要哪些庫。5.3 驗證編譯結(jié)果光編譯通過還不夠得驗證產(chǎn)物能用。最直接的方式是寫一個最小程序調(diào)用 WebRTC 的某個簡單 API比如創(chuàng)建一個PeerConnectionFactory然后編譯鏈接跑一下。如果鏈接報未定義符號說明庫沒鏈全如果運行崩潰可能是初始化順序或依賴庫版本問題。我一般會先跑官方自帶的單元測試里比較小的一個目標確認基礎(chǔ)功能正常ninja -C out/Release peerconnection_unittests ./out/Release/peerconnection_unittests --gtest_filterSomeBasicTest.*跑通一個基礎(chǔ)測試基本能確認編譯產(chǎn)物是健康的。如果測試目標編譯失敗說明你的參數(shù)配置可能裁掉了測試需要的模塊這時候要么補參數(shù)要么就跳過測試直接用自己的程序驗證。6. 常見問題與排查實錄6.1 高頻報錯速查表下面這張表是我這些年遇到最多的問題及對應處理方式按出現(xiàn)頻率排序現(xiàn)象可能原因處理方式gclient sync反復失敗網(wǎng)絡不穩(wěn)或磁盤不足檢查磁盤空間重試 sync調(diào)大 git 緩沖gn gen報參數(shù)未知參數(shù)名拼錯或版本不支持用gn args --list核對參數(shù)名編譯報頭文件找不到依賴未裝或 runhooks 未執(zhí)行補裝 apt 依賴執(zhí)行g(shù)client runhooks鏈接報未定義符號庫沒鏈全或 RTTI 不匹配用gn desc查依賴確認use_rtti一致鏈接階段 OOM并行度過高降低-j數(shù)值運行時段錯誤初始化順序或版本不匹配檢查依賴庫版本確認初始化流程Python 模塊缺失pip 裝到了錯誤的 Python用python3 -m pip明確指定6.2 幾個值得單獨說的坑坑一RTTI 不一致導致的鏈接錯誤。這個特別隱蔽。WebRTC 默認關(guān) RTTI你的項目如果開了 RTTI鏈接時可能報typeinfo相關(guān)的未定義符號。反過來如果 WebRTC 開了 RTTI 而你的項目沒開也可能出問題。解決辦法是兩邊保持一致編譯 WebRTC 時顯式設(shè)use_rttitrue項目編譯也開 RTTI。這個錯誤信息不會直接告訴你“RTTI 不匹配”只會甩一堆符號名第一次遇到很難聯(lián)想到??佣﨟.264 相關(guān)符號缺失。如果你代碼里用了 H.264 相關(guān)接口但編譯時沒開rtc_use_h264true鏈接會報一堆H264Encoder之類的符號找不到。這個好排查看符號名就知道是 H.264 模塊回去補參數(shù)重新編譯即可。但重新編譯整個庫又要等很久所以第一次配置時就把需要的編解碼器想清楚??尤齡client sync 后的分支不一致。如果你切換了 WebRTC 主倉庫的分支但依賴庫還停留在舊版本可能出現(xiàn)接口不匹配的編譯錯誤。正確做法是切分支后重新gclient sync讓依賴也跟著對齊。我見過有人手動改了某個依賴庫的代碼結(jié)果 sync 時被覆蓋白改一場。要改依賴得用gclient的DEPS覆蓋機制而不是直接改文件??铀拇疟P inode 耗盡。這個比磁盤空間滿更隱蔽。WebRTC 依賴里有大量小文件可能空間還剩不少但 inode 用完了表現(xiàn)為“無法創(chuàng)建文件”但df -h看著還有空間。用df -i檢查 inode 使用率清理一些無用的小文件目錄即可。6.3 加速編譯的實用技巧編譯慢是常態(tài)但有些辦法能緩解。首先是ccache它能緩存編譯結(jié)果二次編譯時大幅提速。安裝后在 GN 參數(shù)里加gn gen out/Release --args... cc_wrapper\ccache\第一次編譯還是慢但后續(xù)改動少量文件重新編譯時速度提升非常明顯。其次是合理設(shè)置-j并行度別盲目拉滿OOM 一次重來的時間成本更高。最后是只編譯你需要的目標而不是ninja -C out/Release全量編譯。比如你只需要webrtc這個庫目標ninja -C out/Release webrtc這樣能跳過示例、測試等無關(guān)目標省下不少時間。7. 產(chǎn)物集成與后續(xù)維護的一點經(jīng)驗編譯出庫只是第一步真正把它集成到項目里還有一段路。頭文件路徑這塊WebRTC 的 include 結(jié)構(gòu)比較深建議在構(gòu)建系統(tǒng)里把src目錄整體加入 include path然后用api/...、pc/...這樣的相對路徑引用和官方示例保持一致減少路徑混亂。鏈接順序也有講究。靜態(tài)庫之間有依賴關(guān)系被依賴的庫要放在后面。如果鏈接報未定義符號先調(diào)整庫的順序試試很多時候順序?qū)α朔柧驼业搅恕嵲诟悴欢ㄓ?-start-group和--end-group把一組庫包起來讓鏈接器自己解析雖然會慢一點但省心。后續(xù)維護方面WebRTC 更新頻繁建議把編譯流程腳本化把依賴安裝、源碼同步、GN 配置、編譯、產(chǎn)物收集寫成腳本每次更新只需改分支號重跑。我自己的做法是維護一個build.sh里面把參數(shù)集中定義在開頭換版本時只改幾個變量。這樣既避免了手動操作的遺漏也方便在 CI 環(huán)境里復現(xiàn)。另外編譯產(chǎn)物建議做版本標記記錄對應的 WebRTC 分支號和 GN 參數(shù)不然過幾個月你根本記不清這個庫是用什么配置編出來的。我一般會在產(chǎn)物目錄放一個build_info.txt把git rev-parse HEAD的輸出和完整 GN 參數(shù)寫進去排查問題時非常有用。最后分享一個我踩過的坑有次編譯出來的庫在開發(fā)機上跑得好好的部署到另一臺機器上就崩潰。查了半天發(fā)現(xiàn)是兩臺機器的 glibc 版本不一致編譯機版本高運行機版本低。解決辦法是在較低版本的系統(tǒng)上編譯或者用容器固定編譯環(huán)境。這個教訓讓我后來所有涉及分發(fā)的編譯都在容器里做環(huán)境完全可控省去了大量“在我機器上是好的”式扯皮。