洞察 video 超能力系列——玩轉 mp4

發表於2018-07-11

前言

只要在 HTML5 中使用過視訊播放的同學對 video 標籤一定不會陌生,不過很多同學只使用了 video 的基礎功能,實際上 video 擁有強大潛能的,只要姿勢正確就能讓其擁有超能力。不妨從下面幾個場景來逐漸瞭解下video 未曾被髮掘的神祕空間: * 清晰度無縫切換 * 節省視訊流量

清晰度無縫切換

點播領域裡 mp4 是最普遍、相容性最好的視訊容器,不過 mp4 也有它的侷限性,比如常見的清晰度切換,我們是無法像youtube那樣做到無縫切換的。我們可以看下普通的mp4播放的網路請求和youtube視訊播放的網路請求的區別。 圖1.1 普通mp4的下載請求過程圖1.2 Youtube視訊下載請求過程 這兩張圖不難看出,在預設情況下 mp4 使用一次 http 請求所有的視訊資料,Youtube 則分次請求。當然這個描述很不專業,但確實形象。造成這種差異的是 video 不支援流式的視訊資料,Youtube 採用的是流式的視訊容器 webm,而 mp4 是非流式的。那如何解釋清楚流式的視訊資料呢,從專業的角度三言兩語很難說清楚,但用大白話翻譯過來就是流式的視訊資料支援分段獨立播放,非流式的不可以。換句話說一個10M的視訊檔案,流式的視訊可以把0~1M的資料請求回來單獨播放,但是非流式的不可以。

上面我們描述了視訊格式的不同,接下來我們要說的是第一張圖中的視訊載入是瀏覽器來控制的,通過給 video 的 src 屬性配置視訊地址,觸發播放之後瀏覽器就會開始下載了,JS干涉不了。而 Youtube 的視訊載入是通過JS來控制的,各位可以再次看下第二張圖的網路請求型別:xhr,足以證明這一點。

上面兩點搞清楚之後我們就該說下清晰度切換的事情了。這個需求大家都不陌生,但是直接使用 mp4 格式做無縫清晰度切換,難度還挺大的。先解釋下“無縫清晰度切換”的概念:從播放一個解析度的視訊到另一個解析度且保證畫面、聲音不停頓的平滑切換過程。瞭解了這個概念,大家應該知道了用 video 無縫切換 mp4 有多難。一方面,video 是不支援流式的視訊格式的,一方面,video 的載入是不受JS控制的。通過切換 video 的 src 屬性,必然會導致畫面中斷、重新請求視訊資料等。有的同學想到說利用兩個 video 再結合 z-index 來搞,但是當你生成另一個video去載入視訊的時候,無法保證兩個畫面是嚴格一致的,即使將原來的畫面暫停到一個時刻,用另一個視訊通過 currentTime 屬性與之同步,切換仍然看到畫面閃爍,基本無法和 Youtube 無縫切換的體驗匹敵。而且還會造成更多流量的浪費,背後的原因大家可以研究下 mp4 容器和 webm 容器的異同,也可以看下視訊解碼相關的文章。

還有一種方法就是將 mp4 格式統統轉碼到流式的視訊格式比如 hls、webm 等。不過這種看上去可行的方式實際上會帶來很大的成本開銷,如將大量視訊做轉碼會消耗高昂的機器資源、雙倍儲存的費用、CDN的雙倍費用等等。其實我們也是在這種背景下研究出來新的技術問題解決清晰度無縫切換的。

首先,我們改變對 mp4 視訊的播放流程,不再直接使用 video 的 src 來播放,因為我們沒有任何可以操作的空間。video不僅支援 src 屬性還支援 Blob 物件,我們就是利用後者。播放的流程如下: 圖1.3 mp4 視訊新播放流程 1. 來請求 mp4 視訊資料,這樣可以結合視訊 Range 服務,做到精確載入。
2. 編寫解析器將載入回來的部分 mp4 視訊資料進行解複用
3. 將解複用的視訊資料轉成 fmp4 格式並傳遞給 MediaSource
4. 使用 video 進行解碼完成播放

然後在做清晰度切換的時候流程如下: !圖1.4 mp4視訊清晰度切換原理示意圖 1. 播放視訊A,過程同上
2. 在某個時刻,使用者切換到播放視訊B,首先解析B的索引檔案(moov),反向計算mp4的range區間
3. 載入B的視訊區間資料
4. 解複用
5. 把資料轉換成fmp4格式並傳遞給MediaSource
6. 刪除A的部分Buffer
7. 在下一個關鍵幀自動完成畫質的切換

圖1.5 mp4視訊清晰度切換流程示意圖 這個過程看上去比較繁瑣,但是所有的操作都是在瀏覽器端完成,也就是說都是JS來實現的。這樣之前說的所有成本問題都不存在,還能做到youtube相同體驗的無縫切換。如果大家也想使用這個功能不需要自己再去實現一遍上述流程,可以使用如下程式碼: 如果對這段程式碼有什麼疑惑或者想深入瞭解下它背後是如何實現的可以參考:https://github.com/bytedance/xgplayer

節省視訊流量

使用 video 的同學基本上都是這樣用的,如下: 1. 利用src屬性
2. 利用source標籤
這樣就可以播放視訊了,不過前面我們講過這樣使用 video ,視訊的載入是受瀏覽器控制的,可以看下瀏覽器在視訊剛開始播放的時候下載了多少資料: 圖2.1 video預設下載截圖 我隨便找了個視訊,大家看下視訊總長度是 02:08,在播放到 00:05 的時候,瀏覽器已經下載到 01:30 了,如果使用者終止觀看,下載的視訊就這樣被浪費掉了。當然,如果不斷的 seek 也會造成較多的流量浪費。按照我們之前的統計在短視訊領域,使用者 seek 的頻率在 80%,所以這部分流量是可以節省掉的。具體原理如下:

圖2.2 播放器載入視訊原理 1. 設定每次載入的資料包大小
2. 設定預載入時長
3. 開啟載入佇列,完成第一次資料包下載,判斷緩衝時間和預載入時長是否滿足,不滿足請求下一個資料包

具體實現程式碼如下: 這樣就實現了視訊在播放過程中永遠只預載入10秒的資料,進而保證節省流量。

瞭解超能力西瓜播放器是如何煉成的: http://h5player.bytedance.com

相關文章