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