作者 | 阿里文娛前端技術(shù)專家 歸影
責(zé)編 | 夕顏
出品 | CSDN(ID:CSDNnews)
這不是一篇基于MSE開發(fā)Web播放器的入門文章,而是圍繞Web播放器開發(fā)遇到的常見問題與解決方案,畢竟入門文章常有而趟坑干貨不常有。如果您有Web播放開發(fā)經(jīng)驗和音視頻技術(shù)基礎(chǔ),讀起來會更有共鳴。
Web播放器開發(fā)基礎(chǔ)知識
先介紹Web播放器開發(fā)的一些基礎(chǔ)知識。有人要問了,Web播放器開發(fā)難道不是一個video標(biāo)簽就夠了么?非也!
1、瀏覽器Video支持的格式非常有限
在W3C的標(biāo)準(zhǔn)里面Video只支持MP4格式 準(zhǔn)確的說是ISOBMFF(Fragment MP4)。當(dāng)然chrome支持WEBM,safari支持HLS(MPEG-TS)這都是自家的私有實現(xiàn),做不得數(shù)。
2、瀏覽器Video無法逐個加載視頻切片
現(xiàn)在主流的流媒體點播/直播技術(shù),都會把視頻切片。而video標(biāo)簽src只能掛載整個MP4資源。沒法逐個的加載視頻分段。
所以我們的主角出場—— MediaSource Extenstion,簡稱MSE,是一套能不斷的把音視頻二進(jìn)制數(shù)據(jù)塞給video標(biāo)簽播放的API。
圖1:MSE簡明結(jié)構(gòu)
MSE內(nèi)部可以創(chuàng)建一系列的sourcebuffer,一般是一個音頻buffer,一個視頻buffer。把MSE做成blob url之后綁定給video的src。然后就可以通過AppendBuffer往video里追加音視頻數(shù)據(jù)了。有了MSE,播放器器的整體結(jié)構(gòu)是什么樣的呢,見下圖。
圖2:Web播放器簡明結(jié)構(gòu)
首先在瀏覽器層面,主要使用video標(biāo)簽、MSE、XHR 和UI。那么播放器主要由Manager驅(qū)動加載視頻的playlist(比如HLS里的m3u8,dash里的MPD,F(xiàn)LV雖然不是playlist概念,但是是原理上差別不大,都是為了拿到視頻的一個個的片段的地址),并通過數(shù)據(jù)服務(wù)加載這一個個的分片。然后通過transmuxer也就是所謂的轉(zhuǎn)封裝器,把分片的封裝格式比如TS拆開(demux) 把連原始的音視頻數(shù)據(jù)解出來,再重新打包成fmp4(remux),最后通過MSE API喂給video標(biāo)簽里,讓video去播放。
因此播放器所做的事情最主要有兩點:
1) 轉(zhuǎn)封裝。即將video不支持的封裝格式轉(zhuǎn)碼成video所支持的封裝格式;
2) 如何驅(qū)動整個播放進(jìn)行。即決定何時下載下一個分片,何時需要解碼插入到video的buffer里。
時間戳對齊
轉(zhuǎn)封裝除了的封裝格式的解復(fù)用(demux)和再復(fù)用(remux)之外最重要的環(huán)節(jié)就是分片的時間戳對齊策略,以及音視頻同步。
圖3(傳說中的“開局一張圖 原理全靠猜”)
簡單講一下上圖:紅色代表音頻的時間軸。藍(lán)色/青色是視頻的時間軸。PTS(Presentation Time Stamp) 指的是這一幀需要渲染的時間。DTS(Decoding Time Stamp) 指的是這一幀需要解碼的時間。
1、首片首幀的對齊策略
正常來說音頻PTS和DTS是一樣的,而視頻如果有B幀的話DTS往往要比PTS早一些(因為要預(yù)留一定的時間解碼)。因此視頻的首幀會有一個洞(gap/shift隨便你怎么叫),如果不經(jīng)處理插到video里,那么video里的buffer也會呈現(xiàn)出一小段的洞,一般是0.08s(比如10s的分片 插進(jìn)去可能出現(xiàn)0.08~10.08的情況)?,F(xiàn)在主流的做法是削掉這個洞。就是把DTS跟PTS強(qiáng)行拉平,一般來說chrome 不會出現(xiàn)太大的問題。但是safari不行,如果不預(yù)留一定的DTS/PTS偏移,safari前兩幀的播放會明顯卡頓。
2、后續(xù)對齊策略后續(xù)分片的對齊,會通過DTS/PTS兩個尾部指針來做。如果發(fā)現(xiàn)后續(xù)分片時間軸有間隔就往前推從而填上間隔。如果發(fā)現(xiàn)重疊,就把重疊幀后移。這樣雖然會導(dǎo)致后續(xù)分片的前幾幀重疊。但在播放的過程中幾乎沒有影響。
音視頻同步
首先,什么情況下會導(dǎo)致音畫不同步?
1、視頻源流壓根沒對齊。沒救了,看下一點。
2、還是因為有洞。很多時候視頻切出來的每個分片之間都不一定是嚴(yán)絲合縫的,分片間的音視頻時間戳可能有洞。而且對于TS由于音頻每一幀的duration(≈23ms) 跟視頻每一幀的duration(40ms@25fps) 無法吻合(整除) 所以加劇了這種參差不齊的情況。那么,重點來了!chrome有個特殊的機(jī)制, 如果發(fā)現(xiàn)音頻之間有洞之后,為了保證音頻的平順,會自動把后續(xù)音頻往前推抹平這個洞。如果每個分片都有洞,悲劇了,這種往前推的操作就會積累越來越多導(dǎo)致音視頻不同步。
小tips:
打開chrome的媒體調(diào)試頁面 chrome://media-internals 可以看到媒體播放相關(guān)的所有debug信息和error信息非常有用。其中就會有一條關(guān)于音頻處理的提示:
當(dāng)然這條顯示的具體原因是自動切掉重疊overlap導(dǎo)致的。其實gap/overlap本質(zhì)是一樣的。怎么辦?當(dāng)然是播放器自己主動把洞填上。具體做法是插幀。目前主要是插靜音幀,或者復(fù)制前一幀。靜音幀會帶來毛刺音,復(fù)制幀會導(dǎo)致拖音。我們目前的優(yōu)化方案是判斷附近的音頻數(shù)據(jù)量,數(shù)據(jù)量大時說明此處聲音豐富(其實不算靠譜,姑且這么處理,因為沒有更好的判斷方式),如果插靜音幀會毛刺很明顯,所以此時用復(fù)制幀,反之插靜音幀。
那些年我們趟過的坑
1、 不同版本表現(xiàn)差異 容忍度不同
1) Chrome 35分水嶺。chrome35之前要求關(guān)鍵幀之后的第一幀dts不允許跟關(guān)鍵幀dts相同,否則拋錯。
2) 低延遲的模式。把轉(zhuǎn)封裝出來的FMP4中的視頻軌duration(tkhd box) 設(shè)置成0xffffffff 時會讓chrome認(rèn)為這是直播流,會開啟低延遲模式,所謂低延遲模式就是會極大的減少幀緩存,基本上視頻幀立馬解碼立馬播放減少每個分片的起播延遲。但是呢在CPU負(fù)載過高的情況下(解不過來)會造成視頻頻繁卡頓(網(wǎng)絡(luò)無關(guān)的)。
2、 不同瀏覽器表現(xiàn)有差異
1) timeupdate事件。W3C的標(biāo)準(zhǔn)是不能超過250ms觸發(fā)一次。windows下360等瀏覽器會達(dá)到500ms左右。
2) safari對每一幀duration平順度更敏感。safari需要對每一視頻幀的duration標(biāo)準(zhǔn)化處理,例如TS下要處理成3600。
3) 對洞的容忍度不同。chrome遇到buffer中有0.08的間隔以內(nèi)會自動跳過去。像IE edge等瀏覽器不行會卡住,所以播放器一定要有跳洞邏輯。比如判斷當(dāng)前卡在洞的邊界,要主動跳過去(seek)。
3、內(nèi)存限制
通過MSE push給video的視頻數(shù)據(jù)會在內(nèi)部維護(hù)一個buffer,這個尺寸是有限制的。
1) chrome系列約100M
2) IE系列約30M
超過的話就會導(dǎo)致拋出 QuotaExceededError 。所以需要處理好buffer的尺寸以及及時清除不用的buffer。比如已經(jīng)播放過的,正常瀏覽器會自己清除,但是不那么的及時。
優(yōu)化
簡單說一下卡頓相關(guān)的優(yōu)化。
- 多級Buffer控制
- ABR 自適應(yīng)碼率算法
- 基于WebRTC的P2P
1、多級buffer
為什么要有多級的buffer?因為video本身的解碼buffer有大小限制,而且buffer過長會導(dǎo)致長時間解碼,會導(dǎo)致CPU一直占用高。所以我們搞了兩級buffer一級就是video的buffer另外一級是內(nèi)存中的,只負(fù)責(zé)下載,二級很長??梢韵W(wǎng)絡(luò)抖動帶來的卡頓影響。
2、ABR自適應(yīng)碼率的算法
這個主要是來預(yù)測用戶本身的帶寬范圍,然后選用不同碼率的視頻流來無縫切換播放。當(dāng)然還有一些策略算法,比如根據(jù)用戶現(xiàn)在buffer的水位,或者檢測到用戶頻繁超時,來采用不同的策略。
3、基于WebRTC的P2P
因為P2P是基于UDP的傳輸,可以突破一些帶寬限制或網(wǎng)絡(luò)擁塞而導(dǎo)致的卡頓問題。不過P2P不一定靠譜所以還是要輔以普通的HTTP傳輸相結(jié)合。我們一般是利用P2P加indexDB 來變相延長視頻的緩沖區(qū)。因為P2P帶寬成本便宜,我們利用P2P做了一個非常長又很便宜的buffer。這樣的話網(wǎng)絡(luò)再波動也不會導(dǎo)致卡頓了。