短影片寶貝=慢?阿里巴巴工程師這樣秒開短影片。

閒魚技術發表於2018-12-20

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

02 問題分析

影片格式的選擇

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

  • 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 & 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: 我們使用的網路庫,播放器都是集團內部的,本身做了很多最佳化。本文不涉及網路協議,播放器方面的最佳化討論。

03 技術方案

鑑於上面的分析,我們要做的工作是:把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所示

04 實現模組

預載入模組

確定了技術方案後,預載入模組還是有很多工作要做的。在列表網路資料解析完成後會觸發影片預載入,首先會根據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。

05 結語

本文介紹了常見的影片編碼格式,影片封裝格式,介紹了moov頭資訊對於影片播放的影響。隨著對於播放流程的分析,我們找到了問題的切入點。簡單說就是圍繞著資料預載入展開,把網路請求資料的工作提前完成,播放的時候直接從快取讀取,而且後續的影片回看都是從快取讀取,不僅解決了影片初始化播放慢的問題,還解決了播放快取問題,可以說是一箭雙鵰。Proxy是這個方案的核心思想,本地localhost的url是一個關鍵紐帶,影片預載入模組和播放器模組解耦徹底,換了播放器照樣可以使用。到此為止,影片feeds秒開最佳化就已完成。上線後的資料來看影片開啟速度在800ms左右。

回過頭來,或許我們還可以更進一步,可以對預載入收到的資料進行驗證,確保快取了準確的資訊,而不是固定的數值。還可以進行更加深度的最佳化,讓使用者觀看影片的體驗更加順滑。

06 參考文獻
  1. *  [AndroidVideoCache](https://github.com/danikula/AndroidVideoCache)

  2. *  [影片的封裝格式和編碼格式](https://www.jianshu.com/p/8034fa1ed682)

  3. *  [播放器技術分享(1):架構設計](http://blog.51cto.com/ticktick/2324928?source=dra)

  4. *  [MP4檔案格式的解析,以及MP4檔案的分割演算法](https://cloud.tencent.com/developer/article/1120604)

  5. *  [從天貓某活動影片3次請求說起](https://juejin.im/post/5c0e0f75e51d45410c5e1aea)

  6. *  [視音訊編解碼學習工程:FLV封裝格式分析器]https://blog.csdn.net/leixiaohua1020/article/details/17934487

  7. *  https://www.adobe.com/content/dam/acom/en/devnet/flv/video_file_format_spec_v10_1.pdf

  8. *  https://baike.baidu.com/item/flv

  9. *  https://standards.iso.org/ittf/PubliclyAvailableStandards/index.html

相關文章