天天看點

短視訊寶貝=慢?阿裡巴巴工程師這樣秒開短視訊

作者:閑魚技術-鄰雲

前言

随着短視訊興起,各大APP中短視訊随處可見,feeds流、詳情頁等等。怎樣讓使用者有一個好的視訊觀看體驗顯得越來越重要了。大部分feeds裡面滑動觀看視訊的時候,有明顯的等待感,體驗不是很好。針對這個問題我們展開了一波優化,目标是:視訊播放秒開,視訊播放體驗良好。無圖無真相,上個對比圖,左邊是優化之前的,右邊是優化之後的:

短視訊寶貝=慢?阿裡巴巴工程師這樣秒開短視訊

問題分析

視訊格式的選擇

在正式分析問題之前有必要說明下:我們現在首頁的視訊,都是320p H.264編碼的mp4視訊。

  • H.264 & H.265
    H.264也稱作MPEG-4AVC(Advanced Video Codec,進階視訊編碼),是一種視訊壓縮标準,同時也是一種被廣泛使用的高精度視訊的錄制、壓縮和釋出格式。H.264因其是藍光CD光牒的一種編解碼标準而著名,所有藍光播放器都必須能解碼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 & FLV & MP4

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.

播放流程

一個視訊在用戶端的播放流程是怎麼樣的?播放首開慢耗時在什麼地方?耗時點是否能夠快速低成本的解決?了解視訊的播放流程有助于找到問題的突破口。視訊從加載到播放可以分為三個階段:

  • 讀取(IO):“擷取” 内容 -> 從 “本地” or “伺服器” 上擷取
  • 解析(Parser):“了解” 内容 -> 參考 “格式&協定” 來 “了解” 内容
  • 渲染(Render):“展示” 内容 -> 通過揚聲器/螢幕來 “展示” 内容
短視訊寶貝=慢?阿裡巴巴工程師這樣秒開短視訊

可以看出内容擷取從“伺服器”改為“本地”,這樣會節省很大一部分時間,而且成本很低,是一個很好的切入點。事實也是如此,我們的優化正是圍繞此點展開。

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,調用邏輯都是一樣。圖示說明如下:

短視訊寶貝=慢?阿裡巴巴工程師這樣秒開短視訊

下面逐漸解釋一下,資料的加載過程:

  • Client發起http請求擷取資料,箭頭1所示
  • 檔案緩存如果存在所請求的資料則直接傳回資料,箭頭2所示
  • 若本地檔案緩存資料不夠,則發起網絡請求,向CDN請求資料,箭頭3所示
  • 擷取網絡資料,寫入檔案緩存,箭頭4所示
  • 傳回請求的資料給Client,箭頭2所示

實作子產品

預加載子產品

确定了技術方案後,預加載子產品還是有很多工作要做的。在清單網絡資料解析完成後會觸發視訊預加載,首先會根據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