得物視訊編輯工具優化全指南
得物視訊編輯工具優化全指南
原創 得物技術 運維 11/29 18:48 閱讀數 1.4K 本文被收錄於專區 多媒體處理 進入專區參與更多專題討論一、背景介紹
隨著 4G 網路的推廣和網路頻寬的提升,視訊成為網際網路使用者主要的消費載體,使用者通過短視訊來分享和瀏覽資訊。由此視訊的編輯功能越來越重要、越來越普遍。視訊編輯的 App 也如雨後春筍般湧現。 為更好地推動得物 App 社群業務的發展,得物也自研符合得物需求的視訊編輯工具。我們致力於打造一個 “更快、更強” 的視訊編輯工具。
二、視訊編輯工具介紹
為了讓大家更好地瞭解得物 App 的視訊編輯工具,我們先簡單介紹一下視訊編輯工具的主要功能。
下面是得物 App 視訊編輯工具的主要功能:
視訊編輯工具的重點如下:
-
視訊編輯工具需要操作的資源:
- 文字:包括普通的文字、特殊的藝術字、花字等等;
- 圖片:包括靜態圖,如 JPEG/PNG 等等,也包括 HEIC/GIF 等動態圖;
- 視訊:包括各種各樣的視訊(各種編碼和封裝格式),主流的格式一般是 MP4 的封裝格式、H264 視訊編碼格式、AAC 音訊編碼格式等等;
- 音訊:包括各種各樣的音訊(各種編碼和封裝格式),當然視訊當然也是包含音訊軌道的。
-
視訊編輯工具主要的操作方式:
- 操作圖片、視訊幀:我們知道視訊是一幀一幀的圖片組成的,所以操作視訊幀和操作圖片是一樣的道理,我們通過新增一些特效在圖片和視訊幀上面,實現一些有趣的效果來吸引使用者。
- 操作音訊:主流的操作音訊方式如倍速、調整音量、變調等等,都是現今短視訊的主要玩法。
-
視訊編輯工具最終生成的是一個新的視訊,這個視訊將特定的資源應用一些特效生成一個新的視訊。
下面的流程圖可以很方便地讓大家瞭解視訊編輯的工作流程。為了方便,我們輸入一個視訊,加上一些特效,生成一個新的視訊。
從上面的流程可以看出來,原始視訊 A.mp4 經過解封裝分離出音訊軌道和視訊軌道,對它們解碼之後,對音訊資料應用音訊特效、對視訊幀資料應用視訊特效,然後編碼封裝合成一個新的視訊。當然解碼和編碼都是有一個佇列控制的,流程圖上標註了,沒有深入展開,大家瞭解即可。
經過上面的介紹,大家對視訊編輯工具有了大概得了解,其實衡量一個視訊編輯工具做得好不好,主要從下面這幾個方面著手:
-
記憶體佔用情況
-
匯出視訊的速度如何
-
匯出視訊的清晰度如何
下面從這三方面詳細展開給大家闡述得物 App 的視訊編輯工具優化的心路歷程。
三、記憶體優化
效能是所有程式好不好的首要指標,一個工具即使功能再強大,但是一點就崩潰,或者用著用著記憶體暴漲、應用卡死,估計這個應用不能稱為一個優秀的應用,下面我們具體談一談視訊編輯工具的優化檢測方案。
優化記憶體從良好的編碼習慣開始,尤其對音視訊這種對記憶體需求非常高的應用而言。例如一個 1080 * 1920 的視訊,解碼出來原始資料一幀圖片大小也是 1080 * 1920,佔用記憶體是 1080 * 1920 * (8 * 3 ) / 8 = 5.93 MB,一個視訊幀就佔用這麼大,1 秒一般有 30 幀,那得佔用 177.9MB,如果不加控制,那不管多高效能的手機也經不住這樣的折騰。希望下面的記憶體檢測和優化方案可以給你帶來一些幫助。
3.1 合理設計佇列
上面我們在介紹視訊編輯流程的視訊談到了解碼佇列和編碼佇列的概念。其實佇列這個概念在音視訊中使用非常頻繁,正是因為記憶體的限制,所以才引入佇列這個控制方式。大家可能還有點懵,但是看完下面的流程圖,我相信你一定會豁然開朗。
我們僅選取解碼的部分來分析一下佇列的重要應用。
在視訊編輯工具中有幾個重要的佇列:
-
解碼過程中:
- Video Packet Queue:視訊解碼之前 Packet 存放的佇列,一般建議的佇列大小是 100
- Audio Packet Queue:音訊解碼之前 Packet 存放的佇列,一般建議的佇列大小是 150
- Video Frame Queue:視訊解碼之後 Frame 存放的佇列,一般建議的佇列大小是 3
- Audio Frame Queue:音訊解碼之後 Frame 存放的佇列,一般建議的佇列大小是 8
-
編碼過程中:
- Encode Video Packet Queue:視訊編碼之後 Packet 存放的佇列,一般建議的大小是 100
- Encode Audio Packet Queue:音訊編碼之後的 Packet 存放的佇列,一般建議的大小是 150
按照上面的方式設計佇列的大小,可以在保證功能正常的情況下最大程度的降低記憶體佔用,提升使用者體驗。
3.2 排查記憶體洩漏
Android 上排查記憶體洩漏的方式有很多,這裡介紹兩種:
-
Asan 檢測
-
Profile 檢測
Asan 全稱是 AddressSanitizer 是一種基於編譯器的快速檢測的工具,用於檢測原生程式碼中的記憶體錯誤問題,Asan 可以解決如下四種核心問題:
-
堆疊和堆緩衝區上溢、下溢
-
釋放之後堆重新使用問題
-
超過範圍的堆疊使用情況
-
重複釋放、錯誤釋放問題
Asan 的使用方式建議參考 google 官方文件,這兒就不多作介紹了: https://github.com/google/sanitizers/wiki/AddressSanitizer
關於 Profile 的使用,如果需要檢測 Native 記憶體使用情況,需要滿足 API>=29,大家在使用的時候需要非常注意。
下面是我們在 demo 中應用 Asan 抓取的堆疊:
20042-20042/? A/DEBUG: *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
20042-20042/? A/DEBUG: Build fingerprint: 'samsung/t2qzcx/t2q:11/RP1A.200720.012/G9960ZCU2AUGE:user/release-keys'
20042-20042/? A/DEBUG: Revision: '13'
20042-20042/? A/DEBUG: ABI: 'arm64'
20042-20042/? A/DEBUG: Timestamp: 2021-09-17 00:32:31+0800
20042-20042/? A/DEBUG: pid: 19946, tid: 20011, name: AudioTrack >>> com.jeffmony.audioplayer <<<
20042-20042/? A/DEBUG: uid: 10350
20042-20042/? A/DEBUG: signal 6 (SIGABRT), code -1 (SI_QUEUE), fault addr --------
2021-09-17 00:32:31.157 20042-20042/? A/DEBUG: Abort message: '=================================================================
==19946==ERROR: AddressSanitizer: heap-use-after-free on address 0x004ac1e41080 at pc 0x007157f69580 bp 0x00705c0bb350 sp 0x00705c0bab08
READ of size 1792 at 0x004ac1e41080 thread T32 (AudioTrack)
#0 0x7157f6957c (/data/app/~~G094WKQQj7KZvdhvGYDLDA==/com.jeffmony.audioplayer-kcu1nmgzpBIQDRJDxCJDOQ==/lib/arm64/libclang_rt.asan-aarch64-android.so+0x9f57c)
#1 0x706549c228 (/data/app/~~G094WKQQj7KZvdhvGYDLDA==/com.jeffmony.audioplayer-kcu1nmgzpBIQDRJDxCJDOQ==/lib/arm64/libltpaudio.so+0x14228)
#2 0x706549bcd4 (/data/app/~~G094WKQQj7KZvdhvGYDLDA==/com.jeffmony.audioplayer-kcu1nmgzpBIQDRJDxCJDOQ==/lib/arm64/libltpaudio.so+0x13cd4)
#3 0x70654994f0 (/data/app/~~G094WKQQj7KZvdhvGYDLDA==/com.jeffmony.audioplayer-kcu1nmgzpBIQDRJDxCJDOQ==/lib/arm64/libltpaudio.so+0x114f0)
#4 0x70654a9cbc (/data/app/~~G094WKQQj7KZvdhvGYDLDA==/com.jeffmony.audioplayer-kcu1nmgzpBIQDRJDxCJDOQ==/lib/arm64/libltpaudio.so+0x21cbc)
#5 0x70654a91d4 (/data/app/~~G094WKQQj7KZvdhvGYDLDA==/com.jeffmony.audioplayer-kcu1nmgzpBIQDRJDxCJDOQ==/lib/arm64/libltpaudio.so+0x211d4)
#6 0x715af9d188 (/system/lib64/libwilhelm.so+0x1c188)
#7 0x71570ea290 (/system/lib64/libaudioclient.so+0x8b290)
#8 0x71570e9480 (/system/lib64/libaudioclient.so+0x8a480)
#9 0x7156b664d4 (/system/lib64/libutils.so+0x154d4)
#10 0x71593e9974 (/system/lib64/libandroid_runtime.so+0xa5974)
#11 0x7156b65db0 (/system/lib64/libutils.so+0x14db0)
#12 0x7156ace234 (/apex/com.android.runtime/lib64/bionic/libc.so+0xb6234)
#13 0x7156a68e64 (/apex/com.android.runtime/lib64/bionic/libc.so+0x50e64)
0x004ac1e41080 is located 0 bytes inside of 1792-byte region [0x004ac1e41080,0x004ac1e41780) freed by thread T32 (AudioTrack) here: #0 0x7157f74c64 (/data/app/~~G094WKQQj7KZvdhvGYDLDA==/com.jeffmony.audioplayer-kcu1nmgzpBIQDRJDxCJDOQ==/lib/arm64/libclang_rt.asan-aarch64-android.so+0xaac64) #1 0x70654a6d2c (/data/app/~~G094WKQQj7KZvdhvGYDLDA==/com.jeffmony.audioplayer-kcu1nmgzpBIQDRJDxCJDOQ==/lib/arm64/libltpaudio.so+0x1ed2c) #2 0x70654a6af0 (/data/app/~~G094WKQQj7KZvdhvGYDLDA==/com.jeffmony.audioplayer-kcu1nmgzpBIQDRJDxCJDOQ==/lib/arm64/libltpaudio.so+0x1eaf0) #3 0x706549bf4c (/data/app/~~G094WKQQj7KZvdhvGYDLDA==/com.jeffmony.audioplayer-kcu1nmgzpBIQDRJDxCJDOQ==/lib/arm64/libltpaudio.so+0x13f4c) #4 0x706549bcd4 (/data/app/~~G094WKQQj7KZvdhvGYDLDA==/com.jeffmony.audioplayer-kcu1nmgzpBIQDRJDxCJDOQ==/lib/arm64/libltpaudio.so+0x13cd4) #5 0x70654994f0 (/data/app/~~G094WKQQj7KZvdhvGYDLDA==/com.jeffmony.audioplayer-kcu1nmgzpBIQDRJDxCJDOQ==/lib/arm64/libltpaudio.so+0x114f0) #6 0x70654a9cbc (/data/app/~~G094WKQQj7KZvdhvGYDLDA==/com.jeffmony.audioplayer-kcu1nmgzpBIQDRJDxCJDOQ==/lib/arm64/libltpaudio.so+0x21cbc) #7 0x70654a91d4 (/data/app/~~G094WKQQj7KZvdhvGYDLDA==/com.jeffmony.audioplayer-kcu1nmgzpBIQDRJDxCJDOQ==/lib/arm64/libltpaudio.so+0x211d4) #8 0x715af9d188 (/system/lib64/libwilhelm.so+0x1c188) #9 0x71570ea290 (/system/lib64/libaudioclient.so+0x8b290)
顯示 message 是:heap-use-after-free on address 0x004ac1e41080 說明是使用了已經釋放掉的記憶體了,再繼續看,這個記憶體具體在什麼地方被釋放的?0x004ac1e41080 is located 0 bytes inside of 1792-byte region [0x004ac1e41080,0x004ac1e41780) Asan 一個很大的優勢就是可以追蹤記憶體釋放的路徑,防止出現記憶體洩漏和野指標問題,特別是野指標,一旦出現特別難排查,簡直是 C++ 開發的噩夢,希望大家用好工具,同時培養良好的 C++ 編碼習慣。
3.3 優化執行緒
另一個影響記憶體的重要因素是執行緒,視訊編輯工具涉及到的執行緒非常多,執行緒的使用得遵循一些基本的原則:
-
儘量少建立執行緒
-
儘量少使用 pthread_mutex_t
-
本著功能隔絕原則使用執行緒
-
能同步就別異步
以編輯模組為例,這兒列一下我們使用到的所有執行緒:
-
GL 處理執行緒
-
視訊解封裝執行緒
-
視訊中視訊軌道解碼執行緒
-
視訊音訊軌道解碼執行緒
-
抽取縮圖執行緒
-
音訊編碼執行緒
-
視訊編碼執行緒
-
視訊封裝執行緒
如果插入了獨立的音訊檔案,還需要新增兩個額外的執行緒:
-
音樂檔案播放執行緒
-
音樂檔案解碼執行緒
上面列出的是一個視訊編輯工具能正常工作所必備的最少執行緒,如果你的視訊編輯工具中多了什麼執行緒,我們建議可以適當優化一下,畢竟少一個執行緒,可以少一分開銷,而且少一分執行緒同步的工作。
我們在底層也按照 Android 的訊息機制重寫了一套 C++ 層的訊息分發 SDK,這個我們後續會另外分享文章闡釋我們定製的訊息分發 SDK,這兒點到為止。
四、提升匯出視訊的速度
我們使用視訊編輯工具,最終是希望匯出一個視訊,如果這個匯出的過程很慢,那肯定是無法忍受的,從上面的介紹我們已知視訊的匯出需要經過 “解碼 —— 應用特效 —— 編碼” 的過程,其中解碼和編碼這兩個過程對速度的影響至關重要。因為解碼和編碼視訊需要耗費大量的資源,目前主要有兩種方式 ——“軟解 / 編碼” 和 “硬解 / 編碼”。
如果你使用過 FFmpeg 或者其他使用 CPU 進行視訊編解碼的來處理視訊的話,你可能已經遇到了處理速度慢的問題。這主要是因為軟編碼和軟解碼使用 CPU 進行運算,而 CPU 在處理視訊上的速度遠低於 DSP 晶片;簡而言之 “軟解 / 編碼” 主要通過 CPU 來工作,通過 CPU 來主導大量的計算工作,是原始的處理方式,當然耗費的時間也比較長;“硬解 / 編碼” 是通過 GPU 來處理,GPU 是專用的圖形處理晶片,對視訊的解碼和編碼有專門的優化,所以編碼和解碼的速度非常快。
Android 上使用 MediaCodec 來實現 “硬解 / 編碼”,iOS 上使用 VideoToolBox 來實現 “硬解 / 編碼”,這裡著重介紹 Android 上編碼解碼的速度優化。
從上面的流程我們可以看出,編碼在解碼的後面,一個時長 60s(30fps)的視訊,需要解碼 1800 幀,然後編碼 1800 幀視訊才能完整生成另外一個視訊,這樣序列的等待是耗時的主要原因。
這時候我們參考多執行緒方案,將一個 60s 的視訊均分為兩段,然後這兩段視訊同時進行解碼操作,生成匯出了兩個 30s 的臨時快取視訊檔案,隨後將這兩個 30s 的視訊合併為一個 60s 的 B.mp4 視訊,最後刪除臨時快取檔案,這樣我們只需要同時處理 900 幀的資料,理論上可以提升一倍的匯出速度。
這就是並行匯出,下面是得物 App 並行匯出的基本流程。
首先我們要明確匯出視訊是需要消耗資源的,這個資源就是 MediaCodec,最終是送入到 GPU 中處理,一個手機中的 MediaCodec 例項是有限的,正常情況下,一個手機可以提供的 MediaCodec 例項最多有 16 個,如果當前使用的 MediaCodec 例項超過 16 個,那麼手機將無法正常工作。MediaCodec 資源是手機中的所有 App 共同持有。所以並行分段的個數不是越多越好。
-
只有一段,需要兩個 MediaCodec(一個用來解碼視訊,一個用來編碼視訊),注意:音訊的解碼和編碼可以不要用 MediaCodec,畢竟音訊的耗時少多了,不是瓶頸。
-
分成兩段需要四個 MediaCodec,分成三段需要六個 MediaCodec,分成四段需要八個 MediaCodec,以此類推。
下面是並行匯出的測試結果:
兩段並行速度提升 50% ~ 70%,記憶體增加 20%, 三段並行速度提升 60% ~ 90%,記憶體增加 80%;並行超過三段的話就無法明顯提升速度了。我們比較建議並行兩段,在一些效能很好的機型上並行三段。
如果有些同學對視訊匯出過程中檔案操作還有疑問的,下面的示意圖可以比較清楚地看出並行匯出操作本地檔案的過程:
-
並行匯出的過程中,生成了兩個臨時檔案
-
並行匯出完成後,這兩個臨時檔案合併為一個新的檔案,兩個臨時生成的檔案被刪除了(節省使用者寶貴的儲存空間)
-
原始檔案 jeffmony_out.mp4 並沒有被刪除 / 修改
Tips:目前我們在處理過程中生成的臨時檔案和最終的適配檔案都會儲存在 /sdcard/Pictures/duapp/Compile/ 下,而在處理完成後的臨時檔案清理過程會觸發在某些機型上的保護機制,建議後續調整到 App 的私有目錄下。
當然還有其他的提升匯出速度的建議,例如在視訊幀特效處理的過程中,我們建議:
-
儘量採用 FBO/EBO/ABO 方式處理 texture
-
紋理如果過大要進行壓縮
-
嚴禁採用 glFinish ()
這些做法都是我們在視訊編輯開發過程中的切實經驗,希望能給大家帶來一些幫助。
五、提升匯出視訊的清晰度
一個視訊編輯功能是否足夠優秀,其中的一個重要指標就是同等條件下匯出的視訊是否足夠清楚,通常而言,衡量視訊是否清晰的有兩種方式:
-
主觀標準:找一些使用者觀看不同的視訊,根據使用者的觀感輸出視訊清晰度的對比結果,使用者一般根據色彩、畫面亮度、柔和度等來評估清晰度。
-
客觀標準:利用演算法計算視訊畫面質量分,目前比較推薦 Netflix 推出的開源庫 VMAF 來計算視訊幀的質量分。
實際上主觀標準是比較準確的,但是可操作性比較差,特別是處理海量視訊的時候,需要大量的人力,無法有效開展,因此日常工作中還是推薦客觀標準進行海量計算,主觀標準進行重點判斷。具體的可以結合業務的重要程度來開展。
下面結合我們實際的工作給出具體提升視訊清晰度的方式:
-
視訊基礎編碼資訊優化
- Profile 優化:Profile 有三種 Level,分別是 Baseline、Main、High,其中 Baseline Profile 對應清晰度最低,Android 3.0 之後的版本都支援的,Main Profile 清晰度比 Baseline Profile 清晰度要好,但是從 Android 7.0 之後才支援,High Profile 清晰度最高,也是從 Android 7.0 之後才支援。我們在設定 Encoder Profile Level 之前,需要判斷一下當前是否支援。
- Bitrate 位元速率設定: 視訊位元速率是視訊資料傳輸時單位時間內傳送的資料位數。單位是 kbps,望文生義,位元速率越大,單位時間填充的資料就越多,視訊質量就越高。但位元速率也不是設定的越大越好,超過必要限度,對視訊畫質的提升已不明顯,建議採用合適的 factor 來調整位元速率。Bitrate = width * height * frameRate * factor,其中 factor=0.15。
- Bitrate Mode: 有三種通過的編碼模式 ——VBR(可變位元速率)、CBR(固定位元速率)、ABR(平均位元速率),其中 ABR 是最好的方式,可以兼顧質量和視訊大小。
- B 幀設定: 視訊有 I 幀、P 幀、B 幀構成,其中 I 幀最大,P 幀次之,B 幀最小,我們在編碼時儘量多設定 B 幀(在合理的範圍內),並不會降低清晰度,但是可以大大降低視訊的大小,這樣我們就可以相應地調大位元速率,最終實現了提升清晰度的目標。
-
HEVC 編碼優化: 使用 HEVC 編碼,可以保證在不增加檔案大小的情況下,大大提升視訊的清晰度。在相同的影象質量下,HEVC 編碼的視訊比 H.264 編碼的視訊約減少 40%
-
色彩調優
- 綜合調整亮度、對比度、色溫、飽和度、銳度等顏色引數,進而優化整體的視訊畫面,讓視訊畫面看上去 “更清晰”。
-
** 超分演算法 **: 採用 ESRGAN 演算法,利用機器學習的優勢對圖片和視訊進行去模糊、Resize、降噪、銳化等處理,重建圖片,實現對圖片的超解析度處理。
- 特徵提取:計算噪點
- 非線性對映:放大,模糊化噪點
- 影象重建:差分,平滑過度,去噪
-
下面是使用超分演算法處理前後的對比圖,可以很明顯地看出右邊的圖更加清晰,少了很多噪點、圖片更亮、過度更平滑。
如果大家想了解視訊清晰度優化的技術細節,可以參考文章 -- 視訊清晰度優化指南
六、總結
本文開篇從介紹得物 App 的主要功能展開,提出了視訊編輯工具優化的三個維度:
-
優化記憶體佔用
-
提升視訊匯出速度
-
提升匯出視訊的清晰度
其中在 “提升視訊匯出速度” 時重點談到了 “並行匯出” 的技術方案,從最終的結果來看,視訊匯出速度的提升非常明顯,同時也非常清楚地解釋了 “並行匯出” 過程中為什麼生成臨時檔案?為什麼有必要在匯出完成之後刪除臨時檔案?盡力給使用者帶來較好的體驗。
最後在 “提升匯出視訊的清晰度” 中重點提到的超分演算法應用效果提升明顯,超分之後的視訊幀相比原幀圖更加清晰、噪點更少,而且細節部分更加真實。
後續我們還會結合 AR 特效輸出更多有意義的技術分享,敬請期待。
* 文 /Jeff Mony
關注得物技術,每週一三五晚 18:30 更新技術乾貨
要是覺得文章對你有幫助的話,歡迎評論轉發點贊~