WebAssembly視頻檢測在社區(qū)創(chuàng)作平臺的落地與實(shí)踐
目錄
一、背景&現(xiàn)狀
二、業(yè)界的做法
三、得物音視頻團(tuán)隊(duì)的方案
四、我們需要解決的問題
1. 內(nèi)存泄漏
2.大視頻無法檢測
3.檢測速度慢
五、內(nèi)存優(yōu)化方案
六、總結(jié)
一、背景&現(xiàn)狀
創(chuàng)作者服務(wù)平臺作為得物為社區(qū)創(chuàng)作者提供的PC端視頻發(fā)布入口,地位非常重要。且隨著功能的升級迭代,用戶群體也越來越多。但我們偶爾會收到如下反饋:
- 視頻損壞,無法播放
- 視頻模糊
- 曝光度問題
- 黑屏,只有聲音,沒有畫面
黑屏,無法播放
低清晰度
曝光異常
黑屏,只有聲音
視頻的損壞不僅影響用戶體驗(yàn),還可能導(dǎo)致忠誠用戶的流失。用戶在瀏覽時看到錯誤反饋或者無法播放的視頻,容易產(chǎn)生挫敗感。
第二,流量的上漲導(dǎo)致此類case越來越多,據(jù)統(tǒng)計(jì),自2024年4月份開始,通過創(chuàng)作者平臺發(fā)布的視頻可分發(fā)視頻量較之前上漲多倍。
為提升視頻發(fā)布的質(zhì)量和用戶體驗(yàn),視頻發(fā)布前的檢測能力需盡快落地。
二、業(yè)界的做法
在視頻內(nèi)容平臺興盛的今天,視頻上傳和檢測方面已經(jīng)有了一些有力措施,以確保用戶上傳的視頻質(zhì)量,最大程度地減少損壞視頻對用戶體驗(yàn)和平臺形象的影響,比如服務(wù)端檢測,創(chuàng)作者在上傳完視頻后,會立即觸發(fā)服務(wù)端檢測功能,經(jīng)過等待后會反饋給用戶信息,不強(qiáng)卡發(fā)布。
這一方案是可行的,但也存在些弊端:
- 需要視頻上傳完成才能拿到完整的文件流進(jìn)行檢測,通常在PC上傳的視頻文件很大,要等到傳完作者才能知道自己的視頻有問題,然后再上傳,再檢測
- 帖子發(fā)布時間拉長,增加創(chuàng)作者的等待,從心智上會影響創(chuàng)作者的體驗(yàn)
- 帶寬成本
但這一方案有個優(yōu)秀的點(diǎn),如對涉黃、涉恐等元素的視頻,能同時被檢測到并且禁止分發(fā)。
三、得物音視頻團(tuán)隊(duì)的方案
目前得物音視頻團(tuán)隊(duì)在上傳前的預(yù)檢測這種場景下已有了一套較為完善的方案,那就是使用C+ffmpeg編寫好檢測代碼后再通過Emscripten工具將其打包成WebAssembly的二進(jìn)制文件使代碼運(yùn)行在web端。
圖片
目前這個方案的核心檢測能力已應(yīng)用在得物App發(fā)布工具場景使用,其能通過ffmpeg解析視頻的元數(shù)據(jù),獲取其基本信息,如視頻尺寸,碼率等,能查找目標(biāo)視頻下的視頻流,對音頻和視頻的AVPacket進(jìn)行驗(yàn)證,檢測文件是否損壞,時間軸是否存在異常等等。正常的流程如下:
圖片
經(jīng)過此過程的檢測,我們可以排除絕大多數(shù)文件格式存在問題的視頻文件,下面列出一些常見的文件結(jié)構(gòu)存在問題或者格式不合規(guī)的視頻:
1.文件的moov不存在
圖片
2.視頻幀的NAL結(jié)構(gòu)異常
圖片
3.沒有視頻軌道
除了可以檢測視頻文件是否存在問題之外,我們還可以通過預(yù)檢測獲取大量的視頻相關(guān)的信息:
1.視頻的基本信息
- 寬高、幀率(是否是動態(tài)幀率)、碼率
- 旋轉(zhuǎn)角度
2.色域信息
- 是否是HDR、DP3色域
- 是8bit/10bit/12bit/16bit
3.視頻編碼附加信息
- 當(dāng)前的視頻是否是從其他平臺上搬運(yùn)而來的?
- 當(dāng)前的視頻是否使用其他的剪輯工具導(dǎo)出的?
- 我們可以通過識別視頻中的metadata中的信息來分析當(dāng)前的視頻來自哪些平臺的:
來自微信
來自快手
綜上所述,我們在上傳前預(yù)檢測階段,可以得到視頻的很多信息 + 檢測視頻是否存在結(jié)構(gòu)問題和格式問題。
四、我們需要解決的問題
雖然整體鏈路方案非常完善,但是SDK實(shí)際落地到web端運(yùn)行還存在一些問題。
內(nèi)存泄漏
我們先做個壓力測試,在不刷新頁面的情況,看下網(wǎng)頁端內(nèi)存如何變化:
1.準(zhǔn)備32個50MB以內(nèi)的小視頻,1個800MB的大視頻,內(nèi)存采樣為每3s采樣一次
2.通過一個個上傳,內(nèi)存占用一直在上升,當(dāng)傳入800MB視頻進(jìn)行檢測時,內(nèi)存占用直接飆升至3G,未被正確釋放
舊版SDK壓力測試內(nèi)存占用情況
此時控制臺報錯,內(nèi)存溢出,頁面卡死,用戶必須刷新頁面才可繼續(xù)操作。
控制臺內(nèi)存溢出報錯
ArrayBuffer異常占用
大視頻無法檢測
傳入1.9GB的視頻文件,控制臺直接報錯,無法申請1.9GB的內(nèi)存。
大文件傳入報錯
檢測速度慢
一步步通過裁剪,縮小視頻大小,800MB時視頻傳入檢測成功,耗時94630ms。
五、內(nèi)存優(yōu)化方案
針對以上問題,我們一個個來看:
內(nèi)存溢出:先了解下現(xiàn)有代碼中內(nèi)存是如何被分配和銷毀的,下面是部分核心代碼。
IO核心代碼
大致的流程如下:
圖片
通過流程圖可見內(nèi)存已經(jīng)被正確的申請和釋放了,但是實(shí)際表現(xiàn)確是申請的內(nèi)存一直在被占用,所以可能不是代碼邏輯問題,在翻閱了wasm官方設(shè)計(jì)文檔時發(fā)現(xiàn)一個issue,其中提到了wasm內(nèi)存設(shè)計(jì)方案存在幾個問題(該issue還處于open狀態(tài)):
wasm內(nèi)存設(shè)計(jì)問題
所以正是由于第2、3點(diǎn)導(dǎo)致了wasm占用的內(nèi)存只能被擴(kuò)大,而無法通過釋放被縮小,一旦過多的使用malloc內(nèi)存占用達(dá)到wasm的最大內(nèi)存限制后,申請必將失敗,后續(xù)的鏈路也就無法繼續(xù)了。那么排除其他因素,通過代碼驗(yàn)證一下吧,我們只需要將extract_video_data函數(shù)修改一下,直接釋放掉接收的內(nèi)存指針指向的內(nèi)存塊:
修改代碼片段
依舊還是之前的物料進(jìn)行壓力測試,可見內(nèi)存整體增長情況態(tài)勢與之前別無二致,所以基本可以確認(rèn),內(nèi)存一直占用的問題是wasm底層Memory的設(shè)計(jì)導(dǎo)致的,所以malloc函數(shù)我們肯定是用不了了。
驗(yàn)證內(nèi)存采集
大視頻無法檢測:通過分析"IO核心代碼"一圖可以看到,視頻文件是轉(zhuǎn)成ArrayBuffer后通過forEach把一個個的字節(jié)塞入了提前申請好的內(nèi)存中來實(shí)現(xiàn)數(shù)據(jù)傳遞的,這種方案存在兩個問題:
1. 效率低,不考慮文件轉(zhuǎn)ArrayBuffer的時間,光遍歷動不動就上百兆量級的buffer需要的時間都是巨量的,經(jīng)過測試800MB文件想要全部轉(zhuǎn)化為Uint8ClampedArray,然后寫入到wasm內(nèi)存中耗時大約在14秒左右,這就是導(dǎo)致檢測速度慢的一個原因。
2. ArrayBuffer的size是存在最大限制的,以chrome為例,這個限制是2GB,導(dǎo)致在js側(cè)檢測視頻理論大小限制為了2GB,wasm側(cè)最大可申請內(nèi)存也存在限制,大約在1.6GB左右。在使用malloc申請到內(nèi)存空間后又將其傳入avio_alloc_context,avio_alloc_context內(nèi)部將再次申請buffer_size大小的內(nèi)存空間進(jìn)行數(shù)據(jù)緩存, 從而導(dǎo)致實(shí)際檢測視頻最大為800MB。
所以ArrayBuffer這種方案是不能使用的,天然存在限制。那么到此問題原因都找到了,歸根結(jié)底都出在文件的IO上,那么是否可以換一種思路,如果不需要任何的數(shù)據(jù)格式的轉(zhuǎn)換,使wasm環(huán)境下的ffmpeg直接讀取到文件這樣肯定是最省時且高效的。那么ffmpeg能直接讀取文件嗎,答案是肯定的。
圖片
avformat_open_input可以傳入文件路徑并能將AVFormatContext自動分配并寫入ps。意思是只要能拿到目標(biāo)文件的文件路徑,就能直接調(diào)用avformat_open_input讀取文件到IOContext,省去了將數(shù)據(jù)手動塞入IOContext的邏輯,也就意味著能繞過多余的內(nèi)存的申請和釋放,且不再需要進(jìn)行數(shù)據(jù)格式的轉(zhuǎn)換,可是怎樣才能拿到目標(biāo)文件路徑呢?
因?yàn)镾DK的運(yùn)行環(huán)境是在web端,web端想要訪問本地文件只能通過file類型的input拿到文件對象,那傳入blobUrl行不行呢,經(jīng)過測試,傳入blobUrl會報Permission denied 無權(quán)限的錯誤,因?yàn)閣asm的文件系統(tǒng)有自己的特殊實(shí)現(xiàn),其并不能與JavaScript 直接進(jìn)行交互,讀取文件需要通過nodefs.js,idbfs.js,workerfs.js,proxyfs.js這幾個js分別構(gòu)造的虛擬文件系統(tǒng)才行,其分別是:
- NODEFS,在node環(huán)境中使用的文件系統(tǒng)
- IDBFS,在web瀏覽器中可使用的文件系統(tǒng),強(qiáng)依賴IndexedDB
- WORKERFS,工作在web瀏覽器,且只能運(yùn)行在webWorker中的文件系統(tǒng)
- PROXYFS,允許通過代理的方式訪問本地文件系統(tǒng)或遠(yuǎn)程文件系統(tǒng),主要用于將文件操作傳遞到JavaScript中的其他實(shí)現(xiàn)。這種機(jī)制使得WebAssembly模塊能夠與JavaScript代碼進(jìn)行交互,進(jìn)而訪問不同的文件系統(tǒng)或數(shù)據(jù)源
由此看來想要提供給avformat_open_input目標(biāo)文件的路徑,我們還需將目標(biāo)文件掛載到一個虛擬文件系統(tǒng)中。那么該如何選擇呢?
因?yàn)槲覀兊腟DK是需要運(yùn)行在web瀏覽器中,那么NODEEFS首先就被排除掉了,其次視頻的讀取檢測屬于計(jì)算密集型任務(wù),是需要運(yùn)行在webWorker中的,所以WORKERFS與我們的使用場景更加契合,他提供對webWorker中的file和Blob對象的只讀訪問,而無需將整個數(shù)據(jù)復(fù)制到內(nèi)存中,非常適合對大型文件的讀取,也滿足了我們對于快速讀取和內(nèi)存占用少的要求,簡直完美。
那么說干就干,webWorker + WORKERFS的方案需要對打包命令和代碼進(jìn)行改造。首先啟用WORKERFS需要在wasm的打包命令中添加-l workerfs.js參數(shù),并且導(dǎo)出運(yùn)行時函數(shù)WORKERFS,完整命令如下:
emcc -O3 \
-I ${FFMPEG_DIST_DIR}/include \
-L ${FFMPEG_DIST_DIR}/lib -l avcodec -l avformat -l swresample -l avutil -l workerfs.js\
-I ${CJSON_SOURCE_DIR} \
-s EXPORTED_FUNCTIONS="['_get_video_info', '_extract_video_data']" \
-s WASM=1 \
-s ALLOW_MEMORY_GROWTH=1 \
-s EXPORTED_RUNTIME_METHODS=[\"wasmMemory\", \"FS\", \"WORKERFS\", \"ccall\"]"
-fsanitize=address \
-o ${workspaceFolder}/sociality/main.js ${workspaceFolder}/sociality/main.c \
${CJSON_SOURCE_DIR}/cJSON.c ${CJSON_SOURCE_DIR}/cJSON_Utils.c
如果導(dǎo)出成功就能在wasm模塊中看到WORKERFS的實(shí)例:
圖片
導(dǎo)出成功后想要使用的話只需要在webworker中創(chuàng)建任意文件夾,將目標(biāo)文件通過mount方法掛載到該文件夾上就行,直接上代碼:
WORKERFS在Webworker中的使用
然后修改C語言側(cè)extract_video_data方法:
圖片
文件就能正確地被讀取和處理了??梢娺@個方案非常的簡潔且省去了巨量的IO操作,效率提升了,但是內(nèi)存占用問題還存不存在呢,再次跑個壓力測試試一試:
對比老SDK內(nèi)存占用情況
用同樣的視頻物料進(jìn)行壓力測試,得出的內(nèi)存占用情況如圖,可見優(yōu)化后內(nèi)存使用在壓力測試后一直維持在900MB左右,且繼續(xù)傳入大視頻文件不會繼續(xù)上漲,判斷為正常內(nèi)存占用(綠色線條),檢測速度也做了一個粗略的統(tǒng)計(jì),與舊版SDK對比,性能方面,以800MB文件為例,檢測時長分別為20s和95s,性能預(yù)計(jì)提升約78%;2GB視頻文件檢測時長為61s,對于更大的視頻也能輕松應(yīng)對。至此所有的問題都已解決。目前該功能已上線:得物創(chuàng)作者平臺。
六、總結(jié)
通過WebAssembly技術(shù)的引入與整合,我們在視頻損壞檢測上迎來了新的機(jī)遇。在逐步解決技術(shù)挑戰(zhàn)的過程中,完善了我們的視頻上傳流程,并提升了用戶體驗(yàn)。展望未來,我們希望繼續(xù)優(yōu)化這些功能,確保用戶能夠在平臺上無障礙地上傳和分享他們的創(chuàng)作,進(jìn)一步提升社區(qū)的活躍度和用戶粘性。