本文由QQ音視頻團(tuán)隊賀坤分享原題“Linux QQ能打語音視頻了!一文詳解背后技術(shù)實現(xiàn)!”,下文進(jìn)行了排版和內(nèi)容優(yōu)化等。
1、引言
2024年6月6日,QQ For Linux 3.2.9 正式支持了音視頻通話功能,這是 QQ Linux 版本的又一個里程碑事件。 2024 年,QQ 音視頻正式推出 NTRTC,全平臺(iOS/Android/MacOS/Windows/Linux)的支持是 NTRTC 的重要特性之一,本次 Linux 平臺的適配也是這次升級過程中重要的一環(huán)。
本文詳細(xì)記錄了新版QQ音視頻通話在 Linux 平臺適配開發(fā)過程中的技術(shù)方案與實現(xiàn)細(xì)節(jié),希望能幫助大家理解在 Linux 平臺從 0 到 1 實現(xiàn)音視頻通話能力的過程。
2、系列文章
本文是系列文章中的第12 篇,本系列總目錄如下:
《IM跨平臺技術(shù)學(xué)習(xí)(一):快速了解新一代跨平臺桌面技術(shù)——Electron》
《IM跨平臺技術(shù)學(xué)習(xí)(二):Electron初體驗(快速開始、跨進(jìn)程通信、打包、踩坑等)》
《IM跨平臺技術(shù)學(xué)習(xí)(三):vivo的Electron技術(shù)棧選型、全方位實踐總結(jié)》
《IM跨平臺技術(shù)學(xué)習(xí)(四):蘑菇街基于Electron開發(fā)IM客戶端的技術(shù)實踐》
《IM跨平臺技術(shù)學(xué)習(xí)(五):融云基于Electron的IM跨平臺SDK改造實踐總結(jié)》
《IM跨平臺技術(shù)學(xué)習(xí)(六):網(wǎng)易云信基于Electron的IM消息全文檢索技術(shù)實踐》
《IM跨平臺技術(shù)學(xué)習(xí)(七):得物基于Electron開發(fā)客服IM桌面端的技術(shù)實踐》
《IM跨平臺技術(shù)學(xué)習(xí)(八):新QQ桌面版為何選擇Electron作為跨端框架》
《IM跨平臺技術(shù)學(xué)習(xí)(九):全面解密新QQ桌面版的Electron內(nèi)存占用優(yōu)化》
《IM跨平臺技術(shù)學(xué)習(xí)(十):快速選型跨平臺框架Electron、Flutter、Tauri、React Native等》
《IM跨平臺技術(shù)學(xué)習(xí)(十一):環(huán)信基于Electron打包WebIM桌面端的技術(shù)實踐》
《IM跨平臺技術(shù)學(xué)習(xí)(十二):萬字長文詳解QQ Linux端實時音視頻背后的跨平臺實踐》(* 本文)
3、技術(shù)背景
隨著新版 QQ 桌面端的上線,在網(wǎng)上得到了廣泛的討論,尤其 QQ For Linux 3.0 推出后,比之前的 Linux 版本有了突破性的改變。
QQ For Linux 3.1 還不支持語音、視頻通話,音視頻通話作為基礎(chǔ)能力之一,適配 Linux 平臺,這將是一個從0-1的過程,非常值得期待。
QQ 的音視頻通話能力是基于 AVSDK,在過去3年中,我們持續(xù)對 AVSDK 進(jìn)行基礎(chǔ)架構(gòu)重構(gòu),更新底層基礎(chǔ)庫, 對 AVSDK 持續(xù)優(yōu)化。
在2024年上半年,QQ 音視頻正式推出 NTRTC,全平臺(iOS/Android/MacOS/Windows/Linux)的支持是 NTRTC 的重要特性之一,本次 Linux 平臺的適配也是這次升級過程中重要的一環(huán)。
Linux 平臺上的適配對我們來說是一個挑戰(zhàn)。
一個全新的平臺,從以下思路開展:
- 1)我們要對 Linux 平臺有個調(diào)研,包括平臺信息、開發(fā)環(huán)境等;
- 2)針對 SDK 進(jìn)行編譯適配,這將涉及到所有的代碼跟依賴庫;
- 3)平臺媒體層適配,視頻、音頻鏈路的采集、渲染、編解碼等;
- 4)新增終端的通話業(yè)務(wù)適配,這包括前后端的邏輯,比如新增的終端類型,通話流控控制等;
- 5)發(fā)布部署等,如流水線搭建,版本管理。
那么我們開始!
4、Linux平臺介紹
Linux 內(nèi)核最初只是由芬蘭人林納斯·托瓦茲(Linus Torvalds)在赫爾辛基大學(xué)上學(xué)時出于個人愛好而編寫的。
Linux 是一套免費(fèi)使用和自由傳播的類 Unix 操作系統(tǒng),是一個基于 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。
每個 Linux 發(fā)行版都有其特點和優(yōu)勢,用戶可以根據(jù)自己的需求和偏好來選擇適合自己的發(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。
通過新桌面 QQ Linux 版本的分布數(shù)據(jù),我們會優(yōu)先適配 x64、arm64。
6.2安裝包(可執(zhí)行文件)
這個很好理解,比如軟件包,腳本等可運(yùn)行的軟件。
Linux 系統(tǒng)中的軟件通常通過軟件包的形式進(jìn)行分發(fā)和安裝。軟件包包含了軟件的可執(zhí)行文件、庫文件、配置文件等,以及一些元數(shù)據(jù),如軟件的版本、依賴關(guān)系等。
不同的 Linux 發(fā)行版可能使用不同的軟件包管理系統(tǒng),因此軟件包的類型也會有所不同。
以下是一些常見的 Linux 發(fā)行版和它們的軟件包類型:
- 1)Debian、Ubuntu、Linux Mint:這些基于 Debian 的發(fā)行版通常使用 .deb 格式的軟件包,可以通過 dpkg 命令直接安裝,也可以通過 apt 或 apt-get 命令進(jìn)行包管理;
- 2)Fedora、CentOS、Red Hat:這些發(fā)行版使用 .rpm 格式的軟件包,可以通過 rpm 命令直接安裝,也可以通過 yum 或 dnf 命令進(jìn)行包管理;
- 3)Arch Linux、Manjaro:這些發(fā)行版使用 .pkg.tar.xz 格式的軟件包,可以通過 pacman 命令進(jìn)行包管理;
- 4)Gentoo:Gentoo 使用的是源代碼包,用戶可以通過 emerge 命令進(jìn)行包管理;
- 5)Slackware:Slackware 使用 .tgz 或 .txz 格式的軟件包,可以通過 pkgtool 命令進(jìn)行包管理。
以上只是一些常見的例子,實際上還有許多其他的 Linux 發(fā)行版和軟件包格式。
此外,一些通用的軟件包格式,如 AppImage、Flatpak 和 Snap,也可以在大多數(shù) Linux 發(fā)行版上使用。
我們以桌面版本 QQ 為例,分別打包了 deb、rpm、AppImage 的軟件包格式。
6.3靜態(tài)庫、動態(tài)庫
在 SDK 開發(fā)中,我們交付的會根據(jù)不同平臺,App 不同的使用方式提供 SDK 產(chǎn)物,也就是靜態(tài)庫或者動態(tài)庫。
例如:
- 1)Unix:qav_ntrtc_sdk.a 的靜態(tài)庫和 qav_ntrtc_sdk.so 的動態(tài)庫;
- 2)Windows :qav_ntrtc_sdk.lib 的靜態(tài)庫和 qav_ntrtc_sdk.dll 的動態(tài)庫;
- 3)macOS:qav_ntrtc_sdk.a 的靜態(tài)庫和 qav_ntrtc_sdk.dylib 的動態(tài)庫。
這些只是常見的命名約定,實際上,庫文件的命名可能會因編譯器、開發(fā)環(huán)境和開發(fā)者的選擇而有所不同。
這個比較重要,因為作為 sdk 提供方,需要對不同交付的產(chǎn)物有明確的了解,sdk 也會根據(jù)使用方案提供不同的產(chǎn)物。
6.4開發(fā)環(huán)境
上面提到的不同 Linux 發(fā)行版本,這次開發(fā)申請了一臺 PC 機(jī)(x64),安裝了 TLinux(Ubuntu 20.04.6)。
主開發(fā)機(jī)使用一臺 x64 的真機(jī) Ubuntu20,arm64 架構(gòu)則使用 M1 Pro 搭建虛擬機(jī)環(huán)境(VM ware/UTM)Ubuntu20 來輔助開發(fā)調(diào)試。
其他驗證環(huán)境:
6.5環(huán)境工具
比如:
- 1)編譯依賴:CMake、GCC、Clang(最后會切到 Clang)、ar 等;
- 2)開發(fā)工具:VSCode、Clion、git、apt 等;
- 3)開發(fā)環(huán)境基本上準(zhǔn)備好了,比如 apt 安裝各個依賴的 dev 庫,編譯工具、調(diào)試環(huán)境配置等。
6.6跨平臺開發(fā)架構(gòu)
我們在其他平臺都通過音視頻自回環(huán) Demo 可以快速且輕量模擬音視頻通話場景,驗證功能,同樣在 Linux 平臺我們也通過建立輕量 Demo 來快速驗證該平臺的各項能力。
我們對 Linux 平臺下可用的 GUI 開發(fā)框架做了個調(diào)研,對比了接入效率,最后選擇了 QT 開發(fā)框架。
6.7NTRTC 自回環(huán)的 demo
我們基于 QT6 實現(xiàn)了一個簡單的 Demo,通過自回環(huán)的方式,驗證音頻、視頻、傳輸通道等能力。
開發(fā)環(huán)境:QTLinux6.6.1, Qtcreator。
基于這個 Demo,我們可以提前在 Linux 平臺驗證音頻、視頻編解碼能力。
從平臺知識到開發(fā)環(huán)境基本上準(zhǔn)備差不多了,接下來先介紹下桌面端音視頻通話的的實現(xiàn)方案。
6.8桌面版本 QQ 音視頻通話方案
1)QQ(Electron) + PPAPI:
新桌面 QQ 版本是基于 electron 進(jìn)行開發(fā)的(詳見《新QQ桌面版為何選擇Electron作為跨端框架》),對于 electron 的介紹可以直接看官網(wǎng)。
electron 內(nèi)置了一個 chromium 內(nèi)核,新桌面 QQ 音視頻通話就是基于 Pepper Plugin(PPAPI)方案實現(xiàn)的,這里簡單對 PPAPI 組件做個介紹。
PPAPI 組件可以通過平臺動態(tài)庫的形式(Windows 下為 dll 文件,Linux 下是 so 文件, Mac 下是 dyllib 文件)由瀏覽器直接加載,比如內(nèi)置的 Flash 組件、Pdf組件,或者通過指定命令行參數(shù) --register-pepper-plugins 來加載,比如:chrome --register-pepper-plugins="D:\\ppapi\\ppapi_example_gles2.dll;application/x-ppapi-example-gles2" D:\\ppapi\\gles2.html。可信的 PPAPI 組件以平臺動態(tài)庫的形式存在,所以一般 Chrome 沙箱內(nèi)允許的 API(比如 CreateThread)都可以調(diào)用。
Chromium 插件(Plugin)機(jī)制:https://blog.csdn.net/Luoshengyang/article/details/52665318。
通過了解 PPAPI Plugin 我們可以了解到兩個關(guān)鍵的點:
- 1)進(jìn)程是通過 IPC 進(jìn)行通訊的;
- 2)Plugin 有沙箱機(jī)制(這里是重點,后面有坑);
2)AVSDK Plugin 注冊:
我們看下 AVSDKPlugin 的動態(tài)庫是如何注冊的:
- 1)不同平臺區(qū)獲取對應(yīng)的動態(tài)庫;
- 2)通過 register-pepper-plugins 注冊到 electron app。
音視頻通話相當(dāng)于創(chuàng)建一個瀏覽器窗口,同時會拉起這個對應(yīng)注冊的Plugin,具體加載 Plugin 過程這里不做過多討論,可以看這篇文章 Chromium 插件(Plugin)模塊(Module)加載過程。
7、NTRTC-SDK For Linux 工程
適配前,我們先看一下音視頻 AVSDKPlugin 框架。
可以看到這個 AVSDKPlugin 實際上就是一個 PPAPI Plugin 倉庫,它集合了 NTRTC、GroupVideo、BroadCast-Core 等 SDK,通過 Wrapp 層將它們串聯(lián)起來,在包裝成 PPAPI Plugin 實例對外提供音視頻通話能力,直播能力。
對外提供的產(chǎn)物:可執(zhí)行文件,資源文件,內(nèi)置依賴庫。
8、工程適配
受益于之前 CMake 的統(tǒng)一構(gòu)建, QQ NT 的跨平臺重構(gòu)之旅-音視頻全平臺構(gòu)建統(tǒng)一 本次對 Linux 平臺的編譯適配工作也順利很多。
主要處理下面幾個事項:
- 1)CMake 相關(guān)針對 Linux 平臺增加一些平臺邏輯,比如關(guān)閉某些編譯特性,或者平臺文件僅在 Linux 環(huán)境下編譯;
- 2)業(yè)務(wù)邏輯適配,比如新增的平臺 Type 兼容,平臺基本信息等;
- 3)缺失的一些實現(xiàn);
- 4)Linux 平臺下,各個第三方依賴庫的編譯,如視頻編解碼;
例如CMake 平臺宏差異,可以增加不通的特性選項:
if(WIN32)
# 設(shè)置 Windows 平臺的特定選項
elseif(UNIX AND NOT APPLE AND NOT ANDROID)
# 設(shè)置 Linux 平臺的特定選項
elseif(APPLE)
# 設(shè)置 macOS 平臺的特定選項
endif()
BroadCast-Core 等其他依賴庫CMake 工程化:通過修復(fù)編譯問題,或者重新編譯需要的架構(gòu)版本,過程中遇到了無源碼的情況,或者找不到源碼,那么只能通過屏蔽相關(guān)能力,或者移除該能力來解決。
9、工程編譯和Demo
最開始編譯使用的是 gcc 11.4.0,gcc 已經(jīng)滿足編譯需求。
遇到的編譯問題:
- 1)有源碼的,解決編譯報錯問題即可,主要體現(xiàn)在頭文件沒有引用,或者缺對應(yīng)的實現(xiàn);
- 2)無源碼的第三方庫,也就是該平臺下沒有對應(yīng)架構(gòu)的庫,需要整體重新編譯即可;
- 3)fPIC 問題,編解碼庫 link 到動態(tài)庫時出現(xiàn) fPIC 錯誤。
/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 編碼和解碼庫在鏈接時報 fPIC 的問題,增加 -Bsymbolic 鏈接,關(guān)閉動態(tài)庫 so 中默認(rèn)的符號搶占方式,來繞過 fPIC 的檢查。
合并凈態(tài)庫:
在輸出 avsdk 靜態(tài)庫時,一般都會將各個子庫進(jìn)行合并,生成一個最終 qav_rtc_sdk.a,在 Linux 下沒有類似 libtool、libexe 等工具,不過有個 ar 工具,可以達(dá)到合并的效果。
- 1)通過 ar x 提取靜態(tài)庫的所有.o文件;
- 2)在通過 ar crs 合并所有的.o 文件;
- 3)通過 ranlib 生成新的靜態(tài)庫索引。
但是合并后出現(xiàn)了問題,合并后,link 到 demo 時報錯,符號缺失?符號丟了!但是通過 nm 查看子庫的符號都是全的。
1)不同靜態(tài)庫,相同命名的.o:
經(jīng)過排查,發(fā)現(xiàn)使用 ar x 命令提取文件時,如果歸檔文件中存在多個同名文件,ar 會提取找到的第一個匹配項,這里一個庫的內(nèi)容出現(xiàn)相同的 .o 情況時,會出現(xiàn)覆蓋問題,這里暫時沒有好的 ar 可選項能快速解決這個問題的。
解決方法:那就通過邏輯解決,提取時,每個庫都復(fù)制到獨(dú)立臨時目錄,待歸檔目錄內(nèi)遇到重復(fù)命名的 .o 文件時,重命名這個 .o, 防止同名覆蓋。
這個錯誤時機(jī)上是 ar 提取文件時,復(fù)制到待合并文件夾時環(huán)節(jié)出現(xiàn)的,是不同的靜態(tài)庫有相同命名的 .o 文件,通過重命名,還比較好解決;
2)同一個靜態(tài)庫,相同命名的 .o:
解決了 .o 覆蓋的問題,再次 link,還是缺失符號,通過排查還是丟了對應(yīng)的符號,再次排查哪一步丟的,我們發(fā)現(xiàn)一個靜態(tài)庫內(nèi)出現(xiàn)相同命名的 .o 符號段,兩個符號段在不同位置,ar x 提取時,會優(yōu)先命中第一個搜索到的 .o 段,后面遇到的都會忽略,這就棘手了,是工具提取環(huán)節(jié)出現(xiàn)的丟失,排查了一些 ar 選項沒有解決;
解決方法:通過修改該靜態(tài)庫內(nèi)相同源文件命名解決。
3)Demo Link & Demo Run:
經(jīng)過上面2個方面的適配,解決一系列l(wèi)ink問題后,較順利的輸出了 x64 版本的 qav_rtc_sdk.a。
我們通過之前提到的 qt_demo, 進(jìn)行 link 驗證,也沒有問題,自回環(huán)的邏輯也正常跑起來,基于 QT 開發(fā)環(huán)境也可以正常調(diào)試,此時音頻、視頻能力可以先開始驗證。
4)AVSDKPlugin & electron demo:
我們輸出了 x64 版本的 AVSDKPlugin.so,搭建了一個 electron demo,用于驗證我們的動態(tài)庫是否可以正常運(yùn)行;
這里需要在 Linux 安裝 electron 環(huán)境,具體看 Electron Quick Start。
然后我們遇到了第一個問題:動態(tài)庫拉不起來!
錯誤信息: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)。
通過錯誤信息,我們大致能看出來是權(quán)限問題,首先通過確認(rèn),排除了 so 文件路徑錯誤的問題,那就是權(quán)限問題。
還記得上面介紹 pepper plugin 時的沙箱問題嗎,沒錯,就是這個,electron app 默認(rèn)是開啟沙箱模式的,也就是說 app 住進(jìn)程是開啟沙箱的,住進(jìn)程通過 fork 方式拉起的進(jìn)程都會帶沙箱模式。
既然知道了什么原因,我們暫時先關(guān)閉 electron app 的沙箱模式,后面這個問題通過修改 electron 源碼來解決。
運(yùn)行 Electron Demo,Electron 新創(chuàng)建了一個瀏覽器窗口,并且通過 Pepper Plugin 方式,拉起音視頻進(jìn)程加載了 AVSDKPlugin.so,well done!路通了。
10、工程調(diào)試
QT Demo Debug。
首先我們通過 QT 開發(fā)環(huán)境對運(yùn)行的 demo app 直接進(jìn)行調(diào)試。
demo link 了 qav_ntrtc_sdk.a , 使用了 xpstl::list 做了一些操作。
通過斷點,我們可以方便的進(jìn)行調(diào)試,這是基于直接運(yùn)行 app 做的操作;
那么如何調(diào)試 electron app + plugin 拉起的 AVSDKPlugin.so 呢?
此時有經(jīng)驗的同學(xué)會想到掛載進(jìn)程調(diào)試,沒錯,我們此時也可以通過掛載進(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>選擇對應(yīng)進(jìn)程,確定;
- 2)調(diào)試:正常在 CLion 打斷點即可;
- 3)注意:需要是 Debug 版本的動態(tài)庫。
demo 拉起 avsdk 各個線程:
通過 log,我們也可以看到輸出:
【問題】Linux 掛載進(jìn)程失敗,提示沒權(quán)限:
這里是 Linux 系統(tǒng)有個權(quán)限問題,按 GPT 給出的解決方案,修改一下重啟電腦生效。
11、 GLIBC、GLIBCXX 運(yùn)行依賴
GLIBC 和 GLIBC++ 是兩個不同的庫,它們在 Linux 系統(tǒng)中扮演著重要的角色。
1)GLIBC:
GLIBC,全稱 GNU C Library,是 GNU 項目的 C 標(biāo)準(zhǔn)庫的實現(xiàn),為系統(tǒng)和應(yīng)用程序提供了系統(tǒng)調(diào)用的封裝和許多基本的程序接口。這包括輸入輸出(I/O)、字符串處理、文件操作、內(nèi)存管理、數(shù)學(xué)計算等。GLIBC 是大多數(shù)基于 Linux 的系統(tǒng)的標(biāo)準(zhǔn) C 庫,并且是編譯大多數(shù) C 程序的必要組件。
GLIBC 的版本很重要,因為不同的應(yīng)用程序可能需要不同版本的 GLIBC。例如,一個用較新版本的 GLIBC 編譯的程序可能無法在只有較舊版本 GLIBC 的系統(tǒng)上運(yùn)行。
2)GLIBC++:
GLIBC++ 是 GNU libstdc++ 庫的常見稱呼,它是 C++ 標(biāo)準(zhǔn)庫的 GNU 實現(xiàn)。它提供了 C++ 程序所需的標(biāo)準(zhǔn)功能,包括輸入輸出流(iostream)、數(shù)據(jù)結(jié)構(gòu)(如 STL 容器)、算法、字符串處理等。當(dāng)你編譯 C++ 程序時,通常需要鏈接到 libstdc++ 庫。
與 GLIBC 類似,不同版本的 GNU libstdc++ 支持不同版本的 C++ 標(biāo)準(zhǔn)。例如,較新版本的 libstdc++ 支持 C++11、C++14、C++17 和 C++20 的新特性。
版本查詢和兼容性,在 Linux 系統(tǒng)中,你可以通過運(yùn)行以下命令來查詢 GLIBC 和 GLIBC++ 的版本。
對于 GLIBC,可以使用 `ldd --version` 或 `libc.so.6` 文件來查詢:
ldd --version
# 或者
/lib/x86_64-linux-gnu/libc.so.6
對于 GLIBC++,可以通過檢查 libstdc++ 庫的版本來查詢:
1strings /usr/lib/x86_64-linux-gnu/libstdc++.so.6 | grep GLIBCXX
兼容性通常是向后的,這意味著用舊版本的 GLIBC 或 GLIBC++ 編譯的程序應(yīng)該能在有較新版本庫的系統(tǒng)上運(yùn)行。然而,反過來通常不行,因為舊版本的庫不包含新版本中引入的符號和功能。
在輸出我們編譯好的 AVSDKPlugin 后,在 Ubuntu20、22上正常運(yùn)行起來,但是我們發(fā)現(xiàn)。
AVSDKPlugin.so 放到不同 Linux 版本上運(yùn)行時,比如 Ubuntu 18、Fedora 23、Qlin 等系統(tǒng)上,發(fā)現(xiàn)音視頻拉不起來?
通過ldd AVSDKPlugin.so 我們發(fā)現(xiàn)出現(xiàn)一些依賴庫 no found, 或者 GLIBC need 2.29等錯誤信息。
這個是 Ubuntu 18(x64)的報錯:
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)錯誤信息。
表明我們依賴的庫使用了較高版本的 GLIBC 編譯,在低 GLIBC 版本的系統(tǒng)上無法運(yùn)行!
我們要確定兩個信息:
- 1)編譯時使用的 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
通過輸出的信息,我們知道我們在 Ubuntu 20,x64 環(huán)境,使用 GCC 10.5 編譯輸出的產(chǎn)物,最低支持 GLIBC2.29, 也就是運(yùn)行環(huán)境需要有 GLIBC 2.29,但上面 Ubuntu18、跟 KylinOS 環(huán)境的 GLIBC 版本都太低了,無法運(yùn)行我們的動態(tài)庫,那怎么辦呢?
上面提到了,avsdk、avsdkplugin 都是使用 gcc11.4 進(jìn)行編譯的,使用的系統(tǒng)是 Ubuntu20。
我們通過 strings /usr/lib/x86_64-linux-gnu/libc.so.6 | grep GLIBC來查看 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)境沒有達(dá)到 AVSDKPlugin 依賴的 GLIBC 需要支持2.29,我們編譯使用的 libc++ 版本太高了,那就就要想辦法降級。
3)GCC 10.5:
我們想到的是通過降低編譯工具版本來解決,我們嘗試使用 gcc 10.5,修復(fù)了一些編譯問題,輸出的產(chǎn)物還是依賴較高的 GLIBC 版本,我們通過排查接口,發(fā)現(xiàn)是數(shù)學(xué)庫的一些相關(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)
雖然降級了編譯工具版本,但實際上 link 的還是當(dāng)前系統(tǒng)目錄的 libc, 或者 libm。
一般這種情況,我們就要通過使用低版本的編譯工具鏈(使用指定的低版本的庫)。
通用的做法就是準(zhǔn)備好相關(guān)編譯工具鏈文件,然后通過自定義依賴庫搜索路徑來使用工具鏈的依賴庫進(jìn)行編譯。
4)構(gòu)建工具鏈:buildtools & Clang:
通過跟NTKernel的同學(xué)溝通,得知Kernel編譯使用了一套構(gòu)建工具,支持x64、arm64、loong64、mips64el。
使用的編譯器是 Clang,我們嘗試使用該構(gòu)建工具,配置好 toolchan.cmake,在編譯時發(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 升級到 c++20 即。
6)filesystem 相關(guān)符號缺失:
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)建工具鏈中沒有相關(guān)實現(xiàn)。
需要構(gòu)建工具鏈內(nèi)的 libc++.a 增加 systemfile 的實現(xiàn)編譯。
可以參考 https://libcxx.llvm.org/BuildingLibcxx.html, 編譯出對應(yīng)的 filesystem 版本即可。
7)內(nèi)置:
對于缺失的依賴庫,我們可以內(nèi)置到安裝目錄即可,通過 patchelf 指定搜索目錄,可以設(shè)置搜索路徑查找優(yōu)先級,先搜索自定義目錄,在搜索系統(tǒng)路徑,如下圖所示。
8)提示安裝:
我們嘗試內(nèi)置 OpenGL 庫解決運(yùn)行環(huán)境 OpenGL 庫缺失的問題,但是通過測試下來,在不同的系統(tǒng)環(huán)境運(yùn)行,會出現(xiàn)各種 OpenGL 兼容性的 crash 問題,有些情況通過運(yùn)行環(huán)境安裝的默認(rèn) OpenGL 是好的。
嘗試過通過 patchelf 配置搜索路徑優(yōu)先級, 先搜索系統(tǒng)路徑,如:/usr/lib/x86_64-linux-gnu , 在搜索安裝目錄,來解決。
但這也確實使用了內(nèi)置 OpenGL 庫,直接 crash,整體體驗上更差,還不如早一點檢測依賴,暴露問題,引導(dǎo)用戶安裝。
這個提示比較粗暴,后續(xù)會優(yōu)化。
最后針對 Linux 底層庫的支持,音視頻 GLIBC 低版本支持情況:x64 2.17+, arm64 2.29+ 。
12、 Electron 的修改
12.1概述
electron 的相關(guān)介紹可以去官網(wǎng)看下 Electron。
electron的是一個開源項目,可以自行編譯 electron 版本來滿足自己產(chǎn)品的需求。
構(gòu)建可以參考這個 構(gòu)建 electron。
對于 electron,qq 桌面端的 electron 實際上自己編譯的,也做了一些優(yōu)化跟定制,本次 Linux 適配我們也做了一些修改。
沙盒問題
chromium 有它自己管理的一套沙盒機(jī)制,在前面我們有提過。
QQ Electron App 的主進(jìn)程是開啟沙盒的,那么通過主進(jìn)程 fork 方式拉起來的進(jìn)程都會繼承主進(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)程時不開啟沙盒如何做呢?
/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.
我們針對 ppapi 進(jìn)程修改,來關(guān)閉ppapi進(jìn)程的沙盒模式選項,讓 ppapi 進(jìn)程不開沙盒模式,當(dāng)然這里可能會有一些安全隱患,后面看下是否有更好的方案解決。
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>
在測試過程中,我們發(fā)現(xiàn)通過 electron 拉起的 ppapi plugin 進(jìn)程時,經(jīng)常出現(xiàn)這個 crash,導(dǎo)致音視頻功能經(jīng)常不可用,通過報錯信息,搜索到一些相關(guān)信息。
https://github.com/electron/electron/pull/40677 具體看算是 electron 的bug,找到推薦修改方式,https://source.chromium.org/chro ... 1822c28c78b7115684f 這里官方的說法是重置所有權(quán)。
實際上通過代碼排查,我們發(fā)現(xiàn)這個 FD owner 檢查 crash,實際上是 electron 的一個特性邏輯,我們在 content/app/content_main.cc 看到,electron app 在 Linux 平臺下是開啟了這個 FD Ownership 檢查的,那這里我們就嘗試將它關(guān)閉,是不是就可以解決了。
通過修改 electron 源碼,重新編譯 electron,該問題得到解決。
12.3electron 相關(guān)技巧編譯
electron app 實際上就是 chromium 瀏覽器環(huán)境的一個 app,對于瀏覽器支持的選項大部分都支持,包括一些調(diào)試選項。
在啟動 electron app 加啟動參數(shù)就行,實際上屬于 web 前端的技術(shù)棧,我找到一個不錯的 blog,頁面挺好看的。
Chrome瀏覽器啟動參數(shù)大全(命令行參數(shù)):https://www.cnblogs.com/gurenyumao/p/14721035.html。
例如開啟更多的 log 信息:(參考鏈接)
#控制臺啟動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、平臺媒體硬件適配
音視頻通話、直播都離不開音頻、視頻,相關(guān)的采集、渲染、編解碼都與平臺硬件息息相關(guān)。從采集、渲染、編碼、解碼都會遇到一些問題。這里我就適配過程中,處理的一個視頻渲染降級方案做一下分享。
13.1視頻通話渲染方案
我們先來看一下 Chromium Plugin 執(zhí)行 3D 渲染的過程 的渲染過程。
在 Plugin 進(jìn)程中,OpenGL 上下文通過 Graphics3D 類描述。因此,創(chuàng)建 OpenGL 上下文意味著是創(chuàng)建一個 Graphics3D 對象。這個 Graphics3D 對象在創(chuàng)建的過程中,會調(diào)用 PPB_GRAPHICS_3D_INTERFACE_1_0 接口提供的一個函數(shù) Create。該函數(shù)又會通過一個 APP_ID_RESOURCE_CREATION 接口向 Render 進(jìn)程發(fā)送一個類型為 PpapiHostMsg_PPBGraphics3D_Create 的 IPC 消息。在 Plugin 進(jìn)程中,APP_ID_RESOURCE_CREATION 接口是通過一個 ResourceCreationProxy 對象實現(xiàn)的,因此,Plugin 進(jìn)程實際上是通過 ResourceCreationProxy 類向 Render 進(jìn)程發(fā)送一個類型為 PpapiHostMsg_PPBGraphics3D_Create 的 IPC 消息的。
Plugin 在初始化 OpenGL 環(huán)境的過程中做的第二件事情就是將剛剛創(chuàng)建出來的 OpenGL 上下文指定為當(dāng)前要使用的 OpenGL 上下文。這個過程稱為 OpenGL 上下文綁定,如下圖所示。
音視頻的渲染實際就是使用了 PPB_Graphics3D 的渲染方案,通過共享紋理來做夸進(jìn)程渲染,在支持硬件加速的情況下。
Win 使用了 ID3D11Device、MacOS 使用了 Metal。
13.2PPB_Graphics3D->Create 失敗問題
在開發(fā)過程中,我們在一些虛擬機(jī)的 Linux 系統(tǒng)上發(fā)現(xiàn)視頻渲染黑屏,通過排查 Log,我們發(fā)現(xiàn)以下信息。
具體對應(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)這個 PP_Resource(PPB_Graphics3D) 初始化失敗了,這會導(dǎo)致視頻無法渲染。
我們知道 Plugin 是通過 ppapi 跟 render 進(jìn)程交互的, 這個創(chuàng)建過程實際就是發(fā)送一個創(chuàng)建資源 message 到 render 進(jìn)程創(chuàng)建 3D 畫布資源,我們要確定哪一步出錯。
13.3排查過程
1)確認(rèn)環(huán)境、顯卡驅(qū)動,我們發(fā)現(xiàn)在啟動 QQ 后,有問題的環(huán)境會輸出一些 warning 信息,跟顯卡驅(qū)動相關(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)
此時我們通過啟動 qq 時增加:
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ū)動出了一些警告,或者錯誤。
2)進(jìn)程啟動選項多出的 --disable-gpu-compositing 參數(shù):
我們發(fā)現(xiàn)在有問題的環(huán)境,在音視頻進(jìn)程啟動時多了一個啟動選項--disable-gpu-compositing。
通過排查這個不是我們業(yè)務(wù)增加的,也就是他是 chromium 通過當(dāng)前系統(tǒng)環(huán)境自己加的選項,這個參數(shù)的作用是禁用 GPU(圖形處理單元)合成,也就是它直接導(dǎo)致了 PPB_Graphics3D->Create 失敗。
3)electron 源碼分析:
那么--disable-gpu-compositing 是如何添加到啟動選項中的?
// Prevent the compositor from using its GPU implementation.
const char kDisableGpuCompositing[] = "disable-gpu-compositing";
可以看到 gpu_data_manager_impl_private.cc 里面的實現(xiàn),在 IsGpuCompositingDisabledS 時加了這個 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();
}
可以看到實際訪問了一個 private 對象,它在頭文件這么定義的。
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ān)閉了 gpu 加速 disable_gpu_compositing_ 與 HardwareAccelerationEnabled() 變量不開啟 gpu 加速 或者 硬件不支持 gpu 加速, 這里都返回 false,啟動插件進(jìn)程的cmd就會加上--disable-gpu-compositing。
那么 disable_gpu_compositing_邏輯 ,默認(rèn)是 false, 默認(rèn)會開啟 gpu 加速。
看到唯一修改該變量值的就是 SetGpuCompositingDisabled 調(diào)用。
它調(diào)用了 IsGpuCompositingDisabled 邏輯,判斷已經(jīng)開啟 gpu 加速的情況下,再去設(shè)置這個變量為 true,關(guān)閉 gpu 加速。
void GpuDataManagerImplPrivate::SetGpuCompositingDisabled() {
if (!IsGpuCompositingDisabled()) {
disable_gpu_compositing_ = true;
if (gpu_feature_info_.IsInitialized())
NotifyGpuInfoUpdate();
}
}
我們看到它只有兩處調(diào)用,一個是初始化 gpu_data_manager_impl_private,它判斷了當(dāng)前命令行是否加了--disable-gpu-compositing,如果加了,則調(diào)用 SetGpuCompositingDisabled。
這里我們確認(rèn)主進(jìn)程拉起來時不會帶這個命令,子進(jìn)程啟動時也沒有加,所以不是外部將這個 disable_gpu_compositing_=true 的, 它應(yīng)該還是 false,我們接著看另一個硬件加速的邏輯。
HardwareAccelerationEnabled中的邏輯,具體不展開了,實際上就是檢查當(dāng)前環(huán)境是否啟用通過。
https://source.chromium.org/chro ... rendering_list.json 這個文件的白名單列表確定的。
我們通過修改 electron,增加 debug log 的方式驗證我們的猜想。
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.
確實命中了,那么有什么辦法可以繞過去這個檢查呢?
4)qq electron 啟動時增加選項 --ignore-gpu-blocklist:
通過啟動參數(shù)--ignore-gpu-blocklist 跳過檢查邏輯,渲染與采集可以正常顯示畫面。
但有以下幾個問題:
- 1)QQ 主 render 進(jìn)程會花屏,或者顯示異常;
- 2)音視頻通話 render 進(jìn)程,也會有花屏、綠屏、閃屏(在開啟攝像頭采集的情況);
- 3)某些系統(tǒng)開啟攝像頭采集過一段時間會 crash,目前懷疑是驅(qū)動問題;
QQ 主進(jìn)程受到影響是我們不可接受的,它直接影響了用戶在使用 QQ 的體驗。
經(jīng)過討論,在不影響主進(jìn)程的情況下,還要保證渲染正常,不能使用PPB_Graphics3D方案,降級到PPB_Graphics2D方案來代替,那么PPB_Graphics2D 實際上就是 RGB 的圖片繪制,我們是如何實現(xiàn)的?
13.4PPB_Graphics2D 渲染方案
考慮到 PPB_Graphics3D 渲染方案在 Linux 兼容性問題,目前很難解決。
討論后,在有問題的環(huán)境下降級到 PPB_Graphics2D 方案:
- 1)音視頻進(jìn)程增加獨(dú)立 OpenGL 上下文,新增離屏渲染流程,繪制后,復(fù)制出 rgba 數(shù)據(jù)給到 PPB_Graphics2D 上下文;
- 2)使用 PPB_Graphics2D 進(jìn)行渲染上屏。
流程圖如下:
這套方案實際上是兜底方案,會在 PPB_Graphics3D 初始化失敗的情況在降級到 PPB_Graphics2D。
存在的缺點:
- 1)增加了離屏渲染過程,會有內(nèi)存、cpu 的增長;
- 2)2D 方案,是通過圖片傳遞到 render 進(jìn)程的,畫布尺寸拉的越大,會有卡頓情況;
- 3)兼容性問題,一些渲染操作直接 crash 在驅(qū)動庫里,如下圖,要持續(xù)解決。
這些問題后續(xù)會持續(xù)優(yōu)化。視頻鏈路除了渲染環(huán)節(jié),還有采集、傳輸、編解碼環(huán)節(jié),過程中都遇到了一些問題,音頻鏈路適配也是困難重重,這些在這里不做過多敘述,后面團(tuán)隊的伙伴會單獨(dú)分享。
14、 本文小結(jié)
最后看一下 Linux 端通話效果:
-------過程是曲折的,有遇到難題卡了幾天無法解決,也有現(xiàn)在還存在一些棘手的兼容性問題,但從0-1的感覺還是很不錯的,后面我們會持續(xù)優(yōu)化,遇到各種體驗問題可以直接圈我。
Linux QQ 下載地址:https://im.qq.com/linuxqq/index.shtml。
NTRTC Linux 后續(xù)的規(guī)劃:
- 1)支持 loongarch64、mips64el 架構(gòu);
- 2)解決視頻相關(guān)的兼容性問題。
在此,感謝團(tuán)隊伙伴大力支持!
15、 相關(guān)資料
[1] 快速了解新一代跨平臺桌面技術(shù)——Electron
[2] Electron初體驗(快速開始、跨進(jìn)程通信、打包、踩坑等)
[3] vivo的Electron技術(shù)棧選型、全方位實踐總結(jié)
[4] 融云基于Electron的IM跨平臺SDK改造實踐總結(jié)
[5] 閑魚IM基于Flutter的移動端跨端改造實踐
[6] 網(wǎng)易云信基于Electron的IM消息全文檢索技術(shù)實踐
[7] 即時通訊音視頻開發(fā)(十六):移動端實時音視頻開發(fā)的幾個建議
[8] 福利貼:最全實時音視頻開發(fā)要用到的開源工程匯總
[9] 寫給小白的實時音視頻技術(shù)入門提綱
[10] 零基礎(chǔ)入門:實時音視頻技術(shù)基礎(chǔ)知識全面盤點
[11] 實時音視頻面視必備:快速掌握11個視頻技術(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)