本文由QQ音視頻團(tuán)隊(duì)賀坤分享原題“Linux QQ能打語(yǔ)音視頻了!一文詳解背后技術(shù)實(shí)現(xiàn)!”,下文進(jìn)行了排版和內(nèi)容優(yōu)化等。
1、引言
2024年6月6日,QQ For Linux 3.2.9 正式支持了音視頻通話功能,這是 QQ Linux 版本的又一個(gè)里程碑事件。 2024 年,QQ 音視頻正式推出 NTRTC,全平臺(tái)(iOS/Android/MacOS/Windows/Linux)的支持是 NTRTC 的重要特性之一,本次 Linux 平臺(tái)的適配也是這次升級(jí)過(guò)程中重要的一環(huán)。
本文詳細(xì)記錄了新版QQ音視頻通話在 Linux 平臺(tái)適配開發(fā)過(guò)程中的技術(shù)方案與實(shí)現(xiàn)細(xì)節(jié),希望能幫助大家理解在 Linux 平臺(tái)從 0 到 1 實(shí)現(xiàn)音視頻通話能力的過(guò)程。
2、系列文章
本文是系列文章中的第12 篇,本系列總目錄如下:
《IM跨平臺(tái)技術(shù)學(xué)習(xí)(一):快速了解新一代跨平臺(tái)桌面技術(shù)——Electron》
《IM跨平臺(tái)技術(shù)學(xué)習(xí)(二):Electron初體驗(yàn)(快速開始、跨進(jìn)程通信、打包、踩坑等)》
《IM跨平臺(tái)技術(shù)學(xué)習(xí)(三):vivo的Electron技術(shù)棧選型、全方位實(shí)踐總結(jié)》
《IM跨平臺(tái)技術(shù)學(xué)習(xí)(四):蘑菇街基于Electron開發(fā)IM客戶端的技術(shù)實(shí)踐》
《IM跨平臺(tái)技術(shù)學(xué)習(xí)(五):融云基于Electron的IM跨平臺(tái)SDK改造實(shí)踐總結(jié)》
《IM跨平臺(tái)技術(shù)學(xué)習(xí)(六):網(wǎng)易云信基于Electron的IM消息全文檢索技術(shù)實(shí)踐》
《IM跨平臺(tái)技術(shù)學(xué)習(xí)(七):得物基于Electron開發(fā)客服IM桌面端的技術(shù)實(shí)踐》
《IM跨平臺(tái)技術(shù)學(xué)習(xí)(八):新QQ桌面版為何選擇Electron作為跨端框架》
《IM跨平臺(tái)技術(shù)學(xué)習(xí)(九):全面解密新QQ桌面版的Electron內(nèi)存占用優(yōu)化》
《IM跨平臺(tái)技術(shù)學(xué)習(xí)(十):快速選型跨平臺(tái)框架Electron、Flutter、Tauri、React Native等》
《IM跨平臺(tái)技術(shù)學(xué)習(xí)(十一):環(huán)信基于Electron打包WebIM桌面端的技術(shù)實(shí)踐》
《IM跨平臺(tái)技術(shù)學(xué)習(xí)(十二):萬(wàn)字長(zhǎng)文詳解QQ Linux端實(shí)時(shí)音視頻背后的跨平臺(tái)實(shí)踐》(* 本文)
3、技術(shù)背景
隨著新版 QQ 桌面端的上線,在網(wǎng)上得到了廣泛的討論,尤其 QQ For Linux 3.0 推出后,比之前的 Linux 版本有了突破性的改變。
QQ For Linux 3.1 還不支持語(yǔ)音、視頻通話,音視頻通話作為基礎(chǔ)能力之一,適配 Linux 平臺(tái),這將是一個(gè)從0-1的過(guò)程,非常值得期待。
QQ 的音視頻通話能力是基于 AVSDK,在過(guò)去3年中,我們持續(xù)對(duì) AVSDK 進(jìn)行基礎(chǔ)架構(gòu)重構(gòu),更新底層基礎(chǔ)庫(kù), 對(duì) AVSDK 持續(xù)優(yōu)化。
在2024年上半年,QQ 音視頻正式推出 NTRTC,全平臺(tái)(iOS/Android/MacOS/Windows/Linux)的支持是 NTRTC 的重要特性之一,本次 Linux 平臺(tái)的適配也是這次升級(jí)過(guò)程中重要的一環(huán)。
Linux 平臺(tái)上的適配對(duì)我們來(lái)說(shuō)是一個(gè)挑戰(zhàn)。
一個(gè)全新的平臺(tái),從以下思路開展:
- 1)我們要對(duì) Linux 平臺(tái)有個(gè)調(diào)研,包括平臺(tái)信息、開發(fā)環(huán)境等;
- 2)針對(duì) SDK 進(jìn)行編譯適配,這將涉及到所有的代碼跟依賴庫(kù);
- 3)平臺(tái)媒體層適配,視頻、音頻鏈路的采集、渲染、編解碼等;
- 4)新增終端的通話業(yè)務(wù)適配,這包括前后端的邏輯,比如新增的終端類型,通話流控控制等;
- 5)發(fā)布部署等,如流水線搭建,版本管理。
那么我們開始!
4、Linux平臺(tái)介紹
Linux 內(nèi)核最初只是由芬蘭人林納斯·托瓦茲(Linus Torvalds)在赫爾辛基大學(xué)上學(xué)時(shí)出于個(gè)人愛(ài)好而編寫的。
Linux 是一套免費(fèi)使用和自由傳播的類 Unix 操作系統(tǒng),是一個(gè)基于 POSIX 和 UNIX 的多用戶、多任務(wù)、支持多線程和多 CPU 的操作系統(tǒng)。
5、主流Linux發(fā)行版
Linux 發(fā)行版是由 Linux 內(nèi)核以及各種軟件和工具組成的完整操作系統(tǒng)。由于 Linux 的開源特性,任何人都可以創(chuàng)建自己的 Linux 發(fā)行版。因此,目前存在著數(shù)百種不同的 Linux 發(fā)行版,每種發(fā)行版都有其特定的目標(biāo)用戶和用途。
以下是一些較為知名的 Linux 發(fā)行版:
目前市面上較知名的發(fā)行版有:Ubuntu、RedHat、CentOS、Debian、Fedora、SuSE、OpenSUSE、Arch Linux、SolusOS、Kylin(麒麟),UOS(統(tǒng)信) ,還有騰訊開源的 OpenCloud OS。
每個(gè) Linux 發(fā)行版都有其特點(diǎn)和優(yōu)勢(shì),用戶可以根據(jù)自己的需求和偏好來(lái)選擇適合自己的發(fā)行版。
本次適配也就是在上述的 Linux 發(fā)行版本上開發(fā)可運(yùn)行的軟件。
6、開發(fā)之前的補(bǔ)課
在做開發(fā)前,我們要了解的信息有:開發(fā)環(huán)境、用戶運(yùn)行環(huán)境,除了要確定 Linux 發(fā)行版(后面都統(tǒng)一使用 Linux 系統(tǒng)版本代替),還要考慮到硬件信息,比如不同 CPU 架構(gòu),GPU 信息。
6.1運(yùn)行環(huán)境
主流的 Linux 操作系統(tǒng):Ubuntu、Redhat、Debian、Fedora、Kylin、UOS。
系統(tǒng)架構(gòu):x64、arm64、loong64、mips64el。
通過(guò)新桌面 QQ Linux 版本的分布數(shù)據(jù),我們會(huì)優(yōu)先適配 x64、arm64。
6.2安裝包(可執(zhí)行文件)
這個(gè)很好理解,比如軟件包,腳本等可運(yùn)行的軟件。
Linux 系統(tǒng)中的軟件通常通過(guò)軟件包的形式進(jìn)行分發(fā)和安裝。軟件包包含了軟件的可執(zhí)行文件、庫(kù)文件、配置文件等,以及一些元數(shù)據(jù),如軟件的版本、依賴關(guān)系等。
不同的 Linux 發(fā)行版可能使用不同的軟件包管理系統(tǒng),因此軟件包的類型也會(huì)有所不同。
以下是一些常見的 Linux 發(fā)行版和它們的軟件包類型:
- 1)Debian、Ubuntu、Linux Mint:這些基于 Debian 的發(fā)行版通常使用 .deb 格式的軟件包,可以通過(guò) dpkg 命令直接安裝,也可以通過(guò) apt 或 apt-get 命令進(jìn)行包管理;
- 2)Fedora、CentOS、Red Hat:這些發(fā)行版使用 .rpm 格式的軟件包,可以通過(guò) rpm 命令直接安裝,也可以通過(guò) yum 或 dnf 命令進(jìn)行包管理;
- 3)Arch Linux、Manjaro:這些發(fā)行版使用 .pkg.tar.xz 格式的軟件包,可以通過(guò) pacman 命令進(jìn)行包管理;
- 4)Gentoo:Gentoo 使用的是源代碼包,用戶可以通過(guò) emerge 命令進(jìn)行包管理;
- 5)Slackware:Slackware 使用 .tgz 或 .txz 格式的軟件包,可以通過(guò) pkgtool 命令進(jìn)行包管理。
以上只是一些常見的例子,實(shí)際上還有許多其他的 Linux 發(fā)行版和軟件包格式。
此外,一些通用的軟件包格式,如 AppImage、Flatpak 和 Snap,也可以在大多數(shù) Linux 發(fā)行版上使用。
我們以桌面版本 QQ 為例,分別打包了 deb、rpm、AppImage 的軟件包格式。
6.3靜態(tài)庫(kù)、動(dòng)態(tài)庫(kù)
在 SDK 開發(fā)中,我們交付的會(huì)根據(jù)不同平臺(tái),App 不同的使用方式提供 SDK 產(chǎn)物,也就是靜態(tài)庫(kù)或者動(dòng)態(tài)庫(kù)。
例如:
- 1)Unix:qav_ntrtc_sdk.a 的靜態(tài)庫(kù)和 qav_ntrtc_sdk.so 的動(dòng)態(tài)庫(kù);
- 2)Windows :qav_ntrtc_sdk.lib 的靜態(tài)庫(kù)和 qav_ntrtc_sdk.dll 的動(dòng)態(tài)庫(kù);
- 3)macOS:qav_ntrtc_sdk.a 的靜態(tài)庫(kù)和 qav_ntrtc_sdk.dylib 的動(dòng)態(tài)庫(kù)。
這些只是常見的命名約定,實(shí)際上,庫(kù)文件的命名可能會(huì)因編譯器、開發(fā)環(huán)境和開發(fā)者的選擇而有所不同。
這個(gè)比較重要,因?yàn)樽鳛?sdk 提供方,需要對(duì)不同交付的產(chǎn)物有明確的了解,sdk 也會(huì)根據(jù)使用方案提供不同的產(chǎn)物。
6.4開發(fā)環(huán)境
上面提到的不同 Linux 發(fā)行版本,這次開發(fā)申請(qǐng)了一臺(tái) PC 機(jī)(x64),安裝了 TLinux(Ubuntu 20.04.6)。
主開發(fā)機(jī)使用一臺(tái) x64 的真機(jī) Ubuntu20,arm64 架構(gòu)則使用 M1 Pro 搭建虛擬機(jī)環(huán)境(VM ware/UTM)Ubuntu20 來(lái)輔助開發(fā)調(diào)試。
其他驗(yàn)證環(huán)境:
6.5環(huán)境工具
比如:
- 1)編譯依賴:CMake、GCC、Clang(最后會(huì)切到 Clang)、ar 等;
- 2)開發(fā)工具:VSCode、Clion、git、apt 等;
- 3)開發(fā)環(huán)境基本上準(zhǔn)備好了,比如 apt 安裝各個(gè)依賴的 dev 庫(kù),編譯工具、調(diào)試環(huán)境配置等。
6.6跨平臺(tái)開發(fā)架構(gòu)
我們?cè)谄渌脚_(tái)都通過(guò)音視頻自回環(huán) Demo 可以快速且輕量模擬音視頻通話場(chǎng)景,驗(yàn)證功能,同樣在 Linux 平臺(tái)我們也通過(guò)建立輕量 Demo 來(lái)快速驗(yàn)證該平臺(tái)的各項(xiàng)能力。
我們對(duì) Linux 平臺(tái)下可用的 GUI 開發(fā)框架做了個(gè)調(diào)研,對(duì)比了接入效率,最后選擇了 QT 開發(fā)框架。
6.7NTRTC 自回環(huán)的 demo
我們基于 QT6 實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的 Demo,通過(guò)自回環(huán)的方式,驗(yàn)證音頻、視頻、傳輸通道等能力。
開發(fā)環(huán)境:QTLinux6.6.1, Qtcreator。
基于這個(gè) Demo,我們可以提前在 Linux 平臺(tái)驗(yàn)證音頻、視頻編解碼能力。
從平臺(tái)知識(shí)到開發(fā)環(huán)境基本上準(zhǔn)備差不多了,接下來(lái)先介紹下桌面端音視頻通話的的實(shí)現(xiàn)方案。
6.8桌面版本 QQ 音視頻通話方案
1)QQ(Electron) + PPAPI:
新桌面 QQ 版本是基于 electron 進(jìn)行開發(fā)的(詳見《新QQ桌面版為何選擇Electron作為跨端框架》),對(duì)于 electron 的介紹可以直接看官網(wǎng)。
electron 內(nèi)置了一個(gè) chromium 內(nèi)核,新桌面 QQ 音視頻通話就是基于 Pepper Plugin(PPAPI)方案實(shí)現(xiàn)的,這里簡(jiǎn)單對(duì) PPAPI 組件做個(gè)介紹。
PPAPI 組件可以通過(guò)平臺(tái)動(dòng)態(tài)庫(kù)的形式(Windows 下為 dll 文件,Linux 下是 so 文件, Mac 下是 dyllib 文件)由瀏覽器直接加載,比如內(nèi)置的 Flash 組件、Pdf組件,或者通過(guò)指定命令行參數(shù) --register-pepper-plugins 來(lái)加載,比如:chrome --register-pepper-plugins="D:\\ppapi\\ppapi_example_gles2.dll;application/x-ppapi-example-gles2" D:\\ppapi\\gles2.html。可信的 PPAPI 組件以平臺(tái)動(dòng)態(tài)庫(kù)的形式存在,所以一般 Chrome 沙箱內(nèi)允許的 API(比如 CreateThread)都可以調(diào)用。
Chromium 插件(Plugin)機(jī)制:https://blog.csdn.net/Luoshengyang/article/details/52665318。
通過(guò)了解 PPAPI Plugin 我們可以了解到兩個(gè)關(guān)鍵的點(diǎn):
- 1)進(jìn)程是通過(guò) IPC 進(jìn)行通訊的;
- 2)Plugin 有沙箱機(jī)制(這里是重點(diǎn),后面有坑);
2)AVSDK Plugin 注冊(cè):
我們看下 AVSDKPlugin 的動(dòng)態(tài)庫(kù)是如何注冊(cè)的:
- 1)不同平臺(tái)區(qū)獲取對(duì)應(yīng)的動(dòng)態(tài)庫(kù);
- 2)通過(guò) register-pepper-plugins 注冊(cè)到 electron app。
音視頻通話相當(dāng)于創(chuàng)建一個(gè)瀏覽器窗口,同時(shí)會(huì)拉起這個(gè)對(duì)應(yīng)注冊(cè)的Plugin,具體加載 Plugin 過(guò)程這里不做過(guò)多討論,可以看這篇文章 Chromium 插件(Plugin)模塊(Module)加載過(guò)程。
7、NTRTC-SDK For Linux 工程
適配前,我們先看一下音視頻 AVSDKPlugin 框架。
可以看到這個(gè) AVSDKPlugin 實(shí)際上就是一個(gè) PPAPI Plugin 倉(cāng)庫(kù),它集合了 NTRTC、GroupVideo、BroadCast-Core 等 SDK,通過(guò) Wrapp 層將它們串聯(lián)起來(lái),在包裝成 PPAPI Plugin 實(shí)例對(duì)外提供音視頻通話能力,直播能力。
對(duì)外提供的產(chǎn)物:可執(zhí)行文件,資源文件,內(nèi)置依賴庫(kù)。
8、工程適配
受益于之前 CMake 的統(tǒng)一構(gòu)建, QQ NT 的跨平臺(tái)重構(gòu)之旅-音視頻全平臺(tái)構(gòu)建統(tǒng)一 本次對(duì) Linux 平臺(tái)的編譯適配工作也順利很多。
主要處理下面幾個(gè)事項(xiàng):
- 1)CMake 相關(guān)針對(duì) Linux 平臺(tái)增加一些平臺(tái)邏輯,比如關(guān)閉某些編譯特性,或者平臺(tái)文件僅在 Linux 環(huán)境下編譯;
- 2)業(yè)務(wù)邏輯適配,比如新增的平臺(tái) Type 兼容,平臺(tái)基本信息等;
- 3)缺失的一些實(shí)現(xiàn);
- 4)Linux 平臺(tái)下,各個(gè)第三方依賴庫(kù)的編譯,如視頻編解碼;
例如CMake 平臺(tái)宏差異,可以增加不通的特性選項(xiàng):
if(WIN32)
# 設(shè)置 Windows 平臺(tái)的特定選項(xiàng)
elseif(UNIX AND NOT APPLE AND NOT ANDROID)
# 設(shè)置 Linux 平臺(tái)的特定選項(xiàng)
elseif(APPLE)
# 設(shè)置 macOS 平臺(tái)的特定選項(xiàng)
endif()
BroadCast-Core 等其他依賴庫(kù)CMake 工程化:通過(guò)修復(fù)編譯問(wèn)題,或者重新編譯需要的架構(gòu)版本,過(guò)程中遇到了無(wú)源碼的情況,或者找不到源碼,那么只能通過(guò)屏蔽相關(guān)能力,或者移除該能力來(lái)解決。
9、工程編譯和Demo
最開始編譯使用的是 gcc 11.4.0,gcc 已經(jīng)滿足編譯需求。
遇到的編譯問(wèn)題:
- 1)有源碼的,解決編譯報(bào)錯(cuò)問(wèn)題即可,主要體現(xiàn)在頭文件沒(méi)有引用,或者缺對(duì)應(yīng)的實(shí)現(xiàn);
- 2)無(wú)源碼的第三方庫(kù),也就是該平臺(tái)下沒(méi)有對(duì)應(yīng)架構(gòu)的庫(kù),需要整體重新編譯即可;
- 3)fPIC 問(wèn)題,編解碼庫(kù) link 到動(dòng)態(tài)庫(kù)時(shí)出現(xiàn) fPIC 錯(cuò)誤。
/usr/bin/ld: ../../../qav_rtc_sdk/av_engine/android_ios_mac/Lib/Linux/x86_64/libTcH264Enc.a(cabac-a.asm.o): relocation R_X86_64_PC32 against symbol `g_kuiCabacRangeLps' can not be used when making a shared object; recompile with -fPIC
H264 編碼和解碼庫(kù)在鏈接時(shí)報(bào) fPIC 的問(wèn)題,增加 -Bsymbolic 鏈接,關(guān)閉動(dòng)態(tài)庫(kù) so 中默認(rèn)的符號(hào)搶占方式,來(lái)繞過(guò) fPIC 的檢查。
合并凈態(tài)庫(kù):
在輸出 avsdk 靜態(tài)庫(kù)時(shí),一般都會(huì)將各個(gè)子庫(kù)進(jìn)行合并,生成一個(gè)最終 qav_rtc_sdk.a,在 Linux 下沒(méi)有類似 libtool、libexe 等工具,不過(guò)有個(gè) ar 工具,可以達(dá)到合并的效果。
- 1)通過(guò) ar x 提取靜態(tài)庫(kù)的所有.o文件;
- 2)在通過(guò) ar crs 合并所有的.o 文件;
- 3)通過(guò) ranlib 生成新的靜態(tài)庫(kù)索引。
但是合并后出現(xiàn)了問(wèn)題,合并后,link 到 demo 時(shí)報(bào)錯(cuò),符號(hào)缺失?符號(hào)丟了!但是通過(guò) nm 查看子庫(kù)的符號(hào)都是全的。
1)不同靜態(tài)庫(kù),相同命名的.o:
經(jīng)過(guò)排查,發(fā)現(xiàn)使用 ar x 命令提取文件時(shí),如果歸檔文件中存在多個(gè)同名文件,ar 會(huì)提取找到的第一個(gè)匹配項(xiàng),這里一個(gè)庫(kù)的內(nèi)容出現(xiàn)相同的 .o 情況時(shí),會(huì)出現(xiàn)覆蓋問(wèn)題,這里暫時(shí)沒(méi)有好的 ar 可選項(xiàng)能快速解決這個(gè)問(wèn)題的。
解決方法:那就通過(guò)邏輯解決,提取時(shí),每個(gè)庫(kù)都復(fù)制到獨(dú)立臨時(shí)目錄,待歸檔目錄內(nèi)遇到重復(fù)命名的 .o 文件時(shí),重命名這個(gè) .o, 防止同名覆蓋。
這個(gè)錯(cuò)誤時(shí)機(jī)上是 ar 提取文件時(shí),復(fù)制到待合并文件夾時(shí)環(huán)節(jié)出現(xiàn)的,是不同的靜態(tài)庫(kù)有相同命名的 .o 文件,通過(guò)重命名,還比較好解決;
2)同一個(gè)靜態(tài)庫(kù),相同命名的 .o:
解決了 .o 覆蓋的問(wèn)題,再次 link,還是缺失符號(hào),通過(guò)排查還是丟了對(duì)應(yīng)的符號(hào),再次排查哪一步丟的,我們發(fā)現(xiàn)一個(gè)靜態(tài)庫(kù)內(nèi)出現(xiàn)相同命名的 .o 符號(hào)段,兩個(gè)符號(hào)段在不同位置,ar x 提取時(shí),會(huì)優(yōu)先命中第一個(gè)搜索到的 .o 段,后面遇到的都會(huì)忽略,這就棘手了,是工具提取環(huán)節(jié)出現(xiàn)的丟失,排查了一些 ar 選項(xiàng)沒(méi)有解決;
解決方法:通過(guò)修改該靜態(tài)庫(kù)內(nèi)相同源文件命名解決。
3)Demo Link & Demo Run:
經(jīng)過(guò)上面2個(gè)方面的適配,解決一系列l(wèi)ink問(wèn)題后,較順利的輸出了 x64 版本的 qav_rtc_sdk.a。
我們通過(guò)之前提到的 qt_demo, 進(jìn)行 link 驗(yàn)證,也沒(méi)有問(wèn)題,自回環(huán)的邏輯也正常跑起來(lái),基于 QT 開發(fā)環(huán)境也可以正常調(diào)試,此時(shí)音頻、視頻能力可以先開始驗(yàn)證。
4)AVSDKPlugin & electron demo:
我們輸出了 x64 版本的 AVSDKPlugin.so,搭建了一個(gè) electron demo,用于驗(yàn)證我們的動(dòng)態(tài)庫(kù)是否可以正常運(yùn)行;
這里需要在 Linux 安裝 electron 環(huán)境,具體看 Electron Quick Start。
然后我們遇到了第一個(gè)問(wèn)題:動(dòng)態(tài)庫(kù)拉不起來(lái)!
錯(cuò)誤信息:182204.991288: ERROR:ppapi_thread.cc(269) Failed to load Pepper module from ~/robert/AVSDKPluginDemo/app/avsdk/libAVSDKPlugin.so(error cannot open shared object file: Operation not permitted)。
通過(guò)錯(cuò)誤信息,我們大致能看出來(lái)是權(quán)限問(wèn)題,首先通過(guò)確認(rèn),排除了 so 文件路徑錯(cuò)誤的問(wèn)題,那就是權(quán)限問(wèn)題。
還記得上面介紹 pepper plugin 時(shí)的沙箱問(wèn)題嗎,沒(méi)錯(cuò),就是這個(gè),electron app 默認(rèn)是開啟沙箱模式的,也就是說(shuō) app 住進(jìn)程是開啟沙箱的,住進(jìn)程通過(guò) fork 方式拉起的進(jìn)程都會(huì)帶沙箱模式。
既然知道了什么原因,我們暫時(shí)先關(guān)閉 electron app 的沙箱模式,后面這個(gè)問(wèn)題通過(guò)修改 electron 源碼來(lái)解決。
運(yùn)行 Electron Demo,Electron 新創(chuàng)建了一個(gè)瀏覽器窗口,并且通過(guò) Pepper Plugin 方式,拉起音視頻進(jìn)程加載了 AVSDKPlugin.so,well done!路通了。
10、工程調(diào)試
QT Demo Debug。
首先我們通過(guò) QT 開發(fā)環(huán)境對(duì)運(yùn)行的 demo app 直接進(jìn)行調(diào)試。
demo link 了 qav_ntrtc_sdk.a , 使用了 xpstl::list 做了一些操作。
通過(guò)斷點(diǎn),我們可以方便的進(jìn)行調(diào)試,這是基于直接運(yùn)行 app 做的操作;
那么如何調(diào)試 electron app + plugin 拉起的 AVSDKPlugin.so 呢?
此時(shí)有經(jīng)驗(yàn)的同學(xué)會(huì)想到掛載進(jìn)程調(diào)試,沒(méi)錯(cuò),我們此時(shí)也可以通過(guò)掛載進(jìn)程調(diào)試正在運(yùn)行的音視頻進(jìn)程。
音視頻進(jìn)程 AVSDKPlugin.so 調(diào)試(CLion 掛載進(jìn)程調(diào)試):
- 1)打開 CLion->Run->Attach To Process>選擇對(duì)應(yīng)進(jìn)程,確定;
- 2)調(diào)試:正常在 CLion 打斷點(diǎn)即可;
- 3)注意:需要是 Debug 版本的動(dòng)態(tài)庫(kù)。
demo 拉起 avsdk 各個(gè)線程:
通過(guò) log,我們也可以看到輸出:
【問(wèn)題】Linux 掛載進(jìn)程失敗,提示沒(méi)權(quán)限:
這里是 Linux 系統(tǒng)有個(gè)權(quán)限問(wèn)題,按 GPT 給出的解決方案,修改一下重啟電腦生效。
11、 GLIBC、GLIBCXX 運(yùn)行依賴
GLIBC 和 GLIBC++ 是兩個(gè)不同的庫(kù),它們?cè)?Linux 系統(tǒng)中扮演著重要的角色。
1)GLIBC:
GLIBC,全稱 GNU C Library,是 GNU 項(xiàng)目的 C 標(biāo)準(zhǔn)庫(kù)的實(shí)現(xiàn),為系統(tǒng)和應(yīng)用程序提供了系統(tǒng)調(diào)用的封裝和許多基本的程序接口。這包括輸入輸出(I/O)、字符串處理、文件操作、內(nèi)存管理、數(shù)學(xué)計(jì)算等。GLIBC 是大多數(shù)基于 Linux 的系統(tǒng)的標(biāo)準(zhǔn) C 庫(kù),并且是編譯大多數(shù) C 程序的必要組件。
GLIBC 的版本很重要,因?yàn)椴煌膽?yīng)用程序可能需要不同版本的 GLIBC。例如,一個(gè)用較新版本的 GLIBC 編譯的程序可能無(wú)法在只有較舊版本 GLIBC 的系統(tǒng)上運(yùn)行。
2)GLIBC++:
GLIBC++ 是 GNU libstdc++ 庫(kù)的常見稱呼,它是 C++ 標(biāo)準(zhǔn)庫(kù)的 GNU 實(shí)現(xiàn)。它提供了 C++ 程序所需的標(biāo)準(zhǔn)功能,包括輸入輸出流(iostream)、數(shù)據(jù)結(jié)構(gòu)(如 STL 容器)、算法、字符串處理等。當(dāng)你編譯 C++ 程序時(shí),通常需要鏈接到 libstdc++ 庫(kù)。
與 GLIBC 類似,不同版本的 GNU libstdc++ 支持不同版本的 C++ 標(biāo)準(zhǔn)。例如,較新版本的 libstdc++ 支持 C++11、C++14、C++17 和 C++20 的新特性。
版本查詢和兼容性,在 Linux 系統(tǒng)中,你可以通過(guò)運(yùn)行以下命令來(lái)查詢 GLIBC 和 GLIBC++ 的版本。
對(duì)于 GLIBC,可以使用 `ldd --version` 或 `libc.so.6` 文件來(lái)查詢:
ldd --version
# 或者
/lib/x86_64-linux-gnu/libc.so.6
對(duì)于 GLIBC++,可以通過(guò)檢查 libstdc++ 庫(kù)的版本來(lái)查詢:
1strings /usr/lib/x86_64-linux-gnu/libstdc++.so.6 | grep GLIBCXX
兼容性通常是向后的,這意味著用舊版本的 GLIBC 或 GLIBC++ 編譯的程序應(yīng)該能在有較新版本庫(kù)的系統(tǒng)上運(yùn)行。然而,反過(guò)來(lái)通常不行,因?yàn)榕f版本的庫(kù)不包含新版本中引入的符號(hào)和功能。
在輸出我們編譯好的 AVSDKPlugin 后,在 Ubuntu20、22上正常運(yùn)行起來(lái),但是我們發(fā)現(xiàn)。
AVSDKPlugin.so 放到不同 Linux 版本上運(yùn)行時(shí),比如 Ubuntu 18、Fedora 23、Qlin 等系統(tǒng)上,發(fā)現(xiàn)音視頻拉不起來(lái)?
通過(guò)ldd AVSDKPlugin.so 我們發(fā)現(xiàn)出現(xiàn)一些依賴庫(kù) no found, 或者 GLIBC need 2.29等錯(cuò)誤信息。
這個(gè)是 Ubuntu 18(x64)的報(bào)錯(cuò):
robert@ubuntu:~/.config/QQ/global/ext_lib/avsdk$ ldd libAVSDKPlugin.so
./libAVSDKPlugin.so: /lib/x86_64-linux-gnu/libstdc++.so.6: version `GLIBCXX_3.4.29' not found (required by ./libAVSDKPlugin.so)
./libAVSDKPlugin.so: /lib/x86_64-linux-gnu/libstdc++.so.6: version `CXXABI_1.3.13' not found (required by ./libAVSDKPlugin.so)
在 KylinOS(麒麟) arm64 系統(tǒng)錯(cuò)誤信息。
表明我們依賴的庫(kù)使用了較高版本的 GLIBC 編譯,在低 GLIBC 版本的系統(tǒng)上無(wú)法運(yùn)行!
我們要確定兩個(gè)信息:
- 1)編譯時(shí)使用的 GUN C Library(libc.so) 支持的 GLIBC 版本;
- 2)運(yùn)行環(huán)境的 libc.so 支持的 GLIBC 版本;
要滿足 編譯輸出的產(chǎn)物依賴的 GLIBC 版本,小于運(yùn)行環(huán)境的 libc 支持的 GLIBC 版本,才能正常運(yùn)行。
查看一下我們依賴的 GLIBC 版本,終端輸入:
strings libAVSDKPlugin.so | grep GLIBC
GLIBC_2.3
GLIBC_2.3.3
GLIBC_2.27
GLIBC_2.29
GLIBC_2.2.5
... 省略
GLIBC_2.17
GLIBC_2.4
GLIBC_2.3.2
GLIBC_2.7
GLIBC_2.12
通過(guò)輸出的信息,我們知道我們?cè)?Ubuntu 20,x64 環(huán)境,使用 GCC 10.5 編譯輸出的產(chǎn)物,最低支持 GLIBC2.29, 也就是運(yùn)行環(huán)境需要有 GLIBC 2.29,但上面 Ubuntu18、跟 KylinOS 環(huán)境的 GLIBC 版本都太低了,無(wú)法運(yùn)行我們的動(dòng)態(tài)庫(kù),那怎么辦呢?
上面提到了,avsdk、avsdkplugin 都是使用 gcc11.4 進(jìn)行編譯的,使用的系統(tǒng)是 Ubuntu20。
我們通過(guò) strings /usr/lib/x86_64-linux-gnu/libc.so.6 | grep GLIBC來(lái)查看 GLIBC 的版本信息。
robert@robert-LC0:~$ strings /usr/lib/x86_64-linux-gnu/libc.so.6 | grep GLIBC
GLIBC_2.2.5
GLIBC_2.2.6
GLIBC_2.3
...省略
GLIBC_2.17
...省略
GLIBC_2.27
GLIBC_2.28
GLIBC_2.29
GLIBC_2.30
GLIBC_PRIVATE
GNU C Library (Ubuntu GLIBC 2.31-0ubuntu9.14) stable release version 2.31.
而上面運(yùn)行環(huán)境沒(méi)有達(dá)到 AVSDKPlugin 依賴的 GLIBC 需要支持2.29,我們編譯使用的 libc++ 版本太高了,那就就要想辦法降級(jí)。
3)GCC 10.5:
我們想到的是通過(guò)降低編譯工具版本來(lái)解決,我們嘗試使用 gcc 10.5,修復(fù)了一些編譯問(wèn)題,輸出的產(chǎn)物還是依賴較高的 GLIBC 版本,我們通過(guò)排查接口,發(fā)現(xiàn)是數(shù)學(xué)庫(kù)的一些相關(guān)調(diào)用。
1./libAVSDKPlugin.so: /lib/x86_64-linux-gnu/libm.so.6: version `GLIBC_2.29' not found (required by ./libAVSDKPlugin.so)
雖然降級(jí)了編譯工具版本,但實(shí)際上 link 的還是當(dāng)前系統(tǒng)目錄的 libc, 或者 libm。
一般這種情況,我們就要通過(guò)使用低版本的編譯工具鏈(使用指定的低版本的庫(kù))。
通用的做法就是準(zhǔn)備好相關(guān)編譯工具鏈文件,然后通過(guò)自定義依賴庫(kù)搜索路徑來(lái)使用工具鏈的依賴庫(kù)進(jìn)行編譯。
4)構(gòu)建工具鏈:buildtools & Clang:
通過(guò)跟NTKernel的同學(xué)溝通,得知Kernel編譯使用了一套構(gòu)建工具,支持x64、arm64、loong64、mips64el。
使用的編譯器是 Clang,我們嘗試使用該構(gòu)建工具,配置好 toolchan.cmake,在編譯時(shí)發(fā)現(xiàn)缺失了。
5)experiemental::coroutine undefine:
xplatform-ng/xpng/task/coroutine/task.h:31:30: fatal error: use of undeclared identifier 'experimental'
using coroutine_handle = experimental::coroutine_handle<T>;
這里 coroutine 是 c++20 的特性,cmake配置下從 c++17 升級(jí)到 c++20 即。
6)filesystem 相關(guān)符號(hào)缺失:
ld.lld: error: undefined symbol: std::Cr::__fs::filesystem::__file_size(std::Cr::__fs::filesystem::path const&, std::Cr::error_code*)
>>> referenced by operations.h:108 (/home/robert/buildtools/toolchain/../libcxx/include/__filesystem/operations.h:108)
我們發(fā)現(xiàn) std::Cr::__fs::filesystem, 發(fā)現(xiàn)是構(gòu)建工具鏈中沒(méi)有相關(guān)實(shí)現(xiàn)。
需要構(gòu)建工具鏈內(nèi)的 libc++.a 增加 systemfile 的實(shí)現(xiàn)編譯。
可以參考 https://libcxx.llvm.org/BuildingLibcxx.html, 編譯出對(duì)應(yīng)的 filesystem 版本即可。
7)內(nèi)置:
對(duì)于缺失的依賴庫(kù),我們可以內(nèi)置到安裝目錄即可,通過(guò) patchelf 指定搜索目錄,可以設(shè)置搜索路徑查找優(yōu)先級(jí),先搜索自定義目錄,在搜索系統(tǒng)路徑,如下圖所示。
8)提示安裝:
我們嘗試內(nèi)置 OpenGL 庫(kù)解決運(yùn)行環(huán)境 OpenGL 庫(kù)缺失的問(wèn)題,但是通過(guò)測(cè)試下來(lái),在不同的系統(tǒng)環(huán)境運(yùn)行,會(huì)出現(xiàn)各種 OpenGL 兼容性的 crash 問(wèn)題,有些情況通過(guò)運(yùn)行環(huán)境安裝的默認(rèn) OpenGL 是好的。
嘗試過(guò)通過(guò) patchelf 配置搜索路徑優(yōu)先級(jí), 先搜索系統(tǒng)路徑,如:/usr/lib/x86_64-linux-gnu , 在搜索安裝目錄,來(lái)解決。
但這也確實(shí)使用了內(nèi)置 OpenGL 庫(kù),直接 crash,整體體驗(yàn)上更差,還不如早一點(diǎn)檢測(cè)依賴,暴露問(wèn)題,引導(dǎo)用戶安裝。
這個(gè)提示比較粗暴,后續(xù)會(huì)優(yōu)化。
最后針對(duì) Linux 底層庫(kù)的支持,音視頻 GLIBC 低版本支持情況:x64 2.17+, arm64 2.29+ 。
12、 Electron 的修改
12.1概述
electron 的相關(guān)介紹可以去官網(wǎng)看下 Electron。
electron的是一個(gè)開源項(xiàng)目,可以自行編譯 electron 版本來(lái)滿足自己產(chǎn)品的需求。
構(gòu)建可以參考這個(gè) 構(gòu)建 electron。
對(duì)于 electron,qq 桌面端的 electron 實(shí)際上自己編譯的,也做了一些優(yōu)化跟定制,本次 Linux 適配我們也做了一些修改。
沙盒問(wèn)題
chromium 有它自己管理的一套沙盒機(jī)制,在前面我們有提過(guò)。
QQ Electron App 的主進(jìn)程是開啟沙盒的,那么通過(guò)主進(jìn)程 fork 方式拉起來(lái)的進(jìn)程都會(huì)繼承主進(jìn)程的配置。
例:
/opt/QQ/qq --type=renderer --crashpad-handler-pid=5273 --enable-crash-reporter=bc2ad366-d1b0-4f89-8bb4-e34227773324,no_channel --user-data-dir=/home/haier/.config/QQ --standard-schemes=app --secure-schemes=app --bypasscsp-schemes --cors-schemes --fetch-schemes=app --service-worker-schemes --streaming-schemes --app-path=/opt/QQ/resources/app --enable-sandbox --allow-command-line-plugins --force-color-profile=srgb --register-pepper-plugins=/opt/QQ/resources/app/avsdk/libAVSDKPlugin.so;application/x-ppapi-avSDK --js-flags=--expose-gc --disable-gpu-compositing --lang=zh-CN --num-raster-threads=4 --enable-main-frame-before-activation --renderer-client-id=7 --time-ticks-at-unix-epoch=-1713183163571621 --launch-time-ticks=66654460 --shared-files=v8_context_snapshot_data:100 --field-trial-handle=0,i,12981793670346963750,3504886440467676680,262144 --enable-features=kWebSQLAccess --disable-features=SpareRendererForSitePerProcess --variations-seed-version
那么我們要在拉起子進(jìn)程時(shí)不開啟沙盒如何做呢?
/content/browser/child_process_launcher_helper.cc
void ChildProcessLauncherHelper::LaunchOnLauncherThread() {
int launch_result = LAUNCH_RESULT_FAILURE;
absl::optional<base::LaunchOptions> options;
base::LaunchOptions* options_ptr = nullptr;
if (IsUsingLaunchOptions() || GetProcessType() == switches::kPpapiPluginProcess) {
options.emplace();
options_ptr = &*options;
}
/content/browser/child_process_launcher_helper_linux.cc
bool ChildProcessLauncherHelper::BeforeLaunchOnLauncherThread(
PosixFileDescriptorInfo& files_to_register,
base::LaunchOptions* options) {
if (options) {
DCHECK(!GetZygoteForLaunch() || GetProcessType() == switches::kPpapiPluginProcess);
// Convert FD mapping to FileHandleMappingVector
options->fds_to_remap = files_to_register.GetMappingWithIDAdjustment(
base::GlobalDescriptors::kBaseDescriptor);
ChildProcessLauncherHelper::LaunchProcessOnLauncherThread(
*is_synchronous_launch = true;
Process process;
ZygoteCommunication* zygote_handle = GetZygoteForLaunch();
if (zygote_handle && GetProcessType() != switches::kPpapiPluginProcess) {
// TODO(crbug.com/569191): If chrome supported multiple zygotes they could
// be created lazily here, or in the delegate GetZygote() implementations.
// Additionally, the delegate could provide a UseGenericZygote() method.
我們針對(duì) ppapi 進(jìn)程修改,來(lái)關(guān)閉ppapi進(jìn)程的沙盒模式選項(xiàng),讓 ppapi 進(jìn)程不開沙盒模式,當(dāng)然這里可能會(huì)有一些安全隱患,后面看下是否有更好的方案解決。
12.2Crash due to FD ownership
Crashing due to FD ownership violation:
#1 0x5595aafa4eec <unknown>
#0 0x5595aafabe73 <unknown>
#2 0x5595aafa4ea7 close
#3 0x7fc8275dc27b <unknown>
#4 0x7fc82a8e6615 <unknown>
在測(cè)試過(guò)程中,我們發(fā)現(xiàn)通過(guò) electron 拉起的 ppapi plugin 進(jìn)程時(shí),經(jīng)常出現(xiàn)這個(gè) crash,導(dǎo)致音視頻功能經(jīng)常不可用,通過(guò)報(bào)錯(cuò)信息,搜索到一些相關(guān)信息。
https://github.com/electron/electron/pull/40677 具體看算是 electron 的bug,找到推薦修改方式,https://source.chromium.org/chro ... 1822c28c78b7115684f 這里官方的說(shuō)法是重置所有權(quán)。
實(shí)際上通過(guò)代碼排查,我們發(fā)現(xiàn)這個(gè) FD owner 檢查 crash,實(shí)際上是 electron 的一個(gè)特性邏輯,我們?cè)?nbsp;content/app/content_main.cc 看到,electron app 在 Linux 平臺(tái)下是開啟了這個(gè) FD Ownership 檢查的,那這里我們就嘗試將它關(guān)閉,是不是就可以解決了。
通過(guò)修改 electron 源碼,重新編譯 electron,該問(wèn)題得到解決。
12.3electron 相關(guān)技巧編譯
electron app 實(shí)際上就是 chromium 瀏覽器環(huán)境的一個(gè) app,對(duì)于瀏覽器支持的選項(xiàng)大部分都支持,包括一些調(diào)試選項(xiàng)。
在啟動(dòng) electron app 加啟動(dòng)參數(shù)就行,實(shí)際上屬于 web 前端的技術(shù)棧,我找到一個(gè)不錯(cuò)的 blog,頁(yè)面挺好看的。
Chrome瀏覽器啟動(dòng)參數(shù)大全(命令行參數(shù)):https://www.cnblogs.com/gurenyumao/p/14721035.html。
例如開啟更多的 log 信息:(參考鏈接)
#控制臺(tái)啟動(dòng)qq
qq --enable-logging=stderr --v=1
例如使用自己編譯的 electron 版本運(yùn)行 electron app:直接替換可執(zhí)行文件即可,比如 electron demo、qq 等,找到 electron 的可執(zhí)行文件,替換成你的就好。
例如如何 debug electron:掛載進(jìn)程方式,方法通用,跟上面調(diào)試自回環(huán) Demo 類似。
13、平臺(tái)媒體硬件適配
音視頻通話、直播都離不開音頻、視頻,相關(guān)的采集、渲染、編解碼都與平臺(tái)硬件息息相關(guān)。從采集、渲染、編碼、解碼都會(huì)遇到一些問(wèn)題。這里我就適配過(guò)程中,處理的一個(gè)視頻渲染降級(jí)方案做一下分享。
13.1視頻通話渲染方案
我們先來(lái)看一下 Chromium Plugin 執(zhí)行 3D 渲染的過(guò)程 的渲染過(guò)程。
在 Plugin 進(jìn)程中,OpenGL 上下文通過(guò) Graphics3D 類描述。因此,創(chuàng)建 OpenGL 上下文意味著是創(chuàng)建一個(gè) Graphics3D 對(duì)象。這個(gè) Graphics3D 對(duì)象在創(chuàng)建的過(guò)程中,會(huì)調(diào)用 PPB_GRAPHICS_3D_INTERFACE_1_0 接口提供的一個(gè)函數(shù) Create。該函數(shù)又會(huì)通過(guò)一個(gè) APP_ID_RESOURCE_CREATION 接口向 Render 進(jìn)程發(fā)送一個(gè)類型為 PpapiHostMsg_PPBGraphics3D_Create 的 IPC 消息。在 Plugin 進(jìn)程中,APP_ID_RESOURCE_CREATION 接口是通過(guò)一個(gè) ResourceCreationProxy 對(duì)象實(shí)現(xiàn)的,因此,Plugin 進(jìn)程實(shí)際上是通過(guò) ResourceCreationProxy 類向 Render 進(jìn)程發(fā)送一個(gè)類型為 PpapiHostMsg_PPBGraphics3D_Create 的 IPC 消息的。
Plugin 在初始化 OpenGL 環(huán)境的過(guò)程中做的第二件事情就是將剛剛創(chuàng)建出來(lái)的 OpenGL 上下文指定為當(dāng)前要使用的 OpenGL 上下文。這個(gè)過(guò)程稱為 OpenGL 上下文綁定,如下圖所示。
音視頻的渲染實(shí)際就是使用了 PPB_Graphics3D 的渲染方案,通過(guò)共享紋理來(lái)做夸進(jìn)程渲染,在支持硬件加速的情況下。
Win 使用了 ID3D11Device、MacOS 使用了 Metal。
13.2PPB_Graphics3D->Create 失敗問(wèn)題
在開發(fā)過(guò)程中,我們?cè)谝恍┨摂M機(jī)的 Linux 系統(tǒng)上發(fā)現(xiàn)視頻渲染黑屏,通過(guò)排查 Log,我們發(fā)現(xiàn)以下信息。
具體對(duì)應(yīng)到代碼:
PP_Resource graphics =
g_graphics_3d_interface->Create(g_pp_instance, 0, attributes);
if (!graphics){
log = "avsdk output(wrapper): PP_Resource Create fail";
}
發(fā)現(xiàn)這個(gè) PP_Resource(PPB_Graphics3D) 初始化失敗了,這會(huì)導(dǎo)致視頻無(wú)法渲染。
我們知道 Plugin 是通過(guò) ppapi 跟 render 進(jìn)程交互的, 這個(gè)創(chuàng)建過(guò)程實(shí)際就是發(fā)送一個(gè)創(chuàng)建資源 message 到 render 進(jìn)程創(chuàng)建 3D 畫布資源,我們要確定哪一步出錯(cuò)。
13.3排查過(guò)程
1)確認(rèn)環(huán)境、顯卡驅(qū)動(dòng),我們發(fā)現(xiàn)在啟動(dòng) QQ 后,有問(wèn)題的環(huán)境會(huì)輸出一些 warning 信息,跟顯卡驅(qū)動(dòng)相關(guān):
Warning: vkCreateInstance: Found no drivers!
Warning: vkCreateInstance failed with VK_ERROR_INCOMPATIBLE_DRIVER
at CheckVkSuccessImpl (../../third_party/dawn/src/dawn/native/vulkan/VulkanError.cpp:101)
此時(shí)我們通過(guò)啟動(dòng) qq 時(shí)增加:
1qq --enable-logging=stderr --v=1
又多出一些信息:
[9364:0415/181411.892176:ERROR:gl_utils.cc(412)] [.WebGL-0x200029f800]GL Driver Message (OpenGL, Performance, GL_CLOSE_PATH_NV, High): GPU stall due to ReadPixels
[9364:0415/181411.949701:ERROR:gl_utils.cc(412)] [.WebGL-0x200029f800]GL Driver Message (OpenGL, Performance, GL_CLOSE_PATH_NV, High): GPU stall due to ReadPixels
[9364:0415/181411.976514:ERROR:gl_utils.cc(412)] [.WebGL-0x200029f800]GL Driver Message (OpenGL, Performance, GL_CLOSE_PATH_NV, High): GPU stall due to ReadPixels
[9364:0415/181412.027489:ERROR:gl_utils.cc(412)] [.WebGL-0x200029f800]GL Driver Message (OpenGL, Performance, GL_CLOSE_PATH_NV, High): GPU stall due to ReadPixels (this message will no longer repeat)
可以看到在驅(qū)動(dòng)出了一些警告,或者錯(cuò)誤。
2)進(jìn)程啟動(dòng)選項(xiàng)多出的 --disable-gpu-compositing 參數(shù):
我們發(fā)現(xiàn)在有問(wèn)題的環(huán)境,在音視頻進(jìn)程啟動(dòng)時(shí)多了一個(gè)啟動(dòng)選項(xiàng)--disable-gpu-compositing。
通過(guò)排查這個(gè)不是我們業(yè)務(wù)增加的,也就是他是 chromium 通過(guò)當(dāng)前系統(tǒng)環(huán)境自己加的選項(xiàng),這個(gè)參數(shù)的作用是禁用 GPU(圖形處理單元)合成,也就是它直接導(dǎo)致了 PPB_Graphics3D->Create 失敗。
3)electron 源碼分析:
那么--disable-gpu-compositing 是如何添加到啟動(dòng)選項(xiàng)中的?
// Prevent the compositor from using its GPU implementation.
const char kDisableGpuCompositing[] = "disable-gpu-compositing";
可以看到 gpu_data_manager_impl_private.cc 里面的實(shí)現(xiàn),在 IsGpuCompositingDisabledS 時(shí)加了這個(gè) disable-gpu-compositing。
#if BUILDFLAG(IS_ANDROID)
if (browser_cmd.HasSwitch(switches::kDisableGpuCompositing)) {
renderer_cmd->AppendSwitch(switches::kDisableGpuCompositing);
}
#elif !BUILDFLAG(IS_CHROMEOS_ASH)
// If gpu compositing is not being used, tell the renderer at startup. This
// is inherently racey, as it may change while the renderer is being
// launched, but the renderer will hear about the correct state eventually.
// This optimizes the common case to avoid wasted work.
if (GpuDataManagerImpl::GetInstance()->IsGpuCompositingDisabled())
renderer_cmd->AppendSwitch(switches::kDisableGpuCompositing);
#endif // BUILDFLAG(IS_ANDROID)
位于content/brower/gpu/gpu_data_manager_impl.h/.cc GpuDataManagerImpl::GetInstance->IsGpuCompositingDisabled().
bool GpuDataManagerImpl::IsGpuCompositingDisabledForHardwareGpu() const {
base::AutoLock auto_lock(lock_);
return private_->IsGpuCompositingDisabledForHardwareGpu();
}
可以看到實(shí)際訪問(wèn)了一個(gè) private 對(duì)象,它在頭文件這么定義的。
1std::unique_ptr<GpuDataManagerImplPrivate> private_ GUARDED_BY(lock_)
GpuDataManagerImplPrivate位于content/brower/gpu/gpu_data_manager_impl_private.h/.cc
bool GpuDataManagerImplPrivate::IsGpuCompositingDisabled() const {
return disable_gpu_compositing_ || !HardwareAccelerationEnabled();
}
這里看到它通過(guò)兩個(gè)變量來(lái)決定是否關(guān)閉了 gpu 加速 disable_gpu_compositing_ 與 HardwareAccelerationEnabled() 變量不開啟 gpu 加速 或者 硬件不支持 gpu 加速, 這里都返回 false,啟動(dòng)插件進(jìn)程的cmd就會(huì)加上--disable-gpu-compositing。
那么 disable_gpu_compositing_邏輯 ,默認(rèn)是 false, 默認(rèn)會(huì)開啟 gpu 加速。
看到唯一修改該變量值的就是 SetGpuCompositingDisabled 調(diào)用。
它調(diào)用了 IsGpuCompositingDisabled 邏輯,判斷已經(jīng)開啟 gpu 加速的情況下,再去設(shè)置這個(gè)變量為 true,關(guān)閉 gpu 加速。
void GpuDataManagerImplPrivate::SetGpuCompositingDisabled() {
if (!IsGpuCompositingDisabled()) {
disable_gpu_compositing_ = true;
if (gpu_feature_info_.IsInitialized())
NotifyGpuInfoUpdate();
}
}
我們看到它只有兩處調(diào)用,一個(gè)是初始化 gpu_data_manager_impl_private,它判斷了當(dāng)前命令行是否加了--disable-gpu-compositing,如果加了,則調(diào)用 SetGpuCompositingDisabled。
這里我們確認(rèn)主進(jìn)程拉起來(lái)時(shí)不會(huì)帶這個(gè)命令,子進(jìn)程啟動(dòng)時(shí)也沒(méi)有加,所以不是外部將這個(gè) disable_gpu_compositing_=true 的, 它應(yīng)該還是 false,我們接著看另一個(gè)硬件加速的邏輯。
HardwareAccelerationEnabled中的邏輯,具體不展開了,實(shí)際上就是檢查當(dāng)前環(huán)境是否啟用通過(guò)。
https://source.chromium.org/chro ... rendering_list.json 這個(gè)文件的白名單列表確定的。
我們通過(guò)修改 electron,增加 debug log 的方式驗(yàn)證我們的猜想。
VERBOSE1:gpu_control_list.cc(296)] Control list match for rule #3 in gpu_blocklist.
VERBOSE1:gpu_control_list.cc(296)] Control list match for rule #27 in gpu_blocklist.
VERBOSE1:gpu_control_list.cc(296)] Control list match for rule #28 in gpu_blocklist.
VERBOSE1:gpu_control_list.cc(296)] Control list match for rule #29 in gpu_blocklist.
VERBOSE1:gpu_control_list.cc(296)] Control list match for rule #50 in gpu_blocklist.
VERBOSE1:gpu_control_list.cc(296)] Control list match for rule #134 in gpu_blocklist.
VERBOSE1:gpu_control_list.cc(296)] Control list match for rule #176 in gpu_blocklist.
確實(shí)命中了,那么有什么辦法可以繞過(guò)去這個(gè)檢查呢?
4)qq electron 啟動(dòng)時(shí)增加選項(xiàng) --ignore-gpu-blocklist:
通過(guò)啟動(dòng)參數(shù)--ignore-gpu-blocklist 跳過(guò)檢查邏輯,渲染與采集可以正常顯示畫面。
但有以下幾個(gè)問(wèn)題:
- 1)QQ 主 render 進(jìn)程會(huì)花屏,或者顯示異常;
- 2)音視頻通話 render 進(jìn)程,也會(huì)有花屏、綠屏、閃屏(在開啟攝像頭采集的情況);
- 3)某些系統(tǒng)開啟攝像頭采集過(guò)一段時(shí)間會(huì) crash,目前懷疑是驅(qū)動(dòng)問(wèn)題;
QQ 主進(jìn)程受到影響是我們不可接受的,它直接影響了用戶在使用 QQ 的體驗(yàn)。
經(jīng)過(guò)討論,在不影響主進(jìn)程的情況下,還要保證渲染正常,不能使用PPB_Graphics3D方案,降級(jí)到PPB_Graphics2D方案來(lái)代替,那么PPB_Graphics2D 實(shí)際上就是 RGB 的圖片繪制,我們是如何實(shí)現(xiàn)的?
13.4PPB_Graphics2D 渲染方案
考慮到 PPB_Graphics3D 渲染方案在 Linux 兼容性問(wèn)題,目前很難解決。
討論后,在有問(wèn)題的環(huán)境下降級(jí)到 PPB_Graphics2D 方案:
- 1)音視頻進(jìn)程增加獨(dú)立 OpenGL 上下文,新增離屏渲染流程,繪制后,復(fù)制出 rgba 數(shù)據(jù)給到 PPB_Graphics2D 上下文;
- 2)使用 PPB_Graphics2D 進(jìn)行渲染上屏。
流程圖如下:
這套方案實(shí)際上是兜底方案,會(huì)在 PPB_Graphics3D 初始化失敗的情況在降級(jí)到 PPB_Graphics2D。
存在的缺點(diǎn):
- 1)增加了離屏渲染過(guò)程,會(huì)有內(nèi)存、cpu 的增長(zhǎng);
- 2)2D 方案,是通過(guò)圖片傳遞到 render 進(jìn)程的,畫布尺寸拉的越大,會(huì)有卡頓情況;
- 3)兼容性問(wèn)題,一些渲染操作直接 crash 在驅(qū)動(dòng)庫(kù)里,如下圖,要持續(xù)解決。
這些問(wèn)題后續(xù)會(huì)持續(xù)優(yōu)化。視頻鏈路除了渲染環(huán)節(jié),還有采集、傳輸、編解碼環(huán)節(jié),過(guò)程中都遇到了一些問(wèn)題,音頻鏈路適配也是困難重重,這些在這里不做過(guò)多敘述,后面團(tuán)隊(duì)的伙伴會(huì)單獨(dú)分享。
14、 本文小結(jié)
最后看一下 Linux 端通話效果:
-------過(guò)程是曲折的,有遇到難題卡了幾天無(wú)法解決,也有現(xiàn)在還存在一些棘手的兼容性問(wèn)題,但從0-1的感覺(jué)還是很不錯(cuò)的,后面我們會(huì)持續(xù)優(yōu)化,遇到各種體驗(yàn)問(wèn)題可以直接圈我。
Linux QQ 下載地址:https://im.qq.com/linuxqq/index.shtml。
NTRTC Linux 后續(xù)的規(guī)劃:
- 1)支持 loongarch64、mips64el 架構(gòu);
- 2)解決視頻相關(guān)的兼容性問(wèn)題。
在此,感謝團(tuán)隊(duì)伙伴大力支持!
15、 相關(guān)資料
[1] 快速了解新一代跨平臺(tái)桌面技術(shù)——Electron
[2] Electron初體驗(yàn)(快速開始、跨進(jìn)程通信、打包、踩坑等)
[3] vivo的Electron技術(shù)棧選型、全方位實(shí)踐總結(jié)
[4] 融云基于Electron的IM跨平臺(tái)SDK改造實(shí)踐總結(jié)
[5] 閑魚IM基于Flutter的移動(dòng)端跨端改造實(shí)踐
[6] 網(wǎng)易云信基于Electron的IM消息全文檢索技術(shù)實(shí)踐
[7] 即時(shí)通訊音視頻開發(fā)(十六):移動(dòng)端實(shí)時(shí)音視頻開發(fā)的幾個(gè)建議
[8] 福利貼:最全實(shí)時(shí)音視頻開發(fā)要用到的開源工程匯總
[9] 寫給小白的實(shí)時(shí)音視頻技術(shù)入門提綱
[10] 零基礎(chǔ)入門:實(shí)時(shí)音視頻技術(shù)基礎(chǔ)知識(shí)全面盤點(diǎn)
[11] 實(shí)時(shí)音視頻面視必備:快速掌握11個(gè)視頻技術(shù)相關(guān)的基礎(chǔ)概念
[12] 零基礎(chǔ)快速入門WebRTC:基本概念、關(guān)鍵技術(shù)、與WebSocket的區(qū)別等
[13] 新QQ桌面版為何選擇Electron作為跨端框架
(本文已同步發(fā)布于:http://www.52im.net/thread-4671-1-1.html)