隨着短視頻興起,各大APP中短視頻隨處可見,feeds流、詳情頁等等。怎樣讓用戶有一個好的視頻觀看體驗顯得愈來愈重要了。大部分feeds裏面滑動觀看視頻的時候,有明顯的等待感,體驗不是很好。針對這個問題咱們展開了一波優化,目標是:視頻播放秒開,視頻播放體驗良好。無圖無真相,上個對比圖,左邊是優化以前的,右邊是優化以後的:html
視頻格式的選擇git
在正式分析問題以前有必要說明下:咱們如今首頁的視頻,都是320p H.264編碼的mp4視頻。github
H.264 & H.265算法
H.264也稱做MPEG-4AVC(Advanced Video Codec,高級視頻編碼),是一種視頻壓縮標準,同時也是一種被普遍使用的高精度視頻的錄製、壓縮和發佈格式。H.264因其是藍光光盤的一種編解碼標準而著名,全部藍光播放器都必須能解碼H.264。H.264相較於之前的編碼標準有着一些新特性,如多參考幀的運動補償、變塊尺寸運動補償、幀內預測編碼等,經過利用這些新特性,H.264比其餘編碼標準有着更高的視頻質量和更低的碼率. H.265/HEVC的編碼架構大體上和H.264/AVC的架構類似,也主要包含:幀內預測(intra prediction)、幀間預測(inter prediction)、轉換 (transform)、量化 (quantization)、去區塊濾波器(deblocking filter)、熵編碼(entropy coding)等模塊。但在HEVC編碼架構中,總體被分爲了三個基本單位,分別是:編碼單位(coding unit,CU)、預測單位(predict unit,PU) 和轉換單位(transform unit,TU )。 總的來講H.265壓縮效率更高,傳輸碼率更低,視頻畫質更優。看起來使用H.265彷佛是很明智的選擇, 但咱們這裏選擇的是H.264。緣由是:H.264支持的機型範圍更爲普遍。 PS:閒魚H.265視頻在寶貝詳情頁會在近期上線,敬請關注體驗!
TS是日本高清攝像機拍攝下進行的封裝格式,全稱爲MPEG2-TS。TS即"Transport Stream"的縮寫。MPEG2-TS格式的特色就是要求從視頻流的任一片斷開始都是能夠獨立解碼的。下述命令能夠把mp4轉換成ts格式,從結果來看ts文件(4.3MB)比mp4文件(3.9MB)大10%左右。瀏覽器
ffmpeg -i input.mp4 -c copy output.ts
FLV是FLASH VIDEO的簡稱,FLV流媒體格式是隨着Flash MX的推出發展而來的視頻格式。因爲它造成的文件極小、加載速度極快,使得網絡觀看視頻文件成爲可能,它的出現有效地解決了視頻文件導入Flash後,使導出的SWF文件體積龐大,不能在網絡上很好的使用等問題。FLV只支持一個音頻流、一個視頻流,不能在一個文件裏包含多路音頻流。音頻採樣率不支持48k,視頻編碼不支持H.265。相同編碼格式下,文件大小和mp4幾乎沒有區別。緩存
ffmpeg -i input.mp4 -c copy output.flv
MP4是爲你們所熟知的一種視頻封裝格式,MP4或稱MPEG-4第14部分是一種標準的數字多媒體容器格式。MPEG-4第14部分的擴充名爲.mp4,以存儲數字音頻及數字視頻爲主,但也能夠存儲字幕和靜止圖像。因其可容納支持比特流的視頻流,MP4能夠在網絡傳輸時使用流式傳輸。其兼容性很好,幾乎全部的移動設備都支持,並且還能在瀏覽器、桌面系統進行播放。綜合上面幾個封裝格式的特色,咱們的最終選擇是MP4.服務器
一個視頻在客戶端的播放流程是怎麼樣的?播放首開慢耗時在什麼地方?耗時點是否可以快速低成本的解決?瞭解視頻的播放流程有助於找到問題的突破口。視頻從加載到播放能夠分爲三個階段:網絡
能夠看出內容獲取從「服務器」改成「本地」,這樣會節省很大一部分時間,並且成本很低,是一個很好的切入點。事實也是如此,咱們的優化正是圍繞此點展開。架構
PS: 咱們使用的網絡庫,播放器都是集團內部的,自己作了不少優化。本文不涉及網絡協議,播放器方面的優化討論。編輯器
鑑於上面的分析,咱們要作的工做是:把mp4文件提早緩存一部分,到feeds滑動要播放的時候,播放本地的mp4文件。因爲用戶可能繼續觀看視頻,因此本地的數據播放完後,須要從網絡下載數據進行播放。這裏須要解決兩個問題:
MOOV BOX的位置
對於第一個問題,咱們不得不分析一下mp4的文件結構,看看咱們應該下載多少數據量合適。MP4是由不少Box 組成的,Box裏面能夠嵌套Box:
這裏不詳細介紹MP4的格式信息。可是能夠看出moov box對播放很關鍵,它提供的信息如:寬高、時長、碼率、編碼格式、幀列表、關鍵幀列表等等。播放器沒有獲取到moov box是沒辦法進行播放的。因此下載的數據應該要包含moov box再加上幾十幀的數據。
作了一個簡單的計算:閒魚短視頻通常最長是30s,feeds裏面的分辨率是320p,碼率是1141kb/s,ftyp+moov這個視頻的數據量在31kb左右(打開文件能夠看出mdat是從31754byte的位置開始的),因此,頭部信息+10幀的數據大約是:(31kb + 1141kb/3)/8 = 51KB
Proxy
第二個問題:緩存數據播放完成後該怎麼切換到網絡數據呢?在本地數據播放完成以後,設置一個網絡地址給播放器,告訴播放器下載的offset是多少,而後繼續從網絡下載數據播放。這樣看起來可行,可是須要播放器提供支持:本地數據播放完成的回調;設置網絡url並支持offset。另外,服務端須要支持range參數,並且切換到網絡播放的時候須要新創建網絡鏈接,極可能會形成卡頓。
最終,咱們選擇了proxy的方式,把proxy做爲中間人,負責預加載數據、給播放器提供數據,切換邏輯在proxy裏面來完成。未加入proxy以前流程是這樣的:
加入了proxy以後流程是這樣的:
這樣作的好處很明顯,咱們能夠在proxy裏面作不少事情:例如本地文件緩存數據和網絡數據的切換工做。甚至是和CDN使用其它的協議進行通訊。咱們這裏假定預加載工做已經完成,看看播放器是怎麼和proxy進行交互的。播放的時候會用Proxy提供的一個localhost的url進行播放,這樣代理服務器會收到網絡請求,把本地預加載的數據返回給播放器。播放器徹底感知不到proxy模塊、預加載模塊的存在。播放器、預加載模塊都是Proxy的client,調用邏輯都是同樣。圖示說明以下:
下面逐步解釋一下,數據的加載過程:
預加載模塊
肯定了技術方案後,預加載模塊仍是有不少工做要作的。在列表網絡數據解析完成後會觸發視頻預加載,首先會根據url生成md5值,而後去查看這個md5值對應的任務是否存在,若是存在則不會重複提交。生成任務後會提交到線程池,在後臺線程進行處理。網絡從Wifi切換到3G的時候,會把任務取消,防止消耗用戶的數據流量。
預加載任務在線程池執行的時候,其流程是這樣的:首先會獲取一個本地代理的url。而後發起http請求。Proxy會收到http請求進行處理,開始作真正的數據預加載工做。預加載模塊讀取到指定的數據量後終止。到此,預加載的任務就已完成。流程圖以下所示:
在用戶快速滑動的時候,怎麼能保證視頻還能繼續秒開呢?預加載模塊對於每個任務都會維護一個狀態機,在Fling的時候會把劃過的任務暫停下,把最新要顯示的任務優先級提升,讓其優先執行。
Proxy模塊
Proxy內部有個local的httpServer負責攔截播放器和預加載模塊的http請求。client在請求時會帶入CDN的url,在本地緩存數據沒有的時候會去CDN獲取新鮮數據。由於有多個地方向Proxy請求數據,因此用線程池來處理多個client的鏈接頗有必要,這樣多個client能夠並行,不會由於前面有client在請求而阻塞。文件緩存使用LruDiskCache,在超過指定文件大小後,老的緩存文件會刪除,這是一個在使用文件緩存時很容易忽視的問題。因爲咱們的場景視頻是連續播放的,不存在seek的狀況,因此文件緩存相對比較簡單,不用考慮文件分段的狀況。Proxy內部對於同一個url會映射到一個client,若是預加載和播放同時進行,數據只會有一份,不會去重複下載數據。再來一個Proxy內部構造示意圖:
遇到的問題
在測試中發現,有的視頻仍是會播放很慢,仔細查看本地的確緩存了指望的數據大小,可是播放的時候仍是有較長的等待時間,這種視頻有個特色:moov box在尾部。對於moov在尾部的視頻,是整個文件都下載完成後才進行播放的,緣由是moov box裏面存了不少關鍵信息,前面分析mp4格式的時候有提到。對於這個問題有兩個解法:
服務端在進行轉碼的時候保證moov的頭部在前面,發現moov位置不正確的視頻服務端進行訂正。
PS:查看moov在文件中的位置能夠用hex文本編輯器打開,按字符搜索moov所在的位置便可,MAC上面還可使用MediaParser , 另外還能夠用ffmpeg命令生成moov在頭部或者尾部的mp4文件。
例如: 從1.mp4 copy一個文件,使其moov頭在尾部
ffmpeg -i 1.mp4 -c copy -f mp4 output.mp4
從1.mp4 copy一個文件,使其moov頭在頭部:
ffmpeg -i 1.mp4 -c copy -f mp4 -movflags faststart output2.mp4
不用修改moov box的位置,而是在播放端進行處理,播放端須要檢測流信息,若是moov前面沒有,就去請求文件的尾部信息。具體就是:發起 HTTP MP4 請求,讀取響應 body 的開頭,若是發現 moov 在開頭,就接着往下讀mdat。若是發現開頭沒有,先讀到 mdat,立刻 RESET 這個鏈接,而後經過 Range 頭讀取文件末尾數據,由於前面一個 HTTP 請求已經獲取到了 Content-Length ,知道了 MP4 文件的整個大小,經過 Range 頭讀取部分文件尾部數據也是能夠的。示意圖以下
這個方案的缺點是:對於moov box在尾部的視頻會多兩次http connection。
本文介紹了常見的視頻編碼格式,視頻封裝格式,介紹了moov頭信息對於視頻播放的影響。隨着對於播放流程的分析,咱們找到了問題的切入點。簡單說就是圍繞着數據預加載展開,把網絡請求數據的工做提早完成,播放的時候直接從緩存讀取,並且後續的視頻回看都是從緩存讀取,不只解決了視頻初始化播放慢的問題,還解決了播放緩存問題,能夠說是一舉兩得。Proxy是這個方案的核心思想,本地localhost的url是一個關鍵紐帶,視頻預加載模塊和播放器模塊解耦完全,換了播放器照樣可使用。到此爲止,視頻feeds秒開優化就已完成。上線後的數據來看視頻打開速度在800ms左右。
回過頭來,或許咱們還能夠更進一步,能夠對預加載收到的數據進行驗證,確保緩存了準確的信息,而不是固定的數值。還能夠進行更加深度的優化,讓用戶觀看視頻的體驗更加順滑。
* [AndroidVideoCache](https://github.com/danikula/AndroidVideoCache) * [視頻的封裝格式和編碼格式](https://www.jianshu.com/p/8034fa1ed682) * [播放器技術分享(1):架構設計](http://blog.51cto.com/ticktick/2324928?source=dra) * [MP4文件格式的解析,以及MP4文件的分割算法](https://cloud.tencent.com/developer/article/1120604) * [從天貓某活動視頻3次請求提及](https://juejin.im/post/5c0e0f75e51d45410c5e1aea) * [視音頻編解碼學習工程:FLV封裝格式分析器]https://blog.csdn.net/leixiaohua1020/article/details/17934487 * https://www.adobe.com/content/dam/acom/en/devnet/flv/video_file_format_spec_v10_1.pdf * https://baike.baidu.com/item/flv * https://standards.iso.org/ittf/PubliclyAvailableStandards/index.html
本文爲雲棲社區原創內容,未經容許不得轉載。