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

位元組跳動技術團隊發表於2019-02-25

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


從2016年10月(Chrome 54)開始,Chrome不再內建flash,而是改為使用者第一次訪問flash資源時提示安裝。從Chrome62開始,不再提供“click to play的選項”,改為點選視訊box後,左上方彈出

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

這意味著,flash作為過時的標準將被新技術所取代。

前言

從我們可以在網站上播放視訊開始,到h5播放器們如火如荼地發展之前,使用flash一直都是web播放視訊的不二之選。甚至於說得更加廣泛一些,在html5成為主流之前,網站的多媒體能力,包括動畫、遊戲和視訊一直都是adobe生態在掌控。那麼隨著html5標準的推出,這一切都成為了過去式。

flv與直播方案

flv的全程是 Flash Video,顧名思義,就是專門給flash播放器提供的播放格式,這種格式具有結構簡單、清晰的優點,最早出現是為了解決flash匯出的swf檔案體積過大,不適合在web中播放的問題。隨著flash的逐漸淘汰,新的video標籤自然是不支援flv格式的,人們開始把web視訊的重點放在mp4或者hls上,但是隨著直播這種視訊形式的火熱興起,flv迎來了新一輪的生機。

我們來橫向對比一下可用的直播方案:

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

flv播放實現·跳轉·清晰度切換

可以看到,flv由於其編碼格式的特點,只需要一個MetaData 以及音視訊Track各自的Header就可以在任意的時間點播放,極大地符合實時直播的需求,在GOP足夠小的情況下甚至可以達到0延遲,可以說在現有的技術方案裡,http-flv是最理想的一個,正是因此,flv依然是web播放器不可或缺的一個格式支援。

首先我們通過一張圖瞭解一下前端播放flv的過程

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

從上圖可以看到,flv播放的過程其實是從flvt中提取元資訊、音視訊header以及資料,然後轉碼成fmp4盒子結構的過程,再通過MSE交給video的過程。因為flv的結構本身就是流式的,也就是說,它的資料被拆分到了很多個小的tag中,所以我們可以很方便地做資料的封裝,也就是說將一段flv tag資料封裝成一個獨立的moof_mdat盒子對,然後就可以直接交給MSE處理,非常的方便。這裡我們討論flv是如何處理載入、跳轉播放、重放及清晰度切換問題的

資料載入 資料載入直播和點播兩種模式,首先來聊一下點播:針對點播的資料獲取,我們採用Range這個引數作為分段載入的控制引數

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

如上圖,Range這個引數向伺服器描述了希望載入一個flv檔案的 100000 到 3987705 個位元組 這段資料,相應的,伺服器也需要能夠根據Range引數正確返回這段資料。這裡展示一個極簡的服務端程式碼:

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

從上述程式碼可以看到,服務端是先是解析range,根據range切出檔案分段,然後返回一個readableStream交給前端。

再看一下直播:直播跟點播是非常不一樣的資料流動結構,我們看一下簡單的直播流程

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

可以看到,直播的流程是一個 推流->服務端格式編碼-> 終端播放 的一個流程。那麼這裡就帶來了一個問題,我們不可以像點播時那樣去載入資料了。因為服務端實際上儲存的是一個檔案流,不斷有資料推到服務端,播放器也不存在快取一段資料這樣的情況,而是不斷向伺服器請求,只要有新的流到達服務端,播放器就要拿到這一段進行解碼播放,達到推流和拉流同步進行的一個直播效果。為了實現這種效果,在播放器內,我們使用 fetch+ streamReader,簡單的實現如下:

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

從上述程式碼我們可以看到,我們通過一個reader遞迴地從流裡面讀取資料,再將資料交給解碼器進行處理。關於直播的資料載入還有更加先進的websocket方式,這裡不再深入探討,有興趣的同學可以自行查閱相關資料。

播放時跳轉 播放時點選進度條跳轉(下稱seek)是一個非常高頻的操作,尤其是在長視訊中。使用者遇到不想看的部分,或者想重看的片段,都會點選進度條觸發seek。舉個例子,一段視訊,可能使用者實際觀看的部分只佔不到50%, 如果我們將整段視訊載入,那剩下載入的50%就浪費掉了。所以我們要做的事情就是精確地載入使用者希望播放的部分,節約流量。解決方法如下:

  1. 獲取使用者將要跳轉到的時間點,下稱seekTime根據seekTime計算出離該時間點最近的一幀的位置,稱為 startPos

  2. 以預載入時間為30s 為例, 計算出seekTime + 30s 這個時間點最近的幀位置 稱為 endPos

  3. 我們以 Range: startPos-endPos 為請求引數,向服務端請求這一段資料

  4. 解碼播放

這裡說起來簡單,但是涉及到了一個跟flv格式緊密相關的問題,那就是flv的onMetaData資訊裡是否具有keyframes這個屬性。我們上述所提到的,基於時間點計算某一幀位置的演算法,是完全依賴keyframes這個屬性的,它記錄了flv中所有關鍵幀的時間點和檔案偏移量,keyframes大概長這個樣子:

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

times中每一個時間點都對應著同位置的fileposition的偏移量,正是基於這一點,我們可以計算需要載入的資料Range。值得注意的一點是,並不是所有flv檔案都攜帶了keyframes頭,flv檔案缺失部分onMetaData屬性是很常見的事情。缺失了keyframes資訊,我們就沒辦法做跳轉了,因此我們需要藉助額外的工具幫我們補全flv的onMetaData。這裡推薦使用yamdi,一個輕量級的工具,有興趣的同學可以看一下它的使用。

清晰度切換 清晰度切換是一個非常重要的功能,為什麼重要呢,因為使用者會根據網速的快慢,去選擇更清晰或者更流暢的視訊。假設沒有這個選項,使用者看視訊卡頓了沒法切換清晰度,就只能憤憤地關掉視窗了。那麼多個清晰度的視訊源我們如何做到無縫的切換呢?我們分情況來討論一下:

點播中的清晰度切換方案 點播的切換清晰度是比較容易實現的,流程如下

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

如圖,簡而言之,就是嘗試載入視訊B,通過從視訊B的onMetaData中提取關鍵幀資訊,推算出當前應該載入哪一個關鍵幀,然後載入這個位置之後的資料,直到資料載入到之後,將視訊B的資料交給MSE,同時,清除掉之前視訊A在buffer中的快取。

直播中的清晰度切換方案 直播清晰度切換目前還沒有發現無縫的方案,因此建議在切換時,直接重建解碼器以及MSE,關於這方面的問題我們還在探究。更多內容請關注我們的開源播放器,也歡迎來我們github提issue。

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

相關文章